Allow multiple CoglAtlases for textures

Previously Cogl would only ever use one atlas for textures and if it
reached the maximum texture size then all other new textures would get
their own GL texture. This patch makes it so that we create as many
atlases as needed. This should avoid breaking up some batches and it
will be particularly good if we switch to always using multi-texturing
with a default shader that selects between multiple atlases using a
vertex attribute.

Whenever a new atlas is created it is stored in a GSList on the
context. A weak weference is taken on the atlas using
cogl_object_set_user_data so that it can be removed from the list when
the atlas is destroyed. The atlas textures themselves take a reference
to the atlas and this is the only thing that keeps the atlas
alive. This means that once the atlas becomes empty it will
automatically be destroyed.

All of the COGL_NOTEs pertaining to atlases are now prefixed with the
atlas pointer to make it clearer which atlas is changing.
This commit is contained in:
Neil Roberts 2010-11-27 13:15:02 +00:00
parent 0f0f763570
commit 9aea72fab5
7 changed files with 93 additions and 52 deletions

View File

@ -46,9 +46,10 @@ struct _CoglAtlasTexture
atlas. This includes the 1-pixel border */
CoglRectangleMapEntry rectangle;
/* The texture might need to be migrated out in which case this will
be set to TRUE and sub_texture will actually be a real texture */
gboolean in_atlas;
/* The atlas that this texture is in. If the texture is no longer in
an atlas then this will be NULL. A reference is taken on the
atlas by the texture (but not vice versa so there is no cycle) */
CoglAtlas *atlas;
/* A CoglSubTexture representing the region for easy rendering */
CoglHandle sub_texture;
@ -62,7 +63,4 @@ _cogl_atlas_texture_new_from_bitmap (CoglBitmap *bmp,
CoglTextureFlags flags,
CoglPixelFormat internal_format);
CoglAtlas *
_cogl_atlas_texture_get_atlas (void);
#endif /* __COGL_ATLAS_TEXTURE_H */

View File

