It was a long rainy Sunday here in London. To keep my hopes high I decided to do something a bit more exciting other than rewriting the rendering system for the 10th time :D.

If you reading this article for the 2nd time and you had a chance to see previous examples I have to say I get overexcited and get my numbers wrong.. sorry. But it didn’t change the fact that 100 000 with 60fps is doable as well as 4,200,000 but in this case on my machine I get a bit lower results: 45/60. And yes this is when we’re hitting the limits of Molehill3D performance itself.

For our 2D framework obviously there is no need for this amount of triangles on the screen anyway. In fact for mobile development I am sure you get some app running with just tens of them or at least just hundreds.

A bit more about my approach how this thing is being done: without the technical description’s – here’s my thoughts.

First of all there is plenty resources available over the web you can learn a basics.
bytearray.org would be good starting point.

There is also well written article by jam3.

There are also 2 well known Molehill based frameworks to deal with 2D content:
M2D and ND2D Molehill 2D Engine by nulldesign.

Obviously you can find a lot of examples that you’re probably well aware about already. And I was following almost all of them from very beginning of the public alpha of Molehill3D.

Despite of the fact, all this stuff is suppose to explain to you the basics, I found myself digging into examples much deeper because it failed to do so. A lot of stuff was confusing and left many questions open.

What was the biggest thing bothering me? My first impression: I saw a “Digging more into the Molehill APIs” article describing this low level API that will let you operate on raw data and colours as well as textures as any fully flagged 3D might. But with 2D content in mind I was hoping to investigate the first part of it the most.

Then all the examples I saw on the web afterwards were actually based on textures only! No bloody clue how to display single colour rectangles on the screen. Secondly when the first Molehill2D frameworks arrived It was clear something is about to happen in this area. But I never had time to dig into this deeper. In fact those guys are also showing off texture based stuff only. So, now I could take a closer look.

1. Setting up geometry

The Thibault Imbert’s aricle explained a lot of things and was the ONLY source you could actually get something decent from. But the AGAL part wasn’t so obvious at first (and this part is still a bit blurry for me). However the AS3 part of it makes perfect sense. It was that missing link between how the basic work-flow and procedures are supposed to be done and how you should prepare your data. After all I end up with a single buffer pushing some vertices and indices into 2 Vector arrays and then reusing them. This was my base:

// create a vertex buffer
// format is (x,y,z,r,g,b) = 3 vertices, 6 dwords per vertex
vertexbuffer.uploadFromVector ( Vector.([
-1,-1,0,  255/255,0,0,              // red
0,1,0,    193/255,216/255,47/255,   // green
1,-1,0,   0,164/255,228/255         // blue
]),0, 3 ); // start at offset 0, count 3

So you can see each line actually represents a Vertex property and how to form a triangle.
This told me actual colour representation is the value between 0-1. A bit different from what we get use to, but Thibault already even gave us a clue on how to transpose it from what we know about hex values. All I wanted was a square, so my version was like this:

 vertexbuffer.uploadFromVector ( Vector.([
0,0,0,  0.8,0,0,
1,0,0,  0.8,0,0,
1,1,0,  0.8,0,0,
0,1,0,  0.8,0,0,
]),0, 4 );

As you could expect this defines 4 points with exactly the same colour which should be dark red. Now to specify indices:

 var indices    :Vector. = new Vector.();
indices.push(0,1,2, 0,2,3);

Data from the above renderer needs to this info in order to draw triangles. How does it know the coordinates are 3 and colour is being defined in next 3 values per each vertex?
This is also straight forward. This is as simple as specifying a of row and col for our data matrix.

vBuffer = context3D.createVertexBuffer(num of Vertices, number of data per vertex); //in this case 4,6

and upload all this specifying start number as well as an offset (number of points (vertices) you have on this matrix)

vBuffer = uploadFromVector(vertices, 0, 4 );

vaery similar process with indices:

iBuffer = context3D.createIndexBuffer(indices.length);
iBuffer.uploadFromVector(indices, 0, indices.length);

The syntax might be something new but the whole principal is very similar to what you already know about Drawing API’s and the drawTriangles method. The biggest difference here is the vectorBuffer that you can specify on your own.

