Skip to content

Conversation

@hjohn
Copy link
Collaborator

@hjohn hjohn commented Mar 13, 2025

Adds code to trigger a scene update when a Window is restored

This seems to solve https://bugs.openjdk.org/browse/JDK-8351867 and https://bugs.openjdk.org/browse/JDK-8146479


Progress

  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue
  • Change must be properly reviewed (2 reviews required, with at least 1 Reviewer, 1 Author)

Issues

  • JDK-8351867: No UI changes while iconified (Bug - P3)
  • JDK-8146479: Scene is black after stage is restored (content changed while minimized) (Bug - P4)

Reviewers

Contributors

  • Martin Fox <mfox@openjdk.org>

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jfx.git pull/1733/head:pull/1733
$ git checkout pull/1733

Update a local copy of the PR:
$ git checkout pull/1733
$ git pull https://git.openjdk.org/jfx.git pull/1733/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 1733

View PR using the GUI difftool:
$ git pr show -t 1733

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jfx/pull/1733.diff

Using Webrev

Link to Webrev Comment

@bridgekeeper
Copy link

bridgekeeper bot commented Mar 13, 2025

👋 Welcome back jhendrikx! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk
Copy link

openjdk bot commented Mar 13, 2025

@hjohn This change now passes all automated pre-integration checks.

ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details.

After integration, the commit message for the final commit will be:

8351867: No UI changes while iconified
8146479: Scene is black after stage is restored (content changed while minimized)

Co-authored-by: Martin Fox <mfox@openjdk.org>
Reviewed-by: lkostyra, angorya

You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed.

At the time when this comment was updated there had been 7 new commits pushed to the master branch:

As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details.

➡️ To integrate this PR with the above commit message to the master branch, type /integrate in a new comment.

@openjdk openjdk bot changed the title JDK-8351867 No UI changes while iconified 8351867: No UI changes while iconified Mar 13, 2025
@openjdk openjdk bot added the rfr Ready for review label Mar 13, 2025
@mlbridge
Copy link

mlbridge bot commented Mar 13, 2025

Webrevs

@beldenfox
Copy link
Contributor

When the window is restored glass calls notifySize with RESTORED and then notifyRepaint with valid dimensions. Why is the repaint being dropped?

@hjohn
Copy link
Collaborator Author

hjohn commented Mar 13, 2025

When the window is restored glass calls notifySize with RESTORED and then notifyRepaint with valid dimensions. Why is the repaint being dropped?

Can you give me a bit more detail what you mean here? I suspect it might be related to the size not actually changing, and so a size update with the same size does nothing?

@beldenfox
Copy link
Contributor

Can you give me a bit more detail what you mean here? I suspect it might be related to the size not actually changing, and so a size update with the same size does nothing?

Sorry, I'm asking a general question about how JavaFX deals with repaint events issued by the OS. I've never understood how JavaFX's painting model interfaces with OS level calls that expect drawing to occur on demand and synchronously. But that conversation should probably be moved to another forum.

@kevinrushforth
Copy link
Member

My thought on this bug was to fix it in the scenegraph, rather than forcing a complete repaint in the toolkit. The scenegraph tracks the state of what is dirty and would only need to repaint if something has changed (and probably only those parts that have changed).

@arapte Can you take a look at this and see what you think? I am not able to look at it for at least a week.

@kevinrushforth
Copy link
Member

/reviewers 2

@openjdk
Copy link

openjdk bot commented Mar 13, 2025

@kevinrushforth
The total number of required reviews for this PR (including the jcheck configuration and the last /reviewers command) is now set to 2 (with at least 1 Reviewer, 1 Author).

@hjohn
Copy link
Collaborator Author

hjohn commented Mar 13, 2025

My thought on this bug was to fix it in the scenegraph, rather than forcing a complete repaint in the toolkit. The scenegraph tracks the state of what is dirty and would only need to repaint if something has changed (and probably only those parts that have changed).

@arapte Can you take a look at this and see what you think? I am not able to look at it for at least a week.

Yeah, I'm sort of mimicking what happens when a RESCALE or MOVE occurs, so it is similar to what some of the other handlers do (MOVE does updateSceneState, and RESCALE does that and entireSceneNeedsRepaint). I'm not that well versed in the differences between these two, but it seemed not a bad place to fix this as restores happen very rarely.

