Optimization reference

This page is intended as a reference collection of optimization tricks that can be used to speed up JME3 applications.

Maintain low Geometry count

The more Geometry objects are added to the scene, the harder it gets to handle them in a speedy fashion. The reason for this is that a render command must be done for every object, potentially creating a bottleneck between the CPU and the graphics card.

Possible optimization techniques

  • Use GeometryBatchFactory.optimize(node) to merge the meshes of the geometries contained in the given node into fewer batches, each based on common Materials used.
    You can optimize nodes using the SceneComposer in the SDK as well: Right-click a node and select “Optimize Geometry”.

Side-effects

  • Using GeometryBatchFactory merges individual Geometries into a single mesh. Thereby it becomes hard to apply specific Materials or to remove a single Geometry. Therefore it should be used for static Geometry only that does not require frequent changes or individual materials/texturing.

  • Using a Texture Atlas provides limited individual texturing of batched geometries.

  • Using the still experimental BatchNode allows batching Geometry while keeping the single Geometry objects movable separately (similar to animation, the buffer gets updated per Geometry position).

Avoid creating new objects

Different Java implementations use different garbage collection algorithms, so depending on the platforms you target, different advice applies.

The major variants are Oracle’s JRE, old (pre-Gingerbread) Androids, and newer (Gingerbread or later) Androids.

Oracle’s JRE is a copying collector. This means that it does not need to do any work for objects that have become unreachable, it just keeps copying live objects to new memory areas and recycles the now-unused area as a whole. Older objects are copied less often, so the garbage collection overhead is roughly proportional to the rate at which your code creates new objects that survive for, say, more than a minute.

Gingerbread and newer Androids use a garbage collector that does some optimization tricks with local variables, but you should avoid creating and forgetting lots of objects in the scene graph.

Older Androids use a very naive garbage collector that needs to do real work for every object, both during creation and during collection. Creating local variables can build up a heap of work, particularly if the function is called often.

To avoid creating a temporary object, use local methods to overwrite the contents of an existing object instead of creating a new temporary object for the result.

E.g. when you use math operations like vectorA.mult(vectorB);, they create new objects for the result.

Check your math operations for opportunities to use the local version of the math operations, e.g. vectorA.multLocal(vectorB). Local methods store the result in vectorA and do not create a new object.

Avoid large objects in physics

To offload much computation to the less CPU intense physics broadphase collision check, avoid having large meshes that cover e.g. the whole map of your level. Instead, separate the collision shapes into multiple smaller chunks. Obviously, don’t exaggerate the chunking, because having excessive amounts of physics objects similarly cause performance problems.

Check the Statistics

SimpleApplication displays a HUD with statistics. Use app.setDisplayStatView(true); to activate it, and false to deactivate it. The StatsView counts Objects,Uniforms,Triangles,Vertices are in the scene, and it counts how many FrameBuffers, Textures, or Shaders:

  • … were switched in the last frame (S)

  • … were used during the last frame (F)

  • … exist in OpenGL memory (M)

For example, Textures (M) tells you how many textures are currently in OpenGL memory.

Generally jME3 is well optimized and optimizes these things correctly. Read statsview to learn the details about how to interpret the statistics, how to tell whether your values are off, or whether they point out a problem.