Compare commits

...

8 Commits

Author SHA1 Message Date
Matthias Clasen
50725b67ac Add another shader node demo
This one does stack-like transitions, using
some examples from gl-transitions.com.
2020-09-23 00:54:28 -04:00
Matthias Clasen
aeae3522b6 Complete serialization for GskGLShaderNode
Make the node serialization and parsing handle
shader nodes.
2020-09-23 00:54:28 -04:00
Matthias Clasen
3e3df6ba1e inspector: Show data of glshader nodes
Show the required sources and the values of the uniforms
in the recorder.
2020-09-23 00:54:28 -04:00
Matthias Clasen
f2a046449f Add a missing file
This was reconstructed from build failures.
2020-09-22 15:46:49 -04:00
Alexander Larsson
122304d34a gtk-demo: Add GskGLShaderNode demo 2020-09-22 16:06:54 +02:00
Alexander Larsson
6c13f89f91 Support GLShaderNode in backends
For vulkan/broadway this just means to ignore it, but for the gl
backend we support (with up to 4 texture inputs, which is similar to
what shadertoy does, so should be widely supported).
2020-09-22 16:06:54 +02:00
Alexander Larsson
5f7bf0a23e GtkSnapshot: Add gtk_snapshot_push_glshader()
This is a helper to create GskGLShader nodes
2020-09-22 16:06:54 +02:00
Alexander Larsson
adbd434d8c 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.
2020-09-22 16:06:54 +02:00
35 changed files with 3243 additions and 44 deletions

View File

@@ -133,6 +133,19 @@
<file>cogs.glsl</file>
<file>glowingstars.glsl</file>
</gresource>
<gresource prefix="/glshader">
<file>fire.glsl</file>
<file>gtkshaderbin.h</file>
<file>gtkshaderbin.c</file>
</gresource>
<gresource prefix="/gltransition">
<file>gtkshaderstack.c</file>
<file>gtkshaderstack.h</file>
<file>transition1.glsl</file>
<file>transition2.glsl</file>
<file>transition3.glsl</file>
<file>transition4.glsl</file>
</gresource>
<gresource prefix="/iconscroll">
<file>iconscroll.ui</file>
</gresource>
@@ -247,6 +260,8 @@
<file>gears.c</file>
<file>gestures.c</file>
<file>glarea.c</file>
<file>glshader.c</file>
<file>gltransition.c</file>
<file>headerbar.c</file>
<file>hypertext.c</file>
<file>iconscroll.c</file>

72
demos/gtk-demo/fire.glsl Normal file
View File

@@ -0,0 +1,72 @@
uniform float u_time;
/* 2D -> [0..1] random number generator */
float random(vec2 st) {
return fract(sin(dot(st.xy,
vec2(12.9898,78.233))) *
43758.5453123);
}
/* Generate a smoothed 2d noise based on random() */
float noise(vec2 v) {
/* Round point v to integer grid grid */
vec2 grid_point = floor(v);
/* Randomize in grid corners */
float corner1 = random(grid_point);
float corner2 = random(grid_point + vec2(1, 0));
float corner3 = random(grid_point + vec2(0, 1));
float corner4 = random(grid_point + vec2(1, 1));
/* Interpolate smoothly between grid points */
vec2 fraction = smoothstep(vec2(0.0), vec2(1.0), fract(v));
return mix(mix(corner1, corner2, fraction.x),
mix(corner3, corner4, fraction.x),
fraction.y);
}
/* fractal brownian motion noice, see https://www.iquilezles.org/www/articles/fbm/fbm.htm */
float fbm(in vec2 x)
{
const float octaveScale = 1.9;
const float G = 0.5;
float f = 1.0;
float a = 1.0;
float t = 0.0;
int numOctaves = 5;
for (int i = 0; i < numOctaves; i++) {
t += a*noise(f*x);
f *= octaveScale;
a *= G;
}
return t;
}
void mainImage(out vec4 fragColor, in vec2 fragCoord, in vec2 resolution, in vec2 uv)
{
vec2 xy = fragCoord / resolution;
float zoom = 3.0 - sin(u_time*0.5)*0.3;
// Normalize coord to height of widget
vec2 p = (vec2 (-resolution.x/2 + fragCoord.x, resolution.y - fragCoord.y) / resolution.yy)* zoom;
// Use recursive incantations of fbm
float q1 = fbm(p - vec2(0.8, 0.3) * u_time);
float q2 = fbm(p - vec2(0.5, 1.3) * u_time);
float r = fbm(2.0*p + vec2(q1,q2) - vec2(0.0, 1.0)*u_time*10.0 *0.4);
// Compute intensity, mostly on the bottom
float w = 2 * r * p.y;
// Smooth out left/right side and fade in at start
w /= smoothstep(0.0,0.1, xy.x)* smoothstep(0.0,0.1, 1.0-xy.x) * smoothstep(0.0,0.4, u_time);
// Compute colors
vec3 c = vec3(1.0,.2,.05);
vec3 color = 1.0 / (w*w/c + 1.0);
// Mix in widget
vec4 widget = texture(u_source,uv);
fragColor = mix(vec4(color,1), widget, 1.0-color.x);
}

85
demos/gtk-demo/glshader.c Normal file
View File

@@ -0,0 +1,85 @@
/* OpenGL/GLShader
* #Keywords: OpenGL, shader
*
* Demonstrates using GskGLShaderNodes to integrate GLSL fragment shaders
* with the Gtk widget rendering.
*/
#include <math.h>
#include <gtk/gtk.h>
#include "gtkshaderbin.h"
static GtkWidget *demo_window = NULL;
static void
close_window (GtkWidget *widget)
{
/* Reset the state */
demo_window = NULL;
}
static GtkWidget *
fire_bin_new (void)
{
GtkWidget *bin = gtk_shader_bin_new ();
GBytes *shader_b;
GskGLShader *shader;
shader_b = g_resources_lookup_data ("/glshader/fire.glsl", 0, NULL);
shader = gsk_glshader_new ((const char *)g_bytes_get_data (shader_b, NULL));
gsk_glshader_add_uniform (shader, "u_time", GSK_GLUNIFORM_TYPE_FLOAT);
gtk_shader_bin_add_shader (GTK_SHADER_BIN (bin), shader, GTK_STATE_FLAG_PRELIGHT, GTK_STATE_FLAG_PRELIGHT);
g_bytes_unref (shader_b);
g_object_unref (shader);
return bin;
}
static GtkWidget *
create_glshader_window (GtkWidget *do_widget)
{
GtkWidget *window, *box, *button, *bin;
window = gtk_window_new ();
gtk_window_set_display (GTK_WINDOW (window), gtk_widget_get_display (do_widget));
gtk_window_set_title (GTK_WINDOW (window), "glshader");
g_signal_connect (window, "destroy", G_CALLBACK (close_window), NULL);
box = gtk_box_new (GTK_ORIENTATION_VERTICAL, FALSE);
gtk_widget_set_margin_start (box, 12);
gtk_widget_set_margin_end (box, 12);
gtk_widget_set_margin_top (box, 12);
gtk_widget_set_margin_bottom (box, 12);
gtk_box_set_spacing (GTK_BOX (box), 6);
gtk_window_set_child (GTK_WINDOW (window), box);
bin = fire_bin_new ();
gtk_box_append (GTK_BOX (box), bin);
button = gtk_button_new_with_label ("Click me");
gtk_widget_set_receives_default (button, TRUE);
gtk_shader_bin_set_child (GTK_SHADER_BIN (bin), button);
bin = fire_bin_new ();
gtk_box_append (GTK_BOX (box), bin);
button = gtk_button_new_with_label ("Or me!");
gtk_widget_set_receives_default (button, TRUE);
gtk_shader_bin_set_child (GTK_SHADER_BIN (bin), button);
return window;
}
GtkWidget *
do_glshader (GtkWidget *do_widget)
{
if (!demo_window)
demo_window = create_glshader_window (do_widget);
if (!gtk_widget_get_visible (demo_window))
gtk_widget_show (demo_window);
else
gtk_window_destroy (GTK_WINDOW (demo_window));
return demo_window;
}

View File

