// Copyright 2009-2021 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include "../bvh/bvh.h"
#include "../geometry/primitive.h"
#include "../builders/bvh_builder_msmblur.h"
#include "../builders/heuristic_binning_array_aligned.h"
#include "../builders/heuristic_binning_array_unaligned.h"
#include "../builders/heuristic_timesplit_array.h"
namespace embree
{
namespace isa
{
struct BVHBuilderHairMSMBlur
{
/*! settings for msmblur builder */
struct Settings
{
/*! default settings */
Settings ()
: branchingFactor(2), maxDepth(32), logBlockSize(0), minLeafSize(1), maxLeafSize(8) {}
public:
size_t branchingFactor; //!< branching factor of BVH to build
size_t maxDepth; //!< maximum depth of BVH to build
size_t logBlockSize; //!< log2 of blocksize for SAH heuristic
size_t minLeafSize; //!< minimum size of a leaf
size_t maxLeafSize; //!< maximum size of a leaf
};
struct BuildRecord
{
public:
__forceinline BuildRecord () {}
__forceinline BuildRecord (size_t depth)
: depth(depth) {}
__forceinline BuildRecord (const SetMB& prims, size_t depth)
: depth(depth), prims(prims) {}
__forceinline size_t size() const {
return prims.size();
}
public:
size_t depth; //!< depth of the root of this subtree
SetMB prims; //!< the list of primitives
};
template<typename NodeRef,
typename RecalculatePrimRef,
typename CreateAllocFunc,
typename CreateAABBNodeMBFunc,
typename SetAABBNodeMBFunc,
typename CreateOBBNodeMBFunc,
typename SetOBBNodeMBFunc,
typename CreateLeafFunc,
typename ProgressMonitor>
class BuilderT
{
ALIGNED_CLASS_(16);
static const size_t MAX_BRANCHING_FACTOR = 8; //!< maximum supported BVH branching factor
static const size_t MIN_LARGE_LEAF_LEVELS = 8; //!< create balanced tree if we are that many levels before the maximum tree depth
static const size_t SINGLE_THREADED_THRESHOLD = 4096; //!< threshold to switch to single threaded build
typedef BVHNodeRecordMB<NodeRef> NodeRecordMB;
typedef BVHNodeRecordMB4D<NodeRef> NodeRecordMB4D;
typedef FastAllocator::CachedAllocator Allocator;
typedef LocalChildListT<BuildRecord,MAX_BRANCHING_FACTOR> LocalChildList;
typedef HeuristicMBlurTemporalSplit<PrimRefMB,RecalculatePrimRef,MBLUR_NUM_TEMPORAL_BINS> HeuristicTemporal;
typedef HeuristicArrayBinningMB<PrimRefMB,MBLUR_NUM_OBJECT_BINS> HeuristicBinning;
typedef UnalignedHeuristicArrayBinningMB<PrimRefMB,MBLUR_NUM_OBJECT_BINS> UnalignedHeuristicBinning;
public:
BuilderT (Scene* scene,
const RecalculatePrimRef& recalculatePrimRef,
const CreateAllocFunc& createAlloc,
const CreateAABBNodeMBFunc& createAABBNodeMB,
const SetAABBNodeMBFunc& setAABBNodeMB,
const CreateOBBNodeMBFunc& createOBBNodeMB,
const SetOBBNodeMBFunc& setOBBNodeMB,
const CreateLeafFunc& createLeaf,
const ProgressMonitor& progressMonitor,
const Settings settings)
: cfg(settings),
scene(scene),
recalculatePrimRef(recalculatePrimRef),
createAlloc(createAlloc),
createAABBNodeMB(createAABBNodeMB), setAABBNodeMB(setAABBNodeMB),
createOBBNodeMB(createOBBNodeMB), setOBBNodeMB(setOBBNodeMB),
createLeaf(createLeaf),
progressMonitor(progressMonitor),
unalignedHeuristic(scene),
temporalSplitHeuristic(scene->device,recalculatePrimRef) {}
private:
/*! checks if all primitives are from the same geometry */
__forceinline bool sameGeometry(const SetMB& set)
{
mvector<PrimRefMB>& prims = *set.prims;
unsigned int firstGeomID = prims[set.begin()].geomID();
for (size_t i=set.begin()+1; i<set.end(); i++) {
if (prims[i].geomID() != firstGeomID){
return false;
}
}
return true;
}
/*! performs some split if SAH approaches fail */
void splitFallback(const SetMB& set, SetMB& lset, SetMB& rset)
{
mvector<PrimRefMB>& prims = *set.prims;
const size_t begin = set.begin();
const size_t end = set.end();
const size_t center = (begin + end)/2;
PrimInfoMB linfo = empty;
for (size_t i=begin; i<center; i++)
linfo.add_primref(prims[i]);
PrimInfoMB rinfo = empty;
for (size_t i=center; i<end; i++)
rinfo.add_primref(prims[i]);
new (&lset) SetMB(linfo,set.prims,range<size_t>(begin,center),set.time_range);
new (&rset) SetMB(rinfo,set.prims,range<size_t>(center,end ),set.time_range);
}
void splitByGeometry(const SetMB& set, SetMB& lset, SetMB& rset)
{
assert(set.size() > 1);
const size_t begin = set.begin();
const size_t end = set.end();
PrimInfoMB linfo(empty);
PrimInfoMB rinfo(empty);
unsigned int geomID = (*set.prims)[begin].geomID();
size_t center = serial_partitioning(set.prims->data(),begin,end,linfo,rinfo,
[&] ( const PrimRefMB& prim ) { return prim.geomID() == geomID; },
[ ] ( PrimInfoMB& a, const PrimRefMB& ref ) { a.add_primref(ref); });
new (&lset) SetMB(linfo,set.prims,range<size_t>(begin,center),set.time_range);
new (&rset) SetMB(rinfo,set.prims,range<size_t>(center,end ),set.time_range);
}
/*! creates a large leaf that could be larger than supported by the BVH */
NodeRecordMB4D createLargeLeaf(BuildRecord& current, Allocator alloc)
{
/* this should never occur but is a fatal error */
if (current.depth > cfg.maxDepth)
throw_RTCError(RTC_ERROR_UNKNOWN,"depth limit reached");
/* special case when directly creating leaf without any splits that could shrink time_range */
bool force_split = false;
if (current.depth == 1 && current.size() > 0)
{
BBox1f c = empty;
BBox1f p = current.prims.time_range;
for (size_t i=current.prims.begin(); i<current.prims.end(); i++) {
mvector<PrimRefMB>& prims = *current.prims.prims;
c.extend(prims[i].time_range);
}
force_split = c.lower > p.lower || c.upper < p.upper;
}
/* create leaf for few primitives */
if (current.size() <= cfg.maxLeafSize && sameGeometry(current.prims) && !force_split)
return createLeaf(current.prims,alloc);
/* fill all children by always splitting the largest one */
LocalChildList children(current);
NodeRecordMB4D values[MAX_BRANCHING_FACTOR];
do {
/* find best child with largest bounding box area */
int bestChild = -1;
size_t bestSize = 0;
for (unsigned i=0; i<children.size(); i++)
{
/* ignore leaves as they cannot get split */
if (children[i].size() <= cfg.maxLeafSize && sameGeometry(children[i].prims) && !force_split)
continue;
force_split = false;
/* remember child with largest size */
if (children[i].size() > bestSize) {
bestSize = children[i].size();
bestChild = i;
}
}
if (bestChild == -1) break;
/*! split best child into left and right child */
BuildRecord left(current.depth+1);
BuildRecord right(current.depth+1);
if (!sameGeometry(children[bestChild].prims)) {
splitByGeometry(children[bestChild].prims,left.prims,right.prims);
} else {
splitFallback(children[bestChild].prims,left.prims,right.prims);
}
children.split(bestChild,left,right,std::unique_ptr<mvector<PrimRefMB>>());
} while (children.size() < cfg.branchingFactor);
/* detect time_ranges that have shrunken */
bool timesplit = false;
for (size_t i=0; i<children.size(); i++) {
const BBox1f c = children[i].prims.time_range;
const BBox1f p = current.prims.time_range;
timesplit |= c.lower > p.lower || c.upper < p.upper;
}
/* create node */
NodeRef node = createAABBNodeMB(children.children.data(),children.numChildren,alloc,timesplit);
LBBox3fa bounds = empty;
for (size_t i=0; i<children.size(); i++) {
values[i] = createLargeLeaf(children[i],alloc);
bounds.extend(values[i].lbounds);
}
setAABBNodeMB(current,children.children.data(),node,values,children.numChildren);
if (timesplit)
bounds = current.prims.linearBounds(recalculatePrimRef);
return NodeRecordMB4D(node,bounds,current.prims.time_range);
}
/*! performs split */
std::unique_ptr<mvector<PrimRefMB>> split(const BuildRecord& current, BuildRecord& lrecord, BuildRecord& rrecord, bool& aligned, bool& timesplit)
{
/* variable to track the SAH of the best splitting approach */
float bestSAH = inf;
const float leafSAH = current.prims.leafSAH(cfg.logBlockSize);
/* perform standard binning in aligned space */
HeuristicBinning::Split alignedObjectSplit = alignedHeuristic.find(current.prims,cfg.logBlockSize);
float alignedObjectSAH = alignedObjectSplit.splitSAH();
bestSAH = min(alignedObjectSAH,bestSAH);
/* perform standard binning in unaligned space */
UnalignedHeuristicBinning::Split unalignedObjectSplit;
LinearSpace3fa uspace;
float unalignedObjectSAH = inf;
if (alignedObjectSAH > 0.7f*leafSAH) {
uspace = unalignedHeuristic.computeAlignedSpaceMB(scene,current.prims);
const SetMB sset = current.prims.primInfo(recalculatePrimRef,uspace);
unalignedObjectSplit = unalignedHeuristic.find(sset,cfg.logBlockSize,uspace);
unalignedObjectSAH = 1.3f*unalignedObjectSplit.splitSAH(); // makes unaligned splits more expensive
bestSAH = min(unalignedObjectSAH,bestSAH);
}
/* do temporal splits only if previous approaches failed to produce good SAH and the the time range is large enough */
float temporal_split_sah = inf;
typename HeuristicTemporal::Split temporal_split;
if (bestSAH > 0.5f*leafSAH) {
if (current.prims.time_range.size() > 1.01f/float(current.prims.max_num_time_segments)) {
temporal_split = temporalSplitHeuristic.find(current.prims,cfg.logBlockSize);
temporal_split_sah = temporal_split.splitSAH();
bestSAH = min(temporal_split_sah,bestSAH);
}
}
/* perform fallback split if SAH heuristics failed */
if (unlikely(!std::isfinite(bestSAH))) {
current.prims.deterministic_order();
splitFallback(current.prims,lrecord.prims,rrecord.prims);
}
/* perform aligned split if this is best */
else if (likely(bestSAH == alignedObjectSAH)) {
alignedHeuristic.split(alignedObjectSplit,current.prims,lrecord.prims,rrecord.prims);
}
/* perform unaligned split if this is best */
else if (likely(bestSAH == unalignedObjectSAH)) {
unalignedHeuristic.split(unalignedObjectSplit,uspace,current.prims,lrecord.prims,rrecord.prims);
aligned = false;
}
/* perform temporal split if this is best */
else if (likely(bestSAH == temporal_split_sah)) {
timesplit = true;
return temporalSplitHeuristic.split(temporal_split,current.prims,lrecord.prims,rrecord.prims);
}
else
assert(false);
return std::unique_ptr<mvector<PrimRefMB>>();
}
/*! recursive build */
NodeRecordMB4D recurse(BuildRecord& current, Allocator alloc, bool toplevel)
{
/* get thread local allocator */
if (!alloc)
alloc = createAlloc();
/* call memory monitor function to signal progress */
if (toplevel && current.size() <= SINGLE_THREADED_THRESHOLD)
progressMonitor(current.size());
/* create leaf node */
if (current.depth+MIN_LARGE_LEAF_LEVELS >= cfg.maxDepth || current.size() <= cfg.minLeafSize) {
current.prims.deterministic_order();
return createLargeLeaf(current,alloc);
}
/* fill all children by always splitting the one with the largest surface area */
NodeRecordMB4D values[MAX_BRANCHING_FACTOR];
LocalChildList children(current);
bool aligned = true;
bool timesplit = false;
do {
/* find best child with largest bounding box area */
ssize_t bestChild = -1;
float bestArea = neg_inf;
for (size_t i=0; i<children.size(); i++)
{
/* ignore leaves as they cannot get split */
if (children[i].size() <= cfg.minLeafSize)
continue;
/* remember child with largest area */
const float A = children[i].prims.halfArea();
if (A > bestArea) {
bestArea = children[i].prims.halfArea();
bestChild = i;
}
}
if (bestChild == -1) break;
/*! split best child into left and right child */
BuildRecord left(current.depth+1);
BuildRecord right(current.depth+1);
std::unique_ptr<mvector<PrimRefMB>> new_vector = split(children[bestChild],left,right,aligned,timesplit);
children.split(bestChild,left,right,std::move(new_vector));
} while (children.size() < cfg.branchingFactor);
/* detect time_ranges that have shrunken */
for (size_t i=0; i<children.size(); i++) {
const BBox1f c = children[i].prims.time_range;
const BBox1f p = current.prims.time_range;
timesplit |= c.lower > p.lower || c.upper < p.upper;
}
/* create time split node */
if (timesplit)
{
const NodeRef node = createAABBNodeMB(children.children.data(),children.numChildren,alloc,true);
/* spawn tasks or ... */
if (current.size() > SINGLE_THREADED_THRESHOLD)
{
parallel_for(size_t(0), children.size(), [&] (const range<size_t>& r) {
for (size_t i=r.begin(); i<r.end(); i++) {
values[i] = recurse(children[i],nullptr,true);
_mm_mfence(); // to allow non-temporal stores during build
}
});
}
/* ... continue sequential */
else {
for (size_t i=0; i<children.size(); i++) {
values[i] = recurse(children[i],alloc,false);
}
}
setAABBNodeMB(current,children.children.data(),node,values,children.numChildren);
const LBBox3fa bounds = current.prims.linearBounds(recalculatePrimRef);
return NodeRecordMB4D(node,bounds,current.prims.time_range);
}
/* create aligned node */
else if (aligned)
{
const NodeRef node = createAABBNodeMB(children.children.data(),children.numChildren,alloc,true);
/* spawn tasks or ... */
if (current.size() > SINGLE_THREADED_THRESHOLD)
{
LBBox3fa cbounds[MAX_BRANCHING_FACTOR];
parallel_for(size_t(0), children.size(), [&] (const range<size_t>& r) {
for (size_t i=r.begin(); i<r.end(); i++) {
values[i] = recurse(children[i],nullptr,true);
cbounds[i] = values[i].lbounds;
_mm_mfence(); // to allow non-temporal stores during build
}
});
LBBox3fa bounds = empty;
for (size_t i=0; i<children.size(); i++)
bounds.extend(cbounds[i]);
setAABBNodeMB(current,children.children.data(),node,values,children.numChildren);
return NodeRecordMB4D(node,bounds,current.prims.time_range);
}
/* ... continue sequentially */
else
{
LBBox3fa bounds = empty;
for (size_t i=0; i<children.size(); i++) {
values[i] = recurse(children[i],alloc,false);
bounds.extend(values[i].lbounds);
}
setAABBNodeMB(current,children.children.data(),node,values,children.numChildren);
return NodeRecordMB4D(node,bounds,current.prims.time_range);
}
}
/* create unaligned node */
else
{
const NodeRef node = createOBBNodeMB(alloc);
/* spawn tasks or ... */
if (current.size() > SINGLE_THREADED_THRESHOLD)
{
parallel_for(size_t(0), children.size(), [&] (const range<size_t>& r) {
for (size_t i=r.begin(); i<r.end(); i++) {
const LinearSpace3fa space = unalignedHeuristic.computeAlignedSpaceMB(scene,children[i].prims);
const LBBox3fa lbounds = children[i].prims.linearBounds(recalculatePrimRef,space);
const auto child = recurse(children[i],nullptr,true);
setOBBNodeMB(node,i,child.ref,space,lbounds,children[i].prims.time_range);
_mm_mfence(); // to allow non-temporal stores during build
}
});
}
/* ... continue sequentially */
else
{
for (size_t i=0; i<children.size(); i++) {
const LinearSpace3fa space = unalignedHeuristic.computeAlignedSpaceMB(scene,children[i].prims);
const LBBox3fa lbounds = children[i].prims.linearBounds(recalculatePrimRef,space);
const auto child = recurse(children[i],alloc,false);
setOBBNodeMB(node,i,child.ref,space,lbounds,children[i].prims.time_range);
}
}
const LBBox3fa bounds = current.prims.linearBounds(recalculatePrimRef);
return NodeRecordMB4D(node,bounds,current.prims.time_range);
}
}
public:
/*! entry point into builder */
NodeRecordMB4D operator() (mvector<PrimRefMB>& prims, const PrimInfoMB& pinfo)
{
BuildRecord record(SetMB(pinfo,&prims),1);
auto root = recurse(record,nullptr,true);
_mm_mfence(); // to allow non-temporal stores during build
return root;
}
private:
Settings cfg;
Scene* scene;
const RecalculatePrimRef& recalculatePrimRef;
const CreateAllocFunc& createAlloc;
const CreateAABBNodeMBFunc& createAABBNodeMB;
const SetAABBNodeMBFunc& setAABBNodeMB;
const CreateOBBNodeMBFunc& createOBBNodeMB;
const SetOBBNodeMBFunc& setOBBNodeMB;
const CreateLeafFunc& createLeaf;
const ProgressMonitor& progressMonitor;
private:
HeuristicBinning alignedHeuristic;
UnalignedHeuristicBinning unalignedHeuristic;
HeuristicTemporal temporalSplitHeuristic;
};
template<typename NodeRef,
typename RecalculatePrimRef,
typename CreateAllocFunc,
typename CreateAABBNodeMBFunc,
typename SetAABBNodeMBFunc,
typename CreateOBBNodeMBFunc,
typename SetOBBNodeMBFunc,
typename CreateLeafFunc,
typename ProgressMonitor>
static BVHNodeRecordMB4D<NodeRef> build (Scene* scene, mvector<PrimRefMB>& prims, const PrimInfoMB& pinfo,
const RecalculatePrimRef& recalculatePrimRef,
const CreateAllocFunc& createAlloc,
const CreateAABBNodeMBFunc& createAABBNodeMB,
const SetAABBNodeMBFunc& setAABBNodeMB,
const CreateOBBNodeMBFunc& createOBBNodeMB,
const SetOBBNodeMBFunc& setOBBNodeMB,
const CreateLeafFunc& createLeaf,
const ProgressMonitor& progressMonitor,
const Settings settings)
{
typedef BuilderT<NodeRef,RecalculatePrimRef,CreateAllocFunc,
CreateAABBNodeMBFunc,SetAABBNodeMBFunc,
CreateOBBNodeMBFunc,SetOBBNodeMBFunc,
CreateLeafFunc,ProgressMonitor> Builder;
Builder builder(scene,recalculatePrimRef,createAlloc,
createAABBNodeMB,setAABBNodeMB,
createOBBNodeMB,setOBBNodeMB,
createLeaf,progressMonitor,settings);
return builder(prims,pinfo);
}
};
}
}