GtkGLArea on OSX Sonoma

Dear all,
I am trying to build an OSX version (Sonoma - 14.4.1 - MacBook Pro - M3 Max) of my program “atomes” (https://atomes.ipcms.fr)

“atomes” is a tool box for atomic scale modeling, it uses the GtkGLArea widget to provide 3D rendering of atomistic models, and can handle several OpenGL window simultaneously.
Both GTK3 and GTK4 versions works nicely on Linux and Windows, and I am trying to provide an OSX version.

Using the “brew” system I can build successfully a GTK3, and, a GTK4 version of “atomes”, however things get messy when I try to run any of those versions.

  1. GTK3

I am not sure if it is appropriate to build a GTK3 version on OSX, still I tried,
in that case everything in the program seems to works really fine except the 3D rendering, the OpenGL window(s) remaining empty:

Note that I observe the same empty area in the gtk3-demo application when I give a try to the GLArea widget

  1. GTK4

Overall the GTK4 build seems less stable that the GTK3, with flickering of the GUI.

  • If a single OpenGL window is opened, the rendering works at first, but If I start to rotate the 3D window, then after a short while the rendering freezes and I can see the following message in the terminal:

OPENGL-CRITICAL **: …/gdk/macos/gdkmacosglcontext.c:202: invalid operation

  • If multiple OpenGL windows are opened, then everything is messy, one window seems to work at first but freezes really quickly, the others are flickering constantly, and quickly all OpenGL windows freeze with the previous message:

OPENGL-CRITICAL **: …/gdk/macos/gdkmacosglcontext.c:202: invalid operation

I noticed that same problem was happening with the gtk4-demo application, when testing the GtkGLArea widget (Shadertoy example in particular).

I am definitely not an expert with OSX, so I would appreciate any help to try and improve my work, because to be honest at this point I have not even a clue where or what to look for.

Thanks in advance for your help !

Answering to myself to keep the thread open.
I improved few things in my code but not enough to have something working.

On the GTK3 side of things everything seems to work fine if not for the OpenGL rendering that remains blank.

On the GTK4 side of the things the entire program is highly unstable, and the interface freezes quickly.

I am curious to know if the macOS version is something worth spending time on or not ?
Thanks in advance for your thoughts on the matter.

Hi @Sebastien_Le_Roux, could you open an issue at Issues · GNOME / gtk · GitLab?

Thanks!

Making progress today !
@lb90 thank you for your message, I might indeed open an issue, please read bellow and give me your opinion.

Forgetting about GTK3 I figured where the problem is coming from for GTK4: the fullscreen mode vs. isolated window mode.

  1. When I start the application and open a workspace with several 3D models, I will have as many 3D windows linked to the main software window.

  2. Flickering almost start immediately and I can loose control of the program quickly, that is both true for the main window which behavior completely impacted by the flickering, and for the OpenGL windows that will freeze eventually:

  1. When I go to fullscreen mode over the main window, then the program behavior is going back to normal, the flickering immediately stops:

  1. Also, and still in fullscreen mode, I can re-open each 3D window that now appear bundle to the main window, and the behavior is perfectly fine, no freeze anymore:

  1. Then when I go back to not windowed mode again and quit fullscreen mode then the windows all appears ‘bundled’ together:

But the program is working fine or close enough, 3D windows might freeze or have issues again, but as before I can retrieve the program normal behavior by going in fullscreen mode.

Any idea about what I should do from these observations ?

Nice findings! What happens when running with the environment variable GSK_RENDERER=cairo or GSK_RENDERER=gl?

Can you also check gtk4-demo-application? Start the app and press Ctrl+N to open new windows.

I see that GtkGLArea is rendered outside of the render signal in a few places. I don’t know if that’s allowed or not, but it’s probably best to use gtk_gl_area_queue_render. One way to do that is to queue the arguments passed to render_this_gl_window() and then do the operations from render, emptying the queue afterwards. Otherwise you can create a separate texture (or PBO) and share that with the GtkGLArea’s GL context. However that needs someone knowledgeable in OpenGL :slightly_smiling_face:

It really isn’t, just like it’s not allowed to render widgets outside of the draw/snapshot virtual functions in GTK.

You can only render to the GL area inside the render virtual function/signal: that’s why it exists.

1 Like

@lb90 this does not change a thing (nothing visible for atomes anyway).
For the gtk4-demo-application that is the same:

  1. In both cases in windowed mode the Ctrl+N simply open a new window (not bundled with the first one)

  2. Then if I go for fullscreen mode and use the Ctrl+N shortcut again a new window bundled to the fullscreen on is created.

So that fullscreen and windows will bundle.

In atomes I acually use gtk_gl_area_queue_render each time I want to render any OpenGL window.

1 Like

Yeah, indeed there’s the update function. However there are a few remaining places where queue_render is not used. Search for gtk_gl_area_make_current in the source code:

$ git grep -n make_current
src/opengl/glview.c:763:  gtk_gl_area_make_current (area);
src/opengl/glview.c:1707:    gtk_gl_area_make_current (area);
src/workspace/modelinfo.c:360:  gtk_gl_area_make_current ((GtkGLArea * )this_proj -> modelgl -> plot);

The second is ok, since it’s called from the GtkGLArea::realize signal. However no. 1 and 3 are called from input event handlers, which is not quite right. Basically, you should remove render_this_gl_window and only use the update function.

Can’t say if that will solve the issue on macOS, but it’s worth doing anyway

Try commenting out both render_this_gl_window and update opengl_info, and check if the issue is reproducible

Thank you for your suggestions, but actually I there might be another issue here,
I cannot remove the different gtk_gl_area_make_current (area); instructions that you noticed (thanks for digging up in my code by the way), at least for sure in the file src/opengl/glview.c.
Since the same routines must be able to handle multiple and distnct GtkGLArea widgets, and not just one, it is mandatory to let the code know which one the initialization process or the rendering will refer to, and the only way to set this information is using the gtk_gl_area_make_current (area); instruction.
I tried commenting it out (on Linux) and it completely messes up with the rendering if I have multiple 3D windows opened.

Now the rendering function per say is render_this_gl_window (called by the render event for the GtkGLArea) and in my understanding the update function will simply queue calls to this function.

1 Like

I figured that the extra gtk_gl_area_make_current (area); is required only to handle the mouse button events, as long as I do not use the mouse then I do not need it and can comment it without any problem.
But then if I use the mouse, hence passing thru the signal events that call render_this_gl_window then the extra gtk_gl_area_make_current (area); becomes required or the OpenGL rendering mix each others (on Linux).

Not sure how important this is, please let me know if I should report this and how.
@ebassi when I read your previous message I can only ask for your opinion, what do you think ?

And thank, the both of you, for helping.

I have no idea why you’re calling make_current() in a response to an input event; are you drawing from the event callback? Remember: signal callbacks are synchronous to their emission. Your code should never draw synchronously into an event handler: it should only queue a render. If you want to draw in response to some information gathered from the event handler, you should store that information somewhere that can be accessed from the render signal handler.

Thank you for reply,
I managed to correct my code to follow your advise @ebassi sorry that it took me so long to understand, very tired today … anyway, thank you for helping me.

@lb90 the corrections, so that I only call gtk_gl_area_make_current (area) only once, does not improve the OSX behavior.

But I managed to improve my code, so not such a bad day, thank you for advise.

1 Like

Thanks! Ok, I think this warrants a new issue at https://gitlab.gnome.org/GNOME/gtk/-/issues

Here are some useful links:

  1. GtkGLArea drawing issues during resize (#1298) · Issues · GNOME / gtk · GitLab
  2. GtkGlArea makes the entire window black in macOS (#3087) · Issues · GNOME / gtk · GitLab
  3. Implement GdkGLContext for Quartz backend for Mac OS X (#517) · Issues · GNOME / gtk · GitLab

Hello @lb90,
I will check those links carefully, in the mean time I want to let you know about some new findings, maybe that will help, only no idea how to report this, because it does not make any sense to me …

… so following yesterday idea I managed to remove all non necessary gtk_gl_area_make_current (area) instructions in my code, turns out that one of these has an effect:

src/workspace/modelinfo.c:360:  gtk_gl_area_make_current ((GtkGLArea * )this_proj -> modelgl -> plot);

A lasting effect, that I am not able to understand, it really does not make sense to me.

If I get rid of these instructions, only one remaining in the realize event of the GtkGLArea widget, here is what I see:

  1. In fullscreen mode I see some flickering of images that should appear in the left area tree store:

  1. If I follow what I did yesterday, close the OpenGL windows and re-open then, they appear bundled again:

  1. But then if I change tab in the bundle to go back to the main window, the window is not drawn, and appears black;

I need to go back to windowed mode again, then fullcreen mode, to retrieve a proper window, but eventually glitches will keep happening.

So just to confirm this observation I repeated everything many times, and the fact remains that with this instruction still in the code:

src/workspace/modelinfo.c:360:  gtk_gl_area_make_current ((GtkGLArea * )this_proj -> modelgl -> plot);

The general behavior of the program in fullscreen mode is much, much, better, if not good. However without that same expression, well it is getting bad.

Not sure how this bug report is going to look with all that information … not even sure how to organise it to make it clear :wink:

1 Like

Ok, I reported the issue here: GTK4 + macOS: GtkGLArea and window management (#6813) · Issues · GNOME / gtk · GitLab

I hope I did this properly.
Will keep digging. :pick:

I just confirmed that flickering and glitches are gone if I insert the GtkGLArea
in the main application window defined by gtk_application_window_new (GTK_APPLICATION(my_gtk4_app)); then no visual issues what so ever, but the stacking behavior is still there.

So if the program has only one GtkGLArea in the main application window then the OpenGL rendering should be fine, question is what do I do in my case with several GtkGLArea in separate windows ?

Here are two pieces of code that demonstrate the issues (Sonoma - 14.4.1 - MacBook Pro - M3 Max, Gtk 4.14.4 homebrew).

This codes are based on the gtk4-demo GtkGLArea demo.

  1. Application with a single window, the GtkGLArea + controls are in the main application window: no flickering
Test code N°1: application with a single window
#include <math.h>
#include <gtk/gtk.h>
#include <epoxy/gl.h>

#define GLSL(src) "#version 330\n" #src

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",
                 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 (const char *vertex_path,
              const char *fragment_path,
              GLuint *program_out,
              GLuint *mvp_out)
{
  GLuint vertex, fragment;
  GLuint program = 0;
  GLuint mvp = 0;
  int status;

  vertex = create_shader (GL_VERTEX_SHADER, vertex_path);
  if (vertex == 0)
    {
      *program_out = 0;
      return;
    }

  fragment = create_shader (GL_FRAGMENT_SHADER, fragment_path);

  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;
    }

  /* 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)
{
  const char *vertex_path, *fragment_path;
  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));
  const GLchar * vertex = GLSL(
    uniform mat4 mvp;
    in vec3 vert;
    in vec4 vertColor;

    out vec4 vert_color;
    void main()
    {
      vert_color = vertColor;
      gl_Position = mvp * vec4(vert, 1.0);
    }
  );
  const GLchar * fragment = GLSL(
    out vec4 outputColor;
    void main() {
      float lerpVal = gl_FragCoord.y / 500.0f;
      outputColor = mix(vec4(1.0f, 0.85f, 0.35f, 1.0f), vec4(0.2f, 0.2f, 0.2f, 1.0f), lerpVal);
    }
  );
  
  init_buffers (NULL, &position_buffer);
  init_shaders (vertex, fragment, &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, 0);

  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);
  gtk_widget_set_visible (label, TRUE);

  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);
  gtk_widget_set_visible (slider, TRUE);

  gtk_widget_set_visible (box, TRUE);

  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;
}

static GtkWidget *
create_glarea_widget ()
//gtkWidget *do_widget)
{
  // GtkWidget *window;
  GtkWidget *box, *button, *controls;
  int i;

  //window = gtk_window_new ();
  //gtk_window_set_display (GTK_WINDOW (window),  gtk_widget_get_display (do_widget));
  //gtk_window_set_title (GTK_WINDOW (window), "OpenGL Area");
  //gtk_window_set_default_size (GTK_WINDOW (window), 400, 600);
  //g_signal_connect (window, "destroy", G_CALLBACK (close_window), NULL);

  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);
  
  gl_area = gtk_gl_area_new ();
  gtk_widget_set_hexpand (gl_area, TRUE);
  gtk_widget_set_vexpand (gl_area, TRUE);
  gtk_widget_set_size_request (gl_area, 100, 200);
  gtk_box_append (GTK_BOX (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_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);

  return box;
}

void run_program (GApplication * app, gpointer data)
{
  GtkWidget * window = gtk_application_window_new (GTK_APPLICATION(app));
  gtk_window_set_title (GTK_WINDOW(window), "Test");
  gtk_window_set_resizable (GTK_WINDOW(window), TRUE);
  gtk_widget_set_size_request (window, 900, 450);
  
  gtk_window_set_child (GTK_WINDOW (window), create_glarea_widget ());
  gtk_widget_set_visible (window, TRUE);
}

int main (int argc, char *argv[])
{
  gchar * name = g_strdup_printf ("test._%d.gtk", (int)clock());
#if GLIB_MINOR_VERSION < 74
  GtkApplication * TestApp = gtk_application_new (name, G_APPLICATION_FLAGS_NONE);
#else
  GtkApplication * TestApp = gtk_application_new (name, G_APPLICATION_DEFAULT_FLAGS);
#endif
  g_free (name);
  g_signal_connect (G_OBJECT(TestApp), "activate", G_CALLBACK(run_program), NULL);
  int status = g_application_run (G_APPLICATION (TestApp), 0, NULL);
  g_object_unref (TestApp);
  return status;
}
  1. Test application with 2 windows, the controls for the GtkGLArea are in the main application window, while the GtkGLArea in a secondary window: the main application windows is flickering and display glitches
Test code N°2: application with 2 windows
#include <math.h>
#include <gtk/gtk.h>
#include <epoxy/gl.h>

#define GLSL(src) "#version 330\n" #src

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",
                 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 (const char *vertex_path,
              const char *fragment_path,
              GLuint *program_out,
              GLuint *mvp_out)
{
  GLuint vertex, fragment;
  GLuint program = 0;
  GLuint mvp = 0;
  int status;

  vertex = create_shader (GL_VERTEX_SHADER, vertex_path);
  if (vertex == 0)
    {
      *program_out = 0;
      return;
    }

  fragment = create_shader (GL_FRAGMENT_SHADER, fragment_path);

  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;
    }

  /* 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;

  const GLchar * vertex = GLSL(
    uniform mat4 mvp;
    in vec3 vert;
    in vec4 vertColor;

    out vec4 vert_color;
    void main()
    {
      vert_color = vertColor;
      gl_Position = mvp * vec4(vert, 1.0);
    }
  );
  const GLchar * fragment = GLSL(
    out vec4 outputColor;
    void main() {
      float lerpVal = gl_FragCoord.y / 500.0f;
      outputColor = mix(vec4(1.0f, 0.85f, 0.35f, 1.0f), vec4(0.2f, 0.2f, 0.2f, 1.0f), lerpVal);
    }
  );
  
  init_buffers (NULL, &position_buffer);
  init_shaders (vertex, fragment, &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, 0);

  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);
  gtk_widget_set_visible (label, TRUE);

  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);
  gtk_widget_set_visible (slider, TRUE);

  gtk_widget_set_visible (box, TRUE);

  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;
}

static GtkWidget *
create_glarea_widget (GtkWidget * win)
{
  GtkWidget *window;
  GtkWidget *box;

  window = gtk_window_new ();
  gtk_window_set_display (GTK_WINDOW (window),  gtk_widget_get_display (win));
  gtk_window_set_title (GTK_WINDOW (window), "OpenGL Area");
  gtk_window_set_default_size (GTK_WINDOW (window), 400, 600);
  g_signal_connect (window, "destroy", G_CALLBACK (close_window), NULL);

  box = gtk_box_new (GTK_ORIENTATION_VERTICAL, FALSE);
  gtk_window_set_child (GTK_WINDOW (window), box);
  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);
  
  gl_area = gtk_gl_area_new ();
  gtk_widget_set_hexpand (gl_area, TRUE);
  gtk_widget_set_vexpand (gl_area, TRUE);
  gtk_widget_set_size_request (gl_area, 100, 200);
  gtk_box_append (GTK_BOX (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);


  return window;
}

void run_program (GApplication * app, gpointer data)
{
  GtkWidget * window = gtk_application_window_new (GTK_APPLICATION(app));
  gtk_window_set_title (GTK_WINDOW(window), "Test");
  gtk_window_set_resizable (GTK_WINDOW(window), TRUE);
  gtk_widget_set_size_request (window, 900, 450);
  gtk_widget_set_visible (window, TRUE);
  
  GtkWidget * box = gtk_box_new (GTK_ORIENTATION_VERTICAL, FALSE);
  gtk_window_set_child (GTK_WINDOW (window), box);
  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);

  GtkWidget * button, * controls;
  controls = gtk_box_new (GTK_ORIENTATION_VERTICAL, FALSE);
  gtk_box_append (GTK_BOX (box), controls);
  gtk_widget_set_hexpand (controls, TRUE);
  int i;

  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);

  GtkWidget * area = create_glarea_widget (window); 
  gtk_widget_set_visible (area, TRUE);
}

int main (int argc, char *argv[])
{
  gchar * name = g_strdup_printf ("test._%d.gtk", (int)clock());
#if GLIB_MINOR_VERSION < 74
  GtkApplication * TestApp = gtk_application_new (name, G_APPLICATION_FLAGS_NONE);
#else
  GtkApplication * TestApp = gtk_application_new (name, G_APPLICATION_DEFAULT_FLAGS);
#endif
  g_free (name);
  g_signal_connect (G_OBJECT(TestApp), "activate", G_CALLBACK(run_program), NULL);
  int status = g_application_run (G_APPLICATION (TestApp), 0, NULL);
  g_object_unref (TestApp);
  return status;
}

Ok problem solved using:

GSK_RENDERER=gl program

@lb90 I have no idea why it did not work when your first suggested it, my bad for missing something.

1 Like