2
0
Эх сурвалжийг харах

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..

pspeed42 11 жил өмнө
parent
commit
aea5e4af31

+ 197 - 0
jme3-core/src/main/java/com/jme3/app/BasicProfiler.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 ) {
+    }    
+}
+
+

+ 240 - 0
jme3-core/src/main/java/com/jme3/app/BasicProfilerState.java

@@ -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();
+        }
+    }
+}
+