diff --git a/cogl/Makefile.am b/cogl/Makefile.am index 995453832..d2a4c488e 100644 --- a/cogl/Makefile.am +++ b/cogl/Makefile.am @@ -131,6 +131,10 @@ libclutter_cogl_la_SOURCES = \ $(srcdir)/cogl-texture.c \ $(srcdir)/cogl-texture-2d.c \ $(srcdir)/cogl-texture-2d-sliced.c \ + $(srcdir)/cogl-atlas.h \ + $(srcdir)/cogl-atlas.c \ + $(srcdir)/cogl-atlas-texture-private.h \ + $(srcdir)/cogl-atlas-texture.c \ $(srcdir)/cogl-spans.h \ $(srcdir)/cogl-spans.c \ $(srcdir)/cogl-journal-private.h \ diff --git a/cogl/cogl-atlas-texture-private.h b/cogl/cogl-atlas-texture-private.h new file mode 100644 index 000000000..de0fcdb7f --- /dev/null +++ b/cogl/cogl-atlas-texture-private.h @@ -0,0 +1,64 @@ +/* + * Cogl + * + * An object oriented GL/GLES Abstraction/Utility Layer + * + * Copyright (C) 2009 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_TEXTURE_H +#define __COGL_ATLAS_TEXTURE_H + +#include "cogl-handle.h" +#include "cogl-texture-private.h" +#include "cogl-atlas.h" + +#define COGL_ATLAS_TEXTURE(tex) ((CoglAtlasTexture *) tex) + +typedef struct _CoglAtlasTexture CoglAtlasTexture; + +struct _CoglAtlasTexture +{ + CoglTexture _parent; + + /* The format that the texture is in. This isn't necessarily the + same format as the atlas texture because we can store + pre-multiplied and non-pre-multiplied textures together */ + CoglPixelFormat format; + + /* The rectangle that was used to add this texture to the + atlas. This includes the 1-pixel border */ + CoglAtlasRectangle 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; + + /* A CoglSubTexture representing the region for easy rendering */ + CoglHandle sub_texture; +}; + +GQuark +_cogl_handle_atlas_texture_get_type (void); + +CoglHandle +_cogl_atlas_texture_new_from_bitmap (CoglHandle bmp_handle, + CoglTextureFlags flags, + CoglPixelFormat internal_format); + +#endif /* __COGL_ATLAS_TEXTURE_H */ diff --git a/cogl/cogl-atlas-texture.c b/cogl/cogl-atlas-texture.c new file mode 100644 index 000000000..fb52e91c8 --- /dev/null +++ b/cogl/cogl-atlas-texture.c @@ -0,0 +1,488 @@ +/* + * Cogl + * + * An object oriented GL/GLES Abstraction/Utility Layer + * + * Copyright (C) 2009 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 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "cogl.h" +#include "cogl-internal.h" +#include "cogl-util.h" +#include "cogl-texture-private.h" +#include "cogl-atlas-texture-private.h" +#include "cogl-texture-2d-private.h" +#include "cogl-context.h" +#include "cogl-handle.h" +#include "cogl-texture-driver.h" +#include "cogl-atlas.h" + +static void _cogl_atlas_texture_free (CoglAtlasTexture *sub_tex); + +COGL_HANDLE_DEFINE (AtlasTexture, atlas_texture); + +static const CoglTextureVtable cogl_atlas_texture_vtable; + +static void +_cogl_atlas_texture_foreach_sub_texture_in_region ( + CoglTexture *tex, + float virtual_tx_1, + float virtual_ty_1, + float virtual_tx_2, + float virtual_ty_2, + CoglTextureSliceCallback callback, + void *user_data) +{ + CoglAtlasTexture *atlas_tex = COGL_ATLAS_TEXTURE (tex); + + /* Forward on to the sub texture */ + _cogl_texture_foreach_sub_texture_in_region (atlas_tex->sub_texture, + virtual_tx_1, + virtual_ty_1, + virtual_tx_2, + virtual_ty_2, + callback, + user_data); +} + +static void +_cogl_atlas_texture_set_wrap_mode_parameter (CoglTexture *tex, + GLenum wrap_mode) +{ + CoglAtlasTexture *atlas_tex = COGL_ATLAS_TEXTURE (tex); + + /* Forward on to the sub texture */ + _cogl_texture_set_wrap_mode_parameter (atlas_tex->sub_texture, wrap_mode); +} + +static void +_cogl_atlas_texture_free (CoglAtlasTexture *atlas_tex) +{ + _COGL_GET_CONTEXT (ctx, NO_RETVAL); + + /* Remove the texture from the atlas */ + if (atlas_tex->in_atlas) + cogl_atlas_remove_rectangle ((atlas_tex->format & COGL_A_BIT) ? + ctx->atlas_alpha : + ctx->atlas_no_alpha, + &atlas_tex->rectangle); + + cogl_handle_unref (atlas_tex->sub_texture); +} + +static gint +_cogl_atlas_texture_get_max_waste (CoglTexture *tex) +{ + CoglAtlasTexture *atlas_tex = COGL_ATLAS_TEXTURE (tex); + + /* Forward on to the sub texture */ + return cogl_texture_get_max_waste (atlas_tex->sub_texture); +} + +static gboolean +_cogl_atlas_texture_is_sliced (CoglTexture *tex) +{ + CoglAtlasTexture *atlas_tex = COGL_ATLAS_TEXTURE (tex); + + /* Forward on to the sub texture */ + return cogl_texture_is_sliced (atlas_tex->sub_texture); +} + +static gboolean +_cogl_atlas_texture_can_hardware_repeat (CoglTexture *tex) +{ + CoglAtlasTexture *atlas_tex = COGL_ATLAS_TEXTURE (tex); + + /* Forward on to the sub texture */ + return _cogl_texture_can_hardware_repeat (atlas_tex->sub_texture); +} + +static void +_cogl_atlas_texture_transform_coords_to_gl (CoglTexture *tex, + float *s, + float *t) +{ + CoglAtlasTexture *atlas_tex = COGL_ATLAS_TEXTURE (tex); + + /* Forward on to the sub texture */ + _cogl_texture_transform_coords_to_gl (atlas_tex->sub_texture, s, t); +} + +static gboolean +_cogl_atlas_texture_get_gl_texture (CoglTexture *tex, + GLuint *out_gl_handle, + GLenum *out_gl_target) +{ + CoglAtlasTexture *atlas_tex = COGL_ATLAS_TEXTURE (tex); + + /* Forward on to the sub texture */ + return cogl_texture_get_gl_texture (atlas_tex->sub_texture, + out_gl_handle, + out_gl_target); +} + +static void +_cogl_atlas_texture_set_filters (CoglTexture *tex, + GLenum min_filter, + GLenum mag_filter) +{ + CoglAtlasTexture *atlas_tex = COGL_ATLAS_TEXTURE (tex); + + /* Forward on to the sub texture */ + _cogl_texture_set_filters (atlas_tex->sub_texture, min_filter, mag_filter); +} + +static void +_cogl_atlas_texture_ensure_mipmaps (CoglTexture *tex) +{ + CoglAtlasTexture *atlas_tex = COGL_ATLAS_TEXTURE (tex); + + /* FIXME: If mipmaps are required then we need to migrate the + texture out of the atlas because it will show artifacts */ + + /* Forward on to the sub texture */ + _cogl_texture_ensure_mipmaps (atlas_tex->sub_texture); +} + +static gboolean +_cogl_atlas_texture_set_region (CoglTexture *tex, + int src_x, + int src_y, + int dst_x, + int dst_y, + unsigned int dst_width, + unsigned int dst_height, + int width, + int height, + CoglPixelFormat format, + unsigned int rowstride, + const guint8 *data) +{ + CoglAtlasTexture *atlas_tex = COGL_ATLAS_TEXTURE (tex); + + /* If the texture is in the atlas then we need to copy the edge + pixels to the border */ + if (atlas_tex->in_atlas) + { + CoglHandle big_texture; + + _COGL_GET_CONTEXT (ctx, FALSE); + + big_texture = ((atlas_tex->format & COGL_A_BIT) ? + ctx->atlas_alpha_texture : ctx->atlas_no_alpha_texture); + + /* Copy the central data */ + if (!cogl_texture_set_region (big_texture, + src_x, src_y, + dst_x + atlas_tex->rectangle.x + 1, + dst_y + atlas_tex->rectangle.y + 1, + dst_width, + dst_height, + width, height, + format, + rowstride, + data)) + return FALSE; + + /* Update the left edge pixels */ + if (dst_x == 0 && + !cogl_texture_set_region (big_texture, + src_x, src_y, + atlas_tex->rectangle.x, + dst_y + atlas_tex->rectangle.y + 1, + 1, dst_height, + width, height, + format, rowstride, + data)) + return FALSE; + /* Update the right edge pixels */ + if (dst_x + dst_width == atlas_tex->rectangle.width - 2 && + !cogl_texture_set_region (big_texture, + src_x + dst_width - 1, src_y, + atlas_tex->rectangle.x + + atlas_tex->rectangle.width - 1, + dst_y + atlas_tex->rectangle.y + 1, + 1, dst_height, + width, height, + format, rowstride, + data)) + return FALSE; + /* Update the top edge pixels */ + if (dst_y == 0 && + !cogl_texture_set_region (big_texture, + src_x, src_y, + dst_x + atlas_tex->rectangle.x + 1, + atlas_tex->rectangle.y, + dst_width, 1, + width, height, + format, rowstride, + data)) + return FALSE; + /* Update the bottom edge pixels */ + if (dst_y + dst_height == atlas_tex->rectangle.height - 2 && + !cogl_texture_set_region (big_texture, + src_x, src_y + dst_height - 1, + dst_x + atlas_tex->rectangle.x + 1, + atlas_tex->rectangle.y + + atlas_tex->rectangle.height - 1, + dst_width, 1, + width, height, + format, rowstride, + data)) + return FALSE; + + return TRUE; + } + else + /* Otherwise we can just forward on to the sub texture */ + return cogl_texture_set_region (atlas_tex->sub_texture, + src_x, src_y, + dst_x, dst_y, + dst_width, dst_height, + width, height, + format, rowstride, + data); +} + +static int +_cogl_atlas_texture_get_data (CoglTexture *tex, + CoglPixelFormat format, + unsigned int rowstride, + guint8 *data) +{ + CoglAtlasTexture *atlas_tex = COGL_ATLAS_TEXTURE (tex); + + /* Forward on to the sub texture */ + return cogl_texture_get_data (atlas_tex->sub_texture, + format, + rowstride, + data); +} + +static CoglPixelFormat +_cogl_atlas_texture_get_format (CoglTexture *tex) +{ + CoglAtlasTexture *atlas_tex = COGL_ATLAS_TEXTURE (tex); + + /* We don't want to forward this on the sub-texture because it isn't + the necessarily the same format. This will happen if the texture + isn't pre-multiplied */ + return atlas_tex->format; +} + +static GLenum +_cogl_atlas_texture_get_gl_format (CoglTexture *tex) +{ + CoglAtlasTexture *atlas_tex = COGL_ATLAS_TEXTURE (tex); + + /* Forward on to the sub texture */ + return _cogl_texture_get_gl_format (atlas_tex->sub_texture); +} + +static gint +_cogl_atlas_texture_get_width (CoglTexture *tex) +{ + CoglAtlasTexture *atlas_tex = COGL_ATLAS_TEXTURE (tex); + + /* Forward on to the sub texture */ + return cogl_texture_get_width (atlas_tex->sub_texture); +} + +static gint +_cogl_atlas_texture_get_height (CoglTexture *tex) +{ + CoglAtlasTexture *atlas_tex = COGL_ATLAS_TEXTURE (tex); + + /* Forward on to the sub texture */ + return cogl_texture_get_height (atlas_tex->sub_texture); +} + +static gboolean +_cogl_atlas_texture_reserve_space (CoglPixelFormat format, + CoglAtlas **atlas_ptr, + CoglHandle *atlas_tex_ptr, + gpointer rectangle_data, + guint width, + guint height, + CoglAtlasRectangle *rectangle) +{ + _COGL_GET_CONTEXT (ctx, FALSE); + + /* Create the atlas if we haven't already */ + if (*atlas_ptr == NULL) + *atlas_ptr = cogl_atlas_new (256, 256, NULL); + + /* Create the texture if we haven't already */ + if (*atlas_tex_ptr == NULL) + *atlas_tex_ptr = _cogl_texture_2d_new_with_size (256, 256, + COGL_TEXTURE_NONE, + format); + + /* Try to grab the space in the atlas */ + /* FIXME: This should try to reorganise the atlas to make space and + grow it if necessary. */ + return cogl_atlas_add_rectangle (*atlas_ptr, width, height, + rectangle_data, rectangle); +} + +CoglHandle +_cogl_atlas_texture_new_from_bitmap (CoglHandle bmp_handle, + CoglTextureFlags flags, + CoglPixelFormat internal_format) +{ + CoglAtlasTexture *atlas_tex; + CoglBitmap *bmp = (CoglBitmap *) bmp_handle; + CoglTextureUploadData upload_data; + CoglAtlas **atlas_ptr; + CoglHandle *atlas_tex_ptr; + CoglAtlasRectangle rectangle; + gfloat tx1, ty1, tx2, ty2; + + _COGL_GET_CONTEXT (ctx, COGL_INVALID_HANDLE); + + g_return_val_if_fail (bmp_handle != COGL_INVALID_HANDLE, COGL_INVALID_HANDLE); + + /* We can't put the texture in the atlas if there are any special + flags. This precludes textures with COGL_TEXTURE_NO_ATLAS and + COGL_TEXTURE_NO_SLICING from being atlased */ + if (flags) + return COGL_INVALID_HANDLE; + + /* We can't atlas zero-sized textures because it breaks the atlas + data structure */ + if (bmp->width < 1 || bmp->height < 1) + return COGL_INVALID_HANDLE; + + /* If we can't read back texture data then it will be too slow to + migrate textures and we shouldn't use the atlas */ + if (!cogl_features_available (COGL_FEATURE_TEXTURE_READ_PIXELS)) + return COGL_INVALID_HANDLE; + + upload_data.bitmap = *bmp; + upload_data.bitmap_owner = FALSE; + + if (!_cogl_texture_upload_data_prepare_format (&upload_data, + &internal_format)) + { + _cogl_texture_upload_data_free (&upload_data); + return COGL_INVALID_HANDLE; + } + + /* If the texture is in a strange format then we can't use it */ + if (internal_format != COGL_PIXEL_FORMAT_RGB_888 && + (internal_format & ~COGL_PREMULT_BIT) != COGL_PIXEL_FORMAT_RGBA_8888) + { + _cogl_texture_upload_data_free (&upload_data); + return COGL_INVALID_HANDLE; + } + + if ((internal_format & COGL_A_BIT)) + { + atlas_ptr = &ctx->atlas_alpha; + atlas_tex_ptr = &ctx->atlas_alpha_texture; + } + else + { + atlas_ptr = &ctx->atlas_no_alpha; + atlas_tex_ptr = &ctx->atlas_no_alpha_texture; + } + + /* 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); + + /* Try to make some space in the atlas for the texture */ + if (!_cogl_atlas_texture_reserve_space (internal_format, + atlas_ptr, + atlas_tex_ptr, + atlas_tex, + /* Add two pixels for the border */ + upload_data.bitmap.width + 2, + upload_data.bitmap.height + 2, + &rectangle)) + { + g_free (atlas_tex); + _cogl_texture_upload_data_free (&upload_data); + return COGL_INVALID_HANDLE; + } + + if (!_cogl_texture_upload_data_convert (&upload_data, internal_format)) + { + cogl_atlas_remove_rectangle (*atlas_ptr, &rectangle); + g_free (atlas_tex); + _cogl_texture_upload_data_free (&upload_data); + return COGL_INVALID_HANDLE; + } + + tx1 = (rectangle.x + 1) / (gfloat) cogl_atlas_get_width (*atlas_ptr); + ty1 = (rectangle.y + 1) / (gfloat) cogl_atlas_get_height (*atlas_ptr); + tx2 = ((rectangle.x + rectangle.width - 1) / + (gfloat) cogl_atlas_get_width (*atlas_ptr)); + ty2 = ((rectangle.y + rectangle.height - 1) / + (gfloat) cogl_atlas_get_height (*atlas_ptr)); + + atlas_tex->_parent.vtable = &cogl_atlas_texture_vtable; + atlas_tex->format = internal_format; + atlas_tex->rectangle = rectangle; + atlas_tex->in_atlas = TRUE; + atlas_tex->sub_texture = cogl_texture_new_from_sub_texture (*atlas_tex_ptr, + tx1, ty1, + tx2, ty2); + + /* Defer to set_region so that we can share the code for copying the + edge pixels to the border */ + _cogl_atlas_texture_set_region (COGL_TEXTURE (atlas_tex), + 0, 0, + 0, 0, + upload_data.bitmap.width, + upload_data.bitmap.height, + upload_data.bitmap.width, + upload_data.bitmap.height, + upload_data.bitmap.format, + upload_data.bitmap.rowstride, + upload_data.bitmap.data); + + return _cogl_atlas_texture_handle_new (atlas_tex); +} + +static const CoglTextureVtable +cogl_atlas_texture_vtable = + { + _cogl_atlas_texture_set_region, + _cogl_atlas_texture_get_data, + _cogl_atlas_texture_foreach_sub_texture_in_region, + _cogl_atlas_texture_get_max_waste, + _cogl_atlas_texture_is_sliced, + _cogl_atlas_texture_can_hardware_repeat, + _cogl_atlas_texture_transform_coords_to_gl, + _cogl_atlas_texture_get_gl_texture, + _cogl_atlas_texture_set_filters, + _cogl_atlas_texture_ensure_mipmaps, + _cogl_atlas_texture_set_wrap_mode_parameter, + _cogl_atlas_texture_get_format, + _cogl_atlas_texture_get_gl_format, + _cogl_atlas_texture_get_width, + _cogl_atlas_texture_get_height + }; diff --git a/cogl/cogl-atlas.c b/cogl/cogl-atlas.c new file mode 100644 index 000000000..d368d2aa8 --- /dev/null +++ b/cogl/cogl-atlas.c @@ -0,0 +1,520 @@ +/* + * Cogl + * + * An object oriented GL/GLES Abstraction/Utility Layer + * + * Copyright (C) 2009 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 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "cogl-atlas.h" + +/* Implements a data structure which keeps track of unused + sub-rectangles within a larger rectangle using a binary tree + structure. The algorithm for this is based on the description here: + + http://www.blackpawn.com/texts/lightmaps/default.html +*/ + +typedef struct _CoglAtlasNode CoglAtlasNode; +typedef struct _CoglAtlasStackEntry CoglAtlasStackEntry; + +typedef void (* CoglAtlasInternalForeachCb) (CoglAtlasNode *node, + gpointer data); + +typedef enum +{ + COGL_ATLAS_BRANCH, + COGL_ATLAS_FILLED_LEAF, + COGL_ATLAS_EMPTY_LEAF +} CoglAtlasNodeType; + +struct _CoglAtlas +{ + CoglAtlasNode *root; + + guint space_remaining; + guint n_rectangles; + + GDestroyNotify value_destroy_func; +}; + +struct _CoglAtlasNode +{ + CoglAtlasNodeType type; + + CoglAtlasRectangle rectangle; + + CoglAtlasNode *parent; + + union + { + /* Fields used when this is a branch */ + struct + { + CoglAtlasNode *left; + CoglAtlasNode *right; + } branch; + + /* Field used when this is a filled leaf */ + gpointer data; + } d; +}; + +struct _CoglAtlasStackEntry +{ + /* The node to search */ + CoglAtlasNode *node; + /* Index of next branch of this node to explore. Basically either 0 + to go left or 1 to go right */ + gboolean next_index; + /* Next entry in the stack */ + CoglAtlasStackEntry *next; +}; + +static CoglAtlasNode * +cogl_atlas_node_new (void) +{ + return g_slice_new (CoglAtlasNode); +} + +static void +cogl_atlas_node_free (CoglAtlasNode *node) +{ + g_slice_free (CoglAtlasNode, node); +} + +CoglAtlas * +cogl_atlas_new (guint width, guint height, + GDestroyNotify value_destroy_func) +{ + CoglAtlas *atlas = g_new (CoglAtlas, 1); + CoglAtlasNode *root = cogl_atlas_node_new (); + + root->type = COGL_ATLAS_EMPTY_LEAF; + root->parent = NULL; + root->rectangle.x = 0; + root->rectangle.y = 0; + root->rectangle.width = width; + root->rectangle.height = height; + + atlas->root = root; + atlas->space_remaining = width * height; + atlas->n_rectangles = 0; + atlas->value_destroy_func = value_destroy_func; + + return atlas; +} + +static CoglAtlasStackEntry * +cogl_atlas_stack_push (CoglAtlasStackEntry *stack, + CoglAtlasNode *node, + gboolean next_index) +{ + CoglAtlasStackEntry *new_entry = g_slice_new (CoglAtlasStackEntry); + + new_entry->node = node; + new_entry->next_index = next_index; + new_entry->next = stack; + + return new_entry; +} + +static CoglAtlasStackEntry * +cogl_atlas_stack_pop (CoglAtlasStackEntry *stack) +{ + CoglAtlasStackEntry *next = stack->next; + + g_slice_free (CoglAtlasStackEntry, stack); + + return next; +} + +static CoglAtlasNode * +cogl_atlas_node_split_horizontally (CoglAtlasNode *node, + guint left_width) +{ + /* Splits the node horizontally (according to emacs' definition, not + vim) by converting it to a branch and adding two new leaf + nodes. The leftmost branch will have the width left_width and + will be returned. If the node is already just the right size it + won't do anything */ + + CoglAtlasNode *left_node, *right_node; + + if (node->rectangle.width == left_width) + return node; + + left_node = cogl_atlas_node_new (); + left_node->type = COGL_ATLAS_EMPTY_LEAF; + left_node->parent = node; + left_node->rectangle.x = node->rectangle.x; + left_node->rectangle.y = node->rectangle.y; + left_node->rectangle.width = left_width; + left_node->rectangle.height = node->rectangle.height; + node->d.branch.left = left_node; + + right_node = cogl_atlas_node_new (); + right_node->type = COGL_ATLAS_EMPTY_LEAF; + right_node->parent = node; + right_node->rectangle.x = node->rectangle.x + left_width; + right_node->rectangle.y = node->rectangle.y; + right_node->rectangle.width = node->rectangle.width - left_width; + right_node->rectangle.height = node->rectangle.height; + node->d.branch.right = right_node; + + node->type = COGL_ATLAS_BRANCH; + + return left_node; +} + +static CoglAtlasNode * +cogl_atlas_node_split_vertically (CoglAtlasNode *node, + guint top_height) +{ + /* Splits the node vertically (according to emacs' definition, not + vim) by converting it to a branch and adding two new leaf + nodes. The topmost branch will have the height top_height and + will be returned. If the node is already just the right size it + won't do anything */ + + CoglAtlasNode *top_node, *bottom_node; + + if (node->rectangle.height == top_height) + return node; + + top_node = cogl_atlas_node_new (); + top_node->type = COGL_ATLAS_EMPTY_LEAF; + top_node->parent = node; + top_node->rectangle.x = node->rectangle.x; + top_node->rectangle.y = node->rectangle.y; + top_node->rectangle.width = node->rectangle.width; + top_node->rectangle.height = top_height; + node->d.branch.left = top_node; + + bottom_node = cogl_atlas_node_new (); + bottom_node->type = COGL_ATLAS_EMPTY_LEAF; + bottom_node->parent = node; + bottom_node->rectangle.x = node->rectangle.x; + bottom_node->rectangle.y = node->rectangle.y + top_height; + bottom_node->rectangle.width = node->rectangle.width; + bottom_node->rectangle.height = node->rectangle.height - top_height; + node->d.branch.right = bottom_node; + + node->type = COGL_ATLAS_BRANCH; + + return top_node; +} + +gboolean +cogl_atlas_add_rectangle (CoglAtlas *atlas, + guint width, guint height, + gpointer data, + CoglAtlasRectangle *rectangle) +{ + /* Stack of nodes to search in */ + CoglAtlasStackEntry *node_stack; + CoglAtlasNode *found_node = NULL; + + /* Zero-sized rectangles break the algorithm for removing rectangles + so we'll disallow them */ + g_return_val_if_fail (width > 0 && height > 0, FALSE); + + /* Start with the root node */ + node_stack = cogl_atlas_stack_push (NULL, atlas->root, FALSE); + + /* Depth-first search for an empty node that is big enough */ + while (node_stack) + { + /* Pop an entry off the stack */ + CoglAtlasNode *node = node_stack->node; + int next_index = node_stack->next_index; + node_stack = cogl_atlas_stack_pop (node_stack); + + /* Regardless of the type of the node, there's no point + descending any further if the new rectangle won't fit within + it */ + if (node->rectangle.width >= width && + node->rectangle.height >= height) + { + if (node->type == COGL_ATLAS_EMPTY_LEAF) + { + /* We've found a node we can use */ + found_node = node; + break; + } + else if (node->type == COGL_ATLAS_BRANCH) + { + if (next_index) + /* Try the right branch */ + node_stack = cogl_atlas_stack_push (node_stack, + node->d.branch.right, + 0); + else + { + /* Make sure we remember to try the right branch once + we've finished descending the left branch */ + node_stack = cogl_atlas_stack_push (node_stack, + node, + 1); + /* Try the left branch */ + node_stack = cogl_atlas_stack_push (node_stack, + node->d.branch.left, + 0); + } + } + } + } + + /* Free the stack */ + while (node_stack) + node_stack = cogl_atlas_stack_pop (node_stack); + + if (found_node) + { + /* Split according to whichever axis will leave us with the + largest space */ + if (found_node->rectangle.width - width > + found_node->rectangle.height - height) + { + found_node = cogl_atlas_node_split_horizontally (found_node, width); + found_node = cogl_atlas_node_split_vertically (found_node, height); + } + else + { + found_node = cogl_atlas_node_split_vertically (found_node, height); + found_node = cogl_atlas_node_split_horizontally (found_node, width); + } + + found_node->type = COGL_ATLAS_FILLED_LEAF; + found_node->d.data = data; + if (rectangle) + *rectangle = found_node->rectangle; + + /* Record how much empty space is remaining after this rectangle + is added */ + g_assert (width * height <= atlas->space_remaining); + atlas->space_remaining -= width * height; + atlas->n_rectangles++; + + return TRUE; + } + else + return FALSE; +} + +void +cogl_atlas_remove_rectangle (CoglAtlas *atlas, + const CoglAtlasRectangle *rectangle) +{ + CoglAtlasNode *node = atlas->root; + + /* We can do a binary-chop down the search tree to find the rectangle */ + while (node->type == COGL_ATLAS_BRANCH) + { + CoglAtlasNode *left_node = node->d.branch.left; + + /* If and only if the rectangle is in the left node then the x,y + position of the rectangle will be within the node's + rectangle */ + if (rectangle->x < left_node->rectangle.x + left_node->rectangle.width && + rectangle->y < left_node->rectangle.y + left_node->rectangle.height) + /* Go left */ + node = left_node; + else + /* Go right */ + node = node->d.branch.right; + } + + /* Make sure we found the right node */ + if (node->type != COGL_ATLAS_FILLED_LEAF || + node->rectangle.x != rectangle->x || + node->rectangle.y != rectangle->y || + node->rectangle.width != rectangle->width || + node->rectangle.height != rectangle->height) + /* This should only happen if someone tried to remove a rectangle + that was not in the atlas so something has gone wrong */ + g_return_if_reached (); + else + { + /* Convert the node back to an empty node */ + if (atlas->value_destroy_func) + atlas->value_destroy_func (node->d.data); + node->type = COGL_ATLAS_EMPTY_LEAF; + + /* Walk back up the tree combining branch nodes that have two + empty leaves back into a single empty leaf */ + for (node = node->parent; node; node = node->parent) + { + /* This node is a parent so it should always be a branch */ + g_assert (node->type == COGL_ATLAS_BRANCH); + + if (node->d.branch.left->type == COGL_ATLAS_EMPTY_LEAF && + node->d.branch.right->type == COGL_ATLAS_EMPTY_LEAF) + { + cogl_atlas_node_free (node->d.branch.left); + cogl_atlas_node_free (node->d.branch.right); + node->type = COGL_ATLAS_EMPTY_LEAF; + } + else + break; + } + + /* There is now more free space and one less rectangle */ + atlas->space_remaining += rectangle->width * rectangle->height; + g_assert (atlas->n_rectangles > 0); + atlas->n_rectangles--; + } +} + +guint +cogl_atlas_get_width (CoglAtlas *atlas) +{ + return atlas->root->rectangle.width; +} + +guint +cogl_atlas_get_height (CoglAtlas *atlas) +{ + return atlas->root->rectangle.height; +} + +guint +cogl_atlas_get_remaining_space (CoglAtlas *atlas) +{ + return atlas->space_remaining; +} + +guint +cogl_atlas_get_n_rectangles (CoglAtlas *atlas) +{ + return atlas->n_rectangles; +} + +static void +cogl_atlas_internal_foreach (CoglAtlas *atlas, + CoglAtlasInternalForeachCb callback, + gpointer data) +{ + /* Stack of nodes to search in */ + CoglAtlasStackEntry *node_stack; + + /* Start with the root node */ + node_stack = cogl_atlas_stack_push (NULL, atlas->root, 0); + + /* Iterate all nodes depth-first */ + while (node_stack) + { + CoglAtlasNode *node = node_stack->node; + + switch (node->type) + { + case COGL_ATLAS_BRANCH: + if (node_stack->next_index == 0) + { + /* Next time we come back to this node, go to the right */ + node_stack->next_index = 1; + + /* Explore the left branch next */ + node_stack = cogl_atlas_stack_push (node_stack, + node->d.branch.left, + 0); + } + else if (node_stack->next_index == 1) + { + /* Next time we come back to this node, stop processing it */ + node_stack->next_index = 2; + + /* Explore the right branch next */ + node_stack = cogl_atlas_stack_push (node_stack, + node->d.branch.right, + 0); + } + else + { + /* We're finished with this node so we can call the callback */ + callback (node, data); + node_stack = cogl_atlas_stack_pop (node_stack); + } + break; + + default: + /* Some sort of leaf node, just call the callback */ + callback (node, data); + node_stack = cogl_atlas_stack_pop (node_stack); + break; + } + } + + /* The stack should now be empty */ + g_assert (node_stack == NULL); +} + +typedef struct _CoglAtlasForeachClosure +{ + CoglAtlasCallback callback; + gpointer data; +} CoglAtlasForeachClosure; + +static void +cogl_atlas_foreach_cb (CoglAtlasNode *node, gpointer data) +{ + CoglAtlasForeachClosure *closure = data; + + if (node->type == COGL_ATLAS_FILLED_LEAF) + closure->callback (&node->rectangle, node->d.data, closure->data); +} + +void +cogl_atlas_foreach (CoglAtlas *atlas, + CoglAtlasCallback callback, + gpointer data) +{ + CoglAtlasForeachClosure closure; + + closure.callback = callback; + closure.data = data; + + cogl_atlas_internal_foreach (atlas, cogl_atlas_foreach_cb, &closure); +} + +static void +cogl_atlas_free_cb (CoglAtlasNode *node, gpointer data) +{ + CoglAtlas *atlas = data; + + if (node->type == COGL_ATLAS_FILLED_LEAF && atlas->value_destroy_func) + atlas->value_destroy_func (node->d.data); + + cogl_atlas_node_free (node); +} + +void +cogl_atlas_free (CoglAtlas *atlas) +{ + cogl_atlas_internal_foreach (atlas, cogl_atlas_free_cb, atlas); + g_free (atlas); +} diff --git a/cogl/cogl-atlas.h b/cogl/cogl-atlas.h new file mode 100644 index 000000000..37ccc898c --- /dev/null +++ b/cogl/cogl-atlas.h @@ -0,0 +1,76 @@ +/* + * Cogl + * + * An object oriented GL/GLES Abstraction/Utility Layer + * + * Copyright (C) 2009 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 + +typedef struct _CoglAtlas CoglAtlas; +typedef struct _CoglAtlasRectangle CoglAtlasRectangle; + +typedef void (* CoglAtlasCallback) (const CoglAtlasRectangle *rectangle, + gpointer rectangle_data, + gpointer user_data); + +struct _CoglAtlasRectangle +{ + guint x, y; + guint width, height; +}; + +CoglAtlas * +cogl_atlas_new (guint width, guint height, + GDestroyNotify value_destroy_func); + +gboolean +cogl_atlas_add_rectangle (CoglAtlas *atlas, + guint width, guint height, + gpointer data, + CoglAtlasRectangle *rectangle); + +void +cogl_atlas_remove_rectangle (CoglAtlas *atlas, + const CoglAtlasRectangle *rectangle); + +guint +cogl_atlas_get_width (CoglAtlas *atlas); + +guint +cogl_atlas_get_height (CoglAtlas *atlas); + +guint +cogl_atlas_get_remaining_space (CoglAtlas *atlas); + +guint +cogl_atlas_get_n_rectangles (CoglAtlas *atlas); + +void +cogl_atlas_foreach (CoglAtlas *atlas, + CoglAtlasCallback callback, + gpointer data); + +void +cogl_atlas_free (CoglAtlas *atlas); + +#endif /* __COGL_ATLAS_H */ diff --git a/cogl/cogl-context.c b/cogl/cogl-context.c index c78b2821c..d144fe557 100644 --- a/cogl/cogl-context.c +++ b/cogl/cogl-context.c @@ -147,6 +147,11 @@ cogl_create_context (void) cogl_enable (enable_flags); _cogl_flush_face_winding (); + _context->atlas_alpha = NULL; + _context->atlas_no_alpha = NULL; + _context->atlas_alpha_texture = COGL_INVALID_HANDLE; + _context->atlas_no_alpha_texture = COGL_INVALID_HANDLE; + return TRUE; } @@ -188,6 +193,15 @@ _cogl_destroy_context () if (_context->default_material) cogl_handle_unref (_context->default_material); + if (_context->atlas_alpha) + cogl_atlas_free (_context->atlas_alpha); + if (_context->atlas_no_alpha) + cogl_atlas_free (_context->atlas_no_alpha); + if (_context->atlas_alpha_texture) + cogl_handle_unref (_context->atlas_alpha_texture); + if (_context->atlas_no_alpha_texture) + cogl_handle_unref (_context->atlas_no_alpha_texture); + g_free (_context); } diff --git a/cogl/cogl-context.h b/cogl/cogl-context.h index 717f3534e..f4fff5b78 100644 --- a/cogl/cogl-context.h +++ b/cogl/cogl-context.h @@ -30,6 +30,7 @@ #include "cogl-clip-stack.h" #include "cogl-matrix-stack.h" #include "cogl-material-private.h" +#include "cogl-atlas.h" typedef struct { @@ -111,6 +112,13 @@ typedef struct CoglHandle texture_download_material; + /* Separate atlases for textures with and without alpha */ + CoglAtlas *atlas_alpha; + CoglAtlas *atlas_no_alpha; + /* Textures for the two atlases */ + CoglHandle atlas_alpha_texture; + CoglHandle atlas_no_alpha_texture; + CoglContextDriver drv; } CoglContext; diff --git a/cogl/cogl-texture.c b/cogl/cogl-texture.c index 5b94b1dbe..b316a52cf 100644 --- a/cogl/cogl-texture.c +++ b/cogl/cogl-texture.c @@ -40,6 +40,7 @@ #include "cogl-texture-2d-sliced-private.h" #include "cogl-texture-2d-private.h" #include "cogl-sub-texture-private.h" +#include "cogl-atlas-texture-private.h" #include "cogl-material.h" #include "cogl-context.h" #include "cogl-handle.h" @@ -65,6 +66,7 @@ cogl_is_texture (CoglHandle handle) return FALSE; return (obj->klass->type == _cogl_handle_texture_2d_get_type () || + obj->klass->type == _cogl_handle_atlas_texture_get_type () || obj->klass->type == _cogl_handle_texture_2d_sliced_get_type () || obj->klass->type == _cogl_handle_sub_texture_get_type ()); } @@ -366,18 +368,22 @@ cogl_texture_new_from_bitmap (CoglHandle bmp_handle, { CoglHandle tex; - /* First try creating a fast-path non-sliced texture */ - tex = _cogl_texture_2d_new_from_bitmap (bmp_handle, - flags, - internal_format); + /* First try putting the texture in the atlas */ + if ((tex = _cogl_atlas_texture_new_from_bitmap (bmp_handle, + flags, + internal_format))) + return tex; - /* If it fails resort to sliced textures */ - if (tex == COGL_INVALID_HANDLE) - tex = _cogl_texture_2d_sliced_new_from_bitmap (bmp_handle, - flags, - internal_format); + /* If that doesn't work try a fast path 2D texture */ + if ((tex = _cogl_texture_2d_new_from_bitmap (bmp_handle, + flags, + internal_format))) + return tex; - return tex; + /* Otherwise create a sliced texture */ + return _cogl_texture_2d_sliced_new_from_bitmap (bmp_handle, + flags, + internal_format); } CoglHandle diff --git a/cogl/cogl-types.h b/cogl/cogl-types.h index 30c73d4f7..3d5e3eb02 100644 --- a/cogl/cogl-types.h +++ b/cogl/cogl-types.h @@ -275,7 +275,8 @@ struct _CoglTextureVertex typedef enum { COGL_TEXTURE_NONE = 0, COGL_TEXTURE_NO_AUTO_MIPMAP = 1 << 0, - COGL_TEXTURE_NO_SLICING = 1 << 1 + COGL_TEXTURE_NO_SLICING = 1 << 1, + COGL_TEXTURE_NO_ATLAS = 1 << 2 } CoglTextureFlags; /**