scene/tables/meshes.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // SPDX-License-Identifier: BSD-3-Clause | ||
| 2 | #include "chrono_batch_render/scene/tables/meshes.h" | ||
| 3 | |||
| 4 | #include <algorithm> | ||
| 5 | #include <bit> | ||
| 6 | #include <span> | ||
| 7 | |||
| 8 | #include "chrono_batch_render/device/commands.h" | ||
| 9 | #include "chrono_batch_render/device/queue.h" | ||
| 10 | #include "chrono_batch_render/scene/kernels.h" | ||
| 11 | #include "chrono_batch_render/scene/records/render_tables.h" | ||
| 12 | #include "chrono_batch_render/scene/schema/columns.h" | ||
| 13 | #include "chrono_batch_render/scene/tables/kernels/meshes.h" | ||
| 14 | #include "chrono_batch_render/stage/publish/change_set.h" | ||
| 15 | #include "chrono_batch_render/util/profiler.h" | ||
| 16 | |||
| 17 | namespace chrono::batch_render { | ||
| 18 | |||
| 19 | namespace { | ||
| 20 | |||
| 21 | constexpr uint32_t kPreparedScan = 1024; | ||
| 22 | |||
| 23 | constexpr ColumnRef kInputs[] = {kPointBasedPoints, kMeshFaceVertexIndices, kMeshFaceVertexCounts, | ||
| 24 | kMeshHoleIndices}; | ||
| 25 | |||
| 26 | constexpr TableId kTableOutputs[] = {TableId::Meshes, TableId::MeshPoints}; | ||
| 27 | |||
| 28 | } | ||
| 29 | |||
| 30 | 2616 | std::span<const ColumnRef> MeshTable::Inputs() const { | |
| 31 | 2616 | return kInputs; | |
| 32 | } | ||
| 33 | |||
| 34 | 399 | std::span<const TableId> MeshTable::TableOutputs() const { | |
| 35 | 399 | return kTableOutputs; | |
| 36 | } | ||
| 37 | |||
| 38 | 592 | void MeshTable::Describe(RenderTables& tables) const { | |
| 39 | 592 | tables.meshes.address = m_records.Buffer() ? m_records.Buffer()->Address() : 0; | |
| 40 | 592 | } | |
| 41 | |||
| 42 | 17 | std::optional<MeshShape> MeshTable::Find(uint32_t prim) const { | |
| 43 | 17 | const auto found = m_meshes.find(prim); | |
| 44 | 33 | if (found == m_meshes.end() || !found->second.key) { | |
| 45 | 4 | return std::nullopt; | |
| 46 | } | ||
| 47 | 13 | const Shape& shape = m_shapes.at(*found->second.key); | |
| 48 | 13 | const Topology& topology = m_topologies.at(found->second.key->topology); | |
| 49 | 13 | return MeshShape{shape.blas, shape.pointCount, topology.triangleCount, shape.users}; | |
| 50 | } | ||
| 51 | |||
| 52 | 592 | void MeshTable::Triangulate() { | |
| 53 | 592 | std::vector<Topology*> pending; | |
| 54 | 592 | std::vector<TopologyJob> jobs; | |
| 55 | 592 | uint32_t faces = 0; | |
| 56 | 592 | uint32_t holes = 0; | |
| 57 | 734 | for (auto& [key, topology] : m_topologies) { | |
| 58 | 142 | if (topology.ready) { | |
| 59 | 44 | continue; | |
| 60 | } | ||
| 61 | 98 | TopologyJob job{}; | |
| 62 | 98 | job.indices.address = key.indices; | |
| 63 | 98 | job.counts.address = key.counts; | |
| 64 | 98 | job.holes.address = key.holes; | |
| 65 | 98 | job.indexCount = key.indexCount; | |
| 66 | 98 | job.faceCount = key.faceCount; | |
| 67 | 98 | job.holeCount = key.holeCount; | |
| 68 | 98 | job.firstFace = faces; | |
| 69 | 98 | faces += key.faceCount; | |
| 70 | 98 | holes = std::max(holes, key.holeCount); | |
| 71 | 98 | jobs.push_back(job); | |
| 72 | 98 | pending.push_back(&topology); | |
| 73 | } | ||
| 74 | 592 | if (jobs.empty()) { | |
| 75 | return; | ||
| 76 | } | ||
| 77 | 94 | CHRONO_BATCH_RENDER_ZONE("Geometry/Triangulate"); | |
| 78 | 94 | m_stats.triangulated += uint32_t(jobs.size()); | |
| 79 | 94 | ColumnStore& store = m_structure.GetColumnStore(); | |
| 80 | 94 | device::Device& device = store.GetDevice(); | |
| 81 | 94 | device::Queue& queue = device.GetQueue(); | |
| 82 | 940 | const auto scratch = [&](uint64_t words, const char* label) { | |
| 83 | 846 | return device.CreateBuffer({.bytes = std::max<uint64_t>(words, 1) * 4, .label = label}); | |
| 84 | 1692 | }; | |
| 85 | 376 | const auto scan = [&](uint32_t count) -> const algorithms::Scan& { | |
| 86 | 282 | if (count > m_scanCapacity) { | |
| 87 | 78 | m_scanCapacity = std::bit_ceil(count); | |
| 88 | 78 | m_scan = std::make_unique<algorithms::Scan>(device, m_scanCapacity); | |
| 89 | } | ||
| 90 | 282 | return *m_scan; | |
| 91 | 94 | }; | |
| 92 | 658 | const auto dispatch = [&](device::CommandEncoder& encoder, const char* entry, | |
| 93 | const TriangulateParams& params, uint32_t threads) { | ||
| 94 | 564 | encoder.Dispatch(Kernels().Entry(entry), params, | |
| 95 | 564 | {.x = device::GroupCount(threads, kTopologyGroup)}); | |
| 96 | 658 | }; | |
| 97 | |||
| 98 | 94 | const Ref<device::Buffer> jobBuffer = | |
| 99 | 94 | device.CreateBuffer({.label = "topology jobs"}, std::span<const TopologyJob>(jobs)); | |
| 100 | 94 | const Ref<device::Buffer> work = scratch(faces, "face work"); | |
| 101 | 94 | const Ref<device::Buffer> holeFlags = scratch(faces, "face holes"); | |
| 102 | 94 | const Ref<device::Buffer> faceStarts = scratch(faces, "face starts"); | |
| 103 | 94 | const Ref<device::Buffer> triangleStarts = scratch(faces, "face triangle starts"); | |
| 104 | 94 | const Ref<device::Buffer> sizes = scratch(jobs.size() * 2, "topology sizes"); | |
| 105 | 94 | TriangulateParams params{}; | |
| 106 | 94 | params.jobs.address = jobBuffer->Address(); | |
| 107 | 94 | params.work.address = work->Address(); | |
| 108 | 94 | params.holes.address = holeFlags->Address(); | |
| 109 | 94 | params.faceStarts.address = faceStarts->Address(); | |
| 110 | 94 | params.triangleStarts.address = triangleStarts->Address(); | |
| 111 | 94 | params.sizes.address = sizes->Address(); | |
| 112 | 94 | params.jobCount = uint32_t(jobs.size()); | |
| 113 | 94 | params.faceCount = faces; | |
| 114 | 94 | params.holeCount = holes; | |
| 115 | 94 | static const profiler::ZoneId zone = profiler::ZoneId::Named("Geometry/Triangulate"); | |
| 116 | 94 | { | |
| 117 | 94 | device::CommandEncoder encoder = queue.CreateEncoder(); | |
| 118 | 94 | { | |
| 119 | 94 | const device::ZoneScope scope(encoder, zone); | |
| 120 | 94 | encoder.Fill(*sizes); | |
| 121 | 94 | encoder.Fill(*holeFlags); | |
| 122 | 94 | if (holes > 0) { | |
| 123 | 4 | encoder.Dispatch( | |
| 124 | 2 | Kernels().Entry("MarkHoles"), params, | |
| 125 | 2 | {.x = device::GroupCount(holes, kTopologyGroup), .y = params.jobCount}); | |
| 126 | } | ||
| 127 | 94 | dispatch(encoder, "FaceSizes", params, faces); | |
| 128 | 94 | scan(faces).ExclusiveSum(encoder, *work, *faceStarts, faces); | |
| 129 | 94 | dispatch(encoder, "FaceTriangles", params, faces); | |
| 130 | 94 | scan(faces).ExclusiveSum(encoder, *work, *triangleStarts, faces); | |
| 131 | 94 | dispatch(encoder, "TopologySizes", params, params.jobCount); | |
| 132 | 94 | } | |
| 133 | 94 | queue.Submit(encoder.Finish()); | |
| 134 | ✗ | } | |
| 135 | 94 | std::vector<uint32_t> counted(jobs.size() * 2); | |
| 136 | 94 | queue.Read(sizes->Slice(0, counted.size() * 4), counted.data()); | |
| 137 | |||
| 138 | 94 | ArrayPool& ints = store.Arrays(ColumnType::IntArray); | |
| 139 | 94 | std::vector<TopologyJob> emitted; | |
| 140 | 94 | uint32_t corners = 0; | |
| 141 | 94 | uint32_t points = 0; | |
| 142 | 192 | for (uint32_t i = 0; i < jobs.size(); ++i) { | |
| 143 | 98 | Topology& topology = *pending[i]; | |
| 144 | 98 | topology.ready = true; | |
| 145 | 98 | topology.triangleCount = counted[i * 2]; | |
| 146 | 98 | topology.covered = counted[i * 2 + 1]; | |
| 147 | 98 | if (topology.triangleCount == 0) { | |
| 148 | ✗ | continue; | |
| 149 | } | ||
| 150 | 98 | const uint32_t count = topology.triangleCount; | |
| 151 | 98 | topology.triangles = ints.Allocate(count * 3); | |
| 152 | 98 | topology.corners = ints.Allocate(count * 3); | |
| 153 | 98 | topology.faces = ints.Allocate(count); | |
| 154 | 98 | topology.starts = ints.Allocate(topology.covered + 1); | |
| 155 | 98 | topology.adjacency = ints.Allocate(count * 3); | |
| 156 | 98 | TopologyJob& job = jobs[i]; | |
| 157 | 98 | job.triangles.address = topology.triangles.address; | |
| 158 | 98 | job.corners.address = topology.corners.address; | |
| 159 | 98 | job.faces.address = topology.faces.address; | |
| 160 | 98 | job.starts.address = topology.starts.address; | |
| 161 | 98 | job.adjacency.address = topology.adjacency.address; | |
| 162 | 98 | job.triangleCount = count; | |
| 163 | 98 | job.covered = topology.covered; | |
| 164 | 98 | job.firstCorner = corners; | |
| 165 | 98 | job.firstPoint = points; | |
| 166 | 98 | corners += count * 3; | |
| 167 | 98 | points += topology.covered; | |
| 168 | 98 | emitted.push_back(job); | |
| 169 | } | ||
| 170 | 94 | if (emitted.empty()) { | |
| 171 | ✗ | return; | |
| 172 | } | ||
| 173 | 94 | if (corners > m_sortCapacity) { | |
| 174 | 89 | m_sortCapacity = std::bit_ceil(corners); | |
| 175 | 89 | m_sort = std::make_unique<algorithms::RadixSort>(device, m_sortCapacity, 4); | |
| 176 | } | ||
| 177 | 94 | const Ref<device::Buffer> updated = | |
| 178 | 94 | device.CreateBuffer({.label = "topology jobs"}, std::span<const TopologyJob>(jobs)); | |
| 179 | 94 | const Ref<device::Buffer> emittedBuffer = | |
| 180 | 94 | device.CreateBuffer({.label = "emitted topologies"}, std::span<const TopologyJob>(emitted)); | |
| 181 | 94 | const Ref<device::Buffer> keys = scratch(corners, "corner points"); | |
| 182 | 94 | const Ref<device::Buffer> values = scratch(corners, "corner order"); | |
| 183 | 94 | const Ref<device::Buffer> histogram = scratch(points, "point triangle counts"); | |
| 184 | 94 | const Ref<device::Buffer> pointStarts = scratch(points, "point starts"); | |
| 185 | 94 | params.jobs.address = updated->Address(); | |
| 186 | 94 | params.emitted.address = emittedBuffer->Address(); | |
| 187 | 94 | params.keys.address = keys->Address(); | |
| 188 | 94 | params.values.address = values->Address(); | |
| 189 | 94 | params.histogram.address = histogram->Address(); | |
| 190 | 94 | params.pointStarts.address = pointStarts->Address(); | |
| 191 | 94 | params.emittedCount = uint32_t(emitted.size()); | |
| 192 | 94 | params.cornerCount = corners; | |
| 193 | 94 | params.pointCount = points; | |
| 194 | 94 | device::CommandEncoder encoder = queue.CreateEncoder(); | |
| 195 | 94 | { | |
| 196 | 94 | const device::ZoneScope scope(encoder, zone); | |
| 197 | 94 | encoder.Fill(*histogram); | |
| 198 | 94 | dispatch(encoder, "EmitTriangles", params, faces); | |
| 199 | 188 | m_sort->Sort(encoder, *keys, *values, corners, | |
| 200 | 188 | std::max<int>(1, std::bit_width(points - 1))); | |
| 201 | 94 | scan(points).ExclusiveSum(encoder, *histogram, *pointStarts, points); | |
| 202 | 94 | dispatch(encoder, "WriteAdjacency", params, corners); | |
| 203 | 94 | dispatch(encoder, "WriteStarts", params, points); | |
| 204 | 94 | } | |
| 205 | 94 | queue.Submit(encoder.Finish()); | |
| 206 | 2190 | } | |
| 207 | |||
| 208 | 1184 | void MeshTable::Prune() { | |
| 209 | 1184 | if (!m_stale) { | |
| 210 | return; | ||
| 211 | } | ||
| 212 | 117 | m_stale = false; | |
| 213 | 117 | ColumnStore& store = m_structure.GetColumnStore(); | |
| 214 | 117 | ArrayPool& ints = store.Arrays(ColumnType::IntArray); | |
| 215 | 117 | std::erase_if(m_shapes, [&](const auto& entry) { | |
| 216 | 140 | if (entry.second.users > 0) { | |
| 217 | return false; | ||
| 218 | } | ||
| 219 | 12 | store.Arrays(ColumnType::Float3Array).Free(entry.second.normals); | |
| 220 | 12 | store.Arrays(ColumnType::Float3Array).Free(entry.second.bounds); | |
| 221 | 12 | return true; | |
| 222 | }); | ||
| 223 | 117 | std::erase_if(m_topologies, [&](const auto& entry) { | |
| 224 | 122 | if (entry.second.users > 0) { | |
| 225 | return false; | ||
| 226 | } | ||
| 227 | 9 | const Topology& topology = entry.second; | |
| 228 | 54 | for (const ArrayRef& array : {topology.triangles, topology.corners, topology.faces, | |
| 229 | topology.starts, topology.adjacency}) { | ||
| 230 | 45 | ints.Free(array); | |
| 231 | } | ||
| 232 | 9 | return true; | |
| 233 | }); | ||
| 234 | } | ||
| 235 | |||
| 236 | 592 | void MeshTable::Settle() { | |
| 237 | 592 | m_stale = m_stale || !m_placing.empty(); | |
| 238 | 741 | for (const auto& placing : m_placing) { | |
| 239 | 149 | m_topologies.try_emplace(placing.second.topology); | |
| 240 | } | ||
| 241 | 592 | Triangulate(); | |
| 242 | 592 | ColumnStore& store = m_structure.GetColumnStore(); | |
| 243 | 741 | for (const auto& [prim, key] : m_placing) { | |
| 244 | 149 | Mesh& mesh = m_meshes.at(prim); | |
| 245 | 149 | Topology& topology = m_topologies.at(key.topology); | |
| 246 | 149 | if (topology.triangleCount > 0 && topology.covered <= key.pointCount) { | |
| 247 | 147 | const auto [entry, fresh] = m_shapes.try_emplace(key); | |
| 248 | 147 | Shape& shape = entry->second; | |
| 249 | 147 | if (fresh) { | |
| 250 | 111 | const ArrayRef points = store.Array(kPointBasedPoints, mesh.pointsRow); | |
| 251 | 111 | ArrayPool& float3s = store.Arrays(ColumnType::Float3Array); | |
| 252 | 111 | shape.pointCount = points.count; | |
| 253 | 111 | shape.normals = float3s.Allocate(points.count); | |
| 254 | 111 | shape.bounds = float3s.Allocate(2); | |
| 255 | 444 | shape.blas = m_builder.CreateBlas(device::AccelTriangles{ | |
| 256 | .vertices = | ||
| 257 | 111 | float3s.Range(points).Slice(0, uint64_t(points.count) * sizeof(Float3)), | |
| 258 | .stride = sizeof(Float3), | ||
| 259 | 111 | .vertexCount = points.count, | |
| 260 | 111 | .indices = store.Arrays(ColumnType::IntArray) | |
| 261 | 111 | .Range(topology.triangles) | |
| 262 | 111 | .Slice(0, uint64_t(topology.triangleCount) * 12), | |
| 263 | 111 | .count = topology.triangleCount, | |
| 264 | 111 | .refittable = true}); | |
| 265 | 111 | shape.build = true; | |
| 266 | } | ||
| 267 | 147 | ++shape.users; | |
| 268 | 147 | ++topology.users; | |
| 269 | 147 | mesh.key = key; | |
| 270 | 147 | mesh.shape = &shape; | |
| 271 | } | ||
| 272 | 149 | Link(mesh); | |
| 273 | } | ||
| 274 | 592 | m_placing.clear(); | |
| 275 | 592 | } | |
| 276 | |||
| 277 | 151 | void MeshTable::Place(uint32_t prim) { | |
| 278 | 151 | const Prims& prims = m_structure.GetPrims(); | |
| 279 | 151 | Mesh mesh; | |
| 280 | 151 | mesh.prim = prim; | |
| 281 | 151 | mesh.generation = prims.Generation(prim); | |
| 282 | 151 | mesh.pointsRow = prims.Row(prim, SchemaId::PointBased); | |
| 283 | 151 | mesh.meshRow = prims.Row(prim, SchemaId::Mesh); | |
| 284 | 151 | m_byPointsRow[mesh.pointsRow] = prim; | |
| 285 | 151 | m_byMeshRow[mesh.meshRow] = prim; | |
| 286 | |||
| 287 | 151 | m_meshes[prim] = mesh; | |
| 288 | 151 | const ShapeKey key = Key(mesh); | |
| 289 | 151 | if (key.pointCount == 0 || key.topology.indexCount == 0 || key.topology.faceCount == 0) { | |
| 290 | 2 | Link(mesh); | |
| 291 | 2 | return; | |
| 292 | } | ||
| 293 | 149 | m_topologies.try_emplace(key.topology); | |
| 294 | 149 | m_placing.emplace_back(prim, key); | |
| 295 | } | ||
| 296 | |||
| 297 | 151 | MeshTable::ShapeKey MeshTable::Key(const Mesh& mesh) const { | |
| 298 | 151 | const ColumnStore& store = m_structure.GetColumnStore(); | |
| 299 | 151 | const ArrayRef points = store.Array(kPointBasedPoints, mesh.pointsRow); | |
| 300 | 151 | const ArrayRef indices = store.Array(kMeshFaceVertexIndices, mesh.meshRow); | |
| 301 | 151 | const ArrayRef counts = store.Array(kMeshFaceVertexCounts, mesh.meshRow); | |
| 302 | 151 | const ArrayRef holes = store.Array(kMeshHoleIndices, mesh.meshRow); | |
| 303 | 151 | return { | |
| 304 | 151 | points.address, | |
| 305 | 151 | points.count, | |
| 306 | 151 | {indices.address, indices.count, counts.address, counts.count, holes.address, holes.count}}; | |
| 307 | } | ||
| 308 | |||
| 309 | 19 | void MeshTable::Release(Mesh& mesh) { | |
| 310 | 19 | if (mesh.key) { | |
| 311 | 19 | --m_shapes.at(*mesh.key).users; | |
| 312 | 19 | --m_topologies.at(mesh.key->topology).users; | |
| 313 | 19 | mesh.key.reset(); | |
| 314 | 19 | mesh.shape = nullptr; | |
| 315 | 19 | m_stale = true; | |
| 316 | } | ||
| 317 | 19 | } | |
| 318 | |||
| 319 | 151 | void MeshTable::Link(const Mesh& mesh) { | |
| 320 | 151 | MeshRecord record{}; | |
| 321 | 151 | if (mesh.key) { | |
| 322 | 147 | const Shape& shape = m_shapes.at(*mesh.key); | |
| 323 | 147 | const Topology& topology = m_topologies.at(mesh.key->topology); | |
| 324 | 147 | record.blas = shape.blas->Handle(); | |
| 325 | 147 | record.corners = topology.corners; | |
| 326 | 147 | record.faces = topology.faces; | |
| 327 | 147 | record.starts = topology.starts; | |
| 328 | 147 | record.adjacency = topology.adjacency; | |
| 329 | 147 | record.normals = shape.normals; | |
| 330 | 147 | record.bounds = shape.bounds; | |
| 331 | } | ||
| 332 | 151 | if (m_records.Set(mesh.meshRow, record) && m_structure.GetPrims().Alive(mesh.prim)) { | |
| 333 | 147 | m_relinked.push_back(m_structure.GetPrims().Record(mesh.prim).world); | |
| 334 | } | ||
| 335 | 151 | } | |
| 336 | |||
| 337 | 592 | void MeshTable::Run(const ChangeSet& changes, TableVersions& versions) { | |
| 338 | 592 | CHRONO_BATCH_RENDER_ZONE("Geometry/Run"); | |
| 339 | 592 | const Prims& prims = m_structure.GetPrims(); | |
| 340 | 592 | m_stats.built = 0; | |
| 341 | 592 | m_stats.refit = 0; | |
| 342 | 592 | m_stats.triangulated = 0; | |
| 343 | 7921 | const auto isMesh = [&](uint32_t prim) { | |
| 344 | 7329 | return prims.Alive(prim) && IsA(prims.Record(prim).schema, SchemaId::Mesh); | |
| 345 | 592 | }; | |
| 346 | 596 | const auto forget = [&](uint32_t prim) { | |
| 347 | 4 | Mesh& mesh = m_meshes.at(prim); | |
| 348 | 4 | Release(mesh); | |
| 349 | 4 | m_byPointsRow.erase(mesh.pointsRow); | |
| 350 | 4 | m_byMeshRow.erase(mesh.meshRow); | |
| 351 | 4 | m_meshes.erase(prim); | |
| 352 | 596 | }; | |
| 353 | |||
| 354 | 592 | std::vector<uint32_t> placed; | |
| 355 | 592 | if (changes.resync) { | |
| 356 | 320 | while (!m_meshes.empty()) { | |
| 357 | ✗ | forget(m_meshes.begin()->first); | |
| 358 | } | ||
| 359 | 320 | Prune(); | |
| 360 | 2398 | for (uint32_t prim = 0; prim < prims.Size(); ++prim) { | |
| 361 | 2078 | if (isMesh(prim)) { | |
| 362 | 135 | Place(prim); | |
| 363 | 135 | placed.push_back(prim); | |
| 364 | } | ||
| 365 | } | ||
| 366 | } else { | ||
| 367 | 417 | for (const StructureEntry& entry : changes.removed) { | |
| 368 | 145 | const auto found = m_meshes.find(entry.prim); | |
| 369 | 149 | if (found != m_meshes.end() && found->second.generation == entry.generation) { | |
| 370 | 4 | forget(entry.prim); | |
| 371 | } | ||
| 372 | } | ||
| 373 | 5523 | for (const StructureEntry& entry : changes.added) { | |
| 374 | 5251 | if (isMesh(entry.prim) && prims.Generation(entry.prim) == entry.generation) { | |
| 375 | 1 | if (m_meshes.contains(entry.prim)) { | |
| 376 | ✗ | forget(entry.prim); | |
| 377 | } | ||
| 378 | 1 | Place(entry.prim); | |
| 379 | 1 | placed.push_back(entry.prim); | |
| 380 | } | ||
| 381 | } | ||
| 382 | 272 | CHRONO_BATCH_RENDER_ZONE("Geometry/Changes"); | |
| 383 | 272 | std::ranges::sort(placed); | |
| 384 | 1360 | const auto collect = [&](ColumnRef column, const auto& owners, std::vector<uint32_t>& out) { | |
| 385 | 1229 | for (const uint32_t row : changes.ArrayRows(column)) { | |
| 386 | 141 | const auto owner = owners.find(row); | |
| 387 | 176 | if (owner != owners.end() && !std::ranges::binary_search(placed, owner->second)) { | |
| 388 | 32 | out.push_back(owner->second); | |
| 389 | } | ||
| 390 | } | ||
| 391 | 1088 | std::ranges::sort(out); | |
| 392 | 1088 | out.erase(std::ranges::unique(out).begin(), out.end()); | |
| 393 | 1360 | }; | |
| 394 | 272 | std::vector<uint32_t> moved; | |
| 395 | 272 | std::vector<uint32_t> reshaped; | |
| 396 | 272 | collect(kPointBasedPoints, m_byPointsRow, moved); | |
| 397 | 272 | collect(kMeshFaceVertexIndices, m_byMeshRow, reshaped); | |
| 398 | 272 | collect(kMeshFaceVertexCounts, m_byMeshRow, reshaped); | |
| 399 | 272 | collect(kMeshHoleIndices, m_byMeshRow, reshaped); | |
| 400 | 272 | const ColumnStore& store = m_structure.GetColumnStore(); | |
| 401 | 272 | std::vector<uint32_t> rekeyed; | |
| 402 | 295 | for (const uint32_t prim : moved) { | |
| 403 | 23 | if (std::ranges::binary_search(reshaped, prim)) { | |
| 404 | 1 | continue; | |
| 405 | } | ||
| 406 | 22 | Mesh& mesh = m_meshes.at(prim); | |
| 407 | 22 | const ArrayRef points = store.Array(kPointBasedPoints, mesh.pointsRow); | |
| 408 | 22 | if (mesh.shape && points.address == mesh.key->points && | |
| 409 | 12 | points.count == mesh.key->pointCount) { | |
| 410 | 12 | mesh.shape->refit = true; | |
| 411 | } else { | ||
| 412 | 10 | Release(mesh); | |
| 413 | 10 | rekeyed.push_back(prim); | |
| 414 | } | ||
| 415 | } | ||
| 416 | 277 | for (const uint32_t prim : reshaped) { | |
| 417 | 5 | Release(m_meshes.at(prim)); | |
| 418 | } | ||
| 419 | 272 | Prune(); | |
| 420 | 277 | for (const uint32_t prim : reshaped) { | |
| 421 | 5 | Place(prim); | |
| 422 | } | ||
| 423 | 282 | for (const uint32_t prim : rekeyed) { | |
| 424 | 10 | Place(prim); | |
| 425 | } | ||
| 426 | 272 | } | |
| 427 | 592 | Settle(); | |
| 428 | 592 | Prune(); | |
| 429 | 592 | std::vector<uint32_t> updated; | |
| 430 | 592 | Update(updated); | |
| 431 | 592 | m_records.Reserve(m_structure.GetColumnStore().Schema(SchemaId::Mesh).Capacity()); | |
| 432 | 592 | const bool replaced = m_records.Upload(m_structure.GetColumnStore().GetDevice()); | |
| 433 | 592 | if (replaced) { | |
| 434 | 320 | m_relinked = {kGlobalWorld}; | |
| 435 | } | ||
| 436 | 592 | if (!m_relinked.empty()) { | |
| 437 | 333 | versions.Record(TableId::Meshes, m_relinked); | |
| 438 | } | ||
| 439 | 592 | if (!updated.empty()) { | |
| 440 | 110 | versions.Record(TableId::MeshPoints, updated); | |
| 441 | } | ||
| 442 | 592 | m_relinked.clear(); | |
| 443 | 592 | } | |
| 444 | |||
| 445 | 592 | void MeshTable::Update(std::vector<uint32_t>& updated) { | |
| 446 | 592 | std::vector<Ref<bvh::Blas>> built; | |
| 447 | 592 | std::vector<Ref<bvh::Blas>> refit; | |
| 448 | 592 | std::vector<NormalsShape> normals; | |
| 449 | 592 | uint32_t total = 0; | |
| 450 | 752 | for (auto& [key, shape] : m_shapes) { | |
| 451 | 160 | if (!shape.build && !shape.refit) { | |
| 452 | 37 | continue; | |
| 453 | } | ||
| 454 | 123 | shape.updated = Runs(); | |
| 455 | 135 | (shape.build ? built : refit).push_back(shape.blas); | |
| 456 | 123 | const Topology& topology = m_topologies.at(key.topology); | |
| 457 | 123 | NormalsShape entry{}; | |
| 458 | 123 | entry.points.address = key.points; | |
| 459 | 123 | entry.triangles.address = topology.triangles.address; | |
| 460 | 123 | entry.starts.address = topology.starts.address; | |
| 461 | 123 | entry.adjacency.address = topology.adjacency.address; | |
| 462 | 123 | entry.normals.address = shape.normals.address; | |
| 463 | 123 | entry.bounds.address = shape.bounds.address; | |
| 464 | 123 | entry.first = total; | |
| 465 | 123 | entry.count = shape.pointCount; | |
| 466 | 123 | entry.covered = topology.covered; | |
| 467 | 123 | normals.push_back(entry); | |
| 468 | 123 | total += shape.pointCount; | |
| 469 | 123 | shape.build = false; | |
| 470 | 123 | shape.refit = false; | |
| 471 | } | ||
| 472 | 592 | m_stats.built = uint32_t(built.size()); | |
| 473 | 592 | m_stats.refit = uint32_t(refit.size()); | |
| 474 | 592 | m_stats.meshes = uint32_t(m_meshes.size()); | |
| 475 | 592 | m_stats.shapes = uint32_t(m_shapes.size()); | |
| 476 | 592 | m_stats.topologies = uint32_t(m_topologies.size()); | |
| 477 | 592 | if (normals.empty()) { | |
| 478 | 482 | return; | |
| 479 | } | ||
| 480 | 110 | const Prims& prims = m_structure.GetPrims(); | |
| 481 | 456 | for (const auto& [prim, mesh] : m_meshes) { | |
| 482 | 173 | if (mesh.shape && mesh.shape->updated == Runs() && prims.Alive(prim)) { | |
| 483 | 161 | updated.push_back(prims.Record(prim).world); | |
| 484 | } | ||
| 485 | } | ||
| 486 | |||
| 487 | 110 | static const profiler::ZoneId blasZone = profiler::ZoneId::Named("Geometry/Blas"); | |
| 488 | 110 | static const profiler::ZoneId normalsZone = profiler::ZoneId::Named("Geometry/Normals"); | |
| 489 | 110 | ColumnStore& store = m_structure.GetColumnStore(); | |
| 490 | 110 | device::Device& device = store.GetDevice(); | |
| 491 | 110 | store.Flush(); | |
| 492 | 110 | const Ref<device::Buffer> table = | |
| 493 | 110 | device.CreateBuffer({.label = "normals shapes"}, std::span<const NormalsShape>(normals)); | |
| 494 | 110 | device::Queue& queue = device.GetQueue(); | |
| 495 | 110 | device::CommandEncoder encoder = queue.CreateEncoder(); | |
| 496 | 110 | encoder.BeginZone(blasZone); | |
| 497 | 110 | m_builder.Build(encoder, built); | |
| 498 | 110 | m_builder.Refit(encoder, refit); | |
| 499 | 110 | encoder.EndZone(); | |
| 500 | 110 | encoder.BeginZone(normalsZone); | |
| 501 | 110 | const uint64_t extentWords = uint64_t(normals.size()) * 3; | |
| 502 | 110 | const Ref<device::Buffer> extents = device.CreateBuffer( | |
| 503 | 110 | {.bytes = extentWords * 2 * sizeof(uint32_t), .label = "shape extents"}); | |
| 504 | 110 | encoder.Fill(extents->Slice(0, extentWords * sizeof(uint32_t)), ~0u); | |
| 505 | 110 | encoder.Fill(extents->Slice(extentWords * sizeof(uint32_t)), 0u); | |
| 506 | 110 | NormalsParams params{}; | |
| 507 | 110 | params.shapes.address = table->Address(); | |
| 508 | 110 | params.extents.address = extents->Address(); | |
| 509 | 110 | params.shapeCount = uint32_t(normals.size()); | |
| 510 | 110 | params.total = total; | |
| 511 | 110 | encoder.Dispatch(Kernels().Entry("SmoothNormals"), params, | |
| 512 | 110 | {.x = device::GroupCount(total, kNormalsGroup)}); | |
| 513 | 110 | encoder.Dispatch(Kernels().Entry("FinishShapeBounds"), params, | |
| 514 | 110 | {.x = device::GroupCount(params.shapeCount, kNormalsGroup)}); | |
| 515 | 110 | encoder.EndZone(); | |
| 516 | 110 | queue.Submit(encoder.Finish()); | |
| 517 | 1252 | } | |
| 518 | |||
| 519 | 1013 | void MeshTable::Hold(std::vector<Ref<const RefCounted>>& held) const { | |
| 520 | 2026 | HoldBuffers(held, {m_records.Buffer()}); | |
| 521 | 1249 | for (const auto& [key, shape] : m_shapes) { | |
| 522 | 236 | if (shape.blas) { | |
| 523 | 236 | held.emplace_back(shape.blas); | |
| 524 | } | ||
| 525 | } | ||
| 526 | 2026 | } | |
| 527 | |||
| 528 | 353 | void MeshTable::Prepare() { | |
| 529 | 353 | Kernels(); | |
| 530 | 353 | if (!m_scan) { | |
| 531 | 353 | m_scanCapacity = kPreparedScan; | |
| 532 | 353 | m_scan = std::make_unique<algorithms::Scan>(m_structure.GetColumnStore().GetDevice(), | |
| 533 | 353 | m_scanCapacity); | |
| 534 | } | ||
| 535 | 353 | } | |
| 536 | |||
| 537 | 1139 | device::Program& MeshTable::Kernels() { | |
| 538 | 1139 | if (!m_program) { | |
| 539 | 1176 | m_program = m_structure.GetColumnStore().GetDevice().CreateProgram( | |
| 540 | {.modules = {"chrono_batch_render.scene.tables.kernels.meshes"}, | ||
| 541 | .library = &kSceneKernels, | ||
| 542 | .entries = {"MarkHoles", "FaceSizes", "FaceTriangles", "TopologySizes", | ||
| 543 | "EmitTriangles", "WriteAdjacency", "WriteStarts", "SmoothNormals", | ||
| 544 | "FinishShapeBounds"}, | ||
| 545 | 784 | .label = "geometry"}); | |
| 546 | } | ||
| 547 | 1139 | return *m_program; | |
| 548 | 784 | } | |
| 549 | |||
| 550 | } | ||
| 551 |