Repaint issue with JUCE_COREGRAPHICS_DRAW_ASYNC=1

Hi,

I’m seeing a hard to reproduce repaint issue when I’m using the JUCE_COREGRAPHICS_DRAW_ASYNC=1 flag on a JUCE window with a native title bar (these two things seem to be required for the repaint bug to happen).

It happens more often when using 2 screens and multiple ‘Spaces’ , but I also get it (rarely) on my macbook with no external screen attached.

The bug happens mostly when switching between two spaces. Sometimes, the juce window appears black, or partially black: it is not repainted when being unhidden and all parts which are not actively repainted ( like with a repaint() call in a timer callback) just stay black. It happens with juce 6 and also juce 5.

Of course, I can disable JUCE_COREGRAPHICS_DRAW_ASYNC , but this flag makes such a difference in repaint speed that I would really want to keep it.

I wonder if I’m the only one here to notice that issue (my colleagues can also reproduce it with a toy juce application). The issue is very rare on my laptop, but some customers seem to trigger it quite often (many times per day).

1 Like

It happens to me occasionally but I’ve not had a chance to dig in to why.
Mousing over components seem to bring them back and I think minimising the window and restoring it can help (or maybe resizing it) so I’ve just been doing that.

Maybe a full repaint is needed when the window regains focus?

Yes the black area disappear as soon as your code calls repaint() for the component, however minimising/restoring or switching spaces does not trigger repaint, so black areas can stay for a very long time. Maybe calling a repaint() on the top level component every 10s will fix that, I’ll try it.

I have a customer reporting this as well when using virtual desktop.(spaces)

I’ve just encountered this as well in several of my plugins. It seems to happen when navigating away from the host (making another app “in focus”) and then going back again. It seems much more likely to happen when an external monitor is connected and the laptop switches to using the AMD GPU, though it’s happened on the integrated Intel GPU as well.

Turning off JUCE_COREGRAPHICS_DRAW_ASYNC seems to fix the issue, but the async drawing behavior offers such performance improvements that I am hesitant to remove it. Was anyone ever able to find a solution or sufficient workaround for this?

If it happens when switching away from the app, does forcing a full repaint of the window when you regain focus clear it?

Hm, I’m potentially missing something obvious here, but does a plugin have any reliable way of knowing when the host regains focus?

(Sorry, I’m realizing I should have clarified that I’m coming at this from the plugin side of it, not the host. The host focus is changing, but I don’t see a way to detect that as a plugin)

It does appear that focusOfChildComponentChanged() partially works, though the user needs to click inside the UI for it to be triggered, which is not ideal.

We poll Process::isForegroundProcess() to find out when the app regains focus but that’s in an app. Not sure if that works as well in a plugin, especially if its sandboxed.

Why shouldn’t it? on macOS at least, even when sandboxed. Your code runs in the sandboxed process under a NSWindow created for you by the process. when someone click on the UI, it gains focus for that foreground process (the sandbox).

Is there a way on macOS to attach NSView(s) from other processes?
(I wonder what Apple’s own AUSandbox in Big Sur / Apple Silicon does, but for us, regular people we need to listen for window events which is what BitWig does, REAPER does and I’m quite sure you also do a similar flow on Tracktion/Waveform).

I was thinking more if the host regains focus rather than your plugin window but yeah, you should be able to find that out when your actual window (and hence process) gains focus.

I’m not sure how BitWig/Reaper does it but we just show the window in the child process and manually feed keyboard presses etc. back to the host process via a pipe.

Thanks all, it does look like Process::isForegroundProcess() will track the host app’s focus, assuming the plugin is run in-process. If the plugin is run out-of-process, then it doesn’t follow the host app, but the plugin editor’s window (as expected).

Seems like the best workaround for a plugin is to force a top-level repaint when focus is regained, and otherwise repaint at a long-ish interval if in the background.

It would be nice to find the root cause of this (as it seems like it would affect all JUCE apps/plugins using this flag), but for now this workaround will suffice.

I’m finally able to reproduce the issue consistently (Catalina, 2016 macbook pro), even with the juce DemoRunner:

  • launch DemoRunner, select the GUI/AnimationDemo.h
  • minimize the DemoRunner
  • put the mac to sleep
  • wake up the mac, unminimize DemoRunner : some of the animated white balls are frozen on the gui, and they are erased when a new white ball moves over them.

Thanks for your patience with this issue, and also for providing detailed repro instructions.

I was able to reproduce the issue in the DemoRunner and have pushed a change which seems to resolve the problem on my machine. I was also able to repro the issue in a plugin, and can confirm that the patch fixes the problem for plugins too.

Please let me know if you run into any problems with this change.

4 Likes

Thanks for the fix! Did you by chance check if this fixes the issue in out-of-process plugins (e.g. in Bitwig/REAPER with the appropriate settings, or Logic on Big Sur)? We were seeing that repaints were not forced when the host regained focus and the plugin was out-of-process, because the plugin was not regaining focus until the user clicked on the plugin window or container.

I gave it a quick go in REAPER on Catalina with a plugin loaded out-of-process, and then followed the other instructions (minimise the plugin window, send the machine to sleep, wake it, unminimise the window). This seems to work as expected.

Got it, thanks! Following those instructions, I’d expect your fix to work.

When I was testing, I was seeing the bug in Bitwig on Catalina with the plugin loaded out-of-process, where I would make another app in focus, and then switch back to Bitwig, and the plugin would be blank. This seemed to happen because the plugin window itself wasn’t in focus, but rather the Bitwig window. So the plugin would only repaint when I actually clicked the plugin window and it regained focus. Admittedly, it took a lot of tries and usually several instances of the plugin open for this to happen, but the bug was there.

I haven’t yet had the opportunity to try your fix, just wanted to let you know the problem might still be lurking under certain circumstances where the plugin hasn’t regained focus.

Hi,

I am also experiencing an issue with this async draw flag activated (tested on mojave and bigsur) that appears to be the same announced in this post Incorrect image colour rendering in Pro Tools. All UI colours become slightly lighter with the flag activated. I was only able to reproduce it in ProTools Ultimate (2020.9.1 for instance) on several machines. I also noticed that the first paint, when the plugin is instantiated, is correctly coloured (no lighter colours) and only the following paintings introduce the colour problems.
I do not have any custom colour profiles for ProTools and this happens in both light and dark mode of PT.
Any clues on what this might be?

Are your Components setBufferedToImage() ? if so, perhaps the first paint has the correct colours because the actual primitives are drawn to screen, while subsequent paints are done from the buffered image and the “lighter colours” issue could be affecting only those?

I used to have knobs buffered but not anymore, as it was hurting performance. So, no. I don’t have any buffered components. Note this also happens with a simple loaded from memory Image. This is probably related to this issue - Incorrect image colour rendering in Pro Tools - #17 by railjonrogut - as it only happens in macos, and in my case with the draw async flag activated.