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