5.4. Worklet Arguments

From the ControlSignature and ExecutionSignature defined in worklets, Viskores uses template metaprogramming to build the code required to manage data from the control to the execution environment. These signatures contain tags that define the meaning of each argument and control how the argument data are transferred from the control to execution environments and broken up for each worklet instance.

Chapter 4.3 (Worklet Types) documents the many ControlSignature and ExecutionSignature tags that come with the worklet types. This chapter discusses the internals of these tags and how they control data management. Defining new worklet argument types can allow you to define new data structures in Viskores. New worklet arguments are also usually critical components for making new worklet types, as described in the later chapter on creating new worklet types.

The management of data in worklet arguments is handled by three classes that provide type checking, transportation, and fetching, respectively. This chapter first describes these type-checking, transportation, and fetching classes and then describes how ControlSignature and ExecutionSignature tags specify these classes.

Throughout this chapter, we demonstrate the definition of worklet arguments using an example of a worklet argument that represents line segments in 2D. The input for such an argument expects an viskores::cont::ArrayHandle containing floating-point viskores::Vec objects of size 2 to represent coordinates in the plane. The values in the array are paired to define the two endpoints of each segment, and the worklet instance receives a Vec-2 of Vec-2 objects representing the two endpoints. In practice, it is generally easier to use a viskores::cont::ArrayHandleGroupVec (see Section 4.9.12 (Grouped Vector Arrays)), but this is a simple example for demonstration purposes. We also use this special worklet argument for our later example of a custom worklet type.

5.4.1. Type Checks

Before attempting to move data from the control to the execution environment, the Viskores invokers check the input types to ensure that they are compatible with the associated ControlSignature concept. This is done with the viskores::cont::arg::TypeCheck structure.

template<typename TypeCheckTag, typename Type>
struct TypeCheck

Class for checking that a type matches the semantics for an argument.

The TypeCheck class is used in dispatchers to test whether an argument passed to the Invoke command matches the corresponding argument in the ControlSignature.

This check happens after casting dynamic classes to static classes, so the check need not worry about querying dynamic types.

The generic implementation of TypeCheck always results in failure. When a new type check tag is defined, along with it should be partial specializations that find valid types.

Public Static Attributes

static constexpr bool value = false

The static constant boolean value is set to true if the type is valid for the given check tag and false otherwise.

The viskores::cont::arg::TypeCheck structure is templated with two parameters. The first parameter is a tag that identifies which check to perform. The second parameter is the type of the control argument (after any dynamic casts). The structure contains a static constant Boolean named value that is true if the type in the second parameter is compatible with the tag in the first, or false otherwise.

Type checks are implemented with a defined type-check tag, which by convention is defined in the viskores::cont::arg namespace and starts with TypeCheckTag, and a partial specialization of the viskores::cont::arg::TypeCheck structure. The following type checks, identified by their tags, are provided in Viskores.

struct TypeCheckTagExecObject

The ExecObject type check passes for any object that inherits from ExecutionObjectBase and follows the conventions of that class.

struct TypeCheckTagArrayIn

The Array type check passes for any object that behaves like an ArrayHandle class and can be passed to the ArrayIn transport.

struct TypeCheckTagArrayOut

The Array type check passes for any object that behaves like an ArrayHandle class and can be passed to the ArrayOut transport.

struct TypeCheckTagArrayInOut

The Array type check passes for any object that behaves like an ArrayHandle class and can be passed to the ArrayInOut transport.

struct TypeCheckTagAtomicArray

The atomic array type check passes for an ArrayHandle of a structure that is valid for atomic access.

There are many restrictions on the type of data that can be used for an atomic array.

struct TypeCheckTagBitField

The Array type check passes for viskores::cont::BitField.

struct TypeCheckTagCellSet

Check for a CellSet-like object.

struct TypeCheckTagCellSetStructured

Check for a Structured CellSet-like object.

struct TypeCheckTagKeys

Check for a viskores::worklet::Keys object.

Here are some trivial examples of using viskores::cont::arg::TypeCheck. Typically these checks are done internally in the base Viskores invoker code, so these examples are for demonstration only.

Example 5.16 Behavior of viskores::cont::arg::TypeCheck.
 1struct MyExecObject : viskores::cont::ExecutionObjectBase
 2{
 3  viskores::Id Value;
 4};
 5
 6void DoTypeChecks()
 7{
 8  using viskores::cont::arg::TypeCheck;
 9  using viskores::cont::arg::TypeCheckTagArrayIn;
10  using viskores::cont::arg::TypeCheckTagExecObject;
11
12  bool check1 = TypeCheck<TypeCheckTagExecObject, MyExecObject>::value; // true
13  bool check2 = TypeCheck<TypeCheckTagExecObject, viskores::Id>::value; // false
14
15  using ArrayType = viskores::cont::ArrayHandle<viskores::Float32>;
16
17  bool check3 = TypeCheck<TypeCheckTagArrayIn, ArrayType>::value;    // true
18  bool check4 = TypeCheck<TypeCheckTagExecObject, ArrayType>::value; // false
19}

