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 theGlareaAppWindow
class on activation, unless one is already present, in which case it just brings up into focus the existing instanceglarea-app-menu.ui
- TheGMenu
XML 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
- TheGtkBuilder
XML description of theGlareaAppWindow
classglarea-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 ofGResource
glarea-vertex.glsl
- The GLSL vertex shader source, which we embed into the executable binary by way ofGResource
glarea.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.