BVHBuildNode *CBVH_PBRT::buildUpperSAH( std::vector<BVHBuildNode *> &treeletRoots, int start, int end, int *totalNodes ) { wxASSERT( totalNodes != NULL ); wxASSERT( start < end ); wxASSERT( end <= (int)treeletRoots.size() ); int nNodes = end - start; if( nNodes == 1 ) return treeletRoots[start]; (*totalNodes)++; BVHBuildNode *node = static_cast<BVHBuildNode *>( _mm_malloc( sizeof( BVHBuildNode ), L1_CACHE_LINE_SIZE ) ); m_addresses_pointer_to_mm_free.push_back( node ); node->bounds.Reset(); node->firstPrimOffset = 0; node->nPrimitives = 0; node->splitAxis = 0; node->children[0] = NULL; node->children[1] = NULL; // Compute bounds of all nodes under this HLBVH node CBBOX bounds; bounds.Reset(); for( int i = start; i < end; ++i ) bounds.Union( treeletRoots[i]->bounds ); // Compute bound of HLBVH node centroids, choose split dimension _dim_ CBBOX centroidBounds; centroidBounds.Reset(); for( int i = start; i < end; ++i ) { SFVEC3F centroid = (treeletRoots[i]->bounds.Min() + treeletRoots[i]->bounds.Max()) * 0.5f; centroidBounds.Union(centroid); } const int dim = centroidBounds.MaxDimension(); // FIXME: if this hits, what do we need to do? // Make sure the SAH split below does something... ? wxASSERT( centroidBounds.Max()[dim] != centroidBounds.Min()[dim] ); // Allocate _BucketInfo_ for SAH partition buckets const int nBuckets = 12; BucketInfo buckets[nBuckets]; for( int i = 0; i < nBuckets; ++i ) { buckets[i].count = 0; buckets[i].bounds.Reset(); } // Initialize _BucketInfo_ for HLBVH SAH partition buckets for( int i = start; i < end; ++i ) { const float centroid = ( treeletRoots[i]->bounds.Min()[dim] + treeletRoots[i]->bounds.Max()[dim] ) * 0.5f; int b = nBuckets * ( (centroid - centroidBounds.Min()[dim] ) / (centroidBounds.Max()[dim] - centroidBounds.Min()[dim] ) ); if( b == nBuckets ) b = nBuckets - 1; wxASSERT( (b >= 0) && (b < nBuckets) ); buckets[b].count++; buckets[b].bounds.Union( treeletRoots[i]->bounds ); } // Compute costs for splitting after each bucket float cost[nBuckets - 1]; for( int i = 0; i < nBuckets - 1; ++i ) { CBBOX b0, b1; b0.Reset(); b1.Reset(); int count0 = 0, count1 = 0; for( int j = 0; j <= i; ++j ) { if( buckets[j].count ) { count0 += buckets[j].count; b0.Union( buckets[j].bounds ); } } for( int j = i + 1; j < nBuckets; ++j ) { if( buckets[j].count ) { count1 += buckets[j].count; b1.Union( buckets[j].bounds ); } } cost[i] = .125f + ( count0 * b0.SurfaceArea() + count1 * b1.SurfaceArea() ) / bounds.SurfaceArea(); } // Find bucket to split at that minimizes SAH metric float minCost = cost[0]; int minCostSplitBucket = 0; for( int i = 1; i < nBuckets - 1; ++i ) { if( cost[i] < minCost ) { minCost = cost[i]; minCostSplitBucket = i; } } // Split nodes and create interior HLBVH SAH node BVHBuildNode **pmid = std::partition( &treeletRoots[start], &treeletRoots[end - 1] + 1, HLBVH_SAH_Evaluator( minCostSplitBucket, nBuckets, dim, centroidBounds ) ); const int mid = pmid - &treeletRoots[0]; wxASSERT( (mid > start) && (mid < end) ); node->InitInterior( dim, buildUpperSAH( treeletRoots, start, mid, totalNodes ), buildUpperSAH( treeletRoots, mid, end, totalNodes ) ); return node; }
BVHBuildNode *BVHAccel::buildUpperSAH(MemoryArena &arena, std::vector<BVHBuildNode *> &treeletRoots, int start, int end, int *totalNodes) const { Assert(start < end); int nNodes = end - start; if (nNodes == 1) return treeletRoots[start]; (*totalNodes)++; BVHBuildNode *node = arena.Alloc<BVHBuildNode>(); // Compute bounds of all nodes under this HLBVH node Bounds3f bounds; for (int i = start; i < end; ++i) bounds = Union(bounds, treeletRoots[i]->bounds); // Compute bound of HLBVH node centroids, choose split dimension _dim_ Bounds3f centroidBounds; for (int i = start; i < end; ++i) { Point3f centroid = (treeletRoots[i]->bounds.pMin + treeletRoots[i]->bounds.pMax) * 0.5f; centroidBounds = Union(centroidBounds, centroid); } int dim = centroidBounds.MaximumExtent(); // FIXME: if this hits, what do we need to do? // Make sure the SAH split below does something... ? Assert(centroidBounds.pMax[dim] != centroidBounds.pMin[dim]); // Allocate _BucketInfo_ for SAH partition buckets constexpr int nBuckets = 12; struct BucketInfo { int count = 0; Bounds3f bounds; }; BucketInfo buckets[nBuckets]; // Initialize _BucketInfo_ for HLBVH SAH partition buckets for (int i = start; i < end; ++i) { Float centroid = (treeletRoots[i]->bounds.pMin[dim] + treeletRoots[i]->bounds.pMax[dim]) * 0.5f; int b = nBuckets * ((centroid - centroidBounds.pMin[dim]) / (centroidBounds.pMax[dim] - centroidBounds.pMin[dim])); if (b == nBuckets) b = nBuckets - 1; Assert(b >= 0 && b < nBuckets); buckets[b].count++; buckets[b].bounds = Union(buckets[b].bounds, treeletRoots[i]->bounds); } // Compute costs for splitting after each bucket Float cost[nBuckets - 1]; for (int i = 0; i < nBuckets - 1; ++i) { Bounds3f b0, b1; int count0 = 0, count1 = 0; for (int j = 0; j <= i; ++j) { b0 = Union(b0, buckets[j].bounds); count0 += buckets[j].count; } for (int j = i + 1; j < nBuckets; ++j) { b1 = Union(b1, buckets[j].bounds); count1 += buckets[j].count; } cost[i] = .125f + (count0 * b0.SurfaceArea() + count1 * b1.SurfaceArea()) / bounds.SurfaceArea(); } // Find bucket to split at that minimizes SAH metric Float minCost = cost[0]; int minCostSplitBucket = 0; for (int i = 1; i < nBuckets - 1; ++i) { if (cost[i] < minCost) { minCost = cost[i]; minCostSplitBucket = i; } } // Split nodes and create interior HLBVH SAH node BVHBuildNode **pmid = std::partition( &treeletRoots[start], &treeletRoots[end - 1] + 1, [=](const BVHBuildNode *node) { Float centroid = (node->bounds.pMin[dim] + node->bounds.pMax[dim]) * 0.5f; int b = nBuckets * ((centroid - centroidBounds.pMin[dim]) / (centroidBounds.pMax[dim] - centroidBounds.pMin[dim])); if (b == nBuckets) b = nBuckets - 1; Assert(b >= 0 && b < nBuckets); return b <= minCostSplitBucket; }); int mid = pmid - &treeletRoots[0]; Assert(mid > start && mid < end); node->InitInterior( dim, buildUpperSAH(arena, treeletRoots, start, mid, totalNodes), buildUpperSAH(arena, treeletRoots, mid, end, totalNodes)); return node; }
BVHBuildNode *CBVH_PBRT::HLBVHBuild( const std::vector<BVHPrimitiveInfo> &primitiveInfo, int *totalNodes, CONST_VECTOR_OBJECT &orderedPrims ) { // Compute bounding box of all primitive centroids CBBOX bounds; bounds.Reset(); for( unsigned int i = 0; i < primitiveInfo.size(); ++i ) bounds.Union( primitiveInfo[i].centroid ); // Compute Morton indices of primitives std::vector<MortonPrimitive> mortonPrims( primitiveInfo.size() ); for( int i = 0; i < (int)primitiveInfo.size(); ++i ) { // Initialize _mortonPrims[i]_ for _i_th primitive const int mortonBits = 10; const int mortonScale = 1 << mortonBits; wxASSERT( primitiveInfo[i].primitiveNumber < (int)primitiveInfo.size() ); mortonPrims[i].primitiveIndex = primitiveInfo[i].primitiveNumber; const SFVEC3F centroidOffset = bounds.Offset( primitiveInfo[i].centroid ); wxASSERT( (centroidOffset.x >= 0.0f) && (centroidOffset.x <= 1.0f) ); wxASSERT( (centroidOffset.y >= 0.0f) && (centroidOffset.y <= 1.0f) ); wxASSERT( (centroidOffset.z >= 0.0f) && (centroidOffset.z <= 1.0f) ); mortonPrims[i].mortonCode = EncodeMorton3( centroidOffset * SFVEC3F( (float)mortonScale ) ); } // Radix sort primitive Morton indices RadixSort( &mortonPrims ); // Create LBVH treelets at bottom of BVH // Find intervals of primitives for each treelet std::vector<LBVHTreelet> treeletsToBuild; for( int start = 0, end = 1; end <= (int)mortonPrims.size(); ++end ) { const uint32_t mask = 0b00111111111111000000000000000000; if( (end == (int)mortonPrims.size()) || ( (mortonPrims[start].mortonCode & mask) != (mortonPrims[end].mortonCode & mask) ) ) { // Add entry to _treeletsToBuild_ for this treelet const int numPrimitives = end - start; const int maxBVHNodes = 2 * numPrimitives; // !TODO: implement a memory arena BVHBuildNode *nodes = static_cast<BVHBuildNode *>( _mm_malloc( maxBVHNodes * sizeof( BVHBuildNode ), L1_CACHE_LINE_SIZE ) ); m_addresses_pointer_to_mm_free.push_back( nodes ); for( int i = 0; i < maxBVHNodes; ++i ) { nodes[i].bounds.Reset(); nodes[i].firstPrimOffset = 0; nodes[i].nPrimitives = 0; nodes[i].splitAxis = 0; nodes[i].children[0] = NULL; nodes[i].children[1] = NULL; } LBVHTreelet tmpTreelet; tmpTreelet.startIndex = start; tmpTreelet.numPrimitives = numPrimitives; tmpTreelet.buildNodes = nodes; treeletsToBuild.push_back( tmpTreelet ); start = end; } } // Create LBVHs for treelets in parallel int atomicTotal = 0; int orderedPrimsOffset = 0; orderedPrims.resize( m_primitives.size() ); for( int index = 0; index < (int)treeletsToBuild.size(); ++index ) { // Generate _index_th LBVH treelet int nodesCreated = 0; const int firstBit = 29 - 12; LBVHTreelet &tr = treeletsToBuild[index]; wxASSERT( tr.startIndex < (int)mortonPrims.size() ); tr.buildNodes = emitLBVH( tr.buildNodes, primitiveInfo, &mortonPrims[tr.startIndex], tr.numPrimitives, &nodesCreated, orderedPrims, &orderedPrimsOffset, firstBit ); atomicTotal += nodesCreated; } *totalNodes = atomicTotal; // Initialize _finishedTreelets_ with treelet root node pointers std::vector<BVHBuildNode *> finishedTreelets; finishedTreelets.reserve( treeletsToBuild.size() ); for( int index = 0; index < (int)treeletsToBuild.size(); ++index ) finishedTreelets.push_back( treeletsToBuild[index].buildNodes ); // Create and return SAH BVH from LBVH treelets return buildUpperSAH( finishedTreelets, 0, finishedTreelets.size(), totalNodes ); }
BVHBuildNode *BVHAccel::HLBVHBuild( MemoryArena &arena, const std::vector<BVHPrimitiveInfo> &primitiveInfo, int *totalNodes, std::vector<std::shared_ptr<Primitive>> &orderedPrims) const { // Compute bounding box of all primitive centroids Bounds3f bounds; for (const BVHPrimitiveInfo &pi : primitiveInfo) bounds = Union(bounds, pi.centroid); // Compute Morton indices of primitives std::vector<MortonPrimitive> mortonPrims(primitiveInfo.size()); ParallelFor([&](int i) { // Initialize _mortionPrims[i]_ for _i_th primitive constexpr int mortonBits = 10; constexpr int mortonScale = 1 << mortonBits; mortonPrims[i].primitiveIndex = primitiveInfo[i].primitiveNumber; Vector3f centroidOffset = bounds.Offset(primitiveInfo[i].centroid); mortonPrims[i].mortonCode = EncodeMorton3(centroidOffset * mortonScale); }, primitiveInfo.size(), 512); // Radix sort primitive Morton indices RadixSort(&mortonPrims); // Create LBVH treelets at bottom of BVH // Find intervals of primitives for each treelet std::vector<LBVHTreelet> treeletsToBuild; for (int start = 0, end = 1; end <= (int)mortonPrims.size(); ++end) { uint32_t mask = 0b00111111111111000000000000000000; if (end == (int)mortonPrims.size() || ((mortonPrims[start].mortonCode & mask) != (mortonPrims[end].mortonCode & mask))) { // Add entry to _treeletsToBuild_ for this treelet int nPrimitives = end - start; int maxBVHNodes = 2 * nPrimitives; BVHBuildNode *nodes = arena.Alloc<BVHBuildNode>(maxBVHNodes, false); treeletsToBuild.push_back({start, nPrimitives, nodes}); start = end; } } // Create LBVHs for treelets in parallel std::atomic<int> atomicTotal(0), orderedPrimsOffset(0); orderedPrims.resize(primitives.size()); ParallelFor([&](int i) { // Generate _i_th LBVH treelet int nodesCreated = 0; const int firstBitIndex = 29 - 12; LBVHTreelet &tr = treeletsToBuild[i]; tr.buildNodes = emitLBVH(tr.buildNodes, primitiveInfo, &mortonPrims[tr.startIndex], tr.nPrimitives, &nodesCreated, orderedPrims, &orderedPrimsOffset, firstBitIndex); atomicTotal += nodesCreated; }, treeletsToBuild.size()); *totalNodes = atomicTotal; // Create and return SAH BVH from LBVH treelets std::vector<BVHBuildNode *> finishedTreelets; finishedTreelets.reserve(treeletsToBuild.size()); for (LBVHTreelet &treelet : treeletsToBuild) finishedTreelets.push_back(treelet.buildNodes); return buildUpperSAH(arena, finishedTreelets, 0, finishedTreelets.size(), totalNodes); }