A type check is created by first defining a type-check tag object, which by convention is placed in the viskores::cont::arg namespace and whose name starts with TypeCheckTag. Then, create a specialization of the viskores::cont::arg::TypeCheck template class with the first template argument matching the aforementioned tag. As stated previously, the viskores::cont::arg::TypeCheck class must contain a value static constant Boolean representing whether the type is acceptable for the corresponding viskores::cont::Invoker argument.

This example of a viskores::cont::arg::TypeCheck returns true for control objects that are viskores::cont::ArrayHandle objects with a value type that is a floating-point viskores::Vec of size 2.

Example 5.17 Defining a custom viskores::cont::arg::TypeCheck.
 1namespace viskores
 2{
 3namespace cont
 4{
 5namespace arg
 6{
 7
 8struct TypeCheckTag2DCoordinates
 9{
10};
11
12template<typename ArrayType>
13struct TypeCheck<TypeCheckTag2DCoordinates, ArrayType>
14{
15  static constexpr bool value = false;
16};
17
18template<typename T, typename Storage>
19struct TypeCheck<TypeCheckTag2DCoordinates, viskores::cont::ArrayHandle<T, Storage>>
20{
21  static constexpr bool value = viskores::ListHas<viskores::TypeListFieldVec2, T>::value;
22};
23
24} // namespace arg
25} // namespace cont
26} // namespace viskores

5.4.2. Transport

After all the argument types are checked, the Viskores dispatch mechanism must load the data into the execution environment before scheduling a job to run there. This is done with the viskores::cont::arg::Transport structure.

template<typename TransportTag, typename ContObjectType, typename DeviceAdapterTag>
struct Transport

Class for transporting from the control to the execution environment.

The Transport class is used to transport data of a certain type from the control environment to the execution environment. It is used internally in Viskores’s dispatch mechanism.

Transport is a templated class with three arguments. The first argument is a tag declaring the mechanism of transport. The second argument is the type of data to transport. The third argument is device adapter tag for the device to move the data to.

There is no generic implementation of Transport. There are partial specializations of Transport for each mechanism supported. If you get a compiler error about an incomplete type for Transport, it means you used an invalid TransportTag or it is an invalid combination of data type or device adapter.

Public Types

using ExecObjectType = typename ContObjectType::ReadPortalType

The type used in the execution environment.

All Transport specializations are expected to declare a type named ExecObjectType that is the object type used in the execution environment. For example, for an ArrayHandle, the ExecObjectType is the portal used in the execution environment.

Public Functions

template<typename InputDomainType>
ExecObjectType operator()(ContObjectType &object, const InputDomainType &inputDomain, viskores::Id inputRange, viskores::Id outputRange, viskores::cont::Token &token) const

Send data to the execution environment.

All Transport specializations are expected to have a constant parenthesis operator that takes the data in the control environment and returns an object that is accessible in the execution environment.

Parameters:
  • object – The control-side object that must be transferred to the device indicated by the DeviceAdapterTag template parameter of this struct.

  • inputDomain – A reference to the input domain argument. This might have state necessary to establish the execution-side object. For some transports, this object can be ignored.

  • inputRange – The size of the input domain. This can be used for checking the size of the data to ensure it has values for each input.

  • outputRange – The size of the output domain. This can be used for checking the size of the data to ensure it has has a place for each output.

  • token – A reference to a token object that is used to generate execution-side objects. The token ensures that the execution object remains valid while it is still being used.

The viskores::cont::arg::Transport structure is templated with three parameters. The first parameter is a tag that identifies which transport to perform. The second parameter is the type of the control parameter (after any dynamic casts). The third parameter is a device adapter tag for the device on which the data will be loaded.

A viskores::cont::arg::Transport contains a type named ExecObjectType that is the type used after data is moved to the execution environment. A viskores::cont::arg::Transport also has a const parenthesis operator that takes five arguments: the control-side object to transport to the execution environment, the control-side object that represents the input domain, the size of the input domain, the size of the output domain, and a reference to a viskores::cont::Token object that defines the scope of any generated execution-environment objects. The parenthesis operator returns an execution-side object. This operator is called in the control environment, and it returns an object that is ready to be used in the execution environment.

Transports are implemented with a defined transport tag, which by convention is defined in the viskores::cont::arg namespace and starts with TransportTag, and a partial specialization of the viskores::cont::arg::Transport structure. The following transports, identified by their tags, are provided in Viskores.

struct TransportTagExecObject

Transport tag for execution objects.

Calls PrepareForInput on the provided viskores::cont::ExecutionObjectBase object. The returned execution object is what PrepareForInput provides.

struct TransportTagArrayIn

Transport tag for input arrays.

Loads data from an viskores::cont::ArrayHandle onto the specified device using the array handle’s viskores::cont::ArrayHandle::PrepareForInput() method. The size of the array must be the same as the input domain. The returned execution object is an array portal.

struct TransportTagArrayOut

Transport tag for output arrays.

