4

I have an array of simple 2D objects, mostly made up of two triangles. Although the objects are quite simple, each one is drawn using stencil operations so every object will require it's own drawTriangles() call.

What is the best way to store and handle these objects and their vertex and index buffers?

I can think of a few different ways of doing it :

  1. At start up create one large vertex and index buffer and add each object to it, eg :

    public function initialize():void{
        for each(object in objectList){
            vertexData.push(object.vertices);
            indexData.push(triangle1, triangle2);
        }
    
        indices  = context3D.createIndexBuffer( numTriangles * 3 );
        vertices = context3D.createVertexBuffer( numVertices, dataPerVertex );
        indices.uploadFromVector( indexData, 0, numIndices );
        vertices.uploadFromVector( vertexData, 0, numVertices );
    }
    

    During rendering loop through all objects, check which ones are visible on screen and update their vertices. Then re-upload the entire vertex buffer. Afterwards loop through the visible objects and draw each object's triangles with individual calls to drawTriangles()

    public function onRender():void{
        for each(object in objectList){
            if(object.visibleOnScreen)
                object.updateVertexData();
        }
    
        vertices.uploadFromVector( vertexData, 0, numVertices );
    
        for each(object in objectsOnScreen){
            drawTriangles(indices, object.vertexDataOffset, object.numTriangles);
        }
    }
    
  2. You could also do something similar to number 1 except re-upload each object's vertex data only when needed :

    public function onRender():void{
        for each(object in objectList){
            if(object.visibleOnScreen){
                if(object.hasChanged)
                    vertices.uploadFromVector( vertexData, object.vertexDataOffset, object.numTriangles );
                drawTriangles(indices, object.vertexDataOffset, object.numTriangles);
            }
        }
    }
    
  3. You could also create a new vertex buffer consisting of only visible objects on each frame :

    public function onRender():void{
        for each(object in objectList){
            if(object.visibleOnScreen){
                vertexData.push(object.vertices);
                indexData.push(triangle1, triangle2);
            }
        }
    
        indices  = context3D.createIndexBuffer( numTriangles * 3 );
        vertices = context3D.createVertexBuffer( numVertices, dataPerVertex );
        indices.uploadFromVector( indexData, 0, numIndices );
        vertices.uploadFromVector( vertexData, 0, numVertices );
    
        for each(object in objectsOnScreen){
            drawTriangles(indices, object.vertexDataOffset, object.numTriangles);
        }
    }
    
  4. Another option is to create individual vertex buffers for each object :

    public function initialize():void{
        for each(object in objectList){
            object.indices  = context3D.createIndexBuffer( object.numTriangles * 3 );
            object.vertices = context3D.createVertexBuffer( object.numVertices, dataPerVertex );
        }
    }
    
    public function onRender():void{
        for each(object in objectList){
            if(object.visibleOnScreen){
                if(object.hasChanged)
                    object.vertices.uploadFromVector( object.vertexData, 0, object.numTriangles );
                drawTriangles(indices, 0, object.numTriangles);
            }
        }
    }
    

I can imagine that option 1 will be slow because the entire vertex buffer needs to be uploaded each frame.
Option 2 has the advantage that it only needs to to upload vertex data that has changed but might suffer from multiple calls to uploadFromVector.

UPDATE

I've had a look at the Starling framework source, specifically the QuadBatch class :
http://gamua.com/starling/
https://github.com/PrimaryFeather/Starling-Framework

It seems that all vertices are manually transformed before hand and the the entire vertex buffer is rebuilt and uploaded every frame.

cmann
  • 1,920
  • 4
  • 21
  • 33
  • 1
    I'm not sure that the stage3D is really meant to be used this way. I'm not really experienced with this in flash but as a C++/OpenGL developer I can tell you that re-uploading all of your vertex data to the GPU every single render call is bad practice even when you're using blazing fast compiled assembly (from C++). I can also tell you that there are issues on many versions of Android with doing this, specifically it actually invokes the GC on every single call which brings the app to a grinding halt. Just my 2 cents. :) –  Apr 08 '12 at 14:56
  • As a suggestion, look into the Away3D 4.0 (stage3D based) library. It's open source so you can see exactly how they handle this and other issues you may encounter. Also you might consider just using this library instead of re-inventing the wheel and writing your own code. –  Apr 08 '12 at 14:57
  • So if that's the case then creating individual vertex buffers for each object and instead using a transformation matrix and shaders to move and rotate the objects would be the best option. – cmann Apr 08 '12 at 15:07
  • If your data is changing every frame, there is absolutely nothing wrong with upload the data every frame. – NateS Jun 01 '14 at 14:46
  • If you're writing you own custom 2D engine suited to your need, I wouldn't look to closely to Starling source which is clearly not optimized in the way it refreshes an reuploads its vertex buffers – jauboux Jun 19 '14 at 14:23

1 Answers1

0

You do need to upload your data on each frame no matter what because Stage3D needs to know what to draw where. Ultimately optimization starts by reducing draw calls. You can speed up uploading of data by using byteArray instead on data that has not changed and using Vector on data that has changed. Uploading vector is slower but setting vector data is faster, uploading bytearray is faster but setting bytearray data is slower (so use only for cached data). You also do not need to create index and vertex buffers each time. Create them once with a confortable length and create new ones only if that length becomes too small. All this should speed up everything nicely but still the amount of draw calls will slow down everything (after 25+ draw calls you should start to see the downside).

BotMaster
  • 2,233
  • 1
  • 13
  • 16