Just sharing what I did in case anyone else had the same question about thier app or anyone has a better way.
I’m working on an app for Android and I was troubled because I didn’t understand where my framerate was going. I wanted to find out if the code I implemented in AppStates, Bullet physics processing, or the graphics I’m displaying was having the biggest impact.
Below are a couple patch files I made to log the amount of time spent in each of these areas. It includes time logging for the amount of time spent inside each of the method calls inside each AppState (update routine, render routine, and postRender routine), as well as the amount of time spent inside each of the major rendering processes for each active ViewPort (Processor PreFrame, Scene Rendering, Processor PostQueue, flushQueue, Processor PostFrame, TranslucentQueue, clearQueue).
It was actually very helpful to allow me to understand that Bullet is taking a lot more time when I have Mesh collision shapes vs a compound collision shape with multiple native shapes inside it. I didn’t know if the majority of the time was due to having multiple ViewPorts displayed or if it was due to Native Bullet taking a long time. As it turned out, the multiple ViewPorts have little effect, but the BulletAppState Render method was MUCH higher when using Mesh collision shapes.
As I said, I was just in a sharing mood today.
I display the data in simpleUpdate once every 5 sec (so not to bog down Android :)).
The output is in milliSeconds and looks something like this (I have a few AppStates and 4 ViewPorts) running on Android.
[java]
tpf: 0.047, Frames Per Second: 21.487, milliSeconds Per Frame: 46.539
AppState: com.jme3.app.ResetStatsState, Last Update Time: 0, Last Render Time: 0.025, Last PostRender Time: 0
AppState: com.jme3.bullet.BulletAppState, Last Update Time: 1.679, Last Render Time: 19.595, Last PostRender Time: 0
AppState: com.jme3.app.FlyCamAppState, Last Update Time: 0, Last Render Time: 0, Last PostRender Time: 0
AppState: com.interstatewebgroup.cubiclelife.hud.screens.ScreenManager, Last Update Time: 0.123, Last Render Time: 0, Last PostRender Time: 0
AppState: com.jme3.app.StatsAppState, Last Update Time: 0.025, Last Render Time: 0, Last PostRender Time: 0
AppState: com.interstatewebgroup.cubiclelife.hud.TwoSingleAxisJoySticks, Last Update Time: 0.025, Last Render Time: 0, Last PostRender Time: 0
AppState: com.interstatewebgroup.cubiclelife.world.World, Last Update Time: 0.033, Last Render Time: 0, Last PostRender Time: 0
AppState: com.jme3.app.DebugKeysAppState, Last Update Time: 0.033, Last Render Time: 0.033, Last PostRender Time: 0
ViewPort: NavStick Viewport, procPreFrameTime: 0.033, renderSceneTime: 0.123, procPostQueueTime: 0, flushQueueTime: 0.459, procPostFrameTime: 0, renderTranslucentQueueTime: 0, clearQueueTime: 0
ViewPort: Gui Default, procPreFrameTime: 0.033, renderSceneTime: 0.246, procPostQueueTime: 0.057, flushQueueTime: 4.309, procPostFrameTime: 0, renderTranslucentQueueTime: 0.025, clearQueueTime: 0
ViewPort: Default, procPreFrameTime: 0.033, renderSceneTime: 0.762, procPostQueueTime: 0, flushQueueTime: 13.853, procPostFrameTime: 0, renderTranslucentQueueTime: 0.033, clearQueueTime: 0
ViewPort: NavStick Viewport, procPreFrameTime: 0.025, renderSceneTime: 0.09, procPostQueueTime: 0, flushQueueTime: 0.336, procPostFrameTime: 0, renderTranslucentQueueTime: 0, clearQueueTime: 0
[/java]
simpleUpdate code to display the data:
[java]
timeElapsed += tpf;
numFrames++;
if (timeElapsed >= 5) {
logger.log(Level.INFO, “Elasped Time (sec): {0}, Number of Frames: {1}, Frames Per Second: {2}, milliSeconds Per Frame: {3}”,
new Object[]{timeElapsed, numFrames, numFrames/timeElapsed, (timeElapsed1000) / numFrames});
logger.log(Level.INFO, “tpf: {0}, Frames Per Second: {1}, milliSeconds Per Frame: {2}”,
new Object[]{tpf, 1/tpf, (tpf1000)});
for (AppStateManager.StateTimes stateTimes: stateManager.getStatesTimes()) {
logger.log(Level.INFO,
"AppState: {0}, "
- "Last Update Time: {1}, "
- "Last Render Time: {2}, "
- "Last PostRender Time: {3}",
new Object[]{
stateTimes.getStateClass(),
stateTimes.getUpdateTime()*1000f,
stateTimes.getRenderTime()*1000f,
stateTimes.getPostRenderTime()*1000f
});
}
for (RenderManager.ViewPortRenderTimes renderTimes: renderManager.getViewPortRenderTimes()) {
logger.log(Level.INFO,
"ViewPort: {0}, "
- "procPreFrameTime: {1}, "
- "renderSceneTime: {2}, "
- "procPostQueueTime: {3}, "
- "flushQueueTime: {4}, "
- "procPostFrameTime: {5}, "
- "renderTranslucentQueueTime: {6}, "
- "clearQueueTime: {7}",
new Object[]{
renderTimes.getViewPortName(),
renderTimes.getProcPreFrameTime()*1000f,
renderTimes.getRenderSceneTime()*1000f,
renderTimes.getProcPostQueueTime()*1000f,
renderTimes.getFlushQueueTime()*1000f,
renderTimes.getProcPostFrameTime()*1000f,
renderTimes.getRenderTranslucentQueueTime()*1000f,
renderTimes.getClearQueueTime()*1000f
});
}
numFrames = 0;
timeElapsed = 0;
}
[/java]
AppStateManager patch:
[java]
This patch file was generated by NetBeans IDE
Following Index: paths are relative to: D:UserspotterecDocumentsjMonkeyProjectsjME3srccorecomjme3appstate
This patch can be applied using context Tools: Patch action on respective folder.
It uses platform neutral UTF-8 encoding and n newlines.
Above lines and this line are ignored by the patching process.
Index: AppStateManager.java
— AppStateManager.java Base (BASE)
+++ AppStateManager.java Locally Modified (Based On LOCAL)
@@ -34,9 +34,16 @@
import com.jme3.app.Application;
import com.jme3.renderer.RenderManager;
+import com.jme3.system.Timer;
import com.jme3.util.SafeArrayList;
import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
/**
- The <code>AppStateManager</code> holds a list of {@link AppState}s which
@@ -78,6 +85,67 @@
private final SafeArrayList<AppState> states = new SafeArrayList<AppState>(AppState.class);
/**
-
* Class to hold the last update, render, and postRender times for an AppState.<br />
-
*/<br />
- public class StateTimes {
-
String stateClass = "";<br />
-
float updateTime = 0f;<br />
-
float renderTime = 0f;<br />
-
float postRenderTime = 0f;<br />
+
-
public StateTimes(String stateClass) {<br />
-
this.stateClass = stateClass;<br />
-
}<br />
+
-
public String getStateClass() {<br />
-
return stateClass;<br />
-
}<br />
-
public float getUpdateTime() {<br />
-
return updateTime;<br />
-
}<br />
-
public float getRenderTime() {<br />
-
return renderTime;<br />
-
}<br />
-
public float getPostRenderTime() {<br />
-
return postRenderTime;<br />
-
}<br />
- }
+
- /**
-
* Internal Map for the last times for each active AppState.<br />
-
*/<br />
- private Map<AppState, StateTimes> stateTimes = new ConcurrentHashMap<AppState, StateTimes>();
+
- // logStateTimes enables the time logging for each AppState (timer must also be set using setTimer(Timer timer)
- private boolean logStateTimes = false;
- private Timer timer = null;
+
- /**
-
* Enables/Disables time logging for AppStates. timer must also be set<br />
-
* @param enable When True, time for each stage of the AppState is logged.<br />
-
*/<br />
- public void setStateTimeLogging(boolean enable) {
-
logStateTimes = enable;<br />
- }
+
- /**
-
* Sets the timer implementation to use to record times<br />
-
* @param timer Timer implentation to use<br />
-
*/<br />
- public void setTimer(Timer timer) {
-
this.timer = timer;<br />
- }
+
- /**
-
* Retrieve the last times for each AppState<br />
-
* @return Collection of times for each ViewPort<br />
-
*/<br />
- public Collection<StateTimes> getStatesTimes(){
-
return Collections.unmodifiableCollection(stateTimes.values());<br />
- }
+
+
- /**
- List holding the detached app states that are pending
- cleanup.
*/
@@ -145,6 +213,7 @@
if (states.contains(state)){
state.stateDetached(this);
states.remove(state);
-
stateTimes.remove(state);<br />
terminating.add(state);
return true;
} else if(initializing.contains(state)){
@@ -213,6 +282,9 @@
// did it after then it couldn’t.
List<AppState> transfer = Arrays.asList(array);
states.addAll(transfer);
-
for (AppState appState: transfer) {<br />
-
stateTimes.put(appState, new StateTimes(appState.getClass().getName()));<br />
-
}<br />
initializing.removeAll(transfer);
}
for (AppState state : array) {
@@ -251,11 +323,22 @@
// Update enabled states
AppState[] array = getStates();
for (AppState state : array){
-
boolean logTimes = logStateTimes && (timer != null) && (stateTimes.get(state) != null);<br />
-
float startTime = 0f;<br />
+
-
if (logTimes && (stateTimes.get(state) != null)) {<br />
-
startTime = timer.getTime();<br />
-
}<br />
+
if (state.isEnabled()) {
state.update(tpf);
}
+
-
if (logTimes && (stateTimes.get(state) != null)) {<br />
-
stateTimes.get(state).updateTime = (timer.getTime() - startTime) / (float)timer.getResolution();<br />
}
}
- }
/**
- Calls render for all attached and initialized states, do not call directly.
@@ -264,11 +347,22 @@
public void render(RenderManager rm){
AppState[] array = getStates();
for (AppState state : array){
-
boolean logTimes = logStateTimes && (timer != null) && (stateTimes.get(state) != null);<br />
-
float startTime = 0f;<br />
+
-
if (logTimes && (stateTimes.get(state) != null)) {<br />
-
startTime = timer.getTime();<br />
-
}<br />
+
if (state.isEnabled()) {
state.render(rm);
}
+
-
if (logTimes && (stateTimes.get(state) != null)) {<br />
-
stateTimes.get(state).renderTime = (timer.getTime() - startTime) / (float)timer.getResolution();<br />
}
}
- }
/**
- Calls render for all attached and initialized states, do not call directly.
@@ -276,11 +370,22 @@
public void postRender(){
AppState[] array = getStates();
for (AppState state : array){
-
boolean logTimes = logStateTimes && (timer != null) && (stateTimes.get(state) != null);<br />
-
float startTime = 0f;<br />
+
-
if (logTimes && (stateTimes.get(state) != null)) {<br />
-
startTime = timer.getTime();<br />
-
}<br />
+
if (state.isEnabled()) {
state.postRender();
}
+
-
if (logTimes && (stateTimes.get(state) != null)) {<br />
-
stateTimes.get(state).postRenderTime = (timer.getTime() - startTime) / (float)timer.getResolution();<br />
}
}
- }
/**
- Calls cleanup on attached states, do not call directly.
[/java]
RenderManager patch:
[java]
This patch file was generated by NetBeans IDE
Following Index: paths are relative to: D:UserspotterecDocumentsjMonkeyProjectsjME3srccorecomjme3renderer
This patch can be applied using context Tools: Patch action on respective folder.
It uses platform neutral UTF-8 encoding and n newlines.
Above lines and this line are ignored by the patching process.
Index: RenderManager.java
— RenderManager.java Base (BASE)
+++ RenderManager.java Locally Modified (Based On LOCAL)
@@ -31,6 +31,7 @@
*/
package com.jme3.renderer;
+import com.jme3.app.state.AppStateManager.StateTimes;
import com.jme3.material.Material;
import com.jme3.material.MaterialDef;
import com.jme3.material.RenderState;
@@ -49,8 +50,11 @@
import com.jme3.system.Timer;
import com.jme3.util.TempVars;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
/
@@ -93,6 +97,74 @@
private boolean handleTranlucentBucket = true;
/
-
* Class to hold the last times for the Rendering process for a given ViewPort.<br />
-
*/<br />
- public class ViewPortRenderTimes {
-
String viewPortName = "";<br />
-
float procPreFrameTime = 0f;<br />
-
float renderSceneTime = 0f;<br />
-
float procPostQueueTime = 0f;<br />
-
float flushQueueTime = 0f;<br />
-
float procPostFrameTime = 0f;<br />
-
float renderTranslucentQueueTime = 0f;<br />
-
float clearQueueTime = 0f;<br />
+
-
public ViewPortRenderTimes(String viewPortName) {<br />
-
this.viewPortName = viewPortName;<br />
-
}<br />
+
-
public String getViewPortName() {<br />
-
return viewPortName;<br />
-
}<br />
-
public float getProcPreFrameTime() {<br />
-
return procPreFrameTime;<br />
-
}<br />
-
public float getRenderSceneTime() {<br />
-
return renderSceneTime;<br />
-
}<br />
-
public float getProcPostQueueTime() {<br />
-
return procPostQueueTime;<br />
-
}<br />
-
public float getFlushQueueTime() {<br />
-
return flushQueueTime;<br />
-
}<br />
-
public float getProcPostFrameTime() {<br />
-
return procPostFrameTime;<br />
-
}<br />
-
public float getRenderTranslucentQueueTime() {<br />
-
return renderTranslucentQueueTime;<br />
-
}<br />
-
public float getClearQueueTime() {<br />
-
return clearQueueTime;<br />
-
}<br />
- }
+
- /**
-
* Internal Map for the last times for each active ViewPort.<br />
-
*/<br />
- private Map<ViewPort, ViewPortRenderTimes> viewPortTimes = new ConcurrentHashMap<ViewPort, ViewPortRenderTimes>();
+
- // logViewPortTimes enables the time logging for each ViewPort (timer must also be set, but should be by default
- private boolean logViewPortTimes = false;
+
- /**
-
* Enables/Disables time logging for ViewPorts. timer must also be set, but should<br />
-
* be by default<br />
-
* @param enable When True, time for each stage of the rendering process is logged.<br />
-
*/<br />
- public void setViewPortTimeLogging(boolean enable) {
-
logViewPortTimes = enable;<br />
- }
+
- /**
-
* Retrieve the last scan times for each ViewPort<br />
-
* @return Collection of times for each ViewPort<br />
-
*/<br />
- public Collection<ViewPortRenderTimes> getViewPortRenderTimes(){
-
return Collections.unmodifiableCollection(viewPortTimes.values());<br />
- }
+
- /**
- Create a high-level rendering interface over the
- low-level rendering interface.
-
@param renderer
@@ -128,6 +200,7 @@
-
@see #createPreView(java.lang.String, com.jme3.renderer.Camera)
*/
public boolean removePreView(ViewPort view) {
-
viewPortTimes.remove(view); // add ViewPort to time logging<br />
return preViewPorts.remove(view);
}
@@ -160,6 +233,7 @@
for (int i = 0; i < viewPorts.size(); i++) {
if (viewPorts.get(i).getName().equals(viewName)) {
viewPorts.remove(i);
-
viewPortTimes.remove(viewPorts.get(i)); // remove ViewPort from time logging<br />
return true;
}
}
@@ -175,6 +249,7 @@
-
@see #createMainView(java.lang.String, com.jme3.renderer.Camera)
*/
public boolean removeMainView(ViewPort view) {
-
viewPortTimes.remove(view); // remove ViewPort from time logging<br />
return viewPorts.remove(view);
}
@@ -207,6 +282,7 @@
for (int i = 0; i < postViewPorts.size(); i++) {
if (postViewPorts.get(i).getName().equals(viewName)) {
postViewPorts.remove(i);
-
viewPortTimes.remove(postViewPorts.get(i)); // remove ViewPort from time logging<br />
return true;
}
@@ -223,6 +299,7 @@
* @see #createPostView(java.lang.String, com.jme3.renderer.Camera)
*/
public boolean removePostView(ViewPort view) {
+ viewPortTimes.remove(view); // remove ViewPort from time logging
return postViewPorts.remove(view);
}
@@ -261,6 +338,7 @@
public ViewPort createPreView(String viewName, Camera cam) {
ViewPort vp = new ViewPort(viewName, cam);
preViewPorts.add(vp);
+ viewPortTimes.put(vp, new ViewPortRenderTimes(viewName)); // add ViewPort to time logging
return vp;
}
@@ -273,6 +351,7 @@
public ViewPort createMainView(String viewName, Camera cam) {
ViewPort vp = new ViewPort(viewName, cam);
viewPorts.add(vp);
+ viewPortTimes.put(vp, new ViewPortRenderTimes(viewName)); // add ViewPort to time logging
return vp;
}
@@ -284,6 +363,7 @@
public ViewPort createPostView(String viewName, Camera cam) {
ViewPort vp = new ViewPort(viewName, cam);
postViewPorts.add(vp);
+ viewPortTimes.put(vp, new ViewPortRenderTimes(viewName)); // add ViewPort to time logging
return vp;
}
@@ -1083,7 +1163,24 @@
* @param tpf
*/
public void renderViewPort(ViewPort vp, float tpf) {
+ boolean logTimes = logViewPortTimes && (timer != null) && (viewPortTimes.get(vp) != null);
+ float startTime = 0f;
+
+ if (logTimes && (viewPortTimes.get(vp) != null)) {
+ startTime = timer.getTime();
+ }
+
if (!vp.isEnabled()) {
+ if (logTimes && (viewPortTimes.get(vp) != null)) {
+ ViewPortRenderTimes times = viewPortTimes.get(vp);
+ times.clearQueueTime = 0f;
+ times.flushQueueTime = 0f;
+ times.procPostFrameTime = 0f;
+ times.procPostQueueTime = 0f;
+ times.procPreFrameTime = 0f;
+ times.renderSceneTime = 0f;
+ times.renderTranslucentQueueTime = 0f;
+ }
return;
}
List<SceneProcessor> processors = vp.getProcessors();
@@ -1100,6 +1197,11 @@
}
}
+ if (logTimes && (viewPortTimes.get(vp) != null)) {
+ viewPortTimes.get(vp).procPreFrameTime = (timer.getTime() - startTime) / (float)timer.getResolution();
+ startTime = timer.getTime();
+ }
+
renderer.setFrameBuffer(vp.getOutputFrameBuffer());
setCamera(vp.getCamera(), false);
if (vp.isClearDepth() || vp.isClearColor() || vp.isClearStencil()) {
@@ -1116,25 +1218,57 @@
renderScene(scenes.get(i), vp);
}
+ if (logTimes && (viewPortTimes.get(vp) != null)) {
+ viewPortTimes.get(vp).renderSceneTime = (timer.getTime() - startTime) / (float)timer.getResolution();
+ startTime = timer.getTime();
+ }
+
if (processors != null) {
for (SceneProcessor proc : processors) {
proc.postQueue(vp.getQueue());
}
}
+ if (logTimes && (viewPortTimes.get(vp) != null)) {
+ viewPortTimes.get(vp).procPostQueueTime = (timer.getTime() - startTime) / (float)timer.getResolution();
+ startTime = timer.getTime();
+ }
+
flushQueue(vp);
+ if (logTimes && (viewPortTimes.get(vp) != null)) {
+ viewPortTimes.get(vp).flushQueueTime = (timer.getTime() - startTime) / (float)timer.getResolution();
+ startTime = timer.getTime();
+ }
+
if (processors != null) {
for (SceneProcessor proc : processors) {
proc.postFrame(vp.getOutputFrameBuffer());
}
}
+
+ if (logTimes && (viewPortTimes.get(vp) != null)) {
+ viewPortTimes.get(vp).procPostFrameTime = (timer.getTime() - startTime) / (float)timer.getResolution();
+ startTime = timer.getTime();
+ }
+
//renders the translucent objects queue after processors have been rendered
renderTranslucentQueue(vp);
+
+ if (logTimes && (viewPortTimes.get(vp) != null)) {
+ viewPortTimes.get(vp).renderTranslucentQueueTime = (timer.getTime() - startTime) / (float)timer.getResolution();
+ startTime = timer.getTime();
+ }
+
// clear any remaining spatials that were not rendered.
clearQueue(vp);
+
+ if (logTimes && (viewPortTimes.get(vp) != null)) {
+ viewPortTimes.get(vp).clearQueueTime = (timer.getTime() - startTime) / (float)timer.getResolution();
}
+ }
+
/**
* Called by the application to render any ViewPorts
* added to this RenderManager.
[/java]