Forráskód Böngészése

Merge remote-tracking branch 'origin/master' into jme3-vr-dev

Julien Seinturier 8 éve
szülő
commit
52eb6946af
50 módosított fájl, 1289 hozzáadás és 96 törlés
  1. 1 0
      README.md
  2. 8 3
      jme3-android/src/main/java/com/jme3/app/DefaultAndroidProfiler.java
  3. 7 0
      jme3-android/src/main/java/com/jme3/app/state/VideoRecorderAppState.java
  4. 33 4
      jme3-android/src/main/java/com/jme3/renderer/android/AndroidGL.java
  5. 6 4
      jme3-bullet/src/common/java/com/jme3/bullet/control/CharacterControl.java
  6. 9 3
      jme3-bullet/src/common/java/com/jme3/bullet/control/RigidBodyControl.java
  7. 7 3
      jme3-bullet/src/common/java/com/jme3/bullet/control/VehicleControl.java
  8. 9 15
      jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/MeshCollisionShape.java
  9. 8 4
      jme3-core/src/main/java/com/jme3/app/BasicProfiler.java
  10. 8 1
      jme3-core/src/main/java/com/jme3/app/state/ScreenshotAppState.java
  11. 2 0
      jme3-core/src/main/java/com/jme3/cinematic/events/MotionEvent.java
  12. 6 3
      jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java
  13. 8 2
      jme3-core/src/main/java/com/jme3/light/LightProbeBlendingProcessor.java
  14. 5 1
      jme3-core/src/main/java/com/jme3/material/MatParam.java
  15. 288 0
      jme3-core/src/main/java/com/jme3/post/DetailedProfiler.java
  16. 448 0
      jme3-core/src/main/java/com/jme3/post/DetailedProfilerState.java
  17. 13 0
      jme3-core/src/main/java/com/jme3/post/Filter.java
  18. 15 5
      jme3-core/src/main/java/com/jme3/post/FilterPostProcessor.java
  19. 7 0
      jme3-core/src/main/java/com/jme3/post/HDRRenderer.java
  20. 7 0
      jme3-core/src/main/java/com/jme3/post/PreDepthProcessor.java
  21. 8 0
      jme3-core/src/main/java/com/jme3/post/SceneProcessor.java
  22. 6 1
      jme3-core/src/main/java/com/jme3/profile/AppProfiler.java
  23. 13 0
      jme3-core/src/main/java/com/jme3/profile/SpStep.java
  24. 2 0
      jme3-core/src/main/java/com/jme3/profile/VpStep.java
  25. 8 4
      jme3-core/src/main/java/com/jme3/renderer/RenderManager.java
  26. 42 0
      jme3-core/src/main/java/com/jme3/renderer/Renderer.java
  27. 9 1
      jme3-core/src/main/java/com/jme3/renderer/opengl/GL.java
  28. 31 0
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugES.java
  29. 27 0
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java
  30. 6 0
      jme3-core/src/main/java/com/jme3/shadow/AbstractShadowRenderer.java
  31. 8 1
      jme3-core/src/main/java/com/jme3/shadow/BasicShadowRenderer.java
  32. 8 0
      jme3-core/src/main/java/com/jme3/shadow/PssmShadowRenderer.java
  33. 25 0
      jme3-core/src/main/java/com/jme3/system/NullRenderer.java
  34. 11 12
      jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.frag
  35. 7 0
      jme3-desktop/src/main/java/com/jme3/app/state/VideoRecorderAppState.java
  36. 7 0
      jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanel.java
  37. 1 1
      jme3-effects/src/main/java/com/jme3/post/ssao/SSAOFilter.java
  38. 7 0
      jme3-effects/src/main/java/com/jme3/water/ReflectionProcessor.java
  39. 17 10
      jme3-effects/src/main/java/com/jme3/water/SimpleWaterProcessor.java
  40. 2 2
      jme3-effects/src/main/resources/Common/MatDefs/SSAO/ssao.frag
  41. 1 1
      jme3-examples/src/main/java/jme3test/post/SSAOUI.java
  42. 6 0
      jme3-examples/src/main/java/jme3test/post/TestMultiRenderTarget.java
  43. 6 0
      jme3-examples/src/main/java/jme3test/post/TestRenderToMemory.java
  44. 12 3
      jme3-examples/src/main/java/jme3test/post/TestSSAO2.java
  45. 6 3
      jme3-examples/src/main/java/jme3test/stress/TestShaderNodesStress.java
  46. 25 0
      jme3-ios/src/main/java/com/jme3/renderer/ios/IosGL.java
  47. 33 9
      jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglGL.java
  48. 28 0
      jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java
  49. 25 0
      jme3-lwjgl3/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java
  50. 7 0
      jme3-niftygui/src/main/java/com/jme3/niftygui/NiftyJmeDisplay.java

+ 1 - 0
README.md

