render/rt/top_levels.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // SPDX-License-Identifier: BSD-3-Clause | ||
| 2 | /// @file top_levels.cpp | ||
| 3 | /// @brief Writing instance records on the device and building the top level over them. | ||
| 4 | #include "ernie/render/rt/top_levels.h" | ||
| 5 | |||
| 6 | #include "ernie/shaders/rt/write_instances.h" | ||
| 7 | #include "ernie/util/math.h" | ||
| 8 | #include "ernie/util/profiler.h" | ||
| 9 | |||
| 10 | #include <algorithm> | ||
| 11 | #include <array> | ||
| 12 | #include <bit> | ||
| 13 | #include <vector> | ||
| 14 | |||
| 15 | namespace ernie { | ||
| 16 | namespace { | ||
| 17 | |||
| 18 | using shaders::WriteParameters; | ||
| 19 | static_assert(sizeof(shaders::Instance) == sizeof(AccelerationInstance)); | ||
| 20 | |||
| 21 | 15 | std::vector<std::uint32_t> Compile(const ShaderCompiler& compiler) { | |
| 22 | 15 | std::vector<ShaderModule> modules(TransformGraph::WorldModules().begin(), | |
| 23 | 15 | TransformGraph::WorldModules().end()); | |
| 24 | 15 | modules.push_back(shaders::kWriteInstances); | |
| 25 | 30 | return compiler.Spirv(modules, "main"); | |
| 26 | 15 | } | |
| 27 | |||
| 28 | /// @return The capacity for `count` instances: doubling, and at least 64. | ||
| 29 | 15 | std::uint32_t RoundUpCapacity(std::uint32_t count) { | |
| 30 | 20 | return std::max<std::uint32_t>(64, std::bit_ceil(count)); | |
| 31 | } | ||
| 32 | |||
| 33 | } // namespace | ||
| 34 | |||
| 35 | 15 | TopLevels::TopLevels(DeviceContext& context, const ShaderCompiler& compiler) | |
| 36 | 15 | : context_(context), writer_(context, Compile(compiler), "write instances", | |
| 37 | std::uint32_t{sizeof(WriteParameters)}), | ||
| 38 | 15 | pool_(context, "top levels") {} | |
| 39 | |||
| 40 | 27 | graph::Acceleration TopLevels::AddBuild(graph::Graph& graph, const TransformGraph& transforms, | |
| 41 | const InstanceTable& instances, | ||
| 42 | const BottomLevels& bottoms) { | ||
| 43 | 27 | ERNIE_ZONE("Prepare the top level"); | |
| 44 | 27 | const std::uint32_t count = instances.Count(); | |
| 45 | 27 | EnsureCapacity(count); | |
| 46 | 27 | graph::Buffer records = graph.Create( | |
| 47 | 32 | std::max<std::uint64_t>(count, 1) * sizeof(AccelerationInstance), "instance records"); | |
| 48 | 27 | if (count > 0) { | |
| 49 | 22 | AddWritePass(graph, transforms, instances, bottoms, records); | |
| 50 | } | ||
| 51 | 54 | return AddBuildPass(graph, bottoms, records, count); | |
| 52 | 27 | } | |
| 53 | |||
| 54 | 27 | void TopLevels::EnsureCapacity(std::uint32_t count) { | |
| 55 | 27 | const std::uint32_t capacity = top_ ? top_->Sizes().primitives[0] : 0; | |
| 56 | 27 | if (!top_ || count > capacity || (capacity > 64 && count < capacity / 4)) { | |
| 57 | // Frames in flight keep the old one: destroying it retires it. | ||
| 58 | 15 | top_ = std::make_unique<AccelerationStructure>( | |
| 59 | 30 | pool_, Sizes(context_, RoundUpCapacity(count), kRebuilt), "scene"); | |
| 60 | } | ||
| 61 | 27 | } | |
| 62 | |||
| 63 | 22 | void TopLevels::AddWritePass(graph::Graph& graph, const TransformGraph& transforms, | |
| 64 | const InstanceTable& instances, const BottomLevels& bottoms, | ||
| 65 | graph::Buffer& records) const { | ||
| 66 | // In `WriteParameters`' order. | ||
| 67 | 22 | const std::array reads{graph.Import(transforms.Worlds()), graph.Import(instances.Nodes()), | |
| 68 | 22 | graph.Import(instances.Geometries()), graph.Import(instances.Shapes()), | |
| 69 | 22 | graph.Import(bottoms.Addresses())}; | |
| 70 | 22 | const std::uint32_t count = instances.Count(); | |
| 71 | 44 | graph.Add( | |
| 72 | "Write instances", graph::Kind::Compute, | ||
| 73 | 66 | [&](graph::PassBuilder& pass) { | |
| 74 | 132 | for (const graph::Buffer& read : reads) { | |
| 75 | 110 | pass.ReadAddress(read); | |
| 76 | } | ||
| 77 | 22 | records = pass.WriteAddress(records, graph::Use::StorageWrite); | |
| 78 | 22 | }, | |
| 79 | 22 | [this, reads, records, count](graph::PassContext& pass) { | |
| 80 | 110 | const auto address = [&](std::size_t read) { return pass.Get(reads[read]).Address(); }; | |
| 81 | 66 | writer_.Dispatch(pass.commands, {DivideRoundUp(count, 64u), 1, 1}, | |
| 82 | 22 | WriteParameters{{address(0)}, | |
| 83 | 22 | {address(1)}, | |
| 84 | 22 | {address(2)}, | |
| 85 | 22 | {address(3)}, | |
| 86 | 22 | {address(4)}, | |
| 87 | 22 | pass.Get(records).BufferRef<shaders::Instance>(), | |
| 88 | count}); | ||
| 89 | 22 | }); | |
| 90 | 22 | } | |
| 91 | |||
| 92 | 27 | graph::Acceleration TopLevels::AddBuildPass(graph::Graph& graph, const BottomLevels& bottoms, | |
| 93 | graph::Buffer records, std::uint32_t count) const { | ||
| 94 | 27 | graph::Acceleration top = graph.Import(*top_); | |
| 95 | 27 | const graph::Acceleration levels = graph.Import(bottoms.Pool()); | |
| 96 | 27 | const TopBuild build{.target = top_.get(), .count = count}; | |
| 97 | 27 | graph::Buffer scratch = | |
| 98 | 27 | graph.Create(ScratchBytes({}, std::span(&build, 1)), "top-level scratch"); | |
| 99 | 54 | graph.Add( | |
| 100 | "Build the top level", graph::Kind::Build, | ||
| 101 | 81 | [&](graph::PassBuilder& pass) { | |
| 102 | 27 | pass.Read(levels, graph::Use::BuildInput); | |
| 103 | 27 | if (count > 0) { | |
| 104 | 22 | pass.Read(records, graph::Use::BuildInput); | |
| 105 | } | ||
| 106 | 27 | scratch = pass.Write(scratch, graph::Use::BuildScratch); | |
| 107 | 27 | top = pass.Build(top); | |
| 108 | 27 | }, | |
| 109 | 27 | [build, records, scratch, count](graph::PassContext& pass) { | |
| 110 | 27 | TopBuild recorded = build; | |
| 111 | 27 | if (count > 0) { | |
| 112 | 22 | const graph::BufferSlice slice = pass.Get(records); | |
| 113 | 22 | recorded.instances = {slice.buffer, slice.offset}; | |
| 114 | } | ||
| 115 | 27 | const graph::BufferSlice space = pass.Get(scratch); | |
| 116 | 27 | RecordBuilds(pass.commands, {}, std::span(&recorded, 1), {space.buffer, space.offset}); | |
| 117 | 27 | }); | |
| 118 | 27 | return top; | |
| 119 | } | ||
| 120 | |||
| 121 | } // namespace ernie | ||
| 122 |