Просмотр исходного кода

Separate concept of window size from default framebuffer size (#1750)

* Separate concept of window size from default framebuffer size that are not always interchangeable in modern platforms. Add methods to query both sizes from AppSettings

* Add rescaling support

* Use different workaround that doesn't meddle with the initialization flow, for wrong FB size bug on first frames.

* Ensure listener.rescale/resize is called only after the listener is initialized. Call rescale only when scale changes. Update scale and size when window size changes (if needed).

* Fix documentation. Make SystemListener.rescale a default method

* Fix typo and formatting
Riccardo Balbo 3 лет назад
Родитель
Сommit
2b8661185f

+ 5 - 0
jme3-android/src/main/java/com/jme3/app/AndroidHarness.java

@@ -494,6 +494,11 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt
         app.reshape(width, height);
     }
 
+    @Override
+    public void rescale(float x, float y) {
+        app.rescale(x, y);
+    }
+
     @Override
     public void update() {
         app.update();

+ 5 - 0
jme3-android/src/main/java/com/jme3/app/AndroidHarnessFragment.java

@@ -571,6 +571,11 @@ public class AndroidHarnessFragment extends Fragment implements
         app.reshape(width, height);
     }
 
+    @Override
+    public void rescale(float x, float y) {
+        app.rescale(x, y);
+    }
+
     @Override
     public void update() {
         app.update();

+ 11 - 0
jme3-android/src/main/java/com/jme3/app/jmeSurfaceView/JmeSurfaceView.java

@@ -395,6 +395,17 @@ public class JmeSurfaceView extends RelativeLayout implements SystemListener, Di
         jmeSurfaceViewLogger.log(Level.INFO, "Requested reshaping from the system listener");
     }
 
+
+    @Override
+    public void rescale(float x, float y) {
+        if (legacyApplication == null) {
+            return;
+        }
+        legacyApplication.rescale(x, y);
+        jmeSurfaceViewLogger.log(Level.INFO, "Requested rescaling from the system listener");
+    }
+
+
     @Override
     public void update() {
         /*Invoking can be delayed by delaying the draw of GlSurfaceView component on the screen*/

+ 8 - 0
jme3-core/src/main/java/com/jme3/app/LegacyApplication.java

@@ -578,6 +578,14 @@ public class LegacyApplication implements Application, SystemListener {
         }
     }
 
+
+    @Override
+    public void rescale(float x, float y){
+        if (renderManager != null) {
+            renderManager.notifyRescale(x, y);
+        }
+    }
+
     /**
      * Restarts the context, applying any changed settings.
      * <p>

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

@@ -62,6 +62,17 @@ public interface SceneProcessor {
      */
     public void reshape(ViewPort vp, int w, int h);
 
