Poor performance

Hi to everybody

I’m trying to implement a simple 2D tile scroll (vertical) for a shoot em up prototype.
Basically the idea is to create a mesh of quads, like a checkboard, where every quad has one texture and the “mosaic” of all of them will compose the scrolling background.
iv’e a root node, called n0, then i’ve GRID_HEIGTH nodes, called bgr, that represents the GRID_HEIGTH row of my checkboard, and, at least a GRID_WIDTH * GRID_HEIGTH array of nodes, one for each quad.
The structure is

n0 —> gr0 → gn00,…gn0x
’ —> gr1 —>gr10…gr1x
’ —> …and so on

in the simpleUpdate method, i only update the y coordinate of the n0 node and all the tiles moves vertically.

When the last row disappears on the bottom, i move to the top (outside the camera) and change the texture inside it, so i should obtain an infinite scroll.

Now: the following code works (for now there’s no row movement but this is not a problem) but it is very cpu (or gpu?) killer.
With 32x256 quads the fps drops under 15 on a i7 with a AMD Radeon R7 200 Series, it surprise me a lot. Ok it is not the top-master-race card or the latest CPU but 33724 vertex should run at least 10 or more times faster in my computer.

Sorry for my english and here is the code: what is wrong?

    private static final int GRID_WIDTH = 32;
    private static final int GRID_HEIGTH = 256;
    private Node[] bgr;
    private Node[] bgn;
    private Quad[] qn;
    private Geometry[] gn;
    private Node n0;
    private Node n1;
    private Node n2;
    private Node n3;
    private Quad q3;
    private Geometry geom3;
    Material mat3;
    private boolean bla = true;
    
    public static void main(String[] args) {
        Main app = new Main();
        app.start();
    }

 public void simpleInitApp() {
        bgr = new Node[GRID_HEIGTH];
        bgn = new Node[GRID_WIDTH * GRID_HEIGTH];
        qn = new Quad[GRID_WIDTH * GRID_HEIGTH];
        gn = new Geometry[GRID_WIDTH * GRID_HEIGTH];
        
        flyCam.setMoveSpeed(10);
        DirectionalLight sunLight = new DirectionalLight();
        sunLight.setDirection(new Vector3f(1, -1, -2).normalizeLocal());
        sunLight.setColor(ColorRGBA.White);
        rootNode.addLight(sunLight);
        
        Material mat1 = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
        mat1.setTexture("DiffuseMap", assetManager.loadTexture("Textures/mare.png"));
        mat1.setColor("Diffuse", ColorRGBA.White);
        mat1.setColor("Specular", ColorRGBA.White);
        mat1.setColor("Ambient", ColorRGBA.Blue);
        mat1.setBoolean("UseMaterialColors", true);
        
        Material mat2 = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
        mat2.setTexture("DiffuseMap", assetManager.loadTexture("Textures/bosco.png"));
        mat2.setColor("Diffuse", ColorRGBA.White);
        mat2.setColor("Specular", ColorRGBA.White);
        mat2.setColor("Ambient", ColorRGBA.Blue);
        mat2.setBoolean("UseMaterialColors", true);
        
        mat3 = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
        mat3.setTexture("DiffuseMap", assetManager.loadTexture("Textures/erba.png"));
        mat3.setColor("Diffuse", ColorRGBA.White);
        mat3.setColor("Specular", ColorRGBA.White);
        mat3.setColor("Ambient", ColorRGBA.Blue);
        mat3.setBoolean("UseMaterialColors", true);
        n0 = new Node("n0");
        
        for (int i = 0; i < GRID_HEIGTH; i++) {
            bgr[i] = new Node();
            for (int j = 0; j < GRID_WIDTH; j++) {
                bgn[i * GRID_WIDTH + j] = new Node();
                qn[i * GRID_WIDTH + j] = new Quad(1, 1);
                gn[i * GRID_WIDTH + j] = new Geometry(i + "," + j, qn[i * GRID_WIDTH + j]);
              
                int idx = (i + j) % 3;
                
                Material m = null;
                
                switch (idx) {
                    case 0:
                        m = mat1;
                        break;
                    case 1:
                        m = mat2;
                        break;
                    case 2:
                        m = mat3;
                        break;
                }
                
                gn[i * GRID_WIDTH + j].setMaterial(m);
                
                
                bgn[i * GRID_WIDTH + j].attachChild(gn[i * GRID_WIDTH + j]);
                bgn[i * GRID_WIDTH + j].setLocalTranslation(j, i, 0);
                bgr[i].attachChild(bgn[i * GRID_WIDTH + j]);
            }
            n0.attachChild(bgr[i]);
        }
        
        rootNode.attachChild(n0);
    }
    
    @Override
    public void simpleUpdate(float tpf) {
        Vector3f local = n0.getLocalTranslation();
        local.y -= tpf;
        n0.setLocalTranslation(local);

    }

