1Open VKL API
2============
3
4To access the Open VKL API you first need to include the Open VKL header. For
5C99 or C++:
6
7    #include <openvkl/openvkl.h>
8
9For the Intel® Implicit SPMD Program Compiler (Intel® ISPC):
10
11    #include <openvkl/openvkl.isph>
12
13This documentation will discuss the C99/C++ API.  The ISPC version has the same
14functionality and flavor.  Looking at the headers, the `vklTutorialISPC`
15example, and this documentation should be enough to figure it out.
16
17Device initialization and shutdown
18----------------------------------
19
20To use the API, one of the implemented backends must be loaded.  Currently the
21only one that exists is the CPU device. To load the module that implements the
22CPU device:
23
24    vklLoadModule("cpu_device");
25
26The device then needs to be instantiated:
27
28    VKLDevice device = vklNewDevice("cpu");
29
30By default, the CPU device selects the maximum supported SIMD width (and
31associated ISA) for the system. Optionally, a specific width may be requested
32using the `cpu_4`, `cpu_8`, or `cpu_16` device names. Note that the system
33must support the given width (SSE4.1 for 4-wide, AVX for 8-wide, and AVX512 for
3416-wide).
35
36Once a device is created, you can call
37
38    void vklDeviceSetInt(VKLDevice, const char *name, int val);
39    void vklDeviceSetString(VKLDevice, const char *name, const char *val);
40
41to set parameters on the device. The following parameters are understood by all
42devices:
43
44  ------ -------------- --------------------------------------------------------
45  Type   Name           Description
46  ------ -------------- --------------------------------------------------------
47  int    logLevel       logging level; valid values are `VKL_LOG_DEBUG`,
48                        `VKL_LOG_INFO`, `VKL_LOG_WARNING`, `VKL_LOG_ERROR` and
49                        `VKL_LOG_NONE`
50
51  string logOutput      convenience for setting where log messages go; valid
52                        values are `cout`, `cerr` and `none`
53
54  string errorOutput    convenience for setting where error messages go; valid
55                        values are `cout`, `cerr` and `none`
56
57  int    numThreads     number of threads which Open VKL can use
58
59  int    flushDenormals sets the `Flush to Zero` and `Denormals are Zero` mode
60                        of the MXCSR control and status register (default: 1);
61                        see Performance Recommendations section for details
62  ------ -------------- --------------------------------------------------------
63  : Parameters shared by all devices.
64
65Once parameters are set, the device must be committed with
66
67    vklCommitDevice(device);
68
69The newly committed device is then ready to use. Users may change parameters on
70a device after initialization. In this case the device would need to be
71re-committed.
72
73All Open VKL objects are associated with a device. A device handle must be
74explicitly provided when creating volume and data objects, via `vklNewVolume()`
75and `vklNewData()` respectively. Other object types are automatically associated
76with a device via transitive dependency on a volume.
77
78Open VKL provides vector-wide versions for several APIs. To determine the native
79vector width for a given device, call:
80
81    int width = vklGetNativeSIMDWidth(VKLDevice device);
82
83When the application is finished with an Open VKL device or shutting down,
84release the device via:
85
86    vklReleaseDevice(VKLDevice device);
87
88### Environment variables
89
90The generic device parameters can be overridden via environment variables for
91easy changes to Open VKL’s behavior without needing to change the application
92(variables are prefixed by convention with "`OPENVKL_`"):
93
94  ----------------------- ------------------------------------------------------
95  Variable                Description
96  ----------------------- ------------------------------------------------------
97  OPENVKL_LOG_LEVEL       logging level; valid values are `debug`, `info`,
98                          `warning`, `error` and `none`
99
100  OPENVKL_LOG_OUTPUT      convenience for setting where log messages go; valid
101                          values are `cout`, `cerr` and `none`
102
103  OPENVKL_ERROR_OUTPUT    convenience for setting where error messages go; valid
104                          values are `cout`, `cerr` and `none`
105
106  OPENVKL_THREADS         number of threads which Open VKL can use
107
108  OPENVKL_FLUSH_DENORMALS sets the `Flush to Zero` and `Denormals are Zero` mode
109                          of the MXCSR control and status register (default: 1);
110                          see Performance Recommendations section for details
111  ----------------------- ------------------------------------------------------
112  : Environment variables understood by all devices.
113
114Note that these environment variables take precedence over values set through
115the `vklDeviceSet*()` functions.
116
117Additionally, the CPU device's default SIMD width can be overriden at run time
118with the `OPENVKL_CPU_DEVICE_DEFAULT_WIDTH` environment variable. Legal values
119are 4, 8, or 16. This setting is only applicable when the generic `cpu` device
120is instantiated; if a specific width is requested via the `cpu_[4,8,16]` device
121names then the environment variable is ignored.
122
123### Error handling and log messages
124
125The following errors are currently used by Open VKL:
126
127  ---------------------- -------------------------------------------------------
128  Name                   Description
129  ---------------------- -------------------------------------------------------
130  VKL_NO_ERROR           no error occurred
131
132  VKL_UNKNOWN_ERROR      an unknown error occurred
133
134  VKL_INVALID_ARGUMENT   an invalid argument was specified
135
136  VKL_INVALID_OPERATION  the operation is not allowed for the specified object
137
138  VKL_OUT_OF_MEMORY      there is not enough memory to execute the command
139
140  VKL_UNSUPPORTED_CPU    the CPU is not supported (minimum ISA is SSE4.1)
141  ---------------------- -------------------------------------------------------
142  : Possible error codes, i.e., valid named constants of type `VKLError`.
143
144These error codes are either directly returned by some API functions, or are
145recorded to be later queried by the application via
146
147    VKLError vklDeviceGetLastErrorCode(VKLDevice);
148
149A more descriptive error message can be queried by calling
150
151    const char* vklDeviceGetLastErrorMsg(VKLDevice);
152
153Alternatively, the application can also register a callback function of type
154
155    typedef void (*VKLErrorCallback)(void *, VKLError, const char* message);
156
157via
158
159    void vklDeviceSetErrorCallback(VKLDevice, VKLErrorFunc, void *);
160
161to get notified when errors occur. Applications may be interested in messages
162which Open VKL emits, whether for debugging or logging events. Applications can
163register a callback function of type
164
165    typedef void (*VKLLogCallback)(void *, const char* message);
166
167via
168
169    void vklDeviceSetLogCallback(VKLDevice, VKLLogCallback, void *);
170
171which Open VKL will use to emit log messages. Applications can clear either
172callback by passing `nullptr` instead of an actual function pointer. By default,
173Open VKL uses `cout` and `cerr` to emit log and error messages, respectively.
174The last parameter to `vklDeviceSetErrorCallback` and `vklDeviceSetLogCallback`
175is a user data pointer. Open VKL passes this pointer to the callback functions as
176the first parameter.
177Note that in addition to setting the above callbacks, this behavior can be
178changed via the device parameters and environment variables described
179previously.
180
181Basic data types
182----------------
183
184Open VKL defines 3-component vectors of integer and vector types:
185
186    typedef struct
187    {
188      int x, y, z;
189    } vkl_vec3i;
190
191    typedef struct
192    {
193      float x, y, z;
194    } vkl_vec3f;
195
196Vector versions of these are also defined in structure-of-array format for 4, 8,
197and 16 wide types.
198
199      typedef struct
200      {
201        float x[WIDTH];
202        float y[WIDTH];
203        float z[WIDTH];
204      } vkl_vvec3f##WIDTH;
205
206      typedef struct
207      {
208        float lower[WIDTH], upper[WIDTH];
209      } vkl_vrange1f##WIDTH;
210
2111-D range and 3-D ranges are defined as ranges and boxes, with no vector
212versions:
213
214    typedef struct
215    {
216      float lower, upper;
217    } vkl_range1f;
218
219    typedef struct
220    {
221      vkl_vec3f lower, upper;
222    } vkl_box3f;
223
224Object model
225------------
226
227Objects in Open VKL are exposed to the APIs as handles with internal reference
228counting for lifetime determination.  Objects are created with particular type's
229`vklNew...` API entry point. For example, `vklNewData` and `vklNewVolume`.
230
231In general, modifiable parameters to objects are modified using `vklSet...`
232functions based on the type of the parameter being set. The parameter name is
233passed as a string. Below are all variants of `vklSet...`.
234
235    void vklSetBool(VKLObject object, const char *name, int b);
236    void vklSetFloat(VKLObject object, const char *name, float x);
237    void vklSetVec3f(VKLObject object, const char *name, float x, float y, float z);
238    void vklSetInt(VKLObject object, const char *name, int x);
239    void vklSetVec3i(VKLObject object, const char *name, int x, int y, int z);
240    void vklSetData(VKLObject object, const char *name, VKLData data);
241    void vklSetString(VKLObject object, const char *name, const char *s);
242    void vklSetVoidPtr(VKLObject object, const char *name, void *v);
243
244After parameters have been set, `vklCommit` must be called on the object to make
245them take effect.
246
247Open VKL uses reference counting to manage the lifetime of all objects.
248Therefore one cannot explicitly "delete" any object.  Instead, one can indicate
249the application does not need or will not access the given object anymore by
250calling
251
252    void vklRelease(VKLObject);
253
254This decreases the object's reference count. If the count reaches `0` the
255object will automatically be deleted.
256
257Managed data
258------------
259
260Large data is passed to Open VKL via a `VKLData` handle created with
261`vklNewData`:
262
263    VKLData vklNewData(VKLDevice device,
264                       size_t numItems,
265                       VKLDataType dataType,
266                       const void *source,
267                       VKLDataCreationFlags dataCreationFlags,
268                       size_t byteStride);
269
270Data objects can be created as Open VKL owned (`dataCreationFlags =
271VKL_DATA_DEFAULT`), in which the library will make a copy of the data for its
272use, or shared (`dataCreationFlags = VKL_DATA_SHARED_BUFFER`), which will try
273to use the passed pointer for usage.  The library is allowed to copy data when
274a volume is committed.
275
276The distance between consecutive elements in `source` is given in bytes with
277`byteStride`. If the provided `byteStride` is zero, then it will be determined
278automatically as `sizeof(type)`. Open VKL owned data will be compacted into a
279naturally-strided array on copy, regardless of the original `byteStride`.
280
281As with other object types, when data objects are no longer needed they should
282be released via `vklRelease`.
283
284The enum type `VKLDataType` describes the different element types that can be
285represented in Open VKL. The types accepted vary per volume; see the volume
286section for specifics. Valid constants are listed in the table below.
287
288  -------------------------- ---------------------------------------------------
289  Type/Name                  Description
290  -------------------------- ---------------------------------------------------
291  VKL_DEVICE                 API device object reference
292
293  VKL_DATA                   data reference
294
295  VKL_OBJECT                 generic object reference
296
297  VKL_VOLUME                 volume object reference
298
299  VKL_STRING                 C-style zero-terminated character string
300
301  VKL_CHAR, VKL_VEC[234]C    8\ bit signed character scalar and [234]-element
302                             vector
303
304  VKL_UCHAR, VKL_VEC[234]UC  8\ bit unsigned character scalar and [234]-element
305                             vector
306
307  VKL_SHORT, VKL_VEC[234]S   16\ bit unsigned integer scalar and [234]-element
308                             vector
309
310  VKL_USHORT, VKL_VEC[234]US 16\ bit unsigned integer scalar and [234]-element
311                             vector
312
313  VKL_INT, VKL_VEC[234]I     32\ bit signed integer scalar and [234]-element
314                             vector
315
316  VKL_UINT, VKL_VEC[234]UI   32\ bit unsigned integer scalar and [234]-element
317                             vector
318
319  VKL_LONG, VKL_VEC[234]L    64\ bit signed integer scalar and [234]-element
320                             vector
321
322  VKL_ULONG, VKL_VEC[234]UL  64\ bit unsigned integer scalar and [234]-element
323                             vector
324
325  VKL_HALF, VKL_VEC[234]H    16\ bit half precision floating-point scalar and
326                             [234]-element vector (IEEE 754 `binary16`)
327
328  VKL_FLOAT, VKL_VEC[234]F   32\ bit single precision floating-point scalar and
329                             [234]-element vector
330
331  VKL_DOUBLE, VKL_VEC[234]D  64\ bit double precision floating-point scalar and
332                             [234]-element vector
333
334  VKL_BOX[1234]I             32\ bit integer box (lower + upper bounds)
335
336  VKL_BOX[1234]F             32\ bit single precision floating-point box (lower
337                             + upper bounds)
338
339  VKL_LINEAR[23]F            32\ bit single precision floating-point linear
340                             transform ([23] vectors)
341
342  VKL_AFFINE[23]F            32\ bit single precision floating-point affine
343                             transform (linear transform plus translation)
344
345  VKL_VOID_PTR               raw memory address
346  -------------------------- ---------------------------------------------------
347  : Valid named constants for `VKLDataType`.
348
349Observers
350---------
351
352Volumes and samplers in Open VKL may provide observers to communicate data back
353to the application. Observers may be created with
354
355    VKLObserver vklNewSamplerObserver(VKLSampler sampler,
356                                      const char *type);
357
358    VKLObserver vklNewVolumeObserver(VKLVolume volume,
359                                     const char *type);
360
361The object passed to `vklNew*Observer` must already be committed.  Valid
362observer type strings are defined by volume implementations (see section
363'Volume types' below).
364
365`vklNew*Observer` returns `NULL` on failure.
366
367To access the underlying data, an observer must first be mapped using
368
369    const void * vklMapObserver(VKLObserver observer);
370
371If this fails, the function returns `NULL`. `vklMapObserver` may fail on
372observers that are already mapped.
373On success, the application may query the underlying type, element size in
374bytes, and the number of elements in the buffer using
375
376    VKLDataType vklGetObserverElementType(VKLObserver observer);
377    size_t vklGetObserverElementSize(VKLObserver observer);
378    size_t vklGetObserverNumElements(VKLObserver observer);
379
380On failure, these functions return `VKL_UNKNOWN` and `0`, respectively.
381Possible data types are defined by the volume that provides the observer , as
382are the semantics of the observation. See section 'Volume types' for details.
383
384The pointer returned by `vklMapObserver` may be cast to the type corresponding
385to the value returned by `vklGetObserverElementType` to access the observation.
386For example, if `vklGetObserverElementType` returns `VKL_FLOAT`, then
387the pointer returned by `vklMapObserver` may be cast to `const float *` to access
388up to `vklGetObserverNumElements` consecutive values of type `float`.
389
390Once the application has finished processing the observation, it should unmap
391the observer using
392
393    void vklUnmapObserver(VKLObserver observer);
394
395so that the observer may be mapped again.
396
397When an observer is no longer needed, it should be released using `vklRelease`.
398
399The observer API is not thread safe, and these functions should not
400be called concurrently on the same object.
401
402
403Volume types
404------------
405
406Open VKL currently supports structured volumes on regular and spherical grids;
407unstructured volumes with tetrahedral, wedge, pyramid, and hexaderal primitive
408types; adaptive mesh refinement (AMR) volumes; sparse VDB volumes; and particle
409volumes.  Volumes are created with `vklNewVolume` with a device and appropriate
410type string:
411
412    VKLVolume vklNewVolume(VKLDevice device, const char *type);
413
414In addition to the usual `vklSet...()` and `vklCommit()` APIs, the volume
415bounding box can be queried:
416
417    vkl_box3f vklGetBoundingBox(VKLVolume volume);
418
419The number of attributes in a volume can also be queried:
420
421    unsigned int vklGetNumAttributes(VKLVolume volume);
422
423Finally, the value range of the volume for a given attribute can be queried:
424
425    vkl_range1f vklGetValueRange(VKLVolume volume, unsigned int attributeIndex);
426
427### Structured Volumes
428
429Structured volumes only need to store the values of the samples, because their
430addresses in memory can be easily computed from a 3D position. The dimensions
431for all structured volume types are in units of vertices, not cells. For
432example, a volume with dimensions $(x, y, z)$ will have $(x-1, y-1, z-1)$ cells
433in each dimension. Voxel data provided is assumed vertex-centered, so $x*y*z$
434values must be provided.
435
436#### Structured Regular Volumes
437
438A common type of structured volumes are regular grids, which are
439created by passing a type string of `"structuredRegular"` to `vklNewVolume`.
440The parameters understood by structured regular volumes are summarized in the
441table below.
442
443  --------- -------------------------------- -----------------------------  ---------------------------------------
444  Type      Name                             Default                        Description
445  --------- -------------------------------- -----------------------------  ---------------------------------------
446  vec3i     dimensions                                                      number of voxels in each
447                                                                            dimension $(x, y, z)$
448
449  VKLData   data                                                            VKLData object(s) of voxel data,
450  VKLData[]                                                                 supported types are:
451
452                                                                            `VKL_UCHAR`
453
454                                                                            `VKL_SHORT`
455
456                                                                            `VKL_USHORT`
457
458                                                                            `VKL_HALF`
459
460                                                                            `VKL_FLOAT`
461
462                                                                            `VKL_DOUBLE`
463
464                                                                            Multiple attributes are supported
465                                                                            through passing an array of VKLData
466                                                                            objects.
467
468  vec3f     gridOrigin                       $(0, 0, 0)$                    origin of the grid in object space
469
470  vec3f     gridSpacing                      $(1, 1, 1)$                    size of the grid cells in object space
471
472  uint32    temporalFormat                   `VKL_TEMPORAL_FORMAT_CONSTANT` The temporal format for this volume.
473                                                                            Use `VKLTemporalFormat` for named
474                                                                            constants.
475                                                                            Structured regular volumes support
476                                                                            `VKL_TEMPORAL_FORMAT_CONSTANT`,
477                                                                            `VKL_TEMPORAL_FORMAT_STRUCTURED`, and
478                                                                            `VKL_TEMPORAL_FORMAT_UNSTRUCTURED`.
479
480  int       temporallyStructuredNumTimesteps                                For temporally structured variation,
481                                                                            number of timesteps per voxel. Only
482                                                                            valid if `temporalFormat` is
483                                                                            `VKL_TEMPORAL_FORMAT_STRUCTURED`.
484
485  uint32[]  temporallyUnstructuredIndices                                   For temporally unstructured variation,
486  uint64[]                                                                  indices to `data` time series beginning
487                                                                            per voxel.
488                                                                            Only valid if `temporalFormat` is
489                                                                            `VKL_TEMPORAL_FORMAT_UNSTRUCTURED`.
490
491  float[]   temporallyUnstructuredTimes                                     For temporally unstructured variation,
492                                                                            time values corresponding to values in
493                                                                            `data`.
494                                                                            Only valid if `temporalFormat` is
495                                                                            `VKL_TEMPORAL_FORMAT_UNSTRUCTURED`.
496
497  float[]   background                       `VKL_BACKGROUND_UNDEFINED`     For each attribute, the value that is
498                                                                            returned when sampling an undefined
499                                                                            region outside the volume domain.
500  --------- -------------------------------- -----------------------------  ---------------------------------------
501  : Configuration parameters for structured regular (`"structuredRegular"`) volumes.
502
503Structured regular volumes support temporally structured and temporally
504unstructured temporal variation. See section 'Temporal Variation' for more
505detail.
506
507The following additional parameters can be set both on `"structuredRegular"`
508volumes and their sampler objects. Sampler object parameters default to volume
509parameters.
510
511  ------------  ----------------  ---------------------- ---------------------------------------
512  Type          Name              Default                Description
513  ------------  ----------------  ---------------------- ---------------------------------------
514  int           filter            `VKL_FILTER_TRILINEAR` The filter used for reconstructing the
515                                                         field. Use `VKLFilter` for named
516                                                         constants.
517
518  int           gradientFilter    `filter`               The filter used for reconstructing the
519                                                         field during gradient computations.
520                                                         Use `VKLFilter` for named constants.
521  ------------  ----------------  ---------------------- ---------------------------------------
522  : Configuration parameters for structured regular (`"structuredRegular"`) volumes and their sampler objects.
523
524
525##### Reconstruction filters
526
527Structured regular volumes support the filter types `VKL_FILTER_NEAREST`,
528`VKL_FILTER_TRILINEAR`, and `VKL_FILTER_TRICUBIC` for both `filter` and
529`gradientFilter`.
530
531Note that when `gradientFilter` is set to `VKL_FILTER_NEAREST`, gradients are
532always $(0, 0, 0)$.
533
534#### Structured Spherical Volumes
535
536Structured spherical volumes are also supported, which are created by passing a
537type string of `"structuredSpherical"` to `vklNewVolume`. The grid dimensions
538and parameters are defined in terms of radial distance ($r$), inclination angle
539($\theta$), and azimuthal angle ($\phi$), conforming with the ISO convention for
540spherical coordinate systems. The coordinate system and parameters understood by
541structured spherical volumes are summarized below.
542
543![Structured spherical volume coordinate system: radial distance ($r$), inclination angle ($\theta$), and azimuthal angle ($\phi$).][imgStructuredSphericalCoords]
544
545  --------- ----------------------- -------------------------- -----------------------------------
546  Type      Name                        Default                Description
547  --------- ----------------------- -------------------------- -----------------------------------
548  vec3i     dimensions                                         number of voxels in each
549                                                               dimension $(r, \theta, \phi)$
550
551  VKLData   data                                               VKLData object(s) of voxel data,
552  VKLData[]                                                    supported types are:
553
554                                                               `VKL_UCHAR`
555
556                                                               `VKL_SHORT`
557
558                                                               `VKL_USHORT`
559
560                                                               `VKL_HALF`
561
562                                                               `VKL_FLOAT`
563
564                                                               `VKL_DOUBLE`
565
566                                                               Multiple attributes are supported
567                                                               through passing an array of VKLData
568                                                               objects.
569
570  vec3f     gridOrigin              $(0, 0, 0)$                origin of the grid in units of
571                                                               $(r, \theta, \phi)$; angles in degrees
572
573  vec3f     gridSpacing             $(1, 1, 1)$                size of the grid cells in units of
574                                                               $(r, \theta, \phi)$; angles in degrees
575
576  float[]   background              `VKL_BACKGROUND_UNDEFINED` For each attribute, the value that is
577                                                               returned when sampling an undefined
578                                                               region outside the volume domain.
579  --------- ----------------------- -------------------------- -----------------------------------
580  : Configuration parameters for structured spherical (`"structuredSpherical"`) volumes.
581
582These grid parameters support flexible specification of spheres, hemispheres,
583spherical shells, spherical wedges, and so forth. The grid extents (computed as
584$[gridOrigin, gridOrigin + (dimensions - 1) * gridSpacing]$) however must be
585constrained such that:
586
587  * $r \geq 0$
588  * $0 \leq \theta \leq 180$
589  * $0 \leq \phi \leq 360$
590
591The following additional parameters can be set both on `"structuredSpherical"`
592volumes and their sampler objects. Sampler object parameters default to volume
593parameters.
594
595  ------------  ----------------  ---------------------- ---------------------------------------
596  Type          Name              Default                Description
597  ------------  ----------------  ---------------------- ---------------------------------------
598  int           filter            `VKL_FILTER_TRILINEAR` The filter used for reconstructing the
599                                                         field. Use `VKLFilter` for named
600                                                         constants.
601
602  int           gradientFilter    `filter`               The filter used for reconstructing the
603                                                         field during gradient computations.
604                                                         Use `VKLFilter` for named constants.
605  ------------  ----------------  ---------------------- ---------------------------------------
606  : Configuration parameters for structured spherical (`"structuredSpherical"`) volumes and their sampler objects.
607
608
609### Adaptive Mesh Refinement (AMR) Volumes
610
611Open VKL currently supports block-structured (Berger-Colella) AMR volumes.
612Volumes are specified as a list of blocks, which exist at levels of refinement
613in potentially overlapping regions.  Blocks exist in a tree structure, with
614coarser refinement level blocks containing finer blocks.  The cell width is
615equal for all blocks at the same refinement level, though blocks at a coarser
616level have a larger cell width than finer levels.
617
618There can be any number of refinement levels and any number of blocks at any
619level of refinement.
620
621Blocks are defined by three parameters: their bounds, the refinement level in
622which they reside, and the scalar data contained within each block.
623
624Note that cell widths are defined _per refinement level_, not per block.
625
626AMR volumes are created by passing the type string `"amr"` to `vklNewVolume`,
627and have the following parameters:
628
629  -------------- --------------------- -------------------------- -----------------------------------
630  Type           Name                  Default                    Description
631  -------------- --------------------- -------------------------- -----------------------------------
632  float[]        cellWidth                                        [data] array of each level's cell width
633
634  box3i[]        block.bounds                                     [data] array of each block's bounds (in voxels)
635
636  int[]          block.level                                      [data] array of each block's refinement level
637
638  VKLData[]      block.data                                       [data] array of each block's VKLData object
639                                                                  containing the actual scalar voxel data.
640                                                                  Currently only `VKL_FLOAT` data is supported.
641
642  vec3f          gridOrigin            $(0, 0, 0)$                origin of the grid in object space
643
644  vec3f          gridSpacing           $(1, 1, 1)$                size of the grid cells in object
645                                                                  space
646
647  float          background            `VKL_BACKGROUND_UNDEFINED` The value that is returned when sampling an
648                                                                  undefined region outside the volume domain.
649  -------------- --------------------- -------------------------- -----------------------------------
650  : Configuration parameters for AMR (`"amr"`) volumes.
651
652Note that the `gridOrigin` and `gridSpacing` parameters act just like the
653structured volume equivalent, but they only modify the root (coarsest level) of
654refinement.
655
656The following additional parameters can be set both on `"amr"`
657volumes and their sampler objects. Sampler object parameters default to volume
658parameters.
659
660  -------------- ------------------ -----------------  -------------------------------------
661  Type           Name                         Default  Description
662  -------------- ------------------ -----------------  -------------------------------------
663  `VKLAMRMethod` method             `VKL_AMR_CURRENT`  `VKLAMRMethod` sampling method.
664                                                       Supported methods are:
665
666                                                       `VKL_AMR_CURRENT`
667
668                                                       `VKL_AMR_FINEST`
669
670                                                       `VKL_AMR_OCTANT`
671  -------------- ------------------ -----------------  -------------------------------------
672  : Configuration parameters for AMR (`"AMR"`) volumes and their sampler objects.
673
674Open VKL's AMR implementation was designed to cover Berger-Colella [1] and
675Chombo [2] AMR data.  The `method` parameter above determines the interpolation
676method used when sampling the volume.
677
678* `VKL_AMR_CURRENT` finds the finest refinement level at that cell and
679  interpolates through this "current" level
680* `VKL_AMR_FINEST` will interpolate at the closest existing cell in the
681  volume-wide finest refinement level regardless of the sample cell's level
682* `VKL_AMR_OCTANT` interpolates through all available refinement levels at that
683  cell. This method avoids discontinuities at refinement level boundaries at
684  the cost of performance
685
686Gradients are computed using finite differences, using the `method` defined on
687the sampler.
688
689Details and more information can be found in the publication for the
690implementation [3].
691
6921. M. J. Berger, and P. Colella. "Local adaptive mesh refinement for
693   shock hydrodynamics." Journal of Computational Physics 82.1 (1989): 64-84.
694   DOI: 10.1016/0021-9991(89)90035-1
6952. M. Adams, P. Colella, D. T. Graves, J.N. Johnson, N.D. Keen, T. J. Ligocki.
696   D. F. Martin. P.W. McCorquodale, D. Modiano. P.O. Schwartz, T.D. Sternberg
697   and B. Van Straalen, Chombo Software Package for AMR Applications - Design
698   Document,  Lawrence Berkeley National Laboratory Technical Report
699   LBNL-6616E.
7003. I. Wald, C. Brownlee, W. Usher, and A. Knoll. CPU volume rendering of
701   adaptive mesh refinement data. SIGGRAPH Asia 2017 Symposium on Visualization
702   on - SA ’17, 18(8), 1–8. DOI: 10.1145/3139295.3139305
703
704### Unstructured Volumes
705
706Unstructured volumes can have their topology and geometry freely defined.
707Geometry can be composed of tetrahedral, hexahedral, wedge or pyramid cell
708types. The data format used is compatible with VTK and consists of multiple
709arrays: vertex positions and values, vertex indices, cell start indices, cell
710types, and cell values.
711
712Sampled cell values can be specified either per-vertex (`vertex.data`) or
713per-cell (`cell.data`). If both arrays are set, `cell.data` takes precedence.
714
715Similar to a mesh, each cell is formed by a group of indices into the vertices.
716For each vertex, the corresponding (by array index) data value will be used for
717sampling when rendering, if specified. The index order for a tetrahedron is the
718same as `VTK_TETRA`: bottom triangle counterclockwise, then the top vertex.
719
720For hexahedral cells, each hexahedron is formed by a group of eight indices into
721the vertices and data values. Vertex ordering is the same as `VTK_HEXAHEDRON`:
722four bottom vertices counterclockwise, then top four counterclockwise.
723
724For wedge cells, each wedge is formed by a group of six indices into the
725vertices and data values. Vertex ordering is the same as `VTK_WEDGE`: three
726bottom vertices counterclockwise, then top three counterclockwise.
727
728For pyramid cells, each cell is formed by a group of five indices into the
729vertices and data values. Vertex ordering is the same as `VTK_PYRAMID`: four
730bottom vertices counterclockwise, then the top vertex.
731
732To maintain VTK data compatibility, the `index` array may be specified with cell
733sizes interleaved with vertex indices in the following format: $n, id_1, ...,
734id_n, m, id_1, ..., id_m$. This alternative `index` array layout can be enabled
735through the `indexPrefixed` flag (in which case, the `cell.type` parameter
736should be omitted).
737
738Gradients are computed using finite differences.
739
740Unstructured volumes are created by passing the type string `"unstructured"` to
741`vklNewVolume`, and have the following parameters:
742
743  -------------------  --------------------  -------------------------  ---------------------------------------
744  Type                 Name                  Default                    Description
745  -------------------  --------------------  -------------------------  ---------------------------------------
746  vec3f[]              vertex.position                                  [data] array of vertex positions
747
748  float[]              vertex.data                                      [data] array of vertex data values to
749                                                                        be sampled
750
751  uint32[] / uint64[]  index                                            [data] array of indices (into the
752                                                                        vertex array(s)) that form cells
753
754  bool                 indexPrefixed         false                      indicates that the `index` array is
755                                                                        provided in a VTK-compatible format,
756                                                                        where the indices of each cell are
757                                                                        prefixed with the number of vertices
758
759  uint32[] / uint64[]  cell.index                                       [data] array of locations (into the
760                                                                        index array), specifying the first index
761                                                                        of each cell
762
763  float[]              cell.data                                        [data] array of cell data values to be
764                                                                        sampled
765
766  uint8[]              cell.type                                        [data] array of cell types
767                                                                        (VTK compatible). Supported types are:
768
769                                                                        `VKL_TETRAHEDRON`
770
771                                                                        `VKL_HEXAHEDRON`
772
773                                                                        `VKL_WEDGE`
774
775                                                                        `VKL_PYRAMID`
776
777  bool                 hexIterative          false                      hexahedron
778                                                                        interpolation method, defaults to fast
779                                                                        non-iterative version which could have
780                                                                        rendering inaccuracies may appear
781                                                                        if hex is not parallelepiped
782
783  bool                 precomputedNormals    false                      whether to accelerate by precomputing,
784                                                                        at a cost of 12 bytes/face
785
786  float                background            `VKL_BACKGROUND_UNDEFINED` The value that is returned when
787                                                                        sampling an undefined region outside
788                                                                        the volume domain.
789  -------------------  --------------------  -------------------------  ---------------------------------------
790  : Configuration parameters for unstructured (`"unstructured"`) volumes.
791
792### VDB Volumes
793
794VDB volumes implement a data structure that is very similar to the data structure
795outlined in Museth [1].
796
797The data structure is a hierarchical regular grid at its core: Nodes are regular grids,
798and each grid cell may either store a constant value (this is called a tile), or
799child pointers.
800
801Nodes in VDB trees are wide: Nodes on the first level have a resolution of 32^3 voxels
802by default, on the next level 16^3, and on the leaf level 8^3 voxels. All nodes
803on a given level have the same resolution. This makes it easy to find the node
804containing a coordinate using shift operations (cp. [1]).
805
806VDB leaf nodes are implicit in Open VKL: they are stored as pointers to user-provided data.
807
808![Structure of `"vdb"` volumes in the default configuration][imgVdbStructure]
809
810VDB volumes interpret input data as constant cells (which are then potentially filtered).
811This is in contrast to `structuredRegular` volumes, which have a vertex-centered
812interpretation.
813
814The VDB implementation in Open VKL follows the following goals:
815
816  - Efficient data structure traversal on vector architectures.
817
818  - Enable the use of industry-standard .vdb files created through the OpenVDB library.
819
820  - Compatibility with OpenVDB on a leaf data level, so that .vdb files may be loaded
821    with minimal overhead.
822
823VDB volumes are created by passing the type string `"vdb"` to `vklNewVolume`, and have the
824following parameters:
825
826  ------------  -------------------------------------  ------------------------------  ---------------------------------------
827  Type          Name                                   Default                         Description
828  ------------  -------------------------------------  ------------------------------  ---------------------------------------
829  float[]       indexToObject                          1, 0, 0,                        An array of 12 values of type `float`
830                                                       0, 1, 0,                        that define the transformation from
831                                                       0, 0, 1,                        index space to object space.
832                                                       0, 0, 0                         In index space, the grid is an
833                                                                                       axis-aligned regular grid, and leaf
834                                                                                       voxels have size (1,1,1).
835                                                                                       The first 9 values are interpreted
836                                                                                       as a row-major linear transformation
837                                                                                       matrix. The last 3 values are the
838                                                                                       translation of the grid origin.
839
840  uint32[]      node.format                                                            For each input node, the data format.
841                                                                                       Currently supported are
842                                                                                       `VKL_FORMAT_TILE` for tiles,
843                                                                                       and `VKL_FORMAT_DENSE_ZYX` for
844                                                                                       nodes that are dense regular grids.
845
846  uint32[]      node.level                                                             For each input node, the level on
847                                                                                       which this node exists. Tiles may exist
848                                                                                       on levels [1, `VKL_VDB_NUM_LEVELS-1`],
849                                                                                       all other nodes may only exist on level
850                                                                                       `VKL_VDB_NUM_LEVELS-1`.
851
852  vec3i[]       node.origin                                                            For each input node, the node origin
853                                                                                       index.
854
855  VKLData[]     node.data                                                              For each input node, the attribute
856                                                                                       data. Single-attribute volumes may have
857                                                                                       one array provided per node, while
858                                                                                       multi-attribute volumes require an
859                                                                                       array per attribute for each node.
860                                                                                       Nodes with format `VKL_FORMAT_TILE` are
861                                                                                       expected to have single-entry arrays
862                                                                                       per attribute. Nodes with format
863                                                                                       `VKL_FORMAT_DENSE_ZYX` are expected
864                                                                                       to have arrays with
865                                                                                       `vklVdbLevelNumVoxels(level[i])`
866                                                                                       entries per attribute. `VKL_HALF` and
867                                                                                       `VKL_FLOAT` data is currently
868                                                                                       supported; all nodes for a given
869                                                                                       attribute must be the same data type.
870
871  uint32[]      node.temporalFormat                    `VKL_TEMPORAL_FORMAT_CONSTANT`  The temporal format for this volume.
872                                                                                       Use `VKLTemporalFormat` for named
873                                                                                       constants.
874                                                                                       VDB volumes support
875                                                                                       `VKL_TEMPORAL_FORMAT_CONSTANT`,
876                                                                                       `VKL_TEMPORAL_FORMAT_STRUCTURED`, and
877                                                                                       `VKL_TEMPORAL_FORMAT_UNSTRUCTURED`.
878
879  int[]         node.temporallyStructuredNumTimesteps                                  For temporally structured variation,
880                                                                                       number of timesteps per voxel. Only
881                                                                                       valid if `temporalFormat` is
882                                                                                       `VKL_TEMPORAL_FORMAT_STRUCTURED`.
883
884  VKLData[]     node.temporallyUnstructuredIndices                                     For temporally unstructured variation,
885                                                                                       beginning per voxel. Supported data
886                                                                                       types for each node are `VKL_UINT`
887                                                                                       and `VKL_ULONG`.
888                                                                                       Only valid if `temporalFormat` is
889                                                                                       `VKL_TEMPORAL_FORMAT_UNSTRUCTURED`.
890
891  VKLData[]     node.temporallyUnstructuredTimes                                       For temporally unstructured variation,
892                                                                                       time values corresponding to values in
893                                                                                       `node.data`. For each node, the data
894                                                                                       must be of type `VKL_FLOAT`.
895                                                                                       Only valid if `temporalFormat` is
896                                                                                       `VKL_TEMPORAL_FORMAT_UNSTRUCTURED`.
897
898  float[]       background                             `VKL_BACKGROUND_UNDEFINED`      For each attribute, the value that is
899                                                                                       returned when sampling an undefined
900                                                                                       region outside the volume domain.
901  ------------  -------------------------------------  ------------------------------  ---------------------------------------
902  : Configuration parameters for VDB (`"vdb"`) volumes.
903
904The level, origin, format, and data parameters must have the same size, and there must
905be at least one valid node or `commit()` will fail.
906
907VDB volumes support temporally structured and temporally unstructured temporal
908variation. See section 'Temporal Variation' for more detail.
909
910The following additional parameters can be set both on `vdb` volumes and their sampler
911objects (sampler object parameters default to volume parameters).
912
913  ------------  ----------------  ---------------------- ---------------------------------------
914  Type          Name              Default                Description
915  ------------  ----------------  ---------------------- ---------------------------------------
916  int           filter            `VKL_FILTER_TRILINEAR` The filter used for reconstructing the
917                                                         field. Use `VKLFilter` for named
918                                                         constants.
919
920  int           gradientFilter    `filter`               The filter used for reconstructing the
921                                                         field during gradient computations.
922                                                         Use `VKLFilter` for named constants.
923
924  int           maxSamplingDepth  `VKL_VDB_NUM_LEVELS`-1 Do not descend further than to this
925                                                         depth during sampling.
926  ------------  ----------------  ---------------------- ---------------------------------------
927  : Configuration parameters for VDB (`"vdb"`) volumes and their sampler objects.
928
929VDB volume objects support the following observers:
930
931  --------------  -----------  -------------------------------------------------------------
932  Name            Buffer Type  Description
933  --------------  -----------  -------------------------------------------------------------
934  InnerNode       float[]      Return an array of bounding boxes, along with value ranges,
935                               of inner nodes in the data structure. The bounding box is
936                               given in object space.
937                               For a volume with M attributes, the entries in this array
938                               are (6+2*M)-tuples
939                               `(minX, minY, minZ, maxX, maxY, maxZ, lower_0, upper_0,
940                                lower_1, upper_1, ...)`.
941                               This is in effect a low resolution representation of the
942                               volume.
943                               The InnerNode observer can be parametrized using
944                               `int maxDepth` to control the depth at which inner nodes are
945                               returned. Note that the observer will also return leaf nodes
946                               or tiles at lower levels if they exist.
947  --------------  --------------------------------------------------------------------------
948  : Observers supported by VDB (`"vdb"`) volumes.
949
950VDB sampler objects support the following observers:
951
952  --------------  -----------  -------------------------------------------------------------
953  Name            Buffer Type  Description
954  --------------  -----------  -------------------------------------------------------------
955  LeafNodeAccess  uint32[]     This observer returns an array with as many entries as
956                               input nodes were passed. If the input node i was accessed
957                               during traversal, then the ith entry in this array has a
958                               nonzero value.
959                               This can be used for on-demand loading of leaf nodes.
960  --------------  --------------------------------------------------------------------------
961  : Observers supported by sampler objects created on VDB (`"vdb"`) volumes.
962
963#### Reconstruction filters
964
965VDB volumes support the filter types `VKL_FILTER_NEAREST`, `VKL_FILTER_TRILINEAR`,
966and `VKL_FILTER_TRICUBIC` for both `filter` and `gradientFilter`.
967
968Note that when `gradientFilter` is set to `VKL_FILTER_NEAREST`, gradients are
969always $(0, 0, 0)$.
970
971#### Major differences to OpenVDB
972
973  - Open VKL implements sampling in ISPC, and can exploit wide SIMD architectures.
974
975  - VDB volumes in Open VKL are read-only once committed, and designed for rendering only.
976    Authoring or manipulating datasets is not in the scope of this implementation.
977
978  - The only supported field types are `VKL_HALF` and `VKL_FLOAT` at this point.
979    Other field types may be supported in the future. Note that multi-attribute
980    volumes may be used to represent multi-component (e.g. vector) fields.
981
982  - The root level in Open VKL has a single node with resolution 64^3 (cp. [1]. OpenVDB
983    uses a hash map, instead).
984
985  - Open VKL supports four-level vdb volumes. The resolution of each level
986    can be configured at compile time using CMake variables.
987    * `VKL_VDB_LOG_RESOLUTION_0` sets the base 2 logarithm of the root level
988      resolution. This variable defaults to 6, which means that the root level
989      has a resolution of $(2^6)^3 = 64^3$.
990    * `VKL_VDB_LOG_RESOLUTION_1` and `VKL_VDB_LOG_RESOLUTION_2` default to
991      5 and 4, respectively. This matches the default Open VDB resolution for
992      inner levels.
993    * `VKL_VDB_LOG_RESOLUTION_3` set the base 2 logarithm of the leaf level
994      resolution, and defaults to 3. Therefore, leaf nodes have a resolution
995      of $8^3$ voxels. Again, this matches the Open VDB default.
996    The default settings lead to a domain resolution of $2^18^3=262144^3$ voxels.
997
998
999#### Loading OpenVDB .vdb files
1000
1001Files generated with OpenVDB can be loaded easily since Open VKL `vdb` volumes
1002implement the same leaf data layout. This means that OpenVDB leaf data pointers
1003can be passed to Open VKL using shared data buffers, avoiding copy operations.
1004
1005An example of this can be found in
1006`utility/vdb/include/openvkl/utility/vdb/OpenVdbGrid.h`, where the class
1007`OpenVdbFloatGrid` encapsulates the necessary operations. This class is also
1008accessible through the `vklExamples` application using the `-file` and `-field`
1009command line arguments.
1010
1011To use this example feature, compile Open VKL with `OpenVDB_ROOT` pointing to
1012the OpenVDB prefix.
1013
1014
10151. Museth, K. VDB: High-Resolution Sparse Volumes with Dynamic Topology.
1016   ACM Transactions on Graphics 32(3), 2013. DOI: 10.1145/2487228.2487235
1017
1018
1019### Particle Volumes
1020
1021Particle volumes consist of a set of points in space. Each point has a position,
1022a radius, and a weight typically associated with an attribute. A radial basis
1023function defines the contribution of that particle. Currently, we use the
1024Gaussian radial basis function,
1025
1026phi(P) = w * exp( -0.5 * ((P - p) / r)^2 )
1027
1028where P is the particle position, p is the sample position, r is the radius and
1029w is the weight.
1030
1031At each sample, the scalar field value is then computed as the sum of each
1032radial basis function phi, for each particle that overlaps it. Gradients are
1033similarly computed, based on the summed analytical contributions of each
1034contributing particle.
1035
1036The Open VKL implementation is similar to direct evaluation of samples in Reda
1037et al.[2]. It uses an Embree-built BVH with a custom traversal, similar to the
1038method in [1].
1039
1040Particle volumes are created by passing the type string `"particle"` to
1041`vklNewVolume`, and have the following parameters:
1042
1043  --------  --------------------------  --------  ---------------------------------------
1044  Type      Name                        Default   Description
1045  --------  --------------------------  --------  ---------------------------------------
1046  vec3f[]   particle.position                     [data] array of particle positions
1047
1048  float[]   particle.radius                       [data] array of particle radii
1049
1050  float[]   particle.weight             null      [data] (optional) array of particle
1051                                                  weights, specifying the height of the
1052                                                  kernel.
1053
1054  float     radiusSupportFactor         3.0       The multipler of the particle radius
1055                                                  required for support. Larger radii
1056                                                  ensure smooth results at the cost of
1057                                                  performance. In the Gaussian kernel, the
1058                                                  the radius is one standard deviation
1059                                                  (sigma), so a `radiusSupportFactor` of
1060                                                  3 corresponds to 3*sigma.
1061
1062  float     clampMaxCumulativeValue     0         The maximum cumulative value possible,
1063                                                  set by user. All cumulative values will
1064                                                  be clamped to this, and further
1065                                                  traversal (RBF summation) of particle
1066                                                  contributions will halt when this value
1067                                                  is reached. A value of zero or less
1068                                                  turns this off.
1069
1070  bool      estimateValueRanges         true      Enable heuristic estimation of value
1071                                                  ranges which are used in internal
1072                                                  acceleration structures for interval and
1073                                                  hit iterators, as well as for
1074                                                  determining the volume's overall value
1075                                                  range. When set to `false`, the user
1076                                                  *must* specify
1077                                                  `clampMaxCumulativeValue`, and all value
1078                                                  ranges will be assumed [0,
1079                                                  `clampMaxCumulativeValue`]. Disabling
1080                                                  this may improve volume commit time, but
1081                                                  will make interval and hit iteration
1082                                                  less efficient.
1083  --------  --------------------------  --------  ---------------------------------------
1084  : Configuration parameters for particle (`"particle"`) volumes.
1085
10861. Knoll, A., Wald, I., Navratil, P., Bowen, A., Reda, K., Papka, M.E. and
1087   Gaither, K. (2014), RBF Volume Ray Casting on Multicore and Manycore CPUs.
1088   Computer Graphics Forum, 33: 71-80. doi:10.1111/cgf.12363
1089
10902. K. Reda, A. Knoll, K. Nomura, M. E. Papka, A. E. Johnson and J. Leigh,
1091   "Visualizing large-scale atomistic simulations in ultra-resolution immersive
1092   environments," 2013 IEEE Symposium on Large-Scale Data Analysis and
1093   Visualization (LDAV), Atlanta, GA, 2013, pp. 59-65.
1094
1095Temporal Variation
1096------------------
1097
1098Open VKL supports two types of temporal variation: temporally
1099structured and temporally unstructured. When one of these modes is enabled, the
1100volume can be sampled at different times. In both modes, time is assumed to
1101vary between zero and one. This can be useful for implementing renderers with
1102motion blur, for example.
1103
1104Temporal variation is generally configured through a parameter `temporalFormat`,
1105which accepts constants from the `VKLTemporalFormat` enum, though not all
1106modes may be supported by all volumes. On volumes that expect multiple input
1107nodes, the parameter is an array `node.temporalFormat`, and must provide one
1108value per node.
1109Multiple attributes in a voxel share the same temporal configuration.
1110Please refer to the individual volume sections above to find out supported
1111for each volume type.
1112
1113`temporalFormat` defaults to `VKL_TEMPORAL_FORMAT_CONSTANT` for all volume
1114types. This means that no temporal variation is present in the data.
1115
1116Temporally structured variation is configured by setting `temporalFormat`
1117to `VKL_TEMPORAL_FORMAT_STRUCTURED`. In this mode, the volume expects an
1118additional parameter `[node.]temporallyStructuredNumTimesteps`, which
1119specifies how many time steps are provided for all voxels, and must be at
1120least 2.  A volume, or node, with $N$ voxels expects
1121$N * temporallyStructuredNumTimesteps$ values for each attribute.
1122The values are assumed evenly spaced over times $[0, 1]$:
1123$\{0, 1/(N-1), ..., 1\}$
1124
1125Temporally unstructured variation supports differing time step counts and
1126sample times per voxel.
1127For $N$ input voxels, `temporallyUnstructuredIndices` is an array of $N+1$
1128indices. Voxel $i$ has
1129$N_i = [temporallyUnstructuredIndices[i+1]-temporallyUnstructuredIndices[i])$
1130temporal samples starting at index $temporallyUnstructuredIndices[i]$.
1131`temporallyUnstructuredTimes` specifies the times corresponding to the sample
1132values; the time values for each voxel must be
1133between zero and one and strictly increasing: $t0 < t1 < ... < tN$.  To return
1134a value at sample time t, $t0 <= t <= tN$, Open VKL will interpolate linearly
1135from the two nearest time steps. Time values outside this range are clamped to
1136$[t0, tN]$.
1137
1138Sampler Objects
1139---------------
1140
1141Computing the value of a volume at an object space coordinate is done using the
1142sampling API, and sampler objects. Sampler objects can be created using
1143
1144    VKLSampler vklNewSampler(VKLVolume volume);
1145
1146Sampler objects may then be parametrized with traversal parameters. Available
1147parameters are defined by volumes, and are a subset of the volume parameters.
1148As an example, `filter` can be set on both `vdb` volumes and their sampler objects.
1149The volume parameter is used as the default for sampler objects. The
1150sampler object parameter provides an override per ray.
1151More detail on parameters can be found in the sections on volumes.
1152Use `vklCommit()` to commit parameters to the sampler object.
1153
1154Sampling
1155--------
1156
1157The scalar API takes a volume and coordinate, and returns a float value. The
1158volume's background value (by default `VKL_BACKGROUND_UNDEFINED`) is returned
1159for probe points outside the volume. The attribute index selects the scalar
1160attribute of interest; not all volumes support multiple attributes. The time
1161value, which must be between 0 and 1, specifies the sampling time. For
1162temporally constant volumes, this value has no effect.
1163
1164    float vklComputeSample(VKLSampler sampler,
1165                           const vkl_vec3f *objectCoordinates,
1166                           unsigned int attributeIndex,
1167                           float time);
1168
1169Vector versions allow sampling at 4, 8, or 16 positions at once.  Depending on
1170the machine type and Open VKL device implementation, these can give greater
1171performance.  An active lane mask `valid` is passed in as an array of integers;
1172set 0 for lanes to be ignored, -1 for active lanes. An array of time values
1173corresponding to each object coordinate may be provided; a `NULL` value
1174indicates all times are zero.
1175
1176    void vklComputeSample4(const int *valid,
1177                           VKLSampler sampler,
1178                           const vkl_vvec3f4 *objectCoordinates,
1179                           float *samples,
1180                           unsigned int attributeIndex,
1181                           const float *times);
1182
1183    void vklComputeSample8(const int *valid,
1184                           VKLSampler sampler,
1185                           const vkl_vvec3f8 *objectCoordinates,
1186                           float *samples,
1187                           unsigned int attributeIndex,
1188                           const float *times);
1189
1190    void vklComputeSample16(const int *valid,
1191                            VKLSampler sampler,
1192                            const vkl_vvec3f16 *objectCoordinates,
1193                            float *samples,
1194                            unsigned int attributeIndex,
1195                            const float *times);
1196
1197A stream version allows sampling an arbitrary number of positions at once. While
1198the vector version requires coordinates to be provided in a structure-of-arrays
1199layout, the stream version allows coordinates to be provided in an
1200array-of-structures layout. Thus, the stream API can be used to avoid
1201reformatting of data by the application. As with the vector versions, the stream
1202API can give greater performance than the scalar API.
1203
1204      void vklComputeSampleN(VKLSampler sampler,
1205                             unsigned int N,
1206                             const vkl_vec3f *objectCoordinates,
1207                             float *samples,
1208                             unsigned int attributeIndex,
1209                             const float *times);
1210
1211All of the above sampling APIs can be used, regardless of the device's native
1212SIMD width.
1213
1214### Sampling Multiple Attributes
1215
1216Open VKL provides additional APIs for sampling multiple scalar attributes in a
1217single call through the `vklComputeSampleM*()` interfaces. Beyond convenience,
1218these can give improved performance relative to the single attribute sampling
1219APIs. As with the single attribute APIs, sampling time values may be specified;
1220note that these are provided per object coordinate only (rather than separately
1221per attribute).
1222
1223A scalar API supports sampling `M` attributes specified by `attributeIndices` on
1224a single object space coordinate:
1225
1226    void vklComputeSampleM(VKLSampler sampler,
1227                           const vkl_vec3f *objectCoordinates,
1228                           float *samples,
1229                           unsigned int M,
1230                           const unsigned int *attributeIndices,
1231                           float time);
1232
1233Vector versions allow sampling at 4, 8, or 16 positions at once across the `M`
1234attributes:
1235
1236    void vklComputeSampleM4(const int *valid,
1237                            VKLSampler sampler,
1238                            const vkl_vvec3f4 *objectCoordinates,
1239                            float *samples,
1240                            unsigned int M,
1241                            const unsigned int *attributeIndices,
1242                            const float *times);
1243
1244    void vklComputeSampleM8(const int *valid,
1245                            VKLSampler sampler,
1246                            const vkl_vvec3f8 *objectCoordinates,
1247                            float *samples,
1248                            unsigned int M,
1249                            const unsigned int *attributeIndices,
1250                            const float *times);
1251
1252    void vklComputeSampleM16(const int *valid,
1253                             VKLSampler sampler,
1254                             const vkl_vvec3f16 *objectCoordinates,
1255                             float *samples,
1256                             unsigned int M,
1257                             const unsigned int *attributeIndices,
1258                             const float *times);
1259
1260The `[4, 8, 16] * M` sampled values are populated in the `samples` array in a
1261structure-of-arrays layout, with all values for each attribute provided in
1262sequence. That is, sample values `s_m,n` for the `m`th attribute and `n`th
1263object coordinate will be populated as
1264
1265    samples = [s_0,0,   s_0,1,   ..., s_0,N-1,
1266               s_1,0,   s_1,1,   ..., s_1,N-1,
1267               ...,
1268               s_M-1,0, s_M-1,1, ..., s_M-1,N-1]
1269
1270A stream version allows sampling an arbitrary number of positions at once across
1271the `M` attributes. As with single attribute stream sampling, the `N`
1272coordinates are provided in an array-of-structures layout.
1273
1274    void vklComputeSampleMN(VKLSampler sampler,
1275                            unsigned int N,
1276                            const vkl_vec3f *objectCoordinates,
1277                            float *samples,
1278                            unsigned int M,
1279                            const unsigned int *attributeIndices,
1280                            const float *times);
1281
1282The `M * N` sampled values are populated in the `samples` array in an
1283array-of-structures layout, with all attribute values for each coordinate
1284provided in sequence as
1285
1286    samples = [s_0,0,   s_1,0,   ..., s_M-1,0,
1287               s_0,1,   s_1,1,   ..., s_M-1,1,
1288               ...,
1289               s_0,N-1, s_1,N-1, ..., s_M-1,N-1]
1290
1291All of the above sampling APIs can be used, regardless of the device's native
1292SIMD width.
1293
1294Gradients
1295---------
1296
1297In a very similar API to `vklComputeSample`, `vklComputeGradient` queries the
1298value gradient at an object space coordinate.  Again, a scalar API, now
1299returning a vec3f instead of a float. NaN values are returned for points outside
1300the volume.  The time value, which must be between 0 and 1, specifies the sampling
1301time. For temporally constant volumes, this value has no effect.
1302
1303    vkl_vec3f vklComputeGradient(VKLSampler sampler,
1304                                 const vkl_vec3f *objectCoordinates,
1305                                 unsigned int attributeIndex,
1306                                 float time);
1307
1308Vector versions are also provided:
1309
1310    void vklComputeGradient4(const int *valid,
1311                             VKLSampler sampler,
1312                             const vkl_vvec3f4 *objectCoordinates,
1313                             vkl_vvec3f4 *gradients,
1314                             unsigned int attributeIndex,
1315                             const float *times);
1316
1317    void vklComputeGradient8(const int *valid,
1318                             VKLSampler sampler,
1319                             const vkl_vvec3f8 *objectCoordinates,
1320                             vkl_vvec3f8 *gradients,
1321                             unsigned int attributeIndex,
1322                             const float *times);
1323
1324    void vklComputeGradient16(const int *valid,
1325                              VKLSampler sampler,
1326                              const vkl_vvec3f16 *objectCoordinates,
1327                              vkl_vvec3f16 *gradients,
1328                              unsigned int attributeIndex,
1329                              const float *times);
1330
1331Finally, a stream version is provided:
1332
1333    void vklComputeGradientN(VKLSampler sampler,
1334                             unsigned int N,
1335                             const vkl_vec3f *objectCoordinates,
1336                             vkl_vec3f *gradients,
1337                             unsigned int attributeIndex,
1338                             const float *times);
1339
1340All of the above gradient APIs can be used, regardless of the device's native
1341SIMD width.
1342
1343Iterators
1344---------
1345
1346Open VKL has APIs to search for particular volume values along a ray.  Queries
1347can be for ranges of volume values (`vklIterateInterval`) or for particular
1348values (`vklIterateHit`).
1349
1350Interval iterators require a context object to define the sampler and parameters
1351related to iteration behavior. An interval iterator context is created via
1352
1353    VKLIntervalIteratorContext vklNewIntervalIteratorContext(VKLSampler sampler);
1354
1355The parameters understood by interval iterator contexts are defined in the table
1356below.
1357
1358  -------------- ------------------------- ------------ -------------------------------------
1359  Type           Name                      Default      Description
1360  -------------- ------------------------- ------------ -------------------------------------
1361  int            attributeIndex            0            Defines the volume attribute of
1362                                                        interest.
1363
1364  vkl_range1f[]  valueRanges               [-inf, inf]  Defines the value ranges of interest.
1365                                                        Intervals not containing any of these
1366                                                        values ranges may be skipped during
1367                                                        iteration.
1368
1369  float          intervalResolutionHint    0.5          A value in the range [0, 1] affecting
1370                                                        the resolution (size) of returned
1371                                                        intervals. A value of 0 yields the
1372                                                        lowest resolution (largest) intervals
1373                                                        while 1 gives the highest resolution
1374                                                        (smallest) intervals. This value is
1375                                                        only a hint; it may not impact
1376                                                        behavior for all volume types.
1377  -------------- ------------------------- ------------ -------------------------------------
1378  : Configuration parameters for interval iterator contexts.
1379
1380
1381Most volume types support the `intervalResolutionHint` parameter that can impact
1382the size of intervals returned duration iteration. These include `amr`,
1383`particle`, `structuredRegular`, `unstructured`, and `vdb` volumes. In all cases
1384a value of 1.0 yields the highest resolution (smallest) intervals possible,
1385while a value of 0.0 gives the lowest resolution (largest) intervals. In
1386general, smaller intervals will have tighter bounds on value ranges, and more
1387efficient space skipping behavior than larger intervals, which can be beneficial
1388for some rendering methods.
1389
1390For `structuredRegular`, `unstructured`, and `vdb` volumes, a value of 1.0 will
1391enable elementary cell iteration, such that each interval spans an individual
1392voxel / cell intersection. Note that interval iteration can be significantly
1393slower in this case.
1394
1395As with other objects, the interval iterator context must be committed before
1396being used.
1397
1398To query an interval, a `VKLIntervalIterator` of scalar or vector width must be
1399initialized with `vklInitIntervalIterator`. Time value(s) may be provided to
1400specify the sampling time. These values must be between 0 and 1; for the vector
1401versions, a `NULL` value indicates all times are zero. For temporally constant
1402volumes, the time values have no effect.
1403
1404    VKLIntervalIterator vklInitIntervalIterator(VKLIntervalIteratorContext context,
1405                                                const vkl_vec3f *origin,
1406                                                const vkl_vec3f *direction,
1407                                                const vkl_range1f *tRange,
1408                                                float time,
1409                                                void *buffer);
1410
1411    VKLIntervalIterator4 vklInitIntervalIterator4(const int *valid,
1412                                                  VKLIntervalIteratorContext context,
1413                                                  const vkl_vvec3f4 *origin,
1414                                                  const vkl_vvec3f4 *direction,
1415                                                  const vkl_vrange1f4 *tRange,
1416                                                  const float *times,
1417                                                  void *buffer);
1418
1419    VKLIntervalIterator8 vklInitIntervalIterator8(const int *valid,
1420                                                  VKLIntervalIteratorContext context,
1421                                                  const vkl_vvec3f8 *origin,
1422                                                  const vkl_vvec3f8 *direction,
1423                                                  const vkl_vrange1f8 *tRange,
1424                                                  const float *times,
1425                                                  void *buffer);
1426
1427    VKLIntervalIterator16 vklInitIntervalIterator16(const int *valid,
1428                                                    VKLIntervalIteratorContext context,
1429                                                    const vkl_vvec3f16 *origin,
1430                                                    const vkl_vvec3f16 *direction,
1431                                                    const vkl_vrange1f16 *tRange,
1432                                                    const float *times,
1433                                                    void *buffer);
1434
1435Open VKL places the iterator struct into a user-provided buffer, and the
1436returned handle is essentially a pointer into this buffer. This means that
1437the iterator handle must not be used after the buffer ceases to exist.
1438Copying iterator buffers is currently not supported.
1439
1440The required size, in bytes, of the buffer can be queried with
1441
1442    size_t vklGetIntervalIteratorSize(VKLIntervalIteratorContext context);
1443
1444    size_t vklGetIntervalIteratorSize4(VKLIntervalIteratorContext context);
1445
1446    size_t vklGetIntervalIteratorSize8(VKLIntervalIteratorContext context);
1447
1448    size_t vklGetIntervalIteratorSize16(VKLIntervalIteratorContext context);
1449
1450The values these functions return may change depending on the parameters set
1451on `sampler`.
1452
1453Open VKL also provides a conservative maximum size over all volume types as a
1454preprocessor definition (`VKL_MAX_INTERVAL_ITERATOR_SIZE`). For ISPC use cases,
1455Open VKL will attempt to detect the native vector width using `TARGET_WIDTH`,
1456which is defined in recent versions of ISPC, to provide a less conservative
1457size.
1458
1459Intervals can then be processed by calling `vklIterateInterval` as long as the
1460returned lane masks indicates that the iterator is still within the volume:
1461
1462    int vklIterateInterval(VKLIntervalIterator iterator,
1463                           VKLInterval *interval);
1464
1465    void vklIterateInterval4(const int *valid,
1466                             VKLIntervalIterator4 iterator,
1467                             VKLInterval4 *interval,
1468                             int *result);
1469
1470    void vklIterateInterval8(const int *valid,
1471                             VKLIntervalIterator8 iterator,
1472                             VKLInterval8 *interval,
1473                             int *result);
1474
1475    void vklIterateInterval16(const int *valid,
1476                              VKLIntervalIterator16 iterator,
1477                              VKLInterval16 *interval,
1478                              int *result);
1479
1480The intervals returned have a t-value range, a value range, and a
1481`nominalDeltaT` which is approximately the step size (in units of ray direction)
1482that should be used to walk through the interval, if desired.  The number and
1483length of intervals returned is volume type implementation dependent.  There is
1484currently no way of requesting a particular splitting.
1485
1486    typedef struct
1487    {
1488      vkl_range1f tRange;
1489      vkl_range1f valueRange;
1490      float nominalDeltaT;
1491    } VKLInterval;
1492
1493    typedef struct
1494    {
1495      vkl_vrange1f4 tRange;
1496      vkl_vrange1f4 valueRange;
1497      float nominalDeltaT[4];
1498    } VKLInterval4;
1499
1500    typedef struct
1501    {
1502      vkl_vrange1f8 tRange;
1503      vkl_vrange1f8 valueRange;
1504      float nominalDeltaT[8];
1505    } VKLInterval8;
1506
1507    typedef struct
1508    {
1509      vkl_vrange1f16 tRange;
1510      vkl_vrange1f16 valueRange;
1511      float nominalDeltaT[16];
1512    } VKLInterval16;
1513
1514Querying for particular values is done using a `VKLHitIterator` in much the same
1515fashion.  This API could be used, for example, to find isosurfaces. As with
1516interval iterators, time value(s) may be provided to specify the sampling time.
1517These values must be between 0 and 1; for the vector versions, a `NULL` value
1518indicates all times are zero. For temporally constant volumes, the time values
1519have no effect.
1520
1521Hit iterators similarly require a context object to define the sampler and other
1522iteration parameters. A hit iterator context is created via
1523
1524    VKLHitIteratorContext vklNewHitIteratorContext(VKLSampler sampler);
1525
1526The parameters understood by hit iterator contexts are defined in the table
1527below.
1528
1529  -------------- ---------------- ------------ -------------------------------------
1530  Type           Name             Default      Description
1531  -------------- ---------------- ------------ -------------------------------------
1532  int            attributeIndex   0            Defines the volume attribute of
1533                                               interest.
1534
1535  float[]        values                        Defines the value(s) of interest.
1536  -------------- ---------------- ------------ -------------------------------------
1537  : Configuration parameters for hit iterator contexts.
1538
1539The hit iterator context must be committed before being used.
1540
1541Again, a user allocated buffer must be provided, and a `VKLHitIterator` of the
1542desired width must be initialized:
1543
1544    VKLHitIterator vklInitHitIterator(VKLHitIteratorContext context,
1545                                      const vkl_vec3f *origin,
1546                                      const vkl_vec3f *direction,
1547                                      const vkl_range1f *tRange,
1548                                      float time,
1549                                      void *buffer);
1550
1551    VKLHitIterator4 vklInitHitIterator4(const int *valid,
1552                             VKLHitIteratorContext context,
1553                             const vkl_vvec3f4 *origin,
1554                             const vkl_vvec3f4 *direction,
1555                             const vkl_vrange1f4 *tRange,
1556                             const float *times,
1557                             void *buffer);
1558
1559    VKLHitIterator8 vklInitHitIterator8(const int *valid,
1560                             VKLHitIteratorContext context,
1561                             const vkl_vvec3f8 *origin,
1562                             const vkl_vvec3f8 *direction,
1563                             const vkl_vrange1f8 *tRange,
1564                             const float *times,
1565                             void *buffer);
1566
1567    VKLHitIterator16 vklInitHitIterator16(const int *valid,
1568                              VKLHitIteratorContext context,
1569                              const vkl_vvec3f16 *origin,
1570                              const vkl_vvec3f16 *direction,
1571                              const vkl_vrange1f16 *tRange,
1572                              const float *times,
1573                              void *buffer);
1574
1575Buffer size can be queried with
1576
1577    size_t vklGetHitIteratorSize(VKLHitIteratorContext context);
1578
1579    size_t vklGetHitIteratorSize4(VKLHitIteratorContext context);
1580
1581    size_t vklGetHitIteratorSize8(VKLHitIteratorContext context);
1582
1583    size_t vklGetHitIteratorSize16(VKLHitIteratorContext context);
1584
1585Open VKL also provides the macro `VKL_MAX_HIT_ITERATOR_SIZE` as a conservative
1586estimate.
1587
1588Hits are then queried by looping a call to `vklIterateHit` as long as the
1589returned lane mask indicates that the iterator is still within the volume.
1590
1591    int vklIterateHit(VKLHitIterator iterator, VKLHit *hit);
1592
1593    void vklIterateHit4(const int *valid,
1594                        VKLHitIterator4 iterator,
1595                        VKLHit4 *hit,
1596                        int *result);
1597
1598    void vklIterateHit8(const int *valid,
1599                        VKLHitIterator8 iterator,
1600                        VKLHit8 *hit,
1601                        int *result);
1602
1603    void vklIterateHit16(const int *valid,
1604                         VKLHitIterator16 iterator,
1605                         VKLHit16 *hit,
1606                         int *result);
1607
1608Returned hits consist of a t-value, a volume value (equal to one of the
1609requested values specified in the context), and an (object space) epsilon value
1610estimating the error of the intersection:
1611
1612    typedef struct
1613    {
1614      float t;
1615      float sample;
1616      float epsilon;
1617    } VKLHit;
1618
1619    typedef struct
1620    {
1621      float t[4];
1622      float sample[4];
1623      float epsilon[4];
1624    } VKLHit4;
1625
1626    typedef struct
1627    {
1628      float t[8];
1629      float sample[8];
1630      float epsilon[8];
1631    } VKLHit8;
1632
1633    typedef struct
1634    {
1635      float t[16];
1636      float sample[16];
1637      float epsilon[16];
1638    } VKLHit16;
1639
1640For both interval and hit iterators, only the vector-wide API for the native
1641SIMD width (determined via `vklGetNativeSIMDWidth` can be called. The scalar
1642versions are always valid. This restriction will likely be lifted in the future.
1643
1644Performance Recommendations
1645===========================
1646
1647MXCSR control and status register
1648---------------------------------
1649
1650It is strongly recommended to have the `Flush to Zero` and `Denormals are Zero`
1651mode of the MXCSR control and status register enabled for each thread before
1652calling the sampling, gradient, or interval API functions. Otherwise, under some
1653circumstances special handling of denormalized floating point numbers can
1654significantly reduce application and Open VKL performance. The device parameter
1655`flushDenormals` or environment variable `OPENVKL_FLUSH_DENORMALS` can be used
1656to toggle this mode; by default it is enabled. Alternatively, when using Open
1657VKL together with the Intel® Threading Building Blocks, it is sufficient to
1658execute the following code at the beginning of the application main thread
1659(before the creation of the `tbb::task_scheduler_init` object):
1660
1661    #include <xmmintrin.h>
1662    #include <pmmintrin.h>
1663    ...
1664    _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
1665    _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON);
1666
1667If using a different tasking system, make sure each thread calling into
1668Open VKL has the proper mode set.
1669
1670Iterator Allocation
1671-------------------
1672
1673`vklInitIntervalIterator` and `vklInitHitIterator` expect a user allocated
1674buffer. While this buffer can be allocated by any means, we expect iterators
1675to be used in inner loops and advise against heap allocation in that case.
1676Applications may provide high performance memory pools, but as a preferred
1677alternative we recommend stack allocated buffers.
1678
1679In C99, variable length arrays provide an easy way to achieve this:
1680
1681    const size_t bufferSize = vklGetIntervalIteratorSize(sampler);
1682    char buffer[bufferSize];
1683
1684Note that the call to `vklGetIntervalIteratorSize` or `vklGetHitIteratorSize`
1685should not appear in an inner loop as it is relatively costly. The return value
1686depends on the volume type, target architecture, and parameters to `sampler`.
1687
1688In C++, variable length arrays are not part of the standard. Here, users may
1689rely on `alloca` and similar functions:
1690
1691    #include <alloca.h>
1692    const size_t bufferSize = vklGetIntervalIteratorSize(sampler);
1693    void *buffer = alloca(bufferSize);
1694
1695Similarly for ISPC, variable length arrays are not supported, but `alloca` may
1696be used:
1697
1698    const uniform size_t bufferSize = vklGetIntervalIteratorSizeV(sampler);
1699    void *uniform buffer = alloca(bufferSize);
1700
1701Users should understand the implications of `alloca`. In particular,
1702`alloca` does check available stack space and may result in stack overflow.
1703`buffer` also becomes invalid at the end of the scope. As one consequence, it
1704cannot be returned from a function.
1705On Windows, `_malloca` is a safer option that performs additional error
1706checking, but requires the use of `_freea`.
1707
1708Applications may instead rely on the `VKL_MAX_INTERVAL_ITERATOR_SIZE` and
1709`VKL_MAX_HIT_ITERATOR_SIZE` macros. For example, in ISPC:
1710
1711    uniform unsigned int8 buffer[VKL_MAX_INTERVAL_ITERATOR_SIZE];
1712
1713These values are majorants over all devices and volume types. Note that Open VKL
1714attempts to detect the target SIMD width using `TARGET_WIDTH`, returning smaller
1715buffer sizes for narrow architectures. However, Open VKL may fall back to the
1716largest buffer size over all targets.
1717
1718Multi-attribute Volume Data Layout
1719----------------------------------
1720
1721Open VKL provides flexible managed data APIs that allow applications to specify
1722input data in various formats and layouts. When shared buffers are used
1723(`dataCreationFlags = VKL_DATA_SHARED_BUFFER`), Open VKL will use the
1724application-owned memory directly, respecting the input data layout. Shared
1725buffers therefore allow applications to strategically select the best layout for
1726multi-attribute volume data and expected sampling behavior.
1727
1728For volume attributes that are sampled individually (e.g. using
1729`vklComputeSample[4,8,16,N]()`), it is recommended to use a structure-of-arrays
1730layout. That is, each attribute's data should be compact in contiguous memory.
1731This can be accomplished by simply using Open VKL owned data objects
1732(`dataCreationFlags = VKL_DATA_DEFAULT`), or by using a natural `byteStride` for
1733shared buffers.
1734
1735For volume attributes that are sampled simultaneously (e.g. using
1736`vklComputeSampleM[4,8,16,N]()`), it is recommended to use an
1737array-of-structures layout. That is, data for these attributes should be
1738provided per voxel in a contiguous layout. This is accomplished using shared
1739buffers for each attribute with appropriate byte strides. For example, for a
1740three attribute structured volume representing a velocity field, the data can be
1741provided as:
1742
1743    // used in Open VKL shared buffers, so must not be freed by application
1744    std::vector<vkl_vec3f> velocities(numVoxels);
1745
1746    for (auto &v : velocities) {
1747      v.x = ...;
1748      v.y = ...;
1749      v.z = ...;
1750    }
1751
1752    std::vector<VKLData> attributes;
1753
1754    attributes.push_back(vklNewData(device,
1755                                    velocities.size(),
1756                                    VKL_FLOAT,
1757                                    &velocities[0].x,
1758                                    VKL_DATA_SHARED_BUFFER,
1759                                    sizeof(vkl_vec3f)));
1760
1761    attributes.push_back(vklNewData(device,
1762                                    velocities.size(),
1763                                    VKL_FLOAT,
1764                                    &velocities[0].y,
1765                                    VKL_DATA_SHARED_BUFFER,
1766                                    sizeof(vkl_vec3f)));
1767
1768    attributes.push_back(vklNewData(device,
1769                                    velocities.size(),
1770                                    VKL_FLOAT,
1771                                    &velocities[0].z,
1772                                    VKL_DATA_SHARED_BUFFER,
1773                                    sizeof(vkl_vec3f)));
1774
1775    VKLData attributesData =
1776        vklNewData(device, attributes.size(), VKL_DATA, attributes.data());
1777
1778    for (auto &attribute : attributes)
1779      vklRelease(attribute);
1780
1781    VKLVolume volume = vklNewVolume(device, "structuredRegular");
1782
1783    vklSetData(volume, "data", attributesData);
1784    vklRelease(attributesData);
1785
1786    // set other volume parameters...
1787
1788    vklCommit(volume);
1789
1790These are general recommendations for common scenarios; it is still recommended
1791to evaluate performance of different volume data layouts for your application's
1792particular use case.
1793