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

Carl Worth cworth at freedesktop.org
Sun Jun 22 14:09:32 PDT 2008


 src/threaded_animation_with_cairo.mdwn |  184 ++++++++++++++++++++++++++++++++-
 1 file changed, 183 insertions(+), 1 deletion(-)

New commits:
commit 96929f1562f592be6fdfd8c0d14b800fc921b804
Author: Carl Worth <cworth at freedesktop.org>
Date:   Sun Jun 22 14:09:31 2008 -0700

    web commit by siefkenj: Added an additional example which uses signals to trigger drawing

diff --git a/src/threaded_animation_with_cairo.mdwn b/src/threaded_animation_with_cairo.mdwn
index ac06f05..12f618e 100644
--- a/src/threaded_animation_with_cairo.mdwn
+++ b/src/threaded_animation_with_cairo.mdwn
@@ -396,8 +396,191 @@ For your compiling pleasure, the full source in proper order.
 		return 0;
 	}
 
+##Alternative Method Using Signals
+
+The previous example has the potential to create a new thread each time the timer_exe function is called.  And, although threads in linux are lightweight, we can make drawing with high framerates more efficient by creating one drawing thread and sending it a signal every time we want it to update.  To do this, we will need to `#include <signal.h>`.  We then will tell our application to watch for `SIGALRM` and we will set up a `sigwaitinfo` in our drawing thread to block until it recieves a `SIGALRM`.
+
+Below is the full source of `threaded_examp`, but with signals used to trigger drawing:
+
+	#include <gtk/gtk.h>
+	#include <unistd.h>
+	#include <pthread.h>
+	#include <signal.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){
+
+		//prepare to trap our SIGALRM so we can draw when we recieve it!
+		siginfo_t info;
+		sigset_t sigset;
+
+		sigemptyset(&sigset);
+		sigaddset(&sigset, SIGALRM);
+
+		while(1){
+			//wait for our SIGALRM.  Upon receipt, draw our stuff.  Then, do it again!
+			while (sigwaitinfo(&sigset, &info) > 0) {
+			
+				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;
+				
+			}
+		}
+	}
+
+	gboolean timer_exe(GtkWidget * window){
+		static int first_time = 1;
+		//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 this is the first time, create the drawing thread
+		static pthread_t thread_info;
+		if(first_time == 1){
+			int  iret;
+			iret = pthread_create( &thread_info, NULL, do_draw, NULL);
+		}
+
+		//if we are not currently drawing anything, send a SIGALRM signal
+		//to our thread and tell it to update our pixmap
+		if(drawing_status == 0){
+			pthread_kill(thread_info, SIGALRM);
+		}
+
+		//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);
+
+
+		first_time = 0;
+		return TRUE;
+
+	}
+
+
+	int main (int argc, char *argv[]){
+
+		//Block SIGALRM in the main thread
+		sigset_t sigset;
+		sigemptyset(&sigset);
+		sigaddset(&sigset, SIGALRM);
+		pthread_sigmask(SIG_BLOCK, &sigset, NULL);
+
+		//we need to initialize all these functions so that gtk knows
+		//to be thread-aware
+		if (!g_thread_supported ()){ 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
+ [2] http://www.yolinux.com/TUTORIALS/LinuxTutorialPosixThreads.html


More information about the cairo-commit mailing list