|
17 | 17 | of an OnIndexGrid, independent of occupancy. This file provides: |
18 | 18 | - buildVoxelBlockManager (device): constructs the firstLeafID array and |
19 | 19 | jumpMap on the GPU from a device-resident NanoGrid. |
20 | | - - decodeInverseMaps (device): per-block SIMT decode of the inverse maps |
21 | | - (sequential active-voxel index -> leaf ID + intra-leaf voxel offset), |
22 | | - with one thread per output slot across a CUDA thread block. |
| 20 | + - decodeInverseMap (device): per-slot, thread-local decode of one inverse |
| 21 | + map entry (sequential active-voxel index -> leaf ID + intra-leaf voxel |
| 22 | + offset) into registers; no shared memory or synchronization, callable |
| 23 | + from divergent threads. Block-level facts a cooperative consumer might |
| 24 | + otherwise read from a materialized map are derivable directly from the |
| 25 | + VBM metadata: the block's first leaf is firstLeafID, slot p starts a |
| 26 | + new leaf iff jumpMap bit p is set, and the number of leaves spanned by |
| 27 | + the block is 1 + the jumpMap popcount. |
23 | 28 | */ |
24 | 29 |
|
25 | 30 | #ifndef NANOVDB_VOXELBLOCKMANAGER_CUH_HAS_BEEN_INCLUDED |
@@ -48,138 +53,153 @@ struct VoxelBlockManager : nanovdb::tools::VoxelBlockManagerBase<Log2BlockWidth> |
48 | 53 | using Base::UnusedLeafIndex; |
49 | 54 | using Base::UnusedVoxelOffset; |
50 | 55 |
|
51 | | - // The efficiency of the functions in this class are contingent on |
52 | | - // threadblock-level coordination, which manifests either as using shared |
53 | | - // memory for synchronization, or warp-level shift operations. |
| 56 | + // The decode is a rank + select over the VBM's bit-vectors and is |
| 57 | + // thread-local per output slot: decodeInverseMap requires no threadblock |
| 58 | + // coordination. Consumers that need cross-slot information derive it from |
| 59 | + // the VBM metadata (firstLeafID + jumpMap) rather than from a materialized |
| 60 | + // per-block map. |
54 | 61 |
|
55 | | - /// @brief Decode the inverse maps for a single voxel block on the device. |
| 62 | + /// @brief Rank one output slot into its leaf: the number of leaves that |
| 63 | + /// begin at in-block positions [1, blockOffset] (bit 0 is never set), |
| 64 | + /// counted via popcounts over the block's jumpMap. |
| 65 | + /// Thread-local; no synchronization. |
| 66 | + __device__ |
| 67 | + static uint32_t jumpMapRank(const uint64_t *jumpMap, const int blockOffset) |
| 68 | + { |
| 69 | + uint32_t leafRank = 0; |
| 70 | + const int jumpWord = blockOffset >> 6; // index into jumpMap |
| 71 | + #pragma unroll |
| 72 | + for (int i = 0; i < JumpMapLength; ++i) { |
| 73 | + if (i < jumpWord) leafRank += util::countOn(jumpMap[i]); // count leaves before the current jump word |
| 74 | + else if (i == jumpWord) { |
| 75 | + // count leaves in the current jump word, masking those that are at or before blockOffset |
| 76 | + leafRank += util::countOn(jumpMap[i] & ((uint64_t(2) << (blockOffset & 63)) - 1u)); |
| 77 | + } |
| 78 | + } |
| 79 | + return leafRank; |
| 80 | + } |
| 81 | + |
| 82 | + /// @brief Select the voxel with sequential index @c globalOffset inside leaf |
| 83 | + /// @c leafID: find its 64-bit mask word via the leaf's precomputed 9-bit |
| 84 | + /// prefix sums (mPrefixSum), then its bit within that word via __fns. |
| 85 | + /// Writes the sentinels if globalOffset lies beyond the leaf's last active |
| 86 | + /// voxel (i.e. beyond the last active voxel of the grid). |
| 87 | + /// Thread-local; no synchronization. |
| 88 | + template <class BuildT> |
| 89 | + __device__ |
| 90 | + static typename util::enable_if<BuildTraits<BuildT>::is_index, void>::type |
| 91 | + selectVoxelInLeaf( |
| 92 | + const NanoGrid<BuildT> *grid, |
| 93 | + const uint32_t leafID, |
| 94 | + const uint64_t globalOffset, |
| 95 | + uint32_t &leafIndex, |
| 96 | + uint16_t &voxelOffset) |
| 97 | + { |
| 98 | + const auto& leafData = *grid->tree().template getFirstNode<0>()[leafID].data(); |
| 99 | + // 0-based rank among the leaf's actives |
| 100 | + const uint64_t rankInLeaf = globalOffset - leafData.firstOffset(); |
| 101 | + if (rankInLeaf < leafData.valueCount()) { // if the rank is within the leaf's active voxels (bounds check) |
| 102 | + int wordID = 0; |
| 103 | + uint32_t activesBeforeWord = 0; |
| 104 | + #pragma unroll |
| 105 | + for (int candidateWord = 1; candidateWord < 8; ++candidateWord) { |
| 106 | + // the number of active voxels before the candidateWord's mask word (& 0x1ffu masks to 9 bits) |
| 107 | + const uint32_t cumulative = uint32_t(leafData.mPrefixSum >> (9*(candidateWord-1))) & 0x1ffu; |
| 108 | + if (cumulative <= rankInLeaf) { |
| 109 | + wordID = candidateWord; // word ID of the mask word that contains the rankInLeaf-th active voxel |
| 110 | + activesBeforeWord = cumulative; // the number of active voxels before the wordID's mask word |
| 111 | + } |
| 112 | + } |
| 113 | + uint32_t rankInWord = uint32_t(rankInLeaf) - activesBeforeWord; // active voxel's rank within the mask word (0-based) |
| 114 | + // select the in-word bit position of the voxel using __fns (find n-th set bit) |
| 115 | + // __fns(mask, base, k) is the hardware find-nth-set-bit intrinsic - the k-th set bit (1-based) at/after base |
| 116 | + // but it's a 32-bit op while `maskWord` is 64-bit, so we need to split it into two 32-bit halves |
| 117 | + const uint64_t maskWord = leafData.mValueMask.words()[wordID]; |
| 118 | + const uint32_t lowHalf = uint32_t(maskWord); // low 32 bits of the mask word |
| 119 | + const uint32_t lowHalfCount = __popc(lowHalf); |
| 120 | + int bit; |
| 121 | + // if rank is less than the number of active voxels in the low half, __fns finds the bit in the lower half |
| 122 | + // otherwise, shift maskWord by 32 bits and __fns finds the bit in the upper half |
| 123 | + if (rankInWord < lowHalfCount) bit = __fns(lowHalf, 0, rankInWord + 1); |
| 124 | + else bit = 32 + __fns(uint32_t(maskWord >> 32), 0, rankInWord - lowHalfCount + 1); |
| 125 | + leafIndex = leafID; |
| 126 | + voxelOffset = uint16_t((wordID << 6) + bit); |
| 127 | + } else { // beyond the last active voxel in the grid |
| 128 | + leafIndex = UnusedLeafIndex; |
| 129 | + voxelOffset = UnusedVoxelOffset; |
| 130 | + } |
| 131 | + } |
| 132 | + |
| 133 | + /// @brief Decode a single inverse-map entry into registers on the device. |
56 | 134 | /// |
57 | 135 | /// Given the VBM metadata for one block (firstLeafID and the block's slice of |
58 | | - /// the jumpMap) and the block's base sequential offset, fills smem_leafIndex[] |
59 | | - /// and smem_voxelOffset[] in shared memory so that for each position p in |
60 | | - /// [0, BlockWidth): |
61 | | - /// - smem_leafIndex[p] = index of the leaf node containing sequential voxel |
62 | | - /// (blockFirstOffset + p), or UnusedLeafIndex if that |
63 | | - /// index is beyond the last active voxel. |
64 | | - /// - smem_voxelOffset[p] = local (0..511) offset of that voxel within its leaf, |
65 | | - /// or UnusedVoxelOffset. |
| 136 | + /// the jumpMap), the block's base sequential offset, and a slot position |
| 137 | + /// blockOffset in [0, BlockWidth), computes: |
| 138 | + /// - leafIndex = index of the leaf node containing sequential voxel |
| 139 | + /// (blockFirstOffset + blockOffset), or UnusedLeafIndex if |
| 140 | + /// that index is beyond the last active voxel. |
| 141 | + /// - voxelOffset = local (0..511) offset of that voxel within its leaf, |
| 142 | + /// or UnusedVoxelOffset. |
66 | 143 | /// |
67 | | - /// Must be called by all threads in the block (uses __syncthreads internally). |
68 | | - /// Do not call from divergent threads within a thread block. |
| 144 | + /// No shared memory or synchronization; may be called from divergent threads. |
69 | 145 | /// |
70 | 146 | /// @tparam BuildT Build type of the grid (must be an index type) |
71 | 147 | /// @param grid Device-accessible OnIndex grid |
72 | 148 | /// @param firstLeafID Index of the first leaf overlapping this block |
73 | 149 | /// @param jumpMap Pointer to the JumpMapLength words for this block |
74 | 150 | /// @param blockFirstOffset Sequential index of the first voxel in this block |
75 | | - /// @param smem_leafIndex Output array of length BlockWidth in shared memory |
76 | | - /// @param smem_voxelOffset Output array of length BlockWidth in shared memory |
| 151 | + /// @param blockOffset Slot position within the block, in [0, BlockWidth) |
| 152 | + /// @param leafIndex Output leaf index (register) |
| 153 | + /// @param voxelOffset Output intra-leaf voxel offset (register) |
77 | 154 | template <class BuildT> |
78 | 155 | __device__ |
79 | 156 | static typename util::enable_if<BuildTraits<BuildT>::is_index, void>::type |
80 | | - decodeInverseMaps( |
| 157 | + decodeInverseMap( |
81 | 158 | const NanoGrid<BuildT> *grid, |
82 | 159 | const uint32_t firstLeafID, |
83 | 160 | const uint64_t *jumpMap, |
84 | 161 | const uint64_t blockFirstOffset, |
85 | | - uint32_t *smem_leafIndex, |
86 | | - uint16_t *smem_voxelOffset) |
| 162 | + const int blockOffset, |
| 163 | + uint32_t &leafIndex, |
| 164 | + uint16_t &voxelOffset) |
87 | 165 | { |
88 | 166 | // Verify that the nodes can be accessed linearly |
89 | 167 | NANOVDB_ASSERT(grid->isSequential()); |
90 | | - NANOVDB_ASSERT(blockDim.x <= 512); |
91 | | - |
92 | | - // Select-based decode: one thread per output slot. Here each |
93 | | - // slot ranks itself into its leaf via the jumpMap popcount, |
94 | | - // then locates its voxel with the leaf's 9-bit prefix sums |
95 | | - // plus an in-word bit select - O(1) per slot. |
96 | | - const int tID = threadIdx.x; |
97 | | - const auto *leaf0 = grid->tree().template getFirstNode<0>(); |
98 | | - for (int blockOffset = tID; blockOffset < BlockWidth; blockOffset += blockDim.x) { |
99 | | - // rank this slot into its leaf: count leaves beginning at in-block positions |
100 | | - // [1, blockOffset] (bit 0 is never set) via the jumpMap popcount |
101 | | - uint32_t leafRank = 0; |
102 | | - const int jumpWord = blockOffset >> 6; // index into jumpMap |
103 | | - // count the number of leaves that begin before blockOffset |
104 | | - #pragma unroll |
105 | | - for (int i = 0; i < JumpMapLength; ++i) { |
106 | | - if (i < jumpWord) leafRank += util::countOn(jumpMap[i]); // count leaves before the current jump word |
107 | | - else if (i == jumpWord) { |
108 | | - // count leaves in the current jump word, masking those that are before blockOffset |
109 | | - leafRank += util::countOn(jumpMap[i] & ((uint64_t(2) << (blockOffset & 63)) - 1u)); |
110 | | - } |
| 168 | + NANOVDB_ASSERT(blockOffset >= 0 && blockOffset < BlockWidth); |
111 | 169 |
|
112 | | - } |
113 | | - const uint32_t leafID = firstLeafID + leafRank; |
114 | | - const auto& leafData = *leaf0[leafID].data(); |
115 | | - // 0-based rank among the leaf's actives |
116 | | - const uint64_t rankInLeaf = (blockFirstOffset + blockOffset) - leafData.firstOffset(); |
117 | | - if (rankInLeaf < leafData.valueCount()) { // if the rank is within the leaf's active voxels (bounds check) |
118 | | - // select the rankInLeaf-th active voxel: find its 64-bit mask word via the |
119 | | - // leaf's 9-bit prefix sums, then its bit within that word |
120 | | - int wordID = 0; |
121 | | - uint32_t activesBeforeWord = 0; |
122 | | - #pragma unroll |
123 | | - for (int candidateWord = 1; candidateWord < 8; ++candidateWord) { |
124 | | - // the number of active voxels before the candidateWord's mask word (& 0x1ffu masks to 9 bits) |
125 | | - const uint32_t cumulative = uint32_t(leafData.mPrefixSum >> (9*(candidateWord-1))) & 0x1ffu; |
126 | | - if (cumulative <= rankInLeaf) { |
127 | | - wordID = candidateWord; // word ID of the mask word that contains the rankInLeaf-th active voxel |
128 | | - activesBeforeWord = cumulative; // the number of active voxels before the wordID's mask word |
129 | | - } |
130 | | - } |
131 | | - |
132 | | - uint32_t rankInWord = uint32_t(rankInLeaf) - activesBeforeWord; // active voxel's rank within the mask word (0-based) |
133 | | - // select the in-word bit position of the voxel using __fns (find n-th set bit) |
134 | | - // /__fns(mask, base, k) is the hardware find-nth-set-bit intrinsic - the k-th set bit (1-based) at/after base |
135 | | - // but it's a 32-bit op while `maskWord` is 64-bit, so we need to split it into two 32-bit halves |
136 | | - const uint64_t maskWord = leafData.mValueMask.words()[wordID]; |
137 | | - const uint32_t lowHalf = uint32_t(maskWord); // low 32 bits of the mask word |
138 | | - const uint32_t lowHalfCount = util::countOn(uint64_t(lowHalf)); |
139 | | - int bit; |
140 | | - // if rank is less than the number of active voxels in the low half, __fns finds the bit in the lower half |
141 | | - // otherwise, shift maskWord by 32 bits and __fns finds the bit in the upper half |
142 | | - if (rankInWord < lowHalfCount) bit = __fns(lowHalf, 0, rankInWord + 1); |
143 | | - else bit = 32 + __fns(uint32_t(maskWord >> 32), 0, rankInWord - lowHalfCount + 1); |
144 | | - smem_leafIndex[blockOffset] = leafID; |
145 | | - smem_voxelOffset[blockOffset] = uint16_t((wordID << 6) + bit); |
146 | | - } else {// beyond the last active voxel in the grid |
147 | | - smem_leafIndex[blockOffset] = UnusedLeafIndex; |
148 | | - smem_voxelOffset[blockOffset] = UnusedVoxelOffset; |
149 | | - } |
150 | | - } |
151 | | - __syncthreads(); |
| 170 | + const uint32_t leafRank = jumpMapRank(jumpMap, blockOffset); |
| 171 | + selectVoxelInLeaf(grid, firstLeafID + leafRank, |
| 172 | + blockFirstOffset + blockOffset, leafIndex, voxelOffset); |
152 | 173 | } |
153 | 174 |
|
154 | | - /// @brief Given a grid and its decoded voxel map, compute the stencil. |
155 | | - /// This function accesses shared memory but does not synchronize threads |
156 | | - /// so it may be called from divergent threads within a thread block. |
157 | | - /// offsets for a 3x3x3 box stencil. |
| 175 | + /// @brief Given a grid and one decoded inverse-map entry (from |
| 176 | + /// decodeInverseMap), compute the stencil indices for a 3x3x3 box stencil. |
| 177 | + /// Thread-local: no shared memory, no synchronization; may be called from |
| 178 | + /// divergent threads within a thread block. Leaves stencilIndices untouched |
| 179 | + /// when leafIndex is UnusedLeafIndex (a slot beyond the last active voxel). |
158 | 180 | /// @tparam BuildT Build type of the grid |
159 | | - /// @param grid |
160 | | - /// @param smem_leafIndex Leaf indices stored in shared memory |
161 | | - /// @param smem_voxelOffset Voxel offsets stored in shared memory |
| 181 | + /// @param grid Device-accessible OnIndex grid |
| 182 | + /// @param leafIndex This thread's decoded leaf index (or UnusedLeafIndex) |
| 183 | + /// @param voxelOffset This thread's decoded intra-leaf voxel offset |
162 | 184 | /// @param stencilIndices Pointer to output stencil indices. Must have |
163 | 185 | /// length of at least 27 (corresponding to the 3x3x3 stencil) |
164 | 186 | template <class BuildT> |
165 | 187 | __device__ |
166 | 188 | static typename util::enable_if<BuildTraits<BuildT>::is_index, void>::type |
167 | 189 | computeBoxStencil( |
168 | 190 | const NanoGrid<BuildT> *grid, |
169 | | - const uint32_t *smem_leafIndex, |
170 | | - const uint16_t *smem_voxelOffset, |
| 191 | + const uint32_t leafIndex, |
| 192 | + const uint16_t voxelOffset, |
171 | 193 | uint64_t *stencilIndices) |
172 | 194 | { |
173 | 195 | // Verify that the nodes can be accessed linearly |
174 | 196 | NANOVDB_ASSERT(grid->isSequential()); |
175 | 197 |
|
176 | | - int tID = threadIdx.x; |
177 | 198 | const auto& tree = grid->tree(); |
178 | | - if (smem_leafIndex[tID] != UnusedLeafIndex) { |
| 199 | + if (leafIndex != UnusedLeafIndex) { |
179 | 200 | // This presumes that leaf nodes are fixed-size and sequentially accessible in memory |
180 | | - const auto& leaf = tree.template getFirstNode<0>()[ smem_leafIndex[tID] ]; |
181 | | - const Coord coord = leaf.offsetToGlobalCoord( smem_voxelOffset[tID] ); |
182 | | - const auto index = leaf.getValue( smem_voxelOffset[tID] ); |
| 201 | + const auto& leaf = tree.template getFirstNode<0>()[ leafIndex ]; |
| 202 | + const Coord coord = leaf.offsetToGlobalCoord( voxelOffset ); |
183 | 203 | for (int di = -1; di <= 1; di++) |
184 | 204 | for (int dj = -1; dj <= 1; dj++) |
185 | 205 | for (int dk = -1; dk <= 1; dk++) { |
|
0 commit comments