Blink API
Running Blink Kernels

Loading a Kernel

A Blink Kernel is created from a ProgramSource object. A ProgramSource object is constructed from a string or character array containing the source code for a Blink kernel. For example, the following character array contains the source for a kernel that applies a gain operation to its input image and writes the result to its output:

Gain kernel

// Source string for the Gain kernel.
static const char* const GainKernel = "\
kernel GainKernel : ImageComputationKernel<eComponentWise>\n\
{\n\
Image<eRead, eAccessPoint> src;\n\
Image<Write> dst;\n\
\n\
param:\n\
float multiply;\n\
\n\
void define() {\n\
defineParam(multiply, \"Gain\", 1.0f);\n\
}\n\
\n\
void process() {\n\
dst() = src() * multiply;\n\
}\n\
};\n\
";

To construct a ProgramSource object from this source string, the code needed is simply:

//Construct a ProgramSource for the Gain kernel
ProgramSource gainSource(GainKernel);

Once you have the ProgramSource, this can be used to construct a Blink Kernel.

Creating a Kernel

The constructor for a Kernel takes the following arguments:

Choosing a Blink::ComputeDevice

A target ComputeDevice must be passed in when constructing a kernel. The static functions Blink::ComputeDevice::GetCurrentCPUDevice() and Blink::ComputeDevice::GetCurrentGPUDevice() give access to the currently-selected CPU device and GPU device respectively. At run-time, whenever a kernel is constructed, Blink will generate the code required to run the kernel on the target device. Since this target code is generated at on-the-fly, the decision on which device to use can also be made at run-time - for example, you might want to expose a parameter in your plug-in's UI that allows the user to choose whether or not to use the GPU for processing. You could do this by having a Bool_knob that stores its value in the local variable _useGPU. You could then choose a ComputeDevice at run-time with the following code:

const Blink::ComputeDevice &computeDevice = _useGPU ? Blink::ComputeDevice::GetCurrentGPUDevice() : Blink::ComputeDevice::GetCurrentCPUDevice();

Setting Up the Images to Process

A std::vector containing the Blink::Images to be used by the Kernel must be passed in when a kernel is constructed. Blink::Images should be added to the vector in the order in which they appear in the kernel. For example, suppose you have images called "gainInput" and "gainOutput" that you wish to use as the "src" and "dst" Images respectively for the gain_kernel above. You could set up a vector of Images as follows:

std::vector<Blink::Image> gainImages;
gainImages.push_back(gainInput);
gainImages.push_back(gainOutput);

For more information on constructing Blink::Images, or fetching them from Nuke, see section Getting Data To and From Blink Images.

Constructing the Kernel

We are now ready to construct an instance of the Gain kernel, with the following line of code:

Blink::Kernel gainKernel(gainSource, computeDevice, gainImages);

Setting Parameters to a Kernel

If your kernel has any parameters, now is the time to set values for them, after creating the kernel and before you run it. The Gain kernel has a single parameter, "Gain". Suppose the value to be passed in is stored in the local variable _gain, a floating-point value which can be set by a Float_knob in your plug-in's UI. Parameter values are set by passing the external parameter name as the first argument and the value as the second argument to the setParamValue() function:

gainKernel.setParamValue("Gain", _gain);

The external name for a kernel parameter can be set using the defineParam() function inside a kernel, as in the Gain kernel above. The first argument to this function is the internal name for the parameter, the second is the desired external name and the third is the default value it should take. If defineParam() is not called inside the kernel's define() function, the external name for the parameter will be the same as its internal one (this would be "multiply" in the Gain kernel above).

Running a Kernel

A kernel is run by calling its iterate() function.

//Run the Gain kernel
gainKernel.iterate()

This will execute the kernel at every position in the iteration space. These kernel executions happen in parallel for different positions in the iteration space - typically you will get one kernel execution per core running on the CPU at one time, and many hundreds of kernel executions running simultaneously on the GPU.

When the iteration function is called as above, with no parameters, the iteration space will be the bounds of the last image passed in to the kernel. In the example above, this would be the bounds of the gainOutput image (see Setting Up the Images to Process). You can also set a different iteration space by passing in a Blink::IterationController to the iterate function. An IterationController is constructed with a Blink::Rect that defines the bounds for the iteration space.

//Run the Gain kernel over the bounds (-150, -50) to (400, 600):
Blink::Rect iterationBounds(-150, -50, 400, 600);
Blink::IterationController iterationController;
iterationController.bounds = iterationBounds;
gainKernel.iterate(iterationBounds);

Running a Kernel More Than Once

You can call iterate() as many times as you like on the same kernel. You can also change parameter values in between calls to iterate(). However, if you want to change the images used by the kernel or the ComputeDevice it is run on, you must make a new instance of the kernel and use that instead. This is because the code required to run the kernel on the target device is generated when the kernel is constructed. Different code will be generated for different target devices, and the generated code will also depend on the images passed in - for example, it might differ according to the number of components in each of the images. Of course, you will also need to construct a new instance of the kernel if you want to change what the kernel does (the Program Source - see Loading a Kernel).

Getting Parameter Values Back From a Kernel

You can also get parameter values back from a kernel. You can use this to check the values or, more importantly, to read values that have been calculated by ImageReductionKernels. For example, suppose we have created and set up a Mean kernel as in the Example of a Reduction Kernel. This inherits a parameter, "mean", from the MeanReductionData structure. After the kernel has been run using iterate(), you can read back the value of "mean" using the getParamValue() function.

//Run the mean kernel:
meanKernel.iterate();
//Read 4 floating-point values back from the "mean" parameter and store them in "meanValue":
float meanValue[4];
meanKernel.getParamValue("mean", &meanValue, 4);


©2019 The Foundry Visionmongers, Ltd. All Rights Reserved.
www.thefoundry.co.uk