3.3. Simple Worklets

The simplest way to implement an algorithm in VTK‑m is to create a worklet. A worklet is fundamentally a functor that operates on an element of data. Thus, it is a class or struct that has an overloaded parenthesis operator (which must be declared const for thread safety). However, worklets are also embedded with a significant amount of metadata on how the data should be managed and how the execution should be structured.

Example 3.13 A simple worklet.
 1struct PoundsPerSquareInchToNewtonsPerSquareMeterWorklet : vtkm::worklet::WorkletMapField
 2{
 3  using ControlSignature = void(FieldIn psi, FieldOut nsm);
 4  using ExecutionSignature = void(_1, _2);
 5  using InputDomain = _1;
 6
 7  template<typename T>
 8  VTKM_EXEC void operator()(const T& psi, T& nsm) const
 9  {
10    // 1 psi = 6894.76 N/m^2
11    nsm = T(6894.76f) * psi;
12  }
13};

As can be seen in Example 3.13, a worklet is created by implementing a class or struct with the following features.

  1. The class must publicly inherit from a base worklet class that specifies the type of operation being performed (Example 3.13, line 1).

  2. The class must contain a functional type named ControlSignature (Example 3.13, line 3), which specifies what arguments are expected when invoking the class in the control environment.

  3. The class must contain a functional type named ExecutionSignature (Example 3.13, line 4), which specifies how the data gets passed from the arguments in the control environment to the worklet running in the execution environment.

  4. The class specifies an InputDomain (Example 3.13, line 5), which identifies which input parameter defines the input domain of the data.

  5. The class must contain an implementation of the parenthesis operator, which is the method that is executed in the execution environment (lines 712). The parenthesis operator must be declared const.

3.3.1. Control Signature

The control signature of a worklet is a functional type named ControlSignature. The function prototype matches what data are provided when the worklet is invoked (as described in Section 3.3.5 (Invoking a Worklet)).

Example 3.14 A ControlSignature.
1  using ControlSignature = void(FieldIn psi, FieldOut nsm);

Did You Know?

If the code in Example 3.14 looks strange, you may be unfamiliar with function types. In C++, functions have types just as variables and classes do. A function with a prototype like

void functionName(int arg1, float arg2);

has the type void(int, float). VTK‑m uses function types like this as a signature that defines the structure of a function call.

The return type of the function prototype is always void. The parameters of the function prototype are tags that identify the type of data that is expected to be passed to invoke. ControlSignature tags are defined by the worklet type and the various tags are documented more fully in Chapter 4.3 (Worklet Types). In the case of Example 3.14, the two tags FieldIn and FieldOut represent input and output data, respectively.

By convention, ControlSignature tag names start with the base concept (e.g. Field or Topology) followed by the domain (e.g. Point or Cell) followed by In or Out. For example, FieldPointIn would specify values for a field on the points of a mesh that are used as input (read only). Although they should be there in most cases, some tag names might leave out the domain or in/out parts if they are obvious or ambiguous.

3.3.2. Execution Signature

Like the control signature, the execution signature of a worklet is a functional type named ExecutionSignature. The function prototype must match the parenthesis operator (described in Section 3.3.4 (Worklet Operator)) in terms of arity and argument semantics.

Example 3.15 An ExecutionSignature.
1  using ExecutionSignature = void(_1, _2);

The arguments of the ExecutionSignature’s function prototype are tags that define where the data come from. The most common tags are an underscore followed by a number, such as _1, _2, etc. These numbers refer back to the corresponding argument in the ControlSignature. For example, _1 means data from the first control signature argument, _2 means data from the second control signature argument, etc.

Unlike the control signature, the execution signature optionally can declare a return type if the parenthesis operator returns a value. If this is the case, the return value should be one of the numeric tags (i.e. _1, _2, etc.) to refer to one of the data structures of the control signature. If the parenthesis operator does not return a value, then ExecutionSignature should declare the return type as void.

In addition to the numeric tags, there are other execution signature tags to represent other types of data. For example, the WorkIndex tag identifies the instance of the worklet invocation. Each call to the worklet function will have a unique WorkIndex. Other such tags exist and are described in the following section on worklet types where appropriate.

3.3.3. Input Domain

All worklets represent data parallel operations that are executed over independent elements in some domain. The type of domain is inherent from the worklet type, but the size of the domain is dependent on the data being operated on.

A worklet identifies the argument specifying the domain with a type alias named InputDomain. The InputDomain must be aliased to one of the execution signature numeric tags (i.e. _1, _2, etc.). By default, the InputDomain points to the first argument, but a worklet can override that to point to any argument.

Example 3.16 An InputDomain declaration.
1  using InputDomain = _1;

Different types of worklets can have different types of domain. For example a simple field map worklet has a FieldIn argument as its input domain, and the size of the input domain is taken from the size of the associated field array. Likewise, a worklet that maps topology has a CellSetIn argument as its input domain, and the size of the input domain is taken from the cell set.

Specifying the InputDomain is optional. If it is not specified, the first argument is assumed to be the input domain.

3.3.4. Worklet Operator

A worklet is fundamentally a functor that operates on an element of data. Thus, the algorithm that the worklet represents is contained in or called from the parenthesis operator method.