@@ -0,0 +1,250 @@
/* OpenGL/Transitions
* #Keywords: OpenGL, shader
*
* Create transitions between pages using a custom fragment shader.
* The examples here are taken from gl-transitions.com.
*
* Click to start a transition.
*/
#include <math.h>
#include <gtk/gtk.h>
#include "gtkshaderstack.h"
static GtkWidget *demo_window = NULL;
static void
close_window (GtkWidget *widget)
{
/* Reset the state */
demo_window = NULL;
}
static GskGLShader *
gsk_shader_new_from_resource (const char *resource_path)
{
GBytes *shader_b;
GskGLShader *shader;
shader_b = g_resources_lookup_data (resource_path, 0, NULL);
shader = gsk_glshader_new ((const char *)g_bytes_get_data (shader_b, NULL));
gsk_glshader_set_n_required_sources (shader, 2);
gsk_glshader_add_uniform (shader, "progress", GSK_GLUNIFORM_TYPE_FLOAT);
g_bytes_unref (shader_b);
return shader;
}
static void
text_changed (GtkTextBuffer *buffer,
GtkWidget *button)
{
gtk_widget_show (button);
}
static void
apply_text (GtkWidget *button,
GtkTextBuffer *buffer)
{
GtkWidget *stack;
GskGLShader *shader;
GtkTextIter start, end;
char *text;
stack = gtk_widget_get_ancestor (button, GTK_TYPE_SHADER_STACK);
gtk_text_buffer_get_bounds (buffer, &start, &end);
text = gtk_text_buffer_get_text (buffer, &start, &end, TRUE);
shader = gsk_glshader_new (text);
gsk_glshader_set_n_required_sources (shader, 2);
gsk_glshader_add_uniform (shader, "progress", GSK_GLUNIFORM_TYPE_FLOAT);
gtk_shader_stack_set_shader (GTK_SHADER_STACK (stack), shader);
g_object_unref (shader);
g_free (text);
gtk_widget_hide (button);
}
static void
clicked_cb (GtkGestureClick *gesture,
guint n_pressed,
double x,
double y,
gpointer data)
{
gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
}
static GtkWidget *
make_shader_stack (const char *name,
const char *resource_path,
GtkWidget *scale)
{
GtkWidget *stack, *child, *widget;
GtkWidget *label, *button, *tv;
GskGLShader *shader;
GObjectClass *class;
GParamSpecFloat *pspec;
GtkAdjustment *adjustment;
GtkTextBuffer *buffer;
GBytes *bytes;
GtkEventController *controller;
GtkCssProvider *provider;
stack = gtk_shader_stack_new ();
shader = gsk_shader_new_from_resource (resource_path);
gtk_shader_stack_set_shader (GTK_SHADER_STACK (stack), shader);
g_object_unref (shader);
child = gtk_picture_new_for_resource ("/css_pixbufs/background.jpg");
gtk_picture_set_can_shrink (GTK_PICTURE (child), TRUE);
gtk_shader_stack_add_child (GTK_SHADER_STACK (stack), child);
child = gtk_picture_new_for_resource ("/transparent/portland-rose.jpg");
gtk_picture_set_can_shrink (GTK_PICTURE (child), TRUE);
gtk_shader_stack_add_child (GTK_SHADER_STACK (stack), child);
child = gtk_picture_new_for_resource ("/css_blendmodes/ducky.png");
gtk_picture_set_can_shrink (GTK_PICTURE (child), TRUE);
gtk_shader_stack_add_child (GTK_SHADER_STACK (stack), child);
child = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
widget = gtk_center_box_new ();
label = gtk_label_new (name);
gtk_widget_add_css_class (label, "title-4");
gtk_widget_set_size_request (label, -1, 26);
gtk_center_box_set_center_widget (GTK_CENTER_BOX (widget), label);
button = gtk_button_new_from_icon_name ("view-refresh-symbolic");
provider = gtk_css_provider_new ();
gtk_css_provider_load_from_data (provider, "button.small { padding: 0; }", -1);
gtk_style_context_add_provider (gtk_widget_get_style_context (button),
GTK_STYLE_PROVIDER (provider),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
g_object_unref (provider);
gtk_widget_set_halign (button, GTK_ALIGN_CENTER);
gtk_widget_set_valign (button, GTK_ALIGN_CENTER);
gtk_widget_add_css_class (button, "small");
gtk_widget_hide (button);
gtk_center_box_set_end_widget (GTK_CENTER_BOX (widget), button);
gtk_box_append (GTK_BOX (child), widget);
class = g_type_class_ref (GTK_TYPE_SHADER_STACK);
pspec = G_PARAM_SPEC_FLOAT (g_object_class_find_property (class, "duration"));
adjustment = gtk_range_get_adjustment (GTK_RANGE (scale));
if (gtk_adjustment_get_lower (adjustment) == 0.0 &&
gtk_adjustment_get_upper (adjustment) == 0.0)
{
gtk_adjustment_configure (adjustment,
pspec->default_value,
pspec->minimum,
pspec->maximum,
0.1, 0.5, 0);
}
g_type_class_unref (class);
g_object_bind_property (adjustment, "value",
stack, "duration",
G_BINDING_DEFAULT);
widget = gtk_scrolled_window_new ();
gtk_scrolled_window_set_has_frame (GTK_SCROLLED_WINDOW (widget), TRUE);
gtk_widget_set_hexpand (widget, TRUE);
gtk_widget_set_vexpand (widget, TRUE);
controller = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ());
gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (controller), 0);
g_signal_connect (controller, "released", G_CALLBACK (clicked_cb), NULL);
gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_BUBBLE);
gtk_widget_add_controller (GTK_WIDGET (widget), controller);
tv = gtk_text_view_new ();
gtk_text_view_set_left_margin (GTK_TEXT_VIEW (tv), 4);
gtk_text_view_set_right_margin (GTK_TEXT_VIEW (tv), 4);
gtk_text_view_set_top_margin (GTK_TEXT_VIEW (tv), 4);
gtk_text_view_set_bottom_margin (GTK_TEXT_VIEW (tv), 4);
buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
bytes = g_resources_lookup_data (resource_path, 0, NULL);
gtk_text_buffer_set_text (buffer,
(const char *)g_bytes_get_data (bytes, NULL),
g_bytes_get_size (bytes));
g_bytes_unref (bytes);
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (widget), tv);
g_signal_connect (buffer, "changed", G_CALLBACK (text_changed), button);
g_signal_connect (button, "clicked", G_CALLBACK (apply_text), buffer);
gtk_box_append (GTK_BOX (child), widget);
gtk_shader_stack_add_child (GTK_SHADER_STACK (stack), child);
return stack;
}
static GtkWidget *
create_gltransition_window (GtkWidget *do_widget)
{
GtkWidget *window, *headerbar, *scale, *grid;
window = gtk_window_new ();
gtk_window_set_display (GTK_WINDOW (window), gtk_widget_get_display (do_widget));
gtk_window_set_title (GTK_WINDOW (window), "Transitions");
headerbar = gtk_header_bar_new ();
scale = gtk_scale_new (GTK_ORIENTATION_HORIZONTAL, NULL);
gtk_scale_set_draw_value (GTK_SCALE (scale), FALSE);
gtk_widget_set_size_request (scale, 100, -1);
gtk_header_bar_pack_end (GTK_HEADER_BAR (headerbar), scale);
gtk_window_set_titlebar (GTK_WINDOW (window), headerbar);
gtk_window_set_default_size (GTK_WINDOW (window), 400, 400);
g_signal_connect (window, "destroy", G_CALLBACK (close_window), NULL);
grid = gtk_grid_new ();
gtk_window_set_child (GTK_WINDOW (window), grid);
gtk_widget_set_halign (grid, GTK_ALIGN_CENTER);
gtk_widget_set_valign (grid, GTK_ALIGN_CENTER);
gtk_widget_set_margin_start (grid, 12);
gtk_widget_set_margin_end (grid, 12);
gtk_widget_set_margin_top (grid, 12);
gtk_widget_set_margin_bottom (grid, 12);
gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
gtk_grid_set_row_homogeneous (GTK_GRID (grid), TRUE);
gtk_grid_set_column_homogeneous (GTK_GRID (grid), TRUE);
gtk_grid_attach (GTK_GRID (grid),
make_shader_stack ("Wind", "/gltransition/transition1.glsl", scale),
0, 0, 1, 1);
gtk_grid_attach (GTK_GRID (grid),
make_shader_stack ("Radial", "/gltransition/transition2.glsl", scale),
1, 0, 1, 1);
gtk_grid_attach (GTK_GRID (grid),
make_shader_stack ("Crosswarp", "/gltransition/transition3.glsl", scale),
0, 1, 1, 1);
gtk_grid_attach (GTK_GRID (grid),
make_shader_stack ("Kaleidoscope", "/gltransition/transition4.glsl", scale),
1, 1, 1, 1);
return window;
}
GtkWidget *
do_gltransition (GtkWidget *do_widget)
{
if (!demo_window)
demo_window = create_gltransition_window (do_widget);
if (!gtk_widget_get_visible (demo_window))
gtk_widget_show (demo_window);
else
gtk_window_destroy (GTK_WINDOW (demo_window));
return demo_window;
}

View File

@@ -0,0 +1,220 @@
#include "gtkshaderbin.h"
typedef struct {
GskGLShader *shader;
GtkStateFlags state;
GtkStateFlags state_mask;
} ShaderInfo;
struct _GtkShaderBin
{
GtkWidget parent_instance;
GtkWidget *child;
GskGLShader *active_shader;
GPtrArray *shaders;
guint tick_id;
float time;
gint64 first_frame_time;
};
struct _GtkShaderBinClass
{
GtkWidgetClass parent_class;
};
G_DEFINE_TYPE (GtkShaderBin, gtk_shader_bin, GTK_TYPE_WIDGET)
static void
shader_info_free (ShaderInfo *info)
{
g_object_unref (info->shader);
g_free (info);
}
static void
gtk_shader_bin_finalize (GObject *object)
{
GtkShaderBin *self = GTK_SHADER_BIN (object);
g_ptr_array_free (self->shaders, TRUE);
G_OBJECT_CLASS (gtk_shader_bin_parent_class)->finalize (object);
}
static void
gtk_shader_bin_dispose (GObject *object)
{
GtkShaderBin *self = GTK_SHADER_BIN (object);
g_clear_pointer (&self->child, gtk_widget_unparent);
G_OBJECT_CLASS (gtk_shader_bin_parent_class)->dispose (object);
}
static gboolean
gtk_shader_bin_tick (GtkWidget *widget,
GdkFrameClock *frame_clock,
gpointer unused)
{
GtkShaderBin *self = GTK_SHADER_BIN (widget);
gint64 frame_time;
frame_time = gdk_frame_clock_get_frame_time (frame_clock);
if (self->first_frame_time == 0)
self->first_frame_time = frame_time;
self->time = (frame_time - self->first_frame_time) / (float)G_USEC_PER_SEC;
gtk_widget_queue_draw (widget);
return G_SOURCE_CONTINUE;
}
static void
gtk_shader_bin_init (GtkShaderBin *self)
{
self->shaders = g_ptr_array_new_with_free_func ((GDestroyNotify)shader_info_free);
}
void
gtk_shader_bin_update_active_shader (GtkShaderBin *self)
{
GtkStateFlags new_state = gtk_widget_get_state_flags (GTK_WIDGET (self));
GskGLShader *new_shader = NULL;
for (int i = 0; i < self->shaders->len; i++)
{
ShaderInfo *info = g_ptr_array_index (self->shaders, i);
if ((info->state_mask & new_state) == info->state)
{
new_shader = info->shader;
break;
}
}
if (self->active_shader == new_shader)
return;
self->active_shader = new_shader;
self->first_frame_time = 0;
if (self->active_shader)
{
if (self->tick_id == 0)
self->tick_id = gtk_widget_add_tick_callback (GTK_WIDGET (self),
gtk_shader_bin_tick,
NULL, NULL);
}
else
{
if (self->tick_id != 0)
{
gtk_widget_remove_tick_callback (GTK_WIDGET (self), self->tick_id);
self->tick_id = 0;
}
}
gtk_widget_queue_draw (GTK_WIDGET (self));
}
static void
gtk_shader_bin_state_flags_changed (GtkWidget *widget,
GtkStateFlags previous_state_flags)
{
GtkShaderBin *self = GTK_SHADER_BIN (widget);
gtk_shader_bin_update_active_shader (self);
}
void
gtk_shader_bin_add_shader (GtkShaderBin *self,
GskGLShader *shader,
GtkStateFlags state,
GtkStateFlags state_mask)
{
ShaderInfo *info = g_new0 (ShaderInfo, 1);
info->shader = g_object_ref (shader);
info->state = state;
info->state_mask = state_mask;
g_ptr_array_add (self->shaders, info);
gtk_shader_bin_update_active_shader (self);
}
void
gtk_shader_bin_set_child (GtkShaderBin *self,
GtkWidget *child)
{
if (self->child == child)
return;
g_clear_pointer (&self->child, gtk_widget_unparent);
if (child)
{
self->child = child;
gtk_widget_set_parent (child, GTK_WIDGET (self));
}
}
GtkWidget *
gtk_shader_bin_get_child (GtkShaderBin *self)
{
return self->child;
}
static void
gtk_shader_bin_snapshot (GtkWidget *widget,
GtkSnapshot *snapshot)
{
GtkShaderBin *self = GTK_SHADER_BIN (widget);
int width, height;
width = gtk_widget_get_width (widget);
height = gtk_widget_get_height (widget);
if (self->active_shader)
{
gtk_snapshot_push_glshader (snapshot, self->active_shader,
&GRAPHENE_RECT_INIT(0, 0, width, height),
1,
"u_time", &self->time,
NULL
);
gtk_widget_snapshot_child (widget, self->child, snapshot);
gtk_snapshot_pop (snapshot); /* Fallback */
gtk_widget_snapshot_child (widget, self->child, snapshot);
gtk_snapshot_pop (snapshot); /* Shader node child 1 */
}
else
{
gtk_widget_snapshot_child (widget, self->child, snapshot);
}
}
static void
gtk_shader_bin_class_init (GtkShaderBinClass *class)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
GObjectClass *object_class = G_OBJECT_CLASS (class);
object_class->finalize = gtk_shader_bin_finalize;
object_class->dispose = gtk_shader_bin_dispose;
gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
widget_class->snapshot = gtk_shader_bin_snapshot;
widget_class->state_flags_changed = gtk_shader_bin_state_flags_changed;
}
GtkWidget *
gtk_shader_bin_new (void)
{
GtkShaderBin *self;
self = g_object_new (GTK_TYPE_SHADER_BIN, NULL);
return GTK_WIDGET (self);
}

