Help with a simple scene – Getting low framerate (jme3)

Hello everybody,



first of all, thanks for a wonderful community and an awesome engine. Since I am no master at 3d programming (yet :slight_smile: ) I quickly ran into some frame rate issues.



I have this “simple” scene:





I understand that having lots of objects is not good for performance, but how can I optimize the scene in a good way? There’s not much there yet…



The scene is currently created at init() and made up of some boxes, a sphere, cylinders and a plane (and of course the 8 players). Not too much yet, but my plan is to add more stuff to it later on though which is why I am starting to get a bit nervous.



Some additional info that might be of interest:

  • I have no need for collision detection.
  • The camera will move around a bit (it already does and I get better frame rates when closer to the “grass” and not as many objects are in view)
  • Shadows lowered the frame rate a bit, but needs to be there later on (because shadows make things look cool).
  • The frame rate remains the same if I remove all logic from the update() method (performs movement etc). So it is not related to that.
  • The “0 - 0” at the top of the screen is powered by Nifty GUI



    Can I group the scene into one big chunk? Would that make things easier for rendering (I have no idea how to accomplish this yet though, which is why I have not tried it yet)?



    Any tips, pointers? Let the hanging begin…



    Oh, and the specs from the computer I grabbed this screenshot at:

    Processor: Intel Core™ i3 CPU - M330 @ 2.13 GHz / 2.13 GHz

    RAM: 4 GB

    System type: 64-bit operating system (Win 7)



    And I can report I got the exact same framerate on an older macbook (no idea which revision though. 4 years old is a guestimate if it helps).



    I get around 200 fps on my 2 year old Macbook pro at home (yes, 200).

What is your graphic card? Some laptops graphic card are just not good for 3D gaming.



Could you please post some code?

Both the Macbook (not the Pro) and the Win 7 one have built-in low grade graphics card, and I did not expect them to pull any beautiful scenes. But this scene felt pretty simple, but it seems I might be wrong.





Here’s how I create the scene (tried to comment as much as possible/necessary for easier reading):



[java] public static void main(String[] args) {

Main app = new Main();

app.start();

}



@Override

public void simpleInitApp() {

niftyDisplay = new NiftyJmeDisplay(assetManager, inputManager, audioRenderer, guiViewPort);

nifty = niftyDisplay.getNifty();

flyCam.setDragToRotate(true);

guiViewPort.addProcessor(niftyDisplay);



Properties prop = null;



try {

in = new JsonReader(new BufferedReader(new FileReader(“match.txt”))); //Reads a few parameters, like field length / height etc.

prop = in.read(Properties.class);

InitTeam initTeam1 = in.read(InitTeam.class);

InitTeam initTeam2 = in.read(InitTeam.class);

} catch (Exception ex) {

nifty.fromXml(“Interface/ErrorMessages.xml”, “start”);

currentGameState = GameState.Error;

return;

}



//Render the “0 - 0” thing at the top.

nifty.fromXml(“Interface/MatchGui.xml”, “start”);



//Kept the flyCam for debugging purposes

flyCam.setMoveSpeed(300f);



//Set some often used parameters. Scaler basically takes the value and multiplies by 2

fieldWidth = Scaler.scale(Integer.parseInt(prop.getProperty(“fieldLengthY”)));

fieldLength = Scaler.scale(Integer.parseInt(prop.getProperty(“fieldLengthX”)));

int goalSize = Scaler.scale(Integer.parseInt(prop.getProperty(“goalSize”)));



//Setup the camera starting point… looking down on the middle of the field…

cam.setLocation(new Vector3f(fieldLength/2, fieldWidth/2, fieldLength));

cam.setFrustumPerspective(45f, (float)cam.getWidth() / cam.getHeight(), 1f, fieldLength*2);

cam.lookAt(new Vector3f(fieldLength/2, fieldWidth/2, 1f), Vector3f.UNIT_Z);



team1 = new HashMap<Integer, Spatial>();

team2 = new HashMap<Integer, Spatial>();



//Create the grassy field

Quad quad = new Quad(fieldLength, fieldWidth);

Geometry geom = new Geometry(“field”, quad);

geom.updateModelBound();

geom.setShadowMode(ShadowMode.Receive);

Material mat = new Material(assetManager, “Common/MatDefs/Misc/SimpleTextured.j3md”);

Texture tex_ml = assetManager.loadTexture(“Textures/grass.png”);

mat.setTexture(“m_ColorMap”, tex_ml);

geom.setMaterial(mat);



rootNode.attachChild(geom);



//Player height. used mostly for rendering

playerHeight = Scaler.scale(Integer.parseInt(prop.getProperty(“playerSize”)) * 3);



//Create the first teams players

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

Spatial ninja = assetManager.loadModel(“Models/Ninja/Ninja.mesh.xml”);

ninja.rotate(-1.5f, -3.0f, -1.5f);

//ninja.setShadowMode(ShadowMode.CastAndReceive);



team2Control = ninja.getControl(AnimControl.class);

team2Channel = team2Control.createChannel();

team2Channel.setAnim(“Walk”);

team2Channel.setSpeed(1.5f);



team1.put(x, ninja);

rootNode.attachChild(ninja);

}



