GTK3 Cairo animation stuttering issue

I created a basic animation using GTK3 with Cairo and I’m experiencing periodic stuttering that occurs roughly every second. The animation looks jerky and unprofessional. What causes this stuttering behavior and what’s the best approach to eliminate it?

#include <gtk/gtk.h>
#include <cairo.h>

static int windowWidth, windowHeight,
           ballX = 0,
           speedX = 3;

gboolean on_draw_event(GtkWidget* drawArea, cairo_t* context)
{
    GtkWidget* mainWindow = gtk_widget_get_toplevel(drawArea);
    gtk_window_get_size(GTK_WINDOW(mainWindow), &windowWidth, &windowHeight);

    cairo_set_source_rgb(context, 0.2, 0.4, 0.8);
    cairo_set_line_width(context, 80);

    cairo_rectangle(context, ballX, windowHeight/3, 80, 80);
    cairo_fill(context);

    if(ballX + speedX >= windowWidth || ballX + speedX <= 0)
        speedX = -speedX;
    ballX += speedX;

    gtk_widget_queue_draw(drawArea);
    return TRUE;
}

int main(int argc, char** argv)
{
    GtkWidget* mainWindow;
    GtkWidget* canvas;

    gtk_init(&argc, &argv);
    mainWindow = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    canvas = gtk_drawing_area_new();

    gtk_container_add(GTK_CONTAINER(mainWindow), canvas);
    gtk_window_set_default_size(GTK_WINDOW(mainWindow), 600, 300);

    g_signal_connect(G_OBJECT(mainWindow), "destroy", G_CALLBACK(gtk_main_quit), NULL);
    g_signal_connect(G_OBJECT(canvas), "draw", G_CALLBACK(on_draw_event), NULL);

    g_timeout_add(16, (GSourceFunc)on_draw_event, mainWindow);

    gtk_widget_show_all(mainWindow);
    gtk_main();
}

The Problem:

You’re experiencing stuttering in your GTK3 animation using Cairo, occurring roughly every second. This makes the animation jerky and unprofessional. The provided code uses g_timeout_add and gtk_widget_queue_draw within the drawing function, causing redundant redraw requests which lead to the stuttering.

:thinking: Understanding the “Why” (The Root Cause):

The stuttering is a direct consequence of calling gtk_widget_queue_draw inside the on_draw_event callback. Each time on_draw_event runs, it requests another redraw using gtk_widget_queue_draw. Combined with the g_timeout_add function which is already scheduling redraws at 16ms intervals, this creates a feedback loop: each redraw triggers another redraw request, overloading the system and leading to the jerky animation. GTK’s internal event handling isn’t optimized for this kind of continuous redraw request from within the draw handler. The system struggles to keep up, resulting in missed frames and the observed stuttering.

:gear: Step-by-Step Guide:

  1. Remove Redundant Redraw Requests: The most critical change is removing the gtk_widget_queue_draw(drawArea) call from inside the on_draw_event function. This prevents the infinite redraw loop. The g_timeout_add function will handle all necessary redraws.

  2. Correct Callback Parameter: The g_timeout_add function currently passes mainWindow as the callback parameter. However, on_draw_event expects GtkWidget* drawArea. Correct this by passing the canvas widget instead.

  3. Revised Code: Here’s the corrected code:

#include <gtk/gtk.h>
#include <cairo.h>

static int windowWidth, windowHeight,
           ballX = 0,
           speedX = 3;

gboolean on_draw_event(GtkWidget* drawArea, cairo_t* context)
{
    GtkWidget* mainWindow = gtk_widget_get_toplevel(drawArea);
    gtk_window_get_size(GTK_WINDOW(mainWindow), &windowWidth, &windowHeight);

    cairo_set_source_rgb(context, 0.2, 0.4, 0.8);
    cairo_set_line_width(context, 80);

    cairo_rectangle(context, ballX, windowHeight/3, 80, 80);
    cairo_fill(context);

    return TRUE; // Removed gtk_widget_queue_draw
}

gboolean update_animation(gpointer data) {
    GtkWidget *canvas = (GtkWidget*) data;
    if(ballX + speedX >= windowWidth || ballX + speedX <= 0)
        speedX = -speedX;
    ballX += speedX;
    gtk_widget_queue_draw(canvas);
    return TRUE;
}

int main(int argc, char** argv)
{
    GtkWidget* mainWindow;
    GtkWidget* canvas;

    gtk_init(&argc, &argv);
    mainWindow = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    canvas = gtk_drawing_area_new();

    gtk_container_add(GTK_CONTAINER(mainWindow), canvas);
    gtk_window_set_default_size(GTK_WINDOW(mainWindow), 600, 300);

    g_signal_connect(G_OBJECT(mainWindow), "destroy", G_CALLBACK(gtk_main_quit), NULL);
    g_signal_connect(G_OBJECT(canvas), "draw", G_CALLBACK(on_draw_event), NULL);

    g_timeout_add(16, update_animation, canvas); // Corrected parameter and function

    gtk_widget_show_all(mainWindow);
    gtk_main();
}

:mag: Common Pitfalls & What to Check Next:

  • Timing Issues: Even with the correction, you might still experience minor inconsistencies depending on system load. Consider using gdk_frame_clock_begin_updating for smoother, frame-synchronized animations. This synchronizes your animation with the display’s refresh rate.
  • Complex Animations: For significantly more complex animations, exploring animation libraries or frameworks designed for GTK might be beneficial. They often handle timing and redraw optimization more effectively.

:speech_balloon: Still running into issues? Share your (sanitized) code, the exact steps you took, and any error messages. The community is here to help!

hey, the issue is def the timeout callback! g_timeout_add should return gboolean, but you’re mixing it up with the draw one. try making a separate timer function that only calls gtk_widget_queue_draw(canvas) and returns TRUE. also, remove queue_draw from your draw event to stop double triggers.

Yeah, it’s the mixed callback architecture, but there’s more to it. Your g_timeout_add is passing the wrong widget parameter and casting your draw function wrong. The timeout expects a drawing area but gets mainWindow instead, which creates internal conflicts.

I had the same stuttering issues in a data viz project until I figured out my animation logic was running inside the draw handler. Move all your position calculations to a separate timer callback that just updates coordinates and calls gtk_widget_queue_draw. Keep the draw function for rendering only.

Also, 16ms intervals don’t guarantee smooth animation on every system. GTK’s internal event queue batches redraws unpredictably, especially when the system’s under load. Try gdk_frame_clock_begin_updating for frame-synced animations instead of arbitrary timeouts - it syncs with your actual display refresh instead of fighting it.

You’re calling gtk_widget_queue_draw inside the draw callback - that’s your problem. The draw event should only render, not trigger more redraws. You need a separate timer function for animation updates. Set up an update_animation function that moves the ball and returns TRUE to keep the timer going. Your draw callback just renders the current state without touching position updates. Also, that 16ms timeout probably doesn’t sync with your display refresh rate. Use g_timeout_add with ~17ms instead to match 60Hz monitors - it’ll cut down on the stuttering.

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.