1 | #include <math.h> |
2 | #include <stdlib.h> |
3 | #include <gtk/gtk.h> |
4 | |
5 | #include <epoxy/gl.h> |
6 | |
7 | enum { |
8 | X_AXIS, |
9 | Y_AXIS, |
10 | Z_AXIS, |
11 | |
12 | N_AXIS |
13 | }; |
14 | |
15 | static float rotation_angles[N_AXIS] = { 0.0 }; |
16 | |
17 | static GtkWidget *gl_area; |
18 | |
19 | static 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 | |
25 | static void |
26 | init_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 | |
48 | static GLuint |
49 | create_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 | |
83 | static 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 | |
90 | static 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 | |
97 | static 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 | |
106 | static 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 | |
115 | static 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 | |
124 | static 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 | |
132 | static void |
133 | init_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 | |
190 | out: |
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 | |
201 | static void |
202 | compute_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 | |
244 | static GLuint position_buffer; |
245 | static GLuint program; |
246 | static GLuint mvp_location; |
247 | |
248 | static void |
249 | realize (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 | |
283 | static void |
284 | unrealize (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 | |
295 | static void |
296 | draw_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 | |
321 | static gboolean |
322 | render (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 | |
335 | static void |
336 | on_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 | |
349 | static GtkWidget * |
350 | create_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 | |
390 | static void |
391 | quit_cb (GtkWidget *widget, |
392 | gpointer data) |
393 | { |
394 | gboolean *done = data; |
395 | |
396 | *done = TRUE; |
397 | |
398 | g_main_context_wakeup (NULL); |
399 | } |
400 | |
401 | int |
402 | main (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 | |