1

I've made a very simple animation with GTK3 and cairo and it's too slow for this simple graphics. I don't understand why it's so slow. I tried to use gtk_widget_queue_draw_area, but result is the same, nothing changes. Who can explain me, why it's so slow and how can i fix it?

Here's the program:

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

void draw(GtkWidget* widget, cairo_t* cr)
{
    static int width, height,
               posX = 0,
               vX = 1;

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

    cairo_rectangle(cr, posX, height/2, 1, 1);
    cairo_stroke(cr);

    if(posX + vX >= width || posX + vX == 0)
        vX = -vX;
    posX += vX;

    gtk_widget_queue_draw(window);
}

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

    gtk_widget_show_all(window);
    gtk_main();
}
delxa
  • 99
  • 1
  • 8
  • 1
    Maybe `gtk_queue_draw` is effectively ignored inside a `draw` signal callback. Try running it from a timeout with a small period between calls. I suspect queue draw is ignored precisely to avoid mistakes causing unintended "animations". The `draw` callback is supposed to actually do the drawing, not defer it for later, so calling `gtk_queue_draw` in the middle of it sounds like an incorrect implementation. – user4815162342 Feb 10 '18 at 23:35
  • 1
    There is nothing wrong with your code. I using a stopwatch measured the dot moving across the window to be 8.3 s. Using a refresh rate of 60 Hz, the dot moving 1 pixel each frame and the window width of 500 pixels this works out to take 8.3333 s. So, everything is OK. To increase the speed you will need to increase the distance moved per frame. In your code this would be to increase the value of vX. –  Feb 11 '18 at 09:35

1 Answers1

1

You should use timers, e.g. g_timeout_add. You could register with g_timeout_add your new function redraw called every 0.05 seconds (e.g. every 50 milliseconds), and that redraw should not only draw things but also return TRUE to be restarted 50 milliseconds later. You'll find out that such a 50 millisecond delay is probably too small, and you surely want to increase it.

(notice that your program takes only 3% of CPU time, measured with time(1)... on Linux/Debian/Sid/x86-64 on a Intel i5-4690S, so it is not too slow; the computer spends 97% of time waiting! remember also that the human eyes don't see a lot faster than 30 to 60 Hz most of the time)

There is no animation code in your program (because any animation should be periodically run). The event loop (in gtk_main) is called only when needed. You could want to repaint your window only partially.

Your approach is wrong. You don't want to repeat the drawing with gtk_widget_queue_draw, you want it to happen again periodically (e.g. call that gtk_widget_queue_draw from a new redraw routine registered with g_timeout_add). You'll tune that period experimentally.

You could also use gtk_widget_add_tick_callback (as commented by c-smile). I don't think you need it (because I guess your animation might then run too fast).

Study the source code of some GTK examples (notably the clock example). See also this and that. Look into the custom drawing example, and into the source of aclock.

(your program is not too slow, but your draw is probably called not frequently enough; there is no code to repeat it; you should use the gdb debugger and compile your code with -Wall -g passed to gcc - in addition of other flags provided by pkg-config)

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
  • "There is no animation code in your program", check `gtk_widget_queue_draw(window);` – c-smile Feb 10 '18 at 22:40
  • But nothing repeats the drawing. You need to use timers, you can't avoid using them, if you want to have some animations. Computers are quick! – Basile Starynkevitch Feb 10 '18 at 22:41
  • gtk_widget_queue_draw() -> gdk_window_invalidate_region() -> Invalidates the area of widget defined by region by calling gdk_window_invalidate_region() on the widget’s window and all its child windows. Once the main loop becomes idle (after the current batch of events has been processed, roughly), the window will receive expose events for the union of all regions that have been invalidated. – c-smile Feb 10 '18 at 22:44
  • No, the window won't recieve a lot of expose events. These are given by your X11 or Wayland server, and if you don't more *other* windows (above and around yours) you won't get expose events. If you want some animation, you should have a timeout. Run your code in the debugger, put a breakpoint on your `draw` routine: you'll be surprised how *rarely* it is called. – Basile Starynkevitch Feb 10 '18 at 22:45
  • This is all about incorrect statement "There is no animation code in your program". There is such loop as you see. On Windows/GTK TS's code is roughly this: `case WM_PAINT : InvalidateRect(hwnd,NULL,0)` that will cause WM_PAINT to come to the widget with VSync FPS (60 or so). – c-smile Feb 10 '18 at 22:50
  • 2
    But you are not on Windows, but probably on Linux. Again, **use the debugger** – Basile Starynkevitch Feb 10 '18 at 22:51
  • "use the debugger" for what? And "use timers" for what exactly ? Animations? Timers cannot be used for animations as there is no strong frequency guarantee. Please don't give wrong advices. There is a `gtk_widget_add_tick_callback()` to request animation frames in GTK. – c-smile Feb 10 '18 at 22:59
  • The debugger is certainly useful to understand *when* `draw` is called – Basile Starynkevitch Feb 10 '18 at 23:09
  • @BasileStarynkevitch I tried to use g_timeout_add with 1ms timeout, but speed doesn't increase. – delxa Feb 11 '18 at 21:21
  • @BasileStarynkevitch Also i used `gtk_widget_add_tick_callback()` but nothing changes. Maybe I don't uderstand how to use it. – delxa Feb 11 '18 at 22:39
  • @c-smile how should i use `gtk_widget_add_tick_callback()`? I tried, but it doesn't give the result. Should I use it in main function or how? – delxa Feb 12 '18 at 16:45
  • -1: "Your approach is wrong." Got a source for that? Because first, with gtk's frame clock, queuing a draw from within a draw should work, as it schedules a draw at exactly the right time next frame. 2. Using timeouts + callbacks gives no synchronisation whatsoever with vsyncs, and also involves guessing the screen refresh rate. 3. The recommended approach is actually to use frame clock tick callbacks, not a timeout. – PBS Feb 15 '22 at 01:34