Learning AGAL with AGALMacroAssembler and OpenGL Examples

So the other day I sat down and thought to myself: self, it’s high time you learn you some of this new fancy pants AGAL that’s all the rage these days. Now, after a few days of playing around I am by no means a master at the art of shader programming, but due to the still huge lack of available material on the subject, I figured I’d share a few things I’ve learned. Please feel free to post some comments correcting me where I’m wrong as I’m bound to be. I’m hoping though, that this may help out a few folks who, like me, learned programming via actionscript and have never actually written a shader in their life.

I’m not going to go into the very basics of AGAL and uploading index and vertex data. There are plenty of tutorials out there about that. And if you’re looking for a book, this one by Christer Kaitila (aka Breakdance McFunkypants) is fantastic. In fact, here, I’m only going to use the utmost basic of vertex shaders and focus entirely on fragment shaders.

I know when most people start getting into shader programming they jump straight into the world of 3D. If you have the stamina and knowhow for such a leap, the more power to you. Me though, I’m still sticking to 2D fragment shaders because (a) they’re a good deal simpler to get your head around and (b) I’m completely obsessed those old school 2D demo effects – the likes you see on this site under the Plane Deformations in the drop down box. In fact, if you click on the ‘Fly’ example in that drop down box, that’s what we’re going to be making in Flash here shortly. You can check out the final result here.

Now when I started digging into AGAL (that is when I started googling around), one of the things I came across was the AGALMacroAssembler (as opposed to the AGALMiniAssembler which would normally be used to parse AGAL strings). Unfortunately, there is nearly no documentation about the macro assembler at all. I’m not sure why it’s being kept such a secret. The one place I found that mentioned it was the Ryan Speets site and I’m very thankful for that. There was still no real explanation of how to use it though so I just had to wing it.

I’ll try, then, to start at the beginning. For those who don’t know, AGAL is, essentially, assembly language (Adobe Graphics Assembly Language, that is).  The syntax can be very confusing when first checking it out as all operations are written like OPERATION DESTINATION, SOURCE1, SOURCE2. So, for example you just wanted to say something like: z = x + y, it would look something like this: add z, x, y. Using AGALMacroAssembler though, we can avoid that cryptic looking syntax. In fact, there are essentially 4 wonderful things about using AGALMacroAssembler: (1) You can use simpler syntax with actual math operators, (2) You can write comments – a HUGE plus, (3) You can set alias values (basically you can write your own variable names) and (4) You can write simple macros (essentially, functions). I won’t be bothering with 3 or 4 here, but 1 and 2 are more than enough reasons to hop right into using the AGALMacroAssembler.

Actually, one other benefit is the fact that you can keep all your shader code in a separate file. So, with that said, if you’d like to follow along and learn as I did, start a new actionscript project and, in a place where you would normally place your embed files (or even on the project root, for all I care), create a new text file named ‘shader.macro’. Inside that file type this:

// ***** vertex shader - the simplest around *****

op = mul4x4(va0, vc0);
v0 = va1;

####

// ***** fragment shader *****

ft0 = tex<2d,repeat,linear,nomip>( v0, fs0 );
oc = ft0;

A tiny explanation: Our vertex shader here is the most basic what can be found. All you really need to know is that it spits out uv coordinates into the register v0 which can be accessed in our fragment shader. In this case our fragment shader samples our uploaded texture (fs0) at those uv coordinates, stores that value in the ft0 register then moves that register into the oc (or Output Color register – oc is always the output of a fragment shader).

And what are these registers of which I speak? Well, this is overly simplified, but you can think of registers as the variables of your shader program. In a fragment shader there are 8 temporary registers available (ft0 – ft7) and 28 constant registers (fc0 – fc27). These constant registers hold data uploaded to your program from Actionscript. Each register also has 4 components all of which are like Number types in actionscript. In fact, the best way to think of registers in Actionscript terms is that they are Vectors of Numbers, each one with a length of 4. Instead of accessing them with square brackets like myVector[0], though, you access them with a dot and x, y, z, or w; as in ft0.x or fc1.w.

