scene/tables/sensor_pipelines.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // SPDX-License-Identifier: BSD-3-Clause | ||
| 2 | #include "chrono_batch_render/scene/tables/sensor_pipelines.h" | ||
| 3 | |||
| 4 | #include <algorithm> | ||
| 5 | #include <cstring> | ||
| 6 | |||
| 7 | #include "chrono_batch_render/device/queue.h" | ||
| 8 | #include "chrono_batch_render/scene/schema/columns.h" | ||
| 9 | #include "chrono_batch_render/sensor_pipeline/node.h" | ||
| 10 | #include "chrono_batch_render/stage/publish/change_set.h" | ||
| 11 | #include "chrono_batch_render/stage/views/attribute.h" | ||
| 12 | #include "chrono_batch_render/stage/views/view.h" | ||
| 13 | #include "chrono_batch_render/util/error.h" | ||
| 14 | #include "chrono_batch_render/util/log.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 | enum class AnyEnum : uint32_t {}; | ||
| 23 | |||
| 24 | 275 | std::vector<ColumnRef> SensorPipelineInputs() { | |
| 25 | 275 | std::vector<ColumnRef> inputs; | |
| 26 | 31347 | for (uint32_t id = 0; id < SchemaCount(); ++id) { | |
| 27 | 31072 | const auto schema = SchemaId(id); | |
| 28 | 30522 | if (schema != SchemaId::Connectable && schema != SchemaId::Shader && | |
| 29 | 31072 | !IsA(schema, SchemaId::SensorNode) && schema != SchemaId::SensorPipeline && | |
| 30 | 22258 | schema != SchemaId::RenderVar) { | |
| 31 | 21708 | continue; | |
| 32 | } | ||
| 33 | 9364 | const Span<const ColumnInfo> columns = Columns(schema); | |
| 34 | 24788 | for (uint32_t index = 0; index < columns.size(); ++index) { | |
| 35 | 15424 | inputs.push_back({schema, &columns[index], index}); | |
| 36 | } | ||
| 37 | } | ||
| 38 | 275 | return inputs; | |
| 39 | ✗ | } | |
| 40 | |||
| 41 | 13 | bool SameInstance(const SensorPipelineInstance& a, const SensorPipelineInstance& b) { | |
| 42 | 13 | return a.definition == b.definition && a.words == b.words && a.error == b.error && | |
| 43 | 4 | a.resolved.key == b.resolved.key; | |
| 44 | } | ||
| 45 | |||
| 46 | } | ||
| 47 | |||
| 48 | 353 | SensorPipelineTable::SensorPipelineTable(Structure& structure) | |
| 49 | 706 | : m_structure(structure), m_library(1), m_registry(sensor_pipeline::SensorPipelineNodes()) {} | |
| 50 | |||
| 51 | 2824 | std::span<const ColumnRef> SensorPipelineTable::Inputs() const { | |
| 52 | 2824 | if (m_inputSchemas != SchemaCount()) { | |
| 53 | 275 | m_inputs = SensorPipelineInputs(); | |
| 54 | 275 | m_inputSchemas = SchemaCount(); | |
| 55 | } | ||
| 56 | 2824 | return m_inputs; | |
| 57 | } | ||
| 58 | |||
| 59 | 8746 | std::shared_ptr<const sensor_pipeline::NodeRegistry> SensorPipelineTable::Registry() const { | |
| 60 | 8746 | const std::lock_guard lock(m_mutex); | |
| 61 | 17492 | return m_registry; | |
| 62 | 8746 | } | |
| 63 | |||
| 64 | 197 | std::shared_ptr<const SensorPipelineInstance> SensorPipelineTable::Find(uint32_t prim) const { | |
| 65 | 197 | const std::lock_guard lock(m_mutex); | |
| 66 | 197 | const auto found = m_instances.find(prim); | |
| 67 | 362 | return found == m_instances.end() ? nullptr : found->second; | |
| 68 | 197 | } | |
| 69 | |||
| 70 | 24 | std::shared_ptr<const sensor_pipeline::SensorPipelineSet> SensorPipelineTable::Definitions() const { | |
| 71 | 24 | return m_library.Snapshot(); | |
| 72 | } | ||
| 73 | |||
| 74 | 232 | std::vector<sensor_pipeline::ConnectionDesc> SensorPipelineTable::Connections(uint32_t prim) const { | |
| 75 | 232 | std::vector<sensor_pipeline::ConnectionDesc> connections; | |
| 76 | 426 | for (const ConnectionCache::Connection& connection : m_connections.Get(prim)) { | |
| 77 | 388 | connections.push_back({.input = std::string(connection.input), | |
| 78 | 194 | .source = connection.source, | |
| 79 | .sourcePath = connection.source != kNoPrim | ||
| 80 | 194 | ? m_structure.GetPaths().Path(connection.source) | |
| 81 | : std::string("nothing"), | ||
| 82 | 194 | .output = std::string(connection.output)}); | |
| 83 | } | ||
| 84 | 232 | return connections; | |
| 85 | 388 | } | |
| 86 | |||
| 87 | 187 | sensor_pipeline::NodeDesc SensorPipelineTable::GatherNode(uint32_t prim) const { | |
| 88 | 187 | const Prims& prims = m_structure.GetPrims(); | |
| 89 | 187 | const SchemaId schema = prims.Record(prim).schema; | |
| 90 | 187 | const uint64_t apis = prims.Record(prim).apis; | |
| 91 | 187 | const View view(m_structure, schema, std::vector<uint32_t>{prim}); | |
| 92 | 187 | const Tokens& tokens = m_structure.GetTokens(); | |
| 93 | 187 | sensor_pipeline::NodeDesc node{.prim = prim, .path = m_structure.GetPaths().Path(prim)}; | |
| 94 | 374 | node.id = std::string(tokens.Text(view.Get<Token>(kShaderId).Get().front())); | |
| 95 | 187 | node.inputs = Connections(prim); | |
| 96 | 187 | const sensor_pipeline::NodeRegistry& registry = *m_registry; | |
| 97 | 187 | const uint32_t def = registry.Find(node.id); | |
| 98 | 187 | if (def == sensor_pipeline::kNoDef) { | |
| 99 | return node; | ||
| 100 | } | ||
| 101 | 303 | for (const sensor_pipeline::UniformDef& uniform : registry[def].uniforms) { | |
| 102 | 116 | const ColumnRef column = FindColumn(schema, apis, uniform.name); | |
| 103 | 116 | if (!column) { | |
| 104 | ✗ | continue; | |
| 105 | } | ||
| 106 | 116 | std::string value; | |
| 107 | 116 | switch (column.column->type) { | |
| 108 | 101 | case ColumnType::Enum: { | |
| 109 | 101 | const uint32_t index = uint32_t(view.Get<AnyEnum>(column).Get().front()); | |
| 110 | 101 | value = index < column.column->tokens.size() ? std::string(column.column->tokens[index]) | |
| 111 | 101 | : uniform.fallback; | |
| 112 | 101 | break; | |
| 113 | } | ||
| 114 | ✗ | case ColumnType::Token: | |
| 115 | ✗ | value = std::string(tokens.Text(view.Get<Token>(column).Get().front())); | |
| 116 | ✗ | break; | |
| 117 | 15 | case ColumnType::Bool: | |
| 118 | 30 | value = view.Get<bool>(column).Get().front() ? "true" : "false"; | |
| 119 | 15 | break; | |
| 120 | ✗ | default: | |
| 121 | ✗ | throw Error("{}'s {} is a uniform, so it must be a token or a bool", node.path, | |
| 122 | ✗ | uniform.name); | |
| 123 | } | ||
| 124 | 116 | node.uniforms.emplace_back(uniform.name, std::move(value)); | |
| 125 | 116 | } | |
| 126 | 187 | const std::vector<sensor_pipeline::ParameterDef>& parameters = registry[def].parameters; | |
| 127 | 291 | for (uint32_t index = 0; index < parameters.size(); ++index) { | |
| 128 | 104 | const sensor_pipeline::ParameterDef& parameter = parameters[index]; | |
| 129 | 104 | const ColumnRef column = FindColumn(schema, apis, parameter.name); | |
| 130 | 104 | if (!column) { | |
| 131 | 104 | continue; | |
| 132 | } | ||
| 133 | 104 | const ColumnType type = column.column->type; | |
| 134 | 104 | const bool copied = parameter.type == sensor_pipeline::ParameterType::Int | |
| 135 | 104 | ? type == ColumnType::Int | |
| 136 | : parameter.type == sensor_pipeline::ParameterType::Bool | ||
| 137 | 97 | ? type == ColumnType::Bool | |
| 138 | 94 | : (type == ColumnType::Float || type == ColumnType::Float2 || | |
| 139 | 94 | type == ColumnType::Float3 || type == ColumnType::Float4) && | |
| 140 | 94 | ElementBytes(type) == parameter.words * sizeof(uint32_t); | |
| 141 | 10 | if (copied) { | |
| 142 | 208 | m_gathered[prim].push_back( | |
| 143 | 104 | {index, column, m_structure.GetPrims().Row(prim, column.schema)}); | |
| 144 | 104 | continue; | |
| 145 | } | ||
| 146 | ✗ | std::vector<float> values; | |
| 147 | ✗ | switch (type) { | |
| 148 | ✗ | case ColumnType::Float: | |
| 149 | ✗ | values = {view.Get<float>(column).Get().front()}; | |
| 150 | ✗ | break; | |
| 151 | ✗ | case ColumnType::Int: | |
| 152 | ✗ | values = {float(view.Get<int32_t>(column).Get().front())}; | |
| 153 | ✗ | break; | |
| 154 | ✗ | case ColumnType::Bool: | |
| 155 | ✗ | values = {view.Get<bool>(column).Get().front() ? 1.0f : 0.0f}; | |
| 156 | ✗ | break; | |
| 157 | ✗ | case ColumnType::Float2: { | |
| 158 | ✗ | const Float2 value = view.Get<Float2>(column).Get().front(); | |
| 159 | ✗ | values = {value.x, value.y}; | |
| 160 | ✗ | break; | |
| 161 | } | ||
| 162 | ✗ | case ColumnType::Float3: { | |
| 163 | ✗ | const Float3 value = view.Get<Float3>(column).Get().front(); | |
| 164 | ✗ | values = {value.x, value.y, value.z}; | |
| 165 | ✗ | break; | |
| 166 | } | ||
| 167 | ✗ | case ColumnType::Float4: { | |
| 168 | ✗ | const Float4 value = view.Get<Float4>(column).Get().front(); | |
| 169 | ✗ | values = {value.x, value.y, value.z, value.w}; | |
| 170 | ✗ | break; | |
| 171 | } | ||
| 172 | ✗ | default: | |
| 173 | ✗ | throw Error("{}'s {} is a value, so it must be numeric", node.path, parameter.name); | |
| 174 | } | ||
| 175 | ✗ | node.values.emplace_back(parameter.name, std::move(values)); | |
| 176 | ✗ | } | |
| 177 | return node; | ||
| 178 | 187 | } | |
| 179 | |||
| 180 | 478 | std::vector<SensorPipelineTable::Tap> SensorPipelineTable::GatherTaps() const { | |
| 181 | 478 | const Prims& prims = m_structure.GetPrims(); | |
| 182 | 478 | const Tokens& tokens = m_structure.GetTokens(); | |
| 183 | 478 | std::vector<Tap> taps; | |
| 184 | 11909 | for (uint32_t prim = 0; prim < prims.Size(); ++prim) { | |
| 185 | 11431 | if (!prims.Alive(prim) || prims.Record(prim).schema != SchemaId::RenderVar) { | |
| 186 | 11426 | continue; | |
| 187 | } | ||
| 188 | 454 | const View view(m_structure, SchemaId::RenderVar, std::vector<uint32_t>{prim}); | |
| 189 | 908 | if (tokens.Text(view.Get<Token>(kRenderVarSourceName).Get().front()) != kTapSource) { | |
| 190 | 449 | continue; | |
| 191 | } | ||
| 192 | 5 | const std::vector<uint32_t> targets = view.GetArray<uint32_t>(kRenderVarTap).Get(0); | |
| 193 | 5 | if (targets.empty() || !prims.Alive(targets.front())) { | |
| 194 | ✗ | continue; | |
| 195 | } | ||
| 196 | 10 | taps.push_back( | |
| 197 | 5 | {m_structure.GetPaths().Path(prim), targets.front(), | |
| 198 | 15 | std::string(tokens.Text(view.Get<Token>(kRenderVarTapOutput).Get().front()))}); | |
| 199 | 459 | } | |
| 200 | 478 | return taps; | |
| 201 | 15 | } | |
| 202 | |||
| 203 | 45 | sensor_pipeline::SensorPipelineDesc SensorPipelineTable::Gather(uint32_t pipeline, | |
| 204 | std::span<const Tap> taps) const { | ||
| 205 | 45 | const Prims& prims = m_structure.GetPrims(); | |
| 206 | 45 | sensor_pipeline::SensorPipelineDesc desc{.prim = pipeline, | |
| 207 | 45 | .path = m_structure.GetPaths().Path(pipeline)}; | |
| 208 | 45 | desc.outputs = Connections(pipeline); | |
| 209 | 53 | for (const Tap& tap : taps) { | |
| 210 | 8 | const std::string node = m_structure.GetPaths().Path(tap.node); | |
| 211 | 21 | if (!node.starts_with(desc.path + "/") || | |
| 212 | 5 | !IsA(prims.Record(tap.node).schema, SchemaId::SensorNode)) { | |
| 213 | 3 | continue; | |
| 214 | } | ||
| 215 | 5 | std::string output = tap.output; | |
| 216 | 5 | if (output.empty()) { | |
| 217 | 4 | const sensor_pipeline::NodeDesc described = GatherNode(tap.node); | |
| 218 | 4 | const uint32_t def = m_registry->Find(described.id); | |
| 219 | 4 | if (def == sensor_pipeline::kNoDef || (*m_registry)[def].outputs.size() != 1) { | |
| 220 | ✗ | throw Error("{} taps {}, so its tapOutput must name one of the node's outputs", | |
| 221 | ✗ | tap.name, node); | |
| 222 | } | ||
| 223 | 4 | output = (*m_registry)[def].outputs.front().name; | |
| 224 | 4 | } | |
| 225 | 5 | desc.outputs.push_back( | |
| 226 | 10 | {.input = tap.name, .source = tap.node, .sourcePath = node, .output = output}); | |
| 227 | 8 | } | |
| 228 | 45 | std::vector<uint32_t> pending; | |
| 229 | 45 | std::vector<uint32_t> seen; | |
| 230 | 104 | for (const sensor_pipeline::ConnectionDesc& output : desc.outputs) { | |
| 231 | 59 | pending.push_back(output.source); | |
| 232 | } | ||
| 233 | 241 | while (!pending.empty()) { | |
| 234 | 196 | const uint32_t prim = pending.back(); | |
| 235 | 196 | pending.pop_back(); | |
| 236 | 379 | if (prim == kNoPrim || std::ranges::find(seen, prim) != seen.end() || | |
| 237 | 183 | !IsA(prims.Record(prim).schema, SchemaId::SensorNode)) { | |
| 238 | 13 | continue; | |
| 239 | } | ||
| 240 | 183 | seen.push_back(prim); | |
| 241 | 183 | sensor_pipeline::NodeDesc node = GatherNode(prim); | |
| 242 | 320 | for (const sensor_pipeline::ConnectionDesc& input : node.inputs) { | |
| 243 | 137 | pending.push_back(input.source); | |
| 244 | } | ||
| 245 | 183 | desc.nodes.push_back(std::move(node)); | |
| 246 | 183 | } | |
| 247 | 45 | std::ranges::sort(desc.nodes, {}, &sensor_pipeline::NodeDesc::prim); | |
| 248 | 45 | return desc; | |
| 249 | 50 | } | |
| 250 | |||
| 251 | 485 | void SensorPipelineTable::Run(const ChangeSet& changes, TableVersions&) { | |
| 252 | 485 | CHRONO_BATCH_RENDER_ZONE("Pipelines/Run"); | |
| 253 | 485 | ColumnStore& store = m_structure.GetColumnStore(); | |
| 254 | 485 | std::shared_ptr<const sensor_pipeline::NodeRegistry> nodes = | |
| 255 | 485 | sensor_pipeline::SensorPipelineNodes(); | |
| 256 | 485 | const bool reloaded = nodes != m_registry; | |
| 257 | 485 | if (reloaded) { | |
| 258 | 5 | const std::lock_guard lock(m_mutex); | |
| 259 | 5 | m_registry = std::move(nodes); | |
| 260 | 5 | } | |
| 261 | 485 | std::vector<const ColumnInfo*> written; | |
| 262 | 27692 | for (const ColumnRef input : Inputs()) { | |
| 263 | 27207 | if (store.LastWrite(input) > m_seen) { | |
| 264 | 1210 | written.push_back(input.column); | |
| 265 | } | ||
| 266 | } | ||
| 267 | 485 | m_seen = store.WriteSequence(); | |
| 268 | 485 | const bool restructured = | |
| 269 | 485 | reloaded || changes.resync || changes.structure || !changes.apis.empty(); | |
| 270 | 21 | if (!restructured && std::ranges::all_of(written, [&](const ColumnInfo* column) { | |
| 271 | 23 | return m_pack.Reads(column); | |
| 272 | })) { | ||
| 273 | 7 | if (!written.empty()) { | |
| 274 | 7 | Pack(false); | |
| 275 | } | ||
| 276 | 7 | return; | |
| 277 | } | ||
| 278 | 478 | const Prims& prims = m_structure.GetPrims(); | |
| 279 | 478 | const sensor_pipeline::NodeRegistry& registry = *m_registry; | |
| 280 | 478 | std::map<uint32_t, std::shared_ptr<const SensorPipelineInstance>> resolved; | |
| 281 | 478 | m_connections.Update(changes); | |
| 282 | 478 | const std::vector<Tap> taps = GatherTaps(); | |
| 283 | 478 | m_gathered.clear(); | |
| 284 | 478 | m_pack.Clear(); | |
| 285 | 478 | uint32_t words = 0; | |
| 286 | 478 | bool changed = false; | |
| 287 | 11909 | for (uint32_t prim = 0; prim < prims.Size(); ++prim) { | |
| 288 | 11431 | if (!prims.Alive(prim) || !IsA(prims.Record(prim).schema, SchemaId::SensorPipeline)) { | |
| 289 | 11386 | continue; | |
| 290 | } | ||
| 291 | 45 | auto instance = std::make_shared<SensorPipelineInstance>(); | |
| 292 | 45 | instance->prim = prim; | |
| 293 | 45 | instance->path = m_structure.GetPaths().Path(prim); | |
| 294 | 45 | try { | |
| 295 | 45 | instance->resolved = | |
| 296 | 45 | sensor_pipeline::ResolveSensorPipeline(Gather(prim, taps), registry); | |
| 297 | 44 | instance->definition = m_library.Intern(instance->resolved.key, [&](uint32_t id) { | |
| 298 | 38 | return sensor_pipeline::BuildDefinition(id, instance->resolved, registry); | |
| 299 | }); | ||
| 300 | 44 | const auto definitions = m_library.Snapshot(); | |
| 301 | 44 | const sensor_pipeline::SensorPipelineDefinition& definition = | |
| 302 | 44 | *definitions->Find(instance->definition); | |
| 303 | 44 | instance->words = sensor_pipeline::PackWords(definition, instance->resolved, registry); | |
| 304 | 44 | instance->firstWord = words; | |
| 305 | 44 | words += uint32_t(instance->words.size()); | |
| 306 | 221 | for (uint32_t node = 0; node < instance->resolved.nodes.size(); ++node) { | |
| 307 | 177 | const sensor_pipeline::ResolvedNode& each = instance->resolved.nodes[node]; | |
| 308 | 177 | const auto found = m_gathered.find(each.prim); | |
| 309 | 177 | if (found == m_gathered.end()) { | |
| 310 | 101 | continue; | |
| 311 | } | ||
| 312 | 76 | const std::vector<sensor_pipeline::ParameterDef>& parameters = | |
| 313 | 76 | registry[each.def].parameters; | |
| 314 | 177 | for (const Gathered& gathered : found->second) { | |
| 315 | 101 | uint32_t offset = definition.parameterOffsets[node]; | |
| 316 | 135 | for (uint32_t index = 0; index < gathered.parameter; ++index) { | |
| 317 | 34 | offset += parameters[index].words; | |
| 318 | } | ||
| 319 | 101 | m_pack.Copy(gathered.column, gathered.row, instance->firstWord + offset); | |
| 320 | } | ||
| 321 | } | ||
| 322 | 45 | } catch (const Error& error) { | |
| 323 | 1 | instance->error = error.what(); | |
| 324 | 1 | Log(LogLevel::Warning, "{}", instance->error); | |
| 325 | 1 | } | |
| 326 | 45 | const std::shared_ptr<const SensorPipelineInstance> previous = Find(prim); | |
| 327 | 45 | changed |= !previous || !SameInstance(*previous, *instance); | |
| 328 | 45 | resolved.emplace(prim, std::move(instance)); | |
| 329 | 45 | } | |
| 330 | 478 | { | |
| 331 | 478 | const std::lock_guard lock(m_mutex); | |
| 332 | 478 | changed |= resolved.size() != m_instances.size(); | |
| 333 | 478 | m_instances = std::move(resolved); | |
| 334 | 478 | if (changed) { | |
| 335 | 35 | ++m_version; | |
| 336 | } | ||
| 337 | 478 | } | |
| 338 | 923 | m_words.Reserve(m_structure.GetColumnStore().GetDevice(), std::max(words, 1u)); | |
| 339 | 478 | m_pack.Build(store, false); | |
| 340 | 478 | Pack(true); | |
| 341 | 965 | } | |
| 342 | |||
| 343 | 485 | void SensorPipelineTable::Pack(bool rewrite) { | |
| 344 | 485 | CHRONO_BATCH_RENDER_ZONE("Pipelines/Pack"); | |
| 345 | 485 | device::Device& device = m_structure.GetColumnStore().GetDevice(); | |
| 346 | 485 | device::Queue& queue = device.GetQueue(); | |
| 347 | 485 | if (rewrite) { | |
| 348 | 523 | for (const auto& [prim, instance] : m_instances) { | |
| 349 | 45 | if (!instance->words.empty()) { | |
| 350 | 42 | queue.Write( | |
| 351 | 42 | m_words.Buffer()->Slice(uint64_t(instance->firstWord) * sizeof(uint32_t), | |
| 352 | 42 | instance->words.size() * sizeof(uint32_t)), | |
| 353 | 42 | instance->words.data()); | |
| 354 | } | ||
| 355 | } | ||
| 356 | } | ||
| 357 | 970 | m_pack.Pack(m_structure.GetColumnStore(), m_words.Address()); | |
| 358 | 485 | } | |
| 359 | |||
| 360 | 6 | std::vector<uint32_t> SensorPipelineTable::ReadWords(const SensorPipelineInstance& instance) const { | |
| 361 | 6 | if (instance.words.empty() || !m_words.Buffer()) { | |
| 362 | ✗ | return instance.words; | |
| 363 | } | ||
| 364 | 6 | device::Queue& queue = m_structure.GetColumnStore().GetDevice().GetQueue(); | |
| 365 | 6 | const device::Download download = queue.ReadAsync(m_words.Buffer()->Slice( | |
| 366 | 6 | uint64_t(instance.firstWord) * sizeof(uint32_t), instance.words.size() * sizeof(uint32_t))); | |
| 367 | 6 | download.Wait(); | |
| 368 | 6 | std::vector<uint32_t> words(instance.words.size()); | |
| 369 | 6 | std::memcpy(words.data(), download.Bytes().data(), words.size() * sizeof(uint32_t)); | |
| 370 | 6 | return words; | |
| 371 | 6 | } | |
| 372 | |||
| 373 | } | ||
| 374 |