I've made a very simple animation with gtk3 and cairo. Once a sec it's a little bit twitch. It's really annoying and it doesn't look well. Why it happens and how can I fix this?
#include <gtk/gtk.h>
#include <cairo.h>
static int width, height,
posX = 0,
vX = 2;
gboolean draw(GtkWidget* widget, cairo_t* cr)
{
GtkWidget* window = gtk_widget_get_toplevel(widget);
gtk_window_get_size(GTK_WINDOW(window), &width, &height);
cairo_set_source_rgb(cr, 0, 0, 0);
cairo_set_line_width(cr, 100);
cairo_rectangle(cr, posX, height/2, 100, 100);
cairo_stroke(cr);
if(posX + vX >= width || posX + vX == 0)
vX = -vX;
posX += vX;
gtk_widget_queue_draw(widget);
return TRUE;
}
int main(int argc, char** argv)
{
GtkWidget* window;
GtkWidget* darea;
gtk_init(&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
darea = gtk_drawing_area_new();
gtk_container_add(GTK_CONTAINER(window), darea);
gtk_window_set_default_size(GTK_WINDOW(window), 500, 400);
g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(gtk_main_quit), NULL);
g_signal_connect(G_OBJECT(darea), "draw", G_CALLBACK(draw), NULL);
g_timeout_add(16, (GSourceFunc)draw, window);
gtk_widget_show_all(window);
gtk_main();
}
I think the problem you have is that you're using g_timeout_add
as if it was an exact timing source, which is not as, stated by the documentation:
Note that timeout functions may be delayed, due to the processing of other event sources. Thus they should not be relied on for precise timing. After each call to the timeout function, the time of the next timeout is recalculated based on the current time and the given interval (it does not try to 'catch up' time lost in delays).
This means your code in the draw
callback may be called slightly later (or much later), at each call. As the timeout is not recalculated, the errors add up. You'll get out of sync and draw at the wrong position. This for example happens in video players when a frame is decoded: if the frame takes too long to be decoded, it may be dropped, because maybe we're already too late and need to display the next frame.
I'm not sure of what is the right solution to this, maybe GTK+ (or Clutter, which is really made for animation) developers could give you some hints, so it's a good idea to ask them through their IRC channel.
However, I encountered that issue myself while programming a metronome. If you try to synchronize with g_timeout_add
, errors add up and you get out of sync. Here's what I've done and worked for me.
First, I fire up a GTimer
at the very beginning, so I have a reliable, precise and absolute time reference. Then when my callback is called, I:
G_SOURCE_REMOVE
so that the current event source is not called anymore (I replace it by the new one, basically)Here's my metronome code for reference: https://github.com/liberforce/metrognome/blob/master/metronome.c
You may also use g_timeout_add_full
instead of g_timeout_add
so you can use a higher priority.
I also recommend reading a series of articles from Owen Taylor about animation synchronization in gnome-shell: