Using Pixel Bender for Math in Flash/FLEX

February 27th, 2009 by Rob

Pixel Bender has applications other than manipulating images in Flash/FLEX. While working on some map projection experiments, I decided to look for a way to offload some of the computations to avoid freezing up the display or simply taking too long. Turns out, Pixel Bender not only does math several times faster than Flash, it can also be run asynchronously!

There are a few tricks to using it, though:

Pixel Bender can only accept floats. If you want to operate on doubles, you might be able to work out something with a ByteArray (which is also permitted as Shader input) and Pixel Bender code, but that’s beyond the scope of this article, if it’s even possible.

Though you can feed a Vector of Numbers into your kernel, they are treated internally as pixels. A pixel is an array of floats with one to four elements, but the output pixel cannot have less than three elements. What this means is that you’ll be feeding three or four Numbers into the kernel at a time, executing a computation on each, then returning the results as a 3- or 4-element pixel. In the case of the map projections, I’m feeding in four numbers at a time representing two lat/lon sets.

Because you’ll be operating on chunks of three or four Numbers, the length of your vector must be a multiple of the size of each pixel. For example, if your vector is ten elements long, you must either stop after processing three pixels, or pad out the vector to a length of 12.

Because Pixel Bender considers the data you pass to be a 2-dimensional grid, you have to compute a width and height value for your data. You can use 1 for the height, but only if your data set is small. The kernel will throw an error, saying that the input is too large, otherwise. The best way to fit it all in is to break your data up into a square.

So let’s see an example. Say you want to compute the square root of a list of Numbers. You create a Vector. to store them. Assuming nVec is your vector:

// The number of pixels (assuming a 3-element pixel).
// Since our length is 100, the number of pixels isn’t going to be
// whole, but we won’t worry about that just yet.
var count : Number = nVec.length/3;

// Calculate the width of the grid. We round it up, because the
// width and height have to be whole numbers.
var w : uint = Math.ceil(Math.sqrt(count)); // w = 6

// Calculate the height of the grid.
var h : uint = Math.ceil(count/w); // h = 6

// Multiplying width (6) by height (6) gives us 36, which
// is more than our total number of pixels. We have to pad our vector.
// The length of the vector is going to be 36*3, or 108.
// Chose your padding carefully, because these values will be
// used in the computation. I’ll use 0 here.
while(nVec.length<w*h*3)
     nVec.push(0);

// Create and configure your Shader. Here, shaderCls is
// the embedded .pbj file.
var shader : Shader = new Shader(new shaderCls());
shader .data.src.width  = w;
shader .data.src.height = h;
shader .data.src.input  = nVec;

// Create the ShaderJob. The second parameter is the output
// vector for computed values. I’m using the same vector, because I
// want the new values to replace the old ones.
var job : ShaderJob = new ShaderJob(shader,nVec,w,h);

// Start the computation. Passing true into the start method
// will execute the computations synchronously. False or nothing
// will start asynchronous execution. You’ll have to use a listener
// to detect the completion of the calculations.
job.start(true);

The shader kernel can be loaded with URLLoader, or embedded and passed to the shader as a class instance. To embed the pbj file, you’ll do something like this:

[Embed(source="MyKernel.pbj",mimeType="application/octet-stream")]
private var shaderCls : Class;

Here’s the Pixel Bender kernel:

kernel SQRTKernel
<
namespace : “com.robskelly”;
vendor : “Rob Skelly”;
version : 1;
description : “Calculates the square root of a list of number.”
>
{
input image3 src;
output pixel3 dst;

void evaluatePixel(){
// the input pixel. this is really a list of 3 floats.
pixel3 px = sample(src,outCoord());
// run the sqrt on each float in the pixel
px[0] = sqrt(px[0]);
px[1] = sqrt(px[1]);
px[2] = sqrt(px[2]);
// assign the pixel to the output pixel
dst = px;
}
}

So, does using Pixel Bender for math really help? Yes! In my simple tests, using Pixel Bender for collections of Numbers larger than a few hundred is at least 3x as fast and much better still for larger sets and more complex calculations. There is some overhead associated with setting up the shader objects, but depending on what you’re doing, the savings are definitely worth it.