//Create the second teams players

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

Spatial ninja = assetManager.loadModel(“Models/Oto/Oto.mesh.xml”);

ninja.scale(15f, 15f, 15f);

ninja.rotate(-1.5f, -3.0f, -1.5f);

//ninja.setShadowMode(ShadowMode.CastAndReceive);



team2Control = ninja.getControl(AnimControl.class);

team2Channel = team2Control.createChannel();

team2Channel.setAnim(“Walk”);

team2Channel.setSpeed(1.8f);





team2.put(x, ninja);

rootNode.attachChild(ninja);

}



//Create the ball. Ball is later referenced in the update method

ballSize = Scaler.scale(Integer.parseInt(prop.getProperty(“ballSize”)));

Sphere ballModel = new Sphere(10, 10, ballSize);

ball = new Geometry(“Ball”, ballModel);

Material ballMat = new Material(assetManager, “Common/MatDefs/Misc/SolidColor.j3md”);

//ballMat.setColor(“m_GlowColor”, ColorRGBA.Yellow);

ballMat.setColor(“m_Color”, ColorRGBA.Yellow);

ball.setMaterial(ballMat);

//ball.setShadowMode(ShadowMode.Cast);

rootNode.attachChild(ball);





// Create the walls

Box wallLeft1 = new Box(Vector3f.ZERO, 1, (fieldWidth - goalSize)/4, Scaler.scale(50));

Box wallLeft2 = new Box(Vector3f.ZERO, 1, (fieldWidth - goalSize)/4, Scaler.scale(50));



Box wallRight1 = new Box(Vector3f.ZERO, 1, (fieldWidth - goalSize)/4, Scaler.scale(50));

Box wallRight2 = new Box(Vector3f.ZERO, 1, (fieldWidth - goalSize)/4, Scaler.scale(50));



Box wallTop = new Box(Vector3f.ZERO, fieldLength/2 + 2, 1, Scaler.scale(50));

Box wallBottom = new Box(Vector3f.ZERO, fieldLength/2 + 2, 1, Scaler.scale(50));

wallTop.scaleTextureCoordinates(new Vector2f(20.0f, 20.0f));

wallBottom.scaleTextureCoordinates(new Vector2f(20.0f, 20.0f));



Geometry geomLeft1 = new Geometry(“wallLeft1”, wallLeft1);

Geometry geomLeft2 = new Geometry(“wallLeft2”, wallLeft1);



Geometry geomRight1 = new Geometry(“wallRight1”, wallRight1);

Geometry geomRight2 = new Geometry(“wallRight2”, wallRight2);



Geometry geomTop = new Geometry(“wallTop”, wallTop);

Geometry geomBottom = new Geometry(“wallBottom”, wallBottom);



//geomLeft1.setShadowMode(ShadowMode.CastAndReceive);

//geomLeft2.setShadowMode(ShadowMode.CastAndReceive);

//geomRight1.setShadowMode(ShadowMode.CastAndReceive);

//geomRight2.setShadowMode(ShadowMode.CastAndReceive);

//geomTop.setShadowMode(ShadowMode.CastAndReceive);

//geomBottom.setShadowMode(ShadowMode.CastAndReceive);