@@ -20,6 +20,7 @@ The engine is used by several commercial game studios and computer-science cours
  - [3089 (on steam)](http://store.steampowered.com/app/263360/)
  - [3079 (on steam)](http://store.steampowered.com/app/259620/)
  - [Lightspeed Frontier](http://www.lightspeedfrontier.com/)
+ - [Skullstone](http://www.skullstonegame.com/)
 
 ## Getting started
 

+ 8 - 3
jme3-android/src/main/java/com/jme3/app/DefaultAndroidProfiler.java

@@ -33,8 +33,8 @@
 package com.jme3.app;
 
 import android.os.Build;
-import com.jme3.profile.AppProfiler;
-import com.jme3.profile.AppStep;
+import com.jme3.profile.*;
+
 import static com.jme3.profile.AppStep.BeginFrame;
 import static com.jme3.profile.AppStep.EndFrame;
 import static com.jme3.profile.AppStep.ProcessAudio;
@@ -47,7 +47,6 @@ import static com.jme3.profile.AppStep.RenderPreviewViewPorts;
 import static com.jme3.profile.AppStep.SpatialUpdate;
 import static com.jme3.profile.AppStep.StateManagerRender;
 import static com.jme3.profile.AppStep.StateManagerUpdate;
-import com.jme3.profile.VpStep;
 import static com.jme3.profile.VpStep.BeginRender;
 import static com.jme3.profile.VpStep.EndRender;
 import static com.jme3.profile.VpStep.FlushQueue;
@@ -164,4 +163,10 @@ public class DefaultAndroidProfiler implements AppProfiler {
             }
         }
     }
+
+    @Override
+    public void spStep(SpStep step, String... additionalInfo) {
+
+    }
+
 }

+ 7 - 0
jme3-android/src/main/java/com/jme3/app/state/VideoRecorderAppState.java

@@ -34,6 +34,7 @@ package com.jme3.app.state;
 import android.graphics.Bitmap;
 import com.jme3.app.Application;
 import com.jme3.post.SceneProcessor;
+import com.jme3.profile.AppProfiler;
 import com.jme3.renderer.Camera;
 import com.jme3.renderer.RenderManager;
 import com.jme3.renderer.Renderer;
@@ -226,6 +227,7 @@ public class VideoRecorderAppState extends AbstractAppState {
         private LinkedBlockingQueue<WorkItem> usedItems = new LinkedBlockingQueue<WorkItem>();
         private MjpegFileWriter writer;
         private boolean fastMode = true;
+        private AppProfiler prof;
 
         public void addImage(Renderer renderer, FrameBuffer out) {
             if (freeItems == null) {
@@ -313,6 +315,11 @@ public class VideoRecorderAppState extends AbstractAppState {
             }
             writer = null;
         }
+
+        @Override
+        public void setProfiler(AppProfiler profiler) {
+            this.prof = profiler;
+        }
     }
 
     public static final class IsoTimer extends com.jme3.system.Timer {

+ 33 - 4
jme3-android/src/main/java/com/jme3/renderer/android/AndroidGL.java

@@ -31,11 +31,10 @@
  */
 package com.jme3.renderer.android;
 
-import android.opengl.GLES20;
+import android.opengl.*;
 import com.jme3.renderer.RendererException;
-import com.jme3.renderer.opengl.GL;
-import com.jme3.renderer.opengl.GLExt;
-import com.jme3.renderer.opengl.GLFbo;
+import com.jme3.renderer.opengl.*;
+
 import java.nio.Buffer;
 import java.nio.ByteBuffer;
 import java.nio.FloatBuffer;
@@ -92,6 +91,11 @@ public class AndroidGL implements GL, GLExt, GLFbo {
         GLES20.glAttachShader(program, shader);
     }
 
+    @Override
+    public void glBeginQuery(int target, int query) {
+        GLES30.glBeginQuery(target, query);
+    }
+
     public void glBindBuffer(int target, int buffer) {
         GLES20.glBindBuffer(target, buffer);
     }
@@ -234,6 +238,11 @@ public class AndroidGL implements GL, GLExt, GLFbo {
         GLES20.glEnableVertexAttribArray(index);
     }
 
+    @Override
+    public void glEndQuery(int target) {
+        GLES30.glEndQuery(target);
+    }
+
     public void glGenBuffers(IntBuffer buffers) {
         checkLimit(buffers);
         GLES20.glGenBuffers(buffers.limit(), buffers);
@@ -244,6 +253,11 @@ public class AndroidGL implements GL, GLExt, GLFbo {
         GLES20.glGenTextures(textures.limit(), textures);
     }
 
+    @Override
+    public void glGenQueries(int num, IntBuffer buff) {
+        GLES30.glGenQueries(num, buff);
+    }
+
     public int glGetAttribLocation(int program, String name) {
         return GLES20.glGetAttribLocation(program, name);
     }
@@ -271,6 +285,21 @@ public class AndroidGL implements GL, GLExt, GLFbo {
         return GLES20.glGetProgramInfoLog(program);
     }
 
+    @Override
+    public long glGetQueryObjectui64(int query, int pname) {
+        IntBuffer buff = IntBuffer.allocate(1);
+        //FIXME This is wrong IMO should be glGetQueryObjectui64v with a LongBuffer but it seems the API doesn't provide it.
+        GLES30.glGetQueryObjectuiv(query, pname, buff);
+        return buff.get(0);
+    }
+
+    @Override
+    public int glGetQueryObjectiv(int query, int pname) {
+        IntBuffer buff = IntBuffer.allocate(1);
+        GLES30.glGetQueryiv(query, pname, buff);
+        return buff.get(0);
+    }
+
     public void glGetShader(int shader, int pname, IntBuffer params) {
         checkLimit(params);
         GLES20.glGetShaderiv(shader, pname, params);

+ 6 - 4
jme3-bullet/src/common/java/com/jme3/bullet/control/CharacterControl.java

@@ -121,6 +121,7 @@ public class CharacterControl extends PhysicsCharacter implements PhysicsControl
         control.setUpAxis(getUpAxis());
         control.setApplyPhysicsLocal(isApplyPhysicsLocal());
         control.spatial = this.spatial;
+        control.setEnabled(isEnabled());
         return control;
     }     
 
@@ -207,11 +208,12 @@ public class CharacterControl extends PhysicsCharacter implements PhysicsControl
                 added = false;
             }
         } else {
-            if (this.space == space) {
-                return;
+            if(this.space == space) return;
+            // if this object isn't enabled, it will be added when it will be enabled.
+            if (isEnabled()) {
+                space.addCollisionObject(this);
+                added = true;
             }
-            space.addCollisionObject(this);
-            added = true;
         }
         this.space = space;
     }

+ 9 - 3
jme3-bullet/src/common/java/com/jme3/bullet/control/RigidBodyControl.java

@@ -53,6 +53,7 @@ import com.jme3.scene.shape.Box;
 import com.jme3.scene.shape.Sphere;
 import com.jme3.util.clone.Cloner;
 import com.jme3.util.clone.JmeCloneable;
+
 import java.io.IOException;
 
 /**
@@ -143,6 +144,8 @@ public class RigidBodyControl extends PhysicsRigidBody implements PhysicsControl
         }
         control.setApplyPhysicsLocal(isApplyPhysicsLocal());
         control.spatial = this.spatial;
+        control.setEnabled(isEnabled());
+
         return control;
     }     
 
@@ -273,9 +276,12 @@ public class RigidBodyControl extends PhysicsRigidBody implements PhysicsControl
                 added = false;
             }
         } else {
-            if(this.space==space) return;
-            space.addCollisionObject(this);
-            added = true;
+            if (this.space == space) return;
+            // if this object isn't enabled, it will be added when it will be enabled.
+            if (isEnabled()) {
+                space.addCollisionObject(this);
+                added = true;
+            }
         }
         this.space = space;
     }

+ 7 - 3
jme3-bullet/src/common/java/com/jme3/bullet/control/VehicleControl.java

@@ -200,6 +200,7 @@ public class VehicleControl extends PhysicsVehicle implements PhysicsControl, Jm
             newWheel.setWheelSpatial(wheel.getWheelSpatial());
         }
         control.setApplyPhysicsLocal(isApplyPhysicsLocal());
+        control.setEnabled(isEnabled());
         
         control.spatial = spatial;
         return control;
@@ -268,9 +269,12 @@ public class VehicleControl extends PhysicsVehicle implements PhysicsControl, Jm
                 added = false;
             }
         } else {
-            if(this.space==space) return;
-            space.addCollisionObject(this);
-            added = true;
+            if(this.space == space) return;
+            // if this object isn't enabled, it will be added when it will be enabled.
+            if (isEnabled()) {
+                space.addCollisionObject(this);
+                added = true;
+            }
         }
         this.space = space;
     }

+ 9 - 15
jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/MeshCollisionShape.java

@@ -122,7 +122,7 @@ public class MeshCollisionShape extends CollisionShape {
         this.vertexStride = 12;
         this.triangleIndexStride = 12;
         this.memoryOptimized = memoryOptimized;
-        this.createShape(true);
+        this.createShape(null);
     }
     
     private void createCollisionMesh(Mesh mesh) {
@@ -150,7 +150,7 @@ public class MeshCollisionShape extends CollisionShape {
         vertices.rewind();
         vertices.clear();
 
-        this.createShape(true);
+        this.createShape(null);
     }
 
     @Override
@@ -191,25 +191,19 @@ public class MeshCollisionShape extends CollisionShape {
         this.vertexBase = BufferUtils.createByteBuffer(capsule.readByteArray(MeshCollisionShape.VERTEX_BASE, null));
 
         byte[] nativeBvh = capsule.readByteArray(MeshCollisionShape.NATIVE_BVH, null);
-        if (nativeBvh == null) {
-            // Either using non memory optimized BVH or old J3O file
-            memoryOptimized = false;
-            createShape(true);
-        } else {
-            // Using memory optimized BVH, load from J3O, then assign it.
-            memoryOptimized = true;
-            createShape(false);
-            nativeBVHBuffer = setBVH(nativeBvh, this.objectId);
-        }
+        memoryOptimized=nativeBvh != null;
+        createShape(nativeBvh);
     }
 
-    private void createShape(boolean buildBvt) {
+    private void createShape(byte bvh[]) {
+        boolean buildBvh=bvh==null||bvh.length==0;
         this.meshId = NativeMeshUtil.createTriangleIndexVertexArray(this.triangleIndexBase, this.vertexBase, this.numTriangles, this.numVertices, this.vertexStride, this.triangleIndexStride);
         Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Mesh {0}", Long.toHexString(this.meshId));
-        this.objectId = createShape(memoryOptimized, buildBvt, this.meshId);
+        this.objectId = createShape(memoryOptimized, buildBvh, this.meshId);
         Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Shape {0}", Long.toHexString(this.objectId));
+        if(!buildBvh)   nativeBVHBuffer = setBVH(bvh, this.objectId);                
         this.setScale(this.scale);
-        this.setMargin(this.margin);
+        this.setMargin(this.margin);        
     }
 
     /**

+ 8 - 4
jme3-core/src/main/java/com/jme3/app/BasicProfiler.java

@@ -32,9 +32,7 @@
  
 package com.jme3.app;
 
-import com.jme3.profile.AppProfiler;
-import com.jme3.profile.AppStep;
-import com.jme3.profile.VpStep;
+import com.jme3.profile.*;
 import com.jme3.renderer.ViewPort;
 import com.jme3.renderer.queue.RenderQueue.Bucket;
 import com.jme3.scene.Mesh;
@@ -191,7 +189,13 @@ public class BasicProfiler implements AppProfiler {
     
     @Override
     public void vpStep( VpStep step, ViewPort vp, Bucket bucket ) {
-    }    
+    }
+
+    @Override
+    public void spStep(SpStep step, String... additionalInfo) {
+
+    }
+
 }
 
 

+ 8 - 1
jme3-core/src/main/java/com/jme3/app/state/ScreenshotAppState.java

@@ -37,6 +37,7 @@ import com.jme3.input.KeyInput;
 import com.jme3.input.controls.ActionListener;
 import com.jme3.input.controls.KeyTrigger;
 import com.jme3.post.SceneProcessor;
+import com.jme3.profile.AppProfiler;
 import com.jme3.renderer.Camera;
 import com.jme3.renderer.RenderManager;
 import com.jme3.renderer.Renderer;
@@ -67,6 +68,7 @@ public class ScreenshotAppState extends AbstractAppState implements ActionListen
     private String shotName;
     private long shotIndex = 0;
     private int width, height;
+    private AppProfiler prof;
 
     /**
      * Using this constructor, the screenshot files will be written sequentially to the system
@@ -256,7 +258,12 @@ public class ScreenshotAppState extends AbstractAppState implements ActionListen
             }                
         }
     }
-    
+
+    @Override
+    public void setProfiler(AppProfiler profiler) {
+        this.prof = profiler;
+    }
+
     /**
      *  Called by postFrame() once the screen has been captured to outBuf.
      */

+ 2 - 0
jme3-core/src/main/java/com/jme3/cinematic/events/MotionEvent.java

@@ -214,6 +214,7 @@ public class MotionEvent extends AbstractCinematicEvent implements Control, JmeC
         oc.write(rotation, "rotation", null);
         oc.write(directionType, "directionType", Direction.None);
         oc.write(path, "path", null);
+        oc.write(spatial, "spatial", null);
     }
 
     @Override
@@ -225,6 +226,7 @@ public class MotionEvent extends AbstractCinematicEvent implements Control, JmeC
         rotation = (Quaternion) in.readSavable("rotation", null);
         directionType = in.readEnum("directionType", Direction.class, Direction.None);
         path = (MotionPath) in.readSavable("path", null);
+        spatial = (Spatial) in.readSavable("spatial", null);
     }
 
     /**

+ 6 - 3
jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java

@@ -1101,9 +1101,12 @@ public class ParticleEmitter extends Geometry {
 
         lastPos.set(getWorldTranslation());
 
-        BoundingBox bbox = (BoundingBox) this.getMesh().getBound();
-        bbox.setMinMax(min, max);
-        this.setBoundRefresh();
+        //This check avoids a NaN bounds when all the particles are dead during the first update.
+        if (!min.equals(Vector3f.POSITIVE_INFINITY) && !max.equals(Vector3f.NEGATIVE_INFINITY)) {
+            BoundingBox bbox = (BoundingBox) this.getMesh().getBound();
+            bbox.setMinMax(min, max);
+            this.setBoundRefresh();
+        }
 
         vars.release();
     }

+ 8 - 2
jme3-core/src/main/java/com/jme3/light/LightProbeBlendingProcessor.java

@@ -33,6 +33,7 @@ package com.jme3.light;
 
 import com.jme3.bounding.BoundingSphere;
 import com.jme3.post.SceneProcessor;
+import com.jme3.profile.AppProfiler;
 import com.jme3.renderer.RenderManager;
 import com.jme3.renderer.ViewPort;
 import com.jme3.renderer.queue.RenderQueue;
@@ -56,6 +57,7 @@ public class LightProbeBlendingProcessor implements SceneProcessor {
     private RenderManager renderManager;
     private LightProbe probe = new LightProbe();
     private Spatial poi;
+    private AppProfiler prof;
 
     public LightProbeBlendingProcessor(Spatial poi) {        
         this.poi = poi;
@@ -177,8 +179,12 @@ public class LightProbeBlendingProcessor implements SceneProcessor {
     public void setPoi(Spatial poi) {
         this.poi = poi;
     }
-    
-    
+
+    @Override
+    public void setProfiler(AppProfiler profiler) {
+        this.prof = profiler;
+    }
+
     private class BlendFactor implements Comparable<BlendFactor>{
         
         LightProbe lightProbe;

+ 5 - 1
jme3-core/src/main/java/com/jme3/material/MatParam.java

@@ -237,7 +237,11 @@ When arrays can be inserted in J3M files
                 Texture texVal = (Texture) value;
                 TextureKey texKey = (TextureKey) texVal.getKey();
                 if (texKey == null){
-                    throw new UnsupportedOperationException("The specified MatParam cannot be represented in J3M");
+                  //throw new UnsupportedOperationException("The specified MatParam cannot be represented in J3M");
+                    // this is used in toString and the above line causes blender materials to throw this exception. 
+                    // toStrings should be very robust IMO as even debuggers often invoke toString and logging code
+                    // often does as well, even implicitly. 
+                    return texVal+":returned null key";
                 }
 
                 String ret = "";

+ 288 - 0
jme3-core/src/main/java/com/jme3/post/DetailedProfiler.java

@@ -0,0 +1,288 @@
+package com.jme3.post;
+
+import com.jme3.profile.*;
+import com.jme3.renderer.Renderer;
+import com.jme3.renderer.ViewPort;
+import com.jme3.renderer.queue.RenderQueue;
+
+import java.util.*;
+
+/**
+ * Created by Nehon on 25/01/2017.
+ */
+public class DetailedProfiler implements AppProfiler {
+
+    private final static int MAX_FRAMES = 100;
+    private Map<String, StatLine> data;
+    private Map<String, StatLine> pool;
+    private long startFrame;
+    private static int currentFrame = 0;
+    private String prevPath = null;
+    private boolean frameEnded = false;
+    private Renderer renderer;
+    private boolean ongoingGpuProfiling = false;
+
+
+    private String curAppPath = null;
+    private String curVpPath = null;
+    private String curSpPath = null;
+    private VpStep lastVpStep = null;
+
+    private StringBuilder path = new StringBuilder(256);
+    private StringBuilder vpPath = new StringBuilder(256);
+
+    private Deque<Integer> idsPool = new ArrayDeque<>(100);
+
+    StatLine frameTime;
+
+
+    @Override
+    public void appStep(AppStep step) {
+        curAppPath = step.name();
+
+        if (step == AppStep.BeginFrame) {
+            if (data == null) {
+                data = new LinkedHashMap<>();
+                pool = new HashMap<>();
+                frameTime = new StatLine(currentFrame);
+            }
+            if (frameTime.isActive()) {
+                frameTime.setValueCpu(System.nanoTime() - frameTime.getValueCpu());
+                frameTime.closeFrame();
+
+            }
+            frameTime.setNewFrameValueCpu(System.nanoTime());
+
+            frameEnded = false;
+            for (StatLine statLine : data.values()) {
+                for (Iterator<Integer> i = statLine.taskIds.iterator(); i.hasNext(); ) {
+                    int id = i.next();
+                    if (renderer.isTaskResultAvailable(id)) {
+                        long val = renderer.getProfilingTime(id);
+                        statLine.setValueGpu(val);
+                        i.remove();
+                        idsPool.push(id);
+                    }
+                }
+            }
+            data.clear();
+        }
+
+        if (data != null) {
+            String path = getPath(step.name());
+            if (step == AppStep.EndFrame) {
+                if (frameEnded) {
+                    return;
+                }
+                addStep(path, System.nanoTime());
+                StatLine end = data.get(path);
+                end.setValueCpu(System.nanoTime() - startFrame);
+                frameEnded = true;
+            } else {
+                addStep(path, System.nanoTime());
+            }
+        }
+        if (step == AppStep.EndFrame) {
+
+            closeFrame();
+        }
+    }
+
+    private void closeFrame() {
+        //close frame
+        if (data != null) {
+
+            prevPath = null;
+
+            for (StatLine statLine : data.values()) {
+                statLine.closeFrame();
+            }
+            currentFrame++;
+        }
+    }
+
+    @Override
+    public void vpStep(VpStep step, ViewPort vp, RenderQueue.Bucket bucket) {
+
+        if (data != null) {
+            vpPath.setLength(0);
+            vpPath.append(vp.getName()).append("/").append((bucket == null ? step.name() : bucket.name() + " Bucket"));
+            path.setLength(0);
+            if ((lastVpStep == VpStep.PostQueue || lastVpStep == VpStep.PostFrame) && bucket != null) {
+                path.append(curAppPath).append("/").append(curVpPath).append(curSpPath).append("/").append(vpPath);
+                curVpPath = vpPath.toString();
+            } else {
+                if (bucket != null) {
+                    path.append(curAppPath).append("/").append(curVpPath).append("/").append(bucket.name() + " Bucket");
+                } else {
+                    path.append(curAppPath).append("/").append(vpPath);
+                    curVpPath = vpPath.toString();
+                }
+            }
+            lastVpStep = step;
+
+            addStep(path.toString(), System.nanoTime());
+        }
+    }
+
+    @Override
+    public void spStep(SpStep step, String... additionalInfo) {
+
+        if (data != null) {
+            curSpPath = getPath("", additionalInfo);
+            path.setLength(0);
+            path.append(curAppPath).append("/").append(curVpPath).append(curSpPath);
+            addStep(path.toString(), System.nanoTime());
+        }
+
+    }
+
+    public Map<String, StatLine> getStats() {
+        if (data != null) {
+            return data;//new LinkedHashMap<>(data);
+        }
+        return null;
+    }
+
+    public double getAverageFrameTime() {
+        return frameTime.getAverageCpu();
+    }
+
+
+    private void addStep(String path, long value) {
+        if (ongoingGpuProfiling && renderer != null) {
+            renderer.stopProfiling();
+            ongoingGpuProfiling = false;
+        }
+
+        if (prevPath != null) {
+            StatLine prevLine = data.get(prevPath);
+            if (prevLine != null) {
+                prevLine.setValueCpu(value - prevLine.getValueCpu());
+            }
+        }
+
+        StatLine line = pool.get(path);
+        if (line == null) {
+            line = new StatLine(currentFrame);
+            pool.put(path, line);
+        }
+        data.put(path, line);
+        line.setNewFrameValueCpu(value);
+        if (renderer != null) {
+            int id = getUnusedTaskId();
+            line.taskIds.add(id);
+            renderer.startProfiling(id);
+        }
+        ongoingGpuProfiling = true;
+        prevPath = path;
+
+    }
+
+    private String getPath(String step, String... subPath) {
+        StringBuilder path = new StringBuilder(step);
+        if (subPath != null) {
+            for (String s : subPath) {
+                path.append("/").append(s);
+            }
+        }
+        return path.toString();
+    }
+
+    public void setRenderer(Renderer renderer) {
+        this.renderer = renderer;
+        poolTaskIds(renderer);
+    }
+
+    private void poolTaskIds(Renderer renderer) {
+        int[] ids = renderer.generateProfilingTasks(100);
+        for (int id : ids) {
+            idsPool.push(id);
+        }
+    }
+
+    private int getUnusedTaskId() {
+        if (idsPool.isEmpty()) {
+            poolTaskIds(renderer);
+        }
+
+        return idsPool.pop();
+    }
+
+    public static class StatLine {
+        private long[] cpuTimes = new long[MAX_FRAMES];
+        private long[] gpuTimes = new long[MAX_FRAMES];
+        private int startCursor = 0;
+        private int cpuCursor = 0;
+        private int gpuCursor = 0;
+        private long cpuSum = 0;
+        private long gpuSum = 0;
+        private long lastValue = 0;
+        private int nbFramesCpu;
+        private int nbFramesGpu;
+        List<Integer> taskIds = new ArrayList<>();
+
+
+        private StatLine(int currentFrame) {
+            startCursor = currentFrame % MAX_FRAMES;
+            cpuCursor = startCursor;
+            gpuCursor = startCursor;
+        }
+
+        private void setNewFrameValueCpu(long value) {
+            int newCursor = currentFrame % MAX_FRAMES;
+            if (nbFramesCpu == 0) {
+                startCursor = newCursor;
+            }
+            cpuCursor = newCursor;
+            lastValue = value;
+        }
+
+        private void setValueCpu(long val) {
+            lastValue = val;
+        }
+
+        private long getValueCpu() {
+            return lastValue;
+        }
+
+        private void closeFrame() {
+            if (isActive()) {
+                cpuSum -= cpuTimes[cpuCursor];
+                cpuTimes[cpuCursor] = lastValue;
+                cpuSum += lastValue;
+                nbFramesCpu++;
+            } else {
+                nbFramesCpu = 0;
+            }
+        }
+
+        public void setValueGpu(long value) {
+            gpuSum -= gpuTimes[gpuCursor];
+            gpuTimes[gpuCursor] = value;
+            gpuSum += value;
+            nbFramesGpu++;
+            gpuCursor = (gpuCursor + 1) % MAX_FRAMES;
+        }
+
+        public boolean isActive() {
+            return cpuCursor >= currentFrame % MAX_FRAMES - 1;
+        }
+
+        public double getAverageCpu() {
+            if (nbFramesCpu == 0) {
+                return 0;
+            }
+            return (double) cpuSum / (double) Math.min(nbFramesCpu, MAX_FRAMES);
+        }
+
+        public double getAverageGpu() {
+            if (nbFramesGpu == 0) {
+                return 0;
+            }
+
+            return (double) gpuSum / (double) Math.min(nbFramesGpu, MAX_FRAMES);
+        }
+    }
+
+}

