app state. If the app state is attached then it's 'enabled' state can be toggled with the F6 key. It displays a continnuously updating bar graph of the application wide frame timings for 'update' and 'render'... where 'update' is all of the parts that aren't 'rendering', ie: running enqueued tasks, updating states, updating controls, etc..experimental
parent
a517130528
commit
aea5e4af31
@ -0,0 +1,197 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2014 jMonkeyEngine |
||||||
|
* All rights reserved. |
||||||
|
* |
||||||
|
* Redistribution and use in source and binary forms, with or without |
||||||
|
* modification, are permitted provided that the following conditions are |
||||||
|
* met: |
||||||
|
* |
||||||
|
* * Redistributions of source code must retain the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer. |
||||||
|
* |
||||||
|
* * Redistributions in binary form must reproduce the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer in the |
||||||
|
* documentation and/or other materials provided with the distribution. |
||||||
|
* |
||||||
|
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors |
||||||
|
* may be used to endorse or promote products derived from this software |
||||||
|
* without specific prior written permission. |
||||||
|
* |
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
||||||
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
||||||
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
*/ |
||||||
|
|
||||||
|
package com.jme3.app; |
||||||
|
|
||||||
|
import com.jme3.profile.AppProfiler; |
||||||
|
import com.jme3.profile.AppStep; |
||||||
|
import com.jme3.profile.VpStep; |
||||||
|
import com.jme3.renderer.ViewPort; |
||||||
|
import com.jme3.renderer.queue.RenderQueue.Bucket; |
||||||
|
import com.jme3.scene.Mesh; |
||||||
|
import com.jme3.scene.VertexBuffer.Type; |
||||||
|
import com.jme3.util.BufferUtils; |
||||||
|
import java.nio.FloatBuffer; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* An AppProfiler implementation that collects two |
||||||
|
* per-frame application-wide timings for update versus |
||||||
|
* render and uses it to create a bar chart style Mesh. |
||||||
|
* The number of frames displayed and the update interval |
||||||
|
* can be specified. The chart Mesh is in 'milliseconds' |
||||||
|
* and can be scaled up or down as required. |
||||||
|
* |
||||||
|
* <p>Each column of the chart represents a single frames |
||||||
|
* timing. Yellow represents the time it takes to |
||||||
|
* perform all non-rendering activities (running enqueued |
||||||
|
* tasks, stateManager.update, control.update(), etc) while |
||||||
|
* the cyan portion represents the rendering time.</p> |
||||||
|
* |
||||||
|
* <p>When the end of the chart is reached, the current |
||||||
|
* frame cycles back around to the beginning.</p> |
||||||
|
* |
||||||
|
* @author Paul Speed |
||||||
|
*/ |
||||||
|
public class BasicProfiler implements AppProfiler { |
||||||
|
|
||||||
|
private int size; |
||||||
|
private int frameIndex = 0; |
||||||
|
private long[] frames; |
||||||
|
private long startTime; |
||||||
|
private long renderTime; |
||||||
|
private long previousFrame; |
||||||
|
private long updateInterval = 1000000L; // once a millisecond
|
||||||
|
private long lastUpdate = 0; |
||||||
|
|
||||||
|
private Mesh mesh; |
||||||
|
|
||||||
|
public BasicProfiler() { |
||||||
|
this(1280); |
||||||
|
} |
||||||
|
|
||||||
|
public BasicProfiler( int size ) { |
||||||
|
setFrameCount(size); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the number of frames to display and track. By default |
||||||
|
* this is 1280. |
||||||
|
*/ |
||||||
|
public final void setFrameCount( int size ) { |
||||||
|
if( this.size == size ) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
this.size = size; |
||||||
|
this.frames = new long[size*2]; |
||||||
|
|
||||||
|
createMesh(); |
||||||
|
|
||||||
|
if( frameIndex >= size ) { |
||||||
|
frameIndex = 0; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public int getFrameCount() { |
||||||
|
return size; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the number of nanoseconds to wait before updating the |
||||||
|
* mesh. By default this is once a millisecond, ie: 1000000 nanoseconds. |
||||||
|
*/ |
||||||
|
public void setUpdateInterval( long nanos ) { |
||||||
|
this.updateInterval = nanos; |
||||||
|
} |
||||||
|
|
||||||
|
public long getUpdateInterval() { |
||||||
|
return updateInterval; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the mesh that contains the bar chart of tracked frame |
||||||
|
* timings. |
||||||
|
*/ |
||||||
|
public Mesh getMesh() { |
||||||
|
return mesh; |
||||||
|
} |
||||||
|
|
||||||
|
protected final void createMesh() { |
||||||
|
if( mesh == null ) { |
||||||
|
mesh = new Mesh(); |
||||||
|
mesh.setMode(Mesh.Mode.Lines); |
||||||
|
} |
||||||
|
|
||||||
|
mesh.setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(size * 4 * 3)); |
||||||
|
|
||||||
|
FloatBuffer cb = BufferUtils.createFloatBuffer(size * 4 * 4); |
||||||
|
for( int i = 0; i < size; i++ ) { |
||||||
|
// For each index we add 4 colors, one for each line
|
||||||
|
// endpoint for two layers.
|
||||||
|
cb.put(0.5f).put(0.5f).put(0).put(1); |
||||||
|
cb.put(1).put(1).put(0).put(1); |
||||||
|
cb.put(0).put(0.5f).put(0.5f).put(1); |
||||||
|
cb.put(0).put(1).put(1).put(1); |
||||||
|
} |
||||||
|
mesh.setBuffer(Type.Color, 4, cb); |
||||||
|
} |
||||||
|
|
||||||
|
protected void updateMesh() { |
||||||
|
FloatBuffer pb = (FloatBuffer)mesh.getBuffer(Type.Position).getData(); |
||||||
|
pb.rewind(); |
||||||
|
float scale = 1 / 1000000f; // scaled to ms as pixels
|
||||||
|
for( int i = 0; i < size; i++ ) { |
||||||
|
float t1 = frames[i * 2] * scale; |
||||||
|
float t2 = frames[i * 2 + 1] * scale; |
||||||
|
|
||||||
|
pb.put(i).put(0).put(0); |
||||||
|
pb.put(i).put(t1).put(0); |
||||||
|
pb.put(i).put(t1).put(0); |
||||||
|
pb.put(i).put(t2).put(0); |
||||||
|
} |
||||||
|
mesh.setBuffer(Type.Position, 3, pb); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void appStep( AppStep step ) { |
||||||
|
|
||||||
|
switch(step) { |
||||||
|
case BeginFrame: |
||||||
|
startTime = System.nanoTime(); |
||||||
|
break; |
||||||
|
case RenderFrame: |
||||||
|
renderTime = System.nanoTime(); |
||||||
|
frames[frameIndex * 2] = renderTime - startTime; |
||||||
|
break; |
||||||
|
case EndFrame: |
||||||
|
long time = System.nanoTime(); |
||||||
|
frames[frameIndex * 2 + 1] = time - renderTime; |
||||||
|
previousFrame = startTime; |
||||||
|
frameIndex++; |
||||||
|
if( frameIndex >= size ) { |
||||||
|
frameIndex = 0; |
||||||
|
} |
||||||
|
if( startTime - lastUpdate > updateInterval ) { |
||||||
|
updateMesh(); |
||||||
|
lastUpdate = startTime; |
||||||
|
} |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void vpStep( VpStep step, ViewPort vp, Bucket bucket ) { |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
@ -0,0 +1,240 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2014 jMonkeyEngine |
||||||
|
* All rights reserved. |
||||||
|
* |
||||||
|
* Redistribution and use in source and binary forms, with or without |
||||||
|
* modification, are permitted provided that the following conditions are |
||||||
|
* met: |
||||||
|
* |
||||||
|
* * Redistributions of source code must retain the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer. |
||||||
|
* |
||||||
|
* * Redistributions in binary form must reproduce the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer in the |
||||||
|
* documentation and/or other materials provided with the distribution. |
||||||
|
* |
||||||
|
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors |
||||||
|
* may be used to endorse or promote products derived from this software |
||||||
|
* without specific prior written permission. |
||||||
|
* |
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
||||||
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
||||||
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
*/ |
||||||
|
|
||||||
|
package com.jme3.app; |
||||||
|
|
||||||
|
import com.jme3.app.state.BaseAppState; |
||||||
|
import com.jme3.input.InputManager; |
||||||
|
import com.jme3.input.KeyInput; |
||||||
|
import com.jme3.input.controls.ActionListener; |
||||||
|
import com.jme3.input.controls.KeyTrigger; |
||||||
|
import com.jme3.material.Material; |
||||||
|
import com.jme3.material.RenderState.BlendMode; |
||||||
|
import com.jme3.scene.Geometry; |
||||||
|
import com.jme3.scene.Mesh; |
||||||
|
import com.jme3.scene.Node; |
||||||
|
import com.jme3.scene.VertexBuffer.Type; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Provides a basic profiling visualization that shows |
||||||
|
* per-frame application-wide timings for update and |
||||||
|
* rendering. |
||||||
|
* |
||||||
|
* @author Paul Speed |
||||||
|
*/ |
||||||
|
public class BasicProfilerState extends BaseAppState { |
||||||
|
|
||||||
|
public static final String INPUT_MAPPING_PROFILER_TOGGLE = "BasicProfilerState_Toggle"; |
||||||
|
|
||||||
|
private BasicProfiler profiler; |
||||||
|
private Geometry graph; |
||||||
|
private Geometry background; |
||||||
|
private float scale = 2; |
||||||
|
|
||||||
|
private ProfilerKeyListener keyListener = new ProfilerKeyListener(); |
||||||
|
|
||||||
|
public BasicProfilerState() { |
||||||
|
this(false); |
||||||
|
} |
||||||
|
|
||||||
|
public BasicProfilerState( boolean enabled ) { |
||||||
|
setEnabled(enabled); |
||||||
|
this.profiler = new BasicProfiler(); |
||||||
|
} |
||||||
|
|
||||||
|
public void toggleProfiler() { |
||||||
|
setEnabled(!isEnabled()); |
||||||
|
} |
||||||
|
|
||||||
|
public BasicProfiler getProfiler() { |
||||||
|
return profiler; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the vertical scale of the visualization where |
||||||
|
* each unit is a millisecond. Defaults to 2, ie: a |
||||||
|
* single millisecond stretches two pixels high. |
||||||
|
*/ |
||||||
|
public void setGraphScale( float scale ) { |
||||||
|
if( this.scale == scale ) { |
||||||
|
return; |
||||||
|
} |
||||||
|
this.scale = scale; |
||||||
|
if( graph != null ) { |
||||||
|
graph.setLocalScale(1, scale, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public float getGraphScale() { |
||||||
|
return scale; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the number frames displayed and tracked. |
||||||
|
*/ |
||||||
|
public void setFrameCount( int count ) { |
||||||
|
if( profiler.getFrameCount() == count ) { |
||||||
|
return; |
||||||
|
} |
||||||
|
profiler.setFrameCount(count); |
||||||
|
refreshBackground(); |
||||||
|
} |
||||||
|
|
||||||
|
public int getFrameCount() { |
||||||
|
return profiler.getFrameCount(); |
||||||
|
} |
||||||
|
|
||||||
|
protected void refreshBackground() { |
||||||
|
Mesh mesh = background.getMesh(); |
||||||
|
|
||||||
|
int size = profiler.getFrameCount(); |
||||||
|
float frameTime = 1000f / 60; |
||||||
|
mesh.setBuffer(Type.Position, 3, new float[] { |
||||||
|
|
||||||
|
// first quad
|
||||||
|
0, 0, 0, |
||||||
|
size, 0, 0, |
||||||
|
size, frameTime, 0, |
||||||
|
0, frameTime, 0, |
||||||
|
|
||||||
|
// second quad
|
||||||
|
0, frameTime, 0, |
||||||
|
size, frameTime, 0, |
||||||
|
size, frameTime * 2, 0, |
||||||
|
0, frameTime * 2, 0, |
||||||
|
|
||||||
|
// A lower dark border just to frame the
|
||||||
|
// 'update' stats against bright backgrounds
|
||||||
|
0, -2, 0, |
||||||
|
size, -2, 0, |
||||||
|
size, 0, 0, |
||||||
|
0, 0, 0 |
||||||
|
}); |
||||||
|
|
||||||
|
mesh.setBuffer(Type.Color, 4, new float[] { |
||||||
|
// first quad, within normal frame limits
|
||||||
|
0, 1, 0, 0.25f, |
||||||
|
0, 1, 0, 0.25f, |
||||||
|
0, 0.25f, 0, 0.25f, |
||||||
|
0, 0.25f, 0, 0.25f, |
||||||
|
|
||||||
|
// Second quad, dropped frames
|
||||||
|
0.25f, 0, 0, 0.25f, |
||||||
|
0.25f, 0, 0, 0.25f, |
||||||
|
1, 0, 0, 0.25f, |
||||||
|
1, 0, 0, 0.25f, |
||||||
|
|
||||||
|
0, 0, 0, 0.5f, |
||||||
|
0, 0, 0, 0.5f, |
||||||
|
0, 0, 0, 0.5f, |
||||||
|
0, 0, 0, 0.5f |
||||||
|
}); |
||||||
|
|
||||||
|
mesh.setBuffer(Type.Index, 3, new short[] { |
||||||
|
0, 1, 2, |
||||||
|
0, 2, 3, |
||||||
|
4, 5, 6, |
||||||
|
4, 6, 7, |
||||||
|
8, 9, 10, |
||||||
|
8, 10, 11 |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void initialize( Application app ) { |
||||||
|
|
||||||
|
graph = new Geometry("profiler", profiler.getMesh()); |
||||||
|
|
||||||
|
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); |
||||||
|
mat.setBoolean("VertexColor", true); |
||||||
|
graph.setMaterial(mat); |
||||||
|
graph.setLocalTranslation(0, 300, 0); |
||||||
|
graph.setLocalScale(1, scale, 1); |
||||||
|
|
||||||
|
Mesh mesh = new Mesh(); |
||||||
|
background = new Geometry("profiler.background", mesh); |
||||||
|
mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); |
||||||
|
mat.setBoolean("VertexColor", true); |
||||||
|
mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha); |
||||||
|
background.setMaterial(mat); |
||||||
|
background.setLocalTranslation(0, 300, -1); |
||||||
|
background.setLocalScale(1, scale, 1); |
||||||
|
|
||||||
|
refreshBackground(); |
||||||
|
|
||||||
|
InputManager inputManager = app.getInputManager(); |
||||||
|
if( inputManager != null ) { |
||||||
|
inputManager.addMapping(INPUT_MAPPING_PROFILER_TOGGLE, new KeyTrigger(KeyInput.KEY_F6)); |
||||||
|
inputManager.addListener(keyListener, INPUT_MAPPING_PROFILER_TOGGLE); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void cleanup( Application app ) { |
||||||
|
InputManager inputManager = app.getInputManager(); |
||||||
|
if( inputManager.hasMapping(INPUT_MAPPING_PROFILER_TOGGLE) ) { |
||||||
|
inputManager.deleteMapping(INPUT_MAPPING_PROFILER_TOGGLE); |
||||||
|
} |
||||||
|
inputManager.removeListener(keyListener); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void enable() { |
||||||
|
|
||||||
|
// Set the number of visible frames to the current width of the screen
|
||||||
|
setFrameCount(getApplication().getCamera().getWidth()); |
||||||
|
|
||||||
|
getApplication().setAppProfiler(profiler); |
||||||
|
Node gui = ((SimpleApplication)getApplication()).getGuiNode(); |
||||||
|
gui.attachChild(graph); |
||||||
|
gui.attachChild(background); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void disable() { |
||||||
|
getApplication().setAppProfiler(null); |
||||||
|
graph.removeFromParent(); |
||||||
|
background.removeFromParent(); |
||||||
|
} |
||||||
|
|
||||||
|
private class ProfilerKeyListener implements ActionListener { |
||||||
|
|
||||||
|
public void onAction(String name, boolean value, float tpf) { |
||||||
|
if (!value) { |
||||||
|
return; |
||||||
|
} |
||||||
|
toggleProfiler(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
Loading…
Reference in new issue