View File

@@ -0,0 +1,22 @@
#ifndef __GTK_SHADER_BIN_H__
#define __GTK_SHADER_BIN_H__
#include <gtk/gtk.h>
G_BEGIN_DECLS
#define GTK_TYPE_SHADER_BIN (gtk_shader_bin_get_type ())
G_DECLARE_FINAL_TYPE (GtkShaderBin, gtk_shader_bin, GTK, SHADER_BIN, GtkWidget)
GtkWidget *gtk_shader_bin_new (void);
void gtk_shader_bin_add_shader (GtkShaderBin *self,
GskGLShader *shader,
GtkStateFlags state,
GtkStateFlags state_mask);
void gtk_shader_bin_set_child (GtkShaderBin *self,
GtkWidget *child);
GtkWidget *gtk_shader_bin_get_child (GtkShaderBin *self);
G_END_DECLS
#endif /* __GTK_SHADER_BIN_H__ */

View File

@@ -0,0 +1,356 @@
#include "gtkshaderstack.h"
struct _GtkShaderStack
{
GtkWidget parent_instance;
GskGLShader *shader;
GPtrArray *children;
int current;
int next;
gboolean backwards;
guint tick_id;
float time;
float duration;
gint64 start_time;
};
struct _GtkShaderStackClass
{
GtkWidgetClass parent_class;
};
enum {
PROP_DURATION = 1,
NUM_PROPERTIES
};
static GParamSpec *properties[NUM_PROPERTIES] = { NULL };
G_DEFINE_TYPE (GtkShaderStack, gtk_shader_stack, GTK_TYPE_WIDGET)
static void
gtk_shader_stack_finalize (GObject *object)
{
GtkShaderStack *self = GTK_SHADER_STACK (object);
g_object_unref (self->shader);
G_OBJECT_CLASS (gtk_shader_stack_parent_class)->finalize (object);
}
static void
update_child_visible (GtkShaderStack *self)
{
int i;
for (i = 0; i < self->children->len; i++)
{
GtkWidget *child = g_ptr_array_index (self->children, i);
gtk_widget_set_child_visible (child,
i == self->current || i == self->next);
}
}
static gboolean
transition_cb (GtkWidget *widget,
GdkFrameClock *clock,
gpointer unused)
{
GtkShaderStack *self = GTK_SHADER_STACK (widget);
gint64 frame_time;
frame_time = gdk_frame_clock_get_frame_time (clock);
if (self->start_time == 0)
self->start_time = frame_time;
self->time = (frame_time - self->start_time) / (float)G_USEC_PER_SEC;
gtk_widget_queue_draw (widget);
if (self->time >= self->duration)
{
self->current = self->next;
self->next = -1;
update_child_visible (self);
return G_SOURCE_REMOVE;
}
else
return G_SOURCE_CONTINUE;
}
static void
start_transition (GtkShaderStack *self)
{
self->start_time = 0;
self->tick_id = gtk_widget_add_tick_callback (GTK_WIDGET (self),
transition_cb,
NULL, NULL);
}
static void
stop_transition (GtkShaderStack *self)
{
if (self->tick_id != 0)
{
gtk_widget_remove_tick_callback (GTK_WIDGET (self), self->tick_id);
self->tick_id = 0;
}
self->next = -1;
update_child_visible (self);
}
static void
gtk_shader_stack_dispose (GObject *object)
{
GtkShaderStack *self = GTK_SHADER_STACK (object);
stop_transition (self);
g_clear_pointer (&self->children, g_ptr_array_unref);
G_OBJECT_CLASS (gtk_shader_stack_parent_class)->dispose (object);
}
static void
clicked_cb (GtkGestureClick *gesture,
guint n_pressed,
double x,
double y,
gpointer data)
{
GtkShaderStack *self = GTK_SHADER_STACK (data);
guint button;
button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
stop_transition (self);
self->backwards = button == GDK_BUTTON_SECONDARY;
if (self->backwards)
self->next = (self->current + self->children->len - 1) % self->children->len;
else
self->next = (self->current + 1) % self->children->len;
update_child_visible (self);
start_transition (self);
}
static void
gtk_shader_stack_init (GtkShaderStack *self)
{
GtkEventController *controller;
self->children = g_ptr_array_new_with_free_func ((GDestroyNotify)gtk_widget_unparent);
self->current = -1;
self->next = -1;
self->backwards = FALSE;
self->duration = 1.0;
controller = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ());
gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (controller), 0);
g_signal_connect (controller, "released", G_CALLBACK (clicked_cb), self);
gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_BUBBLE);
gtk_widget_add_controller (GTK_WIDGET (self), controller);
}
static void
gtk_shader_stack_measure (GtkWidget *widget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline)
{
GtkShaderStack *self = GTK_SHADER_STACK (widget);
int i;
*minimum = 0;
*natural = 0;
for (i = 0; i < self->children->len; i++)
{
GtkWidget *child = g_ptr_array_index (self->children, i);
int child_min, child_nat;
if (gtk_widget_get_visible (child))
{
gtk_widget_measure (child, orientation, for_size, &child_min, &child_nat, NULL, NULL);
*minimum = MAX (*minimum, child_min);
*natural = MAX (*natural, child_nat);
}
}
}
static void
gtk_shader_stack_size_allocate (GtkWidget *widget,
int width,
int height,
int baseline)
{
GtkShaderStack *self = GTK_SHADER_STACK (widget);
GtkAllocation child_allocation;
GtkWidget *child;
int i;
child_allocation.x = 0;
child_allocation.y = 0;
child_allocation.width = width;
child_allocation.height = height;
for (i = 0; i < self->children->len; i++)
{
child = g_ptr_array_index (self->children, i);
if (gtk_widget_get_visible (child))
gtk_widget_size_allocate (child, &child_allocation, -1);
}
}
static void
gtk_shader_stack_snapshot (GtkWidget *widget,
GtkSnapshot *snapshot)
{
GtkShaderStack *self = GTK_SHADER_STACK (widget);
int width, height;
GtkWidget *current, *next;
width = gtk_widget_get_width (widget);
height = gtk_widget_get_height (widget);
current = g_ptr_array_index (self->children, self->current);
if (self->next == -1)
{
gtk_widget_snapshot_child (widget, current, snapshot);
}
else
{
float progress;
next = g_ptr_array_index (self->children, self->next);
progress = self->time / self->duration;
if (self->backwards)
{
GtkWidget *tmp = next;
next = current;
current = tmp;
progress = 1. - progress;
}
gtk_snapshot_push_glshader (snapshot,
self->shader,
&GRAPHENE_RECT_INIT(0, 0, width, height),
2,
"progress", &progress,
NULL);
gtk_widget_snapshot_child (widget, next, snapshot);
gtk_snapshot_pop (snapshot); /* Fallback */
gtk_widget_snapshot_child (widget, current, snapshot);
gtk_snapshot_pop (snapshot); /* current child */
gtk_widget_snapshot_child (widget, next, snapshot);
gtk_snapshot_pop (snapshot); /* next child */
}
}
static void
gtk_shader_stack_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkShaderStack *self = GTK_SHADER_STACK (object);
switch (prop_id)
{
case PROP_DURATION:
g_value_set_float (value, self->duration);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_shader_stack_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkShaderStack *self = GTK_SHADER_STACK (object);
switch (prop_id)
{
case PROP_DURATION:
self->duration = g_value_get_float (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_shader_stack_class_init (GtkShaderStackClass *class)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
GObjectClass *object_class = G_OBJECT_CLASS (class);
object_class->finalize = gtk_shader_stack_finalize;
object_class->dispose = gtk_shader_stack_dispose;
object_class->get_property = gtk_shader_stack_get_property;
object_class->set_property = gtk_shader_stack_set_property;
widget_class->snapshot = gtk_shader_stack_snapshot;
widget_class->measure = gtk_shader_stack_measure;
widget_class->size_allocate = gtk_shader_stack_size_allocate;
properties[PROP_DURATION] =
g_param_spec_float ("duration", "Duration", "Duration",
0.1, 3.0, 1.0,
G_PARAM_READWRITE);
g_object_class_install_properties (object_class, NUM_PROPERTIES, properties);
}
GtkWidget *
gtk_shader_stack_new (void)
{
return g_object_new (GTK_TYPE_SHADER_STACK, NULL);
}
void
gtk_shader_stack_set_shader (GtkShaderStack *self,
GskGLShader *shader)
{
g_set_object (&self->shader, shader);
}
void
gtk_shader_stack_add_child (GtkShaderStack *self,
GtkWidget *child)
{
g_ptr_array_add (self->children, child);
gtk_widget_set_parent (child, GTK_WIDGET (self));
gtk_widget_queue_resize (GTK_WIDGET (self));
if (self->current == -1)
self->current = 0;
else
gtk_widget_set_child_visible (child, FALSE);
}

View File

@@ -0,0 +1,19 @@
#ifndef __GTK_SHADER_STACK_H__
#define __GTK_SHADER_STACK_H__
#include <gtk/gtk.h>
G_BEGIN_DECLS
#define GTK_TYPE_SHADER_STACK (gtk_shader_stack_get_type ())
G_DECLARE_FINAL_TYPE (GtkShaderStack, gtk_shader_stack, GTK, SHADER_STACK, GtkWidget)
GtkWidget * gtk_shader_stack_new (void);
void gtk_shader_stack_set_shader (GtkShaderStack *self,
GskGLShader *shader);
void gtk_shader_stack_add_child (GtkShaderStack *self,
GtkWidget *child);
G_END_DECLS
#endif /* __GTK_SHADER_STACK_H__ */

View File

@@ -32,6 +32,8 @@ demos = files([
'gears.c',
'gestures.c',
'glarea.c',
'glshader.c',
'gltransition.c',
'headerbar.c',
'hypertext.c',
'iconscroll.c',
@@ -102,7 +104,9 @@ extra_demo_sources = files(['main.c',
'gtkfishbowl.c',
'fontplane.c',
'gtkgears.c',
'gtkshaderbin.c',
'gtkshadertoy.c',
'gtkshaderstack.c',
'puzzlepiece.c',
'bluroverlay.c',
'demoimage.c',

View File

@@ -0,0 +1,32 @@
uniform float progress;
vec4 getFromColor(vec2 uv) {
return texture(u_source, uv);
}
vec4 getToColor(vec2 uv) {
return texture(u_source2, uv);
}
// Source: https://gl-transitions.com/editor/wind
// Author: gre
// License: MIT
uniform float size = 0.2;
float rand(vec2 co) {
return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);
}
vec4 transition(vec2 p) {
float r = rand(vec2(0, p.y));
float m = smoothstep(0.0, -size, p.x*(1.0-size) + size*r - (progress * (1.0 + size)));
return mix(getFromColor(p), getToColor(p), m);
}
void mainImage(out vec4 fragColor, in vec2 fragCoord, in vec2 resolution, in vec2 uv)
{
fragColor = transition(uv);
}

View File

@@ -0,0 +1,33 @@
uniform float progress;
vec4 getFromColor (vec2 uv) {
return texture(u_source, uv);
}
vec4 getToColor (vec2 uv) {
return texture(u_source2, uv);
}
// Source: https://gl-transitions.com/editor/Radial
// License: MIT
// Author: Xaychru
uniform float smoothness = 1.0;
const float PI = 3.141592653589;
vec4 transition(vec2 p) {
vec2 rp = p*2.-1.;
return mix(
getToColor(p),
getFromColor(p),
smoothstep(0., smoothness, atan(rp.y,rp.x) - (progress-.5) * PI * 2.5)
);
}
void mainImage(out vec4 fragColor, in vec2 fragCoord, in vec2 resolution, in vec2 uv)
{
fragColor = transition(uv);
}

View File

@@ -0,0 +1,26 @@
uniform float progress;
vec4 getFromColor (vec2 uv) {
return texture(u_source, uv);
}
vec4 getToColor (vec2 uv) {
return texture(u_source2, uv);
}
// Source: https://gl-transitions.com/editor/crosswarp
// Author: Eke Péter <peterekepeter@gmail.com>
// License: MIT
vec4 transition(vec2 p) {
float x = progress;
x=smoothstep(.0,1.0,(x*2.0+p.x-1.0));
return mix(getFromColor((p-.5)*(1.-x)+.5), getToColor((p-.5)*x+.5), x);
}
void mainImage(out vec4 fragColor, in vec2 fragCoord, in vec2 resolution, in vec2 uv)
{
fragColor = transition(uv);
}

View File

@@ -0,0 +1,40 @@
uniform float progress;
vec4 getFromColor (vec2 uv) {
return texture(u_source, uv);
}
vec4 getToColor (vec2 uv) {
return texture(u_source2, uv);
}
// Source: https://gl-transitions.com/editor/kaleidoscope
// Author: nwoeanhinnogaehr
// License: MIT
uniform float speed = 1.0;
uniform float angle = 1.0;
uniform float power = 1.5;
vec4 transition(vec2 uv) {
vec2 p = uv.xy / vec2(1.0).xy;
vec2 q = p;
float t = pow(progress, power)*speed;
p = p -0.5;
for (int i = 0; i < 7; i++) {
p = vec2(sin(t)*p.x + cos(t)*p.y, sin(t)*p.y - cos(t)*p.x);
t += angle;
p = abs(mod(p, 2.0) - 1.0);
}
abs(mod(p, 1.0));
return mix(
mix(getFromColor(q), getToColor(q), progress),
mix(getFromColor(p), getToColor(p), progress), 1.0 - 2.0*abs(progress - 0.5));
}
void mainImage(out vec4 fragColor, in vec2 fragCoord, in vec2 resolution, in vec2 uv)
{
fragColor = transition(uv);
}

View File

@@ -272,6 +272,11 @@ collect_reused_child_nodes (GskRenderer *renderer,
/* Bin nodes */
case GSK_GLSHADER_NODE:
collect_reused_node (renderer,
gsk_glshader_node_get_fallback_child (node));
break;
case GSK_SHADOW_NODE:
collect_reused_node (renderer,
gsk_shadow_node_get_child (node));
@@ -792,6 +797,11 @@ gsk_broadway_renderer_add_node (GskRenderer *renderer,
}
return;
case GSK_GLSHADER_NODE:
gsk_broadway_renderer_add_node (renderer,
gsk_glshader_node_get_fallback_child (node), offset_x, offset_y, clip_bounds);
return;
/* Generic nodes */
case GSK_CONTAINER_NODE:

View File

@@ -19,6 +19,7 @@
#include "gskglnodesampleprivate.h"
#include "gsktransform.h"
#include "glutilsprivate.h"
#include "gskglshaderprivate.h"
#include "gskprivate.h"
@@ -64,6 +65,11 @@
glGetUniformLocation(program_ptr->id, "u_" #uniform_basename);\
}G_STMT_END
static Program *gsk_gl_renderer_lookup_custom_program (GskGLRenderer *self,
GskGLShader *shader);
static Program *gsk_gl_renderer_create_custom_program (GskGLRenderer *self,
GskGLShader *shader);
typedef enum
{
FORCE_OFFSCREEN = 1 << 0,
@@ -130,6 +136,13 @@ print_render_node_tree (GskRenderNode *root, int level)
print_render_node_tree (gsk_shadow_node_get_child (root), level + 1);
break;
case GSK_GLSHADER_NODE:
g_print ("%*s GLShader\n", level * INDENT, " ");
print_render_node_tree (gsk_glshader_node_get_fallback_child (root), level + 1);
for (i = 0; i < gsk_glshader_node_get_n_children (root); i++)
print_render_node_tree (gsk_glshader_node_get_child (root, i), level + 1);
break;
case GSK_TEXTURE_NODE:
g_print ("%*s Texture %p\n", level * INDENT, " ", gsk_texture_node_get_texture (root));
break;
@@ -495,6 +508,40 @@ struct _GskGLRendererClass
G_DEFINE_TYPE (GskGLRenderer, gsk_gl_renderer, GSK_TYPE_RENDERER)
static void
init_shader_builder (GskGLRenderer *self,
GskGLShaderBuilder *shader_builder)
{
#ifdef G_ENABLE_DEBUG
if (GSK_RENDERER_DEBUG_CHECK (GSK_RENDERER (self), SHADERS))
shader_builder->debugging = TRUE;
#endif
if (gdk_gl_context_get_use_es (self->gl_context))
{
gsk_gl_shader_builder_set_glsl_version (shader_builder, SHADER_VERSION_GLES);
shader_builder->gles = TRUE;
}
else if (gdk_gl_context_is_legacy (self->gl_context))
{
int maj, min;
gdk_gl_context_get_version (self->gl_context, &maj, &min);
if (maj == 3)
gsk_gl_shader_builder_set_glsl_version (shader_builder, SHADER_VERSION_GL3_LEGACY);
else
gsk_gl_shader_builder_set_glsl_version (shader_builder, SHADER_VERSION_GL2_LEGACY);
shader_builder->legacy = TRUE;
}
else
{
gsk_gl_shader_builder_set_glsl_version (shader_builder, SHADER_VERSION_GL3);
shader_builder->gl3 = TRUE;
}
}
static void G_GNUC_UNUSED
add_rect_ops (RenderOpBuilder *builder,
const graphene_rect_t *r)
@@ -1006,6 +1053,128 @@ render_texture_node (GskGLRenderer *self,
}
}
static inline void
render_glshader_node (GskGLRenderer *self,
GskRenderNode *node,
RenderOpBuilder *builder)
{
GskGLShader *shader = gsk_glshader_node_get_shader (node);
Program *program = gsk_gl_renderer_lookup_custom_program (self, shader);
GskRenderNode *fallback = gsk_glshader_node_get_fallback_child (node);
int n_children = gsk_glshader_node_get_n_children (node);
if (program == NULL)
{
GskGLShaderBuilder shader_builder;
const char *shader_source;
GError *error = NULL;
int n_uniforms;
const GskGLUniform *uniforms;
int n_required_sources = gsk_glshader_get_n_required_sources (shader);
/* We always create the program, so that any compiler warnings or other is only reported once */
program = gsk_gl_renderer_create_custom_program (self, shader);
shader_source = gsk_glshader_get_sourcecode (shader);
uniforms = gsk_glshader_get_uniforms (shader, &n_uniforms);
if (n_uniforms > G_N_ELEMENTS (program->glshader.args_locations))
{
g_set_error (&error, GDK_GL_ERROR, GDK_GL_ERROR_UNSUPPORTED_FORMAT,
"GLShaderNode supports max %d custom uniforms", (int)G_N_ELEMENTS (program->glshader.args_locations));
}
else if (n_required_sources > 1 + G_N_ELEMENTS (program->glshader.extra_source_locations))
{
g_set_error (&error, GDK_GL_ERROR, GDK_GL_ERROR_UNSUPPORTED_FORMAT,
"GLShaderNode supports max %d texture sources", (int)(1 + G_N_ELEMENTS (program->glshader.extra_source_locations)));
}
else
{
gsk_gl_shader_builder_init (&shader_builder,
"/org/gtk/libgsk/glsl/preamble.glsl",
"/org/gtk/libgsk/glsl/preamble.vs.glsl",
"/org/gtk/libgsk/glsl/preamble.fs.glsl");
init_shader_builder (self, &shader_builder);
program->id = gsk_gl_shader_builder_create_program (&shader_builder,
"/org/gtk/libgsk/glsl/custom.glsl",
shader_source,
&error);
gsk_gl_shader_builder_finish (&shader_builder);
if (program->id >= 0)
{
INIT_COMMON_UNIFORM_LOCATION (program, alpha);
INIT_COMMON_UNIFORM_LOCATION (program, source);
INIT_COMMON_UNIFORM_LOCATION (program, clip_rect);
INIT_COMMON_UNIFORM_LOCATION (program, viewport);
INIT_COMMON_UNIFORM_LOCATION (program, projection);
INIT_COMMON_UNIFORM_LOCATION (program, modelview);
program->glshader.size_location = glGetUniformLocation(program->id, "u_size");
program->glshader.extra_source_locations[0] = glGetUniformLocation(program->id, "u_source2");
program->glshader.extra_source_locations[1] = glGetUniformLocation(program->id, "u_source3");
program->glshader.extra_source_locations[2] = glGetUniformLocation(program->id, "u_source4");
for (int i = 0; i < G_N_ELEMENTS (program->glshader.args_locations); i++)
{
if (i < n_uniforms)
{
program->glshader.args_locations[i] = glGetUniformLocation(program->id, uniforms[i].name);
if (program->glshader.args_locations[i] == -1)
g_warning ("Expected uniform `%s` not found in shader", uniforms[i].name);
}
else
program->glshader.args_locations[i] = -1;
}
}
}
if (program->id <= 0)
{
g_warning ("Failed to compile gl shader: %s\n", error->message);
g_error_free (error);
}
}
if (program->id >= 0 && n_children <= 4)
{
const guchar *uniform_data;
TextureRegion regions[4];
gboolean is_offscreen[4];
for (guint i = 0; i < n_children; i++)
{
GskRenderNode *child = gsk_glshader_node_get_child (node, i);
if (!add_offscreen_ops (self, builder,
&node->bounds,
child,
&regions[i], &is_offscreen[i],
FORCE_OFFSCREEN | RESET_CLIP | RESET_OPACITY))
{
if (fallback)
gsk_gl_renderer_add_render_ops (self, fallback, builder);
return;
}
}
uniform_data = gsk_glshader_node_get_uniform_data (node);
ops_set_program (builder, program);
ops_set_glshader_args (builder, shader, node->bounds.size.width, node->bounds.size.height, uniform_data);
if (n_children >= 0)
ops_set_texture (builder, regions[0].texture_id);
for (guint i = 1; i < n_children; i++)
ops_set_extra_texture (builder, regions[i].texture_id, i-1);
load_offscreen_vertex_data (ops_draw (builder, NULL), node, builder);
}
else
{
if (fallback)
gsk_gl_renderer_add_render_ops (self, fallback, builder);
}
}
/* Returns TRUE is applying transform to bounds
* yields an axis-aligned rectangle
*/
@@ -2671,6 +2840,18 @@ apply_source_texture_op (const Program *program,
glBindTexture (GL_TEXTURE_2D, op->texture_id);
}
static inline void
apply_source_extra_texture_op (const Program *program,
const OpExtraTexture *op)
{
g_assert(op->texture_id != 0);
OP_PRINT (" -> New extra texture %d: %d", op->idx, op->texture_id);
/* Use texture unit 1 + op->idx for the source */
glUniform1i (program->glshader.extra_source_locations[op->idx], 1 + op->idx);
glActiveTexture (GL_TEXTURE0 + 1 + op->idx);
glBindTexture (GL_TEXTURE_2D, op->texture_id);
}
static inline void
apply_color_matrix_op (const Program *program,
const OpColorMatrix *op)
@@ -2815,6 +2996,51 @@ apply_border_op (const Program *program,
glUniform4fv (program->border.outline_rect_location, 3, (float *)&op->outline.bounds);
}
static inline void
apply_glshader_args_op (const Program *program,
const OpGLShader *op)
{
int n_uniforms, i;
const GskGLUniform *uniforms;
OP_PRINT (" -> GLShader Args");
glUniform2fv (program->glshader.size_location, 1, op->size);
uniforms = gsk_glshader_get_uniforms (op->shader, &n_uniforms);
for (i = 0; i < n_uniforms; i++)
{
const GskGLUniform *u = &uniforms[i];
const guchar *data = op->uniform_data + u->offset;
switch (u->type)
{
default:
case GSK_GLUNIFORM_TYPE_NONE:
break;
case GSK_GLUNIFORM_TYPE_FLOAT:
glUniform1fv (program->glshader.args_locations[i], 1, (const float *)data);
break;
case GSK_GLUNIFORM_TYPE_INT:
glUniform1iv (program->glshader.args_locations[i], 1, (const gint32 *)data);
break;
case GSK_GLUNIFORM_TYPE_UINT:
case GSK_GLUNIFORM_TYPE_BOOL:
glUniform1uiv (program->glshader.args_locations[i], 1, (const guint32 *) data);
break;
case GSK_GLUNIFORM_TYPE_VEC2:
glUniform2fv (program->glshader.args_locations[i], 1, (const float *)data);
break;
case GSK_GLUNIFORM_TYPE_VEC3:
glUniform3fv (program->glshader.args_locations[i], 1, (const float *)data);
break;
case GSK_GLUNIFORM_TYPE_VEC4:
glUniform4fv (program->glshader.args_locations[i], 1, (const float *)data);
break;
}
}
}
static inline void
apply_border_width_op (const Program *program,
const OpBorder *op)
@@ -2886,6 +3112,28 @@ gsk_gl_renderer_dispose (GObject *gobject)
G_OBJECT_CLASS (gsk_gl_renderer_parent_class)->dispose (gobject);
}
static void
program_init (Program *program)
{
program->index = -1;
program->state.opacity = 1.0f;
}
static void
program_finalize (Program *program)
{
if (program->id > 0)
glDeleteProgram (program->id);
gsk_transform_unref (program->state.modelview);
}
static void
program_free (Program *program)
{
program_finalize (program);
g_free (program);
}
static GskGLRendererPrograms *
gsk_gl_renderer_programs_new (void)
{
@@ -2895,9 +3143,11 @@ gsk_gl_renderer_programs_new (void)
programs = g_new0 (GskGLRendererPrograms, 1);
programs->ref_count = 1;
for (i = 0; i < GL_N_PROGRAMS; i ++)
{
programs->programs[i].state.opacity = 1.0f;
}
program_init (&programs->programs[i]);
/* We use direct hash for performance, not string hash on the source, because we assume each caller
* reuses a single GskGLShader for all uses and different callers will use different source content. */
programs->custom_programs = g_hash_table_new_full (g_direct_hash, g_direct_equal, (GDestroyNotify)g_object_unref, (GDestroyNotify)program_free);
return programs;
}
@@ -2918,15 +3168,33 @@ gsk_gl_renderer_programs_unref (GskGLRendererPrograms *programs)
if (programs->ref_count == 0)
{
for (i = 0; i < GL_N_PROGRAMS; i ++)
{
if (programs->programs[i].id > 0)
glDeleteProgram (programs->programs[i].id);
gsk_transform_unref (programs->programs[i].state.modelview);
}
program_finalize (&programs->programs[i]);
g_hash_table_destroy (programs->custom_programs);
g_free (programs);
}
}
static Program *
gsk_gl_renderer_lookup_custom_program (GskGLRenderer *self,
GskGLShader *shader)
{
return g_hash_table_lookup (self->programs->custom_programs, shader);
}
static Program *
gsk_gl_renderer_create_custom_program (GskGLRenderer *self,
GskGLShader *shader)
{
Program *program = g_new0 (Program, 1);
program_init (program);
g_hash_table_insert (self->programs->custom_programs, g_object_ref (shader), program);
return program;
}
static GskGLRendererPrograms *
gsk_gl_renderer_create_programs (GskGLRenderer *self,
GError **error)
@@ -2961,35 +3229,7 @@ gsk_gl_renderer_create_programs (GskGLRenderer *self,
g_assert (G_N_ELEMENTS (program_definitions) == GL_N_PROGRAMS);
#ifdef G_ENABLE_DEBUG
if (GSK_RENDERER_DEBUG_CHECK (GSK_RENDERER (self), SHADERS))
shader_builder.debugging = TRUE;
#endif
if (gdk_gl_context_get_use_es (self->gl_context))
{
gsk_gl_shader_builder_set_glsl_version (&shader_builder, SHADER_VERSION_GLES);
shader_builder.gles = TRUE;
}
else if (gdk_gl_context_is_legacy (self->gl_context))
{
int maj, min;
gdk_gl_context_get_version (self->gl_context, &maj, &min);
if (maj == 3)
gsk_gl_shader_builder_set_glsl_version (&shader_builder, SHADER_VERSION_GL3_LEGACY);
else
gsk_gl_shader_builder_set_glsl_version (&shader_builder, SHADER_VERSION_GL2_LEGACY);
shader_builder.legacy = TRUE;
}
else
{
gsk_gl_shader_builder_set_glsl_version (&shader_builder, SHADER_VERSION_GL3);
shader_builder.gl3 = TRUE;
}
init_shader_builder (self, &shader_builder);
programs = gsk_gl_renderer_programs_new ();
@@ -3000,7 +3240,7 @@ gsk_gl_renderer_create_programs (GskGLRenderer *self,
prog->index = i;
prog->id = gsk_gl_shader_builder_create_program (&shader_builder,
program_definitions[i].resource_path,
error);
NULL, error);
if (prog->id < 0)
{
g_clear_pointer (&programs, gsk_gl_renderer_programs_unref);
@@ -3445,6 +3685,10 @@ gsk_gl_renderer_add_render_ops (GskGLRenderer *self,
render_repeat_node (self, node, builder);
break;
case GSK_GLSHADER_NODE:
render_glshader_node (self, node, builder);
break;
case GSK_REPEATING_LINEAR_GRADIENT_NODE:
case GSK_REPEATING_RADIAL_GRADIENT_NODE:
case GSK_CAIRO_NODE:
@@ -3727,6 +3971,10 @@ gsk_gl_renderer_render_ops (GskGLRenderer *self)
apply_source_texture_op (program, ptr);
break;
case OP_CHANGE_EXTRA_SOURCE_TEXTURE:
apply_source_extra_texture_op (program, ptr);
break;
case OP_CHANGE_CROSS_FADE:
g_assert (program == &self->programs->cross_fade_program);
apply_cross_fade_op (program, ptr);
@@ -3773,6 +4021,10 @@ gsk_gl_renderer_render_ops (GskGLRenderer *self)
apply_repeat_op (program, ptr);
break;
case OP_CHANGE_GLSHADER_ARGS:
apply_glshader_args_op (program, ptr);
break;
case OP_DRAW:
{
const OpDraw *op = ptr;

View File

@@ -558,6 +558,18 @@ ops_set_texture (RenderOpBuilder *builder,
builder->current_texture = texture_id;
}
void
ops_set_extra_texture (RenderOpBuilder *builder,
int texture_id,
int idx)
{
OpExtraTexture *op;
op = ops_begin (builder, OP_CHANGE_EXTRA_SOURCE_TEXTURE);
op->texture_id = texture_id;
op->idx = idx;
}
int
ops_set_render_target (RenderOpBuilder *builder,
int render_target_id)
@@ -621,6 +633,22 @@ ops_set_color (RenderOpBuilder *builder,
op->rgba = color;
}
void
ops_set_glshader_args (RenderOpBuilder *builder,
GskGLShader *shader,
float width,
float height,
const guchar *uniform_data)
{
OpGLShader *op;
op = ops_begin (builder, OP_CHANGE_GLSHADER_ARGS);
op->shader = shader;
op->size[0] = width;
op->size[1] = height;
op->uniform_data = uniform_data;
}
void
ops_set_color_matrix (RenderOpBuilder *builder,
const graphene_matrix_t *matrix,

View File

@@ -159,6 +159,11 @@ struct _Program
int child_bounds_location;
int texture_rect_location;
} repeat;
struct {
int size_location;
int args_locations[8];
int extra_source_locations[3];
} glshader;
};
ProgramState state;
@@ -185,7 +190,7 @@ typedef struct {
Program unblurred_outset_shadow_program;
};
};
ProgramState state[GL_N_PROGRAMS];
GHashTable *custom_programs; /* GskGLShader -> Program* */
} GskGLRendererPrograms;
typedef struct
@@ -257,6 +262,9 @@ graphene_rect_t ops_set_viewport (RenderOpBuilder *builder,
void ops_set_texture (RenderOpBuilder *builder,
int texture_id);
void ops_set_extra_texture (RenderOpBuilder *builder,
int texture_id,
int idx);
int ops_set_render_target (RenderOpBuilder *builder,
int render_target_id);
@@ -283,6 +291,11 @@ void ops_set_inset_shadow (RenderOpBuilder *self,
const GdkRGBA *color,
float dx,
float dy);
void ops_set_glshader_args (RenderOpBuilder *builder,
GskGLShader *shader,
float width,
float height,
const guchar *uniform_data);
void ops_set_unblurred_outset_shadow (RenderOpBuilder *self,
const GskRoundedRect outline,
float spread,

View File

@@ -96,6 +96,7 @@ check_shader_error (int shader_id,
int
gsk_gl_shader_builder_create_program (GskGLShaderBuilder *self,
const char *resource_path,
const char *extra_fragment_snippet,
GError **error)
{
@@ -156,7 +157,7 @@ gsk_gl_shader_builder_create_program (GskGLShaderBuilder *self,
}
fragment_id = glCreateShader (GL_FRAGMENT_SHADER);
glShaderSource (fragment_id, 8,
glShaderSource (fragment_id, 9,
(const char *[]) {
version_buffer,
self->debugging ? "#define GSK_DEBUG 1\n" : "",
@@ -165,7 +166,8 @@ gsk_gl_shader_builder_create_program (GskGLShaderBuilder *self,
self->gles ? "#define GSK_GLES 1\n" : "",
g_bytes_get_data (self->preamble, NULL),
g_bytes_get_data (self->fs_preamble, NULL),
fragment_shader_start
fragment_shader_start,
extra_fragment_snippet ? extra_fragment_snippet : ""
},
(int[]) {
-1,
@@ -176,6 +178,7 @@ gsk_gl_shader_builder_create_program (GskGLShaderBuilder *self,
-1,
-1,
-1,
-1,
});
glCompileShader (fragment_id);

View File

@@ -33,6 +33,7 @@ void gsk_gl_shader_builder_set_glsl_version (GskGLShaderBuilder *self,
int gsk_gl_shader_builder_create_program (GskGLShaderBuilder *self,
const char *resource_path,
const char *extra_fragment_snippet,
GError **error);
G_END_DECLS

View File

@@ -31,6 +31,8 @@ static guint op_sizes[OP_LAST] = {
sizeof (OpDebugGroup),
0,
sizeof (OpBlend),
sizeof (OpGLShader),
sizeof (OpExtraTexture),
};
void

View File

@@ -39,6 +39,8 @@ typedef enum
OP_PUSH_DEBUG_GROUP = 25,
OP_POP_DEBUG_GROUP = 26,
OP_CHANGE_BLEND = 27,
OP_CHANGE_GLSHADER_ARGS = 28,
OP_CHANGE_EXTRA_SOURCE_TEXTURE = 29,
OP_LAST
} OpKind;
@@ -124,6 +126,12 @@ typedef struct
int texture_id;
} OpTexture;
typedef struct
{
int texture_id;
int idx;
} OpExtraTexture;
typedef struct
{
gsize vao_offset;
@@ -198,6 +206,13 @@ typedef struct
float texture_rect[4];
} OpRepeat;
typedef struct
{
float size[2];
GskGLShader *shader;
const guchar *uniform_data;
} OpGLShader;
void op_buffer_init (OpBuffer *buffer);
void op_buffer_destroy (OpBuffer *buffer);
void op_buffer_clear (OpBuffer *buffer);

View File

@@ -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__ */

529
gsk/gskglshader.c Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
/**
* 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:
*
* |[<!-- 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 (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:
*
* |[<!-- language="plain" -->
* 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;
}

70
gsk/gskglshader.h Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef __GSK_GLSHADER_H__
#define __GSK_GLSHADER_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_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__ */

17
gsk/gskglshaderprivate.h Normal file
View File

@@ -0,0 +1,17 @@
#ifndef __GSK_GL_UNIFORM_H__
#define __GSK_GL_UNIFORM_H__
#include "gskglshader.h"
typedef struct
{
char *name;
GskGLUniformType type;
int offset;
} GskGLUniform;
const GskGLUniform *gsk_glshader_get_uniforms (GskGLShader *shader,
int *n_uniforms);
#endif

View File

@@ -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_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__ */

View File

@@ -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 =
{

View File

@@ -358,6 +358,13 @@ parse_double (GtkCssParser *parser,
return gtk_css_parser_consume_number (parser, out_double);
}
static gboolean
parse_int (GtkCssParser *parser,
gpointer out_int)
{
return gtk_css_parser_consume_integer (parser, out_int);
}
static gboolean
parse_point (GtkCssParser *parser,
gpointer out_point)
@@ -824,9 +831,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 +1096,251 @@ parse_inset_shadow_node (GtkCssParser *parser)
return gsk_inset_shadow_node_new (&outline, &color, dx, dy, spread, blur);
}
typedef struct {
GskGLUniformType type;
char *name;
union {
int i;
double v[4];
} value;
} Uniform;
static gboolean
parse_uniform_decl (GtkCssParser *parser, Uniform *decl)
{
Uniform decls[] = {
{ GSK_GLUNIFORM_TYPE_FLOAT, (char *) "float" },
{ GSK_GLUNIFORM_TYPE_INT, (char *) "int" },
{ GSK_GLUNIFORM_TYPE_UINT, (char*) "uint" },
{ GSK_GLUNIFORM_TYPE_BOOL, (char *) "bool" },
{ GSK_GLUNIFORM_TYPE_VEC2, (char *) "vec2" },
{ GSK_GLUNIFORM_TYPE_VEC3, (char *) "vec3" },
{ GSK_GLUNIFORM_TYPE_VEC4, (char *) "vec4" },
};
int i;
for (i = 0; i < G_N_ELEMENTS (decls); i++)
{
if (gtk_css_parser_try_ident (parser, decls[i].name))
{
decl->type = decls[i].type;
decl->name = gtk_css_parser_consume_ident (parser);
gtk_css_parser_try_token (parser, GTK_CSS_TOKEN_COMMA);
return TRUE;
}
}
return FALSE;
}
static void
clear_uniform_decl (gpointer data)
{
Uniform *decl = data;
g_free (decl->name);
}
static gboolean
parse_uniforms (GtkCssParser *parser, gpointer data)
{
GArray *uniforms = data;
Uniform decl;
while (parse_uniform_decl (parser, &decl))
g_array_append_val (uniforms, decl);
return TRUE;
}
static void
clear_array (gpointer data)
{
GArray *array = data;
g_array_set_size (array, 0);
}
static gboolean
parse_uniform_value (GtkCssParser *parser, Uniform *uniform)
{
switch (uniform->type)
{
case GSK_GLUNIFORM_TYPE_FLOAT:
if (!gtk_css_parser_consume_number (parser, &uniform->value.v[0]))
return FALSE;
break;
case GSK_GLUNIFORM_TYPE_INT:
if (!gtk_css_parser_consume_integer (parser, &uniform->value.i))
return FALSE;
break;
case GSK_GLUNIFORM_TYPE_UINT:
if (!gtk_css_parser_consume_integer (parser, &uniform->value.i) ||
uniform->value.i < 0)
return FALSE;
break;
case GSK_GLUNIFORM_TYPE_BOOL:
if (!gtk_css_parser_consume_integer (parser, &uniform->value.i) ||
(uniform->value.i != 0 && uniform->value.i != 1))
return FALSE;
break;
case GSK_GLUNIFORM_TYPE_VEC2:
if (!gtk_css_parser_consume_number (parser, &uniform->value.v[0]) ||
!gtk_css_parser_consume_number (parser, &uniform->value.v[1]))
return FALSE;
break;
case GSK_GLUNIFORM_TYPE_VEC3:
if (!gtk_css_parser_consume_number (parser, &uniform->value.v[0]) ||
!gtk_css_parser_consume_number (parser, &uniform->value.v[1]) ||
!gtk_css_parser_consume_number (parser, &uniform->value.v[2]))
return FALSE;
break;
case GSK_GLUNIFORM_TYPE_VEC4:
if (!gtk_css_parser_consume_number (parser, &uniform->value.v[0]) ||
!gtk_css_parser_consume_number (parser, &uniform->value.v[1]) ||
!gtk_css_parser_consume_number (parser, &uniform->value.v[2]) ||
!gtk_css_parser_consume_number (parser, &uniform->value.v[3]))
return FALSE;
break;
case GSK_GLUNIFORM_TYPE_NONE:
default:
g_assert_not_reached ();
break;
}
gtk_css_parser_try_token (parser, GTK_CSS_TOKEN_COMMA);
return TRUE;
}
static gboolean
parse_uniform_data (GtkCssParser *parser, gpointer data)
{
GArray *uniforms = data;
int i;
for (i = 0; i < uniforms->len; i++)
{
Uniform *uniform = &g_array_index (uniforms, Uniform, i);
if (!parse_uniform_value (parser, uniform))
return FALSE;
}
return TRUE;
}
static GskRenderNode *
parse_glshader_node (GtkCssParser *parser)
{
graphene_rect_t bounds = GRAPHENE_RECT_INIT (0, 0, 50, 50);
char *sourcecode = NULL;
int required_sources = 0;
GskRenderNode *fallback = NULL;
GskRenderNode *child[4] = { NULL, };
GArray *uniforms = g_array_new (FALSE, FALSE, sizeof (Uniform));
const Declaration declarations[] = {
{ "bounds", parse_rect, NULL, &bounds },
{ "sourcecode", parse_string, NULL, &sourcecode },
{ "required-sources", parse_int, NULL, &required_sources },
{ "uniforms", parse_uniforms, clear_array, uniforms },
{ "uniform-data", parse_uniform_data, clear_array, uniforms },
{ "fallback", parse_node, clear_node, &fallback },
{ "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;
guchar *uniform_data;
int len, i;
g_array_set_clear_func (uniforms, clear_uniform_decl);
parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
for (len = 0; len < 4; len++)
{
if (child[len] == NULL)
break;
}
shader = gsk_glshader_new (sourcecode);
gsk_glshader_set_n_required_sources (shader, required_sources);
for (i = 0; i < uniforms->len; i++)
{
Uniform *uniform = &g_array_index (uniforms, Uniform, i);
gsk_glshader_add_uniform (shader, uniform->name, uniform->type);
}
uniform_data = g_new (guchar, gsk_glshader_get_uniforms_size (shader));
for (i = 0; i < uniforms->len; i++)
{
Uniform *uniform = &g_array_index (uniforms, Uniform, i);
guchar *dest;
dest = uniform_data + gsk_glshader_get_uniform_offset (shader, i);
switch (uniform->type)
{
case GSK_GLUNIFORM_TYPE_FLOAT:
*(float *)dest = uniform->value.v[0];
break;
case GSK_GLUNIFORM_TYPE_INT:
*(gint32 *)dest = uniform->value.i;
break;
case GSK_GLUNIFORM_TYPE_UINT:
*(guint32 *)dest = uniform->value.i;
break;
case GSK_GLUNIFORM_TYPE_BOOL:
*(guint32 *)dest = uniform->value.i;
break;
case GSK_GLUNIFORM_TYPE_VEC4:
((float *)dest)[3] = uniform->value.v[3];
G_GNUC_FALLTHROUGH;
case GSK_GLUNIFORM_TYPE_VEC3:
((float *)dest)[2] = uniform->value.v[2];
G_GNUC_FALLTHROUGH;
case GSK_GLUNIFORM_TYPE_VEC2:
((float *)dest)[1] = uniform->value.v[1];
((float *)dest)[0] = uniform->value.v[0];
break;
case GSK_GLUNIFORM_TYPE_NONE:
default:
g_assert_not_reached ();
}
}
node = gsk_glshader_node_new (shader, &bounds, uniform_data,
fallback, child, len);
g_array_unref (uniforms);
g_free (uniform_data);
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 +1855,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 +2090,51 @@ append_point (GString *str,
string_append_double (str, p->y);
}
static void
append_string (GString *str,
const char *string)
{
gsize len;
g_return_if_fail (str != NULL);
g_return_if_fail (string != NULL);
g_string_append_c (str, '"');
do {
len = strcspn (string, "\\\"\n\r\f");
g_string_append_len (str, string, len);
string += len;
switch (*string)
{
case '\0':
goto out;
case '\n':
g_string_append (str, "\\A ");
break;
case '\r':
g_string_append (str, "\\D ");
break;
case '\f':
g_string_append (str, "\\C ");
break;
case '\"':
g_string_append (str, "\\\"");
break;
case '\\':
g_string_append (str, "\\\\");
break;
default:
g_assert_not_reached ();
break;
}
string++;
} while (*string);
out:
g_string_append_c (str, '"');
}
static void
append_vec4 (GString *str,
const graphene_vec4_t *v)
@@ -1866,6 +2164,19 @@ append_float_param (Printer *p,
g_string_append (p->str, ";\n");
}
static void
append_int_param (Printer *p,
const char *param_name,
int value,
int default_value)
{
if (value == default_value)
return;
_indent (p);
g_string_append_printf (p->str, "%s: %d;\n", param_name, value);
}
static void
append_rgba_param (Printer *p,
const char *param_name,
@@ -1914,6 +2225,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 +2764,131 @@ render_node_print (Printer *p,
}
break;
case GSK_GLSHADER_NODE:
{
GskGLShader *shader = gsk_glshader_node_get_shader (node);
const guchar *uniform_data = gsk_glshader_node_get_uniform_data (node);
start_node (p, "glshader");
append_rect_param (p, "bounds", &node->bounds);
append_string_param (p, "sourcecode", gsk_glshader_get_sourcecode (shader));
append_int_param (p, "required-sources", gsk_glshader_get_n_required_sources (shader), 0);
if (gsk_glshader_get_n_uniforms (shader) > 0)
{
GString *decl = g_string_new ("");
GString *data = g_string_new ("");
for (guint i = 0; i < gsk_glshader_get_n_uniforms (shader); i++)
{
const char *name = gsk_glshader_get_uniform_name (shader, i);
int offset = gsk_glshader_get_uniform_offset (shader, i);
if (i > 0)
{
g_string_append (decl, ", ");
g_string_append (data, ", ");
}
switch (gsk_glshader_get_uniform_type (shader, i))
{
case GSK_GLUNIFORM_TYPE_NONE:
default:
g_assert_not_reached ();
break;
case GSK_GLUNIFORM_TYPE_FLOAT:
{
float value = *(float *)(uniform_data + offset);
g_string_append_printf (decl, "float %s", name);
g_string_append_printf (data, "%f", value);
}
break;
case GSK_GLUNIFORM_TYPE_INT:
{
gint32 value = *(gint32 *)(uniform_data + offset);
g_string_append_printf (decl, "int %s", name);
g_string_append_printf (data, "%d", value);
}
break;
case GSK_GLUNIFORM_TYPE_UINT:
{
guint32 value = *(guint32 *)(uniform_data + offset);
g_string_append_printf (decl, "uint %s", name);
g_string_append_printf (data, "%u", value);
}
break;
case GSK_GLUNIFORM_TYPE_BOOL:
{
gboolean value = *(gboolean *)(uniform_data + offset);
g_string_append_printf (decl, "bool %s", name);
g_string_append_printf (data, "%d", value);
}
break;
case GSK_GLUNIFORM_TYPE_VEC2:
{
graphene_vec2_t *v = (graphene_vec2_t *)(uniform_data + offset);
g_string_append_printf (decl, "vec2 %s", name);
g_string_append_printf (data, "%f %f",
graphene_vec2_get_x (v),
graphene_vec2_get_y (v));
}
break;
case GSK_GLUNIFORM_TYPE_VEC3:
{
graphene_vec3_t *v = (graphene_vec3_t *)(uniform_data + offset);
g_string_append_printf (decl, "vec3 %s", name);
g_string_append_printf (data, "%f %f %f",
graphene_vec3_get_x (v),
graphene_vec3_get_y (v),
graphene_vec3_get_z (v));
}
break;
case GSK_GLUNIFORM_TYPE_VEC4:
{
graphene_vec4_t *v = (graphene_vec4_t *)(uniform_data + offset);
g_string_append_printf (decl, "vec4 %s", name);
g_string_append_printf (data, "%f %f %f %f",
graphene_vec4_get_x (v),
graphene_vec4_get_y (v),
graphene_vec4_get_z (v),
graphene_vec4_get_w (v));
}
break;
}
}
_indent (p);
g_string_append_printf (p->str, "uniforms: %s;\n", decl->str);
_indent (p);
g_string_append_printf (p->str, "uniform-data: %s;\n", data->str);
g_string_free (decl, TRUE);
g_string_free (data, TRUE);
}
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);
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);

View File

@@ -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[];

View File

@@ -16,11 +16,13 @@ gsk_private_gl_shaders = [
'resources/glsl/cross_fade.glsl',
'resources/glsl/blend.glsl',
'resources/glsl/repeat.glsl',
'resources/glsl/custom.glsl',
]
gsk_public_sources = files([
'gskdiff.c',
'gskcairorenderer.c',
'gskglshader.c',
'gskrenderer.c',
'gskrendernode.c',
'gskrendernodeimpl.c',
@@ -52,6 +54,7 @@ gsk_private_sources = files([
gsk_public_headers = files([
'gskcairorenderer.h',
'gskenums.h',
'gskglshader.h',
'gskrenderer.h',
'gskrendernode.h',
'gskroundedrect.h',

View File

@@ -0,0 +1,21 @@
// VERTEX_SHADER:
void main() {
gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0);
vUv = vec2(aUv.x, aUv.y);
}
// FRAGMENT_SHADER:
// The shader supplies:
void mainImage(out vec4 fragColor, in vec2 fragCoord, in vec2 resolution, in vec2 uv);
uniform vec2 u_size;
uniform sampler2D u_source2;
uniform sampler2D u_source3;
uniform sampler2D u_source4;
void main() {
vec4 fragColor;
vec2 fragCoord = vec2(vUv.x * u_size.x, (1.0-vUv.y) * u_size.y);
mainImage(fragColor, fragCoord, u_size, vUv);
gskSetOutputColor(fragColor);
}

View File

@@ -539,6 +539,10 @@ gsk_vulkan_render_pass_add_node (GskVulkanRenderPass *self,
}
return;
case GSK_GLSHADER_NODE:
gsk_vulkan_render_pass_add_node (self, render, constants, gsk_glshader_node_get_fallback_child (node));
return;
case GSK_DEBUG_NODE:
gsk_vulkan_render_pass_add_node (self, render, constants, gsk_debug_node_get_child (node));
return;

View File

@@ -91,6 +91,14 @@ struct _GtkSnapshotState {
struct {
graphene_rect_t bounds;
} clip;
struct {
GskGLShader *shader;
guchar *args;
graphene_rect_t bounds;
int n_children;
int node_idx;
GskRenderNode **nodes;
} glshader;
struct {
GskRoundedRect bounds;
} rounded_clip;
@@ -812,6 +820,154 @@ gtk_snapshot_push_clip (GtkSnapshot *snapshot,
gtk_graphene_rect_scale_affine (bounds, scale_x, scale_y, dx, dy, &state->data.clip.bounds);
}
static GskRenderNode *
maybe_clip (GskRenderNode *node,
const graphene_rect_t *bounds)
{
if (node &&
!graphene_rect_contains_rect (bounds, &node->bounds))
{
return gsk_clip_node_new (node, bounds);
}
return gsk_render_node_ref (node);
}
static GskRenderNode *
gtk_snapshot_collect_glshader (GtkSnapshot *snapshot,
GtkSnapshotState *state,
GskRenderNode **nodes,
guint n_nodes)
{
GskRenderNode *shader_node = NULL, *child_node;
GdkRGBA transparent = { 0, 0, 0, 0 };
child_node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
if (child_node == NULL)
child_node = gsk_color_node_new (&transparent, &state->data.glshader.bounds);
state->data.glshader.nodes[state->data.glshader.node_idx] = child_node;
if (state->data.glshader.node_idx != state->data.glshader.n_children)
return NULL; /* Not last */
/* This is the last pop */
shader_node = NULL;
if (state->data.glshader.bounds.size.width != 0 &&
state->data.glshader.bounds.size.height != 0)
{
GskRenderNode *fallback_node = maybe_clip (state->data.glshader.nodes[0],
&state->data.glshader.bounds);
shader_node = gsk_glshader_node_new (state->data.glshader.shader,
&state->data.glshader.bounds,
state->data.glshader.args,
fallback_node,
&state->data.glshader.nodes[1],
state->data.glshader.n_children);
gsk_render_node_unref (fallback_node);
}
g_object_unref (state->data.glshader.shader);
for (guint i = 0; i < state->data.glshader.n_children + 1; i++)
gsk_render_node_unref (state->data.glshader.nodes[i]);
g_free (state->data.glshader.nodes);
return shader_node;
}
/**
* gtk_snapshot_push_glshader_va:
* @snapshot: a #GtkSnapshot
* @shader: The code to run
* @bounds: the rectangle to render into
* @n_children: The number of extra nodes given as argument to the shader as textures.
* @uniforms: Values for the uniforms of the shader in this instance, ending with NULL
*
* Renders a rectangle with output from a GLSL shader. This is the same as
* gtk_snapshot_push_glshader() but with a va_list instead of a varargs argument,
* so see that for details.
*/
void
gtk_snapshot_push_glshader_va (GtkSnapshot *snapshot,
GskGLShader *shader,
const graphene_rect_t *bounds,
int n_children,
va_list uniforms)
{
GtkSnapshotState *state;
float scale_x, scale_y, dx, dy;
GskRenderNode **nodes;
int node_idx;
graphene_rect_t transformed_bounds;
gtk_snapshot_ensure_affine (snapshot, &scale_x, &scale_y, &dx, &dy);
state = gtk_snapshot_push_state (snapshot,
gtk_snapshot_get_current_state (snapshot)->transform,
gtk_snapshot_collect_glshader);
gtk_graphene_rect_scale_affine (bounds, scale_x, scale_y, dx, dy, &transformed_bounds);
state->data.glshader.bounds = transformed_bounds;
state->data.glshader.shader = g_object_ref (shader);
state->data.glshader.args = gsk_glshader_format_uniform_data_va (shader, uniforms);
state->data.glshader.n_children = n_children;
nodes = g_new0 (GskRenderNode *, n_children + 1);
node_idx = n_children; /* We pop in reverse order */
state->data.glshader.node_idx = node_idx--;
state->data.glshader.nodes = nodes;
for (int i = 0; i < n_children; i++)
{
state = gtk_snapshot_push_state (snapshot,
gtk_snapshot_get_current_state (snapshot)->transform,
gtk_snapshot_collect_glshader);
state->data.glshader.node_idx = node_idx--;
state->data.glshader.n_children = n_children;
state->data.glshader.nodes = nodes;
state->data.glshader.bounds = transformed_bounds;
}
}
/**
* gtk_snapshot_push_glshader:
* @snapshot: a #GtkSnapshot
* @shader: The code to run
* @bounds: the rectangle to render into
* @n_children: The number of extra nodes given as argument to the shader as textures.
* @uniforms: Values for the uniforms of the shader in this instance, ending with NULL
*
* Push a #GskGLShaderNode with a specific #GskGLShader and a set of uniform values
* to use while rendering. Additionally this takes a fallback node and a list of
* @n_children other nodes which will be passed to the #GskGLShaderNode.
*
* The fallback node is used if GLSL shaders are not supported by the backend, or if
* there is any problem compiling the shader. The fallback node needs to be pushed
* directly after the gtk_snapshot_push_glshader() call up until the first call to gtk_snapshot_pop().
*
* If @n_children > 0, then it is expected that you (after the fallback call gtk_snapshot_pop()
* @n_children times. Each of these will generate a node that is passed as a child to the
* glshader node, which in turn will render these to textures and pass as arguments to the
* shader.
*
* For details on how to write shaders, see #GskGLShader.
*/
void
gtk_snapshot_push_glshader (GtkSnapshot *snapshot,
GskGLShader *shader,
const graphene_rect_t *bounds,
int n_children,
...)
{
va_list args;
va_start (args, n_children);
gtk_snapshot_push_glshader_va (snapshot, shader, bounds, n_children, args);
va_end (args);
}
static GskRenderNode *
gtk_snapshot_collect_rounded_clip (GtkSnapshot *snapshot,
GtkSnapshotState *state,

View File

@@ -99,6 +99,18 @@ GDK_AVAILABLE_IN_ALL
void gtk_snapshot_push_cross_fade (GtkSnapshot *snapshot,
double progress);
GDK_AVAILABLE_IN_ALL
void gtk_snapshot_push_glshader (GtkSnapshot *snapshot,
GskGLShader *shader,
const graphene_rect_t *bounds,
int n_children,
...);
GDK_AVAILABLE_IN_ALL
void gtk_snapshot_push_glshader_va (GtkSnapshot *snapshot,
GskGLShader *shader,
const graphene_rect_t *bounds,
int n_children,
va_list uniforms);
GDK_AVAILABLE_IN_ALL
void gtk_snapshot_pop (GtkSnapshot *snapshot);
GDK_AVAILABLE_IN_ALL

View File

@@ -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:
@@ -511,6 +533,34 @@ add_color_row (GtkListStore *store,
g_object_unref (texture);
}
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,
@@ -759,6 +809,88 @@ populate_render_node_properties (GtkListStore *store,
add_float_row (store, "Radius", gsk_blur_node_get_radius (node));
break;
case GSK_GLSHADER_NODE:
{
GskGLShader *shader = gsk_glshader_node_get_shader (node);
const guchar *uniform_data = gsk_glshader_node_get_uniform_data (node);
int i;
add_int_row (store, "Required sources", gsk_glshader_get_n_required_sources (shader));
for (i = 0; i < gsk_glshader_get_n_uniforms (shader); i++)
{
const char *name;
int offset;
char *title;
name = gsk_glshader_get_uniform_name (shader, i);
title = g_strdup_printf ("Uniform %s", name);
offset = gsk_glshader_get_uniform_offset (shader, i);
switch (gsk_glshader_get_uniform_type (shader, i))
{
case GSK_GLUNIFORM_TYPE_NONE:
default:
g_assert_not_reached ();
break;
case GSK_GLUNIFORM_TYPE_FLOAT:
add_float_row (store, title, *(float *)(uniform_data + offset));
break;
case GSK_GLUNIFORM_TYPE_INT:
add_int_row (store, title, *(gint32 *)(uniform_data + offset));
break;
case GSK_GLUNIFORM_TYPE_UINT:
add_uint_row (store, title, *(guint32 *)(uniform_data + offset));
break;
case GSK_GLUNIFORM_TYPE_BOOL:
add_boolean_row (store, title, *(gboolean *)(uniform_data + offset));
break;
case GSK_GLUNIFORM_TYPE_VEC2:
{
graphene_vec2_t *v = (graphene_vec2_t *)(uniform_data + offset);
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_GLUNIFORM_TYPE_VEC3:
{
graphene_vec3_t *v = (graphene_vec3_t *)(uniform_data + offset);
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_GLUNIFORM_TYPE_VEC4:
{
graphene_vec4_t *v = (graphene_vec4_t *)(uniform_data + offset);
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);