Added a basic AppProfiler implementation and corresponding

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
pspeed42 10 years ago
parent a517130528
commit aea5e4af31
  1. 197
      jme3-core/src/main/java/com/jme3/app/BasicProfiler.java
  2. 240
      jme3-core/src/main/java/com/jme3/app/BasicProfilerState.java

@ -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…
Cancel
Save