qbsp: parallelize solidbsp

This commit is contained in:
Eric Wasylishen 2020-01-29 01:22:29 -07:00
parent 1f565d7e7a
commit 2ebfa8d780
2 changed files with 145 additions and 124 deletions

View File

@ -278,6 +278,9 @@ typedef struct surface_s {
bool has_detail; // 1 if the surface has detail brushes
bool has_struct; // 1 if the surface has non-detail brushes
short lmshift;
vec_t metric; // temporary storage used during solidbsp
int splits; // temporary storage used during solidbsp
} surface_t;

View File

@ -20,9 +20,13 @@
*/
#include <limits.h>
#include <algorithm>
#include <qbsp/qbsp.hh>
#include "tbb/parallel_for_each.h"
#include "tbb/task_group.h"
int splitnodes;
static int leaffaces;
@ -232,7 +236,7 @@ DivideBounds(const vec3_t mins, const vec3_t maxs, const qbsp_plane_t *split,
* Calculate the split plane metric for axial planes
*/
static vec_t
SplitPlaneMetric_Axial(const qbsp_plane_t *p, vec3_t mins, vec3_t maxs)
SplitPlaneMetric_Axial(const qbsp_plane_t *p, const vec3_t mins, const vec3_t maxs)
{
vec_t value, dist;
int i;
@ -255,7 +259,7 @@ SplitPlaneMetric_Axial(const qbsp_plane_t *p, vec3_t mins, vec3_t maxs)
* Calculate the split plane metric for non-axial planes
*/
static vec_t
SplitPlaneMetric_NonAxial(const qbsp_plane_t *p, vec3_t mins, vec3_t maxs)
SplitPlaneMetric_NonAxial(const qbsp_plane_t *p, const vec3_t mins, const vec3_t maxs)
{
vec3_t fmins, fmaxs, bmins, bmaxs;
vec_t value = 0.0;
@ -271,7 +275,7 @@ SplitPlaneMetric_NonAxial(const qbsp_plane_t *p, vec3_t mins, vec3_t maxs)
}
static inline vec_t
SplitPlaneMetric(const qbsp_plane_t *p, vec3_t mins, vec3_t maxs)
SplitPlaneMetric(const qbsp_plane_t *p, const vec3_t mins, const vec3_t maxs)
{
vec_t value;
@ -291,43 +295,50 @@ The clipping hull BSP doesn't worry about avoiding splits
==================
*/
static surface_t *
ChooseMidPlaneFromList(surface_t *surfaces, vec3_t mins, vec3_t maxs)
ChooseMidPlaneFromList(const std::vector<surface_t*>& surfaces, const vec3_t mins, const vec3_t maxs)
{
surface_t *surf, *bestsurface;
vec_t metric, bestmetric;
qbsp_plane_t *plane;
int pass;
// compute the metrics in parallel
tbb::parallel_for_each(surfaces, [mins, maxs](surface_t* surf){
surf->metric = VECT_MAX;
if (surf->onnode)
return;
qbsp_plane_t *plane = &map.planes[surf->planenum];
/* calculate the split metric, smaller values are better */
surf->metric = SplitPlaneMetric(plane, mins, maxs);
});
/* pick the plane that splits the least */
bestmetric = VECT_MAX;
bestsurface = NULL;
vec_t bestmetric = VECT_MAX;
surface_t *bestsurface = NULL;
for (pass = 0; pass < 2; pass++) {
for (surf = surfaces; surf; surf = surf->next) {
for (int pass = 0; pass < 2; pass++) {
for (surface_t *surf : surfaces) {
if (surf->onnode)
continue;
if( surf->has_struct && pass )
continue;
if( !surf->has_struct && !pass )
continue;
/* check for axis aligned surfaces */
plane = &map.planes[surf->planenum];
qbsp_plane_t *plane = &map.planes[surf->planenum];
if (!(plane->type < 3))
continue;
/* calculate the split metric, smaller values are better */
metric = SplitPlaneMetric(plane, mins, maxs);
if (metric < bestmetric) {
bestmetric = metric;
if (surf->metric < bestmetric) {
bestmetric = surf->metric;
bestsurface = surf;
}
}
if (!bestsurface) {
/* Choose based on spatial subdivision only */
for (surf = surfaces; surf; surf = surf->next) {
for (surface_t *surf : surfaces) {
if (surf->onnode)
continue;
@ -335,11 +346,9 @@ ChooseMidPlaneFromList(surface_t *surfaces, vec3_t mins, vec3_t maxs)
continue;
if( !surf->has_struct && !pass )
continue;
plane = &map.planes[surf->planenum];
metric = SplitPlaneMetric(plane, mins, maxs);
if (metric < bestmetric) {
bestmetric = metric;
if (surf->metric < bestmetric) {
bestmetric = surf->metric;
bestsurface = surf;
}
}
@ -375,86 +384,84 @@ The real BSP hueristic
==================
*/
static surface_t *
ChoosePlaneFromList(surface_t *surfaces, vec3_t mins, vec3_t maxs)
ChoosePlaneFromList(const std::vector<surface_t*>& surfaces, const vec3_t mins, const vec3_t maxs)
{
int pass, splits, minsplits;
bool hintsplit;
surface_t *surf, *surf2, *bestsurface;
vec_t distribution, bestdistribution;
const qbsp_plane_t *plane, *plane2;
const face_t *face;
// compute the splits in parallel
tbb::parallel_for_each(surfaces, [surfaces, mins, maxs](surface_t* surf){
surf->splits = INT_MAX;
surf->metric = VECT_MAX;
if (surf->onnode)
return;
/*
* Check that the surface has a suitable face for the current pass
* and check whether this is a hint split.
*/
bool hintsplit = false;
for (const face_t *face = surf->faces; face; face = face->next) {
if (map.mtexinfos.at(face->texinfo).flags & TEX_HINT)
hintsplit = true;
}
const qbsp_plane_t *plane = &map.planes[surf->planenum];
int splits = 0;
for (surface_t* surf2 : surfaces) {
if (surf2 == surf || surf2->onnode)
continue;
const qbsp_plane_t *plane2 = &map.planes[surf2->planenum];
if (plane->type < 3 && plane->type == plane2->type)
continue;
for (const face_t *face = surf2->faces; face; face = face->next) {
const uint64_t flags = map.mtexinfos.at(face->texinfo).flags;
/* Don't penalize for splitting skip faces */
if (flags & TEX_SKIP)
continue;
if (FaceSide(face, plane) == SIDE_ON) {
/* Never split a hint face except with a hint */
if (!hintsplit && (flags & TEX_HINT)) {
splits = INT_MAX;
break;
}
splits++;
}
}
}
surf->splits = splits;
surf->metric = SplitPlaneMetric(plane, mins, maxs);
});
/* pick the plane that splits the least */
minsplits = INT_MAX - 1;
bestdistribution = VECT_MAX;
bestsurface = NULL;
int minsplits = INT_MAX - 1;
vec_t bestdistribution = VECT_MAX;
surface_t* bestsurface = NULL;
/* Two passes - exhaust all non-detail faces before details */
for (pass = 0; pass < 2; pass++) {
for (surf = surfaces; surf; surf = surf->next) {
for (int pass = 0; pass < 2; pass++) {
for (surface_t* surf : surfaces) {
if (surf->onnode)
continue;
/*
* Check that the surface has a suitable face for the current pass
* and check whether this is a hint split.
*/
hintsplit = false;
for (face = surf->faces; face; face = face->next) {
if (map.mtexinfos.at(face->texinfo).flags & TEX_HINT)
hintsplit = true;
}
if( surf->has_struct && pass )
continue;
if( !surf->has_struct && !pass )
continue;
plane = &map.planes[surf->planenum];
splits = 0;
for (surf2 = surfaces; surf2; surf2 = surf2->next) {
if (surf2 == surf || surf2->onnode)
continue;
plane2 = &map.planes[surf2->planenum];
if (plane->type < 3 && plane->type == plane2->type)
continue;
for (face = surf2->faces; face; face = face->next) {
const uint64_t flags = map.mtexinfos.at(face->texinfo).flags;
/* Don't penalize for splitting skip faces */
if (flags & TEX_SKIP)
continue;
if (FaceSide(face, plane) == SIDE_ON) {
/* Never split a hint face except with a hint */
if (!hintsplit && (flags & TEX_HINT)) {
splits = INT_MAX;
break;
}
splits++;
if (splits >= minsplits)
break;
}
}
if (splits > minsplits)
break;
}
if (splits > minsplits)
continue;
const qbsp_plane_t *plane = &map.planes[surf->planenum];
/*
* if equal numbers axial planes win, otherwise decide on spatial
* subdivision
*/
if (splits < minsplits || (splits == minsplits && plane->type < 3)) {
if (surf->splits < minsplits || (surf->splits == minsplits && plane->type < 3)) {
if (plane->type < 3) {
distribution = SplitPlaneMetric(plane, mins, maxs);
if (distribution > bestdistribution && splits == minsplits)
if (surf->metric > bestdistribution && surf->splits == minsplits)
continue;
bestdistribution = distribution;
bestdistribution = surf->metric;
}
/* currently the best! */
minsplits = splits;
minsplits = surf->splits;
bestsurface = surf;
}
}
@ -479,16 +486,16 @@ returns NULL if the surface list can not be divided any more (a leaf)
==================
*/
static surface_t *
SelectPartition(surface_t *surfaces)
SelectPartition(const std::vector<surface_t*>& surfaces)
{
int i, surfcount;
vec3_t mins, maxs;
surface_t *surf, *bestsurface;
surface_t *bestsurface;
// count onnode surfaces
surfcount = 0;
bestsurface = NULL;
for (surf = surfaces; surf; surf = surf->next)
for (surface_t *surf : surfaces)
if (!surf->onnode) {
surfcount++;
bestsurface = surf;
@ -505,7 +512,7 @@ SelectPartition(surface_t *surfaces)
mins[i] = VECT_MAX;
maxs[i] = -VECT_MAX;
}
for (surf = surfaces; surf; surf = surf->next)
for (surface_t *surf : surfaces)
for (i = 0; i < 3; i++) {
if (surf->mins[i] < mins[i])
mins[i] = surf->mins[i];
@ -610,8 +617,9 @@ DividePlane
==================
*/
static void
DividePlane(surface_t *in, qbsp_plane_t *split, surface_t **front,
surface_t **back)
DividePlane(surface_t *in, qbsp_plane_t *split,
std::vector<surface_t *>& front,
std::vector<surface_t *>& back)
{
face_t *facet, *next;
face_t *frontlist, *backlist;
@ -620,7 +628,8 @@ DividePlane(surface_t *in, qbsp_plane_t *split, surface_t **front,
qbsp_plane_t *inplane;
inplane = &map.planes[in->planenum];
*front = *back = NULL;
assert(front.empty());
assert(back.empty());
// parallel case is easy
if (VectorCompare(inplane->normal, split->normal, EQUAL_EPSILON)) {
@ -652,12 +661,12 @@ DividePlane(surface_t *in, qbsp_plane_t *split, surface_t **front,
CalcSurfaceInfo(in);
if (in->faces)
*front = in;
front.push_back(in);
else
FreeMem(in, SURFACE, 1);
if (newsurf->faces)
*back = newsurf;
back.push_back(newsurf);
else
FreeMem(newsurf, SURFACE, 1);
@ -665,9 +674,9 @@ DividePlane(surface_t *in, qbsp_plane_t *split, surface_t **front,
}
if (inplane->dist > split->dist)
*front = in;
front.push_back(in);
else
*back = in;
back.push_back(in);
return;
}
// do a real split. may still end up entirely on one side
@ -690,13 +699,13 @@ DividePlane(surface_t *in, qbsp_plane_t *split, surface_t **front,
// if nothing actually got split, just move the in plane
if (frontlist == NULL) {
*back = in;
back.push_back(in);
in->faces = backlist;
return;
}
if (backlist == NULL) {
*front = in;
front.push_back(in);
in->faces = frontlist;
return;
}
@ -705,10 +714,10 @@ DividePlane(surface_t *in, qbsp_plane_t *split, surface_t **front,
newsurf = (surface_t *)AllocMem(SURFACE, 1, true);
*newsurf = *in;
newsurf->faces = backlist;
*back = newsurf;
back.push_back(newsurf);
in->faces = frontlist;
*front = in;
front.push_back(in);
// recalc bboxes and flags
CalcSurfaceInfo(newsurf);
@ -807,10 +816,9 @@ original faces that have some fragment inside this leaf
==================
*/
static void
LinkConvexFaces(surface_t *planelist, node_t *leafnode)
LinkConvexFaces(const std::vector<surface_t*>& planelist, node_t *leafnode)
{
face_t *f, *next;
surface_t *surf, *pnext;
int i, count;
leafnode->faces = NULL;
@ -818,7 +826,7 @@ LinkConvexFaces(surface_t *planelist, node_t *leafnode)
leafnode->planenum = PLANENUM_LEAF;
count = 0;
for (surf = planelist; surf; surf = surf->next) {
for (surface_t *surf : planelist) {
for (f = surf->faces; f; f = f->next) {
count++;
@ -880,8 +888,7 @@ LinkConvexFaces(surface_t *planelist, node_t *leafnode)
leafnode->markfaces = (face_t **)AllocMem(OTHER, sizeof(face_t *) * (count + 1), true);
i = 0;
for (surf = planelist; surf; surf = pnext) {
pnext = surf->next;
for (surface_t *surf : planelist) {
for (f = surf->faces; f; f = next) {
next = f->next;
leafnode->markfaces[i] = f->original;
@ -940,14 +947,13 @@ PartitionSurfaces
==================
*/
static void
PartitionSurfaces(surface_t *surfaces, node_t *node)
PartitionSurfaces(std::vector<surface_t*> surfaces, node_t *node)
{
surface_t *split, *surf, *next;
surface_t *frontlist, *backlist;
surface_t *frontfrag, *backfrag;
// split, *next;
// surface_t *frontfrag, *backfrag;
qbsp_plane_t *splitplane;
split = SelectPartition(surfaces);
surface_t *split = SelectPartition(surfaces);
if (!split) { // this is a leaf node
node->planenum = PLANENUM_LEAF;
@ -971,35 +977,46 @@ PartitionSurfaces(surface_t *surfaces, node_t *node)
DivideNodeBounds(node, splitplane);
// multiple surfaces, so split all the polysurfaces into front and back lists
frontlist = NULL;
backlist = NULL;
std::vector<surface_t*> frontlist;
std::vector<surface_t*> backlist;
for (surf = surfaces; surf; surf = next) {
next = surf->next;
DividePlane(surf, splitplane, &frontfrag, &backfrag);
if (frontfrag && backfrag) {
for (surface_t *surf : surfaces) {
// FIXME: these only ever hold 0 or 1 surface each
std::vector<surface_t*> frontfrag;
std::vector<surface_t*> backfrag;
DividePlane(surf, splitplane, frontfrag, backfrag);
if (!frontfrag.empty() && !backfrag.empty()) {
// the plane was split, which may expose oportunities to merge
// adjacent faces into a single face
// MergePlaneFaces (frontfrag);
// MergePlaneFaces (backfrag);
}
if (frontfrag) {
if (!frontfrag->faces)
if (!frontfrag.empty()) {
if (!frontfrag[0]->faces)
Error("Surface with no faces (%s)", __func__);
frontfrag->next = frontlist;
frontlist = frontfrag;
frontlist.push_back(frontfrag[0]);
}
if (backfrag) {
if (!backfrag->faces)
if (!backfrag.empty()) {
if (!backfrag[0]->faces)
Error("Surface with no faces (%s)", __func__);
backfrag->next = backlist;
backlist = backfrag;
backlist.push_back(backfrag[0]);
}
}
PartitionSurfaces(frontlist, node->children[0]);
PartitionSurfaces(backlist, node->children[1]);
// Hack to match order of code before moving to std::vector
std::reverse(frontlist.begin(), frontlist.end());
std::reverse(backlist.begin(), backlist.end());
// free memory
surfaces.clear();
surfaces.shrink_to_fit();
tbb::task_group g;
g.run([&](){ PartitionSurfaces(std::move(frontlist), node->children[0]); });
g.run([&](){ PartitionSurfaces(std::move(backlist), node->children[1]); });
g.wait();
}
@ -1062,11 +1079,12 @@ SolidBSP(const mapentity_t *entity, surface_t *surfhead, bool midsplit)
c_illusionary_visblocker = 0;
// count map surfaces; this is used when deciding to switch between midsplit and the expensive partitioning
mapsurfaces = 0;
std::vector<surface_t*> surfaces_vec;
for (surface_t *surf = surfhead; surf; surf = surf->next) {
mapsurfaces++;
surfaces_vec.push_back(surf);
}
PartitionSurfaces(surfhead, headnode);
PartitionSurfaces(std::move(surfaces_vec), headnode);
Message(msgStat, "%8d split nodes", splitnodes);
Message(msgStat, "%8d solid leafs", c_solid);