Let’s actually get something compiled though. For my experiments, I came up with a simple Actionscript template which I could easily update with different macro shaders. Here is the basic version.

/**
 *	Copyright (c) 2012 Devon O. Wolfgang
 *
 *	Permission is hereby granted, free of charge, to any person obtaining a copy
 *	of this software and associated documentation files (the "Software"), to deal
 *	in the Software without restriction, including without limitation the rights
 *	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *	copies of the Software, and to permit persons to whom the Software is
 *	furnished to do so, subject to the following conditions:
 *
 *	The above copyright notice and this permission notice shall be included in
 *	all copies or substantial portions of the Software.
 *
 *	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *	THE SOFTWARE.
 */

package
{
	import com.adobe.utils.AGALMacroAssembler;
	import flash.display.BitmapData;
	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.display3D.Context3D;
	import flash.display3D.Context3DProgramType;
	import flash.display3D.Context3DTextureFormat;
	import flash.display3D.Context3DVertexBufferFormat;
	import flash.display3D.IndexBuffer3D;
	import flash.display3D.Program3D;
	import flash.display3D.textures.Texture;
	import flash.display3D.VertexBuffer3D;
	import flash.events.Event;
	import flash.geom.Matrix3D;

	[SWF(width="512", height="512", frameRate="60", backgroundColor="#000000")]
	public class ShaderExample extends Sprite
	{
		// make sure this image is in power of 2
		[Embed(source="wall.jpg")]
		protected const TEXTURE:Class;

		// shader code
		[Embed(source="shader.macro", mimeType="application/octet-stream")]
		protected const ASM:Class;

		private var mContext3d:Context3D;
		private var mVertBuffer:VertexBuffer3D;
		private var mIndexBuffer:IndexBuffer3D;
		private var mProgram:Program3D;
		private var mTexture:Texture;
		private var mTextureData:BitmapData;

		private var mMatrix:Matrix3D = new Matrix3D();

		public function ShaderExample()
		{
			if (stage) init();
			else addEventListener(Event.ADDED_TO_STAGE, init);
		}

		private function init(event:Event = null):void
		{
			removeEventListener(Event.ADDED_TO_STAGE, init);

			initStage();
			initTexture();

			addEventListener(Event.ENTER_FRAME, onTick);
		}

		private function initTexture():void
		{
			mTextureData = new TEXTURE().bitmapData;

			stage.stage3Ds[0].addEventListener( Event.CONTEXT3D_CREATE, initStage3d );
			stage.stage3Ds[0].requestContext3D();
		}

		private function initStage():void
		{
			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.align = StageAlign.TOP_LEFT;
		}

		private function initStage3d(event:Event):void
		{
			mContext3d = stage.stage3Ds[0].context3D;
			mContext3d.enableErrorChecking = true;	// set this to false when complete to improve performance

			mContext3d.configureBackBuffer(stage.stageWidth, stage.stageHeight, 1, true);

			var vertices:Vector. = Vector.([
			//	x		y		z			u 	v
				-1.0, 	-1.0, 	0,  		0, 0,
				-1.0,  	1.0, 	0, 			0, 1,
				 1.0,  	1.0, 	0, 			1, 1,
				 1.0, 	-1.0, 	0,			1, 0  ]);

			mVertBuffer = mContext3d.createVertexBuffer(4, 5);
			mVertBuffer.uploadFromVector(vertices, 0, 4);

			mIndexBuffer = mContext3d.createIndexBuffer(6);
			mIndexBuffer.uploadFromVector (Vector.([0, 1, 2, 2, 3, 0]), 0, 6);

			mTexture = mContext3d.createTexture(mTextureData.width, mTextureData.height, Context3DTextureFormat.BGRA, true);
			mTexture.uploadFromBitmapData(mTextureData);

			// va0 holds xyz
			mContext3d.setVertexBufferAt(0, mVertBuffer, 0, Context3DVertexBufferFormat.FLOAT_3);

			// va1 holds uv
			mContext3d.setVertexBufferAt(1, mVertBuffer, 3, Context3DVertexBufferFormat.FLOAT_2);

			generateMacroProg();

			mContext3d.setTextureAt(0, mTexture);
			mContext3d.setProgram(mProgram);
		}	

		/**
		 * Creates the shader program from the embedded .macro file
		 */
		private function generateMacroProg():void
		{
			var asm:String = String(new ASM());

			// split the vertex and fragment shaders
			var codeSplit:Array = asm.split("####");

			var macroVertex:AGALMacroAssembler 		= new AGALMacroAssembler();
			var macroFragment:AGALMacroAssembler 	= new AGALMacroAssembler();

			macroVertex.assemble(Context3DProgramType.VERTEX, codeSplit[0]);
			macroFragment.assemble(Context3DProgramType.FRAGMENT, codeSplit[1]);

		//	trace("VERTEX: \n" + macroVertex.asmCode);
		//	trace("FRAGMENT: \n" + macroFragment.asmCode);

			mProgram = mContext3d.createProgram();
			mProgram.upload( macroVertex.agalcode, macroFragment.agalcode);
		}

		private var mTime:Number = 0.0;
		private function onTick(event:Event):void
		{
			if ( !mContext3d )
				return;

			mContext3d.clear ( 0, 0, 0, 1 );

			// set vertex data from blank Matrix3D
			mContext3d.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, mMatrix, true);

			// Fragment shader constants go here

			mContext3d.drawTriangles(mIndexBuffer);
			mContext3d.present();
		}
	}
}

