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