Skip to content

Commit

Permalink
Improving AABBTree build speed (#1333)
Browse files Browse the repository at this point in the history
* Avoid lots of individual memory allocations in AABBTreeBuilder.  Instead allocate nodes and leaf triangles from single Arrays.
* Using Array instead of Deque in AABBTreeToBuffer.  Avoids a lot of allocations.

Co-authored-by: Ono-Sendai <nick@indigorenderer.com>
  • Loading branch information
jrouwe and Ono-Sendai authored Nov 9, 2024
1 parent ae10eb3 commit 88de579
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 106 deletions.
123 changes: 63 additions & 60 deletions Jolt/AABBTree/AABBTreeBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,84 +8,72 @@

JPH_NAMESPACE_BEGIN

AABBTreeBuilder::Node::Node()
{
mChild[0] = nullptr;
mChild[1] = nullptr;
}

AABBTreeBuilder::Node::~Node()
{
delete mChild[0];
delete mChild[1];
}

uint AABBTreeBuilder::Node::GetMinDepth() const
uint AABBTreeBuilder::Node::GetMinDepth(const Array<Node> &inNodes) const
{
if (HasChildren())
{
uint left = mChild[0]->GetMinDepth();
uint right = mChild[1]->GetMinDepth();
uint left = inNodes[mChild[0]].GetMinDepth(inNodes);
uint right = inNodes[mChild[1]].GetMinDepth(inNodes);
return min(left, right) + 1;
}
else
return 1;
}

uint AABBTreeBuilder::Node::GetMaxDepth() const
uint AABBTreeBuilder::Node::GetMaxDepth(const Array<Node> &inNodes) const
{
if (HasChildren())
{
uint left = mChild[0]->GetMaxDepth();
uint right = mChild[1]->GetMaxDepth();
uint left = inNodes[mChild[0]].GetMaxDepth(inNodes);
uint right = inNodes[mChild[1]].GetMaxDepth(inNodes);
return max(left, right) + 1;
}
else
return 1;
}

uint AABBTreeBuilder::Node::GetNodeCount() const
uint AABBTreeBuilder::Node::GetNodeCount(const Array<Node> &inNodes) const
{
if (HasChildren())
return mChild[0]->GetNodeCount() + mChild[1]->GetNodeCount() + 1;
return inNodes[mChild[0]].GetNodeCount(inNodes) + inNodes[mChild[1]].GetNodeCount(inNodes) + 1;
else
return 1;
}

uint AABBTreeBuilder::Node::GetLeafNodeCount() const
uint AABBTreeBuilder::Node::GetLeafNodeCount(const Array<Node> &inNodes) const
{
if (HasChildren())
return mChild[0]->GetLeafNodeCount() + mChild[1]->GetLeafNodeCount();
return inNodes[mChild[0]].GetLeafNodeCount(inNodes) + inNodes[mChild[1]].GetLeafNodeCount(inNodes);
else
return 1;
}

uint AABBTreeBuilder::Node::GetTriangleCountInTree() const
uint AABBTreeBuilder::Node::GetTriangleCountInTree(const Array<Node> &inNodes) const
{
if (HasChildren())
return mChild[0]->GetTriangleCountInTree() + mChild[1]->GetTriangleCountInTree();
return inNodes[mChild[0]].GetTriangleCountInTree(inNodes) + inNodes[mChild[1]].GetTriangleCountInTree(inNodes);
else
return GetTriangleCount();
}

void AABBTreeBuilder::Node::GetTriangleCountPerNode(float &outAverage, uint &outMin, uint &outMax) const
void AABBTreeBuilder::Node::GetTriangleCountPerNode(const Array<Node> &inNodes, float &outAverage, uint &outMin, uint &outMax) const
{
outMin = INT_MAX;
outMax = 0;
outAverage = 0;
uint avg_divisor = 0;
GetTriangleCountPerNodeInternal(outAverage, avg_divisor, outMin, outMax);
GetTriangleCountPerNodeInternal(inNodes, outAverage, avg_divisor, outMin, outMax);
if (avg_divisor > 0)
outAverage /= avg_divisor;
}

float AABBTreeBuilder::Node::CalculateSAHCost(float inCostTraversal, float inCostLeaf) const
float AABBTreeBuilder::Node::CalculateSAHCost(const Array<Node> &inNodes, float inCostTraversal, float inCostLeaf) const
{
float surface_area = mBounds.GetSurfaceArea();
return surface_area > 0.0f? CalculateSAHCostInternal(inCostTraversal / surface_area, inCostLeaf / surface_area) : 0.0f;
return surface_area > 0.0f? CalculateSAHCostInternal(inNodes, inCostTraversal / surface_area, inCostLeaf / surface_area) : 0.0f;
}

void AABBTreeBuilder::Node::GetNChildren(uint inN, Array<const Node *> &outChildren) const
void AABBTreeBuilder::Node::GetNChildren(const Array<Node> &inNodes, uint inN, Array<const Node*> &outChildren) const
{
JPH_ASSERT(outChildren.empty());

Expand All @@ -94,8 +82,8 @@ void AABBTreeBuilder::Node::GetNChildren(uint inN, Array<const Node *> &outChild
return;

// Start with the children of this node
outChildren.push_back(mChild[0]);
outChildren.push_back(mChild[1]);
outChildren.push_back(&inNodes[mChild[0]]);
outChildren.push_back(&inNodes[mChild[1]]);

size_t next = 0;
bool all_triangles = true;
Expand All @@ -116,8 +104,8 @@ void AABBTreeBuilder::Node::GetNChildren(uint inN, Array<const Node *> &outChild
if (to_expand->HasChildren())
{
outChildren.erase(outChildren.begin() + next);
outChildren.push_back(to_expand->mChild[0]);
outChildren.push_back(to_expand->mChild[1]);
outChildren.push_back(&inNodes[to_expand->mChild[0]]);
outChildren.push_back(&inNodes[to_expand->mChild[1]]);
all_triangles = false;
}
else
Expand All @@ -127,22 +115,22 @@ void AABBTreeBuilder::Node::GetNChildren(uint inN, Array<const Node *> &outChild
}
}

float AABBTreeBuilder::Node::CalculateSAHCostInternal(float inCostTraversalDivSurfaceArea, float inCostLeafDivSurfaceArea) const
float AABBTreeBuilder::Node::CalculateSAHCostInternal(const Array<Node> &inNodes, float inCostTraversalDivSurfaceArea, float inCostLeafDivSurfaceArea) const
{
if (HasChildren())
return inCostTraversalDivSurfaceArea * mBounds.GetSurfaceArea()
+ mChild[0]->CalculateSAHCostInternal(inCostTraversalDivSurfaceArea, inCostLeafDivSurfaceArea)
+ mChild[1]->CalculateSAHCostInternal(inCostTraversalDivSurfaceArea, inCostLeafDivSurfaceArea);
+ inNodes[mChild[0]].CalculateSAHCostInternal(inNodes, inCostTraversalDivSurfaceArea, inCostLeafDivSurfaceArea)
+ inNodes[mChild[1]].CalculateSAHCostInternal(inNodes, inCostTraversalDivSurfaceArea, inCostLeafDivSurfaceArea);
else
return inCostLeafDivSurfaceArea * mBounds.GetSurfaceArea() * GetTriangleCount();
}

void AABBTreeBuilder::Node::GetTriangleCountPerNodeInternal(float &outAverage, uint &outAverageDivisor, uint &outMin, uint &outMax) const
void AABBTreeBuilder::Node::GetTriangleCountPerNodeInternal(const Array<Node> &inNodes, float &outAverage, uint &outAverageDivisor, uint &outMin, uint &outMax) const
{
if (HasChildren())
{
mChild[0]->GetTriangleCountPerNodeInternal(outAverage, outAverageDivisor, outMin, outMax);
mChild[1]->GetTriangleCountPerNodeInternal(outAverage, outAverageDivisor, outMin, outMax);
inNodes[mChild[0]].GetTriangleCountPerNodeInternal(inNodes, outAverage, outAverageDivisor, outMin, outMax);
inNodes[mChild[1]].GetTriangleCountPerNodeInternal(inNodes, outAverage, outAverageDivisor, outMin, outMax);
}
else
{
Expand All @@ -162,28 +150,36 @@ AABBTreeBuilder::AABBTreeBuilder(TriangleSplitter &inSplitter, uint inMaxTriangl
AABBTreeBuilder::Node *AABBTreeBuilder::Build(AABBTreeBuilderStats &outStats)
{
TriangleSplitter::Range initial = mTriangleSplitter.GetInitialRange();
Node *root = BuildInternal(initial);

// Worst case for number of nodes: 1 leaf node per triangle. At each level above, the number of nodes is half that of the level below.
// This means that at most we'll be allocating 2x the number of triangles in nodes.
mNodes.reserve(2 * initial.Count());
mTriangles.reserve(initial.Count());

// Build the tree
Node &root = mNodes[BuildInternal(initial)];

// Collect stats
float avg_triangles_per_leaf;
uint min_triangles_per_leaf, max_triangles_per_leaf;
root->GetTriangleCountPerNode(avg_triangles_per_leaf, min_triangles_per_leaf, max_triangles_per_leaf);
root.GetTriangleCountPerNode(mNodes, avg_triangles_per_leaf, min_triangles_per_leaf, max_triangles_per_leaf);

mTriangleSplitter.GetStats(outStats.mSplitterStats);

outStats.mSAHCost = root->CalculateSAHCost(1.0f, 1.0f);
outStats.mMinDepth = root->GetMinDepth();
outStats.mMaxDepth = root->GetMaxDepth();
outStats.mNodeCount = root->GetNodeCount();
outStats.mLeafNodeCount = root->GetLeafNodeCount();
outStats.mSAHCost = root.CalculateSAHCost(mNodes, 1.0f, 1.0f);
outStats.mMinDepth = root.GetMinDepth(mNodes);
outStats.mMaxDepth = root.GetMaxDepth(mNodes);
outStats.mNodeCount = root.GetNodeCount(mNodes);
outStats.mLeafNodeCount = root.GetLeafNodeCount(mNodes);
outStats.mMaxTrianglesPerLeaf = mMaxTrianglesPerLeaf;
outStats.mTreeMinTrianglesPerLeaf = min_triangles_per_leaf;
outStats.mTreeMaxTrianglesPerLeaf = max_triangles_per_leaf;
outStats.mTreeAvgTrianglesPerLeaf = avg_triangles_per_leaf;

return root;
return &root;
}

AABBTreeBuilder::Node *AABBTreeBuilder::BuildInternal(const TriangleSplitter::Range &inTriangles)
uint AABBTreeBuilder::BuildInternal(const TriangleSplitter::Range &inTriangles)
{
// Check if there are too many triangles left
if (inTriangles.Count() > mMaxTrianglesPerLeaf)
Expand Down Expand Up @@ -214,26 +210,33 @@ AABBTreeBuilder::Node *AABBTreeBuilder::BuildInternal(const TriangleSplitter::Ra
}

// Recursively build
Node *node = new Node();
node->mChild[0] = BuildInternal(left);
node->mChild[1] = BuildInternal(right);
node->mBounds = node->mChild[0]->mBounds;
node->mBounds.Encapsulate(node->mChild[1]->mBounds);
return node;
const uint node_index = (uint)mNodes.size();
mNodes.push_back(Node());
uint left_index = BuildInternal(left);
uint right_index = BuildInternal(right);
Node &node = mNodes[node_index];
node.mChild[0] = left_index;
node.mChild[1] = right_index;
node.mBounds = mNodes[node.mChild[0]].mBounds;
node.mBounds.Encapsulate(mNodes[node.mChild[1]].mBounds);
return node_index;
}

// Create leaf node
Node *node = new Node();
node->mTriangles.reserve(inTriangles.Count());
const uint node_index = (uint)mNodes.size();
mNodes.push_back(Node());
Node &node = mNodes.back();
node.mTrianglesBegin = (uint)mTriangles.size();
node.mNumTriangles = inTriangles.mEnd - inTriangles.mBegin;
const VertexList &v = mTriangleSplitter.GetVertices();
for (uint i = inTriangles.mBegin; i < inTriangles.mEnd; ++i)
{
const IndexedTriangle &t = mTriangleSplitter.GetTriangle(i);
const VertexList &v = mTriangleSplitter.GetVertices();
node->mTriangles.push_back(t);
node->mBounds.Encapsulate(v, t);
mTriangles.push_back(t);
node.mBounds.Encapsulate(v, t);
}

return node;
return node_index;
}

JPH_NAMESPACE_END
48 changes: 28 additions & 20 deletions Jolt/AABBTree/AABBTreeBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,62 +36,62 @@ class JPH_EXPORT AABBTreeBuilder
{
public:
/// A node in the tree, contains the AABox for the tree and any child nodes or triangles
class Node : public NonCopyable
class Node
{
public:
JPH_OVERRIDE_NEW_DELETE

/// Constructor
Node();
~Node();
/// Indicates that there is no child
static constexpr uint cInvalidNodeIndex = ~uint(0);

/// Get number of triangles in this node
inline uint GetTriangleCount() const { return uint(mTriangles.size()); }
inline uint GetTriangleCount() const { return mNumTriangles; }

/// Check if this node has any children
inline bool HasChildren() const { return mChild[0] != nullptr || mChild[1] != nullptr; }
inline bool HasChildren() const { return mChild[0] != cInvalidNodeIndex || mChild[1] != cInvalidNodeIndex; }

/// Min depth of tree
uint GetMinDepth() const;
uint GetMinDepth(const Array<Node> &inNodes) const;

/// Max depth of tree
uint GetMaxDepth() const;
uint GetMaxDepth(const Array<Node> &inNodes) const;

/// Number of nodes in tree
uint GetNodeCount() const;
uint GetNodeCount(const Array<Node> &inNodes) const;

/// Number of leaf nodes in tree
uint GetLeafNodeCount() const;
uint GetLeafNodeCount(const Array<Node> &inNodes) const;

/// Get triangle count in tree
uint GetTriangleCountInTree() const;
uint GetTriangleCountInTree(const Array<Node> &inNodes) const;

/// Calculate min and max triangles per node
void GetTriangleCountPerNode(float &outAverage, uint &outMin, uint &outMax) const;
void GetTriangleCountPerNode(const Array<Node> &inNodes, float &outAverage, uint &outMin, uint &outMax) const;

/// Calculate the total cost of the tree using the surface area heuristic
float CalculateSAHCost(float inCostTraversal, float inCostLeaf) const;
float CalculateSAHCost(const Array<Node> &inNodes, float inCostTraversal, float inCostLeaf) const;

/// Recursively get children (breadth first) to get in total inN children (or less if there are no more)
void GetNChildren(uint inN, Array<const Node *> &outChildren) const;
void GetNChildren(const Array<Node> &inNodes, uint inN, Array<const Node *> &outChildren) const;

/// Bounding box
AABox mBounds;

/// Triangles (if no child nodes)
IndexedTriangleList mTriangles;
uint mTrianglesBegin; // Index into mTriangles
uint mNumTriangles = 0;

/// Child nodes (if no triangles)
Node * mChild[2];
/// Child node indices (if no triangles)
uint mChild[2] = { cInvalidNodeIndex, cInvalidNodeIndex };

private:
friend class AABBTreeBuilder;

/// Recursive helper function to calculate cost of the tree
float CalculateSAHCostInternal(float inCostTraversalDivSurfaceArea, float inCostLeafDivSurfaceArea) const;
float CalculateSAHCostInternal(const Array<Node> &inNodes, float inCostTraversalDivSurfaceArea, float inCostLeafDivSurfaceArea) const;

/// Recursive helper function to calculate min and max triangles per node
void GetTriangleCountPerNodeInternal(float &outAverage, uint &outAverageDivisor, uint &outMin, uint &outMax) const;
void GetTriangleCountPerNodeInternal(const Array<Node> &inNodes, float &outAverage, uint &outAverageDivisor, uint &outMin, uint &outMax) const;
};

/// Constructor
Expand All @@ -100,11 +100,19 @@ class JPH_EXPORT AABBTreeBuilder
/// Recursively build tree, returns the root node of the tree
Node * Build(AABBTreeBuilderStats &outStats);

/// Get all nodes
const Array<Node> & GetNodes() const { return mNodes; }

/// Get all triangles
const Array<IndexedTriangle> &GetTriangles() const { return mTriangles; }

private:
Node * BuildInternal(const TriangleSplitter::Range &inTriangles);
uint BuildInternal(const TriangleSplitter::Range &inTriangles);

TriangleSplitter & mTriangleSplitter;
const uint mMaxTrianglesPerLeaf;
Array<Node> mNodes;
Array<IndexedTriangle> mTriangles;
};

JPH_NAMESPACE_END
Loading

0 comments on commit 88de579

Please sign in to comment.