Note that this Actionscript file requires an embedded image to use as your texture. This can be any old image you’d like, but make sure it’s dimensions are in powers of 2 (2, 4, 8, 16, 32, 64, etc). Me, I’m using a picture of brick wall that’s 512×512. Now, if you compile that, you’ll see – well, you should just see your embedded image taking up the screen. Not very exciting, but it’s a start. Let’s try screwing around a little bit, just to get an idea of what’s going on. Open your shader.macro file back up and try changing it to this:

// ***** vertex shader - the simplest around *****

op = mul4x4(va0, vc0);
v0 = va1;

####

// ***** fragment shader *****

ft0 = tex<2d,repeat,linear,nomip>( v0, fs0 );
ft1.x = ft0.z;
ft1.y = ft0.x;
ft1.z = ft0.y;
ft1.w = ft0.w;
oc = ft1;

Compile again and now you’ll see the same image but all crazy colored. What the hell happened? Well, it turns out that the x, y, z, w register components actually map to the red, green, blue and alpha of your image (as a side note, when using the AGALMacroAssembler, you can actually use r, g, b, and a instead of x, y, z and w, but I wouldn’t recommend it. Just stick to xyzw and know that they correspond to  red, green, blue, and alpha). So, with this little twist, you’ve now mapped the original blue to the red, the original red to the green, and the orignal green to the blue of your output color (the alpha stays the same).

Let’s try sending the shader program some values from Actionscript now. As I mentioned earlier, Actionscript values are stored in the constant registers, fc0 – fc27. Also remember that these registers are really just vectors of numbers. So to send some values, we just use the setProgramConstantsFromVector() method and send a vector of 4 numbers (even if you plan on only using 1 component value, be sure to send all 4 – just fill out the unused with 1’s).

Change your onTick() method to look like this then:

private var mTime:Number = 0.0;
private function onTick(event:Event):void
{
	if ( !mContext3d )
		return;

	mContext3d.clear ( 0, 0, 0, 1 );

	// set vertex data from blank Matrix3D
	mContext3d.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, mMatrix, true);

	// Fragment shader constants go here

	// FC0
	mContext3d.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, Vector.([Math.abs(Math.sin(mTime)), 1, 1, 1]));

	mContext3d.drawTriangles(mIndexBuffer);
	mContext3d.present();

	mTime += .025;
}

Then change your shader.macro file as so:

// ***** vertex shader - the simplest around *****

op = mul4x4(va0, vc0);
v0 = va1;

####

// ***** fragment shader *****

