Skip to content

Instantly share code, notes, and snippets.

@bert
Last active February 1, 2025 22:42
Show Gist options
  • Select an option

  • Save bert/57b005b8be16999756092a7aed982120 to your computer and use it in GitHub Desktop.

Select an option

Save bert/57b005b8be16999756092a7aed982120 to your computer and use it in GitHub Desktop.
GTK3 Cairo Animation Example

Uli Schlachter psychon at znc.in

Fri Oct 28 16:08:10 UTC 2016

Previous message: cairo Cairo + GTK animation - high Xorg load

Next message: cairo Cairo + GTK animation - high Xorg load

Messages sorted by: date thread subject author

On 27.10.2016 20:40, David J. Herzfeld wrote: [...]

I've updated the example again, to use exactly this model http://pastebin.com/apaFiGyd. Here, the Xorg load is decreased somewhat (at the expense of increased load for the actual application), but Xorg load is still ~15% and scales with the size of the window. I don't understand this - I've already done all of the necessary drawing work in the thread. I could understand if the thread load increased with the size of the surface (which it does not appear to), but why should Xorg's? Essentially I want to say - here is this pixel buffer - just display it as is. Perhaps this is too simplistic an understanding of how Cairo is interacting with the display system.

Well, the pixel data in the buffer is serialised and sent to the X server over a unix socket 60 times per seconds. Let's assume the widget has size 2000x1000, then you are pushing 200010004*60 bytes of data to the server. That's about 457 MiB per second. Over a unix socket. With multiple copies involved until the pixel data reaches its target. Media players know why they don't use this path. ;-)

Also, I don't see the point of the thread, so I started from the previous example.

Attached is an example which actually implements the behaviour proposed by Stefan: Scrolling the old content to the left and only drawing the newest point. For this to work it needs two surfaces, since it has to copy things back and forth between them.

Also, as proposed by Stefan I use cairo_surface_create_similar for creating the surfaces to draw to, so that cairo doesn't have to push "all of that data" to the X server all the time.

CPU usage seems to be a bit lower, but I guess still higher than you wish.

Cheers, Uli

“I’m Olaf and I like warm hugs.”

/*!
* \brief A simple animation using cairo and GTK+
*
* This program shows high /usr/libexec/Xorg load while running full screen on Linux
*
* Compile with:
* gcc `pkg-config --cflags --libs gtk+-3.0` -lm main.c
*/
#include <gtk/gtk.h>
#include <math.h>
#include <cairo.h>
#define NUM_WINDOWS (1u)
#define NUM_POINTS (1000u)
#define PERIOD (100u)
/* Local function prototypes */
static gboolean invalidate_cb(void *);
static gboolean drawing_area_draw_cb (GtkWidget *, cairo_t *, void *);
int
main (int argc, char **argv)
{
unsigned int i;
gtk_init (&argc, &argv);
for (i = 0; i < NUM_WINDOWS; i++)
{
GtkWidget *main_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW (main_window), "Drawing example");
gtk_window_set_default_size (GTK_WINDOW (main_window), 400, 400);
GtkWidget *drawing_area = gtk_drawing_area_new ();
gtk_container_add (GTK_CONTAINER (main_window), drawing_area);
gtk_widget_show_all (main_window);
/* Create a timer to invalidate our window at 60Hz */
g_timeout_add (1000 / 60, invalidate_cb, drawing_area);
/* Connect our redraw callback */
g_signal_connect (drawing_area, "draw", G_CALLBACK (drawing_area_draw_cb), NULL);
/* Connect the destroy signal */
g_signal_connect (main_window, "destroy", G_CALLBACK (gtk_main_quit), NULL);
}
gtk_main ();
}
static gboolean
invalidate_cb (void *ptr)
{
if (GTK_IS_WIDGET(ptr))
{
gtk_widget_queue_draw (GTK_WIDGET (ptr));
return TRUE;
}
return FALSE;
}
static inline float
sine_to_point (int x, int width, int height)
{
(void) width;
return (height / 2.0) * sin (x * 2 * M_PI / (PERIOD)) + height / 2.0;
}
static int current_width = -1;
static int current_height = -1;
static cairo_surface_t *current_source = NULL;
static cairo_surface_t *current_temp = NULL;
static double last_x = -1;
static double last_y = -1;
static int redraw_number = 0;
static void
update_drawing (int width, int height, cairo_surface_t *target)
{
cairo_t *context;
unsigned int i;
if (width != current_width || height != current_height)
{
cairo_surface_destroy (current_source);
cairo_surface_destroy (current_temp);
current_width = width;
current_height = height;
current_source = cairo_surface_create_similar (target, CAIRO_CONTENT_COLOR, NUM_POINTS, height);
current_temp = cairo_surface_create_similar (target, CAIRO_CONTENT_COLOR, NUM_POINTS, height);
context = cairo_create (current_source);
/* Redraw everything */
/* Draw the background */
cairo_set_source_rgb (context, 1, 1, 1);
cairo_paint (context);
/* Draw a moving sine wave */
cairo_set_source_rgb (context, 0.5, 0.5, 0);
cairo_move_to (context, 0, sine_to_point (0 + redraw_number, width, height));
for (i = 1; i < NUM_POINTS; i++)
{
cairo_line_to (context, i, sine_to_point (i + redraw_number, width, height));
}
cairo_get_current_point (context, &last_x, &last_y);
cairo_stroke (context);
cairo_destroy (context);
}
else
{
cairo_surface_t *temp;
/* Move everything left and add a new data point */
context = cairo_create (current_temp);
/* Scroll */
cairo_set_source_surface (context, current_source, -1, 0);
cairo_paint (context);
cairo_set_source_rgb (context, 1, 1, 1);
cairo_rectangle (context, NUM_POINTS-1, 0, 1, height);
cairo_fill (context);
/* Add new point */
cairo_set_source_rgb (context, 0.5, 0.5, 0);
cairo_move_to (context, last_x-1, last_y);
cairo_line_to (context, NUM_POINTS-1, sine_to_point (NUM_POINTS + redraw_number, width, height));
cairo_get_current_point (context, &last_x, &last_y);
cairo_stroke (context);
cairo_destroy (context);
/* Swap surfaces */
temp = current_temp;
current_temp = current_source;
current_source = temp;
}
}
static gboolean
drawing_area_draw_cb (GtkWidget *widget, cairo_t *context, void *ptr)
{
int width, height;
(void) ptr;
width = gtk_widget_get_allocated_width (widget);
height = gtk_widget_get_allocated_height (widget);
update_drawing(width, height, cairo_get_target (context));
/* Draw the background */
cairo_set_source_rgb (context, 1, 1, 1);
cairo_paint (context);
/* Draw the content */
cairo_set_source_surface (context, current_source, 0, 0);
cairo_paint (context);
redraw_number++;
return TRUE;
}
/* EOF */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment