LogoLogo
  • Home
  • Projects
  • About
  • Contact

Learning AGAL with AGALMacroAssembler and OpenGL Examples

Devon O. · June 03, 2012 · Actionscript, Flash, Shaders · 16 comments
8

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:

C#
1
2
3
4
5
6
7
8
9
10
11
// ***** 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.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
/**
*    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.<Number>([
            //    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:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ***** 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:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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.<Number>([Math.abs(Math.sin(mTime)), 1, 1, 1]));
 
    mContext3d.drawTriangles(mIndexBuffer);
    mContext3d.present();
 
    mTime += .025;
}

Then change your shader.macro file as so:

C#
1
2
3
4
5
6
7
8
9
10
11
12
// ***** 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:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// ***** 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):

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// ***** 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:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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.<Number>([1, 1, 1, 1]));
 
    // FC1 = time = [ cos(time * .25), sin(time * .25), .20 * time, 1 ]
    mContext3d.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 1, Vector.<Number>([
        Math.cos(mTime * .25),
        Math.sin(mTime * .25),
        .20 * mTime,
        1]));
 
    // FC2 = constants = [ -1, 2, .25, 1 ]
    mContext3d.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, Vector.<Number>([-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:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// ***** 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:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
// ***** 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:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
/**
*    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.<Number> = Vector.<Number>([
            //    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.<uint>([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.<Number>( [ 1, 1, 1, 1 ]) );
            
            // time
            mContext3d.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 4, Vector.<Number>([ .20 * mTime, Math.sin(.25 * mTime) , Math.cos(.25 * mTime), 1  ]) );
            
            // ONE (identity)
            mContext3d.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 5, Vector.<Number>([ 1, 1, 1, 1 ]) );
            
            // Numbers
            mContext3d.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 6, Vector.<Number>([ 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…

  Facebook   Pinterest   Twitter   Google+
agalagalmacroassemblerglslopenglstage3d
  • OBO_FlashBox – LightBox in Flash (with added 3d)
    December 06, 2008 · 16 comments
    2895
    3
    Read more
  • Last.FM + Twitter = Warbler
    March 08, 2009 · 32 comments
    3192
    3
    Read more
  • Heads Will Roll
    December 29, 2009 · 8 comments
    2335
    6
    Read more
16 Comments:
  1. I’m happy to come across someone else who is taking the time to learn how AGAL works. Great article. It seems Adobe has removed their GitHub account because I have checked for any updates and it had 404d. Unfortunate. I really liked AGALMacroAssembler. The way I learned to use it is with the documentation within the file, as well as reading the source. I hope to see more of your work in the future!

    Ryan Speets · June 03, 2012
  2. Hello Ryan,

    Thank you for the comment. I came across that 404 error myself. After a bit of searching around, I finally found the project here: https://github.com/graphicscore/graphicscorelib . It’s also a part of the ND2D framework so can be found there as well – https://github.com/nulldesign/nd2d . It’s really strange that it’s not being pushed harder by Adobe though. It actually seems a better tool than PixelBender3D (which I gather is defunct now anyway). I just don’t understand those guys sometimes. In any case, thank you for your blog post on the subject. That mixed with the file asdocs really got me going.

    Devon O. · June 03, 2012
  3. I had to confirm (reboot) but plugin crash on my osx (1.6.8) last player, on firefox & chrome… :(

    mrbbp · June 03, 2012
  4. Hi. Good article. I’m using AGALMacroAssembler too and as you I investigated it by myself because thare’s no info at all. Here some broblems that I’ve met so far:
    1. As you said it’s better not to use rgba but xyzw. Why? Because it’s buggy – masking works incorrectly. But I think in fragment shader rgba looks more appropriate.
    2. I see you use build-in macros tex(). But I can’t make it work. Defining my own macros with tex opcode also throws RTE.
    3. Error messages sometimes turns me crazy. For example, if you alias some variable and by mistake use different (non existing) name – you’ll get strange exception message not relating to real problem.

    Volgogradetzzz · June 12, 2012
  5. Hey Volgogradetzzz,
    Thanks for the comments. Seems I was having some slightly different problems.
    1. The main reason I went for xyzw rather than rgba was that I sometimes would turn around and post the code on Wonderfl. Wonderfl though makes use of an old version of the AGALMiniAssembler which doesn’t support rgba at all. Of course it still compiles and throws no errors though, so it took me awhile to figure out what the problem was. Once I did, I decided to just stick with xyzw. I agree though, in fragment shaders, rgba is definitely more appropriate.
    2. It’s strange you had a problem with the tex() macro. Though I ran into problems with other macros sin() and cos() for example wouldn’t work at all and I had to use the standard AGAL syntax for those things. It seems to be hit and miss.
    3. I fully agree about the error messages. They’re pretty much useless. If they could get those sorted out and figure out a way that we could trace / debug values in AGAL, it would be fantastic..

    -d.

    Devon O. · June 14, 2012
  6. Hi, Devon.
    Thanks for sharing this great article. Now, i can make exchange the GLSL samples to AGAL.
    but, Unfortunately, I found a problem in AGAL.
    Maybe, AGAL have a limit of numbers of commands.
    In my case, about 200 commands seems a limit of AGAL…
    and AGAL don’t have any repeating commands now. (maybe, Adobe prepear that as “REP” and “BRK”.)
    So, I can’t exchange GLSL having repeating statements.
    Have you notice that? or do you know alternate repeating method ?

    Goro Matsumoto · June 17, 2012
  7. Really good article!

    I was wondering if it was possible to see the code for the other examples like metablob, I have some slightly different results to yours so it would be cool to see what you did.

    Leo · June 23, 2012
  8. Hey Goro,

    I ran into the same problem of limited commands. I don’t know what the actual number was but I’ve hit it. And, no, I don’t know any of repeating code yet. Even Pixelbender3D doesn’t support loops and as far as I can tell, Adobe’s no longer working on it anyway. Kind of a pain.

    @Leo, I put most of the source on Wonderfl. It’s the actual AGAL only (not what I did with the macro assembler), but it should help out: http://wonderfl.net/user/devon_o/codes

    Devon O. · June 23, 2012
  9. Hi Devon,
    basing on your post I ported the “Flower” example from ShaderToy. I wrote my own atan implementation (approximation with 3 functions with |E|<.005) to accomplish that. Obviously there are some implementations of atan available over the Internet (like the one from EasyAGAL) however I find them too inaccurate. Everything works fine with one small 'but'. The ratio isn't right – I am unable of getting nice 1:1 ratio (I am closer to sth like 1.3:1) and stretching via matrices isn't a right solution as it introduces other visual artefacts. In your post you wrote sth about 'tricky resolution', so maybe you know where this problem come from. I have examined my implementation many times, however 2 weeks ago I knew nothing about AGAL or GLSL so I am probably missing something. If you are interested in the problem I will gladly share the src with you – just drop me an email.

    Szymon Brych · July 21, 2012
  10. Hey devon any change you have the agal code for http://onebyonedesign.com/flash/agal/5/ and http://onebyonedesign.com/flash/agal/2/ I’d like to use these effects in another flash 3d library.

    David Jones · August 15, 2012
  11. Hey David, I actually thought those two were the weakest so never posted them. When I have some time though, I’ll throw them up on Wonderfl and post back here. Really curious to know more about the library you’re working on..

    d.

    Devon O. · August 16, 2012
  12. Hey David, Here you go:
    http://wonderfl.net/c/sZILY
    http://wonderfl.net/c/cyHn

    Devon O. · August 18, 2012
  13. Awesome thank you :)

    Perhaps I didn’t word that very well, I’m just looking to put together a collection of post effects that I can use for some personal projects. I’m not creating a library or anything i’m just using alternativa3d and my own code.

    Checkout my site for some alternativa3d stuff I experiment with.

    Thank you again

    David Jones · August 30, 2012
  14. I’m glad I found this blog post.
    The Mr FunkyPants book is great.

    Colin Davies · October 06, 2012
  15. This is great way of learning AGAL especially that GLSL commands are more descriptive.
    But I was lazy so I build simple conversion tool.

    Please add this declarations of the variables
    uniform vec2 resolution;
    uniform float time;
    uniform sampler2D tex0;

    void main()
    {
    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);
    }

    fshader:
    mul ft4.xy, fc0.x, v0.xyyy
    div ft3.xy, ft4.xyyy, fc2.xyyy
    add ft4.xy, fc0.y, ft3.xyyy
    mov ft3.x, fc3.x
    mul ft1.w, ft3.x, fc0.z
    cos ft0.x, ft1.w
    mul ft3.w, ft4.y, ft0.x
    sin ft3.z, ft1.w
    mul ft3.y, ft4.x, ft3.z
    add ft2.y, ft3.y, ft3.w
    abs ft3.w, ft2.y
    sin ft3.z, ft1.w
    mul ft3.y, ft4.y, ft3.z
    cos ft3.x, ft1.w
    mul ft0.w, ft4.x, ft3.x
    sub ft0.z, ft0.w, ft3.y
    mul ft0.y, fc0.z, ft0.z
    div ft1.x, ft0.y, ft3.w
    abs ft0.w, ft2.y
    div ft0.z, fc0.z, ft0.w
    mov ft0.y, fc0.w
    mul ft0.x, ft0.y, fc3.x
    add ft1.y, ft0.x, ft0.z
    mov ft0.w, fc1.x
    tex ft4, ft1.xyyy, fs0
    mul ft1.xyz, ft4.xyzz, ft2.y
    mul ft0.xyz, ft1.xyzz, ft2.y
    mov oc, ft0

    http://winxalex.blogspot.com/2012/11/glsl-to-agal-convertor-tool.html

    Any suggestions or requests are welcome.
    Regards
    Alex

    winxalex · November 10, 2012
  16. Bitchin’!

    Devon O. · November 11, 2012

Leave a Comment! Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Devon O. Wolfgang

AIR | Unity3D | AR/VR

Unity Certified Developer

Technical Reviewer of “The Essential Guide to Flash CS4 AIR Development” and “Starling Game Development Essentials”

Reviewer of “The Starling Handbook”

Unity Engineer at Touch Press.

Categories
  • Actionscript (95)
  • AIR (16)
  • Flash (99)
  • Games (7)
  • Liberty (13)
  • Life (53)
  • Shaders (20)
  • Unity3D (21)
Recent Comments
  • Devon O. on Unity Ripple or Shock Wave Effect
  • Feral_Pug on Unity Ripple or Shock Wave Effect
  • bavvireal on Unity3D Endless Runner Part I – Curved Worlds
  • Danielius Vargonas on Custom Post Processing with the LWRP
  • Luca G on Unity Ripple or Shock Wave Effect
Archives
  • December 2020 (1)
  • December 2019 (1)
  • September 2019 (1)
  • February 2019 (2)
  • December 2018 (1)
  • July 2018 (1)
  • June 2018 (1)
  • May 2018 (2)
  • January 2018 (1)
  • December 2017 (2)
  • October 2017 (1)
  • September 2017 (2)
  • January 2017 (1)
  • July 2016 (1)
  • December 2015 (2)
  • March 2015 (1)
  • September 2014 (1)
  • January 2014 (1)
  • August 2013 (1)
  • July 2013 (1)
  • May 2013 (1)
  • March 2013 (2)
  • December 2012 (1)
  • November 2012 (1)
  • September 2012 (3)
  • June 2012 (2)
  • May 2012 (1)
  • April 2012 (1)
  • December 2011 (2)
  • October 2011 (3)
  • September 2011 (1)
  • August 2011 (1)
  • July 2011 (1)
  • May 2011 (2)
  • April 2011 (2)
  • March 2011 (1)
  • February 2011 (1)
  • January 2011 (2)
  • December 2010 (3)
  • October 2010 (5)
  • September 2010 (1)
  • July 2010 (2)
  • May 2010 (5)
  • April 2010 (2)
  • March 2010 (7)
  • February 2010 (5)
  • January 2010 (5)
  • December 2009 (3)
  • November 2009 (1)
  • October 2009 (5)
  • September 2009 (5)
  • August 2009 (1)
  • July 2009 (1)
  • June 2009 (2)
  • May 2009 (6)
  • April 2009 (4)
  • March 2009 (2)
  • February 2009 (4)
  • January 2009 (1)
  • December 2008 (5)
  • November 2008 (2)
  • September 2008 (1)
  • August 2008 (6)
  • July 2008 (6)
  • June 2008 (9)
  • May 2008 (4)
  • April 2008 (3)
  • March 2008 (4)
  • February 2008 (9)
  • January 2008 (7)
  • December 2007 (6)
Copyright © 2017 Devon O. Wolfgang