jme2: Drawing 150,000 boxes

Hello guys, I’ve been trying to tackle this issue for a couple days now. The problem is I only get around 40 FPS when drawing about 150,000 textures cubes. I have pretty beefy computer.



If anyone has any ideas, I’d really appreciate it.



[java]

package com.fortwars.map;



import java.net.URL;

import java.nio.FloatBuffer;

import java.util.ArrayList;



import javax.swing.ImageIcon;



import com.jme.bounding.BoundingBox;

import com.jme.image.Texture;

import com.jme.math.Vector3f;

import com.jme.renderer.ColorRGBA;

import com.jme.scene.Node;

import com.jme.scene.TexCoords;

import com.jme.scene.TriMesh;

import com.jme.scene.geometryinstancing.GeometryBatchInstance;

import com.jme.scene.geometryinstancing.GeometryBatchInstanceAttributes;

import com.jme.scene.geometryinstancing.instance.GeometryBatchCreator;

import com.jme.scene.shape.Box;

import com.jme.scene.state.MaterialState;

import com.jme.scene.state.TextureState;

import com.jme.system.DisplaySystem;

import com.jme.util.TextureManager;

import com.jme.util.geom.BufferUtils;

import com.jmex.terrain.util.AbstractHeightMap;

import com.jmex.terrain.util.ImageBasedHeightMap;



public class Map extends Node {



private ArrayList<TriMesh> meshes = new ArrayList<TriMesh>();

private ArrayList<GeometryBatchCreator> geometryBatchCreators = new ArrayList<GeometryBatchCreator>();

AbstractHeightMap heightMap=null;



public Map(String heightMapLoc){

URL heightmapImg = Map.class.getClassLoader().getResource(heightMapLoc);

heightMap = new ImageBasedHeightMap(new ImageIcon(heightmapImg).getImage());



createBoxes();

}

private void createBoxes() {

// A box that will be instantiated

Box box = new Box(“Box”, new Vector3f(0, 0, 0), new Vector3f(1, 1, 1));



float x2 = 0.5f;

float y2 = 0.5f;

float z2 = 0.5f;



float[] texCoordinates = {

0, 0.5f, 0.5f, 0.5f, 0.5f, 0.0f, 0f, 0f, // back

0, 0.5f, 0.5f, 0.5f, 0.5f, 0.0f, 0f, 0f, // right

0, 0.5f, 0.5f, 0.5f, 0.5f, 0.0f, 0f, 0f, // front

0, 0.5f, 0.5f, 0.5f, 0.5f, 0.0f, 0f, 0f, // left

x2, 1, 1, 1, 1, x2, z2, x2, // top

x2, 1, 0, 1, 0, x2, z2, x2, // bottom



};



FloatBuffer buffer = box.getTextureCoords().get(0).coords;

buffer.rewind();

buffer.put(texCoordinates);

// The batch geometry creator





// Loop that creates NxN instances

for (int y = 0; y < 128; y++) {

for (int x = 0; x < 128; x++) {

for(int z = 0;z<(heightMap.getTrueHeightAtPoint(x, y)*0.3f)-8; z++) {

if(geometryBatchCreators.size()-1<z)

geometryBatchCreators.add(z,new GeometryBatchCreator());

// Box instance attributes

GeometryBatchInstanceAttributes attributes =

new GeometryBatchInstanceAttributes(

new Vector3f(x,z,y),

// Translation

new Vector3f(1f,1f,1f),

// Scale

box.getLocalRotation(),

// Rotation

new ColorRGBA(1,1,1,0));

// Color



// Box instance (batch and attributes)

GeometryBatchInstance instance =

new GeometryBatchInstance(box, attributes);



// Add the instance

geometryBatchCreators.get(z).addInstance(instance);

}

}

}



// Create a TriMesh

for(int n = 0;n<geometryBatchCreators.size();n++){

System.out.println(“batch:” +n);

TriMesh mesh = new TriMesh();

mesh.setModelBound(new BoundingBox());



// Create the batch’s buffers

mesh.setIndexBuffer(BufferUtils.createIntBuffer(

geometryBatchCreators.get(n).getNumIndices()));

mesh.setVertexBuffer(BufferUtils.createVector3Buffer(

geometryBatchCreators.get(n).getNumVertices()));

mesh.setNormalBuffer(BufferUtils.createVector3Buffer(

geometryBatchCreators.get(n).getNumVertices()));

mesh.setTextureCoords(new TexCoords(BufferUtils.createVector2Buffer(

geometryBatchCreators.get(n).getNumVertices())), 0);

mesh.setColorBuffer(BufferUtils.createFloatBuffer(

geometryBatchCreators.get(n).getNumVertices() * 4));





// Commit the instances to the mesh batch

geometryBatchCreators.get(n).commit(mesh);







// Add a texture

TextureState ts = DisplaySystem.getDisplaySystem().getRenderer().createTextureState();

TextureManager.COMPRESS_BY_DEFAULT = false;

Texture t0 = TextureManager.loadTexture(

Map.class.getClassLoader().getResource(

“res/grass.png”),

Texture.MinificationFilter.NearestNeighborLinearMipMap,

Texture.MagnificationFilter.NearestNeighbor, 2.0f, false);

t0.setWrap(Texture.WrapMode.Clamp);

ts.setTexture(t0, 0);



// Material

MaterialState ms = DisplaySystem.getDisplaySystem().getRenderer().createMaterialState();

ms.setColorMaterial(MaterialState.ColorMaterial.AmbientAndDiffuse);



// Render states

mesh.setRenderState(ms);

mesh.setRenderState(ts);

mesh.updateRenderState();



// Return the mesh

mesh.updateModelBound();

mesh.lock();



meshes.add(mesh);

this.attachChild(mesh);

}

}

public boolean isTileAt(int x3, int y3, int z3){

for(TriMesh t: meshes){

Vector3f v = t.getWorldTranslation();

if((int)v.x == x3 && (int)v.y==y3 && (int)v.z == z3){

return true;

}

}

return false;

}

}

