stage/columns/column_store.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // SPDX-License-Identifier: BSD-3-Clause | ||
| 2 | #include "chrono_batch_render/stage/columns/column_store.h" | ||
| 3 | |||
| 4 | #include <algorithm> | ||
| 5 | #include <cstring> | ||
| 6 | #include <span> | ||
| 7 | |||
| 8 | #include <fmt/format.h> | ||
| 9 | |||
| 10 | #include "chrono_batch_render/device/commands.h" | ||
| 11 | #include "chrono_batch_render/device/queue.h" | ||
| 12 | #include "chrono_batch_render/stage/columns/kernels/cells.h" | ||
| 13 | #include "chrono_batch_render/stage/kernels.h" | ||
| 14 | #include "chrono_batch_render/util/error.h" | ||
| 15 | #include "chrono_batch_render/util/profiler.h" | ||
| 16 | #include "chrono_batch_render/util/span.h" | ||
| 17 | |||
| 18 | namespace chrono::batch_render { | ||
| 19 | |||
| 20 | namespace { | ||
| 21 | |||
| 22 | constexpr uint32_t kNoPrimFill = 0xFFFFFFFF; | ||
| 23 | constexpr uint64_t kLargestStage = 64 * 1024; | ||
| 24 | |||
| 25 | 85522 | std::vector<std::byte> Repeat(std::span<const std::byte> cell, uint32_t count) { | |
| 26 | 85522 | std::vector<std::byte> bytes; | |
| 27 | 85522 | bytes.reserve(cell.size() * count); | |
| 28 | 386565 | for (uint32_t i = 0; i < count; ++i) { | |
| 29 | 301043 | bytes.insert(bytes.end(), cell.begin(), cell.end()); | |
| 30 | } | ||
| 31 | 85522 | return bytes; | |
| 32 | ✗ | } | |
| 33 | |||
| 34 | 56338 | std::vector<TableColumn> SchemaColumns(SchemaId schema) { | |
| 35 | 56338 | std::vector<TableColumn> columns; | |
| 36 | 56338 | const uint32_t count = Info(schema).columnCount; | |
| 37 | 242346 | for (uint32_t column = 0; column < count; ++column) { | |
| 38 | 186008 | columns.push_back({ElementBytes(Columns(schema)[column].type), 0}); | |
| 39 | } | ||
| 40 | 56338 | columns.push_back({sizeof(uint32_t), kNoPrimFill}); | |
| 41 | 56338 | return columns; | |
| 42 | ✗ | } | |
| 43 | |||
| 44 | } | ||
| 45 | |||
| 46 | 507 | ColumnStore::ColumnStore(device::Device& device) | |
| 47 | 507 | : m_device(device), m_heap(device, "scene"), | |
| 48 | 1014 | m_prims(m_heap, "prims", | |
| 49 | {{4, 0}, {4, kNoPrimFill}, {4, kNoPrimFill}, {4, 0}, {4, kNoPrimFill}, {8, 0}}), | ||
| 50 | 1014 | m_slots(m_heap, "prim slots", {{4, kNoPrimFill}}), | |
| 51 | 2028 | m_worlds(m_heap, "world stamps", {{sizeof(uint64_t), 0}}) { | |
| 52 | 507 | m_entries.reserve(uint32_t(SchemaId::Count)); | |
| 53 | 507 | Lookup(SchemaId(uint32_t(SchemaId::Count) - 1)); | |
| 54 | 507 | m_worlds.Allocate(1); | |
| 55 | 507 | } | |
| 56 | |||
| 57 | 1647277 | const ColumnStore::Entry& ColumnStore::Lookup(SchemaId schema) const { | |
| 58 | 1647277 | if (uint32_t(schema) >= SchemaCount()) { | |
| 59 | ✗ | throw Error("No data for schema {}", uint32_t(schema)); | |
| 60 | } | ||
| 61 | 1703615 | while (m_entries.size() <= uint32_t(schema)) { | |
| 62 | 56338 | const SchemaId added = SchemaId(uint32_t(m_entries.size())); | |
| 63 | 56338 | Entry entry; | |
| 64 | 56338 | entry.table = std::make_unique<ColumnTable>( | |
| 65 | 225352 | m_heap, fmt::format("{} data", Info(added).name), SchemaColumns(added)); | |
| 66 | 112676 | entry.stamps.assign(Info(added).columnCount, kNoStamp); | |
| 67 | 112676 | entry.writes.assign(Info(added).columnCount, 0); | |
| 68 | 56338 | m_entries.push_back(std::move(entry)); | |
| 69 | 56338 | } | |
| 70 | 1647277 | return m_entries[uint32_t(schema)]; | |
| 71 | } | ||
| 72 | |||
| 73 | 410334 | ColumnTable& ColumnStore::Schema(SchemaId schema) { | |
| 74 | 410334 | return *Lookup(schema).table; | |
| 75 | } | ||
| 76 | |||
| 77 | ✗ | const ColumnTable& ColumnStore::Schema(SchemaId schema) const { | |
| 78 | ✗ | return *Lookup(schema).table; | |
| 79 | } | ||
| 80 | |||
| 81 | 40 | void ColumnStore::Pin(SchemaId schema) { | |
| 82 | 40 | Lookup(schema); | |
| 83 | 40 | ++m_entries[uint32_t(schema)].pins; | |
| 84 | 40 | } | |
| 85 | |||
| 86 | 39 | void ColumnStore::Unpin(SchemaId schema) { | |
| 87 | 39 | if (!Pinned(schema)) { | |
| 88 | ✗ | throw Error("{} data isn't pinned", Info(schema).name); | |
| 89 | } | ||
| 90 | 39 | --m_entries[uint32_t(schema)].pins; | |
| 91 | 39 | } | |
| 92 | |||
| 93 | 507 | ColumnStore::~ColumnStore() { | |
| 94 | 507 | if (!m_attachments.empty()) { | |
| 95 | 1 | try { | |
| 96 | 1 | m_device.GetQueue().Mark().Wait(); | |
| 97 | ✗ | } catch (...) { | |
| 98 | ✗ | } | |
| 99 | } | ||
| 100 | 1337 | } | |
| 101 | |||
| 102 | namespace { | ||
| 103 | |||
| 104 | 20 | bool SameColumn(ColumnRef a, ColumnRef b) { | |
| 105 | 20 | return a.schema == b.schema && a.index == b.index; | |
| 106 | } | ||
| 107 | |||
| 108 | } | ||
| 109 | |||
| 110 | 8 | void ColumnStore::Attach(Attachment attachment) { | |
| 111 | 8 | if (Attached(attachment.column, attachment.rows)) { | |
| 112 | ✗ | throw Error("{}'s {} is already attached for some of these prims", | |
| 113 | ✗ | Info(attachment.column.schema).name, attachment.column.column->field); | |
| 114 | } | ||
| 115 | 8 | attachment.sorted = attachment.rows; | |
| 116 | 8 | std::ranges::sort(attachment.sorted); | |
| 117 | 8 | Pin(attachment.column.schema); | |
| 118 | 8 | m_attachments.push_back(std::move(attachment)); | |
| 119 | 8 | } | |
| 120 | |||
| 121 | 9 | const Attachment* ColumnStore::FindAttachment(ColumnRef column, | |
| 122 | std::span<const uint32_t> rows) const { | ||
| 123 | 9 | for (const Attachment& attachment : m_attachments) { | |
| 124 | 9 | if (SameColumn(attachment.column, column) && std::ranges::equal(attachment.rows, rows)) { | |
| 125 | 9 | return &attachment; | |
| 126 | } | ||
| 127 | } | ||
| 128 | ✗ | return nullptr; | |
| 129 | } | ||
| 130 | |||
| 131 | 142 | bool ColumnStore::Attached(ColumnRef column, std::span<const uint32_t> rows) const { | |
| 132 | 142 | for (const Attachment& attachment : m_attachments) { | |
| 133 | 4 | if (!SameColumn(attachment.column, column)) { | |
| 134 | ✗ | continue; | |
| 135 | } | ||
| 136 | 4 | for (const uint32_t row : rows) { | |
| 137 | 4 | if (row != kNoRow && std::ranges::binary_search(attachment.sorted, row)) { | |
| 138 | 4 | return true; | |
| 139 | } | ||
| 140 | } | ||
| 141 | } | ||
| 142 | 138 | return false; | |
| 143 | } | ||
| 144 | |||
| 145 | 8 | Attachment ColumnStore::Detach(ColumnRef column, std::span<const uint32_t> rows) { | |
| 146 | 8 | const auto found = std::ranges::find_if(m_attachments, [&](const Attachment& attachment) { | |
| 147 | 7 | return SameColumn(attachment.column, column) && std::ranges::equal(attachment.rows, rows); | |
| 148 | }); | ||
| 149 | 8 | if (found == m_attachments.end()) { | |
| 150 | 3 | throw Error("{}'s {} isn't attached for these prims", Info(column.schema).name, | |
| 151 | 2 | column.column->field); | |
| 152 | } | ||
| 153 | 7 | Attachment attachment = std::move(*found); | |
| 154 | 7 | m_attachments.erase(found); | |
| 155 | 7 | Unpin(column.schema); | |
| 156 | 7 | return attachment; | |
| 157 | ✗ | } | |
| 158 | |||
| 159 | 2445 | bool ColumnStore::Pinned(SchemaId schema) const { | |
| 160 | 2445 | return Lookup(schema).pins > 0; | |
| 161 | } | ||
| 162 | |||
| 163 | 7161 | ArrayPool& ColumnStore::Arrays(ColumnType type) { | |
| 164 | 7161 | const uint32_t item = ItemBytes(type); | |
| 165 | 7161 | if (item == 0) { | |
| 166 | ✗ | throw Error("Column type {} holds no arrays", uint32_t(type)); | |
| 167 | } | ||
| 168 | 7161 | std::unique_ptr<ArrayPool>& arrays = m_arrayHeaps[item]; | |
| 169 | 7161 | if (!arrays) { | |
| 170 | 1120 | arrays = std::make_unique<ArrayPool>(m_heap, fmt::format("{}-byte arrays", item), item); | |
| 171 | } | ||
| 172 | 7161 | return *arrays; | |
| 173 | } | ||
| 174 | |||
| 175 | 194720 | uint32_t ColumnStore::PrimsColumn(SchemaId schema) const { | |
| 176 | 194720 | Lookup(schema); | |
| 177 | 194720 | return Info(schema).columnCount; | |
| 178 | } | ||
| 179 | |||
| 180 | 679387 | uint32_t ColumnStore::StampColumn(ColumnRef column) const { | |
| 181 | 679387 | const std::vector<uint32_t>& stamps = Lookup(column.schema).stamps; | |
| 182 | 679387 | if (column.index >= stamps.size()) { | |
| 183 | ✗ | throw Error("{} has no column {}", Info(column.schema).name, column.index); | |
| 184 | } | ||
| 185 | 679387 | return stamps[column.index]; | |
| 186 | } | ||
| 187 | |||
| 188 | 9764 | bool ColumnStore::Tracked(ColumnRef column) const { | |
| 189 | 9764 | return StampColumn(column) != kNoStamp; | |
| 190 | } | ||
| 191 | |||
| 192 | 9759 | void ColumnStore::TrackRows(ColumnRef column) { | |
| 193 | 9759 | if (Tracked(column)) { | |
| 194 | 4071 | return; | |
| 195 | } | ||
| 196 | 5688 | Entry& entry = m_entries[uint32_t(column.schema)]; | |
| 197 | 5688 | const uint32_t stamps = entry.table->AddColumn({sizeof(uint64_t), 0}); | |
| 198 | 5688 | entry.stamps[column.index] = stamps; | |
| 199 | 5688 | const Ordinal published = m_stamp - 1; | |
| 200 | 5688 | FillWords(*entry.table, stamps, 0, entry.table->Rows(), | |
| 201 | std::as_bytes(std::span(&published, 1))); | ||
| 202 | 11376 | entry.table->Flush(); | |
| 203 | } | ||
| 204 | |||
| 205 | 150075 | void ColumnStore::Touch(ColumnRef column) { | |
| 206 | 150075 | StampColumn(column); | |
| 207 | 150075 | m_entries[uint32_t(column.schema)].writes[column.index] = ++m_writes; | |
| 208 | 150075 | } | |
| 209 | |||
| 210 | 359844 | uint64_t ColumnStore::LastWrite(ColumnRef column) const { | |
| 211 | 359844 | StampColumn(column); | |
| 212 | 359844 | return Lookup(column.schema).writes[column.index]; | |
| 213 | } | ||
| 214 | |||
| 215 | 70280 | StampTarget ColumnStore::Stamps(ColumnRef column) { | |
| 216 | 70280 | const uint32_t stamps = StampColumn(column); | |
| 217 | 70280 | const ColumnTable& table = Schema(column.schema); | |
| 218 | 70280 | StampTarget target{}; | |
| 219 | 70280 | target.stamps.address = stamps == kNoStamp ? 0 : table.Pages(stamps); | |
| 220 | 70280 | target.tablePrims.address = table.Pages(PrimsColumn(column.schema)); | |
| 221 | 70280 | target.scene.address = DeviceData(); | |
| 222 | 70280 | target.stamp = m_stamp; | |
| 223 | 70280 | return target; | |
| 224 | } | ||
| 225 | |||
| 226 | 81670 | void ColumnStore::StampWorld(uint32_t world) { | |
| 227 | 81670 | const uint32_t slot = WorldSlot(world); | |
| 228 | 81670 | if (slot >= m_worlds.Rows()) { | |
| 229 | ✗ | throw Error("World {} of a scene of {}", world, m_worldCount); | |
| 230 | } | ||
| 231 | 81670 | m_worlds.Stage(0, slot, std::as_bytes(std::span(&m_stamp, 1))); | |
| 232 | 81670 | m_worldStamps[slot] = m_stamp; | |
| 233 | 81670 | } | |
| 234 | |||
| 235 | 12006 | Ordinal ColumnStore::WorldStamp(uint32_t world) const { | |
| 236 | 12006 | const uint32_t slot = WorldSlot(world); | |
| 237 | 12006 | if (slot >= m_worldStamps.size()) { | |
| 238 | ✗ | throw Error("World {} of a scene of {}", world, m_worldCount); | |
| 239 | } | ||
| 240 | 12006 | return m_worldStamps[slot]; | |
| 241 | } | ||
| 242 | |||
| 243 | 37843 | device::Program& ColumnStore::Kernels() { | |
| 244 | 37843 | if (!m_program) { | |
| 245 | 1284 | m_program = m_device.CreateProgram( | |
| 246 | {.modules = {"chrono_batch_render.stage.columns.kernels.cells", | ||
| 247 | "chrono_batch_render.stage.columns.kernels.changes"}, | ||
| 248 | .library = &kStageKernels, | ||
| 249 | .entries = {"CopyWords", "ScatterCells", "StampRows", "GatherCells", "FillCells", | ||
| 250 | "CloneRows", "WritePoses", "ReadPoses", "CompactChanged", "GatherRows"}, | ||
| 251 | 856 | .label = "scene store"}); | |
| 252 | } | ||
| 253 | 37843 | return *m_program; | |
| 254 | 856 | } | |
| 255 | |||
| 256 | 274 | device::Download ColumnStore::ReadRowsAsync(const Ref<device::Buffer>& source, uint32_t stride, | |
| 257 | std::span<const uint32_t> rows) { | ||
| 258 | 274 | if (rows.empty() || !source) { | |
| 259 | 1 | return {}; | |
| 260 | } | ||
| 261 | 273 | const uint64_t bytes = uint64_t(rows.size()) * stride; | |
| 262 | 273 | const Ref<device::Buffer> out = m_device.CreateBuffer({.bytes = bytes, .label = "read rows"}); | |
| 263 | 273 | const Ref<device::Buffer> list = m_device.CreateBuffer({.label = "read row list"}, rows); | |
| 264 | 273 | GatherRowsParams params{}; | |
| 265 | 273 | params.source.address = source->Address(); | |
| 266 | 273 | params.rows.address = list->Address(); | |
| 267 | 273 | params.out.address = out->Address(); | |
| 268 | 273 | params.words = stride / uint32_t(sizeof(uint32_t)); | |
| 269 | 273 | params.count = uint32_t(rows.size()); | |
| 270 | 273 | Dispatch("GatherRows", params, params.count); | |
| 271 | 273 | return m_device.GetQueue().ReadAsync(out->Slice(0, bytes)); | |
| 272 | 1365 | } | |
| 273 | |||
| 274 | 91214 | void ColumnStore::FillWords(ColumnTable& table, uint32_t column, uint32_t first, uint32_t count, | |
| 275 | std::span<const std::byte> cell) { | ||
| 276 | 91214 | if (cell.size() != table.CellBytes(column)) { | |
| 277 | ✗ | throw Error("{} bytes for a cell of {} bytes in {}", cell.size(), table.CellBytes(column), | |
| 278 | ✗ | table.Label()); | |
| 279 | } | ||
| 280 | 91214 | if (count == 0) { | |
| 281 | 91207 | return; | |
| 282 | } | ||
| 283 | 85528 | if (cell.size() * count <= kLargestStage) { | |
| 284 | 85521 | table.Stage(column, first, Repeat(cell, count)); | |
| 285 | 85521 | return; | |
| 286 | } | ||
| 287 | 7 | if (cell.size() > kMaxFillWords * sizeof(uint32_t)) { | |
| 288 | ✗ | throw Error("Cells of {} bytes are too wide to fill", cell.size()); | |
| 289 | } | ||
| 290 | 7 | table.Flush(); | |
| 291 | 7 | FillCellsParams params{}; | |
| 292 | 7 | params.column.address = table.Pages(column); | |
| 293 | 7 | params.first = first; | |
| 294 | 7 | params.count = count; | |
| 295 | 7 | params.words = uint32_t(cell.size() / sizeof(uint32_t)); | |
| 296 | 7 | std::memcpy(params.cell, cell.data(), cell.size()); | |
| 297 | 7 | Dispatch("FillCells", params, uint64_t(count) * params.words); | |
| 298 | } | ||
| 299 | |||
| 300 | 79795 | void ColumnStore::StampRows(ColumnRef column, uint32_t first, uint32_t count) { | |
| 301 | 79795 | const uint32_t stamps = StampColumn(column); | |
| 302 | 79795 | if (stamps != kNoStamp) { | |
| 303 | 10265 | FillWords(Schema(column.schema), stamps, first, count, | |
| 304 | std::as_bytes(std::span(&m_stamp, 1))); | ||
| 305 | } | ||
| 306 | 79795 | } | |
| 307 | |||
| 308 | 3329 | void ColumnStore::StageLive(ColumnRef column, uint32_t first, std::span<const std::byte> cells) { | |
| 309 | 3329 | CheckStoreLock(m_lock, "Writing a scene's columns"); | |
| 310 | 3329 | ColumnTable& table = Schema(column.schema); | |
| 311 | 3329 | table.Stage(column.index, first, cells); | |
| 312 | 3329 | Touch(column); | |
| 313 | 3329 | StampRows(column, first, uint32_t(cells.size() / table.CellBytes(column.index))); | |
| 314 | 3329 | } | |
| 315 | |||
| 316 | 78585 | bool ColumnStore::OverridesBetween(ColumnRef column, uint32_t first, uint32_t count) const { | |
| 317 | 78585 | if (m_overrides.empty() || count == 0) { | |
| 318 | return false; | ||
| 319 | } | ||
| 320 | 2 | const auto found = m_overrides.lower_bound(ArrayKey(column, first)); | |
| 321 | 2 | return found != m_overrides.end() && found->first <= ArrayKey(column, first + count - 1); | |
| 322 | } | ||
| 323 | |||
| 324 | 3323 | void ColumnStore::Stage(ColumnRef column, uint32_t first, std::span<const std::byte> cells) { | |
| 325 | 3323 | const uint32_t cell = Schema(column.schema).CellBytes(column.index); | |
| 326 | 3323 | const auto count = uint32_t(cells.size() / cell); | |
| 327 | 3323 | if (!OverridesBetween(column, first, count)) { | |
| 328 | 3322 | StageLive(column, first, cells); | |
| 329 | 3322 | return; | |
| 330 | } | ||
| 331 | 1 | std::vector<std::byte> live(cells.begin(), cells.end()); | |
| 332 | 1 | for (auto at = m_overrides.lower_bound(ArrayKey(column, first)); | |
| 333 | 2 | at != m_overrides.end() && at->first <= ArrayKey(column, first + count - 1); ++at) { | |
| 334 | 1 | const size_t offset = size_t(uint32_t(at->first) - first) * cell; | |
| 335 | 1 | at->second.authored.assign(cells.begin() + offset, cells.begin() + offset + cell); | |
| 336 | 1 | std::ranges::copy(at->second.layers.back().value, live.begin() + offset); | |
| 337 | } | ||
| 338 | 1 | StageLive(column, first, live); | |
| 339 | 1 | } | |
| 340 | |||
| 341 | 75262 | void ColumnStore::Fill(ColumnRef column, uint32_t first, uint32_t count, | |
| 342 | std::span<const std::byte> cell) { | ||
| 343 | 75262 | CheckStoreLock(m_lock, "Writing a scene's columns"); | |
| 344 | 75262 | if (OverridesBetween(column, first, count)) { | |
| 345 | 1 | Stage(column, first, Repeat(cell, count)); | |
| 346 | 1 | return; | |
| 347 | } | ||
| 348 | 75261 | FillWords(Schema(column.schema), column.index, first, count, cell); | |
| 349 | 75261 | Touch(column); | |
| 350 | 75261 | StampRows(column, first, count); | |
| 351 | } | ||
| 352 | |||
| 353 | 134844 | void ColumnStore::LandAuthored(bool wait) const { | |
| 354 | 134844 | std::erase_if(m_authoredReads, [&](AuthoredRead& pending) { | |
| 355 | 1 | if (!wait && !pending.read.Ready()) { | |
| 356 | return false; | ||
| 357 | } | ||
| 358 | 1 | pending.read.Wait(); | |
| 359 | 1 | const auto found = m_overrides.find(pending.key); | |
| 360 | 1 | const Span<const std::byte> bytes = pending.read.Bytes(); | |
| 361 | 1 | if (found != m_overrides.end() && bytes.size() == found->second.authored.size() && | |
| 362 | 1 | !std::ranges::equal(bytes, found->second.layers.back().value)) { | |
| 363 | 1 | found->second.authored.assign(bytes.begin(), bytes.end()); | |
| 364 | } | ||
| 365 | return true; | ||
| 366 | }); | ||
| 367 | 134844 | } | |
| 368 | |||
| 369 | 3 | void ColumnStore::Override(ColumnRef column, uint32_t row, uint64_t owner, | |
| 370 | std::span<const std::byte> authored, std::span<const std::byte> value) { | ||
| 371 | 3 | LandAuthored(true); | |
| 372 | 3 | const uint32_t cell = Schema(column.schema).CellBytes(column.index); | |
| 373 | 3 | if (value.size() != cell) { | |
| 374 | ✗ | throw Error("{} bytes override {}, whose cells are {}", value.size(), column.column->field, | |
| 375 | ✗ | cell); | |
| 376 | } | ||
| 377 | 3 | OverrideCell& entry = m_overrides[ArrayKey(column, row)]; | |
| 378 | 3 | if (entry.layers.empty()) { | |
| 379 | 2 | entry.authored.assign(authored.begin(), authored.end()); | |
| 380 | } | ||
| 381 | 3 | const auto layer = std::ranges::find(entry.layers, owner, &OverrideLayer::owner); | |
| 382 | 3 | if (layer != entry.layers.end()) { | |
| 383 | ✗ | layer->value.assign(value.begin(), value.end()); | |
| 384 | } else { | ||
| 385 | 3 | entry.layers.push_back({owner, {value.begin(), value.end()}}); | |
| 386 | 3 | std::ranges::sort(entry.layers, {}, &OverrideLayer::owner); | |
| 387 | } | ||
| 388 | 3 | StageLive(column, row, entry.layers.back().value); | |
| 389 | 9 | } | |
| 390 | |||
| 391 | 2 | void ColumnStore::ClearOverride(ColumnRef column, uint32_t row, uint64_t owner) { | |
| 392 | 2 | LandAuthored(true); | |
| 393 | 2 | const auto found = m_overrides.find(ArrayKey(column, row)); | |
| 394 | 2 | if (found == m_overrides.end()) { | |
| 395 | ✗ | return; | |
| 396 | } | ||
| 397 | 2 | std::erase_if(found->second.layers, | |
| 398 | 3 | [&](const OverrideLayer& layer) { return layer.owner == owner; }); | |
| 399 | 2 | if (found->second.layers.empty()) { | |
| 400 | 1 | const std::vector<std::byte> authored = std::move(found->second.authored); | |
| 401 | 1 | m_overrides.erase(found); | |
| 402 | 1 | StageLive(column, row, authored); | |
| 403 | 1 | } else { | |
| 404 | 1 | StageLive(column, row, found->second.layers.back().value); | |
| 405 | } | ||
| 406 | } | ||
| 407 | |||
| 408 | 8 | bool ColumnStore::Overridden(ColumnRef column, uint32_t row) const { | |
| 409 | 8 | return m_overrides.contains(ArrayKey(column, row)); | |
| 410 | } | ||
| 411 | |||
| 412 | 2 | std::vector<std::byte> ColumnStore::Authored(ColumnRef column, uint32_t row) const { | |
| 413 | 2 | LandAuthored(true); | |
| 414 | 2 | const auto found = m_overrides.find(ArrayKey(column, row)); | |
| 415 | 2 | return found == m_overrides.end() ? std::vector<std::byte>() : found->second.authored; | |
| 416 | } | ||
| 417 | |||
| 418 | 70278 | void ColumnStore::Reassert(ColumnRef column, std::span<const uint32_t> rows) { | |
| 419 | 70278 | if (m_overrides.empty()) { | |
| 420 | return; | ||
| 421 | } | ||
| 422 | 1 | ColumnTable& table = Schema(column.schema); | |
| 423 | 2 | for (const uint32_t row : rows) { | |
| 424 | 1 | if (const auto found = m_overrides.find(ArrayKey(column, row)); | |
| 425 | 1 | found != m_overrides.end()) { | |
| 426 | 2 | if (const std::optional<device::BufferRange> cell = | |
| 427 | 1 | table.Contiguous(column.index, row, 1)) { | |
| 428 | 1 | m_authoredReads.push_back( | |
| 429 | 1 | {ArrayKey(column, row), m_device.GetQueue().ReadAsync(*cell)}); | |
| 430 | } | ||
| 431 | 1 | StageLive(column, row, found->second.layers.back().value); | |
| 432 | } | ||
| 433 | } | ||
| 434 | 1 | } | |
| 435 | |||
| 436 | 1214 | void ColumnStore::DropOverrides(SchemaId table, uint32_t row) { | |
| 437 | 1214 | if (m_overrides.empty()) { | |
| 438 | return; | ||
| 439 | } | ||
| 440 | 4 | LandAuthored(true); | |
| 441 | 32 | for (uint32_t index = 0; index < Info(table).columnCount; ++index) { | |
| 442 | 12 | m_overrides.erase(ArrayKey({table, &Columns(table)[index], index}, row)); | |
| 443 | } | ||
| 444 | } | ||
| 445 | |||
| 446 | 338 | void ColumnStore::CloneRows(SchemaId table, std::span<const uint32_t> sources, uint32_t first, | |
| 447 | uint32_t copies) { | ||
| 448 | 338 | if (sources.empty() || copies == 0) { | |
| 449 | ✗ | return; | |
| 450 | } | ||
| 451 | 338 | Flush(); | |
| 452 | 338 | ColumnTable& rows = Schema(table); | |
| 453 | 338 | const Ref<device::Buffer> sourceRows = m_device.CreateBuffer({.label = "cloned rows"}, sources); | |
| 454 | 338 | const uint32_t count = uint32_t(sources.size()); | |
| 455 | 338 | const Span<const ColumnInfo> columns = Columns(table); | |
| 456 | 3086 | for (uint32_t index = 0; index < Info(table).columnCount; ++index) { | |
| 457 | 1205 | const ColumnInfo& info = columns[index]; | |
| 458 | 1205 | const ColumnRef column{table, &info, index}; | |
| 459 | 1205 | CloneRowsParams params{}; | |
| 460 | 1205 | params.column.address = rows.Pages(index); | |
| 461 | 1205 | params.sources.address = sourceRows->Address(); | |
| 462 | 1205 | params.first = first; | |
| 463 | 1205 | params.count = count; | |
| 464 | 1205 | params.copies = copies; | |
| 465 | 1205 | params.words = rows.CellBytes(index) / sizeof(uint32_t); | |
| 466 | 1205 | Dispatch("CloneRows", params, uint64_t(copies) * count * params.words); | |
| 467 | 1205 | Touch(column); | |
| 468 | 1205 | StampRows(column, first, count * copies); | |
| 469 | 1205 | for (uint32_t source = 0; source < count && !m_overrides.empty(); ++source) { | |
| 470 | ✗ | const auto found = m_overrides.find(ArrayKey(column, sources[source])); | |
| 471 | ✗ | if (found == m_overrides.end()) { | |
| 472 | ✗ | continue; | |
| 473 | } | ||
| 474 | ✗ | for (uint32_t copy = 0; copy < copies; ++copy) { | |
| 475 | ✗ | StageLive(column, first + copy * count + source, found->second.authored); | |
| 476 | } | ||
| 477 | } | ||
| 478 | } | ||
| 479 | 1014 | } | |
| 480 | |||
| 481 | 273526 | uint64_t ColumnStore::ArrayKey(ColumnRef column, uint32_t row) { | |
| 482 | 273526 | return (uint64_t(column.schema) << 48) | (uint64_t(column.index) << 32) | row; | |
| 483 | } | ||
| 484 | |||
| 485 | 11458 | ArrayRef ColumnStore::Array(ColumnRef column, uint32_t row) const { | |
| 486 | 11458 | const auto found = m_arrays.find(ArrayKey(column, row)); | |
| 487 | 17363 | return found == m_arrays.end() ? ArrayRef{} : found->second; | |
| 488 | } | ||
| 489 | |||
| 490 | 122 | bool ColumnStore::Exclusive(ColumnRef column, uint32_t row) const { | |
| 491 | 122 | const ArrayRef array = Array(column, row); | |
| 492 | 122 | if (array.address == 0) { | |
| 493 | return false; | ||
| 494 | } | ||
| 495 | 122 | const uint32_t rows[] = {row}; | |
| 496 | 243 | return Attached(column, rows) || | |
| 497 | 121 | const_cast<ColumnStore&>(*this).Arrays(column.column->type).Borrowed(array); | |
| 498 | } | ||
| 499 | |||
| 500 | 2578 | void ColumnStore::SetArray(ColumnRef column, uint32_t row, const ArrayRef& array) { | |
| 501 | 2578 | CheckStoreLock(m_lock, "Writing a scene's columns"); | |
| 502 | 2578 | const uint64_t key = ArrayKey(column, row); | |
| 503 | 2578 | if (const auto found = m_arrays.find(key); found != m_arrays.end()) { | |
| 504 | 396 | const ArrayRef old = found->second; | |
| 505 | 396 | m_arrays.erase(found); | |
| 506 | 396 | if (old.address != array.address) { | |
| 507 | 298 | Arrays(column.column->type).Free(old); | |
| 508 | } | ||
| 509 | } | ||
| 510 | 2578 | if (array.address != 0) { | |
| 511 | 2482 | m_arrays[key] = array; | |
| 512 | } | ||
| 513 | 2578 | Stage(column, row, std::as_bytes(std::span(&array, 1))); | |
| 514 | 2578 | m_arrayWrites[ArrayKey(column, 0)].push_back({m_stamp, row}); | |
| 515 | 2578 | } | |
| 516 | |||
| 517 | 61321 | std::vector<uint32_t> ColumnStore::ArrayRows(ColumnRef column, Ordinal since, Ordinal until) const { | |
| 518 | 61321 | std::vector<uint32_t> rows; | |
| 519 | 61321 | const auto found = m_arrayWrites.find(ArrayKey(column, 0)); | |
| 520 | 61321 | if (found == m_arrayWrites.end()) { | |
| 521 | return rows; | ||
| 522 | } | ||
| 523 | 5721 | const std::vector<ArrayWrite>& writes = found->second; | |
| 524 | 11276 | for (auto at = std::ranges::upper_bound(writes, since, {}, &ArrayWrite::stamp); | |
| 525 | 11276 | at != writes.end() && at->stamp <= until; ++at) { | |
| 526 | 5555 | rows.push_back(at->row); | |
| 527 | } | ||
| 528 | 5721 | std::ranges::sort(rows); | |
| 529 | 5721 | rows.erase(std::ranges::unique(rows).begin(), rows.end()); | |
| 530 | 5721 | return rows; | |
| 531 | ✗ | } | |
| 532 | |||
| 533 | 23 | void ColumnStore::LogArrayRows(ColumnRef column, std::span<const uint32_t> rows) { | |
| 534 | 23 | std::vector<ArrayWrite>& writes = m_arrayWrites[ArrayKey(column, 0)]; | |
| 535 | 70 | for (const uint32_t row : rows) { | |
| 536 | 47 | if (row != kNoRow) { | |
| 537 | 47 | writes.push_back({m_stamp, row}); | |
| 538 | } | ||
| 539 | } | ||
| 540 | 23 | } | |
| 541 | |||
| 542 | 1166 | void ColumnStore::TrimArrayWrites(Ordinal through, | |
| 543 | std::span<const std::pair<ColumnRef, Ordinal>> held) { | ||
| 544 | 4795 | for (auto& [key, writes] : m_arrayWrites) { | |
| 545 | 3629 | Ordinal kept = through; | |
| 546 | 199163 | for (const auto& [column, seen] : held) { | |
| 547 | 195534 | if (ArrayKey(column, 0) == key) { | |
| 548 | 5092 | kept = std::min(kept, seen); | |
| 549 | } | ||
| 550 | } | ||
| 551 | 3629 | writes.erase(writes.begin(), | |
| 552 | 3629 | std::ranges::upper_bound(writes, kept, {}, &ArrayWrite::stamp)); | |
| 553 | } | ||
| 554 | 1166 | } | |
| 555 | |||
| 556 | 2720 | void ColumnStore::SetCounts(uint32_t prims, uint32_t worlds) { | |
| 557 | 2720 | m_primCount = prims; | |
| 558 | 2720 | m_worldCount = worlds; | |
| 559 | 2720 | if (worlds + 1 > m_worlds.Rows()) { | |
| 560 | 138 | m_worlds.Allocate(worlds + 1 - m_worlds.Rows()); | |
| 561 | } | ||
| 562 | 2720 | m_worldStamps.resize(worlds + 1, 0); | |
| 563 | 2720 | } | |
| 564 | |||
| 565 | 820325 | void ColumnStore::Place(std::byte* data, uint32_t offset, uint64_t address) const { | |
| 566 | 820325 | std::memcpy(data + offset, &address, sizeof(address)); | |
| 567 | 820325 | } | |
| 568 | |||
| 569 | 134833 | void ColumnStore::Flush() { | |
| 570 | 134833 | CheckStoreLock(m_lock, "Writing a scene's columns"); | |
| 571 | 134833 | CHRONO_BATCH_RENDER_ZONE("ColumnStore/Flush"); | |
| 572 | 134833 | LandAuthored(false); | |
| 573 | |||
| 574 | 134833 | struct Scattered { | |
| 575 | ColumnTable* table; | ||
| 576 | uint32_t column; | ||
| 577 | uint32_t count; | ||
| 578 | uint32_t words; | ||
| 579 | uint64_t rows; | ||
| 580 | uint64_t cells; | ||
| 581 | }; | ||
| 582 | |||
| 583 | 134833 | std::vector<Scattered> scattered; | |
| 584 | 134833 | std::vector<std::byte>& payload = m_payload; | |
| 585 | 134833 | payload.clear(); | |
| 586 | 15414965 | const auto flush = [&](ColumnTable& table) { | |
| 587 | 15280132 | table.Flush( | |
| 588 | 15280132 | [&](uint32_t column, std::span<const uint32_t> rows, std::span<const std::byte> cells) { | |
| 589 | 3 | const uint64_t at = payload.size(); | |
| 590 | 3 | const std::span<const std::byte> rowBytes = std::as_bytes(rows); | |
| 591 | 3 | payload.insert(payload.end(), rowBytes.begin(), rowBytes.end()); | |
| 592 | 3 | payload.insert(payload.end(), cells.begin(), cells.end()); | |
| 593 | 3 | scattered.push_back({&table, column, uint32_t(rows.size()), | |
| 594 | 3 | uint32_t(cells.size() / rows.size() / sizeof(uint32_t)), at, | |
| 595 | 3 | at + rowBytes.size()}); | |
| 596 | 3 | }); | |
| 597 | 15280132 | }; | |
| 598 | 15010466 | for (Entry& entry : m_entries) { | |
| 599 | 14875633 | flush(*entry.table); | |
| 600 | } | ||
| 601 | 134833 | flush(m_prims); | |
| 602 | 134833 | flush(m_slots); | |
| 603 | 134833 | flush(m_worlds); | |
| 604 | 134833 | std::vector<CopyRun> runs; | |
| 605 | 134833 | uint32_t total = 0; | |
| 606 | 134833 | device::Queue& queue = m_device.GetQueue(); | |
| 607 | 279074 | for (const auto& [type, heap] : m_arrayHeaps) { | |
| 608 | 144241 | const std::span<const std::byte> staged = heap->StagedBytes(); | |
| 609 | 144241 | if (staged.empty()) { | |
| 610 | 143865 | continue; | |
| 611 | } | ||
| 612 | 376 | const uint64_t base = queue.Upload(staged.data(), staged.size()).Address(); | |
| 613 | 1061 | for (const ArrayPool::StagedCopy& copy : heap->TakeStaged(base)) { | |
| 614 | 685 | runs.push_back({copy.dst, copy.source, total, 0}); | |
| 615 | 685 | total += copy.words; | |
| 616 | 376 | } | |
| 617 | } | ||
| 618 | 134833 | if (!runs.empty()) { | |
| 619 | 372 | const std::span<const std::byte> table = std::as_bytes(std::span(runs)); | |
| 620 | 372 | Dispatch("CopyWords", | |
| 621 | 372 | CopyWordsParams{.runs = {queue.Upload(table.data(), table.size()).Address()}, | |
| 622 | 372 | .runCount = uint32_t(runs.size()), | |
| 623 | .total = total}, | ||
| 624 | total); | ||
| 625 | } | ||
| 626 | 134833 | if (scattered.empty()) { | |
| 627 | 134832 | return; | |
| 628 | } | ||
| 629 | 1 | const Ref<device::Buffer> uploaded = | |
| 630 | 1 | m_device.CreateBuffer({.label = "scattered cells"}, std::span<const std::byte>(payload)); | |
| 631 | 4 | for (const Scattered& scatter : scattered) { | |
| 632 | 3 | CellsParams params{}; | |
| 633 | 3 | params.column.address = scatter.table->Pages(scatter.column); | |
| 634 | 3 | params.rows.address = uploaded->Address() + scatter.rows; | |
| 635 | 3 | params.cells.address = uploaded->Address() + scatter.cells; | |
| 636 | 3 | params.count = scatter.count; | |
| 637 | 3 | params.words = scatter.words; | |
| 638 | 3 | Dispatch("ScatterCells", params, uint64_t(scatter.count) * scatter.words); | |
| 639 | } | ||
| 640 | 134835 | } | |
| 641 | |||
| 642 | 1727 | AuthoredData ColumnStore::Data() const { | |
| 643 | 1727 | AuthoredData data{}; | |
| 644 | 1727 | data.prims.schema.pages.address = m_prims.Pages(kPrimSchema); | |
| 645 | 1727 | data.prims.parent.pages.address = m_prims.Pages(kPrimParent); | |
| 646 | 1727 | data.prims.world.pages.address = m_prims.Pages(kPrimWorld); | |
| 647 | 1727 | data.prims.flags.pages.address = m_prims.Pages(kPrimFlags); | |
| 648 | 1727 | data.prims.slots.pages.address = m_prims.Pages(kPrimSlots); | |
| 649 | 1727 | data.prims.apis.pages.address = m_prims.Pages(kPrimApis); | |
| 650 | 1727 | data.rows.pages.address = m_slots.Pages(0); | |
| 651 | 1727 | data.worldStamps.pages.address = m_worlds.Pages(0); | |
| 652 | |||
| 653 | 1727 | std::byte* schemas = reinterpret_cast<std::byte*>(&data.schemas); | |
| 654 | 1727 | uint32_t base = 0; | |
| 655 | 191697 | for (uint32_t id = 0; id < uint32_t(SchemaId::Count); ++id) { | |
| 656 | 189970 | const SchemaId schema = SchemaId(id); | |
| 657 | 189970 | const SchemaInfo& info = Info(schema); | |
| 658 | 189970 | const Entry& entry = m_entries[id]; | |
| 659 | 189970 | const Span<const ColumnInfo> columns = Columns(schema); | |
| 660 | 820325 | for (uint32_t column = 0; column < Info(schema).columnCount; ++column) { | |
| 661 | 630355 | const ColumnInfo& at = columns[column]; | |
| 662 | 630355 | Place(schemas + base, at.offset, entry.table->Pages(column)); | |
| 663 | } | ||
| 664 | 189970 | Place(schemas + base, info.dataBytes - sizeof(uint64_t), | |
| 665 | 189970 | entry.table->Pages(Info(schema).columnCount)); | |
| 666 | 189970 | base += info.dataBytes; | |
| 667 | } | ||
| 668 | 1727 | data.primCount = m_primCount; | |
| 669 | 1727 | data.worldCount = m_worldCount; | |
| 670 | 1727 | return data; | |
| 671 | } | ||
| 672 | |||
| 673 | 80969 | uint64_t ColumnStore::DeviceData() { | |
| 674 | 80969 | CHRONO_BATCH_RENDER_ZONE("ColumnStore/DeviceData"); | |
| 675 | 80969 | Flush(); | |
| 676 | 80969 | if (!m_deviceData) { | |
| 677 | 402 | m_deviceData = | |
| 678 | 402 | m_device.CreateBuffer({.bytes = sizeof(AuthoredData), .label = "scene data"}); | |
| 679 | } | ||
| 680 | 80969 | std::vector<uint64_t> layout = {m_primCount, m_worldCount, m_prims.Layout(), m_slots.Layout(), | |
| 681 | 80969 | m_worlds.Layout()}; | |
| 682 | 9012605 | for (const Entry& entry : m_entries) { | |
| 683 | 8931636 | layout.push_back(uint64_t(reinterpret_cast<uintptr_t>(entry.table.get()))); | |
| 684 | 8931636 | layout.push_back(entry.table ? entry.table->Layout() : 0); | |
| 685 | } | ||
| 686 | 80969 | if (layout == m_uploadedLayout) { | |
| 687 | 80412 | return m_deviceData->Address(); | |
| 688 | } | ||
| 689 | 557 | const AuthoredData data = Data(); | |
| 690 | 557 | const std::span<const std::byte> bytes = std::as_bytes(std::span(&data, 1)); | |
| 691 | 557 | if (m_uploaded.size() != bytes.size() || | |
| 692 | 155 | std::memcmp(m_uploaded.data(), bytes.data(), bytes.size()) != 0) { | |
| 693 | 506 | m_device.GetQueue().Write(*m_deviceData, &data); | |
| 694 | 506 | m_uploaded.assign(bytes.begin(), bytes.end()); | |
| 695 | } | ||
| 696 | 557 | m_uploadedLayout = std::move(layout); | |
| 697 | 557 | return m_deviceData->Address(); | |
| 698 | 81773 | } | |
| 699 | |||
| 700 | } | ||
| 701 |