There are no real set rules, you have full freedom. Use the data, or little matrices or multi dimensional arrays if you like, AGAL is magic and can perform certain GPU accelerated procedures. This is your join point however you are free to specify this bit, I like to make it custom for my needs.

2D and alpha is what bothers me mainly. I don’t need mess with the Z property and I’d like to add transparency. Now my format looks like this:

//x,y, r,g,b,a
vertexbuffer.uploadFromVector ( Vector.([
0,0,  0.4,0,0, 0.5
1,0,  0.4,0,0, 0.5
1,1,  0.8,0,0, 0.5
0,1,  0.8,0,0, 0.5
]),0, 4 );

So far so good.

Now lets try to render something on the screen: (I’m assuming you already know the basic procedures of Molehill after reading the above articles)

So it’s obvious that coordinate systems are a bit different and always start on the middle of your viewport. But the first question is: how do I specify width and height of a square? It’s also made something obvious that the values in our array 0,1 are completely abstract, but what do they actually do?

Quickly changing my array to this:

//x,y, r,g,b,a
vertexbuffer.uploadFromVector ( Vector.([
-0.5,-0.5,  0.4,0,0, 0.5
0.5,-0.5,  0.4,0,0, 0.5
0.5,0.5,  0.8,0,0, 0.5
-0.5,0.5,  0.8,0,0, 0.5
]),0, 4 );

Knowing that the centre point is in the middle moving things around by half everything becomes obvious.

0.5 means half from centrer point. 1 means full distance from centrer point

//x,y, r,g,b,a
vertexbuffer.uploadFromVector ( Vector.([
-1,-1,  0.4,0,0, 0.5
1,-1,  0.4,0,0, 0.5
1,1,  0.8,0,0, 0.5
-1,1,  0.8,0,0, 0.5
]),0, 4 );

Now my square coveres entire screen. Because I’ve changed the colours of the first 2 vertices I see whole gradient on my screen.

2. Setting up a 2D environment

Trying this:

//x,y, r,g,b,a
vertexbuffer.uploadFromVector ( Vector.([
-0.1,-0.1,  0.4,0,0, 0.5
0.1,-0.1,  0.4,0,0, 0.5
0.1,0.1,  0.8,0,0, 0.5
-0.1,0.1,  0.8,0,0, 0.5
]),0, 4 );

If I make a square 10 times smaller to compare to the one above it tells me there is a scale hidden in these values! But hold on. Looking on the implementation of many 2D examples there is a matrix (orthoMatrix) transformation going on to transpose all these weird calculations to 2D environment.

Looking at this simple example it is obvious that we can specify vertices and their depending scale factors. This scale is nothing but a link to your width and height properties of the viewport. And in fact it doesn’t need to be square at all! Values corresponds to scale of the viewport and the only matrix you need is the one to render everything- matrix3D. That telling me there is only one thing I need to do. Negate this scale.

viewMatrix = new Matrix3D();
viewMatrix.appendScale( 1 / (W * 0.5), -1 / (H * 0.5), 1 );

Now I can go back to my vertices and try to deal with normal values like this:

//x,y, r,g,b,a
vertexbuffer.uploadFromVector ( Vector.([
0,0,  0.8,0,0, 0.5
20,0,  0.8,0,0, 0.5
20,20,  0.8,0,0, 0.5
0,20,  0.8,0,0, 0.5
]),0, 4 );

And that recipe will draw 20x20px Square. No crazy additional matrices! Because we’re ignoring the z value, our rendering procedure now looks as follow.

context3D.clear();
context3D.setProgram(shaderProgram);
context3D.setVertexBufferAt( 0, vBuffer, 0, Context3DVertexBufferFormat.FLOAT_2 );
context3D.setVertexBufferAt( 1, vBuffer, 2, Context3DVertexBufferFormat.FLOAT_4 );
context3D.setProgramConstantsFromMatrix( Context3DProgramType.VERTEX, 0, viewMatrix, true );
context3D.drawTriangles(iBuffer, 0 , 4);

This is as simple as it could be to draw 2D stuff using 3D geometry. The magic is happening with those 2 setVertexBufferAt commands. In human readable language it means: Set stream 0 with my vertex pattern I provided, start reading from 0 and use FLOAT_2, basically it means read the next 2 values. X and Y only in this case. The next one is saying, set stream 1 from the same pattern, but start from 2 and read next 4 values.

