From 1626d6132e9bab267463b5f532567e22006771db Mon Sep 17 00:00:00 2001 From: Neil Roberts Date: Wed, 25 Feb 2009 17:10:54 +0000 Subject: [PATCH 1/7] [clutter-texture] Use a GThreadPool for loading async textures Instead of creating a separate thread for each texture load, the loads are now queued in a thread pool which will use at most 3 textures at a time. This avoids the potentially large overhead of creating and destroying a thread for every image while still avoiding throttling the CPU if a large number of textures are loaded at the same time. If the load is cancelled the old code needed to join the thread which effectively meant it had to wait for the load to finish. With this model the threads are owned by the thread pool so there is no need to wait for them to finish. Instead, each thread is given its own thread data which we can use to mark the thread as aborted. Once the load is finished the thread will check whether it is aborted before setting the texture data. That way the ClutterTexture can just disown threads that it needs to cancel. --- clutter/clutter-texture.c | 293 ++++++++++++++++++++++++-------------- 1 file changed, 183 insertions(+), 110 deletions(-) diff --git a/clutter/clutter-texture.c b/clutter/clutter-texture.c index 6d4349ee7..141da4f08 100644 --- a/clutter/clutter-texture.c +++ b/clutter/clutter-texture.c @@ -73,6 +73,8 @@ G_DEFINE_TYPE_WITH_CODE (ClutterTexture, #define CLUTTER_TEXTURE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLUTTER_TYPE_TEXTURE, ClutterTexturePrivate)) +typedef struct _ClutterTextureAsyncData ClutterTextureAsyncData; + struct _ClutterTexturePrivate { gint width; @@ -98,13 +100,33 @@ struct _ClutterTexturePrivate guint in_dispose : 1; guint keep_aspect_ratio : 1; guint load_async : 1; - - GThread *load_thread; - guint load_idle; - gchar *load_filename; - CoglBitmap *load_bitmap; - GError *load_error; - gboolean abort; + + ClutterTextureAsyncData *async_data; +}; + +struct _ClutterTextureAsyncData +{ + /* Mutex used to synchronize setting the abort flag */ + GMutex *mutex; + + /* If set to true, the loaded data will be discarded */ + gboolean abort; + + /* The texture for which the data is being loaded */ + ClutterTexture *texture; + + /* Source ID of the idle handler for loading. If this is zero then + the data is being loaded in a thread from the thread pool. Once + the thread is finished it will be converted to idle load handler + and load_idle will be nonzero. If load_idle is nonzero then the + rest of the load can safely be aborted by just removing the + source, otherwise the abort flag needs to be set and the data + should be disowned */ + guint load_idle; + + gchar *load_filename; + CoglBitmap *load_bitmap; + GError *load_error; }; enum @@ -136,6 +158,8 @@ enum static int texture_signals[LAST_SIGNAL] = { 0 }; +static GThreadPool *async_thread_pool = NULL; + static void texture_fbo_free_resources (ClutterTexture *texture); @@ -633,6 +657,29 @@ clutter_texture_paint (ClutterActor *self) 0, 0, t_w, t_h); } +static void +clutter_texture_async_data_free (ClutterTextureAsyncData *data) +{ + /* This function should only be called either from the main thread + once it is known that the load thread has completed or from the + load thread itself if the abort flag is true (in which case the + main thread has disowned the data) */ + + if (data->load_filename) + g_free (data->load_filename); + + if (data->load_bitmap) + cogl_bitmap_free (data->load_bitmap); + + if (data->load_error) + g_error_free (data->load_error); + + if (data->mutex) + g_mutex_free (data->mutex); + + g_slice_free (ClutterTextureAsyncData, data); +} + /* * clutter_texture_async_load_cancel: * @texture: a #ClutterTexture @@ -645,32 +692,40 @@ clutter_texture_async_load_cancel (ClutterTexture *texture) { ClutterTexturePrivate *priv = texture->priv; - if (priv->load_thread) + if (priv->async_data) { - priv->abort = TRUE; - g_thread_join (priv->load_thread); - priv->load_thread = NULL; - } + GMutex *mutex = priv->async_data->mutex; - if (priv->load_idle) - { - g_source_remove (priv->load_idle); - priv->load_idle = 0; - } + /* The mutex will only be NULL if the no thread was used for + this load, in which case there's no need for any + synchronization */ + if (mutex) + g_mutex_lock (mutex); - if (priv->load_error) - { - g_error_free (priv->load_error); - priv->load_error = NULL; - } + /* If there is no thread behind this load then we can just abort + the idle handler and destroy the load data immediately */ + if (priv->async_data->load_idle) + { + g_source_remove (priv->async_data->load_idle); + priv->async_data->load_idle = 0; - if (priv->load_bitmap) - { - cogl_bitmap_free (priv->load_bitmap); - priv->load_bitmap = NULL; - } + if (mutex) + g_mutex_unlock (mutex); - g_free (priv->load_filename); + clutter_texture_async_data_free (priv->async_data); + } + else + { + /* Otherwise we need to tell the thread to abort and disown + the data */ + priv->async_data->abort = TRUE; + + if (mutex) + g_mutex_unlock (mutex); + } + + priv->async_data = NULL; + } } static void @@ -1553,14 +1608,16 @@ clutter_texture_set_from_yuv_data (ClutterTexture *texture, /* * clutter_texture_async_load_complete: * @self: a #ClutterTexture + * @bitmap: a #CoglBitmap * @error: load error * - * If @error is %NULL, loads the #CoglBitmap into a #CoglTexture. + * If @error is %NULL, loads @bitmap into a #CoglTexture. * * This function emits the ::load-finished signal on @self. */ static void clutter_texture_async_load_complete (ClutterTexture *self, + CoglBitmap *bitmap, const GError *error) { ClutterTexturePrivate *priv = self->priv; @@ -1568,6 +1625,8 @@ clutter_texture_async_load_complete (ClutterTexture *self, CoglTextureFlags flags = COGL_TEXTURE_NONE; gint waste = -1; + priv->async_data = NULL; + if (error == NULL) { if (!priv->no_slice) @@ -1576,14 +1635,11 @@ clutter_texture_async_load_complete (ClutterTexture *self, if (priv->filter_quality == CLUTTER_TEXTURE_QUALITY_HIGH) flags |= COGL_TEXTURE_AUTO_MIPMAP; - handle = cogl_texture_new_from_bitmap (priv->load_bitmap, + handle = cogl_texture_new_from_bitmap (bitmap, waste, flags, COGL_PIXEL_FORMAT_ANY); clutter_texture_set_cogl_texture (self, handle); cogl_texture_unref (handle); - - cogl_bitmap_free (priv->load_bitmap); - priv->load_bitmap = NULL; } g_signal_emit (self, texture_signals[LOAD_FINISHED], 0, error); @@ -1592,96 +1648,100 @@ clutter_texture_async_load_complete (ClutterTexture *self, } static gboolean -clutter_texture_thread_cb (gpointer data) +clutter_texture_thread_idle_func (gpointer user_data) { - ClutterTexture *self = data; - ClutterTexturePrivate *priv = self->priv; + ClutterTextureAsyncData *data = user_data; - priv->load_idle = 0; + /* Grab the mutex so we can be sure the thread has unlocked it + before we destroy it */ + g_mutex_lock (data->mutex); + g_mutex_unlock (data->mutex); - if (priv->load_thread) - { - g_thread_join (priv->load_thread); - priv->load_thread = NULL; - } - else - return FALSE; + clutter_texture_async_load_complete (data->texture, data->load_bitmap, + data->load_error); - clutter_texture_async_load_complete (self, priv->load_error); + clutter_texture_async_data_free (data); - if (priv->load_error) - { - g_error_free (priv->load_error); - priv->load_error = NULL; - } - return FALSE; } -static gpointer -clutter_texture_thread_func (gpointer data) +static void +clutter_texture_thread_func (gpointer user_data, gpointer pool_data) { - static GStaticMutex thread_load_mutex = G_STATIC_MUTEX_INIT; - ClutterTexture *self = data; - ClutterTexturePrivate *priv = self->priv; + ClutterTextureAsyncData *data = user_data; + gboolean should_abort; - /* we aquire the shared lock, only one thread is allowed to - * be loading at a time - */ - g_static_mutex_lock (&thread_load_mutex); - if (priv->abort) + /* Make sure we haven't been told to abort before the thread had a + chance to run */ + g_mutex_lock (data->mutex); + should_abort = data->abort; + g_mutex_unlock (data->mutex); + + if (should_abort) { - g_static_mutex_unlock (&thread_load_mutex); - g_free (priv->load_filename); - priv->load_filename = NULL; - return NULL; + /* If we've been told to abort then main thread has disowned the + async data and we need to free it */ + clutter_texture_async_data_free (data); + return; } - /* Try loading with imaging backend */ - priv->load_bitmap = cogl_bitmap_new_from_file (priv->load_filename, - &priv->load_error); - g_static_mutex_unlock (&thread_load_mutex); - g_free (priv->load_filename); - priv->load_filename = NULL; - /* make sure we load the image in the main thread, where we - * hold the main Clutter lock - */ - priv->load_idle = - clutter_threads_add_idle (clutter_texture_thread_cb, self); + data->load_bitmap = cogl_bitmap_new_from_file (data->load_filename, + &data->load_error); - return NULL; + /* Check again if we've been told to abort */ + g_mutex_lock (data->mutex); + + if (data->abort) + { + g_mutex_unlock (data->mutex); + + clutter_texture_async_data_free (data); + } + else + { + /* Make sure we give the image to GL in the main thread, where we + * hold the main Clutter lock. Once load_idle is non-NULL then the + * main thread is guaranteed not to set the abort flag. It can't + * set it while we're holding the mutex so we can safely start the + * idle handler now without the possibility of calling the + * callback after it is aborted */ + data->load_idle = + clutter_threads_add_idle (clutter_texture_thread_idle_func, data); + + g_mutex_unlock (data->mutex); + } + + return; } static gboolean -clutter_texture_idle_func (gpointer data) +clutter_texture_idle_func (gpointer user_data) { - ClutterTexture *self = data; - ClutterTexturePrivate *priv = self->priv; - GError *internal_error; + ClutterTextureAsyncData *data = user_data; + GError *internal_error = NULL; - internal_error = NULL; - priv->load_bitmap = cogl_bitmap_new_from_file (priv->load_filename, + data->load_bitmap = cogl_bitmap_new_from_file (data->load_filename, &internal_error); - clutter_texture_async_load_complete (self, internal_error); - - g_free (priv->load_filename); - priv->load_filename = NULL; + clutter_texture_async_load_complete (data->texture, data->load_bitmap, + internal_error); if (internal_error) g_error_free (internal_error); + clutter_texture_async_data_free (data); + return FALSE; } - /* * clutter_texture_async_load: - * @self: a #ClutterTexture + * @self: a #ClutterTExture + * @filename: name of the file to load * @error: return location for a #GError * * Starts an asynchronous load of the file name stored inside - * the load_filename private member. + * the load_filename member of @data. * * If threading is enabled we use a GThread to perform the actual * I/O; if threading is not enabled, we use an idle GSource. @@ -1699,20 +1759,20 @@ clutter_texture_idle_func (gpointer data) * initiated, %FALSE otherwise */ static gboolean -clutter_texture_async_load (ClutterTexture *self, - GError **error) +clutter_texture_async_load (ClutterTexture *self, + const gchar *filename, + GError **error) { ClutterTexturePrivate *priv = self->priv; + ClutterTextureAsyncData *data; gint width, height; gboolean res; - g_assert (priv->load_filename != NULL); - /* ask the file for a size; if we cannot get the size then * there's no point in even continuing the asynchronous * loading, so we just stop there */ - res = cogl_bitmap_get_size_from_file (priv->load_filename, + res = cogl_bitmap_get_size_from_file (filename, &width, &height); if (!res) @@ -1720,6 +1780,7 @@ clutter_texture_async_load (ClutterTexture *self, g_set_error (error, CLUTTER_TEXTURE_ERROR, CLUTTER_TEXTURE_ERROR_BAD_FORMAT, "Failed to create COGL texture"); + clutter_texture_async_data_free (data); return FALSE; } else @@ -1728,22 +1789,40 @@ clutter_texture_async_load (ClutterTexture *self, priv->height = height; } + clutter_texture_async_load_cancel (self); + + data = g_slice_new (ClutterTextureAsyncData); + + data->abort = FALSE; + data->texture = self; + data->load_idle = 0; + data->load_filename = g_strdup (filename); + data->load_bitmap = NULL; + data->load_error = NULL; + + priv->async_data = data; + if (g_thread_supported ()) { - priv->load_thread = - g_thread_create ((GThreadFunc) clutter_texture_thread_func, - self, TRUE, - error); + data->mutex = g_mutex_new (); - return priv->load_thread != NULL? TRUE : FALSE; + if (async_thread_pool == NULL) + /* This apparently can't fail if exclusive == FALSE */ + async_thread_pool + = g_thread_pool_new (clutter_texture_thread_func, + NULL, 3, FALSE, NULL); + + g_thread_pool_push (async_thread_pool, data, NULL); } else { - priv->load_idle = - clutter_threads_add_idle (clutter_texture_idle_func, self); + data->mutex = NULL; - return TRUE; + data->load_idle + = clutter_threads_add_idle (clutter_texture_idle_func, data); } + + return TRUE; } /** @@ -1782,13 +1861,7 @@ clutter_texture_set_from_file (ClutterTexture *texture, g_return_val_if_fail (error == NULL || *error == NULL, FALSE); if (priv->load_async) - { - clutter_texture_async_load_cancel (texture); - - priv->load_filename = g_strdup (filename); - - return clutter_texture_async_load (texture, error); - } + return clutter_texture_async_load (texture, filename, error); if (!priv->no_slice) max_waste = priv->max_tile_waste; From e744066e7d82813d9d70d032068c7e6d51fc9f14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Kol=C3=A5s?= Date: Tue, 10 Mar 2009 13:00:57 +0000 Subject: [PATCH 2/7] Change the default size of thread pool from 3 to 1 Concurrently decoding three image files can stall animations even on a dual core system, reducing to 1 concurrent image decoding thread. --- clutter/clutter-texture.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clutter/clutter-texture.c b/clutter/clutter-texture.c index 141da4f08..88329c2b5 100644 --- a/clutter/clutter-texture.c +++ b/clutter/clutter-texture.c @@ -1810,7 +1810,7 @@ clutter_texture_async_load (ClutterTexture *self, /* This apparently can't fail if exclusive == FALSE */ async_thread_pool = g_thread_pool_new (clutter_texture_thread_func, - NULL, 3, FALSE, NULL); + NULL, 1, FALSE, NULL); g_thread_pool_push (async_thread_pool, data, NULL); } From 5d3d9713899919d4cc7e42c5b08a9835b15e5c9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Kol=C3=A5s?= Date: Wed, 11 Mar 2009 18:26:30 +0000 Subject: [PATCH 3/7] [tests] Added test-texture-async --- clutter/clutter-texture.c | 74 ++++++++++++++++++++++--- tests/interactive/Makefile.am | 1 + tests/interactive/test-texture-async.c | 76 ++++++++++++++++++++++++++ 3 files changed, 144 insertions(+), 7 deletions(-) create mode 100644 tests/interactive/test-texture-async.c diff --git a/clutter/clutter-texture.c b/clutter/clutter-texture.c index 88329c2b5..e87bfb7ec 100644 --- a/clutter/clutter-texture.c +++ b/clutter/clutter-texture.c @@ -100,6 +100,8 @@ struct _ClutterTexturePrivate guint in_dispose : 1; guint keep_aspect_ratio : 1; guint load_async : 1; + guint load_size_async : 1; + guint load_data_async : 1; ClutterTextureAsyncData *async_data; }; @@ -143,7 +145,9 @@ enum PROP_COGL_MATERIAL, PROP_FILENAME, PROP_KEEP_ASPECT_RATIO, - PROP_LOAD_ASYNC + PROP_LOAD_ASYNC, + PROP_LOAD_DATA_ASYNC, + PROP_LOAD_SIZE_ASYNC }; enum @@ -152,7 +156,6 @@ enum PIXBUF_CHANGE, LOAD_SUCCESS, LOAD_FINISHED, - LAST_SIGNAL }; @@ -839,6 +842,12 @@ clutter_texture_set_property (GObject *object, case PROP_LOAD_ASYNC: priv->load_async = g_value_get_boolean (value); break; + case PROP_LOAD_DATA_ASYNC: + priv->load_data_async = g_value_get_boolean (value); + break; + case PROP_LOAD_SIZE_ASYNC: + priv->load_size_async = g_value_get_boolean (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -897,6 +906,12 @@ clutter_texture_get_property (GObject *object, case PROP_LOAD_ASYNC: g_value_set_boolean (value, priv->load_async); break; + case PROP_LOAD_DATA_ASYNC: + g_value_set_boolean (value, priv->load_data_async); + break; + case PROP_LOAD_SIZE_ASYNC: + g_value_set_boolean (value, priv->load_size_async); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -1032,6 +1047,24 @@ clutter_texture_class_init (ClutterTextureClass *klass) FALSE, CLUTTER_PARAM_READWRITE)); + /** + * ClutterTexture:load-size-sync: + * + * When set to TRUE clutter will not block loading the size initially, + * when used in this manner the size of the actor will change (and the + * load-size-complete signal will be fired when the size is available). + * + * Since: 1.0 + */ + g_object_class_install_property + (gobject_class, PROP_LOAD_SIZE_ASYNC, + g_param_spec_boolean ("load-size-async", + "Load size asynchronously", + "If set to TRUE clutter will not block until it has " + "loaded the size of the file.", + FALSE, + CLUTTER_PARAM_READWRITE)); + /** * ClutterTexture:load-async: * @@ -1046,6 +1079,24 @@ clutter_texture_class_init (ClutterTextureClass *klass) * * Since: 1.0 */ + g_object_class_install_property + (gobject_class, PROP_LOAD_ASYNC, + g_param_spec_boolean ("load-data-async", + "Load data asynchronously", + "Load files inside a thread to avoid blocking when " + "loading images.", + FALSE, + CLUTTER_PARAM_READWRITE)); + + + /** + * ClutterTexture:load-async: + * + * Load texture fully asynchronosuly, loading both the size and data + * in a separate thread. + * + * Since: 1.0 + */ g_object_class_install_property (gobject_class, PROP_LOAD_ASYNC, g_param_spec_boolean ("load-async", @@ -1053,7 +1104,7 @@ clutter_texture_class_init (ClutterTextureClass *klass) "Load files inside a thread to avoid blocking when " "loading images.", FALSE, - CLUTTER_PARAM_READWRITE)); + CLUTTER_PARAM_WRITABLE)); /** * ClutterTexture::size-change: @@ -1639,6 +1690,8 @@ clutter_texture_async_load_complete (ClutterTexture *self, waste, flags, COGL_PIXEL_FORMAT_ANY); clutter_texture_set_cogl_texture (self, handle); + /*clutter_actor_set_size (self, cogl_texture_get_width (handle), + cogl_texture_get_height (handle));*/ cogl_texture_unref (handle); } @@ -1687,6 +1740,7 @@ clutter_texture_thread_func (gpointer user_data, gpointer pool_data) data->load_bitmap = cogl_bitmap_new_from_file (data->load_filename, &data->load_error); + g_print ("."); /* Check again if we've been told to abort */ g_mutex_lock (data->mutex); @@ -1772,15 +1826,21 @@ clutter_texture_async_load (ClutterTexture *self, * there's no point in even continuing the asynchronous * loading, so we just stop there */ - res = cogl_bitmap_get_size_from_file (filename, - &width, - &height); + + if (priv->load_size_async) + { + res = TRUE; + } + else + { + res = cogl_bitmap_get_size_from_file (filename, &width, &height); + } + if (!res) { g_set_error (error, CLUTTER_TEXTURE_ERROR, CLUTTER_TEXTURE_ERROR_BAD_FORMAT, "Failed to create COGL texture"); - clutter_texture_async_data_free (data); return FALSE; } else diff --git a/tests/interactive/Makefile.am b/tests/interactive/Makefile.am index f9043dff1..4f0bf3b70 100644 --- a/tests/interactive/Makefile.am +++ b/tests/interactive/Makefile.am @@ -1,6 +1,7 @@ UNIT_TESTS = \ test-textures.c \ + test-texture-async.c \ test-events.c \ test-offscreen.c \ test-scale.c \ diff --git a/tests/interactive/test-texture-async.c b/tests/interactive/test-texture-async.c new file mode 100644 index 000000000..9983bac57 --- /dev/null +++ b/tests/interactive/test-texture-async.c @@ -0,0 +1,76 @@ +#include +#include +#include + +G_MODULE_EXPORT gint +test_texture_async_main (int argc, char *argv[]) +{ + ClutterTimeline *timeline; + ClutterAlpha *alpha; + ClutterBehaviour *depth_behavior; + ClutterActor *stage; + ClutterActor *image[4]; + ClutterColor stage_color = { 0x12, 0x34, 0x56, 0xff }; + GError *error; + const gchar *path = "redhand.png"; + gint i; + + clutter_init (&argc, &argv); + + stage = clutter_stage_get_default (); + clutter_stage_set_color (CLUTTER_STAGE (stage), &stage_color); + clutter_stage_set_use_fog (CLUTTER_STAGE (stage), TRUE); + clutter_stage_set_fog (CLUTTER_STAGE (stage), 1.0, 10, -50); + + g_signal_connect (stage, + "button-press-event", G_CALLBACK (clutter_main_quit), + NULL); + + error = NULL; + path = argv[1]?argv[1]:"redhand.png"; + + if (!argv[1]) + g_print ("Hint: the redhand.png isn't a good test image for this test.\n" + "This test can take any clutter loadable image as an argument\n"); + + for (i=0;i<4;i++) + image[i] = g_object_new (CLUTTER_TYPE_TEXTURE, + "filename", path, + NULL); + /* + image[1] = g_object_new (CLUTTER_TYPE_TEXTURE, + "filename", path, + "load-async", TRUE, + NULL); + image[2] = g_object_new (CLUTTER_TYPE_TEXTURE, + "filename", path, + "load-data-async", TRUE, + NULL); + image[3] = g_object_new (CLUTTER_TYPE_TEXTURE, + "filename", path, + "load-data-async", TRUE, + "load-size-async", TRUE, + NULL);*/ + + /* center the image */ + + for (i=0;i<4;i++) + { + clutter_actor_set_position (image[i], 50+i*100, 50+i*50); + clutter_container_add (CLUTTER_CONTAINER (stage), image[i], NULL); + timeline = clutter_timeline_new (60*5, 60); + alpha = clutter_alpha_new_full (timeline, CLUTTER_LINEAR); + depth_behavior = clutter_behaviour_depth_new (alpha, -2500, 0); + clutter_behaviour_apply (depth_behavior, image[i]); + clutter_timeline_start (timeline); + } + + clutter_actor_show (stage); + + clutter_main (); + + /*g_object_unref (depth_behavior); + g_object_unref (timeline);*/ + + return EXIT_SUCCESS; +} From d3b68298ace856938efd7fe8293194582fc7f50c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Kol=C3=A5s?= Date: Thu, 12 Mar 2009 10:14:40 +0000 Subject: [PATCH 4/7] [clutter-texture] add option to not block on size for loaded images Added a construct time property to indicate that we do not want to block on loading images. --- clutter/clutter-texture.c | 86 +++++++++++++--------- tests/interactive/test-texture-async.c | 99 ++++++++++++++++++-------- 2 files changed, 122 insertions(+), 63 deletions(-) diff --git a/clutter/clutter-texture.c b/clutter/clutter-texture.c index e87bfb7ec..b491dee8f 100644 --- a/clutter/clutter-texture.c +++ b/clutter/clutter-texture.c @@ -99,9 +99,10 @@ struct _ClutterTexturePrivate guint repeat_y : 1; guint in_dispose : 1; guint keep_aspect_ratio : 1; - guint load_async : 1; guint load_size_async : 1; guint load_data_async : 1; + guint load_async_set : 1; /* used to make load_async + possible */ ClutterTextureAsyncData *async_data; }; @@ -840,13 +841,26 @@ clutter_texture_set_property (GObject *object, priv->keep_aspect_ratio = g_value_get_boolean (value); break; case PROP_LOAD_ASYNC: - priv->load_async = g_value_get_boolean (value); + if (g_value_get_boolean (value)) + { + priv->load_data_async = TRUE; + priv->load_async_set = TRUE; + priv->load_size_async = TRUE; + } break; case PROP_LOAD_DATA_ASYNC: - priv->load_data_async = g_value_get_boolean (value); + if (g_value_get_boolean (value)) + { + priv->load_async_set = TRUE; + priv->load_data_async = TRUE; + } break; case PROP_LOAD_SIZE_ASYNC: - priv->load_size_async = g_value_get_boolean (value); + if (g_value_get_boolean (value)) + { + priv->load_async_set = TRUE; + priv->load_size_async = TRUE; + } break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -903,9 +917,6 @@ clutter_texture_get_property (GObject *object, case PROP_KEEP_ASPECT_RATIO: g_value_set_boolean (value, priv->keep_aspect_ratio); break; - case PROP_LOAD_ASYNC: - g_value_set_boolean (value, priv->load_async); - break; case PROP_LOAD_DATA_ASYNC: g_value_set_boolean (value, priv->load_data_async); break; @@ -1047,26 +1058,10 @@ clutter_texture_class_init (ClutterTextureClass *klass) FALSE, CLUTTER_PARAM_READWRITE)); - /** - * ClutterTexture:load-size-sync: - * - * When set to TRUE clutter will not block loading the size initially, - * when used in this manner the size of the actor will change (and the - * load-size-complete signal will be fired when the size is available). - * - * Since: 1.0 - */ - g_object_class_install_property - (gobject_class, PROP_LOAD_SIZE_ASYNC, - g_param_spec_boolean ("load-size-async", - "Load size asynchronously", - "If set to TRUE clutter will not block until it has " - "loaded the size of the file.", - FALSE, - CLUTTER_PARAM_READWRITE)); + /** - * ClutterTexture:load-async: + * ClutterTexture:load-data-async: * * Tries to load a texture from a filename by using a local thread * to perform the read operations. Threading is only enabled if @@ -1080,20 +1075,39 @@ clutter_texture_class_init (ClutterTextureClass *klass) * Since: 1.0 */ g_object_class_install_property - (gobject_class, PROP_LOAD_ASYNC, + (gobject_class, PROP_LOAD_DATA_ASYNC, g_param_spec_boolean ("load-data-async", "Load data asynchronously", "Load files inside a thread to avoid blocking when " "loading images.", FALSE, - CLUTTER_PARAM_READWRITE)); + CLUTTER_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + /** + * ClutterTexture:load-size-sync: + * + * When set to TRUE clutter will not block loading the size initially, + * when used in this manner the size of the texture will initially be + * 0x0 when the size is available a "size-change" signal will be emitted. + * + * Since: 1.0 + */ + g_object_class_install_property + (gobject_class, PROP_LOAD_SIZE_ASYNC, + g_param_spec_boolean ("load-size-async", + "Load size asynchronously", + "If set to TRUE clutter will not block until it has " + "loaded the size of the file.", + FALSE, + CLUTTER_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + /** * ClutterTexture:load-async: * * Load texture fully asynchronosuly, loading both the size and data - * in a separate thread. + * in a separate thread. Setting this to TRUE is equivalent of setting + * both load-data-async and load-size-async. * * Since: 1.0 */ @@ -1104,7 +1118,7 @@ clutter_texture_class_init (ClutterTextureClass *klass) "Load files inside a thread to avoid blocking when " "loading images.", FALSE, - CLUTTER_PARAM_WRITABLE)); + CLUTTER_PARAM_WRITABLE | G_PARAM_CONSTRUCT)); /** * ClutterTexture::size-change: @@ -1690,9 +1704,11 @@ clutter_texture_async_load_complete (ClutterTexture *self, waste, flags, COGL_PIXEL_FORMAT_ANY); clutter_texture_set_cogl_texture (self, handle); - /*clutter_actor_set_size (self, cogl_texture_get_width (handle), - cogl_texture_get_height (handle));*/ - cogl_texture_unref (handle); + if (priv->load_size_async) + { + g_signal_emit (self, texture_signals[SIZE_CHANGE], 0, cogl_texture_get_width(handle), cogl_texture_get_height (handle)); + clutter_actor_queue_redraw (self); + } } g_signal_emit (self, texture_signals[LOAD_FINISHED], 0, error); @@ -1760,7 +1776,7 @@ clutter_texture_thread_func (gpointer user_data, gpointer pool_data) * idle handler now without the possibility of calling the * callback after it is aborted */ data->load_idle = - clutter_threads_add_idle (clutter_texture_thread_idle_func, data); + clutter_threads_add_idle_full (G_PRIORITY_LOW, clutter_texture_thread_idle_func, data, NULL); g_mutex_unlock (data->mutex); } @@ -1830,6 +1846,8 @@ clutter_texture_async_load (ClutterTexture *self, if (priv->load_size_async) { res = TRUE; + width = 0; + height = 0; } else { @@ -1920,7 +1938,7 @@ clutter_texture_set_from_file (ClutterTexture *texture, g_return_val_if_fail (error == NULL || *error == NULL, FALSE); - if (priv->load_async) + if (priv->load_data_async) return clutter_texture_async_load (texture, filename, error); if (!priv->no_slice) diff --git a/tests/interactive/test-texture-async.c b/tests/interactive/test-texture-async.c index 9983bac57..b6d29b049 100644 --- a/tests/interactive/test-texture-async.c +++ b/tests/interactive/test-texture-async.c @@ -2,42 +2,40 @@ #include #include -G_MODULE_EXPORT gint -test_texture_async_main (int argc, char *argv[]) + +static void size_change_cb (ClutterTexture *texture, + gint width, + gint height, + gpointer user_data) +{ + guint w,h; + clutter_actor_set_size (user_data, width, height); +} + + const gchar *path = "redhand.png"; + +static gboolean task (gpointer foo) { ClutterTimeline *timeline; ClutterAlpha *alpha; ClutterBehaviour *depth_behavior; - ClutterActor *stage; ClutterActor *image[4]; - ClutterColor stage_color = { 0x12, 0x34, 0x56, 0xff }; - GError *error; - const gchar *path = "redhand.png"; + ClutterActor *clone[4]; + ClutterActor *stage; gint i; - clutter_init (&argc, &argv); - stage = clutter_stage_get_default (); - clutter_stage_set_color (CLUTTER_STAGE (stage), &stage_color); - clutter_stage_set_use_fog (CLUTTER_STAGE (stage), TRUE); - clutter_stage_set_fog (CLUTTER_STAGE (stage), 1.0, 10, -50); - - g_signal_connect (stage, - "button-press-event", G_CALLBACK (clutter_main_quit), - NULL); - - error = NULL; - path = argv[1]?argv[1]:"redhand.png"; - - if (!argv[1]) - g_print ("Hint: the redhand.png isn't a good test image for this test.\n" - "This test can take any clutter loadable image as an argument\n"); - +#if 0 for (i=0;i<4;i++) image[i] = g_object_new (CLUTTER_TYPE_TEXTURE, "filename", path, + "load-async", TRUE, + NULL); +#else + /*for (i=0;i<4;i++)*/ + image[0] = g_object_new (CLUTTER_TYPE_TEXTURE, + "filename", path, NULL); - /* image[1] = g_object_new (CLUTTER_TYPE_TEXTURE, "filename", path, "load-async", TRUE, @@ -46,26 +44,69 @@ test_texture_async_main (int argc, char *argv[]) "filename", path, "load-data-async", TRUE, NULL); + + /* this is a synonym of image[1], is image[1] really needed? (or is this needed? */ + image[3] = g_object_new (CLUTTER_TYPE_TEXTURE, "filename", path, "load-data-async", TRUE, "load-size-async", TRUE, - NULL);*/ + NULL); +#endif + for (i=0;i<4;i++) + { + clutter_container_add (CLUTTER_CONTAINER (stage), image[i], NULL); + } + for (i=0;i<4;i++) + { + clutter_actor_set_position (image[i], 50+i*100, 0+i*50); + clone[i]=clutter_clone_new (image[i]); + g_signal_connect (image[i], "size-change", size_change_cb, clone[i]); + clutter_container_add (CLUTTER_CONTAINER (stage), clone[i], NULL); + clutter_actor_set_position (clone[i], 50+i*100, 150+i*50+100); + } /* center the image */ - for (i=0;i<4;i++) + for (i=0; i<4; i++) { - clutter_actor_set_position (image[i], 50+i*100, 50+i*50); - clutter_container_add (CLUTTER_CONTAINER (stage), image[i], NULL); timeline = clutter_timeline_new (60*5, 60); alpha = clutter_alpha_new_full (timeline, CLUTTER_LINEAR); depth_behavior = clutter_behaviour_depth_new (alpha, -2500, 0); clutter_behaviour_apply (depth_behavior, image[i]); clutter_timeline_start (timeline); } - + return FALSE; +} + + +G_MODULE_EXPORT gint +test_texture_async_main (int argc, char *argv[]) +{ + ClutterActor *stage; + ClutterColor stage_color = { 0x12, 0x34, 0x56, 0xff }; + GError *error; + + clutter_init (&argc, &argv); + + stage = clutter_stage_get_default (); + clutter_stage_set_color (CLUTTER_STAGE (stage), &stage_color); + clutter_actor_show (stage); + g_signal_connect (stage, + "button-press-event", G_CALLBACK (clutter_main_quit), + NULL); + + error = NULL; + + if (!argv[1]) + g_print ("Hint: the redhand.png isn't a good test image for this test.\n" + "This test can take any clutter loadable image as an argument\n"); + + path = argv[1]?argv[1]:"redhand.png"; + + + g_timeout_add (500, task, NULL); clutter_main (); From 93d8f62f6bf9ade7e3049d8c30b5a1e38bf17561 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Kol=C3=A5s?= Date: Thu, 12 Mar 2009 10:38:39 +0000 Subject: [PATCH 5/7] [clutter-texture] remove load-size-async property Only have load-data-async and load-async properties, both are construct only and the latter adds the former load-size-async behavior on top of load-data-async. --- clutter/clutter-texture.c | 51 ++++++-------------------- tests/interactive/test-texture-async.c | 23 +++--------- 2 files changed, 16 insertions(+), 58 deletions(-) diff --git a/clutter/clutter-texture.c b/clutter/clutter-texture.c index b491dee8f..9bcbaad97 100644 --- a/clutter/clutter-texture.c +++ b/clutter/clutter-texture.c @@ -148,7 +148,6 @@ enum PROP_KEEP_ASPECT_RATIO, PROP_LOAD_ASYNC, PROP_LOAD_DATA_ASYNC, - PROP_LOAD_SIZE_ASYNC }; enum @@ -840,14 +839,6 @@ clutter_texture_set_property (GObject *object, case PROP_KEEP_ASPECT_RATIO: priv->keep_aspect_ratio = g_value_get_boolean (value); break; - case PROP_LOAD_ASYNC: - if (g_value_get_boolean (value)) - { - priv->load_data_async = TRUE; - priv->load_async_set = TRUE; - priv->load_size_async = TRUE; - } - break; case PROP_LOAD_DATA_ASYNC: if (g_value_get_boolean (value)) { @@ -855,9 +846,10 @@ clutter_texture_set_property (GObject *object, priv->load_data_async = TRUE; } break; - case PROP_LOAD_SIZE_ASYNC: + case PROP_LOAD_ASYNC: if (g_value_get_boolean (value)) { + priv->load_data_async = TRUE; priv->load_async_set = TRUE; priv->load_size_async = TRUE; } @@ -917,12 +909,6 @@ clutter_texture_get_property (GObject *object, case PROP_KEEP_ASPECT_RATIO: g_value_set_boolean (value, priv->keep_aspect_ratio); break; - case PROP_LOAD_DATA_ASYNC: - g_value_set_boolean (value, priv->load_data_async); - break; - case PROP_LOAD_SIZE_ASYNC: - g_value_set_boolean (value, priv->load_size_async); - break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -1081,33 +1067,17 @@ clutter_texture_class_init (ClutterTextureClass *klass) "Load files inside a thread to avoid blocking when " "loading images.", FALSE, - CLUTTER_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + CLUTTER_PARAM_WRITABLE | G_PARAM_CONSTRUCT)); - /** - * ClutterTexture:load-size-sync: - * - * When set to TRUE clutter will not block loading the size initially, - * when used in this manner the size of the texture will initially be - * 0x0 when the size is available a "size-change" signal will be emitted. - * - * Since: 1.0 - */ - g_object_class_install_property - (gobject_class, PROP_LOAD_SIZE_ASYNC, - g_param_spec_boolean ("load-size-async", - "Load size asynchronously", - "If set to TRUE clutter will not block until it has " - "loaded the size of the file.", - FALSE, - CLUTTER_PARAM_READWRITE | G_PARAM_CONSTRUCT)); - /** * ClutterTexture:load-async: * - * Load texture fully asynchronosuly, loading both the size and data - * in a separate thread. Setting this to TRUE is equivalent of setting - * both load-data-async and load-size-async. + * When set to TRUE load the texture asynchronously, loading both the data + * async as when load-data-async is set, but also deferring the loading of + * the size to the thread. The size of the texture will initially be 0x0 + * a "size-change" signal is emitted when the dimensions of the texture + * has been loaded from disk. * * Since: 1.0 */ @@ -1706,8 +1676,9 @@ clutter_texture_async_load_complete (ClutterTexture *self, clutter_texture_set_cogl_texture (self, handle); if (priv->load_size_async) { - g_signal_emit (self, texture_signals[SIZE_CHANGE], 0, cogl_texture_get_width(handle), cogl_texture_get_height (handle)); - clutter_actor_queue_redraw (self); + g_signal_emit (self, texture_signals[SIZE_CHANGE], 0, + cogl_texture_get_width(handle), + cogl_texture_get_height (handle)); } } diff --git a/tests/interactive/test-texture-async.c b/tests/interactive/test-texture-async.c index b6d29b049..cd2b07e1a 100644 --- a/tests/interactive/test-texture-async.c +++ b/tests/interactive/test-texture-async.c @@ -38,26 +38,19 @@ static gboolean task (gpointer foo) NULL); image[1] = g_object_new (CLUTTER_TYPE_TEXTURE, "filename", path, - "load-async", TRUE, + "load-data-async", TRUE, NULL); image[2] = g_object_new (CLUTTER_TYPE_TEXTURE, "filename", path, - "load-data-async", TRUE, + "load-async", TRUE, NULL); - /* this is a synonym of image[1], is image[1] really needed? (or is this needed? */ - - image[3] = g_object_new (CLUTTER_TYPE_TEXTURE, - "filename", path, - "load-data-async", TRUE, - "load-size-async", TRUE, - NULL); #endif - for (i=0;i<4;i++) + for (i=0;i<3;i++) { clutter_container_add (CLUTTER_CONTAINER (stage), image[i], NULL); } - for (i=0;i<4;i++) + for (i=0;i<3;i++) { clutter_actor_set_position (image[i], 50+i*100, 0+i*50); clone[i]=clutter_clone_new (image[i]); @@ -66,9 +59,7 @@ static gboolean task (gpointer foo) clutter_actor_set_position (clone[i], 50+i*100, 150+i*50+100); } - /* center the image */ - - for (i=0; i<4; i++) + for (i=0; i<3; i++) { timeline = clutter_timeline_new (60*5, 60); alpha = clutter_alpha_new_full (timeline, CLUTTER_LINEAR); @@ -99,10 +90,6 @@ test_texture_async_main (int argc, char *argv[]) error = NULL; - if (!argv[1]) - g_print ("Hint: the redhand.png isn't a good test image for this test.\n" - "This test can take any clutter loadable image as an argument\n"); - path = argv[1]?argv[1]:"redhand.png"; From 5895048f65c7ec8028575f60b7b4a7d58a87077e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Kol=C3=A5s?= Date: Thu, 12 Mar 2009 11:48:44 +0000 Subject: [PATCH 6/7] [clutter-texture] updated documentation. Rewrote documentation, with the smallest amount of documentation for load-data-async which is considered a special case of load-async. --- clutter/clutter-texture.c | 64 ++++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/clutter/clutter-texture.c b/clutter/clutter-texture.c index 20f5bcdb8..95a190543 100644 --- a/clutter/clutter-texture.c +++ b/clutter/clutter-texture.c @@ -1042,39 +1042,22 @@ clutter_texture_class_init (ClutterTextureClass *klass) CLUTTER_PARAM_READWRITE)); - - /** - * ClutterTexture:load-data-async: - * - * Tries to load a texture from a filename by using a local thread - * to perform the read operations. Threading is only enabled if - * g_thread_init() has been called prior to clutter_init(), otherwise - * #ClutterTexture will use the main loop to load the image. - * - * The upload of the texture data on the GL pipeline is not - * asynchronous, as it must be performed from within the same - * thread that called clutter_main(). - * - * Since: 1.0 - */ - g_object_class_install_property - (gobject_class, PROP_LOAD_DATA_ASYNC, - g_param_spec_boolean ("load-data-async", - "Load data asynchronously", - "Load files inside a thread to avoid blocking when " - "loading images.", - FALSE, - CLUTTER_PARAM_WRITABLE | G_PARAM_CONSTRUCT)); - - /** * ClutterTexture:load-async: * - * When set to TRUE load the texture asynchronously, loading both the data - * async as when load-data-async is set, but also deferring the loading of - * the size to the thread. The size of the texture will initially be 0x0 - * a "size-change" signal is emitted when the dimensions of the texture - * has been loaded from disk. + * Tries to load a texture from a filename by using a local thread to perform + * the read operations. The initially created texture has dimensions 0x0 when + * the true size becomes available the ClutterTexture::size-change signal is + * emitted and when the image has completed loading the + * ClutterTexture::load-finished signal is emitted. + * + * Threading is only enabled if g_thread_init() has been called prior to + * clutter_init(), otherwise #ClutterTexture will use the main loop to load + * the image. + * + * The upload of the texture data on the GL pipeline is not asynchronous, as + * it must be performed from within the same thread that called + * clutter_main(). * * Since: 1.0 */ @@ -1087,6 +1070,25 @@ clutter_texture_class_init (ClutterTextureClass *klass) FALSE, CLUTTER_PARAM_WRITABLE | G_PARAM_CONSTRUCT)); + + /** + * ClutterTexture:load-data-async: + * + * Like ClutterTexture:load-async but loads the width and height + * synchronously causing some blocking. + * + * Since: 1.0 + */ + g_object_class_install_property + (gobject_class, PROP_LOAD_DATA_ASYNC, + g_param_spec_boolean ("load-data-async", + "Load data asynchronously", + "Decode image data files inside a thread to reduce " + "blocking when loading images.", + FALSE, + CLUTTER_PARAM_WRITABLE | G_PARAM_CONSTRUCT)); + + /** * ClutterTexture::size-change: * @texture: the texture which received the signal @@ -1677,6 +1679,7 @@ clutter_texture_async_load_complete (ClutterTexture *self, cogl_texture_get_width(handle), cogl_texture_get_height (handle)); } + cogl_texture_unref (handle); } g_signal_emit (self, texture_signals[LOAD_FINISHED], 0, error); @@ -1724,7 +1727,6 @@ clutter_texture_thread_func (gpointer user_data, gpointer pool_data) data->load_bitmap = cogl_bitmap_new_from_file (data->load_filename, &data->load_error); - g_print ("."); /* Check again if we've been told to abort */ g_mutex_lock (data->mutex); From 56568db3b0b9eebf7f000407e1334a5b4d85e889 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Kol=C3=A5s?= Date: Thu, 12 Mar 2009 11:48:44 +0000 Subject: [PATCH 7/7] [clutter-texture] fixed gtk-doc formatting, and init threads in test. Fixed markup to actually work with gtk-doc, also do a g_thread_init in the test. --- clutter/clutter-texture.c | 14 ++++++++------ tests/interactive/test-texture-async.c | 4 +++- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/clutter/clutter-texture.c b/clutter/clutter-texture.c index 95a190543..7e57dba09 100644 --- a/clutter/clutter-texture.c +++ b/clutter/clutter-texture.c @@ -1047,9 +1047,9 @@ clutter_texture_class_init (ClutterTextureClass *klass) * * Tries to load a texture from a filename by using a local thread to perform * the read operations. The initially created texture has dimensions 0x0 when - * the true size becomes available the ClutterTexture::size-change signal is + * the true size becomes available the #ClutterTexture::size-change signal is * emitted and when the image has completed loading the - * ClutterTexture::load-finished signal is emitted. + * #ClutterTexture::load-finished signal is emitted. * * Threading is only enabled if g_thread_init() has been called prior to * clutter_init(), otherwise #ClutterTexture will use the main loop to load @@ -1074,7 +1074,7 @@ clutter_texture_class_init (ClutterTextureClass *klass) /** * ClutterTexture:load-data-async: * - * Like ClutterTexture:load-async but loads the width and height + * Like #ClutterTexture:load-async but loads the width and height * synchronously causing some blocking. * * Since: 1.0 @@ -1089,6 +1089,7 @@ clutter_texture_class_init (ClutterTextureClass *klass) CLUTTER_PARAM_WRITABLE | G_PARAM_CONSTRUCT)); + /** * ClutterTexture::size-change: * @texture: the texture which received the signal @@ -1884,9 +1885,10 @@ clutter_texture_async_load (ClutterTexture *self, * * If #ClutterTexture:load-async is set to %TRUE, this function * will return as soon as possible, and the actual image loading - * from disk will be performed asynchronously. #ClutterTexture::load-finished - * will be emitted when the image has been loaded or if an error - * occurred. + * from disk will be performed asynchronously. #ClutterTexture::size-change + * will be emitten when the size of the texture is available and + * #ClutterTexture::load-finished will be emitted when the image has been + * loaded or if an error occurred. * * Return value: %TRUE if the image was successfully loaded and set * diff --git a/tests/interactive/test-texture-async.c b/tests/interactive/test-texture-async.c index cd2b07e1a..5c1b8064d 100644 --- a/tests/interactive/test-texture-async.c +++ b/tests/interactive/test-texture-async.c @@ -54,7 +54,8 @@ static gboolean task (gpointer foo) { clutter_actor_set_position (image[i], 50+i*100, 0+i*50); clone[i]=clutter_clone_new (image[i]); - g_signal_connect (image[i], "size-change", size_change_cb, clone[i]); + g_signal_connect (image[i], "size-change", + G_CALLBACK (size_change_cb), clone[i]); clutter_container_add (CLUTTER_CONTAINER (stage), clone[i], NULL); clutter_actor_set_position (clone[i], 50+i*100, 150+i*50+100); } @@ -80,6 +81,7 @@ test_texture_async_main (int argc, char *argv[]) clutter_init (&argc, &argv); + g_thread_init (NULL); stage = clutter_stage_get_default (); clutter_stage_set_color (CLUTTER_STAGE (stage), &stage_color);