cogl-atlas-texture: Split out the atlas data structure
Instead of storing a pointer to the CoglRectangleMap and a handle to the atlas texture in the context, there is a now a separate data structure called a CoglAtlas to manage these two. The context just contains a pointer to this. The code to reorganise the atlas has been moved from cogl-atlas-texture.c to cogl-atlas.c
This commit is contained in:
parent
0c8582aad8
commit
9b2f8179f0
@ -146,6 +146,8 @@ cogl_sources_c = \
|
||||
$(srcdir)/cogl-texture-rectangle.c \
|
||||
$(srcdir)/cogl-rectangle-map.h \
|
||||
$(srcdir)/cogl-rectangle-map.c \
|
||||
$(srcdir)/cogl-atlas.h \
|
||||
$(srcdir)/cogl-atlas.c \
|
||||
$(srcdir)/cogl-atlas-texture-private.h \
|
||||
$(srcdir)/cogl-atlas-texture.c \
|
||||
$(srcdir)/cogl-spans.h \
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include "cogl-handle.h"
|
||||
#include "cogl-texture-private.h"
|
||||
#include "cogl-rectangle-map.h"
|
||||
#include "cogl-atlas.h"
|
||||
|
||||
#define COGL_ATLAS_TEXTURE(tex) ((CoglAtlasTexture *) tex)
|
||||
|
||||
@ -61,4 +62,7 @@ _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 */
|
||||
|
@ -42,172 +42,93 @@
|
||||
#include "cogl-rectangle-map.h"
|
||||
#include "cogl-journal-private.h"
|
||||
#include "cogl-material-opengl-private.h"
|
||||
#include "cogl-atlas.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef HAVE_COGL_GLES2
|
||||
|
||||
#include "../gles/cogl-gles2-wrapper.h"
|
||||
|
||||
#else /* HAVE_COGL_GLES2 */
|
||||
|
||||
#define glGenFramebuffers ctx->drv.pf_glGenFramebuffers
|
||||
#define glBindFramebuffer ctx->drv.pf_glBindFramebuffer
|
||||
#define glFramebufferTexture2D ctx->drv.pf_glFramebufferTexture2D
|
||||
#define glCheckFramebufferStatus ctx->drv.pf_glCheckFramebufferStatus
|
||||
#define glDeleteFramebuffers ctx->drv.pf_glDeleteFramebuffers
|
||||
|
||||
#endif /* HAVE_COGL_GLES2 */
|
||||
|
||||
#ifndef GL_FRAMEBUFFER
|
||||
#define GL_FRAMEBUFFER 0x8D40
|
||||
#endif
|
||||
#ifndef GL_FRAMEBUFFER_BINDING
|
||||
#define GL_FRAMEBUFFER_BINDING 0x8CA6
|
||||
#endif
|
||||
#ifndef GL_COLOR_ATTACHMENT0
|
||||
#define GL_COLOR_ATTACHMENT0 0x8CE0
|
||||
#endif
|
||||
#ifndef GL_FRAMEBUFFER_COMPLETE
|
||||
#define GL_FRAMEBUFFER_COMPLETE 0x8CD5
|
||||
#endif
|
||||
|
||||
static void _cogl_atlas_texture_free (CoglAtlasTexture *sub_tex);
|
||||
|
||||
COGL_TEXTURE_INTERNAL_DEFINE (AtlasTexture, atlas_texture);
|
||||
|
||||
static const CoglTextureVtable cogl_atlas_texture_vtable;
|
||||
|
||||
/* If we want to do mulitple blits from a texture (such as when
|
||||
reorganizing the atlas) then it's quicker to download all of the
|
||||
data once and upload multiple times from that. This struct is used
|
||||
to keep the image data for a series of blits */
|
||||
|
||||
typedef struct _CoglAtlasTextureBlitData
|
||||
static CoglHandle
|
||||
_cogl_atlas_texture_create_sub_texture (CoglHandle full_texture,
|
||||
const CoglRectangleMapEntry *rectangle)
|
||||
{
|
||||
CoglHandle src_tex, dst_tex;
|
||||
|
||||
/* If we're using an FBO to blit, then FBO will be non-zero and
|
||||
old_fbo will be the previous framebuffer binding */
|
||||
GLuint fbo, old_fbo;
|
||||
|
||||
/* If we're not using an FBO then we g_malloc a buffer and copy the
|
||||
complete texture data in */
|
||||
unsigned char *image_data;
|
||||
CoglPixelFormat format;
|
||||
int bpp;
|
||||
unsigned int src_height, src_width;
|
||||
|
||||
GLenum dst_gl_target;
|
||||
} CoglAtlasTextureBlitData;
|
||||
|
||||
static void
|
||||
_cogl_atlas_texture_blit_begin (CoglAtlasTextureBlitData *data,
|
||||
CoglHandle dst_tex,
|
||||
CoglHandle src_tex)
|
||||
{
|
||||
GLenum src_gl_target;
|
||||
GLuint src_gl_texture;
|
||||
GLuint dst_gl_texture;
|
||||
|
||||
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
||||
|
||||
data->dst_tex = dst_tex;
|
||||
data->src_tex = src_tex;
|
||||
data->fbo = 0;
|
||||
|
||||
/* If we can use an FBO then we don't need to download the data and
|
||||
we can tell GL to blit directly between the textures */
|
||||
if (cogl_features_available (COGL_FEATURE_OFFSCREEN) &&
|
||||
!cogl_texture_is_sliced (dst_tex) &&
|
||||
cogl_texture_get_gl_texture (src_tex, &src_gl_texture, &src_gl_target) &&
|
||||
cogl_texture_get_gl_texture (dst_tex, &dst_gl_texture,
|
||||
&data->dst_gl_target))
|
||||
{
|
||||
/* Ideally we would use the cogl-offscreen API here, but I'd
|
||||
rather avoid creating a stencil renderbuffer which you can't
|
||||
currently do */
|
||||
/* Preserve the previous framebuffer binding so we don't trample
|
||||
on cogl-offscreen */
|
||||
data->old_fbo = 0;
|
||||
GE( glGetIntegerv (GL_FRAMEBUFFER_BINDING, (GLint *) &data->old_fbo) );
|
||||
|
||||
_cogl_texture_set_filters (src_tex, GL_NEAREST, GL_NEAREST);
|
||||
|
||||
/* Create an FBO to read from the src texture */
|
||||
GE( glGenFramebuffers (1, &data->fbo) );
|
||||
GE( glBindFramebuffer (GL_FRAMEBUFFER, data->fbo) );
|
||||
GE( glFramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
|
||||
src_gl_target, src_gl_texture, 0) );
|
||||
if (glCheckFramebufferStatus (GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
||||
{
|
||||
/* The FBO failed for whatever reason so we'll fallback to
|
||||
reading the texture data */
|
||||
GE( glBindFramebuffer (GL_FRAMEBUFFER, data->old_fbo) );
|
||||
GE( glDeleteFramebuffers (1, &data->fbo) );
|
||||
data->fbo = 0;
|
||||
}
|
||||
|
||||
_cogl_bind_gl_texture_transient (data->dst_gl_target,
|
||||
dst_gl_texture,
|
||||
FALSE);
|
||||
}
|
||||
|
||||
if (data->fbo)
|
||||
COGL_NOTE (ATLAS, "Blit set up using an FBO");
|
||||
else
|
||||
{
|
||||
/* We need to retrieve the entire texture data (there is no
|
||||
glGetTexSubImage2D) */
|
||||
|
||||
data->format = cogl_texture_get_format (src_tex);
|
||||
data->bpp = _cogl_get_format_bpp (data->format);
|
||||
data->src_width = cogl_texture_get_width (src_tex);
|
||||
data->src_height = cogl_texture_get_height (src_tex);
|
||||
|
||||
data->image_data = g_malloc (data->bpp * data->src_width *
|
||||
data->src_height);
|
||||
cogl_texture_get_data (src_tex, data->format,
|
||||
data->src_width * data->bpp, data->image_data);
|
||||
}
|
||||
/* Create a subtexture for the given rectangle not including the
|
||||
1-pixel border */
|
||||
return _cogl_sub_texture_new (full_texture,
|
||||
rectangle->x + 1,
|
||||
rectangle->y + 1,
|
||||
rectangle->width - 2,
|
||||
rectangle->height - 2);
|
||||
}
|
||||
|
||||
static void
|
||||
_cogl_atlas_texture_blit (CoglAtlasTextureBlitData *data,
|
||||
unsigned int src_x,
|
||||
unsigned int src_y,
|
||||
unsigned int dst_x,
|
||||
unsigned int dst_y,
|
||||
unsigned int width,
|
||||
unsigned int height)
|
||||
_cogl_atlas_texture_update_position_cb (gpointer user_data,
|
||||
CoglHandle new_texture,
|
||||
const CoglRectangleMapEntry *rectangle)
|
||||
{
|
||||
/* If we have an FBO then we can do a fast blit */
|
||||
if (data->fbo)
|
||||
GE( glCopyTexSubImage2D (data->dst_gl_target, 0, dst_x, dst_y, src_x, src_y,
|
||||
width, height) );
|
||||
else
|
||||
cogl_texture_set_region (data->dst_tex,
|
||||
src_x, src_y,
|
||||
dst_x, dst_y,
|
||||
width, height,
|
||||
data->src_width, data->src_height,
|
||||
data->format,
|
||||
data->src_width * data->bpp,
|
||||
data->image_data);
|
||||
CoglAtlasTexture *atlas_tex = user_data;
|
||||
|
||||
/* Update the sub texture */
|
||||
if (atlas_tex->sub_texture)
|
||||
cogl_handle_unref (atlas_tex->sub_texture);
|
||||
atlas_tex->sub_texture =
|
||||
_cogl_atlas_texture_create_sub_texture (new_texture, rectangle);
|
||||
|
||||
/* Update the position */
|
||||
atlas_tex->rectangle = *rectangle;
|
||||
}
|
||||
|
||||
static void
|
||||
_cogl_atlas_texture_blit_end (CoglAtlasTextureBlitData *data)
|
||||
_cogl_atlas_texture_reorganize_foreach_cb (const CoglRectangleMapEntry *entry,
|
||||
void *rectangle_data,
|
||||
void *user_data)
|
||||
{
|
||||
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
||||
/* Notify cogl-material.c that the texture's underlying GL texture
|
||||
* storage is changing so it knows it may need to bind a new texture
|
||||
* if the CoglTexture is reused with the same texture unit. */
|
||||
_cogl_material_texture_storage_change_notify (rectangle_data);
|
||||
}
|
||||
|
||||
if (data->fbo)
|
||||
static void
|
||||
_cogl_atlas_texture_reorganize_cb (void *data)
|
||||
{
|
||||
CoglAtlas *atlas;
|
||||
/* We don't know if any materials may currently be referenced in
|
||||
* the journal that depend on the current underlying GL texture
|
||||
* storage so we flush the journal before migrating.
|
||||
*
|
||||
* We are assuming that texture atlas migration never happens
|
||||
* during a flush so we don't have to consider recursion here.
|
||||
*/
|
||||
_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)
|
||||
{
|
||||
_COGL_GET_CONTEXT (ctx, COGL_INVALID_HANDLE);
|
||||
|
||||
if (ctx->atlas == COGL_INVALID_HANDLE)
|
||||
{
|
||||
GE( glBindFramebuffer (GL_FRAMEBUFFER, data->old_fbo) );
|
||||
GE( glDeleteFramebuffers (1, &data->fbo) );
|
||||
ctx->atlas = _cogl_atlas_new (_cogl_atlas_texture_update_position_cb);
|
||||
|
||||
_cogl_atlas_add_reorganize_callback (ctx->atlas,
|
||||
_cogl_atlas_texture_reorganize_cb,
|
||||
NULL);
|
||||
}
|
||||
else
|
||||
g_free (data->image_data);
|
||||
|
||||
return ctx->atlas;
|
||||
}
|
||||
|
||||
static void
|
||||
@ -252,20 +173,8 @@ _cogl_atlas_texture_remove_from_atlas (CoglAtlasTexture *atlas_tex)
|
||||
{
|
||||
if (atlas_tex->in_atlas)
|
||||
{
|
||||
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
||||
|
||||
_cogl_rectangle_map_remove (ctx->rectangle_map, &atlas_tex->rectangle);
|
||||
|
||||
COGL_NOTE (ATLAS, "Removed rectangle sized %ix%i",
|
||||
atlas_tex->rectangle.width,
|
||||
atlas_tex->rectangle.height);
|
||||
COGL_NOTE (ATLAS, "Atlas is %ix%i, has %i textures and is %i%% waste",
|
||||
_cogl_rectangle_map_get_width (ctx->rectangle_map),
|
||||
_cogl_rectangle_map_get_height (ctx->rectangle_map),
|
||||
_cogl_rectangle_map_get_n_rectangles (ctx->rectangle_map),
|
||||
_cogl_rectangle_map_get_remaining_space (ctx->rectangle_map) *
|
||||
100 / (_cogl_rectangle_map_get_width (ctx->rectangle_map) *
|
||||
_cogl_rectangle_map_get_height (ctx->rectangle_map)));
|
||||
_cogl_atlas_remove (_cogl_atlas_texture_get_atlas (),
|
||||
&atlas_tex->rectangle);
|
||||
|
||||
atlas_tex->in_atlas = FALSE;
|
||||
}
|
||||
@ -361,10 +270,6 @@ _cogl_atlas_texture_migrate_out_of_atlas (CoglAtlasTexture *atlas_tex)
|
||||
/* Make sure this texture is not in the atlas */
|
||||
if (atlas_tex->in_atlas)
|
||||
{
|
||||
CoglAtlasTextureBlitData blit_data;
|
||||
|
||||
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
||||
|
||||
COGL_NOTE (ATLAS, "Migrating texture out of the atlas");
|
||||
|
||||
/* We don't know if any materials may currently be referenced in
|
||||
@ -383,27 +288,15 @@ _cogl_atlas_texture_migrate_out_of_atlas (CoglAtlasTexture *atlas_tex)
|
||||
|
||||
cogl_handle_unref (atlas_tex->sub_texture);
|
||||
|
||||
/* Create a new texture at the right size, not including the
|
||||
border */
|
||||
atlas_tex->sub_texture =
|
||||
cogl_texture_new_with_size (atlas_tex->rectangle.width - 2,
|
||||
_cogl_atlas_copy_rectangle (_cogl_atlas_texture_get_atlas (),
|
||||
atlas_tex->rectangle.x + 1,
|
||||
atlas_tex->rectangle.y + 1,
|
||||
atlas_tex->rectangle.width - 2,
|
||||
atlas_tex->rectangle.height - 2,
|
||||
COGL_TEXTURE_NO_ATLAS,
|
||||
atlas_tex->format);
|
||||
|
||||
/* Blit the data out of the atlas to the new texture. If FBOs
|
||||
aren't available this will end up having to copy the entire
|
||||
atlas texture */
|
||||
_cogl_atlas_texture_blit_begin (&blit_data, atlas_tex->sub_texture,
|
||||
ctx->atlas_texture);
|
||||
_cogl_atlas_texture_blit (&blit_data,
|
||||
atlas_tex->rectangle.x + 1,
|
||||
atlas_tex->rectangle.y + 1,
|
||||
0, 0,
|
||||
atlas_tex->rectangle.width - 2,
|
||||
atlas_tex->rectangle.height - 2);
|
||||
_cogl_atlas_texture_blit_end (&blit_data);
|
||||
|
||||
_cogl_atlas_texture_remove_from_atlas (atlas_tex);
|
||||
}
|
||||
}
|
||||
@ -445,10 +338,10 @@ _cogl_atlas_texture_set_region_with_border (CoglAtlasTexture *atlas_tex,
|
||||
unsigned int dst_height,
|
||||
CoglBitmap *bmp)
|
||||
{
|
||||
_COGL_GET_CONTEXT (ctx, FALSE);
|
||||
CoglAtlas *atlas = _cogl_atlas_texture_get_atlas ();
|
||||
|
||||
/* Copy the central data */
|
||||
if (!_cogl_texture_set_region_from_bitmap (ctx->atlas_texture,
|
||||
if (!_cogl_texture_set_region_from_bitmap (atlas->texture,
|
||||
src_x, src_y,
|
||||
dst_x + atlas_tex->rectangle.x + 1,
|
||||
dst_y + atlas_tex->rectangle.y + 1,
|
||||
@ -459,7 +352,7 @@ _cogl_atlas_texture_set_region_with_border (CoglAtlasTexture *atlas_tex,
|
||||
|
||||
/* Update the left edge pixels */
|
||||
if (dst_x == 0 &&
|
||||
!_cogl_texture_set_region_from_bitmap (ctx->atlas_texture,
|
||||
!_cogl_texture_set_region_from_bitmap (atlas->texture,
|
||||
src_x, src_y,
|
||||
atlas_tex->rectangle.x,
|
||||
dst_y + atlas_tex->rectangle.y + 1,
|
||||
@ -468,7 +361,7 @@ _cogl_atlas_texture_set_region_with_border (CoglAtlasTexture *atlas_tex,
|
||||
return FALSE;
|
||||
/* Update the right edge pixels */
|
||||
if (dst_x + dst_width == atlas_tex->rectangle.width - 2 &&
|
||||
!_cogl_texture_set_region_from_bitmap (ctx->atlas_texture,
|
||||
!_cogl_texture_set_region_from_bitmap (atlas->texture,
|
||||
src_x + dst_width - 1, src_y,
|
||||
atlas_tex->rectangle.x +
|
||||
atlas_tex->rectangle.width - 1,
|
||||
@ -478,7 +371,7 @@ _cogl_atlas_texture_set_region_with_border (CoglAtlasTexture *atlas_tex,
|
||||
return FALSE;
|
||||
/* Update the top edge pixels */
|
||||
if (dst_y == 0 &&
|
||||
!_cogl_texture_set_region_from_bitmap (ctx->atlas_texture,
|
||||
!_cogl_texture_set_region_from_bitmap (atlas->texture,
|
||||
src_x, src_y,
|
||||
dst_x + atlas_tex->rectangle.x + 1,
|
||||
atlas_tex->rectangle.y,
|
||||
@ -487,7 +380,7 @@ _cogl_atlas_texture_set_region_with_border (CoglAtlasTexture *atlas_tex,
|
||||
return FALSE;
|
||||
/* Update the bottom edge pixels */
|
||||
if (dst_y + dst_height == atlas_tex->rectangle.height - 2 &&
|
||||
!_cogl_texture_set_region_from_bitmap (ctx->atlas_texture,
|
||||
!_cogl_texture_set_region_from_bitmap (atlas->texture,
|
||||
src_x, src_y + dst_height - 1,
|
||||
dst_x + atlas_tex->rectangle.x + 1,
|
||||
atlas_tex->rectangle.y +
|
||||
@ -597,319 +490,6 @@ _cogl_atlas_texture_get_height (CoglTexture *tex)
|
||||
return cogl_texture_get_height (atlas_tex->sub_texture);
|
||||
}
|
||||
|
||||
static CoglHandle
|
||||
_cogl_atlas_texture_create_sub_texture (CoglHandle full_texture,
|
||||
const CoglRectangleMapEntry *rectangle)
|
||||
{
|
||||
/* Create a subtexture for the given rectangle not including the
|
||||
1-pixel border */
|
||||
return _cogl_sub_texture_new (full_texture,
|
||||
rectangle->x + 1,
|
||||
rectangle->y + 1,
|
||||
rectangle->width - 2,
|
||||
rectangle->height - 2);
|
||||
}
|
||||
|
||||
typedef struct _CoglAtlasTextureRepositionData
|
||||
{
|
||||
/* The current texture which already has a position */
|
||||
CoglAtlasTexture *texture;
|
||||
/* The new position of the texture */
|
||||
CoglRectangleMapEntry new_position;
|
||||
} CoglAtlasTextureRepositionData;
|
||||
|
||||
static void
|
||||
_cogl_atlas_texture_migrate (unsigned int n_textures,
|
||||
CoglAtlasTextureRepositionData *textures,
|
||||
CoglHandle old_texture,
|
||||
CoglHandle new_texture,
|
||||
CoglAtlasTexture *skip_texture)
|
||||
{
|
||||
unsigned int i;
|
||||
CoglAtlasTextureBlitData blit_data;
|
||||
|
||||
/* We don't know if any materials may currently be referenced in the
|
||||
* journal that depend on the current underlying GL texture storage
|
||||
* so we flush the journal before migrating.
|
||||
*
|
||||
* We are assuming that texture atlas migration never happens during
|
||||
* a flush so we don't have to consider recursion here.
|
||||
*/
|
||||
_cogl_journal_flush ();
|
||||
|
||||
_cogl_atlas_texture_blit_begin (&blit_data, new_texture, old_texture);
|
||||
|
||||
for (i = 0; i < n_textures; i++)
|
||||
{
|
||||
/* Notify cogl-material.c that the texture's underlying GL texture
|
||||
* storage is changing so it knows it may need to bind a new texture
|
||||
* if the CoglTexture is reused with the same texture unit. */
|
||||
_cogl_material_texture_storage_change_notify (textures[i].texture);
|
||||
|
||||
/* Skip the texture that is being added because it doesn't contain
|
||||
any data yet */
|
||||
if (textures[i].texture != skip_texture)
|
||||
{
|
||||
_cogl_atlas_texture_blit (&blit_data,
|
||||
textures[i].texture->rectangle.x,
|
||||
textures[i].texture->rectangle.y,
|
||||
textures[i].new_position.x,
|
||||
textures[i].new_position.y,
|
||||
textures[i].new_position.width,
|
||||
textures[i].new_position.height);
|
||||
/* Update the sub texture */
|
||||
cogl_handle_unref (textures[i].texture->sub_texture);
|
||||
textures[i].texture->sub_texture =
|
||||
_cogl_atlas_texture_create_sub_texture (new_texture,
|
||||
&textures[i].new_position);
|
||||
}
|
||||
|
||||
/* Update the texture position */
|
||||
textures[i].texture->rectangle = textures[i].new_position;
|
||||
}
|
||||
|
||||
_cogl_atlas_texture_blit_end (&blit_data);
|
||||
}
|
||||
|
||||
typedef struct _CoglAtlasTextureGetRectanglesData
|
||||
{
|
||||
CoglAtlasTextureRepositionData *textures;
|
||||
/* Number of textures found so far */
|
||||
unsigned int n_textures;
|
||||
} CoglAtlasTextureGetRectanglesData;
|
||||
|
||||
static void
|
||||
_cogl_atlas_texture_get_rectangles_cb (const CoglRectangleMapEntry *rectangle,
|
||||
gpointer rect_data,
|
||||
gpointer user_data)
|
||||
{
|
||||
CoglAtlasTextureGetRectanglesData *data = user_data;
|
||||
|
||||
data->textures[data->n_textures++].texture = rect_data;
|
||||
}
|
||||
|
||||
static void
|
||||
_cogl_atlas_texture_get_next_size (unsigned int *map_width,
|
||||
unsigned int *map_height)
|
||||
{
|
||||
/* Double the size of the texture by increasing whichever dimension
|
||||
is smaller */
|
||||
if (*map_width < *map_height)
|
||||
*map_width <<= 1;
|
||||
else
|
||||
*map_height <<= 1;
|
||||
}
|
||||
|
||||
static CoglRectangleMap *
|
||||
_cogl_atlas_texture_create_map (unsigned int map_width,
|
||||
unsigned int map_height,
|
||||
unsigned int n_textures,
|
||||
CoglAtlasTextureRepositionData *textures)
|
||||
{
|
||||
GLint max_texture_size = 1024;
|
||||
|
||||
GE( glGetIntegerv (GL_MAX_TEXTURE_SIZE, &max_texture_size) );
|
||||
|
||||
/* Sanity check that we're not going to get stuck in an infinite
|
||||
loop if the maximum texture size has the high bit set */
|
||||
if ((max_texture_size & (1 << (sizeof (GLint) * 8 - 2))))
|
||||
max_texture_size >>= 1;
|
||||
|
||||
/* Keep trying increasingly larger atlases until we can fit all of
|
||||
the textures */
|
||||
while (map_width < max_texture_size && map_height < max_texture_size)
|
||||
{
|
||||
CoglRectangleMap *new_atlas = _cogl_rectangle_map_new (map_width,
|
||||
map_height,
|
||||
NULL);
|
||||
unsigned int i;
|
||||
|
||||
/* Add all of the textures and keep track of the new position */
|
||||
for (i = 0; i < n_textures; i++)
|
||||
if (!_cogl_rectangle_map_add (new_atlas,
|
||||
textures[i].texture->rectangle.width,
|
||||
textures[i].texture->rectangle.height,
|
||||
textures[i].texture,
|
||||
&textures[i].new_position))
|
||||
break;
|
||||
|
||||
/* If the atlas can contain all of the textures then we have a
|
||||
winner */
|
||||
if (i >= n_textures)
|
||||
return new_atlas;
|
||||
|
||||
_cogl_rectangle_map_free (new_atlas);
|
||||
_cogl_atlas_texture_get_next_size (&map_width, &map_height);
|
||||
}
|
||||
|
||||
/* If we get here then there's no atlas that can accommodate all of
|
||||
the rectangles */
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
_cogl_atlas_texture_compare_size_cb (const void *a,
|
||||
const void *b)
|
||||
{
|
||||
const CoglAtlasTextureRepositionData *ta = a;
|
||||
const CoglAtlasTextureRepositionData *tb = b;
|
||||
unsigned int a_size, b_size;
|
||||
|
||||
a_size = ta->texture->rectangle.width * ta->texture->rectangle.height;
|
||||
b_size = tb->texture->rectangle.width * tb->texture->rectangle.height;
|
||||
|
||||
return a_size < b_size ? 1 : a_size > b_size ? -1 : 0;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
_cogl_atlas_texture_reserve_space (CoglAtlasTexture *new_sub_tex,
|
||||
unsigned int width,
|
||||
unsigned int height)
|
||||
{
|
||||
CoglAtlasTextureGetRectanglesData data;
|
||||
CoglRectangleMap *new_map;
|
||||
CoglHandle new_tex;
|
||||
unsigned int map_width, map_height;
|
||||
gboolean ret;
|
||||
|
||||
_COGL_GET_CONTEXT (ctx, FALSE);
|
||||
|
||||
/* Check if we can fit the rectangle into the existing atlas */
|
||||
if (ctx->rectangle_map &&
|
||||
_cogl_rectangle_map_add (ctx->rectangle_map, width, height,
|
||||
new_sub_tex,
|
||||
&new_sub_tex->rectangle))
|
||||
{
|
||||
COGL_NOTE (ATLAS, "Atlas is %ix%i, has %i textures and is %i%% waste",
|
||||
_cogl_rectangle_map_get_width (ctx->rectangle_map),
|
||||
_cogl_rectangle_map_get_height (ctx->rectangle_map),
|
||||
_cogl_rectangle_map_get_n_rectangles (ctx->rectangle_map),
|
||||
_cogl_rectangle_map_get_remaining_space (ctx->rectangle_map) *
|
||||
100 / (_cogl_rectangle_map_get_width (ctx->rectangle_map) *
|
||||
_cogl_rectangle_map_get_height (ctx->rectangle_map)));
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* We need to reorganise the atlas so we'll get an array of all the
|
||||
textures currently in the atlas. */
|
||||
data.n_textures = 0;
|
||||
if (ctx->rectangle_map == NULL)
|
||||
data.textures = g_malloc (sizeof (CoglAtlasTextureRepositionData));
|
||||
else
|
||||
{
|
||||
unsigned int n_rectangles =
|
||||
_cogl_rectangle_map_get_n_rectangles (ctx->rectangle_map);
|
||||
data.textures = g_malloc (sizeof (CoglAtlasTextureRepositionData) *
|
||||
(n_rectangles + 1));
|
||||
_cogl_rectangle_map_foreach (ctx->rectangle_map,
|
||||
_cogl_atlas_texture_get_rectangles_cb,
|
||||
&data);
|
||||
}
|
||||
|
||||
/* Add the new rectangle as a dummy texture so that it can be
|
||||
positioned with the rest */
|
||||
data.textures[data.n_textures++].texture = new_sub_tex;
|
||||
|
||||
/* The atlasing algorithm works a lot better if the rectangles are
|
||||
added in decreasing order of size so we'll first sort the
|
||||
array */
|
||||
qsort (data.textures, data.n_textures,
|
||||
sizeof (CoglAtlasTextureRepositionData),
|
||||
_cogl_atlas_texture_compare_size_cb);
|
||||
|
||||
/* Try to create a new atlas that can contain all of the textures */
|
||||
if (ctx->rectangle_map)
|
||||
{
|
||||
map_width = _cogl_rectangle_map_get_width (ctx->rectangle_map);
|
||||
map_height = _cogl_rectangle_map_get_height (ctx->rectangle_map);
|
||||
|
||||
/* If there is enough space in the existing for the new
|
||||
rectangle in the existing atlas we'll start with the same
|
||||
size, otherwise we'll immediately double it */
|
||||
if (_cogl_rectangle_map_get_remaining_space (ctx->rectangle_map) <
|
||||
width * height)
|
||||
_cogl_atlas_texture_get_next_size (&map_width, &map_height);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Start with an initial size of 256x256 */
|
||||
map_width = 256;
|
||||
map_height = 256;
|
||||
}
|
||||
|
||||
new_map = _cogl_atlas_texture_create_map (map_width, map_height,
|
||||
data.n_textures, data.textures);
|
||||
map_width = _cogl_rectangle_map_get_width (new_map);
|
||||
map_height = _cogl_rectangle_map_get_height (new_map);
|
||||
|
||||
/* 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");
|
||||
ret = FALSE;
|
||||
}
|
||||
/* We need to migrate the existing textures into a new texture */
|
||||
else if ((new_tex =
|
||||
_cogl_texture_2d_new_with_size (map_width, map_height,
|
||||
COGL_TEXTURE_NONE,
|
||||
COGL_PIXEL_FORMAT_RGBA_8888)) ==
|
||||
COGL_INVALID_HANDLE)
|
||||
{
|
||||
COGL_NOTE (ATLAS, "Could not create a CoglTexture2D");
|
||||
_cogl_rectangle_map_free (new_map);
|
||||
ret = FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
COGL_NOTE (ATLAS,
|
||||
"Atlas %s with size %ix%i",
|
||||
ctx->rectangle_map == NULL ||
|
||||
_cogl_rectangle_map_get_width (ctx->rectangle_map) !=
|
||||
_cogl_rectangle_map_get_width (new_map) ||
|
||||
_cogl_rectangle_map_get_height (ctx->rectangle_map) !=
|
||||
_cogl_rectangle_map_get_height (new_map) ?
|
||||
"resized" : "reorganized",
|
||||
_cogl_rectangle_map_get_width (new_map),
|
||||
_cogl_rectangle_map_get_height (new_map));
|
||||
|
||||
if (ctx->rectangle_map)
|
||||
{
|
||||
/* Move all the textures to the right position in the new
|
||||
texture. This will also update the texture's rectangle */
|
||||
_cogl_atlas_texture_migrate (data.n_textures,
|
||||
data.textures,
|
||||
ctx->atlas_texture,
|
||||
new_tex,
|
||||
new_sub_tex);
|
||||
_cogl_rectangle_map_free (ctx->rectangle_map);
|
||||
cogl_handle_unref (ctx->atlas_texture);
|
||||
}
|
||||
else
|
||||
/* We know there's only one texture so we can just directly
|
||||
update the rectangle from its new position */
|
||||
data.textures[0].texture->rectangle = data.textures[0].new_position;
|
||||
|
||||
ctx->rectangle_map = new_map;
|
||||
ctx->atlas_texture = new_tex;
|
||||
|
||||
COGL_NOTE (ATLAS, "Atlas is %ix%i, has %i textures and is %i%% waste",
|
||||
_cogl_rectangle_map_get_width (ctx->rectangle_map),
|
||||
_cogl_rectangle_map_get_height (ctx->rectangle_map),
|
||||
_cogl_rectangle_map_get_n_rectangles (ctx->rectangle_map),
|
||||
_cogl_rectangle_map_get_remaining_space (ctx->rectangle_map) *
|
||||
100 / (_cogl_rectangle_map_get_width (ctx->rectangle_map) *
|
||||
_cogl_rectangle_map_get_height (ctx->rectangle_map)));
|
||||
|
||||
ret = TRUE;
|
||||
}
|
||||
|
||||
g_free (data.textures);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
_cogl_atlas_texture_can_use_format (CoglPixelFormat format)
|
||||
{
|
||||
@ -987,15 +567,14 @@ _cogl_atlas_texture_new_from_bitmap (CoglBitmap *bmp,
|
||||
/* We need to allocate the texture now because we need the pointer
|
||||
to set as the data for the rectangle in the atlas */
|
||||
atlas_tex = g_new (CoglAtlasTexture, 1);
|
||||
/* We need to fill in the texture size now because it is used in the
|
||||
reserve_space function below. We add two pixels for the border */
|
||||
atlas_tex->rectangle.width = bmp_width + 2;
|
||||
atlas_tex->rectangle.height = bmp_height + 2;
|
||||
|
||||
atlas_tex->sub_texture = COGL_INVALID_HANDLE;
|
||||
|
||||
/* Try to make some space in the atlas for the texture */
|
||||
if (!_cogl_atlas_texture_reserve_space (atlas_tex,
|
||||
atlas_tex->rectangle.width,
|
||||
atlas_tex->rectangle.height))
|
||||
if (!_cogl_atlas_reserve_space (_cogl_atlas_texture_get_atlas (),
|
||||
/* Add two pixels for the border */
|
||||
bmp_width + 2, bmp_height + 2,
|
||||
atlas_tex))
|
||||
{
|
||||
g_free (atlas_tex);
|
||||
return COGL_INVALID_HANDLE;
|
||||
@ -1010,7 +589,8 @@ _cogl_atlas_texture_new_from_bitmap (CoglBitmap *bmp,
|
||||
|
||||
if (dst_bmp == NULL)
|
||||
{
|
||||
_cogl_rectangle_map_remove (ctx->rectangle_map, &atlas_tex->rectangle);
|
||||
_cogl_atlas_remove (_cogl_atlas_texture_get_atlas (),
|
||||
&atlas_tex->rectangle);
|
||||
g_free (atlas_tex);
|
||||
return COGL_INVALID_HANDLE;
|
||||
}
|
||||
@ -1018,9 +598,6 @@ _cogl_atlas_texture_new_from_bitmap (CoglBitmap *bmp,
|
||||
atlas_tex->_parent.vtable = &cogl_atlas_texture_vtable;
|
||||
atlas_tex->format = internal_format;
|
||||
atlas_tex->in_atlas = TRUE;
|
||||
atlas_tex->sub_texture =
|
||||
_cogl_atlas_texture_create_sub_texture (ctx->atlas_texture,
|
||||
&atlas_tex->rectangle);
|
||||
|
||||
/* Make another bitmap so that we can override the format */
|
||||
override_bmp = _cogl_bitmap_new_shared (dst_bmp,
|
||||
|
596
clutter/cogl/cogl/cogl-atlas.c
Normal file
596
clutter/cogl/cogl/cogl-atlas.c
Normal file
@ -0,0 +1,596 @@
|
||||
/*
|
||||
* Cogl
|
||||
*
|
||||
* An object oriented GL/GLES Abstraction/Utility Layer
|
||||
*
|
||||
* Copyright (C) 2010 Intel Corporation.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*
|
||||
* Authors:
|
||||
* Neil Roberts <neil@linux.intel.com>
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "cogl.h"
|
||||
#include "cogl-atlas.h"
|
||||
#include "cogl-rectangle-map.h"
|
||||
#include "cogl-context.h"
|
||||
#include "cogl-texture-private.h"
|
||||
#include "cogl-texture-2d-private.h"
|
||||
#include "cogl-material-opengl-private.h"
|
||||
#include "cogl-debug.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef HAVE_COGL_GLES2
|
||||
|
||||
#include "../gles/cogl-gles2-wrapper.h"
|
||||
|
||||
#else /* HAVE_COGL_GLES2 */
|
||||
|
||||
#define glGenFramebuffers ctx->drv.pf_glGenFramebuffers
|
||||
#define glBindFramebuffer ctx->drv.pf_glBindFramebuffer
|
||||
#define glFramebufferTexture2D ctx->drv.pf_glFramebufferTexture2D
|
||||
#define glCheckFramebufferStatus ctx->drv.pf_glCheckFramebufferStatus
|
||||
#define glDeleteFramebuffers ctx->drv.pf_glDeleteFramebuffers
|
||||
|
||||
#endif /* HAVE_COGL_GLES2 */
|
||||
|
||||
#ifndef GL_FRAMEBUFFER
|
||||
#define GL_FRAMEBUFFER 0x8D40
|
||||
#endif
|
||||
#ifndef GL_FRAMEBUFFER_BINDING
|
||||
#define GL_FRAMEBUFFER_BINDING 0x8CA6
|
||||
#endif
|
||||
#ifndef GL_COLOR_ATTACHMENT0
|
||||
#define GL_COLOR_ATTACHMENT0 0x8CE0
|
||||
#endif
|
||||
#ifndef GL_FRAMEBUFFER_COMPLETE
|
||||
#define GL_FRAMEBUFFER_COMPLETE 0x8CD5
|
||||
#endif
|
||||
|
||||
/* If we want to do mulitple blits from a texture (such as when
|
||||
reorganizing the atlas) then it's quicker to download all of the
|
||||
data once and upload multiple times from that. This struct is used
|
||||
to keep the image data for a series of blits */
|
||||
|
||||
typedef struct _CoglAtlasBlitData
|
||||
{
|
||||
CoglHandle src_tex, dst_tex;
|
||||
|
||||
/* If we're using an FBO to blit, then FBO will be non-zero and
|
||||
old_fbo will be the previous framebuffer binding */
|
||||
GLuint fbo, old_fbo;
|
||||
|
||||
/* If we're not using an FBO then we g_malloc a buffer and copy the
|
||||
complete texture data in */
|
||||
unsigned char *image_data;
|
||||
CoglPixelFormat format;
|
||||
int bpp;
|
||||
unsigned int src_height, src_width;
|
||||
|
||||
GLenum dst_gl_target;
|
||||
} CoglAtlasBlitData;
|
||||
|
||||
static void
|
||||
_cogl_atlas_blit_begin (CoglAtlasBlitData *data,
|
||||
CoglHandle dst_tex,
|
||||
CoglHandle src_tex)
|
||||
{
|
||||
GLenum src_gl_target;
|
||||
GLuint src_gl_texture;
|
||||
GLuint dst_gl_texture;
|
||||
|
||||
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
||||
|
||||
data->dst_tex = dst_tex;
|
||||
data->src_tex = src_tex;
|
||||
data->fbo = 0;
|
||||
|
||||
/* If we can use an FBO then we don't need to download the data and
|
||||
we can tell GL to blit directly between the textures */
|
||||
if (cogl_features_available (COGL_FEATURE_OFFSCREEN) &&
|
||||
!cogl_texture_is_sliced (dst_tex) &&
|
||||
cogl_texture_get_gl_texture (src_tex, &src_gl_texture, &src_gl_target) &&
|
||||
cogl_texture_get_gl_texture (dst_tex, &dst_gl_texture,
|
||||
&data->dst_gl_target))
|
||||
{
|
||||
/* Ideally we would use the cogl-offscreen API here, but I'd
|
||||
rather avoid creating a stencil renderbuffer which you can't
|
||||
currently do */
|
||||
/* Preserve the previous framebuffer binding so we don't trample
|
||||
on cogl-offscreen */
|
||||
data->old_fbo = 0;
|
||||
GE( glGetIntegerv (GL_FRAMEBUFFER_BINDING, (GLint *) &data->old_fbo) );
|
||||
|
||||
_cogl_texture_set_filters (src_tex, GL_NEAREST, GL_NEAREST);
|
||||
|
||||
/* Create an FBO to read from the src texture */
|
||||
GE( glGenFramebuffers (1, &data->fbo) );
|
||||
GE( glBindFramebuffer (GL_FRAMEBUFFER, data->fbo) );
|
||||
GE( glFramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
|
||||
src_gl_target, src_gl_texture, 0) );
|
||||
if (glCheckFramebufferStatus (GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
||||
{
|
||||
/* The FBO failed for whatever reason so we'll fallback to
|
||||
reading the texture data */
|
||||
GE( glBindFramebuffer (GL_FRAMEBUFFER, data->old_fbo) );
|
||||
GE( glDeleteFramebuffers (1, &data->fbo) );
|
||||
data->fbo = 0;
|
||||
}
|
||||
|
||||
_cogl_bind_gl_texture_transient (data->dst_gl_target,
|
||||
dst_gl_texture,
|
||||
FALSE);
|
||||
}
|
||||
|
||||
if (data->fbo)
|
||||
COGL_NOTE (ATLAS, "Blit set up using an FBO");
|
||||
else
|
||||
{
|
||||
/* We need to retrieve the entire texture data (there is no
|
||||
glGetTexSubImage2D) */
|
||||
|
||||
data->format = cogl_texture_get_format (src_tex);
|
||||
data->bpp = _cogl_get_format_bpp (data->format);
|
||||
data->src_width = cogl_texture_get_width (src_tex);
|
||||
data->src_height = cogl_texture_get_height (src_tex);
|
||||
|
||||
data->image_data = g_malloc (data->bpp * data->src_width *
|
||||
data->src_height);
|
||||
cogl_texture_get_data (src_tex, data->format,
|
||||
data->src_width * data->bpp, data->image_data);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
_cogl_atlas_blit (CoglAtlasBlitData *data,
|
||||
unsigned int src_x,
|
||||
unsigned int src_y,
|
||||
unsigned int dst_x,
|
||||
unsigned int dst_y,
|
||||
unsigned int width,
|
||||
unsigned int height)
|
||||
{
|
||||
/* If we have an FBO then we can do a fast blit */
|
||||
if (data->fbo)
|
||||
GE( glCopyTexSubImage2D (data->dst_gl_target, 0, dst_x, dst_y, src_x, src_y,
|
||||
width, height) );
|
||||
else
|
||||
cogl_texture_set_region (data->dst_tex,
|
||||
src_x, src_y,
|
||||
dst_x, dst_y,
|
||||
width, height,
|
||||
data->src_width, data->src_height,
|
||||
data->format,
|
||||
data->src_width * data->bpp,
|
||||
data->image_data);
|
||||
}
|
||||
|
||||
static void
|
||||
_cogl_atlas_blit_end (CoglAtlasBlitData *data)
|
||||
{
|
||||
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
||||
|
||||
if (data->fbo)
|
||||
{
|
||||
GE( glBindFramebuffer (GL_FRAMEBUFFER, data->old_fbo) );
|
||||
GE( glDeleteFramebuffers (1, &data->fbo) );
|
||||
}
|
||||
else
|
||||
g_free (data->image_data);
|
||||
}
|
||||
|
||||
CoglAtlas *
|
||||
_cogl_atlas_new (CoglAtlasUpdatePositionCallback update_position_cb)
|
||||
{
|
||||
CoglAtlas *atlas = g_new (CoglAtlas, 1);
|
||||
|
||||
atlas->update_position_cb = update_position_cb;
|
||||
atlas->map = NULL;
|
||||
atlas->texture = NULL;
|
||||
_cogl_callback_list_init (&atlas->reorganize_callbacks);
|
||||
|
||||
return atlas;
|
||||
}
|
||||
|
||||
void
|
||||
_cogl_atlas_free (CoglAtlas *atlas)
|
||||
{
|
||||
if (atlas->texture)
|
||||
cogl_handle_unref (atlas->texture);
|
||||
if (atlas->map)
|
||||
_cogl_rectangle_map_free (atlas->map);
|
||||
|
||||
_cogl_callback_list_destroy (&atlas->reorganize_callbacks);
|
||||
|
||||
g_free (atlas);
|
||||
}
|
||||
|
||||
typedef struct _CoglAtlasRepositionData
|
||||
{
|
||||
/* The current user data for this texture */
|
||||
void *user_data;
|
||||
/* The old and new positions of the texture */
|
||||
CoglRectangleMapEntry old_position;
|
||||
CoglRectangleMapEntry new_position;
|
||||
} CoglAtlasRepositionData;
|
||||
|
||||
static void
|
||||
_cogl_atlas_migrate (CoglAtlas *atlas,
|
||||
unsigned int n_textures,
|
||||
CoglAtlasRepositionData *textures,
|
||||
CoglHandle old_texture,
|
||||
CoglHandle new_texture,
|
||||
void *skip_user_data)
|
||||
{
|
||||
unsigned int i;
|
||||
CoglAtlasBlitData blit_data;
|
||||
|
||||
_cogl_atlas_blit_begin (&blit_data, new_texture, old_texture);
|
||||
|
||||
for (i = 0; i < n_textures; i++)
|
||||
{
|
||||
/* Skip the texture that is being added because it doesn't contain
|
||||
any data yet */
|
||||
if (textures[i].user_data != skip_user_data)
|
||||
{
|
||||
_cogl_atlas_blit (&blit_data,
|
||||
textures[i].old_position.x,
|
||||
textures[i].old_position.y,
|
||||
textures[i].new_position.x,
|
||||
textures[i].new_position.y,
|
||||
textures[i].new_position.width,
|
||||
textures[i].new_position.height);
|
||||
}
|
||||
/* Update the texture position */
|
||||
atlas->update_position_cb (textures[i].user_data,
|
||||
new_texture,
|
||||
&textures[i].new_position);
|
||||
}
|
||||
|
||||
_cogl_atlas_blit_end (&blit_data);
|
||||
}
|
||||
|
||||
typedef struct _CoglAtlasGetRectanglesData
|
||||
{
|
||||
CoglAtlasRepositionData *textures;
|
||||
/* Number of textures found so far */
|
||||
unsigned int n_textures;
|
||||
} CoglAtlasGetRectanglesData;
|
||||
|
||||
static void
|
||||
_cogl_atlas_get_rectangles_cb (const CoglRectangleMapEntry *rectangle,
|
||||
void *rect_data,
|
||||
void *user_data)
|
||||
{
|
||||
CoglAtlasGetRectanglesData *data = user_data;
|
||||
|
||||
data->textures[data->n_textures].old_position = *rectangle;
|
||||
data->textures[data->n_textures++].user_data = rect_data;
|
||||
}
|
||||
|
||||
static void
|
||||
_cogl_atlas_get_next_size (unsigned int *map_width,
|
||||
unsigned int *map_height)
|
||||
{
|
||||
/* Double the size of the texture by increasing whichever dimension
|
||||
is smaller */
|
||||
if (*map_width < *map_height)
|
||||
*map_width <<= 1;
|
||||
else
|
||||
*map_height <<= 1;
|
||||
}
|
||||
|
||||
static CoglRectangleMap *
|
||||
_cogl_atlas_create_map (unsigned int map_width,
|
||||
unsigned int map_height,
|
||||
unsigned int n_textures,
|
||||
CoglAtlasRepositionData *textures)
|
||||
{
|
||||
GLint max_texture_size = 1024;
|
||||
|
||||
GE( glGetIntegerv (GL_MAX_TEXTURE_SIZE, &max_texture_size) );
|
||||
|
||||
/* Sanity check that we're not going to get stuck in an infinite
|
||||
loop if the maximum texture size has the high bit set */
|
||||
if ((max_texture_size & (1 << (sizeof (GLint) * 8 - 2))))
|
||||
max_texture_size >>= 1;
|
||||
|
||||
/* Keep trying increasingly larger atlases until we can fit all of
|
||||
the textures */
|
||||
while (map_width < max_texture_size && map_height < max_texture_size)
|
||||
{
|
||||
CoglRectangleMap *new_atlas = _cogl_rectangle_map_new (map_width,
|
||||
map_height,
|
||||
NULL);
|
||||
unsigned int i;
|
||||
|
||||
/* Add all of the textures and keep track of the new position */
|
||||
for (i = 0; i < n_textures; i++)
|
||||
if (!_cogl_rectangle_map_add (new_atlas,
|
||||
textures[i].old_position.width,
|
||||
textures[i].old_position.height,
|
||||
textures[i].user_data,
|
||||
&textures[i].new_position))
|
||||
break;
|
||||
|
||||
/* If the atlas can contain all of the textures then we have a
|
||||
winner */
|
||||
if (i >= n_textures)
|
||||
return new_atlas;
|
||||
|
||||
_cogl_rectangle_map_free (new_atlas);
|
||||
_cogl_atlas_get_next_size (&map_width, &map_height);
|
||||
}
|
||||
|
||||
/* If we get here then there's no atlas that can accommodate all of
|
||||
the rectangles */
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
_cogl_atlas_compare_size_cb (const void *a,
|
||||
const void *b)
|
||||
{
|
||||
const CoglAtlasRepositionData *ta = a;
|
||||
const CoglAtlasRepositionData *tb = b;
|
||||
unsigned int a_size, b_size;
|
||||
|
||||
a_size = ta->old_position.width * ta->old_position.height;
|
||||
b_size = tb->old_position.width * tb->old_position.height;
|
||||
|
||||
return a_size < b_size ? 1 : a_size > b_size ? -1 : 0;
|
||||
}
|
||||
|
||||
static void
|
||||
_cogl_atlas_notify_reorganize (CoglAtlas *atlas)
|
||||
{
|
||||
_cogl_callback_list_invoke (&atlas->reorganize_callbacks);
|
||||
}
|
||||
|
||||
gboolean
|
||||
_cogl_atlas_reserve_space (CoglAtlas *atlas,
|
||||
unsigned int width,
|
||||
unsigned int height,
|
||||
void *user_data)
|
||||
{
|
||||
CoglAtlasGetRectanglesData data;
|
||||
CoglRectangleMap *new_map;
|
||||
CoglHandle new_tex;
|
||||
unsigned int map_width, map_height;
|
||||
gboolean ret;
|
||||
CoglRectangleMapEntry new_position;
|
||||
|
||||
/* Check if we can fit the rectangle into the existing map */
|
||||
if (atlas->map &&
|
||||
_cogl_rectangle_map_add (atlas->map, width, height,
|
||||
user_data,
|
||||
&new_position))
|
||||
{
|
||||
int waste =
|
||||
_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_rectangle_map_get_width (atlas->map),
|
||||
_cogl_rectangle_map_get_height (atlas->map),
|
||||
_cogl_rectangle_map_get_n_rectangles (atlas->map),
|
||||
waste);
|
||||
|
||||
atlas->update_position_cb (user_data,
|
||||
atlas->texture,
|
||||
&new_position);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* We need to reorganise the atlas so we'll get an array of all the
|
||||
textures currently in the atlas. */
|
||||
data.n_textures = 0;
|
||||
if (atlas->map == NULL)
|
||||
data.textures = g_malloc (sizeof (CoglAtlasRepositionData));
|
||||
else
|
||||
{
|
||||
unsigned int n_rectangles =
|
||||
_cogl_rectangle_map_get_n_rectangles (atlas->map);
|
||||
data.textures = g_malloc (sizeof (CoglAtlasRepositionData) *
|
||||
(n_rectangles + 1));
|
||||
_cogl_rectangle_map_foreach (atlas->map,
|
||||
_cogl_atlas_get_rectangles_cb,
|
||||
&data);
|
||||
}
|
||||
|
||||
/* Add the new rectangle as a dummy texture so that it can be
|
||||
positioned with the rest */
|
||||
data.textures[data.n_textures].old_position.x = 0;
|
||||
data.textures[data.n_textures].old_position.y = 0;
|
||||
data.textures[data.n_textures].old_position.width = width;
|
||||
data.textures[data.n_textures].old_position.height = height;
|
||||
data.textures[data.n_textures++].user_data = user_data;
|
||||
|
||||
/* The atlasing algorithm works a lot better if the rectangles are
|
||||
added in decreasing order of size so we'll first sort the
|
||||
array */
|
||||
qsort (data.textures, data.n_textures,
|
||||
sizeof (CoglAtlasRepositionData),
|
||||
_cogl_atlas_compare_size_cb);
|
||||
|
||||
/* Try to create a new atlas that can contain all of the textures */
|
||||
if (atlas->map)
|
||||
{
|
||||
map_width = _cogl_rectangle_map_get_width (atlas->map);
|
||||
map_height = _cogl_rectangle_map_get_height (atlas->map);
|
||||
|
||||
/* If there is enough space in the existing for the new
|
||||
rectangle in the existing atlas we'll start with the same
|
||||
size, otherwise we'll immediately double it */
|
||||
if (_cogl_rectangle_map_get_remaining_space (atlas->map) <
|
||||
width * height)
|
||||
_cogl_atlas_get_next_size (&map_width, &map_height);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Start with an initial size of 256x256 */
|
||||
map_width = 256;
|
||||
map_height = 256;
|
||||
}
|
||||
|
||||
new_map = _cogl_atlas_create_map (map_width, map_height,
|
||||
data.n_textures, data.textures);
|
||||
|
||||
/* 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");
|
||||
ret = FALSE;
|
||||
}
|
||||
/* We need to migrate the existing textures into a new texture */
|
||||
else if ((new_tex = _cogl_texture_2d_new_with_size
|
||||
(_cogl_rectangle_map_get_width (new_map),
|
||||
_cogl_rectangle_map_get_height (new_map),
|
||||
COGL_TEXTURE_NONE,
|
||||
COGL_PIXEL_FORMAT_RGBA_8888)) == COGL_INVALID_HANDLE)
|
||||
{
|
||||
COGL_NOTE (ATLAS, "Could not create a CoglTexture2D");
|
||||
_cogl_rectangle_map_free (new_map);
|
||||
ret = FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
int waste;
|
||||
|
||||
_cogl_atlas_notify_reorganize (atlas);
|
||||
|
||||
COGL_NOTE (ATLAS,
|
||||
"Atlas %s with size %ix%i",
|
||||
atlas->map == NULL ||
|
||||
_cogl_rectangle_map_get_width (atlas->map) !=
|
||||
_cogl_rectangle_map_get_width (new_map) ||
|
||||
_cogl_rectangle_map_get_height (atlas->map) !=
|
||||
_cogl_rectangle_map_get_height (new_map) ?
|
||||
"resized" : "reorganized",
|
||||
_cogl_rectangle_map_get_width (new_map),
|
||||
_cogl_rectangle_map_get_height (new_map));
|
||||
|
||||
if (atlas->map)
|
||||
{
|
||||
/* Move all the textures to the right position in the new
|
||||
texture. This will also update the texture's rectangle */
|
||||
_cogl_atlas_migrate (atlas,
|
||||
data.n_textures,
|
||||
data.textures,
|
||||
atlas->texture,
|
||||
new_tex,
|
||||
user_data);
|
||||
_cogl_rectangle_map_free (atlas->map);
|
||||
cogl_handle_unref (atlas->texture);
|
||||
}
|
||||
else
|
||||
/* We know there's only one texture so we can just directly
|
||||
update the rectangle from its new position */
|
||||
atlas->update_position_cb (data.textures[0].user_data,
|
||||
new_tex,
|
||||
&data.textures[0].new_position);
|
||||
|
||||
atlas->map = new_map;
|
||||
atlas->texture = new_tex;
|
||||
|
||||
waste = (_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_rectangle_map_get_width (atlas->map),
|
||||
_cogl_rectangle_map_get_height (atlas->map),
|
||||
_cogl_rectangle_map_get_n_rectangles (atlas->map),
|
||||
waste);
|
||||
|
||||
ret = TRUE;
|
||||
}
|
||||
|
||||
g_free (data.textures);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
_cogl_atlas_remove (CoglAtlas *atlas,
|
||||
const CoglRectangleMapEntry *rectangle)
|
||||
{
|
||||
_cogl_rectangle_map_remove (atlas->map, rectangle);
|
||||
|
||||
COGL_NOTE (ATLAS, "Removed rectangle sized %ix%i",
|
||||
rectangle->width,
|
||||
rectangle->height);
|
||||
COGL_NOTE (ATLAS, "Atlas is %ix%i, has %i textures and is %i%% waste",
|
||||
_cogl_rectangle_map_get_width (atlas->map),
|
||||
_cogl_rectangle_map_get_height (atlas->map),
|
||||
_cogl_rectangle_map_get_n_rectangles (atlas->map),
|
||||
_cogl_rectangle_map_get_remaining_space (atlas->map) *
|
||||
100 / (_cogl_rectangle_map_get_width (atlas->map) *
|
||||
_cogl_rectangle_map_get_height (atlas->map)));
|
||||
};
|
||||
|
||||
CoglHandle
|
||||
_cogl_atlas_copy_rectangle (CoglAtlas *atlas,
|
||||
unsigned int x,
|
||||
unsigned int y,
|
||||
unsigned int width,
|
||||
unsigned int height,
|
||||
CoglTextureFlags flags,
|
||||
CoglPixelFormat format)
|
||||
{
|
||||
CoglHandle tex;
|
||||
CoglAtlasBlitData blit_data;
|
||||
|
||||
/* Create a new texture at the right size */
|
||||
tex = cogl_texture_new_with_size (width, height, flags, format);
|
||||
|
||||
/* Blit the data out of the atlas to the new texture. If FBOs
|
||||
aren't available this will end up having to copy the entire
|
||||
atlas texture */
|
||||
_cogl_atlas_blit_begin (&blit_data, tex, atlas->texture);
|
||||
_cogl_atlas_blit (&blit_data,
|
||||
x, y,
|
||||
0, 0,
|
||||
width, height);
|
||||
_cogl_atlas_blit_end (&blit_data);
|
||||
|
||||
return tex;
|
||||
}
|
||||
|
||||
void
|
||||
_cogl_atlas_add_reorganize_callback (CoglAtlas *atlas,
|
||||
CoglCallbackListFunc callback,
|
||||
void *user_data)
|
||||
{
|
||||
_cogl_callback_list_add (&atlas->reorganize_callbacks, callback, user_data);
|
||||
}
|
||||
|
||||
void
|
||||
_cogl_atlas_remove_reorganize_callback (CoglAtlas *atlas,
|
||||
CoglCallbackListFunc callback,
|
||||
void *user_data)
|
||||
{
|
||||
_cogl_callback_list_remove (&atlas->reorganize_callbacks,
|
||||
callback, user_data);
|
||||
}
|
86
clutter/cogl/cogl/cogl-atlas.h
Normal file
86
clutter/cogl/cogl/cogl-atlas.h
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Cogl
|
||||
*
|
||||
* An object oriented GL/GLES Abstraction/Utility Layer
|
||||
*
|
||||
* Copyright (C) 2010 Intel Corporation.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#ifndef __COGL_ATLAS_H
|
||||
#define __COGL_ATLAS_H
|
||||
|
||||
#include "cogl-rectangle-map.h"
|
||||
#include "cogl-callback-list.h"
|
||||
|
||||
typedef void
|
||||
(* CoglAtlasUpdatePositionCallback) (void *user_data,
|
||||
CoglHandle new_texture,
|
||||
const CoglRectangleMapEntry *rect);
|
||||
|
||||
typedef struct _CoglAtlas CoglAtlas;
|
||||
|
||||
struct _CoglAtlas
|
||||
{
|
||||
CoglRectangleMap *map;
|
||||
|
||||
CoglHandle texture;
|
||||
|
||||
CoglAtlasUpdatePositionCallback update_position_cb;
|
||||
|
||||
CoglCallbackList reorganize_callbacks;
|
||||
};
|
||||
|
||||
CoglAtlas *
|
||||
_cogl_atlas_new (CoglAtlasUpdatePositionCallback update_position_cb);
|
||||
|
||||
gboolean
|
||||
_cogl_atlas_reserve_space (CoglAtlas *atlas,
|
||||
unsigned int width,
|
||||
unsigned int height,
|
||||
void *user_data);
|
||||
|
||||
void
|
||||
_cogl_atlas_remove (CoglAtlas *atlas,
|
||||
const CoglRectangleMapEntry *rectangle);
|
||||
|
||||
void
|
||||
_cogl_atlas_free (CoglAtlas *atlas);
|
||||
|
||||
CoglHandle
|
||||
_cogl_atlas_copy_rectangle (CoglAtlas *atlas,
|
||||
unsigned int x,
|
||||
unsigned int y,
|
||||
unsigned int width,
|
||||
unsigned int height,
|
||||
CoglTextureFlags flags,
|
||||
CoglPixelFormat format);
|
||||
|
||||
CoglAtlas *
|
||||
_cogl_atlas_get_default (void);
|
||||
|
||||
void
|
||||
_cogl_atlas_add_reorganize_callback (CoglAtlas *atlas,
|
||||
CoglCallbackListFunc callback,
|
||||
void *user_data);
|
||||
|
||||
void
|
||||
_cogl_atlas_remove_reorganize_callback (CoglAtlas *atlas,
|
||||
CoglCallbackListFunc callback,
|
||||
void *user_data);
|
||||
|
||||
#endif /* __COGL_ATLAS_H */
|
@ -214,8 +214,7 @@ cogl_create_context (void)
|
||||
_cogl_enable (enable_flags);
|
||||
_cogl_flush_face_winding ();
|
||||
|
||||
_context->rectangle_map = NULL;
|
||||
_context->atlas_texture = COGL_INVALID_HANDLE;
|
||||
_context->atlas = NULL;
|
||||
|
||||
/* As far as I can tell, GL_POINT_SPRITE doesn't have any effect
|
||||
unless GL_COORD_REPLACE is enabled for an individual
|
||||
@ -273,11 +272,8 @@ _cogl_destroy_context (void)
|
||||
if (_context->default_layer_0)
|
||||
cogl_handle_unref (_context->default_layer_0);
|
||||
|
||||
if (_context->rectangle_map)
|
||||
_cogl_rectangle_map_free (_context->rectangle_map);
|
||||
|
||||
if (_context->atlas_texture)
|
||||
cogl_handle_unref (_context->atlas_texture);
|
||||
if (_context->atlas)
|
||||
_cogl_atlas_free (_context->atlas);
|
||||
|
||||
_cogl_bitmask_destroy (&_context->texcoord_arrays_enabled);
|
||||
_cogl_bitmask_destroy (&_context->temp_bitmask);
|
||||
|
@ -31,9 +31,9 @@
|
||||
#include "cogl-clip-stack.h"
|
||||
#include "cogl-matrix-stack.h"
|
||||
#include "cogl-material-private.h"
|
||||
#include "cogl-rectangle-map.h"
|
||||
#include "cogl-buffer-private.h"
|
||||
#include "cogl-bitmask.h"
|
||||
#include "cogl-atlas.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
@ -143,8 +143,7 @@ typedef struct
|
||||
|
||||
CoglMaterial *texture_download_material;
|
||||
|
||||
CoglRectangleMap *rectangle_map;
|
||||
CoglHandle atlas_texture;
|
||||
CoglAtlas *atlas;
|
||||
|
||||
/* This debugging variable is used to pick a colour for visually
|
||||
displaying the quad batches. It needs to be global so that it can
|
||||
|
Loading…
Reference in New Issue
Block a user