quakeforge/libs/ui/vrect.c
Bill Currie 058e919273 [renderer] Use better heuristics for scrap allocation
More tuning is needed on the actual splits as it falls over when the
lower rect gets too low for the subrects being allocated. However, the
scrap allocator itself will prefer exact width/height fits with larger
cutoff over inexact cuts with smaller cutoff. Many thanks to tdb for the
suggestions.

Fixes the fps dropping from ~3700fps down to ~450fps (cumulative due to
loss of POT rounding and very poor splitting layout), with a bonus boost
to about 4900fps (all speeds at 800x450). The 2d sprites were mostly ok,
but the lightmaps forming a capital gamma shape in a 4k texture really
hurt. Now the lightmaps are a nice dense bar at the top of the texture,
and 2d sprites are pretty good (slight improvement coming next).
2022-09-20 19:32:49 +09:00

321 lines
8.3 KiB
C

/*
vrect.c
Rectangle manipulation
Copyright (C) 2012 Bill Currie <bill@taniwha.org>
Author: Bill Currie <bill@taniwha.org>
Date: 2012/1/6
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to:
Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307, USA
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <stdlib.h>
#include "QF/mathlib.h"
#include "QF/ui/vrect.h"
//#define TEST_MEMORY
#define RECT_BLOCK 128
#ifndef TEST_MEMORY
static vrect_t *free_rects;
#endif
VISIBLE vrect_t *
VRect_New (int x, int y, int width, int height)
{
vrect_t *r;
#ifndef TEST_MEMORY
if (!free_rects) {
int i;
free_rects = malloc (RECT_BLOCK * sizeof (vrect_t));
for (i = 0; i < RECT_BLOCK - 1; i++)
free_rects[i].next = &free_rects[i + 1];
free_rects[i].next = 0;
}
r = free_rects;
free_rects = free_rects->next;
#else
r = malloc (sizeof (vrect_t));
#endif
r->next = 0;
r->x = x;
r->y = y;
r->width = width;
r->height = height;
return r;
}
VISIBLE void
VRect_Delete (vrect_t *rect)
{
#ifndef TEST_MEMORY
rect->next = free_rects;
free_rects = rect;
#else
free (rect);
#endif
}
VISIBLE vrect_t *
VRect_Intersect (const vrect_t *r1, const vrect_t *r2)
{
int minx = max (VRect_MinX (r1), VRect_MinX (r2));
int miny = max (VRect_MinY (r1), VRect_MinY (r2));
int maxx = min (VRect_MaxX (r1), VRect_MaxX (r2));
int maxy = min (VRect_MaxY (r1), VRect_MaxY (r2));
return VRect_New (minx, miny, maxx - minx, maxy - miny);
}
VISIBLE vrect_t *
VRect_HSplit (const vrect_t *r, int y)
{
vrect_t *r1, *r2;
int r1miny = VRect_MinY (r);
int r1maxy = min (VRect_MaxY (r), y);
int r2miny = max (VRect_MinY (r), y);
int r2maxy = VRect_MaxY (r);
r1 = VRect_New (VRect_MinX (r), r1miny, r->width, r1maxy - r1miny);
r2 = VRect_New (VRect_MinX (r), r2miny, r->width, r2maxy - r2miny);
r1->next = r2;
return r1;
}
VISIBLE vrect_t *
VRect_VSplit (const vrect_t *r, int x)
{
vrect_t *r1, *r2;
int r1minx = VRect_MinX (r);
int r1maxx = min (VRect_MaxX (r), x);
int r2minx = max (VRect_MinX (r), x);
int r2maxx = VRect_MaxX (r);
r1 = VRect_New (r1minx, VRect_MinY (r), r1maxx - r1minx, r->height);
r2 = VRect_New (r2minx, VRect_MinY (r), r2maxx - r2minx, r->height);
r1->next = r2;
return r1;
}
VISIBLE vrect_t *
VRect_Difference (const vrect_t *r, const vrect_t *s)
{
vrect_t *i, *t, *_r;
vrect_t *rects = 0;
#define STASH(t) \
do { \
if (!VRect_IsEmpty (t)) { \
t->next = rects; \
rects = t; \
} else { \
VRect_Delete (t); \
} \
} while (0)
// Find the intersection of r (original rect) and s (the rect being
// subtracted from r). The intersection rect is used to carve the original
// rect. If there is no intersection (the intersection rect is empty),
// then there is nothing to subtract and the original rect (actually, a
// copy of it) is returned.
i = VRect_Intersect (r, s);
if (VRect_IsEmpty (i)) {
// copy r's shape to i, and return i.
i->x = r->x;
i->y = r->y;
i->width = r->width;
i->height = r->height;
return i;
}
// Split r along the top of the intersection rect and stash the top
// section if it is not empty.
t = VRect_HSplit (r, VRect_MinY (i));
// can't delete r: we don't own it
_r = t->next;
STASH (t); // maybe stash the top section
// r now represents the portion of the original rect below the top of
// the intersection rect.
// Split r along the bottom of the intersection rect and stash the bottom
// section if it is not empty.
t = VRect_HSplit (_r, VRect_MaxY (i));
VRect_Delete (_r); // finished with that copy of _r
_r = t;
STASH (t->next); // maybe stash the bottom section
// r now represents the horizontal section that is common with the
// intersection rect.
// Split r along the left side of tht intersection rect and stash the
// left section if it is not empty.
t = VRect_VSplit (_r, VRect_MinX (i));
VRect_Delete (_r); // finished with that copy of _r
_r = t->next;
STASH (t); // maybe stash the left section
// r now represets the section of the original rect that is between the
// top and bottom, and to the right of the left side of the intersection
// rect.
// Split r along the right side of the intersection rect and stash the
// right section if it is not empty. The left section is discarded.
t = VRect_VSplit (_r, VRect_MaxX (i));
VRect_Delete (_r); // finished with that copy of _r
STASH (t->next); // maybe stash the right section
VRect_Delete (t); // discard the left section
VRect_Delete (i); // finished with the intersection rect
return rects;
}
VISIBLE vrect_t *
VRect_Union (const vrect_t *r1, const vrect_t *r2)
{
int minx, miny;
int maxx, maxy;
if (VRect_IsEmpty (r1))
return VRect_New (r2->x, r2->y, r2->width, r2->height);
if (VRect_IsEmpty (r2))
return VRect_New (r1->x, r1->y, r1->width, r1->height);
minx = min (VRect_MinX (r1), VRect_MinX (r2));
miny = min (VRect_MinY (r1), VRect_MinY (r2));
maxx = max (VRect_MaxX (r1), VRect_MaxX (r2));
maxy = max (VRect_MaxY (r1), VRect_MaxY (r2));
return VRect_New (minx, miny, maxx - minx, maxy - miny);
}
VISIBLE vrect_t *
VRect_Merge (const vrect_t *r1, const vrect_t *r2)
{
vrect_t *t, *u = 0;
vrect_t *merge;
// cannot merge intersecting rectangles
t = VRect_Intersect (r1, r2);
if (!VRect_IsEmpty (t)) {
VRect_Delete (t);
return 0;
}
VRect_Delete (t);
merge = VRect_Union (r1, r2);
if (VRect_IsEmpty (merge)) {
VRect_Delete (merge);
return 0;
}
// If the subtracting r1 from the union results in more than one rectangle
// then r1 and r2 cannot be merged.
t = VRect_Difference (merge, r1);
if (t && t->next)
goto cleanup;
// If subtracting r2 from the result of the previous difference results in
// any rectangles, then r1 and r2 cannot be merged.
if (t)
u = VRect_Difference (t, r2);
if (!u) {
if (t)
VRect_Delete (t);
return merge;
}
cleanup:
VRect_Delete (merge);
// merge is used as a temp while freeing t and u
while (u) {
merge = u->next;
VRect_Delete (u);
u = merge;
}
while (t) {
merge = t->next;
VRect_Delete (t);
t = merge;
}
return 0;
}
VISIBLE vrect_t *
VRect_SubRect (const vrect_t *rect, int width, int height)
{
if (width > rect->width) {
width = rect->width;
}
if (height > rect->height) {
height = rect->height;
}
// First check for exact fits as no heuristics are necessary
if (width == rect->width) {
if (height == rect->height) {
// Exact fit, return a new rect of the same size
return VRect_New (rect->x, rect->y, rect->width, rect->height);
}
vrect_t *r = VRect_HSplit (rect, rect->y + height);
if (VRect_IsEmpty (r->next)) {
VRect_Delete (r->next);
r->next = 0;
}
return r;
} else if (height == rect->height) {
// Because rect's width ks known to be greater than the requested
// width, the split is guaranteed to produce two non-empty rectangles.
return VRect_VSplit (rect, rect->x + width);
}
int a = rect->height - height;
int b = rect->width - width;
vrect_t *r, *s;
if (b * height <= a * width || 16 * a > 15 * rect->height) {
// horizontal cuts produce better memory locality so favor them
r = VRect_HSplit (rect, rect->y + height);
if (VRect_IsEmpty (r->next)) {
VRect_Delete (r->next);
r->next = 0;
}
s = VRect_VSplit (r, r->x + width);
} else {
// a horizontal cut produces a larger off-cut rectangle than a vertical
// cut, so do a vertical cut first so the off-cut is as small as
// possible
r = VRect_VSplit (rect, rect->x + width);
if (VRect_IsEmpty (r->next)) {
VRect_Delete (r->next);
r->next = 0;
}
s = VRect_HSplit (r, r->y + height);
}
if (VRect_IsEmpty (s->next)) {
VRect_Delete (s->next);
s->next = r->next;
} else {
s->next->next = r->next;
}
VRect_Delete (r);
return s;
}