Apparent serious JUCE painting issue in Big Sur

We’ve discovered a major performance issue with painting, using JUCE apps running in Big Sur - but apparently not in Catalina or other previous versions. Moving any slider or other control, or just mousing over a control, causes the entire application window to invalidate and repaint, causing every control to be re-rendered.

I’ve created a very simple JUCE project to illustrate this:

Compile this application against JUCE 6.0.5 and run on the Big Sur operating system. If you run the application from the command line, you’ll see the output debug statements. The project contains two sliders, called “thing1” and “thing2” that are spaced about 900 pixels apart. Move any of the sliders on Big Sur (or just mouse over a slider), and you’ll see that both of them are redrawing. Your output will look like this:

paint slider: thing1
paint slider: thing2
paint slider: thing1
paint slider: thing2
paint slider: thing1
paint slider: thing2
paint slider: thing1
paint slider: thing2

Run the same executable on a Catalina computer and your output should look like this:

paint slider: thing1
paint slider: thing1
paint slider: thing1
paint slider: thing2
paint slider: thing1
paint slider: thing2
paint slider: thing2
paint slider: thing2

We’ve verified this on two different Macs running Big Sur. In addition, we compiled the same code against JUCE 5.4.7. The resulting executable works properly on Catalina but fails in the same way on Big Sur, so this is not a new issue, but is some sort of Big Sur incompatibility with JUCE’s painting.

You’ll noticed that even on Catalina, when clicking on the left or right slider, the other slider will redraw once, which seems inefficient, but perhaps there’s a reason for that. That’s why the Catalina output shows “paint slider: thing2” followed by “paint slider: thing1” in the middle. But this single repainting of both controls is a minor issue compared to both controls painting constantly.

We’re working on a product with a lot of sliders and knobs, and the GUI performance on Big Sur is very slow even with no audio processing occuring, and CPU jumps up to 70-100% just moving any of the sliders, which is how we initially discovered this issue. I hope it’s solvable!

Thanks,
Dan

3 Likes

Not sure if this is relevant to the JUCE developers, but perhaps this might offer a clue? https://developer.apple.com/forums/thread/663256

“The word from Apple is that the new behavior described in my original report should be considered the correct behavior, and that -getRectsBeingDrawn:count: can no longer be relied upon.”

Dan

I am seeing this issue, as well. Wow. This is huge. Are the JUCE devs seeing this???

It’s been going for a while. I think this is on Apple’s side, as their docs still recommend using getRectsBeingDrawn.

2 Likes

Here’s something speculative to try. In JuceNSViewClass add this in the constructor

addMethod (@selector (viewWillDraw), viewWillDraw, "v@:");

with this implementation

static void viewWillDraw (id self, SEL)
{
    CALayer* layer = ((NSView*) self).layer;
    layer.contentsFormat = kCAContentsFormatRGBA8Uint;

    sendSuperclassMessage<void> (self, @selector (viewWillDraw));
}

This is completely untested so might not work at all, and if it does work it might hurt performance on anything other than Big Sur.

The JUCE team is taking some time off until the end of the year but I’ve just spotted this workaround (it was posted on the Apple Developer forums a few weeks ago) and thought it interesting enough to share immediately.

6 Likes

Yes me and several other people have noticed the same regression since 10.14.
It’s a real shame Apple can break / not test such a fundamental part of the OS and not even acknowledge the problem, let alone fix it.

VSTGUI used a manual rectangle collection strategy in this commit as a workaround, but they reverted it the next commit. Perhaps you could ask on the VSTGUI forum why this wasn’t kept.

3 Likes

I’m happy to report that the viewWillDraw() workaround does fix the problem with our test application, and does not appear to have any negative performance or drawing issues in previous macOS builds. That’s the good news.

The bad news is that even with this fix, if there are multiple invalidations, such as a flashing light in a corner, there still appears to be way too much redrawing, with large rectangles being invalidated causing multiple controls to repaint without need.

The upshot is that with the viewWillDraw() workaround, the JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS #define set, and with setBufferedToImage(true) set on all the controls, rendering is now faster in our application. Rendering on Windows remains very fast without any customizations. On a 4K monitor, though, all the extra painting on the macOS platform is still slowing things down.

This thing is one of the main reasons I moved to using OpenGLContext. That is another alternative. Also had the upshot of making the Ui more vibrant but I guess that is a bit subjective!

1 Like

OpenGL will definitely makes things faster, but you’ll still be redrawing the whole UI every frame.
That’s sub-optimal and inefficient, and users with low-end GPU’s such as integrated graphics as found in many laptops might experience stalls, e.g. with several open plugins, or when the DAW itself also uses the GPU.

I wonder from where’s the observation. is this something you saw? if so, would be nice to know what OS/specs was this seen with. especially since we’re talking about macOS here and I haven’t been able to see real issues using OpenGL backend even on a 2008 iMac here.

Are you sure about that? IIUC, JUCE caches the result of components drawing into a texture and only recompute the parts that need to be repainted.

That’s due to JUCE lacking color-space conversions with the OpenGL backend. It also means that colors may differ between machines that would otherwise know how to show the same color.

Note that this problem is fixed on SR’s JUCE branch as we have to use OpenGL on macOS due to this issue but don’t want the colors to get funky.

1 Like

I put some debug code to display the redrawn regions. If you build with 10.14+ SDK, in macOS 10.14 and above you always get one big rectangle spanning the whole window. With 10.13 SDK or below, you get several small rectangles depending on what you invalidated.

The performance hit will be especially noticeable if you’re using a vector based approach, as the number of pixels to be shaded. In my custom graphics engine GPU usage goes from below 1% to 10% as a result of this.

1 Like

That really depends on the approach you’re using to draw the UI. If you’re using static bitmaps the caching will help, but there’s still going to be a lot of unnecessary blitting.

There’s going to be drawing of the texture representing your whole component’s image once per frame. It should be negligible.

As I said I’m not using bitmaps so I haven’t checked this scenario. The only way to know for sure if it is negligible or not is to profile it.
On mac, OpenGL driver monitor and/or Activity Monitor can be used for this.

I wasn’t talking about user using bitmaps. When simply using normal JUCE components and attaching an OpenGL context, the UI will be cached to a texture automatically by JUCE.

1 Like

For me it’s important that the issue will be fixed. I want to keep things simple. I want to use the default JUCE branch (that unfortunately misses the color space conventions for OpenGL) and i don’t want to use OpenGL on windows, because we don’t have any issues there.

Our UI does not have a lot animations. Just a few sliders. It would be great if that would work out of the box in a smooth way without any OpenGL acceleration. I’m sure this is possible when the UI does not redraw the whole UI when moving a slider.

3 Likes

I’m not really familiar with the way Juce components work, sorry.
Still in case when there’s a bit of automation going on and/or a few meters being updated,
it seems like a totally unecessary waste of resources to redraw the static parts too.
I might be acceptable for a simple UI, but not so for a more complex one.

Changing textures is not free in GL either, nor is blitting a retina framebuffer.

Would indeed be great if this can be improved! We’ve also noticed a significant increase in CPU load for JUCE graphics on macOS Big Sur. Seems a bit of the Achilles heel of JUCE given this seems to be a recurring issue with every macOS update…

OpenGL is deprecated on macOS so that’s not a viable solution I’m afraid.

2 Likes