4.13. Worklet Input Output Semantics

The default scheduling of a worklet provides a 1 to 1 mapping from the input domain to the output domain. For example, a viskores::worklet::WorkletMapField gets run once for every item of the input array and produces one item for the output array. Likewise, viskores::worklet::WorkletVisitCellsWithPoints gets run once for every cell in the input topology and produces one associated item for the output field.

However, there are many operations that do not fall well into this 1 to 1 mapping procedure. The operation might need to pass over elements that produce no value or the operation might need to produce multiple values for a single input element. Such non 1 to 1 mappings can be achieved by defining a scatter or a mask (or both) on a worklet.

4.13.1. Scatter

A scatter allows you to specify for each input element how many output elements should be created. For example, a scatter allows you to create two output elements for every input element. A scatter could also allow you to drop every other input element from the output. The following types of scatter are provided by Viskores.

struct ScatterIdentity : public viskores::worklet::internal::ScatterBase

A scatter that maps input directly to output.

The Scatter classes are responsible for defining how much output is generated based on some sized input. ScatterIdentity establishes a 1 to 1 mapping from input to output (and vice versa). That is, every input element generates one output element associated with it. This is the default for basic maps.

Public Types

using OutputToInputMapType = viskores::cont::ArrayHandleIndex

The type of array handle used to map output indices to input indices.

For the case of ScatterIdentity, this is a viskores::cont::ArrayHandleIndex to do a direct 1-to-1 maping.

using VisitArrayType = viskores::cont::ArrayHandleConstant<viskores::IdComponent>

The type of array handle used for the visit index for each output.

For the case of ScatterIdentity, this is a viskores::cont::ArrayHandleConstant to do a direct 1-to-1 maping (so every visit index is 0).

Public Functions

inline OutputToInputMapType GetOutputToInputMap(viskores::Id inputRange) const

Provides the array that maps output indices to input indices.

Parameters:

inputRange – The size of the input domain.

Returns:

A viskores::cont::ArrayHandleIndex of the same size as the inputRange.

inline OutputToInputMapType GetOutputToInputMap(viskores::Id3 inputRange) const

Provides the array that maps output indices to input indices.

Parameters:

inputRange – The size of the input domain in 3 dimensions.

Returns:

A viskores::cont::ArrayHandleIndex of the same size as the inputRange.

inline VisitArrayType GetVisitArray(viskores::Id inputRange) const

Provides the array that gives the visit index for each output.

Parameters:

inputRange – The size of the input domain.

Returns:

A viskores::cont::ArrayHandleConstant of the same size as the inputRange with value of 0.

inline VisitArrayType GetVisitArray(viskores::Id3 inputRange) const

Provides the array that gives the visit index for each output.

Parameters:

inputRange – The size of the input domain.

Returns:

A viskores::cont::ArrayHandleConstant of the same size as the inputRange with value of 0.

template<typename RangeType>
inline RangeType GetOutputRange(RangeType inputRange) const

Provides the number of output values for a given input domain size.

Parameters:

inputRange – The size of the input domain.

Returns:

The same value as inputRange. For a ScatterIdentity, the number of outputs are the same as the number of inputs.

template<viskores::IdComponent NumOutputsPerInput>
struct ScatterUniform : public viskores::worklet::internal::ScatterBase

A scatter that maps input to some constant numbers of output.

The Scatter classes are responsible for defining how much output is generated based on some sized input. ScatterUniform establishes a 1 to N mapping from input to output. That is, every input element generates N elements associated with it where N is the same for every input. The output elements are grouped by the input associated.

Template Parameters:

NumOutputsPerInput – Specifies how many outputs are generated per input value.

Public Types

using OutputToInputMapType = viskores::cont::ArrayHandleImplicit<detail::FunctorDiv<NumOutputsPerInput>>

The type of array handle used to map output indices to input indices.

For the case of ScatterUniform, this is an implicit array that has every NumOutputsPerInput output indices point to the same input index.

using VisitArrayType = viskores::cont::ArrayHandleImplicit<detail::FunctorModulus<NumOutputsPerInput>>

The type of array handle used for the visit index for each output.

For the case of ScatterUniform, this is an implicit array that repeats 0, 1,… NumOutputsPerInput for every input.

