16

I've got a question whether there is a special mode for grouping models in libGDX. I created a simple class that implements ApplicationListener which shows my problem. I am using the nightly build of libGDX.

I read two different models which use the same texture. The application renders respectively 250 models of every type. This is how the part of rendering code looks like:

  mModelBatch.begin(camera);
  for(int y=0; y<50; y++)
  {
     for(int x=-5; x<5; x++)
     {
        ModelInstance instance;
        if(x%2 == 0) instance = modelInstance1;
        else instance = modelInstance2;

        instance.transform.setToTranslation(x, 0, -y);
        mModelBatch.render(instance);
     }

  }

  mModelBatch.end(); 

I've got about 12 FPS on my phone (Sony Xperia mini pro).

I was trying to find a good solution so I wrote another test code:

public void getRenderables(Array<Renderable> renderables, Pool<Renderable> pool)
{
  for(int y=0; y<50; y++)
  {
     for(int x=-5; x<5; x++)
     {
        ModelInstance instance;
        if(x%2 == 0) instance = modelInstance1;
        else instance = modelInstance2;

        instance.transform.setToTranslation(x, y%3, -y);

        Renderable renderable = pool.obtain();
        renderable = instance.getRenderable(renderable);
        renderables.add(renderable);
     }
  }

}

I used it as it is shown below:

mModelBatch.begin(camera);      
mModelBatch.render(testRenderProvider);
mModelBatch.end();

However it still gave me 13 FPS. Meanwhile to make another test I created in blender the same map as it was in the previous program. Next I grouped everything in just one object (without any additional editions). This way I created one BIG object of size almost 1MB, it can be seen on the screenshot from blender.

enter image description here

I changed the test program in a way that it draws only this one BIG object:

mModelBatch.begin(camera);
      modelInstance1.transform.setToTranslation(0, 0, 0);
      mModelBatch.render(modelInstance1);
mModelBatch.end();

Next thing I did was that I launched the program on my phone (Sony XPeria Mini Pro - the same as previously) and iPod 5g and I've got... 60 FPS! enter image description here

Is it possible to render everything in just one draw call?

Paweł Jastrzębski
  • 747
  • 1
  • 6
  • 24
  • I haven't used LibGDX for 3D yet, and the last time I used OpenGL is also a few years past. But I slightly remember that in case you want to render the same object many times, you should use Vertex Buffer Objects (http://libgdx.badlogicgames.com/nightlies/docs/api/com/badlogic/gdx/graphics/glutils/VertexBufferObject.html). I think the problem is not the texture binding, because I assume that the modelBatch handles that itself in a smart way. – noone Nov 24 '13 at 06:30
  • 1
    Try profiling the code to get an idea of what is taking the most time. Mario has some slides on the topic here: http://www.badlogicgames.com/wordpress/?p=3224 – R Hyde Nov 24 '13 at 11:02
  • I know that all should be done in one draw call. It should use only one texture and all the models should be rendered at once. I don't know how to enable appropriate drawing mode in libgdx (if it exists). Or maybe I have to write own part of the renderer that hadles this issue. Has anybody encountered a similar problem and could share experience with me? – Paweł Jastrzębski Nov 25 '13 at 00:38

1 Answers1

13

Problem solved! I achieved 60 FPS on a ver low-end mobile device. The game runs smootly. I found out how to merge multiple Meshes into one mesh so that VBO mechanizms can be used. There was a bug in libGDX which caused the Mesh-copying method unusable with multiple meshes. After the changes, the map is divided in small sectors. Each sector consists of meshes with the same z-axis value as can be seen on the following image: enter image description here

The VBO mechanisms are very limitated, so not many vertices can be drawn at one-time that is why the sectors have to be rather small. New renderer had to be written to handle the rendering properly. And the parts of the renderer are merging the meshes dynamically (without any seperate tool, like eg. blender).

