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
Scatterclasses are responsible for defining how much output is generated based on some sized input.ScatterIdentityestablishes 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 aviskores::cont::ArrayHandleIndexto 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 aviskores::cont::ArrayHandleConstantto 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::ArrayHandleIndexof the same size as theinputRange.
-
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::ArrayHandleIndexof the same size as theinputRange.
-
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::ArrayHandleConstantof the same size as theinputRangewith 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::ArrayHandleConstantof the same size as theinputRangewith 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 aScatterIdentity, the number of outputs are the same as the number of inputs.
-
using OutputToInputMapType = viskores::cont::ArrayHandleIndex
-
template<viskores::IdComponent NumOutputsPerInput>
struct ScatterUniform : public viskores::worklet::internal::ScatterBase A scatter that maps input to some constant numbers of output.
The
Scatterclasses are responsible for defining how much output is generated based on some sized input.ScatterUniformestablishes 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 everyNumOutputsPerInputoutput 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,…NumOutputsPerInputfor 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
ScatterUniformis theinputRangetimes theNumOutputsPerInput.
-
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
ScatterUniformis theinputRangetimes theNumOutputsPerInput.
-
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
NumOutputsPerInputoutput 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,…
NumOutputsPerInputfor every input.
-
struct ScatterCounting : public viskores::worklet::internal::ScatterBase
A scatter that maps input to some numbers of output.
The
Scatterclasses are responsible for defining how much output is generated based on some sized input.ScatterCountingestablishes 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
ScatterCountingobject 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
ScatterCountingobject 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.)
-
using OutputToInputMapType = viskores::cont::ArrayHandle<viskores::Id>
-
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
Scatterclasses are responsible for defining how much output is generated based on some sized input.ScatterPermutationis similar toScatterCountingbut 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 theVistIndexis 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 theScatterPermutationclass.
-
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 aviskores::cont::ArrayHandleConstantwhere 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
ScatterPermutationgiven 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,VisitIndexis 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::ArrayHandleConstantof the same size as theinputRangewith 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::ArrayHandleConstantof the same size as theinputRangewith value of 0.
-
using OutputToInputMapType = viskores::cont::ArrayHandle<viskores::Id, PermutationStorage>
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.
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).
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.
MaskNoneis 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
MaskNoneis the same as theoutputRange.
-
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.
-
using ThreadToOutputMapType = viskores::cont::ArrayHandleIndex
-
class MaskIndices : public viskores::worklet::internal::MaskBase
Mask using a given array of indices to include in the output.
MaskIndicesis 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
MaskSelectwith theIndexArraytag, 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
MaskSelectwith theIndexArraytag, 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.
-
using ThreadToOutputMapType = viskores::cont::ArrayHandle<viskores::Id>
-
class MaskSelect : public viskores::worklet::internal::MaskBase
Mask using arrays to select specific elements to suppress.
MaskSelectis 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.MaskSelectis 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
MaskSelectobject using an array that masks an output value with0and enables an output value with1.
-
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.
-
using ThreadToOutputMapType = viskores::cont::ArrayHandle<viskores::Id>
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 aviskores::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 ofMaskSelectdirectly 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.
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).
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.
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.
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.