@ -96,7 +96,8 @@ _cogl_atlas_texture_reorganize_foreach_cb (const CoglRectangleMapEntry *entry,
static void
_cogl_atlas_texture_reorganize_cb (void *data)
{
CoglAtlas *atlas;
CoglAtlas *atlas = data;
/* We don't know if any pipelines may currently be referenced in
* the journal that depend on the current underlying GL texture
* storage so we flush the journal before migrating.
@ -106,31 +107,49 @@ _cogl_atlas_texture_reorganize_cb (void *data)
*/
_cogl_journal_flush ();
atlas = _cogl_atlas_texture_get_atlas ();
if (atlas->map)
_cogl_rectangle_map_foreach (atlas->map,
_cogl_atlas_texture_reorganize_foreach_cb,
NULL);
}
CoglAtlas *
_cogl_atlas_texture_get_atlas (void)
static void
_cogl_atlas_texture_atlas_destroyed_cb (void *user_data)
{
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
/* Remove the atlas from the global list */
ctx->atlases = g_slist_remove (ctx->atlases, user_data);
}
static CoglAtlas *
_cogl_atlas_texture_create_atlas (void)
{
static CoglUserDataKey atlas_private_key;
CoglAtlas *atlas;
_COGL_GET_CONTEXT (ctx, COGL_INVALID_HANDLE);
if (ctx->atlas == COGL_INVALID_HANDLE)
{
ctx->atlas = _cogl_atlas_new (COGL_PIXEL_FORMAT_RGBA_8888,
atlas = _cogl_atlas_new (COGL_PIXEL_FORMAT_RGBA_8888,
0,
_cogl_atlas_texture_update_position_cb);
_cogl_atlas_add_reorganize_callback (ctx->atlas,
_cogl_atlas_add_reorganize_callback (atlas,
_cogl_atlas_texture_reorganize_cb,
NULL);
}
atlas);
return ctx->atlas;
ctx->atlases = g_slist_prepend (ctx->atlases, atlas);
/* Set some data on the atlas so we can get notification when it is
destroyed in order to remove it from the list. ctx->atlases
effectively holds a weak reference. We don't need a strong
reference because the atlas textures take a reference on the
atlas so it will stay alive */
cogl_object_set_user_data (COGL_OBJECT (atlas), &atlas_private_key, atlas,
_cogl_atlas_texture_atlas_destroyed_cb);
return atlas;
}
static void
@ -173,12 +192,13 @@ _cogl_atlas_texture_set_wrap_mode_parameters (CoglTexture *tex,
static void
_cogl_atlas_texture_remove_from_atlas (CoglAtlasTexture *atlas_tex)
{
if (atlas_tex->in_atlas)
if (atlas_tex->atlas)
{
_cogl_atlas_remove (_cogl_atlas_texture_get_atlas (),
_cogl_atlas_remove (atlas_tex->atlas,
&atlas_tex->rectangle);
atlas_tex->in_atlas = FALSE;
cogl_object_unref (atlas_tex->atlas);
atlas_tex->atlas = NULL;
}
}
@ -270,7 +290,7 @@ static void
_cogl_atlas_texture_migrate_out_of_atlas (CoglAtlasTexture *atlas_tex)
{
/* Make sure this texture is not in the atlas */
if (atlas_tex->in_atlas)
if (atlas_tex->atlas)
{
COGL_NOTE (ATLAS, "Migrating texture out of the atlas");
@ -291,7 +311,7 @@ _cogl_atlas_texture_migrate_out_of_atlas (CoglAtlasTexture *atlas_tex)
cogl_handle_unref (atlas_tex->sub_texture);
atlas_tex->sub_texture =
_cogl_atlas_copy_rectangle (_cogl_atlas_texture_get_atlas (),
_cogl_atlas_copy_rectangle (atlas_tex->atlas,
atlas_tex->rectangle.x + 1,
atlas_tex->rectangle.y + 1,
atlas_tex->rectangle.width - 2,
@ -340,7 +360,7 @@ _cogl_atlas_texture_set_region_with_border (CoglAtlasTexture *atlas_tex,
unsigned int dst_height,
CoglBitmap *bmp)
{
CoglAtlas *atlas = _cogl_atlas_texture_get_atlas ();
CoglAtlas *atlas = atlas_tex->atlas;
/* Copy the central data */
if (!_cogl_texture_set_region_from_bitmap (atlas->texture,
@ -408,7 +428,7 @@ _cogl_atlas_texture_set_region (CoglTexture *tex,
/* If the texture is in the atlas then we need to copy the edge
pixels to the border */
if (atlas_tex->in_atlas)
if (atlas_tex->atlas)
{
gboolean ret;
@ -505,6 +525,8 @@ _cogl_atlas_texture_new_from_bitmap (CoglBitmap *bmp,
int bmp_width;
int bmp_height;
CoglPixelFormat bmp_format;
CoglAtlas *atlas;
GSList *l;
_COGL_GET_CONTEXT (ctx, COGL_INVALID_HANDLE);
@ -557,15 +579,34 @@ _cogl_atlas_texture_new_from_bitmap (CoglBitmap *bmp,
atlas_tex->sub_texture = COGL_INVALID_HANDLE;
/* Look for an existing atlas that can hold the texture */
for (l = ctx->atlases; l; l = l->next)
/* Try to make some space in the atlas for the texture */
if (!_cogl_atlas_reserve_space (_cogl_atlas_texture_get_atlas (),
if (_cogl_atlas_reserve_space (atlas = l->data,
/* Add two pixels for the border */
bmp_width + 2, bmp_height + 2,
atlas_tex))
{
cogl_object_ref (atlas);
break;
}
/* If we couldn't find a suitable atlas then start another */
if (l == NULL)
{
atlas = _cogl_atlas_texture_create_atlas ();
COGL_NOTE (ATLAS, "Created new atlas for textures: %p", atlas);
if (!_cogl_atlas_reserve_space (atlas,
/* Add two pixels for the border */
bmp_width + 2, bmp_height + 2,
atlas_tex))
{
/* Ok, this means we really can't add it to the atlas */
cogl_object_unref (atlas);
g_free (atlas_tex);
return COGL_INVALID_HANDLE;
}
}
dst_bmp = _cogl_texture_prepare_for_upload (bmp,
internal_format,
@ -576,15 +617,15 @@ _cogl_atlas_texture_new_from_bitmap (CoglBitmap *bmp,
if (dst_bmp == NULL)
{
_cogl_atlas_remove (_cogl_atlas_texture_get_atlas (),
&atlas_tex->rectangle);
_cogl_atlas_remove (atlas, &atlas_tex->rectangle);
cogl_object_unref (atlas);
g_free (atlas_tex);
return COGL_INVALID_HANDLE;
}
atlas_tex->_parent.vtable = &cogl_atlas_texture_vtable;
atlas_tex->format = internal_format;
atlas_tex->in_atlas = TRUE;
atlas_tex->atlas = atlas;
/* Make another bitmap so that we can override the format */
override_bmp = _cogl_bitmap_new_shared (dst_bmp,

View File

@ -489,7 +489,8 @@ _cogl_atlas_reserve_space (CoglAtlas *atlas,
_cogl_rectangle_map_get_remaining_space (atlas->map) *
100 / (_cogl_rectangle_map_get_width (atlas->map) *
_cogl_rectangle_map_get_height (atlas->map));
COGL_NOTE (ATLAS, "Atlas is %ix%i, has %i textures and is %i%% waste",
COGL_NOTE (ATLAS, "%p: Atlas is %ix%i, has %i textures and is %i%% waste",
atlas,
_cogl_rectangle_map_get_width (atlas->map),
_cogl_rectangle_map_get_height (atlas->map),
_cogl_rectangle_map_get_n_rectangles (atlas->map),
@ -559,7 +560,7 @@ _cogl_atlas_reserve_space (CoglAtlas *atlas,
/* If we can't create a map with the texture then give up */
if (new_map == NULL)
{
COGL_NOTE (ATLAS, "Could not fit texture in the atlas");
COGL_NOTE (ATLAS, "%p: Could not fit texture in the atlas", atlas);
ret = FALSE;
}
/* We need to migrate the existing textures into a new texture */
@ -568,7 +569,7 @@ _cogl_atlas_reserve_space (CoglAtlas *atlas,
_cogl_rectangle_map_get_width (new_map),
_cogl_rectangle_map_get_height (new_map))) == COGL_INVALID_HANDLE)
{
COGL_NOTE (ATLAS, "Could not create a CoglTexture2D");
COGL_NOTE (ATLAS, "%p: Could not create a CoglTexture2D", atlas);
_cogl_rectangle_map_free (new_map);
ret = FALSE;
}
@ -579,7 +580,8 @@ _cogl_atlas_reserve_space (CoglAtlas *atlas,
_cogl_atlas_notify_reorganize (atlas);
COGL_NOTE (ATLAS,
"Atlas %s with size %ix%i",
"%p: Atlas %s with size %ix%i",
atlas,
atlas->map == NULL ||
_cogl_rectangle_map_get_width (atlas->map) !=
_cogl_rectangle_map_get_width (new_map) ||
@ -616,7 +618,8 @@ _cogl_atlas_reserve_space (CoglAtlas *atlas,
100 / (_cogl_rectangle_map_get_width (atlas->map) *
_cogl_rectangle_map_get_height (atlas->map)));
COGL_NOTE (ATLAS, "Atlas is %ix%i, has %i textures and is %i%% waste",
COGL_NOTE (ATLAS, "%p: Atlas is %ix%i, has %i textures and is %i%% waste",
atlas,
_cogl_rectangle_map_get_width (atlas->map),
_cogl_rectangle_map_get_height (atlas->map),
_cogl_rectangle_map_get_n_rectangles (atlas->map),
@ -636,10 +639,12 @@ _cogl_atlas_remove (CoglAtlas *atlas,
{
_cogl_rectangle_map_remove (atlas->map, rectangle);
COGL_NOTE (ATLAS, "Removed rectangle sized %ix%i",
COGL_NOTE (ATLAS, "%p: Removed rectangle sized %ix%i",
atlas,
rectangle->width,
rectangle->height);
COGL_NOTE (ATLAS, "Atlas is %ix%i, has %i textures and is %i%% waste",
COGL_NOTE (ATLAS, "%p: Atlas is %ix%i, has %i textures and is %i%% waste",
atlas,
_cogl_rectangle_map_get_width (atlas->map),
_cogl_rectangle_map_get_height (atlas->map),
_cogl_rectangle_map_get_n_rectangles (atlas->map),

View File

@ -82,9 +82,6 @@ _cogl_atlas_copy_rectangle (CoglAtlas *atlas,
CoglTextureFlags flags,
CoglPixelFormat format);
CoglAtlas *
_cogl_atlas_get_default (void);
void
_cogl_atlas_add_reorganize_callback (CoglAtlas *atlas,
CoglCallbackListFunc callback,

View File

@ -285,7 +285,7 @@ cogl_create_context (void)
_cogl_enable (enable_flags);
_cogl_flush_face_winding ();
_context->atlas = NULL;
_context->atlases = NULL;
/* As far as I can tell, GL_POINT_SPRITE doesn't have any effect
unless GL_COORD_REPLACE is enabled for an individual
@ -362,8 +362,7 @@ _cogl_destroy_context (void)
if (_context->current_clip_stack_valid)
_cogl_clip_stack_unref (_context->current_clip_stack);
if (_context->atlas)
cogl_object_unref (_context->atlas);
g_slist_free (_context->atlases);
_cogl_bitmask_destroy (&_context->arrays_enabled);
_cogl_bitmask_destroy (&_context->temp_bitmask);

View File

@ -178,7 +178,7 @@ typedef struct
CoglPipeline *texture_download_pipeline;
CoglAtlas *atlas;
GSList *atlases;
/* This debugging variable is used to pick a colour for visually
displaying the quad batches. It needs to be global so that it can

View File

@ -228,6 +228,7 @@ cogl_pango_glyph_cache_lookup (CoglPangoGlyphCache *cache,
atlas = _cogl_atlas_new (COGL_PIXEL_FORMAT_A_8,
TRUE,
cogl_pango_glyph_cache_update_position_cb);
COGL_NOTE (ATLAS, "Created new atlas for glyphs: %p", atlas);
/* If we still can't reserve space then something has gone
seriously wrong so we'll just give up */
if (!_cogl_atlas_reserve_space (atlas,