+ 448 - 0
jme3-core/src/main/java/com/jme3/post/DetailedProfilerState.java

@@ -0,0 +1,448 @@
+package com.jme3.post;
+
+import com.jme3.app.Application;
+import com.jme3.app.SimpleApplication;
+import com.jme3.app.state.BaseAppState;
+import com.jme3.font.BitmapFont;
+import com.jme3.font.BitmapText;
+import com.jme3.input.*;
+import com.jme3.input.controls.*;
+import com.jme3.material.Material;
+import com.jme3.material.RenderState;
+import com.jme3.math.*;
+import com.jme3.profile.AppStep;
+import com.jme3.scene.*;
+import com.jme3.scene.shape.Quad;
+
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.util.*;
+
+import static com.jme3.post.DetailedProfiler.*;
+
+/**
+ * Created by Nehon on 25/01/2017.
+ */
+public class DetailedProfilerState extends BaseAppState {
+
+    private static final int PANEL_WIDTH = 400;
+    private static final int PADDING = 10;
+    private static final int LINE_HEIGHT = 12;
+    private static final int HEADER_HEIGHT = 100;
+    private static final float REFRESH_TIME = 1.0f;
+    private static final String TOGGLE_KEY = "Toggle_Detailed_Profiler";
+    private static final String CLICK_KEY = "Click_Detailed_Profiler";
+    private static final String INSIGNIFICANT = "Hide insignificant stat";
+    private DetailedProfiler prof = new DetailedProfiler();
+
+    private float time = 0;
+    private BitmapFont font;
+    private BitmapFont bigFont;
+    private Node ui = new Node("Stats ui");
+    private Map<String, StatLineView> lines = new HashMap<>();
+    private double totalTimeCpu;
+    private double totalTimeGpu;
+    private int maxLevel = 0;
+
+    private BitmapText frameTimeValue;
+    private BitmapText frameCpuTimeValue;
+    private BitmapText frameGpuTimeValue;
+    private BitmapText hideInsignificantField;
+
+    private BitmapText selectedField;
+    private double selectedValueCpu = 0;
+    private double selectedValueGpu = 0;
+    private boolean hideInsignificant = false;
+
+    private StatLineView rootLine;
+    private int height = 0;
+    private DecimalFormat df = new DecimalFormat("##0.00", new DecimalFormatSymbols(Locale.US));
+
+    private ColorRGBA dimmedWhite = ColorRGBA.White.mult(0.7f);
+    private ColorRGBA dimmedGreen = ColorRGBA.Green.mult(0.7f);
+    private ColorRGBA dimmedOrange = ColorRGBA.Orange.mult(0.7f);
+    private ColorRGBA dimmedRed = ColorRGBA.Red.mult(0.7f);
+
+    public DetailedProfilerState() {
+
+    }
+
+    @Override
+    protected void initialize(Application app) {
+        Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
+        mat.setColor("Color", new ColorRGBA(0, 0, 0, 0.5f));
+        mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
+        Geometry darkenStats = new Geometry("StatsDarken", new Quad(PANEL_WIDTH, app.getCamera().getHeight()));
+        darkenStats.setMaterial(mat);
+        darkenStats.setLocalTranslation(0, -app.getCamera().getHeight(), -1);
+
+        ui.attachChild(darkenStats);
+        ui.setLocalTranslation(app.getCamera().getWidth() - PANEL_WIDTH, app.getCamera().getHeight(), 0);
+        font = app.getAssetManager().loadFont("Interface/Fonts/Console.fnt");
+        bigFont = app.getAssetManager().loadFont("Interface/Fonts/Default.fnt");
+        prof.setRenderer(app.getRenderer());
+        rootLine = new StatLineView("Frame");
+        rootLine.attachTo(ui);
+
+        BitmapText frameLabel = new BitmapText(bigFont);
+        frameLabel.setText("Total Frame Time: ");
+        ui.attachChild(frameLabel);
+        frameLabel.setLocalTranslation(new Vector3f(PANEL_WIDTH / 2 - bigFont.getLineWidth(frameLabel.getText()), -PADDING, 0));
+
+        BitmapText cpuLabel = new BitmapText(bigFont);
+        cpuLabel.setText("CPU");
+        ui.attachChild(cpuLabel);
+        cpuLabel.setLocalTranslation(PANEL_WIDTH / 4 - bigFont.getLineWidth(cpuLabel.getText()) / 2, -PADDING - 30, 0);
+
+        BitmapText gpuLabel = new BitmapText(bigFont);
+        gpuLabel.setText("GPU");
+        ui.attachChild(gpuLabel);
+        gpuLabel.setLocalTranslation(3 * PANEL_WIDTH / 4 - bigFont.getLineWidth(gpuLabel.getText()) / 2, -PADDING - 30, 0);
+
+        frameTimeValue = new BitmapText(bigFont);
+        frameCpuTimeValue = new BitmapText(bigFont);
+        frameGpuTimeValue = new BitmapText(bigFont);
+
+        selectedField = new BitmapText(font);
+        selectedField.setText("Selected: ");
+        selectedField.setLocalTranslation(PANEL_WIDTH / 2, -PADDING - 75, 0);
+        selectedField.setColor(ColorRGBA.Yellow);
+
+
+        ui.attachChild(frameTimeValue);
+        ui.attachChild(frameCpuTimeValue);
+        ui.attachChild(frameGpuTimeValue);
+        ui.attachChild(selectedField);
+
+        hideInsignificantField = new BitmapText(font);
+        hideInsignificantField.setText("O " + INSIGNIFICANT);
+        hideInsignificantField.setLocalTranslation(PADDING, -PADDING - 75, 0);
+        ui.attachChild(hideInsignificantField);
+
+        final InputManager inputManager = app.getInputManager();
+        if (inputManager != null) {
+            inputManager.addMapping(TOGGLE_KEY, new KeyTrigger(KeyInput.KEY_F6));
+            inputManager.addMapping(CLICK_KEY, new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
+            inputManager.addListener(new ActionListener() {
+                @Override
+                public void onAction(String name, boolean isPressed, float tpf) {
+                    if (name.equals(TOGGLE_KEY) && isPressed) {
+                        setEnabled(!isEnabled());
+                    }
+                    if (isEnabled() && name.equals(CLICK_KEY) && isPressed) {
+                        handleClick(inputManager.getCursorPosition());
+                    }
+                }
+            }, TOGGLE_KEY, CLICK_KEY);
+        }
+    }
+
+    @Override
+    protected void cleanup(Application app) {
+
+    }
+
+    @Override
+    public void update(float tpf) {
+        time += tpf;
+    }
+
+    private void displayData(Map<String, DetailedProfiler.StatLine> data) {
+        if (data == null || data.isEmpty()) {
+            return;
+        }
+
+        for (StatLineView statLine : lines.values()) {
+            statLine.reset();
+            statLine.removeFromParent();
+        }
+        rootLine.reset();
+        maxLevel = 0;
+        for (String path : data.keySet()) {
+            if (path.equals("EndFrame")) {
+                continue;
+            }
+            maxLevel = Math.max(maxLevel, path.split("/").length);
+            StatLineView line = getStatLineView(path);
+            StatLine statLine = data.get(path);
+            line.updateValues(statLine.getAverageCpu(), statLine.getAverageGpu());
+            String parent = getParent(path);
+            while (parent != null) {
+                StatLineView parentView = getStatLineView(parent);
+                parentView.updateValues(statLine.getAverageCpu(), statLine.getAverageGpu());
+                parentView.children.add(line);
+                line.attachTo(ui);
+                line = parentView;
+                parent = getParent(parent);
+            }
+            rootLine.children.add(line);
+            line.attachTo(ui);
+            rootLine.updateValues(statLine.getAverageCpu(), statLine.getAverageGpu());
+        }
+
+        totalTimeCpu = rootLine.cpuValue;
+        totalTimeGpu = rootLine.gpuValue + data.get("EndFrame").getAverageGpu();
+
+        layout();
+
+    }
+
+    private void layout() {
+        height = 0;
+        selectedValueCpu = 0;
+        selectedValueGpu = 0;
+        rootLine.layout(0);
+
+        frameTimeValue.setText(df.format(getMsFromNs(prof.getAverageFrameTime())) + "ms");
+        frameTimeValue.setLocalTranslation(PANEL_WIDTH / 2, -PADDING, 0);
+        setColor(frameTimeValue, prof.getAverageFrameTime(), totalTimeCpu, false, false);
+
+        frameCpuTimeValue.setText(df.format(getMsFromNs(totalTimeCpu)) + "ms");
+        frameCpuTimeValue.setLocalTranslation(new Vector3f(PANEL_WIDTH / 4 - bigFont.getLineWidth(frameCpuTimeValue.getText()) / 2, -PADDING - 50, 0));
+        setColor(frameCpuTimeValue, totalTimeCpu, totalTimeCpu, false, false);
+
+        frameGpuTimeValue.setText(df.format(getMsFromNs(totalTimeGpu)) + "ms");
+        frameGpuTimeValue.setLocalTranslation(new Vector3f(3 * PANEL_WIDTH / 4 - bigFont.getLineWidth(frameGpuTimeValue.getText()) / 2, -PADDING - 50, 0));
+        setColor(frameGpuTimeValue, totalTimeGpu, totalTimeGpu, false, false);
+
+        selectedField.setText("Selected: " + df.format(getMsFromNs(selectedValueCpu)) + "ms / " + df.format(getMsFromNs(selectedValueGpu)) + "ms");
+
+        selectedField.setLocalTranslation(3 * PANEL_WIDTH / 4 - font.getLineWidth(selectedField.getText()) / 2, -PADDING - 75, 0);
+    }
+
+    private StatLineView getStatLineView(String path) {
+        StatLineView line = lines.get(path);
+
+        if (line == null) {
+            line = new StatLineView(getLeaf(path));
+            lines.put(path, line);
+            line.attachTo(ui);
+        }
+        return line;
+    }
+
+    private String getLeaf(String path) {
+        int idx = path.lastIndexOf("/");
+        return idx >= 0 ? path.substring(idx + 1) : path;
+    }
+
+    private String getParent(String path) {
+        int idx = path.lastIndexOf("/");
+        return idx >= 0 ? path.substring(0, idx) : null;
+    }
+
+
+    @Override
+    public void postRender() {
+        if (time > REFRESH_TIME) {
+            prof.appStep(AppStep.EndFrame);
+            Map<String, StatLine> data = prof.getStats();
+            displayData(data);
+            time = 0;
+        }
+    }
+
+    private double getMsFromNs(double time) {
+        return time / 1000000.0;
+    }
+
+    @Override
+    protected void onEnable() {
+        getApplication().setAppProfiler(prof);
+        ((SimpleApplication) getApplication()).getGuiNode().attachChild(ui);
+    }
+
+    @Override
+    protected void onDisable() {
+        getApplication().setAppProfiler(null);
+        ui.removeFromParent();
+    }
+
+    public boolean setColor(BitmapText t, double value, double totalTime, boolean isParent, boolean expended) {
+
+        boolean dimmed = isParent && expended;
+        boolean insignificant = false;
+
+        if (value > 1000000000.0 / 30.0) {
+            t.setColor(dimmed ? dimmedRed : ColorRGBA.Red);
+        } else if (value > 1000000000.0 / 60.0) {
+            t.setColor(dimmed ? dimmedOrange : ColorRGBA.Orange);
+        } else if (value > totalTime / 3) {
+            t.setColor(dimmed ? dimmedGreen : ColorRGBA.Green);
+        } else if (value < 30000) {
+            t.setColor(ColorRGBA.DarkGray);
+            insignificant = true;
+        } else {
+            t.setColor(dimmed ? dimmedWhite : ColorRGBA.White);
+        }
+        return insignificant;
+    }
+
+    private void handleClick(Vector2f pos) {
+
+        Vector3f lp = hideInsignificantField.getWorldTranslation();
+        float width = font.getLineWidth(hideInsignificantField.getText());
+        if (pos.x > lp.x && pos.x < (lp.x + width)
+                && pos.y < lp.y && pos.y > lp.y - LINE_HEIGHT) {
+            hideInsignificant = !hideInsignificant;
+            hideInsignificantField.setText((hideInsignificant ? "X " : "O ") + INSIGNIFICANT);
+            if (!hideInsignificant) {
+                rootLine.setExpended(true);
+            }
+        }
+
+        rootLine.onClick(pos);
+        for (StatLineView statLineView : lines.values()) {
+            statLineView.onClick(pos);
+        }
+        layout();
+    }
+
+    private class StatLineView {
+        BitmapText label;
+        BitmapText cpuText;
+        BitmapText gpuText;
+        BitmapText checkBox;
+        double cpuValue;
+        double gpuValue;
+        private boolean expended = true;
+        private boolean visible = true;
+        private boolean selected = false;
+        String text;
+
+        Set<StatLineView> children = new LinkedHashSet<>();
+
+        public StatLineView(String label) {
+            this.text = label;
+            this.label = new BitmapText(font);
+            this.checkBox = new BitmapText(font);
+            this.checkBox.setText("O");
+            this.label.setText("- " + label);
+            this.cpuText = new BitmapText(font);
+            this.gpuText = new BitmapText(font);
+        }
+
+        public void onClick(Vector2f pos) {
+
+            if (!visible) {
+                return;
+            }
+
+            Vector3f lp = label.getWorldTranslation();
+            Vector3f cp = checkBox.getWorldTranslation();
+            if (pos.x > cp.x
+                    && pos.y < lp.y && pos.y > lp.y - LINE_HEIGHT) {
+
+                float width = font.getLineWidth(checkBox.getText());
+                if (pos.x >= cp.x && pos.x <= (cp.x + width)) {
+                    selected = !selected;
+                    if (selected) {
+                        checkBox.setText("X");
+                    } else {
+                        checkBox.setText("O");
+                    }
+                } else {
+                    setExpended(!expended);
+                }
+            }
+        }
+
+        public void setExpended(boolean expended) {
+            this.expended = expended;
+            if (expended) {
+                label.setText("- " + text);
+            } else {
+                label.setText("+ " + text);
+            }
+            for (StatLineView child : children) {
+                child.setVisible(expended);
+            }
+        }
+
+        public void layout(int indent) {
+
+            boolean insignificant;
+            cpuText.setText(df.format(getMsFromNs(cpuValue)) + "ms /");
+            insignificant = setColor(cpuText, cpuValue, totalTimeCpu, !children.isEmpty(), expended);
+            gpuText.setText(" " + df.format(getMsFromNs(gpuValue)) + "ms");
+            insignificant &= setColor(gpuText, gpuValue, totalTimeGpu, !children.isEmpty(), expended);
+
+            if (insignificant && hideInsignificant) {
+                setVisible(false);
+            }
+
+            if (!visible) {
+                return;
+            }
+
+            if (selected) {
+                label.setColor(ColorRGBA.Yellow);
+                selectedValueCpu += cpuValue;
+                selectedValueGpu += gpuValue;
+            } else {
+                label.setColor(ColorRGBA.White);
+            }
+
+            int y = -(height * LINE_HEIGHT + HEADER_HEIGHT);
+
+            label.setLocalTranslation(PADDING + indent * PADDING, y, 0);
+            float gpuPos = PANEL_WIDTH - font.getLineWidth(gpuText.getText()) - PADDING * (maxLevel - indent + 1);
+            cpuText.setLocalTranslation(gpuPos - font.getLineWidth(cpuText.getText()), y, 0);
+            gpuText.setLocalTranslation(gpuPos, y, 0);
+
+            checkBox.setLocalTranslation(3, y, 0);
+            height++;
+            for (StatLineView child : children) {
+                child.layout(indent + 1);
+            }
+        }
+
+        public void updateValues(double cpu, double gpu) {
+            cpuValue += cpu;
+            gpuValue += gpu;
+        }
+
+        public void attachTo(Node node) {
+            node.attachChild(label);
+            node.attachChild(cpuText);
+            node.attachChild(gpuText);
+            node.attachChild(checkBox);
+        }
+
+        public void removeFromParent() {
+            label.removeFromParent();
+            cpuText.removeFromParent();
+            gpuText.removeFromParent();
+            checkBox.removeFromParent();
+        }
+
+        public void reset() {
+            children.clear();
+            cpuValue = 0;
+            gpuValue = 0;
+        }
+
+        public void setVisible(boolean visible) {
+            this.visible = visible;
+            label.setCullHint(visible ? Spatial.CullHint.Dynamic : Spatial.CullHint.Always);
+            cpuText.setCullHint(visible ? Spatial.CullHint.Dynamic : Spatial.CullHint.Always);
+            gpuText.setCullHint(visible ? Spatial.CullHint.Dynamic : Spatial.CullHint.Always);
+            checkBox.setCullHint(visible ? Spatial.CullHint.Dynamic : Spatial.CullHint.Always);
+
+
+            for (StatLineView child : children) {
+                child.setVisible(visible && expended);
+            }
+
+        }
+
+
+        @Override
+        public String toString() {
+            return label.getText() + " - " + df.format(getMsFromNs(cpuValue)) + "ms / " + df.format(getMsFromNs(gpuValue)) + "ms";
+        }
+
+
+    }
+}
+

+ 13 - 0
jme3-core/src/main/java/com/jme3/post/Filter.java

@@ -85,6 +85,14 @@ public abstract class Filter implements Savable {
         protected Texture2D renderedTexture;
         protected Texture2D depthTexture;
         protected Material passMaterial;
+        protected String name;
+
+        public Pass(String name) {
+            this.name = name;
+        }
+
+        public Pass() {
+        }
 
         /**
          * init the pass called internally
@@ -197,6 +205,11 @@ public abstract class Filter implements Savable {
                 depthTexture.getImage().dispose();
             }  
         }
+
+        @Override
+        public String toString() {
+            return name == null ? super.toString() : name;
+        }
     }
 
     /**

+ 15 - 5
jme3-core/src/main/java/com/jme3/post/FilterPostProcessor.java

@@ -34,6 +34,7 @@ package com.jme3.post;
 import com.jme3.asset.AssetManager;
 import com.jme3.export.*;
 import com.jme3.material.Material;
+import com.jme3.profile.*;
 import com.jme3.renderer.*;
 import com.jme3.renderer.queue.RenderQueue;
 import com.jme3.texture.FrameBuffer;
@@ -56,6 +57,7 @@ import java.util.List;
  */
 public class FilterPostProcessor implements SceneProcessor, Savable {
 
+    public static final String FPP = FilterPostProcessor.class.getSimpleName();
     private RenderManager renderManager;
     private Renderer renderer;
     private ViewPort viewPort;
@@ -80,6 +82,7 @@ public class FilterPostProcessor implements SceneProcessor, Savable {
     private int lastFilterIndex = -1;
     private boolean cameraInit = false;
     private boolean multiView = false;
+    private AppProfiler prof;
 
     private Format fbFormat = Format.RGB111110F;
     
@@ -216,7 +219,6 @@ public class FilterPostProcessor implements SceneProcessor, Savable {
         r.setFrameBuffer(buff);        
         r.clearBuffers(true, true, true);
         renderManager.renderGeometry(fsQuad);
-
     }
     
     public boolean isInitialized() {
@@ -224,13 +226,12 @@ public class FilterPostProcessor implements SceneProcessor, Savable {
     }
 
     public void postQueue(RenderQueue rq) {
-
         for (Filter filter : filters.getArray()) {
             if (filter.isEnabled()) {
+                if (prof != null) prof.spStep(SpStep.ProcPostQueue, FPP, filter.getName());
                 filter.postQueue(rq);
             }
         }
-
     }   
 
     /**
@@ -244,10 +245,12 @@ public class FilterPostProcessor implements SceneProcessor, Savable {
         boolean msDepth = depthTexture != null && depthTexture.getImage().getMultiSamples() > 1;
         for (int i = 0; i < filters.size(); i++) {
             Filter filter = filters.get(i);
+            if (prof != null) prof.spStep(SpStep.ProcPostFrame, FPP, filter.getName());
             if (filter.isEnabled()) {
                 if (filter.getPostRenderPasses() != null) {
                     for (Iterator<Filter.Pass> it1 = filter.getPostRenderPasses().iterator(); it1.hasNext();) {
                         Filter.Pass pass = it1.next();
+                        if (prof != null) prof.spStep(SpStep.ProcPostFrame, FPP, filter.getName(), pass.toString());
                         pass.beforeRender();
                         if (pass.requiresSceneAsTexture()) {
                             pass.getPassMaterial().setTexture("Texture", tex);
@@ -269,7 +272,7 @@ public class FilterPostProcessor implements SceneProcessor, Savable {
                         renderProcessing(r, pass.getRenderFrameBuffer(), pass.getPassMaterial());
                     }
                 }
-
+                if (prof != null) prof.spStep(SpStep.ProcPostFrame, FPP, filter.getName(), "postFrame");
                 filter.postFrame(renderManager, viewPort, buff, sceneFb);
 
                 Material mat = filter.getMaterial();
@@ -298,7 +301,9 @@ public class FilterPostProcessor implements SceneProcessor, Savable {
                     tex = filter.getRenderedTexture();
 
                 }
+                if (prof != null) prof.spStep(SpStep.ProcPostFrame, FPP, filter.getName(), "render");
                 renderProcessing(r, buff, mat);
+                if (prof != null) prof.spStep(SpStep.ProcPostFrame, FPP, filter.getName(), "postFilter");
                 filter.postFilter(r, buff);
                 
                 if (wantsBilinear) {
@@ -324,7 +329,6 @@ public class FilterPostProcessor implements SceneProcessor, Savable {
         if (viewPort != null) {
             renderManager.setCamera(viewPort.getCamera(), false);
         }
-
     }
 
     public void preFrame(float tpf) {
@@ -351,6 +355,7 @@ public class FilterPostProcessor implements SceneProcessor, Savable {
 
         for (Filter filter : filters.getArray()) {
             if (filter.isEnabled()) {
+                if (prof != null) prof.spStep(SpStep.ProcPreFrame, FPP, filter.getName());
                 filter.preFrame(tpf);
             }
         }
@@ -419,6 +424,11 @@ public class FilterPostProcessor implements SceneProcessor, Savable {
 
     }
 
+    @Override
+    public void setProfiler(AppProfiler profiler) {
+        this.prof = profiler;
+    }
+
     public void reshape(ViewPort vp, int w, int h) {
         Camera cam = vp.getCamera();
         //this has no effect at first init but is useful when resizing the canvas with multi views

+ 7 - 0
jme3-core/src/main/java/com/jme3/post/HDRRenderer.java

@@ -34,6 +34,7 @@ package com.jme3.post;
 import com.jme3.asset.AssetManager;
 import com.jme3.material.Material;
 import com.jme3.math.Vector2f;
+import com.jme3.profile.AppProfiler;
 import com.jme3.renderer.*;
 import com.jme3.renderer.queue.RenderQueue;
 import com.jme3.texture.FrameBuffer;
@@ -61,6 +62,7 @@ public class HDRRenderer implements SceneProcessor {
     private RenderManager renderManager;
     private ViewPort viewPort;
     private static final Logger logger = Logger.getLogger(HDRRenderer.class.getName());
+    private AppProfiler prof;
 
     private Camera fbCam = new Camera(1, 1);
 
@@ -418,4 +420,9 @@ public class HDRRenderer implements SceneProcessor {
         }
     }
 
+    @Override
+    public void setProfiler(AppProfiler profiler) {
+        this.prof = profiler;
+    }
+
 }

+ 7 - 0
jme3-core/src/main/java/com/jme3/post/PreDepthProcessor.java

@@ -35,6 +35,7 @@ import com.jme3.asset.AssetManager;
 import com.jme3.material.Material;
 import com.jme3.material.RenderState;
 import com.jme3.material.RenderState.FaceCullMode;
+import com.jme3.profile.AppProfiler;
 import com.jme3.renderer.RenderManager;
 import com.jme3.renderer.ViewPort;
 import com.jme3.renderer.queue.RenderQueue;
@@ -51,6 +52,7 @@ public class PreDepthProcessor implements SceneProcessor {
     private AssetManager assetManager;
     private Material preDepth;
     private RenderState forcedRS;
+    private AppProfiler prof;
 
     public PreDepthProcessor(AssetManager assetManager){
         this.assetManager = assetManager;
@@ -96,4 +98,9 @@ public class PreDepthProcessor implements SceneProcessor {
         vp = null;
     }
 
+    @Override
+    public void setProfiler(AppProfiler profiler) {
+        this.prof = profiler;
+    }
+
 }

+ 8 - 0
jme3-core/src/main/java/com/jme3/post/SceneProcessor.java

@@ -31,6 +31,7 @@
  */
 package com.jme3.post;
 
+import com.jme3.profile.AppProfiler;
 import com.jme3.renderer.RenderManager;
 import com.jme3.renderer.ViewPort;
 import com.jme3.renderer.queue.RenderQueue;
@@ -90,4 +91,11 @@ public interface SceneProcessor {
      */
     public void cleanup();
 
+    /**
+     * Sets a profiler Instance for this processor.
+     *
+     * @param profiler the profiler instance.
+     */
+    public void setProfiler(AppProfiler profiler);
+
 }

+ 6 - 1
jme3-core/src/main/java/com/jme3/profile/AppProfiler.java

@@ -57,7 +57,12 @@ public interface AppProfiler {
      *  steps the Bucket parameter will be non-null.
      */
     public void vpStep(VpStep step, ViewPort vp, Bucket bucket);
-    
+
+    /**
+     * Called at the beginning of the specified SpStep (SceneProcessor step).
+     * For more detailed steps it is possible to provide additional information as strings, like the name of the processor.
+     */
+    public void spStep(SpStep step, String... additionalInfo);
 }
 
 

+ 13 - 0
jme3-core/src/main/java/com/jme3/profile/SpStep.java

@@ -0,0 +1,13 @@
+package com.jme3.profile;
+
+/**
+ * Indicates a scene processor-level step within the profiled
+ * frame.
+ * <p>
+ * Created by Nehon on 25/01/2017.
+ */
+public enum SpStep {
+    ProcPreFrame,
+    ProcPostQueue,
+    ProcPostFrame,
+}

+ 2 - 0
jme3-core/src/main/java/com/jme3/profile/VpStep.java

@@ -42,9 +42,11 @@ package com.jme3.profile;
 public enum VpStep {
     BeginRender,
     RenderScene,
+    PreFrame,
     PostQueue,
     FlushQueue,
     PostFrame,
+    ProcEndRender,
     RenderBucket,
     EndRender
 }

+ 8 - 4
jme3-core/src/main/java/com/jme3/renderer/RenderManager.java

@@ -42,9 +42,7 @@ import com.jme3.material.Technique;
 import com.jme3.material.TechniqueDef;
 import com.jme3.math.*;
 import com.jme3.post.SceneProcessor;
-import com.jme3.profile.AppProfiler;
-import com.jme3.profile.AppStep;
-import com.jme3.profile.VpStep;
+import com.jme3.profile.*;
 import com.jme3.renderer.queue.GeometryList;
 import com.jme3.renderer.queue.RenderQueue;
 import com.jme3.renderer.queue.RenderQueue.Bucket;
@@ -1070,10 +1068,13 @@ public class RenderManager {
         }
 
         if (processors != null) {
+            if (prof != null) prof.vpStep(VpStep.PreFrame, vp, null);
             for (SceneProcessor proc : processors.getArray()) {
                 if (!proc.isInitialized()) {
                     proc.initialize(this, vp);
                 }
+                proc.setProfiler(this.prof);
+                if (prof != null) prof.spStep(SpStep.ProcPreFrame, proc.getClass().getSimpleName());
                 proc.preFrame(tpf);
             }
         }
@@ -1098,6 +1099,7 @@ public class RenderManager {
         if (processors != null) {
             if (prof!=null) prof.vpStep(VpStep.PostQueue, vp, null);
             for (SceneProcessor proc : processors.getArray()) {
+                if (prof != null) prof.spStep(SpStep.ProcPostQueue, proc.getClass().getSimpleName());
                 proc.postQueue(vp.getQueue());
             }
         }
@@ -1108,14 +1110,16 @@ public class RenderManager {
         if (processors != null) {
             if (prof!=null) prof.vpStep(VpStep.PostFrame, vp, null);
             for (SceneProcessor proc : processors.getArray()) {
+                if (prof != null) prof.spStep(SpStep.ProcPostFrame, proc.getClass().getSimpleName());
                 proc.postFrame(vp.getOutputFrameBuffer());
             }
+            if (prof != null) prof.vpStep(VpStep.ProcEndRender, vp, null);
         }
         //renders the translucent objects queue after processors have been rendered
         renderTranslucentQueue(vp);
         // clear any remaining spatials that were not rendered.
         clearQueue(vp);
-        
+
         if (prof!=null) prof.vpStep(VpStep.EndRender, vp, null);
     }
     

+ 42 - 0
jme3-core/src/main/java/com/jme3/renderer/Renderer.java

@@ -387,4 +387,46 @@ public interface Renderer {
       */
      public void setLinearizeSrgbImages(boolean linearize);
 
+
+    /**
+     * Generates a pool of gpu queries meant to use as profiling tasks
+     *
+     * @param numTasks the number of task ids to generate
+     * @return an array of tasks ids.
+     */
+    public int[] generateProfilingTasks(int numTasks);
+
+    /**
+     * Starts a time profiling task on the GPU.
+     * This will profile all operations called between startProfiling and stopProfiling
+     *
+     * @param taskId the id of the task to start profiling.
+     */
+    public void startProfiling(int taskId);
+
+    /**
+     * Will stop the last profiling task started with startProfiling
+     */
+    public void stopProfiling();
+
+    /**
+     * Returns the time in nano seconds elapsed for the task with the given id.
+     * Note that the result may not be available right after stopProfiling has been called.
+     * You need to check if the result is available with isTaskResultAvailable.
+     * Also note that it's guaranteed that the result will be available on next frame.
+     * If you use getProfilingTime on the next frame you called stopProfiling, you don't need to check the result availability with isTaskResultAvailable
+     *
+     * @param taskId the id of the task given by startProfiling.
+     * @return the time in nano second of the profiling task with the given id.
+     */
+    public long getProfilingTime(int taskId);
+
+    /**
+     * Check if the profiling results are available
+     *
+     * @param taskId the id of the task provided by startProfiling
+     * @return true if the resulst of the task with the given task id are available.
+     */
+    public boolean isTaskResultAvailable(int taskId);
+
 }

+ 9 - 1
jme3-core/src/main/java/com/jme3/renderer/opengl/GL.java

@@ -126,6 +126,8 @@ public interface GL {
     public static final int GL_OUT_OF_MEMORY = 0x505;
     public static final int GL_POINTS = 0x0;
     public static final int GL_POLYGON_OFFSET_FILL = 0x8037;
+    public static final int GL_QUERY_RESULT = 0x8866;
+    public static final int GL_QUERY_RESULT_AVAILABLE = 0x8867;
     public static final int GL_RED = 0x1903;
     public static final int GL_RENDERER = 0x1F01;
     public static final int GL_REPEAT = 0x2901;
@@ -177,6 +179,7 @@ public interface GL {
     public static final int GL_TEXTURE_MIN_FILTER = 0x2801;
     public static final int GL_TEXTURE_WRAP_S = 0x2802;
     public static final int GL_TEXTURE_WRAP_T = 0x2803;
+    public static final int GL_TIME_ELAPSED = 0x88BF;
     public static final int GL_TRIANGLES = 0x4;
     public static final int GL_TRIANGLE_FAN = 0x6;
     public static final int GL_TRIANGLE_STRIP = 0x5;
@@ -196,6 +199,7 @@ public interface GL {
         
 	public void glActiveTexture(int texture);
 	public void glAttachShader(int program, int shader);
+    public void glBeginQuery(int target, int query);
 	public void glBindBuffer(int target, int buffer);
 	public void glBindTexture(int target, int texture);
 	public void glBlendEquationSeparate(int colorMode, int alphaMode);
@@ -232,8 +236,10 @@ public interface GL {
 	public void glDrawRangeElements(int mode, int start, int end, int count, int type, long indices); /// GL2+
 	public void glEnable(int cap);
 	public void glEnableVertexAttribArray(int index);
+    public void glEndQuery(int target);
 	public void glGenBuffers(IntBuffer buffers);
 	public void glGenTextures(IntBuffer textures);
+    public void glGenQueries(int number, IntBuffer ids);
 	public int glGetAttribLocation(int program, String name);
 	public void glGetBoolean(int pname, ByteBuffer params);
         public void glGetBufferSubData(int target, long offset, ByteBuffer data);
@@ -241,7 +247,9 @@ public interface GL {
 	public void glGetInteger(int pname, IntBuffer params);
 	public void glGetProgram(int program, int pname, IntBuffer params);
 	public String glGetProgramInfoLog(int program, int maxSize);
-	public void glGetShader(int shader, int pname, IntBuffer params);
+    public long glGetQueryObjectui64(int query, int pname);
+    public int glGetQueryObjectiv(int query, int pname);
+    public void glGetShader(int shader, int pname, IntBuffer params);
 	public String glGetShaderInfoLog(int shader, int maxSize);
 	public String glGetString(int name);
 	public int glGetUniformLocation(int program, String name);

+ 31 - 0
jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugES.java

@@ -30,6 +30,12 @@ public class GLDebugES extends GLDebug implements GL, GLFbo, GLExt {
         checkError();
     }
 
+    @Override
+    public void glBeginQuery(int target, int query) {
+        gl.glBeginQuery(target, query);
+        checkError();
+    }
+
     public void glBindBuffer(int target, int buffer) {
         gl.glBindBuffer(target, buffer);
         checkError();
@@ -198,6 +204,11 @@ public class GLDebugES extends GLDebug implements GL, GLFbo, GLExt {
         checkError();
     }
 
+    @Override
+    public void glEndQuery(int target) {
+        checkError();
+    }
+
     public void glGenBuffers(IntBuffer buffers) {
         gl.glGenBuffers(buffers);
         checkError();
@@ -208,6 +219,12 @@ public class GLDebugES extends GLDebug implements GL, GLFbo, GLExt {
         checkError();
     }
 
+    @Override
+    public void glGenQueries(int num, IntBuffer ids) {
+        glGenQueries(num, ids);
+        checkError();
+    }
+
     public int glGetAttribLocation(int program, String name) {
         int location = gl.glGetAttribLocation(program, name);
         checkError();
@@ -240,6 +257,20 @@ public class GLDebugES extends GLDebug implements GL, GLFbo, GLExt {
         return infoLog;
     }
 
+    @Override
+    public long glGetQueryObjectui64(int query, int pname) {
+        long res = gl.glGetQueryObjectui64(query, pname);
+        checkError();
+        return res;
+    }
+
+    @Override
+    public int glGetQueryObjectiv(int query, int pname) {
+        int res = gl.glGetQueryObjectiv(query, pname);
+        checkError();
+        return res;
+    }
+
     public void glGetShader(int shader, int pname, IntBuffer params) {
         gl.glGetShader(shader, pname, params);
         checkError();

+ 27 - 0
jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java

@@ -2851,4 +2851,31 @@ public final class GLRenderer implements Renderer {
             linearizeSrgbImages = linearize;
         }
     }
+
+    @Override
+    public int[] generateProfilingTasks(int numTasks) {
+        IntBuffer ids = BufferUtils.createIntBuffer(numTasks);
+        gl.glGenQueries(numTasks, ids);
+        return BufferUtils.getIntArray(ids);
+    }
+
+    @Override
+    public void startProfiling(int taskId) {
+        gl.glBeginQuery(GL.GL_TIME_ELAPSED, taskId);
+    }
+
+    @Override
+    public void stopProfiling() {
+        gl.glEndQuery(GL.GL_TIME_ELAPSED);
+    }
+
+    @Override
+    public long getProfilingTime(int taskId) {
+        return gl.glGetQueryObjectui64(taskId, GL.GL_QUERY_RESULT);
+    }
+
+    @Override
+    public boolean isTaskResultAvailable(int taskId) {
+        return gl.glGetQueryObjectiv(taskId, GL.GL_QUERY_RESULT_AVAILABLE) == 1;
+    }
 }

+ 6 - 0
jme3-core/src/main/java/com/jme3/shadow/AbstractShadowRenderer.java

@@ -44,6 +44,7 @@ import com.jme3.math.Matrix4f;
 import com.jme3.math.Vector2f;
 import com.jme3.math.Vector3f;
 import com.jme3.post.SceneProcessor;
+import com.jme3.profile.AppProfiler;
 import com.jme3.renderer.Camera;
 import com.jme3.renderer.RenderManager;
 import com.jme3.renderer.Renderer;
@@ -99,6 +100,7 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable,
     protected Picture[] dispPic;
     protected RenderState forcedRenderState = new RenderState();
     protected Boolean renderBackFacesShadows = true;
+    protected AppProfiler prof;
 
     /**
      * true if the fallback material should be used, otherwise false
@@ -807,6 +809,10 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable,
         init(assetManager, nbShadowMaps, (int) shadowMapSize);
     }
 
+    public void setProfiler(AppProfiler profiler) {
+        this.prof = profiler;
+    }
+
     /**
      * De-serialize this instance, for example when loading from a J3O file.
      *

+ 8 - 1
jme3-core/src/main/java/com/jme3/shadow/BasicShadowRenderer.java

@@ -35,6 +35,7 @@ import com.jme3.asset.AssetManager;
 import com.jme3.material.Material;
 import com.jme3.math.Vector3f;
 import com.jme3.post.SceneProcessor;
+import com.jme3.profile.AppProfiler;
 import com.jme3.renderer.Camera;
 import com.jme3.renderer.RenderManager;
 import com.jme3.renderer.Renderer;
@@ -75,7 +76,8 @@ public class BasicShadowRenderer implements SceneProcessor {
 
     protected GeometryList lightReceivers = new GeometryList(new OpaqueComparator());
     protected GeometryList shadowOccluders = new GeometryList(new OpaqueComparator());
-    
+    private AppProfiler prof;
+
     /**
      * Creates a BasicShadowRenderer
      * @param manager the asset manager
@@ -221,6 +223,11 @@ public class BasicShadowRenderer implements SceneProcessor {
     public void cleanup() {
     }
 
+    @Override
+    public void setProfiler(AppProfiler profiler) {
+        this.prof = profiler;
+    }
+
     public void reshape(ViewPort vp, int w, int h) {
         dispPic.setPosition(w / 20f, h / 20f);
         dispPic.setWidth(w / 5f);

+ 8 - 0
jme3-core/src/main/java/com/jme3/shadow/PssmShadowRenderer.java

@@ -38,6 +38,7 @@ import com.jme3.math.Matrix4f;
 import com.jme3.math.Vector2f;
 import com.jme3.math.Vector3f;
 import com.jme3.post.SceneProcessor;
+import com.jme3.profile.AppProfiler;
 import com.jme3.renderer.Camera;
 import com.jme3.renderer.Caps;
 import com.jme3.renderer.RenderManager;
@@ -76,6 +77,8 @@ import java.util.List;
 @Deprecated
 public class PssmShadowRenderer implements SceneProcessor {
 
+    private AppProfiler prof;
+
     /**
      * <code>FilterMode</code> specifies how shadows are filtered
      * @deprecated use {@link EdgeFilteringMode}
@@ -725,6 +728,11 @@ public class PssmShadowRenderer implements SceneProcessor {
         }
     }
 
+    @Override
+    public void setProfiler(AppProfiler profiler) {
+        this.prof = profiler;
+    }
+
     /**
      * get the length over which the shadow will fade out when using a
      * shadowZextend

+ 25 - 0
jme3-core/src/main/java/com/jme3/system/NullRenderer.java

@@ -172,6 +172,31 @@ public class NullRenderer implements Renderer {
     public void setLinearizeSrgbImages(boolean linearize) {    
     }
 
+    @Override
+    public int[] generateProfilingTasks(int numTasks) {
+        return new int[0];
+    }
+
+    @Override
+    public void startProfiling(int id) {
+
+    }
+
+    @Override
+    public void stopProfiling() {
+
+    }
+
+    @Override
+    public long getProfilingTime(int taskId) {
+        return 0;
+    }
+
+    @Override
+    public boolean isTaskResultAvailable(int taskId) {
+        return false;
+    }
+
     public void readFrameBufferWithFormat(FrameBuffer fb, ByteBuffer byteBuf, Image.Format format) {        
     }
 

+ 11 - 12
jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.frag

@@ -148,18 +148,6 @@ void main(){
       vec3 normal = normalize(wNormal);            
     #endif
 
-   
-    #ifdef LIGHTMAP
-       vec3 lightMapColor;
-       #ifdef SEPARATE_TEXCOORD
-          lightMapColor = texture2D(m_LightMap, texCoord2).rgb;
-       #else
-          lightMapColor = texture2D(m_LightMap, texCoord).rgb;
-       #endif
-       specularColor.rgb *= lightMapColor;
-       albedo.rgb  *= lightMapColor;
-    #endif
-
     float specular = 0.5;
     #ifdef SPECGLOSSPIPELINE
           vec4 specularColor = texture2D(m_SpecularMap, newTexCoord);
@@ -171,6 +159,17 @@ void main(){
         vec4 diffuseColor = albedo - albedo * Metallic;
     #endif
 
+    #ifdef LIGHTMAP
+       vec3 lightMapColor;
+       #ifdef SEPARATE_TEXCOORD
+          lightMapColor = texture2D(m_LightMap, texCoord2).rgb;
+       #else
+          lightMapColor = texture2D(m_LightMap, texCoord).rgb;
+       #endif
+       specularColor.rgb *= lightMapColor;
+       albedo.rgb  *= lightMapColor;
+    #endif
+
     gl_FragColor.rgb = vec3(0.0);
     float ndotv = max( dot( normal, viewDir ),0.0);
     for( int i = 0;i < NB_LIGHTS; i+=3){

+ 7 - 0
jme3-desktop/src/main/java/com/jme3/app/state/VideoRecorderAppState.java

@@ -33,6 +33,7 @@ package com.jme3.app.state;
 
 import com.jme3.app.Application;
 import com.jme3.post.SceneProcessor;
+import com.jme3.profile.AppProfiler;
 import com.jme3.renderer.Camera;
 import com.jme3.renderer.RenderManager;
 import com.jme3.renderer.Renderer;
@@ -220,6 +221,7 @@ public class VideoRecorderAppState extends AbstractAppState {
         private LinkedBlockingQueue<WorkItem> freeItems;
         private LinkedBlockingQueue<WorkItem> usedItems = new LinkedBlockingQueue<WorkItem>();
         private MjpegFileWriter writer;
+        private AppProfiler prof;
 
         public void addImage(Renderer renderer, FrameBuffer out) {
             if (freeItems == null) {
@@ -298,6 +300,11 @@ public class VideoRecorderAppState extends AbstractAppState {
             }
             writer = null;
         }
+
+        @Override
+        public void setProfiler(AppProfiler profiler) {
+            this.prof = profiler;
+        }
     }
 
     public static final class IsoTimer extends com.jme3.system.Timer {

+ 7 - 0
jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanel.java

@@ -32,6 +32,7 @@
 package com.jme3.system.awt;
 
 import com.jme3.post.SceneProcessor;
+import com.jme3.profile.AppProfiler;
 import com.jme3.renderer.RenderManager;
 import com.jme3.renderer.ViewPort;
 import com.jme3.renderer.queue.RenderQueue;
@@ -79,6 +80,7 @@ public class AwtPanel extends Canvas implements SceneProcessor {
     private int newHeight = 1;
     private AtomicBoolean reshapeNeeded = new AtomicBoolean(false);
     private final Object lock = new Object();
+    private AppProfiler prof;
 
     public AwtPanel(PaintMode paintMode) {
         this(paintMode, false);
@@ -333,4 +335,9 @@ public class AwtPanel extends Canvas implements SceneProcessor {
     @Override
     public void cleanup() {
     }
+
+    @Override
+    public void setProfiler(AppProfiler profiler) {
+        this.prof = profiler;
+    }
 }

+ 1 - 1
jme3-effects/src/main/java/com/jme3/post/ssao/SSAOFilter.java

@@ -156,7 +156,7 @@ public class SSAOFilter extends Filter {
         random.setWrap(Texture.WrapMode.Repeat);
         ssaoMat.setTexture("RandomMap", random);
 
-        ssaoPass = new Pass() {
+        ssaoPass = new Pass("SSAO pass") {
 
             @Override
             public boolean requiresDepthAsTexture() {

+ 7 - 0
jme3-effects/src/main/java/com/jme3/water/ReflectionProcessor.java

@@ -33,6 +33,7 @@ package com.jme3.water;
 
 import com.jme3.math.Plane;
 import com.jme3.post.SceneProcessor;
+import com.jme3.profile.AppProfiler;
 import com.jme3.renderer.Camera;
 import com.jme3.renderer.RenderManager;
 import com.jme3.renderer.ViewPort;
@@ -50,6 +51,7 @@ public class ReflectionProcessor implements SceneProcessor {
     private Camera reflectionCam;
     private FrameBuffer reflectionBuffer;
     private Plane reflectionClipPlane;
+    private AppProfiler prof;
 
     /**
      * Creates a ReflectionProcessor
@@ -100,6 +102,11 @@ public class ReflectionProcessor implements SceneProcessor {
     public void cleanup() {
     }
 
+    @Override
+    public void setProfiler(AppProfiler profiler) {
+        this.prof = profiler;
+    }
+
     /**
      * Internal use only<br>
      * returns the frame buffer

+ 17 - 10
jme3-effects/src/main/java/com/jme3/water/SimpleWaterProcessor.java

@@ -35,21 +35,16 @@ import com.jme3.asset.AssetManager;
 import com.jme3.material.Material;
 import com.jme3.math.*;
 import com.jme3.post.SceneProcessor;
-import com.jme3.renderer.Camera;
-import com.jme3.renderer.RenderManager;
-import com.jme3.renderer.Renderer;
-import com.jme3.renderer.ViewPort;
+import com.jme3.profile.AppProfiler;
+import com.jme3.renderer.*;
 import com.jme3.renderer.queue.RenderQueue;
 import com.jme3.scene.Geometry;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.shape.Quad;
-import com.jme3.texture.FrameBuffer;
+import com.jme3.texture.*;
 import com.jme3.texture.Image.Format;
-import com.jme3.texture.Texture;
 import com.jme3.texture.Texture.WrapMode;
-import com.jme3.texture.Texture2D;
 import com.jme3.ui.Picture;
-import com.jme3.util.TempVars;
 
 /**
  *
@@ -125,8 +120,9 @@ public class SimpleWaterProcessor implements SceneProcessor {
     private float distortionScale = 0.2f;
     private float distortionMix = 0.5f;
     private float texScale = 1f;
-    
-       
+    private AppProfiler prof;
+
+
     /**
      * Creates a SimpleWaterProcessor
      * @param manager the asset manager
@@ -222,6 +218,11 @@ public class SimpleWaterProcessor implements SceneProcessor {
     public void cleanup() {
     }
 
+    @Override
+    public void setProfiler(AppProfiler profiler) {
+        this.prof = profiler;
+    }
+
     //debug only : displays maps
     protected void displayMap(Renderer r, Picture pic, int left) {
         Camera cam = vp.getCamera();
@@ -585,6 +586,7 @@ public class SimpleWaterProcessor implements SceneProcessor {
 
         RenderManager rm;
         ViewPort vp;
+        private AppProfiler prof;
 
         public void initialize(RenderManager rm, ViewPort vp) {
             this.rm = rm;
@@ -611,5 +613,10 @@ public class SimpleWaterProcessor implements SceneProcessor {
 
         public void cleanup() {
         }
+
+        @Override
+        public void setProfiler(AppProfiler profiler) {
+            this.prof = profiler;
+        }
     }
 }

+ 2 - 2
jme3-effects/src/main/resources/Common/MatDefs/SSAO/ssao.frag

@@ -33,8 +33,8 @@ vec3 getPosition(float depthv, in vec2 uv){
 vec3 approximateNormal(in vec3 pos,in vec2 texCoord){
     float step = g_ResolutionInverse.x ;
     float stepy = g_ResolutionInverse.y ;
-    float depth2 = texture2D(m_DepthTexture,texCoord + vec2(step,-stepy)).r;
-    float depth3 = texture2D(m_DepthTexture,texCoord + vec2(-step,-stepy)).r;
+    float depth2 = getDepth(m_DepthTexture,texCoord + vec2(step,-stepy)).r;
+    float depth3 = getDepth(m_DepthTexture,texCoord + vec2(-step,-stepy)).r;
     vec3 pos2 = vec3(getPosition(depth2,texCoord + vec2(step,-stepy)));
     vec3 pos3 = vec3(getPosition(depth3,texCoord + vec2(-step,-stepy)));
 

+ 1 - 1
jme3-examples/src/main/java/jme3test/post/SSAOUI.java

@@ -52,7 +52,7 @@ public class SSAOUI {
     }
 
     private void init(InputManager inputManager) {
-        System.out.println("----------------- Water UI Debugger --------------------");
+        System.out.println("----------------- SSAO UI Debugger --------------------");
         System.out.println("-- Sample Radius : press Y to increase, H to decrease");
         System.out.println("-- AO Intensity : press U to increase, J to decrease");
         System.out.println("-- AO scale : press I to increase, K to decrease");

+ 6 - 0
jme3-examples/src/main/java/jme3test/post/TestMultiRenderTarget.java

@@ -41,6 +41,7 @@ import com.jme3.math.Matrix4f;
 import com.jme3.math.Quaternion;
 import com.jme3.math.Vector3f;
 import com.jme3.post.SceneProcessor;
+import com.jme3.profile.AppProfiler;
 import com.jme3.renderer.RenderManager;
 import com.jme3.renderer.ViewPort;
 import com.jme3.renderer.queue.RenderQueue;
@@ -231,4 +232,9 @@ public class TestMultiRenderTarget extends SimpleApplication implements ScenePro
     public void cleanup() {
     }
 
+    @Override
+    public void setProfiler(AppProfiler profiler) {
+
+    }
+
 }

+ 6 - 0
jme3-examples/src/main/java/jme3test/post/TestRenderToMemory.java

@@ -39,6 +39,7 @@ import com.jme3.math.FastMath;
 import com.jme3.math.Quaternion;
 import com.jme3.math.Vector3f;
 import com.jme3.post.SceneProcessor;
+import com.jme3.profile.AppProfiler;
 import com.jme3.renderer.Camera;
 import com.jme3.renderer.RenderManager;
 import com.jme3.renderer.ViewPort;
@@ -256,5 +257,10 @@ public class TestRenderToMemory extends SimpleApplication implements SceneProces
     public void cleanup() {
     }
 
+    @Override
+    public void setProfiler(AppProfiler profiler) {
+
+    }
+
 
 }

+ 12 - 3
jme3-examples/src/main/java/jme3test/post/TestSSAO2.java

@@ -36,11 +36,10 @@ import com.jme3.light.*;
 import com.jme3.material.Material;
 import com.jme3.math.*;
 import com.jme3.post.FilterPostProcessor;
+import com.jme3.post.DetailedProfilerState;
 import com.jme3.post.ssao.SSAOFilter;
 import com.jme3.scene.*;
-import com.jme3.scene.control.LodControl;
 import com.jme3.scene.shape.Box;
-import com.jme3.texture.Texture;
 
 public class TestSSAO2 extends SimpleApplication {
 
@@ -57,6 +56,11 @@ public class TestSSAO2 extends SimpleApplication {
         dl.setDirection(new Vector3f(-1,-1,-1).normalizeLocal());
         rootNode.addLight(dl);
 
+        flyCam.setDragToRotate(true);
+        setPauseOnLostFocus(false);
+
+        getStateManager().attach(new DetailedProfilerState());
+
         Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
         mat.setFloat("Shininess", 16f);
         //mat.setBoolean("VertexLighting", true);
@@ -96,7 +100,12 @@ public class TestSSAO2 extends SimpleApplication {
 
         FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
         SSAOFilter ssaoFilter = new SSAOFilter(2.9299974f,25f,5.8100376f,0.091000035f);
-        ssaoFilter.setApproximateNormals(true);
+        int numSamples = context.getSettings().getSamples();
+        if (numSamples > 0) {
+            fpp.setNumSamples(numSamples);
+        }
+
+        //ssaoFilter.setApproximateNormals(true);
         fpp.addFilter(ssaoFilter);
         SSAOUI ui = new SSAOUI(inputManager, ssaoFilter);
 

+ 6 - 3
jme3-examples/src/main/java/jme3test/stress/TestShaderNodesStress.java

@@ -6,9 +6,7 @@ import com.jme3.material.Material;
 import com.jme3.math.ColorRGBA;
 import com.jme3.math.Quaternion;
 import com.jme3.math.Vector3f;
-import com.jme3.profile.AppProfiler;
-import com.jme3.profile.AppStep;
-import com.jme3.profile.VpStep;
+import com.jme3.profile.*;
 import com.jme3.renderer.ViewPort;
 import com.jme3.renderer.queue.RenderQueue;
 import com.jme3.scene.Geometry;
@@ -98,5 +96,10 @@ public class TestShaderNodesStress extends SimpleApplication {
 
         }
 
+        @Override
+        public void spStep(SpStep step, String... additionalInfo) {
+
+        }
+
     }
 }

+ 25 - 0
jme3-ios/src/main/java/com/jme3/renderer/ios/IosGL.java

@@ -124,6 +124,11 @@ public class IosGL implements GL, GLExt, GLFbo {
         JmeIosGLES.glAttachShader(program, shader);
     }
 
+    @Override
+    public void glBeginQuery(int target, int query) {
+        throw new UnsupportedOperationException("Today is not a good day for this");
+    }
+
     public void glBindBuffer(int target, int buffer) {
         JmeIosGLES.glBindBuffer(target, buffer);
     }
@@ -269,6 +274,11 @@ public class IosGL implements GL, GLExt, GLFbo {
         JmeIosGLES.glEnableVertexAttribArray(index);
     }
 
+    @Override
+    public void glEndQuery(int target) {
+        throw new UnsupportedOperationException("Today is not a good day for this");
+    }
+
     public void glGenBuffers(IntBuffer buffers) {
         checkLimit(buffers);
         JmeIosGLES.glGenBuffers(buffers.remaining(), temp_array, 0);
@@ -281,6 +291,11 @@ public class IosGL implements GL, GLExt, GLFbo {
         fromArray(textures.remaining(), temp_array, textures);
     }
 
+    @Override
+    public void glGenQueries(int num, IntBuffer buff) {
+        throw new UnsupportedOperationException("Today is not a good day for this");
+    }
+
     public int glGetAttribLocation(int program, String name) {
         return JmeIosGLES.glGetAttribLocation(program, name);
     }
@@ -311,6 +326,16 @@ public class IosGL implements GL, GLExt, GLFbo {
         return JmeIosGLES.glGetProgramInfoLog(program);
     }
 
+    @Override
+    public long glGetQueryObjectui64(int query, int pname) {
+        throw new UnsupportedOperationException("Today is not a good day for this");
+    }
+
+    @Override
+    public int glGetQueryObjectiv(int query, int pname) {
+        throw new UnsupportedOperationException("Today is not a good day for this");
+    }
+
     public void glGetShader(int shader, int pname, IntBuffer params) {
         checkLimit(params);
         JmeIosGLES.glGetShaderiv(shader, pname, temp_array, 0);

+ 33 - 9
jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglGL.java

@@ -5,12 +5,7 @@ import com.jme3.renderer.opengl.GL;
 import com.jme3.renderer.opengl.GL2;
 import com.jme3.renderer.opengl.GL3;
 
-import java.nio.Buffer;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.FloatBuffer;
-import java.nio.IntBuffer;
-import java.nio.ShortBuffer;
+import java.nio.*;
 
 import com.jme3.renderer.opengl.GL4;
 import com.jogamp.opengl.GLContext;
@@ -69,7 +64,12 @@ public class JoglGL implements GL, GL2, GL3, GL4 {
     }
 
     @Override
-	public void glBindBuffer(int param1, int param2) {
+    public void glBeginQuery(int target, int query) {
+        GLContext.getCurrentGL().getGL2ES2().glBeginQuery(target, query);
+    }
+
+    @Override
+    public void glBindBuffer(int param1, int param2) {
         GLContext.getCurrentGL().glBindBuffer(param1, param2);
     }
 
@@ -271,13 +271,23 @@ public class JoglGL implements GL, GL2, GL3, GL4 {
     }
 
     @Override
-	public void glGenBuffers(IntBuffer param1) {
+    public void glEndQuery(int target) {
+        GLContext.getCurrentGL().getGL2ES2().glEndQuery(target);
+    }
+
+    @Override
+    public void glGenBuffers(IntBuffer param1) {
         checkLimit(param1);
         GLContext.getCurrentGL().glGenBuffers(param1.limit(), param1);
     }
 
     @Override
-	public void glGenTextures(IntBuffer param1) {
+    public void glGenQueries(int num, IntBuffer buff) {
+        GLContext.getCurrentGL().getGL2ES2().glGenQueries(num, buff);
+    }
+
+    @Override
+    public void glGenTextures(IntBuffer param1) {
         checkLimit(param1);
         GLContext.getCurrentGL().glGenTextures(param1.limit(), param1);
     }
@@ -569,6 +579,20 @@ public class JoglGL implements GL, GL2, GL3, GL4 {
 		return new String(bytes);
     }
 
+    @Override
+    public long glGetQueryObjectui64(int query, int target) {
+        LongBuffer buff = LongBuffer.allocate(1);
+        GLContext.getCurrentGL().getGL2ES2().glGetQueryObjectui64v(query, target, buff);
+        return buff.get(0);
+    }
+
+    @Override
+    public int glGetQueryObjectiv(int query, int pname) {
+        IntBuffer buff = IntBuffer.allocate(1);
+        GLContext.getCurrentGL().getGL2ES2().glGetQueryObjectiv(query, pname, buff);
+        return buff.get(0);
+    }
+
     @Override
 	public String glGetShaderInfoLog(int shader, int maxSize) {
     	ByteBuffer buffer = ByteBuffer.allocateDirect(maxSize);

+ 28 - 0
jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java

@@ -11,9 +11,12 @@ import java.nio.IntBuffer;
 import java.nio.ShortBuffer;
 
 import com.jme3.renderer.opengl.GL4;
+import com.jme3.util.BufferUtils;
 import org.lwjgl.opengl.*;
 
 public final class LwjglGL implements GL, GL2, GL3, GL4 {
+
+    IntBuffer tmpBuff = BufferUtils.createIntBuffer(1);
     
     private static void checkLimit(Buffer buffer) {
         if (buffer == null) {
@@ -42,6 +45,11 @@ public final class LwjglGL implements GL, GL2, GL3, GL4 {
         GL20.glAttachShader(param1, param2);
     }
 
+    @Override
+    public void glBeginQuery(int target, int query) {
+        GL15.glBeginQuery(target, query);
+    }
+
     public void glBindBuffer(int param1, int param2) {
         GL15.glBindBuffer(param1, param2);
     }
@@ -206,11 +214,21 @@ public final class LwjglGL implements GL, GL2, GL3, GL4 {
         GL20.glEnableVertexAttribArray(param1);
     }
 
+    @Override
+    public void glEndQuery(int target) {
+        GL15.glEndQuery(target);
+    }
+
     public void glGenBuffers(IntBuffer param1) {
         checkLimit(param1);
         GL15.glGenBuffers(param1);
     }
 
+    @Override
+    public void glGenQueries(int num, IntBuffer ids) {
+        GL15.glGenQueries(ids);
+    }
+
     public void glGenTextures(IntBuffer param1) {
         checkLimit(param1);
         GL11.glGenTextures(param1);
@@ -435,6 +453,16 @@ public final class LwjglGL implements GL, GL2, GL3, GL4 {
         return GL20.glGetProgramInfoLog(program, maxSize);
     }
 
+    @Override
+    public long glGetQueryObjectui64(int query, int target) {
+        return ARBTimerQuery.glGetQueryObjectui64(query, target);
+    }
+
+    @Override
+    public int glGetQueryObjectiv(int query, int pname) {
+        return GL15.glGetQueryObjecti(query, pname);
+    }
+
     public String glGetShaderInfoLog(int shader, int maxSize) {
         return GL20.glGetShaderInfoLog(shader, maxSize);
     }

+ 25 - 0
jme3-lwjgl3/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java

@@ -71,6 +71,11 @@ public class LwjglGL implements GL, GL2, GL3, GL4 {
         GL20.glAttachShader(param1, param2);
     }
 
+    @Override
+    public void glBeginQuery(int target, int query) {
+        GL15.glBeginQuery(target, query);
+    }
+
     public void glBindBuffer(int param1, int param2) {
         GL15.glBindBuffer(param1, param2);
     }
@@ -235,6 +240,11 @@ public class LwjglGL implements GL, GL2, GL3, GL4 {
         GL20.glEnableVertexAttribArray(param1);
     }
 
+    @Override
+    public void glEndQuery(int target) {
+        GL15.glEndQuery(target);
+    }
+
     public void glGenBuffers(IntBuffer param1) {
         checkLimit(param1);
         GL15.glGenBuffers(param1);
@@ -245,6 +255,11 @@ public class LwjglGL implements GL, GL2, GL3, GL4 {
         GL11.glGenTextures(param1);
     }
 
+    @Override
+    public void glGenQueries(int num, IntBuffer ids) {
+        GL15.glGenQueries(ids);
+    }
+
     public void glGetBoolean(int param1, ByteBuffer param2) {
         checkLimit(param2);
         GL11.glGetBooleanv(param1, param2);
@@ -464,6 +479,16 @@ public class LwjglGL implements GL, GL2, GL3, GL4 {
         return GL20.glGetProgramInfoLog(program, maxSize);
     }
 
+    @Override
+    public long glGetQueryObjectui64(int query, int target) {
+        return ARBTimerQuery.glGetQueryObjectui64(query, target);
+    }
+
+    @Override
+    public int glGetQueryObjectiv(int query, int pname) {
+        return GL15.glGetQueryObjecti(query, pname);
+    }
+
     public String glGetShaderInfoLog(int shader, int maxSize) {
         return GL20.glGetShaderInfoLog(shader, maxSize);
     }

+ 7 - 0
jme3-niftygui/src/main/java/com/jme3/niftygui/NiftyJmeDisplay.java

@@ -42,6 +42,7 @@ import com.jme3.audio.AudioRenderer;
 import com.jme3.input.InputManager;
 import com.jme3.input.event.KeyInputEvent;
 import com.jme3.post.SceneProcessor;
+import com.jme3.profile.AppProfiler;
 import com.jme3.renderer.RenderManager;
 import com.jme3.renderer.Renderer;
 import com.jme3.renderer.ViewPort;
@@ -71,6 +72,7 @@ public class NiftyJmeDisplay implements SceneProcessor {
     protected ResourceLocationJme resourceLocation;
 
     protected int w, h;
+    private AppProfiler prof;
 
     protected class ResourceLocationJme implements ResourceLocation {
 
@@ -359,4 +361,9 @@ public class NiftyJmeDisplay implements SceneProcessor {
         }
     }
 
+    @Override
+    public void setProfiler(AppProfiler profiler) {
+        this.prof = profiler;
+    }
+
 }