Allocates data on the specified device for an viskores::cont::ArrayHandle using the array handle’s viskores::cont::ArrayHandle::PrepareForOutput() method. The array is allocated to the size of the output domain. The returned execution object is an array portal.

struct TransportTagArrayInOut

Transport tag for in-place arrays.

Loads data from an viskores::cont::ArrayHandle onto the specified device using the array handle’s viskores::cont::ArrayHandle::PrepareForInPlace() method. The size of the array must be the same as the output domain, which is not necessarily the same size as the input domain. The returned execution object is an array portal.

struct TransportTagWholeArrayIn

Transport tag for in-place arrays with random access.

Loads data from an viskores::cont::ArrayHandle onto the specified device using the array handle’s viskores::cont::ArrayHandle::PrepareForInput() method. This transport is designed for random-access whole arrays, so unlike viskores::cont::arg::TransportTagArrayIn, the array size can be unassociated with the input domain. The returned execution object is an array portal.

struct TransportTagWholeArrayOut

Transport tag for in-place arrays with random access.

Readies data from an viskores::cont::ArrayHandle on the specified device using the array handle’s viskores::cont::ArrayHandle::PrepareForOutput() method. This transport is designed for random-access whole arrays, so unlike viskores::cont::arg::TransportTagArrayOut, the array size can be unassociated with the input domain. Thus, the array must be preallocated and its size is not changed. The returned execution object is an array portal.

The worklet will have random access to the array through a portal interface, but care should be taken to not write a value in one instance that will be overridden by another entry.

struct TransportTagWholeArrayInOut

Transport tag for in-place arrays with random access.

Loads data from an viskores::cont::ArrayHandle onto the specified device using the array handle’s viskores::cont::ArrayHandle::PrepareForInPlace() method. This transport is designed for random-access whole arrays, so unlike viskores::cont::arg::TransportTagArrayInOut, the array size can be unassociated with the input domain. The returned execution object is an array portal.

The worklet will have random access to the array through a portal interface, but care should be taken to not write a value in one instance that will be read by or overridden by another entry.

struct TransportTagAtomicArray

Transport tag for in-place arrays with atomic operations.

TransportTagAtomicArray is a tag used with the Transport class to transport ArrayHandle objects for data that is both input and output (that is, in place modification of array data). The array will be wrapped in a viskores::exec::AtomicArray class that provides atomic operations (like add and compare/swap).

struct TransportTagBitFieldIn

Loads data from a viskores::cont::BitField.

The worklet receives a bit portal, so the size of the field does not have to match the input domain.

struct TransportTagBitFieldOut

Readies a viskores::cont::BitField for writing from a worklet.

The worklet receives a bit portal, so the array must be preallocated and its size does not have to match the output domain.

struct TransportTagBitFieldInOut

Readies a viskores::cont::BitField for reading and writing from a worklet.

The worklet receives a bit portal, so its size does not have to match the output domain.

template<typename VisitTopology, typename IncidentTopology>
struct TransportTagCellSetIn

Transport tag for input arrays.

Loads data from a viskores::cont::CellSet object. TransportTagCellSetIn is a templated class with two parameters: the “visit” topology and the “incident” topology. The returned execution object is a connectivity object.

template<typename TopologyElementTag>
struct TransportTagTopologyFieldIn

Transport tag for input arrays in topology maps.

Similar to viskores::cont::arg::TransportTagArrayIn, except that the size is checked against the topology of a cell set for the input domain. The input-domain object is assumed to be a viskores::cont::CellSet.

struct TransportTagKeysIn

Transport tag for keys in a reduce by key.

Loads data from a viskores::worklet::Keys object. This transport is intended for the input domain of a viskores::worklet::WorkletReduceByKey. The returned execution object is of type viskores::exec::internal::ReduceByKeyLookup.

struct TransportTagKeyedValuesIn

Transport tag for input values in a reduce by key.

TransportTagKeyedValuesIn is a tag used with the Transport class to transport ArrayHandle objects for input values. The values are rearranged and grouped based on the keys they are associated with, which come from the input domain, which is expected to be a viskores::worklet::Keys object.

struct TransportTagKeyedValuesOut

Transport tag for input values in a reduce by key.

TransportTagKeyedValuesOut is a tag used with the Transport class to transport ArrayHandle objects for output values. The values are rearranged and grouped based on the keys they are associated with, which come from the input domain, which is expected to be a viskores::worklet::Keys object.

struct TransportTagKeyedValuesInOut

Transport tag for input values in a reduce by key.

TransportTagKeyedValuesInOut is a tag used with the Transport class to transport ArrayHandle objects for input/output values. The values are rearranged and grouped based on the keys they are associated with, which come from the input domain, which is expected to be a viskores::worklet::Keys object.

Here are some trivial examples of using viskores::cont::arg::Transport. Typically this movement is done internally in the Viskores dispatching code, so these examples are for demonstration only.

