render/ray_cast/ray_cast.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // SPDX-License-Identifier: BSD-3-Clause | ||
| 2 | #include "chrono_batch_render/render/ray_cast/ray_cast.h" | ||
| 3 | |||
| 4 | #include <algorithm> | ||
| 5 | #include <memory> | ||
| 6 | #include <string> | ||
| 7 | #include <utility> | ||
| 8 | #include <vector> | ||
| 9 | |||
| 10 | #include "chrono_batch_render/device/device.h" | ||
| 11 | #include "chrono_batch_render/device/program.h" | ||
| 12 | #include "chrono_batch_render/device/queue.h" | ||
| 13 | #include "chrono_batch_render/render/kernels.h" | ||
| 14 | #include "chrono_batch_render/render/kernels/query/ray_cast.h" | ||
| 15 | #include "chrono_batch_render/scene/scene.h" | ||
| 16 | #include "chrono_batch_render/scene/scene_impl.h" | ||
| 17 | #include "chrono_batch_render/util/error.h" | ||
| 18 | #include "chrono_batch_render/util/profiler.h" | ||
| 19 | #include "chrono_batch_render/util/span.h" | ||
| 20 | |||
| 21 | namespace chrono::batch_render { | ||
| 22 | namespace { | ||
| 23 | |||
| 24 | 1183 | int64_t CountRays(device::Device& device, const device::Tensor& tensor, int64_t width, | |
| 25 | device::DType type, const char* what) { | ||
| 26 | 1183 | const Span<const int64_t> shape = tensor.Shape(); | |
| 27 | 1183 | const bool shaped = width == 1 ? shape.size() == 1 : shape.size() == 2 && shape[1] == width; | |
| 28 | 1183 | const bool typed = tensor.Type() == type || | |
| 29 | ✗ | (type == device::DType::UInt32 && tensor.Type() == device::DType::Int32); | |
| 30 | 1183 | if (!shaped || !typed) { | |
| 31 | 1 | throw Error("Ray {} are a [N{}] {} tensor", what, | |
| 32 | 3 | width == 1 ? "" : ", " + std::to_string(width), | |
| 33 | 3 | type == device::DType::Float32 ? "float32" : "uint32"); | |
| 34 | } | ||
| 35 | 1182 | if (&tensor.GetDevice() != &device) { | |
| 36 | ✗ | throw Error("Ray {} are on {}, not the scene's {}; Device::Copy or Device::Import moves " | |
| 37 | "them", | ||
| 38 | ✗ | what, tensor.GetDevice().Adapter().alias, device.Adapter().alias); | |
| 39 | } | ||
| 40 | 1182 | const Span<const int64_t> strides = tensor.Strides(); | |
| 41 | 1182 | if (!strides.empty() && ((shape.back() > 1 && strides.back() != 1) || | |
| 42 | 1177 | (width > 1 && shape[0] > 1 && strides[0] != width))) { | |
| 43 | ✗ | throw UnsupportedError("Ray {} are cast from contiguous tensors only", what); | |
| 44 | } | ||
| 45 | 1182 | return shape[0]; | |
| 46 | } | ||
| 47 | |||
| 48 | 1463 | uint64_t InputAddress(device::Device& device, const device::Tensor& tensor, int64_t width, | |
| 49 | device::DType type, int64_t count, const char* what) { | ||
| 50 | 1463 | if (tensor.Empty()) { | |
| 51 | return 0; | ||
| 52 | } | ||
| 53 | 591 | if (CountRays(device, tensor, width, type, what) != count) { | |
| 54 | 1 | throw Error("Ray {} hold {} rows for {} rays", what, tensor.Shape()[0], count); | |
| 55 | } | ||
| 56 | 590 | return tensor.Address(); | |
| 57 | } | ||
| 58 | |||
| 59 | 11 | void Collect(const Prim& prim, std::vector<uint32_t>& prims) { | |
| 60 | 11 | prims.push_back(prim.Index()); | |
| 61 | 15 | for (const Prim& child : prim.Children()) { | |
| 62 | 4 | Collect(child, prims); | |
| 63 | 11 | } | |
| 64 | 11 | } | |
| 65 | |||
| 66 | 291 | std::vector<uint32_t> IgnoredPrims(const std::vector<Prim>& ignore) { | |
| 67 | 291 | std::vector<uint32_t> prims; | |
| 68 | 298 | for (const Prim& prim : ignore) { | |
| 69 | 7 | Collect(prim, prims); | |
| 70 | } | ||
| 71 | 291 | std::ranges::sort(prims); | |
| 72 | 291 | prims.erase(std::unique(prims.begin(), prims.end()), prims.end()); | |
| 73 | 291 | return prims; | |
| 74 | ✗ | } | |
| 75 | |||
| 76 | 1752 | uint64_t Output(device::Device& device, device::Tensor& tensor, bool wanted, device::DType type, | |
| 77 | std::vector<int64_t> shape) { | ||
| 78 | 1752 | if (!wanted) { | |
| 79 | 883 | tensor = device::Tensor(); | |
| 80 | 883 | return 0; | |
| 81 | } | ||
| 82 | 869 | const bool fits = !tensor.Empty() && &tensor.GetDevice() == &device && tensor.Type() == type && | |
| 83 | ✗ | std::ranges::equal(tensor.Shape(), shape); | |
| 84 | ✗ | if (!fits) { | |
| 85 | 869 | tensor = device.CreateTensor(type, std::move(shape), "ray hits"); | |
| 86 | } | ||
| 87 | 869 | return tensor.Address(); | |
| 88 | } | ||
| 89 | |||
| 90 | } | ||
| 91 | |||
| 92 | 51 | struct RayCaster::State { | |
| 93 | Ref<Scene> scene; | ||
| 94 | Ref<device::Program> program; | ||
| 95 | std::string options; | ||
| 96 | |||
| 97 | 50 | void Link() { | |
| 98 | 50 | options = SceneImpl::Get(*scene).GetCurves().ShapeOptions(); | |
| 99 | 300 | program = scene->GetDevice().CreateProgram( | |
| 100 | {.modules = {"chrono_batch_render.render.kernels.query.ray_cast"}, | ||
| 101 | .library = &kRenderKernels, | ||
| 102 | .sources = {{kShapeOptionsFile, options}}, | ||
| 103 | .entries = {"CastClosest", "CastAny"}, | ||
| 104 | 50 | .label = "ray cast", | |
| 105 | 100 | .traversal = SceneImpl::Get(*scene).GetBuilder().Format()}); | |
| 106 | 200 | } | |
| 107 | }; | ||
| 108 | |||
| 109 | 51 | RayCaster::RayCaster(Ref<Scene> scene) : m_state(std::make_unique<State>()) { | |
| 110 | 51 | if (!scene) { | |
| 111 | 2 | throw Error("A ray caster of no scene"); | |
| 112 | } | ||
| 113 | 50 | m_state->scene = std::move(scene); | |
| 114 | 50 | m_state->Link(); | |
| 115 | 51 | } | |
| 116 | |||
| 117 | 50 | RayCaster::~RayCaster() = default; | |
| 118 | ✗ | RayCaster::RayCaster(RayCaster&&) noexcept = default; | |
| 119 | ✗ | RayCaster& RayCaster::operator=(RayCaster&&) noexcept = default; | |
| 120 | |||
| 121 | 297 | device::Operation RayCaster::Cast(const Rays& rays, RayHits& hits, const RayCastDesc& desc, | |
| 122 | Stream stream) { | ||
| 123 | 297 | CHRONO_BATCH_RENDER_ZONE("Query/RayCast"); | |
| 124 | 297 | Scene& scene = *m_state->scene; | |
| 125 | 297 | SceneUpdate update = SceneImpl::Get(scene).Update({}, stream); | |
| 126 | 297 | if (SceneImpl::Get(scene).GetCurves().ShapeOptions() != m_state->options) { | |
| 127 | ✗ | m_state->Link(); | |
| 128 | } | ||
| 129 | 297 | device::Device& device = scene.GetDevice(); | |
| 130 | 297 | if (rays.origins.Empty() || rays.directions.Empty()) { | |
| 131 | 2 | throw Error("A cast needs ray origins and directions"); | |
| 132 | } | ||
| 133 | 296 | constexpr device::DType kFloat = device::DType::Float32; | |
| 134 | 296 | const int64_t count = CountRays(device, rays.origins, 3, kFloat, "origins"); | |
| 135 | 296 | if (CountRays(device, rays.directions, 3, kFloat, "directions") != count) { | |
| 136 | 2 | throw Error("{} ray origins with {} directions", count, rays.directions.Shape()[0]); | |
| 137 | } | ||
| 138 | 293 | if (count > int64_t(~uint32_t(0))) { | |
| 139 | ✗ | throw Error("A cast of {} rays; at most {} fit one cast", count, ~uint32_t(0)); | |
| 140 | } | ||
| 141 | 293 | RayCastParams params{}; | |
| 142 | 293 | params.origins.address = InputAddress(device, rays.origins, 3, kFloat, count, "origins"); | |
| 143 | 586 | params.directions.address = | |
| 144 | 293 | InputAddress(device, rays.directions, 3, kFloat, count, "directions"); | |
| 145 | 293 | params.tMins.address = InputAddress(device, rays.tMins, 1, kFloat, count, "minimum distances"); | |
| 146 | 584 | params.tMaxes.address = | |
| 147 | 292 | InputAddress(device, rays.tMaxes, 1, kFloat, count, "maximum distances"); | |
| 148 | 584 | params.worlds.address = | |
| 149 | 292 | InputAddress(device, rays.worlds, 1, device::DType::UInt32, count, "worlds"); | |
| 150 | 292 | params.minDistance = rays.minDistance; | |
| 151 | 292 | params.maxDistance = rays.maxDistance; | |
| 152 | 292 | params.world = rays.world; | |
| 153 | 292 | params.count = uint32_t(count); | |
| 154 | |||
| 155 | 292 | const bool closest = desc.mode == RayCastMode::Closest; | |
| 156 | 2044 | const auto rows = [&](int64_t width) { | |
| 157 | 1752 | return width == 1 ? std::vector<int64_t>{count} : std::vector<int64_t>{count, width}; | |
| 158 | 292 | }; | |
| 159 | 292 | params.hits.address = Output(device, hits.hit, true, device::DType::UInt32, rows(1)); | |
| 160 | 584 | params.distances.address = | |
| 161 | 293 | Output(device, hits.distance, closest && desc.outputs & RayOutput::Distance, | |
| 162 | 292 | device::DType::Float32, rows(1)); | |
| 163 | 316 | params.prims.address = Output(device, hits.prim, closest && desc.outputs & RayOutput::Prim, | |
| 164 | 292 | device::DType::UInt32, rows(1)); | |
| 165 | 584 | params.instances.address = | |
| 166 | 580 | Output(device, hits.instance, closest && desc.outputs & RayOutput::InstanceId, | |
| 167 | 292 | device::DType::Int64, rows(1)); | |
| 168 | 584 | params.normals.address = | |
| 169 | 567 | Output(device, hits.normal, closest && desc.outputs & RayOutput::Normal, | |
| 170 | 292 | device::DType::Float32, rows(3)); | |
| 171 | 584 | params.positions.address = | |
| 172 | 572 | Output(device, hits.position, closest && desc.outputs & RayOutput::Position, | |
| 173 | 292 | device::DType::Float32, rows(3)); | |
| 174 | |||
| 175 | 292 | params.spaceRow = kNoRow; | |
| 176 | 292 | if (rays.space) { | |
| 177 | 3 | if (!rays.space->IsA<Xformable>()) { | |
| 178 | 1 | throw Error("rays are cast in the space of {}, which has no transform", | |
| 179 | 2 | rays.space->Path()); | |
| 180 | } | ||
| 181 | } | ||
| 182 | 291 | const std::vector<uint32_t> ignored = IgnoredPrims(desc.ignore); | |
| 183 | 291 | params.ignoredCount = uint32_t(ignored.size()); | |
| 184 | |||
| 185 | 291 | if (rays.space) { | |
| 186 | 2 | const uint32_t prim = rays.space->Index(); | |
| 187 | 4 | params.spaceRow = SceneImpl::Get(scene).GetStructure().GetPrims().Rows( | |
| 188 | 2 | std::span<const uint32_t>(&prim, 1), SchemaId::Xformable)[0]; | |
| 189 | } | ||
| 190 | 291 | if (count == 0) { | |
| 191 | ✗ | hits.done = update.Published(); | |
| 192 | ✗ | return hits.done; | |
| 193 | } | ||
| 194 | 291 | const Snapshot& snapshot = update.Tables(); | |
| 195 | 291 | params.tables.address = snapshot.tables; | |
| 196 | 291 | device::Queue& queue = device.GetQueue(); | |
| 197 | 291 | if (!ignored.empty()) { | |
| 198 | 5 | params.ignored.address = | |
| 199 | 5 | queue.Upload(ignored.data(), ignored.size() * sizeof(uint32_t)).Address(); | |
| 200 | } | ||
| 201 | 291 | queue.Follow(stream); | |
| 202 | 291 | device::CommandEncoder encoder = queue.CreateEncoder(); | |
| 203 | 291 | encoder.BeginZone(profiler::ZoneId::Named(desc.label)); | |
| 204 | 294 | encoder.Dispatch(m_state->program->Entry(closest ? "CastClosest" : "CastAny"), params, | |
| 205 | 291 | {.x = device::GroupCount(params.count, kRayCastGroup)}); | |
| 206 | 291 | encoder.EndZone(); | |
| 207 | 291 | hits.done = queue.Submit(encoder.Finish()); | |
| 208 | 291 | if (!stream.IsNone()) { | |
| 209 | 291 | hits.done.Wait(stream); | |
| 210 | } | ||
| 211 | 291 | return hits.done; | |
| 212 | 303 | } | |
| 213 | |||
| 214 | 18 | Scene& RayCaster::GetScene() const { | |
| 215 | 18 | return *m_state->scene; | |
| 216 | } | ||
| 217 | |||
| 218 | 288 | RayHits RayCaster::Cast(const Rays& rays, const RayCastDesc& desc) { | |
| 219 | 288 | RayHits hits; | |
| 220 | 288 | Cast(rays, hits, desc, Stream::Host()); | |
| 221 | 283 | return hits; | |
| 222 | 5 | } | |
| 223 | |||
| 224 | } | ||
| 225 |