Public Functions

inline viskores::Id GetOutputRange(viskores::Id inputRange) const

Provides the number of output values for a given input domain size.

Parameters:

inputRange – The size of the input domain.

Returns:

The number of output values, which for a ScatterUniform is the inputRange times the NumOutputsPerInput.

inline viskores::Id GetOutputRange(viskores::Id3 inputRange) const

Provides the number of output values for a given input domain size.

Parameters:

inputRange – The size of the input domain.

Returns:

The number of output values, which for a ScatterUniform is the inputRange times the NumOutputsPerInput.

template<typename RangeType>
inline OutputToInputMapType GetOutputToInputMap(RangeType inputRange) const

Provides the array that maps output indices to input indices.

Parameters:

inputRange – The size of the input domain.

Returns:

An implicit array that has every NumOutputsPerInput output indices point to the same input index.

template<typename RangeType>
inline VisitArrayType GetVisitArray(RangeType inputRange) const

Provides the array that gives the visit index for each output.

Parameters:

inputRange – The size of the input domain.

Returns:

An implicit array that repeats 0, 1,… NumOutputsPerInput for every input.

struct ScatterCounting : public viskores::worklet::internal::ScatterBase

A scatter that maps input to some numbers of output.

The Scatter classes are responsible for defining how much output is generated based on some sized input. ScatterCounting establishes a 1 to N mapping from input to output. That is, every input element generates 0 or more output elements associated with it. The output elements are grouped by the input associated.

A counting scatter takes an array of counts for each input. The data is taken in the constructor and the index arrays are derived from that. So changing the counts after the scatter is created will have no effect.

Public Types

using OutputToInputMapType = viskores::cont::ArrayHandle<viskores::Id>

The type of array handle used to map output indices to input indices.

For the case of ScatterCounting, this is a basic array handle.

using VisitArrayType = viskores::cont::ArrayHandle<viskores::IdComponent>

The type of array handle used for the visit index for each output.

For the case of ScatterCounting, this is a basic array handle.

Public Functions

inline ScatterCounting(const viskores::cont::UnknownArrayHandle &countArray, viskores::cont::DeviceAdapterId device = viskores::cont::DeviceAdapterTagAny(), bool saveInputToOutputMap = false)

Construct a ScatterCounting object using an array of counts for the number of outputs for each input.

Part of the construction requires generating an input to output map, but this map is not needed for the operations of ScatterCounting, so by default it is deleted. However, other users might make use of it, so you can instruct the constructor to save the input to output map.

inline ScatterCounting(const viskores::cont::UnknownArrayHandle &countArray, bool saveInputToOutputMap)

Construct a ScatterCounting object using an array of counts for the number of outputs for each input.

Part of the construction requires generating an input to output map, but this map is not needed for the operations of ScatterCounting, so by default it is deleted. However, other users might make use of it, so you can instruct the constructor to save the input to output map.

template<typename RangeType>
inline OutputToInputMapType GetOutputToInputMap(RangeType inputRange) const

Provides the array that maps output indices to input indices.

Parameters:

inputRange – The size of the input domain, which must be the same size as the count array provided in the constructor.

Returns:

A basic array of indices that identifies which input provides data for each output.

inline OutputToInputMapType GetOutputToInputMap() const

Provides the array that maps output indices to input indices.

Returns:

A basic array of indices that identifies which input provides data for each output.

inline viskores::Id GetOutputRange(viskores::Id inputRange) const

Provides the number of output values for a given input domain size.

Parameters:

inputRange – The size of the input domain, which must be the same size as the count array provided in the constructor.

Returns:

The total number of output values.

inline viskores::Id GetOutputRange(viskores::Id3 inputRange) const

Provides the number of output values for a given input domain size.

Parameters:

inputRange – The size of the input domain, which must be the same size as the count array provided in the constructor.

Returns:

The total number of output values.

inline viskores::cont::ArrayHandle<viskores::Id> GetInputToOutputMap() const

Provides an array that maps input values to output values.

This array will not be valid unless explicitly instructed to be saved. (See documentation for the constructor.)