Example 5.18 Behavior of viskores::cont::arg::Transport.
 1template<typename ArrayType, typename Device>
 2void DoTransport(ArrayType inArray, ArrayType outArray, Device)
 3{
 4  VISKORES_IS_ARRAY_HANDLE(ArrayType);
 5  VISKORES_IS_DEVICE_ADAPTER_TAG(Device);
 6
 7  using viskores::cont::arg::Transport;
 8  using viskores::cont::arg::TransportTagArrayIn;
 9  using viskores::cont::arg::TransportTagArrayOut;
10  using viskores::cont::arg::TransportTagWholeArrayInOut;
11
12  viskores::cont::Token token;
13
14  // The array in transport returns a read-only array portal.
15  using ArrayInTransport = Transport<TransportTagArrayIn, ArrayType, Device>;
16  typename ArrayInTransport::ExecObjectType inPortal =
17    ArrayInTransport()(inArray, inArray, 10, 10, token);
18
19  // The array out transport returns an allocated array portal.
20  using ArrayOutTransport = Transport<TransportTagArrayOut, ArrayType, Device>;
21  typename ArrayOutTransport::ExecObjectType outPortal =
22    ArrayOutTransport()(outArray, inArray, 10, 10, token);
23
24  // The whole array in transport returns a read-only array portal wrapped in
25  // a viskores::exec::ExecutionWholeArrayConst.
26  using WholeArrayTransport = Transport<TransportTagWholeArrayInOut, ArrayType, Device>;
27  typename WholeArrayTransport::ExecObjectType wholeArray =
28    WholeArrayTransport()(inArray, inArray, 10, 10, token);
29}

A transport is created by first defining a transport-tag object, which by convention is placed in the viskores::cont::arg namespace and whose name starts with TransportTag. Then, create a specialization of the viskores::cont::arg::Transport template class with the first template argument matching the aforementioned tag. As stated previously, the viskores::cont::arg::Transport class must contain an ExecObjectType type and a parenthesis operator that turns the associated control argument into an execution-environment object.

This example internally uses a viskores::cont::ArrayHandleGroupVec to take values from an input viskores::cont::ArrayHandle and pair them to represent line segments. The resulting execution object is an array portal containing Vec-2 values of Vec-2 objects.

Example 5.19 Defining a custom viskores::cont::arg::Transport.
 1namespace viskores
 2{
 3namespace cont
 4{
 5namespace arg
 6{
 7
 8struct TransportTag2DLineSegmentsIn
 9{
10};
11
12template<typename ContObjectType, typename Device>
13struct Transport<viskores::cont::arg::TransportTag2DLineSegmentsIn,
14                 ContObjectType,
15                 Device>
16{
17  VISKORES_IS_ARRAY_HANDLE(ContObjectType);
18
19  using GroupedArrayType = viskores::cont::ArrayHandleGroupVec<ContObjectType, 2>;
20
21  using ExecObjectType = typename GroupedArrayType::ReadPortalType;
22
23  template<typename InputDomainType>
24  VISKORES_CONT ExecObjectType operator()(const ContObjectType& object,
25                                          const InputDomainType&,
26                                          viskores::Id inputRange,
27                                          viskores::Id,
28                                          viskores::cont::Token& token) const
29  {
30    if (object.GetNumberOfValues() != inputRange * 2)
31    {
32      throw viskores::cont::ErrorBadValue(
33        "2D line segment array size does not agree with input size.");
34    }
35
36    GroupedArrayType groupedArray(object);
37    return groupedArray.PrepareForInput(Device{}, token);
38  }
39};
40
41} // namespace arg
42} // namespace cont
43} // namespace viskores

Common Errors

It is fair to assume that the viskores::cont::arg::Transport control-object type matches whatever the associated viskores::cont::arg::TypeCheck allows. However, it is good practice to provide a secondary compile-time check in the viskores::cont::arg::Transport class, like the one at Example 5.19, line 17, for debugging in case there is a problem with the viskores::cont::arg::TypeCheck or this viskores::cont::arg::Transport is used with an unexpected viskores::cont::arg::TypeCheck.

5.4.3. Fetch

Before a worklet function is invoked, the Viskores internals pull the appropriate data out of the execution object and pass it to the worklet function. A class named viskores::exec::arg::Fetch is responsible for pulling this data out of execution objects and putting computed data into them.

template<typename FetchTag, typename AspectTag, typename ExecObjectType>
struct Fetch

Class for loading and storing values in thread instance.

The Fetch class is used within a thread in the execution environment to load a value from an execution object specific for the given thread instance and to store a resulting value back in the object. (Either load or store can be a no-op.)

Fetch is a templated class with four arguments. The first argument is a tag declaring the type of fetch, which is usually tied to a particular type of execution object. The second argument is an aspect tag that declares what type of data to pull/push. Together, these two tags determine the mechanism for the fetch. The third argument is the type of thread indices used (one of the classes that starts with ThreadIndices in the viskores::exec::arg namespace), which defines the type of thread-local indices available during the fetch. The fourth argument is the type of execution object associated where the fetch (nominally) gets its data from. This execution object is the data provided by the transport.

