bvh/software_builder.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // SPDX-License-Identifier: BSD-3-Clause | ||
| 2 | #include "chrono_batch_render/bvh/software_builder.h" | ||
| 3 | |||
| 4 | #include <algorithm> | ||
| 5 | #include <cstdint> | ||
| 6 | #include <limits> | ||
| 7 | #include <memory> | ||
| 8 | #include <optional> | ||
| 9 | #include <variant> | ||
| 10 | #include <vector> | ||
| 11 | |||
| 12 | #include "chrono_batch_render/bvh/common.h" | ||
| 13 | #include "chrono_batch_render/bvh/kernels.h" | ||
| 14 | #include "chrono_batch_render/bvh/kernels/instances.h" | ||
| 15 | #include "chrono_batch_render/bvh/kernels/refit.h" | ||
| 16 | #include "chrono_batch_render/bvh/lbvh.h" | ||
| 17 | #include "chrono_batch_render/device/device.h" | ||
| 18 | #include "chrono_batch_render/device/program.h" | ||
| 19 | #include "chrono_batch_render/device/queue.h" | ||
| 20 | #include "chrono_batch_render/util/error.h" | ||
| 21 | #include "chrono_batch_render/util/log.h" | ||
| 22 | #include "chrono_batch_render/util/profiler.h" | ||
| 23 | |||
| 24 | namespace chrono::batch_render::bvh { | ||
| 25 | |||
| 26 | using device::Buffer; | ||
| 27 | using device::BufferRange; | ||
| 28 | using device::CommandEncoder; | ||
| 29 | using device::Device; | ||
| 30 | using device::Traversal; | ||
| 31 | |||
| 32 | namespace { | ||
| 33 | |||
| 34 | class SoftwareBuilder final : public Builder { | ||
| 35 | public: | ||
| 36 | 414 | explicit SoftwareBuilder(Device& device) : Builder(device) {} | |
| 37 | |||
| 38 | 263 | Traversal Format() const override { | |
| 39 | 263 | return Traversal::Software; | |
| 40 | } | ||
| 41 | |||
| 42 | 356 | void Prepare() override { | |
| 43 | 356 | Fit(1, 1); | |
| 44 | 356 | Updating(); | |
| 45 | 356 | } | |
| 46 | |||
| 47 | 619 | Ref<Blas> CreateBlas(const Geometry& geometry) override { | |
| 48 | 619 | return Ref<Blas>(new SoftwareBlas(m_device, geometry)); | |
| 49 | } | ||
| 50 | |||
| 51 | 313 | Ref<Tlas> CreateTlas(std::span<const uint32_t> capacities) override { | |
| 52 | 313 | return Ref<Tlas>(new SoftwareTlas(m_device, capacities)); | |
| 53 | } | ||
| 54 | |||
| 55 | 631 | void Build(CommandEncoder& encoder, std::span<const Ref<Blas>> blases) override { | |
| 56 | 631 | CHRONO_BATCH_RENDER_ZONE("Bvh/SoftwareBlases"); | |
| 57 | 631 | static const profiler::ZoneId zone = profiler::ZoneId::Named("Bvh/BlasBuild"); | |
| 58 | 631 | if (blases.empty()) { | |
| 59 | 45 | return; | |
| 60 | } | ||
| 61 | 586 | Log(LogLevel::Debug, "building {} software BLASes", blases.size()); | |
| 62 | 586 | const device::ZoneScope scope(encoder, zone); | |
| 63 | 1758 | for (const uint32_t kind : {kBvhTriangles, kBvhBoxes}) { | |
| 64 | 1172 | std::vector<Geometry> geometries; | |
| 65 | 1172 | std::vector<BufferRange> outputs; | |
| 66 | 1172 | uint64_t primitives = 0; | |
| 67 | 1172 | bool flushed = false; | |
| 68 | 2344 | const auto flush = [&] { | |
| 69 | 1172 | if (geometries.empty()) { | |
| 70 | return; | ||
| 71 | } | ||
| 72 | 588 | if (flushed) { | |
| 73 | ✗ | encoder.Barrier(); | |
| 74 | } | ||
| 75 | 588 | Fit(primitives, geometries.size()).Build(encoder, geometries, outputs); | |
| 76 | 588 | flushed = true; | |
| 77 | 588 | geometries.clear(); | |
| 78 | 588 | outputs.clear(); | |
| 79 | 588 | primitives = 0; | |
| 80 | 1172 | }; | |
| 81 | 2488 | for (const Ref<Blas>& blas : blases) { | |
| 82 | 1316 | const auto& software = RequireFormat<SoftwareBlas>("Build", *blas); | |
| 83 | 1316 | const Geometry& geometry = software.GetGeometry(); | |
| 84 | 1316 | if (Kind(geometry) != kind) { | |
| 85 | 658 | continue; | |
| 86 | } | ||
| 87 | 658 | const uint64_t count = PrimitiveCount(geometry); | |
| 88 | 658 | if (primitives + count > LbvhBuilder::kMaxPrimitives) { | |
| 89 | ✗ | flush(); | |
| 90 | } | ||
| 91 | 658 | geometries.push_back(geometry); | |
| 92 | 658 | outputs.emplace_back(software.Storage()); | |
| 93 | 658 | primitives += count; | |
| 94 | } | ||
| 95 | 1172 | flush(); | |
| 96 | 1172 | } | |
| 97 | 631 | } | |
| 98 | |||
| 99 | 251 | void Refit(CommandEncoder& encoder, std::span<const Ref<Blas>> blases) override { | |
| 100 | 251 | CHRONO_BATCH_RENDER_ZONE("Bvh/SoftwareBlases"); | |
| 101 | 251 | static const profiler::ZoneId zone = profiler::ZoneId::Named("Bvh/BlasRefit"); | |
| 102 | 251 | if (blases.empty()) { | |
| 103 | 204 | return; | |
| 104 | } | ||
| 105 | 47 | Log(LogLevel::Debug, "refitting {} software BLASes", blases.size()); | |
| 106 | 47 | const device::ZoneScope scope(encoder, zone); | |
| 107 | 139 | for (const uint32_t kind : {kBvhTriangles, kBvhBoxes}) { | |
| 108 | 93 | std::vector<RefitSegment> segments; | |
| 109 | 213 | for (const Ref<Blas>& blas : blases) { | |
| 110 | 121 | const auto& software = RequireFormat<SoftwareBlas>("Refit", *blas); | |
| 111 | 121 | const Geometry& geometry = software.GetGeometry(); | |
| 112 | 121 | RequireRefittable("Refit", ToAccelInput(geometry)); | |
| 113 | 120 | if (Kind(geometry) == kind) { | |
| 114 | 120 | segments.push_back({software.Handle(), 0, | |
| 115 | 60 | LbvhEntry(Updating(), "Refit", geometry), | |
| 116 | 60 | PrimitiveCount(geometry)}); | |
| 117 | } | ||
| 118 | } | ||
| 119 | 92 | RefitSegments(encoder, kind, 0, segments); | |
| 120 | 93 | } | |
| 121 | 252 | } | |
| 122 | |||
| 123 | 626 | void Build(CommandEncoder& encoder, Tlas& tlas, BufferRange instances, | |
| 124 | std::optional<BufferRange> counts, std::span<const uint32_t> worlds) override { | ||
| 125 | 626 | CHRONO_BATCH_RENDER_ZONE("Bvh/SoftwareTlas"); | |
| 126 | 626 | static const profiler::ZoneId zone = profiler::ZoneId::Named("Bvh/TlasBuild"); | |
| 127 | 626 | const auto& software = RequireFormat<SoftwareTlas>("Build", tlas); | |
| 128 | 626 | RequireInstances("Build", tlas, instances); | |
| 129 | 625 | const std::vector<uint32_t> selected = SelectWorlds("Build", tlas, worlds); | |
| 130 | 623 | Log(LogLevel::Debug, "building {} of {} software TLAS worlds", selected.size(), | |
| 131 | 623 | tlas.Count()); | |
| 132 | 623 | const device::ZoneScope scope(encoder, zone); | |
| 133 | 623 | std::vector<BufferRange> outputs; | |
| 134 | 623 | std::vector<uint32_t> capacities; | |
| 135 | 1365 | for (const uint32_t w : selected) { | |
| 136 | 742 | outputs.push_back(software.World(w)); | |
| 137 | 742 | capacities.push_back(tlas.Capacities()[w]); | |
| 138 | } | ||
| 139 | 623 | if (selected.size() == tlas.Count()) { | |
| 140 | 607 | Fit(Total(capacities), capacities.size()) | |
| 141 | 607 | .Build(encoder, instances, capacities, counts, outputs); | |
| 142 | 607 | return; | |
| 143 | } | ||
| 144 | |||
| 145 | 16 | const std::vector<uint32_t> offsets = Offsets(tlas.Capacities()); | |
| 146 | 16 | const uint64_t total = std::max<uint64_t>(Total(capacities), 1); | |
| 147 | 16 | Buffer& compact = | |
| 148 | 16 | Reserve(m_device, m_compact, | |
| 149 | 16 | {.bytes = total * sizeof(InstanceRecord), .label = "tlas world instances"}); | |
| 150 | 16 | Buffer& compactCounts = | |
| 151 | 16 | Reserve(m_device, m_compactCounts, | |
| 152 | 16 | {.bytes = selected.size() * sizeof(uint32_t), .label = "tlas world counts"}); | |
| 153 | 16 | std::vector<uint32_t> sourceOffsets; | |
| 154 | 34 | for (const uint32_t w : selected) { | |
| 155 | 18 | sourceOffsets.push_back(offsets[w]); | |
| 156 | } | ||
| 157 | 16 | Tables tables; | |
| 158 | 16 | const uint64_t sourceStart = tables.Add(sourceOffsets); | |
| 159 | 16 | const uint64_t offsetsStart = tables.Add(Offsets(capacities)); | |
| 160 | 16 | const uint64_t worldsStart = tables.Add(selected); | |
| 161 | 16 | const Ref<Buffer> table = tables.Upload(m_device, "tlas world gather"); | |
| 162 | 16 | const GatherWorldsParams params = {.source = {instances.Address()}, | |
| 163 | 16 | .destination = {compact.Address()}, | |
| 164 | 16 | .sourceOffsets = {table->Address() + sourceStart}, | |
| 165 | 16 | .offsets = {table->Address() + offsetsStart}, | |
| 166 | 16 | .worlds = {table->Address() + worldsStart}, | |
| 167 | 16 | .counts = {counts ? counts->Address() : 0}, | |
| 168 | 16 | .gatheredCounts = {compactCounts.Address()}, | |
| 169 | 16 | .total = uint32_t(Total(capacities)), | |
| 170 | 30 | .selected = uint32_t(selected.size())}; | |
| 171 | 16 | encoder.Barrier(); | |
| 172 | 16 | encoder.Dispatch(Updating().Entry("GatherWorlds"), params, | |
| 173 | 16 | {.x = device::GroupCount(std::max<uint64_t>(params.total, params.selected), | |
| 174 | kGatherWorldsThreads)}); | ||
| 175 | 16 | encoder.Barrier(); | |
| 176 | 16 | std::optional<BufferRange> compactRange; | |
| 177 | 16 | if (counts) { | |
| 178 | 14 | compactRange = compactCounts.Slice(0, selected.size() * sizeof(uint32_t)); | |
| 179 | } | ||
| 180 | 16 | Fit(Total(capacities), capacities.size()) | |
| 181 | 16 | .Build(encoder, compact.Slice(0, total * sizeof(InstanceRecord)), capacities, | |
| 182 | compactRange, outputs); | ||
| 183 | 674 | } | |
| 184 | |||
| 185 | 159 | void Refit(CommandEncoder& encoder, Tlas& tlas, BufferRange instances, | |
| 186 | std::optional<BufferRange>, std::span<const uint32_t> worlds) override { | ||
| 187 | 159 | CHRONO_BATCH_RENDER_ZONE("Bvh/SoftwareTlas"); | |
| 188 | 159 | static const profiler::ZoneId zone = profiler::ZoneId::Named("Bvh/TlasRefit"); | |
| 189 | 159 | RequireFormat<SoftwareTlas>("Refit", tlas); | |
| 190 | 159 | RequireInstances("Refit", tlas, instances); | |
| 191 | 159 | const std::vector<uint32_t> offsets = Offsets(tlas.Capacities()); | |
| 192 | 159 | const std::vector<uint32_t> selected = SelectWorlds("Refit", tlas, worlds); | |
| 193 | 159 | Log(LogLevel::Debug, "refitting {} of {} software TLAS worlds", selected.size(), | |
| 194 | 159 | tlas.Count()); | |
| 195 | 159 | const device::ZoneScope scope(encoder, zone); | |
| 196 | 159 | std::vector<RefitSegment> segments; | |
| 197 | 328 | for (const uint32_t w : selected) { | |
| 198 | 169 | segments.push_back({tlas.Handle(w), offsets[w], {}, tlas.Capacities()[w]}); | |
| 199 | } | ||
| 200 | 159 | RefitSegments(encoder, kBvhInstances, instances.Address(), segments); | |
| 201 | 159 | } | |
| 202 | |||
| 203 | private: | ||
| 204 | struct RefitSegment { | ||
| 205 | uint64_t header = 0; | ||
| 206 | uint32_t instanceOffset = 0; | ||
| 207 | LbvhGeometry geometry{}; | ||
| 208 | uint32_t primitives = 0; | ||
| 209 | }; | ||
| 210 | |||
| 211 | 638 | device::Program& Updating() { | |
| 212 | 638 | if (!m_updating) { | |
| 213 | 383 | m_updating = | |
| 214 | 1149 | m_device.CreateProgram({.modules = {"chrono_batch_render.bvh.kernels.refit", | |
| 215 | "chrono_batch_render.bvh.kernels.instances"}, | ||
| 216 | .library = &kBvhKernels, | ||
| 217 | .entries = {"RefitLeaves", "RefitNodes", "GatherWorlds"}, | ||
| 218 | 766 | .label = "bvh updates"}); | |
| 219 | } | ||
| 220 | 638 | return *m_updating; | |
| 221 | 766 | } | |
| 222 | |||
| 223 | 251 | void RefitSegments(CommandEncoder& encoder, uint32_t kind, uint64_t instances, | |
| 224 | std::span<const RefitSegment> segments) { | ||
| 225 | 251 | if (segments.empty()) { | |
| 226 | 45 | return; | |
| 227 | } | ||
| 228 | 206 | std::vector<uint64_t> headers; | |
| 229 | 206 | std::vector<LbvhGeometry> geometries; | |
| 230 | 206 | std::vector<uint32_t> instanceOffsets; | |
| 231 | 206 | std::vector<uint32_t> rooms; | |
| 232 | 435 | for (const RefitSegment& segment : segments) { | |
| 233 | 229 | headers.push_back(segment.header); | |
| 234 | 229 | geometries.push_back(segment.geometry); | |
| 235 | 229 | instanceOffsets.push_back(segment.instanceOffset); | |
| 236 | 229 | rooms.push_back(std::max(segment.primitives, 1u)); | |
| 237 | } | ||
| 238 | 206 | const std::vector<uint32_t> nodeOffsets = Offsets(rooms); | |
| 239 | 206 | const uint32_t nodes = nodeOffsets.back(); | |
| 240 | |||
| 241 | 206 | Tables tables; | |
| 242 | 206 | const uint64_t headersStart = tables.Add(headers); | |
| 243 | 206 | const uint64_t geometriesStart = tables.Add(geometries); | |
| 244 | 206 | const uint64_t instanceOffsetsStart = tables.Add(instanceOffsets); | |
| 245 | 206 | const uint64_t nodeOffsetsStart = tables.Add(nodeOffsets); | |
| 246 | 206 | const Ref<Buffer> table = tables.Upload(m_device, "bvh refit tables"); | |
| 247 | |||
| 248 | 206 | const uint64_t bytes = uint64_t(nodes) * 16 * sizeof(uint32_t); | |
| 249 | 206 | const uint64_t base = | |
| 250 | 206 | Reserve(m_device, m_refitScratch, {.bytes = bytes, .label = "bvh refit scratch"}) | |
| 251 | 206 | .Address(); | |
| 252 | 206 | const RefitParams params = {.headers = {table->Address() + headersStart}, | |
| 253 | .instances = {instances}, | ||
| 254 | 206 | .instanceOffsets = {table->Address() + instanceOffsetsStart}, | |
| 255 | 206 | .geometries = {table->Address() + geometriesStart}, | |
| 256 | 206 | .nodeOffsets = {table->Address() + nodeOffsetsStart}, | |
| 257 | .parents = {base}, | ||
| 258 | 206 | .arrivals = {base + uint64_t(nodes) * sizeof(uint32_t)}, | |
| 259 | 206 | .boxes = {base + uint64_t(nodes) * 2 * sizeof(uint32_t)}, | |
| 260 | 206 | .leafBoxes = {base + uint64_t(nodes) * 8 * sizeof(uint32_t)}, | |
| 261 | 206 | .segments = uint32_t(segments.size()), | |
| 262 | .nodes = nodes, | ||
| 263 | 206 | .kind = kind}; | |
| 264 | |||
| 265 | 206 | const device::Size3 groups{.x = device::GroupCount(nodes, kRefitThreads)}; | |
| 266 | 206 | device::Program& program = Updating(); | |
| 267 | 206 | encoder.Barrier(); | |
| 268 | 206 | encoder.Dispatch(program.Entry("RefitLeaves"), params, groups); | |
| 269 | 206 | encoder.Barrier(); | |
| 270 | 412 | encoder.Dispatch(program.Entry("RefitNodes"), params, groups); | |
| 271 | 618 | } | |
| 272 | |||
| 273 | 1567 | LbvhBuilder& Fit(uint64_t primitives, size_t segments) { | |
| 274 | 1567 | if (primitives > LbvhBuilder::kMaxPrimitives) { | |
| 275 | ✗ | throw Error("Build of {} primitives; a build takes at most {}", primitives, | |
| 276 | ✗ | LbvhBuilder::kMaxPrimitives); | |
| 277 | } | ||
| 278 | 1567 | if (segments > std::numeric_limits<uint32_t>::max()) { | |
| 279 | ✗ | throw Error("Build of {} BVHs; a build takes at most {}", segments, | |
| 280 | ✗ | std::numeric_limits<uint32_t>::max()); | |
| 281 | } | ||
| 282 | |||
| 283 | 1567 | if (!m_lbvh || m_lbvh->Capacity() < primitives || m_lbvh->Segments() < segments) { | |
| 284 | 826 | uint64_t capacity = primitives; | |
| 285 | 826 | uint64_t most = segments; | |
| 286 | 826 | if (m_lbvh) { | |
| 287 | 416 | capacity = std::max(capacity, uint64_t(m_lbvh->Capacity()) * 3 / 2); | |
| 288 | 430 | most = std::max(most, uint64_t(m_lbvh->Segments()) * 3 / 2); | |
| 289 | } | ||
| 290 | 826 | Log(LogLevel::Debug, "the LBVH builder grows to {} primitives in {} BVHs", capacity, | |
| 291 | most); | ||
| 292 | 1652 | m_lbvh.emplace( | |
| 293 | 826 | m_device, uint32_t(std::min<uint64_t>(capacity, LbvhBuilder::kMaxPrimitives)), | |
| 294 | 1652 | uint32_t(std::min<uint64_t>(most, std::numeric_limits<uint32_t>::max()))); | |
| 295 | } | ||
| 296 | 1567 | return *m_lbvh; | |
| 297 | } | ||
| 298 | |||
| 299 | std::optional<LbvhBuilder> m_lbvh; | ||
| 300 | Ref<device::Program> m_updating; | ||
| 301 | Ref<Buffer> m_refitScratch; | ||
| 302 | Ref<Buffer> m_compact; | ||
| 303 | Ref<Buffer> m_compactCounts; | ||
| 304 | }; | ||
| 305 | |||
| 306 | } | ||
| 307 | |||
| 308 | 619 | SoftwareBlas::SoftwareBlas(Device& device, const Geometry& geometry) | |
| 309 | 619 | : Blas(kFormat), m_geometry(geometry), m_buffers(GeometryBuffers(geometry)), | |
| 310 | 619 | m_storage(device.CreateBuffer( | |
| 311 | 1238 | {.bytes = StorageBytes(PrimitiveCount(geometry), Kind(geometry)), .label = "blas"})) {} | |
| 312 | |||
| 313 | 313 | SoftwareTlas::SoftwareTlas(Device& device, std::span<const uint32_t> capacities) | |
| 314 | 313 | : Tlas(kFormat, capacities) { | |
| 315 | 312 | std::vector<uint64_t> handles; | |
| 316 | 312 | uint64_t bytes = 0; | |
| 317 | 734 | for (const uint32_t capacity : capacities) { | |
| 318 | 422 | handles.push_back(bytes); | |
| 319 | 422 | bytes += StorageBytes(capacity, kBvhInstances); | |
| 320 | } | ||
| 321 | 312 | m_storage = device.CreateBuffer({.bytes = bytes, .label = "tlas"}); | |
| 322 | 734 | for (uint64_t& handle : handles) { | |
| 323 | 422 | handle += m_storage->Address(); | |
| 324 | } | ||
| 325 | 312 | SetHandles(device, std::move(handles)); | |
| 326 | 936 | } | |
| 327 | |||
| 328 | 750 | BufferRange SoftwareTlas::World(uint32_t world) const { | |
| 329 | 750 | return m_storage->Slice(Handle(world) - m_storage->Address(), | |
| 330 | 750 | StorageBytes(Capacities()[world], kBvhInstances)); | |
| 331 | } | ||
| 332 | |||
| 333 | 414 | std::unique_ptr<Builder> CreateSoftwareBuilder(Device& device) { | |
| 334 | 414 | return std::make_unique<SoftwareBuilder>(device); | |
| 335 | } | ||
| 336 | |||
| 337 | } | ||
| 338 |