From 2cea22e6a05e13c32c2f13ee1d369a5806605866 Mon Sep 17 00:00:00 2001 From: Chris Lord Date: Wed, 7 Jan 2009 17:02:43 +0000 Subject: [PATCH 1/5] Update/clean and apply the async-texture patch from bug #1144 --- clutter/clutter-texture.c | 152 +++++++++++++++++++++++++++++- clutter/clutter-texture.h | 3 +- clutter/cogl/cogl-texture.h | 42 +++++++++ clutter/cogl/cogl-types.h | 7 ++ clutter/cogl/common/cogl-bitmap.c | 31 ++++++ clutter/cogl/common/cogl-bitmap.h | 2 - clutter/cogl/gl/cogl-texture.c | 52 +++++----- 7 files changed, 262 insertions(+), 27 deletions(-) diff --git a/clutter/clutter-texture.c b/clutter/clutter-texture.c index 086a5134c..10cb2172c 100644 --- a/clutter/clutter-texture.c +++ b/clutter/clutter-texture.c @@ -96,6 +96,13 @@ struct _ClutterTexturePrivate guint repeat_y : 1; guint in_dispose : 1; guint keep_aspect_ratio : 1; + guint load_async : 1; + + GThread *load_thread; + gchar *load_filename; + CoglBitmap *load_bitmap; + GError *load_error; + guint load_source; }; enum @@ -110,13 +117,17 @@ enum PROP_FILTER_QUALITY, PROP_COGL_TEXTURE, PROP_FILENAME, - PROP_KEEP_ASPECT_RATIO + PROP_KEEP_ASPECT_RATIO, + PROP_LOAD_ASYNC }; enum { SIZE_CHANGE, PIXBUF_CHANGE, + LOAD_SUCCESS, + LOAD_FINISHED, + LAST_SIGNAL }; @@ -601,6 +612,35 @@ clutter_texture_paint (ClutterActor *self) 0, 0, t_w, t_h); } +static void +clutter_texture_thread_cancel (ClutterTexture *texture) +{ + ClutterTexturePrivate *priv = texture->priv; + + if (priv->load_thread) + { + g_thread_join (priv->load_thread); + priv->load_thread = NULL; + } + + if (priv->load_source) + { + g_source_remove (priv->load_source); + priv->load_source = 0; + + if (priv->load_error) + { + g_error_free (priv->load_error); + priv->load_error = NULL; + } + else + { + cogl_bitmap_free (priv->load_bitmap); + priv->load_bitmap = NULL; + } + } +} + static void clutter_texture_dispose (GObject *object) { @@ -625,7 +665,9 @@ clutter_texture_dispose (GObject *object) g_free (priv->local_data); priv->local_data = NULL; } - + + clutter_texture_thread_cancel (texture); + G_OBJECT_CLASS (clutter_texture_parent_class)->dispose (object); } @@ -686,6 +728,11 @@ 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: + priv->load_async = g_value_get_boolean (value); + if (priv->load_async && !g_thread_supported()) + priv->load_async = FALSE; + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -736,6 +783,9 @@ 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; @@ -861,6 +911,15 @@ clutter_texture_class_init (ClutterTextureClass *klass) FALSE, CLUTTER_PARAM_READWRITE)); + g_object_class_install_property + (gobject_class, PROP_LOAD_ASYNC, + g_param_spec_boolean ("load-async", + "Load asynchronously", + "Load files inside a thread to avoid blocking when " + "loading images.", + FALSE, + CLUTTER_PARAM_READWRITE)); + /** * ClutterTexture::size-change: * @texture: the texture which received the signal @@ -896,6 +955,25 @@ clutter_texture_class_init (ClutterTextureClass *klass) g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + /** + * ClutterTexture::load-finished: + * @texture: the texture which received the signal + * @error: A set error, or %NULL + * + * The ::load-finished signal is emitted when asynchronous texture + * load has completed. If there was an error during loading, @error will + * be set. + */ + texture_signals[LOAD_FINISHED] = + g_signal_new ("load-finished", + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ClutterTextureClass, load_finished), + NULL, NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, + G_TYPE_POINTER); } static ClutterScriptableIface *parent_scriptable_iface = NULL; @@ -1301,6 +1379,65 @@ clutter_texture_set_from_yuv_data (ClutterTexture *texture, error); } +static gboolean +clutter_texture_thread_cb (ClutterTexture *self) +{ + ClutterTexturePrivate *priv = self->priv; + + priv->load_source = 0; + + if (priv->load_thread) + { + g_thread_join (priv->load_thread); + priv->load_thread = NULL; + } + else + return FALSE; + + if (!priv->load_error) + { + CoglHandle handle; + + handle = cogl_texture_new_from_bitmap (priv->load_bitmap, + priv->no_slice ? + -1 : priv->max_tile_waste, + priv->filter_quality == + CLUTTER_TEXTURE_QUALITY_HIGH, + 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, priv->load_error); + + if (priv->load_error) + { + g_error_free (priv->load_error); + priv->load_error = NULL; + } + + return FALSE; +} + +static gpointer +clutter_texture_thread_func (ClutterTexture *self) +{ + ClutterTexturePrivate *priv = self->priv; + + /* Try loading with imaging backend */ + priv->load_bitmap = cogl_bitmap_new_from_file (priv->load_filename, + &priv->load_error); + g_free (priv->load_filename); + priv->load_filename = NULL; + + clutter_threads_add_idle ((GSourceFunc)clutter_texture_thread_cb, self); + + return NULL; +} + /** * clutter_texture_set_from_file: * @texture: A #ClutterTexture @@ -1326,6 +1463,17 @@ clutter_texture_set_from_file (ClutterTexture *texture, g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + if (priv->load_async) + { + clutter_texture_thread_cancel (texture); + + priv->load_filename = g_strdup (filename); + priv->load_thread = g_thread_create ((GThreadFunc) + clutter_texture_thread_func, + texture, TRUE, error); + return priv->load_thread ? TRUE : FALSE; + } + if ((new_texture = cogl_texture_new_from_file (filename, priv->no_slice ? -1 : priv->max_tile_waste, diff --git a/clutter/clutter-texture.h b/clutter/clutter-texture.h index e15f798b1..3f20413e8 100644 --- a/clutter/clutter-texture.h +++ b/clutter/clutter-texture.h @@ -84,6 +84,8 @@ struct _ClutterTextureClass gint width, gint height); void (*pixbuf_change) (ClutterTexture *texture); + void (*load_finished) (ClutterTexture *texture, + GError *error); /*< private >*/ /* padding, for future expansion */ @@ -92,7 +94,6 @@ struct _ClutterTextureClass void (*_clutter_texture3) (void); void (*_clutter_texture4) (void); void (*_clutter_texture5) (void); - void (*_clutter_texture6) (void); }; /** diff --git a/clutter/cogl/cogl-texture.h b/clutter/cogl/cogl-texture.h index 07cec1ce1..7c1dd9c9f 100644 --- a/clutter/cogl/cogl-texture.h +++ b/clutter/cogl/cogl-texture.h @@ -134,6 +134,26 @@ CoglHandle cogl_texture_new_from_foreign (GLuint gl_handle, GLuint y_pot_waste, CoglPixelFormat format); +/** + * cogl_texture_new_from_bitmap: + * @handle: handle of the preloaded texture. + * @max_waste: maximum extra horizontal and|or vertical margin pixels to make + * texture fit GPU limitations. + * @auto_mipmap: enable or disable automatic generation of mipmap pyramid + * from the base level image whenever it is updated. + * @internal_format: the #CoglPixelFormat to use for the GPU storage of the + * texture. + * + * Create a cogl texture from a #CoglBitmap. + * + * Returns: a #CoglHandle to the newly created texture or COGL_INVALID_HANDLE + * if creating the texture failed. + */ +CoglHandle cogl_texture_new_from_bitmap (CoglBitmap *bitmap, + gint max_waste, + gboolean auto_mipmap, + CoglPixelFormat internal_format); + /** * cogl_is_texture: * @handle: A CoglHandle @@ -385,6 +405,28 @@ void cogl_texture_polygon (CoglHandle handle, CoglTextureVertex *vertices, gboolean use_color); +/** + * cogl_bitmap_new_from_file: + * @filename: the file to load. + * @error: a #GError or %NULL. + * + * Load an image file from disk. This function can be safely called from + * within a thread. + * + * Returns: A #CoglBitmap to the new loaded image data, or %NULL if loading + * the image failed. + */ +CoglBitmap * cogl_bitmap_new_from_file (const gchar *filename, + GError **error); + +/** + * cogl_bitmap_free: + * @bmp: a #CoglBitmap. + * + * Frees a #CoglBitmap. + */ +void cogl_bitmap_free (CoglBitmap *bmp); + G_END_DECLS #endif /* __COGL_TEXTURE_H__ */ diff --git a/clutter/cogl/cogl-types.h b/clutter/cogl/cogl-types.h index 4fb69953a..6818d1393 100644 --- a/clutter/cogl/cogl-types.h +++ b/clutter/cogl/cogl-types.h @@ -28,6 +28,13 @@ G_BEGIN_DECLS +/** + * CoglBitmap: + * + * Type used for storing image data. + */ +typedef struct _CoglBitmap CoglBitmap; + /** * CoglHandle: * diff --git a/clutter/cogl/common/cogl-bitmap.c b/clutter/cogl/common/cogl-bitmap.c index 39341e0b0..6f31ebb5c 100644 --- a/clutter/cogl/common/cogl-bitmap.c +++ b/clutter/cogl/common/cogl-bitmap.c @@ -148,3 +148,34 @@ _cogl_bitmap_copy_subregion (CoglBitmap *src, dstdata += dst->rowstride; } } + +CoglBitmap * +cogl_bitmap_new_from_file (const gchar *filename, + GError **error) +{ + CoglBitmap bmp; + + g_return_val_if_fail (error == NULL || *error == NULL, COGL_INVALID_HANDLE); + + /* Try loading with imaging backend */ + if (!_cogl_bitmap_from_file (&bmp, filename, error)) + { + /* Try fallback */ + if (!_cogl_bitmap_fallback_from_file (&bmp, filename)) + return NULL; + else if (error && *error) + { + g_error_free (*error); + *error = NULL; + } + } + + return (CoglBitmap *) g_memdup (&bmp, sizeof (CoglBitmap)); +} + +void +cogl_bitmap_free (CoglBitmap *bmp) +{ + g_free (bmp->data); + g_free (bmp); +} diff --git a/clutter/cogl/common/cogl-bitmap.h b/clutter/cogl/common/cogl-bitmap.h index ca9f8a9c3..9baec8170 100644 --- a/clutter/cogl/common/cogl-bitmap.h +++ b/clutter/cogl/common/cogl-bitmap.h @@ -28,8 +28,6 @@ #include -typedef struct _CoglBitmap CoglBitmap; - struct _CoglBitmap { guchar *data; diff --git a/clutter/cogl/gl/cogl-texture.c b/clutter/cogl/gl/cogl-texture.c index 12caf7c58..ec4f17d32 100644 --- a/clutter/cogl/gl/cogl-texture.c +++ b/clutter/cogl/gl/cogl-texture.c @@ -1323,30 +1323,13 @@ cogl_texture_new_from_data (guint width, } CoglHandle -cogl_texture_new_from_file (const gchar *filename, - gint max_waste, - gboolean auto_mipmap, - CoglPixelFormat internal_format, - GError **error) +cogl_texture_new_from_bitmap (CoglBitmap *bmp, + gint max_waste, + gboolean auto_mipmap, + CoglPixelFormat internal_format) { - CoglBitmap bmp; CoglTexture *tex; - g_return_val_if_fail (error == NULL || *error == NULL, COGL_INVALID_HANDLE); - - /* Try loading with imaging backend */ - if (!_cogl_bitmap_from_file (&bmp, filename, error)) - { - /* Try fallback */ - if (!_cogl_bitmap_fallback_from_file (&bmp, filename)) - return COGL_INVALID_HANDLE; - else if (error && *error) - { - g_error_free (*error); - *error = NULL; - } - } - /* Create new texture and fill with loaded data */ tex = (CoglTexture*) g_malloc ( sizeof (CoglTexture)); @@ -1356,8 +1339,9 @@ cogl_texture_new_from_file (const gchar *filename, tex->is_foreign = FALSE; tex->auto_mipmap = auto_mipmap; - tex->bitmap = bmp; + tex->bitmap = *bmp; tex->bitmap_owner = TRUE; + bmp->data = NULL; tex->slice_x_spans = NULL; tex->slice_y_spans = NULL; @@ -1398,6 +1382,30 @@ cogl_texture_new_from_file (const gchar *filename, return _cogl_texture_handle_new (tex); } +CoglHandle +cogl_texture_new_from_file (const gchar *filename, + gint max_waste, + gboolean auto_mipmap, + CoglPixelFormat internal_format, + GError **error) +{ + CoglBitmap *bmp; + CoglHandle handle; + + g_return_val_if_fail (error == NULL || *error == NULL, COGL_INVALID_HANDLE); + + if (!(bmp = cogl_bitmap_new_from_file (filename, error))) + return COGL_INVALID_HANDLE; + + handle = cogl_texture_new_from_bitmap (bmp, + max_waste, + auto_mipmap, + internal_format); + cogl_bitmap_free (bmp); + + return handle; +} + CoglHandle cogl_texture_new_from_foreign (GLuint gl_handle, GLenum gl_target, From 1c114be31a50410c1b2d0621848250102b6dd166 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Mon, 12 Jan 2009 16:52:20 +0000 Subject: [PATCH 2/5] Add API for extracting image size from a file For the asynchronous loading we need a function call that parses a file, given its path, and retrieves the image width and height. This commit adds cogl_bitmap_get_size_from_file() to the CoglBitmap API. --- clutter/cogl/cogl-texture.h | 17 +++++++ clutter/cogl/common/cogl-bitmap-pixbuf.c | 65 ++++++++++++++++++++---- clutter/cogl/common/cogl-bitmap.c | 8 +++ clutter/cogl/common/cogl-bitmap.h | 5 ++ 4 files changed, 86 insertions(+), 9 deletions(-) diff --git a/clutter/cogl/cogl-texture.h b/clutter/cogl/cogl-texture.h index 65b29b68c..3a509196a 100644 --- a/clutter/cogl/cogl-texture.h +++ b/clutter/cogl/cogl-texture.h @@ -415,10 +415,27 @@ void cogl_texture_polygon (CoglHandle handle, * * Returns: A #CoglBitmap to the new loaded image data, or %NULL if loading * the image failed. + * + * Since: 1.0 */ CoglBitmap * cogl_bitmap_new_from_file (const gchar *filename, GError **error); +/** + * cogl_bitmap_get_size_from_file: + * @filename: the file to check + * @width: return location for the bitmap width + * @height: return location for the bitmap height + * + * Parses an image file enough to extract the width and height + * of the bitmap. + * + * Since: 1.0 + */ +gboolean cogl_bitmap_get_size_from_file (const gchar *filename, + gint *width, + gint *height); + /** * cogl_bitmap_free: * @bmp: a #CoglBitmap. diff --git a/clutter/cogl/common/cogl-bitmap-pixbuf.c b/clutter/cogl/common/cogl-bitmap-pixbuf.c index 77316dff5..8d86d94e4 100644 --- a/clutter/cogl/common/cogl-bitmap-pixbuf.c +++ b/clutter/cogl/common/cogl-bitmap-pixbuf.c @@ -84,6 +84,20 @@ cogl_bitmap_error_quark (void) return g_quark_from_static_string ("cogl-bitmap-error-quark"); } +gboolean +_cogl_bitmap_get_size_from_file (const gchar *filename, + gint *width, + gint *height) +{ + if (width) + *width = 0; + + if (height) + *height = 0; + + return TRUE; +} + /* the error does not contain the filename as the caller already has it */ gboolean _cogl_bitmap_from_file (CoglBitmap *bmp, @@ -177,9 +191,22 @@ _cogl_bitmap_from_file (CoglBitmap *bmp, #elif defined(USE_GDKPIXBUF) gboolean -_cogl_bitmap_from_file (CoglBitmap *bmp, - const gchar *filename, - GError **error) +_cogl_bitmap_get_size_from_file (const gchar *filename, + gint *width, + gint *height) +{ + g_return_val_if_fail (filename != NULL, FALSE); + + if (gdk_pixbuf_get_file_info (filename, width, height) != NULL) + return TRUE; + + return FALSE; +} + +gboolean +_cogl_bitmap_from_file (CoglBitmap *bmp, + const gchar *filename, + GError **error) { GdkPixbuf *pixbuf; gboolean has_alpha; @@ -198,11 +225,13 @@ _cogl_bitmap_from_file (CoglBitmap *bmp, g_return_val_if_fail (error == NULL || *error == NULL, FALSE); - if (bmp == NULL) return FALSE; - + if (bmp == NULL) + return FALSE; + /* Load from file using GdkPixbuf */ pixbuf = gdk_pixbuf_new_from_file (filename, error); - if (pixbuf == NULL) return FALSE; + if (pixbuf == NULL) + return FALSE; /* Get pixbuf properties */ has_alpha = gdk_pixbuf_get_has_alpha (pixbuf); @@ -278,6 +307,20 @@ _cogl_bitmap_from_file (CoglBitmap *bmp, #include "stb_image.c" +gboolean +_cogl_bitmap_get_size_from_file (const gchar *filename, + gint *width, + gint *height) +{ + if (width) + *width = 0; + + if (height) + *height = 0; + + return TRUE; +} + gboolean _cogl_bitmap_from_file (CoglBitmap *bmp, const gchar *filename, @@ -290,11 +333,15 @@ _cogl_bitmap_from_file (CoglBitmap *bmp, g_return_val_if_fail (error == NULL || *error == NULL, FALSE); - if (bmp == NULL) return FALSE; + if (bmp == NULL) + return FALSE; /* Load from file using stb */ - pixels = stbi_load (filename, &width, &height, &stb_pixel_format, STBI_rgb_alpha); - if (pixels == NULL) return FALSE; + pixels = stbi_load (filename, + &width, &height, &stb_pixel_format, + STBI_rgb_alpha); + if (pixels == NULL) + return FALSE; /* Store bitmap info */ bmp->data = g_memdup (pixels, height * width * 4); diff --git a/clutter/cogl/common/cogl-bitmap.c b/clutter/cogl/common/cogl-bitmap.c index 6f31ebb5c..b0a3b7138 100644 --- a/clutter/cogl/common/cogl-bitmap.c +++ b/clutter/cogl/common/cogl-bitmap.c @@ -149,6 +149,14 @@ _cogl_bitmap_copy_subregion (CoglBitmap *src, } } +gboolean +cogl_bitmap_get_size_from_file (const gchar *filename, + gint *width, + gint *height) +{ + return _cogl_bitmap_get_size_from_file (filename, width, height); +} + CoglBitmap * cogl_bitmap_new_from_file (const gchar *filename, GError **error) diff --git a/clutter/cogl/common/cogl-bitmap.h b/clutter/cogl/common/cogl-bitmap.h index 9baec8170..953ac5a32 100644 --- a/clutter/cogl/common/cogl-bitmap.h +++ b/clutter/cogl/common/cogl-bitmap.h @@ -90,4 +90,9 @@ _cogl_bitmap_copy_subregion (CoglBitmap *src, gint width, gint height); +gboolean +_cogl_bitmap_get_size_from_file (const gchar *filename, + gint *width, + gint *height); + #endif /* __COGL_BITMAP_H */ From 168d558bcf7dcbd707364004fe968093b5bb5546 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Mon, 12 Jan 2009 16:54:30 +0000 Subject: [PATCH 3/5] [async-loading] Update asynchronous image loading Provide a main loop-based fallback to the asynchronous loading in case the GLib threading support hasn't been enabled. This also allows us to clean up the asynchronous loading machinery and have it behave consistently across different scenarios. Emit the ::load-finished even if the asynchronous loading from disk was not enabled. Finally, block clutter_texture_set_from_file() until we have an image width and height, so that querying the texture actor size after set_from_file() will still yield the correct result even when asynchronous loading is set. --- clutter/clutter-texture.c | 307 ++++++++++++++++++++++++++++---------- 1 file changed, 230 insertions(+), 77 deletions(-) diff --git a/clutter/clutter-texture.c b/clutter/clutter-texture.c index 10cb2172c..c1a55c3fe 100644 --- a/clutter/clutter-texture.c +++ b/clutter/clutter-texture.c @@ -99,10 +99,10 @@ struct _ClutterTexturePrivate guint load_async : 1; GThread *load_thread; + guint load_idle; gchar *load_filename; CoglBitmap *load_bitmap; GError *load_error; - guint load_source; }; enum @@ -612,8 +612,15 @@ clutter_texture_paint (ClutterActor *self) 0, 0, t_w, t_h); } +/* + * clutter_texture_async_load_cancel: + * @texture: a #ClutterTexture + * + * Cancels an asynchronous loading operation, whether done + * with threads enabled or just using the main loop + */ static void -clutter_texture_thread_cancel (ClutterTexture *texture) +clutter_texture_async_load_cancel (ClutterTexture *texture) { ClutterTexturePrivate *priv = texture->priv; @@ -622,23 +629,26 @@ clutter_texture_thread_cancel (ClutterTexture *texture) g_thread_join (priv->load_thread); priv->load_thread = NULL; } - - if (priv->load_source) + + if (priv->load_idle) { - g_source_remove (priv->load_source); - priv->load_source = 0; - - if (priv->load_error) - { - g_error_free (priv->load_error); - priv->load_error = NULL; - } - else - { - cogl_bitmap_free (priv->load_bitmap); - priv->load_bitmap = NULL; - } + g_source_remove (priv->load_idle); + priv->load_idle = 0; } + + if (priv->load_error) + { + g_error_free (priv->load_error); + priv->load_error = NULL; + } + + if (priv->load_bitmap) + { + cogl_bitmap_free (priv->load_bitmap); + priv->load_bitmap = NULL; + } + + g_free (priv->load_filename); } static void @@ -666,7 +676,7 @@ clutter_texture_dispose (GObject *object) priv->local_data = NULL; } - clutter_texture_thread_cancel (texture); + clutter_texture_async_load_cancel (texture); G_OBJECT_CLASS (clutter_texture_parent_class)->dispose (object); } @@ -730,8 +740,6 @@ clutter_texture_set_property (GObject *object, break; case PROP_LOAD_ASYNC: priv->load_async = g_value_get_boolean (value); - if (priv->load_async && !g_thread_supported()) - priv->load_async = FALSE; break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -911,6 +919,20 @@ clutter_texture_class_init (ClutterTextureClass *klass) FALSE, CLUTTER_PARAM_READWRITE)); + /** + * ClutterTexture:load-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_ASYNC, g_param_spec_boolean ("load-async", @@ -960,12 +982,14 @@ clutter_texture_class_init (ClutterTextureClass *klass) * @texture: the texture which received the signal * @error: A set error, or %NULL * - * The ::load-finished signal is emitted when asynchronous texture - * load has completed. If there was an error during loading, @error will - * be set. + * The ::load-finished signal is emitted when a texture load has + * completed. If there was an error during loading, @error will + * be set, otherwise it will be %NULL + * + * Since: 1.0 */ texture_signals[LOAD_FINISHED] = - g_signal_new ("load-finished", + g_signal_new (I_("load-finished"), G_TYPE_FROM_CLASS (gobject_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (ClutterTextureClass, load_finished), @@ -1340,14 +1364,14 @@ clutter_texture_set_from_rgb_data (ClutterTexture *texture, * Return value: %TRUE if the texture was successfully updated * * Since: 0.4 - **/ + */ gboolean -clutter_texture_set_from_yuv_data (ClutterTexture *texture, - const guchar *data, - gint width, - gint height, - ClutterTextureFlags flags, - GError **error) +clutter_texture_set_from_yuv_data (ClutterTexture *texture, + const guchar *data, + gint width, + gint height, + ClutterTextureFlags flags, + GError **error) { ClutterTexturePrivate *priv; @@ -1379,13 +1403,53 @@ clutter_texture_set_from_yuv_data (ClutterTexture *texture, error); } -static gboolean -clutter_texture_thread_cb (ClutterTexture *self) +/* + * clutter_texture_async_load_complete: + * @self: a #ClutterTexture + * @error: load error + * + * If @error is %NULL, loads the #CoglBitmap into a #CoglTexture. + * + * This function emits the ::load-finished signal on @self. + */ +static void +clutter_texture_async_load_complete (ClutterTexture *self, + const GError *error) { ClutterTexturePrivate *priv = self->priv; - - priv->load_source = 0; - + CoglHandle handle; + gboolean enable_mipmap = FALSE; + gint waste = -1; + + if (error == NULL) + { + if (priv->no_slice) + waste = priv->max_tile_waste; + + if (priv->filter_quality == CLUTTER_TEXTURE_QUALITY_HIGH) + enable_mipmap = TRUE; + + handle = cogl_texture_new_from_bitmap (priv->load_bitmap, + waste, enable_mipmap, + 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); +} + +static gboolean +clutter_texture_thread_cb (gpointer data) +{ + ClutterTexture *self = data; + ClutterTexturePrivate *priv = self->priv; + + priv->load_idle = 0; + if (priv->load_thread) { g_thread_join (priv->load_thread); @@ -1393,26 +1457,9 @@ clutter_texture_thread_cb (ClutterTexture *self) } else return FALSE; - - if (!priv->load_error) - { - CoglHandle handle; - - handle = cogl_texture_new_from_bitmap (priv->load_bitmap, - priv->no_slice ? - -1 : priv->max_tile_waste, - priv->filter_quality == - CLUTTER_TEXTURE_QUALITY_HIGH, - 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, priv->load_error); - + + clutter_texture_async_load_complete (self, priv->load_error); + if (priv->load_error) { g_error_free (priv->load_error); @@ -1423,21 +1470,116 @@ clutter_texture_thread_cb (ClutterTexture *self) } static gpointer -clutter_texture_thread_func (ClutterTexture *self) +clutter_texture_thread_func (gpointer data) { + ClutterTexture *self = data; ClutterTexturePrivate *priv = self->priv; - + /* Try loading with imaging backend */ priv->load_bitmap = cogl_bitmap_new_from_file (priv->load_filename, &priv->load_error); g_free (priv->load_filename); priv->load_filename = NULL; - - clutter_threads_add_idle ((GSourceFunc)clutter_texture_thread_cb, self); - + + /* 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); + return NULL; } +static gboolean +clutter_texture_idle_func (gpointer data) +{ + ClutterTexture *self = data; + ClutterTexturePrivate *priv = self->priv; + GError *internal_error; + + internal_error = NULL; + priv->load_bitmap = cogl_bitmap_new_from_file (priv->load_filename, + &internal_error); + + clutter_texture_async_load_complete (self, internal_error); + + g_free (priv->load_filename); + priv->load_filename = NULL; + + if (internal_error) + g_error_free (internal_error); + + return FALSE; +} + +/* + * clutter_texture_async_load: + * @self: a #ClutterTexture + * @error: return location for a #GError + * + * Starts an asynchronous load of the file name stored inside + * the load_filename private member. + * + * If threading is enabled we use a GThread to perform the actual + * I/O; if threading is not enabled, we use an idle GSource. + * + * The I/O is the only bit done in a thread -- uploading the + * texture data to the GL pipeline must be done from within the + * same thread that called clutter_main(). Threaded upload should + * be part of the GL implementation. + * + * This function will block until we get a size from the file + * so that we can effectively get the size the texture actor after + * clutter_texture_set_from_file(). + * + * Return value: %TRUE if the asynchronous loading was successfully + * initiated, %FALSE otherwise + */ +static gboolean +clutter_texture_async_load (ClutterTexture *self, + GError **error) +{ + ClutterTexturePrivate *priv = self->priv; + 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 (!res) + { + g_set_error (error, CLUTTER_TEXTURE_ERROR, + CLUTTER_TEXTURE_ERROR_BAD_FORMAT, + "Failed to create COGL texture"); + return FALSE; + } + else + clutter_actor_set_size (CLUTTER_ACTOR (self), width, height); + + if (g_thread_supported ()) + { + priv->load_thread = + g_thread_create ((GThreadFunc) clutter_texture_thread_func, + self, TRUE, + error); + + return priv->load_thread != NULL? TRUE : FALSE; + } + else + { + priv->load_idle = + clutter_threads_add_idle (clutter_texture_idle_func, self); + + return TRUE; + } +} + /** * clutter_texture_set_from_file: * @texture: A #ClutterTexture @@ -1456,8 +1598,11 @@ clutter_texture_set_from_file (ClutterTexture *texture, const gchar *filename, GError **error) { - CoglHandle new_texture; - ClutterTexturePrivate *priv; + ClutterTexturePrivate *priv; + CoglHandle new_texture = COGL_INVALID_HANDLE; + GError *internal_error = NULL; + gboolean enable_mipmap = FALSE; + gint max_waste = -1; priv = texture->priv; @@ -1465,30 +1610,36 @@ clutter_texture_set_from_file (ClutterTexture *texture, if (priv->load_async) { - clutter_texture_thread_cancel (texture); - + clutter_texture_async_load_cancel (texture); + priv->load_filename = g_strdup (filename); - priv->load_thread = g_thread_create ((GThreadFunc) - clutter_texture_thread_func, - texture, TRUE, error); - return priv->load_thread ? TRUE : FALSE; + + return clutter_texture_async_load (texture, error); } - if ((new_texture = cogl_texture_new_from_file - (filename, - priv->no_slice ? -1 : priv->max_tile_waste, - priv->filter_quality == CLUTTER_TEXTURE_QUALITY_HIGH, - COGL_PIXEL_FORMAT_ANY, - error)) - == COGL_INVALID_HANDLE) + if (priv->no_slice) + max_waste = priv->max_tile_waste; + + if (priv->filter_quality == CLUTTER_TEXTURE_QUALITY_HIGH) + enable_mipmap = TRUE; + + new_texture = cogl_texture_new_from_file (filename, + max_waste, enable_mipmap, + COGL_PIXEL_FORMAT_ANY, + &internal_error); + if (new_texture == COGL_INVALID_HANDLE) { /* If COGL didn't give an error then make one up */ - if (error && *error == NULL) + if (internal_error == NULL) { g_set_error (error, CLUTTER_TEXTURE_ERROR, CLUTTER_TEXTURE_ERROR_BAD_FORMAT, "Failed to create COGL texture"); } + else + g_propagate_error (error, internal_error); + + g_signal_emit (texture, texture_signals[LOAD_FINISHED], 0, error); return FALSE; } @@ -1501,6 +1652,8 @@ clutter_texture_set_from_file (ClutterTexture *texture, cogl_texture_unref (new_texture); + g_signal_emit (texture, texture_signals[LOAD_FINISHED], 0, error); + return TRUE; } From 5ed62aaf76b49f9539d722f8b78ca99b8304a791 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Mon, 12 Jan 2009 17:09:47 +0000 Subject: [PATCH 4/5] [async-loading] Do not force the texture size on async load The size of the texture as retrieved by the filename should be set as the image size, not as the actor size, in order to respect the :sync-size property. When the asynchronous loading process terminates, we queue a relayout so that the scene is updated. --- clutter/clutter-texture.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/clutter/clutter-texture.c b/clutter/clutter-texture.c index c1a55c3fe..71d1f288d 100644 --- a/clutter/clutter-texture.c +++ b/clutter/clutter-texture.c @@ -1440,6 +1440,8 @@ clutter_texture_async_load_complete (ClutterTexture *self, } g_signal_emit (self, texture_signals[LOAD_FINISHED], 0, error); + + clutter_actor_queue_relayout (CLUTTER_ACTOR (self)); } static gboolean @@ -1560,7 +1562,10 @@ clutter_texture_async_load (ClutterTexture *self, return FALSE; } else - clutter_actor_set_size (CLUTTER_ACTOR (self), width, height); + { + priv->width = width; + priv->height = height; + } if (g_thread_supported ()) { @@ -1589,6 +1594,12 @@ clutter_texture_async_load (ClutterTexture *self, * Sets the #ClutterTexture image data from an image file. In case of * failure, %FALSE is returned and @error is set. * + * 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. + * * Return value: %TRUE if the image was successfully loaded and set * * Since: 0.8 From e730cd70b1c3cc23b01936878ac913a9fd7d7308 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Mon, 12 Jan 2009 17:12:24 +0000 Subject: [PATCH 5/5] Whitespace fixes in ClutterTexture Small whitespace fixes patch; ClutterTexture requires much more love than I can provide at the moment. --- clutter/clutter-texture.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/clutter/clutter-texture.c b/clutter/clutter-texture.c index 71d1f288d..a3b1bd3f8 100644 --- a/clutter/clutter-texture.c +++ b/clutter/clutter-texture.c @@ -263,8 +263,8 @@ clutter_texture_realize (ClutterActor *actor) if (priv->texture != COGL_INVALID_HANDLE) cogl_texture_unref (priv->texture); - priv->texture - = cogl_texture_new_with_size + priv->texture = + cogl_texture_new_with_size (priv->width, priv->height, priv->no_slice ? -1 : priv->max_tile_waste, @@ -2026,7 +2026,7 @@ on_fbo_source_size_change (GObject *object, return; } - clutter_actor_set_size (CLUTTER_ACTOR(texture), w, h); + clutter_actor_set_size (CLUTTER_ACTOR (texture), w, h); } } @@ -2201,9 +2201,9 @@ clutter_texture_new_from_actor (ClutterActor *actor) priv->width = w; priv->height = h; - clutter_actor_set_size (CLUTTER_ACTOR(texture), priv->width, priv->height); + clutter_actor_set_size (CLUTTER_ACTOR (texture), priv->width, priv->height); - return CLUTTER_ACTOR(texture); + return CLUTTER_ACTOR (texture); } static void