diff --git a/clutter/clutter-texture.c b/clutter/clutter-texture.c index bd380fd2a..20f5bcdb8 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; @@ -97,14 +99,37 @@ struct _ClutterTexturePrivate guint repeat_y : 1; 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; + guint load_size_async : 1; + guint load_data_async : 1; + guint load_async_set : 1; /* used to make load_async + possible */ + + 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 @@ -121,7 +146,8 @@ enum PROP_COGL_MATERIAL, PROP_FILENAME, PROP_KEEP_ASPECT_RATIO, - PROP_LOAD_ASYNC + PROP_LOAD_ASYNC, + PROP_LOAD_DATA_ASYNC, }; enum @@ -130,12 +156,13 @@ enum PIXBUF_CHANGE, LOAD_SUCCESS, LOAD_FINISHED, - LAST_SIGNAL }; static int texture_signals[LAST_SIGNAL] = { 0 }; +static GThreadPool *async_thread_pool = NULL; + static void texture_fbo_free_resources (ClutterTexture *texture); @@ -630,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 @@ -642,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 @@ -778,8 +836,20 @@ clutter_texture_set_property (GObject *object, case PROP_KEEP_ASPECT_RATIO: priv->keep_aspect_ratio = g_value_get_boolean (value); break; + case PROP_LOAD_DATA_ASYNC: + if (g_value_get_boolean (value)) + { + priv->load_async_set = TRUE; + priv->load_data_async = TRUE; + } + 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; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -836,9 +906,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; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -974,8 +1041,10 @@ clutter_texture_class_init (ClutterTextureClass *klass) 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 @@ -988,6 +1057,27 @@ clutter_texture_class_init (ClutterTextureClass *klass) * * 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. + * + * Since: 1.0 + */ g_object_class_install_property (gobject_class, PROP_LOAD_ASYNC, g_param_spec_boolean ("load-async", @@ -995,7 +1085,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 | G_PARAM_CONSTRUCT)); /** * ClutterTexture::size-change: @@ -1550,14 +1640,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; @@ -1565,6 +1657,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) @@ -1573,14 +1667,16 @@ 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; + if (priv->load_size_async) + { + g_signal_emit (self, texture_signals[SIZE_CHANGE], 0, + cogl_texture_get_width(handle), + cogl_texture_get_height (handle)); + } } g_signal_emit (self, texture_signals[LOAD_FINISHED], 0, error); @@ -1589,96 +1685,101 @@ 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); + g_print ("."); - 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_full (G_PRIORITY_LOW, clutter_texture_thread_idle_func, data, NULL); + + 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. @@ -1696,22 +1797,31 @@ 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, - &width, - &height); + + if (priv->load_size_async) + { + res = TRUE; + width = 0; + height = 0; + } + else + { + res = cogl_bitmap_get_size_from_file (filename, &width, &height); + } + if (!res) { g_set_error (error, CLUTTER_TEXTURE_ERROR, @@ -1725,22 +1835,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, 1, 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; } /** @@ -1778,14 +1906,8 @@ 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); - } + if (priv->load_data_async) + return clutter_texture_async_load (texture, filename, error); if (!priv->no_slice) max_waste = priv->max_tile_waste; 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..cd2b07e1a --- /dev/null +++ b/tests/interactive/test-texture-async.c @@ -0,0 +1,104 @@ +#include +#include +#include + + +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 *image[4]; + ClutterActor *clone[4]; + ClutterActor *stage; + gint i; + + stage = clutter_stage_get_default (); +#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-data-async", TRUE, + NULL); + image[2] = g_object_new (CLUTTER_TYPE_TEXTURE, + "filename", path, + "load-async", TRUE, + NULL); + +#endif + for (i=0;i<3;i++) + { + clutter_container_add (CLUTTER_CONTAINER (stage), image[i], NULL); + } + 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]); + 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); + } + + for (i=0; i<3; i++) + { + 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; + + path = argv[1]?argv[1]:"redhand.png"; + + + g_timeout_add (500, task, NULL); + + clutter_main (); + + /*g_object_unref (depth_behavior); + g_object_unref (timeline);*/ + + return EXIT_SUCCESS; +}