[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.
This commit is contained in:
Emmanuele Bassi 2009-01-12 16:54:30 +00:00
parent 1c114be31a
commit 168d558bcf

View File

@ -99,10 +99,10 @@ struct _ClutterTexturePrivate
guint load_async : 1; guint load_async : 1;
GThread *load_thread; GThread *load_thread;
guint load_idle;
gchar *load_filename; gchar *load_filename;
CoglBitmap *load_bitmap; CoglBitmap *load_bitmap;
GError *load_error; GError *load_error;
guint load_source;
}; };
enum enum
@ -612,8 +612,15 @@ clutter_texture_paint (ClutterActor *self)
0, 0, t_w, t_h); 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 static void
clutter_texture_thread_cancel (ClutterTexture *texture) clutter_texture_async_load_cancel (ClutterTexture *texture)
{ {
ClutterTexturePrivate *priv = texture->priv; ClutterTexturePrivate *priv = texture->priv;
@ -622,23 +629,26 @@ clutter_texture_thread_cancel (ClutterTexture *texture)
g_thread_join (priv->load_thread); g_thread_join (priv->load_thread);
priv->load_thread = NULL; priv->load_thread = NULL;
} }
if (priv->load_source) if (priv->load_idle)
{ {
g_source_remove (priv->load_source); g_source_remove (priv->load_idle);
priv->load_source = 0; priv->load_idle = 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;
}
} }
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 static void
@ -666,7 +676,7 @@ clutter_texture_dispose (GObject *object)
priv->local_data = NULL; priv->local_data = NULL;
} }
clutter_texture_thread_cancel (texture); clutter_texture_async_load_cancel (texture);
G_OBJECT_CLASS (clutter_texture_parent_class)->dispose (object); G_OBJECT_CLASS (clutter_texture_parent_class)->dispose (object);
} }
@ -730,8 +740,6 @@ clutter_texture_set_property (GObject *object,
break; break;
case PROP_LOAD_ASYNC: case PROP_LOAD_ASYNC:
priv->load_async = g_value_get_boolean (value); priv->load_async = g_value_get_boolean (value);
if (priv->load_async && !g_thread_supported())
priv->load_async = FALSE;
break; break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@ -911,6 +919,20 @@ clutter_texture_class_init (ClutterTextureClass *klass)
FALSE, FALSE,
CLUTTER_PARAM_READWRITE)); 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 g_object_class_install_property
(gobject_class, PROP_LOAD_ASYNC, (gobject_class, PROP_LOAD_ASYNC,
g_param_spec_boolean ("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 * @texture: the texture which received the signal
* @error: A set error, or %NULL * @error: A set error, or %NULL
* *
* The ::load-finished signal is emitted when asynchronous texture * The ::load-finished signal is emitted when a texture load has
* load has completed. If there was an error during loading, @error will * completed. If there was an error during loading, @error will
* be set. * be set, otherwise it will be %NULL
*
* Since: 1.0
*/ */
texture_signals[LOAD_FINISHED] = texture_signals[LOAD_FINISHED] =
g_signal_new ("load-finished", g_signal_new (I_("load-finished"),
G_TYPE_FROM_CLASS (gobject_class), G_TYPE_FROM_CLASS (gobject_class),
G_SIGNAL_RUN_LAST, G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (ClutterTextureClass, load_finished), 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 * Return value: %TRUE if the texture was successfully updated
* *
* Since: 0.4 * Since: 0.4
**/ */
gboolean gboolean
clutter_texture_set_from_yuv_data (ClutterTexture *texture, clutter_texture_set_from_yuv_data (ClutterTexture *texture,
const guchar *data, const guchar *data,
gint width, gint width,
gint height, gint height,
ClutterTextureFlags flags, ClutterTextureFlags flags,
GError **error) GError **error)
{ {
ClutterTexturePrivate *priv; ClutterTexturePrivate *priv;
@ -1379,13 +1403,53 @@ clutter_texture_set_from_yuv_data (ClutterTexture *texture,
error); 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; ClutterTexturePrivate *priv = self->priv;
CoglHandle handle;
priv->load_source = 0; 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) if (priv->load_thread)
{ {
g_thread_join (priv->load_thread); g_thread_join (priv->load_thread);
@ -1393,26 +1457,9 @@ clutter_texture_thread_cb (ClutterTexture *self)
} }
else else
return FALSE; return FALSE;
if (!priv->load_error) clutter_texture_async_load_complete (self, 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) if (priv->load_error)
{ {
g_error_free (priv->load_error); g_error_free (priv->load_error);
@ -1423,21 +1470,116 @@ clutter_texture_thread_cb (ClutterTexture *self)
} }
static gpointer static gpointer
clutter_texture_thread_func (ClutterTexture *self) clutter_texture_thread_func (gpointer data)
{ {
ClutterTexture *self = data;
ClutterTexturePrivate *priv = self->priv; ClutterTexturePrivate *priv = self->priv;
/* Try loading with imaging backend */ /* Try loading with imaging backend */
priv->load_bitmap = cogl_bitmap_new_from_file (priv->load_filename, priv->load_bitmap = cogl_bitmap_new_from_file (priv->load_filename,
&priv->load_error); &priv->load_error);
g_free (priv->load_filename); g_free (priv->load_filename);
priv->load_filename = NULL; 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; 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: * clutter_texture_set_from_file:
* @texture: A #ClutterTexture * @texture: A #ClutterTexture
@ -1456,8 +1598,11 @@ clutter_texture_set_from_file (ClutterTexture *texture,
const gchar *filename, const gchar *filename,
GError **error) 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; priv = texture->priv;
@ -1465,30 +1610,36 @@ clutter_texture_set_from_file (ClutterTexture *texture,
if (priv->load_async) if (priv->load_async)
{ {
clutter_texture_thread_cancel (texture); clutter_texture_async_load_cancel (texture);
priv->load_filename = g_strdup (filename); priv->load_filename = g_strdup (filename);
priv->load_thread = g_thread_create ((GThreadFunc)
clutter_texture_thread_func, return clutter_texture_async_load (texture, error);
texture, TRUE, error);
return priv->load_thread ? TRUE : FALSE;
} }
if ((new_texture = cogl_texture_new_from_file if (priv->no_slice)
(filename, max_waste = priv->max_tile_waste;
priv->no_slice ? -1 : priv->max_tile_waste,
priv->filter_quality == CLUTTER_TEXTURE_QUALITY_HIGH, if (priv->filter_quality == CLUTTER_TEXTURE_QUALITY_HIGH)
COGL_PIXEL_FORMAT_ANY, enable_mipmap = TRUE;
error))
== COGL_INVALID_HANDLE) 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 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, g_set_error (error, CLUTTER_TEXTURE_ERROR,
CLUTTER_TEXTURE_ERROR_BAD_FORMAT, CLUTTER_TEXTURE_ERROR_BAD_FORMAT,
"Failed to create COGL texture"); "Failed to create COGL texture");
} }
else
g_propagate_error (error, internal_error);
g_signal_emit (texture, texture_signals[LOAD_FINISHED], 0, error);
return FALSE; return FALSE;
} }
@ -1501,6 +1652,8 @@ clutter_texture_set_from_file (ClutterTexture *texture,
cogl_texture_unref (new_texture); cogl_texture_unref (new_texture);
g_signal_emit (texture, texture_signals[LOAD_FINISHED], 0, error);
return TRUE; return TRUE;
} }