5
\$\begingroup\$

I am writing custom voxel engine in Java with JME3, but it is very slow, for simplicity I am rendering cubes instead of faces, also border detection messes up output mesh (doesn't draw cubes on chunk edges).

I would be very thankful if someone with more skill looked into it.

I have included all the code so you won't miss anything. You could have a look at CubicalMeshificator and improve on it, lets say by making only necessary faces not whole cubes.

You can find rest of the code here: http://pastebin.com/7S90L1Bd

CubicalMeshificator (extracts mesh from voxel data)

public class CubicalMeshificator {
    private static LinkedList<Vector3f> verticesPosition = new LinkedList<Vector3f>();
    private static LinkedList<Vector3f> verticesNormal = new LinkedList<Vector3f>();
    private static LinkedList<Vector2f> verticesUV = new LinkedList<Vector2f>();
    private static LinkedList<Integer> indices = new LinkedList<Integer>();

    public static Mesh createMesh(Voxel [][][] voxels, Vector3f size, float voxelSize) {
        for(int x = 0; x < size.x; x++) {
            for(int y = 0; y < size.y; y++) {
                for(int z = 0; z < size.z; z++) {
                    if(x-1 >= 0 && x+1 < size.x &&
                       y-1 >= 0 && y+1 < size.y &&
                       z-1 >= 0 && z+1 < size.z)
                    if(voxels[x][y][z].getId() != EVoxel.Air &&
                        (voxels[x+1][y][z].getId() == EVoxel.Air ||
                        voxels[x-1][y][z].getId() == EVoxel.Air ||
                        voxels[x][y][z+1].getId() == EVoxel.Air ||
                        voxels[x][y][z-1].getId() == EVoxel.Air ||
                        voxels[x][y+1][z].getId() == EVoxel.Air ||
                        voxels[x][y-1][z].getId() == EVoxel.Air) ) {
                        verticesPosition.add(new Vector3f(x*voxelSize, y*voxelSize, z*voxelSize));
                        verticesPosition.add(new Vector3f(x*voxelSize+voxelSize, y*voxelSize, z*voxelSize));
                        verticesPosition.add(new Vector3f(x*voxelSize, y*voxelSize+voxelSize, z*voxelSize));
                        verticesPosition.add(new Vector3f(x*voxelSize+voxelSize, y*voxelSize+voxelSize, z*voxelSize));
                        verticesPosition.add(new Vector3f(x*voxelSize, y*voxelSize, z*voxelSize+voxelSize));
                        verticesPosition.add(new Vector3f(x*voxelSize+voxelSize, y*voxelSize, z*voxelSize+voxelSize));
                        verticesPosition.add(new Vector3f(x*voxelSize, y*voxelSize+voxelSize, z*voxelSize+voxelSize));
                        verticesPosition.add(new Vector3f(x*voxelSize+voxelSize, y*voxelSize+voxelSize, z*voxelSize+voxelSize));

                       indices.add(verticesPosition.size() + 3);
                       indices.add(verticesPosition.size() + 1);
                       indices.add(verticesPosition.size() + 0);
                       indices.add(verticesPosition.size() + 0);
                       indices.add(verticesPosition.size() + 2);
                       indices.add(verticesPosition.size() + 3);

                       indices.add(verticesPosition.size() + 7);
                       indices.add(verticesPosition.size() + 5);
                       indices.add(verticesPosition.size() + 1);
                       indices.add(verticesPosition.size() + 1);
                       indices.add(verticesPosition.size() + 3);
                       indices.add(verticesPosition.size() + 7);

                       indices.add(verticesPosition.size() + 6);
                       indices.add(verticesPosition.size() + 4);
                       indices.add(verticesPosition.size() + 5);
                       indices.add(verticesPosition.size() + 5);
                       indices.add(verticesPosition.size() + 7);
                       indices.add(verticesPosition.size() + 6);

                       indices.add(verticesPosition.size() + 2);
                       indices.add(verticesPosition.size() + 0);
                       indices.add(verticesPosition.size() + 4);
                       indices.add(verticesPosition.size() + 4);
                       indices.add(verticesPosition.size() + 6);
                       indices.add(verticesPosition.size() + 2);

                       indices.add(verticesPosition.size() + 7);
                       indices.add(verticesPosition.size() + 3);
                       indices.add(verticesPosition.size() + 2);
                       indices.add(verticesPosition.size() + 2);
                       indices.add(verticesPosition.size() + 6);
                       indices.add(verticesPosition.size() + 7);

                       indices.add(verticesPosition.size() + 1);
                       indices.add(verticesPosition.size() + 5);
                       indices.add(verticesPosition.size() + 4);
                       indices.add(verticesPosition.size() + 4);
                       indices.add(verticesPosition.size() + 0);
                       indices.add(verticesPosition.size() + 1);
                    }
                }
            }
        }

        Mesh mesh = new Mesh();
        mesh.setBuffer(VertexBuffer.Type.Position, 3, BufferUtils.createFloatBuffer(verticesPosition.toArray(new Vector3f[0])));
        mesh.setBuffer(VertexBuffer.Type.Normal, 3, BufferUtils.createFloatBuffer(verticesNormal.toArray(new Vector3f[0])));
        mesh.setBuffer(VertexBuffer.Type.TexCoord, 2, BufferUtils.createFloatBuffer(verticesUV.toArray(new Vector2f[0])));
        mesh.setBuffer(VertexBuffer.Type.Index, 3, listToBuffer(indices));
        mesh.scaleTextureCoordinates(new Vector2f(5, 5));
        mesh.updateBound();

        return mesh;
    }

    public static IntBuffer listToBuffer(List<Integer> list)
    {
        IntBuffer buff = BufferUtils.createIntBuffer(list.size());
        buff.clear();
        for (Integer e : list) {
            buff.put(e.intValue());
        }
        buff.flip();
        return buff;
    }
}
\$\endgroup\$
  • 1
    \$\begingroup\$ Welcome to CodeReview, Neomex. This is quite a lot of code and is pretty daunting, have you any idea of the source of the slowdown or any particular thing you'd like to focus on? \$\endgroup\$ – Legato May 9 '15 at 12:43
  • \$\begingroup\$ Thanks for the welcome Legato, you could start by looking at CubicalMeshificator and trying to make it render only necessary faces, but i am welcome to any suggestions to the whole code, \$\endgroup\$ – Neomex May 10 '15 at 13:10
  • \$\begingroup\$ @Neomex What Legato is trying to ask is that you add some explanation of what your code does, and an area of code you want to specifically focus on, after that, I'll nominate your question for reopening. Thanks! \$\endgroup\$ – Ethan Bierlein May 10 '15 at 13:13
  • \$\begingroup\$ I have added small explanations but I think rest is self-explanatory. You could focus on CubicalMeshificator but I added rest of code so you won't miss out on how anything works. \$\endgroup\$ – Neomex May 10 '15 at 13:43
  • \$\begingroup\$ @Neomex If you want us to focus on CubicalMeshificator primarily, then you can remove all other blocks of code, and just include a link to them on github saying something like "If you need the background logic, then click here: insert link here". This will make the review less intimidating for users. \$\endgroup\$ – Ethan Bierlein May 10 '15 at 13:46
5
\$\begingroup\$

Profiling

The first thing I would do when I have performance problems is profile the code, to see where the problem is. Profiling is not hard to do, most IDEs come with tools that can do it. There really isn't a need to optimize code that isn't responsible for performance problems, especially because faster code often is less readable.

Most of the things I mention further down might decrease the readability of your code, and you should profile all of them, because it's hard to say what will actually help and what will not, because it's hard to predict what the compiler will optimize, etc.

You will get better profiling results if you extract some code to its own function, eg the code that adds indices, the code that adds vertices, the code that creates the actual mesh, etc. If your worried about the additional overhead because of the function calls, you can remove them after you profiled the code.

Unnecessary Checks in Loop

You perform some checks more often then necessary. When you have three nested loops, checks that only involve the outer loop can be moved to that loop, so they are checked less often and you avoid going through the inner loops. You shoudn't overdo this though, because the extra variables might cause performance loss, and will lead to harder to read code.

For example, you have this check in the inner-most loop: if(x-1 >= 0 && x+1 < size.x, but x is only dependend on the outer-most loop, so if x is for example -1, you still walk through the y loop, and through the z loop, which isn't necessary. So you could rewrite your code as:

    for(int x = 0; x < size.x; x++) {
        if (x > 0 && x < size.x - 1) {
            for(int y = 0; y < size.y; y++) {
                for(int z = 0; z < size.z; z++) {
                    [...]
                }
            }
        }

You can extract the y check the same way. Note also that I rephrased the condition from x - 1 >= 0 to x > 0 which is faster[*] and a lot clearer. You could also negate the expression and use continue if you think that this will lead to too deply nested code.

[*] well, maybe; the compiler might optimize the difference away

Unnecessary Operations in Loop

You also have some operations which you perform quite a lot of times, for example x*voxelSize. You could extract that to directly inside the x loop as well.

Unnecessary Function calls

Function calls aren't free, so in performance critical code it might be a good idea to avoid calling the same function a bunch of times. You could for example try to save the result of verticesPosition.size() in a local variable.

Data Structure Transformations

You perform a lot of transformations from one data structure to another. For example, verticesPosition is a linked list, which you then transform to an array, which is then transformed to a float buffer. Try to reduce the amount of different data structures you use.

Misc

  • always use curly brackets, even around one-line statements. Your first if statement is hard to read without it, and in case you later add another block of code, this can very easily lead to bugs.
  • at the very least, use correct indentation; wrong indentation makes this even more confusing.
  • you could also combine the two if-statements.
\$\endgroup\$
3
\$\begingroup\$

Locality of reference

You are using linked lists. Linked lists have notoriously bad locality of reference and this will severely impact your CPU cache hit/miss ratio and is likely a part of your performance problem.

The best thing you can do is to use a float buffer directly and build the data in it, I'm assuming the FloatBuffer is basically just an array of floats. This will have the best locality of reference you can get.

The fact that you are allocating a new Vector3f for each vertex is likely causing significant stress on the GC. Another good reason to use FloatBuffer directly.

Use fixed sizes

With some math you can figure out how large your index list needs to be, this means here too you can use an IntBuffer directly and save lots of work of adding to a list.

Avoid unnecessary branches

This here:

    for(int x = 0; x < size.x; x++) {
        for(int y = 0; y < size.y; y++) {
            for(int z = 0; z < size.z; z++) {
                if(x-1 >= 0 && x+1 < size.x &&
                   y-1 >= 0 && y+1 < size.y &&
                   z-1 >= 0 && z+1 < size.z)

You can just as well write like this:

    for(int x = 1; x < size.x-1; x++) {
        for(int y = 1; y < size.y-1; y++) {
            for(int z = 1; z < size.z-1; z++) {

and save yourself a lot of branches and evaluations of the branching conditions.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.