@andy-goryachev-oracle
Copy link
Contributor

andy-goryachev-oracle commented Mar 13, 2025

@hjohn this did not fix the issue for me on macOS 15.3.1 M1
(the reproducer still shows "Initial State" label)

@kevinrushforth
Copy link
Member

Yeah, your solution might be fine. I just wanted to take a look at an alternative (or rather ask Ambarish to).

@hjohn
Copy link
Collaborator Author

hjohn commented Mar 13, 2025

@hjohn this did not fix the issue for me on macOS 15.3.1 M1 (the reproducer still shows "Initial State" label)

@andy-goryachev-oracle Hm, I can't test it on Mac, but this solution works on Windows. I just tested again with your reproducer, and it fails without this change and works with it. Maybe someone with a Mac can take a closer look?

For mac, perhaps also do entireSceneNeedsRepaint and see if that fixes it?

@beldenfox
Copy link
Contributor

I just verified that this PR does not fix the original issue on the Mac.

Note: I could not reproduce the bug on my Mac 15.3.2 M2 Max using the latest master. Then I went to System Settings > Desktop & Dock and turned on "Minimize windows into application icon". Then the problem reproduced. But when I toggled that switch back off and the bug still reproduced.

This may be irrelevant but I did notice a difference between Mac and Windows. After glass sends notifySize RESTORE it sends notifyRepaint on Windows but not on Mac. In fact I don't think the Mac ever sends notifyRepaint.

@beldenfox
Copy link
Contributor

Adding entireSceneNeedsRepaint fixes this on the Mac.

@arapte
Copy link
Member

arapte commented Mar 18, 2025

This does fix the issue on windows.
The issue on mac seems intermittent even without this change, and stays intermittent with this change.

The change seems to achieve the intended but the trigger check may not be the best way. updateSceneState is used when there are any changes with scene properties.
The redraw should be triggered by checking the change in scene graph. This change just triggers irrespective of that.

@beldenfox
Copy link
Contributor

At least on the Mac there seems to be a timing issue. If the MIMIMIZE notification is sent before a scheduled draw occurs the drawing code will clear the scene dirty bit but then skip the actual drawing since the window is minimized. But I've also seen cases where drawing completes before MINIMIZE is sent.

When the window is restored notifyRepaint will be called on Windows and Linux which looks like it will call entireSceneNeedsRepaint to set the scene dirty bit (based on my reading of the code). Windows calls notifyRepaint whenever it gets a WM_PAINT event and I've verified that that happens after a window is restored. Looking at the sources it seems that Linux calls notifyRepaint only when a window is restored (!)

Mac never calls notifyRepaint. There's code to call it but given where it's located I don't think it will ever be executed.

It would be nice to get some clarification on when notifyRepaint should be called and get the platforms to behave more consistently.

@hjohn
Copy link
Collaborator Author

hjohn commented Mar 18, 2025

This does fix the issue on windows. The issue on mac seems intermittent even without this change, and stays intermittent with this change.

The change seems to achieve the intended but the trigger check may not be the best way. updateSceneState is used when there are any changes with scene properties. The redraw should be triggered by checking the change in scene graph. This change just triggers irrespective of that.

I think the bug can also appear without the scene graph having changed, when you iconify immediately programmatically (ie. before showing the window). The change detection would need to make sure it also detects the case where a scene was not drawn yet at all:

public class App extends Application {

    public static void main(String[] args) {
        Application.launch(args);
    }

    @Override
    public void start(Stage stage) {
        Scene scene = new Scene(new Label("This should be modified when the signal is received"), 500, 500);

        stage.setScene(scene);

        stage.setIconified(true);
        stage.show();
    }
}

@kevinrushforth
Copy link
Member

Reviewers: @arapte @lukostyra

@hjohn
Copy link
Collaborator Author

hjohn commented Apr 4, 2025

This doesn't look like an easy fix; although it fixes the issue for me on Windows, I don't have access to a Mac to fix the problem on that platform as well. Perhaps someone else wants to take a look at this issue.

@beldenfox
Copy link
Contributor

First an errata: I wrote earlier that notifyRepaint is only called on Linux when restoring a window. I was wrong, it's also called for EXPOSE events.

