0

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.

John U
  • 2,886
  • 3
  • 27
  • 39
  • 'A TCP socketed connection'...run on a different thread? – Martin James Jan 31 '23 at 11:00
  • I'm not 100% clear on GTK and threads... the connection handler is a GIO `g_socket_service` using `g_socket_listener` and a callback when there's data to handle. – John U Jan 31 '23 at 11:11
  • @MartinJames I replaced `g_socket_service ` with `g_threaded_socket_service` and there's no effect on the timings so I'm going to guess it's not blocking / not contributing to the problem. – John U Jan 31 '23 at 11:15
  • It seems very inefficient to create CSS for each button every time, as it then has to be parsed and applied. Could you provide the code for `BuildButtonCSS()`? – nielsdg Feb 03 '23 at 10:27
  • @nielsdg it seems to be the only way to modify the button CSS, but also that call only takes 0.01ms out of the chain of calls - I'll edit the TLDR again to add timings. – John U Feb 03 '23 at 13:02
  • @JohnU: the reason I'm asking is that the usual way of using CSS (and what the GTK CSS engine is optimized for), is to write your CSS to define classes outside of hot code paths and then use `gtk_style_context_add_class (gtk_widget_get_style_context (button), "YOUR-CLASS")` for example – nielsdg Feb 04 '23 at 14:07
  • @nielsdg - but if I have a button with an existing CSS style specifically applied to it (EG button #5 text is red) and I now need the text to be green I can't just *add* another CSS class to it can I? – John U Feb 05 '23 at 22:49
  • @JohnU: well, I'm not sure of your use case, but you could have a CSS `.good { color: green; } .bad { color: red; }` and then use those classes to do `gtk_style_context_remove_class (context, "bad");` and `gtk_style_context_add_class (context, "good");` for example – nielsdg Feb 06 '23 at 12:23
  • @nielsdg our use case is the other end can modify any/all buttons to be any 24-bit RGB colour at any time, so switching between styles or stacking modifications doesn't really cut it. – John U Feb 06 '23 at 13:00

0 Answers0