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