summaryrefslogtreecommitdiff
path: root/cogl/cogl/cogl-rectangle-map.c
diff options
context:
space:
mode:
Diffstat (limited to 'cogl/cogl/cogl-rectangle-map.c')
-rw-r--r--cogl/cogl/cogl-rectangle-map.c764
1 files changed, 764 insertions, 0 deletions
diff --git a/cogl/cogl/cogl-rectangle-map.c b/cogl/cogl/cogl-rectangle-map.c
new file mode 100644
index 000000000..69368eed6
--- /dev/null
+++ b/cogl/cogl/cogl-rectangle-map.c
@@ -0,0 +1,764 @@
+/*
+ * Cogl
+ *
+ * A Low Level GPU Graphics and Utilities API
+ *
+ * Copyright (C) 2009 Intel Corporation.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ *
+ *
+ * Authors:
+ * Neil Roberts <neil@linux.intel.com>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <glib.h>
+
+#include "cogl-util.h"
+#include "cogl-rectangle-map.h"
+#include "cogl-debug.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
+*/
+
+#if defined (COGL_ENABLE_DEBUG) && defined (HAVE_CAIRO)
+
+/* The cairo header is only used for debugging to generate an image of
+ the atlas */
+#include <cairo.h>
+
+static void _cogl_rectangle_map_dump_image (CoglRectangleMap *map);
+
+#endif /* COGL_ENABLE_DEBUG && HAVE_CAIRO */
+
+typedef struct _CoglRectangleMapNode CoglRectangleMapNode;
+typedef struct _CoglRectangleMapStackEntry CoglRectangleMapStackEntry;
+
+typedef void (* CoglRectangleMapInternalForeachCb) (CoglRectangleMapNode *node,
+ void *data);
+
+typedef enum
+{
+ COGL_RECTANGLE_MAP_BRANCH,
+ COGL_RECTANGLE_MAP_FILLED_LEAF,
+ COGL_RECTANGLE_MAP_EMPTY_LEAF
+} CoglRectangleMapNodeType;
+
+struct _CoglRectangleMap
+{
+ CoglRectangleMapNode *root;
+
+ unsigned int n_rectangles;
+
+ unsigned int space_remaining;
+
+ GDestroyNotify value_destroy_func;
+
+ /* Stack used for walking the structure. This is only used during
+ the lifetime of a single function call but it is kept here as an
+ optimisation to avoid reallocating it every time it is needed */
+ GArray *stack;
+};
+
+struct _CoglRectangleMapNode
+{
+ CoglRectangleMapNodeType type;
+
+ CoglRectangleMapEntry rectangle;
+
+ unsigned int largest_gap;
+
+ CoglRectangleMapNode *parent;
+
+ union
+ {
+ /* Fields used when this is a branch */
+ struct
+ {
+ CoglRectangleMapNode *left;
+ CoglRectangleMapNode *right;
+ } branch;
+
+ /* Field used when this is a filled leaf */
+ void *data;
+ } d;
+};
+
+struct _CoglRectangleMapStackEntry
+{
+ /* The node to search */
+ CoglRectangleMapNode *node;
+ /* Index of next branch of this node to explore. Basically either 0
+ to go left or 1 to go right */
+ CoglBool next_index;
+};
+
+static CoglRectangleMapNode *
+_cogl_rectangle_map_node_new (void)
+{
+ return g_slice_new (CoglRectangleMapNode);
+}
+
+static void
+_cogl_rectangle_map_node_free (CoglRectangleMapNode *node)
+{
+ g_slice_free (CoglRectangleMapNode, node);
+}
+
+CoglRectangleMap *
+_cogl_rectangle_map_new (unsigned int width,
+ unsigned int height,
+ GDestroyNotify value_destroy_func)
+{
+ CoglRectangleMap *map = g_new (CoglRectangleMap, 1);
+ CoglRectangleMapNode *root = _cogl_rectangle_map_node_new ();
+
+ root->type = COGL_RECTANGLE_MAP_EMPTY_LEAF;
+ root->parent = NULL;
+ root->rectangle.x = 0;
+ root->rectangle.y = 0;
+ root->rectangle.width = width;
+ root->rectangle.height = height;
+ root->largest_gap = width * height;
+
+ map->root = root;
+ map->n_rectangles = 0;
+ map->value_destroy_func = value_destroy_func;
+ map->space_remaining = width * height;
+
+ map->stack = g_array_new (FALSE, FALSE, sizeof (CoglRectangleMapStackEntry));
+
+ return map;
+}
+
+static void
+_cogl_rectangle_map_stack_push (GArray *stack,
+ CoglRectangleMapNode *node,
+ CoglBool next_index)
+{
+ CoglRectangleMapStackEntry *new_entry;
+
+ g_array_set_size (stack, stack->len + 1);
+
+ new_entry = &g_array_index (stack, CoglRectangleMapStackEntry,
+ stack->len - 1);
+
+ new_entry->node = node;
+ new_entry->next_index = next_index;
+}
+
+static void
+_cogl_rectangle_map_stack_pop (GArray *stack)
+{
+ g_array_set_size (stack, stack->len - 1);
+}
+
+static CoglRectangleMapStackEntry *
+_cogl_rectangle_map_stack_get_top (GArray *stack)
+{
+ return &g_array_index (stack, CoglRectangleMapStackEntry,
+ stack->len - 1);
+}
+
+static CoglRectangleMapNode *
+_cogl_rectangle_map_node_split_horizontally (CoglRectangleMapNode *node,
+ unsigned int 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 */
+
+ CoglRectangleMapNode *left_node, *right_node;
+
+ if (node->rectangle.width == left_width)
+ return node;
+
+ left_node = _cogl_rectangle_map_node_new ();
+ left_node->type = COGL_RECTANGLE_MAP_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;
+ left_node->largest_gap = (left_node->rectangle.width *
+ left_node->rectangle.height);
+ node->d.branch.left = left_node;
+
+ right_node = _cogl_rectangle_map_node_new ();
+ right_node->type = COGL_RECTANGLE_MAP_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;
+ right_node->largest_gap = (right_node->rectangle.width *
+ right_node->rectangle.height);
+ node->d.branch.right = right_node;
+
+ node->type = COGL_RECTANGLE_MAP_BRANCH;
+
+ return left_node;
+}
+
+static CoglRectangleMapNode *
+_cogl_rectangle_map_node_split_vertically (CoglRectangleMapNode *node,
+ unsigned int 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 */
+
+ CoglRectangleMapNode *top_node, *bottom_node;
+
+ if (node->rectangle.height == top_height)
+ return node;
+
+ top_node = _cogl_rectangle_map_node_new ();
+ top_node->type = COGL_RECTANGLE_MAP_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;
+ top_node->largest_gap = (top_node->rectangle.width *
+ top_node->rectangle.height);
+ node->d.branch.left = top_node;
+
+ bottom_node = _cogl_rectangle_map_node_new ();
+ bottom_node->type = COGL_RECTANGLE_MAP_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;
+ bottom_node->largest_gap = (bottom_node->rectangle.width *
+ bottom_node->rectangle.height);
+ node->d.branch.right = bottom_node;
+
+ node->type = COGL_RECTANGLE_MAP_BRANCH;
+
+ return top_node;
+}
+
+#ifdef COGL_ENABLE_DEBUG
+
+static unsigned int
+_cogl_rectangle_map_verify_recursive (CoglRectangleMapNode *node)
+{
+ /* This is just used for debugging the data structure. It
+ recursively walks the tree to verify that the largest gap values
+ all add up */
+
+ switch (node->type)
+ {
+ case COGL_RECTANGLE_MAP_BRANCH:
+ {
+ int sum =
+ _cogl_rectangle_map_verify_recursive (node->d.branch.left) +
+ _cogl_rectangle_map_verify_recursive (node->d.branch.right);
+ g_assert (node->largest_gap ==
+ MAX (node->d.branch.left->largest_gap,
+ node->d.branch.right->largest_gap));
+ return sum;
+ }
+
+ case COGL_RECTANGLE_MAP_EMPTY_LEAF:
+ g_assert (node->largest_gap ==
+ node->rectangle.width * node->rectangle.height);
+ return 0;
+
+ case COGL_RECTANGLE_MAP_FILLED_LEAF:
+ g_assert (node->largest_gap == 0);
+ return 1;
+ }
+
+ return 0;
+}
+
+static unsigned int
+_cogl_rectangle_map_get_space_remaining_recursive (CoglRectangleMapNode *node)
+{
+ /* This is just used for debugging the data structure. It
+ recursively walks the tree to verify that the remaining space
+ value adds up */
+
+ switch (node->type)
+ {
+ case COGL_RECTANGLE_MAP_BRANCH:
+ {
+ CoglRectangleMapNode *l = node->d.branch.left;
+ CoglRectangleMapNode *r = node->d.branch.right;
+
+ return (_cogl_rectangle_map_get_space_remaining_recursive (l) +
+ _cogl_rectangle_map_get_space_remaining_recursive (r));
+ }
+
+ case COGL_RECTANGLE_MAP_EMPTY_LEAF:
+ return node->rectangle.width * node->rectangle.height;
+
+ case COGL_RECTANGLE_MAP_FILLED_LEAF:
+ return 0;
+ }
+
+ return 0;
+}
+
+static void
+_cogl_rectangle_map_verify (CoglRectangleMap *map)
+{
+ unsigned int actual_n_rectangles =
+ _cogl_rectangle_map_verify_recursive (map->root);
+ unsigned int actual_space_remaining =
+ _cogl_rectangle_map_get_space_remaining_recursive (map->root);
+
+ g_assert_cmpuint (actual_n_rectangles, ==, map->n_rectangles);
+ g_assert_cmpuint (actual_space_remaining, ==, map->space_remaining);
+}
+
+#endif /* COGL_ENABLE_DEBUG */
+
+CoglBool
+_cogl_rectangle_map_add (CoglRectangleMap *map,
+ unsigned int width,
+ unsigned int height,
+ void *data,
+ CoglRectangleMapEntry *rectangle)
+{
+ unsigned int rectangle_size = width * height;
+ /* Stack of nodes to search in */
+ GArray *stack = map->stack;
+ CoglRectangleMapNode *found_node = NULL;
+
+ /* Zero-sized rectangles break the algorithm for removing rectangles
+ so we'll disallow them */
+ _COGL_RETURN_VAL_IF_FAIL (width > 0 && height > 0, FALSE);
+
+ /* Start with the root node */
+ g_array_set_size (stack, 0);
+ _cogl_rectangle_map_stack_push (stack, map->root, FALSE);
+
+ /* Depth-first search for an empty node that is big enough */
+ while (stack->len > 0)
+ {
+ CoglRectangleMapStackEntry *stack_top;
+ CoglRectangleMapNode *node;
+ int next_index;
+
+ /* Pop an entry off the stack */
+ stack_top = _cogl_rectangle_map_stack_get_top (stack);
+ node = stack_top->node;
+ next_index = stack_top->next_index;
+ _cogl_rectangle_map_stack_pop (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 &&
+ node->largest_gap >= rectangle_size)
+ {
+ if (node->type == COGL_RECTANGLE_MAP_EMPTY_LEAF)
+ {
+ /* We've found a node we can use */
+ found_node = node;
+ break;
+ }
+ else if (node->type == COGL_RECTANGLE_MAP_BRANCH)
+ {
+ if (next_index)
+ /* Try the right branch */
+ _cogl_rectangle_map_stack_push (stack,
+ node->d.branch.right,
+ 0);
+ else
+ {
+ /* Make sure we remember to try the right branch once
+ we've finished descending the left branch */
+ _cogl_rectangle_map_stack_push (stack,
+ node,
+ 1);
+ /* Try the left branch */
+ _cogl_rectangle_map_stack_push (stack,
+ node->d.branch.left,
+ 0);
+ }
+ }
+ }
+ }
+
+ if (found_node)
+ {
+ CoglRectangleMapNode *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_rectangle_map_node_split_horizontally (found_node, width);
+ found_node =
+ _cogl_rectangle_map_node_split_vertically (found_node, height);
+ }
+ else
+ {
+ found_node =
+ _cogl_rectangle_map_node_split_vertically (found_node, height);
+ found_node =
+ _cogl_rectangle_map_node_split_horizontally (found_node, width);
+ }
+
+ found_node->type = COGL_RECTANGLE_MAP_FILLED_LEAF;
+ found_node->d.data = data;
+ found_node->largest_gap = 0;
+ if (rectangle)
+ *rectangle = found_node->rectangle;
+
+ /* Walk back up the tree and update the stored largest gap for
+ the node's sub tree */
+ for (node = found_node->parent; node; node = node->parent)
+ {
+ /* This node is a parent so it should always be a branch */
+ g_assert (node->type == COGL_RECTANGLE_MAP_BRANCH);
+
+ node->largest_gap = MAX (node->d.branch.left->largest_gap,
+ node->d.branch.right->largest_gap);
+ }
+
+ /* There is now an extra rectangle in the map */
+ map->n_rectangles++;
+ /* and less space */
+ map->space_remaining -= rectangle_size;
+
+#ifdef COGL_ENABLE_DEBUG
+ if (G_UNLIKELY (COGL_DEBUG_ENABLED (COGL_DEBUG_DUMP_ATLAS_IMAGE)))
+ {
+#ifdef HAVE_CAIRO
+ _cogl_rectangle_map_dump_image (map);
+#endif
+ /* Dumping the rectangle map is really slow so we might as well
+ verify the space remaining here as it is also quite slow */
+ _cogl_rectangle_map_verify (map);
+ }
+#endif
+
+ return TRUE;
+ }
+ else
+ return FALSE;
+}
+
+void
+_cogl_rectangle_map_remove (CoglRectangleMap *map,
+ const CoglRectangleMapEntry *rectangle)
+{
+ CoglRectangleMapNode *node = map->root;
+ unsigned int rectangle_size = rectangle->width * rectangle->height;
+
+ /* We can do a binary-chop down the search tree to find the rectangle */
+ while (node->type == COGL_RECTANGLE_MAP_BRANCH)
+ {
+ CoglRectangleMapNode *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_RECTANGLE_MAP_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 map so something has gone wrong */
+ g_return_if_reached ();
+ else
+ {
+ /* Convert the node back to an empty node */
+ if (map->value_destroy_func)
+ map->value_destroy_func (node->d.data);
+ node->type = COGL_RECTANGLE_MAP_EMPTY_LEAF;
+ node->largest_gap = rectangle_size;
+
+ /* 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_RECTANGLE_MAP_BRANCH);
+
+ if (node->d.branch.left->type == COGL_RECTANGLE_MAP_EMPTY_LEAF &&
+ node->d.branch.right->type == COGL_RECTANGLE_MAP_EMPTY_LEAF)
+ {
+ _cogl_rectangle_map_node_free (node->d.branch.left);
+ _cogl_rectangle_map_node_free (node->d.branch.right);
+ node->type = COGL_RECTANGLE_MAP_EMPTY_LEAF;
+
+ node->largest_gap = (node->rectangle.width *
+ node->rectangle.height);
+ }
+ else
+ break;
+ }
+
+ /* Reduce the amount of space remaining in all of the parents
+ further up the chain */
+ for (; node; node = node->parent)
+ node->largest_gap = MAX (node->d.branch.left->largest_gap,
+ node->d.branch.right->largest_gap);
+
+ /* There is now one less rectangle */
+ g_assert (map->n_rectangles > 0);
+ map->n_rectangles--;
+ /* and more space */
+ map->space_remaining += rectangle_size;
+ }
+
+#ifdef COGL_ENABLE_DEBUG
+ if (G_UNLIKELY (COGL_DEBUG_ENABLED (COGL_DEBUG_DUMP_ATLAS_IMAGE)))
+ {
+#ifdef HAVE_CAIRO
+ _cogl_rectangle_map_dump_image (map);
+#endif
+ /* Dumping the rectangle map is really slow so we might as well
+ verify the space remaining here as it is also quite slow */
+ _cogl_rectangle_map_verify (map);
+ }
+#endif
+}
+
+unsigned int
+_cogl_rectangle_map_get_width (CoglRectangleMap *map)
+{
+ return map->root->rectangle.width;
+}
+
+unsigned int
+_cogl_rectangle_map_get_height (CoglRectangleMap *map)
+{
+ return map->root->rectangle.height;
+}
+
+unsigned int
+_cogl_rectangle_map_get_remaining_space (CoglRectangleMap *map)
+{
+ return map->space_remaining;
+}
+
+unsigned int
+_cogl_rectangle_map_get_n_rectangles (CoglRectangleMap *map)
+{
+ return map->n_rectangles;
+}
+
+static void
+_cogl_rectangle_map_internal_foreach (CoglRectangleMap *map,
+ CoglRectangleMapInternalForeachCb func,
+ void *data)
+{
+ /* Stack of nodes to search in */
+ GArray *stack = map->stack;
+
+ /* Start with the root node */
+ g_array_set_size (stack, 0);
+ _cogl_rectangle_map_stack_push (stack, map->root, 0);
+
+ /* Iterate all nodes depth-first */
+ while (stack->len > 0)
+ {
+ CoglRectangleMapStackEntry *stack_top =
+ _cogl_rectangle_map_stack_get_top (stack);
+ CoglRectangleMapNode *node = stack_top->node;
+
+ switch (node->type)
+ {
+ case COGL_RECTANGLE_MAP_BRANCH:
+ if (stack_top->next_index == 0)
+ {
+ /* Next time we come back to this node, go to the right */
+ stack_top->next_index = 1;
+
+ /* Explore the left branch next */
+ _cogl_rectangle_map_stack_push (stack,
+ node->d.branch.left,
+ 0);
+ }
+ else if (stack_top->next_index == 1)
+ {
+ /* Next time we come back to this node, stop processing it */
+ stack_top->next_index = 2;
+
+ /* Explore the right branch next */
+ _cogl_rectangle_map_stack_push (stack,
+ node->d.branch.right,
+ 0);
+ }
+ else
+ {
+ /* We're finished with this node so we can call the callback */
+ func (node, data);
+ _cogl_rectangle_map_stack_pop (stack);
+ }
+ break;
+
+ default:
+ /* Some sort of leaf node, just call the callback */
+ func (node, data);
+ _cogl_rectangle_map_stack_pop (stack);
+ break;
+ }
+ }
+
+ /* The stack should now be empty */
+ g_assert (stack->len == 0);
+}
+
+typedef struct _CoglRectangleMapForeachClosure
+{
+ CoglRectangleMapCallback callback;
+ void *data;
+} CoglRectangleMapForeachClosure;
+
+static void
+_cogl_rectangle_map_foreach_cb (CoglRectangleMapNode *node, void *data)
+{
+ CoglRectangleMapForeachClosure *closure = data;
+
+ if (node->type == COGL_RECTANGLE_MAP_FILLED_LEAF)
+ closure->callback (&node->rectangle, node->d.data, closure->data);
+}
+
+void
+_cogl_rectangle_map_foreach (CoglRectangleMap *map,
+ CoglRectangleMapCallback callback,
+ void *data)
+{
+ CoglRectangleMapForeachClosure closure;
+
+ closure.callback = callback;
+ closure.data = data;
+
+ _cogl_rectangle_map_internal_foreach (map,
+ _cogl_rectangle_map_foreach_cb,
+ &closure);
+}
+
+static void
+_cogl_rectangle_map_free_cb (CoglRectangleMapNode *node, void *data)
+{
+ CoglRectangleMap *map = data;
+
+ if (node->type == COGL_RECTANGLE_MAP_FILLED_LEAF && map->value_destroy_func)
+ map->value_destroy_func (node->d.data);
+
+ _cogl_rectangle_map_node_free (node);
+}
+
+void
+_cogl_rectangle_map_free (CoglRectangleMap *map)
+{
+ _cogl_rectangle_map_internal_foreach (map,
+ _cogl_rectangle_map_free_cb,
+ map);
+
+ g_array_free (map->stack, TRUE);
+
+ g_free (map);
+}
+
+#if defined (COGL_ENABLE_DEBUG) && defined (HAVE_CAIRO)
+
+static void
+_cogl_rectangle_map_dump_image_cb (CoglRectangleMapNode *node, void *data)
+{
+ cairo_t *cr = data;
+
+ if (node->type == COGL_RECTANGLE_MAP_FILLED_LEAF ||
+ node->type == COGL_RECTANGLE_MAP_EMPTY_LEAF)
+ {
+ /* Fill the rectangle using a different colour depending on
+ whether the rectangle is used */
+ if (node->type == COGL_RECTANGLE_MAP_FILLED_LEAF)
+ cairo_set_source_rgb (cr, 0.0, 0.0, 1.0);
+ else
+ cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
+
+ cairo_rectangle (cr,
+ node->rectangle.x,
+ node->rectangle.y,
+ node->rectangle.width,
+ node->rectangle.height);
+
+ cairo_fill_preserve (cr);
+
+ /* Draw a white outline around the rectangle */
+ cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
+ cairo_stroke (cr);
+ }
+}
+
+static void
+_cogl_rectangle_map_dump_image (CoglRectangleMap *map)
+{
+ /* This dumps a png to help visualize the map. Each leaf rectangle
+ is drawn with a white outline. Unused leaves are filled in black
+ and used leaves are blue */
+
+ cairo_surface_t *surface =
+ cairo_image_surface_create (CAIRO_FORMAT_RGB24,
+ _cogl_rectangle_map_get_width (map),
+ _cogl_rectangle_map_get_height (map));
+ cairo_t *cr = cairo_create (surface);
+
+ _cogl_rectangle_map_internal_foreach (map,
+ _cogl_rectangle_map_dump_image_cb,
+ cr);
+
+ cairo_destroy (cr);
+
+ cairo_surface_write_to_png (surface, "cogl-rectangle-map-dump.png");
+
+ cairo_surface_destroy (surface);
+}
+
+#endif /* COGL_ENABLE_DEBUG && HAVE_CAIRO */