drawTraingles method needs to know where to pick up the points and how many of the points are in your pattern, that’s all. Now if you need to move your square around using our pattern since we’re operating on natural numbers, things are very easy:

//x,y, r,g,b,a
vertexbuffer.uploadFromVector ( Vector.([
x, y         ,  0.8,0,0, 0.5
x+width, y,  0.8,0,0, 0.5
x+width ,y + height,  0.8,0,0, 0.5
x, y + height,  0.8,0,0, 0.5
]),0, 4 );

Simple isn’t it? Running it all you notice one more thing. My custom 6th (witch is alpha) is not working at all. Don’t mess around with AGAL and the shader settings. This is actually working perfectly. You already told the renderer to read all 4 values. You just didn’t set how Molehill needed to deal with this, since what’s is happening on one flat layer in 2D space.

context3D.setDepthTest(false, Context3DCompareMode.NOT_EQUAL);
context3D.setBlendFactors(Context3DBlendFactor.SOURCE_ALPHA, Context3DBlendFactor.ONE_MINUS_SOURCE_ALPHA);

Job done.

Here is my complete code:

package
{
    import com.adobe.utils.AGALMiniAssembler;
    import com.flaemo.debuger.MobileStats;
   
    import flash.display.Sprite;
    import flash.display.Stage3D;
    import flash.display.StageAlign;
    import flash.display.StageScaleMode;
    import flash.display3D.Context3DBlendFactor;
    import flash.display3D.Context3DCompareMode;
    import flash.display3D.Context3D;
    import flash.display3D.Context3DProgramType;
    import flash.display3D.Context3DRenderMode;
    import flash.display3D.Context3DTextureFormat;
    import flash.display3D.Context3DVertexBufferFormat;
    import flash.display3D.IndexBuffer3D;
    import flash.display3D.Program3D;
    import flash.display3D.VertexBuffer3D;
    import flash.events.Event;
    import flash.geom.Vector3D;
    import flash.geom.Matrix3D;
    import flash.geom.Rectangle;
    import flash.utils.getTimer;
   
    [SWF(width = "800", height = "480", frameRate = "60", backgroundColor = "0xFFFFFF")]
   
    public class Main extends Sprite
    {
        private var W:Number = 800;
        private var H:Number = 480;
       
        private var stage3D         :Stage3D;
        private var context3D       :Context3D;
        private var shaderProgram   :Program3D;
        private var iBuffer         :IndexBuffer3D;
        private var vBuffers        :Vector.<VertexBuffer3D> = new Vector.<VertexBuffer3D>();
       
        private var viewMatrix      :Matrix3D;
       
        private var stats           :MobileStats;
        private var max             :int;
       
        public function Main():void
        {
            stage.scaleMode = StageScaleMode.NO_SCALE;
            stage.align = StageAlign.TOP_LEFT;
           
            W = stage.stageWidth;
            H = stage.stageHeight;
           
            stage3D = stage.stage3Ds[0];
            stage3D.addEventListener( Event.CONTEXT3D_CREATE, initialize );
            stage3D.requestContext3D( Context3DRenderMode.AUTO );
            stage3D.viewPort = new Rectangle( 0, 0, W, H );
           
            stats = new MobileStats();
            stage.addChild(stats);
        }
       
        private function initialize( e:Event ):void
        {
            context3D = stage3D.context3D;
            context3D.enableErrorChecking = true;
            context3D.configureBackBuffer( W, H, 4, true );
           
            var vertexShader:AGALMiniAssembler = new AGALMiniAssembler();
            vertexShader.assemble( Context3DProgramType.VERTEX,
            "m44 op, va0, vc0\n" +
            "mov v0, va1 \n"
            );
           
            var fragmentShader:AGALMiniAssembler = new AGALMiniAssembler();
            fragmentShader.assemble( Context3DProgramType.FRAGMENT,
            "mov oc, v0 \n"
            );
           
            shaderProgram = context3D.createProgram();
            shaderProgram.upload( vertexShader.agalcode, fragmentShader.agalcode );
           
            setGeometry();
           
            var indices     :Vector.<uint> = new Vector.<uint>();
           
            var j:int;
            for (var i:int = 0; i < max; i++){
                indices.push(j+0, j+1, j+2, j+0, j+2, j+3);
                j += 4;
           
                iBuffer = context3D.createIndexBuffer(indices.length);
                iBuffer.uploadFromVector(indices, 0, indices.length);
            }
           
            indices.fixed = true;
           
            viewMatrix = new Matrix3D();
            viewMatrix.appendScale( 1 / (W * 0.5), -1 / (H * 0.5), 1 );
           
            addEventListener(Event.EXIT_FRAME, renderFrame);
        }
       
        private function setGeometry():void
        {
            drawGeometry();
        }
       
        private function drawGeometry():void
        {
            max = 1000;
           
            var i:int
            var vertices:Vector.<Number>;
           
            for (var u:int = 0; u < 10; u++){
               
                vertices = new Vector.<Number>();
               
                for (i = 0; i < max; i++){
                    var xp:Number = Math.round( -1200 + Math.random() * 2400);
                    var yp:Number = Math.round( -800 + Math.random() * 1600);
                   
                    var r:Number = Math.random() * 1.0;
                    var g:Number = Math.random() * 1.0;
                    var b:Number = Math.random() * 1.0;
                    var a:Number = 0.5;
                   
                    //(x,y, r,g,b,a )
                    vertices.push(
                        xp      , yp        ,r, g, b, a,    
                        xp+10   , yp        ,r, g, b, a,    
                        xp+10   , yp+10     ,r, g, b, a,
                        xp      , yp+10     ,r, g, b, a
                    );
                }
               
                vertices.fixed = true;
           
                vBuffers[u] = context3D.createVertexBuffer(max * 4, 6);
                vBuffers[u].uploadFromVector(vertices, 0, max * 4 );
            }
        }
       
        private function renderFrame(e:Event):void
        {
            var t:Number = getTimer();
           
            //drawGeometry();
           
            viewMatrix.prependRotation(0.1, new Vector3D(0, 0, 1));
           
            context3D.clear();
            context3D.setProgram(shaderProgram);
           
            for (var i:int = 0; i < 10; i++){
                context3D.setVertexBufferAt( 0, vBuffers[i], 0, Context3DVertexBufferFormat.FLOAT_2 );
                context3D.setVertexBufferAt( 1, vBuffers[i], 2, Context3DVertexBufferFormat.FLOAT_4 );
                context3D.setProgramConstantsFromMatrix( Context3DProgramType.VERTEX, 0, viewMatrix, true );
               
                context3D.setDepthTest(false, Context3DCompareMode.NOT_EQUAL);
                context3D.setBlendFactors(Context3DBlendFactor.SOURCE_ALPHA, Context3DBlendFactor.ONE_MINUS_SOURCE_ALPHA);
               
                context3D.drawTriangles(iBuffer);
            }
           
            context3D.present();
           
            stats.trace("Calc time", getTimer() - t, 0);
        }
    }
}