geomLeft1.updateModelBound();

geomLeft2.updateModelBound();

geomRight1.updateModelBound();

geomRight2.updateModelBound();

geomTop.updateModelBound();

geomBottom.updateModelBound();



geomLeft1.setLocalTranslation(-1, (fieldWidth - goalSize)/4, Scaler.scale(50));

geomLeft2.setLocalTranslation(-1, fieldWidth - (fieldWidth - goalSize)/4, Scaler.scale(50));



geomRight1.setLocalTranslation(fieldLength+1, (fieldWidth - goalSize)/4, Scaler.scale(50));

geomRight2.setLocalTranslation(fieldLength+1, fieldWidth - (fieldWidth - goalSize)/4, Scaler.scale(50));



geomTop.setLocalTranslation(fieldLength/2, fieldWidth + 1, Scaler.scale(50));

geomBottom.setLocalTranslation(fieldLength/2, -1, Scaler.scale(50));



Material wallMat = new Material(assetManager, “Common/MatDefs/Misc/SolidColor.j3md”);

wallMat.setColor(“m_Color”, ColorRGBA.Gray);



geomLeft1.setMaterial(wallMat);

geomLeft2.setMaterial(wallMat);



geomRight1.setMaterial(wallMat);

geomRight2.setMaterial(wallMat);



geomTop.setMaterial(wallMat);

geomBottom.setMaterial(wallMat);



rootNode.attachChild(geomLeft1);

rootNode.attachChild(geomLeft2);

rootNode.attachChild(geomRight1);

rootNode.attachChild(geomRight2);

rootNode.attachChild(geomTop);

rootNode.attachChild(geomBottom);



//Create the goal posts. Two on each side of the field

Cylinder goalPostLeft1 = new Cylinder(20, 20, Scaler.scale(Integer.parseInt(prop.getProperty(“postSize”))), 100, true);

Cylinder goalPostLeft2 = new Cylinder(20, 20, Scaler.scale(Integer.parseInt(prop.getProperty(“postSize”))), 100, true);

Cylinder goalPostRight1 = new Cylinder(20, 20, Scaler.scale(Integer.parseInt(prop.getProperty(“postSize”))), 100, true);

Cylinder goalPostRight2 = new Cylinder(20, 20, Scaler.scale(Integer.parseInt(prop.getProperty(“postSize”))), 100, true);



Geometry goalpostLeft1 = new Geometry(“postLeft1”, goalPostLeft1);

Geometry goalpostLeft2 = new Geometry(“postLeft2”, goalPostLeft2);

Geometry goalpostRight1 = new Geometry(“postRight1”, goalPostRight1);

Geometry goalpostRight2 = new Geometry(“postRight2”, goalPostRight2);



Material postMat = new Material(assetManager, “Common/MatDefs/Misc/SolidColor.j3md”);

postMat.setColor(“m_Color”, ColorRGBA.White);

goalpostLeft1.setMaterial(postMat);

goalpostLeft2.setMaterial(postMat);

goalpostRight1.setMaterial(postMat);

goalpostRight2.setMaterial(postMat);



//goalpostLeft1.setShadowMode(ShadowMode.Cast);

//goalpostLeft2.setShadowMode(ShadowMode.Cast);

//goalpostRight1.setShadowMode(ShadowMode.Cast);

//goalpostRight2.setShadowMode(ShadowMode.Cast);



goalpostLeft1.setLocalTranslation(0, fieldWidth/2 - goalSize /2, 50);

goalpostLeft2.setLocalTranslation(0, fieldWidth/2 + goalSize /2, 50);

goalpostRight1.setLocalTranslation(fieldLength, fieldWidth/2 - goalSize /2, 50);

goalpostRight2.setLocalTranslation(fieldLength, fieldWidth/2 + goalSize /2, 50);



rootNode.attachChild(goalpostLeft1);

rootNode.attachChild(goalpostLeft2);

rootNode.attachChild(goalpostRight1);

rootNode.attachChild(goalpostRight2);



//A counter that I use in the update method to decide when to switch states

updateCounter = 0;



//Let there be light

DirectionalLight sun = new DirectionalLight();

sun.setDirection(new Vector3f(1, 1, -1.0f));

rootNode.addLight(sun);

