scene/tables/products.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // SPDX-License-Identifier: BSD-3-Clause | ||
| 2 | #include "chrono_batch_render/scene/tables/products.h" | ||
| 3 | |||
| 4 | #include <algorithm> | ||
| 5 | #include <cmath> | ||
| 6 | #include <initializer_list> | ||
| 7 | #include <optional> | ||
| 8 | |||
| 9 | #include <fmt/format.h> | ||
| 10 | |||
| 11 | #include "chrono_batch_render/scene/records/render_tables.h" | ||
| 12 | #include "chrono_batch_render/scene/scene.h" | ||
| 13 | #include "chrono_batch_render/scene/schema/columns.h" | ||
| 14 | #include "chrono_batch_render/stage/publish/change_set.h" | ||
| 15 | #include "chrono_batch_render/stage/views/view.h" | ||
| 16 | #include "chrono_batch_render/util/error.h" | ||
| 17 | #include "chrono_batch_render/util/log.h" | ||
| 18 | #include "chrono_batch_render/util/profiler.h" | ||
| 19 | |||
| 20 | namespace chrono::batch_render { | ||
| 21 | |||
| 22 | namespace { | ||
| 23 | |||
| 24 | 5 | std::vector<ColumnRef> ProductInputs() { | |
| 25 | 5 | std::vector<ColumnRef> inputs = { | |
| 26 | kLidarFiringsPerScan, kLidarEmitterElevations, kLidarMaxReturns, | ||
| 27 | 5 | kRadarRays, kDetectorAPIDetectorPattern, kSensorPipelineBinding}; | |
| 28 | 40 | for (const SchemaId schema : | |
| 29 | {SchemaId::RenderProduct, SchemaId::RenderVar, SchemaId::MinimalAPI, | ||
| 30 | SchemaId::BackgroundAPI, SchemaId::RealTimeAPI, SchemaId::SensorAPI, | ||
| 31 | 45 | SchemaId::ColorBackground, SchemaId::GradientBackground}) { | |
| 32 | 40 | const Span<const ColumnInfo> columns = Columns(schema); | |
| 33 | 215 | for (uint32_t index = 0; index < columns.size(); ++index) { | |
| 34 | 175 | inputs.push_back({schema, &columns[index], index}); | |
| 35 | } | ||
| 36 | } | ||
| 37 | 5 | return inputs; | |
| 38 | ✗ | } | |
| 39 | |||
| 40 | struct Shape { | ||
| 41 | 441 | Uint2 resolution; | |
| 42 | 441 | uint32_t returns = 0; | |
| 43 | |||
| 44 | 882 | bool operator==(const Shape&) const = default; | |
| 45 | }; | ||
| 46 | |||
| 47 | 844 | Shape SensorShape(const Prim& sensor, Uint2 resolution) { | |
| 48 | 844 | if (sensor.IsA<Lidar>()) { | |
| 49 | 30 | const Shape shape{{sensor.Get(Lidar::kFiringsPerScan), | |
| 50 | ✗ | uint32_t(sensor.Get(Lidar::kEmitterElevations).size())}, | |
| 51 | 30 | sensor.Get(Lidar::kMaxReturns)}; | |
| 52 | 30 | if (shape.returns == 0 || shape.returns > kMostLidarReturns) { | |
| 53 | ✗ | throw Error("{} reports {} returns per beam, and lidars report 1 to {}", sensor.Path(), | |
| 54 | ✗ | shape.returns, kMostLidarReturns); | |
| 55 | } | ||
| 56 | 30 | if (shape.resolution.y == 0) { | |
| 57 | ✗ | throw Error("{} has no emitters", sensor.Path()); | |
| 58 | } | ||
| 59 | 30 | return shape; | |
| 60 | } | ||
| 61 | 814 | if (sensor.IsA<Radar>()) { | |
| 62 | 34 | return {sensor.Get(Radar::kRays)}; | |
| 63 | } | ||
| 64 | 780 | return {resolution}; | |
| 65 | } | ||
| 66 | |||
| 67 | 780 | int64_t DetectorChannels(const Prim& camera) { | |
| 68 | 780 | switch (camera.Get(DetectorAPI::kDetectorPattern)) { | |
| 69 | case DetectorPattern::Mono: | ||
| 70 | return 1; | ||
| 71 | ✗ | case DetectorPattern::Rccc: | |
| 72 | ✗ | return 2; | |
| 73 | 2 | case DetectorPattern::Bands: | |
| 74 | 2 | return kDetectorBands; | |
| 75 | 773 | default: | |
| 76 | 773 | return 3; | |
| 77 | } | ||
| 78 | } | ||
| 79 | |||
| 80 | struct VarFormat { | ||
| 81 | device::DType type; | ||
| 82 | int64_t channels; | ||
| 83 | }; | ||
| 84 | |||
| 85 | 407 | VarFormat SourceFormat(RenderVarSource source) { | |
| 86 | 407 | switch (source) { | |
| 87 | 37 | case RenderVarSource::LdrColor: | |
| 88 | 37 | case RenderVarSource::DiffuseAlbedo: | |
| 89 | 37 | return {device::DType::UInt8, 4}; | |
| 90 | 81 | case RenderVarSource::HdrColor: | |
| 91 | 81 | return {device::DType::Float32, 4}; | |
| 92 | 237 | case RenderVarSource::DistanceToCamera: | |
| 93 | 237 | case RenderVarSource::DistanceToImagePlane: | |
| 94 | 237 | case RenderVarSource::NormalizedDepth: | |
| 95 | 237 | case RenderVarSource::Intensity: | |
| 96 | 237 | case RenderVarSource::RadialVelocity: | |
| 97 | 237 | return {device::DType::Float32, 1}; | |
| 98 | 17 | case RenderVarSource::NonStableInstanceSegmentation: | |
| 99 | 17 | case RenderVarSource::SemanticSegmentation: | |
| 100 | 17 | return {device::DType::UInt32, 1}; | |
| 101 | 17 | case RenderVarSource::Normal: | |
| 102 | 17 | return {device::DType::Float32, 3}; | |
| 103 | ✗ | case RenderVarSource::SemanticInstanceSegmentation: | |
| 104 | ✗ | case RenderVarSource::StableIdSegmentation: | |
| 105 | ✗ | case RenderVarSource::InstancerStableId: | |
| 106 | ✗ | return {device::DType::UInt32, 4}; | |
| 107 | 3 | case RenderVarSource::InstanceId: | |
| 108 | 3 | return {device::DType::Int64, 1}; | |
| 109 | 15 | default: | |
| 110 | 15 | return {device::DType::UInt8, 0}; | |
| 111 | } | ||
| 112 | } | ||
| 113 | |||
| 114 | constexpr uint64_t Sources(std::initializer_list<RenderVarSource> sources) { | ||
| 115 | uint64_t mask = 0; | ||
| 116 | for (const RenderVarSource source : sources) { | ||
| 117 | mask |= uint64_t(1) << uint32_t(source); | ||
| 118 | } | ||
| 119 | return mask; | ||
| 120 | } | ||
| 121 | |||
| 122 | 428 | constexpr bool Includes(uint64_t sources, RenderVarSource source) { | |
| 123 | 428 | return (sources >> uint32_t(source) & 1) != 0; | |
| 124 | } | ||
| 125 | |||
| 126 | constexpr uint64_t kSemanticSources = | ||
| 127 | Sources({RenderVarSource::NonStableInstanceSegmentation, RenderVarSource::SemanticSegmentation, | ||
| 128 | RenderVarSource::SemanticIdMap, RenderVarSource::SemanticInstanceSegmentation, | ||
| 129 | RenderVarSource::StableIdSegmentation, RenderVarSource::StableIdMap}); | ||
| 130 | |||
| 131 | constexpr uint64_t kRangeSources = | ||
| 132 | kSemanticSources | Sources({RenderVarSource::DistanceToCamera, RenderVarSource::Normal, | ||
| 133 | RenderVarSource::Intensity, RenderVarSource::PointCloud}); | ||
| 134 | |||
| 135 | struct SensorKind { | ||
| 136 | SchemaId schema; | ||
| 137 | std::optional<RenderEntry> entry; | ||
| 138 | uint64_t outputs; | ||
| 139 | }; | ||
| 140 | |||
| 141 | const SensorKind kSensorKinds[] = { | ||
| 142 | {SchemaId::Camera, std::nullopt, | ||
| 143 | ((uint64_t(1) << kRenderVarSourceCount) - 1) & | ||
| 144 | ~Sources({RenderVarSource::Intensity, RenderVarSource::PointCloud, | ||
| 145 | RenderVarSource::RadialVelocity})}, | ||
| 146 | {SchemaId::Lidar, RenderEntry::Lidar, kRangeSources}, | ||
| 147 | {SchemaId::Radar, RenderEntry::Radar, | ||
| 148 | kRangeSources | Sources({RenderVarSource::RadialVelocity})}, | ||
| 149 | }; | ||
| 150 | |||
| 151 | 403 | const SensorKind& FindSensorKind(SchemaId schema) { | |
| 152 | 452 | for (const SensorKind& kind : kSensorKinds) { | |
| 153 | 452 | if (IsA(schema, kind.schema)) { | |
| 154 | 403 | return kind; | |
| 155 | } | ||
| 156 | } | ||
| 157 | ✗ | throw Error("A {} isn't a sensor the renderer renders", Info(schema).name); | |
| 158 | } | ||
| 159 | |||
| 160 | 394 | struct BoundSensorPipeline { | |
| 161 | std::shared_ptr<const SensorPipelineInstance> instance; | ||
| 162 | }; | ||
| 163 | |||
| 164 | 32 | std::vector<PointChannel> CloudChannels(const Prim& prim, const SensorKind& kind, bool& semantic) { | |
| 165 | 32 | std::vector<std::string> names = prim.Get(RenderVar::kChannels); | |
| 166 | 32 | if (names.empty()) { | |
| 167 | ✗ | names = {"Coordinates", "Intensity"}; | |
| 168 | } | ||
| 169 | 32 | std::vector<PointChannel> channels; | |
| 170 | 121 | for (const std::string& channel : names) { | |
| 171 | 89 | const auto found = std::ranges::find(kPointChannels, channel, &PointChannelInfo::name); | |
| 172 | 89 | if (found == std::end(kPointChannels)) { | |
| 173 | ✗ | throw Error("{} asks for {}, which a PointCloud has no channel for", prim.Path(), | |
| 174 | ✗ | channel); | |
| 175 | } | ||
| 176 | 89 | const auto point = PointChannel(found - std::begin(kPointChannels)); | |
| 177 | 89 | if ((point == PointChannel::RadialVelocity || | |
| 178 | 89 | point == PointChannel::RadialVelocityVector) && | |
| 179 | 10 | !Includes(kind.outputs, RenderVarSource::RadialVelocity)) { | |
| 180 | ✗ | throw Error("{} asks for {}, which a {} doesn't measure", prim.Path(), channel, | |
| 181 | ✗ | Info(kind.schema).name); | |
| 182 | } | ||
| 183 | 89 | semantic |= point == PointChannel::StableId; | |
| 184 | 89 | channels.push_back(point); | |
| 185 | } | ||
| 186 | 32 | return channels; | |
| 187 | 32 | } | |
| 188 | |||
| 189 | 68 | ResolvedVar SensorPipelineVar(const Prim& prim, const ResolvedProduct& product, | |
| 190 | const SensorKind& kind, const BoundSensorPipeline& bound, | ||
| 191 | const std::string& name) { | ||
| 192 | 68 | const bool tap = name == kTapSource; | |
| 193 | 68 | const std::string output = tap ? prim.Path() : name; | |
| 194 | 68 | const auto found = std::ranges::find(bound.instance->resolved.outputs, output, | |
| 195 | &sensor_pipeline::ResolvedOutput::name); | ||
| 196 | 68 | if (found == bound.instance->resolved.outputs.end()) { | |
| 197 | 2 | if (tap) { | |
| 198 | 2 | throw Error("{} taps no node of {}", prim.Path(), bound.instance->path); | |
| 199 | } | ||
| 200 | 1 | throw Error("{} reads {}, which isn't an output the renderer writes or {} makes", | |
| 201 | 2 | prim.Path(), name, bound.instance->path); | |
| 202 | } | ||
| 203 | 66 | const sensor_pipeline::SignalLayout& layout = product.layouts[found->value]; | |
| 204 | 66 | ResolvedVar var{.prim = prim.Index(), | |
| 205 | .path = prim.Path(), | ||
| 206 | 66 | .frame = prim.Get(RenderVar::kFrameOfReference), | |
| 207 | .type = device::DType::Float32, | ||
| 208 | .output = output, | ||
| 209 | 132 | .tap = tap}; | |
| 210 | 66 | const sensor_pipeline::SignalType type = bound.instance->resolved.values[found->value].type; | |
| 211 | 66 | if (type == sensor_pipeline::SignalType::PointCloud || | |
| 212 | 2 | (type == sensor_pipeline::SignalType::Returns && | |
| 213 | 2 | (kind.schema == SchemaId::Lidar || kind.schema == SchemaId::Radar))) { | |
| 214 | 12 | if (kind.schema != SchemaId::Lidar && kind.schema != SchemaId::Radar) { | |
| 215 | ✗ | throw Error("{} reads the point cloud {} makes, and only lidars and radars make them " | |
| 216 | "in a pipeline", | ||
| 217 | ✗ | prim.Path(), bound.instance->path); | |
| 218 | } | ||
| 219 | 12 | var.source = RenderVarSource::PointCloud; | |
| 220 | 12 | var.channels = CloudChannels(prim, kind, var.semantic); | |
| 221 | 12 | return var; | |
| 222 | } | ||
| 223 | 54 | var.shape = {int64_t(product.sensors.size())}; | |
| 224 | 54 | if (layout.perCapture) { | |
| 225 | 1 | var.shape.push_back(sensor_pipeline::kStatWords); | |
| 226 | 1 | return var; | |
| 227 | } | ||
| 228 | 53 | var.shape.insert(var.shape.end(), {int64_t(product.height), int64_t(product.width)}); | |
| 229 | 53 | if (product.returns > 0) { | |
| 230 | ✗ | var.shape.push_back(product.returns); | |
| 231 | } | ||
| 232 | 53 | if (layout.channels > 1) { | |
| 233 | 49 | var.shape.push_back(layout.channels); | |
| 234 | } | ||
| 235 | return var; | ||
| 236 | 66 | } | |
| 237 | |||
| 238 | 497 | ResolvedVar ResolveVar(const Prim& prim, const ResolvedProduct& product, const SensorKind& kind, | |
| 239 | int64_t channels, const BoundSensorPipeline& bound) { | ||
| 240 | 497 | const std::string name = prim.Get(RenderVar::kSourceName); | |
| 241 | 497 | const std::optional<RenderVarSource> builtIn = FindRenderVarSource(name); | |
| 242 | 497 | if (!builtIn) { | |
| 243 | 69 | if (!bound.instance && name == kTapSource) { | |
| 244 | ✗ | throw Error("{} taps a pipeline node, but its product binds no pipeline", prim.Path()); | |
| 245 | } | ||
| 246 | 69 | if (!bound.instance) { | |
| 247 | 1 | throw Error("{} reads {}, which isn't an output the renderer writes", prim.Path(), | |
| 248 | 3 | name); | |
| 249 | } | ||
| 250 | 68 | return SensorPipelineVar(prim, product, kind, bound, name); | |
| 251 | } | ||
| 252 | 428 | const RenderVarSource source = *builtIn; | |
| 253 | 428 | ResolvedVar var{.prim = prim.Index(), | |
| 254 | .path = prim.Path(), | ||
| 255 | .source = source, | ||
| 256 | 428 | .frame = prim.Get(RenderVar::kFrameOfReference), | |
| 257 | 428 | .semantic = Includes(kSemanticSources, source)}; | |
| 258 | 428 | if (!Includes(kind.outputs, source)) { | |
| 259 | ✗ | throw Error("{} writes {}, which a {} doesn't output", prim.Path(), name, | |
| 260 | 3 | Info(kind.schema).name); | |
| 261 | } | ||
| 262 | 427 | if (source == RenderVarSource::PointCloud) { | |
| 263 | 40 | var.channels = CloudChannels(prim, kind, var.semantic); | |
| 264 | 20 | return var; | |
| 265 | } | ||
| 266 | 407 | VarFormat format = SourceFormat(source); | |
| 267 | 407 | if (source == RenderVarSource::ElectronRate) { | |
| 268 | 15 | if (channels == 0) { | |
| 269 | 1 | throw Error("{} writes ElectronRate from cameras whose detector patterns have " | |
| 270 | "different channel counts", | ||
| 271 | 1 | var.path); | |
| 272 | } | ||
| 273 | 14 | format = {device::DType::Float32, channels}; | |
| 274 | } | ||
| 275 | 406 | if (format.channels == 0) { | |
| 276 | ✗ | return var; | |
| 277 | } | ||
| 278 | 406 | var.type = format.type; | |
| 279 | 406 | var.shape = {int64_t(product.sensors.size()), product.height, product.width}; | |
| 280 | 406 | if (product.returns > 0) { | |
| 281 | ✗ | var.shape.push_back(product.returns); | |
| 282 | } | ||
| 283 | 406 | if (format.channels > 1) { | |
| 284 | 147 | var.shape.push_back(format.channels); | |
| 285 | } | ||
| 286 | 406 | return var; | |
| 287 | 920 | } | |
| 288 | |||
| 289 | 395 | RenderEntry SelectEntry(const ResolvedProduct& product, const SensorKind& kind) { | |
| 290 | 395 | if (kind.entry) { | |
| 291 | 32 | return *kind.entry; | |
| 292 | } | ||
| 293 | 363 | const bool shaded = std::ranges::any_of(product.vars, [](const ResolvedVar& var) { | |
| 294 | 374 | return var.source == RenderVarSource::LdrColor || var.source == RenderVarSource::HdrColor || | |
| 295 | var.source == RenderVarSource::DiffuseAlbedo || | ||
| 296 | 213 | var.source == RenderVarSource::ElectronRate || !var.output.empty(); | |
| 297 | }); | ||
| 298 | 363 | if (!shaded) { | |
| 299 | return RenderEntry::MinimalData; | ||
| 300 | } | ||
| 301 | 161 | const ProductRecord& record = product.record; | |
| 302 | 161 | if (RenderMode(record.renderMode) == RenderMode::RealTime) { | |
| 303 | 75 | return SpectralMode(record.spectralMode) == SpectralMode::Bands ? RenderEntry::RealTimeBands | |
| 304 | 75 | : RenderEntry::RealTime; | |
| 305 | } | ||
| 306 | 86 | switch (MinimalMode(record.minimalMode)) { | |
| 307 | case MinimalMode::DisplayColor: | ||
| 308 | return RenderEntry::MinimalDisplayColor; | ||
| 309 | 57 | case MinimalMode::TextureDiffuse: | |
| 310 | 57 | return RenderEntry::MinimalTextureDiffuse; | |
| 311 | 21 | case MinimalMode::Full: | |
| 312 | 21 | return RenderEntry::MinimalFull; | |
| 313 | ✗ | default: | |
| 314 | ✗ | return RenderEntry::MinimalData; | |
| 315 | } | ||
| 316 | } | ||
| 317 | |||
| 318 | } | ||
| 319 | |||
| 320 | 2394 | std::span<const ColumnRef> ProductTable::Inputs() const { | |
| 321 | 2394 | static const std::vector<ColumnRef> inputs = ProductInputs(); | |
| 322 | 2394 | return inputs; | |
| 323 | } | ||
| 324 | |||
| 325 | 501 | void ProductTable::Describe(RenderTables& tables) const { | |
| 326 | 501 | tables.products.address = m_records.Buffer() ? m_records.Buffer()->Address() : 0; | |
| 327 | 501 | } | |
| 328 | |||
| 329 | 3982 | std::shared_ptr<const ResolvedProduct> ProductTable::Find(uint32_t prim) const { | |
| 330 | 3982 | const std::lock_guard lock(m_mutex); | |
| 331 | 3982 | const auto found = m_products.find(prim); | |
| 332 | 7779 | return found == m_products.end() ? nullptr : found->second; | |
| 333 | 3982 | } | |
| 334 | |||
| 335 | 527 | std::vector<std::shared_ptr<const ResolvedProduct>> ProductTable::Products() const { | |
| 336 | 527 | const std::lock_guard lock(m_mutex); | |
| 337 | 527 | std::vector<std::shared_ptr<const ResolvedProduct>> products; | |
| 338 | 960 | for (const auto& [prim, product] : m_products) { | |
| 339 | 433 | products.push_back(product); | |
| 340 | } | ||
| 341 | 527 | return products; | |
| 342 | 527 | } | |
| 343 | |||
| 344 | 404 | ResolvedProduct ProductTable::Resolve(uint32_t index) const { | |
| 345 | 404 | const Prims& prims = m_structure.GetPrims(); | |
| 346 | 404 | ResolvedProduct product; | |
| 347 | 404 | product.prim = index; | |
| 348 | 404 | const Prim prim = m_scene.ByIndex(index); | |
| 349 | 404 | const std::string path = prim.Path(); | |
| 350 | 404 | const View view(m_structure, SchemaId::RenderProduct, std::vector<uint32_t>{index}); | |
| 351 | 1209 | const auto targets = [&](ColumnRef column, SchemaId schema) { | |
| 352 | 805 | const std::vector<uint32_t> found = view.GetArray<uint32_t>(column).Get(0); | |
| 353 | 1744 | for (const uint32_t target : found) { | |
| 354 | 939 | if (!prims.Alive(target) || !IsA(prims.Record(target).schema, schema)) { | |
| 355 | ✗ | throw Error("{}'s {} targets prim {}, which isn't a {}", path, column.column->field, | |
| 356 | ✗ | target, Info(schema).name); | |
| 357 | } | ||
| 358 | } | ||
| 359 | 805 | return found; | |
| 360 | ✗ | }; | |
| 361 | 404 | ProductRecord& record = product.record; | |
| 362 | 404 | record.textureLodBias = prim.Get(RenderProduct::kTextureLodBias); | |
| 363 | 404 | record.maxAnisotropy = uint32_t(std::max(prim.Get(RenderProduct::kMaxAnisotropy), 1)); | |
| 364 | 404 | record.maxBounces = uint32_t(std::max(prim.Get(RealTimeAPI::kMaxBounces), 0)); | |
| 365 | 404 | record.pixelSamples = uint32_t(std::max(prim.Get(RealTimeAPI::kPixelSamples), 1)); | |
| 366 | 404 | record.conform = uint32_t(prim.Get(RenderProduct::kAspectRatioConformPolicy)); | |
| 367 | 404 | record.pixelAspect = prim.Get(RenderProduct::kPixelAspectRatio); | |
| 368 | 404 | if (!(record.pixelAspect > 0)) { | |
| 369 | ✗ | record.pixelAspect = 1; | |
| 370 | } | ||
| 371 | 404 | record.castShadows = prim.Get(MinimalAPI::kCastShadows) ? 1 : 0; | |
| 372 | 404 | record.shadowVisibility = prim.Get(MinimalAPI::kShadowVisibility); | |
| 373 | 404 | record.hemispheric = prim.Get(MinimalAPI::kAmbientHemispheric) ? 1 : 0; | |
| 374 | 404 | const float intensity = prim.Get(MinimalAPI::kAmbientLightIntensity); | |
| 375 | 404 | const Float3 ambient = prim.Get(MinimalAPI::kAmbientLightColor); | |
| 376 | 404 | const Float3 ground = prim.Get(MinimalAPI::kAmbientGroundColor); | |
| 377 | 404 | record.ambient = {ambient.x * intensity, ambient.y * intensity, ambient.z * intensity}; | |
| 378 | 404 | record.ground = {ground.x * intensity, ground.y * intensity, ground.z * intensity}; | |
| 379 | 404 | record.backgroundColor = prim.Get(BackgroundAPI::kBackgroundColor); | |
| 380 | 404 | const std::vector<uint32_t> backgrounds = | |
| 381 | 404 | view.GetArray<uint32_t>(kBackgroundAPIBackground).Get(0); | |
| 382 | 404 | record.background = backgrounds.empty() ? kNoPrim : backgrounds.front(); | |
| 383 | 404 | record.backgroundKind = uint32_t(SchemaId::None); | |
| 384 | 404 | if (record.background != kNoPrim && prims.Alive(record.background)) { | |
| 385 | 2 | const Prim background = m_scene.ByIndex(record.background); | |
| 386 | 2 | record.backgroundKind = uint32_t(prims.Record(record.background).schema); | |
| 387 | 2 | if (background.IsA<ColorBackground>()) { | |
| 388 | 2 | record.backgroundLow = background.Get(ColorBackground::kColor); | |
| 389 | ✗ | } else if (background.IsA<GradientBackground>()) { | |
| 390 | ✗ | record.backgroundLow = background.Get(GradientBackground::kHorizonColor); | |
| 391 | ✗ | record.backgroundHigh = background.Get(GradientBackground::kZenithColor); | |
| 392 | } | ||
| 393 | } | ||
| 394 | 404 | record.renderMode = uint32_t(prim.Get(RenderProduct::kRenderMode)); | |
| 395 | 404 | record.minimalMode = uint32_t(prim.Get(MinimalAPI::kMode)); | |
| 396 | 404 | record.spectralMode = uint32_t(prim.Get(RenderProduct::kSpectralMode)); | |
| 397 | 404 | product.missDistance = prim.Get(RenderProduct::kMissDistance); | |
| 398 | 404 | record.sensors = m_structure.GetColumnStore().Array(kRenderProductCamera, | |
| 399 | prims.Row(index, SchemaId::RenderProduct)); | ||
| 400 | 404 | record.vars = m_structure.GetColumnStore().Array(kRenderProductOrderedVars, | |
| 401 | prims.Row(index, SchemaId::RenderProduct)); | ||
| 402 | 404 | product.taxonomy = view.Get<Token>(kRenderProductSemanticTaxonomy).Get().front(); | |
| 403 | 404 | try { | |
| 404 | 404 | product.sensors = targets(kRenderProductCamera, SchemaId::Sensor); | |
| 405 | 404 | if (product.sensors.empty()) { | |
| 406 | 1 | throw Error("{} renders no sensor", path); | |
| 407 | } | ||
| 408 | 403 | const Prim lead = m_scene.ByIndex(product.sensors.front()); | |
| 409 | 403 | const SchemaId schema = prims.Record(lead.Index()).schema; | |
| 410 | 403 | const Uint2 resolution = prim.Get(RenderProduct::kResolution); | |
| 411 | 403 | const Shape shape = SensorShape(lead, resolution); | |
| 412 | 403 | product.sensor = schema; | |
| 413 | 403 | product.frameRate = lead.Get(SensorAPI::kSensorFrameRate); | |
| 414 | 403 | product.startTime = lead.Get(SensorAPI::kSensorStartTime); | |
| 415 | 844 | for (const uint32_t sensor : product.sensors) { | |
| 416 | 441 | const Prim each = m_scene.ByIndex(sensor); | |
| 417 | 441 | if (prims.Record(sensor).schema != schema) { | |
| 418 | ✗ | throw Error("{} stacks {} and {}, which are different kinds of sensor", path, | |
| 419 | ✗ | lead.Path(), each.Path()); | |
| 420 | } | ||
| 421 | 441 | if (each.Get(SensorAPI::kSensorFrameRate) != product.frameRate || | |
| 422 | 441 | each.Get(SensorAPI::kSensorStartTime) != product.startTime) { | |
| 423 | ✗ | throw Error("{} stacks {} and {}, which capture at different times", path, | |
| 424 | ✗ | lead.Path(), each.Path()); | |
| 425 | } | ||
| 426 | 882 | if (SensorShape(each, resolution) != shape) { | |
| 427 | ✗ | throw Error("{} stacks {} and {}, which have different shapes", path, lead.Path(), | |
| 428 | ✗ | each.Path()); | |
| 429 | } | ||
| 430 | } | ||
| 431 | 403 | product.width = shape.resolution.x; | |
| 432 | 403 | product.height = shape.resolution.y; | |
| 433 | 403 | product.returns = shape.returns; | |
| 434 | 403 | if (product.width == 0 || product.height == 0) { | |
| 435 | ✗ | throw Error("{} has a resolution of {} x {}", path, product.width, product.height); | |
| 436 | } | ||
| 437 | 403 | const SensorKind& kind = FindSensorKind(schema); | |
| 438 | 403 | int64_t channels = 0; | |
| 439 | 403 | if (lead.IsA<Camera>()) { | |
| 440 | 371 | channels = DetectorChannels(lead); | |
| 441 | 780 | for (const uint32_t sensor : product.sensors) { | |
| 442 | 817 | channels = DetectorChannels(m_scene.ByIndex(sensor)) == channels ? channels : 0; | |
| 443 | } | ||
| 444 | } | ||
| 445 | 403 | product.channels = uint32_t(channels); | |
| 446 | 1626 | const auto binding = [&](ColumnRef column, uint32_t at) { | |
| 447 | 1223 | const View bound(m_structure, prims.Record(at).schema, std::vector<uint32_t>{at}); | |
| 448 | 1223 | const std::vector<uint32_t> found = bound.GetArray<uint32_t>(column).Get(0); | |
| 449 | 1223 | return found.empty() || !prims.Alive(found.front()) ? kNoPrim : found.front(); | |
| 450 | 2446 | }; | |
| 451 | 403 | product.pipeline = binding(kRenderProductPipelineBinding, index); | |
| 452 | 403 | if (product.pipeline == kNoPrim) { | |
| 453 | 391 | product.pipeline = binding(kSensorPipelineBinding, lead.Index()); | |
| 454 | 820 | for (const uint32_t sensor : product.sensors) { | |
| 455 | 429 | if (binding(kSensorPipelineBinding, sensor) != product.pipeline) { | |
| 456 | ✗ | throw Error("{} stacks {} and {}, which bind different pipelines", path, | |
| 457 | ✗ | lead.Path(), m_scene.ByIndex(sensor).Path()); | |
| 458 | } | ||
| 459 | } | ||
| 460 | } | ||
| 461 | 403 | BoundSensorPipeline bound; | |
| 462 | 403 | if (product.pipeline != kNoPrim) { | |
| 463 | 59 | if (!IsA(prims.Record(product.pipeline).schema, SchemaId::SensorPipeline)) { | |
| 464 | ✗ | throw Error("{} binds {}, which isn't a SensorPipeline", path, | |
| 465 | ✗ | m_scene.ByIndex(product.pipeline).Path()); | |
| 466 | } | ||
| 467 | 118 | bound.instance = m_pipelines.Find(product.pipeline); | |
| 468 | 59 | if (!bound.instance) { | |
| 469 | ✗ | throw Error("{} binds {}, which isn't resolved yet", path, | |
| 470 | ✗ | m_scene.ByIndex(product.pipeline).Path()); | |
| 471 | } | ||
| 472 | 59 | if (!bound.instance->error.empty()) { | |
| 473 | 2 | throw Error("{}", bound.instance->error); | |
| 474 | } | ||
| 475 | 57 | const DetectorPattern pattern = lead.IsA<Camera>() | |
| 476 | 57 | ? lead.Get(DetectorAPI::kDetectorPattern) | |
| 477 | 57 | : DetectorPattern::Mono; | |
| 478 | 57 | const bool mosaics = pattern != DetectorPattern::Mono && | |
| 479 | pattern != DetectorPattern::Rgb && | ||
| 480 | 57 | pattern != DetectorPattern::Bands; | |
| 481 | 114 | product.layouts = sensor_pipeline::PropagateLayouts( | |
| 482 | 57 | bound.instance->resolved, *m_pipelines.Registry(), | |
| 483 | 125 | {.channels = uint32_t(std::max<int64_t>(channels, 1)), | |
| 484 | .pattern = uint32_t(pattern), | ||
| 485 | 57 | .mosaics = mosaics}); | |
| 486 | } | ||
| 487 | 893 | for (const uint32_t var : targets(kRenderProductOrderedVars, SchemaId::RenderVar)) { | |
| 488 | 492 | const ResolvedVar& added = product.vars.emplace_back( | |
| 489 | 989 | ResolveVar(m_scene.ByIndex(var), product, kind, channels, bound)); | |
| 490 | 492 | product.semantic |= added.semantic; | |
| 491 | 401 | } | |
| 492 | 396 | const bool rated = | |
| 493 | 396 | std::ranges::any_of(product.vars, | |
| 494 | 474 | [](const ResolvedVar& var) { | |
| 495 | 474 | return var.output.empty() && | |
| 496 | 423 | var.source == RenderVarSource::ElectronRate; | |
| 497 | 396 | }) || | |
| 498 | 44 | (bound.instance && | |
| 499 | 44 | std::ranges::any_of(bound.instance->resolved.values, | |
| 500 | 64 | [](const sensor_pipeline::Value& value) { | |
| 501 | 64 | return value.type == sensor_pipeline::SignalType::ElectronRate; | |
| 502 | 382 | })); | |
| 503 | 396 | if (bound.instance && std::ranges::any_of(bound.instance->resolved.values, | |
| 504 | 218 | [](const sensor_pipeline::Value& value) { | |
| 505 | 218 | return value.type == | |
| 506 | sensor_pipeline::SignalType::Returns; | ||
| 507 | 396 | })) { | |
| 508 | 11 | std::optional<FrameOfReference> frame; | |
| 509 | 34 | for (const ResolvedVar& var : product.vars) { | |
| 510 | 23 | if (var.output.empty() || var.source != RenderVarSource::PointCloud) { | |
| 511 | 11 | continue; | |
| 512 | } | ||
| 513 | 12 | if (frame && *frame != var.frame) { | |
| 514 | ✗ | throw Error("{} reads point clouds from {} in two frames of reference, and a " | |
| 515 | "pipeline's clouds share one", | ||
| 516 | ✗ | path, bound.instance->path); | |
| 517 | } | ||
| 518 | 12 | frame = var.frame; | |
| 519 | } | ||
| 520 | 11 | if (kind.schema != SchemaId::Lidar && kind.schema != SchemaId::Radar) { | |
| 521 | ✗ | throw Error("{} runs {}, which reads returns, so its sensor must be a Lidar or a " | |
| 522 | "Radar", | ||
| 523 | ✗ | path, bound.instance->path); | |
| 524 | } | ||
| 525 | } | ||
| 526 | 396 | if (rated && RenderMode(record.renderMode) != RenderMode::RealTime) { | |
| 527 | 1 | throw Error("{} measures electron rates, which only a RealTime render traces, so set " | |
| 528 | "its renderMode to RealTime", | ||
| 529 | 1 | path); | |
| 530 | } | ||
| 531 | 395 | product.entry = SelectEntry(product, kind); | |
| 532 | 404 | product.overlays = prim.Get(RenderProduct::kOverlays); | |
| 533 | 395 | if (!product.overlays.empty() && kind.schema != SchemaId::Camera) { | |
| 534 | 1 | throw Error("{} draws overlays over a {}, and only cameras draw them", path, | |
| 535 | 2 | Info(kind.schema).name); | |
| 536 | } | ||
| 537 | 404 | } catch (const Error& error) { | |
| 538 | 10 | product.error = error.what(); | |
| 539 | 10 | } | |
| 540 | 404 | record.sensorSchema = uint32_t(product.sensor); | |
| 541 | 404 | record.width = product.width; | |
| 542 | 404 | record.height = product.height; | |
| 543 | 404 | record.returns = product.returns; | |
| 544 | 404 | record.frameRate = product.frameRate; | |
| 545 | 404 | record.startTime = product.startTime; | |
| 546 | 404 | record.missDistance = product.missDistance; | |
| 547 | 808 | return product; | |
| 548 | 808 | } | |
| 549 | |||
| 550 | 501 | void ProductTable::Run(const ChangeSet& changes, TableVersions& versions) { | |
| 551 | 501 | CHRONO_BATCH_RENDER_ZONE("Products/Run"); | |
| 552 | 501 | ColumnStore& store = m_structure.GetColumnStore(); | |
| 553 | 227 | const bool edited = changes.resync || changes.structure || !changes.apis.empty() || | |
| 554 | 579 | m_pipelines.Version() != m_pipelineVersion || | |
| 555 | 39 | std::ranges::any_of(Inputs(), [&](ColumnRef input) { | |
| 556 | 556 | return store.LastWrite(input) > m_seen; | |
| 557 | 501 | }); | |
| 558 | 501 | m_seen = store.WriteSequence(); | |
| 559 | 501 | m_pipelineVersion = m_pipelines.Version(); | |
| 560 | 501 | if (edited) { | |
| 561 | 501 | Resolve(); | |
| 562 | 501 | versions.Record(TableId::Products, std::vector<uint32_t>{kGlobalWorld}); | |
| 563 | } | ||
| 564 | 501 | } | |
| 565 | |||
| 566 | 501 | void ProductTable::Resolve() { | |
| 567 | 501 | const Prims& prims = m_structure.GetPrims(); | |
| 568 | 501 | std::map<uint32_t, std::shared_ptr<const ResolvedProduct>> resolved; | |
| 569 | 12060 | for (uint32_t prim = 0; prim < prims.Size(); ++prim) { | |
| 570 | 11559 | if (!prims.Alive(prim) || prims.Record(prim).schema != SchemaId::RenderProduct) { | |
| 571 | 11155 | continue; | |
| 572 | } | ||
| 573 | 404 | ResolvedProduct product = Resolve(prim); | |
| 574 | 404 | const std::shared_ptr<const ResolvedProduct> previous = Find(prim); | |
| 575 | 404 | const bool same = previous && static_cast<const ProductShape&>(*previous) == product; | |
| 576 | 404 | product.version = same ? previous->version : ++m_versions; | |
| 577 | 404 | if (!same && product.error.empty()) { | |
| 578 | 207 | std::string sources; | |
| 579 | 502 | for (const ResolvedVar& var : product.vars) { | |
| 580 | ✗ | sources += fmt::format( | |
| 581 | 685 | "{}{}", sources.empty() ? "" : ", ", | |
| 582 | 590 | var.output.empty() ? std::string(kRenderVarSourceTokens[uint32_t(var.source)]) | |
| 583 | 348 | : var.output); | |
| 584 | } | ||
| 585 | 207 | Log(LogLevel::Debug, "resolved {}: {} at {} x {} writing {}{}", | |
| 586 | 414 | m_scene.ByIndex(prim).Path(), Counted(product.sensors.size(), "sensor"), | |
| 587 | 414 | product.width, product.height, sources.empty() ? "nothing" : sources, | |
| 588 | 414 | product.frameRate > 0 ? fmt::format(", capturing at {} Hz", product.frameRate) | |
| 589 | 202 | : ""); | |
| 590 | 207 | } | |
| 591 | 404 | m_records.Set(prims.Row(prim, SchemaId::RenderProduct), product.record); | |
| 592 | 808 | resolved.emplace(prim, std::make_shared<const ResolvedProduct>(std::move(product))); | |
| 593 | 404 | } | |
| 594 | 501 | { | |
| 595 | 501 | const std::lock_guard lock(m_mutex); | |
| 596 | 501 | m_products = std::move(resolved); | |
| 597 | 501 | } | |
| 598 | 501 | m_records.Reserve(m_structure.GetColumnStore().Schema(SchemaId::RenderProduct).Capacity()); | |
| 599 | 501 | m_records.Upload(m_structure.GetColumnStore().GetDevice()); | |
| 600 | 501 | } | |
| 601 | |||
| 602 | } | ||
| 603 |