There is no generic implementation of Fetch. There are partial specializations of Fetch for each mechanism (fetch-aspect tag combination) supported. If you get a compiler error about an incomplete type for Fetch, it means you used an invalid FetchTag - AspectTag combination. Most likely this means that a parameter in an ExecutionSignature with a particular aspect is pointing to the wrong argument or an invalid argument in the ControlSignature.

Public Types

using ValueType = typename ExecObjectType::ValueType

The type of value to load and store.

All Fetch specializations are expected to declare a type named ValueType that is the type of object returned from Load and passed to Store.

Public Functions

template<typename ThreadIndicesType>
ValueType Load(const ThreadIndicesType &indices, const ExecObjectType &execObject) const

Load data for a work instance.

All Fetch specializations are expected to have a constant method named Load that takes a ThreadIndices object containing thread-local indices and an execution object and returns the value appropriate for the work instance. If there is no actual data to load (for example for a write-only fetch), this method can be a no-op and return any value.

template<typename ThreadIndicesType>
void Store(const ThreadIndicesType &indices, const ExecObjectType &execObject, const ValueType &value) const

Store data from a work instance.

All Fetch specializations are expected to have a constant method named Store that takes a ThreadIndices object containing thread-local indices, an execution object, and a value computed by the worklet call and stores that value into the execution object associated with this fetch. If the store is not applicable (for example for a read-only fetch), this method can be a no-op.

The viskores::exec::arg::Fetch structure is templated with three parameters. The first parameter is a tag that identifies which type of fetch to perform. The second parameter is a different tag that identifies the aspect of the data to fetch.

The third template parameter to a viskores::exec::arg::Fetch is the type of execution object created by the viskores::cont::arg::Transport, as described in Section 5.4.2 (Transport). This is generally where the data are fetched from.

A viskores::exec::arg::Fetch also has a pair of methods named Load() and Store() that get data from and add data to the execution object at a given domain or thread index.

In both the Load() and Store() methods, the first parameter is a thread indices object that manages the multiple indices relevant to the worklet invocation, including the input index, output index, and visit index, all of which can be different.

The specific type of the thread indices object depends on the type of worklet being invoked, but all thread indices classes implement methods named GetInputIndex, GetOutputIndex, and GetVisitIndex to get those respective indices. The thread indices object may also contain other methods to get information pertinent to the associated worklet’s execution. For example, a thread indices object associated with a topology map has methods to get the shape identifier and incident-from indices of the current input object. Thread indices objects are discussed in more detail in the later section on thread indices.

Fetches are specified with a pair of fetch and aspect tags. Fetch tags are by convention defined in the viskores::exec::arg namespace and start with FetchTag. Likewise, aspect tags are also defined in the viskores::exec::arg namespace and start with AspectTag. The viskores::exec::arg::Fetch class is partially specialized on these two tags.

The most common aspect tag is viskores::exec::arg::AspectTagDefault, and all fetch tags should have a specialization of viskores::exec::arg::Fetch with this tag. The following list of fetch tags describes the execution objects they work with and the data they pull for each aspect tag they support.

5.4.3.1. Fetch Tags

struct FetchTagExecObject

Fetch tag for execution objects.

FetchTagExecObject is a tag used with the Fetch class to retrieve execution objects. For safety, execution objects are read-only. FetchTagExecObject is almost always used in conjunction with TransportTagExecObject and vice versa.

This fetch supports only the viskores::exec::arg::AspectTagDefault aspect. Its Load() method returns the execution object in the associated parameter, and its Store() method does nothing.

struct FetchTagWholeCellSetIn

Fetch tag for whole cell sets.

This fetch supports only the viskores::exec::arg::AspectTagDefault aspect. Its Load() method returns the execution object in the associated parameter, and its Store() method does nothing.

struct FetchTagArrayDirectIn

Fetch tag for getting array values with direct indexing.

FetchTagArrayDirectIn is a tag used with the Fetch class to retrieve values from an array portal. The fetch uses direct indexing, so the thread index given to Load() is used as the index into the array. This fetch supports only the viskores::exec::arg::AspectTagDefault aspect.

struct FetchTagArrayDirectOut

Fetch tag for setting array values with direct indexing.

FetchTagArrayDirectOut is a tag used with the Fetch class to store values in an array portal. The fetch uses direct indexing, so the thread index given to Store() is used as the index into the array. The Load() method does nothing. This fetch supports only the viskores::exec::arg::AspectTagDefault aspect.

struct FetchTagArrayDirectInOut

Fetch tag for in-place modifying array values with direct indexing.

FetchTagArrayDirectInOut is a tag used with the Fetch class to do in-place modification of values in an array portal. The fetch uses direct indexing, so the thread index given to Load() and Store() is used as the index into the array. This fetch supports only the viskores::exec::arg::AspectTagDefault aspect.

When using FetchTagArrayDirectInOut with a worklet invocation with a scatter, it is a bit undefined how the in/out array should be indexed. Should it be the size of the input arrays and written back there, or should it be the size of the output arrays and pre-filled with the output? The implementation indexes based on the output because it is safer. The output will have a unique index for each worklet instance, so you don’t have to worry about writes stomping on each other (which they would inevitably do if indexed as input).