ft0 = tex<2d,repeat,linear,nomip>( v0, fs0 );
ft0.x = ft0.x * fc0.x;
oc = ft0;

Compile again, and wowie zowie. You now have an image with a red value that pulses with time. Easy peasy. Try doing the same to the other color components or using +, -, or / instead of * just to get an idea of what happens.

So, that’s all good and well, but how do we do something cool? Well, unfortunately, there are very few AGAL shader examples scattered around the internet. There are, though, at least a bijillion and 6, OpenGL / GLSL examples to be found. Go back and look at that ‘Fly’ example on the Shadertoy page. In fact, copy that main function and paste it into your shader.macro file (remember with AGALMacroAssembler we can add comments just as we can in Actionscript). So now your file should look like this:

// ***** vertex shader - the simplest around *****

op = mul4x4(va0, vc0);
v0 = va1;

####

// ***** fragment shader *****

/*
void main(void)
{
    vec2 p = -1.0 + 2.0 * gl_FragCoord.xy / resolution.xy;
    vec2 uv;

    float an = time*.25;

    float x = p.x*cos(an)-p.y*sin(an);
    float y = p.x*sin(an)+p.y*cos(an);

    uv.x = .25*x/abs(y);
    uv.y = .20*time + .25/abs(y);

    gl_FragColor = vec4(texture2D(tex0,uv).xyz * y*y, 1.0);
}
*/

ft0 = tex<2d,repeat,linear,nomip>( v0, fs0 );
ft0.x = ft0.x + fc0.x;
oc = ft0;

The first thing to note about GLSL fragment shaders is that there is always a gl_FragCoord and a gl_FragColor. The gl_FragCoord is the input uv position (in our case that’s v0) and the gl_FragColor is always the output (recall in AGAL terms that means ‘oc’). You will also see ‘resolution’ used quite a bit. This is a little trickier, but I usually just make it 1 x 1 and that seems to do the trick as we’ll do here

Now the first thing you will want to do when porting OpenGL examples to AGAL is to weed out the constants. This is actually trickier than it might seem. AGAL will not let you perform any mathematical operations on two constants – that will need to be pre computed in Actionscript. For example, you cannot say ft1 = fc1.x * fc2.y – you will have to multiply those things together in actionscript and send them along as a separate constant. So, what I do is look through the OpenGL and write comments about what constants I’ll need right there in my .macro file. Doing so now and I get something that looks like this (I also like to try to group varying items like time into a single register):

// ***** vertex shader - the simplest around *****

op = mul4x4(va0, vc0);
v0 = va1;

####

// ***** fragment shader *****

/*
void main(void)
{
    vec2 p = -1.0 + 2.0 * gl_FragCoord.xy / resolution.xy;
    vec2 uv;

    float an = time*.25;

    float x = p.x* cos(an) - p.y * sin(an);
    float y = p.x*sin(an)+p.y*cos(an);

    uv.x = .25*x/abs(y);
    uv.y = .20*time + .25/abs(y);

    gl_FragColor = vec4(texture2D(tex0,uv).xyz * y*y, 1.0);
}
*/

// CONSTANTS
// FC0 = resolution = [ 1, 1, 1, 1 ]
// FC1 = time = [ cos(time * .25), sin(time * .25), .20 * time, 1 ]
// FC2 = constants = [ -1, 2, .25, 1 ]

ft0 = tex<2d,repeat,linear,nomip>( v0, fs0 );
ft0.x = ft0.x + fc0.x;
oc = ft0;

Remember that you’ll want to fill all four components of the register which is why you see them padded with 1’s. Go ahead and add these straight into the onTick method of your Actionscript file like so:

