cogl-rectangle-map: Store the remaining space per node

Previously the remaining space was managed as part of the
CoglRectangleMap struct. Now it is stored per node so that at any
point in the hierarchy we can quickly determine how much space is
remaining in all of the node's children. That way when adding a
rectangle we can miss out entire branches more quickly if we know that
there is no way the new rectangle would fit in that branch.

This also adds a function to recursively verify the cached state in
the nodes such as the remaining space and the number of
rectangles. This function is only called when the dump-atlas-image
debug flag is set because it is potentially quite slow.
This commit is contained in:
Neil Roberts 2010-08-06 16:13:20 +01:00
parent 92b712f6e4
commit 9a62c069aa

View File

@ -67,7 +67,6 @@ struct _CoglRectangleMap
{ {
CoglRectangleMapNode *root; CoglRectangleMapNode *root;
unsigned int space_remaining;
unsigned int n_rectangles; unsigned int n_rectangles;
GDestroyNotify value_destroy_func; GDestroyNotify value_destroy_func;
@ -79,6 +78,8 @@ struct _CoglRectangleMapNode
CoglRectangleMapEntry rectangle; CoglRectangleMapEntry rectangle;
unsigned int space_remaining;
CoglRectangleMapNode *parent; CoglRectangleMapNode *parent;
union union
@ -132,9 +133,9 @@ _cogl_rectangle_map_new (unsigned int width,
root->rectangle.y = 0; root->rectangle.y = 0;
root->rectangle.width = width; root->rectangle.width = width;
root->rectangle.height = height; root->rectangle.height = height;
root->space_remaining = width * height;
map->root = root; map->root = root;
map->space_remaining = width * height;
map->n_rectangles = 0; map->n_rectangles = 0;
map->value_destroy_func = value_destroy_func; map->value_destroy_func = value_destroy_func;
@ -188,6 +189,8 @@ _cogl_rectangle_map_node_split_horizontally (CoglRectangleMapNode *node,
left_node->rectangle.y = node->rectangle.y; left_node->rectangle.y = node->rectangle.y;
left_node->rectangle.width = left_width; left_node->rectangle.width = left_width;
left_node->rectangle.height = node->rectangle.height; left_node->rectangle.height = node->rectangle.height;
left_node->space_remaining = (left_node->rectangle.width *
left_node->rectangle.height);
node->d.branch.left = left_node; node->d.branch.left = left_node;
right_node = _cogl_rectangle_map_node_new (); right_node = _cogl_rectangle_map_node_new ();
@ -197,6 +200,8 @@ _cogl_rectangle_map_node_split_horizontally (CoglRectangleMapNode *node,
right_node->rectangle.y = node->rectangle.y; right_node->rectangle.y = node->rectangle.y;
right_node->rectangle.width = node->rectangle.width - left_width; right_node->rectangle.width = node->rectangle.width - left_width;
right_node->rectangle.height = node->rectangle.height; right_node->rectangle.height = node->rectangle.height;
right_node->space_remaining = (right_node->rectangle.width *
right_node->rectangle.height);
node->d.branch.right = right_node; node->d.branch.right = right_node;
node->type = COGL_RECTANGLE_MAP_BRANCH; node->type = COGL_RECTANGLE_MAP_BRANCH;
@ -226,6 +231,8 @@ _cogl_rectangle_map_node_split_vertically (CoglRectangleMapNode *node,
top_node->rectangle.y = node->rectangle.y; top_node->rectangle.y = node->rectangle.y;
top_node->rectangle.width = node->rectangle.width; top_node->rectangle.width = node->rectangle.width;
top_node->rectangle.height = top_height; top_node->rectangle.height = top_height;
top_node->space_remaining = (top_node->rectangle.width *
top_node->rectangle.height);
node->d.branch.left = top_node; node->d.branch.left = top_node;
bottom_node = _cogl_rectangle_map_node_new (); bottom_node = _cogl_rectangle_map_node_new ();
@ -235,6 +242,8 @@ _cogl_rectangle_map_node_split_vertically (CoglRectangleMapNode *node,
bottom_node->rectangle.y = node->rectangle.y + top_height; bottom_node->rectangle.y = node->rectangle.y + top_height;
bottom_node->rectangle.width = node->rectangle.width; bottom_node->rectangle.width = node->rectangle.width;
bottom_node->rectangle.height = node->rectangle.height - top_height; bottom_node->rectangle.height = node->rectangle.height - top_height;
bottom_node->space_remaining = (bottom_node->rectangle.width *
bottom_node->rectangle.height);
node->d.branch.right = bottom_node; node->d.branch.right = bottom_node;
node->type = COGL_RECTANGLE_MAP_BRANCH; node->type = COGL_RECTANGLE_MAP_BRANCH;
@ -242,6 +251,52 @@ _cogl_rectangle_map_node_split_vertically (CoglRectangleMapNode *node,
return top_node; 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 remaining space
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->space_remaining ==
node->d.branch.left->space_remaining +
node->d.branch.right->space_remaining);
return sum;
}
case COGL_RECTANGLE_MAP_EMPTY_LEAF:
g_assert (node->space_remaining ==
node->rectangle.width * node->rectangle.height);
return 0;
case COGL_RECTANGLE_MAP_FILLED_LEAF:
g_assert (node->space_remaining == 0);
return 1;
}
return 0;
}
static void
_cogl_rectangle_map_verify (CoglRectangleMap *map)
{
unsigned int actual_n_rectangles =
_cogl_rectangle_map_verify_recursive (map->root);
g_assert_cmpuint (actual_n_rectangles, ==, map->n_rectangles);
}
#endif /* COGL_ENABLE_DEBUG */
gboolean gboolean
_cogl_rectangle_map_add (CoglRectangleMap *map, _cogl_rectangle_map_add (CoglRectangleMap *map,
unsigned int width, unsigned int width,
@ -249,6 +304,7 @@ _cogl_rectangle_map_add (CoglRectangleMap *map,
void *data, void *data,
CoglRectangleMapEntry *rectangle) CoglRectangleMapEntry *rectangle)
{ {
unsigned int rectangle_size = width * height;
/* Stack of nodes to search in */ /* Stack of nodes to search in */
CoglRectangleMapStackEntry *node_stack; CoglRectangleMapStackEntry *node_stack;
CoglRectangleMapNode *found_node = NULL; CoglRectangleMapNode *found_node = NULL;
@ -272,7 +328,8 @@ _cogl_rectangle_map_add (CoglRectangleMap *map,
descending any further if the new rectangle won't fit within descending any further if the new rectangle won't fit within
it */ it */
if (node->rectangle.width >= width && if (node->rectangle.width >= width &&
node->rectangle.height >= height) node->rectangle.height >= height &&
node->space_remaining >= rectangle_size)
{ {
if (node->type == COGL_RECTANGLE_MAP_EMPTY_LEAF) if (node->type == COGL_RECTANGLE_MAP_EMPTY_LEAF)
{ {
@ -312,6 +369,8 @@ _cogl_rectangle_map_add (CoglRectangleMap *map,
if (found_node) if (found_node)
{ {
CoglRectangleMapNode *node;
/* Split according to whichever axis will leave us with the /* Split according to whichever axis will leave us with the
largest space */ largest space */
if (found_node->rectangle.width - width > if (found_node->rectangle.width - width >
@ -332,18 +391,31 @@ _cogl_rectangle_map_add (CoglRectangleMap *map,
found_node->type = COGL_RECTANGLE_MAP_FILLED_LEAF; found_node->type = COGL_RECTANGLE_MAP_FILLED_LEAF;
found_node->d.data = data; found_node->d.data = data;
found_node->space_remaining = 0;
if (rectangle) if (rectangle)
*rectangle = found_node->rectangle; *rectangle = found_node->rectangle;
/* Record how much empty space is remaining after this rectangle /* Walk back up the tree and reduce the amount of space
is added */ remaining in each rectangle containing this node */
g_assert (width * height <= map->space_remaining); for (node = found_node->parent; node; node = node->parent)
map->space_remaining -= width * height; {
/* This node is a parent so it should always be a branch */
g_assert (node->type == COGL_RECTANGLE_MAP_BRANCH);
node->space_remaining -= rectangle_size;
}
/* There is now an extra rectangle in the map */
map->n_rectangles++; map->n_rectangles++;
#ifdef COGL_ENABLE_DEBUG #ifdef COGL_ENABLE_DEBUG
if (G_UNLIKELY (cogl_debug_flags & COGL_DEBUG_DUMP_ATLAS_IMAGE)) if (G_UNLIKELY (cogl_debug_flags & COGL_DEBUG_DUMP_ATLAS_IMAGE))
_cogl_rectangle_map_dump_image (map); {
_cogl_rectangle_map_dump_image (map);
/* 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 #endif
return TRUE; return TRUE;
@ -357,6 +429,7 @@ _cogl_rectangle_map_remove (CoglRectangleMap *map,
const CoglRectangleMapEntry *rectangle) const CoglRectangleMapEntry *rectangle)
{ {
CoglRectangleMapNode *node = map->root; 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 */ /* We can do a binary-chop down the search tree to find the rectangle */
while (node->type == COGL_RECTANGLE_MAP_BRANCH) while (node->type == COGL_RECTANGLE_MAP_BRANCH)
@ -390,6 +463,7 @@ _cogl_rectangle_map_remove (CoglRectangleMap *map,
if (map->value_destroy_func) if (map->value_destroy_func)
map->value_destroy_func (node->d.data); map->value_destroy_func (node->d.data);
node->type = COGL_RECTANGLE_MAP_EMPTY_LEAF; node->type = COGL_RECTANGLE_MAP_EMPTY_LEAF;
node->space_remaining += rectangle_size;
/* Walk back up the tree combining branch nodes that have two /* Walk back up the tree combining branch nodes that have two
empty leaves back into a single empty leaf */ empty leaves back into a single empty leaf */
@ -404,20 +478,30 @@ _cogl_rectangle_map_remove (CoglRectangleMap *map,
_cogl_rectangle_map_node_free (node->d.branch.left); _cogl_rectangle_map_node_free (node->d.branch.left);
_cogl_rectangle_map_node_free (node->d.branch.right); _cogl_rectangle_map_node_free (node->d.branch.right);
node->type = COGL_RECTANGLE_MAP_EMPTY_LEAF; node->type = COGL_RECTANGLE_MAP_EMPTY_LEAF;
node->space_remaining += rectangle_size;
} }
else else
break; break;
} }
/* There is now more free space and one less rectangle */ /* Reduce the amount of space remaining in all of the parents
map->space_remaining += rectangle->width * rectangle->height; further up the chain */
for (; node; node = node->parent)
node->space_remaining += rectangle_size;
/* There is now one less rectangle */
g_assert (map->n_rectangles > 0); g_assert (map->n_rectangles > 0);
map->n_rectangles--; map->n_rectangles--;
} }
#ifdef COGL_ENABLE_DEBUG #ifdef COGL_ENABLE_DEBUG
if (G_UNLIKELY (cogl_debug_flags & COGL_DEBUG_DUMP_ATLAS_IMAGE)) if (G_UNLIKELY (cogl_debug_flags & COGL_DEBUG_DUMP_ATLAS_IMAGE))
_cogl_rectangle_map_dump_image (map); {
_cogl_rectangle_map_dump_image (map);
/* 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 #endif
} }
@ -436,7 +520,7 @@ _cogl_rectangle_map_get_height (CoglRectangleMap *map)
unsigned int unsigned int
_cogl_rectangle_map_get_remaining_space (CoglRectangleMap *map) _cogl_rectangle_map_get_remaining_space (CoglRectangleMap *map)
{ {
return map->space_remaining; return map->root->space_remaining;
} }
unsigned int unsigned int