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