1#include <math.h>
2#include <stdlib.h>
3#include <gtk/gtk.h>
4
5#include <epoxy/gl.h>
6
7enum {
8 X_AXIS,
9 Y_AXIS,
10 Z_AXIS,
11
12 N_AXIS
13};
14
15static float rotation_angles[N_AXIS] = { 0.0 };
16
17static GtkWidget *gl_area;
18
19static const GLfloat vertex_data[] = {
20 0.f, 0.5f, 0.f, 1.f,
21 0.5f, -0.366f, 0.f, 1.f,
22 -0.5f, -0.366f, 0.f, 1.f,
23};
24
25static void
26init_buffers (GLuint *vao_out,
27 GLuint *buffer_out)
28{
29 GLuint vao, buffer;
30
31 /* we only use one VAO, so we always keep it bound */
32 glGenVertexArrays (1, &vao);
33 glBindVertexArray (vao);
34
35 glGenBuffers (1, &buffer);
36
37 glBindBuffer (GL_ARRAY_BUFFER, buffer);
38 glBufferData (GL_ARRAY_BUFFER, sizeof (vertex_data), vertex_data, GL_STATIC_DRAW);
39 glBindBuffer (GL_ARRAY_BUFFER, 0);
40
41 if (vao_out != NULL)
42 *vao_out = vao;
43
44 if (buffer_out != NULL)
45 *buffer_out = buffer;
46}
47
48static GLuint
49create_shader (int type, const char *src)
50{
51 GLuint shader;
52 int status;
53
54 shader = glCreateShader (type);
55 glShaderSource (shader, 1, &src, NULL);
56 glCompileShader (shader);
57
58 glGetShaderiv (shader, GL_COMPILE_STATUS, &status);
59 if (status == GL_FALSE)
60 {
61 int log_len;
62 char *buffer;
63
64 glGetShaderiv (shader, GL_INFO_LOG_LENGTH, &log_len);
65
66 buffer = g_malloc (n_bytes: log_len + 1);
67 glGetShaderInfoLog (shader, log_len, NULL, buffer);
68
69 g_warning ("Compile failure in %s shader:\n%s",
70 type == GL_VERTEX_SHADER ? "vertex" : "fragment",
71 buffer);
72
73 g_free (mem: buffer);
74
75 glDeleteShader (shader);
76
77 return 0;
78 }
79
80 return shader;
81}
82
83static const char *vertex_shader_code_gles =
84"attribute vec4 position;\n" \
85"uniform mat4 mvp;\n" \
86"void main() {\n" \
87" gl_Position = mvp * position;\n" \
88"}";
89
90static const char *fragment_shader_code_gles =
91"precision mediump float;\n" \
92"void main() {\n" \
93" float lerpVal = gl_FragCoord.y / 400.0;\n" \
94" gl_FragColor = mix(vec4(1.0, 0.85, 0.35, 1.0), vec4(0.2, 0.2, 0.2, 1.0), lerpVal);\n" \
95"}";
96
97static const char *vertex_shader_code_330 =
98"#version 330\n" \
99"\n" \
100"layout(location = 0) in vec4 position;\n" \
101"uniform mat4 mvp;\n"
102"void main() {\n" \
103" gl_Position = mvp * position;\n" \
104"}";
105
106static const char *vertex_shader_code_legacy =
107"#version 130\n" \
108"\n" \
109"attribute vec4 position;\n" \
110"uniform mat4 mvp;\n" \
111"void main() {\n" \
112" gl_Position = mvp * position;\n" \
113"}";
114
115static const char *fragment_shader_code_330 =
116"#version 330\n" \
117"\n" \
118"out vec4 outputColor;\n" \
119"void main() {\n" \
120" float lerpVal = gl_FragCoord.y / 400.0f;\n" \
121" outputColor = mix(vec4(1.0f, 0.85f, 0.35f, 1.0f), vec4(0.2f, 0.2f, 0.2f, 1.0f), lerpVal);\n" \
122"}";
123
124static const char *fragment_shader_code_legacy =
125"#version 130\n" \
126"\n" \
127"void main() {\n" \
128" float lerpVal = gl_FragCoord.y / 400.0f;\n" \
129" gl_FragColor = mix(vec4(1.0f, 0.85f, 0.35f, 1.0f), vec4(0.2f, 0.2, 0.2f, 1.0f), lerpVal);\n" \
130"}";
131
132static void
133init_shaders (const char *vertex_shader_code,
134 const char *fragment_shader_code,
135 GLuint *program_out,
136 GLuint *mvp_out)
137{
138 GLuint vertex, fragment;
139 GLuint program = 0;
140 GLuint mvp = 0;
141 int status;
142
143 vertex = create_shader (GL_VERTEX_SHADER, src: vertex_shader_code);
144 if (vertex == 0)
145 {
146 *program_out = 0;
147 return;
148 }
149
150 fragment = create_shader (GL_FRAGMENT_SHADER, src: fragment_shader_code);
151 if (fragment == 0)
152 {
153 glDeleteShader (vertex);
154 *program_out = 0;
155 return;
156 }
157
158 program = glCreateProgram ();
159 glAttachShader (program, vertex);
160 glAttachShader (program, fragment);
161
162 glLinkProgram (program);
163
164 glGetProgramiv (program, GL_LINK_STATUS, &status);
165 if (status == GL_FALSE)
166 {
167 int log_len;
168 char *buffer;
169
170 glGetProgramiv (program, GL_INFO_LOG_LENGTH, &log_len);
171
172 buffer = g_malloc (n_bytes: log_len + 1);
173 glGetProgramInfoLog (program, log_len, NULL, buffer);
174
175 g_warning ("Linking failure:\n%s", buffer);
176
177 g_free (mem: buffer);
178
179 glDeleteProgram (program);
180 program = 0;
181
182 goto out;
183 }
184
185 mvp = glGetUniformLocation (program, "mvp");
186
187 glDetachShader (program, vertex);
188 glDetachShader (program, fragment);
189
190out:
191 glDeleteShader (vertex);
192 glDeleteShader (fragment);
193
194 if (program_out != NULL)
195 *program_out = program;
196
197 if (mvp_out != NULL)
198 *mvp_out = mvp;
199}
200
201static void
202compute_mvp (float *res,
203 float phi,
204 float theta,
205 float psi)
206{
207 float x = phi * (G_PI / 180.f);
208 float y = theta * (G_PI / 180.f);
209 float z = psi * (G_PI / 180.f);
210 float c1 = cosf (x: x), s1 = sinf (x: x);
211 float c2 = cosf (x: y), s2 = sinf (x: y);
212 float c3 = cosf (x: z), s3 = sinf (x: z);
213 float c3c2 = c3 * c2;
214 float s3c1 = s3 * c1;
215 float c3s2s1 = c3 * s2 * s1;
216 float s3s1 = s3 * s1;
217 float c3s2c1 = c3 * s2 * c1;
218 float s3c2 = s3 * c2;
219 float c3c1 = c3 * c1;
220 float s3s2s1 = s3 * s2 * s1;
221 float c3s1 = c3 * s1;
222 float s3s2c1 = s3 * s2 * c1;
223 float c2s1 = c2 * s1;
224 float c2c1 = c2 * c1;
225
226 /* initialize to the identity matrix */
227 res[0] = 1.f; res[4] = 0.f; res[8] = 0.f; res[12] = 0.f;
228 res[1] = 0.f; res[5] = 1.f; res[9] = 0.f; res[13] = 0.f;
229 res[2] = 0.f; res[6] = 0.f; res[10] = 1.f; res[14] = 0.f;
230 res[3] = 0.f; res[7] = 0.f; res[11] = 0.f; res[15] = 1.f;
231
232 /* apply all three rotations using the three matrices:
233 *
234 * ⎡ c3 s3 0 ⎤ ⎡ c2 0 -s2 ⎤ ⎡ 1 0 0 ⎤
235 * ⎢ -s3 c3 0 ⎥ ⎢ 0 1 0 ⎥ ⎢ 0 c1 s1 ⎥
236 * ⎣ 0 0 1 ⎦ ⎣ s2 0 c2 ⎦ ⎣ 0 -s1 c1 ⎦
237 */
238 res[0] = c3c2; res[4] = s3c1 + c3s2s1; res[8] = s3s1 - c3s2c1; res[12] = 0.f;
239 res[1] = -s3c2; res[5] = c3c1 - s3s2s1; res[9] = c3s1 + s3s2c1; res[13] = 0.f;
240 res[2] = s2; res[6] = -c2s1; res[10] = c2c1; res[14] = 0.f;
241 res[3] = 0.f; res[7] = 0.f; res[11] = 0.f; res[15] = 1.f;
242}
243
244static GLuint position_buffer;
245static GLuint program;
246static GLuint mvp_location;
247
248static void
249realize (GtkWidget *widget)
250{
251 const char *fragment, *vertex;
252 GdkGLContext *context;
253
254 gtk_gl_area_make_current (GTK_GL_AREA (widget));
255
256 if (gtk_gl_area_get_error (GTK_GL_AREA (widget)) != NULL)
257 return;
258
259 context = gtk_gl_area_get_context (GTK_GL_AREA (widget));
260 if (gdk_gl_context_get_use_es (context))
261 {
262 vertex = vertex_shader_code_gles;
263 fragment = fragment_shader_code_gles;
264 }
265 else
266 {
267 if (!gdk_gl_context_is_legacy (context))
268 {
269 vertex = vertex_shader_code_330;
270 fragment = fragment_shader_code_330;
271 }
272 else
273 {
274 vertex = vertex_shader_code_legacy;
275 fragment = fragment_shader_code_legacy;
276 }
277 }
278
279 init_buffers (vao_out: &position_buffer, NULL);
280 init_shaders (vertex_shader_code: vertex, fragment_shader_code: fragment, program_out: &program, mvp_out: &mvp_location);
281}
282
283static void
284unrealize (GtkWidget *widget)
285{
286 gtk_gl_area_make_current (GTK_GL_AREA (widget));
287
288 if (gtk_gl_area_get_error (GTK_GL_AREA (widget)) != NULL)
289 return;
290
291 glDeleteBuffers (1, &position_buffer);
292 glDeleteProgram (program);
293}
294
295static void
296draw_triangle (void)
297{
298 float mvp[16];
299
300 g_assert (position_buffer != 0);
301 g_assert (program != 0);
302
303 compute_mvp (res: mvp,
304 phi: rotation_angles[X_AXIS],
305 theta: rotation_angles[Y_AXIS],
306 psi: rotation_angles[Z_AXIS]);
307
308 glUseProgram (program);
309 glUniformMatrix4fv (mvp_location, 1, GL_FALSE, &mvp[0]);
310
311 glBindBuffer (GL_ARRAY_BUFFER, position_buffer);
312 glEnableVertexAttribArray (0);
313 glVertexAttribPointer (0, 4, GL_FLOAT, GL_FALSE, 0, 0);
314
315 glDrawArrays (GL_TRIANGLES, 0, 3);
316
317 glDisableVertexAttribArray (0);
318 glUseProgram (0);
319}
320
321static gboolean
322render (GtkGLArea *area,
323 GdkGLContext *context)
324{
325 glClearColor (0.5, 0.5, 0.5, 1.0);
326 glClear (GL_COLOR_BUFFER_BIT);
327
328 draw_triangle ();
329
330 glFlush ();
331
332 return TRUE;
333}
334
335static void
336on_axis_value_change (GtkAdjustment *adjustment,
337 gpointer data)
338{
339 int axis = GPOINTER_TO_INT (data);
340
341 if (axis < 0 || axis >= N_AXIS)
342 return;
343
344 rotation_angles[axis] = gtk_adjustment_get_value (adjustment);
345
346 gtk_widget_queue_draw (widget: gl_area);
347}
348
349static GtkWidget *
350create_axis_slider (int axis)
351{
352 GtkWidget *box, *label, *slider;
353 GtkAdjustment *adj;
354 const char *text;
355
356 box = gtk_box_new (orientation: GTK_ORIENTATION_HORIZONTAL, FALSE);
357
358 switch (axis)
359 {
360 case X_AXIS:
361 text = "X axis";
362 break;
363
364 case Y_AXIS:
365 text = "Y axis";
366 break;
367
368 case Z_AXIS:
369 text = "Z axis";
370 break;
371
372 default:
373 g_assert_not_reached ();
374 }
375
376 label = gtk_label_new (str: text);
377 gtk_box_append (GTK_BOX (box), child: label);
378
379 adj = gtk_adjustment_new (value: 0.0, lower: 0.0, upper: 360.0, step_increment: 1.0, page_increment: 12.0, page_size: 0.0);
380 g_signal_connect (adj, "value-changed",
381 G_CALLBACK (on_axis_value_change),
382 GINT_TO_POINTER (axis));
383 slider = gtk_scale_new (orientation: GTK_ORIENTATION_HORIZONTAL, adjustment: adj);
384 gtk_box_append (GTK_BOX (box), child: slider);
385 gtk_widget_set_hexpand (widget: slider, TRUE);
386
387 return box;
388}
389
390static void
391quit_cb (GtkWidget *widget,
392 gpointer data)
393{
394 gboolean *done = data;
395
396 *done = TRUE;
397
398 g_main_context_wakeup (NULL);
399}
400
401int
402main (int argc, char *argv[])
403{
404 GtkWidget *window, *box, *button, *controls;
405 int i;
406 gboolean done = FALSE;
407
408 gtk_init ();
409
410 /* create a new pixel format; we use this to configure the
411 * GL context, and to check for features
412 */
413
414 window = gtk_window_new ();
415 gtk_window_set_title (GTK_WINDOW (window), title: "GtkGLArea - Triangle");
416 gtk_window_set_default_size (GTK_WINDOW (window), width: 400, height: 600);
417 g_signal_connect (window, "destroy", G_CALLBACK (quit_cb), &done);
418
419 box = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, FALSE);
420 gtk_widget_set_margin_start (widget: box, margin: 12);
421 gtk_widget_set_margin_end (widget: box, margin: 12);
422 gtk_widget_set_margin_top (widget: box, margin: 12);
423 gtk_widget_set_margin_bottom (widget: box, margin: 12);
424 gtk_box_set_spacing (GTK_BOX (box), spacing: 6);
425 gtk_window_set_child (GTK_WINDOW (window), child: box);
426
427 gl_area = gtk_gl_area_new ();
428 gtk_widget_set_hexpand (widget: gl_area, TRUE);
429 gtk_widget_set_vexpand (widget: gl_area, TRUE);
430 gtk_box_append (GTK_BOX (box), child: gl_area);
431 g_signal_connect (gl_area, "realize", G_CALLBACK (realize), NULL);
432 g_signal_connect (gl_area, "unrealize", G_CALLBACK (unrealize), NULL);
433 g_signal_connect (gl_area, "render", G_CALLBACK (render), NULL);
434
435 controls = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, FALSE);
436 gtk_box_append (GTK_BOX (box), child: controls);
437 gtk_widget_set_hexpand (widget: controls, TRUE);
438
439 for (i = 0; i < N_AXIS; i++)
440 gtk_box_append (GTK_BOX (controls), child: create_axis_slider (axis: i));
441
442 button = gtk_button_new_with_label (label: "Quit");
443 gtk_widget_set_hexpand (widget: button, TRUE);
444 gtk_box_append (GTK_BOX (box), child: button);
445 g_signal_connect_swapped (button, "clicked", G_CALLBACK (gtk_window_destroy), window);
446
447 gtk_widget_show (widget: window);
448
449 while (!done)
450 g_main_context_iteration (NULL, TRUE);
451
452 return EXIT_SUCCESS;
453}
454

source code of gtk/tests/testglarea.c