template<typename PermutationStorage = ::viskores::cont::StorageTagBasic>
struct ScatterPermutation : public viskores::worklet::internal::ScatterBase

A scatter that maps input to output based on a permutation array.

The Scatter classes are responsible for defining how much output is generated based on some sized input. ScatterPermutation is similar to ScatterCounting but can have lesser memory usage for some cases. The constructor takes an array of ids, where each entry maps the corresponding output to an input. The ids can be in any order and there can be duplicates. Note that even with duplicates the VistIndex is always 0.

Public Types

using OutputToInputMapType = viskores::cont::ArrayHandle<viskores::Id, PermutationStorage>

The type of array handle used to map output indices to input indices.

For the case of ScatterPermutation, this is an array handle. It is a basic array handle by default, but can be modified by the template parameter of the ScatterPermutation class.

using VisitArrayType = viskores::cont::ArrayHandleConstant<viskores::IdComponent>

The type of array handle used for the visit index for each output.

For the case of ScatterPermutation, this is a viskores::cont::ArrayHandleConstant where are values are 0. All outputs are assumed to point to a single input. This is not enforced, but if two outputs point to the same input, they cannot be differentiated by the visit index.

Public Functions

inline ScatterPermutation(const OutputToInputMapType &permutation)

Constructs a ScatterPermutation given an array of indices that point from output to input.

The provided array handle is sized to the number of output values and maps output indices to input indices. For example, if index $i$ of the permutation array contains $j$, then the worklet invocation for output $i$ will get the $j^{th}$ input values. The reordering does not have to be 1 to 1. Any input not referenced by the permutation array will be dropped, and any input referenced by the permutation array multiple times will be duplicated. However, unlike ScatterCounting, VisitIndex is always 0 even if an input value happens to be duplicated.

template<typename RangeType>
inline viskores::Id GetOutputRange(RangeType inputRange) const

Provides the number of output values for a given input domain size.

Parameters:

inputRange – The size of the input domain, which must be the same size as the permutation array provided in the constructor.

Returns:

The total number of output values.

template<typename RangeType>
inline OutputToInputMapType GetOutputToInputMap(RangeType inputRange) const

Provides the array that maps output indices to input indices.

Parameters:

inputRange – The size of the input domain, which must be the same size as the permutation array provided in the constructor.

Returns:

The provided permutation array, which is the same as the output to input map.

inline OutputToInputMapType GetOutputToInputMap() const

Provides the array that maps output indices to input indices.

Returns:

The provided permutation array, which is the same as the output to input map.

inline VisitArrayType GetVisitArray(viskores::Id inputRange) const

Provides the array that gives the visit index for each output.

Parameters:

inputRange – The size of the input domain.

Returns:

A viskores::cont::ArrayHandleConstant of the same size as the inputRange with value of 0.

inline VisitArrayType GetVisitArray(viskores::Id3 inputRange) const

Provides the array that gives the visit index for each output.

Parameters:

inputRange – The size of the input domain.

Returns:

A viskores::cont::ArrayHandleConstant of the same size as the inputRange with value of 0.

Did You Know?

Scatters are often used to create multiple outputs for a single input, but they can also be used to remove inputs from the output. In particular, if you provide a count of 0 in a viskores::worklet::ScatterCounting count array, no outputs will be created for the associated input. To simply mask out some elements from the input, provide viskores::worklet::ScatterCounting with a stencil array of 0’s and 1’s with a 0 for every element you want to remove and a 1 for every element you want to pass. You can also mix 0’s with counts larger than 1 to drop some elements and add multiple results for other elements. viskores::worklet::ScatterPermutation can similarly be used to remove input values by leaving them out of the permutation.

To define a scatter procedure, the worklet must provide a type definition named ScatterType. The ScatterType must be set to one of the aforementioned Scatter* classes.