struct FetchTagCellSetIn

Fetch tag for getting topology information.

FetchTagCellSetIn is a tag used with the Fetch class to retrieve values from a topology object. Loads data from a cell set. This fetch is used with worklet topology maps to pull topology information from a cell set. When used with viskores::exec::arg::AspectTagDefault, its Load() method simply returns the cell shape of the given input cells, and its Store() method does nothing. This tag is typically used with the input-domain object, and aspects such as viskores::exec::arg::AspectTagIncidentElementCount and viskores::exec::arg::AspectTagIncidentElementIndices are used to get more detailed information.

struct FetchTagArrayTopologyMapIn

Fetch tag for getting array values determined by topology connections.

Data is loaded from the “incident” topology in a topology map. For example, in a point-to-cell topology map, this fetch gets the field values for all points attached to the cell being visited. Its Load method returns a Vec-like object containing all the incident field values, whereas its Store method does nothing. This fetch is designed for use in topology maps and expects the input domain to be a cell set.

A fetch is created by first defining a fetch-tag object, which by convention is placed in the viskores::exec::arg namespace and whose name starts with FetchTag. Then, create a specialization of the viskores::exec::arg::Fetch template class with the first template argument matching the aforementioned tag. As stated previously, the viskores::exec::arg::Fetch class must contain a pair of Load() and Store() methods that get a value out of the data and store a value in the data, respectively.

Example 5.20 Defining a custom viskores::exec::arg::Fetch.
 1namespace viskores
 2{
 3namespace exec
 4{
 5namespace arg
 6{
 7
 8struct FetchTag2DLineSegmentsIn
 9{
10};
11
12template<typename ExecObjectType>
13struct Fetch<viskores::exec::arg::FetchTag2DLineSegmentsIn,
14             viskores::exec::arg::AspectTagDefault,
15             ExecObjectType>
16{
17  using ValueType = typename ExecObjectType::ValueType;
18
19  VISKORES_SUPPRESS_EXEC_WARNINGS
20  template<typename ThreadIndicesType>
21  VISKORES_EXEC ValueType Load(const ThreadIndicesType& indices,
22                               const ExecObjectType& arrayPortal) const
23  {
24    return arrayPortal.Get(indices.GetInputIndex());
25  }
26
27  template<typename ThreadIndicesType>
28  VISKORES_EXEC void Store(const ThreadIndicesType&,
29                           const ExecObjectType&,
30                           const ValueType&) const
31  {
32    // Store is a no-op for this fetch.
33  }
34};
35
36} // namespace arg
37} // namespace exec
38} // namespace viskores

Did You Know?

The fetch defined in Example 5.20 could actually be replaced by the more general viskores::exec::arg::FetchTagArrayDirectIn that already comes with Viskores. This example is provided mostly for demonstrative purposes.

5.4.3.2. Aspect Tags

In addition to the aforementioned aspect tags that are explicitly paired with fetch tags, Viskores also provides some aspect tags that either modify the behavior of a general fetch or simply ignore the type of fetch. These descriptions are notional as any viskores::exec::arg::Fetch is free to interpret the aspect however it likes.

struct AspectTagDefault

Aspect tag to use for default load/store of data.

struct AspectTagWorkIndex

Aspect tag to use for getting the work index.

The AspectTagWorkIndex aspect tag causes the Fetch class to ignore whatever data is in the associated execution object and return the index.

struct AspectTagInputIndex

Aspect tag to use for getting the work index.

The AspectTagInputIndex aspect tag causes the Fetch class to ignore whatever data is in the associated execution object and return the index of the input element. The input index is often the same as the work index, but may be different if a scatter or mask is being used.

struct AspectTagOutputIndex

Aspect tag to use for getting the work index.

The AspectTagOutputIndex aspect tag causes the Fetch class to ignore whatever data is in the associated execution object and return the index of the output element.

struct AspectTagVisitIndex

Aspect tag to use for getting the work index.

The AspectTagVisitIndex aspect tag causes the Fetch class to ignore whatever data is in the associated execution object and return the visit index. Together, the input index and visit index are unique.

struct AspectTagCellShape

Aspect tag to use for getting the cell shape.

The AspectTagCellShape aspect tag causes the Fetch class to obtain the type of element (e.g. cell shape) from the topology object.

struct AspectTagIncidentElementCount

Aspect tag to use for getting the incident element count.

The AspectTagIncidentElementCount aspect tag causes the Fetch class to obtain the number of indices that map to the current topology element.

struct AspectTagIncidentElementIndices

Aspect tag to use for getting the visited indices.

The AspectTagIncidentElementIndices aspect tag causes the Fetch class to obtain the indices that map to the current topology element.

struct AspectTagValueCount

Aspect tag to use for getting the value count.

The AspectTagValueCount aspect tag causes the Fetch class to obtain the number of values that map to the key of the current instance. This aspect is designed for use with reduce-by-key maps.

An aspect is created by first defining an aspect-tag object, which by convention is placed in the viskores::exec::arg namespace and whose name starts with AspectTag. Then, create specializations of the viskores::exec::arg::Fetch template class where appropriate, with the second template argument matching the aforementioned tag.