private var mTime:Number = 0.0;
private function onTick(event:Event):void
{
	if ( !mContext3d )
		return;

	mContext3d.clear ( 0, 0, 0, 1 );

	// set vertex data from blank Matrix3D
	mContext3d.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, mMatrix, true);

	// Fragment shader constants go here

	// FC0 = resolution = [ 1, 1, 1, 1 ]
	mContext3d.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, Vector.([1, 1, 1, 1]));

	// FC1 = time = [ cos(time * .25), sin(time * .25), .20 * time, 1 ]
	mContext3d.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 1, Vector.([
		Math.cos(mTime * .25),
		Math.sin(mTime * .25),
		.20 * mTime,
		1]));

	// FC2 = constants = [ -1, 2, .25, 1 ]
	mContext3d.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, Vector.([-1, 2, .25, 1]));

	mContext3d.drawTriangles(mIndexBuffer);
	mContext3d.present();

	mTime += .025;
}

The next thing you’ll want to do is ‘unwrap’ the glsl code. Essentially, you’ll want to have only one math operation per line. You can use some generic a, b, c variable names for the time being, but use the correct constant registers and  components. Eventually, you’ll get something that looks like this:

// ***** vertex shader - the simplest around *****

op = mul4x4(va0, vc0);
v0 = va1;

####

// ***** fragment shader *****

/*
void main(void)
{
    vec2 p = -1.0 + 2.0 * gl_FragCoord.xy / resolution.xy;
    vec2 uv;

    float an = time*.25;

    float x = p.x* cos(an) - p.y * sin(an);
    float y = p.x*sin(an)+p.y*cos(an);

    uv.x = .25*x/abs(y);
    uv.y = .20*time + .25/abs(y);

    gl_FragColor = vec4(texture2D(tex0,uv).xyz * y*y, 1.0);
}
*/

// CONSTANTS
// FC0 = resolution = [ 1, 1, 1, 1 ]
// FC1 = time = [ cos(time * .25), sin(time * .25), .20 * time, 1 ]
// FC2 = constants = [ -1, 2, .25, 1 ]

// STEP 1 - P
// p = -1.0 + 2.0 * gl_FragCoord.xy / resolution.xy;
a = v0.xy / fc0.xy;
b = fc2.y * a;
p = fc2.x + b;

// STEP 2 - X
// x = p.x* cos(an) - p.y * sin(an);
a = p.x * fc1.x;
b = p.y * fc1.y;
x = a - b;

// STEP 3 - Y
// p.x*sin(an)+p.y*cos(an);
a = p.x * fc1.y;
b = p.y * fc1.x;
y = a + b;

// STEP 4 - UV
// instantiate UV as resolution
uv = fc0;

// uv.x = .25*x/abs(y);
a = fc2.z * x;
// this is regular agal to get absolute value
abs b, y
uv.x = a / b;

// uv.y = .20*time + .25/abs(y);
abs a, y
b = fc2.z / a;
uv.y = fc1.z + b;

// now the output
// gl_FragColor = vec4(texture2D(tex0,uv).xyz * y*y, 1.0);

// sample the texture using uv
sample = tex<2d,repeat,linear,nomip>( uv, fs0 );
a = y * y;;
out = sample.xyz * a;
out.w = fc0.x;
oc = out;

Now comes the tricky part. Now we need to replace those generic variable names with temporary registers. Remember there are only 8 to choose from so you’ll really want to keep in mind what you want to save to use for later computations and what you can overwrite. With a little perseverance though, you may come up with something like this:

// ***** vertex shader - the simplest around *****

op = mul4x4(va0, vc0);
v0 = va1;

####

// ***** fragment shader *****

/*
void main(void)
{
    vec2 p = -1.0 + 2.0 * gl_FragCoord.xy / resolution.xy;
    vec2 uv;

    float an = time*.25;

    float x = p.x* cos(an) - p.y * sin(an);
    float y = p.x*sin(an)+p.y*cos(an);
     
    uv.x = .25*x/abs(y);
    uv.y = .20*time + .25/abs(y);

    gl_FragColor = vec4(texture2D(tex0,uv).xyz * y*y, 1.0);
}
*/

//		Step 1	- FT3 = P
//	ec2 p = -1.0 + 2.0 * v0.xy / resolution.xy;
ft0 = v0;
ft1 = ft0.xy / fc3.xy;
ft2 = fc6.x * ft1;
ft3 = ft2 - fc5.x;


