util/rectpack: pool sections, support custom allocators

This commit is contained in:
Andrei Alexeyev 2023-02-09 15:57:47 +01:00
parent 9089a2f16e
commit 8b9602c402
No known key found for this signature in database
GPG key ID: 72D26128040B9690
3 changed files with 77 additions and 57 deletions

View file

@ -79,7 +79,7 @@ static const char* ft_error_str(FT_Error err_code) {
typedef struct SpriteSheet {
LIST_INTERFACE(struct SpriteSheet);
Texture *tex;
RectPack *rectpack;
RectPack rectpack;
uint glyphs;
} SpriteSheet;
@ -379,7 +379,6 @@ void font_set_kerning_enabled(Font *font, bool newval) {
static SpriteSheet* add_spritesheet(SpriteSheetAnchor *spritesheets) {
auto ss = ALLOC(SpriteSheet, {
.rectpack = rectpack_new(SS_WIDTH, SS_HEIGHT),
.tex = r_texture_create(&(TextureParams) {
.width = SS_WIDTH,
.height = SS_HEIGHT,
@ -394,6 +393,8 @@ static SpriteSheet* add_spritesheet(SpriteSheetAnchor *spritesheets) {
})
});
rectpack_init(&ss->rectpack, &default_allocator, SS_WIDTH, SS_HEIGHT);
#ifdef DEBUG
char buf[128];
snprintf(buf, sizeof(buf), "Fonts spritesheet %p", (void*)ss);
@ -412,7 +413,7 @@ static bool add_glyph_to_spritesheet(Glyph *glyph, Pixmap *pixmap, SpriteSheet *
uint padded_w = pixmap->width + 2 * GLYPH_SPRITE_PADDING;
uint padded_h = pixmap->height + 2 * GLYPH_SPRITE_PADDING;
glyph->spritesheet_section = rectpack_add(ss->rectpack, padded_w, padded_h);
glyph->spritesheet_section = rectpack_add(&ss->rectpack, padded_w, padded_h);
if(glyph->spritesheet_section == NULL) {
return false;
@ -479,7 +480,7 @@ static const char* pixmode_name(FT_Pixel_Mode mode) {
static void delete_spritesheet(SpriteSheetAnchor *spritesheets, SpriteSheet *ss) {
r_texture_destroy(ss->tex);
rectpack_free(ss->rectpack);
rectpack_deinit(&ss->rectpack);
alist_unlink(spritesheets, ss);
mem_free(ss);
}
@ -687,9 +688,7 @@ static void wipe_glyph_cache(Font *font) {
continue;
}
RectPack *rp = ss->rectpack;
assume(rp != NULL);
RectPack *rp = &ss->rectpack;
RectPackSection *section = g->spritesheet_section;
assume(section != NULL);

View file

@ -27,43 +27,45 @@
#define RP_DEBUG(...) ((void)0)
#endif
struct RectPackSection {
LIST_INTERFACE(RectPackSection);
Rect rect;
RectPackSection *parent;
RectPackSection *sibling;
RectPackSection *children[2];
};
struct RectPack {
RectPackSection root;
RectPackSection *freelist;
};
static inline bool section_is_free(RectPackSection *s) {
static inline bool section_is_unused(RectPackSection *s) {
return s->next != s;
}
static inline void section_make_used(RectPack *rp, RectPackSection *s) {
assert(section_is_free(s));
list_unlink(&rp->freelist, s);
assert(section_is_unused(s));
list_unlink(&rp->unused_sections, s);
s->next = s->prev = s;
}
RectPack* rectpack_new(double width, double height) {
auto rp = ALLOC(RectPack, {
static RectPackSection *acquire_section(RectPack *rp) {
RectPackSection *s = list_pop(&rp->sections_freelist);
if(!s) {
s = ALLOC_VIA(rp->allocator, typeof(*s));
}
*s = (RectPackSection) { };
return s;
}
static void release_section(RectPack *rp, RectPackSection *s) {
list_push(&rp->sections_freelist, s);
}
void rectpack_init(RectPack *rp, Allocator *alloc, double width, double height) {
*rp = (RectPack) {
.root.rect = {
.top_left = CMPLX(0, 0),
.bottom_right = CMPLX(width, height),
},
});
list_push(&rp->freelist, &rp->root);
.allocator = alloc,
};
list_push(&rp->unused_sections, &rp->root);
assert(rectpack_is_empty(rp));
return rp;
}
bool rectpack_is_empty(RectPack *rp) {
if(rp->freelist == &rp->root) {
if(rp->unused_sections == &rp->root) {
assert(rp->root.next == NULL);
return true;
}
@ -71,29 +73,33 @@ bool rectpack_is_empty(RectPack *rp) {
return false;
}
static void delete_subsections(RectPackSection *restrict s) {
static void delete_subsections(RectPack *rp, RectPackSection *restrict s) {
if(s->children[0] != NULL) {
assume(s->children[1] != NULL);
assume(s->children[0]->parent == s);
assume(s->children[1]->parent == s);
delete_subsections(s->children[0]);
mem_free(s->children[0]);
delete_subsections(rp, s->children[0]);
release_section(rp, s->children[0]);
s->children[0] = NULL;
delete_subsections(s->children[1]);
mem_free(s->children[1]);
delete_subsections(rp, s->children[1]);
release_section(rp, s->children[1]);
s->children[1] = NULL;
}
}
void rectpack_reset(RectPack *rp) {
delete_subsections(&rp->root);
delete_subsections(rp, &rp->root);
}
void rectpack_free(RectPack *rp) {
delete_subsections(&rp->root);
mem_free(rp);
void rectpack_deinit(RectPack *rp) {
delete_subsections(rp, &rp->root);
Allocator *alloc = rp->allocator;
for(RectPackSection *s; (s = list_pop(&rp->sections_freelist));) {
allocator_free(alloc, s);
}
}
static double section_fitness(RectPackSection *s, double w, double h) {
@ -119,23 +125,23 @@ void rectpack_reclaim(RectPack *rp, RectPackSection *s) {
RP_DEBUG("BEGIN RECLAIM %p[%gx%g]", (void*)s, w, h);
if(s->sibling && section_is_free(s->sibling)) {
if(s->sibling && section_is_unused(s->sibling)) {
RP_DEBUG("has free sibling; merging and reclaiming parent");
RectPackSection *parent = s->parent;
assume(parent != NULL);
assume(s->sibling->parent == parent);
assert(!section_is_free(s->parent));
assert(!section_is_free(s));
assert(!section_is_unused(s->parent));
assert(!section_is_unused(s));
list_unlink(&rp->freelist, s->sibling);
list_unlink(&rp->unused_sections, s->sibling);
// NOTE: the following frees s->sibling and s, in unspecified order
mem_free(parent->children[0]);
release_section(rp, parent->children[0]);
parent->children[0] = NULL;
mem_free(parent->children[1]);
release_section(rp, parent->children[1]);
parent->children[1] = NULL;
if(parent != NULL) {
@ -145,7 +151,7 @@ void rectpack_reclaim(RectPack *rp, RectPackSection *s) {
RP_DEBUG("done reclaiming parent of %p", (void*)s);
} else {
RP_DEBUG("added to free list");
list_push(&rp->freelist, s);
list_push(&rp->unused_sections, s);
assert(s != &rp->root || rectpack_is_empty(rp));
}
@ -158,7 +164,7 @@ static RectPackSection *select_fittest_section(RectPack *rp, double width, doubl
RP_DEBUG("trying to fit %gx%g...", width, height);
for(RectPackSection *s = rp->freelist; s; s = s->next) {
for(RectPackSection *s = rp->unused_sections; s; s = s->next) {
assume(s->children[0] == NULL);
assume(s->children[1] == NULL);
@ -199,7 +205,7 @@ static RectPackSection *split_horizontal(RectPack *rp, RectPackSection *s, doubl
return split_vertical(rp, s, width, height);
}
auto sub = ALLOC(RectPackSection);
auto sub = acquire_section(rp);
rect_set_xywh(&sub->rect,
rect_x(s->rect),
rect_y(s->rect),
@ -211,8 +217,7 @@ static RectPackSection *split_horizontal(RectPack *rp, RectPackSection *s, doubl
sub->parent = s;
s->children[0] = sub;
s->children[1] = ALLOC(typeof(*s->children[1]));
s->children[1] = acquire_section(rp);
rect_set_xywh(&s->children[1]->rect,
rect_x(s->rect),
rect_y(s->rect) + height,
@ -222,7 +227,7 @@ static RectPackSection *split_horizontal(RectPack *rp, RectPackSection *s, doubl
s->children[1]->parent = s;
sub->sibling = s->children[1];
s->children[1]->sibling = sub;
list_push(&rp->freelist, s->children[1]);
list_push(&rp->unused_sections, s->children[1]);
RP_DEBUG("made new subsections from %p: %p[%gx%g]; %p[%gx%g]",
(void*)s,
@ -249,7 +254,7 @@ static RectPackSection *split_vertical(RectPack *rp, RectPackSection *s, double
return split_horizontal(rp, s, width, height);
}
auto sub = ALLOC(RectPackSection);
auto sub = acquire_section(rp);
rect_set_xywh(&sub->rect,
rect_x(s->rect),
rect_y(s->rect),
@ -261,8 +266,7 @@ static RectPackSection *split_vertical(RectPack *rp, RectPackSection *s, double
sub->parent = s;
s->children[0] = sub;
s->children[1] = ALLOC(typeof(*s->children[1]));
s->children[1] = acquire_section(rp);
rect_set_xywh(&s->children[1]->rect,
rect_x(s->rect) + width,
rect_y(s->rect),
@ -272,7 +276,7 @@ static RectPackSection *split_vertical(RectPack *rp, RectPackSection *s, double
s->children[1]->parent = s;
sub->sibling = s->children[1];
s->children[1]->sibling = sub;
list_push(&rp->freelist, s->children[1]);
list_push(&rp->unused_sections, s->children[1]);
RP_DEBUG("made new subsections from %p: %p[%gx%g]; %p[%gx%g]",
(void*)s,

View file

@ -10,17 +10,34 @@
#include "taisei.h"
#include "geometry.h"
#include "list.h"
#include "memory/allocator.h"
typedef struct RectPack RectPack;
typedef struct RectPackSection RectPackSection;
RectPack *rectpack_new(double width, double height)
attr_returns_max_aligned attr_returns_nonnull attr_nodiscard;
struct RectPackSection {
LIST_INTERFACE(RectPackSection);
Rect rect;
RectPackSection *parent;
RectPackSection *sibling;
RectPackSection *children[2];
};
struct RectPack {
RectPackSection root;
RectPackSection *unused_sections;
RectPackSection *sections_freelist;
Allocator *allocator;
};
void rectpack_init(RectPack *rp, Allocator *alloc, double width, double height)
attr_nonnull_all;
void rectpack_reset(RectPack *rp)
attr_nonnull(1);
void rectpack_free(RectPack *rp)
void rectpack_deinit(RectPack *rp)
attr_nonnull(1);
RectPackSection *rectpack_add(RectPack *rp, double width, double height)