Stage3D / AGAL from scratch. Part III – Hello Triangle

Here we go

So, you’ve read Part I and Part II, and you want things to get dirty. Alright.

You can start by downloading the source code : HelloTriangle Sources

In this exercise, we will draw our first triangle using only Stage3D. There will be a small part or AGAL, which is actually the minimum required to display anything on screen but don’t worry, it’s easier to understand than it seems.

In the last articles, I talked a lot about Vertices. A vertex is a point in space that will be used with 2 other Vertices by our GPU to render our triangle.

A VertexBuffer is a simple list of Number so you have to define a structure that will be the same for each Vertex. Every vertices in the same Buffer needs to follow the same structure. The interessting thing is that you can pass whatever you want in your VertexBuffer, and you will have to decide what data is used for.

There is, however, at least 2 common structures used for VertexBuffer but I will describe the first one, that is used in that example (baby steps !)

As you can see, our VertexBuffer will be structured as a simple list of Number, where the first three numbers are our Vertex coordinate, and the three others our Vertex color. Every Vertex we may add to the buffer needs to follow the same syntax.

 Time for some code

Now if you didn’t already, download the source code, and open the HelloTriangle class in your favorite editor. Then come back to me.

Class constructor is quite simple to understand, so we move on to the __init function.

// wait for Stage3D to provide us a Context3D
stage.stage3Ds[0].addEventListener(Event.CONTEXT3D_CREATE, __onCreate);
stage.stage3Ds[0].requestContext3D();

When working with Stage3D, you are actually not working at all with the Stage3D class. Instead, you are working with a Context3D.

First thing you have to do is to target one of your Stage3D (stage.stage3Ds[0]) and request it for a context. You will be able to access the context when receiving the proper event.

At this step, Flash will try to reach your graphic card. If it can, then you will get an hardware accelerated context, either driven by DirectX for Windows platform, or OpenGL for Linux and MacOS platform.

If Flash can’t reach your graphic card for any reason, you will still get a context3D but actually driven by the new CPU rasterizer called SwiftShader.

Once we get a context, we stock it in a class member, and then configure the back Buffer

