[cairo-commit] [cairo-www] src/threaded_animation_with_cairo.mdwn
Carl Worth
cworth at freedesktop.org
Thu Jan 17 20:05:24 PST 2008
src/threaded_animation_with_cairo.mdwn | 1 -
1 file changed, 1 deletion(-)
New commits:
commit 20c271c698d5a27857b4f83f8d28f05600e50a32
Author: Carl Worth <cworth at freedesktop.org>
Date: Thu Jan 17 20:05:23 2008 -0800
web commit by Albert: Threading tutorial first draft
diff --git a/src/threaded_animation_with_cairo.mdwn b/src/threaded_animation_with_cairo.mdwn
index 09522f8..61cd1be 100644
--- a/src/threaded_animation_with_cairo.mdwn
+++ b/src/threaded_animation_with_cairo.mdwn
@@ -122,4 +122,266 @@ Before, we implement our timer function, we must consider one important issue.
* `g_atomic_int_get(¤tly_drawing)` is a thread-safe way to get the value of our global integer `currently_drawing`. Using this function allows us to avoid the possibility of reading a number at the same moment our other thread is trying to change it. It is also much easier to implement than mutexes for reading a single integer.
* `pthread_create( &thread_info, NULL, do_draw, NULL)` is the `unistd.h` way to launch the function `do_draw()` as a separate thread. (The final `NULL` is actually a `(void *)` to a data structure that we pass to `do_draw()`.)
- * `gtk_widget_queue_draw_area(window, 0, 0, width, height)` sends an artificial expose event with upper left corner 0,0 and width and heigh of `width`, `height`, respectively. This allows our `expose_event` to do the actual painting.
\ No newline at end of file
+ * `gtk_widget_queue_draw_area(window, 0, 0, width, height)` sends an artificial expose event with upper left corner 0,0 and width and heigh of `width`, `height`, respectively. This allows our `expose_event` to do the actual painting.
+
+##Do the Drawing
+
+We now have a `timer_exe` function that launches our `do_draw()` in a new thread for us. Now lets implement `do_draw()`. Because we are now in a thread that is running outside of `gtk_main()`, we need to encase any calls involving gtk objects between `gdk_threads_enter()` and `gdk_threads_leave()`. This includes interactions with `GdkPixmap`s!
+
+_Note: the `gtk_main()` thread will be blocked until the code between `gdk_threads_enter()` and `gdk_threads_leave()` has executed, so don't put avoidable, processor-intensive code there._
+
+Our goal is to draw with cairo and have our drawing end up in `pixmap`, our global pixmap to be drawn upon `expose_event`. However, any cairo context created from `pixmap` must be accessed between `gdk_threads_enter()` and `gdk_threads_leave()`. If we did all our drawing this way, it would defeat the purpose of multi-threading, as we would have no speedup. We need to create a cairo context that that is independent from GTK so that we may draw upon it without a lock. After we have done the processor-intensive drawing, we can do the relatively-quick operation of copying our drawing to `pixmap` between `gdk_threads_enter()` and `gdk_threads_leave()`.
+
+The solution comes from `cairo_image_surface_create()`, which will create an area in memory from which we can create a cairo context that can be drawn upon without fear of thread issues.
+
+
+ static int currently_drawing = 0;
+
+ //do_draw will be executed in a separate thread whenever we would like to update
+ //our animation
+ void *do_draw(void *ptr){
+
+ currently_drawing = 1;
+
+ int width, height;
+ gdk_threads_enter();
+ gdk_drawable_get_size(pixmap, &width, &height);
+ gdk_threads_leave();
+
+ //create a gtk-independant surface to draw on
+ cairo_surface_t *cst = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
+ cairo_t *cr = cairo_create(cst);
+
+ /* do all your drawing here */
+
+ cairo_destroy(cr);
+
+
+ //When dealing with gdkPixmap's, we need to make sure not to
+ //access them from outside gtk_main().
+ gdk_threads_enter();
+
+ cairo_t *cr_pixmap = gdk_cairo_create(pixmap);
+ cairo_set_source_surface (cr_pixmap, cst, 0, 0);
+ cairo_paint(cr_pixmap);
+ cairo_destroy(cr_pixmap);
+
+ gdk_threads_leave();
+
+ cairo_surface_destroy(cst);
+
+ currently_drawing = 0;
+
+ return NULL;
+ }
+
+ * `currently_drawing` is a global variable that tells `timer_exe` whether or not we've finished
+ * `gdk_drawable_get_size(pixmap, &width, &height)` is used to get the size of `pixmap`, our eventual target so that we create a cairo context of equivalent size. Note that
+our call to `gdk_drawable_get_size()` is encased between `gdk_threads_enter()` and `gdk_threads_leave()`
+ * `cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height)` creates a region in memory suitable for a 32-bit image of width `width` and height `height`.
+ * `cairo_create(cst)` creates a cairo context out of our memmap so that we may draw upon it.
+ * `cairo_destroy(cr)` destroys the cairo context we used. We can safely destroy the drawing context as soon as we've finished drawing. We don't want to forget this and have a memory leak.
+ * `gdk_cairo_create(pixmap)` creates a cairo context out of `pixmap` so that we can draw the contents of `cst` onto it. Note that this is done between `gdk_threads_enter()` and `gdk_threads_leave()`.
+ * `cairo_set_source_surface (cr_pixmap, cst, 0, 0)` will copy the contents of `cst` to the upper left corner of `cr_pixmap`, which is the cairo context currently associated with `pixmap`.
+
+##The Callbacks
+The last thing we need to do is handle our callbacks. These are `on_window_configure_event()` where we will hand resize-events and `on_window_expose_event()` where we will paint `pixmap` to the screen. The only thing of note is in `on_window_configure_event()`. Here we create a new pixmap of the same size as our window. We then delete the old one. Doing exactly these steps in this order would result in a bunch of garbage being painted on the screen (because the new pixmap we create is not initialized). Since our drawing operation may take quite a while, we minimize the uglyness by copying the contens of our old pixmap to our new pixmap before we destroy the old one.
+
+ gboolean on_window_configure_event(GtkWidget * da, GdkEventConfigure * event, gpointer user_data){
+ static int oldw = 0;
+ static int oldh = 0;
+ //make our selves a properly sized pixmap if our window has been resized
+ if (oldw != event->width || oldh != event->height){
+ //create our new pixmap with the correct size.
+ GdkPixmap *tmppixmap = gdk_pixmap_new(da->window, event->width, event->height, -1);
+ //copy the contents of the old pixmap to the new pixmap. This keeps ugly uninitialized
+ //pixmaps from being painted upon resize
+ int minw = oldw, minh = oldh;
+ if( event->width < minw ){ minw = event->width; }
+ if( event->height < minh ){ minh = event->height; }
+ gdk_draw_drawable(tmppixmap, da->style->fg_gc[GTK_WIDGET_STATE(da)], pixmap, 0, 0, 0, 0, minw, minh);
+ //we're done with our old pixmap, so we can get rid of it and replace it with our properly-sized one.
+ g_object_unref(pixmap);
+ pixmap = tmppixmap;
+ }
+ oldw = event->width;
+ oldh = event->height;
+ return TRUE;
+ }
+
+ gboolean on_window_expose_event(GtkWidget * da, GdkEventExpose * event, gpointer user_data){
+ gdk_draw_drawable(da->window,
+ da->style->fg_gc[GTK_WIDGET_STATE(da)], pixmap,
+ // Only copy the area that was exposed.
+ event->area.x, event->area.y,
+ event->area.x, event->area.y,
+ event->area.width, event->area.height);
+ return TRUE;
+ }
+
+##Compiling
+
+To compile, use `pkg-config --cflags --libs gtk+-2.0 --libs gthread-2.0` For example, if you save this code to `threaded_examp.c`, you would compile it with the command:
+
+ gcc threaded_examp.c `pkg-config --cflags --libs gtk+-2.0 --libs gthread-2.0` -Wall -o threaded_examp
+
+##Full Source
+
+For your compiling pleasure, the full source in proper order.
+
+ #include <gtk/gtk.h>
+ #include <unistd.h>
+ #include <pthread.h>
+
+ //the global pixmap that will serve as our buffer
+ static GdkPixmap *pixmap = NULL;
+
+ gboolean on_window_configure_event(GtkWidget * da, GdkEventConfigure * event, gpointer user_data){
+ static int oldw = 0;
+ static int oldh = 0;
+ //make our selves a properly sized pixmap if our window has been resized
+ if (oldw != event->width || oldh != event->height){
+ //create our new pixmap with the correct size.
+ GdkPixmap *tmppixmap = gdk_pixmap_new(da->window, event->width, event->height, -1);
+ //copy the contents of the old pixmap to the new pixmap. This keeps ugly uninitialized
+ //pixmaps from being painted upon resize
+ int minw = oldw, minh = oldh;
+ if( event->width < minw ){ minw = event->width; }
+ if( event->height < minh ){ minh = event->height; }
+ gdk_draw_drawable(tmppixmap, da->style->fg_gc[GTK_WIDGET_STATE(da)], pixmap, 0, 0, 0, 0, minw, minh);
+ //we're done with our old pixmap, so we can get rid of it and replace it with our properly-sized one.
+ g_object_unref(pixmap);
+ pixmap = tmppixmap;
+ }
+ oldw = event->width;
+ oldh = event->height;
+ return TRUE;
+ }
+
+ gboolean on_window_expose_event(GtkWidget * da, GdkEventExpose * event, gpointer user_data){
+ gdk_draw_drawable(da->window,
+ da->style->fg_gc[GTK_WIDGET_STATE(da)], pixmap,
+ // Only copy the area that was exposed.
+ event->area.x, event->area.y,
+ event->area.x, event->area.y,
+ event->area.width, event->area.height);
+ return TRUE;
+ }
+
+
+ static int currently_drawing = 0;
+ //do_draw will be executed in a separate thread whenever we would like to update
+ //our animation
+ void *do_draw(void *ptr){
+
+ currently_drawing = 1;
+
+ int width, height;
+ gdk_threads_enter();
+ gdk_drawable_get_size(pixmap, &width, &height);
+ gdk_threads_leave();
+
+ //create a gtk-independant surface to draw on
+ cairo_surface_t *cst = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
+ cairo_t *cr = cairo_create(cst);
+
+ //do some time-consuming drawing
+ static int i = 0;
+ ++i; i = i % 300; //give a little movement to our animation
+ cairo_set_source_rgb (cr, .9, .9, .9);
+ cairo_paint(cr);
+ int j,k;
+ for(k=0; k<100; ++k){ //lets just redraw lots of times to use a lot of proc power
+ for(j=0; j < 1000; ++j){
+ cairo_set_source_rgb (cr, (double)j/1000.0, (double)j/1000.0, 1.0 - (double)j/1000.0);
+ cairo_move_to(cr, i,j/2);
+ cairo_line_to(cr, i+100,j/2);
+ cairo_stroke(cr);
+ }
+ }
+ cairo_destroy(cr);
+
+
+ //When dealing with gdkPixmap's, we need to make sure not to
+ //access them from outside gtk_main().
+ gdk_threads_enter();
+
+ cairo_t *cr_pixmap = gdk_cairo_create(pixmap);
+ cairo_set_source_surface (cr_pixmap, cst, 0, 0);
+ cairo_paint(cr_pixmap);
+ cairo_destroy(cr_pixmap);
+
+ gdk_threads_leave();
+
+ cairo_surface_destroy(cst);
+
+ currently_drawing = 0;
+
+ return NULL;
+ }
+
+ gboolean timer_exe(GtkWidget * window){
+
+ //use a safe function to get the value of currently_drawing so
+ //we don't run into the usual multithreading issues
+ int drawing_status = g_atomic_int_get(¤tly_drawing);
+
+ //if we are not currently drawing anything, launch a thread to
+ //update our pixmap
+ if(drawing_status == 0){
+ pthread_t thread_info;
+ int iret;
+ iret = pthread_create( &thread_info, NULL, do_draw, NULL);
+ }
+
+ //tell our window it is time to draw our animation.
+ int width, height;
+ gdk_drawable_get_size(pixmap, &width, &height);
+ gtk_widget_queue_draw_area(window, 0, 0, width, height);
+
+ return TRUE;
+
+ }
+
+
+ int main (int argc, char *argv[]){
+
+
+ //we need to initialize all these functions so that gtk knows
+ //to be thread-aware
+ g_thread_init(NULL);
+ gdk_threads_init();
+ gdk_threads_enter();
+
+ gtk_init(&argc, &argv);
+
+ GtkWidget *window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(gtk_main_quit), NULL);
+ g_signal_connect(G_OBJECT(window), "expose_event", G_CALLBACK(on_window_expose_event), NULL);
+ g_signal_connect(G_OBJECT(window), "configure_event", G_CALLBACK(on_window_configure_event), NULL);
+
+ //this must be done before we define our pixmap so that it can reference
+ //the colour depth and such
+ gtk_widget_show_all(window);
+
+ //set up our pixmap so it is ready for drawing
+ pixmap = gdk_pixmap_new(window->window,500,500,-1);
+ //because we will be painting our pixmap manually during expose events
+ //we can turn off gtk's automatic painting and double buffering routines.
+ gtk_widget_set_app_paintable(window, TRUE);
+ gtk_widget_set_double_buffered(window, FALSE);
+
+ (void)g_timeout_add(33, (GSourceFunc)timer_exe, window);
+
+
+ gtk_main();
+ gdk_threads_leave();
+
+ return 0;
+ }
+
+##References
+
+ [1] http://research.operationaldynamics.com/blogs/andrew/software/gnome-desktop/gtk-thread-awareness.html
+
+ [2] http://www.yolinux.com/TUTORIALS/LinuxTutorialPosixThreads.html
\ No newline at end of file
More information about the cairo-commit
mailing list