scene/tables/subsets.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // SPDX-License-Identifier: BSD-3-Clause | ||
| 2 | #include "chrono_batch_render/scene/tables/subsets.h" | ||
| 3 | |||
| 4 | #include <algorithm> | ||
| 5 | |||
| 6 | #include "chrono_batch_render/device/commands.h" | ||
| 7 | #include "chrono_batch_render/device/queue.h" | ||
| 8 | #include "chrono_batch_render/scene/kernels.h" | ||
| 9 | #include "chrono_batch_render/scene/records/render_tables.h" | ||
| 10 | #include "chrono_batch_render/scene/schema/columns.h" | ||
| 11 | #include "chrono_batch_render/scene/tables/kernels/subsets.h" | ||
| 12 | #include "chrono_batch_render/stage/publish/change_set.h" | ||
| 13 | #include "chrono_batch_render/util/profiler.h" | ||
| 14 | #include "chrono_batch_render/util/span.h" | ||
| 15 | |||
| 16 | namespace chrono::batch_render { | ||
| 17 | |||
| 18 | namespace { | ||
| 19 | |||
| 20 | constexpr ColumnRef kInputs[] = {kGeomSubsetElementType, kGeomSubsetIndices, kGeomSubsetFamilyName, | ||
| 21 | kMaterialBindingAPIMaterialBinding, kMeshFaceVertexCounts}; | ||
| 22 | |||
| 23 | } | ||
| 24 | |||
| 25 | 2320 | std::span<const ColumnRef> SubsetTable::Inputs() const { | |
| 26 | 2320 | return kInputs; | |
| 27 | } | ||
| 28 | |||
| 29 | 466 | void SubsetTable::Describe(RenderTables& tables) const { | |
| 30 | 466 | tables.subsets.address = m_records.Buffer() ? m_records.Buffer()->Address() : 0; | |
| 31 | 466 | } | |
| 32 | |||
| 33 | 466 | void SubsetTable::Run(const ChangeSet& changes, TableVersions&) { | |
| 34 | 466 | CHRONO_BATCH_RENDER_ZONE("Subsets/Run"); | |
| 35 | 466 | m_stats.rebuilt = false; | |
| 36 | 466 | const Prims& prims = m_structure.GetPrims(); | |
| 37 | 466 | ColumnStore& store = m_structure.GetColumnStore(); | |
| 38 | 7568 | const auto isSubset = [&](uint32_t prim) { | |
| 39 | 7102 | return prims.Alive(prim) && prims.Record(prim).schema == SchemaId::GeomSubset; | |
| 40 | 466 | }; | |
| 41 | 466 | bool dirty = !m_built || changes.resync; | |
| 42 | 192 | if (dirty) { | |
| 43 | 274 | m_subsets.clear(); | |
| 44 | 2130 | for (uint32_t prim = 0; prim < prims.Size(); ++prim) { | |
| 45 | 1856 | if (isSubset(prim)) { | |
| 46 | 8 | m_subsets.insert(prim); | |
| 47 | } | ||
| 48 | } | ||
| 49 | } else { | ||
| 50 | 333 | for (const StructureEntry& entry : changes.removed) { | |
| 51 | 281 | dirty = m_subsets.erase(entry.prim) > 0 || dirty; | |
| 52 | } | ||
| 53 | 5438 | for (const StructureEntry& entry : changes.added) { | |
| 54 | 5246 | if (isSubset(entry.prim) && prims.Generation(entry.prim) == entry.generation) { | |
| 55 | ✗ | m_subsets.insert(entry.prim); | |
| 56 | dirty = true; | ||
| 57 | } | ||
| 58 | } | ||
| 59 | 204 | for (const StructureEntry& entry : changes.apis) { | |
| 60 | 24 | dirty = dirty || | |
| 61 | 12 | (entry.api == SchemaId::SemanticsLabelsAPI && m_subsets.contains(entry.prim)); | |
| 62 | } | ||
| 63 | } | ||
| 64 | 466 | dirty = dirty || (!m_subsets.empty() && std::ranges::any_of(kInputs, [&](ColumnRef column) { | |
| 65 | 9 | return store.LastWrite(column) > m_seen; | |
| 66 | })); | ||
| 67 | 466 | m_seen = store.WriteSequence(); | |
| 68 | 466 | m_built = true; | |
| 69 | 466 | if (dirty) { | |
| 70 | 278 | Rebuild(); | |
| 71 | } | ||
| 72 | 466 | m_records.Reserve(store.Schema(SchemaId::Mesh).Capacity()); | |
| 73 | 466 | m_records.Upload(store.GetDevice()); | |
| 74 | 466 | } | |
| 75 | |||
| 76 | 278 | void SubsetTable::Rebuild() { | |
| 77 | 278 | CHRONO_BATCH_RENDER_ZONE("Subsets/Rebuild"); | |
| 78 | 278 | m_stats = {.rebuilt = true}; | |
| 79 | 278 | const Prims& prims = m_structure.GetPrims(); | |
| 80 | 278 | const Paths& paths = m_structure.GetPaths(); | |
| 81 | 278 | ColumnStore& store = m_structure.GetColumnStore(); | |
| 82 | 278 | ArrayPool& ints = store.Arrays(ColumnType::IntArray); | |
| 83 | 284 | for (const auto& [meshRow, record] : m_written) { | |
| 84 | 3 | ints.Free(record.materials); | |
| 85 | 3 | ints.Free(record.parts); | |
| 86 | 3 | m_records.Set(meshRow, {}); | |
| 87 | } | ||
| 88 | 278 | m_written.clear(); | |
| 89 | 278 | if (m_subsets.empty()) { | |
| 90 | return; | ||
| 91 | } | ||
| 92 | |||
| 93 | 7 | ColumnTable& table = store.Schema(SchemaId::GeomSubset); | |
| 94 | 7 | const std::vector<uint32_t> elements = | |
| 95 | 14 | Items<uint32_t>(table.Read(kGeomSubsetElementType.index, 0, table.Rows())); | |
| 96 | 7 | const std::vector<uint32_t> families = | |
| 97 | 14 | Items<uint32_t>(table.Read(kGeomSubsetFamilyName.index, 0, table.Rows())); | |
| 98 | 7 | const uint32_t materialBind = m_structure.GetTokens().Intern("materialBind").id; | |
| 99 | 7 | std::set<uint32_t> meshes; | |
| 100 | 28 | for (const uint32_t subset : m_subsets) { | |
| 101 | 21 | const uint32_t parent = paths.Parent(subset); | |
| 102 | 42 | if (parent != kNoPrim && prims.Alive(parent) && | |
| 103 | 21 | IsA(prims.Record(parent).schema, SchemaId::Mesh)) { | |
| 104 | 21 | meshes.insert(parent); | |
| 105 | } | ||
| 106 | } | ||
| 107 | |||
| 108 | 7 | std::vector<SubsetJob> jobs; | |
| 109 | 7 | std::vector<SubsetOutput> outputs; | |
| 110 | 7 | uint32_t indexCount = 0; | |
| 111 | 7 | uint32_t entryCount = 0; | |
| 112 | 14 | for (const uint32_t mesh : meshes) { | |
| 113 | 7 | const uint32_t meshRow = prims.Row(mesh, SchemaId::Mesh); | |
| 114 | 7 | const uint32_t faces = store.Array(kMeshFaceVertexCounts, meshRow).count; | |
| 115 | 7 | if (faces == 0) { | |
| 116 | ✗ | continue; | |
| 117 | } | ||
| 118 | 7 | const std::vector<uint32_t> children = paths.Children(mesh); | |
| 119 | 7 | SubsetRecord record{}; | |
| 120 | 21 | for (const SubsetMap map : {SubsetMap::Materials, SubsetMap::Parts}) { | |
| 121 | 14 | const size_t firstJob = jobs.size(); | |
| 122 | 56 | for (const uint32_t child : children) { | |
| 123 | 42 | if (!m_subsets.contains(child)) { | |
| 124 | 28 | continue; | |
| 125 | } | ||
| 126 | 42 | const uint32_t row = prims.Row(child, SchemaId::GeomSubset); | |
| 127 | 42 | const ArrayRef indices = store.Array(kGeomSubsetIndices, row); | |
| 128 | 42 | const bool wanted = map == SubsetMap::Materials | |
| 129 | 42 | ? families[row] == materialBind | |
| 130 | 21 | : prims.HasApi(child, SchemaId::SemanticsLabelsAPI); | |
| 131 | 42 | if (!wanted || elements[row] != uint32_t(ElementType::Face) || indices.count == 0) { | |
| 132 | 28 | continue; | |
| 133 | } | ||
| 134 | 14 | SubsetJob job{}; | |
| 135 | 14 | job.indices.address = indices.address; | |
| 136 | 14 | job.subset = child; | |
| 137 | 14 | job.count = indices.count; | |
| 138 | 14 | job.firstIndex = indexCount; | |
| 139 | 14 | job.firstEntry = entryCount; | |
| 140 | 14 | job.faceCount = faces; | |
| 141 | 14 | job.map = uint32_t(map); | |
| 142 | 14 | jobs.push_back(job); | |
| 143 | 14 | indexCount += indices.count; | |
| 144 | 14 | ++m_stats.subsets; | |
| 145 | } | ||
| 146 | 14 | if (jobs.size() == firstJob) { | |
| 147 | 6 | continue; | |
| 148 | } | ||
| 149 | 8 | const ArrayRef values = ints.Allocate(faces); | |
| 150 | 8 | (map == SubsetMap::Materials ? record.materials : record.parts) = values; | |
| 151 | 8 | SubsetOutput output{}; | |
| 152 | 8 | output.values.address = values.address; | |
| 153 | 8 | output.firstEntry = entryCount; | |
| 154 | 8 | output.count = faces; | |
| 155 | 8 | outputs.push_back(output); | |
| 156 | 8 | entryCount += faces; | |
| 157 | } | ||
| 158 | 7 | if (record.materials.count > 0 || record.parts.count > 0) { | |
| 159 | 6 | m_written[meshRow] = record; | |
| 160 | 6 | m_records.Set(meshRow, record); | |
| 161 | 6 | ++m_stats.meshes; | |
| 162 | } | ||
| 163 | 7 | } | |
| 164 | 7 | if (jobs.empty()) { | |
| 165 | 1 | return; | |
| 166 | } | ||
| 167 | |||
| 168 | 6 | store.Flush(); | |
| 169 | 6 | device::Device& device = store.GetDevice(); | |
| 170 | 6 | const Ref<device::Buffer> jobBuffer = | |
| 171 | 6 | device.CreateBuffer({.label = "subset jobs"}, std::span<const SubsetJob>(jobs)); | |
| 172 | 6 | const Ref<device::Buffer> outputBuffer = | |
| 173 | 6 | device.CreateBuffer({.label = "subset outputs"}, std::span<const SubsetOutput>(outputs)); | |
| 174 | 6 | const Ref<device::Buffer> ranks = | |
| 175 | 6 | device.CreateBuffer({.bytes = uint64_t(entryCount) * 4, .label = "subset face ranks"}); | |
| 176 | 6 | SubsetParams params{}; | |
| 177 | 12 | params.scene.address = store.DeviceData(); | |
| 178 | 6 | params.jobs.address = jobBuffer->Address(); | |
| 179 | 6 | params.outputs.address = outputBuffer->Address(); | |
| 180 | 6 | params.ranks.address = ranks->Address(); | |
| 181 | 6 | params.jobCount = uint32_t(jobs.size()); | |
| 182 | 6 | params.outputCount = uint32_t(outputs.size()); | |
| 183 | 6 | params.indexCount = indexCount; | |
| 184 | 6 | params.entryCount = entryCount; | |
| 185 | 6 | static const profiler::ZoneId zone = profiler::ZoneId::Named("Subsets/Write"); | |
| 186 | 6 | device::Queue& queue = device.GetQueue(); | |
| 187 | 6 | device::CommandEncoder encoder = queue.CreateEncoder(); | |
| 188 | 6 | { | |
| 189 | 6 | const device::ZoneScope scope(encoder, zone); | |
| 190 | 6 | encoder.Fill(ranks->Slice(0), ~0u); | |
| 191 | 6 | encoder.Dispatch(Kernels().Entry("ClaimFaces"), params, | |
| 192 | 6 | {.x = device::GroupCount(indexCount, kSubsetGroup)}); | |
| 193 | 6 | encoder.Dispatch(Kernels().Entry("WriteFaces"), params, | |
| 194 | 6 | {.x = device::GroupCount(entryCount, kSubsetGroup)}); | |
| 195 | 6 | } | |
| 196 | 6 | queue.Submit(encoder.Finish()); | |
| 197 | 320 | } | |
| 198 | |||
| 199 | 353 | void SubsetTable::Prepare() { | |
| 200 | 353 | Kernels(); | |
| 201 | 353 | } | |
| 202 | |||
| 203 | 365 | device::Program& SubsetTable::Kernels() { | |
| 204 | 365 | if (!m_program) { | |
| 205 | 1059 | m_program = m_structure.GetColumnStore().GetDevice().CreateProgram( | |
| 206 | {.modules = {"chrono_batch_render.scene.tables.kernels.subsets"}, | ||
| 207 | .library = &kSceneKernels, | ||
| 208 | .entries = {"ClaimFaces", "WriteFaces"}, | ||
| 209 | 706 | .label = "subsets"}); | |
| 210 | } | ||
| 211 | 365 | return *m_program; | |
| 212 | 706 | } | |
| 213 | |||
| 214 | } | ||
| 215 |