Procházet zdrojové kódy

Merge branch 'modularpbr2' into patch-3

Riccardo Balbo před 1 rokem
rodič
revize
0ee174f4af
43 změnil soubory, kde provedl 2861 přidání a 422 odebrání
  1. 5 5
      .github/workflows/main.yml
  2. 1 0
      README.md
  3. 1 1
      build.gradle
  4. 1 1
      gradle/wrapper/gradle-wrapper.properties
  5. 50 8
      jme3-core/src/main/java/com/jme3/anim/tween/action/ClipAction.java
  6. 236 0
      jme3-core/src/main/java/com/jme3/app/state/CompositeAppState.java
  7. 5 17
      jme3-core/src/main/java/com/jme3/environment/util/BoundingSphereDebug.java
  8. 41 30
      jme3-core/src/main/java/com/jme3/material/MatParam.java
  9. 10 9
      jme3-core/src/main/java/com/jme3/material/Material.java
  10. 2 1
      jme3-core/src/main/java/com/jme3/material/RenderState.java
  11. 24 0
      jme3-core/src/main/java/com/jme3/math/Vector2f.java
  12. 55 43
      jme3-core/src/main/java/com/jme3/shader/VarType.java
  13. 3 5
      jme3-core/src/main/java/com/jme3/shadow/AbstractShadowFilter.java
  14. 4 8
      jme3-core/src/main/java/com/jme3/shadow/AbstractShadowRenderer.java
  15. 28 33
      jme3-core/src/main/java/com/jme3/shadow/DirectionalLightShadowFilter.java
  16. 14 19
      jme3-core/src/main/java/com/jme3/shadow/PointLightShadowFilter.java
  17. 6 3
      jme3-core/src/main/java/com/jme3/shadow/ShadowUtil.java
  18. 14 19
      jme3-core/src/main/java/com/jme3/shadow/SpotLightShadowFilter.java
  19. 23 32
      jme3-core/src/main/java/com/jme3/util/MaterialDebugAppState.java
  20. 73 0
      jme3-core/src/main/resources/Common/MatDefs/Light/modular/PBRLighting.frag
  21. 321 0
      jme3-core/src/main/resources/Common/MatDefs/Light/modular/PBRLighting.j3md
  22. 75 0
      jme3-core/src/main/resources/Common/MatDefs/Light/modular/PBRLighting.vert
  23. 14 1
      jme3-core/src/main/resources/Common/ShaderLib/Math.glsllib
  24. 34 0
      jme3-core/src/main/resources/Common/ShaderLib/module/Light.glsl
  25. 39 0
      jme3-core/src/main/resources/Common/ShaderLib/module/PBRSurface.glsl
  26. 600 0
      jme3-core/src/main/resources/Common/ShaderLib/module/pbrlighting/PBRLightingUtils.glsllib
  27. 48 0
      jme3-core/src/test/java/com/jme3/shader/GLSLPreprocessorTest.java
  28. 8 0
      jme3-core/src/test/resources/GLSLPreprocessorTest.testStruct.validOutput
  29. 17 0
      jme3-core/src/test/resources/GLSLPreprocessorTest.testStructExtends.validOutput
  30. 21 0
      jme3-core/src/test/resources/GLSLPreprocessorTest.testStructExtendsMulti.validOutput
  31. 93 0
      jme3-core/src/tools/java/jme3tools/shader/Preprocessor.java
  32. 2 2
      jme3-examples/build.gradle
  33. 1 2
      jme3-examples/src/main/java/jme3test/awt/TestCanvas.java
  34. 3 0
      jme3-lwjgl3/build.gradle
  35. 623 168
      jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java
  36. 14 10
      jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java
  37. 0 3
      jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/Sync.java
  38. 66 0
      jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/LwjglxDefaultGLPlatform.java
  39. 48 0
      jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/LwjglxGLPlatform.java
  40. 56 0
      jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/MacOSXGLPlatform.java
  41. 56 0
      jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/Win32GLPlatform.java
  42. 123 0
      jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/X11GLPlatform.java
  43. 3 2
      jme3-lwjgl3/src/main/java/com/jme3/util/LWJGLBufferAllocator.java

+ 5 - 5
.github/workflows/main.yml

@@ -69,7 +69,7 @@ jobs:
         with:
           fetch-depth: 1
       - name: Validate the Gradle wrapper
-        uses: gradle/wrapper-validation-action@v1
+        uses: gradle/wrapper-validation-action@v2
       - name: Build
         run: |
           ./gradlew -PuseCommitHashAsVersionName=true --no-daemon -PbuildNativeProjects=true \
@@ -113,7 +113,7 @@ jobs:
           fetch-depth: 1
 
       - name: Setup the java environment
-        uses: actions/setup-java@v3
+        uses: actions/setup-java@v4
         with:
           distribution: 'temurin'
           java-version: ${{ matrix.jdk }}
@@ -125,7 +125,7 @@ jobs:
           path: build/native
 
       - name: Validate the Gradle wrapper
-        uses: gradle/wrapper-validation-action@v1
+        uses: gradle/wrapper-validation-action@v2
       - name: Build Engine
         shell: bash
         run: |
@@ -307,7 +307,7 @@ jobs:
 
       # Setup jdk 17 used for building Maven-style artifacts
       - name: Setup the java environment
-        uses: actions/setup-java@v3
+        uses: actions/setup-java@v4
         with:
           distribution: 'temurin'
           java-version: '17'
@@ -351,7 +351,7 @@ jobs:
 
       # Setup jdk 17 used for building Sonatype OSSRH artifacts
       - name: Setup the java environment
-        uses: actions/setup-java@v3
+        uses: actions/setup-java@v4
         with:
           distribution: 'temurin'
           java-version: '17'

+ 1 - 0
README.md