+    /**
+     * Called when the scale of the viewport has been changed.
+     *
+     * @param vp the affected ViewPort
+     * @param x the new horizontal scale 
+     * @param y the new vertical scale 
+     */
+    public default void rescale(ViewPort vp, float x, float y) {
+
+    }
+
     /**
      * @return True if initialize() has been called on this SceneProcessor,
      * false if otherwise.

+ 30 - 0
jme3-core/src/main/java/com/jme3/renderer/RenderManager.java

@@ -342,6 +342,17 @@ public class RenderManager {
         }
     }
 
+    private void notifyRescale(ViewPort vp, float x, float y) {
+        List<SceneProcessor> processors = vp.getProcessors();
+        for (SceneProcessor proc : processors) {
+            if (!proc.isInitialized()) {
+                proc.initialize(this, vp);
+            } else {
+                proc.rescale(vp, x, y);
+            }
+        }
+    }
+
     /**
      * Internal use only.
      * Updates the resolution of all on-screen cameras to match
@@ -374,6 +385,25 @@ public class RenderManager {
         }
     }
 
+    /**
+     * Internal use only.
+     * Updates the scale of all on-screen ViewPorts
+     *
+     * @param x the new horizontal scale
+     * @param y the new vertical scale
+     */
+    public void notifyRescale(float x, float y) {
+        for (ViewPort vp : preViewPorts) {
+            notifyRescale(vp, x, y);
+        }
+        for (ViewPort vp : viewPorts) {        
+            notifyRescale(vp, x, y);
+        }
+        for (ViewPort vp : postViewPorts) {
+            notifyRescale(vp, x, y);
+        }
+    }
+
     /**
      * Sets the material to use to render all future objects.
      * This overrides the material set on the geometry and renders

+ 40 - 5
jme3-core/src/main/java/com/jme3/system/AppSettings.java

@@ -269,6 +269,8 @@ public final class AppSettings extends HashMap<String, Object> {
         defaults.put("CenterWindow", true);
         defaults.put("Width", 640);
         defaults.put("Height", 480);
+        defaults.put("WindowWidth", Integer.MIN_VALUE);
+        defaults.put("WindowHeight", Integer.MIN_VALUE);
         defaults.put("BitsPerPixel", 24);
         defaults.put("Frequency", 60);
         defaults.put("DepthBits", 24);
@@ -735,7 +737,7 @@ public final class AppSettings extends HashMap<String, Object> {
     }
 
     /**
-     * @param value the width for the rendering display.
+     * @param value the width for the default framebuffer.
      * (Default: 640)
      */
     public void setWidth(int value) {
@@ -743,7 +745,7 @@ public final class AppSettings extends HashMap<String, Object> {
     }
 
     /**
-     * @param value the height for the rendering display.
+     * @param value the height for the default framebuffer.
      * (Default: 480)
      */
     public void setHeight(int value) {
@@ -751,7 +753,8 @@ public final class AppSettings extends HashMap<String, Object> {
     }
 
     /**
-     * Set the resolution for the rendering display
+     * Set the resolution for the default framebuffer
+     * Use {@link #setWindowSize(int, int)} instead, for HiDPI display support.
      * @param width The width
      * @param height The height
      * (Default: 640x480)
@@ -761,6 +764,16 @@ public final class AppSettings extends HashMap<String, Object> {
         setHeight(height);
     }
 
+    /**
+     * Set the size of the window
+     * 
+     * @param width The width in pixels (default = width of the default framebuffer)
+     * @param height The height in pixels (default = height of the default framebuffer)
+     */
+    public void setWindowSize(int width, int height) {
+        putInteger("WindowWidth", width);
+        putInteger("WindowHeight", height);
+    }
 
     /**
      * @param value the minimum width the settings window will allow for the rendering display.
@@ -991,7 +1004,7 @@ public final class AppSettings extends HashMap<String, Object> {
     /**
      * Get the width
      *
-     * @return the width of the rendering display (in pixels)
+     * @return the width of the default framebuffer (in pixels)
      * @see #setWidth(int)
      */
     public int getWidth() {
@@ -1001,13 +1014,35 @@ public final class AppSettings extends HashMap<String, Object> {
     /**
      * Get the height
      *
-     * @return the height of the rendering display (in pixels)
+     * @return the height of the default framebuffer (in pixels)
      * @see #setHeight(int)
      */
     public int getHeight() {
         return getInteger("Height");
     }
 
+    /**
+     * Get the width of the window
+     *
+     * @return the width of the window (in pixels)
+     * @see #setWindowWidth(int)
+     */
+    public int getWindowWidth() {
+        int w = getInteger("WindowWidth");
+        return w != Integer.MIN_VALUE ? w : getWidth();
+    }
+
+    /**
+     * Get the height of the window
+     *
+     * @return the height of the window (in pixels)
+     * @see #setWindowHeight(int)
+     */
+    public int getWindowHeight() {
+        int h = getInteger("WindowHeight");
+        return h != Integer.MIN_VALUE ? h : getHeight();
+    }
+
     /**
      * Get the width
      *

+ 10 - 0
jme3-core/src/main/java/com/jme3/system/SystemListener.java

@@ -51,6 +51,16 @@ public interface SystemListener {
      */
     public void reshape(int width, int height);
 
+    /**
+     * Called to notify the application that the scale has changed.
+     * @param x the new horizontal scale of the display 
+     * @param y the new vertical scale of the display
+     */
+    public default void rescale(float x, float y){
+
+    }
+
+
     /**
      * Callback to update the application state, and render the scene
      * to the back buffer.

+ 5 - 0
jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanelsContext.java

@@ -67,6 +67,11 @@ public class AwtPanelsContext implements JmeContext {
             throw new IllegalStateException();
         }
 
+        @Override
+        public void rescale(float x, float y) {
+            throw new IllegalStateException();
+        }
+
         @Override
         public void update() {
             updateInThread();

+ 43 - 20
jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java

@@ -151,7 +151,8 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable {
     // temp variables used for glfw calls
     private int width[] = new int[1];
     private int height[] = new int[1];
-    
+    private final Vector2f currentScale = new Vector2f(1, 1);
+
     public LwjglWindow(final JmeContext.Type type) {
 
         if (!SUPPORTED_TYPES.contains(type)) {
@@ -281,11 +282,16 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable {
 
         final GLFWVidMode videoMode = glfwGetVideoMode(glfwGetPrimaryMonitor());
 
-        if (settings.getWidth() <= 0 || settings.getHeight() <= 0) {
-            settings.setResolution(videoMode.width(), videoMode.height());
+        if (settings.getWindowWidth() <= 0 || settings.getWindowHeight() <= 0) {
+            settings.setWindowSize(videoMode.width(), videoMode.height());
+        } else {
+            settings.setWindowSize(settings.getWindowWidth(), settings.getWindowHeight());
         }
 
-        window = glfwCreateWindow(settings.getWidth(), settings.getHeight(), settings.getTitle(), monitor, NULL);
+        // Assume default framebuffer size == window size
+        settings.setResolution(settings.getWindowWidth(), settings.getWindowHeight());
+
+        window = glfwCreateWindow(settings.getWindowWidth(), settings.getWindowHeight(), settings.getTitle(), monitor, NULL);
 
         if (window == NULL) {
             throw new RuntimeException("Failed to create the GLFW window");
@@ -333,6 +339,10 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable {
         setWindowIcon(settings);
         showWindow();
 
+        // HACK: the framebuffer seems to be initialized with the wrong size
+        // on some HiDPI platforms until glfwPollEvents is called 2 or 3 times
+        for (int i = 0; i < 4; i++) glfwPollEvents();
+        
         // Windows resize callback
         glfwSetWindowSizeCallback(window, windowSizeCallback = new GLFWWindowSizeCallback() {
 
@@ -347,6 +357,7 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable {
                 for (WindowSizeListener listener : windowSizeListeners.getArray()) {
                     listener.onWindowSizeChanged(width, height);
                 }
+                updateDefaultFramebufferSize(true);
             }
         });
 
@@ -355,8 +366,7 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable {
 
             @Override
             public void invoke(final long window, final int width, final int height) {
-                // https://www.glfw.org/docs/latest/window_guide.html#window_fbsize
-                listener.reshape(width, height);
+                updateDefaultFramebufferSize(true);
             }
         });
 
@@ -367,7 +377,31 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable {
             initOpenCL(window);
         }
 
-        framesAfterContextStarted = 0;
+
+        updateDefaultFramebufferSize(false);
+
+    }
+
+    
+    private void updateDefaultFramebufferSize(boolean updateListener) {
+        // If default framebuffer size is different than window size (e.g. HiDPI)
+        int[] width = new int[1];
+        int[] height = new int[1];
+        glfwGetFramebufferSize(window, width, height);
+
+        Vector2f scale = getWindowContentScale(null);
+
+        if (updateListener) {
+            if (settings.getWidth() != width[0] || settings.getHeight() != height[0]) {
+                listener.reshape(width[0], height[0]);
+            }
+            if(!scale.equals(currentScale)){
+                listener.rescale(scale.x, scale.y);
+                currentScale.set(scale);
+            }
+
+        }       
+        settings.setResolution(width[0], height[0]);
     }
 
     private void onWindowSizeChanged(final int width, final int height) {
@@ -568,10 +602,11 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable {
         }
 
         listener.initialize();
+        updateDefaultFramebufferSize(true);
+
         return true;
     }
 
-    private int framesAfterContextStarted = 0;
 
     /**
      * execute one iteration of the render loop in the OpenGL thread
@@ -586,18 +621,6 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable {
             throw new IllegalStateException();
         }
 
-        // Update the frame buffer size from 2nd frame since the initial value
-        // of frame buffer size from glfw maybe incorrect when HiDPI display is in use
-        if (framesAfterContextStarted < 2) {
-            framesAfterContextStarted++;
-            if (framesAfterContextStarted == 2) {
-                glfwGetFramebufferSize(window, width, height);
-
-                if (settings.getWidth() != width[0] || settings.getHeight() != height[0]) {
-                    listener.reshape(width[0], height[0]);
-                }
-            }
-        }
 
         listener.update();