1 // Copyright 2020-2021 Intel Corporation 2 // SPDX-License-Identifier: Apache-2.0 3 4 #pragma once 5 6 #include <numeric> 7 #include <vector> 8 #include "ProceduralVdbVolume.h" 9 #include "ProceduralVolumeMulti.h" 10 #include "TestingVolume.h" 11 #include "openvkl/vdb.h" 12 #include "rkcommon/common.h" 13 14 namespace openvkl { 15 namespace testing { 16 17 struct ProceduralVdbVolumeMulti : public TestingVolume, 18 public ProceduralVolumeMulti 19 { 20 using Buffers = utility::vdb::VdbVolumeBuffers; 21 22 ProceduralVdbVolumeMulti( 23 VKLDevice device, 24 const vec3i &dimensions, 25 const vec3f &gridOrigin, 26 const vec3f &gridSpacing, 27 const std::vector<std::shared_ptr<ProceduralVdbVolumeBase>> 28 &attributeVolumes, 29 VKLDataCreationFlags dataCreationFlags, 30 bool useAOSLayout); 31 32 // maps to first attribute only 33 range1f getComputedValueRange() const override; 34 35 range1f getComputedValueRange(unsigned int attributeIndex) const; 36 37 vec3i getDimensions() const; 38 vec3f getGridOrigin() const; 39 vec3f getGridSpacing() const; 40 41 unsigned int getNumAttributes() const override; 42 43 vec3f transformLocalToObjectCoordinates( 44 const vec3f &localCoordinates) const; 45 46 protected: 47 float computeProceduralValueImpl(const vec3f &objectCoordinates, 48 unsigned int attributeIndex, 49 float time) const override; 50 51 vec3f computeProceduralGradientImpl(const vec3f &objectCoordinates, 52 unsigned int attributeIndex, 53 float time) const override; 54 55 void generateVKLVolume(VKLDevice device) override final; 56 57 std::unique_ptr<Buffers> buffers; 58 vec3i dimensions; 59 vec3f gridOrigin; 60 vec3f gridSpacing; 61 std::vector<std::shared_ptr<ProceduralVdbVolumeBase>> attributeVolumes; 62 VKLDataCreationFlags dataCreationFlags; 63 bool useAOSLayout; 64 65 // leaf data may need to be retained for shared data buffers 66 std::vector<std::vector<unsigned char>> leaves; 67 std::vector<std::vector<uint32_t>> indices; 68 std::vector<std::vector<float>> times; 69 }; 70 71 // Inlined definitions //////////////////////////////////////////////////// 72 ProceduralVdbVolumeMulti(VKLDevice device,const vec3i & dimensions,const vec3f & gridOrigin,const vec3f & gridSpacing,const std::vector<std::shared_ptr<ProceduralVdbVolumeBase>> & attributeVolumes,VKLDataCreationFlags dataCreationFlags,bool useAOSLayout)73 inline ProceduralVdbVolumeMulti::ProceduralVdbVolumeMulti( 74 VKLDevice device, 75 const vec3i &dimensions, 76 const vec3f &gridOrigin, 77 const vec3f &gridSpacing, 78 const std::vector<std::shared_ptr<ProceduralVdbVolumeBase>> 79 &attributeVolumes, 80 VKLDataCreationFlags dataCreationFlags, 81 bool useAOSLayout) 82 : dimensions(dimensions), 83 gridOrigin(gridOrigin), 84 gridSpacing(gridSpacing), 85 attributeVolumes(attributeVolumes), 86 dataCreationFlags(dataCreationFlags), 87 useAOSLayout(useAOSLayout), 88 ProceduralVolumeMulti(false) 89 { 90 if (attributeVolumes.size() == 0) { 91 throw std::runtime_error("no provided attribute volumes"); 92 } 93 94 const TemporalConfig temporalConfig = 95 attributeVolumes[0]->getTemporalConfig(); 96 97 // verify provided attribute volumes are consistent with the provided 98 // parameters 99 for (const auto &v : attributeVolumes) { 100 bool compatible = (v->getDimensions() == dimensions) && 101 (v->getGridOrigin() == gridOrigin) && 102 (v->getGridSpacing() == gridSpacing) && 103 temporalConfig.isCompatible(v->getTemporalConfig()); 104 105 if (!compatible) { 106 throw std::runtime_error( 107 "a provided attribute volume is not compatible with the " 108 "constructed ProceduralVdbVolumeMulti instance"); 109 } 110 } 111 112 // voxel type and size per attribute 113 std::vector<VKLDataType> voxelTypes; 114 std::vector<size_t> voxelSizes; 115 116 for (const auto &av : attributeVolumes) { 117 voxelTypes.push_back(av->getVoxelType()); 118 voxelSizes.push_back(sizeOfVKLDataType(av->getVoxelType())); 119 } 120 121 // voxel size for all attributes combined 122 const size_t voxelSizeSum = 123 std::accumulate(voxelSizes.begin(), voxelSizes.end(), 0); 124 125 // voxel offset for each attribute 126 std::vector<size_t> voxelSizeOffsets; 127 for (size_t a = 0; a < attributeVolumes.size(); ++a) { 128 voxelSizeOffsets.push_back( 129 std::accumulate(voxelSizes.begin(), voxelSizes.begin() + a, 0)); 130 } 131 132 buffers = rkcommon::make_unique<Buffers>(device, voxelTypes); 133 134 buffers->setIndexToObject(gridSpacing.x, 135 0, 136 0, 137 0, 138 gridSpacing.y, 139 0, 140 0, 141 0, 142 gridSpacing.z, 143 gridOrigin.x, 144 gridOrigin.y, 145 gridOrigin.z); 146 147 // The number of leaf nodes, in each dimension. 148 const vec3i numLeafNodesIn(getNumLeaves(dimensions.x), 149 getNumLeaves(dimensions.y), 150 getNumLeaves(dimensions.z)); 151 152 const size_t numLeafNodes = numLeafNodesIn.x * 153 static_cast<size_t>(numLeafNodesIn.y) * 154 numLeafNodesIn.z; 155 156 const uint32_t leafLevel = vklVdbNumLevels() - 1; 157 const uint32_t leafRes = vklVdbLevelRes(leafLevel); 158 const size_t numLeafVoxels = vklVdbLevelNumVoxels(leafLevel); 159 160 buffers->reserve(numLeafNodes); 161 162 for (int x = 0; x < numLeafNodesIn.x; ++x) 163 for (int y = 0; y < numLeafNodesIn.y; ++y) 164 for (int z = 0; z < numLeafNodesIn.z; ++z) { 165 // Buffer for leaf data. 166 leaves.emplace_back( 167 std::vector<unsigned char>(numLeafVoxels * voxelSizeSum)); 168 std::vector<unsigned char> &leaf = leaves.back(); 169 170 std::vector<range1f> leafValueRanges(attributeVolumes.size()); 171 172 const vec3i nodeOrigin(leafRes * x, leafRes * y, leafRes * z); 173 174 for (uint32_t vx = 0; vx < leafRes; ++vx) 175 for (uint32_t vy = 0; vy < leafRes; ++vy) 176 for (uint32_t vz = 0; vz < leafRes; ++vz) { 177 const vec3f samplePosIndex = vec3f( 178 nodeOrigin.x + vx, nodeOrigin.y + vy, nodeOrigin.z + vz); 179 const vec3f samplePosObject = 180 transformLocalToObjectCoordinates(samplePosIndex); 181 182 for (size_t a = 0; a < attributeVolumes.size(); ++a) { 183 // Note: column major data! 184 // Index in bytes 185 uint64_t idx; 186 187 if (useAOSLayout) { 188 idx = (static_cast<uint64_t>(vx) * leafRes * leafRes + 189 static_cast<uint64_t>(vy) * leafRes + 190 static_cast<uint64_t>(vz)) * 191 voxelSizeSum + 192 voxelSizeOffsets[a]; 193 } else { 194 idx = voxelSizeOffsets[a] * numLeafVoxels + 195 (static_cast<uint64_t>(vx) * leafRes * leafRes + 196 static_cast<uint64_t>(vy) * leafRes + 197 static_cast<uint64_t>(vz)) * 198 voxelSizes[a]; 199 } 200 201 const float fieldValue = 202 computeProceduralValue(samplePosObject, a); 203 204 if (voxelTypes[a] == VKL_HALF) { 205 half_float::half *leafValueTyped = 206 (half_float::half *)(leaf.data() + idx); 207 *leafValueTyped = fieldValue; 208 } else if (voxelTypes[a] == VKL_FLOAT) { 209 float *leafValueTyped = (float *)(leaf.data() + idx); 210 *leafValueTyped = fieldValue; 211 } else { 212 throw std::runtime_error("unsupported voxel type"); 213 } 214 215 leafValueRanges[a].extend(fieldValue); 216 } 217 } 218 219 bool nodeEmpty = true; 220 221 for (size_t a = 0; a < attributeVolumes.size(); ++a) { 222 if (leafValueRanges[a].lower != 0.f || 223 leafValueRanges[a].upper != 0.f) { 224 nodeEmpty = false; 225 break; 226 } 227 } 228 229 // Skip empty nodes (all attributes must be empty!). 230 if (!nodeEmpty) { 231 // We compress constant areas of space into tiles. 232 // We also copy all leaf data so that we do not have to 233 // explicitly store it. 234 235 bool allConstant = true; 236 237 for (size_t a = 0; a < attributeVolumes.size(); a++) { 238 if (!(std::fabs(leafValueRanges[a].upper - 239 leafValueRanges[a].lower) < 240 std::fabs(leafValueRanges[a].upper) * 241 std::numeric_limits<float>::epsilon())) { 242 allConstant = false; 243 break; 244 } 245 } 246 247 if (allConstant) { 248 std::vector<unsigned char> tileValues(voxelSizeSum); 249 std::vector<void *> ptrs(attributeVolumes.size()); 250 251 for (size_t a = 0; a < attributeVolumes.size(); a++) { 252 if (voxelTypes[a] == VKL_HALF) { 253 half_float::half *tileValueTyped = 254 (half_float::half *)(tileValues.data() + 255 voxelSizeOffsets[a]); 256 *tileValueTyped = 257 half_float::half(leafValueRanges[a].upper); 258 } else if (voxelTypes[a] == VKL_FLOAT) { 259 float *tileValueTyped = 260 (float *)(tileValues.data() + voxelSizeOffsets[a]); 261 *tileValueTyped = leafValueRanges[a].upper; 262 } else { 263 throw std::runtime_error("unsupported voxel type"); 264 } 265 266 ptrs[a] = tileValues.data() + voxelSizeOffsets[a]; 267 } 268 269 buffers->addTile(leafLevel, nodeOrigin, ptrs); 270 271 } else { 272 std::vector<void *> ptrs; 273 std::vector<size_t> byteStrides; 274 275 if (useAOSLayout) { 276 for (size_t a = 0; a < attributeVolumes.size(); a++) { 277 ptrs.push_back(leaf.data() + voxelSizeOffsets[a]); 278 byteStrides.push_back(voxelSizeSum); 279 } 280 } else { 281 for (size_t a = 0; a < attributeVolumes.size(); a++) { 282 ptrs.push_back(leaf.data() + 283 numLeafVoxels * voxelSizeOffsets[a]); 284 byteStrides.push_back(voxelSizes[a]); 285 } 286 } 287 288 buffers->addConstant(leafLevel, 289 nodeOrigin, 290 ptrs, 291 dataCreationFlags, 292 byteStrides); 293 } 294 } 295 296 if (dataCreationFlags != VKL_DATA_SHARED_BUFFER) { 297 leaves.clear(); 298 } 299 } 300 } 301 getComputedValueRange()302 inline range1f ProceduralVdbVolumeMulti::getComputedValueRange() const 303 { 304 return attributeVolumes[0]->getComputedValueRange(); 305 } 306 getComputedValueRange(unsigned int attributeIndex)307 inline range1f ProceduralVdbVolumeMulti::getComputedValueRange( 308 unsigned int attributeIndex) const 309 { 310 return attributeVolumes[attributeIndex]->getComputedValueRange(); 311 } 312 computeProceduralValueImpl(const vec3f & objectCoordinates,unsigned int attributeIndex,float time)313 inline float ProceduralVdbVolumeMulti::computeProceduralValueImpl( 314 const vec3f &objectCoordinates, 315 unsigned int attributeIndex, 316 float time) const 317 { 318 return attributeVolumes[attributeIndex]->computeProceduralValue( 319 objectCoordinates, time); 320 } 321 computeProceduralGradientImpl(const vec3f & objectCoordinates,unsigned int attributeIndex,float time)322 inline vec3f ProceduralVdbVolumeMulti::computeProceduralGradientImpl( 323 const vec3f &objectCoordinates, 324 unsigned int attributeIndex, 325 float time) const 326 { 327 return attributeVolumes[attributeIndex]->computeProceduralGradient( 328 objectCoordinates, time); 329 } 330 getDimensions()331 inline vec3i ProceduralVdbVolumeMulti::getDimensions() const 332 { 333 return dimensions; 334 } 335 getGridOrigin()336 inline vec3f ProceduralVdbVolumeMulti::getGridOrigin() const 337 { 338 return gridOrigin; 339 } 340 getGridSpacing()341 inline vec3f ProceduralVdbVolumeMulti::getGridSpacing() const 342 { 343 return gridSpacing; 344 } 345 getNumAttributes()346 inline unsigned int ProceduralVdbVolumeMulti::getNumAttributes() const 347 { 348 return attributeVolumes.size(); 349 } 350 transformLocalToObjectCoordinates(const vec3f & localCoordinates)351 inline vec3f ProceduralVdbVolumeMulti::transformLocalToObjectCoordinates( 352 const vec3f &localCoordinates) const 353 { 354 // at construction we're guaranteed all attribute volumes have the same 355 // grid parameters, so we can simply do the transformation on the first 356 // volume 357 return attributeVolumes[0]->transformLocalToObjectCoordinates( 358 localCoordinates); 359 } 360 generateVKLVolume(VKLDevice device)361 inline void ProceduralVdbVolumeMulti::generateVKLVolume(VKLDevice device) 362 { 363 if (buffers) { 364 release(); 365 366 if (device != buffers->getVKLDevice()) { 367 throw std::runtime_error( 368 "specified device not compatible with VdbVolumeBuffers device"); 369 } 370 371 volume = buffers->createVolume(); 372 } 373 } 374 375 /////////////////////////////////////////////////////////////////////////// 376 // Procedural volume generation helpers /////////////////////////////////// 377 /////////////////////////////////////////////////////////////////////////// 378 379 inline ProceduralVdbVolumeMulti *generateMultiAttributeVdbVolumeHalf( 380 VKLDevice device, 381 const vec3i &dimensions, 382 const vec3f &gridOrigin, 383 const vec3f &gridSpacing, 384 VKLDataCreationFlags dataCreationFlags, 385 bool useAOSLayout, 386 TemporalConfig temporalConfig = TemporalConfig()) 387 { 388 // Not supported for multi attribute, as attributes share temporal config. 389 temporalConfig.useTemporalCompression = false; 390 391 std::vector<std::shared_ptr<ProceduralVdbVolumeBase>> volumes; 392 393 volumes.push_back( 394 std::make_shared<WaveletVdbVolumeHalf>(device, 395 dimensions, 396 gridOrigin, 397 gridSpacing, 398 temporalConfig)); 399 400 volumes.push_back(std::make_shared<XVdbVolumeHalf>(device, 401 dimensions, 402 gridOrigin, 403 gridSpacing, 404 temporalConfig)); 405 406 volumes.push_back(std::make_shared<YVdbVolumeHalf>(device, 407 dimensions, 408 gridOrigin, 409 gridSpacing, 410 temporalConfig)); 411 412 volumes.push_back(std::make_shared<ZVdbVolumeHalf>(device, 413 dimensions, 414 gridOrigin, 415 gridSpacing, 416 temporalConfig)); 417 418 return new ProceduralVdbVolumeMulti(device, 419 dimensions, 420 gridOrigin, 421 gridSpacing, 422 volumes, 423 dataCreationFlags, 424 useAOSLayout); 425 } 426 427 inline ProceduralVdbVolumeMulti *generateMultiAttributeVdbVolumeFloat( 428 VKLDevice device, 429 const vec3i &dimensions, 430 const vec3f &gridOrigin, 431 const vec3f &gridSpacing, 432 VKLDataCreationFlags dataCreationFlags, 433 bool useAOSLayout, 434 TemporalConfig temporalConfig = TemporalConfig()) 435 { 436 // Not supported for multi attribute, as attributes share temporal config. 437 temporalConfig.useTemporalCompression = false; 438 439 std::vector<std::shared_ptr<ProceduralVdbVolumeBase>> volumes; 440 441 volumes.push_back( 442 std::make_shared<WaveletVdbVolumeFloat>(device, 443 dimensions, 444 gridOrigin, 445 gridSpacing, 446 temporalConfig)); 447 448 volumes.push_back(std::make_shared<XVdbVolumeFloat>(device, 449 dimensions, 450 gridOrigin, 451 gridSpacing, 452 temporalConfig)); 453 454 volumes.push_back(std::make_shared<YVdbVolumeFloat>(device, 455 dimensions, 456 gridOrigin, 457 gridSpacing, 458 temporalConfig)); 459 460 volumes.push_back(std::make_shared<ZVdbVolumeFloat>(device, 461 dimensions, 462 gridOrigin, 463 gridSpacing, 464 temporalConfig)); 465 466 return new ProceduralVdbVolumeMulti(device, 467 dimensions, 468 gridOrigin, 469 gridSpacing, 470 volumes, 471 dataCreationFlags, 472 useAOSLayout); 473 } 474 475 } // namespace testing 476 } // namespace openvkl 477