JDK-8087565 : Scaling problem on OSX Retina
  • Type: Bug
  • Component: javafx
  • Sub-Component: graphics
  • Affected Version: 8u40,9
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • Submitted: 2015-03-25
  • Updated: 2020-01-31
  • Resolved: 2016-08-29
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 8 JDK 9
8u152Fixed 9Fixed
Related Reports
Duplicate :  
Relates :  
Relates :  
Description
Since 8u40 there is a problem with app scaling on Retina Displays, when stage.show() is done while the initialize-method of a Component. 

Steps to reproduce:
.) Connect external display to macbook
.) Set external display as mainpanel.
.) set "parentStage.setX(0);" in FxmlComponent.java
.) Startup RetinaTest.java
.) JavaFX app should be displayed on external display and everything should be fine
.) close JavaFX app
.) set "parentStage.setX(.....);" to a value that the app will be displayed on retina display
.) wrong scaling

Also tried it with 8u31 and 8u25 => everything is fine.
Anyways this stage.show() in initialize method was a ugly hack in our app, so we won't this anymore. 





***********************************
RetinaTest.java
***********************************

package sample;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class RetinaTest extends Application {
    public static void main(String[] args) {
        launch(args);
    }
    @Override
    public void start(Stage stage) throws Exception {
        FxmlComponent root = new FxmlComponent(stage);
        stage.setScene(new Scene(root));
    }
}



***********************************
FxmlComponent.java
***********************************

package sample;

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;

import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;

public class FxmlComponent extends AnchorPane implements Initializable {

    private Stage parentStage;

    @FXML
    private Label label;

    public FxmlComponent(Stage parentStage) {
        this.parentStage = parentStage;

        FXMLLoader fxmlLoader = new FXMLLoader();
        fxmlLoader.setLocation(getClass().getResource("FxmlComponent.fxml"));
        fxmlLoader.setRoot(this);
        fxmlLoader.setController(this);
        try {
            fxmlLoader.load();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @FXML
    private void handleButtonAction(ActionEvent event) {
        System.out.println("You clicked me!");
        label.setText("Hello World!");
    }

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        
        //set X to a value, that it will be moved to retina screen
        parentStage.setX(3000);
        parentStage.setY(100);

        parentStage.setWidth(150);
        parentStage.setHeight(70);
        parentStage.show();
    }
}



***********************************
FxmlComponent.fxml
***********************************

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<fx:root xmlns:fx="http://javafx.com/fxml"
         type="sample.FxmlComponent">
    <children>
        <Button id="button" layoutX="5" layoutY="5" text="Click Me!" onAction="#handleButtonAction" fx:id="button" />
        <Label id="label" layoutX="5" layoutY="35" minHeight="16" minWidth="70" prefHeight="16" prefWidth="70" fx:id="label" />
    </children>
</fx:root>
Comments
http://hg.openjdk.java.net/openjfx/9-dev/rt/rev/93f6d1fa087f
29-08-2016

Approved to backport to 8u-dev for 8u122
29-08-2016

Looks good. +1
29-08-2016

Here is the same webrev applied to the 9u-dev repo: http://cr.openjdk.java.net/~flar/JDK-8087565/webrev.9u.00/ Note that in both cases, the added test for self->view != nil is not necessary as the subsequent call seems to just be ignored when the target is null, but I thought it would be better to explicitly test the view there to indicate that this "null view" condition is a possibility and that it has effect on the way that information is propagated between the various objects in the system.
29-08-2016

Adding 8-bp label since we will almost certainly want an 8u backport.
27-08-2016

Here is a webrev that fixes the problem for the 8u-dev workspace. The same patch should apply to the 9-dev workspace, but I will need to put in some work to get my 9-dev build environment up to the latest changes. I wanted to get the webrev recorded for reference, though. http://cr.openjdk.java.net/~flar/JDK-8087565/webrev.8u.00/ The fix is to add an additional notification to the view of the scale when we set the view.
27-08-2016

This is a very similar problem to JDK-8145516, but in that case the location was set and the view was set before the window was set to visible. In this test case, the location is set and the window is made visible before a view is set - and the fix for 8145516 tries to fire off a notification of the correct scale, but there is no view to fire it to so the notification was being dropped on the floor. The fix is to add another notification in the setView method so that we notify the new view of the current scale in case it missed the other notifications.
27-08-2016

Additional information to reproduce the bug is available at https://bugs.openjdk.java.net/browse/JDK-8158193
31-05-2016

upgraded to P3 as regression labeled, if this is mistakenly labeled - then please remove the label and downgrade back
13-10-2015

Adding Chien to the watch list since he is most familiar with this method.
05-06-2015

It looks like GlassLayer3D:initWithSharedContext is called after everything has decided that "this window is on the second screen", but it doesn't know that and doesn't bother to figure it out - so it shrugs its shoulders and initializes everything based on being on the main screen. Can this method figure out - oh, wait, I'm actually on the second screen? Or should it be told that from whoever calls it?
04-06-2015

That's an explanation of what we are already doing, but the problem is that something is missing. Basically, the native code always sets up the GlassLayer initially asuming that it will be on the first screen, as in this comment in GlassLayer3D.m: // Initially the view is not in any window yet, so using the // screens[0]'s scale is a good starting point (this is most probably // the notebook's main LCD display which is HiDPI-capable). // Note that mainScreen is the screen with the current app bar focus // in Mavericks and later OS so it will likely not match the screen // we initially show windows on if an app is started from an external // monitor. [self notifyScaleFactorChanged:GetScreenScaleFactor([[NSScreen screens] objectAtIndex:0])]; The problem is, when the screen is eventually chosen based on its location, the Java structures are all updated with the appropriate scaling factors, but nobody tells this native code. It looks like MacWindow.setScreen() needs to do something so that the native code can tell the layer that it has a new scaling factor, but what? So, far, all it does is update its internal fields with new values and returns.
04-06-2015

Yes - see: GlassApplication.m -> GlassScreenDidChangeScreenParameters() -> GlassScreen.m GlassScreenDidChangeScreenParameters() calls the JNI method "notifySettingsChanged" also GlassApplication.m -> Java_com_sun_glass_ui_mac_MacApplication__1getScreens -> GlassScreen.m -> createJavaScreens also this comment in GlassWindow+Overrides.m // Screen.notifySettingsChanged() calls Window.setScreen(), and the latter // generates the Window.EventHandler.handleScreenChangedEvent notification. also in GlassWindow+Java.m: GlassWindow+Java.m: (*env)->CallVoidMethod(env, jWindow, jWindowNotifyMoveToAnotherScreen, createJavaScreen(env, newScreen)); Hope this helps.
04-06-2015

Morris? Is there a way to have the Mac code tell a Window/Scene that the Screen has changed when it does not come up on the default/main screen so that it can update its scaling properties?
04-06-2015

Adding Morris to the watch list. I note that this is a regression, but if it only happens when showing a Stage before the Scene is added, we might consider deferring it to 9. An easy "workaround" is to set the scene on a stage before showing the stage, which is a best practice anyway.
23-05-2015

It looks like the code that creates the window assumes it will go on the main screen, but when it gets located on the other screen there are no events fired off to tell it that it has a new screen (with a potentially different scale). Is there someone more familiar with the Mac platform events that could look into what event we may be missing?
19-05-2015

yes, you are right, its a graphics issue.
30-03-2015

Yes, this is almost certainly a graphics issue. I have a retina Mac and will run the test case.
26-03-2015

I doubt FXML has any relationship with the scaling issue, so I'm reassigning this to the graphics team to review (and also, I don't have a retina display to test with).
26-03-2015