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..
This commit is contained in:
parent
a517130528
commit
aea5e4af31
197
jme3-core/src/main/java/com/jme3/app/BasicProfiler.java
Normal file
197
jme3-core/src/main/java/com/jme3/app/BasicProfiler.java
Normal file
@ -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 ) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
240
jme3-core/src/main/java/com/jme3/app/BasicProfilerState.java
Normal file
240
jme3-core/src/main/java/com/jme3/app/BasicProfilerState.java
Normal file
@ -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…
x
Reference in New Issue
Block a user