[/java]

I could be misstaken, but isn’t 150k cubes a lot? What is your aim?



What kind of computer do you have anyway? Also, could you add code to it’s testable?



Very interesting sofar :slight_smile:

You should join some boxes to geometries. Many objects are more of a strain to the GPU than many faces.

Normen: I use geometrybatchcreator and do indeed join boxes.



150,000 cubes is only 1.5 million triangles, a GTX 260 should easily be about to handle this.



Here is my updated code, I changed the map to be grouped into 16x16 chunks:



HelloWorld.java

[java]

import com.fortwars.entity.Player;

import com.fortwars.map.Map;

import com.jme.app.SimpleGame;

import com.jme.input.ChaseCamera;

import com.jme.math.Vector3f;



/**

  • Started Date: Jul 20, 2004



  • Simple HelloWorld program for jME

    *
  • @author Jack Lindamood

    /

    public class HelloWorld extends SimpleGame {

    public static void main(String[] args) {



    HelloWorld app = new HelloWorld();

    app.setConfigShowMode(ConfigShowMode.AlwaysShow);

    app.start();

    }



    protected void simpleInitGame() {





    Map map = new Map(“res/heightmap.png”);

    map.lock();

    rootNode.attachChild(map);



    cam.setLocation(new Vector3f(10,8,15));



    }

    }

    [/java]



    Map.java

    [java]

    package com.fortwars.map;



    import java.net.URL;

    import java.nio.FloatBuffer;

    import java.util.ArrayList;



    import javax.swing.ImageIcon;



    import com.jme.bounding.BoundingBox;

    import com.jme.image.Texture;

    import com.jme.math.Vector3f;

    import com.jme.renderer.ColorRGBA;

    import com.jme.scene.Node;

    import com.jme.scene.SharedMesh;

    import com.jme.scene.TexCoords;

    import com.jme.scene.TriMesh;

    import com.jme.scene.geometryinstancing.GeometryBatchInstance;

    import com.jme.scene.geometryinstancing.GeometryBatchInstanceAttributes;

    import com.jme.scene.geometryinstancing.instance.GeometryBatchCreator;

    import com.jme.scene.shape.Box;

    import com.jme.scene.state.MaterialState;

    import com.jme.scene.state.TextureState;

    import com.jme.system.DisplaySystem;

    import com.jme.util.TextureManager;

    import com.jme.util.geom.BufferUtils;

    import com.jmex.terrain.util.AbstractHeightMap;

    import com.jmex.terrain.util.ImageBasedHeightMap;



    public class Map extends Node {



    ArrayList batchCreators = new ArrayList ();

    private ArrayList geomBatchInstances = new ArrayList();

    Box box = new Box(“Box”, new Vector3f(0, 0, 0), new Vector3f(1, 1, 1));

    AbstractHeightMap heightMap=null;

    int[][][] mapData = new int[128][128][16];



    float x2 = 0.5f;

    float y2 = 0.5f;

    float z2 = 0.5f;



    float[] texCoordinates = {

    0, 0.5f, 0.5f, 0.5f, 0.5f, 0.0f, 0f, 0f, // back

    0, 0.5f, 0.5f, 0.5f, 0.5f, 0.0f, 0f, 0f, // right

    0, 0.5f, 0.5f, 0.5f, 0.5f, 0.0f, 0f, 0f, // front

    0, 0.5f, 0.5f, 0.5f, 0.5f, 0.0f, 0f, 0f, // left

    x2, 1, 1, 1, 1, x2, z2, x2, // top

    x2, 1, 0, 1, 0, x2, z2, x2, // bottom

    };



    public Map(String heightMapLoc){



    FloatBuffer buffer = box.getTextureCoords().get(0).coords;

    buffer.rewind();

    buffer.put(texCoordinates);



    URL heightmapImg = Map.class.getClassLoader().getResource(heightMapLoc);

    heightMap = new ImageBasedHeightMap(new ImageIcon(heightmapImg).getImage());



    //createBoxes();

    loadMapData();

    buildMap();

    }

    public void buildMap(){



    for (int x = 0; x < 8; x++) {

    for (int y = 0; y < 8; y++) {



    int[][][] tempChunk = new int[16][16][16];

    for(int k = 0;k<16;k++){

    for(int l = 0;l<16;l++){

    for(int z = 0;z<16; z++) {

    tempChunk[k][l][z]=mapData[((x
    16)+k)][((y16)+l)][z];

    }

    }

    }

    TriMesh mesh = createChunk(tempChunk);

    mesh.setLocalTranslation(x
    16,0,y*16);

    attachChild(mesh);

    }

    }



    }

    private void loadMapData(){



    for (int x = 0; x < 128; x++) {

    for (int y = 0; y < 128; y++) {

    for(int z = 0;z<(heightMap.getTrueHeightAtPoint(x, y)*0.3f)-9; z++) {

    mapData[x][y][z]=1;

    }

    }

    }



    }

    //

    private TriMesh createChunk(int[][][] chunkData){



    GeometryBatchCreator batchCreator = new GeometryBatchCreator();

    System.out.println(“chunk created”);

    //Adds instance data together for all the boxes in the chunk

    for (int x = 0; x < 16; x++) {

    for (int y = 0; y < 16; y++) {

    for(int z = 0;z<16; z++) {

    // Box instance attributes

    if(chunkData[x][y][z]==1){

    GeometryBatchInstanceAttributes attributes =

    new GeometryBatchInstanceAttributes(

    new Vector3f(x,z,y),

    // Translation

    new Vector3f(1f,1f,1f),

    // Scale

    box.getLocalRotation(),

    // Rotation

    new ColorRGBA(1,1,1,0));

    // Color



    // Box instance (batch and attributes)

    GeometryBatchInstance instance =

    new GeometryBatchInstance(box, attributes);

    geomBatchInstances.add(instance);



    // Add the instance

    batchCreator.addInstance(instance);

    }

    }

    }

    }



    // Create a TriMesh

    TriMesh mesh = new TriMesh();

    mesh.setModelBound(new BoundingBox());



    // Create the batch’s buffers

    System.out.println(batchCreator.getNumIndices());



    mesh.setIndexBuffer(BufferUtils.createIntBuffer(

    batchCreator.getNumIndices()));

    mesh.setVertexBuffer(BufferUtils.createVector3Buffer(

    batchCreator.getNumVertices()));

    mesh.setNormalBuffer(BufferUtils.createVector3Buffer(

    batchCreator.getNumVertices()));

    mesh.setTextureCoords(new TexCoords(BufferUtils.createVector2Buffer(

    batchCreator.getNumVertices())), 0);

    mesh.setColorBuffer(BufferUtils.createFloatBuffer(

    batchCreator.getNumVertices() * 4));



    // Commit the instances to the mesh batch

    batchCreator.commit(mesh);



    batchCreators.add(batchCreator);



    // Add a texture

    TextureState ts = DisplaySystem.getDisplaySystem().getRenderer().createTextureState();

    TextureManager.COMPRESS_BY_DEFAULT = false;

    Texture t0 = TextureManager.loadTexture(

    Map.class.getClassLoader().getResource(

    “res/grass.png”),

    Texture.MinificationFilter.NearestNeighborLinearMipMap,

    Texture.MagnificationFilter.NearestNeighbor, 2.0f, false);

    t0.setWrap(Texture.WrapMode.Clamp);

    ts.setTexture(t0, 0);



    // Material

    MaterialState ms = DisplaySystem.getDisplaySystem().getRenderer().createMaterialState();

    ms.setColorMaterial(MaterialState.ColorMaterial.AmbientAndDiffuse);



    // Render states

    mesh.setRenderState(ms);

    mesh.setRenderState(ts);

    mesh.updateRenderState();



    // Return the mesh

    mesh.updateModelBound();

    //mesh.lock();

    return mesh;

    }

    /public boolean isTileAt(int x3, int y3, int z3){

    for(TriMesh t: meshes){

    Vector3f v = t.getWorldTranslation();

    if((int)v.x == x3 && (int)v.y==y3 && (int)v.z == z3){

    return true;

    }

    }

    return false;

    }
    /

    }

    [/java]

I am at 23fps for 285152 boxes on screen (looking top down), on a Radeon 4800 using JME3 and a Lighting shader.

I found that for my combo the optimum batch size consists of 140 boxes.



Being ‘in the landscape’ looking around, the generic fps is around 120.



Strange thing is, that CPU load is always at or slightly over 25% (1 full core). I’m sure that’s the current bottleneck when using a sufficiently sized batch. I wonder if multiple cores can be used.



Hope that helps getting some perspective.

Don’t load the same texture (TextureState ts, MaterialState ms) several times, but outside createChunk()

I assume an instance of the same texture is loaded per each mesh, so it can explain slow rendering. (and a huge memory allocation !)



Damien