public static Mesh mergeMeshes(AbstractList<Mesh> meshes, AbstractList<Matrix4> transformations)
{
    if(meshes.size() == 0) return null;

    int vertexArrayTotalSize = 0;
    int indexArrayTotalSize = 0;

    VertexAttributes va = meshes.get(0).getVertexAttributes();
    int vaA[] = new int [va.size()];
    for(int i=0; i<va.size(); i++)
    {
        vaA[i] = va.get(i).usage;
    }

    for(int i=0; i<meshes.size(); i++)
    {
        Mesh mesh = meshes.get(i);
        if(mesh.getVertexAttributes().size() != va.size()) 
        {
            meshes.set(i, copyMesh(mesh, true, false, vaA));
        }

        vertexArrayTotalSize += mesh.getNumVertices() * mesh.getVertexSize() / 4;
        indexArrayTotalSize += mesh.getNumIndices();
    }

    final float vertices[] = new float[vertexArrayTotalSize];
    final short indices[] = new short[indexArrayTotalSize];

    int indexOffset = 0;
    int vertexOffset = 0;
    int vertexSizeOffset = 0;
    int vertexSize = 0;

    for(int i=0; i<meshes.size(); i++)
    {
        Mesh mesh = meshes.get(i);

        int numIndices = mesh.getNumIndices();
        int numVertices = mesh.getNumVertices();
        vertexSize = mesh.getVertexSize() / 4;
        int baseSize = numVertices * vertexSize;
        VertexAttribute posAttr = mesh.getVertexAttribute(Usage.Position);
        int offset = posAttr.offset / 4;
        int numComponents = posAttr.numComponents;

        { //uzupelnianie tablicy indeksow
            mesh.getIndices(indices, indexOffset);
            for(int c = indexOffset; c < (indexOffset + numIndices); c++)
            {
                indices[c] += vertexOffset;
            }
            indexOffset += numIndices;
        }

        mesh.getVertices(0, baseSize, vertices, vertexSizeOffset);
        Mesh.transform(transformations.get(i), vertices, vertexSize, offset, numComponents, vertexOffset, numVertices);
        vertexOffset += numVertices;
        vertexSizeOffset += baseSize;
    }

    Mesh result = new Mesh(true, vertexOffset, indices.length, meshes.get(0).getVertexAttributes());
    result.setVertices(vertices);
    result.setIndices(indices);
    return result;
} 

    public static Mesh copyMesh(Mesh meshToCopy, boolean isStatic, boolean removeDuplicates, final int[] usage) {
    // TODO move this to a copy constructor?
    // TODO duplicate the buffers without double copying the data if possible.
    // TODO perhaps move this code to JNI if it turns out being too slow.
    final int vertexSize = meshToCopy.getVertexSize() / 4;
    int numVertices = meshToCopy.getNumVertices();
    float[] vertices = new float[numVertices * vertexSize];
    meshToCopy.getVertices(0, vertices.length, vertices);
    short[] checks = null;
    VertexAttribute[] attrs = null;
    int newVertexSize = 0;
    if (usage != null) {
        int size = 0;
        int as = 0;
        for (int i = 0; i < usage.length; i++)
            if (meshToCopy.getVertexAttribute(usage[i]) != null) {
                size += meshToCopy.getVertexAttribute(usage[i]).numComponents;
                as++;
            }
        if (size > 0) {
            attrs = new VertexAttribute[as];
            checks = new short[size];
            int idx = -1;
            int ai = -1;
            for (int i = 0; i < usage.length; i++) {
                VertexAttribute a = meshToCopy.getVertexAttribute(usage[i]);
                if (a == null)
                    continue;
                for (int j = 0; j < a.numComponents; j++)
                    checks[++idx] = (short)(a.offset/4 + j);
                attrs[++ai] = new VertexAttribute(a.usage, a.numComponents, a.alias);
                newVertexSize += a.numComponents;
            }
        }
    }
    if (checks == null) {
        checks = new short[vertexSize];
        for (short i = 0; i < vertexSize; i++)
            checks[i] = i;
        newVertexSize = vertexSize;
    }

    int numIndices = meshToCopy.getNumIndices();
    short[] indices = null; 
    if (numIndices > 0) {
        indices = new short[numIndices];
        meshToCopy.getIndices(indices);
        if (removeDuplicates || newVertexSize != vertexSize) {
            float[] tmp = new float[vertices.length];
            int size = 0;
            for (int i = 0; i < numIndices; i++) {
                final int idx1 = indices[i] * vertexSize;
                short newIndex = -1;
                if (removeDuplicates) {
                    for (short j = 0; j < size && newIndex < 0; j++) {
                        final int idx2 = j*newVertexSize;
                        boolean found = true;
                        for (int k = 0; k < checks.length && found; k++) {
                            if (tmp[idx2+k] != vertices[idx1+checks[k]])
                                found = false;
                        }
                        if (found)
                            newIndex = j;
                    }
                }
                if (newIndex > 0)
                    indices[i] = newIndex;
                else {
                    final int idx = size * newVertexSize;
                    for (int j = 0; j < checks.length; j++)
                        tmp[idx+j] = vertices[idx1+checks[j]];
                    indices[i] = (short)size;
                    size++;
                }
            }
            vertices = tmp;
            numVertices = size;
        }
    }

    Mesh result;
    if (attrs == null)
        result = new Mesh(isStatic, numVertices, indices == null ? 0 : indices.length, meshToCopy.getVertexAttributes());
    else
        result = new Mesh(isStatic, numVertices, indices == null ? 0 : indices.length, attrs);
    result.setVertices(vertices, 0, numVertices * newVertexSize);
    result.setIndices(indices);
    return result;
}

This can be very useful for the people trying to write their own 3D games in libGDX. Without this mechanizm it is rather impossible to write anything more compilcated than a few models.

Paweł Jastrzębski
  • 747
  • 1
  • 6
  • 24
  • 2
    Nice answer, do not hesitate to accept it. It is ok accepting your own answers :) – Daahrien Jan 08 '14 at 09:46
  • 2
    Hi Paweł. 60 fps is really great! I would like to know how do you use this methods? I'm a beginner with libgdx and the fps of my game drops to 8 very often. Had a look at libgdx codes and saw that SpriteBatch creates Mesh with VertexArray and not VertexBufferObject. I read that VBO s are a way for a good performance. I set forceVBO tp true in Mesh but no visible differences. Will go on with further details if you can help me. Any help is appreciated. – Ruzanna Jan 28 '14 at 16:12
  • Of course I will help you, but it isn't ver simple as you need to write your own parts of libGDX. It's like that because libGDX code has bugs and you can't use it. The trick is to group your models (I am assuming that you are using them in 3D) into small chunks and then use VBO (force VBO works for one separate model). So the trick is to join every mesh to create one bigger (but not too big) mesh and then use the VBO to render it. If you need any more complex answer (with the code snippets) just tell me and I will very keen on helping you. – Paweł Jastrzębski Jan 28 '14 at 20:18
  • 1
    I believe I have the same problem, but I'm not exactly sure how you use the `mergeMeshes()` method. Would you mind giving an example of some code that uses it? Basically, I'm confused where you get the `AbstractList meshes` value to pass as a parameter. – twiz Nov 14 '14 at 22:43
  • Did you ever put your code on github, would be useful – Burf2000 Aug 10 '15 at 18:47
  • Hello @PawełJastrzębski i have been looking for this code please help me solve some issues. i am making 2d rect models from texture region, i don't know how to combine them, any help or code will be a great help, please provide explanation about your code above and the draw methods you used. – Diljeet Dec 19 '15 at 11:57