Anyway, I can reproduce this bug on all three platforms.

JavaFX won't paint to a minimized window. This suggests to me that when we transition out of the minimized state someone needs to call entireSceneNeedsRepaint. The glass code on Windows and Linux both issue notifyRepaint which eventually calls entireSceneNeedsRepaint. The Mac code never issues notifyRepaint and entireSceneNeedsRepaint isn't called.

I can tweak the Mac code to call notifyRepaint to match Windows and Linux but presumably that won't fix the bug. It looks like some other bit of state gets out of whack due to the way the system refuses to paint while minimized. I have no idea whether updateSceneState is the appropriate call to get the system back into sync or not.

I have verified that on all platforms updateSceneState is not being called when the window is restored. I'll also note that with this test case there's no focus ring or flashing caret that needs to be drawn which seems to make a difference.

Reproducing this on macOS 15 is complicated by some weirdness going on under the hood. The address of the main screen changes unexpectedly. This causes glass to send notifyMoveToAnotherScreen which resets a whole lot of state. This can happen when I first move a window or when I iconify and de-iconify it. Depending on the "Minimize windows to application icon" setting this can happen every time I de-iconify or only the first time. I have no idea why the system keeps generating new NSScreen objects on an ongoing basis.

@beldenfox
Copy link
Contributor

I think this PR is the correct fix on Windows and Linux. The scene state tracks the minimized state of the window. When the window is restored someone needs to tell the scene state to go update the isWindowMinimized flag.

The Mac will require another tweak. Personally I would just modify glass to send notifyRepaint in this specific instance. I suppose that should be treated as a separate bug. Unfortunately it's going to be a bear to test given the strange things happening under the hood with NSScreen (at least on my system).

@andy-goryachev-oracle
Copy link
Contributor

andy-goryachev-oracle commented Apr 4, 2025

I can help with the testing: I have 15.3.2 with one or two external monitors.
What might help is to enumerate scenarios we want to be tested.

Do you want to address the macOS side in a separate PR?

@beldenfox
Copy link
Contributor

I can help with the testing: I have 15.3.2 with one or two external monitors. What might help is to enumerate scenarios we want to be tested.

Thanks for the offer. I'm also running 15.3.2. I suspect I'll have to try test on an older version of the OS to avoid all those unexpected screen change notifications.

Do you want to address the macOS side in a separate PR?

Maybe? It's clear that someone needs to call entireSceneNeedsRepaint to trigger the redraw. Currently there are many places in the core code that do this (for example, when the window's size changes). So maybe this is just another instance where the core should handle it. In that case it could be folded into this PR.

The alternative is to update the Mac glass code to more closely match what Windows and Linux are doing. In that case it should be a separate PR. But it's beginning to look like notifyPaint is sort of vestigial. Outside of this bug the Mac is just fine not calling it because the core handles everything internally.

From my newbie perspective it looks like this is just another area where the core code should be calling entireSceneNeedsRepaint. But this is not an area I'm familiar with.

@tsayao
Copy link
Collaborator

tsayao commented Apr 6, 2025

I spotted this while working on Linux glass. The test case I was doing was "showing" an iconified fullscreened stage. Linux glass has a repaint call, but it seems wrong.

@tsayao
Copy link
Collaborator

tsayao commented Apr 6, 2025

Hmm, it also relates to something else I was intrigued by:

What happens if the Stage is both Maximized and Iconified? If RESTORED assumes that the window is neither Maximized nor Iconified, then when a Maximized and Iconified window is de-iconified (presented), it should not be considered RESTORED — and in this case, the bug still persists.

From the Stage docs:

In case that more Stage modes are set simultaneously their order of importance is iconified, fullScreen, maximized (from strongest to weakest).

Update: Just a note about another possibly related bug involving the RESTORED logic not matching the documentation.

@tsayao
Copy link
Collaborator

tsayao commented Apr 10, 2025

About the comment on the change:

After some experiments, I’d say the size can change because you can call setFullScreen(true) or setMaximized(true) and then deiconify. While this doesn’t directly set the width and height of the Scene, the actual scene size does change.