DirectionalLight sun2 = new DirectionalLight();

sun2.setDirection(new Vector3f(-10f, -5f, -10.0f));

rootNode.addLight(sun2);



//Use the basic shadow implementation… not used for anything now though since all shadow fluff is commented out of the process

rootNode.setShadowMode(ShadowMode.Off);

bsr = new BasicShadowRenderer(assetManager, 256);

//psr = new PssmShadowRenderer(assetManager, 512, 3);

//psr.setDirection(sun.getDirection());

//psr.setShadowIntensity(0.3f);



//viewPort.addProcessor(psr);

bsr.setDirection(new Vector3f(-1,-1,-1).normalizeLocal());

viewPort.addProcessor(bsr);



currentGameState = GameState.Playing;

}[/java]



Since removing all the code in the update() method did not affect the frame rate I won’t post it for ease of reading



Removing nifty does nothing to the frame rate (seem to be a pretty nice product as well).



See anything out of order?



Add:

Actually removing everything but the ball, players and field does nothing so it might not have anything to do with the objects. :frowning:

monsharen said:
Both the Macbook (not the Pro) and the Win 7 one have built-in low grade graphics card, and I did not expect them to pull any beautiful scenes. But this scene felt pretty simple, but it seems I might be wrong.

Yeah but, rendering is really GPU bound, especially with JME3, that is based on shaders. Low grade cards will give low grade performances.
But maybe there is something else. I can't test your code, because there are some references to your own classes, and you didn't post variables declarations.

Could you please post runnable code?
I know that sounds crazy, but we are just mere humans, even in the JME team, and we can't test run a code just by reading it :D
Especially when it's 200 lines long :p

I'm kidding, heh don't take it the wrong way.
We are not really humans... :D

hehe :slight_smile:



This viewer got a few external dependencies needed to run so if you really feel like debugging this thing I could let you check out the entire project (svn). The rendering is not much more than was posted though, basically just the update method. It would surely grant you a spot in monkey heaven but I understand if you don’t have the time.



I will set up a guest account on the svn just in case. Will be back with details in a few.



add: I have sent you ze details

It runs at approximately 400 fps on my computer. But this is not really relevant because it’s a gamer desktop computer.



Try to reduce the number of spits to 1 for the PSSM renderer.

psr = new PssmShadowRenderer(assetManager, 512, 1);

Doing this i have 100fps increase with no noticeable changes on your scene.



PSSM is slow for now because it has some frustum culling issues, if i remove it i’m at 570 fps.

Beside this, maybe you can add LOD control to the ninja and Oto models.

They are seen from far and lod will reduce the number of triangles to display, which can be unnoticeable to the bare eye, but very noticeable on the frame rate.



see testHoverTank, or TestLodStress for hints on Lod.

1 Like

This is a 2D scene, it shouldn’t run at 13 fps, something else is wrong…



For starters, why are there 40,000~ triangles? There really should be about 100 at most.

LOD won’t help in this case since there are no models in the scene. Shadows dont really make sense either since this is a 2D scene.

Oh I am sorry, I am not sure I get you there. The eight players in the scene are the Ninja and the robot model so they are indeed in 3D (in the screenshot above I have disabled shadows and other fancy effects). The only element not in 3D is the plane (which of course still exist in the 3d space :slight_smile: )



The reason I don’t want to retort to 2D is the way I am planning on using the camera later on.



Maybe this is not what you meant?

Double post but want to bump the thread. :slight_smile:



I think I found the “villain”. If I use the ninja model instead of Oto (the robot mesh) for both teams my frame rate goes up to around 30 (which is acceptable). I tested with using the other models in the test-data as well and got various frame rates (-5 to +20 fps difference depending on model).



So, when I create my own models I guess I have to think about the LOD (I was unable to test the pregenerated LOD nehon suggested, but I think that might do the trick as well). This is only a problem on the computers without dedicated graphics cards and I am thinking of using low poly graphics anyway.



Case closed I guess :slight_smile:

Yeah sorry I didn’t notice you were actually using 3D models.

Anyway you’re right, the Oto model I believe has about 15,000 tris and that’s quite a lot for an animated character model, so some GPUs might not handle it well.

1 Like