1 | #include <math.h> |
2 | #include <stdlib.h> |
3 | #include <string.h> |
4 | #include <epoxy/gl.h> |
5 | |
6 | #include "gtkshadertoy.h" |
7 | |
8 | const 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 | |
24 | const 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 | |
41 | const 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 |
63 | const char *fragment_suffix = |
64 | " void main() {\n" |
65 | " vec4 c;\n" |
66 | " mainImage(c, fragCoord);\n" |
67 | " vFragColor = c;\n" |
68 | " }\n" ; |
69 | |
70 | typedef 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 | |
103 | G_DEFINE_TYPE_WITH_PRIVATE (GtkShadertoy, gtk_shadertoy, GTK_TYPE_GL_AREA) |
104 | |
105 | static gboolean gtk_shadertoy_render (GtkGLArea *area, |
106 | GdkGLContext *context); |
107 | static void gtk_shadertoy_reshape (GtkGLArea *area, |
108 | int width, |
109 | int height); |
110 | static void gtk_shadertoy_realize (GtkWidget *widget); |
111 | static void gtk_shadertoy_unrealize (GtkWidget *widget); |
112 | static gboolean gtk_shadertoy_tick (GtkWidget *widget, |
113 | GdkFrameClock *frame_clock, |
114 | gpointer user_data); |
115 | |
116 | GtkWidget * |
117 | gtk_shadertoy_new (void) |
118 | { |
119 | return g_object_new (object_type: gtk_shadertoy_get_type (), NULL); |
120 | } |
121 | |
122 | static void |
123 | drag_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 | |
139 | static void |
140 | drag_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 | |
164 | static void |
165 | drag_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 | |
177 | static void |
178 | gtk_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 | |
193 | static void |
194 | gtk_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 | |
205 | static void |
206 | gtk_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 */ |
218 | static void |
219 | gtk_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 | |
231 | static GLuint |
232 | create_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 | |
269 | static gboolean |
270 | init_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 | |
333 | out: |
334 | /* These are now owned by the program and can be deleted */ |
335 | glDeleteShader (vertex); |
336 | glDeleteShader (fragment); |
337 | |
338 | return res; |
339 | } |
340 | |
341 | static void |
342 | gtk_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 | |
364 | static gboolean |
365 | gtk_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 | |
413 | const char * |
414 | gtk_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 | |
421 | void |
422 | gtk_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 | |
439 | static void |
440 | gtk_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 | |
474 | static void |
475 | gtk_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 | |
496 | static gboolean |
497 | gtk_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 | |