[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(&currently_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(&currently_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