[cairo-commit] [cairo-www] src/threaded_animation_with_cairo.mdwn

Carl Worth cworth at freedesktop.org
Thu Jan 17 19:16:12 PST 2008


 src/threaded_animation_with_cairo.mdwn |    1 -
 1 file changed, 1 deletion(-)

New commits:
commit 55805118ee66a9a8c643be637d75d5969c4a7025
Author: Carl Worth <cworth at freedesktop.org>
Date:   Thu Jan 17 19:16:11 2008 -0800

    web commit by Albert

diff --git a/src/threaded_animation_with_cairo.mdwn b/src/threaded_animation_with_cairo.mdwn
index 78206b9..09522f8 100644
--- a/src/threaded_animation_with_cairo.mdwn
+++ b/src/threaded_animation_with_cairo.mdwn
@@ -38,4 +38,88 @@ A minimal thread-aware gtk program might look like:
 		gdk_threads_leave();
 
 		return 0;
-	}
\ No newline at end of file
+	}
+
+##Setting up Callbacks for Animation
+We will use a `g_timeout_add` to call our `do_draw()` routine at 30 fps.  Eventually, our `do_draw` will draw to a global `GdkPixmap` and we will paint this pixmap to the screen upon an `expose_event`.  It is considered good practice to draw on a widget during its `expose_event` only.  
+
+	#include <gtk/gtk.h>
+	#include <unistd.h>
+	#include <pthread.h>
+
+	//the global pixmap that will serve as our buffer
+	static GdkPixmap *pixmap = NULL;
+
+	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;
+	}
+
+ * `g_signal_connect(G_OBJECT(window), "configure_event", G_CALLBACK(on_window_configure_event), NULL)` adds a callback to the configure event so that we can detect and handle resizes.  
+ * `gtk_widget_set_app_paintable(window, TRUE)` and `gtk_widget_set_double_buffered(window, FALSE)` tells gtk that we will be doing our own buffering of the window
+ * `(void)g_timeout_add(33, (GSourceFunc)timer_exe, window)` adds a timer that will be executed by `gtk_main()` 30 times a second (unless `gtk_main()` is too busy with other things).  We pass it the name of the function we'd like to call `timer_exe` cast as a `GSourceFunc` and we also pass it a pointer to the object we'd like to draw on, this time 
+`window`.
+
+##Timer Function
+Our timer function will be responsible for launching a new thread that executes our `do_draw()` function.  It will then artifically send an expose event to our window so that
+it knows it should redraw (sending an expose event rather than doing the drawing ourself will allow gtk to process events closer tot he way it wants).
+
+Before, we implement our timer function, we must consider one important issue.  If our drawing application is going to take a long time (longer than 1/30th of a second) then simply launching a new drawing thread every time the timer executes could result in a pile-up of threads and a lot of memory badness.  To solve this problem, we will have a variable `currently_drawing`.  If `currently_drawing=0` then it is safe to launch a drawing thread.  If it isn't, we know we haven't finished the drawing we started last time!  Using this solution instead of sending a signal from our thread when it finishes, results in simpler code and has the added benefit that our framerate is limited to whatever rate our timer is called at.  
+
+	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;
+
+	}
+
+ * `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


More information about the cairo-commit mailing list