You might notice there are more loops going on. This is because a single buffer has its own limitations. To draw more you need to have multiple buffers.

It will be good practice to use different buffers for a different purposes. For example one that deals with alpha objects, another with solid shapes. Then you don’t need to run a setDepthTest and increase performance.

Even more, solid colours and textured objects can also be divided. It’s a whole new area of exploration for me and if you feel I’m being mistaken somewhere here, please correct me if I am wrong. But it looks like the current approach being taken by 2D molehill enthusiasts is not the best. If you think about 3D space you’ll have difficulties understanding how to represent 2D stuff in it.

So don’t mess with heavy Math. Just put a few yellow stickers on your flat screen, don’t move your head or spin around and then things become obvious. Where there is 3D there is 2D already 😉

If the GPU has been designed for performing the heaviest 3D operations they can perform 2D ones even better. The only difference is you, as a 2D content creator have to translate your drawings into the world made up of triangles!

Here is the result of my tests done bases on the above learning curve:

Platform : CPU – PC Quad Core AMD Phenom II 955, GPU – ATI Radeon HD 5800 Series, OS – Windows 7

100 000 transparent colourful squares floating around (20 000 triangles)!


Flaemo2D

File size : 10.2kb
FPS (60/60)
RAM Initial pick up to 10MB then 8.6MB STABLE
Calculation time : 0ms
processor consumption: 2% (Maybe winamp is on in the background 😉 )

