Tutorial Field Worklet

From Daxtoolkit
Jump to: navigation, search

The tutorial will walk you through writing your first field worklet. By the end you will have created a worklet that finds the inverse square root, a fairly common request when computing normals.

The first step is to write the worklet definition. A field worklet is a struct that inherits from dax::exec::WorkletMapField, implements the '()' operator, and provides two special typedefs which we will talk about later. You can consider a dax worklet to be a functor that describes what input it expects when executed in parallel.

Lets go ahead and write out the InverseSquareRoot empty structure

struct InverseSquareRoot: dax::exec::WorkletMapField
{
 
  void operator()() const
  {
 
  }
 
};

That is pretty pointless so lets continue. We know that InverseSquareRoot requires a vector of scalar values so we can fill in the implementation of the '()' operator.

struct InverseSquareRoot: dax::exec::WorkletMapField
{
 
  template<typename T>
  dax::Scalar operator()(const T& coord) const
  {
  dax::Scalar dot = dax::dot(coord,coord);
  return dax::math::RSqrt(dot);
  }
 
};

Now we have a functor that is templated across any type and find the inverseSquareRoot of it. Now we need to add the special sauce to convert from a basic functor into a Dax worklet.


struct InverseSquareRoot: dax::exec::WorkletMapField
{
  typedef void ControlSignature(Field(In),Field(Out))
  typedef _2 ExecutionSignature(_1)
 
  template<typename T>
  dax::Scalar operator()(const T& coord) const
  {
  dax::Scalar dot = dax::dot(coord,coord);
  return dax::math::RSqrt(dot);
  }
 
};

The ControlSignature describes how we will call this worklet in our code. It can be read as that this worklet requires two arguments, where the first will be read only input and in the second will write out some results.

Now the ExecutionSignature mirrors the signature of the '()' operator describing how to map the ControlSignature arguments to the actual worklet. The signature for the InverseSquareRoot shows that we will return the field we are saving and the only argument will be the input field.

Now that we have written the worklet it is time to write the c++ code to call the worklet.

std::vector<dax::Vector3> coords(10);
  for(int i=0; i < 10; i++)
    {
    const dax::Scalar x(1.0f + i);
    coords[i] = dax::Vector3(dax::math::Sin(x)/i,
                              1/(x*x),
                              0);
    }
  //make a dax array handle to the coordinates
  ArrayHandle<dax::Vector3> coordHandle = make_ArrayHandle(coords);
 
  //make a dax array handle to store the results
  ArrayHandle<dax::Scalar> result;
 
  Schedule<> scheduler; //construct a scheduler
 
  //actually execute the InverseSquareRoot in parallel!
  scheduler.Invoke(InverseSquareRoot(),coordHandle, result);

We start off by creating a vector and filling it with some random coordinates. We than create a Dax ArrayHandle to that vector. ArrayHandle is the class in Dax that manages the transfer between the control and execution structure. On the control side we don't copy or move the data passed in so the vector has to stay in scope! For more information of the ArrayHandle design please read Understanding_ArrayHandle

Next we create another ArrayHandle to hold the results, which is super nifty since it is smart and won't allocate any memory outside the execution enviornment.

Now we are ready to compute the InverseSquareRoot of all ten of our coordinates. So we construct an instance of the Schedule class choosing the default device adapter by leaving the template argument empty ( Options are: Serial, Cuda, OpenMP ). Next we call the instance like it is a method are we are done! We have just computed the inverse square root of all ten values.

Now Finally putting it all together we get:

#include <dax/Types.h>
#include <dax/exec/WorkletMapField.h>
#include <dax/math/Exp.h>
#include <dax/math/Trig.h>
 
#include <dax/cont/Schedule.h>
#include <iostream>
 
struct InverseSquareRoot: dax::exec::WorkletMapField
{
  typedef void ControlSignature(Field(In),Field(Out));
  typedef _2 ExecutionSignature(_1);
 
  template<typename T>
  dax::Scalar operator()(const T& coord)
   {
   dax::Scalar dot = dax::dot(coord,coord);
   return dax::math::RSqrt(dot);
   }
};
 
int main()
{
  using namespace dax::cont;
 
  std::vector<dax::Vector3> coords(10);
  for(int i=0; i < 10; i++)
    {
    const dax::Scalar x(1.0f + i);
    coords[i] = dax::Vector3(dax::math::Sin(x)/i,
                              1/(x*x),
                              0);
    }
  //make a dax array handle to the coordinates
  ArrayHandle<dax::Vector3> coordHandle = make_ArrayHandle(coords);
 
  //make a dax array handle to store the results
  ArrayHandle<dax::Scalar> result;
 
  Schedule< > scheduler;
  scheduler.Invoke(InverseSquareRoot(),coordHandle, result);
 
  //now lets print the results!
 
  //first copy the results from execution to control environment
  std::vector<dax::Scalar> controlResult(result.GetNumberOfValues());
  result.CopyInto(controlResult.begin());
 
  for(int i=0; i < controlResult.size(); ++i)
    {
    dax::Vector3 norm = controlResult[i] * coords[i];
    std::cout << "normal of " << coords[i][0] << ", "
                              << coords[1][1] << ", "
                              << coords[1][2]
              << " is : "     << norm[0] << ","
                                  << norm[1] << ","
                                  << norm[2] << ","
                                  << std::endl;
    }
 
}