From 2ebfa8d780f0fb1140417d4b2a5aed744985eb68 Mon Sep 17 00:00:00 2001 From: Eric Wasylishen Date: Wed, 29 Jan 2020 01:22:29 -0700 Subject: [PATCH] qbsp: parallelize solidbsp --- include/qbsp/qbsp.hh | 3 + qbsp/solidbsp.cc | 266 +++++++++++++++++++++++-------------------- 2 files changed, 145 insertions(+), 124 deletions(-) diff --git a/include/qbsp/qbsp.hh b/include/qbsp/qbsp.hh index eff7c070..4e032f2a 100644 --- a/include/qbsp/qbsp.hh +++ b/include/qbsp/qbsp.hh @@ -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; diff --git a/qbsp/solidbsp.cc b/qbsp/solidbsp.cc index 269a6112..bbb98e33 100644 --- a/qbsp/solidbsp.cc +++ b/qbsp/solidbsp.cc @@ -20,9 +20,13 @@ */ #include +#include #include +#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& 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& 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& 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& front, + std::vector& 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& 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 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 frontlist; + std::vector 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 frontfrag; + std::vector 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 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);