Now, if this is not mind blowing enough – think about a test scenario where you need to cover entire screen pixel by pixel with 1px coloured and transparent squares made up of 2 triangles each.
Let’s take Full HD Spec 1080*1920*2 – we’re ending up with 4 147 200 triangles. Here you go!

4 200 000 triangles!


Flaemo2D

File size : 10.2kb
FPS (45/60)
RAM Initial pick up to 10MB then at 8.1MB it’s STABLE
Calculation time : 0ms (sometimes it’s trying to show me something but…)
processor consumption: 7%.

You almost have a feeling that Molehill is limitless! In both cases AA is set on 4 to have a decent quality. Obviously this ridiculous amount of triangles would never be in a final product due to the limitations of the language speed, processing and executions. Also, who needs to cover every single pixel with 2 triangles 😀 (maybe some clients). Much more needs to happen but you can see the biggest drawback of the Flash Player renderer itself will soon be the strongest key and most powerful technology available on the web.

The way Molehill is processing/rendering data is 100% compatible with the current state of my Flaemo Framework custom display list. In fact I need to get rid of the matrices since this bit is also better to accelerated by the GPU. Molehill leaves me with a huge hope for the future.

Now all I need to do is integrate this stuff with the custom display list.

Stay tuned!

Posted by Daniel Wasilewski   @    5 June 2011 15 comments

Share This Post

RSS Digg Twitter StumbleUpon Delicious Technorati
 15 Comments
Comments
Jun 9, 2011
8:51 pm
#1 Clark Stevenson :

Scotland here 😀

This is really great stuff!

I myself have been interested in 2d molehill but dispite looking at M2D… im not really getting it.

This article is excellent, i look forward to reading more of your thoughts!

Sep 25, 2011
3:00 am
#2 lee min ho :

Hello there, just became alert to your blog through Google, and found that it is truly informative. I’m gonna watch out for brussels. I will be grateful if you continue this in future. Lots of people will be benefited from your writing. Cheers!

Oct 17, 2011
2:10 am
#3 BG mail :

I was studying some of your articles on this site and I conceive this website is really instructive! Continue posting.

Oct 27, 2011
9:59 pm

It’s a good shame you don’t contain a give money button! I’d definitely give money for this fantastic webpage! That i suppose for the time being i’ll be satisfied bookmarking together with including an individual’s Feed that will my best Msn balance. That i appearance forward that will recent messages and definitely will share the web site utilizing my best Facebook or twitter team:

Nov 4, 2011
4:20 pm
#5 free-xbox :

That’s why i have bookmarked flaemo.com
xbox 360 giveaway

Nov 16, 2011
7:06 am

This web site does not show up properly on my blackberry – you may want to try and fix that

Nov 16, 2011
1:00 pm

Sorry mate, it’s a Word Press template. I don’t have to much control on it. When time permit I will do some custom one.

Nov 21, 2011
2:28 am
#8 kboy :

thanks a lot for transparent part !

Dec 26, 2011
3:37 am

Hi, i read your blog from time to time and i own a similar one and i was just wondering if you get a lot of spam remarks? If so how do you prevent it, any plugin or anything you can suggest? I get so much lately it’s driving me insane so any assistance is very much appreciated.

Dec 29, 2011
1:45 pm

Yes, this is main wp problem. I was trying few of the ip/spam blockers but don’t work very well. The one is working will block normal comments as well. So.. useless. Planing to write my own blog framework.

Feb 2, 2012
5:16 pm

Awesome post! I will keep an on eye on your blog.

Mar 5, 2012
6:29 pm

Hi there! This is our very first remark here i really wanted to provide a simple shout out there and inform you We honestly take pleasure in examining your blog blogposts. Is it possible to advocate another weblogs And websites / community forums that cover the identical subjects? Regards!

Mar 15, 2012
10:34 pm

Hey there, You’ve got done a amazing job. I will undoubtedly digg it and personally recommend to my buddies. I’m confident they will probably be benefited from this web site.

Mar 22, 2012
10:22 am
#14 emu360v1.4 :

I enjoy, cause I found exactly what I was looking for. You have ended my 4 day lengthy hunt! God Bless you man. Have a great day. Bye

Apr 7, 2012
10:38 pm
#15 Alexis Modzelewski :

I had to read this twice to totally understand what you’re saying. This is some deep stuff!

Leave a comment

Previous Post
«
Next Post
»