scene/tables/textures.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // SPDX-License-Identifier: BSD-3-Clause | ||
| 2 | #include "chrono_batch_render/scene/tables/textures.h" | ||
| 3 | |||
| 4 | #include <algorithm> | ||
| 5 | #include <span> | ||
| 6 | #include <string> | ||
| 7 | |||
| 8 | #include "chrono_batch_render/io/image.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/stage/publish/change_set.h" | ||
| 12 | #include "chrono_batch_render/util/profiler.h" | ||
| 13 | |||
| 14 | namespace chrono::batch_render { | ||
| 15 | namespace { | ||
| 16 | |||
| 17 | 54 | TextureWrap Wrap(TextureWrapMode mode) { | |
| 18 | 54 | switch (mode) { | |
| 19 | case TextureWrapMode::Black: | ||
| 20 | return TextureWrap::Border; | ||
| 21 | ✗ | case TextureWrapMode::Clamp: | |
| 22 | ✗ | return TextureWrap::Clamp; | |
| 23 | ✗ | case TextureWrapMode::Mirror: | |
| 24 | ✗ | return TextureWrap::Mirror; | |
| 25 | 52 | default: | |
| 26 | 52 | return TextureWrap::Repeat; | |
| 27 | } | ||
| 28 | } | ||
| 29 | |||
| 30 | } | ||
| 31 | |||
| 32 | namespace { | ||
| 33 | |||
| 34 | 360 | std::map<SchemaId, std::vector<TextureSlot>> MaterialSlots() { | |
| 35 | 360 | std::map<SchemaId, std::vector<TextureSlot>> materials; | |
| 36 | 720 | materials[SchemaId::UsdUVTexture] = { | |
| 37 | 360 | {.table = SchemaId::UsdUVTexture, .texture = kUsdUVTextureFile.index, .color = true}}; | |
| 38 | 720 | materials[SchemaId::DomeLight] = { | |
| 39 | 360 | {.table = SchemaId::DomeLight, .texture = kDomeLightTextureFile.index, .color = true}}; | |
| 40 | 3240 | for (const SchemaId light : {SchemaId::SphereLight, SchemaId::RectLight, SchemaId::DiskLight, | |
| 41 | 1800 | SchemaId::CylinderLight}) { | |
| 42 | 1440 | materials[light] = {{.table = SchemaId::ShapingAPI, | |
| 43 | .texture = kShapingAPIShapingIesFile.index, | ||
| 44 | 1440 | .color = false}}; | |
| 45 | } | ||
| 46 | 360 | materials[SchemaId::RectLight].push_back( | |
| 47 | {.table = SchemaId::RectLight, .texture = kRectLightTextureFile.index, .color = true}); | ||
| 48 | 360 | return materials; | |
| 49 | ✗ | } | |
| 50 | |||
| 51 | 360 | std::vector<ColumnRef> InputColumns(const std::map<SchemaId, std::vector<TextureSlot>>& materials) { | |
| 52 | 360 | std::vector<ColumnRef> inputs = {kUsdUVTextureWrapS, kUsdUVTextureWrapT, | |
| 53 | 360 | kUsdUVTextureSourceColorSpace, kCheckerboardAPICheckerboard}; | |
| 54 | 2520 | for (const auto& [schema, slots] : materials) { | |
| 55 | 4680 | for (const TextureSlot& slot : slots) { | |
| 56 | 2520 | const ColumnRef column = {slot.table, &Columns(slot.table)[slot.texture], slot.texture}; | |
| 57 | 2520 | if (std::ranges::none_of(inputs, | |
| 58 | 14400 | [&](ColumnRef c) { return c.column == column.column; })) { | |
| 59 | 1440 | inputs.push_back(column); | |
| 60 | } | ||
| 61 | } | ||
| 62 | } | ||
| 63 | 360 | return inputs; | |
| 64 | ✗ | } | |
| 65 | |||
| 66 | } | ||
| 67 | |||
| 68 | 360 | TextureTable::TextureTable(Structure& structure, unsigned threads) | |
| 69 | 360 | : m_structure(structure), | |
| 70 | ✗ | m_cache( | |
| 71 | 360 | structure.GetColumnStore().GetDevice(), | |
| 72 | 382 | [&structure](const std::string& path) { return structure.FindFile(path); }, threads), | |
| 73 | 720 | m_slots(MaterialSlots()), m_inputs(InputColumns(m_slots)) { | |
| 74 | 360 | UploadBindings(); | |
| 75 | 360 | } | |
| 76 | |||
| 77 | 360 | TextureTable::~TextureTable() = default; | |
| 78 | |||
| 79 | 501 | void TextureTable::Describe(RenderTables& tables) const { | |
| 80 | 501 | tables.textures.address = m_records.Buffer() ? m_records.Buffer()->Address() : 0; | |
| 81 | 501 | tables.textureBindings.address = m_bindingAddress; | |
| 82 | 501 | } | |
| 83 | |||
| 84 | 946 | void TextureTable::Hold(std::vector<Ref<const RefCounted>>& held) const { | |
| 85 | 1892 | HoldBuffers(held, {m_records.Buffer()}); | |
| 86 | 1892 | } | |
| 87 | |||
| 88 | 8679 | bool TextureTable::Pending() const { | |
| 89 | 8679 | return m_cache.Pending(); | |
| 90 | } | ||
| 91 | |||
| 92 | 692 | void TextureTable::Finish() { | |
| 93 | 692 | m_cache.Finish(); | |
| 94 | 692 | } | |
| 95 | |||
| 96 | 31 | uint64_t TextureTable::Handle(uint32_t prim, std::string_view input) const { | |
| 97 | 31 | const auto found = m_materials.find(prim); | |
| 98 | 31 | if (found == m_materials.end()) { | |
| 99 | return 0; | ||
| 100 | } | ||
| 101 | 31 | for (const Bound& bound : found->second.slots) { | |
| 102 | 31 | if (Columns(bound.slot.table)[bound.slot.texture].field == input) { | |
| 103 | 31 | return bound.binding.handle; | |
| 104 | } | ||
| 105 | } | ||
| 106 | ✗ | return 0; | |
| 107 | } | ||
| 108 | |||
| 109 | 861 | void TextureTable::UploadBindings() { | |
| 110 | 861 | const uint64_t address = m_cache.UploadBindings(); | |
| 111 | 861 | m_bindingAddress = address; | |
| 112 | 861 | } | |
| 113 | |||
| 114 | 501 | std::set<uint32_t> TextureTable::Collect() { | |
| 115 | 501 | const std::set<TextureKey> finished = m_cache.Collect(); | |
| 116 | 501 | std::set<uint32_t> dirty; | |
| 117 | 527 | for (const TextureKey& key : finished) { | |
| 118 | 84 | for (const auto& [prim, material] : m_materials) { | |
| 119 | 29 | if (std::ranges::any_of(material.slots, | |
| 120 | 53 | [&](const Bound& bound) { return bound.binding.key == key; }) || | |
| 121 | 6 | (material.checkered && key.first == kCheckerboardTexture)) { | |
| 122 | 28 | dirty.insert(prim); | |
| 123 | } | ||
| 124 | } | ||
| 125 | } | ||
| 126 | 501 | return dirty; | |
| 127 | 501 | } | |
| 128 | |||
| 129 | 22 | void TextureTable::Refresh(uint32_t prim) { | |
| 130 | 22 | Material& material = m_materials.at(prim); | |
| 131 | 22 | bool resolved = false; | |
| 132 | 44 | for (Bound& bound : material.slots) { | |
| 133 | 22 | if (bound.binding.handle == 0) { | |
| 134 | 22 | m_cache.Resolve(bound.binding); | |
| 135 | 22 | resolved = resolved || bound.binding.handle != 0; | |
| 136 | } | ||
| 137 | } | ||
| 138 | 22 | if (!resolved) { | |
| 139 | return; | ||
| 140 | } | ||
| 141 | 18 | if (material.bindings == kNoTextures) { | |
| 142 | 18 | material.bindings = m_cache.AllocateBindings(uint32_t(material.slots.size())); | |
| 143 | } | ||
| 144 | 36 | for (uint32_t index = 0; index < material.slots.size(); ++index) { | |
| 145 | 18 | m_cache.SetBinding(material.bindings + index, material.slots[index].binding.handle); | |
| 146 | } | ||
| 147 | 18 | Publish(prim, material); | |
| 148 | } | ||
| 149 | |||
| 150 | 142 | void TextureTable::Publish(uint32_t prim, const Material& material) { | |
| 151 | 142 | const Prims& prims = m_structure.GetPrims(); | |
| 152 | 142 | const SchemaId schema = prims.Record(prim).schema; | |
| 153 | 142 | TextureRecord record{}; | |
| 154 | 142 | if (schema == SchemaId::UsdUVTexture || schema == SchemaId::DomeLight) { | |
| 155 | 60 | record.textures = material.bindings; | |
| 156 | } | ||
| 157 | 233 | for (uint32_t index = 0; index < material.slots.size(); ++index) { | |
| 158 | 91 | const SchemaId table = material.slots[index].slot.table; | |
| 159 | 182 | const uint32_t binding = | |
| 160 | 91 | material.bindings == kNoTextures ? kNoTextures : material.bindings + index; | |
| 161 | 91 | if (table == SchemaId::ShapingAPI && prims.HasApi(prim, SchemaId::ShapingAPI)) { | |
| 162 | 1 | record.shaping = binding; | |
| 163 | 90 | } else if (table == SchemaId::RectLight) { | |
| 164 | 3 | record.textures = binding; | |
| 165 | } | ||
| 166 | } | ||
| 167 | 142 | if (schema == SchemaId::DomeLight && material.bindings != kNoTextures) { | |
| 168 | 2 | if (const auto found = m_cache.Size(material.slots[0].binding.key)) { | |
| 169 | 2 | record.width = found->first; | |
| 170 | 2 | record.height = found->second; | |
| 171 | } | ||
| 172 | } | ||
| 173 | 142 | m_records.Set(prim, record); | |
| 174 | 142 | Rebound(prims.Record(prim).world); | |
| 175 | 142 | } | |
| 176 | |||
| 177 | 465 | void TextureTable::Rebound(uint32_t world) { | |
| 178 | 465 | if (std::ranges::find(m_rebound, world) == m_rebound.end()) { | |
| 179 | 352 | m_rebound.push_back(world); | |
| 180 | } | ||
| 181 | 465 | } | |
| 182 | |||
| 183 | 266 | void TextureTable::Release(uint32_t prim) { | |
| 184 | 266 | const auto found = m_materials.find(prim); | |
| 185 | 266 | if (found == m_materials.end()) { | |
| 186 | 254 | return; | |
| 187 | } | ||
| 188 | 12 | m_records.Set(prim, TextureRecord{}); | |
| 189 | 12 | const Prims& prims = m_structure.GetPrims(); | |
| 190 | 12 | if (prims.Alive(prim)) { | |
| 191 | 11 | Rebound(prims.Record(prim).world); | |
| 192 | } else { | ||
| 193 | 1 | Rebound(kGlobalWorld); | |
| 194 | } | ||
| 195 | 12 | const Material& material = found->second; | |
| 196 | 18 | for (const Bound& bound : material.slots) { | |
| 197 | 6 | m_cache.Unbind(bound.binding); | |
| 198 | } | ||
| 199 | 12 | if (material.bindings != kNoTextures) { | |
| 200 | 5 | m_cache.FreeBindings(material.bindings, uint32_t(material.slots.size())); | |
| 201 | } | ||
| 202 | 12 | if (material.checkered && --m_checkerUsers == 0) { | |
| 203 | 4 | m_cache.Unbind(m_checker); | |
| 204 | 4 | m_checker = {}; | |
| 205 | 4 | m_cache.SetBinding(kCheckerTextureBinding, 0); | |
| 206 | } | ||
| 207 | 24 | for (const RowKey& row : material.rows) { | |
| 208 | 12 | m_byRow.erase(row); | |
| 209 | } | ||
| 210 | 12 | m_materials.erase(found); | |
| 211 | 4 | } | |
| 212 | |||
| 213 | 181 | const std::vector<uint32_t>& TextureTable::Read(Cells& cells, SchemaId table, uint32_t column) { | |
| 214 | 181 | auto [found, added] = cells.columns.try_emplace({table, column}); | |
| 215 | 181 | if (added) { | |
| 216 | 169 | ColumnTable& rows = m_structure.GetColumnStore().Schema(table); | |
| 217 | 338 | found->second = Items<uint32_t>(rows.Read(column, 0, rows.Rows())); | |
| 218 | } | ||
| 219 | 181 | return found->second; | |
| 220 | } | ||
| 221 | |||
| 222 | 124 | void TextureTable::Rebind(uint32_t prim, Cells& cells) { | |
| 223 | 124 | const Prims& prims = m_structure.GetPrims(); | |
| 224 | 124 | const SchemaId schema = prims.Record(prim).schema; | |
| 225 | 124 | Material material; | |
| 226 | 124 | TextureWrap wrapS = TextureWrap::Clamp; | |
| 227 | 124 | TextureWrap wrapT = TextureWrap::Clamp; | |
| 228 | 124 | TextureColorSpace space = TextureColorSpace::Auto; | |
| 229 | 124 | if (schema == SchemaId::UsdUVTexture) { | |
| 230 | 27 | const uint32_t row = prims.Row(prim, SchemaId::UsdUVTexture); | |
| 231 | 54 | wrapS = Wrap( | |
| 232 | 27 | TextureWrapMode(Read(cells, SchemaId::UsdUVTexture, kUsdUVTextureWrapS.index)[row])); | |
| 233 | 54 | wrapT = Wrap( | |
| 234 | 27 | TextureWrapMode(Read(cells, SchemaId::UsdUVTexture, kUsdUVTextureWrapT.index)[row])); | |
| 235 | 27 | space = TextureColorSpace( | |
| 236 | 27 | Read(cells, SchemaId::UsdUVTexture, kUsdUVTextureSourceColorSpace.index)[row]); | |
| 237 | } | ||
| 238 | 124 | TextureBinding checker; | |
| 239 | 124 | if (IsA(schema, SchemaId::Plane)) { | |
| 240 | 54 | const uint32_t row = prims.Row(prim, SchemaId::CheckerboardAPI); | |
| 241 | 108 | material.checkered = | |
| 242 | 54 | Read(cells, SchemaId::CheckerboardAPI, kCheckerboardAPICheckerboard.index)[row] != 0; | |
| 243 | 54 | if (material.checkered) { | |
| 244 | 20 | checker = m_cache.Bind({std::string(kCheckerboardTexture), false}, TextureWrap::Repeat, | |
| 245 | 10 | TextureWrap::Repeat); | |
| 246 | } | ||
| 247 | 54 | material.rows.push_back({SchemaId::CheckerboardAPI, row}); | |
| 248 | } | ||
| 249 | 124 | const auto slots = m_slots.find(schema); | |
| 250 | 124 | if (slots != m_slots.end()) { | |
| 251 | 143 | for (const TextureSlot& slot : slots->second) { | |
| 252 | 73 | if (IsApi(slot.table) && !prims.HasApi(prim, slot.table)) { | |
| 253 | 27 | material.slots.push_back({slot, {}}); | |
| 254 | 27 | continue; | |
| 255 | } | ||
| 256 | 46 | const uint32_t row = prims.Row(prim, slot.table); | |
| 257 | 46 | std::string path( | |
| 258 | 46 | m_structure.GetTokens().Text(Token{Read(cells, slot.table, slot.texture)[row]})); | |
| 259 | 46 | const bool srgb = space == TextureColorSpace::SRGB || | |
| 260 | 67 | (space == TextureColorSpace::Auto && slot.color && !path.empty() && | |
| 261 | 46 | IsPngOrJpeg(path)); | |
| 262 | 46 | if (schema == SchemaId::DomeLight) { | |
| 263 | 15 | wrapS = TextureWrap::Repeat; | |
| 264 | 15 | wrapT = TextureWrap::Repeat; | |
| 265 | } | ||
| 266 | 46 | material.slots.push_back({slot, m_cache.Bind({std::move(path), srgb}, wrapS, wrapT)}); | |
| 267 | 46 | if (std::ranges::find(material.rows, RowKey{slot.table, row}) == material.rows.end()) { | |
| 268 | 46 | material.rows.push_back({slot.table, row}); | |
| 269 | } | ||
| 270 | 46 | } | |
| 271 | } | ||
| 272 | |||
| 273 | 124 | Release(prim); | |
| 274 | 124 | if (material.checkered) { | |
| 275 | 10 | if (m_checkerUsers++ == 0 || m_checker.handle == 0) { | |
| 276 | 9 | m_cache.Unbind(m_checker); | |
| 277 | 9 | m_checker = checker; | |
| 278 | 9 | m_cache.SetBinding(kCheckerTextureBinding, m_checker.handle); | |
| 279 | } else { | ||
| 280 | 1 | m_cache.Unbind(checker); | |
| 281 | } | ||
| 282 | } | ||
| 283 | 124 | if (std::ranges::any_of(material.slots, | |
| 284 | 73 | [](const Bound& bound) { return bound.binding.handle != 0; })) { | |
| 285 | 2 | material.bindings = m_cache.AllocateBindings(uint32_t(material.slots.size())); | |
| 286 | 4 | for (uint32_t index = 0; index < material.slots.size(); ++index) { | |
| 287 | 2 | m_cache.SetBinding(material.bindings + index, material.slots[index].binding.handle); | |
| 288 | } | ||
| 289 | } | ||
| 290 | 124 | Publish(prim, material); | |
| 291 | 224 | for (const RowKey& row : material.rows) { | |
| 292 | 100 | m_byRow[row] = prim; | |
| 293 | } | ||
| 294 | 124 | m_structure.GetColumnStore().StampWorld(prims.Record(prim).world); | |
| 295 | 124 | m_materials[prim] = std::move(material); | |
| 296 | 367 | } | |
| 297 | |||
| 298 | 501 | void TextureTable::Run(const ChangeSet& changes, TableVersions& versions) { | |
| 299 | 501 | CHRONO_BATCH_RENDER_ZONE("Textures/Run"); | |
| 300 | 501 | const Prims& prims = m_structure.GetPrims(); | |
| 301 | 7737 | const auto textured = [&](uint32_t prim) { | |
| 302 | 14318 | return prims.Alive(prim) && (m_slots.contains(prims.Record(prim).schema) || | |
| 303 | 7082 | IsA(prims.Record(prim).schema, SchemaId::Plane)); | |
| 304 | 501 | }; | |
| 305 | |||
| 306 | 501 | const std::set<uint32_t> loaded = Collect(); | |
| 307 | 501 | std::set<uint32_t> dirty; | |
| 308 | 501 | if (changes.resync) { | |
| 309 | 2146 | for (uint32_t prim = 0; prim < prims.Size(); ++prim) { | |
| 310 | 1865 | if (textured(prim)) { | |
| 311 | 109 | dirty.insert(prim); | |
| 312 | } | ||
| 313 | } | ||
| 314 | } else { | ||
| 315 | 362 | for (const StructureEntry& entry : changes.removed) { | |
| 316 | 142 | Release(entry.prim); | |
| 317 | 142 | dirty.erase(entry.prim); | |
| 318 | } | ||
| 319 | 5467 | for (const StructureEntry& entry : changes.added) { | |
| 320 | 5247 | if (textured(entry.prim) && prims.Generation(entry.prim) == entry.generation) { | |
| 321 | 7 | dirty.insert(entry.prim); | |
| 322 | } | ||
| 323 | } | ||
| 324 | 232 | for (const StructureEntry& entry : changes.apis) { | |
| 325 | 12 | if (entry.api == SchemaId::ShapingAPI && textured(entry.prim) && | |
| 326 | ✗ | prims.Generation(entry.prim) == entry.generation) { | |
| 327 | ✗ | dirty.insert(entry.prim); | |
| 328 | } | ||
| 329 | } | ||
| 330 | 1980 | for (const ColumnRef column : m_inputs) { | |
| 331 | 1760 | const ChangedRows* rows = changes.Rows(column); | |
| 332 | 1760 | if (rows == nullptr) { | |
| 333 | ✗ | continue; | |
| 334 | } | ||
| 335 | 1786 | for (const uint32_t row : rows->ToHost()) { | |
| 336 | 26 | if (const auto prim = m_byRow.find({column.schema, row}); prim != m_byRow.end()) { | |
| 337 | 6 | dirty.insert(prim->second); | |
| 338 | } | ||
| 339 | 1760 | } | |
| 340 | } | ||
| 341 | } | ||
| 342 | |||
| 343 | 529 | for (const uint32_t prim : loaded) { | |
| 344 | 28 | const auto material = m_materials.find(prim); | |
| 345 | 28 | if (dirty.contains(prim) || material == m_materials.end()) { | |
| 346 | 1 | continue; | |
| 347 | } | ||
| 348 | 27 | if (material->second.checkered) { | |
| 349 | 5 | dirty.insert(prim); | |
| 350 | } else { | ||
| 351 | 22 | Refresh(prim); | |
| 352 | } | ||
| 353 | } | ||
| 354 | |||
| 355 | 501 | Cells cells; | |
| 356 | 625 | for (const uint32_t prim : dirty) { | |
| 357 | 124 | if (textured(prim)) { | |
| 358 | 124 | Rebind(prim, cells); | |
| 359 | } else { | ||
| 360 | ✗ | Release(prim); | |
| 361 | } | ||
| 362 | } | ||
| 363 | 501 | UploadBindings(); | |
| 364 | 501 | m_records.Reserve(prims.Size()); | |
| 365 | 501 | if (m_records.Upload(m_structure.GetColumnStore().GetDevice())) { | |
| 366 | 311 | Rebound(kGlobalWorld); | |
| 367 | } | ||
| 368 | 501 | if (!m_rebound.empty()) { | |
| 369 | 340 | versions.Record(TableId::Textures, m_rebound); | |
| 370 | } | ||
| 371 | 501 | m_rebound.clear(); | |
| 372 | |||
| 373 | 501 | m_stats.images = m_cache.Images(); | |
| 374 | 501 | m_stats.loaded = m_cache.Loaded(); | |
| 375 | 501 | m_stats.failed = m_cache.Failed(); | |
| 376 | 501 | m_stats.loading = m_cache.Loading(); | |
| 377 | 501 | m_stats.bound = 0; | |
| 378 | 831 | for (const auto& [prim, material] : m_materials) { | |
| 379 | 278 | for (const Bound& bound : material.slots) { | |
| 380 | 201 | m_stats.bound += bound.binding.handle != 0 ? 1 : 0; | |
| 381 | } | ||
| 382 | } | ||
| 383 | 501 | } | |
| 384 | |||
| 385 | } | ||
| 386 |