1#include <math.h>
2#include <stdlib.h>
3#include <string.h>
4#include <epoxy/gl.h>
5
6#include "gtkshadertoy.h"
7
8const char *default_image_shader =
9 "void mainImage(out vec4 fragColor, in vec2 fragCoord) {\n"
10 " // Normalized pixel coordinates (from 0 to 1)\n"
11 " vec2 uv = fragCoord/iResolution.xy;\n"
12 "\n"
13 " // Time varying pixel color\n"
14 " vec3 col = 0.5 + 0.5*cos(iTime+uv.xyx+vec3(0,2,4));\n"
15 "\n"
16 " if (distance(iMouse.xy, fragCoord.xy) <= 10.0) {\n"
17 " col = vec3(0.0);\n"
18 " }\n"
19 "\n"
20 " // Output to screen\n"
21 " fragColor = vec4(col,1.0);\n"
22 "}\n";
23
24const char *shadertoy_vertex_shader =
25 "#version 150 core\n"
26 "\n"
27 "uniform vec3 iResolution;\n"
28 "\n"
29 "in vec2 position;\n"
30 "out vec2 fragCoord;\n"
31 "\n"
32 "void main() {\n"
33 " gl_Position = vec4(position, 0.0, 1.0);\n"
34 "\n"
35 " // Convert from OpenGL coordinate system (with origin in center\n"
36 " // of screen) to Shadertoy/texture coordinate system (with origin\n"
37 " // in lower left corner)\n"
38 " fragCoord = (gl_Position.xy + vec2(1.0)) / vec2(2.0) * iResolution.xy;\n"
39 "}\n";
40
41const char *fragment_prefix =
42 "#version 150 core\n"
43 "\n"
44 "uniform vec3 iResolution; // viewport resolution (in pixels)\n"
45 "uniform float iTime; // shader playback time (in seconds)\n"
46 "uniform float iTimeDelta; // render time (in seconds)\n"
47 "uniform int iFrame; // shader playback frame\n"
48 "uniform float iChannelTime[4]; // channel playback time (in seconds)\n"
49 "uniform vec3 iChannelResolution[4]; // channel resolution (in pixels)\n"
50 "uniform vec4 iMouse; // mouse pixel coords. xy: current (if MLB down), zw: click\n"
51 "uniform sampler2D iChannel0;\n"
52 "uniform sampler2D iChannel1;\n"
53 "uniform sampler2D iChannel2;\n"
54 "uniform sampler2D iChannel3;\n"
55 "uniform vec4 iDate; // (year, month, day, time in seconds)\n"
56 "uniform float iSampleRate; // sound sample rate (i.e., 44100)\n"
57 "\n"
58 "in vec2 fragCoord;\n"
59 "out vec4 vFragColor;\n";
60
61
62// Fragment shader suffix
63const char *fragment_suffix =
64 " void main() {\n"
65 " vec4 c;\n"
66 " mainImage(c, fragCoord);\n"
67 " vFragColor = c;\n"
68 " }\n";
69
70typedef struct {
71 char *image_shader;
72 gboolean image_shader_dirty;
73
74 gboolean error_set;
75
76 /* Vertex buffers */
77 GLuint vao;
78 GLuint buffer;
79
80 /* Active program */
81 GLuint program;
82
83 /* Location of uniforms for program */
84 GLuint resolution_location;
85 GLuint time_location;
86 GLuint timedelta_location;
87 GLuint frame_location;
88 GLuint mouse_location;
89
90 /* Current uniform values */
91 float resolution[3];
92 float time;
93 float timedelta;
94 float mouse[4];
95 int frame;
96
97 /* Animation data */
98 gint64 first_frame_time;
99 gint64 first_frame;
100 guint tick;
101} GtkShadertoyPrivate;
102
103G_DEFINE_TYPE_WITH_PRIVATE (GtkShadertoy, gtk_shadertoy, GTK_TYPE_GL_AREA)
104
105static gboolean gtk_shadertoy_render (GtkGLArea *area,
106 GdkGLContext *context);
107static void gtk_shadertoy_reshape (GtkGLArea *area,
108 int width,
109 int height);
110static void gtk_shadertoy_realize (GtkWidget *widget);
111static void gtk_shadertoy_unrealize (GtkWidget *widget);
112static gboolean gtk_shadertoy_tick (GtkWidget *widget,
113 GdkFrameClock *frame_clock,
114 gpointer user_data);
115
116GtkWidget *
117gtk_shadertoy_new (void)
118{
119 return g_object_new (object_type: gtk_shadertoy_get_type (), NULL);
120}
121
122static void
123drag_begin_cb (GtkGestureDrag *drag,
124 double x,
125 double y,
126 gpointer user_data)
127{
128 GtkShadertoy *shadertoy = GTK_SHADERTOY (user_data);
129 GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (self: shadertoy);
130 int height = gtk_widget_get_height (GTK_WIDGET (shadertoy));
131 int scale = gtk_widget_get_scale_factor (GTK_WIDGET (shadertoy));
132
133 priv->mouse[0] = x * scale;
134 priv->mouse[1] = (height - y) * scale;
135 priv->mouse[2] = priv->mouse[0];
136 priv->mouse[3] = priv->mouse[1];
137}
138
139static void
140drag_update_cb (GtkGestureDrag *drag,
141 double dx,
142 double dy,
143 gpointer user_data)
144{
145 GtkShadertoy *shadertoy = GTK_SHADERTOY (user_data);
146 GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (self: shadertoy);
147 int width = gtk_widget_get_width (GTK_WIDGET (shadertoy));
148 int height = gtk_widget_get_height (GTK_WIDGET (shadertoy));
149 int scale = gtk_widget_get_scale_factor (GTK_WIDGET (shadertoy));
150 double x, y;
151
152 gtk_gesture_drag_get_start_point (gesture: drag, x: &x, y: &y);
153 x += dx;
154 y += dy;
155
156 if (x >= 0 && x < width &&
157 y >= 0 && y < height)
158 {
159 priv->mouse[0] = x * scale;
160 priv->mouse[1] = (height - y) * scale;
161 }
162}
163
164static void
165drag_end_cb (GtkGestureDrag *drag,
166 double dx,
167 double dy,
168 gpointer user_data)
169{
170 GtkShadertoy *shadertoy = GTK_SHADERTOY (user_data);
171 GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (self: shadertoy);
172
173 priv->mouse[2] = -priv->mouse[2];
174 priv->mouse[3] = -priv->mouse[3];
175}
176
177static void
178gtk_shadertoy_init (GtkShadertoy *shadertoy)
179{
180 GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (self: shadertoy);
181 GtkGesture *drag;
182
183 priv->image_shader = g_strdup (str: default_image_shader);
184 priv->tick = gtk_widget_add_tick_callback (GTK_WIDGET (shadertoy), callback: gtk_shadertoy_tick, user_data: shadertoy, NULL);
185
186 drag = gtk_gesture_drag_new ();
187 gtk_widget_add_controller (GTK_WIDGET (shadertoy), GTK_EVENT_CONTROLLER (drag));
188 g_signal_connect (drag, "drag-begin", (GCallback)drag_begin_cb, shadertoy);
189 g_signal_connect (drag, "drag-update", (GCallback)drag_update_cb, shadertoy);
190 g_signal_connect (drag, "drag-end", (GCallback)drag_end_cb, shadertoy);
191}
192
193static void
194gtk_shadertoy_finalize (GObject *obj)
195{
196 GtkShadertoy *shadertoy = GTK_SHADERTOY (obj);
197 GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (self: shadertoy);
198
199 gtk_widget_remove_tick_callback (GTK_WIDGET (shadertoy), id: priv->tick);
200 g_free (mem: priv->image_shader);
201
202 G_OBJECT_CLASS (gtk_shadertoy_parent_class)->finalize (obj);
203}
204
205static void
206gtk_shadertoy_class_init (GtkShadertoyClass *klass)
207{
208 GTK_GL_AREA_CLASS (klass)->render = gtk_shadertoy_render;
209 GTK_GL_AREA_CLASS (klass)->resize = gtk_shadertoy_reshape;
210
211 GTK_WIDGET_CLASS (klass)->realize = gtk_shadertoy_realize;
212 GTK_WIDGET_CLASS (klass)->unrealize = gtk_shadertoy_unrealize;
213
214 G_OBJECT_CLASS (klass)->finalize = gtk_shadertoy_finalize;
215}
216
217/* new window size or exposure */
218static void
219gtk_shadertoy_reshape (GtkGLArea *area, int width, int height)
220{
221 GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (self: (GtkShadertoy *) area);
222
223 priv->resolution[0] = width;
224 priv->resolution[1] = height;
225 priv->resolution[2] = 1.0; /* screen aspect ratio */
226
227 /* Set the viewport */
228 glViewport (0, 0, (GLint) width, (GLint) height);
229}
230
231static GLuint
232create_shader (int type,
233 const char *src,
234 GError **error)
235{
236 GLuint shader;
237 int status;
238
239 shader = glCreateShader (type);
240 glShaderSource (shader, 1, &src, NULL);
241 glCompileShader (shader);
242
243 glGetShaderiv (shader, GL_COMPILE_STATUS, &status);
244 if (status == GL_FALSE)
245 {
246 int log_len;
247 char *buffer;
248
249 glGetShaderiv (shader, GL_INFO_LOG_LENGTH, &log_len);
250
251 buffer = g_malloc (n_bytes: log_len + 1);
252 glGetShaderInfoLog (shader, log_len, NULL, buffer);
253
254 g_set_error (err: error, GDK_GL_ERROR, code: GDK_GL_ERROR_COMPILATION_FAILED,
255 format: "Compile failure in %s shader:\n%s",
256 type == GL_VERTEX_SHADER ? "vertex" : "fragment",
257 buffer);
258
259 g_free (mem: buffer);
260
261 glDeleteShader (shader);
262
263 return 0;
264 }
265
266 return shader;
267}
268
269static gboolean
270init_shaders (GtkShadertoy *shadertoy,
271 const char *vertex_source,
272 const char *fragment_source,
273 GError **error)
274{
275 GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (self: shadertoy);
276 GLuint vertex, fragment;
277 GLuint program = 0;
278 int status;
279 gboolean res = TRUE;
280
281 vertex = create_shader (GL_VERTEX_SHADER, src: vertex_source, error);
282 if (vertex == 0)
283 return FALSE;
284
285 fragment = create_shader (GL_FRAGMENT_SHADER, src: fragment_source, error);
286 if (fragment == 0)
287 {
288 glDeleteShader (vertex);
289 return FALSE;
290 }
291
292 program = glCreateProgram ();
293 glAttachShader (program, vertex);
294 glAttachShader (program, fragment);
295
296 glLinkProgram (program);
297
298 glGetProgramiv (program, GL_LINK_STATUS, &status);
299 if (status == GL_FALSE)
300 {
301 int log_len;
302 char *buffer;
303
304 glGetProgramiv (program, GL_INFO_LOG_LENGTH, &log_len);
305
306 buffer = g_malloc (n_bytes: log_len + 1);
307 glGetProgramInfoLog (program, log_len, NULL, buffer);
308
309 g_set_error (err: error, GDK_GL_ERROR, code: GDK_GL_ERROR_LINK_FAILED,
310 format: "Linking failure:\n%s", buffer);
311 res = FALSE;
312
313 g_free (mem: buffer);
314
315 glDeleteProgram (program);
316
317 goto out;
318 }
319
320 if (priv->program != 0)
321 glDeleteProgram (priv->program);
322
323 priv->program = program;
324 priv->resolution_location = glGetUniformLocation (program, "iResolution");
325 priv->time_location = glGetUniformLocation (program, "iTime");
326 priv->timedelta_location = glGetUniformLocation (program, "iTimeDelta");
327 priv->frame_location = glGetUniformLocation (program, "iFrame");
328 priv->mouse_location = glGetUniformLocation (program, "iMouse");
329
330 glDetachShader (program, vertex);
331 glDetachShader (program, fragment);
332
333out:
334 /* These are now owned by the program and can be deleted */
335 glDeleteShader (vertex);
336 glDeleteShader (fragment);
337
338 return res;
339}
340
341static void
342gtk_shadertoy_realize_shader (GtkShadertoy *shadertoy)
343{
344 GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (self: shadertoy);
345 char *fragment_shader;
346 GError *error = NULL;
347
348 fragment_shader = g_strconcat (string1: fragment_prefix, priv->image_shader, fragment_suffix, NULL);
349 if (!init_shaders (shadertoy, vertex_source: shadertoy_vertex_shader, fragment_source: fragment_shader, error: &error))
350 {
351 priv->error_set = TRUE;
352 gtk_gl_area_set_error (GTK_GL_AREA (shadertoy), error);
353 g_error_free (error);
354 }
355 g_free (mem: fragment_shader);
356
357 /* Start new shader at time zero */
358 priv->first_frame_time = 0;
359 priv->first_frame = 0;
360
361 priv->image_shader_dirty = FALSE;
362}
363
364static gboolean
365gtk_shadertoy_render (GtkGLArea *area,
366 GdkGLContext *context)
367{
368 GtkShadertoy *shadertoy = GTK_SHADERTOY (area);
369 GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (self: shadertoy);
370
371 if (gtk_gl_area_get_error (area) != NULL)
372 return FALSE;
373
374 if (priv->image_shader_dirty)
375 gtk_shadertoy_realize_shader (shadertoy);
376
377 /* Clear the viewport */
378 glClearColor (0.0, 0.0, 0.0, 1.0);
379 glClear (GL_COLOR_BUFFER_BIT);
380
381 glUseProgram (priv->program);
382
383 /* Update uniforms */
384 if (priv->resolution_location != -1)
385 glUniform3fv (priv->resolution_location, 1, priv->resolution);
386 if (priv->time_location != -1)
387 glUniform1f (priv->time_location, priv->time);
388 if (priv->timedelta_location != -1)
389 glUniform1f (priv->timedelta_location, priv->timedelta);
390 if (priv->frame_location != -1)
391 glUniform1i (priv->frame_location, priv->frame);
392 if (priv->mouse_location != -1)
393 glUniform4fv (priv->mouse_location, 1, priv->mouse);
394
395 /* Use the vertices in our buffer */
396 glBindBuffer (GL_ARRAY_BUFFER, priv->buffer);
397 glEnableVertexAttribArray (0);
398 glVertexAttribPointer (0, 4, GL_FLOAT, GL_FALSE, 0, 0);
399
400 glDrawArrays (GL_TRIANGLES, 0, 6);
401
402 /* We finished using the buffers and program */
403 glDisableVertexAttribArray (0);
404 glBindBuffer (GL_ARRAY_BUFFER, 0);
405 glUseProgram (0);
406
407 /* Flush the contents of the pipeline */
408 glFlush ();
409
410 return TRUE;
411}
412
413const char *
414gtk_shadertoy_get_image_shader (GtkShadertoy *shadertoy)
415{
416 GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (self: shadertoy);
417
418 return priv->image_shader;
419}
420
421void
422gtk_shadertoy_set_image_shader (GtkShadertoy *shadertoy,
423 const char *shader)
424{
425 GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (self: shadertoy);
426
427 g_free (mem: priv->image_shader);
428 priv->image_shader = g_strdup (str: shader);
429
430 /* Don't override error we didn't set it ourselves */
431 if (priv->error_set)
432 {
433 gtk_gl_area_set_error (GTK_GL_AREA (shadertoy), NULL);
434 priv->error_set = FALSE;
435 }
436 priv->image_shader_dirty = TRUE;
437}
438
439static void
440gtk_shadertoy_realize (GtkWidget *widget)
441{
442 GtkGLArea *glarea = GTK_GL_AREA (widget);
443 GtkShadertoy *shadertoy = GTK_SHADERTOY (widget);
444 GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (self: shadertoy);
445
446 /* Draw two triangles across whole screen */
447 const GLfloat vertex_data[] = {
448 -1.0f, -1.0f, 0.f, 1.f,
449 -1.0f, 1.0f, 0.f, 1.f,
450 1.0f, 1.0f, 0.f, 1.f,
451
452 -1.0f, -1.0f, 0.f, 1.f,
453 1.0f, 1.0f, 0.f, 1.f,
454 1.0f, -1.0f, 0.f, 1.f,
455 };
456
457 GTK_WIDGET_CLASS (gtk_shadertoy_parent_class)->realize (widget);
458
459 gtk_gl_area_make_current (area: glarea);
460 if (gtk_gl_area_get_error (area: glarea) != NULL)
461 return;
462
463 glGenVertexArrays (1, &priv->vao);
464 glBindVertexArray (priv->vao);
465
466 glGenBuffers (1, &priv->buffer);
467 glBindBuffer (GL_ARRAY_BUFFER, priv->buffer);
468 glBufferData (GL_ARRAY_BUFFER, sizeof (vertex_data), vertex_data, GL_STATIC_DRAW);
469 glBindBuffer (GL_ARRAY_BUFFER, 0);
470
471 gtk_shadertoy_realize_shader (shadertoy);
472}
473
474static void
475gtk_shadertoy_unrealize (GtkWidget *widget)
476{
477 GtkGLArea *glarea = GTK_GL_AREA (widget);
478 GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (self: (GtkShadertoy *) widget);
479
480 gtk_gl_area_make_current (area: glarea);
481 if (gtk_gl_area_get_error (area: glarea) == NULL)
482 {
483 if (priv->buffer != 0)
484 glDeleteBuffers (1, &priv->buffer);
485
486 if (priv->vao != 0)
487 glDeleteVertexArrays (1, &priv->vao);
488
489 if (priv->program != 0)
490 glDeleteProgram (priv->program);
491 }
492
493 GTK_WIDGET_CLASS (gtk_shadertoy_parent_class)->unrealize (widget);
494}
495
496static gboolean
497gtk_shadertoy_tick (GtkWidget *widget,
498 GdkFrameClock *frame_clock,
499 gpointer user_data)
500{
501 GtkShadertoy *shadertoy = GTK_SHADERTOY (widget);
502 GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (self: shadertoy);
503 gint64 frame_time;
504 gint64 frame;
505 float previous_time;
506
507 frame = gdk_frame_clock_get_frame_counter (frame_clock);
508 frame_time = gdk_frame_clock_get_frame_time (frame_clock);
509
510 if (priv->first_frame_time == 0)
511 {
512 priv->first_frame_time = frame_time;
513 priv->first_frame = frame;
514 previous_time = 0;
515 }
516 else
517 previous_time = priv->time;
518
519 priv->time = (frame_time - priv->first_frame_time) / 1000000.0f;
520 priv->frame = frame - priv->first_frame;
521 priv->timedelta = priv->time - previous_time;
522
523 gtk_widget_queue_draw (widget);
524
525 return G_SOURCE_CONTINUE;
526}
527

source code of gtk/demos/gtk-demo/gtkshadertoy.c