#include <math.h> #include <stdlib.h> #include <gtk/gtk.h> #include <epoxy/gl.h> enum { X_AXIS, Y_AXIS, Z_AXIS, N_AXIS }; static float rotation_angles[N_AXIS] = { 0.0 }; static GtkWidget *gl_area; static const GLfloat vertex_data[] = { 0.f, 0.5f, 0.f, 1.f, 0.5f, -0.366f, 0.f, 1.f, -0.5f, -0.366f, 0.f, 1.f, }; static void init_buffers (GLuint *vao_out, GLuint *buffer_out) { GLuint vao, buffer; /* we only use one VAO, so we always keep it bound */ glGenVertexArrays (1, &vao); glBindVertexArray (vao); glGenBuffers (1, &buffer); glBindBuffer (GL_ARRAY_BUFFER, buffer); glBufferData (GL_ARRAY_BUFFER, sizeof (vertex_data), vertex_data, GL_STATIC_DRAW); glBindBuffer (GL_ARRAY_BUFFER, 0); if (vao_out != NULL) *vao_out = vao; if (buffer_out != NULL) *buffer_out = buffer; } static GLuint create_shader (int type, const char *src) { GLuint shader; int status; shader = glCreateShader (type); glShaderSource (shader, 1, &src, NULL); glCompileShader (shader); glGetShaderiv (shader, GL_COMPILE_STATUS, &status); if (status == GL_FALSE) { int log_len; char *buffer; glGetShaderiv (shader, GL_INFO_LOG_LENGTH, &log_len); buffer = g_malloc (log_len + 1); glGetShaderInfoLog (shader, log_len, NULL, buffer); g_warning ("Compile failure in %s shader:\n%s", type == GL_VERTEX_SHADER ? "vertex" : "fragment", buffer); g_free (buffer); glDeleteShader (shader); return 0; } return shader; } static const char *vertex_shader_code_gles = "attribute vec4 position;\n" \ "uniform mat4 mvp;\n" \ "void main() {\n" \ " gl_Position = mvp * position;\n" \ "}"; static const char *fragment_shader_code_gles = "precision mediump float;\n" \ "void main() {\n" \ " float lerpVal = gl_FragCoord.y / 400.0;\n" \ " gl_FragColor = mix(vec4(1.0, 0.85, 0.35, 1.0), vec4(0.2, 0.2, 0.2, 1.0), lerpVal);\n" \ "}"; static const char *vertex_shader_code_330 = "#version 330\n" \ "\n" \ "layout(location = 0) in vec4 position;\n" \ "uniform mat4 mvp;\n" "void main() {\n" \ " gl_Position = mvp * position;\n" \ "}"; static const char *vertex_shader_code_legacy = "#version 130\n" \ "\n" \ "attribute vec4 position;\n" \ "uniform mat4 mvp;\n" \ "void main() {\n" \ " gl_Position = mvp * position;\n" \ "}"; static const char *fragment_shader_code_330 = "#version 330\n" \ "\n" \ "out vec4 outputColor;\n" \ "void main() {\n" \ " float lerpVal = gl_FragCoord.y / 400.0f;\n" \ " outputColor = mix(vec4(1.0f, 0.85f, 0.35f, 1.0f), vec4(0.2f, 0.2f, 0.2f, 1.0f), lerpVal);\n" \ "}"; static const char *fragment_shader_code_legacy = "#version 130\n" \ "\n" \ "void main() {\n" \ " float lerpVal = gl_FragCoord.y / 400.0f;\n" \ " gl_FragColor = mix(vec4(1.0f, 0.85f, 0.35f, 1.0f), vec4(0.2f, 0.2, 0.2f, 1.0f), lerpVal);\n" \ "}"; static void init_shaders (const char *vertex_shader_code, const char *fragment_shader_code, GLuint *program_out, GLuint *mvp_out) { GLuint vertex, fragment; GLuint program = 0; GLuint mvp = 0; int status; vertex = create_shader (GL_VERTEX_SHADER, vertex_shader_code); if (vertex == 0) { *program_out = 0; return; } fragment = create_shader (GL_FRAGMENT_SHADER, fragment_shader_code); if (fragment == 0) { glDeleteShader (vertex); *program_out = 0; return; } program = glCreateProgram (); glAttachShader (program, vertex); glAttachShader (program, fragment); glLinkProgram (program); glGetProgramiv (program, GL_LINK_STATUS, &status); if (status == GL_FALSE) { int log_len; char *buffer; glGetProgramiv (program, GL_INFO_LOG_LENGTH, &log_len); buffer = g_malloc (log_len + 1); glGetProgramInfoLog (program, log_len, NULL, buffer); g_warning ("Linking failure:\n%s", buffer); g_free (buffer); glDeleteProgram (program); program = 0; goto out; } mvp = glGetUniformLocation (program, "mvp"); glDetachShader (program, vertex); glDetachShader (program, fragment); out: glDeleteShader (vertex); glDeleteShader (fragment); if (program_out != NULL) *program_out = program; if (mvp_out != NULL) *mvp_out = mvp; } static void compute_mvp (float *res, float phi, float theta, float psi) { float x = phi * (G_PI / 180.f); float y = theta * (G_PI / 180.f); float z = psi * (G_PI / 180.f); float c1 = cosf (x), s1 = sinf (x); float c2 = cosf (y), s2 = sinf (y); float c3 = cosf (z), s3 = sinf (z); float c3c2 = c3 * c2; float s3c1 = s3 * c1; float c3s2s1 = c3 * s2 * s1; float s3s1 = s3 * s1; float c3s2c1 = c3 * s2 * c1; float s3c2 = s3 * c2; float c3c1 = c3 * c1; float s3s2s1 = s3 * s2 * s1; float c3s1 = c3 * s1; float s3s2c1 = s3 * s2 * c1; float c2s1 = c2 * s1; float c2c1 = c2 * c1; /* initialize to the identity matrix */ res[0] = 1.f; res[4] = 0.f; res[8] = 0.f; res[12] = 0.f; res[1] = 0.f; res[5] = 1.f; res[9] = 0.f; res[13] = 0.f; res[2] = 0.f; res[6] = 0.f; res[10] = 1.f; res[14] = 0.f; res[3] = 0.f; res[7] = 0.f; res[11] = 0.f; res[15] = 1.f; /* apply all three rotations using the three matrices: * * ⎡ c3 s3 0 ⎤ ⎡ c2 0 -s2 ⎤ ⎡ 1 0 0 ⎤ * ⎢ -s3 c3 0 ⎥ ⎢ 0 1 0 ⎥ ⎢ 0 c1 s1 ⎥ * ⎣ 0 0 1 ⎦ ⎣ s2 0 c2 ⎦ ⎣ 0 -s1 c1 ⎦ */ res[0] = c3c2; res[4] = s3c1 + c3s2s1; res[8] = s3s1 - c3s2c1; res[12] = 0.f; res[1] = -s3c2; res[5] = c3c1 - s3s2s1; res[9] = c3s1 + s3s2c1; res[13] = 0.f; res[2] = s2; res[6] = -c2s1; res[10] = c2c1; res[14] = 0.f; res[3] = 0.f; res[7] = 0.f; res[11] = 0.f; res[15] = 1.f; } static GLuint position_buffer; static GLuint program; static GLuint mvp_location; static void realize (GtkWidget *widget) { const char *fragment, *vertex; GdkGLContext *context; gtk_gl_area_make_current (GTK_GL_AREA (widget)); if (gtk_gl_area_get_error (GTK_GL_AREA (widget)) != NULL) return; context = gtk_gl_area_get_context (GTK_GL_AREA (widget)); if (gdk_gl_context_get_use_es (context)) { vertex = vertex_shader_code_gles; fragment = fragment_shader_code_gles; } else { if (!gdk_gl_context_is_legacy (context)) { vertex = vertex_shader_code_330; fragment = fragment_shader_code_330; } else { vertex = vertex_shader_code_legacy; fragment = fragment_shader_code_legacy; } } init_buffers (&position_buffer, NULL); init_shaders (vertex, fragment, &program, &mvp_location); } static void unrealize (GtkWidget *widget) { gtk_gl_area_make_current (GTK_GL_AREA (widget)); if (gtk_gl_area_get_error (GTK_GL_AREA (widget)) != NULL) return; glDeleteBuffers (1, &position_buffer); glDeleteProgram (program); } static void draw_triangle (void) { float mvp[16]; g_assert (position_buffer != 0); g_assert (program != 0); compute_mvp (mvp, rotation_angles[X_AXIS], rotation_angles[Y_AXIS], rotation_angles[Z_AXIS]); glUseProgram (program); glUniformMatrix4fv (mvp_location, 1, GL_FALSE, &mvp[0]); glBindBuffer (GL_ARRAY_BUFFER, position_buffer); glEnableVertexAttribArray (0); glVertexAttribPointer (0, 4, GL_FLOAT, GL_FALSE, 0, 0); glDrawArrays (GL_TRIANGLES, 0, 3); glDisableVertexAttribArray (0); glUseProgram (0); } static gboolean render (GtkGLArea *area, GdkGLContext *context) { glClearColor (0.5, 0.5, 0.5, 1.0); glClear (GL_COLOR_BUFFER_BIT); draw_triangle (); glFlush (); return TRUE; } static void on_axis_value_change (GtkAdjustment *adjustment, gpointer data) { int axis = GPOINTER_TO_INT (data); if (axis < 0 || axis >= N_AXIS) return; rotation_angles[axis] = gtk_adjustment_get_value (adjustment); gtk_widget_queue_draw (gl_area); } static GtkWidget * create_axis_slider (int axis) { GtkWidget *box, *label, *slider; GtkAdjustment *adj; const char *text; box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE); switch (axis) { case X_AXIS: text = "X axis"; break; case Y_AXIS: text = "Y axis"; break; case Z_AXIS: text = "Z axis"; break; default: g_assert_not_reached (); } label = gtk_label_new (text); gtk_box_append (GTK_BOX (box), label); adj = gtk_adjustment_new (0.0, 0.0, 360.0, 1.0, 12.0, 0.0); g_signal_connect (adj, "value-changed", G_CALLBACK (on_axis_value_change), GINT_TO_POINTER (axis)); slider = gtk_scale_new (GTK_ORIENTATION_HORIZONTAL, adj); gtk_box_append (GTK_BOX (box), slider); gtk_widget_set_hexpand (slider, TRUE); return box; } static void quit_cb (GtkWidget *widget, gpointer data) { gboolean *done = data; *done = TRUE; g_main_context_wakeup (NULL); } int main (int argc, char *argv[]) { GtkWidget *window, *box, *button, *controls; int i; gboolean done = FALSE; gtk_init (); /* create a new pixel format; we use this to configure the * GL context, and to check for features */ window = gtk_window_new (); gtk_window_set_title (GTK_WINDOW (window), "GtkGLArea - Triangle"); gtk_window_set_default_size (GTK_WINDOW (window), 400, 600); g_signal_connect (window, "destroy", G_CALLBACK (quit_cb), &done); box = gtk_box_new (GTK_ORIENTATION_VERTICAL, FALSE); gtk_widget_set_margin_start (box, 12); gtk_widget_set_margin_end (box, 12); gtk_widget_set_margin_top (box, 12); gtk_widget_set_margin_bottom (box, 12); gtk_box_set_spacing (GTK_BOX (box), 6); gtk_window_set_child (GTK_WINDOW (window), box); gl_area = gtk_gl_area_new (); gtk_widget_set_hexpand (gl_area, TRUE); gtk_widget_set_vexpand (gl_area, TRUE); gtk_box_append (GTK_BOX (box), gl_area); g_signal_connect (gl_area, "realize", G_CALLBACK (realize), NULL); g_signal_connect (gl_area, "unrealize", G_CALLBACK (unrealize), NULL); g_signal_connect (gl_area, "render", G_CALLBACK (render), NULL); controls = gtk_box_new (GTK_ORIENTATION_VERTICAL, FALSE); gtk_box_append (GTK_BOX (box), controls); gtk_widget_set_hexpand (controls, TRUE); for (i = 0; i < N_AXIS; i++) gtk_box_append (GTK_BOX (controls), create_axis_slider (i)); button = gtk_button_new_with_label ("Quit"); gtk_widget_set_hexpand (button, TRUE); gtk_box_append (GTK_BOX (box), button); g_signal_connect_swapped (button, "clicked", G_CALLBACK (gtk_window_destroy), window); gtk_widget_show (window); while (!done) g_main_context_iteration (NULL, TRUE); return EXIT_SUCCESS; }