John U
John U

Reputation: 2993

Simple GTK/GDK app very slow to update/refresh window & buttons

Apologies in advance, I'm not a GTK/GDK master and have been feeling my way round some code written by someone else who's no longer around.

Edited to add TL;DR - Full question below with some detail.

The TL;DR is that gtk_button_set_image seems to take ~1ms, multiplied by 50 buttons that causes a bottleneck when changing the image on every button in our window.

EDIT again to add timings for the various calls:

Call                             Time (ms) approximate
gtk_button_set_image             1ms
gtk_button_set_label             0.5
gdk_pixbuf_scale                 0.5 
recolour_pixbuf (re-written)     0.5
gdk_pixbuf_new_subpixbuf         0.4
gtk_css_provider_load_from_data  0.3
gtk_image_new_from_pixbuf        0.15
BuildButtonCSS                   0.01

And yes, recolour_pixbuf() takes a long time but I can find no other way of colour-swapping pixels in a pixbuf other than going through the whole thing pixel-by-pixel.

This adds up to GTK/GDK calls taking ~2.35ms to update each button each time. I have refactored my code to check what's changed and ONLY execute necessary changes - but even then, the whole window is fairly regularly updated with new images for every button plus new colours etc. so it's not an edge case and it is noticeable.


Basically we have a pretty simple GTK C app, just a window with a grid of buttons in it. A TCP socketed connection sends messages to the app to (for example) change the label or colour of a button, and we send messages back when a button is pushed.

However, with 100ms polling on the main loop for refreshing/re-drawing the buttons it seems to be taking a very long time to refresh the window.

I'll try to keep this sane + readable - I can't really post a minimal working example (it would be huge) but I'll try and break down the basics of the code so you can see what's done.

Each button is a widget that can contain a straight text label or instead be an image created from a pixbuf.

Each button is attached to a grid, the grid is inside a window.

Hopefully this is sensible and obvious so far.

In our main application we have a check that happens every 100ms which will run through all the button data, and for any that have changed (EG new label or new pixbuf) it will update the button accordingly.

g_timeout_add(100, (GSourceFunc)check_refresh, _context->refresh);

The code then happening for each button (including the timestamps I've added to get debug info) is:

static void refresh_button(int buttonId)
{
    char name[12];
    snprintf(name, 10, "BTN_%02d", buttonId);
    int bid = buttonId-1;
    char tstr[VERY_LONG_STR];
    struct dev_button *dbp;
    dbp = &_context->buttons[bid];
    // For debug timestamps:
    struct timespec start, stop;
    double result;
    
    clock_gettime(CLOCK_MONOTONIC, &start);

    if(dbp->css_modified != 0)
    {
        BuildButtonCSS(dbp, tstr, NULL);
        gtk_css_provider_load_from_data(dbp->bp, tstr, -1, NULL);

        if(dbp->text[0] != '\0')
        {
            gtk_button_set_label(GTK_BUTTON(dbp->btn), dbp->text);
        }
        else
        {
            gtk_button_set_label(GTK_BUTTON(dbp->btn), NULL);
        }
    }
    
    clock_gettime(CLOCK_MONOTONIC, &stop);
    result = ((stop.tv_sec - start.tv_sec) * 1e3) + ((stop.tv_nsec - start.tv_nsec) / 1e6);    // in milliseconds
    g_message("[BRF] %s took %.3fms to here", name, result);

    /*
     * CSS changes affect button image drawing (cropping etc.)
     */
    if(dbp->image_modified != 0 || dbp->css_modified != 0)
    {
        uint8_t b = dbp->bpx; // Border in pixels
        GdkPixbuf* tmp = gdk_pixbuf_new_subpixbuf (dbp->pixbuf, b, b, _context->innerButton.width - (b * 2), _context->innerButton.height - (b * 2));
        dbp->image = (GtkImage*)gtk_image_new_from_pixbuf(tmp);
        gtk_button_set_image(GTK_BUTTON(dbp->btn), GTK_WIDGET(dbp->image));
    }

    btn_timediff(buttonId);
    clock_gettime(CLOCK_MONOTONIC, &stop);
    result = ((stop.tv_sec - start.tv_sec) * 1e3) + ((stop.tv_nsec - start.tv_nsec) / 1e6);    // in milliseconds
    g_message("[BRF] %s took %.3fms for update", name, result);

    dbp->css_modified = 0;
}

So I'm timing the milliseconds taken to update the button from CSS, then the time to have updated the image from pixbuf - running on a Raspberry Pi CM4 I'm getting results like this:

** Message: 10:25:22.956: [BRF] BTN_03 took 1.443ms to here
** Message: 10:25:22.959: [BRF] BTN_03 took 5.061ms for update

So around ~1.5ms to update a button from simple CSS, and ~3.5ms to update a button image from a pixbuf.

And before you say the Raspberry Pi is slow - even on a full fat Linux desktop machine I'm seeing similar timings - a little faster on average but sometimes the total can be beyond 10ms for a single button.

This feels very slow to me - almost like there's something blocking on screen refresh after each change to each button. I wonder if we're going about this wrong, perhaps we should be somehow inhibiting re-draws of the window until we get to the last button and then let the whole thing re-draw once?

As I said - I'm not experienced with GTK and am a bit in at the deep end on this project so may well be doing this all wrong or totally missing some obvious method or call or something.

Upvotes: 0

Views: 371

Answers (0)

Related Questions