@@ -32,6 +32,7 @@ The engine is used by several commercial game studios and computer-science cours
  - [Boxer (on Google Play)](https://play.google.com/store/apps/details?id=com.tharg.boxer)
  - [Depthris (on Itch)](https://codewalker.itch.io/depthris)
  - [Stranded (on Itch)](https://tgiant.itch.io/stranded)
+ - [The Afflicted Forests (Coming Soon to Steam)](https://www.indiedb.com/games/the-afflicted-forests)
 
 ## Getting Started
 

+ 1 - 1
build.gradle

@@ -283,7 +283,7 @@ if (skipPrebuildLibraries != "true" && buildNativeProjects != "true") {
 //}
 
 wrapper {
-    gradleVersion = '7.6.2'
+    gradleVersion = '7.6.4'
 }
 
 

+ 1 - 1
gradle/wrapper/gradle-wrapper.properties

@@ -1,6 +1,6 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-bin.zip
 networkTimeout=10000
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists

+ 50 - 8
jme3-core/src/main/java/com/jme3/anim/tween/action/ClipAction.java

@@ -1,15 +1,52 @@
+/*
+ * Copyright (c) 2009-2024 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.anim.tween.action;
 
-import com.jme3.anim.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import com.jme3.anim.AnimClip;
+import com.jme3.anim.AnimTrack;
+import com.jme3.anim.MorphTrack;
+import com.jme3.anim.TransformTrack;
+import com.jme3.anim.tween.action.BlendableAction;
 import com.jme3.anim.util.HasLocalTransform;
 import com.jme3.math.Transform;
 import com.jme3.scene.Geometry;
 import com.jme3.util.clone.Cloner;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
 
 public class ClipAction extends BlendableAction {
+    
     private AnimClip clip;
     private Transform transform = new Transform();
 
@@ -59,8 +96,13 @@ public class ClipAction extends BlendableAction {
 //        }
     }
 
-    public void reset() {
-
+    /**
+     * Gets the animation clip associated with this action.
+     * 
+     * @return The animation clip
+     */
+    public AnimClip getAnimClip() {
+        return clip;
     }
 
     @Override
@@ -100,8 +142,8 @@ public class ClipAction extends BlendableAction {
         try {
             ClipAction clone = (ClipAction) super.clone();
             return clone;
-        } catch (CloneNotSupportedException exception) {
-            throw new RuntimeException(exception);
+        } catch (CloneNotSupportedException ex) {
+            throw new RuntimeException(ex);
         }
     }
 

+ 236 - 0
jme3-core/src/main/java/com/jme3/app/state/CompositeAppState.java

@@ -0,0 +1,236 @@
+/*
+ * 
+ * Copyright (c) 2014-2024 jMonkeyEngine
+ * Copied with Paul Speed's permission from: https://github.com/Simsilica/SiO2
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions 
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright 
+ *    notice, this list of conditions and the following disclaimer.
+ * 
+ * 2. 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.
+ * 
+ * 3. Neither the name of the copyright holder 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 HOLDER 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.state;
+
+import com.jme3.app.Application;
+import com.jme3.util.SafeArrayList;
+
+/**
+ *  An AppState that manages a set of child app states, making sure
+ *  they are attached/detached and optional enabled/disabled with the
+ *  parent state.
+ *
+ *  @author    Paul Speed
+ */
+public class CompositeAppState extends BaseAppState {
+
+    private final SafeArrayList<AppStateEntry> states = new SafeArrayList<>(AppStateEntry.class);
+    private boolean childrenEnabled;
+    
+    /**
+     *  Since we manage attachmend/detachment possibly before
+     *  initialization, we need to keep track of the stateManager we
+     *  were given in stateAttached() in case we have to attach another
+     *  child prior to initialization (but after we're attached).
+     *  It's possible that we should actually be waiting for initialize
+     *  to add these but I feel like there was some reason I did it this 
+     *  way originally.  Past-me did not leave any clues.
+     */
+    private AppStateManager stateManager;
+    private boolean attached;  
+    
+    public CompositeAppState( AppState... states ) {
+        for( AppState a : states ) {
+            this.states.add(new AppStateEntry(a, false));
+        }
+    }
+
+    private int indexOf( AppState state ) {
+        for( int i = 0; i < states.size(); i++ ) {
+            AppStateEntry e = states.get(i);
+            if( e.state == state ) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    private AppStateEntry entry( AppState state ) {
+        for( AppStateEntry e : states.getArray() ) {
+            if( e.state == state ) {
+                return e;
+            }
+        }
+        return null;
+    }
+
+    protected <T extends AppState> T addChild( T state ) {
+        return addChild(state, false);
+    }
+    
+    protected <T extends AppState> T addChild( T state, boolean overrideEnable ) {
+        if( indexOf(state) >= 0 ) {
+            return state;
+        }
+        states.add(new AppStateEntry(state, overrideEnable));
+        if( attached ) {
+            stateManager.attach(state);
+        }
+        return state;   
+    }
+ 
+    protected void removeChild( AppState state ) {
+        int index = indexOf(state);
+        if( index < 0 ) {
+            return;
+        }
+        states.remove(index);
+        if( attached ) {
+            stateManager.detach(state);
+        }
+    }
+    
+    protected <T extends AppState> T getChild( Class<T> stateType ) {
+        for( AppStateEntry e : states.getArray() ) {
+            if( stateType.isInstance(e.state) ) {
+                return stateType.cast(e.state);
+            }
+        }
+        return null;
+    }
+
+    protected void clearChildren() {
+        for( AppStateEntry e : states.getArray() ) {
+            removeChild(e.state);
+        }
+    }
+    
+    @Override 
+    public void stateAttached( AppStateManager stateManager ) {
+        this.stateManager = stateManager;
+        for( AppStateEntry e : states.getArray() ) {
+            stateManager.attach(e.state);
+        }
+        this.attached = true;
+    }
+    
+    @Override
+    public void stateDetached( AppStateManager stateManager ) {
+        // Reverse order
+        for( int i = states.size() - 1; i >= 0; i-- ) {
+            stateManager.detach(states.get(i).state);
+        }
+        this.attached = false;
+        this.stateManager = null;
+    }
+
+    protected void setChildrenEnabled( boolean b ) {
+        if( childrenEnabled == b ) {
+            return;
+        }
+        childrenEnabled = b;
+        for( AppStateEntry e : states.getArray() ) {
+            e.setEnabled(b);
+        }
+    }
+
+    /**
+     *  Overrides the automatic synching of a child's enabled state.
+     *  When override is true, a child will remember its old state when
+     *  the parent's enabled state is false so that when the parent is
+     *  re-enabled the child can resume its previous enabled state.  This
+     *  is useful for the cases where a child may want to be disabled
+     *  independent of the parent... and then not automatically become
+     *  enabled just because the parent does.
+     *  Currently, the parent's disabled state always disables the children,
+     *  too.  Override is about remembering the child's state before that
+     *  happened and restoring it when the 'family' is enabled again as a whole.
+     */
+    public void setOverrideEnabled( AppState state, boolean override ) {
+        AppStateEntry e = entry(state);
+        if( e == null ) {
+            throw new IllegalArgumentException("State not managed:" + state);
+        }
+        if( override ) {
+            e.override = true;
+        } else {
+            e.override = false;
+            e.state.setEnabled(isEnabled());
+        }   
+    }
+
+    @Override
+    protected void initialize(Application app) {
+    }
+
+    @Override
+    protected void cleanup(Application app) {
+    }
+
+    @Override
+    protected void onEnable() {
+        setChildrenEnabled(true);
+    }
+
+    @Override
+    protected void onDisable() {
+        setChildrenEnabled(false);
+    }
+    
+    private class AppStateEntry {
+        AppState state;
+        boolean enabled;
+        boolean override;
+        
+        public AppStateEntry( AppState state, boolean overrideEnable ) {
+            this.state = state;
+            this.override = overrideEnable;
+            this.enabled = state.isEnabled();
+        }
+        
+        public void setEnabled( boolean b ) {
+ 
+            if( override ) {
+                if( b ) {
+                    // Set it to whatever its enabled state
+                    // was before going disabled last time.
+                    state.setEnabled(enabled);
+                } else {
+                    // We are going to set enabled to false
+                    // but keep track of what it was before we did
+                    // that
+                    this.enabled = state.isEnabled();
+                    state.setEnabled(false);
+                }               
+            } else {
+                // Just synch it always
+                state.setEnabled(b);
+            }
+        }
+    }
+}
+

+ 5 - 17
jme3-core/src/main/java/com/jme3/environment/util/BoundingSphereDebug.java

@@ -1,5 +1,5 @@
  /*
- * Copyright (c) 2009-2021 jMonkeyEngine
+ * Copyright (c) 2009-2024 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -56,14 +56,6 @@ public class BoundingSphereDebug extends Mesh {
     protected int radialSamples = 32;
     protected boolean useEvenSlices;
     protected boolean interior;
-    /**
-     * the distance from the center point each point falls on
-     */
-    public float radius;
-
-    public float getRadius() {
-        return radius;
-    }
 
     public BoundingSphereDebug() {
         setGeometryData();
@@ -151,27 +143,23 @@ public class BoundingSphereDebug extends Mesh {
             if (segDone == radialSamples || segDone == radialSamples * 2) {
                 idx++;
             }
-
         }
-
     }
-
     
     /**
      * Convenience factory method that creates a debug bounding-sphere geometry
+     * 
      * @param assetManager the assetManager
      * @return the bounding sphere debug geometry.
      */
     public static Geometry createDebugSphere(AssetManager assetManager) {
-        BoundingSphereDebug b = new BoundingSphereDebug();
-        Geometry geom = new Geometry("BoundingDebug", b);
-
+        BoundingSphereDebug mesh = new BoundingSphereDebug();
+        Geometry geom = new Geometry("BoundingDebug", mesh);
         Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
         mat.setBoolean("VertexColor", true);
         mat.getAdditionalRenderState().setWireframe(true);
-        
         geom.setMaterial(mat);
         return geom;
-
     }
+    
 }

+ 41 - 30
jme3-core/src/main/java/com/jme3/material/MatParam.java

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2021 jMonkeyEngine
+ * Copyright (c) 2009-2024 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -31,16 +31,26 @@
  */
 package com.jme3.material;
 
+import java.io.IOException;
+import java.util.Arrays;
+
 import com.jme3.asset.TextureKey;
-import com.jme3.export.*;
-import com.jme3.math.*;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.export.Savable;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Matrix3f;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.math.Vector4f;
 import com.jme3.shader.VarType;
 import com.jme3.texture.Texture;
 import com.jme3.texture.Texture.WrapMode;
 
-import java.io.IOException;
-import java.util.Arrays;
-
 /**
  * Describes a material parameter. This is used for both defining a name and type
  * as well as a material parameter value.
@@ -58,8 +68,8 @@ public class MatParam implements Savable, Cloneable {
     /**
      * Create a new material parameter. For internal use only.
      *
-     * @param type the type of the parameter
-     * @param name the desired parameter name
+     * @param type  the type of the parameter
+     * @param name  the desired parameter name
      * @param value the desired parameter value (alias created)
      */
     public MatParam(VarType type, String name, Object value) {
@@ -75,20 +85,19 @@ public class MatParam implements Savable, Cloneable {
     protected MatParam() {
     }
 
-
     public boolean isTypeCheckEnabled() {
         return typeCheck;
     }
 
-
     /**
      * Enable type check for this param.
      * When type check is enabled a RuntimeException is thrown if 
      * an object of the wrong type is passed to setValue.
-     * @param v (default = true)
+     * 
+     * @param typeCheck (default = true)
      */
-    public void setTypeCheckEnabled(boolean v) {
-        typeCheck = v;
+    public void setTypeCheckEnabled(boolean typeCheck) {
+        this.typeCheck = typeCheck;
     }
 
     /**
@@ -102,6 +111,7 @@ public class MatParam implements Savable, Cloneable {
 
     /**
      * Returns the name of the material parameter.
+     * 
      * @return the name of the material parameter.
      */
     public String getName() {
@@ -158,15 +168,16 @@ public class MatParam implements Savable, Cloneable {
                     }
                 }
                 if (!valid) {
-                    throw new RuntimeException("Trying to assign a value of type " + value.getClass() + " to " + this.getName() + " of type " + type.name() + ". Valid types are "
-                            + Arrays.deepToString(type.getJavaType()));
+                    throw new RuntimeException("Trying to assign a value of type " + value.getClass() 
+                            + " to " + this.getName() 
+                            + " of type " + type.name() 
+                            + ". Valid types are " + Arrays.deepToString(type.getJavaType()));
                 }
             }
         }
         this.value = value;
     }
 
-
     /**
      * Returns the material parameter value as it would appear in a J3M
      * file. E.g.
@@ -274,12 +285,12 @@ When arrays can be inserted in J3M files
             case TextureCubeMap:
                 Texture texVal = (Texture) value;
                 TextureKey texKey = (TextureKey) texVal.getKey();
-                if (texKey == null){
-                  //throw new UnsupportedOperationException("The specified MatParam cannot be represented in J3M");
+                if (texKey == null) {
+                    // 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";
+                    return texVal + ":returned null key";
                 }
 
                 String ret = "";
@@ -287,22 +298,22 @@ When arrays can be inserted in J3M files
                     ret += "Flip ";
                 }
 
-                //Wrap mode
+                // Wrap mode
                 ret += getWrapMode(texVal, Texture.WrapAxis.S);
                 ret += getWrapMode(texVal, Texture.WrapAxis.T);
                 ret += getWrapMode(texVal, Texture.WrapAxis.R);
 
-                //Min and Mag filter
-                Texture.MinFilter def =  Texture.MinFilter.BilinearNoMipMaps;
-                if(texVal.getImage().hasMipmaps() || texKey.isGenerateMips()){
+                // Min and Mag filter
+                Texture.MinFilter def = Texture.MinFilter.BilinearNoMipMaps;
+                if (texVal.getImage().hasMipmaps() || texKey.isGenerateMips()) {
                     def = Texture.MinFilter.Trilinear;
                 }
-                if(texVal.getMinFilter() != def){
-                    ret += "Min" + texVal.getMinFilter().name()+ " ";
+                if (texVal.getMinFilter() != def) {
+                    ret += "Min" + texVal.getMinFilter().name() + " ";
                 }
 
-                if(texVal.getMagFilter() != Texture.MagFilter.Bilinear){
-                    ret += "Mag" + texVal.getMagFilter().name()+ " ";
+                if (texVal.getMagFilter() != Texture.MagFilter.Bilinear) {
+                    ret += "Mag" + texVal.getMagFilter().name() + " ";
                 }
 
                 return ret + "\"" + texKey.getName() + "\"";
@@ -315,12 +326,12 @@ When arrays can be inserted in J3M files
         WrapMode mode = WrapMode.EdgeClamp;
         try {
             mode = texVal.getWrap(axis);
-        } catch (IllegalArgumentException e) {
-            //this axis doesn't exist on the texture
+        } catch (IllegalArgumentException ex) {
+            // this axis doesn't exist on the texture
             return "";
         }
         if (mode != WrapMode.EdgeClamp) {
-            return"Wrap"+ mode.name() + "_" + axis.name() + " ";
+            return "Wrap" + mode.name() + "_" + axis.name() + " ";
         }
         return "";
     }

+ 10 - 9
jme3-core/src/main/java/com/jme3/material/Material.java

@@ -907,22 +907,23 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
 
 
     private void updateRenderState(Geometry geometry, RenderManager renderManager, Renderer renderer, TechniqueDef techniqueDef) {
+        RenderState finalRenderState;
         if (renderManager.getForcedRenderState() != null) {
-            mergedRenderState.copyFrom(renderManager.getForcedRenderState());
+            finalRenderState = mergedRenderState.copyFrom(renderManager.getForcedRenderState());
         } else if (techniqueDef.getRenderState() != null) {
-            mergedRenderState.copyFrom(RenderState.DEFAULT);
-            techniqueDef.getRenderState().copyMergedTo(additionalState, mergedRenderState);
+            finalRenderState = mergedRenderState.copyFrom(RenderState.DEFAULT);
+            finalRenderState = techniqueDef.getRenderState().copyMergedTo(additionalState, finalRenderState);
         } else {
-            mergedRenderState.copyFrom(RenderState.DEFAULT);
-            RenderState.DEFAULT.copyMergedTo(additionalState, mergedRenderState);
+            finalRenderState = mergedRenderState.copyFrom(RenderState.DEFAULT);
+            finalRenderState = RenderState.DEFAULT.copyMergedTo(additionalState, finalRenderState);
         }
         // test if the face cull mode should be flipped before render
-        if (mergedRenderState.isFaceCullFlippable() && isNormalsBackward(geometry.getWorldScale())) {
-            mergedRenderState.flipFaceCull();
+        if (finalRenderState.isFaceCullFlippable() && isNormalsBackward(geometry.getWorldScale())) {
+            finalRenderState.flipFaceCull();
         }
-        renderer.applyRenderState(mergedRenderState);
+        renderer.applyRenderState(finalRenderState);
     }
-    
+
     /**
      * Returns true if the geometry world scale indicates that normals will be backward.
      * @param scalar geometry world scale

+ 2 - 1
jme3-core/src/main/java/com/jme3/material/RenderState.java

@@ -1692,7 +1692,7 @@ public class RenderState implements Cloneable, Savable {
      * This method is more precise than {@link #set(com.jme3.material.RenderState)}.
      * @param state state to copy from
      */
-    public void copyFrom(RenderState state) {
+    public RenderState copyFrom(RenderState state) {
         this.applyBlendMode = state.applyBlendMode;
         this.applyColorWrite = state.applyColorWrite;
         this.applyCullMode = state.applyCullMode;
@@ -1734,6 +1734,7 @@ public class RenderState implements Cloneable, Savable {
         this.sfactorRGB = state.sfactorRGB;
         this.stencilTest = state.stencilTest;
         this.wireframe = state.wireframe;
+        return this;
     }
 
     @Override

+ 24 - 0
jme3-core/src/main/java/com/jme3/math/Vector2f.java

@@ -54,10 +54,34 @@ public final class Vector2f implements Savable, Cloneable, java.io.Serializable
      * Shared instance of the all-zero vector (0,0). Do not modify!
      */
     public static final Vector2f ZERO = new Vector2f(0f, 0f);
+    /**
+     * Shared instance of the all-NaN vector (NaN,NaN). Do not modify!
+     */
+    public static final Vector2f NAN = new Vector2f(Float.NaN, Float.NaN);
+    /**
+     * Shared instance of the +X direction (1,0). Do not modify!
+     */
+    public static final Vector2f UNIT_X = new Vector2f(1, 0);
+    /**
+     * Shared instance of the +Y direction (0,1). Do not modify!
+     */
+    public static final Vector2f UNIT_Y = new Vector2f(0, 1);
     /**
      * Shared instance of the all-ones vector (1,1). Do not modify!
      */
     public static final Vector2f UNIT_XY = new Vector2f(1f, 1f);
+    /**
+     * Shared instance of the all-plus-infinity vector (+Inf,+Inf). Do not modify!
+     */
+    public static final Vector2f POSITIVE_INFINITY = new Vector2f(
+            Float.POSITIVE_INFINITY,
+            Float.POSITIVE_INFINITY);
+    /**
+     * Shared instance of the all-negative-infinity vector (-Inf,-Inf). Do not modify!
+     */
+    public static final Vector2f NEGATIVE_INFINITY = new Vector2f(
+            Float.NEGATIVE_INFINITY,
+            Float.NEGATIVE_INFINITY);
     /**
      * The first (X) component.
      */

+ 55 - 43
jme3-core/src/main/java/com/jme3/shader/VarType.java

@@ -30,57 +30,66 @@
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 package com.jme3.shader;
-import com.jme3.math.*;
+
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Matrix3f;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.math.Vector4f;
 import com.jme3.shader.bufferobject.BufferObject;
-import com.jme3.texture.*;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture2D;
+import com.jme3.texture.Texture3D;
+import com.jme3.texture.TextureArray;
+import com.jme3.texture.TextureCubeMap;
+
 public enum VarType {
 
-    Float("float",float.class,Float.class),
-    Vector2("vec2",Vector2f.class),
-    Vector3("vec3",Vector3f.class),
-    Vector4("vec4",Vector4f.class, ColorRGBA.class),
-
-    IntArray(true,false,"int",int[].class,Integer[].class),
-    FloatArray(true,false,"float",float[].class,Float[].class),
-    Vector2Array(true,false,"vec2",Vector2f[].class),
-    Vector3Array(true,false,"vec3",Vector3f[].class),
-    Vector4Array(true,false,"vec4",Vector4f[].class),
-
-    Boolean("bool",Boolean.class,boolean.class),
-
-    Matrix3(true,false,"mat3",Matrix3f.class),
-    Matrix4(true,false,"mat4",Matrix4f.class),
-
-    Matrix3Array(true,false,"mat3",Matrix3f[].class),
-    Matrix4Array(true,false,"mat4",Matrix4f[].class),
-    
-    TextureBuffer(false,true,"sampler1D|sampler1DShadow"), 
-    Texture2D(false,true,"sampler2D|sampler2DShadow",Texture2D.class,Texture.class),
-    Texture3D(false,true,"sampler3D",Texture3D.class,Texture.class),
-    TextureArray(false,true,"sampler2DArray|sampler2DArrayShadow",TextureArray.class,Texture.class),
-    TextureCubeMap(false,true,"samplerCube",TextureCubeMap.class,Texture.class),
-    Int("int",int.class,Integer.class),
-    UniformBufferObject(false, false, "custom",BufferObject.class), 
-    ShaderStorageBufferObject(false, false, "custom",BufferObject.class);
+    Float("float", float.class, Float.class),
+    Vector2("vec2", Vector2f.class),
+    Vector3("vec3", Vector3f.class),
+    Vector4("vec4", Vector4f.class, ColorRGBA.class),
+
+    IntArray(true, false, "int", int[].class, Integer[].class),
+    FloatArray(true, false, "float", float[].class, Float[].class),
+    Vector2Array(true, false, "vec2", Vector2f[].class),
+    Vector3Array(true, false, "vec3", Vector3f[].class),
+    Vector4Array(true, false, "vec4", Vector4f[].class),
+
+    Boolean("bool", Boolean.class, boolean.class),
+
+    Matrix3(true, false, "mat3", Matrix3f.class),
+    Matrix4(true, false, "mat4", Matrix4f.class),
+
+    Matrix3Array(true, false, "mat3", Matrix3f[].class),
+    Matrix4Array(true, false, "mat4", Matrix4f[].class),
+
+    TextureBuffer(false, true, "sampler1D|sampler1DShadow"),
+    Texture2D(false, true, "sampler2D|sampler2DShadow", Texture2D.class, Texture.class),
+    Texture3D(false, true, "sampler3D", Texture3D.class, Texture.class),
+    TextureArray(false, true, "sampler2DArray|sampler2DArrayShadow", TextureArray.class, Texture.class),
+    TextureCubeMap(false, true, "samplerCube", TextureCubeMap.class, Texture.class),
+    Int("int", int.class, Integer.class),
+    UniformBufferObject(false, false, "custom", BufferObject.class),
+    ShaderStorageBufferObject(false, false, "custom", BufferObject.class);
 
     private boolean usesMultiData = false;
     private boolean textureType = false;
-    final private String glslType;
-    private Class<?> javaTypes[];
-    
-    VarType(String glslType,Class<?> ...javaTypes){
+    private final String glslType;
+    private Class<?>[] javaTypes;
+
+    VarType(String glslType, Class<?>... javaTypes) {
         this.glslType = glslType;
         if (javaTypes != null) {
             this.javaTypes = javaTypes;
         } else {
             this.javaTypes = new Class<?>[0];
         }
-        
     }
 
-
     VarType(boolean multiData, boolean textureType, String glslType, Class<?>... javaTypes) {
-        usesMultiData = multiData;
+        this.usesMultiData = multiData;
         this.textureType = textureType;
         this.glslType = glslType;
         if (javaTypes != null) {
@@ -89,25 +98,28 @@ public enum VarType {
             this.javaTypes = new Class<?>[0];
         }
     }
-    
+
     /**
      * Check if the passed object is of a type mapped to this VarType
+     * 
      * @param o Object to check
      * @return true if the object type is mapped to this VarType
      */
-    public boolean isOfType(Object o){
-        for(Class<?> c : javaTypes){
-            if(c.isAssignableFrom(o.getClass()))return true;
+    public boolean isOfType(Object o) {
+        for (Class<?> c : javaTypes) {
+            if (c.isAssignableFrom(o.getClass())) {
+                return true;
+            }
         }
         return false;
     }
-    
 
     /**
      * Get the java types mapped to this VarType
+     * 
      * @return an array of classes mapped to this VarType
      */
-    public Class<?>[] getJavaType(){
+    public Class<?>[] getJavaType() {
         return javaTypes;
     }
 
@@ -121,6 +133,6 @@ public enum VarType {
 
     public String getGlslType() {
         return glslType;
-    }    
+    }
 
 }

+ 3 - 5
jme3-core/src/main/java/com/jme3/shadow/AbstractShadowFilter.java

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2021 jMonkeyEngine
+ * Copyright (c) 2009-2024 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -108,7 +108,6 @@ public abstract class AbstractShadowFilter<T extends AbstractShadowRenderer> ext
         material.setMatrix4("ViewProjectionMatrixInverse", viewPort.getCamera().getViewProjectionMatrix().invert());
         Matrix4f m = viewPort.getCamera().getViewProjectionMatrix();
         material.setVector4("ViewProjectionMatrixRow2", tmpv.set(m.m20, m.m21, m.m22, m.m23));
-
     }
 
     @Override
@@ -258,7 +257,7 @@ public abstract class AbstractShadowFilter<T extends AbstractShadowRenderer> ext
     /**
      *
      * !! WARNING !! this parameter is defaulted to true for the ShadowFilter.
-     * Setting it to true, may produce edges artifacts on shadows.     *
+     * Setting it to true, may produce edges artifacts on shadows.
      *
      * Set to true if you want back faces shadows on geometries.
      * Note that back faces shadows will be blended over dark lighten areas and may produce overly dark lighting.
@@ -272,7 +271,7 @@ public abstract class AbstractShadowFilter<T extends AbstractShadowRenderer> ext
      *
      * @param renderBackFacesShadows true or false.
      */
-    public void setRenderBackFacesShadows(Boolean renderBackFacesShadows) {
+    public void setRenderBackFacesShadows(boolean renderBackFacesShadows) {
         shadowRenderer.setRenderBackFacesShadows(renderBackFacesShadows);
     }
 
@@ -294,7 +293,6 @@ public abstract class AbstractShadowFilter<T extends AbstractShadowRenderer> ext
         return shadowRenderer.getPreShadowForcedRenderState();
     }
 
-
     /**
      * returns the edge filtering mode
      *

+ 4 - 8
jme3-core/src/main/java/com/jme3/shadow/AbstractShadowRenderer.java

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2021 jMonkeyEngine
+ * Copyright (c) 2009-2024 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -145,12 +145,10 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable,
      * maps the more quality, the fewer fps).
      */
     protected AbstractShadowRenderer(AssetManager assetManager, int shadowMapSize, int nbShadowMaps) {
-
         this.assetManager = assetManager;
         this.nbShadowMaps = nbShadowMaps;
         this.shadowMapSize = shadowMapSize;
         init(assetManager, nbShadowMaps, shadowMapSize);
-
     }
 
     private void init(AssetManager assetManager, int nbShadowMaps, int shadowMapSize) {
@@ -430,7 +428,6 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable,
         renderManager.setForcedMaterial(null);
         renderManager.setForcedTechnique(null);
         renderManager.setCamera(viewPort.getCamera(), false);
-
     }
 
     protected void renderShadowMap(int shadowMapIndex) {
@@ -806,12 +803,12 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable,
      *
      * @param renderBackFacesShadows true or false.
      */
-    public void setRenderBackFacesShadows(Boolean renderBackFacesShadows) {
+    public void setRenderBackFacesShadows(boolean renderBackFacesShadows) {
         this.renderBackFacesShadows = renderBackFacesShadows;
-        if(renderBackFacesShadows) {
+        if (renderBackFacesShadows) {
             getPreShadowForcedRenderState().setPolyOffset(5, 3);
             getPreShadowForcedRenderState().setFaceCullMode(RenderState.FaceCullMode.Back);
-        }else{
+        } else {
             getPreShadowForcedRenderState().setPolyOffset(0, 0);
             getPreShadowForcedRenderState().setFaceCullMode(RenderState.FaceCullMode.Front);
         }
@@ -864,7 +861,6 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable,
         init(assetManager, nbShadowMaps, (int) shadowMapSize);
         edgesThickness = ic.readFloat("edgesThickness", 1.0f);
         postshadowMat.setFloat("PCFEdge", edgesThickness);
-
     }
 
     /**

+ 28 - 33
jme3-core/src/main/java/com/jme3/shadow/DirectionalLightShadowFilter.java

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2021 jMonkeyEngine
+ * Copyright (c) 2009-2024 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -40,49 +40,43 @@ import com.jme3.light.DirectionalLight;
 import java.io.IOException;
 
 /**
- *
  * This Filter does basically the same as a DirectionalLightShadowRenderer
  * except it renders the post shadow pass as a fullscreen quad pass instead of a
  * geometry pass. It's mostly faster than PssmShadowRenderer as long as you have
  * more than about ten shadow receiving objects. The expense is the drawback
  * that the shadow Receive mode set on spatial is ignored. So basically all and
- * only objects that render depth in the scene receive shadows. See this post
- * for more details
- * http://jmonkeyengine.org/groups/general-2/forum/topic/silly-question-about-shadow-rendering/#post-191599
+ * only objects that render depth in the scene receive shadows.
  *
- * API is basically the same as the PssmShadowRenderer;
+ * API is basically the same as the PssmShadowRenderer.
  *
  * @author Rémy Bouquet aka Nehon
  */
 public class DirectionalLightShadowFilter extends AbstractShadowFilter<DirectionalLightShadowRenderer> {
 
     /**
-     * Used for serialization.
-     * Use DirectionalLightShadowFilter#DirectionalLightShadowFilter
-     * (AssetManager assetManager, int shadowMapSize, int nbSplits)
-     * instead.
+     * For serialization only. Do not use.
+     * 
+     * @see #DirectionalLightShadowFilter(AssetManager assetManager, int shadowMapSize, int nbSplits)
      */
     public DirectionalLightShadowFilter() {
         super();
     }
     
     /**
-     * Creates a DirectionalLight shadow filter. More info on the
-     * technique at <a
-     * href="http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html">http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html</a>
-     *
-     * @param assetManager the application's asset manager
-     * @param shadowMapSize the size of the rendered shadowmaps (512, 1024, 2048,
-     *     etcetera)
-     * @param nbSplits the number of shadow maps rendered (More shadow maps mean
-     *     better quality, fewer frames per second.)
+     * Creates a DirectionalLightShadowFilter.
+     * 
+     * @param assetManager  the application's asset manager
+     * @param shadowMapSize the size of the rendered shadow maps (512, 1024, 2048, etc...)
+     * @param nbSplits      the number of shadow maps rendered (more shadow maps = better quality, but slower)
+     * 
+     * @throws IllegalArgumentException if the provided 'nbSplits' is not within the valid range of 1 to 4.
      */
     public DirectionalLightShadowFilter(AssetManager assetManager, int shadowMapSize, int nbSplits) {
         super(assetManager, shadowMapSize, new DirectionalLightShadowRenderer(assetManager, shadowMapSize, nbSplits));
     }
 
     /**
-     * return the light used to cast shadows
+     * Returns the light used to cast shadows.
      *
      * @return the DirectionalLight
      */
@@ -91,7 +85,7 @@ public class DirectionalLightShadowFilter extends AbstractShadowFilter<Direction
     }
 
     /**
-     * Sets the light to use to cast shadows
+     * Sets the light to use to cast shadows.
      *
      * @param light a DirectionalLight
      */
@@ -100,7 +94,7 @@ public class DirectionalLightShadowFilter extends AbstractShadowFilter<Direction
     }
 
     /**
-     * returns the lambda parameter
+     * Returns the lambda parameter.
      *
      * @see #setLambda(float lambda)
      * @return lambda
@@ -110,24 +104,25 @@ public class DirectionalLightShadowFilter extends AbstractShadowFilter<Direction
     }
 
     /**
-     * Adjusts the partition of the shadow extend into shadow maps.
-     * Lambda is usually between 0 and 1.
-     * A low value gives a more linear partition,
-     * resulting in consistent shadow quality over the extend,
-     * but near shadows could look very jagged.
-     * A high value gives a more logarithmic partition,
-     * resulting in high quality for near shadows,
-     * but quality decreases rapidly with distance.
+     * Adjusts the partition of the shadow extend into shadow maps. Lambda is
+     * usually between 0 and 1.
+     * <p>
+     * A low value gives a more linear partition, resulting in consistent shadow
+     * quality over the extend, but near shadows could look very jagged. A high
+     * value gives a more logarithmic partition, resulting in high quality for near
+     * shadows, but quality decreases rapidly with distance.
+     * <p>
      * The default value is 0.65 (the theoretical optimum).
      *
-     * @param lambda the lambda value.
+     * @param lambda the lambda value
      */
     public void setLambda(float lambda) {
         shadowRenderer.setLambda(lambda);
     }
 
     /**
-     * returns true if stabilization is enabled
+     * Returns true if stabilization is enabled.
+     * 
      * @return true if stabilization is enabled
      */
     public boolean isEnabledStabilization() {
@@ -150,7 +145,6 @@ public class DirectionalLightShadowFilter extends AbstractShadowFilter<Direction
         super.write(ex);
         OutputCapsule oc = ex.getCapsule(this);
         oc.write(shadowRenderer, "shadowRenderer", null);
-
     }
 
     @Override
@@ -159,4 +153,5 @@ public class DirectionalLightShadowFilter extends AbstractShadowFilter<Direction
         InputCapsule ic = im.getCapsule(this);
         shadowRenderer = (DirectionalLightShadowRenderer) ic.readSavable("shadowRenderer", null);
     }
+    
 }

+ 14 - 19
jme3-core/src/main/java/com/jme3/shadow/PointLightShadowFilter.java

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2021 jMonkeyEngine
+ * Copyright (c) 2009-2024 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -40,56 +40,51 @@ import com.jme3.light.PointLight;
 import java.io.IOException;
 
 /**
- *
  * This Filter does basically the same as a PointLightShadowRenderer except it
  * renders the post shadow pass as a fullscreen quad pass instead of a geometry
  * pass. It's mostly faster than PointLightShadowRenderer as long as you have
  * more than about ten shadow receiving objects. The expense is the drawback
  * that the shadow Receive mode set on spatial is ignored. So basically all and
- * only objects that render depth in the scene receive shadows. See this post
- * for more details
- * http://jmonkeyengine.org/groups/general-2/forum/topic/silly-question-about-shadow-rendering/#post-191599
+ * only objects that render depth in the scene receive shadows.
  *
- * API is basically the same as the PssmShadowRenderer;
+ * API is basically the same as the PssmShadowRenderer.
  *
  * @author Rémy Bouquet aka Nehon
  */
 public class PointLightShadowFilter extends AbstractShadowFilter<PointLightShadowRenderer> {
 
     /**
-     * Used for serialization.
-     * Use PointLightShadowFilter#PointLightShadowFilter(AssetManager
-     * assetManager, int shadowMapSize)
-     * instead.
+     * For serialization only. Do not use.
+     * 
+     * @see #PointLightShadowFilter(AssetManager assetManager, int shadowMapSize)
      */
     protected PointLightShadowFilter() {
         super();
     }
 
     /**
-     * Creates a PointLightShadowFilter
+     * Creates a PointLightShadowFilter.
      *
-     * @param assetManager the application asset manager
-     * @param shadowMapSize the size of the rendered shadowmaps (512,1024,2048,
-     * etc...)
+     * @param assetManager  the application's asset manager
+     * @param shadowMapSize the size of the rendered shadow maps (512, 1024, 2048, etc...)
      */
     public PointLightShadowFilter(AssetManager assetManager, int shadowMapSize) {
         super(assetManager, shadowMapSize, new PointLightShadowRenderer(assetManager, shadowMapSize));
     }
 
     /**
-     * gets the point light used to cast shadows with this processor
+     * Returns the light used to cast shadows.
      *
-     * @return the point light
+     * @return the PointLight
      */
     public PointLight getLight() {
         return shadowRenderer.getLight();
     }
 
     /**
-     * sets the light to use for casting shadows with this processor
+     * Sets the light to use to cast shadows.
      *
-     * @param light the point light
+     * @param light the PointLight
      */
     public void setLight(PointLight light) {
         shadowRenderer.setLight(light);
@@ -100,7 +95,6 @@ public class PointLightShadowFilter extends AbstractShadowFilter<PointLightShado
         super.write(ex);
         OutputCapsule oc = ex.getCapsule(this);
         oc.write(shadowRenderer, "shadowRenderer", null);
-
     }
 
     @Override
@@ -109,4 +103,5 @@ public class PointLightShadowFilter extends AbstractShadowFilter<PointLightShado
         InputCapsule ic = im.getCapsule(this);
         shadowRenderer = (PointLightShadowRenderer) ic.readSavable("shadowRenderer", null);
     }
+    
 }

+ 6 - 3
jme3-core/src/main/java/com/jme3/shadow/ShadowUtil.java

@@ -569,8 +569,10 @@ public class ShadowUtil {
         float scaleX, scaleY, scaleZ;
         float offsetX, offsetY, offsetZ;
 
-        scaleX = (2.0f) / (cropMax.x - cropMin.x);
-        scaleY = (2.0f) / (cropMax.y - cropMin.y);
+        float deltaCropX = cropMax.x - cropMin.x;
+        float deltaCropY = cropMax.y - cropMin.y;
+        scaleX = deltaCropX == 0 ? 0 : 2.0f / deltaCropX;
+        scaleY = deltaCropY == 0 ? 0 : 2.0f / deltaCropY;
 
         //Shadow map stabilization approximation from shaderX 7
         //from Practical Cascaded Shadow maps adapted to PSSM
@@ -595,7 +597,8 @@ public class ShadowUtil {
             offsetY = FastMath.ceil(offsetY * halfTextureSize) / halfTextureSize;
         }
 
-        scaleZ = 1.0f / (cropMax.z - cropMin.z);
+        float deltaCropZ = cropMax.z - cropMin.z;
+        scaleZ = deltaCropZ == 0 ? 0 : 1.0f / deltaCropZ;
         offsetZ = -cropMin.z * scaleZ;
 
 

+ 14 - 19
jme3-core/src/main/java/com/jme3/shadow/SpotLightShadowFilter.java

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2021 jMonkeyEngine
+ * Copyright (c) 2009-2024 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -40,45 +40,40 @@ import com.jme3.light.SpotLight;
 import java.io.IOException;
 
 /**
- *
  * This Filter does basically the same as a SpotLightShadowRenderer except it
  * renders the post shadow pass as a fullscreen quad pass instead of a geometry
  * pass. It's mostly faster than PssmShadowRenderer as long as you have more
  * than about ten shadow receiving objects. The expense is the drawback that
  * the shadow Receive mode set on spatial is ignored. So basically all and only
- * objects that render depth in the scene receive shadows. See this post for
- * more details
- * http://jmonkeyengine.org/groups/general-2/forum/topic/silly-question-about-shadow-rendering/#post-191599
+ * objects that render depth in the scene receive shadows.
  *
- * API is basically the same as the PssmShadowRenderer;
+ * API is basically the same as the PssmShadowRenderer.
  *
  * @author Rémy Bouquet aka Nehon
  */
 public class SpotLightShadowFilter extends AbstractShadowFilter<SpotLightShadowRenderer> {
 
     /**
-     * Used for serialization.
-     * Use SpotLightShadowFilter#SpotLightShadowFilter(AssetManager assetManager,
-     * int shadowMapSize)
-     * instead.
+     * For serialization only. Do not use.
+     * 
+     * @see #SpotLightShadowFilter(AssetManager assetManager, int shadowMapSize)
      */
     protected SpotLightShadowFilter() {
         super();
     }
     
     /**
-     * Creates a SpotLight Shadow Filter
+     * Creates a SpotLightShadowFilter.
      *
-     * @param assetManager the application asset manager
-     * @param shadowMapSize the size of the rendered shadowmaps (512,1024,2048,
-     * etc...) The more quality, the fewer fps.
+     * @param assetManager  the application's asset manager
+     * @param shadowMapSize the size of the rendered shadow maps (512, 1024, 2048, etc...)
      */
     public SpotLightShadowFilter(AssetManager assetManager, int shadowMapSize) {
         super(assetManager, shadowMapSize, new SpotLightShadowRenderer(assetManager, shadowMapSize));
     }
 
     /**
-     * return the light used to cast shadows
+     * Returns the light used to cast shadows.
      *
      * @return the SpotLight
      */
@@ -87,20 +82,19 @@ public class SpotLightShadowFilter extends AbstractShadowFilter<SpotLightShadowR
     }
 
     /**
-     * Sets the light to use to cast shadows
+     * Sets the light to use to cast shadows.
      *
-     * @param light a SpotLight
+     * @param light the SpotLight
      */
     public void setLight(SpotLight light) {
         shadowRenderer.setLight(light);
     }
-   
+
     @Override
     public void write(JmeExporter ex) throws IOException {
         super.write(ex);
         OutputCapsule oc = ex.getCapsule(this);
         oc.write(shadowRenderer, "shadowRenderer", null);
-
     }
 
     @Override
@@ -109,4 +103,5 @@ public class SpotLightShadowFilter extends AbstractShadowFilter<SpotLightShadowR
         InputCapsule ic = im.getCapsule(this);
         shadowRenderer = (SpotLightShadowRenderer) ic.readSavable("shadowRenderer", null);
     }
+    
 }

+ 23 - 32
jme3-core/src/main/java/com/jme3/util/MaterialDebugAppState.java

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2021 jMonkeyEngine
+ * Copyright (c) 2009-2024 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -98,9 +98,8 @@ public class MaterialDebugAppState extends AbstractAppState {
     private RenderManager renderManager;
     private AssetManager assetManager;
     private InputManager inputManager;
-    final private List<Binding> bindings = new ArrayList<>();
-    final private Map<Trigger,List<Binding>> fileTriggers = new HashMap<> ();
-    
+    private final List<Binding> bindings = new ArrayList<>();
+    private final Map<Trigger, List<Binding>> fileTriggers = new HashMap<>();
 
     @Override
     public void initialize(AppStateManager stateManager, Application app) {
@@ -119,20 +118,18 @@ public class MaterialDebugAppState extends AbstractAppState {
      * @param spat the spatial to reload
      */
     public void registerBinding(Trigger trigger, final Spatial spat) {
-        if(spat instanceof Geometry){
-            GeometryBinding binding = new GeometryBinding(trigger, (Geometry)spat);           
+        if (spat instanceof Geometry) {
+            GeometryBinding binding = new GeometryBinding(trigger, (Geometry) spat);
             bindings.add(binding);
             if (isInitialized()) {
                 bind(binding);
             }
-        }else if (spat instanceof Node){
-            for (Spatial child : ((Node)spat).getChildren()) {
+        } else if (spat instanceof Node) {
+            for (Spatial child : ((Node) spat).getChildren()) {
                 registerBinding(trigger, child);
             }
         }
     }
-    
-    
 
     /**
      * Will reload the filter's materials whenever the trigger is fired.
@@ -146,7 +143,6 @@ public class MaterialDebugAppState extends AbstractAppState {
             bind(binding);
         }
     }
-
     
     /**
      * Will reload the filter's materials whenever the shader file is changed 
@@ -174,7 +170,7 @@ public class MaterialDebugAppState extends AbstractAppState {
         if (binding.getTrigger() instanceof FileChangedTrigger) {
             FileChangedTrigger t = (FileChangedTrigger) binding.getTrigger();
             List<Binding> b = fileTriggers.get(t);
-            if(b == null){
+            if (b == null) {
                 t.init();
                 b = new ArrayList<Binding>();
                 fileTriggers.put(t, b);
@@ -186,7 +182,7 @@ public class MaterialDebugAppState extends AbstractAppState {
                 @Override
                 public void onAction(String name, boolean isPressed, float tpf) {
                     if (actionName.equals(name) && isPressed) {
-                        //reloading the material
+                        // reloading the material
                         binding.reload();
                     }
                 }
@@ -197,42 +193,41 @@ public class MaterialDebugAppState extends AbstractAppState {
     }
 
     public Material reloadMaterial(Material mat) {
-        //clear the entire cache, there might be more clever things to do, like clearing only the matdef, and the associated shaders.
+        // clear the entire cache, there might be more clever things to do, like
+        // clearing only the matdef, and the associated shaders.
         assetManager.clearCache();
 
-        //creating a dummy mat with the mat def of the mat to reload
+        // creating a dummy mat with the mat def of the mat to reload
         // Force the reloading of the asset, otherwise the new shader code will not be applied.
         Material dummy = new Material(assetManager, mat.getMaterialDef().getAssetName());
 
         for (MatParam matParam : mat.getParams()) {
             dummy.setParam(matParam.getName(), matParam.getVarType(), matParam.getValue());
         }
-        
-        dummy.getAdditionalRenderState().set(mat.getAdditionalRenderState());        
 
-        //creating a dummy geom and assigning the dummy material to it
+        dummy.getAdditionalRenderState().set(mat.getAdditionalRenderState());
+
+        // creating a dummy geom and assigning the dummy material to it
         Geometry dummyGeom = new Geometry("dummyGeom", new Box(1f, 1f, 1f));
         dummyGeom.setMaterial(dummy);
 
         try {
-            //preloading the dummyGeom, this call will compile the shader again
+            // preloading the dummyGeom, this call will compile the shader again
             renderManager.preloadScene(dummyGeom);
         } catch (RendererException e) {
-            //compilation error, the shader code will be output to the console
-            //the following code will output the error
-            //System.err.println(e.getMessage());
+            // compilation error, the shader code will be output to the console
+            // the following code will output the error
             Logger.getLogger(MaterialDebugAppState.class.getName()).log(Level.SEVERE, e.getMessage());
             return null;
         }
 
         Logger.getLogger(MaterialDebugAppState.class.getName()).log(Level.INFO, "Material successfully reloaded");
-        //System.out.println("Material successfully reloaded");
         return dummy;
     }
    
     @Override
     public void update(float tpf) {
-        super.update(tpf); //To change body of generated methods, choose Tools | Templates.
+        super.update(tpf);
         for (Trigger trigger : fileTriggers.keySet()) {
             if (trigger instanceof FileChangedTrigger) {
                 FileChangedTrigger t = (FileChangedTrigger) trigger;
@@ -243,7 +238,7 @@ public class MaterialDebugAppState extends AbstractAppState {
                     }
                 }
             }
-        }       
+        }
     }
 
     private interface Binding {
@@ -263,15 +258,14 @@ public class MaterialDebugAppState extends AbstractAppState {
         public GeometryBinding(Trigger trigger, Geometry geom) {
             this.trigger = trigger;
             this.geom = geom;
-
         }
 
         @Override
         public void reload() {
             Material reloadedMat = reloadMaterial(geom.getMaterial());
-            //if the reload is successful, we re setup the material with its params and reassign it to the box
+            // if the reload is successful, we re setup the material with its params and
+            // reassign it to the box
             if (reloadedMat != null) {
-                // setupMaterial(reloadedMat);
                 geom.setMaterial(reloadedMat);
             }
         }
@@ -279,7 +273,6 @@ public class MaterialDebugAppState extends AbstractAppState {
         @Override
         public String getActionName() {
             return geom.getName() + "Reload";
-
         }
 
         @Override
@@ -319,12 +312,11 @@ public class MaterialDebugAppState extends AbstractAppState {
                         } else {
                             field.set(filter, mat);
                         }
-
                     }
                     if (field.getType().isInstance(p)) {
                         field.setAccessible(true);
                         p = (Filter.Pass) field.get(filter);
-                        if (p!= null && p.getPassMaterial() != null) {
+                        if (p != null && p.getPassMaterial() != null) {
                             Material mat = reloadMaterial(p.getPassMaterial());
                             if (mat == null) {
                                 return;
@@ -352,7 +344,6 @@ public class MaterialDebugAppState extends AbstractAppState {
             } catch (IllegalArgumentException | IllegalAccessException ex) {
                 Logger.getLogger(MaterialDebugAppState.class.getName()).log(Level.SEVERE, null, ex);
             }
-
         }
 
         @Override

+ 73 - 0
jme3-core/src/main/resources/Common/MatDefs/Light/modular/PBRLighting.frag

@@ -0,0 +1,73 @@
+#import "Common/ShaderLib/GLSLCompat.glsllib"
+
+// enable apis and import PBRLightingUtils
+#define ENABLE_PBRLightingUtils_getWorldPosition 1
+#define ENABLE_PBRLightingUtils_getWorldNormal 1
+#define ENABLE_PBRLightingUtils_getWorldTangent 1
+#define ENABLE_PBRLightingUtils_getTexCoord 1
+#define ENABLE_PBRLightingUtils_readPBRSurface 1
+#define ENABLE_PBRLightingUtils_computeDirectLightContribution 1
+#define ENABLE_PBRLightingUtils_computeProbesContribution 1
+
+#import "Common/ShaderLib/module/pbrlighting/PBRLightingUtils.glsllib"
+
+#ifdef DEBUG_VALUES_MODE
+    uniform int m_DebugValuesMode;
+#endif
+
+uniform vec4 g_LightData[NB_LIGHTS];
+uniform vec3 g_CameraPosition;
+
+void main(){
+    vec3 wpos = PBRLightingUtils_getWorldPosition();
+    vec3 worldViewDir = normalize(g_CameraPosition - wpos);
+    
+    // Load surface data
+    PBRSurface surface=PBRLightingUtils_readPBRSurface(worldViewDir);
+    
+    // Calculate direct lights
+    for(int i = 0;i < NB_LIGHTS; i+=3){
+        vec4 lightData0 = g_LightData[i];
+        vec4 lightData1 = g_LightData[i+1];
+        vec4 lightData2 = g_LightData[i+2];    
+        PBRLightingUtils_computeDirectLightContribution(
+          lightData0, lightData1, lightData2, 
+          surface
+        );
+    }
+
+
+    // Calculate env probes
+    PBRLightingUtils_computeProbesContribution(surface);
+
+    // Put it all together
+    gl_FragColor.rgb = vec3(0.0);
+    gl_FragColor.rgb += surface.bakedLightContribution;
+    gl_FragColor.rgb += surface.directLightContribution;
+    gl_FragColor.rgb += surface.envLightContribution;
+    gl_FragColor.rgb += surface.emission;
+    gl_FragColor.a = surface.alpha;
+
+  
+    // outputs the final value of the selected layer as a color for debug purposes. 
+    #ifdef DEBUG_VALUES_MODE
+        if(m_DebugValuesMode == 0){
+            gl_FragColor.rgb = vec3(surface.albedo);
+        }
+        else if(m_DebugValuesMode == 1){
+            gl_FragColor.rgb = vec3(surface.normal);
+        }
+        else if(m_DebugValuesMode == 2){
+            gl_FragColor.rgb = vec3(surface.roughness);
+        }
+        else if(m_DebugValuesMode == 3){
+            gl_FragColor.rgb = vec3(surface.metallic);
+        }
+        else if(m_DebugValuesMode == 4){
+            gl_FragColor.rgb = surface.ao.rgb;
+        }
+        else if(m_DebugValuesMode == 5){
+            gl_FragColor.rgb = vec3(surface.emission.rgb);          
+        }        
+    #endif   
+}

+ 321 - 0
jme3-core/src/main/resources/Common/MatDefs/Light/modular/PBRLighting.j3md

@@ -0,0 +1,321 @@
+MaterialDef PBR Lighting {
+
+    MaterialParameters {
+        Int BoundDrawBuffer
+
+        // Alpha threshold for fragment discarding
+        Float AlphaDiscardThreshold (AlphaTestFallOff)
+
+        //metallicity of the material
+        Float Metallic : 1.0
+        //Roughness of the material
+        Float Roughness : 1.0        
+        // Base material color
+        Color BaseColor : 1.0 1.0 1.0 1.0
+        // The emissive color of the object
+        Color Emissive        
+        // the emissive power
+        Float EmissivePower : 3.0        
+        // the emissive intensity
+        Float EmissiveIntensity : 2.0
+
+        // BaseColor map
+        Texture2D BaseColorMap
+
+        // Metallic map
+        Texture2D MetallicMap -LINEAR
+        
+        // Roughness Map
+        Texture2D RoughnessMap -LINEAR
+
+        //Metallic and Roughness are packed respectively in the b and g channel of a single map
+        // r: AO (if AoPackedInMRMap is true)
+        // g: Roughness
+        // b: Metallic
+        Texture2D MetallicRoughnessMap -LINEAR
+        
+        // Texture of the emissive parts of the material
+        Texture2D EmissiveMap
+
+        // Normal map
+        Texture2D NormalMap -LINEAR
+        // The scalar parameter applied to each normal vector of the normal map
+        Float NormalScale
+
+        //The type of normal map: -1.0 (DirectX), 1.0 (OpenGl)
+        Float NormalType : -1.0
+
+        // For Spec gloss pipeline
+        Boolean UseSpecGloss
+        Texture2D SpecularMap
+        Texture2D GlossinessMap
+        Texture2D SpecularGlossinessMap
+        Color Specular : 1.0 1.0 1.0 1.0
+        Float Glossiness : 1.0
+
+        // Parallax/height map
+        Texture2D ParallaxMap -LINEAR
+
+        // Specular-AA
+        Boolean UseSpecularAA : true
+        // screen space variance,Use the slider to set the strength of the geometric specular anti-aliasing effect between 0 and 1. Higher values produce a blurrier result with less aliasing.
+        Float SpecularAASigma
+        // clamping threshold,Use the slider to set a maximum value for the offset that HDRP subtracts from the smoothness value to reduce artifacts.
+        Float SpecularAAKappa
+
+        //Set to true if parallax map is stored in the alpha channel of the normal map
+        Boolean PackedNormalParallax   
+
+        //Sets the relief height for parallax mapping
+        Float ParallaxHeight : 0.05       
+
+        //Set to true to activate Steep Parallax mapping
+        Boolean SteepParallax
+
+        //Horizon fade
+        Boolean HorizonFade
+
+        // Set to Use Lightmap
+        Texture2D LightMap
+
+        // A scalar multiplier controlling the amount of occlusion applied.
+        // A value of `0.0` means no occlusion. A value of `1.0` means full occlusion.
+        Float AoStrength
+
+        // Set to use TexCoord2 for the lightmap sampling
+        Boolean SeparateTexCoord
+        // the light map is a grayscale ao map, only the r channel will be read.
+        Boolean LightMapAsAOMap
+        Boolean AoPackedInMRMap
+        //shadows
+        Int FilterMode
+        Boolean HardwareShadows
+
+        Texture2D ShadowMap0
+        Texture2D ShadowMap1
+        Texture2D ShadowMap2
+        Texture2D ShadowMap3
+        //pointLights
+        Texture2D ShadowMap4
+        Texture2D ShadowMap5
+        
+        Float ShadowIntensity
+        Vector4 Splits
+        Vector2 FadeInfo
+
+        Matrix4 LightViewProjectionMatrix0
+        Matrix4 LightViewProjectionMatrix1
+        Matrix4 LightViewProjectionMatrix2
+        Matrix4 LightViewProjectionMatrix3
+        //pointLight
+        Matrix4 LightViewProjectionMatrix4
+        Matrix4 LightViewProjectionMatrix5   
+        Vector3 LightPos
+        Vector3 LightDir
+
+        Float PCFEdge
+        Float ShadowMapSize
+
+        // For hardware skinning
+        Int NumberOfBones
+        Matrix4Array BoneMatrices
+
+        // For Morph animation
+        FloatArray MorphWeights
+        Int NumberOfMorphTargets
+        Int NumberOfTargetsBuffers
+                
+        // For instancing
+        Boolean UseInstancing
+
+        // For Vertex Color
+        Boolean UseVertexColor
+
+        Boolean BackfaceShadows : false
+
+        Boolean UseVertexColorsAsSunIntensity
+        Float StaticSunIntensity
+        Boolean BrightenIndoorShadows
+    
+        Int DebugValuesMode
+            // debugs the final value of the selected layer as a color output:         
+            // Layers:
+            //   0 - albedo (unshaded)
+            //   1 - normals
+            //   2 - roughness
+            //   3 - metallic
+            //   4 - ao
+            //   5 - emissive
+    }
+
+    Technique {
+        LightMode SinglePassAndImageBased
+        
+        VertexShader GLSL300 GLSL150 GLSL110:   Common/MatDefs/Light/modular/PBRLighting.vert
+        FragmentShader GLSL300 GLSL150 GLSL110: Common/MatDefs/Light/modular/PBRLighting.frag
+
+        WorldParameters {
+            WorldViewProjectionMatrix
+            CameraPosition
+            WorldMatrix
+            WorldNormalMatrix
+            ViewProjectionMatrix
+            ViewMatrix
+        }
+
+        Defines {  
+            BOUND_DRAW_BUFFER: BoundDrawBuffer       
+            BASECOLORMAP : BaseColorMap            
+            NORMALMAP : NormalMap
+            NORMALSCALE : NormalScale
+            METALLICMAP : MetallicMap
+            ROUGHNESSMAP : RoughnessMap
+            EMISSIVEMAP : EmissiveMap
+            EMISSIVE : Emissive
+            SPECGLOSSPIPELINE : UseSpecGloss
+            PARALLAXMAP : ParallaxMap
+            NORMALMAP_PARALLAX : PackedNormalParallax
+            STEEP_PARALLAX : SteepParallax
+            LIGHTMAP : LightMap
+            SEPARATE_TEXCOORD : SeparateTexCoord
+            DISCARD_ALPHA : AlphaDiscardThreshold                        
+            NUM_BONES : NumberOfBones                        
+            INSTANCING : UseInstancing
+            USE_PACKED_MR: MetallicRoughnessMap
+            USE_PACKED_SG: SpecularGlossinessMap
+            SPECULARMAP : SpecularMap
+            SPECULAR_AA : UseSpecularAA
+            SPECULAR_AA_SCREEN_SPACE_VARIANCE : SpecularAASigma
+            SPECULAR_AA_THRESHOLD : SpecularAAKappa
+            GLOSSINESSMAP : GlossinessMap
+            NORMAL_TYPE: NormalType
+            VERTEX_COLOR : UseVertexColor
+            AO_MAP: LightMapAsAOMap
+            AO_PACKED_IN_MR_MAP : AoPackedInMRMap
+            AO_STRENGTH : AoStrength
+            NUM_MORPH_TARGETS: NumberOfMorphTargets
+            NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
+            HORIZON_FADE: HorizonFade
+            USE_VERTEX_COLORS_AS_SUN_INTENSITY : UseVertexColorsAsSunIntensity
+            STATIC_SUN_INTENSITY : StaticSunIntensity
+            BRIGHTEN_INDOOR_SHADOWS : BrightenIndoorShadows
+            DEBUG_VALUES_MODE : DebugValuesMode
+        }
+    }
+
+
+    Technique PreShadow {
+
+        VertexShader   GLSL300 GLSL150 GLSL100:   Common/MatDefs/Shadow/PreShadow.vert
+        FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/PreShadowPBR.frag
+
+        WorldParameters {
+            WorldViewProjectionMatrix
+            WorldViewMatrix
+            ViewProjectionMatrix
+            ViewMatrix
+        }
+
+        Defines {
+            BOUND_DRAW_BUFFER: BoundDrawBuffer
+            DISCARD_ALPHA : AlphaDiscardThreshold
+            NUM_BONES : NumberOfBones
+            INSTANCING : UseInstancing
+            NUM_MORPH_TARGETS: NumberOfMorphTargets
+            NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
+        }
+
+        ForcedRenderState {
+            FaceCull Off
+            DepthTest On
+            DepthWrite On
+            PolyOffset 5 3
+            ColorWrite Off
+        }
+
+    }
+
+
+    Technique PostShadow {
+        VertexShader   GLSL310 GLSL300 GLSL150 GLSL100:   Common/MatDefs/Shadow/PostShadow.vert
+        FragmentShader GLSL310 GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/PostShadowPBR.frag
+
+        WorldParameters {
+            WorldViewProjectionMatrix
+            WorldMatrix
+            ViewProjectionMatrix
+            ViewMatrix
+        }
+
+        Defines {
+            BOUND_DRAW_BUFFER: BoundDrawBuffer
+            HARDWARE_SHADOWS : HardwareShadows
+            FILTER_MODE : FilterMode
+            PCFEDGE : PCFEdge
+            DISCARD_ALPHA : AlphaDiscardThreshold           
+            SHADOWMAP_SIZE : ShadowMapSize
+            FADE : FadeInfo
+            PSSM : Splits
+            POINTLIGHT : LightViewProjectionMatrix5
+            NUM_BONES : NumberOfBones
+            INSTANCING : UseInstancing
+            BACKFACE_SHADOWS: BackfaceShadows
+            NUM_MORPH_TARGETS: NumberOfMorphTargets
+            NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
+        }
+
+        ForcedRenderState {
+            Blend Modulate
+            DepthWrite Off                 
+            PolyOffset -0.1 0
+        }
+    }
+
+    Technique PreNormalPass {
+
+        VertexShader   GLSL300 GLSL150 GLSL100 :   Common/MatDefs/SSAO/normal.vert
+        FragmentShader GLSL300 GLSL150 GLSL100 : Common/MatDefs/SSAO/normal.frag
+
+        WorldParameters {
+            WorldViewProjectionMatrix
+            WorldViewMatrix
+            NormalMatrix
+            ViewProjectionMatrix
+            ViewMatrix
+        }
+
+        Defines {
+            BOUND_DRAW_BUFFER: BoundDrawBuffer
+            BASECOLORMAP_ALPHA : BaseColorMap            
+            NUM_BONES : NumberOfBones
+            INSTANCING : UseInstancing
+            NUM_MORPH_TARGETS: NumberOfMorphTargets
+            NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
+        }
+
+    }
+
+    Technique Glow {
+
+        VertexShader   GLSL300 GLSL150 GLSL100:   Common/MatDefs/Misc/Unshaded.vert
+        FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Light/PBRGlow.frag
+
+        WorldParameters {
+            WorldViewProjectionMatrix
+            ViewProjectionMatrix
+            ViewMatrix
+        }
+
+        Defines {
+            HAS_EMISSIVEMAP : EmissiveMap
+            HAS_EMISSIVECOLOR : Emissive
+            BOUND_DRAW_BUFFER: BoundDrawBuffer
+            NEED_TEXCOORD1
+            NUM_BONES : NumberOfBones
+            INSTANCING : UseInstancing
+            NUM_MORPH_TARGETS: NumberOfMorphTargets
+            NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
+        }
+    }
+
+}

+ 75 - 0
jme3-core/src/main/resources/Common/MatDefs/Light/modular/PBRLighting.vert

@@ -0,0 +1,75 @@
+#import "Common/ShaderLib/GLSLCompat.glsllib"
+#import "Common/ShaderLib/Instancing.glsllib"
+#import "Common/ShaderLib/Skinning.glsllib"
+#import "Common/ShaderLib/MorphAnim.glsllib"
+
+uniform vec4 m_BaseColor;
+uniform vec4 g_AmbientLightColor;
+varying vec2 texCoord;
+
+#ifdef SEPARATE_TEXCOORD
+  varying vec2 texCoord2;
+  attribute vec2 inTexCoord2;
+#endif
+
+varying vec4 Color;
+
+attribute vec3 inPosition;
+attribute vec2 inTexCoord;
+attribute vec3 inNormal;
+
+#if defined (VERTEX_COLOR) || defined(USE_VERTEX_COLORS_AS_SUN_INTENSITY)
+    attribute vec4 inColor;
+#endif
+
+#if defined(USE_VERTEX_COLORS_AS_SUN_INTENSITY)
+    varying vec4 vertColors;
+#endif
+
+varying vec3 wNormal;
+varying vec3 wPosition;
+
+attribute vec4 inTangent;
+varying vec4 wTangent;
+
+void main(){
+    vec4 modelSpacePos = vec4(inPosition, 1.0);
+    vec3 modelSpaceNorm = inNormal;
+    vec3 modelSpaceTan  = inTangent.xyz;
+
+    #ifdef USE_VERTEX_COLORS_AS_SUN_INTENSITY
+        vertColors = inColor;
+    #endif
+
+    #ifdef NUM_MORPH_TARGETS
+         #if defined(NORMALMAP) && !defined(VERTEX_LIGHTING)
+            Morph_Compute(modelSpacePos, modelSpaceNorm, modelSpaceTan);
+         #else
+            Morph_Compute(modelSpacePos, modelSpaceNorm);
+         #endif
+    #endif
+
+    #ifdef NUM_BONES
+         #if defined(NORMALMAP) && !defined(VERTEX_LIGHTING)
+            Skinning_Compute(modelSpacePos, modelSpaceNorm, modelSpaceTan);
+         #else
+            Skinning_Compute(modelSpacePos, modelSpaceNorm);
+         #endif
+    #endif
+
+    gl_Position = TransformWorldViewProjection(modelSpacePos);
+    texCoord = inTexCoord;
+    #ifdef SEPARATE_TEXCOORD
+       texCoord2 = inTexCoord2;
+    #endif
+
+    wPosition = TransformWorld(modelSpacePos).xyz;
+    wNormal  = TransformWorldNormal(modelSpaceNorm);
+    wTangent = vec4(TransformWorldNormal(modelSpaceTan),inTangent.w);
+
+    Color = m_BaseColor;
+    
+    #ifdef VERTEX_COLOR                    
+        Color *= inColor;
+    #endif
+}

+ 14 - 1
jme3-core/src/main/resources/Common/ShaderLib/Math.glsllib

@@ -1,4 +1,17 @@
+#ifndef __MATH_GLSLLIB__
+#define __MATH_GLSLLIB__
+
 /// Multiplies the vector by the quaternion, then returns the resultant vector.
 vec3 Math_QuaternionMult(in vec4 quat, in vec3 vec){
 	return vec + 2.0 * cross(quat.xyz, cross(quat.xyz, vec) + quat.w * vec);
-}
+}
+
+void Math_lengthAndNormalize(in vec3 vec,out float outLength,out vec3 outNormal){
+    float dotv=dot(vec,vec);
+    float invl=inversesqrt(dotv);
+    outNormal=vec*invl;
+    outLength=invl*dotv;
+}
+
+
+#endif

+ 34 - 0
jme3-core/src/main/resources/Common/ShaderLib/module/Light.glsl

@@ -0,0 +1,34 @@
+#ifndef __LIGHT_MODULE__
+#define __LIGHT_MODULE__
+
+/**
+* Defines a light
+*/
+
+
+#ifndef Light
+    #struct StdLight 
+        vec4 color;
+        vec3 position;
+        float type;
+
+        float invRadius;
+        float spotAngleCos;
+        vec3 spotDirection;
+
+        bool ready;
+        
+        float NdotL;                  // cos angle between normal and light direction
+        float NdotH;                  // cos angle between normal and half vector
+        float LdotH;                  // cos angle between light direction and half vector
+        float HdotV;                  // cos angle between view direction and half vector
+        vec3 vector;  
+        vec3 dir;
+        float fallOff;
+    #endstruct
+    #define Light StdLight
+#endif
+
+
+
+#endif

+ 39 - 0
jme3-core/src/main/resources/Common/ShaderLib/module/PBRSurface.glsl

@@ -0,0 +1,39 @@
+#ifndef __SURFACE_MODULE__
+#define __SURFACE_MODULE__
+
+#ifndef PBRSurface
+    #struct StdPBRSurface
+        // from geometry
+        vec3 position; // position in w space
+        vec3 viewDir; // view dir in worldSpace
+        vec3 geometryNormal; // normals w/o normalmap
+        vec3 normal; // normals w/ normalmap
+        bool frontFacing; //gl_FrontFacing
+        float depth;
+
+        // from texture
+        vec3 albedo;
+        float alpha;
+        float metallic;              // metallic value at the surface
+        float roughness;
+        vec3 diffuseColor;
+        vec3 specularColor;
+        vec3 fZero;
+        vec3 ao;
+        float exposure;
+        vec3 emission;
+
+
+        // computed
+        float NdotV;
+
+        // from env
+        vec3 bakedLightContribution; // light from light map or other baked sources
+        vec3 directLightContribution; // light from direct light sources
+        vec3 envLightContribution; // light from environment 
+
+        float brightestLightStrength;
+    #endstruct
+    #define PBRSurface StdPBRSurface    
+#endif
+#endif

+ 600 - 0
jme3-core/src/main/resources/Common/ShaderLib/module/pbrlighting/PBRLightingUtils.glsllib

@@ -0,0 +1,600 @@
+#ifndef __PBR_LIGHT_UTILS_MODULE__
+#define __PBR_LIGHT_UTILS_MODULE__
+
+#import "Common/ShaderLib/Math.glsllib"
+#import "Common/ShaderLib/PBR.glsllib"
+#import "Common/ShaderLib/Parallax.glsllib"
+
+#import "Common/ShaderLib/module/Light.glsl"
+#import "Common/ShaderLib/module/PBRSurface.glsl"
+
+// enable all apis
+// #define ENABLE_PBRLightingUtils_getWorldPosition 1
+// #define ENABLE_PBRLightingUtils_getWorldNormal 1
+// #define ENABLE_PBRLightingUtils_getWorldTangent 1
+// #define ENABLE_PBRLightingUtils_getTexCoord 1
+// #define ENABLE_PBRLightingUtils_newLight 1
+// #define ENABLE_PBRLightingUtils_computeLightInWorldSpace 1
+// #define ENABLE_PBRLightingUtils_readPBRSurface 1
+// #define ENABLE_PBRLightingUtils_computeDirectLight 1
+// #define ENABLE_PBRLightingUtils_computeDirectLightContribution 1
+// #define ENABLE_PBRLightingUtils_computeProbesContribution 1
+
+#if defined(ENABLE_PBRLightingUtils_readPBRSurface) || 
+    defined(ENABLE_PBRLightingUtils_getWorldPosition)
+    varying vec3 wPosition;
+#endif 
+
+#if defined(ENABLE_PBRLightingUtils_readPBRSurface) || 
+    defined(ENABLE_PBRLightingUtils_getWorldNormal)
+    varying vec3 wNormal;
+#endif 
+
+#if (defined(ENABLE_PBRLightingUtils_readPBRSurface) &&
+    (defined(NORMALMAP) || defined(PARALLAXMAP))
+    ) || 
+    defined(ENABLE_PBRLightingUtils_getWorldTangent)
+    varying vec3 wTangent;
+#endif 
+
+#if defined(ENABLE_PBRLightingUtils_readPBRSurface) ||
+    defined(ENABLE_PBRLightingUtils_getTexCoord)
+    varying vec2 texCoord;
+    #ifdef SEPARATE_TEXCOORD
+        varying vec2 texCoord2;
+    #endif
+#endif
+
+
+
+#ifdef ENABLE_PBRLightingUtils_readPBRSurface   
+
+    varying vec4 Color;
+
+    
+    uniform vec4 g_AmbientLightColor;   
+    uniform float m_Roughness;
+    uniform float m_Metallic;
+
+
+    #ifdef BASECOLORMAP
+        uniform sampler2D m_BaseColorMap;
+    #endif
+
+    #ifdef USE_PACKED_MR
+        uniform sampler2D m_MetallicRoughnessMap;
+    #else
+        #ifdef METALLICMAP
+            uniform sampler2D m_MetallicMap;
+        #endif
+        #ifdef ROUGHNESSMAP
+            uniform sampler2D m_RoughnessMap;
+        #endif
+    #endif
+
+    #ifdef EMISSIVE
+        uniform vec4 m_Emissive;
+    #endif
+    #ifdef EMISSIVEMAP
+        uniform sampler2D m_EmissiveMap;
+    #endif
+    #if defined(EMISSIVE) || defined(EMISSIVEMAP)
+        uniform float m_EmissivePower;
+        uniform float m_EmissiveIntensity;
+    #endif 
+
+
+
+    #ifdef SPECGLOSSPIPELINE
+        uniform vec4 m_Specular;
+        uniform float m_Glossiness;
+        #ifdef USE_PACKED_SG
+            uniform sampler2D m_SpecularGlossinessMap;
+        #else
+            uniform sampler2D m_SpecularMap;
+            uniform sampler2D m_GlossinessMap;
+        #endif
+    #endif
+
+    #ifdef PARALLAXMAP
+        uniform sampler2D m_ParallaxMap;  
+    #endif
+    #if (defined(PARALLAXMAP) || (defined(NORMALMAP_PARALLAX) && defined(NORMALMAP)))
+        uniform float m_ParallaxHeight;
+    #endif
+
+    #ifdef LIGHTMAP
+        uniform sampler2D m_LightMap;
+    #endif
+
+    #ifdef AO_STRENGTH
+        uniform float m_AoStrength;
+    #endif
+    
+    #if defined(NORMALMAP) || defined(PARALLAXMAP)
+        uniform sampler2D m_NormalMap;       
+    #endif
+    #ifdef NORMALSCALE
+        uniform float m_NormalScale;
+    #endif
+
+    #ifdef DISCARD_ALPHA
+        uniform float m_AlphaDiscardThreshold;
+    #endif
+
+    #if defined(USE_VERTEX_COLORS_AS_SUN_INTENSITY)
+        varying vec4 vertColors;
+    #endif
+    #ifdef STATIC_SUN_INTENSITY
+        uniform float m_StaticSunIntensity;
+    #endif
+
+#endif
+
+#if defined(ENABLE_PBRLightingUtils_computeDirectLight) || defined(ENABLE_PBRLightingUtils_computeDirectLightContribution)
+    // Specular-AA
+    #ifdef SPECULAR_AA_SCREEN_SPACE_VARIANCE
+        uniform float m_SpecularAASigma;
+    #endif
+    #ifdef SPECULAR_AA_THRESHOLD
+        uniform float m_SpecularAAKappa;
+    #endif
+#endif
+
+
+
+
+#ifdef ENABLE_PBRLightingUtils_computeProbesContribution
+    #if NB_PROBES >= 1
+        uniform samplerCube g_PrefEnvMap;
+        uniform vec3 g_ShCoeffs[9];
+        uniform mat4 g_LightProbeData;
+    #endif
+    #if NB_PROBES >= 2
+        uniform samplerCube g_PrefEnvMap2;
+        uniform vec3 g_ShCoeffs2[9];
+        uniform mat4 g_LightProbeData2;
+    #endif
+    #if NB_PROBES == 3
+        uniform samplerCube g_PrefEnvMap3;
+        uniform vec3 g_ShCoeffs3[9];
+        uniform mat4 g_LightProbeData3;
+    #endif
+#endif
+
+#ifdef ENABLE_PBRLightingUtils_getWorldPosition
+    vec3 PBRLightingUtils_getWorldPosition(){
+        return wPosition.xyz;
+    }
+#endif
+
+#ifdef ENABLE_PBRLightingUtils_getWorldNormal
+    vec3 PBRLightingUtils_getWorldNormal(){
+        return normalize(wNormal.xyz);
+    }
+#endif
+
+#ifdef ENABLE_PBRLightingUtils_getWorldTangent
+    vec4 PBRLightingUtils_getWorldTangent(){
+        return wTangent;
+    }
+#endif
+
+#ifdef ENABLE_PBRLightingUtils_getTexCoord
+    vec2 PBRLightingUtils_getTexCoord(){
+        return texCoord;
+    }
+
+    #ifdef SEPARATE_TEXCOORD
+        vec2 PBRLightingUtils_getTexCoord2(){
+            return texCoord2;
+        }
+    #endif
+#endif
+
+
+
+#if defined(ENABLE_PBRLightingUtils_computeDirectLightContribution) || defined(ENABLE_PBRLightingUtils_newLight)
+    Light PBRLightingUtils_newLight(vec4 color, vec3 position, float type, float invRadius, float spotAngleCos, vec3 spotDirection){
+        Light l;
+        l.color = color;
+        l.position = position;
+        l.type = type;
+        l.invRadius = invRadius;
+        l.spotAngleCos = spotAngleCos;
+        l.spotDirection = spotDirection;
+        l.ready = false;
+        return l;
+    }
+#endif
+
+
+#if  defined(ENABLE_PBRLightingUtils_computeDirectLightContribution) || defined(ENABLE_PBRLightingUtils_computeLightInWorldSpace)
+    void PBRLightingUtils_computeLightInWorldSpace(vec3 worldPos,vec3 worldNormal, vec3 viewDir, inout Light light){
+        if(light.ready) return;
+
+        // lightComputeDir
+        float posLight = step(0.5, light.type);     
+        light.vector = light.position.xyz * sign(posLight - 0.5) - (worldPos * posLight); //tempVec lightVec
+
+        vec3 L; // lightDir
+        float dist;
+        Math_lengthAndNormalize(light.vector,dist,L);
+
+        float invRange=light.invRadius; // position.w
+        const float light_threshold=0.01;
+
+        #ifdef SRGB
+            light.fallOff = (1.0 - invRange * dist) / (1.0 + invRange * dist * dist); // lightDir.w
+            light.fallOff = clamp(light.fallOff, 1.0 - posLight, 1.0);
+        #else
+            light.fallOff = clamp(1.0 - invRange * dist * posLight, 0.0, 1.0);
+        #endif
+        
+        // computeSpotFalloff
+        if(light.type>1.){
+            vec3 spotdir = normalize(light.spotDirection);
+            float curAngleCos = dot(-L, spotdir);    
+            float innerAngleCos = floor(light.spotAngleCos) * 0.001;
+            float outerAngleCos = fract(light.spotAngleCos);
+            float innerMinusOuter = innerAngleCos - outerAngleCos;
+            float falloff = clamp((curAngleCos - outerAngleCos) / innerMinusOuter, 0.0, 1.0);
+            #ifdef SRGB
+                // Use quadratic falloff (notice the ^4)
+                falloff = pow(clamp((curAngleCos - outerAngleCos) / innerMinusOuter, 0.0, 1.0), 4.0);
+            #endif
+            light.fallOff*=falloff;
+        }
+
+
+        vec3 h=normalize(L+viewDir);
+        light.dir=L;
+        light.NdotL = max(dot(worldNormal, L), 0.0);
+        light.NdotH = max(dot(worldNormal, h), 0.0);
+        light.LdotH = max(dot(L, h), 0.0);
+        light.HdotV = max(dot(viewDir,h), 0.);
+    }
+#endif
+
+
+#ifdef ENABLE_PBRLightingUtils_readPBRSurface
+    PBRSurface PBRLightingUtils_readPBRSurface(    
+        in vec3 wViewDir
+    ){
+        
+        PBRSurface surface;
+        surface.position = wPosition;
+        surface.viewDir = wViewDir;
+        surface.geometryNormal = normalize(wNormal);
+
+        surface.bakedLightContribution = vec3(0);
+        surface.directLightContribution = vec3(0);
+        surface.envLightContribution = vec3(0);
+
+        #if defined(NORMALMAP) || defined(PARALLAXMAP)
+            vec3 tan = normalize(wTangent.xyz);
+            mat3 tbnMat = mat3(tan, wTangent.w * cross( surface.geometryNormal, tan), surface.geometryNormal);
+        #endif
+        
+        vec2 newTexCoord;
+        #if (defined(PARALLAXMAP) || (defined(NORMALMAP_PARALLAX) && defined(NORMALMAP)))
+            vec3 vViewDir =  wViewDir * tbnMat;  
+            #ifdef STEEP_PARALLAX
+                #ifdef NORMALMAP_PARALLAX
+                    //parallax map is stored in the alpha channel of the normal map         
+                    newTexCoord = steepParallaxOffset(m_NormalMap, vViewDir, texCoord, m_ParallaxHeight);
+                #else
+                    //parallax map is a texture
+                    newTexCoord = steepParallaxOffset(m_ParallaxMap, vViewDir, texCoord, m_ParallaxHeight);         
+                #endif
+            #else
+                #ifdef NORMALMAP_PARALLAX
+                    //parallax map is stored in the alpha channel of the normal map         
+                    newTexCoord = classicParallaxOffset(m_NormalMap, vViewDir, texCoord, m_ParallaxHeight);
+                #else
+                //parallax map is a texture
+                newTexCoord = classicParallaxOffset(m_ParallaxMap, vViewDir, texCoord, m_ParallaxHeight);
+            #endif
+        #endif
+        #else
+        newTexCoord = texCoord;    
+        #endif
+        
+        #ifdef BASECOLORMAP
+            vec4 baseColor = texture2D(m_BaseColorMap, newTexCoord) * Color;
+        #else
+            vec4 baseColor = Color;
+        #endif
+        
+        #ifdef DISCARD_ALPHA
+            if( baseColor.a < m_AlphaDiscardThreshold) discard;        
+        #endif
+
+        surface.albedo = baseColor.rgb;
+        surface.alpha = baseColor.a;
+
+        
+
+        //ao in r channel, roughness in green channel, metallic in blue channel!
+        vec3 aoRoughnessMetallicValue = vec3(1.0, 1.0, 0.0);
+        #ifdef USE_PACKED_MR
+            aoRoughnessMetallicValue = texture2D(m_MetallicRoughnessMap, newTexCoord).rgb;
+            surface.roughness = aoRoughnessMetallicValue.g * max(m_Roughness, 1e-4);
+            surface.metallic = aoRoughnessMetallicValue.b * max(m_Metallic, 0.0);
+        #else
+            #ifdef ROUGHNESSMAP
+                surface.roughness = texture2D(m_RoughnessMap, newTexCoord).r * max(m_Roughness, 1e-4);
+            #else
+                surface.roughness =  max(m_Roughness, 1e-4);
+            #endif
+            #ifdef METALLICMAP
+                surface.metallic = texture2D(m_MetallicMap, newTexCoord).r * max(m_Metallic, 0.0);
+            #else
+                surface.metallic =  max(m_Metallic, 0.0);
+            #endif
+        #endif
+    
+
+        
+        #if defined(NORMALMAP)
+            vec4 normalHeight = texture2D(m_NormalMap, newTexCoord);
+            // Note we invert directx style normal maps to opengl style
+            #ifdef NORMALSCALE
+                vec3 normal = normalize((normalHeight.xyz * vec3(2.0, NORMAL_TYPE * 2.0, 2.0) - vec3(1.0, NORMAL_TYPE * 1.0, 1.0)) * vec3(m_NormalScale, m_NormalScale, 1.0));
+            #else
+                vec3 normal = normalize((normalHeight.xyz * vec3(2.0, NORMAL_TYPE * 2.0, 2.0) - vec3(1.0, NORMAL_TYPE * 1.0, 1.0)));
+            #endif
+            surface.normal = normalize(tbnMat * normal);
+        #else 
+            surface.normal = normal;
+        #endif
+        
+        //spec gloss tex reads:
+        
+        #ifdef SPECGLOSSPIPELINE
+            #ifdef USE_PACKED_SG
+                vec4 specularColor = texture2D(m_SpecularGlossinessMap, newTexCoord);
+                float glossiness = specularColor.a * m_Glossiness;
+                specularColor *= m_Specular;
+            #else
+                #ifdef SPECULARMAP
+                    vec4 specularColor = texture2D(m_SpecularMap, newTexCoord);
+                #else
+                    vec4 specularColor = vec4(1.0);
+                #endif
+                #ifdef GLOSSINESSMAP
+                    float glossiness = texture2D(m_GlossinesMap, newTexCoord).r * m_Glossiness;
+                #else
+                    float glossiness = m_Glossiness;
+                #endif
+                specularColor *= m_Specular;
+            #endif
+            surface.diffuseColor = surface.albedo;// * (1.0 - max(max(specularColor.r, specularColor.g), specularColor.b));
+            surface.roughness = 1.0 - glossiness;
+            surface.fZero = specularColor.xyz;
+            surface.specularColor = specularColor;
+        #else
+            float specular = 0.5;
+            float nonMetalSpec = 0.08 * specular;
+            surface.specularColor = (nonMetalSpec - nonMetalSpec * surface.metallic) + surface.albedo * surface.metallic;
+            surface.diffuseColor = surface.albedo - surface.albedo * surface.metallic;
+            surface.fZero = vec3(specular);
+        #endif
+        
+
+        vec3 ao=vec3(1.0);
+        #ifdef LIGHTMAP
+            vec3 lightMapColor;
+            #ifdef SEPARATE_TEXCOORD
+                lightMapColor = texture2D(m_LightMap, texCoord2).rgb;
+            #else
+                lightMapColor = texture2D(m_LightMap, texCoord).rgb;
+            #endif               
+            #ifdef AO_MAP
+                lightMapColor.gb = lightMapColor.rr; 
+                ao = lightMapColor;
+            #else  
+                surface.bakedLightContribution +=  diffuseColor.rgb * lightMapColor;
+            #endif
+            surface.specularColor.rgb *= lightMapColor;
+        #endif
+        
+        #if defined(AO_PACKED_IN_MR_MAP) && defined(USE_PACKED_MR) 
+            ao = aoRoughnessMetallicValue.rrr; //note that this will override the AO value if it was previously read from a lightMap that is being used as AO_Map above. so don't try to use an AO map packed in metallic roughness while also using lightmap as ao map
+        #endif
+
+        #ifdef AO_STRENGTH
+            ao = 1.0 + m_AoStrength * (ao - 1.0);
+            // sanity check
+            ao = clamp(ao, 0.0, 1.0);
+        #endif
+        surface.ao=ao;
+        
+        #if defined(EMISSIVE) || defined (EMISSIVEMAP)
+            #ifdef EMISSIVEMAP
+                vec4 emissive = texture2D(m_EmissiveMap, newTexCoord);
+                #ifdef EMISSIVE
+                    emissive *= m_Emissive;
+                #endif
+            #else
+                vec4 emissive = m_Emissive;
+            #endif
+            surface.emission = emissive.rgb * pow(emissive.a, m_EmissivePower) * m_EmissiveIntensity;
+        #else 
+            surface.emission = vec3(0);
+        #endif
+        
+        #if defined(STATIC_SUN_INTENSITY)
+            surface.exposure = m_StaticSunIntensity; //single float value to indicate percentage of sunlight hitting the model (only suitable for small models or models with equal sunlight exposure accross the entire model
+        #elif defined(USE_VERTEX_COLORS_AS_SUN_INTENSITY)
+            surface.exposure = vertColors.r;    // use red channel of vertexColors for non-uniform sunlighting accross a single model
+        #else 
+            surface.exposure = 1.0; //default value
+        #endif 
+        
+        surface.frontFacing = gl_FrontFacing;
+        surface.depth = gl_FragCoord.z;
+
+
+        // surface.alphaRoughness = clamp(surface.roughness * surface.roughness, minRoughness, 1.0);
+        surface.NdotV = clamp(abs(dot(!surface.frontFacing?-surface.normal:surface.normal, surface.viewDir)), 0.001, 1.0);
+        // surface.reflectedVec = normalize(reflect(-surface.viewDir, surface.normal));
+
+        surface.brightestLightStrength=0.0;
+        return surface;
+    }
+#endif
+
+
+#if defined(ENABLE_PBRLightingUtils_computeDirectLight) || defined(ENABLE_PBRLightingUtils_computeDirectLightContribution)
+    void PBRLightingUtils_computeDirectLight(in Light light, in PBRSurface surface, inout vec3 directDiffuse, inout vec3 directSpecular, out float hdotv){
+
+        #ifdef SPECULAR_AA
+            #ifdef SPECULAR_AA_SCREEN_SPACE_VARIANCE
+                float sigma = m_SpecularAASigma;
+            #else 
+                float sigma = 1.0;
+            #endif
+            
+            #ifdef SPECULAR_AA_THRESHOLD
+                float kappa = m_SpecularAAKappa;
+            #else
+                float kappa = 0.18;
+            #endif    
+            
+            hdotv = PBR_ComputeDirectLightWithSpecularAA(
+                                    surface.normal,
+                                    light.dir.xyz, 
+                                    surface.viewDir,
+                                    light.color.rgb, 
+                                    surface.fZero, 
+                                    surface.roughness, 
+                                    sigma,
+                                    kappa, 
+                                    surface.NdotV,
+                                    directDiffuse,  
+                                    directSpecular
+                    );
+        #else
+            hdotv = PBR_ComputeDirectLight(
+                                    surface.normal, 
+                                    light.dir.xyz, 
+                                    surface.viewDir,
+                                    light.color.rgb, 
+                                    surface.fZero, 
+                                    surface.roughness, 
+                                    surface.NdotV,
+                                    directDiffuse,  
+                                    directSpecular
+                    );
+        #endif
+
+    }
+#endif
+
+ 
+#ifdef ENABLE_PBRLightingUtils_computeDirectLightContribution
+    void PBRLightingUtils_computeDirectLightContribution(
+        in vec4 lightData0, 
+        in vec4 lightData1, 
+        in vec4 lightData2,
+        inout PBRSurface surface
+    ){
+        vec4 lightColor = vec4(lightData0.rgb,1.0);
+        float lightType = lightData0.w;  
+
+        vec3 lightPosition = lightData1.xyz;
+        float lightInvRadius = lightData1.w;
+        
+        vec3 spotDirection = lightData2.xyz;
+        float spotAngleCos = lightData2.w;
+
+        Light light = PBRLightingUtils_newLight(lightColor, lightPosition, lightType, lightInvRadius, spotAngleCos, spotDirection);
+        PBRLightingUtils_computeLightInWorldSpace(surface.position, surface.normal, surface.viewDir, light);
+
+        vec3 directDiffuse;
+        vec3 directSpecular;
+        float hdotv;
+        PBRLightingUtils_computeDirectLight(light, surface, directDiffuse, directSpecular, hdotv); 
+
+        vec3 directLighting = surface.diffuseColor.rgb * directDiffuse + directSpecular;
+
+        #if defined(USE_VERTEX_COLORS_AS_SUN_INTENSITY) || defined(STATIC_SUN_INTENSITY)         
+            if(light.fallOff == 1.0){
+                directLighting.rgb *= surface.exposure;//  used to scale down how intense just the sun is indoors, and so the ambientLighting can be scaled back up indoors based on nearest pointlight intensity (ambient and direct light are 1.0 fallOff)                
+            } else{
+                surface.brightestLightStrength = max(light.fallOff, surface.brightestLightStrength);
+            }
+        #endif
+
+        surface.directLightContribution.rgb += directLighting * light.fallOff;
+    }
+#endif
+
+#ifdef ENABLE_PBRLightingUtils_computeProbesContribution
+    void PBRLightingUtils_computeProbesContribution(inout PBRSurface surface){   
+
+        #ifdef BRIGHTEN_INDOOR_SHADOWS
+            float minVertLighting = 0.0833; //enable this when using shadows, in order to brighten indoor areas (which are naturally covered from the DL shadows) so that indoor areas are not way too dark when using IndoorLighting with shadows compared to when shadows are off
+        #else
+            float minVertLighting = 0.0533;
+        #endif
+        
+        float finalLightingScale=1.0;
+        finalLightingScale = max(finalLightingScale, surface.brightestLightStrength);    
+        finalLightingScale = max(finalLightingScale, minVertLighting); //essentially just the vertColors.r (aka indoor light exposure) multiplied by the time of day scale.   
+
+
+        #if NB_PROBES > 0
+            float probeNdfSum=0;
+            float invProbeNdfSum=0;    
+            
+            #for i=1..4 ( #if NB_PROBES >= $i $0 #endif )
+                vec3 probeColor$i;
+                float probeNdf$i = renderProbe(
+                    surface.viewDir, 
+                    surface.position, 
+                    surface.normal,
+                    surface.geometryNormal,
+                    surface.roughness,
+                    vec4(surface.diffuseColor,1.0),
+                    vec4(surface.specularColor,1.0), 
+                    surface.NdotV, 
+                    surface.ao, 
+                    #if $i == 1
+                        g_LightProbeData, 
+                    #else  
+                        g_LightProbeData$i,
+                    #endif
+                    g_ShCoeffs,
+                    g_PrefEnvMap,
+                    probeColor$i
+                );
+                float probeInvNdf$i =  max(1.0 - probeNdf$i,0.0);
+                probeNdfSum += probeNdf$i;
+                invProbeNdfSum += probeInvNdf$i;
+                #ifdef USE_AMBIENT_LIGHT
+                    probeColor$i.rgb *= g_AmbientLightColor.rgb;
+                #endif
+                probeColor$i.rgb *= finalLightingScale;
+            #endfor
+
+            #if NB_PROBES > 1
+                float probeWeightSum=0;
+                #for i=1..4 ( #if NB_PROBES >= $i $0 #endif )
+                    float probeWeight$i = ((1.0 - (probeNdf$i / probeNdfSum)) / (NB_PROBES - 1)) *  ( probeInvNdf$i / invProbeNdfSum);
+                    probeWeightSum += probeWeight$i;    
+                #endfor 
+
+                #for i=1..4 ( #if NB_PROBES >= $i $0 #endif )       
+                    surface.envLightContribution.rgb += probeColor$i * clamp( probeWeight$i / probeWeightSum, 0., 1.);
+                #endfor 
+            #else
+                surface.envLightContribution.rgb += probeColor1;
+            #endif
+        #endif
+    }
+#endif
+
+
+#endif

+ 48 - 0
jme3-core/src/test/java/com/jme3/shader/GLSLPreprocessorTest.java

@@ -75,4 +75,52 @@ public class GLSLPreprocessorTest {
         String sourceCheck=readAllAsString(testData.openStream());
         assertEquals(sourceCheck, processedSource);                  
     }
+
+    @Test
+    public void testStruct() throws Exception {
+            String source = "// nothing\n#struct MyStruct \n" + "  float x ;//nothing  \n" + "     float y;\n"
+                            + "#endstruct\n//nothing";
+            String processedSource = readAllAsString(
+                            Preprocessor.apply(new ByteArrayInputStream(source.getBytes("UTF-8"))));
+            System.out.println(processedSource);
+            AssetInfo testData = TestUtil.createAssetManager()
+                            .locateAsset(new AssetKey("GLSLPreprocessorTest.testStruct.validOutput"));
+            assertNotNull(testData);
+            String sourceCheck = readAllAsString(testData.openStream());
+            assertEquals(sourceCheck, processedSource);
+    }
+
+    @Test
+    public void testStructExtends() throws Exception {
+            String source = "// nothing\n#struct BaseStruct \n" + "  float x0;\n" + "  float y0;\n"
+                            + "#endstruct\n//nothing\n";
+            source += "//nothing\n#struct MyStruct extends BaseStruct \n" + "  float x;\n" + "  float y;\n"
+                            + "#endstruct\n//nothing\n";
+            String processedSource = readAllAsString(
+                            Preprocessor.apply(new ByteArrayInputStream(source.getBytes("UTF-8"))));
+            System.out.println(processedSource);
+            AssetInfo testData = TestUtil.createAssetManager()
+                            .locateAsset(new AssetKey("GLSLPreprocessorTest.testStructExtends.validOutput"));
+            assertNotNull(testData);
+            String sourceCheck = readAllAsString(testData.openStream());
+            assertEquals(sourceCheck, processedSource);
+    }
+
+    @Test
+    public void testStructExtendsMulti() throws Exception {
+            String source = "#struct BaseStruct \n" + "  float x0;\n" + "  float y0;\n" + "#endstruct\n";
+            source += "#struct BaseStruct2 \n" + "  float x1;\n" + "  float y1;\n"
+                            + "#endstruct\n//nothing\n";
+            source += "#struct MyStruct extends BaseStruct, BaseStruct2\n" + "  float x;\n" + "  float y;\n"
+                            + "#endstruct\n";
+            String processedSource = readAllAsString(
+                            Preprocessor.apply(new ByteArrayInputStream(source.getBytes("UTF-8"))));
+            System.out.println(processedSource);
+            AssetInfo testData = TestUtil.createAssetManager().locateAsset(
+                            new AssetKey("GLSLPreprocessorTest.testStructExtendsMulti.validOutput"));
+            assertNotNull(testData);
+            String sourceCheck = readAllAsString(testData.openStream());
+            assertEquals(sourceCheck, processedSource);
+    }
+
 }

+ 8 - 0
jme3-core/src/test/resources/GLSLPreprocessorTest.testStruct.validOutput

@@ -0,0 +1,8 @@
+// nothing
+#define STRUCT_MyStruct \
+float x ; \
+float y;
+struct MyStruct { 
+STRUCT_MyStruct
+};
+//nothing

+ 17 - 0
jme3-core/src/test/resources/GLSLPreprocessorTest.testStructExtends.validOutput

@@ -0,0 +1,17 @@
+// nothing
+#define STRUCT_BaseStruct \
+float x0; \
+float y0;
+struct BaseStruct { 
+STRUCT_BaseStruct
+};
+//nothing
+//nothing
+#define STRUCT_MyStruct \
+STRUCT_BaseStruct \
+float x; \
+float y;
+struct MyStruct { 
+STRUCT_MyStruct
+};
+//nothing

+ 21 - 0
jme3-core/src/test/resources/GLSLPreprocessorTest.testStructExtendsMulti.validOutput

@@ -0,0 +1,21 @@
+#define STRUCT_BaseStruct \
+float x0; \
+float y0;
+struct BaseStruct { 
+STRUCT_BaseStruct
+};
+#define STRUCT_BaseStruct2 \
+float x1; \
+float y1;
+struct BaseStruct2 { 
+STRUCT_BaseStruct2
+};
+//nothing
+#define STRUCT_MyStruct \
+STRUCT_BaseStruct \
+STRUCT_BaseStruct2 \
+float x; \
+float y;
+struct MyStruct { 
+STRUCT_MyStruct
+};

+ 93 - 0
jme3-core/src/tools/java/jme3tools/shader/Preprocessor.java

@@ -58,6 +58,7 @@ public class Preprocessor {
         String code = bos.toString("UTF-8");
 
         code = Preprocessor.forMacro(code);
+        code = Preprocessor.structMacro(code);
 
         return new ByteArrayInputStream(code.getBytes("UTF-8"));
     }
@@ -118,4 +119,96 @@ public class Preprocessor {
         return code;
     }
 
+    /**
+     * <code>
+     * #struct MyStruct extends BaseStruct, BaseStruct2
+     *  int i; 
+     *  int b; 
+     * #endstruct
+     * </code>
+     */
+    // match #struct MyStruct extends BaseStruct, BaseStruct2
+    // extends is optional
+    // private static final Pattern FOR_REGEX = Pattern
+    // .compile("([^=]+)=\\s*([0-9]+)\\s*\\.\\.\\s*([0-9]+)\\s*\\((.+)\\)");
+
+    private static final Pattern STRUCT_REGEX = Pattern
+            .compile("(\\w+)(?:\\s+extends\\s+(\\w+(?:,\\s*\\w+)*))?");
+
+    public static String structMacro(String code) {
+        StringBuilder expandedCode = new StringBuilder();
+        StringBuilder currentStruct = null;
+        String structDec = null;
+        int skip = 0;
+        String[] codeLines = code.split("\n");
+        boolean captured = false;
+        for (String line : codeLines) {
+            if (!captured) {
+                String trimmedLine = line.trim();
+                if (trimmedLine.startsWith("#struct")) {
+                    if (skip == 0) {
+                        structDec = trimmedLine;
+                        currentStruct = new StringBuilder();
+                        skip++;
+                        continue;
+                    }
+                    skip++;
+                } else if (trimmedLine.startsWith("#endstruct")) {
+                    skip--;
+                    if (skip == 0) {
+                        structDec = structDec.substring("#struct ".length()).trim();
+
+                        Matcher matcher = STRUCT_REGEX.matcher(structDec);
+                        if (matcher.matches()) {
+                            String structName = matcher.group(1);
+                            if (structName == null) structName = "";
+
+                            String extendsStructs = matcher.group(2);
+                            String extendedStructs[];
+                            if (extendsStructs != null) {
+                                extendedStructs = extendsStructs.split(",\\s*");
+                            } else {
+                                extendedStructs = new String[0];
+                            }
+                            String structBody = currentStruct.toString();
+                            if (structBody == null) structBody = "";
+                            else {
+                                // remove tail spaces
+                                structBody = structBody.replaceAll("\\s+$", "");
+                            }
+
+                            currentStruct = null;
+                            expandedCode.append("#define STRUCT_").append(structName).append(" \\\n");
+                            for (String extendedStruct : extendedStructs) {
+                                expandedCode.append("STRUCT_").append(extendedStruct).append(" \\\n");
+                            }
+                            String structBodyLines[] = structBody.split("\n");
+                            for (int i = 0; i < structBodyLines.length; i++) {
+                                String structBodyLine = structBodyLines[i];
+                                structBodyLine = structBodyLine.trim();
+                                if (structBodyLine == "") continue;
+                                // remove comments if any
+                                int commentIndex = structBodyLine.indexOf("//");
+                                if (commentIndex >= 0)
+                                    structBodyLine = structBodyLine.substring(0, commentIndex);
+                                expandedCode.append(structBodyLine);
+                                if (i < structBodyLines.length - 1) expandedCode.append(" \\");
+                                expandedCode.append("\n");
+                            }
+                            expandedCode.append("struct ").append(structName).append(" { \nSTRUCT_")
+                                    .append(structName).append("\n};\n");
+                            captured = true;
+                            continue;
+                        }
+                    }
+                }
+            }
+            if (currentStruct != null) currentStruct.append(line).append("\n");
+            else expandedCode.append(line).append("\n");
+        }
+        code = expandedCode.toString();
+        if (captured) code = structMacro(code);
+        return code;
+    }
+
 }

+ 2 - 2
jme3-examples/build.gradle

@@ -19,8 +19,8 @@ dependencies {
     implementation project(':jme3-effects')
     implementation project(':jme3-jbullet')
     implementation project(':jme3-jogg')
-    implementation project(':jme3-lwjgl')
-//    implementation project(':jme3-lwjgl3')
+//    implementation project(':jme3-lwjgl')
+    implementation project(':jme3-lwjgl3')
     implementation project(':jme3-networking')
     implementation project(':jme3-niftygui')
     implementation project(':jme3-plugins')

+ 1 - 2
jme3-examples/src/main/java/jme3test/awt/TestCanvas.java

@@ -29,7 +29,6 @@
  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
-
 package jme3test.awt;
 
 import com.jme3.app.LegacyApplication;
@@ -171,7 +170,7 @@ public class TestCanvas {
             public void actionPerformed(ActionEvent e) {
                 currentPanel.remove(canvas);
                 app.stop(true);
-
+                
                 createCanvas(appClass);
                 currentPanel.add(canvas, BorderLayout.CENTER);
                 frame.pack();

+ 3 - 0
jme3-lwjgl3/build.gradle

@@ -1,9 +1,12 @@
 dependencies {
     api project(':jme3-core')
     api project(':jme3-desktop')
+    
+    api "org.lwjglx:lwjgl3-awt:0.1.8"
 
     api "org.lwjgl:lwjgl:${lwjgl3Version}"
     api "org.lwjgl:lwjgl-glfw:${lwjgl3Version}"
+    api "org.lwjgl:lwjgl-jawt:${lwjgl3Version}"
     api "org.lwjgl:lwjgl-jemalloc:${lwjgl3Version}"
     api "org.lwjgl:lwjgl-openal:${lwjgl3Version}"
     api "org.lwjgl:lwjgl-opencl:${lwjgl3Version}"

+ 623 - 168
jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2022 jMonkeyEngine
+ * Copyright (c) 2009-2024 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -37,151 +37,538 @@ import com.jme3.input.awt.AwtKeyInput;
 import com.jme3.input.awt.AwtMouseInput;
 import com.jme3.system.AppSettings;
 import com.jme3.system.JmeCanvasContext;
-import com.jme3.system.JmeContext.Type;
-import com.jme3.texture.FrameBuffer;
-import com.jme3.texture.FrameBuffer.FrameBufferTarget;
-import com.jme3.texture.Image;
-import com.jme3.util.BufferUtils;
-import com.jme3.util.Screenshots;
+import com.jme3.system.lwjglx.LwjglxGLPlatform;
+
 import java.awt.AWTException;
-import java.awt.BufferCapabilities;
 import java.awt.Canvas;
 import java.awt.Graphics;
 import java.awt.Graphics2D;
-import java.awt.ImageCapabilities;
-import java.awt.RenderingHints;
+import java.awt.GraphicsConfiguration;
+import java.awt.Toolkit;
 import java.awt.event.ComponentAdapter;
 import java.awt.event.ComponentEvent;
+import java.awt.event.ComponentListener;
 import java.awt.geom.AffineTransform;
-import java.awt.image.AffineTransformOp;
-import java.awt.image.BufferStrategy;
-import java.awt.image.BufferedImage;
-import java.nio.ByteBuffer;
-import java.nio.IntBuffer;
+import java.text.MessageFormat;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
+import org.lwjgl.awthacks.NonClearGraphics;
+import org.lwjgl.awthacks.NonClearGraphics2D;
+import org.lwjgl.opengl.awt.GLData;
+import org.lwjgl.system.Platform;
+
+import static org.lwjgl.system.MemoryUtil.*;
+import static com.jme3.system.lwjglx.LwjglxDefaultGLPlatform.*;
+
+/**
+ * Class <code>LwjglCanvas</code> that integrates <a href="https://github.com/LWJGLX/lwjgl3-awt">LWJGLX</a>
+ * which allows using AWT-Swing components.
+ * 
+ * <p>
+ * If <b>LwjglCanvas</b> throws an exception due to configuration problems, we can debug as follows:
+ * <br>
+ * - In <code>AppSettings</code>, set this property to enable a debug that displays
+ * the effective data for the context.
+ * <pre><code>
+ * ....
+ *  AppSettings settings = new AppSettings(true);
+ *  settings.putBoolean("GLDataEffectiveDebug", true);
+ * ...
+ * </code></pre>
+ * 
+ * <p>
+ * <b>NOTE:</b> If running <code>LwjglCanvas</code> on older machines, the <code>SRGB | Gamma Correction</code> option 
+ * will raise an exception, so it should be disabled.
+ * <pre><code>
+ * ....
+ *  AppSettings settings = new AppSettings(true);
+ *  settings.setGammaCorrection(false);
+ * ...
+ * </code></pre>
+ * 
+ * @author wil
+ */
 public class LwjglCanvas extends LwjglWindow implements JmeCanvasContext, Runnable {
 
-    private static final Logger logger = Logger.getLogger(LwjglCanvas.class.getName());
-
-    private final Canvas canvas;
-
-    private BufferedImage img;
-    private FrameBuffer fb;
-
-    private ByteBuffer byteBuf;
-    private IntBuffer intBuf;
-
-    private BufferStrategy strategy;
-    private AffineTransformOp transformOp;
+    /** Logger class. */
+    private static final Logger LOGGER = Logger.getLogger(LwjglCanvas.class.getName());
+    
+    /** GL versions map. */
+    private static final Map<String, Consumer<GLData>> RENDER_CONFIGS = new HashMap<>();
+    
+    /** Type of operating system where this context is running. */
+    private static final Platform OS = Platform.get();
+    
+    /*
+        Register the different versions.
+    */
+    static {
+        RENDER_CONFIGS.put(AppSettings.LWJGL_OPENGL30, (data) -> {
+            data.majorVersion = 3;
+            data.minorVersion = 0;
+        });
+        RENDER_CONFIGS.put(AppSettings.LWJGL_OPENGL31, (data) -> {
+            data.majorVersion = 3;
+            data.minorVersion = 1;
+        });
+        RENDER_CONFIGS.put(AppSettings.LWJGL_OPENGL32, (data) -> {
+            data.majorVersion = 3;
+            data.minorVersion = 2;
+            data.profile = GLData.Profile.COMPATIBILITY;
+        });
+        RENDER_CONFIGS.put(AppSettings.LWJGL_OPENGL33, (data) -> {
+            data.majorVersion = 3;
+            data.minorVersion = 3;
+            data.profile = GLData.Profile.COMPATIBILITY;
+        });
+        RENDER_CONFIGS.put(AppSettings.LWJGL_OPENGL40, (data) -> {
+            data.majorVersion = 4;
+            data.minorVersion = 0;
+            data.profile = GLData.Profile.COMPATIBILITY;
+        });
+        RENDER_CONFIGS.put(AppSettings.LWJGL_OPENGL41, (data) -> {
+            data.majorVersion = 4;
+            data.minorVersion = 1;
+            data.profile = GLData.Profile.COMPATIBILITY;
+        });
+        RENDER_CONFIGS.put(AppSettings.LWJGL_OPENGL42, (data) -> {
+            data.majorVersion = 4;
+            data.minorVersion = 2;
+            data.profile = GLData.Profile.COMPATIBILITY;
+        });
+        RENDER_CONFIGS.put(AppSettings.LWJGL_OPENGL43, (data) -> {
+            data.majorVersion = 4;
+            data.minorVersion = 3;
+            data.profile = GLData.Profile.COMPATIBILITY;
+        });
+        RENDER_CONFIGS.put(AppSettings.LWJGL_OPENGL44, (data) -> {
+            data.majorVersion = 4;
+            data.minorVersion = 4;
+            data.profile = GLData.Profile.COMPATIBILITY;
+        });
+        RENDER_CONFIGS.put(AppSettings.LWJGL_OPENGL45, (data) -> {
+            data.majorVersion = 4;
+            data.minorVersion = 5;
+            data.profile = GLData.Profile.COMPATIBILITY;
+        });
+    }
+    
+    /**
+     * An AWT <code>java.awt.Canvas</code> that supports to be drawn on using OpenGL.
+     */
+    private class LwjglAWTGLCanvas extends Canvas {
+        
+        /** 
+         * A {@link com.jme3.system.lwjglx.LwjglxGLPlatform} object. 
+         * @see org.lwjgl.opengl.awt.PlatformGLCanvas
+         */
+        private LwjglxGLPlatform platformCanvas;
+        
+        /**  The OpenGL context (LWJGL3-AWT). */
+        private long context;
+        
+        /**
+         * Information object used to create the OpenGL context.
+         */
+        private GLData data;
+        
+        /** Effective data to initialize the context. */
+        private GLData effective;
+
+        /**
+         * Constructor of the <code>LwjglAWTGLCanva</code> class where objects are
+         * initialized for OpenGL-AWT rendering
+         * 
+         * @param data A {@link org.lwjgl.opengl.awt.GLData} object
+         */
+        public LwjglAWTGLCanvas(GLData data) {
+            this.effective = new GLData();
+            this.context   = NULL;
+            this.data      = data;
+            
+            try {
+                platformCanvas = createLwjglxGLPlatform();
+            } catch (UnsupportedOperationException e) {
+                listener.handleError(e.getLocalizedMessage(), e);
+            }
+        }
+        
+        /**
+         * (non-Javadoc)
+         * @see java.awt.Component#addComponentListener(java.awt.event.ComponentListener) 
+         * @param l object-listener
+         */
+        @Override
+        public synchronized void addComponentListener(ComponentListener l) {
+            super.addComponentListener(l);
+        }
+        
+        /**
+         * Returns the effective data (recommended or ideal) to initialize the 
+         * LWJGL3-AWT context.
+         * 
+         * @return A {@link org.lwjgl.opengl.awt.GLData} object
+         */
+        public GLData getGLDataEffective() {
+            return effective;
+        }
+        
+        /**
+         * Called after <code>beforeRender()</code> to release the threads (unlock); 
+         * so that AWT can update its threads normally.
+         * <p>
+         * <b>NOTE:</b> It is very important to call this method and not leave AWT 
+         * hanging (breaking it) regardless of whether an error occurs during OpenGL
+         * rendering.
+         */
+        public void afterRender() {
+            // release the rendering context
+            platformCanvas.makeCurrent(NULL);
+            try {
+                platformCanvas.unlock(); // <- MUST unlock on Linux
+            } catch (AWTException e) {
+                listener.handleError("Failed to unlock Canvas", e);
+            }
+        }
+        
+        /**
+         * Called before <code>afterRender()</code> to prepare the AWT drawing surface.
+         */
+        public void beforeRender() {
+            // this is where the OpenGL rendering context is generated.
+            if (context == NULL) {
+                try {
+                    context = platformCanvas.create(this, data, effective);
+                } catch (AWTException e) {
+                    listener.handleError("Exception while creating the OpenGL context", e);
+                    return;
+                }
+            }
+            
+            /*
+             * To start drawing on the AWT surface, the AWT threads must be locked to
+             * avoid conflicts when drawing on the canvas.
+             */
+            try {
+                platformCanvas.lock(); // <- MUST lock on Linux
+            } catch (AWTException e) {
+                listener.handleError("Failed to lock Canvas", e);
+            }
+            
+            /*
+             * The 'makeCurrent(long)' method converts the specified OpenGL rendering 
+             * context to the current rendering context.
+             */
+            platformCanvas.makeCurrent(context);
+        }
+        
+        /**
+         * Frees up the drawing surface (only on Windows and MacOSX).
+         */
+        public void doDisposeCanvas() {
+            if (OS != Platform.LINUX) {
+                platformCanvas.dispose();
+            }
+        }
+        
+        /**
+         * This is where you actually draw on the canvas (framebuffer).
+         */
+        public void swapBuffers() {
+            platformCanvas.swapBuffers();
+        }
+        
+        /**
+         * (non-Javadoc)
+         * @see java.awt.Component#addNotify() 
+         */
+        @Override
+        public void addNotify() {
+            super.addNotify();
+            /* you have to notify if the canvas is visible to draw on it. */
+            synchronized (lock) {
+                hasNativePeer.set(true);
+            }
+            requestFocusInWindow();
+        }
+        
+        /**
+         * (non-Javadoc)
+         * @see java.awt.Component#removeNotify() 
+         */
+        @Override
+        public void removeNotify() {
+            synchronized (lock) {
+                // prepare for a possible re-adding
+                if ((OS != Platform.LINUX) && (context != NULL)) {
+                    platformCanvas.deleteContext(context);
+                    context = NULL;
+                }
+                hasNativePeer.set(false);
+            }            
+            super.removeNotify();
+            if (OS == Platform.WINDOWS) {
+                LOGGER.log(Level.WARNING, "Windows does not support this functionality: remove(__canvas__)");
+            }
+        }
+        
+        /**
+         * (non-Javadoc)
+         * @see com.jme3.system.lwjglx.LwjglxGLPlatform#destroy() 
+         */
+        public void destroy() {
+            platformCanvas.destroy();
+        }
+        
+        /**
+         * Returns Graphics object that ignores {@link java.awt.Graphics#clearRect(int, int, int, int)}
+         * calls.
+         * <p>
+         * This is done so that the frame buffer will not be cleared by AWT/Swing internals.
+         * 
+         * @see org.lwjgl.awthacks.NonClearGraphics2D
+         * @see org.lwjgl.awthacks.NonClearGraphics
+         * @return Graphics
+         */
+        @Override
+        public Graphics getGraphics() {
+            Graphics graphics = super.getGraphics();
+            if (graphics instanceof Graphics2D) {
+                return new NonClearGraphics2D((Graphics2D) graphics);
+            }
+            return new NonClearGraphics(graphics);
+        }
+    }
+    
+    /** Canvas-AWT. */
+    private final LwjglAWTGLCanvas canvas;
+    
+    /**
+     * Configuration data to start the AWT context, this is used by the
+     * {@code lwjgl-awt} library.
+     */
+    private GLData glData;
+    
+    /** Used to display the effective data for the {@code AWT-Swing} drawing surface per console. */
+    private final AtomicBoolean showGLDataEffective = new AtomicBoolean(false);
+    
+    /** Used to notify the canvas status ({@code remove()/add()}). */
     private final AtomicBoolean hasNativePeer = new AtomicBoolean(false);
+    
+    /** Notify if the canvas is visible and has a parent.*/
     private final AtomicBoolean showing = new AtomicBoolean(false);
-
-    private int width = 1;
-    private int height = 1;
+    
+    /** Notify if there is a change in canvas dimensions. */
     private AtomicBoolean needResize = new AtomicBoolean(false);
-    private final Object lock = new Object();
+    
+    /**
+     * Flag that uses the context to check if it is initialized or not, this prevents
+     * it from being initialized multiple times and potentially breaking the JVM.
+     */
+    private AtomicBoolean contextFlag = new AtomicBoolean(false);
+
+    /** Semaphort used to check the "terminate" signal. */
+    private final Semaphore signalTerminate = new Semaphore(0);
 
+    /** lock-object. */
+    private final Object lock = new Object();
+    
+    /** Framebuffer width. */
+    private int framebufferWidth = 1;
+    
+    /** Framebuffer height. */
+    private int framebufferHeight = 1;
+
+    /** AWT keyboard input manager. */
     private AwtKeyInput keyInput;
+    
+    /** AWT mouse input manager. */
     private AwtMouseInput mouseInput;
-
+    
+    /**
+     * Generate a new OpenGL context (<code>LwjglCanvas</code>) to integrate 
+     * AWT/Swing with JME3 in your desktop applications.
+     */
     public LwjglCanvas() {
         super(Type.Canvas);
-
-        canvas = new Canvas() {
-            @Override
-            public void paint(Graphics g) {
-                Graphics2D g2d = (Graphics2D) g;
-                synchronized (lock) {
-                    g2d.drawImage(img, transformOp, 0, 0);
-                }
-            }
-
-            @Override
-            public void addNotify() {
-                super.addNotify();
-
-                synchronized (lock) {
-                    hasNativePeer.set(true);
-                }
-
-                requestFocusInWindow();
-            }
-
+        glData = new GLData();
+        canvas = new LwjglAWTGLCanvas(glData);
+        canvas.setIgnoreRepaint(true);
+        
+        // To determine the size of the framebuffer every time the user resizes
+        // the canvas (this works if the component has a parent)
+        canvas.addComponentListener(new ComponentAdapter() {
             @Override
-            public void removeNotify() {
+            public void componentResized(ComponentEvent e) {                
                 synchronized (lock) {
-                    hasNativePeer.set(false);
-                }
+                    GraphicsConfiguration gc = canvas.getGraphicsConfiguration();
+                    if (gc == null) {
+                        return;
+                    }
+                    
+                    AffineTransform at = gc.getDefaultTransform();
+                    float sx = (float) at.getScaleX(),
+                          sy = (float) at.getScaleY();
 
-                super.removeNotify();
-            }
-        };
-        canvas.addComponentListener(new ComponentAdapter() {
+                    int fw = (int) (canvas.getWidth() * sx);
+                    int fh = (int) (canvas.getHeight() * sy);
 
-            @Override
-            public void componentResized(ComponentEvent e) {
-                synchronized (lock) {
-                    int newWidth = Math.max(canvas.getWidth(), 1);
-                    int newHeight = Math.max(canvas.getHeight(), 1);
-                    if (width != newWidth || height != newHeight) {
-                        width = newWidth;
-                        height = newHeight;
+                    if (fw != framebufferWidth || fh != framebufferHeight) {
+                        framebufferWidth = Math.max(fw, 1);
+                        framebufferHeight = Math.max(fh, 1);
                         needResize.set(true);
                     }
                 }
-            }
+            }            
         });
-        canvas.setFocusable(true);
-        canvas.setIgnoreRepaint(true);
     }
 
+    /**
+     * (non-Javadoc)
+     * @see com.jme3.system.JmeContext#destroy(boolean) 
+     * @param waitFor boolean
+     */
     @Override
-    public Canvas getCanvas() {
-        return canvas;
+    public void destroy(boolean waitFor) {
+        super.destroy(waitFor);
+        this.contextFlag.set(false);
     }
 
+    /**
+     * (non-Javadoc)
+     * @see com.jme3.system.JmeContext#create(boolean) 
+     * @param waitFor boolean
+     */
     @Override
-    protected void showWindow() {
+    public void create(boolean waitFor) {
+        if (this.contextFlag.get()) {
+            return;
+        }
+        // create context
+        super.create(waitFor);
+        this.contextFlag.set(true);
     }
-
+    
+    /**
+     * (non-Javadoc)
+     * @see com.jme3.system.lwjgl.LwjglWindow#createContext(com.jme3.system.AppSettings) 
+     * @param settings A {@link com.jme3.system.AppSettings} object
+     */
     @Override
-    protected void setWindowIcon(final AppSettings settings) {
+    protected void createContext(AppSettings settings)  {
+        try {
+            Thread.sleep(1000);
+        } catch (InterruptedException ex) {
+            LOGGER.log(Level.SEVERE, "LWJGL3-AWT: Interrupted!", ex);
+        }
+        
+        super.createContext(settings);
+        RENDER_CONFIGS.computeIfAbsent(settings.getRenderer(), (t) -> {
+            return (data) -> {
+                data.majorVersion = 2;
+                data.minorVersion = 0;
+            };
+        }).accept(glData);
+        
+        if (settings.getBitsPerPixel() == 24) {
+            glData.redSize = 8;
+            glData.greenSize = 8;
+            glData.blueSize = 8;            
+        } else if (settings.getBitsPerPixel() == 16) {
+            glData.redSize = 5;
+            glData.greenSize = 6;
+            glData.blueSize = 5;            
+        }
+        
+        // Enable vsync for LWJGL3-AWT
+        if (settings.isVSync()) {
+            glData.swapInterval = 1;
+        } else {
+            glData.swapInterval = 0;
+        }
+        
+        // This will activate the "effective data" scrubber.
+        showGLDataEffective.set(settings.getBoolean("GLDataEffectiveDebug"));
+        
+        glData.depthSize = settings.getBitsPerPixel();
+        glData.alphaSize = settings.getAlphaBits();
+        glData.sRGB = settings.isGammaCorrection(); // Not compatible with very old devices
+        
+        glData.depthSize = settings.getDepthBits();
+        glData.stencilSize = settings.getStencilBits();
+        glData.samples = settings.getSamples();
+        glData.stereo = settings.useStereo3D();  
+       
+        glData.debug = settings.isGraphicsDebug();
+        glData.api = GLData.API.GL;
     }
-
+    
+    /**
+     * Returns the AWT component where it is drawn (canvas).
+     * @return Canvas
+     */
     @Override
-    public void setTitle(String title) {
+    public Canvas getCanvas() {
+        return canvas;
     }
 
+    /** (non-Javadoc) */
+    @Override
+    protected void showWindow() { }
+    /** (non-Javadoc) */
+    @Override 
+    protected void setWindowIcon(final AppSettings settings) { }
+    /** (non-Javadoc) */
+    @Override
+    public void setTitle(String title) { }
+
+    /**
+     * (non-Javadoc)
+     * @see com.jme3.system.lwjgl.LwjglWindow#getKeyInput() 
+     * @return returns a {@link com.jme3.input.awt.AwtKeyInput} object
+     */
     @Override
     public KeyInput getKeyInput() {
         if (keyInput == null) {
             keyInput = new AwtKeyInput();
             keyInput.setInputSource(canvas);
         }
-
         return keyInput;
     }
 
+    /**
+     * (non-Javadoc)
+     * @see com.jme3.system.lwjgl.LwjglWindow#getMouseInput() 
+     * @return returns a {@link com.jme3.input.awt.AwtMouseInput} object
+     */
     @Override
     public MouseInput getMouseInput() {
         if (mouseInput == null) {
             mouseInput = new AwtMouseInput();
             mouseInput.setInputSource(canvas);
         }
-
         return mouseInput;
     }
 
+    /**
+     * Check if the canvas is displayed, that is, if it has a parent that has set it up.
+     * <p>
+     * It is very important that this verification be done so that LWJGL3-AWT works correctly.
+     * 
+     * @return returns <code>true</code> if the canvas is ready to draw; otherwise 
+     * returns <code>false</code>
+     */
     public boolean checkVisibilityState() {
         if (!hasNativePeer.get()) {
             synchronized (lock) {
-                if (strategy != null) {
-                    strategy.dispose();
-                    strategy = null;
-                }
+                canvas.doDisposeCanvas();
             }
             return false;
         }
@@ -191,111 +578,179 @@ public class LwjglCanvas extends LwjglWindow implements JmeCanvasContext, Runnab
         return currentShowing;
     }
 
+    /**
+     * (non-Javadoc)
+     * @see com.jme3.system.lwjgl.LwjglWindow#destroyContext() 
+     */
     @Override
     protected void destroyContext() {
         synchronized (lock) {
-            destroyFrameBuffer();
-            img = null;
-            byteBuf = null;
-            intBuf = null;
+            canvas.destroy();
         }
 
+        // request the cleanup
+        signalTerminate.release();
         super.destroyContext();
     }
 
-    @Override
-    protected void createContext(AppSettings settings) {
-        super.createContext(settings);
-
-        if (renderer != null) {
-            createFrameBuffer(width, height);
-        }
-    }
-
+    /**
+     * (non-Javadoc)
+     * @see com.jme3.system.lwjgl.LwjglWindow#runLoop() 
+     */
     @Override
     protected void runLoop() {
         if (needResize.get()) {
             needResize.set(false);
-            listener.reshape(width, height);
-            createFrameBuffer(width, height);
+            settings.setResolution(framebufferWidth, framebufferHeight);
+            listener.reshape(framebufferWidth, framebufferHeight);
         }
-
+        
+        // check component status
         if (!checkVisibilityState()) {
             return;
         }
 
-        super.runLoop();
-
-        drawFrameInThread();
-    }
-
-    public void drawFrameInThread() {
-
-        // Convert screenshot
-        byteBuf.clear();
-        renderer.readFrameBuffer(fb, byteBuf);
-        Screenshots.convertScreenShot2(intBuf, img);
-
-        synchronized (lock) {
-            // All operations on strategy should be synchronized (?)
-            if (strategy == null) {
-                try {
-                    canvas.createBufferStrategy(1,
-                            new BufferCapabilities(
-                                    new ImageCapabilities(true),
-                                    new ImageCapabilities(true),
-                                    BufferCapabilities.FlipContents.UNDEFINED)
-                    );
-                } catch (AWTException ex) {
-                    logger.log(Level.SEVERE, "Failed to create buffer strategy!", ex);
-                }
-                strategy = canvas.getBufferStrategy();
+        //----------------------------------------------------------------------
+        //                          AWT - RENDERER
+        //----------------------------------------------------------------------
+        /*
+         * The same logic as AWTGLCanvas is used to draw on the awt drawing surface:
+         * 
+         * 1. Lock any thread to avoid any conflict.
+         * 2. Buffer swap (this is where the framebuffer is actually drawn): swapBuffers()
+         * 3. Unlock so that the AWT thread can work normally. IF NOT DONE, IT WILL 
+         *    BE WAITING AND BREAK ANY AWT/Swing APP.
+         */
+        canvas.beforeRender();
+        try {
+            super.runLoop();
+            if (allowSwapBuffers && autoFlush) {
+                canvas.swapBuffers();
             }
-
-            // Draw screenshot
-            do {
-                do {
-                    Graphics2D g2d = (Graphics2D) strategy.getDrawGraphics();
-                    if (g2d == null) {
-                        logger.log(Level.WARNING, "OGL: DrawGraphics was null.");
-                        return;
-                    }
-
-                    g2d.setRenderingHint(RenderingHints.KEY_RENDERING,
-                            RenderingHints.VALUE_RENDER_QUALITY);
-
-                    g2d.drawImage(img, transformOp, 0, 0);
-                    g2d.dispose();
-                    strategy.show();
-                } while (strategy.contentsRestored());
-            } while (strategy.contentsLost());
+        } finally {
+            canvas.afterRender();
+        }
+        
+        // Sync the display on some systems.
+        Toolkit.getDefaultToolkit().sync();
+        
+        //----------------------------------------------------------------------
+        /*
+         * Whether it is necessary to know the effective attributes to 
+         * initialize the LWJGL3-AWT context
+         */
+        //----------------------------------------------------------------------
+        if (showGLDataEffective.get()) {
+            showGLDataEffective.set(false);
+            System.out.println(MessageFormat.format("[ DEBUGGER ] :Effective data to initialize the LWJGL3-AWT context\n{0}", 
+                                                getPrintContextInitInfo(canvas.getGLDataEffective())));
         }
-    }
-
-    private void createFrameBuffer(int width, int height) {
-        byteBuf = BufferUtils.ensureLargeEnough(byteBuf, width * height * 4);
-        intBuf = byteBuf.asIntBuffer();
-
-        destroyFrameBuffer();
-
-        fb = new FrameBuffer(width, height, settings.getSamples());
-        fb.setDepthTarget(FrameBufferTarget.newTarget(Image.Format.Depth));
-        fb.addColorTarget(FrameBufferTarget.newTarget(Image.Format.RGB8));
-        fb.setSrgb(settings.isGammaCorrection());
-
-        renderer.setMainFrameBufferOverride(fb);
-
-        img = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);
 
-        AffineTransform tx = AffineTransform.getScaleInstance(1, -1);
-        tx.translate(0, -img.getHeight());
-        transformOp = new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
+        try {
+            if (signalTerminate.tryAcquire(10, TimeUnit.MILLISECONDS)) {
+                canvas.doDisposeCanvas();
+            }
+        } catch (InterruptedException ignored) { }
+    }
+    
+    /**
+     * (non-Javadoc)
+     * @see com.jme3.system.lwjgl.LwjglContext#printContextInitInfo() 
+     */
+    @Override
+    protected void printContextInitInfo() {
+        super.printContextInitInfo();
+        LOGGER.log(Level.INFO, "Initializing LWJGL3-AWT with jMonkeyEngine\n{0}", getPrintContextInitInfo(glData));
+    }
+    
+    /**
+     * Returns a string with the information obtained from <code>GLData</code>
+     * so that it can be displayed.
+     * 
+     * @param glData context information
+     * @return String
+     */
+    protected String getPrintContextInitInfo(GLData glData) {
+        StringBuilder sb = new StringBuilder();
+            sb.append(" *  Double Buffer: ").append(glData.doubleBuffer);
+            sb.append('\n')
+              .append(" *  Stereo: ").append(glData.stereo);
+            sb.append('\n')
+              .append(" *  Red Size: ").append(glData.redSize);
+            sb.append('\n')
+              .append(" *  Rreen Size: ").append(glData.greenSize);
+            sb.append('\n')
+              .append(" *  Blue Size: ").append(glData.blueSize);
+            sb.append('\n')
+              .append(" *  Alpha Size: ").append(glData.alphaSize);
+            sb.append('\n')
+              .append(" *  Depth Size: ").append(glData.depthSize);
+            sb.append('\n')
+              .append(" *  Stencil Size: ").append(glData.stencilSize);
+            sb.append('\n')
+              .append(" *  Accum Red Size: ").append(glData.accumRedSize);
+            sb.append('\n')
+              .append(" *  Accum Green Size: ").append(glData.accumGreenSize);
+            sb.append('\n')
+              .append(" *  Accum Blue Size: ").append(glData.accumBlueSize);
+            sb.append('\n')
+              .append(" *  Accum Alpha Size: ").append(glData.accumAlphaSize);
+            sb.append('\n')
+              .append(" *  Sample Buffers: ").append(glData.sampleBuffers);
+            sb.append('\n')
+              .append(" *  Share Context: ").append(glData.shareContext);
+            sb.append('\n')
+              .append(" *  Major Version: ").append(glData.majorVersion);
+            sb.append('\n')
+              .append(" *  Minor Version: ").append(glData.minorVersion);
+            sb.append('\n')
+              .append(" *  Forward Compatible: ").append(glData.forwardCompatible);
+            sb.append('\n')
+              .append(" *  Profile: ").append(glData.profile);
+            sb.append('\n')
+              .append(" *  API: ").append(glData.api);
+            sb.append('\n')
+              .append(" *  Debug: ").append(glData.debug);
+            sb.append('\n')
+              .append(" *  Swap Interval: ").append(glData.swapInterval);
+            sb.append('\n')
+              .append(" *  SRGB (Gamma Correction): ").append(glData.sRGB);
+            sb.append('\n')
+              .append(" *  Pixel Format Float: ").append(glData.pixelFormatFloat);
+            sb.append('\n')
+              .append(" *  Context Release Behavior: ").append(glData.contextReleaseBehavior);
+            sb.append('\n')
+              .append(" *  Color Samples NV: ").append(glData.colorSamplesNV);
+            sb.append('\n')
+              .append(" *  Swap Group NV: ").append(glData.swapGroupNV);
+            sb.append('\n')
+              .append(" *  Swap Barrier NV: ").append(glData.swapBarrierNV);
+            sb.append('\n')
+              .append(" *  Robustness: ").append(glData.robustness);
+            sb.append('\n')
+              .append(" *  Lose Context On Reset: ").append(glData.loseContextOnReset);
+            sb.append('\n')
+              .append(" *  Context Reset Isolation: ").append(glData.contextResetIsolation);
+        return String.valueOf(sb);
+    }
+    
+    /**
+     * (non-Javadoc)
+     * @see com.jme3.system.lwjgl.LwjglWindow#getFramebufferHeight() 
+     * @return int
+     */
+    @Override
+    public int getFramebufferHeight() {
+        return this.framebufferHeight;
     }
 
-    public void destroyFrameBuffer() {
-        if (fb != null) {
-            fb.dispose();
-            fb = null;
-        }
+    /**
+     * (non-Javadoc)
+     * @see com.jme3.system.lwjgl.LwjglWindow#getFramebufferWidth() 
+     * @return int
+     */
+    @Override
+    public int getFramebufferWidth() {
+        return this.framebufferWidth;
     }
 }

+ 14 - 10
jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java

@@ -46,6 +46,16 @@ import com.jme3.system.JmeSystem;
 import com.jme3.system.NanoTimer;
 import com.jme3.util.BufferUtils;
 import com.jme3.util.SafeArrayList;
+
+import org.lwjgl.Version;
+import org.lwjgl.glfw.GLFWErrorCallback;
+import org.lwjgl.glfw.GLFWFramebufferSizeCallback;
+import org.lwjgl.glfw.GLFWImage;
+import org.lwjgl.glfw.GLFWVidMode;
+import org.lwjgl.glfw.GLFWWindowFocusCallback;
+import org.lwjgl.glfw.GLFWWindowSizeCallback;
+import org.lwjgl.system.Platform;
+
 import java.awt.Graphics2D;
 import java.awt.image.BufferedImage;
 import java.nio.ByteBuffer;
@@ -55,17 +65,10 @@ import java.util.Map;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.logging.Level;
 import java.util.logging.Logger;
-import org.lwjgl.Version;
+
 import static org.lwjgl.glfw.GLFW.*;
-import org.lwjgl.glfw.GLFWErrorCallback;
-import org.lwjgl.glfw.GLFWFramebufferSizeCallback;
-import org.lwjgl.glfw.GLFWImage;
-import org.lwjgl.glfw.GLFWVidMode;
-import org.lwjgl.glfw.GLFWWindowFocusCallback;
-import org.lwjgl.glfw.GLFWWindowSizeCallback;
 import static org.lwjgl.opengl.GL11.GL_FALSE;
 import static org.lwjgl.system.MemoryUtil.NULL;
-import org.lwjgl.system.Platform;
 
 /**
  * A wrapper class over the GLFW framework in LWJGL 3.
@@ -631,9 +634,10 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable {
         // If the canvas is not active, there's no need to waste time
         // doing that.
         if (renderable.get()) {
-            // calls swap buffers, etc.
             try {
-                if (allowSwapBuffers && autoFlush) {
+                // If type is 'Canvas'; lwjgl-awt takes care of swap buffers.
+                if ((type != Type.Canvas) && allowSwapBuffers && autoFlush) {
+                    // calls swap buffers, etc.
                     glfwSwapBuffers(window);
                 }
             } catch (Throwable ex) {

+ 0 - 3
jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/Sync.java

@@ -31,7 +31,6 @@
  */
 package com.jme3.system.lwjgl;
 
-
 /**
 * A highly accurate sync method that continually adapts to the system 
 * it runs on to provide reliable results.
@@ -41,8 +40,6 @@ package com.jme3.system.lwjgl;
 */
 class Sync {
 
-
-
     /** number of nanoseconds in a second */
     private static final long NANOS_IN_SECOND = 1000L * 1000L * 1000L;
 

+ 66 - 0
jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/LwjglxDefaultGLPlatform.java

@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2009-2023 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.system.lwjglx;
+
+import org.lwjgl.system.Platform;
+import static org.lwjgl.system.Platform.*;
+
+/**
+ * Class <code>wjglxDefaultGLPlatform</code> used to create a drawing platform.
+ * @author wil
+ */
+public final class LwjglxDefaultGLPlatform {
+    
+    /**
+     * Returns a drawing platform based on the platform it is running on.
+     * @return LwjglxGLPlatform
+     * @throws UnsupportedOperationException throws exception if platform is not supported
+     */
+    public static LwjglxGLPlatform createLwjglxGLPlatform() throws UnsupportedOperationException {
+        switch (Platform.get()) {
+            case WINDOWS:
+                return new Win32GLPlatform();
+            //case FREEBSD:  -> In future versions of lwjgl3 (possibly)
+            case LINUX:
+                return new X11GLPlatform();
+            case MACOSX:
+                return new MacOSXGLPlatform();
+            default:
+                throw new UnsupportedOperationException("Platform " + Platform.get() + " not yet supported");
+        }
+    }
+    
+    /**
+     * private constructor.
+     */
+    private LwjglxDefaultGLPlatform() {}
+}

+ 48 - 0
jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/LwjglxGLPlatform.java

@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2009-2023 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.system.lwjglx;
+
+import org.lwjgl.opengl.awt.PlatformGLCanvas;
+
+/**
+ * Interface <code>LwjglxGLPlatform</code>; It is used to implement and manage
+ * the context of a specific platform.
+ * 
+ * @author wil
+ */
+public interface LwjglxGLPlatform extends PlatformGLCanvas {
+
+    /**
+     * Free the drawing surface.
+     */
+    public void destroy();    
+}

+ 56 - 0
jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/MacOSXGLPlatform.java

@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2009-2023 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.system.lwjglx;
+
+import org.lwjgl.opengl.awt.PlatformMacOSXGLCanvas;
+import static org.lwjgl.system.jawt.JAWTFunctions.*;
+
+/**
+ * <code>MacOSXGLPlatform</code> class that implements the {@link com.jme3.system.lwjglx.LwjglxGLPlatform} 
+ * interface for the MacOS platform.
+ * 
+ * @author wil
+ */
+final class MacOSXGLPlatform extends PlatformMacOSXGLCanvas implements LwjglxGLPlatform {
+
+    /**
+     * (non-Javadoc)
+     * @see com.jme3.system.lwjglx.LwjglxGLPlatform#destroy() 
+     */
+    @Override
+    public void destroy() {
+        if (ds != null) {
+            JAWT_FreeDrawingSurface(ds, awt.FreeDrawingSurface());
+            awt.free();
+        }
+    }
+ }

+ 56 - 0
jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/Win32GLPlatform.java

@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2009-2023 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.system.lwjglx;
+
+import static org.lwjgl.system.jawt.JAWTFunctions.*;
+import org.lwjgl.opengl.awt.PlatformWin32GLCanvas;
+
+/**
+ * <code>Win32GLPlatform</code> class that implements the {@link com.jme3.system.lwjglx.LwjglxGLPlatform} 
+ * interface for the Windows (Win32) platform.
+ * 
+ * @author wil
+ */
+final class Win32GLPlatform extends PlatformWin32GLCanvas implements LwjglxGLPlatform {
+
+    /**
+     * (non-Javadoc)
+     * @see com.jme3.system.lwjglx.LwjglxGLPlatform#destroy() 
+     */
+    @Override
+    public void destroy() {
+        if (ds != null) {
+            JAWT_FreeDrawingSurface(ds, awt.FreeDrawingSurface());
+            awt.free();
+        }
+    }
+}

+ 123 - 0
jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/X11GLPlatform.java

@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2009-2023 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.system.lwjglx;
+
+import org.lwjgl.opengl.awt.PlatformLinuxGLCanvas;
+import org.lwjgl.system.jawt.*;
+
+import static org.lwjgl.system.MemoryUtil.*;
+import static org.lwjgl.system.jawt.JAWTFunctions.*;
+
+/**
+ * Class <code>X11GLPlatform</code>; overrides the following methods: <code>swapBuffers()</code> 
+ * and <code>makeCurrent(long context)</code>. So that the canvas can be removed and 
+ * added back from its parent component.
+ * 
+ * <p>
+ * Works only for <b>Linux</b> based platforms
+ * 
+ * @author wil
+ */
+final class X11GLPlatform extends PlatformLinuxGLCanvas implements LwjglxGLPlatform {
+    
+    /**
+     * (non-Javadoc)
+     * @see org.lwjgl.opengl.awt.PlatformGLCanvas#swapBuffers() 
+     * @return boolean
+     */
+    @Override
+    public boolean swapBuffers() {
+         // Get the drawing surface info
+        JAWTDrawingSurfaceInfo dsi = JAWT_DrawingSurface_GetDrawingSurfaceInfo(ds, ds.GetDrawingSurfaceInfo());
+        if (dsi == null) {
+            throw new IllegalStateException("JAWT_DrawingSurface_GetDrawingSurfaceInfo() failed");
+        }
+        
+        try {
+            // Get the platform-specific drawing info
+            JAWTX11DrawingSurfaceInfo dsi_x11 = JAWTX11DrawingSurfaceInfo.create(dsi.platformInfo());
+
+            // Set new values
+            display  = dsi_x11.display();
+            drawable = dsi_x11.drawable();
+            
+            // Swap-Buffers            
+            return super.swapBuffers();
+        } finally {
+            JAWT_DrawingSurface_FreeDrawingSurfaceInfo(dsi, ds.FreeDrawingSurfaceInfo());
+        }
+    }
+
+    /**
+     * (non-Javadoc)
+     * @see org.lwjgl.opengl.awt.PlatformGLCanvas#makeCurrent(long) 
+     * 
+     * @param context long
+     * @return boolean
+     */
+    @Override
+    public boolean makeCurrent(long context) {
+        // Get the drawing surface info
+        JAWTDrawingSurfaceInfo dsi = JAWT_DrawingSurface_GetDrawingSurfaceInfo(ds, ds.GetDrawingSurfaceInfo());
+        if (dsi == null) {
+            throw new IllegalStateException("JAWT_DrawingSurface_GetDrawingSurfaceInfo() failed");
+        }
+
+        try {
+            // Get the platform-specific drawing info
+            JAWTX11DrawingSurfaceInfo dsi_x11 = JAWTX11DrawingSurfaceInfo.create(dsi.platformInfo());
+            
+            // Set new values
+            display  = dsi_x11.display();
+            drawable = dsi_x11.drawable();
+            
+            if (drawable == NULL) {
+                return false;
+            }
+            return super.makeCurrent(context);
+        } finally {
+            JAWT_DrawingSurface_FreeDrawingSurfaceInfo(dsi, ds.FreeDrawingSurfaceInfo());
+        }
+    }
+
+    /**
+     * (non-Javadoc)
+     * @see com.jme3.system.lwjglx.LwjglxGLPlatform#destroy() 
+     */
+    @Override
+    public void destroy() {
+        if (ds != null) {
+            JAWT_FreeDrawingSurface(ds, awt.FreeDrawingSurface());
+            awt.free();
+        }
+    }
+}

+ 3 - 2
jme3-lwjgl3/src/main/java/com/jme3/util/LWJGLBufferAllocator.java

@@ -8,6 +8,7 @@ import java.nio.*;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.locks.StampedLock;
+import java.util.logging.Level;
 import java.util.logging.Logger;
 
 /**
@@ -164,7 +165,7 @@ public class LWJGLBufferAllocator implements BufferAllocator {
         final long address = getAddress(buffer);
 
         if (address == -1) {
-            LOGGER.warning("Not found address of the " + buffer);
+            LOGGER.log(Level.WARNING, "Not found address of the {0}", buffer);
             return;
         }
 
@@ -172,7 +173,7 @@ public class LWJGLBufferAllocator implements BufferAllocator {
         final Deallocator deallocator = DEALLOCATORS.remove(address);
 
         if (deallocator == null) {
-            LOGGER.warning("Not found a deallocator for address " + address);
+            LOGGER.log(Level.WARNING, "Not found a deallocator for address {0}", address);
             return;
         }