scene/tables/transforms.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // SPDX-License-Identifier: BSD-3-Clause | ||
| 2 | #include "chrono_batch_render/scene/tables/transforms.h" | ||
| 3 | |||
| 4 | #include <algorithm> | ||
| 5 | #include <cstring> | ||
| 6 | #include <functional> | ||
| 7 | #include <iterator> | ||
| 8 | #include <map> | ||
| 9 | #include <numeric> | ||
| 10 | #include <set> | ||
| 11 | #include <string> | ||
| 12 | #include <unordered_map> | ||
| 13 | |||
| 14 | #include "chrono_batch_render/device/commands.h" | ||
| 15 | #include "chrono_batch_render/device/queue.h" | ||
| 16 | #include "chrono_batch_render/scene/kernels.h" | ||
| 17 | #include "chrono_batch_render/scene/records/render_tables.h" | ||
| 18 | #include "chrono_batch_render/scene/schema/columns.h" | ||
| 19 | #include "chrono_batch_render/scene/tables/captures.h" | ||
| 20 | #include "chrono_batch_render/scene/tables/kernels/transforms.h" | ||
| 21 | #include "chrono_batch_render/stage/columns/kernels/cells.h" | ||
| 22 | #include "chrono_batch_render/stage/publish/change_set.h" | ||
| 23 | #include "chrono_batch_render/stage/views/attribute.h" | ||
| 24 | #include "chrono_batch_render/stage/views/view.h" | ||
| 25 | #include "chrono_batch_render/util/log.h" | ||
| 26 | #include "chrono_batch_render/util/profiler.h" | ||
| 27 | |||
| 28 | namespace chrono::batch_render { | ||
| 29 | |||
| 30 | namespace { | ||
| 31 | |||
| 32 | constexpr ColumnRef kFollowInputs[] = {kFollowAPIFollowTarget, | ||
| 33 | kFollowAPIFollowPath, | ||
| 34 | kFollowAPIFollowPathMode, | ||
| 35 | kFollowAPIFollowPathPosition, | ||
| 36 | kFollowAPIFollowPathSpeed, | ||
| 37 | kFollowAPIFollowPathCurve, | ||
| 38 | kFollowAPIFollowLookAt, | ||
| 39 | kFollowAPIFollowTranslate, | ||
| 40 | kFollowAPIFollowRotate, | ||
| 41 | kFollowAPIFollowUp, | ||
| 42 | kFollowAPIFollowInheritScale, | ||
| 43 | kFollowAPIFollowOffsetTranslate, | ||
| 44 | kFollowAPIFollowOffsetOrient, | ||
| 45 | kFollowAPIFollowOffsetScale, | ||
| 46 | kFollowAPIFollowLookAtOffsetTranslate, | ||
| 47 | kFollowAPIFollowPositionLag, | ||
| 48 | kFollowAPIFollowRotationLag, | ||
| 49 | kFollowAPIFollowLagMaxDistance, | ||
| 50 | kFollowAPIFollowWeight}; | ||
| 51 | |||
| 52 | constexpr ColumnRef kInputs[] = {kXformableTranslate, | ||
| 53 | kXformableOrient, | ||
| 54 | kXformableScale, | ||
| 55 | kXformableResetXformStack, | ||
| 56 | kImageableVisibility, | ||
| 57 | kImageablePurpose, | ||
| 58 | kMaterialBindingAPIMaterialBinding, | ||
| 59 | kMotionAPIMotionVelocity, | ||
| 60 | kMotionAPIMotionAngularVelocity, | ||
| 61 | kFollowAPIFollowTarget, | ||
| 62 | kFollowAPIFollowPath, | ||
| 63 | kFollowAPIFollowPathMode, | ||
| 64 | kFollowAPIFollowPathPosition, | ||
| 65 | kFollowAPIFollowPathSpeed, | ||
| 66 | kFollowAPIFollowPathCurve, | ||
| 67 | kFollowAPIFollowLookAt, | ||
| 68 | kFollowAPIFollowTranslate, | ||
| 69 | kFollowAPIFollowRotate, | ||
| 70 | kFollowAPIFollowUp, | ||
| 71 | kFollowAPIFollowInheritScale, | ||
| 72 | kFollowAPIFollowOffsetTranslate, | ||
| 73 | kFollowAPIFollowOffsetOrient, | ||
| 74 | kFollowAPIFollowOffsetScale, | ||
| 75 | kFollowAPIFollowLookAtOffsetTranslate, | ||
| 76 | kFollowAPIFollowPositionLag, | ||
| 77 | kFollowAPIFollowRotationLag, | ||
| 78 | kFollowAPIFollowLagMaxDistance, | ||
| 79 | kFollowAPIFollowWeight}; | ||
| 80 | |||
| 81 | constexpr ColumnRef kPathInputs[] = {kPointBasedPoints, kCurvesCurveVertexCounts, kBasisCurvesType, | ||
| 82 | kBasisCurvesBasis, kBasisCurvesWrap}; | ||
| 83 | |||
| 84 | constexpr ColumnRef kStamped[] = {kXformableTranslate, | ||
| 85 | kXformableOrient, | ||
| 86 | kXformableScale, | ||
| 87 | kXformableResetXformStack, | ||
| 88 | kImageableVisibility, | ||
| 89 | kImageablePurpose, | ||
| 90 | kMaterialBindingAPIMaterialBinding}; | ||
| 91 | |||
| 92 | constexpr TableId kTableOutputs[] = {TableId::Transforms, TableId::Imageables}; | ||
| 93 | |||
| 94 | constexpr uint32_t kUnresolved = kNoPrim - 1; | ||
| 95 | |||
| 96 | } | ||
| 97 | |||
| 98 | 390 | TransformTable::TransformTable(Structure& structure, const SimulationClock* clock) | |
| 99 | 1950 | : m_structure(structure), m_clock(clock) { | |
| 100 | 3120 | for (const ColumnRef column : kStamped) { | |
| 101 | 2730 | m_structure.GetColumnStore().TrackRows(column); | |
| 102 | } | ||
| 103 | 390 | } | |
| 104 | |||
| 105 | 2841 | std::span<const ColumnRef> TransformTable::Inputs() const { | |
| 106 | 5 | static const std::vector<ColumnRef> inputs = [] { | |
| 107 | 5 | std::vector<ColumnRef> columns(std::begin(kInputs), std::end(kInputs)); | |
| 108 | 5 | columns.insert(columns.end(), std::begin(kPathInputs), std::end(kPathInputs)); | |
| 109 | 5 | return columns; | |
| 110 | 2841 | }(); | |
| 111 | 2841 | return inputs; | |
| 112 | } | ||
| 113 | |||
| 114 | 390 | std::span<const TableId> TransformTable::TableOutputs() const { | |
| 115 | 390 | return kTableOutputs; | |
| 116 | } | ||
| 117 | |||
| 118 | 808 | void TransformTable::Describe(RenderTables& tables) const { | |
| 119 | 808 | tables.transforms.address = m_transforms.Address(); | |
| 120 | 808 | tables.imageables.address = m_imageables.Address(); | |
| 121 | 808 | tables.motions.address = m_motions.Address(); | |
| 122 | 808 | } | |
| 123 | |||
| 124 | 999 | void TransformTable::Hold(std::vector<Ref<const RefCounted>>& held) const { | |
| 125 | 3996 | HoldBuffers(held, {m_transforms.Buffer(), m_imageables.Buffer(), m_motions.Buffer()}); | |
| 126 | 1998 | } | |
| 127 | |||
| 128 | 6878 | uint32_t TransformTable::ParentRow(uint32_t prim) const { | |
| 129 | 6878 | const Prims& prims = m_structure.GetPrims(); | |
| 130 | 6878 | uint32_t up = prims.Record(prim).parent; | |
| 131 | 6878 | while (up != kNoPrim && !IsA(prims.Record(up).schema, SchemaId::Imageable)) { | |
| 132 | ✗ | up = prims.Record(up).parent; | |
| 133 | } | ||
| 134 | 6878 | return up == kNoPrim ? kNoRow : prims.Row(up, SchemaId::Imageable); | |
| 135 | } | ||
| 136 | |||
| 137 | 331 | void TransformTable::Rebuild() { | |
| 138 | 331 | CHRONO_BATCH_RENDER_ZONE("Hierarchy/Rebuild"); | |
| 139 | 331 | const Prims& prims = m_structure.GetPrims(); | |
| 140 | 331 | ColumnStore& store = m_structure.GetColumnStore(); | |
| 141 | 331 | device::Device& device = store.GetDevice(); | |
| 142 | 331 | const uint32_t capacity = std::max(store.Schema(SchemaId::Imageable).Capacity(), 1u); | |
| 143 | 331 | m_transforms.Allocate(device, store.Schema(SchemaId::Xformable).Capacity()); | |
| 144 | 331 | m_imageables.Allocate(device, capacity); | |
| 145 | 331 | m_parents.Assign(capacity, kNoRow); | |
| 146 | 331 | m_xforms.Assign(capacity, kNoRow); | |
| 147 | 331 | m_rows.clear(); | |
| 148 | 331 | std::vector<std::vector<uint32_t>> segments(prims.WorldCount() + 1); | |
| 149 | 7955 | for (uint32_t prim = 0; prim < prims.Size(); ++prim) { | |
| 150 | 7624 | if (!prims.Alive(prim) || !IsA(prims.Record(prim).schema, SchemaId::Imageable)) { | |
| 151 | 1004 | continue; | |
| 152 | } | ||
| 153 | 6620 | const PrimRecord& record = prims.Record(prim); | |
| 154 | 6620 | const uint32_t row = prims.Row(prim, SchemaId::Imageable); | |
| 155 | 6620 | m_parents.Set(row, ParentRow(prim)); | |
| 156 | 6620 | if (IsA(record.schema, SchemaId::Xformable)) { | |
| 157 | 6403 | m_xforms.Set(row, prims.Row(prim, SchemaId::Xformable)); | |
| 158 | } | ||
| 159 | 6928 | segments[WorldSlot(record.world)].push_back(row); | |
| 160 | 6620 | m_rows[prim] = row; | |
| 161 | } | ||
| 162 | 331 | m_segments.Assign(std::move(segments)); | |
| 163 | |||
| 164 | 331 | m_parents.Upload(device); | |
| 165 | 331 | m_xforms.Upload(device); | |
| 166 | 662 | m_segmentRows = device.CreateBuffer( | |
| 167 | {.label = "hierarchy segment rows"}, | ||
| 168 | 993 | std::vector<uint32_t>(m_segments.Items().begin(), m_segments.Items().end())); | |
| 169 | 993 | m_segmentStart = device.CreateBuffer( | |
| 170 | 331 | {.label = "hierarchy segment starts"}, | |
| 171 | 993 | std::vector<uint32_t>(m_segments.Starts().begin(), m_segments.Starts().end())); | |
| 172 | 993 | m_segmentCount = device.CreateBuffer( | |
| 173 | 331 | {.label = "hierarchy segment counts"}, | |
| 174 | 993 | std::vector<uint32_t>(m_segments.Counts().begin(), m_segments.Counts().end())); | |
| 175 | 331 | m_built = true; | |
| 176 | 331 | ++m_rebuilds; | |
| 177 | 662 | Log(LogLevel::Debug, "rebuilt the hierarchy: {} rows in {} segments", m_rows.size(), | |
| 178 | 331 | m_segments.Size()); | |
| 179 | 2317 | } | |
| 180 | |||
| 181 | 186 | bool TransformTable::Apply(const ChangeSet& changes) { | |
| 182 | 186 | CHRONO_BATCH_RENDER_ZONE("Hierarchy/Apply"); | |
| 183 | 186 | const Prims& prims = m_structure.GetPrims(); | |
| 184 | 186 | if (m_segments.Size() != prims.WorldCount() + 1) { | |
| 185 | return false; | ||
| 186 | } | ||
| 187 | 181 | std::map<uint32_t, std::vector<uint32_t>> touched; | |
| 188 | 539 | const auto segment = [&](uint32_t index) -> std::vector<uint32_t>& { | |
| 189 | 358 | auto [entry, fresh] = touched.try_emplace(index); | |
| 190 | 358 | if (fresh) { | |
| 191 | 143 | const std::span<const uint32_t> items = m_segments.Items(index); | |
| 192 | 143 | entry->second.assign(items.begin(), items.end()); | |
| 193 | } | ||
| 194 | 358 | return entry->second; | |
| 195 | 181 | }; | |
| 196 | 328 | for (const StructureEntry& entry : changes.removed) { | |
| 197 | 147 | const auto found = m_rows.find(entry.prim); | |
| 198 | 147 | if (found == m_rows.end()) { | |
| 199 | 47 | continue; | |
| 200 | } | ||
| 201 | 100 | const uint32_t row = found->second; | |
| 202 | 102 | std::erase(segment(WorldSlot(entry.world)), row); | |
| 203 | 100 | m_parents.Set(row, kNoRow); | |
| 204 | 100 | m_xforms.Set(row, kNoRow); | |
| 205 | 100 | m_rows.erase(found); | |
| 206 | } | ||
| 207 | 497 | for (const StructureEntry& entry : changes.added) { | |
| 208 | 608 | if (!prims.Alive(entry.prim) || prims.Generation(entry.prim) != entry.generation || | |
| 209 | 291 | !IsA(prims.Record(entry.prim).schema, SchemaId::Imageable)) { | |
| 210 | 58 | continue; | |
| 211 | } | ||
| 212 | 259 | const uint32_t row = prims.Row(entry.prim, SchemaId::Imageable); | |
| 213 | 259 | if (row >= m_parents.Rows()) { | |
| 214 | 1 | return false; | |
| 215 | } | ||
| 216 | 258 | if (IsA(prims.Record(entry.prim).schema, SchemaId::Xformable) && | |
| 217 | 257 | prims.Row(entry.prim, SchemaId::Xformable) >= m_transforms.Rows()) { | |
| 218 | return false; | ||
| 219 | } | ||
| 220 | 258 | m_parents.Set(row, ParentRow(entry.prim)); | |
| 221 | 258 | m_xforms.Set(row, IsA(prims.Record(entry.prim).schema, SchemaId::Xformable) | |
| 222 | 258 | ? prims.Row(entry.prim, SchemaId::Xformable) | |
| 223 | : kNoRow); | ||
| 224 | 260 | segment(WorldSlot(entry.world)).push_back(row); | |
| 225 | 258 | m_rows[entry.prim] = row; | |
| 226 | } | ||
| 227 | 320 | for (const auto& [index, items] : touched) { | |
| 228 | 143 | if (!m_segments.Replace(index, items)) { | |
| 229 | 3 | return false; | |
| 230 | } | ||
| 231 | } | ||
| 232 | |||
| 233 | 177 | device::Device& device = m_structure.GetColumnStore().GetDevice(); | |
| 234 | 177 | m_parents.Upload(device); | |
| 235 | 177 | m_xforms.Upload(device); | |
| 236 | 177 | device::Queue& queue = device.GetQueue(); | |
| 237 | 317 | for (const auto& [index, items] : touched) { | |
| 238 | 140 | const uint32_t start = m_segments.Starts()[index]; | |
| 239 | 140 | queue.Write(m_segmentRows->Slice(start * sizeof(uint32_t), | |
| 240 | 140 | m_segments.Capacity(index) * sizeof(uint32_t)), | |
| 241 | 280 | m_segments.Items().data() + start); | |
| 242 | } | ||
| 243 | 177 | if (!touched.empty()) { | |
| 244 | 140 | queue.Write(*m_segmentCount, m_segments.Counts().data()); | |
| 245 | } | ||
| 246 | return true; | ||
| 247 | 186 | } | |
| 248 | |||
| 249 | 808 | void TransformTable::Run(const ChangeSet& changes, TableVersions& versions) { | |
| 250 | 808 | CHRONO_BATCH_RENDER_ZONE("Hierarchy/Run"); | |
| 251 | 808 | ColumnStore& store = m_structure.GetColumnStore(); | |
| 252 | 808 | const bool rebuild = changes.resync || !m_built || (changes.structure && !Apply(changes)); | |
| 253 | 808 | if (rebuild) { | |
| 254 | 331 | Rebuild(); | |
| 255 | } | ||
| 256 | 820 | WriteMotions(rebuild || !changes.apis.empty()); | |
| 257 | |||
| 258 | 808 | std::vector<uint32_t> segments; | |
| 259 | 808 | const bool global = rebuild || changes.Global(); | |
| 260 | 808 | if (global) { | |
| 261 | 774 | segments.resize(m_segments.Size()); | |
| 262 | 1548 | std::iota(segments.begin(), segments.end(), 0u); | |
| 263 | } else { | ||
| 264 | 59 | for (const uint32_t world : changes.worlds) { | |
| 265 | 50 | segments.push_back(WorldSlot(world)); | |
| 266 | } | ||
| 267 | } | ||
| 268 | 808 | std::erase_if(segments, [&](uint32_t segment) { | |
| 269 | 928 | return segment >= m_segments.Size() || m_segments.Counts()[segment] == 0; | |
| 270 | }); | ||
| 271 | 808 | m_dispatched = 0; | |
| 272 | 1097 | const bool replanned = rebuild || changes.structure || !changes.apis.empty() || | |
| 273 | 289 | std::ranges::any_of(kFollowInputs, [&](ColumnRef input) { | |
| 274 | 5344 | return store.LastWrite(input) > m_followSeen; | |
| 275 | 1338 | }); | |
| 276 | 530 | bool refollowed = false; | |
| 277 | 530 | if (replanned) { | |
| 278 | 530 | refollowed = !m_follows.frames.empty(); | |
| 279 | 530 | PlanFollows(); | |
| 280 | 530 | m_followSeen = store.WriteSequence(); | |
| 281 | } | ||
| 282 | 3221 | const auto written = [&](std::span<const ColumnRef> inputs) { | |
| 283 | 2413 | return rebuild || std::ranges::any_of(inputs, [&](ColumnRef input) { | |
| 284 | 4411 | return store.LastWrite(input) > m_seen; | |
| 285 | 2413 | }); | |
| 286 | 808 | }; | |
| 287 | 808 | const bool pathsWritten = written(kPathInputs); | |
| 288 | 808 | const bool pathsMoved = m_follows.pathed && pathsWritten; | |
| 289 | 808 | if (pathsWritten && !global && !replanned && changes.apis.empty() && !written(kInputs)) { | |
| 290 | 6 | segments.clear(); | |
| 291 | } | ||
| 292 | 808 | if (refollowed && !global) { | |
| 293 | ✗ | segments.resize(m_segments.Size()); | |
| 294 | ✗ | std::iota(segments.begin(), segments.end(), 0u); | |
| 295 | ✗ | std::erase_if(segments, | |
| 296 | ✗ | [&](uint32_t segment) { return m_segments.Counts()[segment] == 0; }); | |
| 297 | } | ||
| 298 | 808 | const std::span<const ColumnRef> stamped = kStamped; | |
| 299 | 808 | const bool moved = written(stamped.first(4)); | |
| 300 | 808 | const bool following = !m_follows.frames.empty() && (moved || replanned || m_follows.lagged || | |
| 301 | 6 | m_follows.riding || pathsMoved); | |
| 302 | 808 | if (segments.empty() && !following) { | |
| 303 | 17 | return; | |
| 304 | } | ||
| 305 | 791 | uint32_t widest = 0; | |
| 306 | 1694 | for (const uint32_t segment : segments) { | |
| 307 | 903 | widest = std::max(widest, m_segments.Counts()[segment]); | |
| 308 | 903 | m_dispatched += m_segments.Counts()[segment]; | |
| 309 | } | ||
| 310 | |||
| 311 | 791 | ResolveParams params{}; | |
| 312 | 791 | params.tree.parent.address = m_parents.Buffer()->Address(); | |
| 313 | 791 | params.tree.xform.address = m_xforms.Buffer()->Address(); | |
| 314 | 791 | params.tree.segmentRows.address = m_segmentRows->Address(); | |
| 315 | 791 | params.tree.segmentStart.address = m_segmentStart->Address(); | |
| 316 | 791 | params.tree.segmentCount.address = m_segmentCount->Address(); | |
| 317 | 791 | params.scene.address = store.DeviceData(); | |
| 318 | 791 | params.transforms.address = m_transforms.Address(); | |
| 319 | 791 | params.imageables.address = m_imageables.Address(); | |
| 320 | 791 | Paged<uint64_t>* stamps[] = {¶ms.translateStamps, ¶ms.orientStamps, | |
| 321 | ¶ms.scaleStamps, ¶ms.resetXformStackStamps, | ||
| 322 | ¶ms.visibilityStamps, ¶ms.purposeStamps, | ||
| 323 | 791 | ¶ms.materialBindingStamps}; | |
| 324 | 6328 | for (size_t at = 0; at < std::size(kStamped); ++at) { | |
| 325 | 5537 | const ColumnRef column = kStamped[at]; | |
| 326 | 5537 | stamps[at]->pages.address = store.Schema(column.schema).Pages(store.StampColumn(column)); | |
| 327 | } | ||
| 328 | 791 | params.since = changes.sinceStamp; | |
| 329 | 791 | params.until = changes.untilStamp; | |
| 330 | 791 | params.all = rebuild || refollowed ? 1 : 0; | |
| 331 | 791 | params.inherit = changes.removed.empty() && changes.apis.empty() ? 0 : 1; | |
| 332 | |||
| 333 | 791 | const bool inherited = | |
| 334 | 791 | written(stamped.subspan(4)) || !changes.removed.empty() || !changes.apis.empty(); | |
| 335 | 791 | m_seen = store.WriteSequence(); | |
| 336 | 791 | std::vector<uint32_t> worlds; | |
| 337 | 791 | if (global || refollowed) { | |
| 338 | 763 | worlds.push_back(kGlobalWorld); | |
| 339 | } else { | ||
| 340 | 50 | for (const uint32_t segment : segments) { | |
| 341 | 44 | worlds.push_back(SlotWorld(segment)); | |
| 342 | } | ||
| 343 | } | ||
| 344 | 791 | if ((moved || refollowed) && !segments.empty()) { | |
| 345 | 654 | versions.Record(TableId::Transforms, worlds); | |
| 346 | } | ||
| 347 | 791 | if (inherited && !segments.empty()) { | |
| 348 | 506 | versions.Record(TableId::Imageables, worlds); | |
| 349 | } | ||
| 350 | 791 | if (following) { | |
| 351 | 37 | versions.Record(TableId::Transforms, m_follows.worlds); | |
| 352 | } | ||
| 353 | 791 | device::Queue& queue = store.GetDevice().GetQueue(); | |
| 354 | 791 | static const profiler::ZoneId zone = profiler::ZoneId::Named("Hierarchy/Resolve"); | |
| 355 | 791 | device::CommandEncoder encoder = queue.CreateEncoder(); | |
| 356 | 791 | Ref<device::Buffer> dirty; | |
| 357 | 791 | if (!segments.empty()) { | |
| 358 | 784 | dirty = store.GetDevice().CreateBuffer({.label = "hierarchy dirty segments"}, segments); | |
| 359 | 784 | params.segments.address = dirty->Address(); | |
| 360 | 784 | const device::ZoneScope scope(encoder, zone); | |
| 361 | 784 | encoder.Dispatch( | |
| 362 | 784 | Kernels().Entry("ResolveRows"), params, | |
| 363 | 784 | {.x = device::GroupCount(widest, kHierarchyGroup), .y = uint32_t(segments.size())}); | |
| 364 | 784 | } | |
| 365 | 791 | if (following) { | |
| 366 | 37 | RecordFollows(encoder, params); | |
| 367 | } | ||
| 368 | 791 | queue.Submit(encoder.Finish()); | |
| 369 | 3167 | } | |
| 370 | |||
| 371 | 530 | void TransformTable::PlanFollows() { | |
| 372 | 530 | CHRONO_BATCH_RENDER_ZONE("Hierarchy/PlanFollows"); | |
| 373 | 530 | const Prims& prims = m_structure.GetPrims(); | |
| 374 | 530 | m_follows = {}; | |
| 375 | 530 | std::vector<uint32_t> followers; | |
| 376 | 12469 | for (uint32_t prim = 0; prim < prims.Size(); ++prim) { | |
| 377 | 21468 | if (prims.Alive(prim) && IsA(prims.Record(prim).schema, SchemaId::Xformable) && | |
| 378 | 9529 | prims.HasApi(prim, SchemaId::FollowAPI)) { | |
| 379 | 31 | followers.push_back(prim); | |
| 380 | } | ||
| 381 | } | ||
| 382 | 530 | if (followers.empty()) { | |
| 383 | return; | ||
| 384 | } | ||
| 385 | |||
| 386 | 106 | const auto resolve = [&](const std::vector<std::string>& paths) { | |
| 387 | 80 | if (paths.empty()) { | |
| 388 | return kNoPrim; | ||
| 389 | } | ||
| 390 | 32 | const uint32_t prim = m_structure.Find(paths.front()); | |
| 391 | 64 | const bool valid = prim != kNoPrim && prims.Alive(prim) && | |
| 392 | 32 | IsA(prims.Record(prim).schema, SchemaId::Xformable); | |
| 393 | return valid ? prim : kNoPrim; | ||
| 394 | 26 | }; | |
| 395 | |||
| 396 | 57 | struct Link { | |
| 397 | uint32_t target = kNoPrim; | ||
| 398 | uint32_t lookAt = kNoPrim; | ||
| 399 | }; | ||
| 400 | |||
| 401 | 26 | std::unordered_map<uint32_t, Link> links; | |
| 402 | 26 | { | |
| 403 | 26 | const View view(m_structure, SchemaId::FollowAPI, followers); | |
| 404 | 26 | const UntypedAttribute targets = view.Get(kFollowAPIFollowTarget); | |
| 405 | 26 | const UntypedAttribute paths = view.Get(kFollowAPIFollowPath); | |
| 406 | 26 | const UntypedAttribute looks = view.Get(kFollowAPIFollowLookAt); | |
| 407 | 26 | const std::vector<Float3> lags = view.Get<Float3>(kFollowAPIFollowPositionLag).Get(); | |
| 408 | 26 | const std::vector<float> turns = view.Get<float>(kFollowAPIFollowRotationLag).Get(); | |
| 409 | 26 | const std::vector<float> speeds = view.Get<float>(kFollowAPIFollowPathSpeed).Get(); | |
| 410 | 57 | for (uint32_t index = 0; index < followers.size(); ++index) { | |
| 411 | 31 | uint32_t target = resolve(paths.GetTargets(index)); | |
| 412 | 31 | if (target != kNoPrim && IsA(prims.Record(target).schema, SchemaId::BasisCurves)) { | |
| 413 | 13 | m_follows.pathed = true; | |
| 414 | 19 | m_follows.riding = m_follows.riding || speeds[index] != 0; | |
| 415 | } else { | ||
| 416 | 18 | target = resolve(targets.GetTargets(index)); | |
| 417 | } | ||
| 418 | 31 | if (target == kNoPrim) { | |
| 419 | ✗ | continue; | |
| 420 | } | ||
| 421 | 31 | links[followers[index]] = {target, resolve(looks.GetTargets(index))}; | |
| 422 | 31 | const Float3 lag = lags[index]; | |
| 423 | 31 | m_follows.lagged = | |
| 424 | 31 | m_follows.lagged || lag.x > 0 || lag.y > 0 || lag.z > 0 || turns[index] > 0; | |
| 425 | } | ||
| 426 | 104 | } | |
| 427 | |||
| 428 | 26 | std::vector<uint32_t> owner; | |
| 429 | 26 | std::unordered_map<uint32_t, uint32_t> levels; | |
| 430 | 26 | std::unordered_map<uint32_t, uint8_t> marks; | |
| 431 | 26 | std::vector<uint32_t> cyclic; | |
| 432 | 26 | std::vector<uint32_t> resolving; | |
| 433 | 125 | const auto findOwner = [&](uint32_t prim) { | |
| 434 | 99 | std::vector<uint32_t> chain; | |
| 435 | 99 | uint32_t found = kNoPrim; | |
| 436 | 135 | for (uint32_t at = prim; at != kNoPrim; at = prims.Record(at).parent) { | |
| 437 | 102 | if (owner[at] != kUnresolved) { | |
| 438 | found = owner[at]; | ||
| 439 | break; | ||
| 440 | } | ||
| 441 | 68 | chain.push_back(at); | |
| 442 | 68 | if (links.contains(at)) { | |
| 443 | 32 | found = at; | |
| 444 | 32 | break; | |
| 445 | } | ||
| 446 | } | ||
| 447 | 167 | for (const uint32_t walked : chain) { | |
| 448 | 68 | owner[walked] = found; | |
| 449 | } | ||
| 450 | 99 | return found; | |
| 451 | 99 | }; | |
| 452 | 26 | std::function<uint32_t(uint32_t)> findLevel; | |
| 453 | ✗ | const std::function<uint32_t(uint32_t)> follow = [&](uint32_t follower) -> uint32_t { | |
| 454 | 38 | const uint8_t mark = marks[follower]; | |
| 455 | 38 | if (mark == 2) { | |
| 456 | 4 | return levels[follower]; | |
| 457 | } | ||
| 458 | 34 | if (mark == 1) { | |
| 459 | 1 | const auto loop = std::ranges::find(resolving, follower); | |
| 460 | 1 | cyclic.insert(cyclic.end(), loop, resolving.end()); | |
| 461 | 1 | return 0; | |
| 462 | } | ||
| 463 | 33 | marks[follower] = 1; | |
| 464 | 33 | resolving.push_back(follower); | |
| 465 | 33 | const Link link = links.at(follower); | |
| 466 | 33 | const uint32_t deepest = std::max({findLevel(prims.Record(follower).parent), | |
| 467 | 33 | findLevel(link.target), findLevel(link.lookAt)}); | |
| 468 | 33 | resolving.pop_back(); | |
| 469 | 33 | marks[follower] = 2; | |
| 470 | 33 | levels[follower] = deepest + 1; | |
| 471 | 33 | return deepest + 1; | |
| 472 | 26 | }; | |
| 473 | 26 | findLevel = [&](uint32_t prim) -> uint32_t { | |
| 474 | 99 | if (prim == kNoPrim) { | |
| 475 | return 0; | ||
| 476 | } | ||
| 477 | 35 | const uint32_t found = findOwner(prim); | |
| 478 | 35 | return found == kNoPrim ? 0 : follow(found); | |
| 479 | 26 | }; | |
| 480 | 28 | while (true) { | |
| 481 | 27 | owner.assign(prims.Size(), kUnresolved); | |
| 482 | 27 | levels.clear(); | |
| 483 | 27 | marks.clear(); | |
| 484 | 27 | cyclic.clear(); | |
| 485 | 60 | for (const auto& entry : links) { | |
| 486 | 33 | follow(entry.first); | |
| 487 | } | ||
| 488 | 27 | if (cyclic.empty()) { | |
| 489 | break; | ||
| 490 | } | ||
| 491 | 1 | std::ranges::sort(cyclic); | |
| 492 | 1 | cyclic.erase(std::unique(cyclic.begin(), cyclic.end()), cyclic.end()); | |
| 493 | 3 | for (const uint32_t follower : cyclic) { | |
| 494 | 2 | Log(LogLevel::Warning, "{} follows itself through its targets, so it follows nothing", | |
| 495 | 2 | m_structure.GetPaths().Path(follower)); | |
| 496 | 2 | links.erase(follower); | |
| 497 | } | ||
| 498 | 1 | } | |
| 499 | 26 | if (links.empty()) { | |
| 500 | ✗ | m_follows.lagged = false; | |
| 501 | ✗ | m_follows.pathed = false; | |
| 502 | ✗ | m_follows.riding = false; | |
| 503 | ✗ | return; | |
| 504 | } | ||
| 505 | |||
| 506 | 26 | uint32_t deepest = 0; | |
| 507 | 55 | for (const auto& entry : levels) { | |
| 508 | 55 | deepest = std::max(deepest, entry.second); | |
| 509 | } | ||
| 510 | 26 | std::vector<std::vector<uint32_t>> frames(deepest + 1); | |
| 511 | 26 | std::vector<std::vector<uint32_t>> owned(deepest + 1); | |
| 512 | 26 | std::vector<std::vector<uint32_t>> owners(deepest + 1); | |
| 513 | 26 | std::set<uint32_t> worlds; | |
| 514 | 90 | for (uint32_t prim = 0; prim < prims.Size(); ++prim) { | |
| 515 | 64 | if (!prims.Alive(prim) || !IsA(prims.Record(prim).schema, SchemaId::Xformable)) { | |
| 516 | 32 | continue; | |
| 517 | } | ||
| 518 | 64 | const uint32_t found = findOwner(prim); | |
| 519 | 64 | if (found == kNoPrim) { | |
| 520 | 32 | continue; | |
| 521 | } | ||
| 522 | 32 | const uint32_t level = levels.at(found); | |
| 523 | 32 | const uint32_t row = prims.Row(prim, SchemaId::Imageable); | |
| 524 | 32 | if (found == prim) { | |
| 525 | 29 | frames[level].push_back(row); | |
| 526 | } else { | ||
| 527 | 3 | owned[level].push_back(row); | |
| 528 | 3 | owners[level].push_back(prims.Row(found, SchemaId::Imageable)); | |
| 529 | } | ||
| 530 | 32 | worlds.insert(prims.Record(prim).world); | |
| 531 | } | ||
| 532 | 26 | std::vector<uint32_t> rows; | |
| 533 | 26 | std::vector<uint32_t> rowOwners; | |
| 534 | 80 | for (uint32_t level = 0; level <= deepest; ++level) { | |
| 535 | 54 | m_follows.frameStarts.push_back(uint32_t(rows.size())); | |
| 536 | 54 | rows.insert(rows.end(), frames[level].begin(), frames[level].end()); | |
| 537 | 54 | rowOwners.insert(rowOwners.end(), frames[level].size(), kNoRow); | |
| 538 | 54 | m_follows.frames.insert(m_follows.frames.end(), frames[level].begin(), frames[level].end()); | |
| 539 | } | ||
| 540 | 26 | m_follows.frameStarts.push_back(uint32_t(rows.size())); | |
| 541 | 80 | for (uint32_t level = 0; level <= deepest; ++level) { | |
| 542 | 54 | m_follows.ownedStarts.push_back(uint32_t(rows.size())); | |
| 543 | 54 | rows.insert(rows.end(), owned[level].begin(), owned[level].end()); | |
| 544 | 54 | rowOwners.insert(rowOwners.end(), owners[level].begin(), owners[level].end()); | |
| 545 | 54 | m_follows.owned.insert(m_follows.owned.end(), owned[level].begin(), owned[level].end()); | |
| 546 | } | ||
| 547 | 26 | m_follows.ownedStarts.push_back(uint32_t(rows.size())); | |
| 548 | 26 | m_follows.worlds.assign(worlds.begin(), worlds.end()); | |
| 549 | 26 | device::Device& device = m_structure.GetColumnStore().GetDevice(); | |
| 550 | 39 | m_followRows = device.CreateBuffer({.label = "follow rows"}, rows); | |
| 551 | 39 | m_followOwners = device.CreateBuffer({.label = "follow owners"}, rowOwners); | |
| 552 | 582 | } | |
| 553 | |||
| 554 | 37 | void TransformTable::RecordFollows(device::CommandEncoder& encoder, const ResolveParams& resolved) { | |
| 555 | 37 | ColumnStore& store = m_structure.GetColumnStore(); | |
| 556 | 37 | device::Device& device = store.GetDevice(); | |
| 557 | 111 | if (m_followStates.Reserve(device, | |
| 558 | 37 | std::max(store.Schema(SchemaId::FollowAPI).Capacity(), 1u))) { | |
| 559 | 26 | encoder.Fill(m_followStates.Buffer()->Slice(0, uint64_t(m_followStates.Rows()) * | |
| 560 | sizeof(FollowState))); | ||
| 561 | } | ||
| 562 | 37 | FollowParams params{}; | |
| 563 | 37 | params.tree = resolved.tree; | |
| 564 | 37 | params.scene = resolved.scene; | |
| 565 | 37 | params.transforms = resolved.transforms; | |
| 566 | 37 | params.rows.address = m_followRows->Address(); | |
| 567 | 37 | params.owners.address = m_followOwners->Address(); | |
| 568 | 37 | params.states.address = m_followStates.Address(); | |
| 569 | 37 | params.zero = resolved.zero; | |
| 570 | 37 | if (m_clock != nullptr) { | |
| 571 | 37 | if (m_clock->resets != m_clockResets) { | |
| 572 | ✗ | params.snap = 1; | |
| 573 | 37 | } else if (m_clock->steps != m_clockSteps) { | |
| 574 | 34 | params.dt = float(std::max(m_clock->end - m_clockEnd, 0.0)); | |
| 575 | } | ||
| 576 | 37 | m_clockResets = m_clock->resets; | |
| 577 | 37 | m_clockSteps = m_clock->steps; | |
| 578 | 37 | m_clockEnd = m_clock->end; | |
| 579 | } | ||
| 580 | 37 | static const profiler::ZoneId zone = profiler::ZoneId::Named("Hierarchy/Follow"); | |
| 581 | 37 | const device::ZoneScope scope(encoder, zone); | |
| 582 | 37 | const uint32_t levels = uint32_t(m_follows.frameStarts.size()) - 1; | |
| 583 | 113 | for (uint32_t level = 0; level < levels; ++level) { | |
| 584 | 76 | const std::pair<const char*, const std::vector<uint32_t>*> passes[] = { | |
| 585 | 76 | {"FollowFrames", &m_follows.frameStarts}, {"FollowOwned", &m_follows.ownedStarts}}; | |
| 586 | 228 | for (const auto& [entry, starts] : passes) { | |
| 587 | 152 | params.first = (*starts)[level]; | |
| 588 | 152 | params.count = (*starts)[level + 1] - params.first; | |
| 589 | 152 | if (params.count == 0) { | |
| 590 | 109 | continue; | |
| 591 | } | ||
| 592 | 43 | encoder.Barrier(); | |
| 593 | 86 | encoder.Dispatch(Kernels().Entry(entry), params, | |
| 594 | 43 | {.x = device::GroupCount(params.count, kHierarchyGroup)}); | |
| 595 | } | ||
| 596 | } | ||
| 597 | 37 | } | |
| 598 | |||
| 599 | 808 | void TransformTable::WriteMotions(bool restructured) { | |
| 600 | 808 | ColumnStore& store = m_structure.GetColumnStore(); | |
| 601 | 808 | const bool written = store.LastWrite(kMotionAPIMotionVelocity) > m_motionSeen || | |
| 602 | 797 | store.LastWrite(kMotionAPIMotionAngularVelocity) > m_motionSeen; | |
| 603 | 808 | m_motionSeen = store.WriteSequence(); | |
| 604 | 808 | device::Device& device = store.GetDevice(); | |
| 605 | 808 | const bool grown = m_motions.Reserve(device, store.Schema(SchemaId::MotionAPI).Capacity()); | |
| 606 | 808 | const uint32_t rows = store.Schema(SchemaId::MotionAPI).Rows(); | |
| 607 | 808 | if (rows == 0 || !(written || restructured || grown)) { | |
| 608 | 797 | return; | |
| 609 | } | ||
| 610 | 11 | MotionParams params{}; | |
| 611 | 11 | params.scene.address = store.DeviceData(); | |
| 612 | 11 | params.motions.address = m_motions.Address(); | |
| 613 | 11 | params.rows = rows; | |
| 614 | 11 | device::Queue& queue = device.GetQueue(); | |
| 615 | 11 | device::CommandEncoder encoder = queue.CreateEncoder(); | |
| 616 | 11 | encoder.Dispatch(Kernels().Entry("WriteMotions"), params, | |
| 617 | 11 | {.x = device::GroupCount(rows, kHierarchyGroup)}); | |
| 618 | 11 | queue.Submit(encoder.Finish()); | |
| 619 | 11 | } | |
| 620 | |||
| 621 | template <class Params> | ||
| 622 | 238 | device::Operation TransformTable::Dispatch(const char* entry, const Params& params, | |
| 623 | uint32_t threads) { | ||
| 624 | 238 | device::Queue& queue = m_structure.GetColumnStore().GetDevice().GetQueue(); | |
| 625 | 238 | if (threads == 0) { | |
| 626 | ✗ | return queue.Mark(); | |
| 627 | } | ||
| 628 | 238 | device::CommandEncoder encoder = queue.CreateEncoder(); | |
| 629 | { | ||
| 630 | 476 | const device::ZoneScope zone(encoder, | |
| 631 | 476 | profiler::ZoneId::Named(std::string("Hierarchy/") + entry)); | |
| 632 | 238 | encoder.Dispatch(Kernels().Entry(entry), params, | |
| 633 | 238 | {.x = device::GroupCount(threads, kHierarchyGroup)}); | |
| 634 | 238 | } | |
| 635 | 238 | return queue.Submit(encoder.Finish()); | |
| 636 | 238 | } | |
| 637 | |||
| 638 | 230 | device::Tensor TransformTable::ReadWorldPoses(ViewState& view, device::DType type, Stream consumer, | |
| 639 | QuaternionOrder order) { | ||
| 640 | 230 | CHRONO_BATCH_RENDER_ZONE("Hierarchy/ReadWorldPoses"); | |
| 641 | 230 | device::Device& device = m_structure.GetColumnStore().GetDevice(); | |
| 642 | 230 | const uint32_t count = uint32_t(view.Prims().size()); | |
| 643 | 230 | const bool doubles = type == device::DType::Float64; | |
| 644 | 230 | const Ref<device::Buffer> values = device.CreateBuffer( | |
| 645 | 230 | {.bytes = std::max<uint64_t>(uint64_t(count) * kPoseWords * (doubles ? 8 : 4), 4), | |
| 646 | .usage = device::BufferUsage::Interop, | ||
| 647 | 230 | .label = "world poses"}); | |
| 648 | 230 | WorldPoseParams params{}; | |
| 649 | 230 | params.transforms.address = Transforms()->Address(); | |
| 650 | 230 | const RowSource rows = view.Source(SchemaId::Xformable); | |
| 651 | 230 | params.rows.address = rows.rows; | |
| 652 | 230 | params.firstRow = rows.firstRow; | |
| 653 | 230 | params.out.address = values->Address(); | |
| 654 | 230 | params.count = count; | |
| 655 | 230 | params.doubles = doubles ? 1 : 0; | |
| 656 | 230 | params.order = uint32_t(order); | |
| 657 | 230 | Dispatch("ReadWorldPoses", params, count).Wait(consumer); | |
| 658 | 920 | return device::Tensor(values, 0, type, {int64_t(count), int64_t(kPoseWords)}); | |
| 659 | 920 | } | |
| 660 | |||
| 661 | 1 | device::Tensor TransformTable::ReadWorldScales(ViewState& view, device::DType type, | |
| 662 | Stream consumer) { | ||
| 663 | 1 | CHRONO_BATCH_RENDER_ZONE("Hierarchy/ReadWorldScales"); | |
| 664 | 1 | device::Device& device = m_structure.GetColumnStore().GetDevice(); | |
| 665 | 1 | const uint32_t count = uint32_t(view.Prims().size()); | |
| 666 | 1 | const bool doubles = type == device::DType::Float64; | |
| 667 | 1 | const Ref<device::Buffer> values = device.CreateBuffer( | |
| 668 | 1 | {.bytes = std::max<uint64_t>(uint64_t(count) * 3 * (doubles ? 8 : 4), 4), | |
| 669 | .usage = device::BufferUsage::Interop, | ||
| 670 | 1 | .label = "world scales"}); | |
| 671 | 1 | WorldPoseParams params{}; | |
| 672 | 1 | params.transforms.address = Transforms()->Address(); | |
| 673 | 1 | const RowSource rows = view.Source(SchemaId::Xformable); | |
| 674 | 1 | params.rows.address = rows.rows; | |
| 675 | 1 | params.firstRow = rows.firstRow; | |
| 676 | 1 | params.out.address = values->Address(); | |
| 677 | 1 | params.count = count; | |
| 678 | 1 | params.doubles = doubles ? 1 : 0; | |
| 679 | 1 | Dispatch("ReadWorldScales", params, count).Wait(consumer); | |
| 680 | 4 | return device::Tensor(values, 0, type, {int64_t(count), 3}); | |
| 681 | 4 | } | |
| 682 | |||
| 683 | 7 | device::Tensor TransformTable::Localize(ViewState& view, const device::Tensor& values, | |
| 684 | const device::Tensor* rotations, QuaternionOrder order, | ||
| 685 | const device::Tensor& sources) { | ||
| 686 | 7 | CHRONO_BATCH_RENDER_ZONE("Hierarchy/Localize"); | |
| 687 | 7 | ColumnStore& store = m_structure.GetColumnStore(); | |
| 688 | 7 | device::Device& device = store.GetDevice(); | |
| 689 | 7 | const uint32_t count = uint32_t(view.Prims().size()); | |
| 690 | 7 | const Ref<device::Buffer> local = device.CreateBuffer( | |
| 691 | 7 | {.bytes = std::max<uint64_t>(uint64_t(count) * kPoseWords * 8, 4), .label = "local poses"}); | |
| 692 | 14 | const Ref<device::Buffer>& parents = view.XformParents(); | |
| 693 | 7 | WorldPoseParams params{}; | |
| 694 | 7 | params.scene.address = store.DeviceData(); | |
| 695 | 7 | params.transforms.address = Transforms()->Address(); | |
| 696 | 7 | const RowSource rows = view.Source(SchemaId::Xformable); | |
| 697 | 7 | params.rows.address = rows.rows; | |
| 698 | 7 | params.firstRow = rows.firstRow; | |
| 699 | 7 | params.parents.address = parents->Address(); | |
| 700 | 7 | params.values.address = values.Address(); | |
| 701 | 7 | params.rotations.address = rotations ? rotations->Address() : 0; | |
| 702 | 7 | params.sources.address = sources.Empty() ? 0 : sources.Address(); | |
| 703 | 7 | params.out.address = local->Address(); | |
| 704 | 7 | params.count = count; | |
| 705 | 7 | params.doubles = values.Type() == device::DType::Float64 ? 1 : 0; | |
| 706 | 7 | params.order = uint32_t(order); | |
| 707 | 7 | Dispatch("WorldToLocalPoses", params, count); | |
| 708 | 28 | return device::Tensor(local, 0, device::DType::Float64, {int64_t(count), int64_t(kPoseWords)}); | |
| 709 | 21 | } | |
| 710 | |||
| 711 | 353 | void TransformTable::Prepare() { | |
| 712 | 353 | Kernels(); | |
| 713 | 353 | } | |
| 714 | |||
| 715 | 1429 | device::Program& TransformTable::Kernels() { | |
| 716 | 1429 | if (!m_program) { | |
| 717 | 1167 | m_program = m_structure.GetColumnStore().GetDevice().CreateProgram( | |
| 718 | {.modules = {"chrono_batch_render.scene.tables.kernels.transforms"}, | ||
| 719 | .library = &kSceneKernels, | ||
| 720 | .entries = {"ResolveRows", "WriteMotions", "FollowFrames", "FollowOwned", | ||
| 721 | "ReadWorldPoses", "ReadWorldScales", "WorldToLocalPoses"}, | ||
| 722 | 778 | .label = "hierarchy"}); | |
| 723 | } | ||
| 724 | 1429 | return *m_program; | |
| 725 | 778 | } | |
| 726 | |||
| 727 | } | ||
| 728 |