4
* An object oriented GL/GLES Abstraction/Utility Layer
6
* Copyright (C) 2009 Intel Corporation.
8
* This library is free software; you can redistribute it and/or
9
* modify it under the terms of the GNU Lesser General Public
10
* License as published by the Free Software Foundation; either
11
* version 2 of the License, or (at your option) any later version.
13
* This library is distributed in the hope that it will be useful,
14
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16
* Lesser General Public License for more details.
18
* You should have received a copy of the GNU Lesser General Public
19
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
24
* Neil Roberts <neil@linux.intel.com>
33
#include "cogl-atlas.h"
34
#include "cogl-debug.h"
36
/* Implements a data structure which keeps track of unused
37
sub-rectangles within a larger rectangle using a binary tree
38
structure. The algorithm for this is based on the description here:
40
http://www.blackpawn.com/texts/lightmaps/default.html
43
#ifdef COGL_ENABLE_DEBUG
45
/* The cairo header is only used for debugging to generate an image of
49
static void _cogl_atlas_dump_image (CoglAtlas *atlas);
51
#endif /* COGL_ENABLE_DEBUG */
53
typedef struct _CoglAtlasNode CoglAtlasNode;
54
typedef struct _CoglAtlasStackEntry CoglAtlasStackEntry;
56
typedef void (* CoglAtlasInternalForeachCb) (CoglAtlasNode *node,
62
COGL_ATLAS_FILLED_LEAF,
70
unsigned int space_remaining;
71
unsigned int n_rectangles;
73
GDestroyNotify value_destroy_func;
78
CoglAtlasNodeType type;
80
CoglAtlasRectangle rectangle;
82
CoglAtlasNode *parent;
86
/* Fields used when this is a branch */
93
/* Field used when this is a filled leaf */
98
struct _CoglAtlasStackEntry
100
/* The node to search */
102
/* Index of next branch of this node to explore. Basically either 0
103
to go left or 1 to go right */
105
/* Next entry in the stack */
106
CoglAtlasStackEntry *next;
109
static CoglAtlasNode *
110
_cogl_atlas_node_new (void)
112
return g_slice_new (CoglAtlasNode);
116
_cogl_atlas_node_free (CoglAtlasNode *node)
118
g_slice_free (CoglAtlasNode, node);
122
_cogl_atlas_new (unsigned int width,
124
GDestroyNotify value_destroy_func)
126
CoglAtlas *atlas = g_new (CoglAtlas, 1);
127
CoglAtlasNode *root = _cogl_atlas_node_new ();
129
root->type = COGL_ATLAS_EMPTY_LEAF;
131
root->rectangle.x = 0;
132
root->rectangle.y = 0;
133
root->rectangle.width = width;
134
root->rectangle.height = height;
137
atlas->space_remaining = width * height;
138
atlas->n_rectangles = 0;
139
atlas->value_destroy_func = value_destroy_func;
144
static CoglAtlasStackEntry *
145
_cogl_atlas_stack_push (CoglAtlasStackEntry *stack,
149
CoglAtlasStackEntry *new_entry = g_slice_new (CoglAtlasStackEntry);
151
new_entry->node = node;
152
new_entry->next_index = next_index;
153
new_entry->next = stack;
158
static CoglAtlasStackEntry *
159
_cogl_atlas_stack_pop (CoglAtlasStackEntry *stack)
161
CoglAtlasStackEntry *next = stack->next;
163
g_slice_free (CoglAtlasStackEntry, stack);
168
static CoglAtlasNode *
169
_cogl_atlas_node_split_horizontally (CoglAtlasNode *node,
170
unsigned int left_width)
172
/* Splits the node horizontally (according to emacs' definition, not
173
vim) by converting it to a branch and adding two new leaf
174
nodes. The leftmost branch will have the width left_width and
175
will be returned. If the node is already just the right size it
178
CoglAtlasNode *left_node, *right_node;
180
if (node->rectangle.width == left_width)
183
left_node = _cogl_atlas_node_new ();
184
left_node->type = COGL_ATLAS_EMPTY_LEAF;
185
left_node->parent = node;
186
left_node->rectangle.x = node->rectangle.x;
187
left_node->rectangle.y = node->rectangle.y;
188
left_node->rectangle.width = left_width;
189
left_node->rectangle.height = node->rectangle.height;
190
node->d.branch.left = left_node;
192
right_node = _cogl_atlas_node_new ();
193
right_node->type = COGL_ATLAS_EMPTY_LEAF;
194
right_node->parent = node;
195
right_node->rectangle.x = node->rectangle.x + left_width;
196
right_node->rectangle.y = node->rectangle.y;
197
right_node->rectangle.width = node->rectangle.width - left_width;
198
right_node->rectangle.height = node->rectangle.height;
199
node->d.branch.right = right_node;
201
node->type = COGL_ATLAS_BRANCH;
206
static CoglAtlasNode *
207
_cogl_atlas_node_split_vertically (CoglAtlasNode *node,
208
unsigned int top_height)
210
/* Splits the node vertically (according to emacs' definition, not
211
vim) by converting it to a branch and adding two new leaf
212
nodes. The topmost branch will have the height top_height and
213
will be returned. If the node is already just the right size it
216
CoglAtlasNode *top_node, *bottom_node;
218
if (node->rectangle.height == top_height)
221
top_node = _cogl_atlas_node_new ();
222
top_node->type = COGL_ATLAS_EMPTY_LEAF;
223
top_node->parent = node;
224
top_node->rectangle.x = node->rectangle.x;
225
top_node->rectangle.y = node->rectangle.y;
226
top_node->rectangle.width = node->rectangle.width;
227
top_node->rectangle.height = top_height;
228
node->d.branch.left = top_node;
230
bottom_node = _cogl_atlas_node_new ();
231
bottom_node->type = COGL_ATLAS_EMPTY_LEAF;
232
bottom_node->parent = node;
233
bottom_node->rectangle.x = node->rectangle.x;
234
bottom_node->rectangle.y = node->rectangle.y + top_height;
235
bottom_node->rectangle.width = node->rectangle.width;
236
bottom_node->rectangle.height = node->rectangle.height - top_height;
237
node->d.branch.right = bottom_node;
239
node->type = COGL_ATLAS_BRANCH;
245
_cogl_atlas_add_rectangle (CoglAtlas *atlas,
249
CoglAtlasRectangle *rectangle)
251
/* Stack of nodes to search in */
252
CoglAtlasStackEntry *node_stack;
253
CoglAtlasNode *found_node = NULL;
255
/* Zero-sized rectangles break the algorithm for removing rectangles
256
so we'll disallow them */
257
g_return_val_if_fail (width > 0 && height > 0, FALSE);
259
/* Start with the root node */
260
node_stack = _cogl_atlas_stack_push (NULL, atlas->root, FALSE);
262
/* Depth-first search for an empty node that is big enough */
265
/* Pop an entry off the stack */
266
CoglAtlasNode *node = node_stack->node;
267
int next_index = node_stack->next_index;
268
node_stack = _cogl_atlas_stack_pop (node_stack);
270
/* Regardless of the type of the node, there's no point
271
descending any further if the new rectangle won't fit within
273
if (node->rectangle.width >= width &&
274
node->rectangle.height >= height)
276
if (node->type == COGL_ATLAS_EMPTY_LEAF)
278
/* We've found a node we can use */
282
else if (node->type == COGL_ATLAS_BRANCH)
285
/* Try the right branch */
286
node_stack = _cogl_atlas_stack_push (node_stack,
287
node->d.branch.right,
291
/* Make sure we remember to try the right branch once
292
we've finished descending the left branch */
293
node_stack = _cogl_atlas_stack_push (node_stack,
296
/* Try the left branch */
297
node_stack = _cogl_atlas_stack_push (node_stack,
307
node_stack = _cogl_atlas_stack_pop (node_stack);
311
/* Split according to whichever axis will leave us with the
313
if (found_node->rectangle.width - width >
314
found_node->rectangle.height - height)
316
found_node = _cogl_atlas_node_split_horizontally (found_node, width);
317
found_node = _cogl_atlas_node_split_vertically (found_node, height);
321
found_node = _cogl_atlas_node_split_vertically (found_node, height);
322
found_node = _cogl_atlas_node_split_horizontally (found_node, width);
325
found_node->type = COGL_ATLAS_FILLED_LEAF;
326
found_node->d.data = data;
328
*rectangle = found_node->rectangle;
330
/* Record how much empty space is remaining after this rectangle
332
g_assert (width * height <= atlas->space_remaining);
333
atlas->space_remaining -= width * height;
334
atlas->n_rectangles++;
336
#ifdef COGL_ENABLE_DEBUG
337
if (G_UNLIKELY (cogl_debug_flags & COGL_DEBUG_DUMP_ATLAS_IMAGE))
338
_cogl_atlas_dump_image (atlas);
348
_cogl_atlas_remove_rectangle (CoglAtlas *atlas,
349
const CoglAtlasRectangle *rectangle)
351
CoglAtlasNode *node = atlas->root;
353
/* We can do a binary-chop down the search tree to find the rectangle */
354
while (node->type == COGL_ATLAS_BRANCH)
356
CoglAtlasNode *left_node = node->d.branch.left;
358
/* If and only if the rectangle is in the left node then the x,y
359
position of the rectangle will be within the node's
361
if (rectangle->x < left_node->rectangle.x + left_node->rectangle.width &&
362
rectangle->y < left_node->rectangle.y + left_node->rectangle.height)
367
node = node->d.branch.right;
370
/* Make sure we found the right node */
371
if (node->type != COGL_ATLAS_FILLED_LEAF ||
372
node->rectangle.x != rectangle->x ||
373
node->rectangle.y != rectangle->y ||
374
node->rectangle.width != rectangle->width ||
375
node->rectangle.height != rectangle->height)
376
/* This should only happen if someone tried to remove a rectangle
377
that was not in the atlas so something has gone wrong */
378
g_return_if_reached ();
381
/* Convert the node back to an empty node */
382
if (atlas->value_destroy_func)
383
atlas->value_destroy_func (node->d.data);
384
node->type = COGL_ATLAS_EMPTY_LEAF;
386
/* Walk back up the tree combining branch nodes that have two
387
empty leaves back into a single empty leaf */
388
for (node = node->parent; node; node = node->parent)
390
/* This node is a parent so it should always be a branch */
391
g_assert (node->type == COGL_ATLAS_BRANCH);
393
if (node->d.branch.left->type == COGL_ATLAS_EMPTY_LEAF &&
394
node->d.branch.right->type == COGL_ATLAS_EMPTY_LEAF)
396
_cogl_atlas_node_free (node->d.branch.left);
397
_cogl_atlas_node_free (node->d.branch.right);
398
node->type = COGL_ATLAS_EMPTY_LEAF;
404
/* There is now more free space and one less rectangle */
405
atlas->space_remaining += rectangle->width * rectangle->height;
406
g_assert (atlas->n_rectangles > 0);
407
atlas->n_rectangles--;
410
#ifdef COGL_ENABLE_DEBUG
411
if (G_UNLIKELY (cogl_debug_flags & COGL_DEBUG_DUMP_ATLAS_IMAGE))
412
_cogl_atlas_dump_image (atlas);
417
_cogl_atlas_get_width (CoglAtlas *atlas)
419
return atlas->root->rectangle.width;
423
_cogl_atlas_get_height (CoglAtlas *atlas)
425
return atlas->root->rectangle.height;
429
_cogl_atlas_get_remaining_space (CoglAtlas *atlas)
431
return atlas->space_remaining;
435
_cogl_atlas_get_n_rectangles (CoglAtlas *atlas)
437
return atlas->n_rectangles;
441
_cogl_atlas_internal_foreach (CoglAtlas *atlas,
442
CoglAtlasInternalForeachCb callback,
445
/* Stack of nodes to search in */
446
CoglAtlasStackEntry *node_stack;
448
/* Start with the root node */
449
node_stack = _cogl_atlas_stack_push (NULL, atlas->root, 0);
451
/* Iterate all nodes depth-first */
454
CoglAtlasNode *node = node_stack->node;
458
case COGL_ATLAS_BRANCH:
459
if (node_stack->next_index == 0)
461
/* Next time we come back to this node, go to the right */
462
node_stack->next_index = 1;
464
/* Explore the left branch next */
465
node_stack = _cogl_atlas_stack_push (node_stack,
469
else if (node_stack->next_index == 1)
471
/* Next time we come back to this node, stop processing it */
472
node_stack->next_index = 2;
474
/* Explore the right branch next */
475
node_stack = _cogl_atlas_stack_push (node_stack,
476
node->d.branch.right,
481
/* We're finished with this node so we can call the callback */
482
callback (node, data);
483
node_stack = _cogl_atlas_stack_pop (node_stack);
488
/* Some sort of leaf node, just call the callback */
489
callback (node, data);
490
node_stack = _cogl_atlas_stack_pop (node_stack);
495
/* The stack should now be empty */
496
g_assert (node_stack == NULL);
499
typedef struct _CoglAtlasForeachClosure
501
CoglAtlasCallback callback;
503
} CoglAtlasForeachClosure;
506
_cogl_atlas_foreach_cb (CoglAtlasNode *node, gpointer data)
508
CoglAtlasForeachClosure *closure = data;
510
if (node->type == COGL_ATLAS_FILLED_LEAF)
511
closure->callback (&node->rectangle, node->d.data, closure->data);
515
_cogl_atlas_foreach (CoglAtlas *atlas,
516
CoglAtlasCallback callback,
519
CoglAtlasForeachClosure closure;
521
closure.callback = callback;
524
_cogl_atlas_internal_foreach (atlas, _cogl_atlas_foreach_cb, &closure);
528
_cogl_atlas_free_cb (CoglAtlasNode *node, gpointer data)
530
CoglAtlas *atlas = data;
532
if (node->type == COGL_ATLAS_FILLED_LEAF && atlas->value_destroy_func)
533
atlas->value_destroy_func (node->d.data);
535
_cogl_atlas_node_free (node);
539
_cogl_atlas_free (CoglAtlas *atlas)
541
_cogl_atlas_internal_foreach (atlas, _cogl_atlas_free_cb, atlas);
545
#ifdef COGL_ENABLE_DEBUG
548
_cogl_atlas_dump_image_cb (CoglAtlasNode *node, gpointer data)
552
if (node->type == COGL_ATLAS_FILLED_LEAF ||
553
node->type == COGL_ATLAS_EMPTY_LEAF)
555
/* Fill the rectangle using a different colour depending on
556
whether the rectangle is used */
557
if (node->type == COGL_ATLAS_FILLED_LEAF)
558
cairo_set_source_rgb (cr, 0.0, 0.0, 1.0);
560
cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
565
node->rectangle.width,
566
node->rectangle.height);
568
cairo_fill_preserve (cr);
570
/* Draw a white outline around the rectangle */
571
cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
577
_cogl_atlas_dump_image (CoglAtlas *atlas)
579
/* This dumps a png to help visualize the atlas. Each leaf rectangle
580
is drawn with a white outline. Unused leaves are filled in black
581
and used leaves are blue */
583
cairo_surface_t *surface =
584
cairo_image_surface_create (CAIRO_FORMAT_RGB24,
585
_cogl_atlas_get_width (atlas),
586
_cogl_atlas_get_height (atlas));
587
cairo_t *cr = cairo_create (surface);
589
_cogl_atlas_internal_foreach (atlas, _cogl_atlas_dump_image_cb, cr);
593
cairo_surface_write_to_png (surface, "cogl-atlas-dump.png");
595
cairo_surface_destroy (surface);
598
#endif /* COGL_ENABLE_DEBUG */