Example 4.123 Declaration of a scatter type in a worklet.
1  class Generate : public viskores::worklet::WorkletMapField
2  {
3  public:
4    using ControlSignature = void(FieldIn inPoints, FieldOut outPoints);
5    using ExecutionSignature = void(_1, _2);
6    using InputDomain = _1;
7
8    using ScatterType = viskores::worklet::ScatterCounting;

When using a scatter that produces multiple outputs for a single input, the worklet is invoked multiple times with the same input values. In such an event the worklet operator needs to distinguish these calls to produce the correct associated output. This is done by declaring one of the ExecutionSignature arguments as VisitIndex. This tag will pass a viskores::IdComponent to the worklet that identifies which invocation is being called.

It is also the case that the when a scatter can produce multiple outputs for some input that the index of the input element is not the same as the WorkIndex. If the index to the input element is needed, you can use the InputIndex tag in the ExecutionSignature. It is also good practice to use the OutputIndex tag if the index to the output element is needed.

Most Scatter objects have a state, and this state must be passed to the viskores::cont::Invoker when invoking the worklet. In this case, the Scatter object should be passed as the second object to the call to the viskores::cont::Invoker (after the worklet object).

Example 4.124 Invoking with a custom scatter.
1    viskores::worklet::ScatterCounting generateScatter(countArray);
2    this->Invoke(ClipPoints::Generate{}, generateScatter, inField, clippedPointsArray);

Did You Know?

A scatter object does not have to be tied to a single worklet/invoker instance. In some cases it makes sense to use the same scatter object multiple times for worklets that have the same input to output mapping. Although this is not common, it can save time by reusing the set up computations of viskores::worklet::ScatterCounting.

To demonstrate using scatters with worklets, we provide some contrived but illustrative examples. The first example is a worklet that takes a pair of input arrays and interleaves them so that the first, third, fifth, and so on entries come from the first array and the second, fourth, sixth, and so on entries come from the second array. We achieve this by using a viskores::worklet::ScatterUniform of size 2 and using the VisitIndex to determine from which array to pull a value.

 1struct InterleaveArrays : viskores::worklet::WorkletMapField
 2{
 3  using ControlSignature = void(FieldIn, FieldIn, FieldOut);
 4  using ExecutionSignature = void(_1, _2, _3, VisitIndex);
 5  using InputDomain = _1;
 6
 7  using ScatterType = viskores::worklet::ScatterUniform<2>;
 8
 9  template<typename T>
10  VISKORES_EXEC void operator()(const T& input0,
11                                const T& input1,
12                                T& output,
13                                viskores::IdComponent visitIndex) const
14  {
15    if (visitIndex == 0)
16    {
17      output = input0;
18    }
19    else // visitIndex == 1
20    {
21      output = input1;
22    }
23  }
24};

The second example takes a collection of point coordinates and clips them by an axis-aligned bounding box. It does this using a viskores::worklet::ScatterCounting with an array containing 0 for all points outside the bounds and 1 for all points inside the bounds. As is typical with this type of operation, we use another worklet with a default identity scatter to build the count array.

 1struct ClipPoints
 2{
 3  class Count : public viskores::worklet::WorkletMapField
 4  {
 5  public:
 6    using ControlSignature = void(FieldIn points, FieldOut count);
 7    using ExecutionSignature = _2(_1);
 8    using InputDomain = _1;
 9
10    VISKORES_CONT Count(const viskores::Bounds& bounds)
11      : Bounds(bounds)
12    {
13    }
14
15    template<typename T>
16    VISKORES_EXEC viskores::IdComponent operator()(
17      const viskores::Vec<T, 3>& point) const
18    {
19      return (this->Bounds.Contains(point) ? 1 : 0);
20    }
21
22  private:
23    viskores::Bounds Bounds;
24  };
25
26  class Generate : public viskores::worklet::WorkletMapField
27  {
28  public:
29    using ControlSignature = void(FieldIn inPoints, FieldOut outPoints);
30    using ExecutionSignature = void(_1, _2);
31    using InputDomain = _1;
32
33    using ScatterType = viskores::worklet::ScatterCounting;
34
35    template<typename InType, typename OutType>
36    VISKORES_EXEC void operator()(const viskores::Vec<InType, 3>& inPoint,
37                                  viskores::Vec<OutType, 3>& outPoint) const
38    {
39      // The scatter ensures that this method is only called for input points
40      // that are passed to the output (where the count was 1). Thus, in this
41      // case we know that we just need to copy the input to the output.
42      outPoint = viskores::Vec<OutType, 3>(inPoint[0], inPoint[1], inPoint[2]);
43    }
44  };
45};
46
47//
48// Later in the associated Filter class...
49//
50
51    viskores::cont::ArrayHandle<viskores::IdComponent> countArray;
52
53    this->Invoke(ClipPoints::Count(this->Bounds), inField, countArray);
54
55    viskores::cont::ArrayHandle<T> clippedPointsArray;
56
57    viskores::worklet::ScatterCounting generateScatter(countArray);
58    this->Invoke(ClipPoints::Generate{}, generateScatter, inField, clippedPointsArray);

The third example takes an input array and reverses the ordering. It does this using a viskores::worklet::ScatterPermutation with a permutation array generated from a viskores::cont::ArrayHandleCounting counting down from the input array size to 0.

 1struct ReverseArrayWorklet : viskores::worklet::WorkletMapField
 2{
 3  using ControlSignature = void(FieldIn inputArray, FieldOut outputArray);
 4  using ExecutionSignature = void(_1, _2);
 5  using InputDomain = _1;
 6
 7  using ArrayStorageTag =
 8    typename viskores::cont::ArrayHandleCounting<viskores::Id>::StorageTag;
 9  using ScatterType = viskores::worklet::ScatterPermutation<ArrayStorageTag>;
10
11  VISKORES_CONT
12  static ScatterType MakeScatter(viskores::Id arraySize)
13  {
14    return ScatterType(
15      viskores::cont::ArrayHandleCounting<viskores::Id>(arraySize - 1, -1, arraySize));
16  }
17
18  template<typename FieldType>
19  VISKORES_EXEC void operator()(FieldType inputArrayField,
20                                FieldType& outputArrayField) const
21  {
22    outputArrayField = inputArrayField;
23  }
24};
25
26//
27// Later in the associated Filter class...
28//
29
30    viskores::cont::ArrayHandle<T> outputField;
31    this->Invoke(ReverseArrayWorklet{},
32                 ReverseArrayWorklet::MakeScatter(inputField.GetNumberOfValues()),
33                 inputField,
34                 outputField);

Did You Know?

A viskores::worklet::ScatterPermutation can have less memory usage than a viskores::worklet::ScatterCounting when zeroing indices. By default, a viskores::worklet::ScatterPermutation will omit all fields that are not specified in the input permutation, whereas viskores::worklet::ScatterCounting requires 0 values. If mapping an input to an output that omits fields, consider using a viskores::worklet::ScatterPermutation to save memory.

Common Errors

A permutation array provided to viskores::worklet::ScatterPermutation can be filled with arbitrary id values. If an input permutation id exceeds the bounds of an input provided to a worklet, an out of bounds error will occur in the worklet functor. To prevent this kind of error, you should ensure that ids in the viskores::worklet::ScatterPermutation do not exceed the bounds of provided inputs.

4.13.2. Mask

A mask allows you to mask out particular output entries. For example, a mask allows you to write out to array indices with an even index while leaving those with an odd index untouched. A mask could also allow you to change values that match a certain criteria. The worklet is only run for the indices for which a value is generated. Because a worklet with a mask only writes to select indices, it is best to write to arrays with in/out semantics.

The following types of mask are provided by Viskores.

struct MaskNone : public viskores::worklet::internal::MaskBase

Default mask object that does not suppress anything.

MaskNone is a worklet mask object that does not suppress any items in the output domain. This is the default mask object so that the worklet is run for every possible output element.

Public Types

using ThreadToOutputMapType = viskores::cont::ArrayHandleIndex

The type of array handle used to map thread indices to output indices.

For the case of MaskNone, this is an index array.

Public Functions

template<typename RangeType>
inline RangeType GetThreadRange(RangeType outputRange) const

Provides the number of threads for a given output domain size.

Parameters:

outputRange – The size of the full output domain.

Returns:

The total number of output values, which for MaskNone is the same as the outputRange.

inline ThreadToOutputMapType GetThreadToOutputMap(viskores::Id outputRange) const

Provides the array that maps thread indices to output indices.

Parameters:

outputRange – The size of the full output domain.

Returns:

A basic array of indices that identifies which output each thread writes to.

inline ThreadToOutputMapType GetThreadToOutputMap(const viskores::Id3 &outputRange) const

The type of array handle used to map thread indices to output indices.

For the case of MaskNone, this is an index array.

class MaskIndices : public viskores::worklet::internal::MaskBase

Mask using a given array of indices to include in the output.

MaskIndices is a worklet mask object that is used to select elements in the output of a worklet to include in the output. This is done by providing a mask array. This array contains an entry for every output to create. Any output index not included is not generated.

It is OK to give indices that are out of order, but any index must be provided at most one time. It is an error to have the same index listed twice.

Public Types

using ThreadToOutputMapType = viskores::cont::ArrayHandle<viskores::Id>

The type of array handle used to map thread indices to output indices.

For the case of MaskIndices, this is a basic array handle.

Public Functions

inline explicit MaskIndices(const viskores::cont::ArrayHandle<viskores::Id> &indexArray, viskores::cont::DeviceAdapterId = viskores::cont::DeviceAdapterTagAny())

Construct using an index array.

When you construct a MaskSelect with the IndexArray tag, you provide an array containing an index for each output to produce. It is OK to give indices that are out of order, but any index must be provided at most one time. It is an error to have the same index listed twice.

Note that depending on the type of the array passed in, the index may be shallow copied or deep copied into the state of this mask object. Thus, it is a bad idea to alter the array once given to this object.

template<typename T, typename S>
inline explicit MaskIndices(const viskores::cont::ArrayHandle<T, S> &indexArray, viskores::cont::DeviceAdapterId device = viskores::cont::DeviceAdapterTagAny())

Construct using an index array.

When you construct a MaskSelect with the IndexArray tag, you provide an array containing an index for each output to produce. It is OK to give indices that are out of order, but any index must be provided at most one time. It is an error to have the same index listed twice.

Note that depending on the type of the array passed in, the index may be shallow copied or deep copied into the state of this mask object. Thus, it is a bad idea to alter the array once given to this object.

template<typename RangeType>
inline viskores::Id GetThreadRange(RangeType outputRange) const

Provides the number of threads for a given output domain size.

Parameters:

outputRange – The size of the full output domain (including masked entries).

Returns:

The total number of threads.

template<typename RangeType>
inline ThreadToOutputMapType GetThreadToOutputMap(RangeType outputRange) const

Provides the array that maps thread indices to output indices.

Parameters:

outputRange – The size of the full output domain (including masked entries).

Returns:

A basic array of indices that identifies which output each thread writes to.

class MaskSelect : public viskores::worklet::internal::MaskBase

Mask using arrays to select specific elements to suppress.

MaskSelect is a worklet mask object that is used to select elements in the output of a worklet to suppress the invocation. That is, the worklet will only be invoked for elements in the output that are not masked out by the given array.

MaskSelect is initialized with a mask array. This array should contain a 0 for any entry that should be masked and a 1 for any output that should be generated. It is an error to have any value that is not a 0 or 1. This method is slower than specifying an index array.

Subclassed by viskores::worklet::MaskSelectTemplate

Public Types

using ThreadToOutputMapType = viskores::cont::ArrayHandle<viskores::Id>

The type of array handle used to map thread indices to output indices.

For the case of MaskSelect, this is a basic array handle.

Public Functions

inline MaskSelect(const viskores::cont::UnknownArrayHandle &maskArray, viskores::cont::DeviceAdapterId device = viskores::cont::DeviceAdapterTagAny())

Construct a MaskSelect object using an array that masks an output value with 0 and enables an output value with 1.

template<typename RangeType>
inline viskores::Id GetThreadRange(RangeType outputRange) const

Provides the number of threads for a given output domain size.

Parameters:

outputRange – The size of the full output domain (including masked entries), which must be the same size as the select array provided in the constructor.

Returns:

The total number of threads.

template<typename RangeType>
inline ThreadToOutputMapType GetThreadToOutputMap(RangeType outputRange) const

Provides the array that maps thread indices to output indices.

Parameters:

outputRange – The size of the full output domain (including masked entries), which must be the same size as the select array provided in the constructor.

Returns:

A basic array of indices that identifies which output each thread writes to.

The constructor of viskores::worklet::MaskSelect takes a viskores::cont::UnknownArrayHandle and is precompiled for a set of expected array types. However, if you have a custom array handle type like many of those in Chapter 4.9 (Fancy Array Handles), it is often more efficient to use viskores::worklet::MaskSelectTemplate, which has a templated constructor to compile for a specific array handle type.

class MaskSelectTemplate : public viskores::worklet::MaskSelect

A templated version MaskSelect.

To construct a MaskSelect, you provide a mask array, which gets processed to construct a lookup array. To prevent multiple recompiles, this is compiled into a library. However, if your mask array is of an atypical type, such as a viskores::cont::ArrayHandleTransform, the underlying code will have to copy the array into a form it is familiar with. In this case where you have such an array (and Viskores is warning you about an inefficient array copy), you can use the constructor of this subclass to compile a version of MaskSelect directly for your array type.

Once constructed, this object can (and probably should) be cast to a MaskSelect.

To define a mask procedure, the worklet must provide a type definition named MaskType. The MaskType must be set to one of the aforementioned Mask* classes.

Example 4.128 Declaration of a mask type in a worklet.
1  using MaskType = viskores::worklet::MaskSelect;

When using a mask, there is no longer a 1-to-1 correspondence between the WorkIndex and the indices of the input and output. If the index to the input or output element is needed, you can use the InputIndex tag and OutputIndex tag, respectively, in the ExecutionSignature.

Most Mask objects have a state, and this state must be passed to the viskores::cont::Invoker when invoking the worklet. In this case, the Mask object should be passed as the second object to the call to the viskores::cont::Invoker (after the worklet object).

Example 4.129 Invoking with a custom mask.
1    viskores::worklet::MaskSelectTemplate mask{
2      viskores::cont::make_ArrayHandleTransform(dataArray, MaskExactFibonacci{})
3    };
4    this->Invoke(NearestFibonacci{}, mask, dataArray);

Did You Know?

A mask object does not have to be tied to a single worklet/invoker instance. In some cases it makes sense to use the same mask object multiple times for worklets that have the same input to output mapping. Although this is not common, it can save time by reusing the set up computations of viskores::worklet::MaskSelect.

To demonstrate using a mask with a worklet, here is a contrived but illustrative example. In this example, we write a worklet that takes an array and for each entry replaces the value with the number in the Fibonacci sequence closest to that value.

Example 4.130 A worklet for finding the closest Fibonacci value.
 1struct NearestFibonacci : viskores::worklet::WorkletMapField
 2{
 3  using ControlSignature = void(FieldInOut);
 4  using ExecutionSignature = void(_1);
 5
 6  using MaskType = viskores::worklet::MaskSelect;
 7
 8  template<typename T>
 9  VISKORES_EXEC void operator()(T& targetNumber) const
10  {
11    T beforeLast = T(0);
12    T last = T(1);
13    while (last < targetNumber)
14    {
15      T next = beforeLast + last;
16      beforeLast = last;
17      last = next;
18    }
19
20    targetNumber =
21      ((targetNumber - beforeLast) < (last - targetNumber)) ? beforeLast : last;
22  }
23};

A simple observation is that the Fibonacci sequence contains all positive integers up to 3. Thus, values for these small positive numbers, the value will not change. If it is likely that there will be many such small values, we can potentially speed up our operation by only operating on values greater than 3.

Example 4.131 Using a mask to select which array entries to update.
 1struct MaskExactFibonacci
 2{
 3  template<typename T>
 4  VISKORES_EXEC T operator()(T x) const
 5  {
 6    return (x > 3) ? 1 : 0;
 7  }
 8};
 9
10/// Later in the filter...
11
12    viskores::worklet::MaskSelectTemplate mask{
13      viskores::cont::make_ArrayHandleTransform(dataArray, MaskExactFibonacci{})
14    };
15    this->Invoke(NearestFibonacci{}, mask, dataArray);

Did You Know?

In Example 4.131 a viskores::cont::ArrayHandleTransform is used to create a select array of 0’s and 1’s in place. To accelerate the construction of the mask indices, a viskores::worklet::MaskSelectTemplate is used. This is trivial subclass of viskores::worklet::MaskSelect so it can be constructed and then used for the mask of the worklet.