Example 3.17 An overloaded parenthesis operator of a worklet.
1  template<typename T>
2  VTKM_EXEC void operator()(const T& psi, T& nsm) const
3  {

There are some constraints on the parenthesis operator. First, it must have the same arity as the ExecutionSignature, and the types of the parameters and return must be compatible. Second, because it runs in the execution environment, it must be declared with the VTKM_EXEC (or VTKM_EXEC_CONT) modifier. Third, the method must be declared const to help preserve thread safety.

3.3.5. Invoking a Worklet

Previously in this chapter we discussed creating a simple worklet. In this section we describe how to run the worklet in parallel.

A worklet is run using the vtkm::cont::Invoker class.

Example 3.18 Invoking a worklet.
1  vtkm::cont::ArrayHandle<vtkm::FloatDefault> psiArray;
2  // Fill psiArray with values...
3
4  vtkm::cont::Invoker invoke;
5
6  vtkm::cont::ArrayHandle<vtkm::FloatDefault> nsmArray;
7  invoke(PoundsPerSquareInchToNewtonsPerSquareMeterWorklet{}, psiArray, nsmArray);

Using an vtkm::cont::Invoker is simple. First, an vtkm::cont::Invoker can be simply constructed with no arguments (Example 3.18, line 4). Next, the vtkm::cont::Invoker is called as if it were a function (Example 3.18, line 7).

The first argument to the invoke is always an instance of the worklet. The remaining arguments are data that are passed (indirectly) to the worklet. Each of these arguments (after the worklet) match a corresponding argument listed in the ControlSignature. So in the invocation in Example 3.18, line 7, the second and third arguments correspond the the two ControlSignature arguments given in Example 3.14. psiArray corresponds to the FieldIn argument and nmsArray corresponds to the FieldOut argument.

struct Invoker

Allows launching any worklet without a dispatcher.

Invoker is a generalized Dispatcher that is able to automatically determine how to properly launch/invoke any worklet that is passed to it. When an Invoker is constructed it is provided the desired device adapter that all worklets invoked by it should be launched on.

Invoker is designed to not only reduce the verbosity of constructing multiple dispatchers inside a block of logic, but also makes it easier to make sure all worklets execute on the same device.

Public Functions

inline explicit Invoker()

Constructs an Invoker that will try to launch worklets on any device that is enabled.

inline explicit Invoker(vtkm::cont::DeviceAdapterId device)

Constructs an Invoker that will try to launch worklets only on the provided device adapter.

template<typename Worklet, typename T, typename ...Args, typename std::enable_if<detail::scatter_or_mask<T>::value, int>::type* = nullptr>
inline void operator()(Worklet &&worklet, T &&scatterOrMask, Args&&... args) const

Launch the worklet that is provided as the first parameter.

Optional second parameter is either the scatter or mask type associated with the worklet. Any additional parameters are the ControlSignature arguments for the worklet.

template<typename Worklet, typename T, typename U, typename ...Args, typename std::enable_if<detail::scatter_or_mask<T>::value && detail::scatter_or_mask<U>::value, int>::type* = nullptr>
inline void operator()(Worklet &&worklet, T &&scatterOrMaskA, U &&scatterOrMaskB, Args&&... args) const

Launch the worklet that is provided as the first parameter.

Optional second parameter is either the scatter or mask type associated with the worklet. Optional third parameter is either the scatter or mask type associated with the worklet. Any additional parameters are the ControlSignature arguments for the worklet.

template<typename Worklet, typename T, typename ...Args, typename std::enable_if<!detail::scatter_or_mask<T>::value, int>::type* = nullptr>
inline void operator()(Worklet &&worklet, T &&t, Args&&... args) const

Launch the worklet that is provided as the first parameter.

Optional second parameter is either the scatter or mask type associated with the worklet. Any additional parameters are the ControlSignature arguments for the worklet.

inline vtkm::cont::DeviceAdapterId GetDevice() const

Get the device adapter that this Invoker is bound too.

3.3.6. Preview of More Complex Worklets

This chapter demonstrates the creation of a worklet that performs a very simple math operation in parallel. However, we have just scratched the surface of the kinds of algorithms that can be expressed with VTK‑m worklets. There are many more execution patterns and data handling constructs. The following example gives a preview of some of the more advanced features of worklets.

Example 3.19 A more complex worklet.
 1  struct EdgesExtract : vtkm::worklet::WorkletVisitCellsWithPoints
 2  {
 3    using ControlSignature = void(CellSetIn, FieldOutCell edgeIndices);
 4    using ExecutionSignature = void(CellShape, PointIndices, VisitIndex, _2);
 5    using InputDomain = _1;
 6
 7    using ScatterType = vtkm::worklet::ScatterCounting;
 8
 9    template<typename CellShapeTag,
10             typename PointIndexVecType,
11             typename EdgeIndexVecType>
12    VTKM_EXEC void operator()(CellShapeTag cellShape,
13                              const PointIndexVecType& globalPointIndicesForCell,
14                              vtkm::IdComponent edgeIndex,
15                              EdgeIndexVecType& edgeIndices) const
16    {

We will discuss the many features available in the worklet framework throughout Part 4 (Advanced Development).