This example creates a specialization of a viskores::exec::arg::Fetch to retrieve the first point of a line segment.

Example 5.21 Defining a custom aspect.
 1namespace viskores
 2{
 3namespace exec
 4{
 5namespace arg
 6{
 7
 8struct AspectTagFirstPoint
 9{
10};
11
12template<typename ExecObjectType>
13struct Fetch<viskores::exec::arg::FetchTag2DLineSegmentsIn,
14             viskores::exec::arg::AspectTagFirstPoint,
15             ExecObjectType>
16{
17  using ValueType = typename ExecObjectType::ValueType::ComponentType;
18
19  VISKORES_SUPPRESS_EXEC_WARNINGS
20  template<typename ThreadIndicesType>
21  VISKORES_EXEC ValueType Load(const ThreadIndicesType& indices,
22                               const ExecObjectType& arrayPortal) const
23  {
24    return arrayPortal.Get(indices.GetInputIndex())[0];
25  }
26
27  template<typename ThreadIndicesType>
28  VISKORES_EXEC void Store(const ThreadIndicesType&,
29                           const ExecObjectType&,
30                           const ValueType&) const
31  {
32    // Store is a no-op for this fetch.
33  }
34};
35
36} // namespace arg
37} // namespace exec
38} // namespace viskores

5.4.4. Creating New ControlSignature Tags

The type checks, transports, and fetches defined in the previous sections of this chapter conspire to interpret the arguments given to a viskores::cont::Invoker and provide data to an instance of a worklet. What remains to be defined are the tags used in the ControlSignature and ExecutionSignature that bring these three items together. These two types of tags are defined differently. This section discusses the ControlSignature tags.

A ControlSignature tag is defined by a struct (or, equivalently, a class). This structure is typically defined inside a worklet, or more typically a worklet superclass, so that it can be used without qualifying its namespace. Viskores has requirements for every defined ControlSignature tag.

The first requirement is that a ControlSignature tag must inherit from viskores::cont::arg::ControlSignatureTagBase. You will get a compile error if you attempt to use a type that is not a subclass of viskores::cont::arg::ControlSignatureTagBase in a ControlSignature.

struct ControlSignatureTagBase

The base class for all tags used in a ControlSignature.

If a new ControlSignature tag is created, it must be derived from this class in some way. This helps identify ControlSignature tags in the VISKORES_IS_CONTROL_SIGNATURE_TAG macro and allows checking the validity of a ControlSignature.

In addition to inheriting from this base class, a ControlSignature tag must define the following three typedefs: TypeCheckTag, TransportTag and FetchTag.

Subclassed by viskores::worklet::internal::WorkletBase::WholeCellSetIn< Cell, Point >, viskores::worklet::WorkletMapField::FieldIn, viskores::worklet::WorkletMapField::FieldInOut, viskores::worklet::WorkletMapField::FieldOut, viskores::worklet::WorkletMapTopology< VisitTopology, IncidentTopology >::CellSetIn, viskores::worklet::WorkletMapTopology< VisitTopology, IncidentTopology >::FieldInIncident, viskores::worklet::WorkletMapTopology< VisitTopology, IncidentTopology >::FieldInOut, viskores::worklet::WorkletMapTopology< VisitTopology, IncidentTopology >::FieldInVisit, viskores::worklet::WorkletMapTopology< VisitTopology, IncidentTopology >::FieldOut, viskores::worklet::WorkletNeighborhood::CellSetIn, viskores::worklet::WorkletNeighborhood::FieldIn, viskores::worklet::WorkletNeighborhood::FieldInNeighborhood, viskores::worklet::WorkletNeighborhood::FieldInOut, viskores::worklet::WorkletNeighborhood::FieldOut, viskores::worklet::WorkletReduceByKey::KeysIn, viskores::worklet::WorkletReduceByKey::ReducedValuesIn, viskores::worklet::WorkletReduceByKey::ReducedValuesInOut, viskores::worklet::WorkletReduceByKey::ReducedValuesOut, viskores::worklet::WorkletReduceByKey::ValuesIn, viskores::worklet::WorkletReduceByKey::ValuesInOut, viskores::worklet::WorkletReduceByKey::ValuesOut, viskores::worklet::internal::WorkletBase::AtomicArrayInOut, viskores::worklet::internal::WorkletBase::BitFieldIn, viskores::worklet::internal::WorkletBase::BitFieldInOut, viskores::worklet::internal::WorkletBase::BitFieldOut, viskores::worklet::internal::WorkletBase::ExecObject, viskores::worklet::internal::WorkletBase::WholeArrayIn, viskores::worklet::internal::WorkletBase::WholeArrayInOut, viskores::worklet::internal::WorkletBase::WholeArrayOut, viskores::worklet::internal::WorkletBase::WholeCellSetIn< VisitTopology, IncidentTopology >

