scene/tables/points.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // SPDX-License-Identifier: BSD-3-Clause | ||
| 2 | #include "chrono_batch_render/scene/tables/points.h" | ||
| 3 | |||
| 4 | #include <algorithm> | ||
| 5 | #include <bit> | ||
| 6 | |||
| 7 | #include "chrono_batch_render/device/commands.h" | ||
| 8 | #include "chrono_batch_render/device/queue.h" | ||
| 9 | #include "chrono_batch_render/scene/kernels.h" | ||
| 10 | #include "chrono_batch_render/scene/records/render_tables.h" | ||
| 11 | #include "chrono_batch_render/scene/schema/columns.h" | ||
| 12 | #include "chrono_batch_render/scene/tables/kernels/points.h" | ||
| 13 | #include "chrono_batch_render/stage/publish/change_set.h" | ||
| 14 | #include "chrono_batch_render/util/profiler.h" | ||
| 15 | |||
| 16 | namespace chrono::batch_render { | ||
| 17 | |||
| 18 | namespace { | ||
| 19 | |||
| 20 | constexpr ColumnRef kInputs[] = {kPointBasedPoints, kPointBasedNormals, kPointsWidths, kPointsIds}; | ||
| 21 | |||
| 22 | constexpr TableId kTableOutputs[] = {TableId::Points, TableId::PointsMoved}; | ||
| 23 | |||
| 24 | } | ||
| 25 | |||
| 26 | 2456 | std::span<const ColumnRef> PointsTable::Inputs() const { | |
| 27 | 2456 | return kInputs; | |
| 28 | } | ||
| 29 | |||
| 30 | 358 | std::span<const TableId> PointsTable::TableOutputs() const { | |
| 31 | 358 | return kTableOutputs; | |
| 32 | } | ||
| 33 | |||
| 34 | 556 | void PointsTable::Describe(RenderTables& tables) const { | |
| 35 | 556 | tables.points.address = m_records.Buffer() ? m_records.Buffer()->Address() : 0; | |
| 36 | 556 | } | |
| 37 | |||
| 38 | 48 | void PointsTable::Place(uint32_t prim) { | |
| 39 | 48 | const Prims& prims = m_structure.GetPrims(); | |
| 40 | 48 | Cloud cloud; | |
| 41 | 48 | cloud.prim = prim; | |
| 42 | 48 | cloud.generation = prims.Generation(prim); | |
| 43 | 48 | cloud.pointBasedRow = prims.Row(prim, SchemaId::PointBased); | |
| 44 | 48 | cloud.row = prims.Row(prim, SchemaId::Points); | |
| 45 | 48 | m_byPointBasedRow[cloud.pointBasedRow] = prim; | |
| 46 | 48 | m_byRow[cloud.row] = prim; | |
| 47 | 48 | Link(m_clouds[prim] = std::move(cloud)); | |
| 48 | 48 | } | |
| 49 | |||
| 50 | ✗ | void PointsTable::Forget(uint32_t prim) { | |
| 51 | ✗ | Cloud& cloud = m_clouds.at(prim); | |
| 52 | ✗ | cloud.bvh.Release(m_structure.GetColumnStore()); | |
| 53 | ✗ | m_records.Set(cloud.row, PointsRecord{}); | |
| 54 | ✗ | m_byPointBasedRow.erase(cloud.pointBasedRow); | |
| 55 | ✗ | m_byRow.erase(cloud.row); | |
| 56 | ✗ | m_clouds.erase(prim); | |
| 57 | ✗ | } | |
| 58 | |||
| 59 | 167 | void PointsTable::Link(Cloud& cloud) { | |
| 60 | 167 | ColumnStore& store = m_structure.GetColumnStore(); | |
| 61 | 167 | const ArrayRef points = store.Array(kPointBasedPoints, cloud.pointBasedRow); | |
| 62 | 167 | const ArrayRef widths = store.Array(kPointsWidths, cloud.row); | |
| 63 | 167 | const ArrayRef normals = store.Array(kPointBasedNormals, cloud.pointBasedRow); | |
| 64 | 167 | const ArrayRef ids = store.Array(kPointsIds, cloud.row); | |
| 65 | 167 | if (points.count > cloud.capacity || points.count < cloud.capacity / 4) { | |
| 66 | 178 | cloud.capacity = points.count > cloud.capacity | |
| 67 | 98 | ? std::max(points.count, cloud.capacity + cloud.capacity / 2) | |
| 68 | : points.count; | ||
| 69 | 89 | cloud.order = nullptr; | |
| 70 | 89 | cloud.packed = nullptr; | |
| 71 | 89 | device::Device& device = store.GetDevice(); | |
| 72 | 89 | cloud.bvh.Allocate(device, m_builder, | |
| 73 | 89 | (cloud.capacity + m_pointsPerBox - 1) / m_pointsPerBox, "point boxes"); | |
| 74 | 89 | if (cloud.capacity > 0) { | |
| 75 | 81 | if (m_pointsPerBox > 1) { | |
| 76 | 54 | cloud.order = device.CreateBuffer( | |
| 77 | 27 | {.bytes = uint64_t(cloud.capacity) * sizeof(uint32_t), .label = "point order"}); | |
| 78 | 27 | cloud.packed = | |
| 79 | 54 | device.CreateBuffer({.bytes = uint64_t(cloud.capacity) * 4 * sizeof(float), | |
| 80 | 54 | .label = "packed points"}); | |
| 81 | } | ||
| 82 | } | ||
| 83 | } | ||
| 84 | 167 | if (points.count != cloud.count) { | |
| 85 | 126 | cloud.count = points.count; | |
| 86 | 126 | cloud.bvh.fresh = true; | |
| 87 | } | ||
| 88 | 167 | cloud.bvh.Bound(store); | |
| 89 | 167 | cloud.update = points.count > 0; | |
| 90 | 167 | PointsRecord record{}; | |
| 91 | 167 | if (cloud.bvh.blas && points.count > 0) { | |
| 92 | 157 | record.blas = cloud.bvh.blas->Handle(); | |
| 93 | 157 | record.points = points.address; | |
| 94 | 157 | record.widths = widths.address; | |
| 95 | 157 | record.pointCount = points.count; | |
| 96 | 157 | record.widthCount = widths.count; | |
| 97 | 157 | record.normals = normals.address; | |
| 98 | 157 | record.normalCount = normals.count; | |
| 99 | 157 | record.ids = ids.address; | |
| 100 | 157 | record.idCount = ids.count; | |
| 101 | 157 | record.order = cloud.order ? cloud.order->Address() : 0; | |
| 102 | 157 | record.packed = cloud.packed ? cloud.packed->Address() : 0; | |
| 103 | 157 | record.perBox = m_pointsPerBox; | |
| 104 | 157 | record.bounds = cloud.bvh.bounds; | |
| 105 | } | ||
| 106 | 167 | const Prims& prims = m_structure.GetPrims(); | |
| 107 | 167 | if (m_records.Set(cloud.row, record) && prims.Alive(cloud.prim)) { | |
| 108 | 131 | m_relinked.push_back(prims.Record(cloud.prim).world); | |
| 109 | } | ||
| 110 | 248 | } | |
| 111 | |||
| 112 | 556 | void PointsTable::Run(const ChangeSet& changes, TableVersions& versions) { | |
| 113 | 556 | CHRONO_BATCH_RENDER_ZONE("Points/Run"); | |
| 114 | 556 | const Prims& prims = m_structure.GetPrims(); | |
| 115 | 556 | TrackPrims( | |
| 116 | 604 | changes, prims, SchemaId::Points, m_clouds, [&](uint32_t prim) { Place(prim); }, | |
| 117 | ✗ | [&](uint32_t prim) { Forget(prim); }); | |
| 118 | 556 | if (!changes.resync) { | |
| 119 | 1385 | const auto relink = [&](ColumnRef column, const auto& owners) { | |
| 120 | 1246 | for (const uint32_t row : changes.ArrayRows(column)) { | |
| 121 | 138 | const auto owner = owners.find(row); | |
| 122 | 138 | if (owner != owners.end()) { | |
| 123 | 120 | Cloud& cloud = m_clouds.at(owner->second); | |
| 124 | 120 | if (!cloud.update) { | |
| 125 | 119 | Link(cloud); | |
| 126 | } | ||
| 127 | } | ||
| 128 | } | ||
| 129 | 1385 | }; | |
| 130 | 277 | relink(kPointBasedPoints, m_byPointBasedRow); | |
| 131 | 277 | relink(kPointBasedNormals, m_byPointBasedRow); | |
| 132 | 277 | relink(kPointsWidths, m_byRow); | |
| 133 | 277 | relink(kPointsIds, m_byRow); | |
| 134 | } | ||
| 135 | 556 | std::vector<uint32_t> updated; | |
| 136 | 556 | Build(updated); | |
| 137 | 556 | PublishRecords(m_records, m_structure.GetColumnStore(), SchemaId::Points, m_relinked, updated, | |
| 138 | versions, TableId::Points, TableId::PointsMoved); | ||
| 139 | 556 | } | |
| 140 | |||
| 141 | 556 | void PointsTable::Build(std::vector<uint32_t>& updated) { | |
| 142 | 556 | std::vector<PointsJob> jobs; | |
| 143 | 556 | std::vector<PointsJob> sorts; | |
| 144 | 556 | std::vector<Ref<bvh::Blas>> built; | |
| 145 | 556 | std::vector<Ref<bvh::Blas>> refit; | |
| 146 | 556 | uint32_t total = 0; | |
| 147 | 556 | uint32_t keys = 0; | |
| 148 | 556 | uint32_t active = 0; | |
| 149 | 556 | const Prims& prims = m_structure.GetPrims(); | |
| 150 | 912 | for (auto& [prim, cloud] : m_clouds) { | |
| 151 | 178 | if (!cloud.update) { | |
| 152 | 21 | continue; | |
| 153 | } | ||
| 154 | 157 | cloud.update = false; | |
| 155 | 157 | PointsJob job{}; | |
| 156 | 157 | job.record = m_records.Row(cloud.row); | |
| 157 | 157 | job.boxes.address = cloud.bvh.boxes->Address(); | |
| 158 | 157 | job.bounds.address = cloud.bvh.bounds.address; | |
| 159 | 157 | if (cloud.bvh.Rebuilds(m_maxRefits)) { | |
| 160 | 118 | built.push_back(cloud.bvh.blas); | |
| 161 | 118 | if (cloud.order) { | |
| 162 | 42 | PointsJob sort = job; | |
| 163 | 42 | sort.first = keys; | |
| 164 | 42 | sort.capacity = cloud.capacity; | |
| 165 | 42 | sorts.push_back(sort); | |
| 166 | 42 | keys += cloud.capacity; | |
| 167 | } | ||
| 168 | } else { | ||
| 169 | 39 | refit.push_back(cloud.bvh.blas); | |
| 170 | } | ||
| 171 | 157 | job.first = total; | |
| 172 | 157 | job.capacity = (cloud.capacity + m_pointsPerBox - 1) / m_pointsPerBox; | |
| 173 | 157 | jobs.push_back(job); | |
| 174 | 157 | total += job.capacity; | |
| 175 | 157 | active += cloud.count; | |
| 176 | 157 | if (prims.Alive(prim)) { | |
| 177 | 157 | updated.push_back(prims.Record(prim).world); | |
| 178 | } | ||
| 179 | } | ||
| 180 | 556 | m_stats.clouds = uint32_t(m_clouds.size()); | |
| 181 | 556 | m_stats.built = uint32_t(built.size()); | |
| 182 | 556 | m_stats.refit = uint32_t(refit.size()); | |
| 183 | 556 | m_stats.sorted = uint32_t(sorts.size()); | |
| 184 | 556 | m_stats.capacity = total; | |
| 185 | 556 | m_stats.points = active; | |
| 186 | 556 | if (jobs.empty()) { | |
| 187 | 455 | return; | |
| 188 | } | ||
| 189 | 101 | ColumnStore& store = m_structure.GetColumnStore(); | |
| 190 | 101 | device::Device& device = store.GetDevice(); | |
| 191 | 101 | store.Flush(); | |
| 192 | 101 | const Ref<device::Buffer> table = | |
| 193 | 101 | device.CreateBuffer({.label = "points jobs"}, std::span<const PointsJob>(jobs)); | |
| 194 | 101 | const uint64_t extentWords = uint64_t(jobs.size()) * 3; | |
| 195 | 101 | const Ref<device::Buffer> extents = device.CreateBuffer( | |
| 196 | 101 | {.bytes = extentWords * 2 * sizeof(uint32_t), .label = "points extents"}); | |
| 197 | 101 | PointsParams params{}; | |
| 198 | 101 | params.jobs.address = table->Address(); | |
| 199 | 101 | params.extents.address = extents->Address(); | |
| 200 | 101 | params.jobCount = uint32_t(jobs.size()); | |
| 201 | 101 | params.total = total; | |
| 202 | 101 | static const profiler::ZoneId boxesZone = profiler::ZoneId::Named("Points/Boxes"); | |
| 203 | 101 | static const profiler::ZoneId blasZone = profiler::ZoneId::Named("Points/Blas"); | |
| 204 | 101 | device::Queue& queue = device.GetQueue(); | |
| 205 | 101 | device::CommandEncoder encoder = queue.CreateEncoder(); | |
| 206 | 101 | if (!sorts.empty()) { | |
| 207 | 24 | static const profiler::ZoneId sortZone = profiler::ZoneId::Named("Points/Sort"); | |
| 208 | 24 | if (keys > m_sortCapacity) { | |
| 209 | 11 | m_sortCapacity = std::bit_ceil(keys); | |
| 210 | 11 | m_sort = std::make_unique<algorithms::RadixSort>(device, m_sortCapacity, 8); | |
| 211 | 22 | m_keys = device.CreateBuffer( | |
| 212 | 11 | {.bytes = uint64_t(m_sortCapacity) * 2 * sizeof(uint32_t), .label = "point keys"}); | |
| 213 | 22 | m_values = device.CreateBuffer( | |
| 214 | 11 | {.bytes = uint64_t(m_sortCapacity) * sizeof(uint32_t), .label = "point values"}); | |
| 215 | } | ||
| 216 | 24 | const Ref<device::Buffer> sortTable = | |
| 217 | 24 | device.CreateBuffer({.label = "points sorts"}, std::span<const PointsJob>(sorts)); | |
| 218 | 24 | const uint64_t sortWords = uint64_t(sorts.size()) * 3; | |
| 219 | 24 | const Ref<device::Buffer> sortExtents = device.CreateBuffer( | |
| 220 | 24 | {.bytes = sortWords * 2 * sizeof(uint32_t), .label = "points sort extents"}); | |
| 221 | 24 | PointsParams sortParams{}; | |
| 222 | 24 | sortParams.jobs.address = sortTable->Address(); | |
| 223 | 24 | sortParams.extents.address = sortExtents->Address(); | |
| 224 | 24 | sortParams.keys.address = m_keys->Address(); | |
| 225 | 24 | sortParams.values.address = m_values->Address(); | |
| 226 | 24 | sortParams.jobCount = uint32_t(sorts.size()); | |
| 227 | 24 | sortParams.total = keys; | |
| 228 | 24 | const device::Size3 groups{.x = device::GroupCount(keys, kPointsGroup)}; | |
| 229 | 24 | encoder.BeginZone(sortZone); | |
| 230 | 24 | encoder.Fill(sortExtents->Slice(0, sortWords * sizeof(uint32_t)), ~0u); | |
| 231 | 24 | encoder.Fill(sortExtents->Slice(sortWords * sizeof(uint32_t)), 0u); | |
| 232 | 24 | encoder.Dispatch(Kernels().Entry("ReducePointExtents"), sortParams, groups); | |
| 233 | 24 | encoder.Dispatch(Kernels().Entry("FinishPointBounds"), sortParams, | |
| 234 | 24 | {.x = device::GroupCount(sortParams.jobCount, kPointsGroup)}); | |
| 235 | 24 | encoder.Dispatch(Kernels().Entry("WritePointKeys"), sortParams, groups); | |
| 236 | 42 | m_sort->Sort(encoder, m_keys->Slice(0, uint64_t(keys) * 2 * sizeof(uint32_t)), | |
| 237 | m_values->Slice(0, uint64_t(keys) * sizeof(uint32_t)), keys, | ||
| 238 | 42 | 32 + std::bit_width(sortParams.jobCount - 1)); | |
| 239 | 24 | encoder.Dispatch(Kernels().Entry("StorePointOrder"), sortParams, groups); | |
| 240 | 24 | encoder.EndZone(); | |
| 241 | 48 | } | |
| 242 | 101 | encoder.BeginZone(boxesZone); | |
| 243 | 101 | encoder.Fill(extents->Slice(0, extentWords * sizeof(uint32_t)), ~0u); | |
| 244 | 101 | encoder.Fill(extents->Slice(extentWords * sizeof(uint32_t)), 0u); | |
| 245 | 101 | encoder.Dispatch(Kernels().Entry("WritePointBoxes"), params, | |
| 246 | 101 | {.x = device::GroupCount(total, kPointsGroup)}); | |
| 247 | 101 | encoder.Dispatch(Kernels().Entry("FinishPointBounds"), params, | |
| 248 | 101 | {.x = device::GroupCount(params.jobCount, kPointsGroup)}); | |
| 249 | 101 | encoder.EndZone(); | |
| 250 | 101 | encoder.BeginZone(blasZone); | |
| 251 | 101 | m_builder.Build(encoder, built); | |
| 252 | 101 | m_builder.Refit(encoder, refit); | |
| 253 | 101 | encoder.EndZone(); | |
| 254 | 101 | queue.Submit(encoder.Finish()); | |
| 255 | 1155 | } | |
| 256 | |||
| 257 | 951 | void PointsTable::Hold(std::vector<Ref<const RefCounted>>& held) const { | |
| 258 | 3804 | HoldBuffers(held, {m_records.Buffer(), m_keys, m_values}); | |
| 259 | 1387 | for (const auto& [prim, cloud] : m_clouds) { | |
| 260 | 218 | cloud.bvh.Hold(held); | |
| 261 | 218 | if (cloud.bvh.blas) { | |
| 262 | 210 | if (cloud.order) { | |
| 263 | 73 | held.emplace_back(cloud.order); | |
| 264 | 73 | held.emplace_back(cloud.packed); | |
| 265 | } | ||
| 266 | } | ||
| 267 | } | ||
| 268 | 1902 | } | |
| 269 | |||
| 270 | 353 | void PointsTable::Prepare() { | |
| 271 | 353 | Kernels(); | |
| 272 | 353 | } | |
| 273 | |||
| 274 | 651 | device::Program& PointsTable::Kernels() { | |
| 275 | 651 | if (!m_program) { | |
| 276 | 1074 | m_program = m_structure.GetColumnStore().GetDevice().CreateProgram( | |
| 277 | {.modules = {"chrono_batch_render.scene.tables.kernels.points"}, | ||
| 278 | .library = &kSceneKernels, | ||
| 279 | .entries = {"ReducePointExtents", "WritePointKeys", "StorePointOrder", | ||
| 280 | "WritePointBoxes", "FinishPointBounds"}, | ||
| 281 | 716 | .label = "points"}); | |
| 282 | } | ||
| 283 | 651 | return *m_program; | |
| 284 | 716 | } | |
| 285 | |||
| 286 | } | ||
| 287 |