A simple example of how to use OpenGL and the GtkGLArea widget available since GTK+ 3.16.
See also: https://www.bassi.io/articles/2015/02/17/using-opengl-with-gtk/
NOTE: This example is meant to be used with GTK+ 3.x only. GTK 4 has a similar API, and it allows more flexibility when it comes to selecting the version of GL to use, as well as supporting OpenGL and OpenGLES.
A binary build of this example is available as a Flatpak. Make sure you have Flatpak installed on your system, and then use:
$ flatpak install --from http://ebassi.github.io/glarea-example/glarea-example.flatpakref
To install the binary build. A launcher should appear in the list of your applications; if it doesn't, you can use:
$ flatpak run io.bassi.Glarea
To run the example.
You will need GTK+ 3.16 or later to build this example.
Clone the repository, as usual:
$ git clone https://github.com/ebassi/glarea-example
Then run make inside the cloned repository:
$ cd glarea-example
$ make
Finally, run the example:
$ ./glarea
If everything worked as it should, you should see this:
Use the range widgets to control the rotation of the triangle, and feel free to play around with the source.
This is a simple example, but it's been broken down in a way that allows easy reuse of the parts.
glarea-app.[ch]- This is the main application singleton; just a thin wrapper around GtkApplication which creates an application window of theGlareaAppWindowclass on activation, unless one is already present, in which case it just brings up into focus the existing instanceglarea-app-menu.ui- TheGMenuXML description of the application menu, loaded by the application singleton instanceglarea-app-window.[ch]- This is the main application window, and where the real magic happensglarea-app-window.ui- TheGtkBuilderXML description of theGlareaAppWindowclassglarea-error.[ch]- A GError error domain, for our internal useglarea-fragment.glsl- The GLSL fragment shader source, which we embed into the executable binary by way ofGResourceglarea-vertex.glsl- The GLSL vertex shader source, which we embed into the executable binary by way ofGResourceglarea.gresource.xml- The list of resources we want to embed into the executable binarymain.c- The main entry point, which creates the application singletong and spins the main loop
The whole application logic can be broken down into roughly four separate parts:
- Initialization
- State updates
- Drawing
- Deinitialization
We use the GtkWidget::realize signal to know when the GtkGLArea widget
has created the windowing system resources associated with the GL context;
in order to use the signal, we connect the gl_init function to it inside
the UI description.
The basic step is to make the GL context current, so we can use the GL API:
gtk_gl_area_make_current (GTK_GL_AREA (self->gl_drawing_area));
if (gtk_gl_area_get_error (GTK_GL_AREA (self->gl_drawing_area)) != NULL)
return;We check if the GtkGLArea widget is in an error state to decide whether
to bail out and not use GL API on an invalid context.
After that, we initialize our vertex and fragment shaders, as well as the buffer objects.
Every time the user changes the value of the three GtkRange widgets, the
corresponding GtkAdjustment is also updated; we catch the value change
in the adjustment_changed signal handler, which we connected using the
UI description.
The adjustment_changed signal recomputes the modelview-projection matrix
using a rotation transformation, and calls gtk_widget_queue_draw() on
the GtkGLArea widget, signalling that the contents of the widget should
be updated, i.e. redrawn.
The gl_draw function is connected to the GtkGLArea::render signal by
way of the UI description.
We do not need to call gtk_gl_area_make_current(), as the context is
made current before emitting the signal; we also do not need to check for
an error state, because the signal will not be emitted in that case.
We load the various GL objects we created inside gl_init(), like the
vertex array object and the program; we upload the mvp matrix to the
location of the mvp uniform in the vertex shader; and we call the
glDrawArrays() function to draw the vertices of the triangle. At the
end of the process, we reset the state.
We use the GtkWidget::unrealize signal to release the resources we
allocated inside the gl_init() function. As with the initialization
process, we need to check if the GtkGLArea widget is in an error
state after making the GL context current.
Copyright 2015 Emmanuele Bassi
Released under the terms of the CC0. See the LICENSE file for more details.