Lots of objects isn’t a good idea. Check out the documentation on best practices. You should probably put your quads in one geometry and move them there. You could try to use a BatchNode but for best efficiency you should do this yourself, possibly even in a shader.

1 Like

+1 for what normen said.

We had the same problem when building a tile based 3d editor. We built our world out of block tiles (think minecraft, technicalle) but our performance with a 100x100 map with height 1 was horrible.

We combined it to a single mesh now and the performance is super fast :smile:

Also, note that in the case of Minecraft style games, if you aren’t removing the invisible faces (between blocks) then you are also rendering about 80% more surfaces than you need to be.

Edit: this is what we mean when we say that block worlds are not made of blocks.

Also, culling can be your friend too, if you batch your whole world into one mesh you loose that feature.

a sample usage of batchNode, I used to create “brick/column” in a top-down game:

http://ludumdare.com/compo/wp-content/compo2//407933/20693-shot0.png-eq-900-500.jpg

Hope it help

It is much faster if you do it manually, rather than using batch()

Here is the sample code of custom mesh for flat map. It creates only flat land, which is about 90% of the map in my game. The rest are borderlines and water tiles, which are separate geometries - dont need to optimize them.

Note that I’m using static buffers, it speeds up the whole process, With this solution I can generate new 70x70 part of map in few milliseconds. Having 90% of 70x70 map in one mesh gives me many fps.

public class GroundMesh extends Mesh
{
    private static int _mapSize = 0;
    private static float[] _vert;
    private static float[] _texC;
    private static int[] _indexes;
    
    public GroundMesh(byte[][] tmap, int mapSize, int mx, int my)
    {
        if (_mapSize < mapSize)
        {
            int s = mapSize * mapSize;
            _vert = new float[s * 4 * 3];
            _texC = new float[s * 4 * 2];
            _indexes = new int[s * 6];
        }
        
        int vertCnt = 0;
        int vertPos = 0;
        int texPos = 0;
        int icnt = 0;
        
        for (int x = 0; x < mapSize; x++)
        {
            for (int y = 0; y < mapSize; y++)
            {
                if (tmap[x][y] == 0) continue;
                                    
                //Ja tu 'obiegam' dookoła kratke, Wg wiki kolejnośc jest inna
                // tam izie wg odwróconej litery Z
                
                _vert[vertPos++] = (x * 2);
                _vert[vertPos++] = 0;
                _vert[vertPos++] = (y * 2) + 2;
                
                _vert[vertPos++] = (x * 2) + 2;
                _vert[vertPos++] = 0;
                _vert[vertPos++] = (y * 2) + 2;
                
                _vert[vertPos++] = (x * 2) + 2;
                _vert[vertPos++] = 0;
                _vert[vertPos++] = (y * 2);
                
                _vert[vertPos++] = (x * 2);
                _vert[vertPos++] = 0;
                _vert[vertPos++] = (y * 2);
                
                float px = (float)((mx + x) % 2) * 0.499f;
                float py = (float)(1 - ((my + y) % 2)) * 0.499f;
                
                _texC[texPos++] = 0.001f + px;
                _texC[texPos++] = 0.001f + py;
                
                _texC[texPos++] = 0.500f + px;
                _texC[texPos++] = 0.001f + py;
                
                _texC[texPos++] = 0.500f + px;
                _texC[texPos++] = 0.500f + py;
                
                _texC[texPos++] = 0.001f + px;
                _texC[texPos++] = 0.500f + py;
                                   
                _indexes[icnt++] = vertCnt + 2;
                _indexes[icnt++] = vertCnt + 0;
                _indexes[icnt++] = vertCnt + 1;
                _indexes[icnt++] = vertCnt + 2;
                _indexes[icnt++] = vertCnt + 3;
                _indexes[icnt++] = vertCnt + 0;
                
                vertCnt += 4;        
            }
        }
        
        setBuffer(Type.Position, 3, createFloatBuffer(vertPos, _vert));
        setBuffer(Type.TexCoord, 2, createFloatBuffer(texPos, _texC));
        setBuffer(Type.Index,    3, createIntBuffer(icnt, _indexes));
        updateBound();
    }
    
    private IntBuffer createIntBuffer(int cnt, int... data) 
    {
        if (data == null) 
        {
            return null;
        }
        IntBuffer buff = BufferUtils.createIntBuffer(cnt);
        buff.clear();
        buff.put(data, 0, cnt);
        buff.flip();
        return buff;
    }
    
    private FloatBuffer createFloatBuffer(int len, float[] data) 
    {
        if (data == null) 
        {
            return null;
        }
        FloatBuffer buff = BufferUtils.createFloatBuffer(len);
        buff.put(data, 0, len);
        buff.flip();
        return buff;
    }
}