Anyways, this is just a comment for a case to consider, but the change does make the scene repaint in this case (on Linux, removing the call of repaint on glass native code and some additional changes I'm working on).

@openjdk
Copy link

openjdk bot commented Jun 18, 2025

@hjohn beldenfox was not found in the census.

Syntax: /contributor (add|remove) [@user | openjdk-user | Full Name <email@address>]. For example:

  • /contributor add @openjdk-bot
  • /contributor add duke
  • /contributor add J. Duke <duke@openjdk.org>

User names can only be used for users in the census associated with this repository. For other contributors you need to supply the full name and email address.

@hjohn
Copy link
Collaborator Author

hjohn commented Jun 18, 2025

/contributor add @beldenfox

@openjdk
Copy link

openjdk bot commented Jun 18, 2025

@hjohn
Contributor Martin Fox <mfox@openjdk.org> successfully added.

@lukostyra
Copy link
Contributor

I have noticed the tests for maximized stages fail on my system:

DrawAfterDeiconifyTest > maximizedStageRedrawsAfterDeiconify(StageStyle) > [1] DECORATED FAILED
    org.opentest4j.AssertionFailedError: expected:rgba(255,105,180,255) but was:rgba(0,255,0,255)
        at app//org.junit.jupiter.api.AssertionUtils.fail(AssertionUtils.java:38)
        at app//org.junit.jupiter.api.Assertions.fail(Assertions.java:138)
        at app//test.robot.testharness.VisualTestBase.assertColorEquals(VisualTestBase.java:168)
        at app//test.robot.javafx.stage.DrawAfterDeiconifyTest.lambda$redrawsAfterDeiconify$6(DrawAfterDeiconifyTest.java:110)

DrawAfterDeiconifyTest > maximizedStageRedrawsAfterDeiconify(StageStyle) > [2] UNDECORATED FAILED
    org.opentest4j.AssertionFailedError: expected:rgba(255,105,180,255) but was:rgba(0,255,0,255)
        at app//org.junit.jupiter.api.AssertionUtils.fail(AssertionUtils.java:38)
        at app//org.junit.jupiter.api.Assertions.fail(Assertions.java:138)
        at app//test.robot.testharness.VisualTestBase.assertColorEquals(VisualTestBase.java:168)
        at app//test.robot.javafx.stage.DrawAfterDeiconifyTest.lambda$redrawsAfterDeiconify$6(DrawAfterDeiconifyTest.java:110)

DrawAfterDeiconifyTest > maximizedStageRedrawsAfterDeiconify(StageStyle) > [3] TRANSPARENT FAILED
    org.opentest4j.AssertionFailedError: expected:rgba(255,105,180,255) but was:rgba(0,255,0,255)
        at app//org.junit.jupiter.api.AssertionUtils.fail(AssertionUtils.java:38)
        at app//org.junit.jupiter.api.Assertions.fail(Assertions.java:138)
        at app//test.robot.testharness.VisualTestBase.assertColorEquals(VisualTestBase.java:168)
        at app//test.robot.javafx.stage.DrawAfterDeiconifyTest.lambda$redrawsAfterDeiconify$6(DrawAfterDeiconifyTest.java:110)

This is on Windows 11, the Stage in fact does not change its background. I'm not sure what the problem could be, or if it's fixable.

@beldenfox
Copy link
Contributor

I have noticed the tests for maximized stages fail on my system:

That's expected. This PR fixes the case where the window goes from iconified to restored. When looking at the code I realized the same fix needs to be applied when the window goes from iconified to maximized so I added that to the test.

@hjohn
Copy link
Collaborator Author

hjohn commented Jun 18, 2025

I have noticed the tests for maximized stages fail on my system:

That's expected. This PR fixes the case where the window goes from iconified to restored. When looking at the code I realized the same fix needs to be applied when the window goes from iconified to maximized so I added that to the test.

Do I need to add additional code for that case? The fix is now in WindowEvent.RESTORE, which I think should be catching both cases, or does de-iconifying to a maximized stage somehow not send a restore event? The terms are somewhat confusing; if RESTORE indicates go to a non-maximized/non-minimized/non-iconified "normal" state, then I suppose I need to add more code in MAXIMIZE as well...

Edit: I see that iconified state is explicitly set to false in the MAXIMIZE branch, so it looks like we'd need to do the same there. I'll do some tests.

@beldenfox
Copy link
Contributor

Do I need to add additional code for that case? The fix is now in WindowEvent.RESTORE, which I think should be catching both cases, or does de-iconifying to a maximized stage somehow not send a restore event?

At least on Windows you can go straight from an iconified state to a maximized state and the only event you'll see is WindowEvent.MAXIMIZE. That should be true on the Mac as well but there are bugs in the way glass handles maximized windows so I'm not entirely sure what the event sequence is.

Copy link
Contributor

@lukostyra lukostyra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, tests on Windows pass as they used to.

@andy-goryachev-oracle
Copy link
Contributor

@hjohn you might want to add JDK-8146479 to the PR via / issue add command.

@andy-goryachev-oracle
Copy link
Contributor

Looks good on macOS 15.5 M1.
Could we test this tested on linux please? I'll add one reviewer temporarily for now just for that.

/reviewers 3

}
return null;
});
forceRepaint();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(github diff is confusing, this code is for WindowEvent.RESCALE case)