//		Step 2	- FT4 = x
//	float x = p.x * cos(an) - p.y * sin(an);
//	an = .25 * time
//	fc4.y = sin(an)
//	fc4.z = cos(an)
ft1 = ft3.y * fc4.y;
ft2 = ft3.x * fc4.z;
ft4 = ft2 - ft1;


//		Step 3  - FT5 = y
//	float y = p.x*sin(an)+p.y*cos(an);
ft1 = ft3.x * fc4.y;
ft2 = ft3.y * fc4.z;
ft5 = ft1 + ft2;


//		Step3	- FT6 = uv
//	vec2 uv;
//	'instantiate' ft6 as 1, 1, 1, 1
ft6 = fc5;


//		Step 4  - FT6.x = uv.x 
//	uv.x = .25*x/abs(y);
abs ft1, ft5
ft2 = fc6.y * ft4;
ft6.x = ft2 / ft1;


//		Step 5	- FT6.y = uv.y
//	uv.y = .20*time + .25/abs(y);
abs ft1, ft5
ft2 = fc6.y / ft1;
ft6.y = fc4.x + ft2;


//		Step 6	- output
//	gl_FragColor = vec4(texture2D(tex0,uv).xyz * y*y, 1.0);
//	gl_FragColor = oc
ft0 = tex<2d,repeat,linear,nomip>( ft6, fs0 ); //texture2D(tex0,uv);
ft1 = ft5 * ft5;
ft1 = ft0.xyz * ft1;
ft1.w = fc5.x;
oc = ft1;

Now, because I went back and used a previous version of something I had written (yes, I cheated), here is the Actionscript that goes with the above AGAL macro in its entirety:

/**
 *	Copyright (c) 2012 Devon O. Wolfgang
 *
 *	Permission is hereby granted, free of charge, to any person obtaining a copy
 *	of this software and associated documentation files (the "Software"), to deal
 *	in the Software without restriction, including without limitation the rights
 *	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *	copies of the Software, and to permit persons to whom the Software is
 *	furnished to do so, subject to the following conditions:
 *
 *	The above copyright notice and this permission notice shall be included in
 *	all copies or substantial portions of the Software.
 *
 *	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *	THE SOFTWARE.
 */

