mutter/cogl/cogl-rectangle-map.c
Robert Bragg a0441778ad This re-licenses Cogl 1.18 under the MIT license
Since the Cogl 1.18 branch is actively maintained in parallel with the
master branch; this is a counter part to commit 1b83ef938fc16b which
re-licensed the master branch to use the MIT license.

This re-licensing is a follow up to the proposal that was sent to the
Cogl mailing list:
http://lists.freedesktop.org/archives/cogl/2013-December/001465.html

Note: there was a copyright assignment policy in place for Clutter (and
therefore Cogl which was part of Clutter at the time) until the 11th of
June 2010 and so we only checked the details after that point (commit
0bbf50f905)

For each file, authors were identified via this Git command:
$ git blame -p -C -C -C20 -M -M10  0bbf50f905..HEAD

We received blanket approvals for re-licensing all Red Hat and Collabora
contributions which reduced how many people needed to be contacted
individually:
- http://lists.freedesktop.org/archives/cogl/2013-December/001470.html
- http://lists.freedesktop.org/archives/cogl/2014-January/001536.html

Individual approval requests were sent to all the other identified authors
who all confirmed the re-license on the Cogl mailinglist:
http://lists.freedesktop.org/archives/cogl/2014-January

As well as updating the copyright header in all sources files, the
COPYING file has been updated to reflect the license change and also
document the other licenses used in Cogl such as the SGI Free Software
License B, version 2.0 and the 3-clause BSD license.

This patch was not simply cherry-picked from master; but the same
methodology was used to check the source files.
2014-02-22 02:02:53 +00:00

765 lines
23 KiB
C

/*
* 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 */