private function __onCreate(event:Event):void {
	// // // CREATE CONTEXT // //
	context = stage.stage3Ds[0].context3D;
 
	// By enabling the Error reporting, you can get some valuable information about errors in your shaders
	// But it also dramatically slows down your program.
	// context.enableErrorChecking=true;
 
	// Configure the back buffer, in width and height. You can also specify the antialiasing
	// The backbuffer is the memory space where your final image is rendered.
	context.configureBackBuffer(W, H, 4, true);

The back Buffer is a specific memory space where all your rendering is copied. You will be able then to display on screen what does the back buffer contains.

Setting up buffers and program

Then we create the buffers and the program. Don’t worry too much on the AGAL part right now, ill explain it later.

private function __createBuffers():void {
	// // // CREATE BUFFERS // //
	vertexBuffer = context.createVertexBuffer(3, 6);
	indexBuffer = context.createIndexBuffer(3);
}

Creating buffer is rather simple. The only thing you need to know is :

  1. How much Vertex it will have : 3
  2. How much information each Vertex have : x,y,z,r,g,b = 6

The index buffer needs to know how many instruction he will have. Here 3.

The program part is a bit complicated right now, because we need to see more to understand the AGAL code. Still, we can have a look at the code.

private function __createAndCompileProgram() : void {
	// // // CREATE SHADER PROGRAM // //
	// When you call the createProgram method you are actually allocating some V-Ram space
	// for your shader program.
	program = context.createProgram();
 
	// Create an AGALMiniAssembler.
	// The MiniAssembler is an Adobe tool that uses a simple
	// Assembly-like language to write and compile your shader into bytecode
	var assembler:AGALMiniAssembler = new AGALMiniAssembler();
 
	// VERTEX SHADER
	var code:String = "";
	code += "mov op, va0n"; // Move the Vertex Attribute 0 (va0), which is our Vertex Coordinate, to the Output Point
	code += "mov v0, va1n"; // Move the Vertex Attribute 1 (va1), which is our Vertex Color, to the variable register v0
	 // Variable register are memory space shared between your Vertex Shader and your Fragment Shader
 
	// Compile our AGAL Code into ByteCode using the MiniAssembler
	vertexShader = assembler.assemble(Context3DProgramType.VERTEX, code);
	code = "mov oc, v0n"; // Move the Variable register 0 (v0) where we copied our Vertex Color, to the output color
 
	// Compile our AGAL Code into Bytecode using the MiniAssembler
	fragmentShader = assembler.assemble(Context3DProgramType.FRAGMENT, code);
}

Creating a program is simple. Then we have the simplest AGAL code we can have. Just skip it. Then we use the AGALMiniAssembler tool from Adobe to compile at runtime our AGAL program into ByteCode, which give us two Shaders : one VertexShader, and one FragmentShader.

Uploading Data to the graphic card

Now we need to upload the data and the program to the graphic card.

private function __uploadBuffers():void {
	var vertexData:Vector=Vector.([
	-0.3, -0.3, 0, 1, 0, 0, 	// - 1st vertex x,y,z,r,g,b
	0, 0.3, 0, 0, 1, 0, 		// - 2nd vertex x,y,z,r,g,b
	0.3, -0.3, 0, 0, 0, 1		// - 3rd vertex x,y,z,r,g,b
	]);
 
	vertexBuffer.uploadFromVector(vertexData, 0, 3);
	indexBuffer.uploadFromVector(Vector.([0, 1, 2]), 0, 3);
}

To upload Data to a buffer, the simplest way is to upload a Vector. As you can see, I am creating a Vector or 3 * 6 entries, which correspond to the data of three vertices. The coordinates goes from -1 to 1 for x and y, and we don’t bother with the z part now. The colors component, R, G and B, goes from 0 to 1. So the first line match the top Vertex which will be red, the second line match the bottom left vertex and will be green, and so on.

As an index buffer, we simple tell our graphic car that the triangle is drawn in the order 0, 1, 2, which are indexes of the vertices in the vertex buffer.

Now time to upload the program

private function __uploadProgram():void {
	// UPLOAD TO GPU PROGRAM
	program.upload(vertexShader, fragmentShader); // Upload the combined program to the video Ram
}

Uploading the program is rather simple, so now we may move to the AGAL stuff.

Using the VertexBuffer inside the Shaders

When we created the VertexBuffer, we told the graphic card that each vertices will have a length of 6 components. This is a valuable information for the graphic card because when the VertexShader will run, it will be executed for each vertices in your buffer.

The GPU will then “cut” in the buffer 6 number at a time, because we told it that were the amount of data we needed. Each Vertex needs to be copied to fast access register before being used by the program. Think of register as a very small piece of RAM that is extremely fast, and very close to the GPU.

The last step is to tell the GPU how to split each chunk of data, and where to copy it.

For the code part :

private function __splitAndMakeChunkOfDataAvailableToProgram():void {
	// So here, basically, your telling your GPU that for each Vertex with a vertex being x,y,y,r,g,b
	// you will copy in register "0", from the buffer "vertexBuffer, starting from the postion "0" the FLOAT_3 next number
	context.setVertexBufferAt(0, vertexBuffer, 0, Context3DVertexBufferFormat.FLOAT_3); // register "0" now contains x,y,z
 
	// Here, you will copy in register "1" from "vertexBuffer", starting from index "3", the next FLOAT_3 numbers
	context.setVertexBufferAt(1, vertexBuffer, 3, Context3DVertexBufferFormat.FLOAT_3); // register 1 now contains r,g,b
}

Let’s go AGAL

We just instructed how the GPU should use the VertexBuffer. As a reminder :

  1. Take the 6 first information
  2. From 0 to 3, copy the data into the register 0
  3. From 3 to 6, copy the data into the register 1
  4. Register 0 now contains some coordinates, register 1 now contains some color.

The AGAL code will be separated in two Shaders. The Vertex Shader, and the Fragment Shader. The registers can only be accessed from the Vertex Shader, but we will learn a trick to make the color available for the Fragment Shader.

Before going any further, let’s have a look at the AGAL syntax :

opcode destination, source1[, source2][, options]

for instance :

mov op, va0

means :

op = va0

Another example :

mul vt0, va0, vc0

means :

vt0 = va0 * vc0

The names of the “variables” are actually names of register. It is rather simple to remember, but right now, let just learn the one we are using :

  • va0 : Vertex Attribute 0. This is the register where we copy the coordinate of each Vertex (see above). There are 8 registers, from va0 to va7.
  • op : Output Point. This is a special register where the final coordinate must be moved to.
  • v0 : Variable Register 0. The Variable registers are shared between the Vertex Shader and the Fragment Shader. Use them to pass some data to the Fragment Shader. There are 8 Variable Register from v0 to v7.
  • oc : Output Color. This is a special register where the final color must be moved to.

Let’s have a look at the first sample of AGAL code we have and which is the Vertex Shader (Line 159 and 160 of the Example Class)

mov op, va0
mov v0, va1

The first line move the va0 register where we copied the vertex coordinate to the output point without any modification. Then, we need to move some information to the Fragment Shader using the Variable Register. In our case, we only need to pass the color information, which is stored in the va1 register (see previous chapter for buffer splitting into register).

Simple isn’t it ? let’s have a look a the Fragment Shader.

mov oc, v0

Really simple : move the color we just copied into the Variable Register to the ouput color without any modification. That’s all !

Once the program compiled using AGALMiniAssembler, and uploaded to the GPU, we can make it the active program for the GPU

private function __setActiveProgram():void {
	// Set our program as the current active one
	 context.setProgram(program);
}

Head up to the rendering part.

Rendering our triangle on screen

The rendering part is quite simple in our example since we don’t have any interaction, camera, animation or whatever. The render method is called on each frame.

private function render(event:Event):void {
	context.clear(1, 1, 1, 1); // Clear the backbuffer by filling it with the given color
 
	context.drawTriangles(indexBuffer); // Draw the triangle according to the indexBuffer instructions into the backbuffer
	context.present(); // render the backbuffer on screen.
}

on each frame, we start by clearing the scene and filling it with a plain white. Then, we draw the triangle according to the indexBuffer instruction. The drawTriangles method actually draws into the back buffer. To render the back buffer content on screen, we simply call the present() method.

Compile, and relax.

Maybe some of you may have noticed that this example is simpler than the other you may have read before : no Matrix3D, no m44 opcode. This is intended.

You really need to understand how everything works together before going any further. If you can get it, you will find the following so much easier.

Practice !

You should try the following to help you understand how everything works together, as a practice :

  1. Render the triangle on a black background
  2. Get rid of the z coordinate since we are not using it
  3. Without modify the vertexData vector or the AGAL code, use the coordinate as color and the color as coordinate. You shoud get something like that :

If you need any help with either the tutorial or the exercises, feel free to leave a comment on the blog, or drop me a message on my twitter.

Again, as it is the first time I am writing a blog, I’d really like some feedback. Any criticism,  or encouragement, is welcomed.

Hope you liked it !

19 thoughts on “Stage3D / AGAL from scratch. Part III – Hello Triangle

  1. Hey, really liking this tutorial! Nice and simple. I like that you’ve skipped the m33 / m44 operations for now, it’s better to grasp “why” the GPU draws a certain way with raw coordinates and color information.

    I’ve done a few examples similarly on my website. Here it is if you want to check it out:
    http://pierrechamberlain.ca/blog/tag/agal

    What I’m curious about is how to efficiently use VertexBuffer3D and IndexBuffer3D when various elements are in the mix (2D Quads, 2.5D Quads, Particles, Screen Post-Processing effects, etc.) I been doing lots of research and asking around questions on StackOverflow to get more familiar on using those GPU resources.

    It sure is a challenging new territory for Flash Developers to walk on!

    • When I started, I found a lot of tutorial to draw that triangle, but none of them would have explained me what were the registry or why did we need a matrix for.

      Anyway, many thanks for the feedback !

      I see from your blog you are exploring a lot of alternatives to “raw” AGAL. Didn’t start to have a look at them right now, So I am keeping your articles under my pillow ;)

      I already did some benches about Buffers, if you want we can talk about it. Feel free to send me a tweet or to drop me a mail.

      Anyway, glad you liked it, see ya !

  2. Pingback: Stage3D / AGAL from scratch. Part IV – Adding some depth | Norbz's Dev Blog

  3. Pingback: Stage3D / AGAL from scratch. Part V – Indexes and culling mode | Norbz's Dev Blog

  4. Thanks dude.
    Awesome .
    this is the best article i have ever read for stage3D even articles that has been submitted for adobe by others.

    exactly now we can imagine how the stage3D WORKS LIKE A MOTOR ENGINE.

    BR

  5. private function __uploadBuffers():void {
    var vertexData:Vector=Vector.([
    -0.3, -0.3, 0, 1, 0, 0, // - 1st vertex x,y,z,r,g,b (left vertax)
    0, 0.3, 0, 0, 1, 0, // - 2nd vertex x,y,z,r,g,b (top vertax)
    0.3, -0.3, 0, 0, 0, 1 // - 3rd vertex x,y,z,r,g,b (right vertax)
    ]);

    vertexBuffer.uploadFromVector(vertexData, 0, 3);
    indexBuffer.uploadFromVector(Vector.([0, 1, 2]), 0, 3);
    }
    ——————————————————————————
    vertexBuffer.uploadFromVector(vertexData, 0, 3);
    vertaxData is a vector(array) and we are targeting first row with 0 and third(3) column from this first row, which is actually a color attribute

    indexBuffer.uploadFromVector(Vector.([0, 1, 2]), 0, 3);
    why we are calling the object type Vector here (and not the array)?
    here we are calling 1st, 2nd and 3rd row with 0,1,2,
    and for each row we are calling 0 and 3, what this 0 and 3s function here

  6. I tried to create a rectangle with your code – but stuck at a point

    my new matrix is
    -.5, .5, 0, 1, 0, 0,
    .5, .5, 0, 0, 1, 0,
    .5, -.5, 0, 0, 0, 1,
    -.5, -.5, 0, 1, 0, 1

    and vertax buffer is
    vertexBuffer = context.createVertexBuffer(4, 8);
    indexBuffer = context.createIndexBuffer(4);

    and uplod buffer is
    vertexBuffer.uploadFromVector(vertexData, 0, 3);
    indexBuffer.uploadFromVector(Vector.([0, 1, 2, 3]), 0, 3);

    while compiling it does not throw any error but, it does not render anything, as I could not do the agal part, please help.

  7. Pingback: [AGAL个人笔记]第三篇: Hello Stage3D`s World(译) | 影箜技术小站-ShadowKong.com

  8. Practice ~
    private function __uploadBuffers():void {
    var vertexData:Vector.=Vector.([
    0, 0, 0, 0.3, 0, 0, // - 1st vertex x,y,z,r,g,b
    1, 0, 0, 0, 0, 0, // - 2nd vertex x,y,z,r,g,b
    0, 1, 0, 0, 0.3, 0 // - 3rd vertex x,y,z,r,g,b
    ]);
    pass?

    • Actually no, you were suppose to do it without modifying those lines.

      The solution was to switch which part of the data was allocated as the coordinate, and which part was allocated as the color :

      context.setVertexBufferAt(1, vertexBuffer, 0, Context3DVertexBufferFormat.FLOAT_3); // register “1 < changed” now contains x,y,z
      context.setVertexBufferAt(0, vertexBuffer, 3, Context3DVertexBufferFormat.FLOAT_3); // register “0 < changed” now contains r,g,b

      Just swap the first argument on those two method :)

  9. Hi norbz,

    There is an error in the assembler strings, the “n” at the end of each causes misleads in newbies like me, I have to figure out that is “\n” in replace.

    Thanks about this nice tutorials! Helps a lot!

    • Hi. Yes indeed, it’s actually just a bug from the plugin I use to display the code, but the provided sources should be good.
      I am sorry for misleading you, please feel free to ask any questions if you are struggling on some stupid thing like this ;)

      See you

Leave a Reply

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


*

* Copy This Password *

* Type Or Paste Password Here *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>