package
{
	import com.adobe.utils.AGALMacroAssembler;
	import flash.display.BitmapData;
	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.display3D.Context3D;
	import flash.display3D.Context3DProgramType;
	import flash.display3D.Context3DTextureFormat;
	import flash.display3D.Context3DVertexBufferFormat;
	import flash.display3D.IndexBuffer3D;
	import flash.display3D.Program3D;
	import flash.display3D.textures.Texture;
	import flash.display3D.VertexBuffer3D;
	import flash.events.Event;
	import flash.geom.Matrix3D;

	[SWF(width="512", height="512", frameRate="60", backgroundColor="#000000")]
	public class ShaderExample extends Sprite
	{
		// make sure this image is in power of 2
		[Embed(source="wall.jpg")]
		protected const TEXTURE:Class;
		
		// shader code
		[Embed(source="shader.macro", mimeType="application/octet-stream")]
		protected const ASM:Class;
		
		private var mContext3d:Context3D;
		private var mVertBuffer:VertexBuffer3D;
		private var mIndexBuffer:IndexBuffer3D; 
		private var mProgram:Program3D;
		private var mTexture:Texture;
		private var mTextureData:BitmapData;
		
		private var mMatrix:Matrix3D = new Matrix3D();
		
		public function ShaderExample()
		{	
			if (stage) init();
			else addEventListener(Event.ADDED_TO_STAGE, init);	
		}
		
		private function init(event:Event = null):void
		{
			removeEventListener(Event.ADDED_TO_STAGE, init);
			
			initStage();
			initTexture();
			
			addEventListener(Event.ENTER_FRAME, onTick);
		}
		
		private function initTexture():void
		{
			mTextureData = new TEXTURE().bitmapData;
			
			stage.stage3Ds[0].addEventListener( Event.CONTEXT3D_CREATE, initStage3d );
			stage.stage3Ds[0].requestContext3D();
		}
		
		private function initStage():void
		{
			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.align = StageAlign.TOP_LEFT;
		}
		
		private function initStage3d(event:Event):void
		{
			mContext3d = stage.stage3Ds[0].context3D;		
			mContext3d.enableErrorChecking = true;	// set this to false when complete to improve performance
			
			mContext3d.configureBackBuffer(stage.stageWidth, stage.stageHeight, 1, true);
			
			var vertices:Vector. = Vector.([
			//	x		y		z			u 	v
				-1.0, 	-1.0, 	0,  		0, 0, 
				-1.0,  	1.0, 	0, 			0, 1,
				 1.0,  	1.0, 	0, 			1, 1,
				 1.0, 	-1.0, 	0,			1, 0  ]);
			
			mVertBuffer = mContext3d.createVertexBuffer(4, 5);
			mVertBuffer.uploadFromVector(vertices, 0, 4);
			
			mIndexBuffer = mContext3d.createIndexBuffer(6);			
			mIndexBuffer.uploadFromVector (Vector.([0, 1, 2, 2, 3, 0]), 0, 6);
			
			mTexture = mContext3d.createTexture(mTextureData.width, mTextureData.height, Context3DTextureFormat.BGRA, true);
			mTexture.uploadFromBitmapData(mTextureData);
			
			// va0 holds xyz
			mContext3d.setVertexBufferAt(0, mVertBuffer, 0, Context3DVertexBufferFormat.FLOAT_3);
			
			// va1 holds uv
			mContext3d.setVertexBufferAt(1, mVertBuffer, 3, Context3DVertexBufferFormat.FLOAT_2);

			generateMacroProg();
			
			mContext3d.setTextureAt(0, mTexture);
			mContext3d.setProgram(mProgram);
		}	
		
		/**
		 * Creates the shader program from the embedded .macro file
		 */
		private function generateMacroProg():void
		{
			var asm:String = String(new ASM());
			
			// split the vertex and fragment shaders
			var codeSplit:Array = asm.split("####");
			
			var macroVertex:AGALMacroAssembler 		= new AGALMacroAssembler();
			var macroFragment:AGALMacroAssembler 	= new AGALMacroAssembler();
			
			macroVertex.assemble(Context3DProgramType.VERTEX, codeSplit[0]);
			macroFragment.assemble(Context3DProgramType.FRAGMENT, codeSplit[1]);
			
		//	trace("VERTEX: \n" + macroVertex.asmCode);
		//	trace("FRAGMENT: \n" + macroFragment.asmCode);
			
			mProgram = mContext3d.createProgram();
			mProgram.upload( macroVertex.agalcode, macroFragment.agalcode);
		}
		
		
		private var mTime:Number = 0.0;
		private function onTick(event:Event):void
		{
			if ( !mContext3d ) 
				return;
			
			mContext3d.clear ( 0, 0, 0, 1 );
			
			// set vertex data from blank Matrix3D
			mContext3d.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, mMatrix, true);
			
			// Fragment shader constants go here
			
			// resolution
			mContext3d.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 3, Vector.( [ 1, 1, 1, 1 ]) );
			
			// time
			mContext3d.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 4, Vector.([ .20 * mTime, Math.sin(.25 * mTime) , Math.cos(.25 * mTime), 1  ]) );
			
			// ONE (identity)
			mContext3d.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 5, Vector.([ 1, 1, 1, 1 ]) );
			
			// Numbers
			mContext3d.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 6, Vector.([ 2, .250, 1, 1 ]) );
			
			mContext3d.drawTriangles(mIndexBuffer);
			mContext3d.present();
			
			mTime += .025;
		}
	}
}

Cross your fingers, compile, and you should have something like this.

And that is, as they say, that. Hopefully, that can help some folks out. Here’s a few other examples I’ve been playing around with (ported from OpenGL sources posted round the web):

A little pulse thing
A little bokeh like thing
A little metablob thing
A little post processing image effect thing

Oh, and I suppose I should have mentioned, you can find AGALMacroAssembler here.

Have fun and happy shading…

Date:
Category: