Merge branch 'async-texture-thread-pool'

This commit is contained in:
Øyvind Kolås 2009-03-16 00:40:27 +00:00
commit 0674fded7f
3 changed files with 364 additions and 131 deletions

View File

@ -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)) #define CLUTTER_TEXTURE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLUTTER_TYPE_TEXTURE, ClutterTexturePrivate))
typedef struct _ClutterTextureAsyncData ClutterTextureAsyncData;
struct _ClutterTexturePrivate struct _ClutterTexturePrivate
{ {
gint width; gint width;
@ -97,14 +99,37 @@ struct _ClutterTexturePrivate
guint repeat_y : 1; guint repeat_y : 1;
guint in_dispose : 1; guint in_dispose : 1;
guint keep_aspect_ratio : 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 */
GThread *load_thread; 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; guint load_idle;
gchar *load_filename; gchar *load_filename;
CoglBitmap *load_bitmap; CoglBitmap *load_bitmap;
GError *load_error; GError *load_error;
gboolean abort;
}; };
enum enum
@ -121,7 +146,8 @@ enum
PROP_COGL_MATERIAL, PROP_COGL_MATERIAL,
PROP_FILENAME, PROP_FILENAME,
PROP_KEEP_ASPECT_RATIO, PROP_KEEP_ASPECT_RATIO,
PROP_LOAD_ASYNC PROP_LOAD_ASYNC,
PROP_LOAD_DATA_ASYNC,
}; };
enum enum
@ -130,12 +156,13 @@ enum
PIXBUF_CHANGE, PIXBUF_CHANGE,
LOAD_SUCCESS, LOAD_SUCCESS,
LOAD_FINISHED, LOAD_FINISHED,
LAST_SIGNAL LAST_SIGNAL
}; };
static int texture_signals[LAST_SIGNAL] = { 0 }; static int texture_signals[LAST_SIGNAL] = { 0 };
static GThreadPool *async_thread_pool = NULL;
static void static void
texture_fbo_free_resources (ClutterTexture *texture); texture_fbo_free_resources (ClutterTexture *texture);
@ -630,6 +657,29 @@ clutter_texture_paint (ClutterActor *self)
0, 0, t_w, t_h); 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: * clutter_texture_async_load_cancel:
* @texture: a #ClutterTexture * @texture: a #ClutterTexture
@ -642,32 +692,40 @@ clutter_texture_async_load_cancel (ClutterTexture *texture)
{ {
ClutterTexturePrivate *priv = texture->priv; ClutterTexturePrivate *priv = texture->priv;
if (priv->load_thread) if (priv->async_data)
{ {
priv->abort = TRUE; GMutex *mutex = priv->async_data->mutex;
g_thread_join (priv->load_thread);
priv->load_thread = NULL; /* 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 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 (mutex)
g_mutex_unlock (mutex);
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);
} }
if (priv->load_idle) priv->async_data = 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 static void
@ -778,8 +836,20 @@ clutter_texture_set_property (GObject *object,
case PROP_KEEP_ASPECT_RATIO: case PROP_KEEP_ASPECT_RATIO:
priv->keep_aspect_ratio = g_value_get_boolean (value); priv->keep_aspect_ratio = g_value_get_boolean (value);
break; 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: 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; break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); 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: case PROP_KEEP_ASPECT_RATIO:
g_value_set_boolean (value, priv->keep_aspect_ratio); g_value_set_boolean (value, priv->keep_aspect_ratio);
break; break;
case PROP_LOAD_ASYNC:
g_value_set_boolean (value, priv->load_async);
break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break; break;
@ -974,17 +1041,23 @@ clutter_texture_class_init (ClutterTextureClass *klass)
FALSE, FALSE,
CLUTTER_PARAM_READWRITE)); CLUTTER_PARAM_READWRITE));
/** /**
* ClutterTexture:load-async: * ClutterTexture:load-async:
* *
* Tries to load a texture from a filename by using a local thread * Tries to load a texture from a filename by using a local thread to perform
* to perform the read operations. Threading is only enabled if * the read operations. The initially created texture has dimensions 0x0 when
* g_thread_init() has been called prior to clutter_init(), otherwise * the true size becomes available the #ClutterTexture::size-change signal is
* #ClutterTexture will use the main loop to load the image. * emitted and when the image has completed loading the
* #ClutterTexture::load-finished signal is emitted.
* *
* The upload of the texture data on the GL pipeline is not * Threading is only enabled if g_thread_init() has been called prior to
* asynchronous, as it must be performed from within the same * clutter_init(), otherwise #ClutterTexture will use the main loop to load
* thread that called clutter_main(). * 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 * Since: 1.0
*/ */
@ -995,7 +1068,27 @@ clutter_texture_class_init (ClutterTextureClass *klass)
"Load files inside a thread to avoid blocking when " "Load files inside a thread to avoid blocking when "
"loading images.", "loading images.",
FALSE, FALSE,
CLUTTER_PARAM_READWRITE)); 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: * ClutterTexture::size-change:
@ -1550,14 +1643,16 @@ clutter_texture_set_from_yuv_data (ClutterTexture *texture,
/* /*
* clutter_texture_async_load_complete: * clutter_texture_async_load_complete:
* @self: a #ClutterTexture * @self: a #ClutterTexture
* @bitmap: a #CoglBitmap
* @error: load error * @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. * This function emits the ::load-finished signal on @self.
*/ */
static void static void
clutter_texture_async_load_complete (ClutterTexture *self, clutter_texture_async_load_complete (ClutterTexture *self,
CoglBitmap *bitmap,
const GError *error) const GError *error)
{ {
ClutterTexturePrivate *priv = self->priv; ClutterTexturePrivate *priv = self->priv;
@ -1565,6 +1660,8 @@ clutter_texture_async_load_complete (ClutterTexture *self,
CoglTextureFlags flags = COGL_TEXTURE_NONE; CoglTextureFlags flags = COGL_TEXTURE_NONE;
gint waste = -1; gint waste = -1;
priv->async_data = NULL;
if (error == NULL) if (error == NULL)
{ {
if (!priv->no_slice) if (!priv->no_slice)
@ -1573,14 +1670,17 @@ clutter_texture_async_load_complete (ClutterTexture *self,
if (priv->filter_quality == CLUTTER_TEXTURE_QUALITY_HIGH) if (priv->filter_quality == CLUTTER_TEXTURE_QUALITY_HIGH)
flags |= COGL_TEXTURE_AUTO_MIPMAP; flags |= COGL_TEXTURE_AUTO_MIPMAP;
handle = cogl_texture_new_from_bitmap (priv->load_bitmap, handle = cogl_texture_new_from_bitmap (bitmap,
waste, flags, waste, flags,
COGL_PIXEL_FORMAT_ANY); COGL_PIXEL_FORMAT_ANY);
clutter_texture_set_cogl_texture (self, handle); 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));
}
cogl_texture_unref (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); g_signal_emit (self, texture_signals[LOAD_FINISHED], 0, error);
@ -1589,96 +1689,100 @@ clutter_texture_async_load_complete (ClutterTexture *self,
} }
static gboolean static gboolean
clutter_texture_thread_cb (gpointer data) clutter_texture_thread_idle_func (gpointer user_data)
{ {
ClutterTexture *self = data; ClutterTextureAsyncData *data = user_data;
ClutterTexturePrivate *priv = self->priv;
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) clutter_texture_async_load_complete (data->texture, data->load_bitmap,
{ data->load_error);
g_thread_join (priv->load_thread);
priv->load_thread = NULL;
}
else
return FALSE;
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; return FALSE;
} }
static gpointer static void
clutter_texture_thread_func (gpointer data) clutter_texture_thread_func (gpointer user_data, gpointer pool_data)
{ {
static GStaticMutex thread_load_mutex = G_STATIC_MUTEX_INIT; ClutterTextureAsyncData *data = user_data;
ClutterTexture *self = data; gboolean should_abort;
ClutterTexturePrivate *priv = self->priv;
/* we aquire the shared lock, only one thread is allowed to /* Make sure we haven't been told to abort before the thread had a
* be loading at a time chance to run */
*/ g_mutex_lock (data->mutex);
g_static_mutex_lock (&thread_load_mutex); should_abort = data->abort;
if (priv->abort) g_mutex_unlock (data->mutex);
if (should_abort)
{ {
g_static_mutex_unlock (&thread_load_mutex); /* If we've been told to abort then main thread has disowned the
g_free (priv->load_filename); async data and we need to free it */
priv->load_filename = NULL; clutter_texture_async_data_free (data);
return NULL; 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 data->load_bitmap = cogl_bitmap_new_from_file (data->load_filename,
* hold the main Clutter lock &data->load_error);
*/
priv->load_idle =
clutter_threads_add_idle (clutter_texture_thread_cb, self);
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 static gboolean
clutter_texture_idle_func (gpointer data) clutter_texture_idle_func (gpointer user_data)
{ {
ClutterTexture *self = data; ClutterTextureAsyncData *data = user_data;
ClutterTexturePrivate *priv = self->priv; GError *internal_error = NULL;
GError *internal_error;
internal_error = NULL; data->load_bitmap = cogl_bitmap_new_from_file (data->load_filename,
priv->load_bitmap = cogl_bitmap_new_from_file (priv->load_filename,
&internal_error); &internal_error);
clutter_texture_async_load_complete (self, internal_error); clutter_texture_async_load_complete (data->texture, data->load_bitmap,
internal_error);
g_free (priv->load_filename);
priv->load_filename = NULL;
if (internal_error) if (internal_error)
g_error_free (internal_error); g_error_free (internal_error);
clutter_texture_async_data_free (data);
return FALSE; return FALSE;
} }
/* /*
* clutter_texture_async_load: * clutter_texture_async_load:
* @self: a #ClutterTexture * @self: a #ClutterTExture
* @filename: name of the file to load
* @error: return location for a #GError * @error: return location for a #GError
* *
* Starts an asynchronous load of the file name stored inside * 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 * If threading is enabled we use a GThread to perform the actual
* I/O; if threading is not enabled, we use an idle GSource. * I/O; if threading is not enabled, we use an idle GSource.
@ -1697,21 +1801,30 @@ clutter_texture_idle_func (gpointer data)
*/ */
static gboolean static gboolean
clutter_texture_async_load (ClutterTexture *self, clutter_texture_async_load (ClutterTexture *self,
const gchar *filename,
GError **error) GError **error)
{ {
ClutterTexturePrivate *priv = self->priv; ClutterTexturePrivate *priv = self->priv;
ClutterTextureAsyncData *data;
gint width, height; gint width, height;
gboolean res; gboolean res;
g_assert (priv->load_filename != NULL);
/* ask the file for a size; if we cannot get the size then /* ask the file for a size; if we cannot get the size then
* there's no point in even continuing the asynchronous * there's no point in even continuing the asynchronous
* loading, so we just stop there * loading, so we just stop there
*/ */
res = cogl_bitmap_get_size_from_file (priv->load_filename,
&width, if (priv->load_size_async)
&height); {
res = TRUE;
width = 0;
height = 0;
}
else
{
res = cogl_bitmap_get_size_from_file (filename, &width, &height);
}
if (!res) if (!res)
{ {
g_set_error (error, CLUTTER_TEXTURE_ERROR, g_set_error (error, CLUTTER_TEXTURE_ERROR,
@ -1725,22 +1838,40 @@ clutter_texture_async_load (ClutterTexture *self,
priv->height = height; 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 ()) if (g_thread_supported ())
{ {
priv->load_thread = data->mutex = g_mutex_new ();
g_thread_create ((GThreadFunc) clutter_texture_thread_func,
self, TRUE,
error);
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 else
{ {
priv->load_idle = data->mutex = NULL;
clutter_threads_add_idle (clutter_texture_idle_func, self);
data->load_idle
= clutter_threads_add_idle (clutter_texture_idle_func, data);
}
return TRUE; return TRUE;
}
} }
/** /**
@ -1754,9 +1885,10 @@ clutter_texture_async_load (ClutterTexture *self,
* *
* If #ClutterTexture:load-async is set to %TRUE, this function * If #ClutterTexture:load-async is set to %TRUE, this function
* will return as soon as possible, and the actual image loading * will return as soon as possible, and the actual image loading
* from disk will be performed asynchronously. #ClutterTexture::load-finished * from disk will be performed asynchronously. #ClutterTexture::size-change
* will be emitted when the image has been loaded or if an error * will be emitten when the size of the texture is available and
* occurred. * #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 * Return value: %TRUE if the image was successfully loaded and set
* *
@ -1778,14 +1910,8 @@ clutter_texture_set_from_file (ClutterTexture *texture,
g_return_val_if_fail (error == NULL || *error == NULL, FALSE); 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);
clutter_texture_async_load_cancel (texture);
priv->load_filename = g_strdup (filename);
return clutter_texture_async_load (texture, error);
}
if (!priv->no_slice) if (!priv->no_slice)
max_waste = priv->max_tile_waste; max_waste = priv->max_tile_waste;

View File

@ -1,6 +1,7 @@
UNIT_TESTS = \ UNIT_TESTS = \
test-textures.c \ test-textures.c \
test-texture-async.c \
test-events.c \ test-events.c \
test-offscreen.c \ test-offscreen.c \
test-scale.c \ test-scale.c \

View File

@ -0,0 +1,106 @@
#include <stdlib.h>
#include <gmodule.h>
#include <clutter/clutter.h>
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",
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);
}
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);
g_thread_init (NULL);
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;
}