I suspect this might also fix an issue that I encountered in one of my applications which uses Canvas-based text editor. After the macOS is woken up after a sleep with an external monitor attached, the text on retina looked blurred. I could not get a reproducer in time, but I think it was because while I put it to sleep, the scene got re-rendered on the external monitor going from scale 2 to scale 1, but when woken up, the scene was correctly re-rendered for retina but the canvas was not resized, requiring it to be scaled.

I'll try to test this scenario, but it may take a few days.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, no hurry to integrate this

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually, the reason I bumped the reviewer count is to let you add the issue and make sure we get the testing results for linux (which we got) and our CI run (which we did).

I may not be able to test my failing scenario before you integrate anyway. We are good to go.

@openjdk openjdk bot added the ready Ready to be integrated label Jun 27, 2025
@openjdk
Copy link

openjdk bot commented Jun 27, 2025

@andy-goryachev-oracle
The total number of required reviews for this PR (including the jcheck configuration and the last /reviewers command) is now set to 3 (with at least 1 Reviewer, 2 Authors).

@openjdk openjdk bot removed the ready Ready to be integrated label Jun 27, 2025
@andy-goryachev-oracle
Copy link
Contributor

andy-goryachev-oracle commented Jun 27, 2025

I am going to launch a headful CI run, will report in about an hour.

edit: @lukostyra already started one, thanks!

edit2: headful CI run passes (with one unrelated failure with QPathTest on mac-13-aarch64 that we occasionally see).

@beldenfox
Copy link
Contributor

Tested on Ubuntu 22.04. Test failed in master branch and succeeded with this PR.

@hjohn
Copy link
Collaborator Author

hjohn commented Jun 27, 2025

/issue add JDK-8146479

@openjdk
Copy link

openjdk bot commented Jun 27, 2025

@hjohn
Adding additional issue to issue list: 8146479: Scene is black after stage is restored (content changed while minimized).

@andy-goryachev-oracle
Copy link
Contributor

/reviewers 2

@openjdk
Copy link

openjdk bot commented Jun 27, 2025

@andy-goryachev-oracle
The total number of required reviews for this PR (including the jcheck configuration and the last /reviewers command) is now set to 2 (with at least 1 Reviewer, 1 Author).

@openjdk openjdk bot added the ready Ready to be integrated label Jun 27, 2025
@hjohn
Copy link
Collaborator Author

hjohn commented Jun 30, 2025

/integrate

@openjdk
Copy link

openjdk bot commented Jun 30, 2025

Going to push as commit 0270847.
Since your change was applied there have been 7 commits pushed to the master branch:

Your commit was automatically rebased without conflicts.

@openjdk openjdk bot added the integrated Pull request has been integrated label Jun 30, 2025
@openjdk openjdk bot closed this Jun 30, 2025
@openjdk openjdk bot removed ready Ready to be integrated rfr Ready for review labels Jun 30, 2025
@openjdk
Copy link

openjdk bot commented Jun 30, 2025

@hjohn Pushed as commit 0270847.

💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored.

@andy-goryachev-oracle
Copy link
Contributor

thank you @hjohn for fixing this bug!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

integrated Pull request has been integrated

Development

Successfully merging this pull request may close these issues.

7 participants