Disclaimer: Beta API, APIs may change. Article based on investigatory work via code introspection.
Update: “Hydra” is now known as “Pixel Bender”
Loading Hydra Filters
The Flash API encapsulates Hydra Filters as a flash.display::Shader instances. To create a Shader instance we need to inject the byte code from a compiled Hydra filter file into it. We can do this in two familiar ways.
1) Using ActionScript 3 meta tags to inject the byte code directly into our compiled SWF.
[Embed(source="HydraFilter.hbc", mimeType="application/octet-stream")]
var Filter:Class;
var shader:Shader = new Shader(new Filter());
2) With the use of the URLLoader class which brings all the benefits of runtime loading.
function loadFilter() {
loader = new URLLoader();
loader.dataFormat = URLLoaderDataFormat.BINARY;
loader.addEventListener(Event.COMPLETE, loadCompleteHandler);
loader.load(new URLRequest("HydraFilter.hbc"));
}
function loadCompleteHandler(event:Event):void {
var shader:Shader = new Shader(loader.data);
}
Accessing Filter Properties and Defining Input
Hydra filters usually take different parameters in order to control their output. For example, the existing BlurFilter in Flash has blurX and blurY to control the strength of its “blurring”. Flash 10 encapsulates a single filter parameter into a dynamic flash.display::ShaderParameter class instance. This class is dynamic in order for it to be able to inspect custom parameter meta-data . For example you might have minValue / maxValue set on the filter parameter (see below).
parameter float strength
<
description: "The strength of the blur";
minValue: 0.0;
maxValue: 50.0;
>;
To set the value of a ShaderParameter, you can access the ‘value’ property of ShaderParameter and get/set Arrays on it. The size and type of data in the array all depends on the type of data the filter parameter is expecting. You can ask the ShaderParameter which flavor of Array it’s expecting by calling the ‘type’ property of ShaderParameter. In our Hydra code above you can see that the parameter is a float. A float requires a single Array element with the data type of that element being a Number. So for example, we could set … value = [10] .. and now the value of the strength is 10.
All of these ShaderParameter’s are wrapped up in another shader centric class called flash.display::ShaderData. This class is completely dynamic and gives access to ShaderParameter’s by their name. You should also be able to enumerate over them using a “for each” or “for in” loop. ShaderData also gives you assess to meta-data from the header of the Hydra filter such as its name, version, etc. So extending on the previous example, we can now say .. shaderData.strength.value = [10]
ShaderData also exposes the image input properties of a Hydra filter. For example our Hydra filter may be expecting a bitmap with an alpha channel …
input image4 src;
We represent this bitmap data input with yet another shader centric dynamic class called flash.display::ShaderInput. This class exposes some key meta-data type information about the input image property such as how many channels the bitmap should process (shaderInput.channels:int) and the index order of the property within the filter (shaderInput.index:int). The main property of this class is the ‘input’ property. Here we can set either a BitmapData, ByteArray or a Vector.<Number> instance that contains our input image. If the input is not a BitmapData type, then ‘width’ and ‘height’ properties must also be assigned to the ShaderInput instance.
Below is an example of assigning BitmapData to a ShaderInput (input image4 src) through our ShaderData:
shaderData.src.input = new BitmapData(123, 321, true, 0xFFCC00CC);
ActionScript developers should not typically need to create ShaderParameter and ShaderInput instances. All instances are created automatically to map to the requirements of the custom Hydra filter. All of these data instances should be accessible via the flash.display::Shader instance that we created before from our Hydra filter byte code. The property of Shader that gives us access to all of these parameters, inputs and meta-data is the ‘data’ property.
Example:
var shader:Shader = new Shader(hydraShaderByteCode);
trace(shader.data.name) // "DemoFilter"
trace(shader.data.version) // "1"
trace(shader.data.strength.maxValue) // 50.0
trace(shader.data.strength.description) // "The strength of the blur"
trace(shader.data.src.channels) // 4
shader.data.strength.value = [10]
shader.data.src.input = new BitmapData(123, 321, true, 0xFFCC00CC);
Filter Rendering - BitmapFilter
Once you have encapsulated your hydra filter as a Shader, everything else starts to become very familiar. We just have one more element to add. The missing element is the flash.filters::ShaderFilter class. This class extends flash.filters::BitmapFilter, which is the base class for all existing DisplayObject filters. And so, we can now utilize our Shaders in exact same way that we would traditionally work with existing built in filters such as BlurFilter.
NOTE: BitmapFilter will automatically set the first ShaderInput for you. The DisplayObject that the BitmapFilter is assigned to becomes the first image input. Additional image inputs shall need to be defined in the usual way.
In this example below, we can how we wrap the Shader up into a ShaderFilter and apply it to a Sprite.
[Embed(source="HydraFilter.hbc", mimeType="application/octet-stream")]
var MyFilter:Class;
var shader:Shader = new Shader(hydraShaderByteCode);
var filter:ShaderFilter = new new ShaderFilter(shader);
filter.data.strength.value = [10];
var sprite:Sprite = new Sprite();
sprite.graphics.beginFill(0xcc00cc);
sprite.graphics.drawRect(0,0,123,321);
sprite.filters = [filter];
addChild(sprite);
Filter Rendering - Threaded Background Jobs
Sometimes a Hydra filter might take longer than desired to render. And because Flash currently runs on a single threaded rendering loop, a taxing filter could leave an applications UI unresponsive for an undesired amount of time. To avoid this scenario, Adobe have added the ability to perform background GPU filter rendering outside of the main thread, in the form of “jobs”.
The Flash API defines the flash.display::ShaderJob class for developers to manage this background rendering process. Here is an example using a BitmapData object as our output “bucket”.
function startJob(shader:Shader, target:BitmapData) {
var job:ShaderJob = ShaderJob(shader, target);
job.addEventListener(ShaderEvent.COMPLETE, shaderJobCompleteHandler)
job.start()
}
function shaderJobCompleteHandler(e:ShaderEvent):void {
var result:BitmapData = e.bitmapData;
}