The second requirement is that a ControlSignature tag must contain the following three types: TypeCheckTag, TransportTag, and FetchTag. As the names imply, these specify tags for viskores::cont::arg::TypeCheck, viskores::cont::arg::Transport, and viskores::exec::arg::Fetch, respectively, which were discussed earlier in this chapter.

The following example defines a ControlSignature tag for an array that represents 2D line segments using the classes defined in previous examples.

Example 5.22 Defining a new ControlSignature tag.
1  struct LineSegment2DCoordinatesIn : viskores::cont::arg::ControlSignatureTagBase
2  {
3    using TypeCheckTag = viskores::cont::arg::TypeCheckTag2DCoordinates;
4    using TransportTag = viskores::cont::arg::TransportTag2DLineSegmentsIn;
5    using FetchTag = viskores::exec::arg::FetchTag2DLineSegmentsIn;
6  };

Once defined, this tag can be used like any other ControlSignature tag.

Example 5.23 Using a custom ControlSignature tag.
1  using ControlSignature = void(LineSegment2DCoordinatesIn coordsIn,
2                                FieldOut vecOut,
3                                FieldIn index);

5.4.5. Creating New ExecutionSignature Tags

An ExecutionSignature tag is defined by a struct (or, equivalently, a class). This structure is typically defined inside a worklet, or more typically a worklet superclass, so that it can be used without qualifying its namespace. Viskores has requirements for every defined ExecutionSignature tag.

The first requirement is that an ExecutionSignature tag must inherit from viskores::exec::arg::ExecutionSignatureTagBase. You will get a compile error if you attempt to use a type that is not a subclass of viskores::exec::arg::ExecutionSignatureTagBase in an ExecutionSignature.

struct ExecutionSignatureTagBase

The base class for all tags used in an ExecutionSignature.

If a new ExecutionSignature tag is created, it must be derived from this class in some way. This helps identify ExecutionSignature tags in the VISKORES_IS_EXECUTION_SIGNATURE_TAG macro and allows checking the validity of an ExecutionSignature.

In addition to inheriting from this base class, an ExecutionSignature tag must define a static const viskores::IdComponent named INDEX that points to a parameter in the ControlSignature and a typedef named AspectTag that defines the aspect of the fetch.

Subclassed by viskores::exec::arg::BasicArg< ControlSignatureIndex >, viskores::exec::arg::Boundary, viskores::exec::arg::CellShape, viskores::exec::arg::IncidentElementCount, viskores::exec::arg::IncidentElementIndices, viskores::exec::arg::InputIndex, viskores::exec::arg::OutputIndex, viskores::exec::arg::ThreadIndices, viskores::exec::arg::ValueCount, viskores::exec::arg::VisitIndex, viskores::exec::arg::WorkIndex, viskores::worklet::internal::WorkletBase::Device

The second requirement is that an ExecutionSignature tag must contain a type named AspectTag, which is set to an aspect tag. As discussed in Section 5.4.3 (Fetch), the aspect tag is passed as a template argument to the viskores::exec::arg::Fetch class to modify the data it loads and stores. The numerical ExecutionSignature tags (that is, _1, _2, and so on) operate by setting AspectTag to viskores::exec::arg::AspectTagDefault, effectively engaging the default fetch.

The third requirement is that an ExecutionSignature tag contain an INDEX member that is a static const viskores::IdComponent. The number to which INDEX is set refers to the ControlSignature argument from which that data come, indexed starting at 1. The numerical ExecutionSignature tags (that is, _1, _2, and so on) operate by setting their INDEX values to the corresponding number (1, 2, and so on). An ExecutionSignature tag might take another tag as a template argument and copy the INDEX from one to the other. This allows you to use a tag to modify the aspect of another tag. Most often, this is used to apply a particular aspect to a numerical ExecutionSignature tag (that is, _1, _2, and so on). Still other ExecutionSignature tags might not need direct access to any ControlSignature arguments, such as those that pull information from thread indices. If INDEX does not matter because the execution-object parameter to the viskores::exec::arg::Fetch Load() and Store() methods is ignored, the ExecutionSignature tag can set INDEX to 1 because there is guaranteed to be at least one control argument.

The following example defines an ExecutionSignature tag to get the coordinates for only the first point in a 2D line segment. The defined tag takes another tag as an argument, generally one of the numeric tags, which is expected to point to a ControlSignature argument with a LineSegment2DCoordinatesIn tag, as defined in Example 5.22.

Example 5.24 Defining a new ExecutionSignature tag.
1  template<typename ArgTag>
2  struct FirstPoint : viskores::exec::arg::ExecutionSignatureTagBase
3  {
4    static const viskores::IdComponent INDEX = ArgTag::INDEX;
5    using AspectTag = viskores::exec::arg::AspectTagFirstPoint;
6  };

Once defined, this tag can be used like any other ExecutionSignature tag.

Example 5.25 Using a custom ExecutionSignature tag.
1  using ControlSignature = void(LineSegment2DCoordinatesIn coordsIn,
2                                FieldOut vecOut,
3                                FieldIn index);
4  using ExecutionSignature = void(FirstPoint<_1>, SecondPoint<_1>, _2);