unsafe void SplitSubtreesIntoChildrenBinned(ref BinnedResources resources, int start, int count, int stagingNodeIndex, ref int stagingNodesCount, out float childrenTreeletsCost) { Debug.Assert(count > 2); FindPartitionBinned(ref resources, start, count, out int splitIndex, out BoundingBox aBounds, out BoundingBox bBounds, out int leafCountA, out int leafCountB); //Recursion bottomed out. var stagingNode = resources.StagingNodes + stagingNodeIndex; var stagingChildren = &stagingNode->A; ref var a = ref stagingNode->A;
public static unsafe void CreateBinnedResources(BufferPool bufferPool, int maximumSubtreeCount, out RawBuffer buffer, out BinnedResources resources) { //TODO: This is a holdover from the pre-BufferPool tree design. It's pretty ugly. While some preallocation is useful (there's no reason to suffer the overhead of //pulling things out of the BufferPool over and over and over again), the degree to which this preallocates has a negative impact on L1 cache for subtree refines. int nodeCount = maximumSubtreeCount - 1; int bytesRequired = 16 * (3 + 3 + 1) + sizeof(BoundingBox) * (maximumSubtreeCount + 3 * nodeCount + 3 * MaximumBinCount) + 16 * (6 + 3 + 8) + sizeof(int) * (maximumSubtreeCount * 6 + nodeCount * 3 + MaximumBinCount * 8) + 16 * (1) + sizeof(Vector3) * maximumSubtreeCount + 16 * (1) + sizeof(SubtreeHeapEntry) * maximumSubtreeCount + 16 * (1) + sizeof(Node) * nodeCount + 16 * (1) + sizeof(int) * nodeCount; bufferPool.Take(bytesRequired, out buffer); var memory = buffer.Memory; int memoryAllocated = 0; resources.BoundingBoxes = (BoundingBox *)Suballocate(memory, ref memoryAllocated, sizeof(BoundingBox) * maximumSubtreeCount); resources.LeafCounts = (int *)Suballocate(memory, ref memoryAllocated, sizeof(int) * maximumSubtreeCount); resources.IndexMap = (int *)Suballocate(memory, ref memoryAllocated, sizeof(int) * maximumSubtreeCount); resources.Centroids = (Vector3 *)Suballocate(memory, ref memoryAllocated, sizeof(Vector3) * maximumSubtreeCount); resources.SubtreeHeapEntries = (SubtreeHeapEntry *)Suballocate(memory, ref memoryAllocated, sizeof(SubtreeHeapEntry) * maximumSubtreeCount); resources.StagingNodes = (Node *)Suballocate(memory, ref memoryAllocated, sizeof(Node) * nodeCount); resources.RefineFlags = (int *)Suballocate(memory, ref memoryAllocated, sizeof(int) * nodeCount); resources.SubtreeBinIndicesX = (int *)Suballocate(memory, ref memoryAllocated, sizeof(int) * maximumSubtreeCount); resources.SubtreeBinIndicesY = (int *)Suballocate(memory, ref memoryAllocated, sizeof(int) * maximumSubtreeCount); resources.SubtreeBinIndicesZ = (int *)Suballocate(memory, ref memoryAllocated, sizeof(int) * maximumSubtreeCount); resources.TempIndexMap = (int *)Suballocate(memory, ref memoryAllocated, sizeof(int) * maximumSubtreeCount); resources.ALeafCountsX = (int *)Suballocate(memory, ref memoryAllocated, sizeof(int) * nodeCount); resources.ALeafCountsY = (int *)Suballocate(memory, ref memoryAllocated, sizeof(int) * nodeCount); resources.ALeafCountsZ = (int *)Suballocate(memory, ref memoryAllocated, sizeof(int) * nodeCount); resources.AMergedX = (BoundingBox *)Suballocate(memory, ref memoryAllocated, sizeof(BoundingBox) * nodeCount); resources.AMergedY = (BoundingBox *)Suballocate(memory, ref memoryAllocated, sizeof(BoundingBox) * nodeCount); resources.AMergedZ = (BoundingBox *)Suballocate(memory, ref memoryAllocated, sizeof(BoundingBox) * nodeCount); resources.BinBoundingBoxesX = (BoundingBox *)Suballocate(memory, ref memoryAllocated, sizeof(BoundingBox) * MaximumBinCount); resources.BinBoundingBoxesY = (BoundingBox *)Suballocate(memory, ref memoryAllocated, sizeof(BoundingBox) * MaximumBinCount); resources.BinBoundingBoxesZ = (BoundingBox *)Suballocate(memory, ref memoryAllocated, sizeof(BoundingBox) * MaximumBinCount); resources.BinLeafCountsX = (int *)Suballocate(memory, ref memoryAllocated, sizeof(int) * MaximumBinCount); resources.BinLeafCountsY = (int *)Suballocate(memory, ref memoryAllocated, sizeof(int) * MaximumBinCount); resources.BinLeafCountsZ = (int *)Suballocate(memory, ref memoryAllocated, sizeof(int) * MaximumBinCount); resources.BinSubtreeCountsX = (int *)Suballocate(memory, ref memoryAllocated, sizeof(int) * MaximumBinCount); resources.BinSubtreeCountsY = (int *)Suballocate(memory, ref memoryAllocated, sizeof(int) * MaximumBinCount); resources.BinSubtreeCountsZ = (int *)Suballocate(memory, ref memoryAllocated, sizeof(int) * MaximumBinCount); resources.BinStartIndices = (int *)Suballocate(memory, ref memoryAllocated, sizeof(int) * MaximumBinCount); resources.BinSubtreeCountsSecondPass = (int *)Suballocate(memory, ref memoryAllocated, sizeof(int) * MaximumBinCount); Debug.Assert(memoryAllocated <= buffer.Length, "The allocated buffer should be large enough for all the suballocations."); }
unsafe void FindPartitionBinned(ref BinnedResources resources, int start, int count, out int splitIndex, out BoundingBox a, out BoundingBox b, out int leafCountA, out int leafCountB) { //Initialize the per-axis candidate maps. var localIndexMap = resources.IndexMap + start; BoundingBox centroidBoundingBox; centroidBoundingBox.Min = resources.Centroids[localIndexMap[0]]; centroidBoundingBox.Max = centroidBoundingBox.Min; for (int i = 1; i < count; ++i) { var centroid = resources.Centroids + localIndexMap[i]; centroidBoundingBox.Min = Vector3.Min(*centroid, centroidBoundingBox.Min); centroidBoundingBox.Max = Vector3.Max(*centroid, centroidBoundingBox.Max); } //Bin along all three axes simultaneously. var nullBoundingBox = new BoundingBox { Min = new Vector3(float.MaxValue), Max = new Vector3(float.MinValue) }; var span = centroidBoundingBox.Max - centroidBoundingBox.Min; const float epsilon = 1e-12f; if (span.X < epsilon && span.Y < epsilon && span.Z < epsilon) { //All axes are degenerate. //This is the one situation in which we can end up with all objects in the same bin. //To stop this, just short circuit. splitIndex = count / 2; a = nullBoundingBox; b = nullBoundingBox; leafCountA = 0; leafCountB = 0; for (int i = 0; i < splitIndex; ++i) { BoundingBox.CreateMerged(a, resources.BoundingBoxes[localIndexMap[i]], out a); leafCountA += resources.LeafCounts[localIndexMap[i]]; } for (int i = splitIndex; i < count; ++i) { BoundingBox.CreateMerged(b, resources.BoundingBoxes[localIndexMap[i]], out b); leafCountB += resources.LeafCounts[localIndexMap[i]]; } splitIndex += start; return; } //There is no real value in having tons of bins when there are very few children. //At low counts, many of them even end up empty. //You can get huge speed boosts by simply dropping the bin count adaptively. var binCount = (int)Math.Min(MaximumBinCount, Math.Max(count * .25f, 2)); //Take into account zero-width cases. //This will result in degenerate axes all being dumped into the first bin. var inverseBinSize = new Vector3( span.X > epsilon ? binCount / span.X : 0, span.Y > epsilon ? binCount / span.Y : 0, span.Z > epsilon ? binCount / span.Z : 0); //inverseBinSize = new Vector3(inverseBinSize.X, inverseBinSize.Y, inverseBinSize.Z); //If the span along an axis is too small, just ignore it. var maximumBinIndex = new Vector3(binCount - 1); //Initialize bin information. for (int i = 0; i < binCount; ++i) { resources.BinBoundingBoxesX[i] = nullBoundingBox; resources.BinBoundingBoxesY[i] = nullBoundingBox; resources.BinBoundingBoxesZ[i] = nullBoundingBox; resources.BinSubtreeCountsX[i] = 0; resources.BinSubtreeCountsY[i] = 0; resources.BinSubtreeCountsZ[i] = 0; resources.BinLeafCountsX[i] = 0; resources.BinLeafCountsY[i] = 0; resources.BinLeafCountsZ[i] = 0; } //var startAllocateToBins = Stopwatch.GetTimestamp(); //Allocate subtrees to bins for all axes simultaneously. for (int i = 0; i < count; ++i) { var subtreeIndex = localIndexMap[i]; var binIndices = Vector3.Min((resources.Centroids[subtreeIndex] - centroidBoundingBox.Min) * inverseBinSize, maximumBinIndex); var x = (int)binIndices.X; var y = (int)binIndices.Y; var z = (int)binIndices.Z; resources.SubtreeBinIndicesX[i] = x; resources.SubtreeBinIndicesY[i] = y; resources.SubtreeBinIndicesZ[i] = z; var leafCount = resources.LeafCounts + subtreeIndex; var subtreeBoundingBox = resources.BoundingBoxes + subtreeIndex; resources.BinLeafCountsX[x] += *leafCount; resources.BinLeafCountsY[y] += *leafCount; resources.BinLeafCountsZ[z] += *leafCount; ++resources.BinSubtreeCountsX[x]; ++resources.BinSubtreeCountsY[y]; ++resources.BinSubtreeCountsZ[z]; BoundingBox.CreateMerged(resources.BinBoundingBoxesX[x], *subtreeBoundingBox, out resources.BinBoundingBoxesX[x]); BoundingBox.CreateMerged(resources.BinBoundingBoxesY[y], *subtreeBoundingBox, out resources.BinBoundingBoxesY[y]); BoundingBox.CreateMerged(resources.BinBoundingBoxesZ[z], *subtreeBoundingBox, out resources.BinBoundingBoxesZ[z]); } //Determine split axes for all axes simultaneously. //Sweep from low to high. var lastIndex = binCount - 1; resources.ALeafCountsX[0] = resources.BinLeafCountsX[0]; resources.ALeafCountsY[0] = resources.BinLeafCountsY[0]; resources.ALeafCountsZ[0] = resources.BinLeafCountsZ[0]; resources.AMergedX[0] = resources.BinBoundingBoxesX[0]; resources.AMergedY[0] = resources.BinBoundingBoxesY[0]; resources.AMergedZ[0] = resources.BinBoundingBoxesZ[0]; for (int i = 1; i < lastIndex; ++i) { var previousIndex = i - 1; resources.ALeafCountsX[i] = resources.BinLeafCountsX[i] + resources.ALeafCountsX[previousIndex]; resources.ALeafCountsY[i] = resources.BinLeafCountsY[i] + resources.ALeafCountsY[previousIndex]; resources.ALeafCountsZ[i] = resources.BinLeafCountsZ[i] + resources.ALeafCountsZ[previousIndex]; BoundingBox.CreateMerged(resources.AMergedX[previousIndex], resources.BinBoundingBoxesX[i], out resources.AMergedX[i]); BoundingBox.CreateMerged(resources.AMergedY[previousIndex], resources.BinBoundingBoxesY[i], out resources.AMergedY[i]); BoundingBox.CreateMerged(resources.AMergedZ[previousIndex], resources.BinBoundingBoxesZ[i], out resources.AMergedZ[i]); } //Sweep from high to low. BoundingBox bMergedX = nullBoundingBox; BoundingBox bMergedY = nullBoundingBox; BoundingBox bMergedZ = nullBoundingBox; int bLeafCountX = 0; int bLeafCountY = 0; int bLeafCountZ = 0; int bestAxis = 0; float cost = float.MaxValue; var binSplitIndex = 0; a = nullBoundingBox; b = nullBoundingBox; leafCountA = 0; leafCountB = 0; for (int i = lastIndex; i >= 1; --i) { int aIndex = i - 1; BoundingBox.CreateMerged(bMergedX, resources.BinBoundingBoxesX[i], out bMergedX); BoundingBox.CreateMerged(bMergedY, resources.BinBoundingBoxesY[i], out bMergedY); BoundingBox.CreateMerged(bMergedZ, resources.BinBoundingBoxesZ[i], out bMergedZ); bLeafCountX += resources.BinLeafCountsX[i]; bLeafCountY += resources.BinLeafCountsY[i]; bLeafCountZ += resources.BinLeafCountsZ[i]; //It's possible for a lot of bins in a row to be unpopulated. In that event, the metric isn't defined; don't bother calculating it. float costCandidateX, costCandidateY, costCandidateZ; if (bLeafCountX > 0 && resources.ALeafCountsX[aIndex] > 0) { var metricAX = ComputeBoundsMetric(ref resources.AMergedX[aIndex]); var metricBX = ComputeBoundsMetric(ref bMergedX); costCandidateX = resources.ALeafCountsX[aIndex] * metricAX + bLeafCountX * metricBX; } else { costCandidateX = float.MaxValue; } if (bLeafCountY > 0 && resources.ALeafCountsY[aIndex] > 0) { var metricAY = ComputeBoundsMetric(ref resources.AMergedY[aIndex]); var metricBY = ComputeBoundsMetric(ref bMergedY); costCandidateY = resources.ALeafCountsY[aIndex] * metricAY + bLeafCountY * metricBY; } else { costCandidateY = float.MaxValue; } if (bLeafCountZ > 0 && resources.ALeafCountsZ[aIndex] > 0) { var metricAZ = ComputeBoundsMetric(ref resources.AMergedZ[aIndex]); var metricBZ = ComputeBoundsMetric(ref bMergedZ); costCandidateZ = resources.ALeafCountsZ[aIndex] * metricAZ + bLeafCountZ * metricBZ; } else { costCandidateZ = float.MaxValue; } if (costCandidateX < costCandidateY && costCandidateX < costCandidateZ) { if (costCandidateX < cost) { bestAxis = 0; cost = costCandidateX; binSplitIndex = i; a = resources.AMergedX[aIndex]; b = bMergedX; leafCountA = resources.ALeafCountsX[aIndex]; leafCountB = bLeafCountX; } } else if (costCandidateY < costCandidateZ) { if (costCandidateY < cost) { bestAxis = 1; cost = costCandidateY; binSplitIndex = i; a = resources.AMergedY[aIndex]; b = bMergedY; leafCountA = resources.ALeafCountsY[aIndex]; leafCountB = bLeafCountY; } } else { if (costCandidateZ < cost) { bestAxis = 2; cost = costCandidateZ; binSplitIndex = i; a = resources.AMergedZ[aIndex]; b = bMergedZ; leafCountA = resources.ALeafCountsZ[aIndex]; leafCountB = bLeafCountZ; } } } int *bestBinSubtreeCounts; int *bestSubtreeBinIndices; switch (bestAxis) { case 0: bestBinSubtreeCounts = resources.BinSubtreeCountsX; bestSubtreeBinIndices = resources.SubtreeBinIndicesX; break; case 1: bestBinSubtreeCounts = resources.BinSubtreeCountsY; bestSubtreeBinIndices = resources.SubtreeBinIndicesY; break; default: bestBinSubtreeCounts = resources.BinSubtreeCountsZ; bestSubtreeBinIndices = resources.SubtreeBinIndicesZ; break; } //Rebuild the index map. resources.BinStartIndices[0] = 0; resources.BinSubtreeCountsSecondPass[0] = 0; for (int i = 1; i < binCount; ++i) { resources.BinStartIndices[i] = resources.BinStartIndices[i - 1] + bestBinSubtreeCounts[i - 1]; resources.BinSubtreeCountsSecondPass[i] = 0; } //var startIndexMapTime = Stopwatch.GetTimestamp(); for (int i = 0; i < count; ++i) { var index = bestSubtreeBinIndices[i]; resources.TempIndexMap[resources.BinStartIndices[index] + resources.BinSubtreeCountsSecondPass[index]++] = localIndexMap[i]; } //Update the real index map. for (int i = 0; i < count; ++i) { localIndexMap[i] = resources.TempIndexMap[i]; } //Transform the split index into object indices. splitIndex = resources.BinStartIndices[binSplitIndex] + start; }