diff --git a/clutter/clutter-texture.c b/clutter/clutter-texture.c index 086a5134c..a3b1bd3f8 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; + guint load_idle; + gchar *load_filename; + CoglBitmap *load_bitmap; + GError *load_error; }; 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 }; @@ -252,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, @@ -601,6 +612,45 @@ 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_async_load_cancel (ClutterTexture *texture) +{ + ClutterTexturePrivate *priv = texture->priv; + + if (priv->load_thread) + { + g_thread_join (priv->load_thread); + priv->load_thread = NULL; + } + + if (priv->load_idle) + { + 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 clutter_texture_dispose (GObject *object) { @@ -625,7 +675,9 @@ clutter_texture_dispose (GObject *object) g_free (priv->local_data); priv->local_data = NULL; } - + + clutter_texture_async_load_cancel (texture); + G_OBJECT_CLASS (clutter_texture_parent_class)->dispose (object); } @@ -686,6 +738,9 @@ 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); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -736,6 +791,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 +919,29 @@ 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", + "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 +977,27 @@ 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 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 (I_("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; @@ -1262,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; @@ -1301,6 +1403,188 @@ clutter_texture_set_from_yuv_data (ClutterTexture *texture, error); } +/* + * 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; + 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); + + clutter_actor_queue_relayout (CLUTTER_ACTOR (self)); +} + +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); + priv->load_thread = NULL; + } + else + return FALSE; + + clutter_texture_async_load_complete (self, 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 (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; + + /* 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 + { + priv->width = width; + priv->height = 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 @@ -1310,6 +1594,12 @@ clutter_texture_set_from_yuv_data (ClutterTexture *texture, * 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 @@ -1319,28 +1609,48 @@ 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; g_return_val_if_fail (error == NULL || *error == NULL, FALSE); - 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->load_async) + { + clutter_texture_async_load_cancel (texture); + + priv->load_filename = g_strdup (filename); + + return clutter_texture_async_load (texture, error); + } + + 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; } @@ -1353,6 +1663,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; } @@ -1714,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); } } @@ -1889,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 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 5a854d7b0..cee2f439e 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,45 @@ 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. + * + * 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. + * + * Frees a #CoglBitmap. + */ +void cogl_bitmap_free (CoglBitmap *bmp); + /** * cogl_texture_multiple_rectangles: * @handle: a @CoglHandle. diff --git a/clutter/cogl/cogl-types.h b/clutter/cogl/cogl-types.h index 3ae0d9caa..ece3270b7 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-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 39341e0b0..b0a3b7138 100644 --- a/clutter/cogl/common/cogl-bitmap.c +++ b/clutter/cogl/common/cogl-bitmap.c @@ -148,3 +148,42 @@ _cogl_bitmap_copy_subregion (CoglBitmap *src, dstdata += dst->rowstride; } } + +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) +{ + 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..953ac5a32 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; @@ -92,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 */ diff --git a/clutter/cogl/gl/cogl-texture.c b/clutter/cogl/gl/cogl-texture.c index a27a8b253..4c30639a9 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,