forked from AuroraMiddleware/gtk
721134b008
We should not call OpenGL API if GtkGLArea is in an error state. https://bugzilla.gnome.org/show_bug.cgi?id=746746
427 lines
10 KiB
C
427 lines
10 KiB
C
/* OpenGL Area
|
|
*
|
|
* GtkGLArea is a widget that allows custom drawing using OpenGL calls.
|
|
*/
|
|
|
|
#include <math.h>
|
|
#include <gtk/gtk.h>
|
|
#include <epoxy/gl.h>
|
|
|
|
static GtkWidget *demo_window = NULL;
|
|
|
|
/* the GtkGLArea widget */
|
|
static GtkWidget *gl_area = NULL;
|
|
|
|
enum {
|
|
X_AXIS,
|
|
Y_AXIS,
|
|
Z_AXIS,
|
|
|
|
N_AXIS
|
|
};
|
|
|
|
/* Rotation angles on each axis */
|
|
static float rotation_angles[N_AXIS] = { 0.0 };
|
|
|
|
/* The object we are drawing */
|
|
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,
|
|
};
|
|
|
|
/* Initialize the GL buffers */
|
|
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);
|
|
|
|
/* This is the buffer that holds the vertices */
|
|
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;
|
|
}
|
|
|
|
/* Create and compile a shader */
|
|
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\n",
|
|
type == GL_VERTEX_SHADER ? "vertex" : "fragment",
|
|
buffer);
|
|
|
|
g_free (buffer);
|
|
|
|
glDeleteShader (shader);
|
|
|
|
return 0;
|
|
}
|
|
|
|
return shader;
|
|
}
|
|
|
|
/* Initialize the shaders and link them into a program */
|
|
static void
|
|
init_shaders (GLuint *program_out,
|
|
GLuint *mvp_out)
|
|
{
|
|
GLuint vertex, fragment;
|
|
GLuint program = 0;
|
|
GLuint mvp = 0;
|
|
int status;
|
|
GBytes *source;
|
|
|
|
source = g_resources_lookup_data ("/glarea/glarea-vertex.glsl", 0, NULL);
|
|
vertex = create_shader (GL_VERTEX_SHADER, g_bytes_get_data (source, NULL));
|
|
g_bytes_unref (source);
|
|
|
|
if (vertex == 0)
|
|
{
|
|
*program_out = 0;
|
|
return;
|
|
}
|
|
|
|
source = g_resources_lookup_data ("/glarea/glarea-fragment.glsl", 0, NULL);
|
|
fragment = create_shader (GL_FRAGMENT_SHADER, g_bytes_get_data (source, NULL));
|
|
g_bytes_unref (source);
|
|
|
|
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\n", buffer);
|
|
|
|
g_free (buffer);
|
|
|
|
glDeleteProgram (program);
|
|
program = 0;
|
|
|
|
goto out;
|
|
}
|
|
|
|
/* Get the location of the "mvp" uniform */
|
|
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;
|
|
|
|
/* We need to set up our state when we realize the GtkGLArea widget */
|
|
static void
|
|
realize (GtkWidget *widget)
|
|
{
|
|
gtk_gl_area_make_current (GTK_GL_AREA (widget));
|
|
|
|
if (gtk_gl_area_get_error (GTK_GL_AREA (widget)) != NULL)
|
|
return;
|
|
|
|
init_buffers (&position_buffer, NULL);
|
|
init_shaders (&program, &mvp_location);
|
|
}
|
|
|
|
/* We should tear down the state when unrealizing */
|
|
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];
|
|
|
|
/* Compute the model view projection matrix using the
|
|
* rotation angles specified through the GtkRange widgets
|
|
*/
|
|
compute_mvp (mvp,
|
|
rotation_angles[X_AXIS],
|
|
rotation_angles[Y_AXIS],
|
|
rotation_angles[Z_AXIS]);
|
|
|
|
/* Use our shaders */
|
|
glUseProgram (program);
|
|
|
|
/* Update the "mvp" matrix we use in the shader */
|
|
glUniformMatrix4fv (mvp_location, 1, GL_FALSE, &mvp[0]);
|
|
|
|
/* Use the vertices in our buffer */
|
|
glBindBuffer (GL_ARRAY_BUFFER, position_buffer);
|
|
glEnableVertexAttribArray (0);
|
|
glVertexAttribPointer (0, 4, GL_FLOAT, GL_FALSE, 0, 0);
|
|
|
|
/* Draw the three vertices as a triangle */
|
|
glDrawArrays (GL_TRIANGLES, 0, 3);
|
|
|
|
/* We finished using the buffers and program */
|
|
glDisableVertexAttribArray (0);
|
|
glBindBuffer (GL_ARRAY_BUFFER, 0);
|
|
glUseProgram (0);
|
|
}
|
|
|
|
static gboolean
|
|
render (GtkGLArea *area,
|
|
GdkGLContext *context)
|
|
{
|
|
if (gtk_gl_area_get_error (area) != NULL)
|
|
return FALSE;
|
|
|
|
/* Clear the viewport */
|
|
glClearColor (0.5, 0.5, 0.5, 1.0);
|
|
glClear (GL_COLOR_BUFFER_BIT);
|
|
|
|
/* Draw our object */
|
|
draw_triangle ();
|
|
|
|
/* Flush the contents of the pipeline */
|
|
glFlush ();
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
on_axis_value_change (GtkAdjustment *adjustment,
|
|
gpointer data)
|
|
{
|
|
int axis = GPOINTER_TO_INT (data);
|
|
|
|
g_assert (axis >= 0 && axis < N_AXIS);
|
|
|
|
/* Update the rotation angle */
|
|
rotation_angles[axis] = gtk_adjustment_get_value (adjustment);
|
|
|
|
/* Update the contents of the GL drawing area */
|
|
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_container_add (GTK_CONTAINER (box), label);
|
|
gtk_widget_show (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_container_add (GTK_CONTAINER (box), slider);
|
|
gtk_widget_set_hexpand (slider, TRUE);
|
|
gtk_widget_show (slider);
|
|
|
|
gtk_widget_show (box);
|
|
|
|
return box;
|
|
}
|
|
|
|
static void
|
|
close_window (GtkWidget *widget)
|
|
{
|
|
/* Reset the state */
|
|
demo_window = NULL;
|
|
gl_area = NULL;
|
|
|
|
rotation_angles[X_AXIS] = 0.0;
|
|
rotation_angles[Y_AXIS] = 0.0;
|
|
rotation_angles[Z_AXIS] = 0.0;
|
|
}
|
|
|
|
GtkWidget *
|
|
create_glarea_window (GtkWidget *do_widget)
|
|
{
|
|
GtkWidget *window, *box, *button, *controls;
|
|
int i;
|
|
|
|
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
|
gtk_window_set_screen (GTK_WINDOW (window), gtk_widget_get_screen (do_widget));
|
|
gtk_window_set_title (GTK_WINDOW (window), "GtkGLArea - Golden Triangle");
|
|
gtk_window_set_default_size (GTK_WINDOW (window), 400, 600);
|
|
gtk_container_set_border_width (GTK_CONTAINER (window), 12);
|
|
g_signal_connect (window, "destroy", G_CALLBACK (close_window), NULL);
|
|
|
|
box = gtk_box_new (GTK_ORIENTATION_VERTICAL, FALSE);
|
|
gtk_box_set_spacing (GTK_BOX (box), 6);
|
|
gtk_container_add (GTK_CONTAINER (window), box);
|
|
|
|
gl_area = gtk_gl_area_new ();
|
|
gtk_widget_set_hexpand (gl_area, TRUE);
|
|
gtk_widget_set_vexpand (gl_area, TRUE);
|
|
gtk_container_add (GTK_CONTAINER (box), gl_area);
|
|
|
|
/* We need to initialize and free GL resources, so we use
|
|
* the realize and unrealize signals on the widget
|
|
*/
|
|
g_signal_connect (gl_area, "realize", G_CALLBACK (realize), NULL);
|
|
g_signal_connect (gl_area, "unrealize", G_CALLBACK (unrealize), NULL);
|
|
|
|
/* The main "draw" call for GtkGLArea */
|
|
g_signal_connect (gl_area, "render", G_CALLBACK (render), NULL);
|
|
|
|
controls = gtk_box_new (GTK_ORIENTATION_VERTICAL, FALSE);
|
|
gtk_container_add (GTK_CONTAINER (box), controls);
|
|
gtk_widget_set_hexpand (controls, TRUE);
|
|
|
|
for (i = 0; i < N_AXIS; i++)
|
|
gtk_container_add (GTK_CONTAINER (controls), create_axis_slider (i));
|
|
|
|
button = gtk_button_new_with_label ("Quit");
|
|
gtk_widget_set_hexpand (button, TRUE);
|
|
gtk_container_add (GTK_CONTAINER (box), button);
|
|
g_signal_connect_swapped (button, "clicked", G_CALLBACK (gtk_widget_destroy), window);
|
|
|
|
return window;
|
|
}
|
|
|
|
GtkWidget*
|
|
do_glarea (GtkWidget *do_widget)
|
|
{
|
|
if (demo_window == NULL)
|
|
demo_window = create_glarea_window (do_widget);
|
|
|
|
if (!gtk_widget_get_visible (demo_window))
|
|
gtk_widget_show_all (demo_window);
|
|
else
|
|
gtk_widget_destroy (demo_window);
|
|
|
|
return demo_window;
|
|
}
|