scene/tables/instance_layout.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // SPDX-License-Identifier: BSD-3-Clause | ||
| 2 | #include "chrono_batch_render/scene/tables/instances.h" | ||
| 3 | |||
| 4 | #include <algorithm> | ||
| 5 | #include <functional> | ||
| 6 | #include <limits> | ||
| 7 | #include <map> | ||
| 8 | |||
| 9 | #include "chrono_batch_render/device/commands.h" | ||
| 10 | #include "chrono_batch_render/device/kernels/shared.h" | ||
| 11 | #include "chrono_batch_render/device/queue.h" | ||
| 12 | #include "chrono_batch_render/scene/schema/columns.h" | ||
| 13 | #include "chrono_batch_render/scene/tables/kernels/instances.h" | ||
| 14 | #include "chrono_batch_render/stage/publish/change_set.h" | ||
| 15 | #include "chrono_batch_render/util/error.h" | ||
| 16 | #include "chrono_batch_render/util/log.h" | ||
| 17 | #include "chrono_batch_render/util/profiler.h" | ||
| 18 | #include "chrono_batch_render/util/span.h" | ||
| 19 | |||
| 20 | namespace chrono::batch_render { | ||
| 21 | |||
| 22 | namespace { | ||
| 23 | |||
| 24 | 12319 | bool Drawable(SchemaId schema) { | |
| 25 | 12319 | return IsA(schema, SchemaId::Gprim) || IsEmitter(uint32_t(schema)); | |
| 26 | } | ||
| 27 | |||
| 28 | } | ||
| 29 | |||
| 30 | 580 | bool InstanceTable::ShowsGlobals(uint32_t tlas) const { | |
| 31 | 580 | const auto composed = m_composed.find(tlas); | |
| 32 | 580 | return composed == m_composed.end() || composed->second.global; | |
| 33 | } | ||
| 34 | |||
| 35 | 560 | std::vector<uint32_t> InstanceTable::RegionItems(uint32_t tlas) const { | |
| 36 | 560 | std::vector<uint32_t> items = m_blockItems[tlas]; | |
| 37 | 560 | if (tlas < m_worlds) { | |
| 38 | 114 | items.insert(items.end(), m_own[tlas].begin(), m_own[tlas].end()); | |
| 39 | } | ||
| 40 | 560 | if (const auto composed = m_composed.find(tlas); composed != m_composed.end()) { | |
| 41 | 2 | for (const uint32_t world : composed->second.worlds) { | |
| 42 | 1 | if (world < m_worlds && world != tlas) { | |
| 43 | 1 | items.insert(items.end(), m_own[world].begin(), m_own[world].end()); | |
| 44 | } | ||
| 45 | } | ||
| 46 | } | ||
| 47 | 560 | if (ShowsGlobals(tlas)) { | |
| 48 | 560 | items.insert(items.end(), m_globals.begin(), m_globals.end()); | |
| 49 | } | ||
| 50 | 560 | return items; | |
| 51 | ✗ | } | |
| 52 | |||
| 53 | 311 | void InstanceTable::Compose() { | |
| 54 | 311 | m_compositions = m_structure.CompositionVersion(); | |
| 55 | 311 | m_composed.clear(); | |
| 56 | 311 | m_shownIn.clear(); | |
| 57 | 312 | for (const auto& [world, desc] : m_structure.Compositions()) { | |
| 58 | 1 | if (world >= m_worlds) { | |
| 59 | ✗ | continue; | |
| 60 | } | ||
| 61 | 1 | m_composed[world] = desc; | |
| 62 | 2 | for (const uint32_t shown : desc.worlds) { | |
| 63 | 1 | if (shown < m_worlds && shown != world) { | |
| 64 | 1 | m_shownIn[shown].insert(world); | |
| 65 | } | ||
| 66 | } | ||
| 67 | } | ||
| 68 | 311 | } | |
| 69 | |||
| 70 | 319 | std::vector<InstanceTable::Instancer> InstanceTable::GatherInstancers() const { | |
| 71 | 319 | const Prims& prims = m_structure.GetPrims(); | |
| 72 | 319 | ColumnStore& store = m_structure.GetColumnStore(); | |
| 73 | 343 | const auto targets = [&](ColumnRef column, uint32_t row) { | |
| 74 | 24 | const ArrayRef array = store.Array(column, row); | |
| 75 | 24 | std::vector<uint32_t> found = | |
| 76 | 24 | array.address == 0 ? std::vector<uint32_t>{} | |
| 77 | 24 | : Items<uint32_t>(store.Arrays(ColumnType::PrimArray).Read(array)); | |
| 78 | 48 | for (uint32_t& prim : found) { | |
| 79 | 24 | if (prim != kNoPrim && !prims.Alive(prim)) { | |
| 80 | ✗ | prim = kNoPrim; | |
| 81 | } | ||
| 82 | } | ||
| 83 | 24 | return found; | |
| 84 | 319 | }; | |
| 85 | 319 | std::vector<Instancer> instancers; | |
| 86 | 7721 | for (uint32_t prim = 0; prim < prims.Size(); ++prim) { | |
| 87 | 7402 | if (!prims.Alive(prim)) { | |
| 88 | 7378 | continue; | |
| 89 | } | ||
| 90 | 7146 | const PrimRecord& record = prims.Record(prim); | |
| 91 | 7146 | Instancer instancer{.prim = prim, .world = record.world}; | |
| 92 | 7146 | if (record.schema == SchemaId::PointInstancer) { | |
| 93 | 23 | const uint32_t row = prims.Row(prim, SchemaId::PointInstancer); | |
| 94 | 23 | instancer.count = store.Array(kPointInstancerPositions, row).count; | |
| 95 | 46 | instancer.roots = targets(kPointInstancerPrototypes, row); | |
| 96 | 7123 | } else if (prims.HasApi(prim, SchemaId::PrototypeAPI)) { | |
| 97 | 1 | instancer.native = true; | |
| 98 | 1 | instancer.count = 1; | |
| 99 | 1 | instancer.roots = | |
| 100 | 2 | targets(kPrototypeAPIPrototype, prims.Row(prim, SchemaId::PrototypeAPI)); | |
| 101 | 1 | instancer.roots.resize(std::min<size_t>(instancer.roots.size(), 1)); | |
| 102 | } else { | ||
| 103 | 7122 | continue; | |
| 104 | } | ||
| 105 | 24 | instancers.push_back(std::move(instancer)); | |
| 106 | 7146 | } | |
| 107 | 319 | return instancers; | |
| 108 | ✗ | } | |
| 109 | |||
| 110 | 5214 | bool InstanceTable::Member(uint32_t prim) const { | |
| 111 | 5214 | const Prims& prims = m_structure.GetPrims(); | |
| 112 | 10476 | for (uint32_t at = prim; at != kNoPrim; at = prims.Record(at).parent) { | |
| 113 | 5262 | if (m_roots.contains(at)) { | |
| 114 | ✗ | return true; | |
| 115 | } | ||
| 116 | } | ||
| 117 | 5214 | return false; | |
| 118 | } | ||
| 119 | |||
| 120 | 311 | void InstanceTable::Relayout() { | |
| 121 | 311 | CHRONO_BATCH_RENDER_ZONE("Instances/Relayout"); | |
| 122 | 311 | const Prims& prims = m_structure.GetPrims(); | |
| 123 | 311 | device::Device& device = m_structure.GetColumnStore().GetDevice(); | |
| 124 | 311 | m_worlds = prims.WorldCount(); | |
| 125 | 311 | m_own.assign(m_worlds, {}); | |
| 126 | 311 | m_globals.clear(); | |
| 127 | 311 | m_origins.clear(); | |
| 128 | 311 | m_products.clear(); | |
| 129 | 311 | m_instancers = GatherInstancers(); | |
| 130 | 311 | m_roots.clear(); | |
| 131 | 327 | for (const Instancer& instancer : m_instancers) { | |
| 132 | 32 | for (const uint32_t root : instancer.roots) { | |
| 133 | 16 | if (root != kNoPrim) { | |
| 134 | 16 | m_roots.insert(root); | |
| 135 | } | ||
| 136 | } | ||
| 137 | } | ||
| 138 | 311 | m_members.clear(); | |
| 139 | 311 | m_drawables.clear(); | |
| 140 | 311 | m_nested.clear(); | |
| 141 | 311 | std::set<uint32_t> instancers; | |
| 142 | 327 | for (const Instancer& instancer : m_instancers) { | |
| 143 | 16 | instancers.insert(instancer.prim); | |
| 144 | } | ||
| 145 | 311 | Compose(); | |
| 146 | 569 | std::vector<uint32_t> origins(std::max(m_worlds, 1u), kNoRow); | |
| 147 | 7672 | for (uint32_t prim = 0; prim < prims.Size(); ++prim) { | |
| 148 | 7361 | if (!prims.Alive(prim)) { | |
| 149 | 256 | continue; | |
| 150 | } | ||
| 151 | 7105 | const PrimRecord& record = prims.Record(prim); | |
| 152 | 7105 | uint32_t root = kNoPrim; | |
| 153 | 15327 | for (uint32_t at = prim; at != kNoPrim && root == kNoPrim; at = prims.Record(at).parent) { | |
| 154 | 8222 | if (m_roots.contains(at)) { | |
| 155 | 26 | root = at; | |
| 156 | } | ||
| 157 | } | ||
| 158 | 7105 | if (root != kNoPrim) { | |
| 159 | 26 | m_members.insert(prim); | |
| 160 | 26 | if (Drawable(record.schema)) { | |
| 161 | 19 | m_drawables[root].push_back(prim); | |
| 162 | } | ||
| 163 | 26 | if (instancers.contains(prim)) { | |
| 164 | 1 | m_nested[root].push_back(prim); | |
| 165 | } | ||
| 166 | 7079 | } else if (Drawable(record.schema)) { | |
| 167 | 5699 | (record.world == kGlobalWorld ? m_globals : m_own[record.world]).push_back(prim); | |
| 168 | } | ||
| 169 | 7105 | if (IsA(record.schema, SchemaId::World)) { | |
| 170 | 107 | origins[record.world] = prims.Row(prim, SchemaId::Xformable); | |
| 171 | 107 | m_origins.insert(prim); | |
| 172 | } | ||
| 173 | 7105 | if (record.schema == SchemaId::RenderProduct) { | |
| 174 | 189 | m_products.push_back(prim); | |
| 175 | } | ||
| 176 | } | ||
| 177 | 311 | m_productBuffer = | |
| 178 | 622 | device.CreateBuffer({.label = "render products"}, | |
| 179 | std::span<const uint32_t>( | ||
| 180 | 933 | m_products.empty() ? std::vector<uint32_t>{kNoPrim} : m_products)); | |
| 181 | |||
| 182 | 311 | BuildInstancing(); | |
| 183 | |||
| 184 | 311 | std::vector<std::vector<uint32_t>> lists; | |
| 185 | 729 | for (uint32_t tlas = 0; tlas <= m_worlds; ++tlas) { | |
| 186 | 836 | lists.push_back(RegionItems(tlas)); | |
| 187 | } | ||
| 188 | 311 | m_regions.Assign(std::move(lists)); | |
| 189 | 311 | std::vector<uint32_t> capacities; | |
| 190 | 729 | for (uint32_t tlas = 0; tlas <= m_worlds; ++tlas) { | |
| 191 | 418 | capacities.push_back(m_regions.Capacity(tlas)); | |
| 192 | 418 | if (capacities.back() > kInstanceCustomMask + 1) { | |
| 193 | ✗ | throw Error("{} instances in one TLAS; a TLAS holds at most {}", capacities.back(), | |
| 194 | ✗ | kInstanceCustomMask + 1); | |
| 195 | } | ||
| 196 | } | ||
| 197 | |||
| 198 | 311 | m_tlas = m_builder.CreateTlas(capacities); | |
| 199 | 311 | const uint32_t slots = uint32_t(m_regions.Items().size()); | |
| 200 | 311 | m_records.Allocate(device, slots); | |
| 201 | 311 | m_shapes.Allocate(device, slots); | |
| 202 | 311 | m_hits.Allocate(device, slots); | |
| 203 | 311 | m_slotWorlds.Allocate(device, slots); | |
| 204 | 311 | m_renderOrigins.Allocate(device, m_worlds + 1); | |
| 205 | 311 | device::CommandEncoder clear = device.GetQueue().CreateEncoder(); | |
| 206 | 311 | for (const Ref<device::Buffer>& buffer : | |
| 207 | {m_records.Buffer(), m_shapes.Buffer(), m_hits.Buffer(), m_slotWorlds.Buffer(), | ||
| 208 | 4976 | m_renderOrigins.Buffer()}) { | |
| 209 | 1555 | clear.Fill(*buffer); | |
| 210 | 1866 | } | |
| 211 | 311 | device.GetQueue().Submit(clear.Finish()); | |
| 212 | |||
| 213 | 311 | std::vector<uint32_t> words(sizeof(InstanceLayout) / sizeof(uint32_t)); | |
| 214 | 2177 | const auto append = [&](std::span<const uint32_t> values) { | |
| 215 | 1866 | const uint64_t at = words.size() * sizeof(uint32_t); | |
| 216 | 1866 | words.insert(words.end(), values.begin(), values.end()); | |
| 217 | 1866 | return at; | |
| 218 | 311 | }; | |
| 219 | 311 | const uint64_t firstInstancesStart = append(m_regions.Starts()); | |
| 220 | 311 | const uint64_t countsStart = append(m_regions.Counts()); | |
| 221 | 311 | const uint64_t primsStart = append(m_regions.Items()); | |
| 222 | 311 | const uint64_t originsStart = append(origins); | |
| 223 | 311 | std::vector<uint32_t> bases(m_worlds + 1, kNoRow); | |
| 224 | 311 | std::vector<uint32_t> offsets; | |
| 225 | 312 | for (const auto& [world, desc] : m_composed) { | |
| 226 | 1 | bases[world] = uint32_t(offsets.size() / 3); | |
| 227 | 1 | const float hidden = std::numeric_limits<float>::quiet_NaN(); | |
| 228 | 1 | std::vector<Float3> shifts(m_worlds, Float3{hidden, hidden, hidden}); | |
| 229 | 2 | for (size_t at = 0; at < desc.worlds.size(); ++at) { | |
| 230 | 1 | if (desc.worlds[at] < m_worlds) { | |
| 231 | 1 | const Double3 offset = at < desc.offsets.size() ? desc.offsets[at] : Double3{}; | |
| 232 | 1 | shifts[desc.worlds[at]] = {float(offset.x), float(offset.y), float(offset.z)}; | |
| 233 | } | ||
| 234 | } | ||
| 235 | 1 | const auto* words32 = reinterpret_cast<const uint32_t*>(shifts.data()); | |
| 236 | 1 | offsets.insert(offsets.end(), words32, words32 + shifts.size() * 3); | |
| 237 | 1 | } | |
| 238 | 311 | const uint64_t basesStart = append(bases); | |
| 239 | 311 | const uint64_t offsetsStart = append(offsets); | |
| 240 | 311 | m_layout = | |
| 241 | 322 | device.CreateBuffer({.bytes = words.size() * sizeof(uint32_t), .label = "instance layout"}); | |
| 242 | 622 | device.GetQueue().Write(*m_layout, words.data()); | |
| 243 | 311 | const uint64_t layout = m_layout->Address(); | |
| 244 | 311 | m_header.firstInstances.address = layout + firstInstancesStart; | |
| 245 | 311 | m_header.instanceCounts.address = layout + countsStart; | |
| 246 | 311 | m_header.instancePrims.address = layout + primsStart; | |
| 247 | 311 | m_header.worldOrigins.address = layout + originsStart; | |
| 248 | 311 | m_header.offsetBases.address = layout + basesStart; | |
| 249 | 311 | m_header.offsets.address = layout + offsetsStart; | |
| 250 | 933 | m_viewers = device.CreateBuffer( | |
| 251 | 311 | {.label = "viewers"}, | |
| 252 | 933 | std::span<const uint32_t>(std::vector<uint32_t>(m_worlds + 1, kNoPrim))); | |
| 253 | 311 | WriteLayoutHeader(); | |
| 254 | |||
| 255 | 311 | m_refits.assign(m_worlds + 1, 0); | |
| 256 | 311 | ++m_relayouts; | |
| 257 | 311 | Log(LogLevel::Debug, | |
| 258 | "laid out {} TLASes with room for {} instances, {} global prims in each and {} " | ||
| 259 | "instancers", | ||
| 260 | 311 | m_worlds + 1, m_regions.Items().size(), m_globals.size(), m_instancers.size()); | |
| 261 | 2488 | } | |
| 262 | |||
| 263 | 317 | void InstanceTable::BuildInstancing() { | |
| 264 | 317 | const Prims& prims = m_structure.GetPrims(); | |
| 265 | 317 | ColumnStore& store = m_structure.GetColumnStore(); | |
| 266 | |||
| 267 | 338 | struct Placed { | |
| 268 | uint32_t first = 0; | ||
| 269 | uint32_t count = 0; | ||
| 270 | uint32_t width = 0; | ||
| 271 | uint32_t lights = 0; | ||
| 272 | }; | ||
| 273 | |||
| 274 | 317 | std::vector<PrototypeEntry> entries; | |
| 275 | 317 | std::vector<PrototypePiece> pieces; | |
| 276 | 317 | std::vector<InstancerBlock> blocks; | |
| 277 | 317 | std::vector<uint32_t> ranges; | |
| 278 | 317 | std::vector<int64_t> hidden; | |
| 279 | 317 | std::map<uint32_t, Placed> placed; | |
| 280 | 317 | std::set<uint32_t> building; | |
| 281 | 317 | std::map<uint32_t, const Instancer*> byPrim; | |
| 282 | 339 | for (const Instancer& instancer : m_instancers) { | |
| 283 | 22 | byPrim[instancer.prim] = &instancer; | |
| 284 | } | ||
| 285 | 317 | m_blockItems.assign(m_worlds + 1, {}); | |
| 286 | 317 | m_lightCopies.assign(m_worlds + 1, {}); | |
| 287 | 317 | m_instancerPrims.clear(); | |
| 288 | 317 | m_watch.clear(); | |
| 289 | |||
| 290 | 317 | std::function<std::optional<InstancerBlock>(const Instancer&, uint32_t)> describe; | |
| 291 | 339 | const auto place = [&](uint32_t root, uint32_t depth) { | |
| 292 | 22 | if (const auto found = placed.find(root); found != placed.end()) { | |
| 293 | 1 | return found->second; | |
| 294 | } | ||
| 295 | 21 | if (building.contains(root)) { | |
| 296 | ✗ | return Placed{}; | |
| 297 | } | ||
| 298 | 21 | building.insert(root); | |
| 299 | 21 | std::vector<PrototypePiece> local; | |
| 300 | 21 | uint32_t start = 0; | |
| 301 | 21 | uint32_t lightStart = 0; | |
| 302 | 51 | for (const uint32_t drawable : m_drawables[root]) { | |
| 303 | 30 | const uint32_t light = IsEmitter(uint32_t(prims.Record(drawable).schema)) ? 1 : 0; | |
| 304 | 30 | local.push_back({.prim = drawable, | |
| 305 | .start = start, | ||
| 306 | .slots = 1, | ||
| 307 | .nested = kNoNested, | ||
| 308 | .lightStart = lightStart, | ||
| 309 | .lights = light}); | ||
| 310 | 30 | ++start; | |
| 311 | 30 | lightStart += light; | |
| 312 | } | ||
| 313 | 22 | for (const uint32_t nested : m_nested[root]) { | |
| 314 | 1 | const auto instancer = byPrim.find(nested); | |
| 315 | 1 | if (instancer == byPrim.end()) { | |
| 316 | ✗ | continue; | |
| 317 | } | ||
| 318 | 1 | const std::optional<InstancerBlock> inner = describe(*instancer->second, depth + 1); | |
| 319 | 1 | if (!inner || inner->count == 0 || inner->width == 0) { | |
| 320 | ✗ | continue; | |
| 321 | } | ||
| 322 | 1 | local.push_back({.prim = nested, | |
| 323 | .start = start, | ||
| 324 | 1 | .slots = inner->count * inner->width, | |
| 325 | 1 | .nested = uint32_t(blocks.size()), | |
| 326 | .lightStart = lightStart, | ||
| 327 | 1 | .lights = inner->count * inner->lightWidth}); | |
| 328 | 1 | blocks.push_back(*inner); | |
| 329 | 1 | start += local.back().slots; | |
| 330 | 1 | lightStart += local.back().lights; | |
| 331 | } | ||
| 332 | 21 | building.erase(root); | |
| 333 | 21 | const Placed result{.first = uint32_t(pieces.size()), | |
| 334 | 21 | .count = uint32_t(local.size()), | |
| 335 | .width = start, | ||
| 336 | 21 | .lights = lightStart}; | |
| 337 | 21 | pieces.insert(pieces.end(), local.begin(), local.end()); | |
| 338 | 21 | placed[root] = result; | |
| 339 | 21 | return result; | |
| 340 | 21 | }; | |
| 341 | ✗ | describe = [&](const Instancer& instancer, uint32_t depth) -> std::optional<InstancerBlock> { | |
| 342 | 22 | if (depth >= kMostNesting) { | |
| 343 | ✗ | return std::nullopt; | |
| 344 | } | ||
| 345 | 22 | std::vector<PrototypeEntry> mine; | |
| 346 | 22 | uint32_t width = 0; | |
| 347 | 22 | uint32_t lightWidth = 0; | |
| 348 | 44 | for (const uint32_t root : instancer.roots) { | |
| 349 | 22 | PrototypeEntry entry{ | |
| 350 | .first = 0, .count = 0, .parent = kNoPrim, .width = 0, .lights = 0}; | ||
| 351 | 22 | if (root != kNoPrim) { | |
| 352 | 22 | const Placed pieced = place(root, depth); | |
| 353 | 22 | entry.first = pieced.first; | |
| 354 | 22 | entry.count = pieced.count; | |
| 355 | 22 | entry.width = pieced.width; | |
| 356 | 22 | entry.lights = pieced.lights; | |
| 357 | 22 | entry.parent = instancer.native ? root : prims.Record(root).parent; | |
| 358 | 78 | while (entry.parent != kNoPrim && | |
| 359 | 39 | !IsA(prims.Record(entry.parent).schema, SchemaId::Xformable)) { | |
| 360 | 17 | entry.parent = prims.Record(entry.parent).parent; | |
| 361 | } | ||
| 362 | } | ||
| 363 | 22 | width = std::max(width, entry.width); | |
| 364 | 22 | lightWidth = std::max(lightWidth, entry.lights); | |
| 365 | 22 | mine.push_back(entry); | |
| 366 | } | ||
| 367 | 22 | const uint32_t first = uint32_t(entries.size()); | |
| 368 | 22 | entries.insert(entries.end(), mine.begin(), mine.end()); | |
| 369 | 22 | const uint32_t range = uint32_t(ranges.size() / 2); | |
| 370 | 22 | ranges.push_back(uint32_t(hidden.size())); | |
| 371 | 22 | if (!instancer.native) { | |
| 372 | 21 | const ArrayRef ids = store.Array(kPointInstancerInvisibleIds, | |
| 373 | 21 | prims.Row(instancer.prim, SchemaId::PointInstancer)); | |
| 374 | 21 | if (ids.address != 0) { | |
| 375 | 2 | std::vector<int64_t> sorted = | |
| 376 | 2 | Items<int64_t>(store.Arrays(ColumnType::Int64Array).Read(ids)); | |
| 377 | 2 | std::ranges::sort(sorted); | |
| 378 | 2 | hidden.insert(hidden.end(), sorted.begin(), sorted.end()); | |
| 379 | 2 | } | |
| 380 | } | ||
| 381 | 22 | ranges.push_back(uint32_t(hidden.size()) - ranges.back()); | |
| 382 | 22 | return InstancerBlock{.instancer = instancer.prim, | |
| 383 | .first = 0, | ||
| 384 | 22 | .count = instancer.count, | |
| 385 | .width = width, | ||
| 386 | .prototypes = first, | ||
| 387 | 22 | .prototypeCount = uint32_t(instancer.roots.size()), | |
| 388 | 22 | .native = instancer.native ? 1u : 0u, | |
| 389 | .hidden = range, | ||
| 390 | 22 | .lightWidth = lightWidth}; | |
| 391 | 339 | }; | |
| 392 | |||
| 393 | 339 | for (const Instancer& instancer : m_instancers) { | |
| 394 | 22 | m_instancerPrims.insert(instancer.prim); | |
| 395 | 22 | if (m_members.contains(instancer.prim)) { | |
| 396 | 3 | continue; | |
| 397 | } | ||
| 398 | 21 | const std::optional<InstancerBlock> block = describe(instancer, 0); | |
| 399 | 21 | if (!block || block->width == 0 || block->count == 0) { | |
| 400 | 2 | continue; | |
| 401 | } | ||
| 402 | 19 | std::set<uint32_t> worlds; | |
| 403 | 38 | for (const uint32_t root : instancer.roots) { | |
| 404 | 19 | if (root != kNoPrim) { | |
| 405 | 46 | for (const uint32_t drawable : m_drawables[root]) { | |
| 406 | 27 | worlds.insert(prims.Record(drawable).world); | |
| 407 | } | ||
| 408 | } | ||
| 409 | } | ||
| 410 | 39 | const auto put = [&](uint32_t tlas) { | |
| 411 | 20 | std::vector<uint32_t>& items = m_blockItems[tlas]; | |
| 412 | 20 | InstancerBlock placedBlock = *block; | |
| 413 | 20 | placedBlock.first = uint32_t(items.size()); | |
| 414 | 20 | const uint32_t index = uint32_t(blocks.size()); | |
| 415 | 20 | blocks.push_back(placedBlock); | |
| 416 | 20 | items.insert(items.end(), size_t(block->count) * block->width, kInstancerSlot | index); | |
| 417 | 30 | for (uint32_t offset = 0; offset < block->count * block->lightWidth; ++offset) { | |
| 418 | 10 | m_lightCopies[tlas].push_back({index, offset}); | |
| 419 | } | ||
| 420 | 39 | for (const uint32_t world : worlds) { | |
| 421 | 19 | if (world != instancer.world) { | |
| 422 | ✗ | m_watch[world].insert(tlas); | |
| 423 | } | ||
| 424 | } | ||
| 425 | 39 | }; | |
| 426 | 19 | if (instancer.world == kGlobalWorld) { | |
| 427 | 39 | for (uint32_t tlas = 0; tlas <= m_worlds; ++tlas) { | |
| 428 | 20 | if (ShowsGlobals(tlas)) { | |
| 429 | 20 | put(tlas); | |
| 430 | } | ||
| 431 | } | ||
| 432 | ✗ | } else if (instancer.world < m_worlds) { | |
| 433 | ✗ | put(instancer.world); | |
| 434 | ✗ | if (const auto shown = m_shownIn.find(instancer.world); shown != m_shownIn.end()) { | |
| 435 | ✗ | for (const uint32_t tlas : shown->second) { | |
| 436 | ✗ | put(tlas); | |
| 437 | } | ||
| 438 | } | ||
| 439 | } | ||
| 440 | 19 | } | |
| 441 | |||
| 442 | 317 | std::vector<std::byte> tables; | |
| 443 | 1585 | const auto table = [&](auto values) { | |
| 444 | 1585 | const uint64_t at = RoundUp(tables.size(), 16); | |
| 445 | 1585 | tables.resize(at); | |
| 446 | 1585 | const std::span<const std::byte> bytes = std::as_bytes(std::span(values)); | |
| 447 | 1585 | tables.insert(tables.end(), bytes.begin(), bytes.end()); | |
| 448 | 1585 | return at; | |
| 449 | }; | ||
| 450 | 317 | const uint64_t blocksStart = table(blocks); | |
| 451 | 317 | const uint64_t entriesStart = table(entries); | |
| 452 | 317 | const uint64_t piecesStart = table(pieces); | |
| 453 | 317 | const uint64_t rangesStart = table(ranges); | |
| 454 | 317 | const uint64_t hiddenStart = table(hidden); | |
| 455 | 614 | tables.resize(std::max<size_t>(RoundUp(tables.size(), 16), 16)); | |
| 456 | 317 | m_instancing = | |
| 457 | 334 | store.GetDevice().CreateBuffer({.label = "instancers"}, std::span<const std::byte>(tables)); | |
| 458 | 317 | const uint64_t instancing = m_instancing->Address(); | |
| 459 | 317 | m_header.blocks.address = instancing + blocksStart; | |
| 460 | 317 | m_header.prototypes.address = instancing + entriesStart; | |
| 461 | 317 | m_header.pieces.address = instancing + piecesStart; | |
| 462 | 317 | m_header.hiddenRanges.address = instancing + rangesStart; | |
| 463 | 317 | m_header.hiddenIds = instancing + hiddenStart; | |
| 464 | 634 | } | |
| 465 | |||
| 466 | 317 | void InstanceTable::WriteLayoutHeader() { | |
| 467 | 317 | m_header.handles.address = m_tlas->Handles().Address(); | |
| 468 | 317 | m_header.shapes.address = m_shapes.Address(); | |
| 469 | 317 | m_header.hits.address = m_hits.Address(); | |
| 470 | 317 | m_header.records = m_records.Address(); | |
| 471 | 317 | m_header.worldCount = m_worlds; | |
| 472 | 317 | m_header.displayColorName = m_structure.GetTokens().Intern("displayColor").id; | |
| 473 | 317 | m_structure.GetColumnStore().GetDevice().GetQueue().Write(m_layout->Slice(0, sizeof(m_header)), | |
| 474 | 317 | &m_header); | |
| 475 | 317 | } | |
| 476 | |||
| 477 | 1065 | device::BufferRange InstanceTable::LayoutWords(uint64_t address, uint32_t first, | |
| 478 | uint32_t count) const { | ||
| 479 | 1065 | return m_layout->Slice(address - m_layout->Address() + uint64_t(first) * sizeof(uint32_t), | |
| 480 | 1065 | uint64_t(count) * sizeof(uint32_t)); | |
| 481 | } | ||
| 482 | |||
| 483 | 187 | void InstanceTable::WriteRegions(const std::set<uint32_t>& tlases) { | |
| 484 | 187 | if (tlases.empty()) { | |
| 485 | return; | ||
| 486 | } | ||
| 487 | 136 | device::Queue& queue = m_structure.GetColumnStore().GetDevice().GetQueue(); | |
| 488 | 275 | for (const uint32_t tlas : tlases) { | |
| 489 | 139 | const uint32_t start = m_regions.Starts()[tlas]; | |
| 490 | 139 | queue.Write(LayoutWords(m_header.instancePrims.address, start, m_regions.Capacity(tlas)), | |
| 491 | 278 | m_regions.Items().data() + start); | |
| 492 | } | ||
| 493 | 136 | queue.Write(LayoutWords(m_header.instanceCounts.address, 0, m_regions.Size()), | |
| 494 | 272 | m_regions.Counts().data()); | |
| 495 | } | ||
| 496 | |||
| 497 | 6 | bool InstanceTable::Restack() { | |
| 498 | 6 | BuildInstancing(); | |
| 499 | 6 | std::set<uint32_t> tlases; | |
| 500 | 12 | for (uint32_t tlas = 0; tlas < m_regions.Size(); ++tlas) { | |
| 501 | 6 | if (!m_regions.Replace(tlas, RegionItems(tlas))) { | |
| 502 | ✗ | return false; | |
| 503 | } | ||
| 504 | 6 | tlases.insert(tlas); | |
| 505 | } | ||
| 506 | 6 | WriteRegions(tlases); | |
| 507 | 6 | WriteLayoutHeader(); | |
| 508 | return true; | ||
| 509 | 6 | } | |
| 510 | |||
| 511 | 191 | std::optional<std::set<uint32_t>> InstanceTable::Apply(const ChangeSet& changes) { | |
| 512 | 191 | CHRONO_BATCH_RENDER_ZONE("Instances/Apply"); | |
| 513 | 191 | const Prims& prims = m_structure.GetPrims(); | |
| 514 | 191 | if (prims.WorldCount() != m_worlds) { | |
| 515 | 5 | return std::nullopt; | |
| 516 | } | ||
| 517 | 186 | std::set<uint32_t> touched; | |
| 518 | 5394 | const auto touch = [&](uint32_t world) { | |
| 519 | 5208 | if (world == kGlobalWorld) { | |
| 520 | 10411 | for (uint32_t tlas = 0; tlas <= m_worlds; ++tlas) { | |
| 521 | 5207 | touched.insert(tlas); | |
| 522 | } | ||
| 523 | } else { | ||
| 524 | 4 | touched.insert(world); | |
| 525 | 4 | if (const auto shown = m_shownIn.find(world); shown != m_shownIn.end()) { | |
| 526 | ✗ | touched.insert(shown->second.begin(), shown->second.end()); | |
| 527 | } | ||
| 528 | } | ||
| 529 | 5394 | }; | |
| 530 | 197 | for (const StructureEntry& entry : changes.apis) { | |
| 531 | 11 | if (entry.api == SchemaId::PrototypeAPI) { | |
| 532 | ✗ | return std::nullopt; | |
| 533 | } | ||
| 534 | } | ||
| 535 | 327 | for (const StructureEntry& entry : changes.removed) { | |
| 536 | 426 | if (m_origins.contains(entry.prim) || m_instancerPrims.contains(entry.prim) || | |
| 537 | 426 | m_members.contains(entry.prim) || | |
| 538 | 142 | std::ranges::find(m_products, entry.prim) != m_products.end()) { | |
| 539 | 1 | return std::nullopt; | |
| 540 | } | ||
| 541 | 141 | std::vector<uint32_t>& list = entry.world == kGlobalWorld ? m_globals : m_own[entry.world]; | |
| 542 | 141 | if (std::erase(list, entry.prim) > 0) { | |
| 543 | 95 | touch(entry.world); | |
| 544 | } | ||
| 545 | } | ||
| 546 | 5425 | for (const StructureEntry& entry : changes.added) { | |
| 547 | 5241 | if (!prims.Alive(entry.prim) || prims.Generation(entry.prim) != entry.generation) { | |
| 548 | 26 | continue; | |
| 549 | } | ||
| 550 | 5215 | const SchemaId schema = prims.Record(entry.prim).schema; | |
| 551 | 5215 | if (IsA(schema, SchemaId::World) || schema == SchemaId::RenderProduct || | |
| 552 | 10429 | schema == SchemaId::PointInstancer || | |
| 553 | 15643 | prims.HasApi(entry.prim, SchemaId::PrototypeAPI) || Member(entry.prim)) { | |
| 554 | 1 | return std::nullopt; | |
| 555 | } | ||
| 556 | 5214 | if (Drawable(schema)) { | |
| 557 | 5113 | (entry.world == kGlobalWorld ? m_globals : m_own[entry.world]).push_back(entry.prim); | |
| 558 | 5113 | touch(entry.world); | |
| 559 | } | ||
| 560 | } | ||
| 561 | 317 | for (const uint32_t tlas : touched) { | |
| 562 | 136 | if (!m_regions.Replace(tlas, RegionItems(tlas))) { | |
| 563 | 3 | return std::nullopt; | |
| 564 | } | ||
| 565 | } | ||
| 566 | |||
| 567 | 181 | WriteRegions(touched); | |
| 568 | 181 | return touched; | |
| 569 | 191 | } | |
| 570 | |||
| 571 | } | ||
| 572 |