1 | /* OpenGL/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 | |
10 | static GtkWidget *demo_window = NULL; |
11 | |
12 | /* the GtkGLArea widget */ |
13 | static GtkWidget *gl_area = NULL; |
14 | |
15 | enum { |
16 | X_AXIS, |
17 | Y_AXIS, |
18 | Z_AXIS, |
19 | |
20 | N_AXIS |
21 | }; |
22 | |
23 | /* Rotation angles on each axis */ |
24 | static float rotation_angles[N_AXIS] = { 0.0 }; |
25 | |
26 | /* The object we are drawing */ |
27 | static 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 */ |
34 | static void |
35 | init_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 */ |
58 | static GLuint |
59 | create_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 (n_bytes: 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 (mem: 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 */ |
95 | static void |
96 | init_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 (path: vertex_path, lookup_flags: 0, NULL); |
108 | vertex = create_shader (GL_VERTEX_SHADER, src: g_bytes_get_data (bytes: source, NULL)); |
109 | g_bytes_unref (bytes: source); |
110 | |
111 | if (vertex == 0) |
112 | { |
113 | *program_out = 0; |
114 | return; |
115 | } |
116 | |
117 | source = g_resources_lookup_data (path: fragment_path, lookup_flags: 0, NULL); |
118 | fragment = create_shader (GL_FRAGMENT_SHADER, src: g_bytes_get_data (bytes: source, NULL)); |
119 | g_bytes_unref (bytes: 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 (n_bytes: log_len + 1); |
143 | glGetProgramInfoLog (program, log_len, NULL, buffer); |
144 | |
145 | g_warning ("Linking failure:\n%s" , buffer); |
146 | |
147 | g_free (mem: 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 | |
161 | out: |
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 | |
172 | static void |
173 | compute_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: x), s1 = sinf (x: x); |
182 | float c2 = cosf (x: y), s2 = sinf (x: y); |
183 | float c3 = cosf (x: z), s3 = sinf (x: 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 | |
215 | static GLuint position_buffer; |
216 | static GLuint program; |
217 | static GLuint mvp_location; |
218 | |
219 | /* We need to set up our state when we realize the GtkGLArea widget */ |
220 | static void |
221 | realize (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 (NULL, buffer_out: &position_buffer); |
245 | init_shaders (vertex_path, fragment_path, program_out: &program, mvp_out: &mvp_location); |
246 | } |
247 | |
248 | /* We should tear down the state when unrealizing */ |
249 | static void |
250 | unrealize (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 | |
261 | static void |
262 | draw_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 (res: mvp, |
270 | phi: rotation_angles[X_AXIS], |
271 | theta: rotation_angles[Y_AXIS], |
272 | psi: 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 | |
294 | static gboolean |
295 | render (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 | |
314 | static void |
315 | on_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 (widget: gl_area); |
327 | } |
328 | |
329 | static GtkWidget * |
330 | create_axis_slider (int axis) |
331 | { |
332 | GtkWidget *box, *label, *slider; |
333 | GtkAdjustment *adj; |
334 | const char *text; |
335 | |
336 | box = gtk_box_new (orientation: GTK_ORIENTATION_HORIZONTAL, spacing: 0); |
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 (str: text); |
357 | gtk_box_append (GTK_BOX (box), child: label); |
358 | gtk_widget_show (widget: label); |
359 | |
360 | 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); |
361 | g_signal_connect (adj, "value-changed" , |
362 | G_CALLBACK (on_axis_value_change), |
363 | GINT_TO_POINTER (axis)); |
364 | slider = gtk_scale_new (orientation: GTK_ORIENTATION_HORIZONTAL, adjustment: adj); |
365 | gtk_box_append (GTK_BOX (box), child: slider); |
366 | gtk_widget_set_hexpand (widget: slider, TRUE); |
367 | gtk_widget_show (widget: slider); |
368 | |
369 | gtk_widget_show (widget: box); |
370 | |
371 | return box; |
372 | } |
373 | |
374 | static void |
375 | close_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 | |
386 | static GtkWidget * |
387 | create_glarea_window (GtkWidget *do_widget) |
388 | { |
389 | GtkWidget *window, *box, *button, *controls; |
390 | int i; |
391 | |
392 | window = gtk_window_new (); |
393 | gtk_window_set_display (GTK_WINDOW (window), display: gtk_widget_get_display (widget: do_widget)); |
394 | gtk_window_set_title (GTK_WINDOW (window), title: "OpenGL Area" ); |
395 | gtk_window_set_default_size (GTK_WINDOW (window), width: 400, height: 600); |
396 | g_signal_connect (window, "destroy" , G_CALLBACK (close_window), NULL); |
397 | |
398 | box = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, FALSE); |
399 | gtk_widget_set_margin_start (widget: box, margin: 12); |
400 | gtk_widget_set_margin_end (widget: box, margin: 12); |
401 | gtk_widget_set_margin_top (widget: box, margin: 12); |
402 | gtk_widget_set_margin_bottom (widget: box, margin: 12); |
403 | gtk_box_set_spacing (GTK_BOX (box), spacing: 6); |
404 | gtk_window_set_child (GTK_WINDOW (window), child: box); |
405 | |
406 | gl_area = gtk_gl_area_new (); |
407 | gtk_widget_set_hexpand (widget: gl_area, TRUE); |
408 | gtk_widget_set_vexpand (widget: gl_area, TRUE); |
409 | gtk_widget_set_size_request (widget: gl_area, width: 100, height: 200); |
410 | gtk_box_append (GTK_BOX (box), child: gl_area); |
411 | |
412 | /* We need to initialize and free GL resources, so we use |
413 | * the realize and unrealize signals on the widget |
414 | */ |
415 | g_signal_connect (gl_area, "realize" , G_CALLBACK (realize), NULL); |
416 | g_signal_connect (gl_area, "unrealize" , G_CALLBACK (unrealize), NULL); |
417 | |
418 | /* The main "draw" call for GtkGLArea */ |
419 | g_signal_connect (gl_area, "render" , G_CALLBACK (render), NULL); |
420 | |
421 | controls = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, FALSE); |
422 | gtk_box_append (GTK_BOX (box), child: controls); |
423 | gtk_widget_set_hexpand (widget: controls, TRUE); |
424 | |
425 | for (i = 0; i < N_AXIS; i++) |
426 | gtk_box_append (GTK_BOX (controls), child: create_axis_slider (axis: i)); |
427 | |
428 | button = gtk_button_new_with_label (label: "Quit" ); |
429 | gtk_widget_set_hexpand (widget: button, TRUE); |
430 | gtk_box_append (GTK_BOX (box), child: button); |
431 | g_signal_connect_swapped (button, "clicked" , G_CALLBACK (gtk_window_destroy), window); |
432 | |
433 | return window; |
434 | } |
435 | |
436 | GtkWidget* |
437 | do_glarea (GtkWidget *do_widget) |
438 | { |
439 | if (demo_window == NULL) |
440 | demo_window = create_glarea_window (do_widget); |
441 | |
442 | if (!gtk_widget_get_visible (widget: demo_window)) |
443 | gtk_widget_show (widget: demo_window); |
444 | else |
445 | gtk_window_destroy (GTK_WINDOW (demo_window)); |
446 | |
447 | return demo_window; |
448 | } |
449 | |