scene/tables/materials.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // SPDX-License-Identifier: BSD-3-Clause | ||
| 2 | #include "chrono_batch_render/scene/tables/materials.h" | ||
| 3 | |||
| 4 | #include <set> | ||
| 5 | |||
| 6 | #include <algorithm> | ||
| 7 | #include <bit> | ||
| 8 | #include <cstddef> | ||
| 9 | #include <memory> | ||
| 10 | #include <string_view> | ||
| 11 | |||
| 12 | #include "chrono_batch_render/device/commands.h" | ||
| 13 | #include "chrono_batch_render/device/queue.h" | ||
| 14 | #include "chrono_batch_render/graph/graph.h" | ||
| 15 | #include "chrono_batch_render/material/codegen.h" | ||
| 16 | #include "chrono_batch_render/scene/kernels.h" | ||
| 17 | #include "chrono_batch_render/scene/records/render_tables.h" | ||
| 18 | #include "chrono_batch_render/scene/schema/columns.h" | ||
| 19 | #include "chrono_batch_render/scene/tables/connections.h" | ||
| 20 | #include "chrono_batch_render/scene/tables/kernels/materials.h" | ||
| 21 | #include "chrono_batch_render/scene/tables/textures.h" | ||
| 22 | #include "chrono_batch_render/stage/publish/change_set.h" | ||
| 23 | #include "chrono_batch_render/util/log.h" | ||
| 24 | #include "chrono_batch_render/util/profiler.h" | ||
| 25 | #include "chrono_batch_render/util/span.h" | ||
| 26 | |||
| 27 | namespace chrono::batch_render { | ||
| 28 | |||
| 29 | namespace { | ||
| 30 | |||
| 31 | 3374 | constexpr uint32_t Word(size_t offset) { | |
| 32 | 44 | return uint32_t(offset / sizeof(uint32_t)); | |
| 33 | } | ||
| 34 | |||
| 35 | constexpr std::string_view kPreviewInputNames[kPreviewInputs] = { | ||
| 36 | "diffuseColor", "emissiveColor", "specularColor", "metallic", "roughness", | ||
| 37 | "clearcoat", "clearcoatRoughness", "opacity", "occlusion", "normal", | ||
| 38 | "detailNormal", "detailAlbedo", "displacement", | ||
| 39 | }; | ||
| 40 | |||
| 41 | constexpr Float4 kItuMaterials[] = { | ||
| 42 | {1, 0, 0, 0}, | ||
| 43 | {5.24f, 0, 0.0462f, 0.7822f}, | ||
| 44 | {3.91f, 0, 0.0238f, 0.16f}, | ||
| 45 | {2.73f, 0, 0.0085f, 0.9395f}, | ||
| 46 | {1.99f, 0, 0.0047f, 1.0718f}, | ||
| 47 | {6.31f, 0, 0.0036f, 1.3394f}, | ||
| 48 | {1.48f, 0, 0.0011f, 1.0750f}, | ||
| 49 | {2.58f, 0, 0.0217f, 0.7800f}, | ||
| 50 | {2.71f, 0, 0.33f, 0}, | ||
| 51 | {7.074f, 0, 0.0055f, 0.9262f}, | ||
| 52 | {3.66f, 0, 0.0044f, 1.3515f}, | ||
| 53 | {1, 0, 1e7f, 0}, | ||
| 54 | {3, 0, 0.00015f, 2.52f}, | ||
| 55 | {15, -0.1f, 0.035f, 1.63f}, | ||
| 56 | {30, -0.4f, 0.15f, 1.30f}, | ||
| 57 | }; | ||
| 58 | |||
| 59 | constexpr float kVisibleFirstNm = 380; | ||
| 60 | constexpr float kVisibleLastNm = 780; | ||
| 61 | |||
| 62 | constexpr bool Spectral(PreviewInput input) { | ||
| 63 | return input == PreviewInput::DiffuseColor || input == PreviewInput::EmissiveColor || | ||
| 64 | input == PreviewInput::SpecularColor; | ||
| 65 | } | ||
| 66 | |||
| 67 | 7 | constexpr bool ColorInput(uint32_t input) { | |
| 68 | 7 | return input == uint32_t(PreviewInput::DiffuseColor) || | |
| 69 | 7 | input == uint32_t(PreviewInput::EmissiveColor) || | |
| 70 | 7 | input == uint32_t(PreviewInput::SpecularColor) || | |
| 71 | 7 | input == uint32_t(PreviewInput::Normal) || | |
| 72 | input == uint32_t(PreviewInput::DetailNormal) || | ||
| 73 | 7 | input == uint32_t(PreviewInput::DetailAlbedo); | |
| 74 | } | ||
| 75 | |||
| 76 | 14 | bool Channel(std::string_view output, bool color, TextureChannel& channel) { | |
| 77 | 14 | if (output == "rgb") { | |
| 78 | 13 | channel = color ? TextureChannel::Rgb : TextureChannel::R; | |
| 79 | 1 | } else if (output == "r") { | |
| 80 | 1 | channel = TextureChannel::R; | |
| 81 | ✗ | } else if (output == "g") { | |
| 82 | ✗ | channel = TextureChannel::G; | |
| 83 | ✗ | } else if (output == "b") { | |
| 84 | ✗ | channel = TextureChannel::B; | |
| 85 | ✗ | } else if (output == "a") { | |
| 86 | ✗ | channel = TextureChannel::A; | |
| 87 | } else { | ||
| 88 | return false; | ||
| 89 | } | ||
| 90 | return true; | ||
| 91 | } | ||
| 92 | |||
| 93 | constexpr SchemaId kBuiltinSurfaces[] = {SchemaId::HapkeSurface, SchemaId::LommelSeeligerSurface, | ||
| 94 | SchemaId::LunarLambertSurface, SchemaId::OrenNayarSurface}; | ||
| 95 | |||
| 96 | constexpr std::string_view kBuiltinSurfaceModule = | ||
| 97 | "chrono_batch_render.render.kernels.material.surfaces.planetary"; | ||
| 98 | |||
| 99 | 33 | std::shared_ptr<const material::MaterialNodeDefinition> BuiltinSurface(SchemaId schema) { | |
| 100 | 2 | static const std::vector<std::shared_ptr<const material::MaterialNodeDefinition>> nodes = [] { | |
| 101 | 2 | std::vector<std::shared_ptr<const material::MaterialNodeDefinition>> built; | |
| 102 | 10 | for (const SchemaId id : kBuiltinSurfaces) { | |
| 103 | 8 | material::MaterialNodeDefinition node{.name = std::string(Info(id).name), | |
| 104 | .role = material::MaterialNodeRole::Surface, | ||
| 105 | .schema = uint32_t(id), | ||
| 106 | 16 | .module = std::string(kBuiltinSurfaceModule)}; | |
| 107 | 38 | for (const ColumnInfo& column : Columns(id)) { | |
| 108 | 30 | const bool color = column.type == ColumnType::Float3; | |
| 109 | 30 | if (color && column.field == "albedo") { | |
| 110 | 8 | node.detailAlbedo = uint32_t(node.inputs.size()); | |
| 111 | } | ||
| 112 | 60 | node.inputs.push_back({.name = std::string(column.field), | |
| 113 | 30 | .type = color ? material::MaterialInputType::Spectral | |
| 114 | : material::MaterialInputType::Float}); | ||
| 115 | } | ||
| 116 | 8 | node.detail = true; | |
| 117 | 16 | built.push_back( | |
| 118 | 8 | std::make_shared<const material::MaterialNodeDefinition>(std::move(node))); | |
| 119 | 8 | } | |
| 120 | 2 | return built; | |
| 121 | 123 | }(); | |
| 122 | 33 | const auto found = std::ranges::find(kBuiltinSurfaces, schema); | |
| 123 | 33 | return found == std::end(kBuiltinSurfaces) | |
| 124 | 33 | ? nullptr | |
| 125 | 48 | : nodes[size_t(found - std::begin(kBuiltinSurfaces))]; | |
| 126 | } | ||
| 127 | |||
| 128 | 275 | std::vector<ColumnRef> MaterialInputs() { | |
| 129 | 275 | std::vector<ColumnRef> inputs = {kPlaneAxis, kDetectorAPIDetectorSpectralRange, | |
| 130 | 275 | kThermalAPIThermalTemperature}; | |
| 131 | 3575 | for (const SchemaId schema : | |
| 132 | {SchemaId::Connectable, SchemaId::UsdPreviewSurface, SchemaId::SurfaceDetailAPI, | ||
| 133 | SchemaId::UsdUVTexture, SchemaId::UsdPrimvarReader_float2, SchemaId::UsdTransform2d, | ||
| 134 | SchemaId::TexturePlacement, SchemaId::ItuMaterial, SchemaId::CheckerboardAPI, | ||
| 135 | SchemaId::HapkeSurface, SchemaId::LommelSeeligerSurface, SchemaId::LunarLambertSurface, | ||
| 136 | 3850 | SchemaId::OrenNayarSurface}) { | |
| 137 | 3575 | const Span<const ColumnInfo> columns = Columns(schema); | |
| 138 | 25025 | for (uint32_t index = 0; index < columns.size(); ++index) { | |
| 139 | 21450 | inputs.push_back({schema, &columns[index], index}); | |
| 140 | } | ||
| 141 | } | ||
| 142 | 275 | return inputs; | |
| 143 | ✗ | } | |
| 144 | |||
| 145 | } | ||
| 146 | |||
| 147 | 536 | bool MaterialTable::Restructured(const ChangeSet& changes) const { | |
| 148 | 536 | if (changes.resync) { | |
| 149 | return true; | ||
| 150 | } | ||
| 151 | 720 | for (const ColumnRef column : {kConnectableConnectionsSources, kConnectableConnectionsInputs, | |
| 152 | 982 | kConnectableConnectionsOutputs}) { | |
| 153 | 742 | if (!changes.ArrayRows(column).empty()) { | |
| 154 | 22 | return true; | |
| 155 | } | ||
| 156 | } | ||
| 157 | 240 | const Prims& prims = m_structure.GetPrims(); | |
| 158 | 5365 | const auto touched = [&](uint32_t prim) { return prim < m_touched.size() && m_touched[prim]; }; | |
| 159 | 5457 | for (const StructureEntry& entry : changes.added) { | |
| 160 | 10434 | if (touched(entry.prim) || (prims.Alive(entry.prim) && | |
| 161 | 5203 | IsA(prims.Record(entry.prim).schema, SchemaId::Connectable))) { | |
| 162 | 14 | return true; | |
| 163 | } | ||
| 164 | } | ||
| 165 | 348 | for (const StructureEntry& entry : changes.removed) { | |
| 166 | 122 | if (touched(entry.prim)) { | |
| 167 | ✗ | return true; | |
| 168 | } | ||
| 169 | } | ||
| 170 | 238 | for (const StructureEntry& entry : changes.apis) { | |
| 171 | 12 | if (touched(entry.prim) || entry.api == SchemaId::SurfaceDetailAPI) { | |
| 172 | ✗ | return true; | |
| 173 | } | ||
| 174 | } | ||
| 175 | 226 | return false; | |
| 176 | } | ||
| 177 | |||
| 178 | 435 | bool MaterialTable::Changed(SchemaId schema) const { | |
| 179 | 435 | const ColumnStore& store = m_structure.GetColumnStore(); | |
| 180 | 435 | return std::ranges::any_of(Inputs(), [&](ColumnRef column) { | |
| 181 | 36014 | return column.schema == schema && store.LastWrite(column) > m_writes; | |
| 182 | 435 | }); | |
| 183 | } | ||
| 184 | |||
| 185 | 2825 | std::span<const ColumnRef> MaterialTable::Inputs() const { | |
| 186 | 2825 | if (m_inputSchemas != SchemaCount()) { | |
| 187 | 275 | m_inputs = MaterialInputs(); | |
| 188 | 1097 | for (uint32_t index = uint32_t(SchemaId::Count); index < SchemaCount(); ++index) { | |
| 189 | 822 | const SchemaId schema = SchemaId(index); | |
| 190 | 822 | const Span<const ColumnInfo> columns = Columns(schema); | |
| 191 | 2170 | for (uint32_t column = 0; column < columns.size(); ++column) { | |
| 192 | 1348 | m_inputs.push_back({schema, &columns[column], column}); | |
| 193 | } | ||
| 194 | } | ||
| 195 | 275 | m_inputSchemas = SchemaCount(); | |
| 196 | } | ||
| 197 | 2825 | return m_inputs; | |
| 198 | } | ||
| 199 | |||
| 200 | 536 | void MaterialTable::Describe(RenderTables& tables) const { | |
| 201 | 536 | tables.materials.address = m_records.Buffer() ? m_records.Buffer()->Address() : 0; | |
| 202 | 536 | tables.materialWords.address = m_words.Address(); | |
| 203 | 536 | tables.checkers.address = m_checkers.Address(); | |
| 204 | 536 | tables.temperatures.address = m_temperatures.Address(); | |
| 205 | 536 | tables.radios.address = m_radios.Buffer() ? m_radios.Buffer()->Address() : 0; | |
| 206 | 536 | tables.temperaturePrimvar = m_temperaturePrimvar; | |
| 207 | 536 | } | |
| 208 | |||
| 209 | 291 | std::span<const MaterialTable::Connection> MaterialTable::Connections(uint32_t prim) { | |
| 210 | 291 | const std::span<const Connection> connections = m_connections.Get(prim); | |
| 211 | 291 | if (prim < m_touched.size()) { | |
| 212 | 291 | m_touched[prim] = true; | |
| 213 | } | ||
| 214 | 498 | for (const Connection& connection : connections) { | |
| 215 | 207 | if (connection.source < m_touched.size()) { | |
| 216 | 205 | m_touched[connection.source] = true; | |
| 217 | } | ||
| 218 | } | ||
| 219 | 291 | return connections; | |
| 220 | } | ||
| 221 | |||
| 222 | 1392 | void MaterialTable::CopyCell(ColumnRef column, uint32_t prim, uint32_t destination) { | |
| 223 | 1392 | m_pack.Copy(column, m_structure.GetPrims().Row(prim, column.schema), destination); | |
| 224 | 1392 | } | |
| 225 | |||
| 226 | 69 | void MaterialTable::ResolvePlacement(uint32_t placement, uint32_t mapping) { | |
| 227 | 69 | const Prims& prims = m_structure.GetPrims(); | |
| 228 | 962 | const auto at = [&](size_t offset) { return mapping + Word(offset); }; | |
| 229 | 879 | const auto literal = [&](size_t offset, uint32_t value) { m_pack.Literal(at(offset), value); }; | |
| 230 | 676 | const auto real = [&](size_t offset, float value) { | |
| 231 | 607 | literal(offset, std::bit_cast<uint32_t>(value)); | |
| 232 | 67 | }; | |
| 233 | 69 | const SchemaId schema = placement == kNoPrim ? SchemaId::None : prims.Record(placement).schema; | |
| 234 | 2 | if (schema == SchemaId::TexturePlacement) { | |
| 235 | 1 | CopyCell(kTexturePlacementProjection, placement, at(offsetof(MappingRecord, projection))); | |
| 236 | 1 | CopyCell(kTexturePlacementProjectionSpace, placement, | |
| 237 | at(offsetof(MappingRecord, projectionSpace))); | ||
| 238 | 1 | CopyCell(kTexturePlacementUvPrimvar, placement, at(offsetof(MappingRecord, uvPrimvar))); | |
| 239 | 1 | CopyCell(kTexturePlacementTileSize, placement, at(offsetof(MappingRecord, tileSize))); | |
| 240 | 1 | CopyCell(kTexturePlacementTriplanarBlend, placement, | |
| 241 | at(offsetof(MappingRecord, triplanarBlend))); | ||
| 242 | 1 | CopyCell(kTexturePlacementUvScale, placement, at(offsetof(MappingRecord, uvScale))); | |
| 243 | 1 | CopyCell(kTexturePlacementUvOffset, placement, at(offsetof(MappingRecord, uvOffset))); | |
| 244 | 1 | CopyCell(kTexturePlacementUvRotation, placement, at(offsetof(MappingRecord, uvRotation))); | |
| 245 | 1 | CopyCell(kTexturePlacementHexTiling, placement, at(offsetof(MappingRecord, hexTiling))); | |
| 246 | 1 | CopyCell(kTexturePlacementHexTilingContrast, placement, | |
| 247 | at(offsetof(MappingRecord, hexTilingContrast))); | ||
| 248 | 1 | CopyCell(kTexturePlacementHexTilingRotation, placement, | |
| 249 | at(offsetof(MappingRecord, hexTilingRotation))); | ||
| 250 | 1 | return; | |
| 251 | } | ||
| 252 | 68 | literal(offsetof(MappingRecord, projection), 0); | |
| 253 | 68 | literal(offsetof(MappingRecord, projectionSpace), 0); | |
| 254 | 68 | real(offsetof(MappingRecord, tileSize), 1); | |
| 255 | 68 | real(offsetof(MappingRecord, triplanarBlend), 0.25f); | |
| 256 | 68 | literal(offsetof(MappingRecord, hexTiling), 0); | |
| 257 | 68 | real(offsetof(MappingRecord, hexTilingContrast), 0.5f); | |
| 258 | 68 | real(offsetof(MappingRecord, hexTilingRotation), 180); | |
| 259 | 68 | uint32_t reader = placement; | |
| 260 | 68 | if (schema == SchemaId::UsdTransform2d) { | |
| 261 | 1 | CopyCell(kUsdTransform2dScale, placement, at(offsetof(MappingRecord, uvScale))); | |
| 262 | 1 | CopyCell(kUsdTransform2dTranslation, placement, at(offsetof(MappingRecord, uvOffset))); | |
| 263 | 1 | CopyCell(kUsdTransform2dRotation, placement, at(offsetof(MappingRecord, uvRotation))); | |
| 264 | 1 | reader = kNoPrim; | |
| 265 | 1 | for (const Connection& connection : Connections(placement)) { | |
| 266 | ✗ | if (connection.input == "in") { | |
| 267 | ✗ | reader = connection.source; | |
| 268 | } | ||
| 269 | } | ||
| 270 | } else { | ||
| 271 | 67 | real(offsetof(MappingRecord, uvScale), 1); | |
| 272 | 67 | real(offsetof(MappingRecord, uvScale) + sizeof(float), 1); | |
| 273 | 67 | real(offsetof(MappingRecord, uvOffset), 0); | |
| 274 | 67 | real(offsetof(MappingRecord, uvOffset) + sizeof(float), 0); | |
| 275 | 67 | real(offsetof(MappingRecord, uvRotation), 0); | |
| 276 | } | ||
| 277 | 68 | if (reader != kNoPrim && prims.Record(reader).schema == SchemaId::UsdPrimvarReader_float2) { | |
| 278 | ✗ | CopyCell(kUsdPrimvarReader_float2Varname, reader, at(offsetof(MappingRecord, uvPrimvar))); | |
| 279 | } else { | ||
| 280 | 68 | if (reader != kNoPrim) { | |
| 281 | ✗ | Log(LogLevel::Warning, | |
| 282 | "{} reads its coordinates from {}, which isn't a UsdPrimvarReader_float2; it " | ||
| 283 | "reads the st primvar instead", | ||
| 284 | ✗ | m_structure.GetPaths().Path(placement), m_structure.GetPaths().Path(reader)); | |
| 285 | } | ||
| 286 | 68 | literal(offsetof(MappingRecord, uvPrimvar), m_structure.GetTokens().Intern("st").id); | |
| 287 | } | ||
| 288 | } | ||
| 289 | |||
| 290 | 52 | void MaterialTable::ResolvePreview(uint32_t material, uint32_t shader) { | |
| 291 | 52 | const Prims& prims = m_structure.GetPrims(); | |
| 292 | 52 | const Paths& paths = m_structure.GetPaths(); | |
| 293 | 52 | const uint32_t base = m_wordCount; | |
| 294 | 52 | m_wordCount += Word(sizeof(PreviewParams)); | |
| 295 | 3165 | const auto at = [&](size_t offset) { return base + Word(offset); }; | |
| 296 | 52 | CopyCell(kUsdPreviewSurfaceDiffuseColor, shader, at(offsetof(PreviewParams, diffuseColor))); | |
| 297 | 52 | CopyCell(kUsdPreviewSurfaceUseSpecularWorkflow, shader, | |
| 298 | at(offsetof(PreviewParams, useSpecularWorkflow))); | ||
| 299 | 52 | CopyCell(kUsdPreviewSurfaceEmissiveColor, shader, at(offsetof(PreviewParams, emissiveColor))); | |
| 300 | 52 | CopyCell(kUsdPreviewSurfaceMetallic, shader, at(offsetof(PreviewParams, metallic))); | |
| 301 | 52 | CopyCell(kUsdPreviewSurfaceSpecularColor, shader, at(offsetof(PreviewParams, specularColor))); | |
| 302 | 52 | CopyCell(kUsdPreviewSurfaceRoughness, shader, at(offsetof(PreviewParams, roughness))); | |
| 303 | 52 | CopyCell(kUsdPreviewSurfaceIor, shader, at(offsetof(PreviewParams, ior))); | |
| 304 | 52 | CopyCell(kUsdPreviewSurfaceClearcoat, shader, at(offsetof(PreviewParams, clearcoat))); | |
| 305 | 52 | CopyCell(kUsdPreviewSurfaceClearcoatRoughness, shader, | |
| 306 | at(offsetof(PreviewParams, clearcoatRoughness))); | ||
| 307 | 52 | CopyCell(kUsdPreviewSurfaceOpacity, shader, at(offsetof(PreviewParams, opacity))); | |
| 308 | 52 | const size_t detail = offsetof(PreviewParams, detail); | |
| 309 | 52 | CopyCell(kSurfaceDetailAPINormal, shader, at(detail + offsetof(DetailRecord, normal))); | |
| 310 | 52 | CopyCell(kSurfaceDetailAPINormalStrength, shader, | |
| 311 | at(detail + offsetof(DetailRecord, normalStrength))); | ||
| 312 | 52 | CopyCell(kSurfaceDetailAPIDetailNormal, shader, | |
| 313 | at(detail + offsetof(DetailRecord, detailNormal))); | ||
| 314 | 52 | CopyCell(kSurfaceDetailAPIDetailNormalStrength, shader, | |
| 315 | at(detail + offsetof(DetailRecord, detailNormalStrength))); | ||
| 316 | 52 | CopyCell(kSurfaceDetailAPIDetailAlbedo, shader, | |
| 317 | at(detail + offsetof(DetailRecord, detailAlbedo))); | ||
| 318 | 52 | CopyCell(kSurfaceDetailAPIDetailAlbedoStrength, shader, | |
| 319 | at(detail + offsetof(DetailRecord, detailAlbedoStrength))); | ||
| 320 | 52 | CopyCell(kSurfaceDetailAPIDetailScale, shader, | |
| 321 | at(detail + offsetof(DetailRecord, detailScale))); | ||
| 322 | 52 | CopyCell(kSurfaceDetailAPIDetailOffset, shader, | |
| 323 | at(detail + offsetof(DetailRecord, detailOffset))); | ||
| 324 | 52 | CopyCell(kSurfaceDetailAPIDetailRotation, shader, | |
| 325 | at(detail + offsetof(DetailRecord, detailRotation))); | ||
| 326 | 52 | CopyCell(kSurfaceDetailAPIDetailHexTiling, shader, | |
| 327 | at(detail + offsetof(DetailRecord, detailHexTiling))); | ||
| 328 | 52 | CopyCell(kSurfaceDetailAPIEnableDetail, shader, | |
| 329 | at(detail + offsetof(DetailRecord, enableDetail))); | ||
| 330 | |||
| 331 | 52 | const std::span<const Connection> connections = Connections(shader); | |
| 332 | 52 | uint32_t placement = kNoPrim; | |
| 333 | 52 | bool placed = false; | |
| 334 | 52 | uint32_t textured = 0; | |
| 335 | 728 | for (uint32_t input = 0; input < kPreviewInputs; ++input) { | |
| 336 | 676 | const size_t slot = offsetof(PreviewParams, inputs) + input * sizeof(TextureInput); | |
| 337 | 1345 | const auto unbound = [&] { | |
| 338 | 669 | m_pack.Literal(at(slot + offsetof(TextureInput, handle)), 0); | |
| 339 | 669 | m_pack.Literal(at(slot + offsetof(TextureInput, handle)) + 1, 0); | |
| 340 | 1345 | }; | |
| 341 | 676 | const auto found = | |
| 342 | 676 | std::ranges::find(connections, kPreviewInputNames[input], &Connection::input); | |
| 343 | 676 | const bool spectral = found != connections.end() && found->source != kNoPrim && | |
| 344 | 12 | prims.Record(found->source).schema == SchemaId::SampledSpectrum; | |
| 345 | 676 | uint32_t spectrum = kNoSpectrum; | |
| 346 | 676 | if (spectral && Spectral(PreviewInput(input))) { | |
| 347 | 5 | spectrum = prims.Row(found->source, SchemaId::SampledSpectrum); | |
| 348 | 5 | m_spectral.insert(material); | |
| 349 | 671 | } else if (spectral) { | |
| 350 | ✗ | Log(LogLevel::Warning, | |
| 351 | "{}'s {} connects to the spectrum {}, but only diffuseColor, emissiveColor and " | ||
| 352 | "specularColor take spectra; the input keeps its value", | ||
| 353 | ✗ | m_structure.GetPaths().Path(shader), kPreviewInputNames[input], | |
| 354 | ✗ | m_structure.GetPaths().Path(found->source)); | |
| 355 | } | ||
| 356 | 676 | m_pack.Literal(at(slot + offsetof(TextureInput, spectrum)), spectrum); | |
| 357 | 676 | if (found == connections.end() || spectral) { | |
| 358 | 669 | unbound(); | |
| 359 | 669 | continue; | |
| 360 | } | ||
| 361 | 7 | const uint32_t texture = found->source; | |
| 362 | 7 | TextureChannel channel = TextureChannel::Rgb; | |
| 363 | 7 | if (!BindTexture(shader, *found, kPreviewInputNames[input], ColorInput(input), at(slot), | |
| 364 | channel)) { | ||
| 365 | ✗ | unbound(); | |
| 366 | ✗ | continue; | |
| 367 | } | ||
| 368 | 7 | ++textured; | |
| 369 | 7 | if (input == uint32_t(PreviewInput::DetailNormal) || | |
| 370 | input == uint32_t(PreviewInput::DetailAlbedo)) { | ||
| 371 | ✗ | continue; | |
| 372 | } | ||
| 373 | 7 | uint32_t coordinates = kNoPrim; | |
| 374 | 8 | for (const Connection& connection : Connections(texture)) { | |
| 375 | 1 | if (connection.input == "st") { | |
| 376 | 1 | coordinates = connection.source; | |
| 377 | } | ||
| 378 | } | ||
| 379 | 7 | if (!placed) { | |
| 380 | placement = coordinates; | ||
| 381 | placed = true; | ||
| 382 | ✗ | } else if (coordinates != placement) { | |
| 383 | ✗ | Log(LogLevel::Warning, | |
| 384 | "{}'s textures read coordinates from more than one source; {} reads them through " | ||
| 385 | "the placement of its other textures", | ||
| 386 | ✗ | paths.Path(shader), paths.Path(texture)); | |
| 387 | } | ||
| 388 | } | ||
| 389 | 52 | ResolvePlacement(placement, at(offsetof(PreviewParams, mapping))); | |
| 390 | 97 | Define(material, textured > 0 ? kDefinitionPreviewTextured : kDefinitionPreviewConstant, base); | |
| 391 | 52 | if (GetLogLevel() <= LogLevel::Debug) { | |
| 392 | ✗ | Log(LogLevel::Debug, "{} shades with {} through {}, with {} textures in {} words", | |
| 393 | ✗ | paths.Path(material), | |
| 394 | ✗ | textured > 0 ? "a textured UsdPreviewSurface" : "UsdPreviewSurface", paths.Path(shader), | |
| 395 | ✗ | textured, Word(sizeof(PreviewParams))); | |
| 396 | } | ||
| 397 | 52 | } | |
| 398 | |||
| 399 | 15 | bool MaterialTable::ResolvePlanetary(uint32_t material, uint32_t shader) { | |
| 400 | 15 | const Prims& prims = m_structure.GetPrims(); | |
| 401 | 15 | const Paths& paths = m_structure.GetPaths(); | |
| 402 | 15 | const SchemaId schema = prims.Record(shader).schema; | |
| 403 | 15 | const std::span<const Connection> connections = Connections(shader); | |
| 404 | 21 | for (const Connection& connection : connections) { | |
| 405 | 6 | const SchemaId source = | |
| 406 | 6 | connection.source == kNoPrim ? SchemaId::None : prims.Record(connection.source).schema; | |
| 407 | 6 | if (IsRuntime(source)) { | |
| 408 | ✗ | if (GetLogLevel() <= LogLevel::Debug) { | |
| 409 | ✗ | Log(LogLevel::Debug, | |
| 410 | "{} reads the Slang node {}, so it shades through a generated definition", | ||
| 411 | ✗ | paths.Path(shader), paths.Path(connection.source)); | |
| 412 | } | ||
| 413 | ✗ | return false; | |
| 414 | } | ||
| 415 | } | ||
| 416 | 15 | uint32_t definition = kDefinitionHapke; | |
| 417 | 15 | if (schema == SchemaId::LommelSeeligerSurface) { | |
| 418 | definition = kDefinitionLommelSeeliger; | ||
| 419 | 12 | } else if (schema == SchemaId::LunarLambertSurface) { | |
| 420 | definition = kDefinitionLunarLambert; | ||
| 421 | 10 | } else if (schema == SchemaId::OrenNayarSurface) { | |
| 422 | 3 | definition = kDefinitionOrenNayar; | |
| 423 | } | ||
| 424 | 15 | m_wordCount = (m_wordCount + 1) & ~1u; | |
| 425 | 15 | const uint32_t base = m_wordCount; | |
| 426 | 15 | m_wordCount += Word(sizeof(PlanetaryParams)); | |
| 427 | 477 | const auto at = [&](size_t offset) { return base + Word(offset); }; | |
| 428 | 15 | const Span<const ColumnInfo> columns = Columns(schema); | |
| 429 | 51 | for (uint32_t i = std::max<uint32_t>(uint32_t(columns.size()), 1) - 1; i < kPlanetaryValues; | |
| 430 | ++i) { | ||
| 431 | 36 | m_pack.Literal(at(offsetof(PlanetaryParams, values) + i * sizeof(float)), 0); | |
| 432 | } | ||
| 433 | 15 | uint32_t textured = 0; | |
| 434 | 15 | uint32_t coordinates = kNoPrim; | |
| 435 | 84 | for (uint32_t i = 0; i < columns.size() && i < kPlanetaryInputs; ++i) { | |
| 436 | 69 | const ColumnInfo& info = columns[i]; | |
| 437 | 69 | const bool color = i == 0; | |
| 438 | 138 | CopyCell({schema, &info, i}, shader, | |
| 439 | 15 | color ? at(offsetof(PlanetaryParams, albedo)) | |
| 440 | 54 | : at(offsetof(PlanetaryParams, values) + (i - 1) * sizeof(float))); | |
| 441 | 69 | const size_t slot = offsetof(PlanetaryParams, inputs) + i * sizeof(TextureInput); | |
| 442 | 135 | const auto unbound = [&] { | |
| 443 | 66 | m_pack.Literal(at(slot + offsetof(TextureInput, handle)), 0); | |
| 444 | 66 | m_pack.Literal(at(slot + offsetof(TextureInput, handle)) + 1, 0); | |
| 445 | 135 | }; | |
| 446 | 69 | const auto found = std::ranges::find(connections, info.field, &Connection::input); | |
| 447 | 69 | const bool spectral = found != connections.end() && found->source != kNoPrim && | |
| 448 | 3 | prims.Record(found->source).schema == SchemaId::SampledSpectrum; | |
| 449 | 69 | uint32_t spectrum = kNoSpectrum; | |
| 450 | 69 | if (spectral && color) { | |
| 451 | ✗ | spectrum = prims.Row(found->source, SchemaId::SampledSpectrum); | |
| 452 | ✗ | m_spectral.insert(material); | |
| 453 | 69 | } else if (spectral) { | |
| 454 | ✗ | Log(LogLevel::Warning, | |
| 455 | "{}'s {} connects to the spectrum {}, but only its albedo takes spectra; the " | ||
| 456 | "input keeps its value", | ||
| 457 | ✗ | paths.Path(shader), info.field, paths.Path(found->source)); | |
| 458 | } | ||
| 459 | 69 | m_pack.Literal(at(slot + offsetof(TextureInput, spectrum)), spectrum); | |
| 460 | 69 | TextureChannel channel = TextureChannel::Rgb; | |
| 461 | 72 | if (found == connections.end() || spectral || | |
| 462 | 3 | !BindTexture(shader, *found, info.field, color, at(slot), channel)) { | |
| 463 | 66 | unbound(); | |
| 464 | 66 | continue; | |
| 465 | } | ||
| 466 | 3 | ++textured; | |
| 467 | 4 | for (const Connection& read : Connections(found->source)) { | |
| 468 | 1 | if (read.input == "st" && coordinates == kNoPrim) { | |
| 469 | 1 | coordinates = read.source; | |
| 470 | } | ||
| 471 | } | ||
| 472 | } | ||
| 473 | 51 | for (uint32_t i = uint32_t(columns.size()); i < kPlanetaryInputs; ++i) { | |
| 474 | 36 | const size_t slot = offsetof(PlanetaryParams, inputs) + i * sizeof(TextureInput); | |
| 475 | 36 | m_pack.Literal(at(slot + offsetof(TextureInput, handle)), 0); | |
| 476 | 36 | m_pack.Literal(at(slot + offsetof(TextureInput, handle)) + 1, 0); | |
| 477 | 36 | m_pack.Literal(at(slot + offsetof(TextureInput, spectrum)), kNoSpectrum); | |
| 478 | } | ||
| 479 | 15 | m_pack.Literal(at(offsetof(PlanetaryParams, textured)), textured); | |
| 480 | 15 | const uint32_t detailed = PackDetail(shader, at(offsetof(PlanetaryParams, detail)), | |
| 481 | at(offsetof(PlanetaryParams, details))); | ||
| 482 | 16 | ResolvePlacement(coordinates != kNoPrim ? coordinates : detailed, | |
| 483 | at(offsetof(PlanetaryParams, mapping))); | ||
| 484 | 15 | Define(material, definition, base); | |
| 485 | 15 | if (GetLogLevel() <= LogLevel::Debug) { | |
| 486 | 5 | Log(LogLevel::Debug, "{} shades with the built-in {} through {}, with {} textures", | |
| 487 | 15 | paths.Path(material), Info(schema).name, paths.Path(shader), textured); | |
| 488 | } | ||
| 489 | return true; | ||
| 490 | } | ||
| 491 | |||
| 492 | 15 | uint32_t MaterialTable::PackDetail(uint32_t shader, uint32_t detail, uint32_t textures) { | |
| 493 | 165 | const auto at = [&](size_t offset) { return detail + Word(offset); }; | |
| 494 | 15 | CopyCell(kSurfaceDetailAPINormal, shader, at(offsetof(DetailRecord, normal))); | |
| 495 | 15 | CopyCell(kSurfaceDetailAPINormalStrength, shader, at(offsetof(DetailRecord, normalStrength))); | |
| 496 | 15 | CopyCell(kSurfaceDetailAPIDetailNormal, shader, at(offsetof(DetailRecord, detailNormal))); | |
| 497 | 15 | CopyCell(kSurfaceDetailAPIDetailNormalStrength, shader, | |
| 498 | at(offsetof(DetailRecord, detailNormalStrength))); | ||
| 499 | 15 | CopyCell(kSurfaceDetailAPIDetailAlbedo, shader, at(offsetof(DetailRecord, detailAlbedo))); | |
| 500 | 15 | CopyCell(kSurfaceDetailAPIDetailAlbedoStrength, shader, | |
| 501 | at(offsetof(DetailRecord, detailAlbedoStrength))); | ||
| 502 | 15 | CopyCell(kSurfaceDetailAPIDetailScale, shader, at(offsetof(DetailRecord, detailScale))); | |
| 503 | 15 | CopyCell(kSurfaceDetailAPIDetailOffset, shader, at(offsetof(DetailRecord, detailOffset))); | |
| 504 | 15 | CopyCell(kSurfaceDetailAPIDetailRotation, shader, at(offsetof(DetailRecord, detailRotation))); | |
| 505 | 15 | CopyCell(kSurfaceDetailAPIDetailHexTiling, shader, at(offsetof(DetailRecord, detailHexTiling))); | |
| 506 | 15 | CopyCell(kSurfaceDetailAPIEnableDetail, shader, at(offsetof(DetailRecord, enableDetail))); | |
| 507 | 15 | constexpr std::string_view kNames[material::kDetailTextures] = {"normal", "detailNormal", | |
| 508 | "detailAlbedo"}; | ||
| 509 | 15 | const std::span<const Connection> connections = Connections(shader); | |
| 510 | 15 | uint32_t coordinates = kNoPrim; | |
| 511 | 60 | for (uint32_t i = 0; i < material::kDetailTextures; ++i) { | |
| 512 | 45 | const uint32_t slot = textures + Word(i * material::kTextureInputBytes); | |
| 513 | 45 | const auto found = std::ranges::find(connections, kNames[i], &Connection::input); | |
| 514 | 45 | TextureChannel channel = TextureChannel::Rgb; | |
| 515 | 45 | if (found == connections.end() || | |
| 516 | ✗ | !BindTexture(shader, *found, kNames[i], true, slot, channel)) { | |
| 517 | 45 | const uint32_t handle = slot + Word(offsetof(TextureInput, handle)); | |
| 518 | 45 | m_pack.Literal(handle, 0); | |
| 519 | 45 | m_pack.Literal(handle + 1, 0); | |
| 520 | 45 | continue; | |
| 521 | 45 | } | |
| 522 | ✗ | if (i != 0) { | |
| 523 | ✗ | continue; | |
| 524 | } | ||
| 525 | ✗ | for (const Connection& connection : Connections(found->source)) { | |
| 526 | ✗ | if (connection.input == "st") { | |
| 527 | ✗ | coordinates = connection.source; | |
| 528 | } | ||
| 529 | } | ||
| 530 | } | ||
| 531 | 15 | return coordinates; | |
| 532 | } | ||
| 533 | |||
| 534 | 14 | bool MaterialTable::BindTexture(uint32_t shader, const Connection& connection, | |
| 535 | std::string_view input, bool color, uint32_t slot, | ||
| 536 | TextureChannel& channel) { | ||
| 537 | 14 | const Prims& prims = m_structure.GetPrims(); | |
| 538 | 14 | const Paths& paths = m_structure.GetPaths(); | |
| 539 | 14 | const uint32_t texture = connection.source; | |
| 540 | 14 | if (texture == kNoPrim || prims.Record(texture).schema != SchemaId::UsdUVTexture) { | |
| 541 | ✗ | Log(LogLevel::Warning, | |
| 542 | "{}'s {} connects to {}, which isn't a UsdUVTexture; the input keeps its value", | ||
| 543 | ✗ | paths.Path(shader), input, | |
| 544 | ✗ | texture == kNoPrim ? std::string("nothing") : paths.Path(texture)); | |
| 545 | ✗ | return false; | |
| 546 | } | ||
| 547 | 14 | if (!Channel(connection.output, color, channel)) { | |
| 548 | ✗ | Log(LogLevel::Warning, | |
| 549 | "{}'s {} reads output {} of {}, which isn't rgb, r, g, b or a; the input keeps " | ||
| 550 | "its value", | ||
| 551 | ✗ | paths.Path(shader), input, connection.output, paths.Path(texture)); | |
| 552 | ✗ | return false; | |
| 553 | } | ||
| 554 | 14 | if (slot == kNoPrim) { | |
| 555 | return true; | ||
| 556 | } | ||
| 557 | 12 | const uint32_t handle = slot + Word(offsetof(TextureInput, handle)); | |
| 558 | 12 | m_bindings.push_back({texture, m_pack.Literal(handle, 0), m_pack.Literal(handle + 1, 0)}); | |
| 559 | 12 | m_pack.Literal(slot + Word(offsetof(TextureInput, channel)), uint32_t(channel)); | |
| 560 | 12 | CopyCell(kUsdUVTextureScale, texture, slot + Word(offsetof(TextureInput, scale))); | |
| 561 | 12 | CopyCell(kUsdUVTextureBias, texture, slot + Word(offsetof(TextureInput, bias))); | |
| 562 | 12 | return true; | |
| 563 | } | ||
| 564 | |||
| 565 | 89 | void MaterialTable::Define(uint32_t material, uint32_t definition, uint32_t words) { | |
| 566 | 89 | m_records.Set(material, {.definition = definition, | |
| 567 | .words = words, | ||
| 568 | .previousDefinition = kDefinitionPlaceholder, | ||
| 569 | .previousWords = 0}); | ||
| 570 | 89 | } | |
| 571 | |||
| 572 | std::vector<MaterialTable::Reached> | ||
| 573 | 15 | MaterialTable::ReachNodes(uint32_t shader, | |
| 574 | std::shared_ptr<const material::MaterialNodeDefinition> surface) { | ||
| 575 | 15 | const Prims& prims = m_structure.GetPrims(); | |
| 576 | 15 | const Paths& paths = m_structure.GetPaths(); | |
| 577 | 30 | std::vector<Reached> reached = {{.prim = shader, .node = std::move(surface)}}; | |
| 578 | 33 | for (uint32_t k = 0; k < reached.size(); ++k) { | |
| 579 | 18 | const uint32_t prim = reached[k].prim; | |
| 580 | 18 | const std::shared_ptr<const material::MaterialNodeDefinition> node = reached[k].node; | |
| 581 | 18 | const std::span<const Connection> connections = Connections(prim); | |
| 582 | 18 | std::vector<material::InputBinding> inputs(node->inputs.size()); | |
| 583 | 18 | std::vector<Connection> textures(node->inputs.size()); | |
| 584 | 18 | std::vector<uint32_t> spectra(node->inputs.size(), kNoPrim); | |
| 585 | 46 | for (uint32_t i = 0; i < node->inputs.size(); ++i) { | |
| 586 | 28 | const material::MaterialNodeInput& input = node->inputs[i]; | |
| 587 | 28 | const auto found = std::ranges::find(connections, input.name, &Connection::input); | |
| 588 | 28 | if (found == connections.end()) { | |
| 589 | 26 | continue; | |
| 590 | } | ||
| 591 | 7 | const uint32_t source = found->source; | |
| 592 | 7 | const SchemaId schema = | |
| 593 | 7 | source == kNoPrim ? SchemaId::None : prims.Record(source).schema; | |
| 594 | 7 | std::shared_ptr<const material::MaterialNodeDefinition> pattern = | |
| 595 | 7 | IsRuntime(schema) ? material::FindMaterialNode(Info(schema).name) : nullptr; | |
| 596 | 7 | if (schema == SchemaId::SampledSpectrum) { | |
| 597 | 1 | if (input.type == material::MaterialInputType::Spectral) { | |
| 598 | 1 | spectra[i] = source; | |
| 599 | } else { | ||
| 600 | ✗ | Log(LogLevel::Warning, | |
| 601 | "{}'s {} connects to the spectrum {}, but only SpectralValue inputs take " | ||
| 602 | "spectra; the input keeps its value", | ||
| 603 | ✗ | paths.Path(prim), input.name, paths.Path(source)); | |
| 604 | } | ||
| 605 | 1 | continue; | |
| 606 | } | ||
| 607 | 12 | const material::MaterialInputType fed = | |
| 608 | 6 | input.type == material::MaterialInputType::Spectral | |
| 609 | 6 | ? material::MaterialInputType::Float3 | |
| 610 | : input.type; | ||
| 611 | 6 | if (pattern && pattern->role == material::MaterialNodeRole::Pattern) { | |
| 612 | 4 | if (pattern->result != fed) { | |
| 613 | ✗ | Log(LogLevel::Warning, | |
| 614 | "{}'s {} connects to {}, whose result has another type; the input keeps " | ||
| 615 | "its value", | ||
| 616 | ✗ | paths.Path(prim), input.name, paths.Path(source)); | |
| 617 | ✗ | continue; | |
| 618 | } | ||
| 619 | 4 | auto at = std::ranges::find(reached, source, &Reached::prim); | |
| 620 | 4 | if (at == reached.end()) { | |
| 621 | 3 | reached.push_back({.prim = source, .node = std::move(pattern)}); | |
| 622 | 3 | at = reached.end() - 1; | |
| 623 | } | ||
| 624 | 4 | inputs[i] = {material::BindingKind::Pattern, uint32_t(at - reached.begin())}; | |
| 625 | 4 | continue; | |
| 626 | 4 | } | |
| 627 | 2 | if (!material::Texturable(input.type)) { | |
| 628 | ✗ | Log(LogLevel::Warning, | |
| 629 | "{}'s {} connects to {}, but only float and float3 inputs take textures; " | ||
| 630 | "the input keeps its value", | ||
| 631 | ✗ | paths.Path(prim), input.name, | |
| 632 | ✗ | source == kNoPrim ? std::string("nothing") : paths.Path(source)); | |
| 633 | ✗ | continue; | |
| 634 | } | ||
| 635 | 2 | TextureChannel channel = TextureChannel::Rgb; | |
| 636 | 2 | if (BindTexture(prim, *found, input.name, | |
| 637 | 2 | input.type != material::MaterialInputType::Float, kNoPrim, channel)) { | |
| 638 | 2 | inputs[i] = {material::BindingKind::Texture, 0}; | |
| 639 | 2 | textures[i] = *found; | |
| 640 | } | ||
| 641 | 7 | } | |
| 642 | 18 | reached[k].inputs = std::move(inputs); | |
| 643 | 18 | reached[k].textures = std::move(textures); | |
| 644 | 18 | reached[k].spectra = std::move(spectra); | |
| 645 | 36 | } | |
| 646 | 15 | return reached; | |
| 647 | 18 | } | |
| 648 | |||
| 649 | 15 | std::vector<uint32_t> MaterialTable::OrderPatterns(std::vector<Reached>& reached) const { | |
| 650 | 15 | const Paths& paths = m_structure.GetPaths(); | |
| 651 | 15 | std::vector<uint32_t> order; | |
| 652 | 17 | while (true) { | |
| 653 | 16 | graph::Graph patterns; | |
| 654 | 37 | for (size_t node = 0; node < reached.size(); ++node) { | |
| 655 | 21 | patterns.Add(0); | |
| 656 | } | ||
| 657 | 37 | for (uint32_t k = 0; k < reached.size(); ++k) { | |
| 658 | 52 | for (uint32_t i = 0; i < reached[k].inputs.size(); ++i) { | |
| 659 | 31 | if (reached[k].inputs[i].kind == material::BindingKind::Pattern) { | |
| 660 | 6 | patterns.Connect(k, i, reached[k].inputs[i].source, 0); | |
| 661 | } | ||
| 662 | } | ||
| 663 | } | ||
| 664 | 16 | const graph::Ordering ordering = patterns.Order(); | |
| 665 | 16 | if (ordering.cycle.empty()) { | |
| 666 | 15 | order = ordering.order; | |
| 667 | 15 | break; | |
| 668 | } | ||
| 669 | 1 | const uint32_t closing = ordering.cycle.back(); | |
| 670 | 2 | for (material::InputBinding& binding : reached[closing].inputs) { | |
| 671 | 1 | if (binding.kind == material::BindingKind::Pattern && | |
| 672 | 1 | binding.source == ordering.cycle.front()) { | |
| 673 | 1 | binding = {}; | |
| 674 | } | ||
| 675 | } | ||
| 676 | 1 | Log(LogLevel::Warning, "{} feeds back into itself through {}; that input keeps its value", | |
| 677 | 2 | paths.Path(reached[ordering.cycle.front()].prim), paths.Path(reached[closing].prim)); | |
| 678 | 16 | } | |
| 679 | 15 | order.resize(size_t(std::ranges::find(order, 0u) - order.begin()) + 1); | |
| 680 | 15 | return order; | |
| 681 | ✗ | } | |
| 682 | |||
| 683 | 15 | void MaterialTable::ResolveNode(uint32_t material, uint32_t shader, | |
| 684 | std::shared_ptr<const material::MaterialNodeDefinition> surface) { | ||
| 685 | 15 | const Prims& prims = m_structure.GetPrims(); | |
| 686 | 15 | const Paths& paths = m_structure.GetPaths(); | |
| 687 | 15 | std::vector<Reached> reached = ReachNodes(shader, std::move(surface)); | |
| 688 | 15 | const std::vector<uint32_t> order = OrderPatterns(reached); | |
| 689 | 15 | std::vector<uint32_t> position(reached.size(), kNoPrim); | |
| 690 | 15 | material::ShadingGraph graph; | |
| 691 | 33 | for (const uint32_t k : order) { | |
| 692 | 18 | position[k] = uint32_t(graph.nodes.size()); | |
| 693 | 36 | graph.nodes.push_back({reached[k].node, reached[k].inputs}); | |
| 694 | } | ||
| 695 | 33 | for (material::ShadingNode& node : graph.nodes) { | |
| 696 | 46 | for (material::InputBinding& binding : node.inputs) { | |
| 697 | 28 | if (binding.kind == material::BindingKind::Pattern) { | |
| 698 | 3 | binding.source = position[binding.source]; | |
| 699 | } | ||
| 700 | } | ||
| 701 | } | ||
| 702 | 15 | const material::GraphLayout layout = material::LayOut(graph); | |
| 703 | 15 | const uint32_t definition = | |
| 704 | 15 | m_library.Intern(material::MaterialDefinitionKey(graph), [&](uint32_t id) { | |
| 705 | 14 | material::MaterialDefinition module = material::GenerateMaterialDefinition(id, graph); | |
| 706 | 14 | Log(LogLevel::Debug, | |
| 707 | "new material definition {} for {} with {}, {} in {} words and {} bytes of " | ||
| 708 | "generated " | ||
| 709 | "Slang", | ||
| 710 | 28 | id, graph.nodes.back().node->name, Counted(graph.nodes.size() - 1, "pattern"), | |
| 711 | 14 | Counted(layout.textureCount, "texture"), Word(layout.bytes), module.source.size()); | |
| 712 | 14 | return module; | |
| 713 | 15 | }); | |
| 714 | 15 | m_wordCount = (m_wordCount + 1) & ~1u; | |
| 715 | 15 | const uint32_t base = m_wordCount; | |
| 716 | 15 | m_wordCount += Word(layout.bytes); | |
| 717 | 15 | uint32_t coordinates = kNoPrim; | |
| 718 | 15 | bool placed = false; | |
| 719 | 33 | for (uint32_t n = 0; n < order.size(); ++n) { | |
| 720 | 18 | const Reached& node = reached[order[n]]; | |
| 721 | 18 | const SchemaId schema = SchemaId(node.node->schema); | |
| 722 | 18 | const Span<const ColumnInfo> columns = Columns(schema); | |
| 723 | 46 | for (uint32_t i = 0; i < node.node->inputs.size(); ++i) { | |
| 724 | 28 | CopyCell({schema, &columns[i], i}, node.prim, base + Word(layout.cells[n][i])); | |
| 725 | 28 | if (node.node->inputs[i].type == material::MaterialInputType::Spectral) { | |
| 726 | 2 | uint32_t spectrum = kNoSpectrum; | |
| 727 | 2 | if (node.spectra[i] != kNoPrim) { | |
| 728 | 1 | spectrum = prims.Row(node.spectra[i], SchemaId::SampledSpectrum); | |
| 729 | 1 | m_spectral.insert(material); | |
| 730 | } | ||
| 731 | 2 | m_pack.Literal(base + Word(layout.cells[n][i] + 12), spectrum); | |
| 732 | } | ||
| 733 | 28 | if (node.inputs[i].kind != material::BindingKind::Texture) { | |
| 734 | 26 | continue; | |
| 735 | } | ||
| 736 | 2 | TextureChannel channel = TextureChannel::Rgb; | |
| 737 | 2 | BindTexture(node.prim, node.textures[i], node.node->inputs[i].name, | |
| 738 | 2 | node.node->inputs[i].type != material::MaterialInputType::Float, | |
| 739 | 2 | base + Word(layout.textures[n][i] * material::kTextureInputBytes), channel); | |
| 740 | 2 | uint32_t read = kNoPrim; | |
| 741 | 2 | for (const Connection& connection : Connections(node.textures[i].source)) { | |
| 742 | ✗ | if (connection.input == "st") { | |
| 743 | ✗ | read = connection.source; | |
| 744 | } | ||
| 745 | } | ||
| 746 | 2 | if (!placed) { | |
| 747 | 2 | coordinates = read; | |
| 748 | 2 | placed = true; | |
| 749 | } | ||
| 750 | } | ||
| 751 | } | ||
| 752 | 15 | if (layout.detailed) { | |
| 753 | ✗ | const uint32_t read = | |
| 754 | ✗ | PackDetail(shader, base + Word(layout.detail), base + Word(layout.detailTextures)); | |
| 755 | ✗ | if (!placed) { | |
| 756 | ✗ | coordinates = read; | |
| 757 | ✗ | placed = read != kNoPrim; | |
| 758 | } | ||
| 759 | } | ||
| 760 | 15 | if (layout.mapped) { | |
| 761 | 2 | ResolvePlacement(coordinates, base + Word(layout.mapping)); | |
| 762 | } | ||
| 763 | 15 | Define(material, definition, base); | |
| 764 | 15 | if (GetLogLevel() <= LogLevel::Debug) { | |
| 765 | ✗ | Log(LogLevel::Debug, "{} shades with node {} through {} as definition {}, in {} words", | |
| 766 | ✗ | paths.Path(material), graph.nodes.back().node->name, paths.Path(shader), definition, | |
| 767 | ✗ | Word(layout.bytes)); | |
| 768 | } | ||
| 769 | 33 | } | |
| 770 | |||
| 771 | 89 | void MaterialTable::ResolveMaterial(uint32_t material) { | |
| 772 | 89 | const Prims& prims = m_structure.GetPrims(); | |
| 773 | 89 | const Paths& paths = m_structure.GetPaths(); | |
| 774 | 89 | uint32_t surface = kNoPrim; | |
| 775 | 89 | bool connected = false; | |
| 776 | 176 | for (const Connection& connection : Connections(material)) { | |
| 777 | 87 | if (connection.input == "surface") { | |
| 778 | 86 | surface = connection.source; | |
| 779 | 86 | connected = true; | |
| 780 | } | ||
| 781 | } | ||
| 782 | 89 | if (surface != kNoPrim && prims.Record(surface).schema == SchemaId::UsdPreviewSurface) { | |
| 783 | 52 | ResolvePreview(material, surface); | |
| 784 | 52 | return; | |
| 785 | } | ||
| 786 | 37 | if (surface != kNoPrim) { | |
| 787 | 66 | if (std::shared_ptr<const material::MaterialNodeDefinition> node = | |
| 788 | 33 | BuiltinSurface(prims.Record(surface).schema)) { | |
| 789 | 15 | if (!ResolvePlanetary(material, surface)) { | |
| 790 | ✗ | ResolveNode(material, surface, std::move(node)); | |
| 791 | } | ||
| 792 | 15 | return; | |
| 793 | 15 | } | |
| 794 | } | ||
| 795 | 18 | if (surface != kNoPrim && IsRuntime(prims.Record(surface).schema)) { | |
| 796 | 15 | std::shared_ptr<const material::MaterialNodeDefinition> node = | |
| 797 | 30 | material::FindMaterialNode(Info(prims.Record(surface).schema).name); | |
| 798 | 15 | if (node && node->role != material::MaterialNodeRole::Pattern) { | |
| 799 | 15 | ResolveNode(material, surface, std::move(node)); | |
| 800 | 15 | return; | |
| 801 | } | ||
| 802 | 15 | } | |
| 803 | 7 | if (!connected) { | |
| 804 | 3 | if (GetLogLevel() <= LogLevel::Debug) { | |
| 805 | ✗ | Log(LogLevel::Debug, "{} has no surface connected, so it shades as plain diffuse", | |
| 806 | ✗ | paths.Path(material)); | |
| 807 | } | ||
| 808 | } else { | ||
| 809 | 4 | Log(LogLevel::Warning, | |
| 810 | "{}'s surface connects to {}, which isn't a surface Chrono Batch Render knows, so it " | ||
| 811 | "shades as " | ||
| 812 | "plain diffuse", | ||
| 813 | 8 | paths.Path(material), | |
| 814 | 8 | surface == kNoPrim ? std::string("nothing") : paths.Path(surface)); | |
| 815 | } | ||
| 816 | 7 | Define(material, kDefinitionDiffuse, 0); | |
| 817 | } | ||
| 818 | |||
| 819 | 311 | void MaterialTable::Resolve(const ChangeSet& changes) { | |
| 820 | 311 | CHRONO_BATCH_RENDER_ZONE("Materials/Resolve"); | |
| 821 | 311 | ++m_resolves; | |
| 822 | 311 | const Prims& prims = m_structure.GetPrims(); | |
| 823 | 311 | m_pack.Clear(); | |
| 824 | 311 | m_bindings.clear(); | |
| 825 | 311 | m_spectral.clear(); | |
| 826 | 311 | m_wordCount = 0; | |
| 827 | 311 | m_connections.Update(changes); | |
| 828 | 311 | m_touched.assign(prims.Size(), false); | |
| 829 | 311 | std::vector<MaterialRecord> before; | |
| 830 | 609 | for (uint32_t prim = 0; prim < m_generations.size(); ++prim) { | |
| 831 | 596 | before.push_back(m_records.Row(prim)); | |
| 832 | } | ||
| 833 | 311 | m_records.Assign(std::max(prims.Size(), 1u), MaterialRecord{}); | |
| 834 | 311 | ColumnTable& shaders = m_structure.GetColumnStore().Schema(SchemaId::Shader); | |
| 835 | 311 | const std::vector<uint32_t> ids = | |
| 836 | 622 | Items<uint32_t>(shaders.Read(kShaderId.index, 0, shaders.Rows())); | |
| 837 | 311 | uint32_t materials = 0; | |
| 838 | 2478 | for (uint32_t prim = 0; prim < prims.Size(); ++prim) { | |
| 839 | 2167 | if (!prims.Alive(prim)) { | |
| 840 | 3 | continue; | |
| 841 | } | ||
| 842 | 2164 | const SchemaId schema = prims.Record(prim).schema; | |
| 843 | 2164 | if (IsA(schema, SchemaId::Material)) { | |
| 844 | 89 | ResolveMaterial(prim); | |
| 845 | 89 | ++materials; | |
| 846 | 2075 | } else if (IsA(schema, SchemaId::Shader)) { | |
| 847 | 287 | const std::string_view id = | |
| 848 | 287 | m_structure.GetTokens().Text(Token{ids[prims.Row(prim, SchemaId::Shader)]}); | |
| 849 | 574 | if (id != Info(schema).name) { | |
| 850 | ✗ | Log(LogLevel::Warning, "{} has info:id {} but is a {}, so it runs {}", | |
| 851 | ✗ | m_structure.GetPaths().Path(prim), id, Info(schema).name, Info(schema).name); | |
| 852 | } | ||
| 853 | } | ||
| 854 | } | ||
| 855 | 311 | CarryPrevious(before); | |
| 856 | 311 | ResolveRadios(); | |
| 857 | 311 | if (const uint32_t twice = m_pack.Twice(m_wordCount); twice != kNoPrim) { | |
| 858 | ✗ | Log(LogLevel::Error, "material word {} is packed twice, so its value is undefined", twice); | |
| 859 | } | ||
| 860 | 311 | Log(LogLevel::Debug, "resolved {} materials into {} words, {} copies and {} textures", | |
| 861 | 311 | materials, m_wordCount, m_pack.CopyCount(), m_bindings.size()); | |
| 862 | 311 | } | |
| 863 | |||
| 864 | 311 | void MaterialTable::CarryPrevious(const std::vector<MaterialRecord>& before) { | |
| 865 | 311 | const Prims& prims = m_structure.GetPrims(); | |
| 866 | 311 | m_carries.clear(); | |
| 867 | 311 | std::vector<uint32_t> generations(prims.Size(), 0); | |
| 868 | 311 | std::set<uint32_t> used; | |
| 869 | 311 | const auto library = m_library.Snapshot(); | |
| 870 | 317 | const auto definitionWords = [&](uint32_t id) -> uint32_t { | |
| 871 | 6 | if (id == kDefinitionPreviewConstant || id == kDefinitionPreviewTextured) { | |
| 872 | return Word(sizeof(PreviewParams)); | ||
| 873 | } | ||
| 874 | 6 | const std::shared_ptr<const material::MaterialDefinition> found = library->Find(id); | |
| 875 | 6 | return found ? found->words : 0; | |
| 876 | 6 | }; | |
| 877 | 2478 | for (uint32_t prim = 0; prim < prims.Size(); ++prim) { | |
| 878 | 2167 | if (!prims.Alive(prim) || !IsA(prims.Record(prim).schema, SchemaId::Material)) { | |
| 879 | 2078 | continue; | |
| 880 | } | ||
| 881 | 89 | generations[prim] = prims.Generation(prim) + 1; | |
| 882 | 89 | MaterialRecord record = m_records.Row(prim); | |
| 883 | 89 | if (record.definition < material::kFirstGeneratedDefinition) { | |
| 884 | 74 | record.previousDefinition = kDefinitionPlaceholder; | |
| 885 | 74 | record.previousWords = 0; | |
| 886 | 74 | m_records.Set(prim, record); | |
| 887 | 15 | } else if (prim < before.size() && m_generations[prim] == generations[prim]) { | |
| 888 | 6 | const MaterialRecord& old = before[prim]; | |
| 889 | 6 | const bool changed = old.definition != record.definition; | |
| 890 | 6 | const uint32_t previous = changed ? old.definition : old.previousDefinition; | |
| 891 | 6 | const uint32_t from = changed ? old.words : old.previousWords; | |
| 892 | 6 | const uint32_t words = definitionWords(previous); | |
| 893 | 6 | record.previousDefinition = previous; | |
| 894 | 6 | record.previousWords = 0; | |
| 895 | 6 | if (words > 0) { | |
| 896 | 4 | m_wordCount = (m_wordCount + 1) & ~1u; | |
| 897 | 4 | record.previousWords = m_wordCount; | |
| 898 | 4 | m_carries.push_back({from, m_wordCount, words}); | |
| 899 | 4 | m_wordCount += words; | |
| 900 | } | ||
| 901 | 6 | if (changed && GetLogLevel() <= LogLevel::Debug) { | |
| 902 | ✗ | Log(LogLevel::Debug, | |
| 903 | "{} changes from definition {} to {}, and shows {} until the renderer links it", | ||
| 904 | ✗ | m_structure.GetPaths().Path(prim), old.definition, record.definition, | |
| 905 | ✗ | old.definition); | |
| 906 | } | ||
| 907 | 6 | m_records.Set(prim, record); | |
| 908 | } | ||
| 909 | 89 | used.insert(record.definition); | |
| 910 | 89 | used.insert(record.previousDefinition); | |
| 911 | } | ||
| 912 | 311 | m_generations = std::move(generations); | |
| 913 | 311 | if (!m_carries.empty() && m_words.Buffer()) { | |
| 914 | 4 | const Ref<device::Buffer>& arena = m_words.Buffer(); | |
| 915 | 4 | device::Device& device = m_structure.GetColumnStore().GetDevice(); | |
| 916 | 4 | m_carried = | |
| 917 | 5 | device.CreateBuffer({.bytes = arena->Bytes(), .label = "carried material words"}); | |
| 918 | 4 | device::Queue& queue = device.GetQueue(); | |
| 919 | 4 | device::CommandEncoder encoder = queue.CreateEncoder(); | |
| 920 | 4 | encoder.Copy(arena->Slice(0), m_carried->Slice(0)); | |
| 921 | 4 | queue.Submit(encoder.Finish()); | |
| 922 | 311 | } else if (m_carries.empty()) { | |
| 923 | 307 | m_carried = nullptr; | |
| 924 | } | ||
| 925 | 311 | std::vector<material::MaterialDefinition> definitions; | |
| 926 | 332 | for (const auto& entry : library->Entries()) { | |
| 927 | 21 | if (used.contains(entry.id)) { | |
| 928 | 20 | definitions.push_back(*entry.definition); | |
| 929 | } | ||
| 930 | } | ||
| 931 | 311 | const bool planetary = std::ranges::any_of(used, [](uint32_t id) { | |
| 932 | 141 | return id >= material::kFirstPlanetaryDefinition && | |
| 933 | id < material::kFirstGeneratedDefinition; | ||
| 934 | 311 | }); | |
| 935 | 311 | auto set = std::make_shared<const material::MaterialDefinitionSet>( | |
| 936 | 622 | std::move(definitions), material::ClosurePrimitiveSources(material::ClosurePrimitives()), | |
| 937 | 311 | planetary); | |
| 938 | 311 | if (set->Hash() != m_definitions->Hash()) { | |
| 939 | 61 | Log(LogLevel::Debug, "the scene now uses {}", | |
| 940 | 61 | Counted(set->Definitions().size(), "generated material definition")); | |
| 941 | 61 | m_definitions = std::move(set); | |
| 942 | } | ||
| 943 | 630 | } | |
| 944 | |||
| 945 | 311 | void MaterialTable::ResolveRadios() { | |
| 946 | 311 | const Prims& prims = m_structure.GetPrims(); | |
| 947 | 311 | m_radarLinks.clear(); | |
| 948 | 2478 | for (uint32_t material = 0; material < prims.Size(); ++material) { | |
| 949 | 2167 | if (!prims.Alive(material) || !IsA(prims.Record(material).schema, SchemaId::Material)) { | |
| 950 | 2078 | continue; | |
| 951 | } | ||
| 952 | 176 | for (const Connection& connection : Connections(material)) { | |
| 953 | 87 | if (connection.input != "radar") { | |
| 954 | 86 | continue; | |
| 955 | } | ||
| 956 | 1 | if (connection.source == kNoPrim || | |
| 957 | 1 | prims.Record(connection.source).schema != SchemaId::ItuMaterial) { | |
| 958 | ✗ | Log(LogLevel::Warning, | |
| 959 | "{}'s radar connects to {}, which isn't an ItuMaterial; radar sees its surface " | ||
| 960 | "instead", | ||
| 961 | ✗ | m_structure.GetPaths().Path(material), | |
| 962 | ✗ | connection.source == kNoPrim ? std::string("nothing") | |
| 963 | ✗ | : m_structure.GetPaths().Path(connection.source)); | |
| 964 | ✗ | continue; | |
| 965 | } | ||
| 966 | 1 | m_radarLinks.push_back({material, prims.Row(connection.source, SchemaId::ItuMaterial)}); | |
| 967 | } | ||
| 968 | } | ||
| 969 | 311 | } | |
| 970 | |||
| 971 | 334 | void MaterialTable::WriteRadios() { | |
| 972 | 334 | ColumnStore& store = m_structure.GetColumnStore(); | |
| 973 | 334 | std::vector<RadioRecord> radios(std::max(m_structure.GetPrims().Size(), 1u)); | |
| 974 | 334 | if (!m_radarLinks.empty()) { | |
| 975 | 9 | ColumnTable& itu = store.Schema(SchemaId::ItuMaterial); | |
| 976 | 27 | const auto column = [&](ColumnRef ref) { return itu.Read(ref.index, 0, itu.Rows()); }; | |
| 977 | 9 | const std::vector<uint32_t> kinds = Items<uint32_t>(column(kItuMaterialMaterial)); | |
| 978 | 9 | const std::vector<float> heights = Items<float>(column(kItuMaterialRmsHeight)); | |
| 979 | 9 | const std::vector<float> lengths = Items<float>(column(kItuMaterialCorrelationLength)); | |
| 980 | 18 | for (const RadarLink& link : m_radarLinks) { | |
| 981 | 9 | const uint32_t kind = std::min<uint32_t>(kinds[link.row], std::size(kItuMaterials) - 1); | |
| 982 | 9 | radios[link.material] = {.kind = 1, | |
| 983 | .valid = 1, | ||
| 984 | 9 | .rmsHeight = heights[link.row], | |
| 985 | 9 | .correlationLength = lengths[link.row], | |
| 986 | 9 | .data = kItuMaterials[kind]}; | |
| 987 | } | ||
| 988 | 9 | } | |
| 989 | 8035 | for (uint32_t row = 0; row < radios.size(); ++row) { | |
| 990 | 7701 | m_radios.Set(row, radios[row]); | |
| 991 | } | ||
| 992 | 4303 | for (uint32_t row = uint32_t(radios.size()); row < m_radios.Rows(); ++row) { | |
| 993 | 3969 | m_radios.Set(row, RadioRecord{}); | |
| 994 | } | ||
| 995 | 334 | m_radios.Upload(store.GetDevice()); | |
| 996 | 334 | } | |
| 997 | |||
| 998 | 320 | void MaterialTable::WarnOutsideVisible() { | |
| 999 | 320 | const Prims& prims = m_structure.GetPrims(); | |
| 1000 | 320 | ColumnStore& store = m_structure.GetColumnStore(); | |
| 1001 | 320 | ColumnTable& detectors = store.Schema(SchemaId::DetectorAPI); | |
| 1002 | 320 | const std::vector<Float2> ranges = | |
| 1003 | 640 | Items<Float2>(detectors.Read(kDetectorAPIDetectorSpectralRange.index, 0, detectors.Rows())); | |
| 1004 | 320 | const std::vector<uint32_t> owners = Items<uint32_t>( | |
| 1005 | 640 | detectors.Read(store.PrimsColumn(SchemaId::DetectorAPI), 0, detectors.Rows())); | |
| 1006 | 536 | for (uint32_t row = 0; row < ranges.size(); ++row) { | |
| 1007 | 216 | const uint32_t sensor = owners[row]; | |
| 1008 | 216 | if (sensor == kNoPrim || !prims.Alive(sensor) || | |
| 1009 | 216 | (ranges[row].x >= kVisibleFirstNm && ranges[row].y <= kVisibleLastNm)) { | |
| 1010 | 205 | continue; | |
| 1011 | } | ||
| 1012 | 89 | for (uint32_t material = 0; material < prims.Size(); ++material) { | |
| 1013 | 162 | if (!prims.Alive(material) || !IsA(prims.Record(material).schema, SchemaId::Material) || | |
| 1014 | 84 | m_spectral.contains(material) || | |
| 1015 | !m_warned | ||
| 1016 | 4 | .emplace(uint64_t(prims.Generation(material)) << 32 | material, | |
| 1017 | 4 | uint64_t(prims.Generation(sensor)) << 32 | sensor) | |
| 1018 | 4 | .second) { | |
| 1019 | 75 | continue; | |
| 1020 | } | ||
| 1021 | 3 | Log(LogLevel::Warning, | |
| 1022 | "{} has only RGB colours, but {} sees {} to {} nm, so it sees them extended by a " | ||
| 1023 | "smooth spectrum; connect a SampledSpectrum for measured values", | ||
| 1024 | 6 | m_structure.GetPaths().Path(material), m_structure.GetPaths().Path(sensor), | |
| 1025 | 3 | ranges[row].x, ranges[row].y); | |
| 1026 | } | ||
| 1027 | } | ||
| 1028 | 320 | } | |
| 1029 | |||
| 1030 | 350 | void MaterialTable::BuildEntries() { | |
| 1031 | 375 | for (const Binding& binding : m_bindings) { | |
| 1032 | 25 | const uint64_t handle = m_textures.Handle(binding.texture, "file"); | |
| 1033 | 25 | m_pack.SetLiteral(binding.low, uint32_t(handle)); | |
| 1034 | 25 | m_pack.SetLiteral(binding.high, uint32_t(handle >> 32)); | |
| 1035 | } | ||
| 1036 | 350 | m_pack.Build(m_structure.GetColumnStore(), true); | |
| 1037 | 350 | } | |
| 1038 | |||
| 1039 | 536 | void MaterialTable::Run(const ChangeSet& changes, TableVersions&) { | |
| 1040 | 536 | CHRONO_BATCH_RENDER_ZONE("Materials/Run"); | |
| 1041 | 536 | ColumnStore& store = m_structure.GetColumnStore(); | |
| 1042 | 536 | device::Device& device = store.GetDevice(); | |
| 1043 | 536 | const uint64_t writes = store.WriteSequence(); | |
| 1044 | 762 | const bool resolve = Restructured(changes) || !m_records.Buffer() || | |
| 1045 | 226 | m_nodeVersion != material::MaterialNodeVersion(); | |
| 1046 | 311 | if (resolve) { | |
| 1047 | 311 | m_nodeVersion = material::MaterialNodeVersion(); | |
| 1048 | 311 | Resolve(changes); | |
| 1049 | } | ||
| 1050 | 536 | const uint32_t prims = m_structure.GetPrims().Size(); | |
| 1051 | 536 | m_records.Reserve(std::max(prims, 1u)); | |
| 1052 | 536 | m_records.Upload(device); | |
| 1053 | 536 | const bool rebound = resolve || changes.Wrote(TableId::Textures); | |
| 1054 | 350 | if (rebound) { | |
| 1055 | 350 | BuildEntries(); | |
| 1056 | } | ||
| 1057 | 536 | if (resolve) { | |
| 1058 | 558 | m_words.Reserve(device, std::max(m_wordCount, 1u)); | |
| 1059 | 311 | device::Queue& queue = device.GetQueue(); | |
| 1060 | 311 | device::CommandEncoder encoder = queue.CreateEncoder(); | |
| 1061 | 311 | encoder.Fill(m_words.Buffer()->Slice(0, uint64_t(m_words.Buffer()->Bytes()))); | |
| 1062 | 311 | if (m_carried && !m_carries.empty()) { | |
| 1063 | 4 | encoder.Barrier(); | |
| 1064 | 8 | for (const Carry& carry : m_carries) { | |
| 1065 | 4 | encoder.Copy(m_carried->Slice(uint64_t(carry.from) * sizeof(uint32_t), | |
| 1066 | 4 | uint64_t(carry.words) * sizeof(uint32_t)), | |
| 1067 | 4 | m_words.Buffer()->Slice(uint64_t(carry.to) * sizeof(uint32_t), | |
| 1068 | 4 | uint64_t(carry.words) * sizeof(uint32_t))); | |
| 1069 | } | ||
| 1070 | } | ||
| 1071 | 311 | queue.Submit(encoder.Finish()); | |
| 1072 | 311 | } | |
| 1073 | 536 | if (rebound || m_pack.Stale(store, m_writes)) { | |
| 1074 | 738 | m_pack.Pack(store, m_words.Address()); | |
| 1075 | } | ||
| 1076 | 536 | if (resolve || Changed(SchemaId::DetectorAPI)) { | |
| 1077 | 320 | WarnOutsideVisible(); | |
| 1078 | } | ||
| 1079 | 536 | m_checkers.Reserve(device, store.Schema(SchemaId::CheckerboardAPI).Capacity()); | |
| 1080 | 536 | m_temperatures.Reserve(device, std::max(prims, 1u)); | |
| 1081 | 536 | m_temperaturePrimvar = m_structure.GetTokens().Intern("chrono:temperature").id; | |
| 1082 | 536 | if (resolve || prims > m_radios.Rows() || Changed(SchemaId::ItuMaterial)) { | |
| 1083 | 334 | WriteRadios(); | |
| 1084 | } | ||
| 1085 | 536 | m_writes = writes; | |
| 1086 | 536 | if (prims == 0) { | |
| 1087 | ✗ | return; | |
| 1088 | } | ||
| 1089 | 536 | MaterialParams params{}; | |
| 1090 | 536 | params.scene.address = store.DeviceData(); | |
| 1091 | 536 | params.checkers.address = m_checkers.Address(); | |
| 1092 | 536 | params.temperatures.address = m_temperatures.Address(); | |
| 1093 | 536 | device::Queue& queue = device.GetQueue(); | |
| 1094 | 536 | static const profiler::ZoneId zone = profiler::ZoneId::Named("Materials/Write"); | |
| 1095 | 536 | device::CommandEncoder encoder = queue.CreateEncoder(); | |
| 1096 | 536 | { | |
| 1097 | 536 | const device::ZoneScope scope(encoder, zone); | |
| 1098 | 536 | encoder.Dispatch(Kernels().Entry("WriteMaterials"), params, | |
| 1099 | 536 | {.x = device::GroupCount(prims, kMaterialGroup)}); | |
| 1100 | 536 | } | |
| 1101 | 536 | queue.Submit(encoder.Finish()); | |
| 1102 | 536 | } | |
| 1103 | |||
| 1104 | 353 | void MaterialTable::Prepare() { | |
| 1105 | 353 | Kernels(); | |
| 1106 | 353 | } | |
| 1107 | |||
| 1108 | 889 | device::Program& MaterialTable::Kernels() { | |
| 1109 | 889 | if (!m_program) { | |
| 1110 | 1059 | m_program = m_structure.GetColumnStore().GetDevice().CreateProgram( | |
| 1111 | {.modules = {"chrono_batch_render.scene.tables.kernels.materials"}, | ||
| 1112 | .library = &kSceneKernels, | ||
| 1113 | .entries = {"WriteMaterials"}, | ||
| 1114 | 706 | .label = "materials"}); | |
| 1115 | } | ||
| 1116 | 889 | return *m_program; | |
| 1117 | 706 | } | |
| 1118 | |||
| 1119 | } | ||
| 1120 |