UI Frozen Until Mouse Moves

Hi,

A few of my users are seeing some odd behavior where the UI of my main Juce-based application stops updating until they move their mouse over the UI.

Are there any known issues between Juce 7.0.2 and 7.0.5 that might cause UI stopping from updating?

For some more details and observations see below…

My application (Massive Meter Bridge) is designed to be a monitoring application that the users don’t necessarily interact with but is put on a secondary monitor for the user to view.

This issue happens very infrequently (several times a day) and only happens in the latest version of my software. Of course, I can’t reproduce this issue.

The updated version where the bug occurs uses Juce 7.0.5 as compared to the previous version which uses Juce 7.0.2.

I’ve scrubbed through the changes in my code between the 2 releases. It is not obvious that anything changed that could cause this behavior. I’ve also scrubbed through the changes between Juce 7.0.2 and 7.0.5. There are various changes in juce_mac_NSViewComponentPeer, juce_OpenGLContext.cpp, and juce_Threads*. It’s not clear from looking at the code if these could cause the issue.

Thanks,
Bob

Does your app uses timer callbacks for repainting?

It would also be useful to know as many details as possible about any details that might be relevant to the drawing mechanism, e.g.

  • JUCE version (exact commit)
  • OS version
  • platform architecture
  • renderer (OpenGL, software renderer, core graphics, etc.)
  • preprocessor flags (JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS etc.)

Yes, repaints are triggered by a juce::Timer.

Reuk,

The Juce 7.0.5 commit is 69795dc8e
The version of the app that does not have the issue is based on Juce 7.0.2 with the commit hash of 965d0ca4b

macOS 12.6.2

Platform architecture is Intel.

For preprocessor flags…
USE_COREGRAPHICS_RENDERING == 1
JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS == 0

So this means metalRenderer == nullptr

I can only think of one way forward, which is to check if the timer is the cause, since it is not possible to display anything in the window, nor to debug manually because when entering a mouse (or keyboard?) event, the system recovers, it could be done by sending a periodic signal to the output window or to a text file, for example writing the current time every 30 seconds. If the time also stopped updating when the window freezes, the problem must be in the timer.

If you’re not using OpenGL, the main change between 7.0.2 and 7.0.5 that I think could be related is the addition of VBlank-synced rendering. Specifically, if the app failed to receive screen-update callbacks for the screen containing the main window, then it wouldn’t paint. I’m not sure why moving the mouse over the window would correct things, though…

I can’t see anything particularly suspicious in the code, but I do notice that we don’t do any error handling (beyond asserting) in the case that setting up the CVDisplayLink fails. Perhaps setting up new display links fails occasionally, although I don’t think this would be fixed by moving the mouse over the application window. Another thought is that we might just miss certain screen configuration messages. At the moment, we update the display links every time we get a NSApplicationDidChangeScreenParametersNotification notification. Perhaps there are other notifications that we should be observing, or perhaps the system sometimes delays this notification if the app is in the background.

If you’re considering adding logging to the application, I think it would be a good idea to add some in the refreshScreens() function in juce_PerScreenDisplayLinks_mac.h, to check that this is being called whenever the screens update. You could also log the results of CVDisplayLinkCreateWithCGDisplay, CVDisplayLinkSetOutputCallback, and CVDisplayLinkStart in that same file.

You could also see whether refreshing the display links a bit more aggressively helps. One idea would be to make refreshScreens() public in PerScreenDisplayLinks_mac.h, then to add the following changes in NSViewComponentPeer_mac.mm:

// In the constructor of NSViewComponentPeer, after the other scopedObservers.emplace_back() calls
scopedObservers.emplace_back (view, @selector (windowDidChangeScreen:), NSWindowDidChangeScreenNotification, window);

// In the constructor of JuceNSViewClass
addMethod (@selector (windowDidChangeScreen:), [] (id, SEL, NSNotification*)
{
    SharedResourcePointer<PerScreenDisplayLinks>()->refreshScreens();
});