sensor_pipeline/resolve.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // SPDX-License-Identifier: BSD-3-Clause | ||
| 2 | #include "chrono_batch_render/sensor_pipeline/resolve.h" | ||
| 3 | |||
| 4 | #include <algorithm> | ||
| 5 | #include <unordered_map> | ||
| 6 | |||
| 7 | #include "chrono_batch_render/util/error.h" | ||
| 8 | #include "chrono_batch_render/util/text.h" | ||
| 9 | |||
| 10 | namespace chrono::batch_render::sensor_pipeline { | ||
| 11 | |||
| 12 | namespace { | ||
| 13 | |||
| 14 | 693 | uint32_t Port(std::span<const PortDef> ports, std::string_view name) { | |
| 15 | 693 | const auto found = std::ranges::find(ports, name, &PortDef::name); | |
| 16 | 693 | return found == ports.end() ? kNoValue : uint32_t(found - ports.begin()); | |
| 17 | } | ||
| 18 | |||
| 19 | 274 | uint32_t ChooseUniform(const NodeDesc& node, const UniformDef& uniform) { | |
| 20 | 274 | const auto set = | |
| 21 | 274 | std::ranges::find(node.uniforms, uniform.name, &std::pair<std::string, std::string>::first); | |
| 22 | 274 | const std::string& value = set == node.uniforms.end() ? uniform.fallback : set->second; | |
| 23 | 274 | const auto found = std::ranges::find(uniform.allowed, value); | |
| 24 | 274 | if (found == uniform.allowed.end()) { | |
| 25 | 2 | throw Error("{}'s {} is {}, and it takes {}", node.path, uniform.name, value, | |
| 26 | 2 | Join(uniform.allowed)); | |
| 27 | } | ||
| 28 | 273 | return uint32_t(found - uniform.allowed.begin()); | |
| 29 | } | ||
| 30 | |||
| 31 | 232 | std::vector<float> ParameterValues(const NodeDesc& node, const ParameterDef& parameter) { | |
| 32 | 232 | const auto set = std::ranges::find(node.values, parameter.name, | |
| 33 | &std::pair<std::string, std::vector<float>>::first); | ||
| 34 | 232 | std::vector<float> values = set != node.values.end() ? set->second | |
| 35 | 227 | : parameter.fallback.empty() | |
| 36 | 227 | ? std::vector<float>(parameter.words, 0) | |
| 37 | 232 | : parameter.fallback; | |
| 38 | 232 | if (values.size() != parameter.words) { | |
| 39 | ✗ | throw Error("{}'s {} has {} values, and it takes {}", node.path, parameter.name, | |
| 40 | ✗ | values.size(), parameter.words); | |
| 41 | } | ||
| 42 | 232 | return values; | |
| 43 | ✗ | } | |
| 44 | |||
| 45 | 123 | std::string_view UniformValue(const NodeDef& def, const ResolvedNode& node, std::string_view name) { | |
| 46 | 235 | for (uint32_t index = 0; index < def.uniforms.size(); ++index) { | |
| 47 | 235 | if (def.uniforms[index].name == name) { | |
| 48 | 123 | return def.uniforms[index].allowed[node.uniforms[index]]; | |
| 49 | } | ||
| 50 | } | ||
| 51 | ✗ | return {}; | |
| 52 | } | ||
| 53 | |||
| 54 | class Resolver { | ||
| 55 | public: | ||
| 56 | 101 | Resolver(const SensorPipelineDesc& desc, const NodeRegistry& registry) | |
| 57 | 101 | : m_desc(desc), m_registry(registry) { | |
| 58 | 491 | for (uint32_t index = 0; index < desc.nodes.size(); ++index) { | |
| 59 | 391 | const NodeDesc& node = desc.nodes[index]; | |
| 60 | 391 | const uint32_t def = registry.Find(node.id); | |
| 61 | 391 | if (def == kNoDef) { | |
| 62 | 1 | throw Error("{} runs {}, which no pipeline node is", node.path, node.id); | |
| 63 | } | ||
| 64 | 390 | m_byPrim.emplace(node.prim, index); | |
| 65 | 390 | m_defs.push_back(def); | |
| 66 | } | ||
| 67 | 103 | } | |
| 68 | |||
| 69 | 100 | ResolvedSensorPipeline Resolve() { | |
| 70 | 100 | if (m_desc.outputs.empty()) { | |
| 71 | 1 | throw Error("{} has no outputs", m_desc.path); | |
| 72 | } | ||
| 73 | 99 | std::vector<Source> outputs; | |
| 74 | 217 | for (const ConnectionDesc& output : m_desc.outputs) { | |
| 75 | 236 | outputs.push_back(FindSource(output, m_desc.path, output.input)); | |
| 76 | } | ||
| 77 | 99 | const std::vector<uint32_t> nodes = ReachedFrom(outputs); | |
| 78 | 99 | const graph::Graph graph = Connect(nodes); | |
| 79 | 96 | const std::vector<uint32_t> order = Order(graph, nodes); | |
| 80 | |||
| 81 | 94 | ResolvedSensorPipeline pipeline; | |
| 82 | 94 | std::vector<uint32_t> position(graph.Nodes().size()); | |
| 83 | 463 | for (const uint32_t node : order) { | |
| 84 | 370 | position[node] = uint32_t(pipeline.nodes.size()); | |
| 85 | 370 | AddNode(pipeline, nodes[node], graph[node], position); | |
| 86 | } | ||
| 87 | 204 | for (uint32_t index = 0; index < outputs.size(); ++index) { | |
| 88 | 111 | const ResolvedNode& made = pipeline.nodes[position[m_graphNode[outputs[index].node]]]; | |
| 89 | 111 | pipeline.outputs.push_back( | |
| 90 | 111 | {m_desc.outputs[index].input, made.outputs[outputs[index].port]}); | |
| 91 | } | ||
| 92 | 93 | std::ranges::sort(pipeline.outputs, {}, &ResolvedOutput::name); | |
| 93 | 93 | pipeline.key = Key(graph, order, pipeline, position); | |
| 94 | 93 | return pipeline; | |
| 95 | 219 | } | |
| 96 | |||
| 97 | private: | ||
| 98 | struct Source { | ||
| 99 | uint32_t node; | ||
| 100 | uint32_t port; | ||
| 101 | }; | ||
| 102 | |||
| 103 | 1444 | const NodeDef& Def(uint32_t node) const { | |
| 104 | 1444 | return m_registry[m_defs[node]]; | |
| 105 | } | ||
| 106 | |||
| 107 | 406 | Source FindSource(const ConnectionDesc& connection, std::string_view reader, | |
| 108 | std::string_view input) const { | ||
| 109 | 406 | const auto found = m_byPrim.find(connection.source); | |
| 110 | 406 | if (found == m_byPrim.end()) { | |
| 111 | 1 | throw Error("{}'s {} connects to {}, which isn't a node of {}", reader, input, | |
| 112 | 1 | connection.sourcePath, m_desc.path); | |
| 113 | } | ||
| 114 | 405 | const NodeDef& def = Def(found->second); | |
| 115 | 405 | const uint32_t port = Port(def.outputs, connection.output); | |
| 116 | 405 | if (port == kNoValue) { | |
| 117 | ✗ | throw Error("{}'s {} reads {}'s {}, which {} doesn't output", reader, input, | |
| 118 | ✗ | connection.sourcePath, connection.output, def.id); | |
| 119 | } | ||
| 120 | 405 | return {found->second, port}; | |
| 121 | } | ||
| 122 | |||
| 123 | 99 | std::vector<uint32_t> ReachedFrom(std::span<const Source> outputs) const { | |
| 124 | 99 | std::vector<bool> reached(m_desc.nodes.size(), false); | |
| 125 | 99 | std::vector<uint32_t> pending; | |
| 126 | 504 | const auto reach = [&](uint32_t node) { | |
| 127 | 405 | if (!reached[node]) { | |
| 128 | 383 | reached[node] = true; | |
| 129 | 383 | pending.push_back(node); | |
| 130 | } | ||
| 131 | 504 | }; | |
| 132 | 217 | for (const Source& output : outputs) { | |
| 133 | 118 | reach(output.node); | |
| 134 | } | ||
| 135 | 482 | while (!pending.empty()) { | |
| 136 | 383 | const uint32_t node = pending.back(); | |
| 137 | 383 | pending.pop_back(); | |
| 138 | 671 | for (const ConnectionDesc& connection : m_desc.nodes[node].inputs) { | |
| 139 | 288 | if (const auto found = m_byPrim.find(connection.source); found != m_byPrim.end()) { | |
| 140 | 287 | reach(found->second); | |
| 141 | } | ||
| 142 | } | ||
| 143 | } | ||
| 144 | 99 | std::vector<uint32_t> nodes; | |
| 145 | 488 | for (uint32_t node = 0; node < reached.size(); ++node) { | |
| 146 | 389 | if (reached[node]) { | |
| 147 | 383 | nodes.push_back(node); | |
| 148 | } | ||
| 149 | } | ||
| 150 | 198 | return nodes; | |
| 151 | 99 | } | |
| 152 | |||
| 153 | 99 | graph::Graph Connect(std::span<const uint32_t> nodes) { | |
| 154 | 99 | graph::Graph graph; | |
| 155 | 99 | m_graphNode.assign(m_desc.nodes.size(), graph::kNoNode); | |
| 156 | 482 | for (const uint32_t node : nodes) { | |
| 157 | 383 | m_graphNode[node] = graph.Add(m_defs[node]); | |
| 158 | } | ||
| 159 | 477 | for (const uint32_t node : nodes) { | |
| 160 | 382 | const NodeDesc& desc = m_desc.nodes[node]; | |
| 161 | 382 | const NodeDef& def = Def(node); | |
| 162 | 386 | std::vector<bool> connected(def.inputs.size(), false); | |
| 163 | 667 | for (const ConnectionDesc& connection : desc.inputs) { | |
| 164 | 288 | const uint32_t input = Port(def.inputs, connection.input); | |
| 165 | 288 | if (input == kNoValue) { | |
| 166 | ✗ | throw Error("{}'s {} is connected, but {} has no such signal input", desc.path, | |
| 167 | ✗ | connection.input, def.id); | |
| 168 | } | ||
| 169 | 288 | const Source source = FindSource(connection, desc.path, connection.input); | |
| 170 | 287 | const SignalType made = Def(source.node).outputs[source.port].type; | |
| 171 | 287 | const SignalType taken = def.inputs[input].type; | |
| 172 | 287 | if (made != taken) { | |
| 173 | 4 | throw Error("{}'s {} takes {}, but {}'s {} is {}", desc.path, connection.input, | |
| 174 | 2 | Signal(taken).name, connection.sourcePath, connection.output, | |
| 175 | 2 | Signal(made).name); | |
| 176 | } | ||
| 177 | 285 | graph.Connect(m_graphNode[node], input, m_graphNode[source.node], source.port); | |
| 178 | 285 | connected[input] = true; | |
| 179 | } | ||
| 180 | 665 | for (uint32_t input = 0; input < def.inputs.size(); ++input) { | |
| 181 | 287 | if (!connected[input] && !def.inputs[input].optional) { | |
| 182 | 1 | throw Error("{}'s {} isn't connected, and {} needs it", desc.path, | |
| 183 | 1 | def.inputs[input].name, def.id); | |
| 184 | } | ||
| 185 | } | ||
| 186 | 382 | } | |
| 187 | 95 | return graph; | |
| 188 | 4 | } | |
| 189 | |||
| 190 | 95 | std::vector<uint32_t> Order(const graph::Graph& graph, std::span<const uint32_t> nodes) const { | |
| 191 | 95 | graph::Ordering ordering = graph.Order(); | |
| 192 | 95 | if (!ordering.cycle.empty()) { | |
| 193 | 1 | std::vector<std::string> paths; | |
| 194 | 3 | for (const uint32_t node : ordering.cycle) { | |
| 195 | 2 | paths.push_back(m_desc.nodes[nodes[node]].path); | |
| 196 | } | ||
| 197 | 2 | throw Error("{} loops through {}", m_desc.path, Join(paths)); | |
| 198 | 1 | } | |
| 199 | 94 | return std::move(ordering.order); | |
| 200 | 94 | } | |
| 201 | |||
| 202 | 370 | void AddNode(ResolvedSensorPipeline& pipeline, uint32_t node, const graph::Node& connected, | |
| 203 | std::span<const uint32_t> position) const { | ||
| 204 | 370 | const NodeDesc& desc = m_desc.nodes[node]; | |
| 205 | 370 | const NodeDef& def = Def(node); | |
| 206 | 370 | const auto at = uint32_t(pipeline.nodes.size()); | |
| 207 | 370 | ResolvedNode& resolved = pipeline.nodes.emplace_back(); | |
| 208 | 370 | resolved.def = m_defs[node]; | |
| 209 | 370 | resolved.prim = desc.prim; | |
| 210 | 370 | resolved.path = desc.path; | |
| 211 | 643 | for (const UniformDef& uniform : def.uniforms) { | |
| 212 | 274 | resolved.uniforms.push_back(ChooseUniform(desc, uniform)); | |
| 213 | } | ||
| 214 | 601 | for (const ParameterDef& parameter : def.parameters) { | |
| 215 | 232 | const std::vector<float> values = ParameterValues(desc, parameter); | |
| 216 | 232 | resolved.parameters.insert(resolved.parameters.end(), values.begin(), values.end()); | |
| 217 | 232 | } | |
| 218 | 738 | for (uint32_t port = 0; port < def.outputs.size(); ++port) { | |
| 219 | 369 | resolved.outputs.push_back(uint32_t(pipeline.values.size())); | |
| 220 | 369 | pipeline.values.push_back({at, port, def.outputs[port].type}); | |
| 221 | } | ||
| 222 | 369 | if (def.kind == NodeKind::Global) { | |
| 223 | 14 | for (uint32_t pass = 0; pass + 1 < def.passes; ++pass) { | |
| 224 | 8 | resolved.scratch.push_back(uint32_t(pipeline.values.size())); | |
| 225 | 8 | pipeline.values.push_back( | |
| 226 | 8 | {at, uint32_t(def.outputs.size()) + pass, def.outputs[0].type}); | |
| 227 | } | ||
| 228 | } | ||
| 229 | 369 | resolved.inputs.assign(def.inputs.size(), kNoValue); | |
| 230 | 646 | for (const graph::Edge& edge : connected.inputs) { | |
| 231 | 277 | resolved.inputs[edge.input] = | |
| 232 | 277 | pipeline.nodes[position[edge.source]].outputs[edge.output]; | |
| 233 | } | ||
| 234 | 369 | } | |
| 235 | |||
| 236 | 93 | graph::ShapeKey Key(const graph::Graph& graph, std::span<const uint32_t> order, | |
| 237 | const ResolvedSensorPipeline& pipeline, | ||
| 238 | std::span<const uint32_t> position) const { | ||
| 239 | 553 | return graph::GraphShape(graph, order, [&](uint32_t node, graph::ShapeWriter& writer) { | |
| 240 | 367 | const ResolvedNode& resolved = pipeline.nodes[position[node]]; | |
| 241 | 367 | writer.Add(m_registry[resolved.def].code.version); | |
| 242 | 638 | for (const uint32_t uniform : resolved.uniforms) { | |
| 243 | 271 | writer.Add(uniform); | |
| 244 | } | ||
| 245 | 367 | if (node == order.back()) { | |
| 246 | 93 | writer.Add(uint32_t(pipeline.outputs.size())); | |
| 247 | 204 | for (const ResolvedOutput& output : pipeline.outputs) { | |
| 248 | 111 | writer.Add(output.name); | |
| 249 | 111 | writer.Add(output.value); | |
| 250 | } | ||
| 251 | } | ||
| 252 | 186 | }); | |
| 253 | } | ||
| 254 | |||
| 255 | const SensorPipelineDesc& m_desc; | ||
| 256 | const NodeRegistry& m_registry; | ||
| 257 | std::unordered_map<uint32_t, uint32_t> m_byPrim; | ||
| 258 | std::vector<uint32_t> m_defs; | ||
| 259 | std::vector<uint32_t> m_graphNode; | ||
| 260 | }; | ||
| 261 | |||
| 262 | } | ||
| 263 | |||
| 264 | 101 | ResolvedSensorPipeline ResolveSensorPipeline(const SensorPipelineDesc& desc, | |
| 265 | const NodeRegistry& registry) { | ||
| 266 | 101 | return Resolver(desc, registry).Resolve(); | |
| 267 | } | ||
| 268 | |||
| 269 | 124 | std::vector<SignalLayout> PropagateLayouts(const ResolvedSensorPipeline& pipeline, | |
| 270 | const NodeRegistry& registry, DetectorLayout detector) { | ||
| 271 | 124 | std::vector<SignalLayout> layouts(pipeline.values.size()); | |
| 272 | 658 | for (const ResolvedNode& node : pipeline.nodes) { | |
| 273 | 534 | const NodeDef& def = registry[node.def]; | |
| 274 | 534 | const auto first = | |
| 275 | 534 | std::ranges::find_if(node.inputs, [](uint32_t value) { return value != kNoValue; }); | |
| 276 | 534 | SignalLayout layout = first != node.inputs.end() ? layouts[*first] : SignalLayout{}; | |
| 277 | 534 | switch (def.layout) { | |
| 278 | case LayoutRule::FirstInput: | ||
| 279 | break; | ||
| 280 | 112 | case LayoutRule::Detector: | |
| 281 | 112 | layout = SignalLayout{.channels = detector.channels, .pattern = detector.pattern}; | |
| 282 | 112 | layout.mosaic = detector.mosaics && UniformValue(def, node, "layout") == "mosaic"; | |
| 283 | 112 | if (layout.mosaic) { | |
| 284 | 8 | layout.channels = 1; | |
| 285 | } | ||
| 286 | 112 | layout.element = UniformValue(def, node, "precision") == "half" ? ElementType::F16 | |
| 287 | : ElementType::F32; | ||
| 288 | 112 | break; | |
| 289 | 4 | case LayoutRule::FullChannels: | |
| 290 | 4 | layout.channels = detector.channels; | |
| 291 | 4 | layout.pattern = detector.pattern; | |
| 292 | 4 | layout.mosaic = false; | |
| 293 | 4 | break; | |
| 294 | 38 | case LayoutRule::Color: | |
| 295 | 38 | layout = SignalLayout{.channels = 3}; | |
| 296 | 38 | break; | |
| 297 | 18 | case LayoutRule::Stat: | |
| 298 | 18 | layout = SignalLayout{.channels = kStatStorageWords, .perCapture = true}; | |
| 299 | 18 | break; | |
| 300 | 12 | case LayoutRule::Returns: | |
| 301 | 12 | layout = SignalLayout{.channels = kReturnsChannels}; | |
| 302 | 12 | break; | |
| 303 | } | ||
| 304 | 1068 | for (const uint32_t value : node.outputs) { | |
| 305 | 534 | layouts[value] = layout; | |
| 306 | } | ||
| 307 | 544 | for (const uint32_t value : node.scratch) { | |
| 308 | 10 | layouts[value] = layout; | |
| 309 | } | ||
| 310 | } | ||
| 311 | 124 | return layouts; | |
| 312 | } | ||
| 313 | |||
| 314 | } | ||
| 315 |