setNeedsDisplayInRect broken on Sonoma?

I filed an apple bug on this, and to be clear I don’t think it is a Juce bug at all, but I figured I would post the details here and see if the Juce team or anyone else has any more information about the situation that might be helpful.

It appears that in Sonoma, setNeedsDisplayInRect ignores the rect you pass it. The resulting drawRect: seems to always include the entire bounds of the NSView.

In a juce based app - where likely we will have one NSView for the whole window - this results in repainting the entire window each time any small individual component has repaint() called on it.

In our app this causes things to be unacceptably slow. In Ventura, when this API worked as expected, the mac UI was very fast I would say. We are using the default rendering options in juce ( core graphics with the asynchronous feature ). This is on Juce 6 I should say.

Anyone else experience with this or have any intel on how to deal with it or when it might be fixed?

It really seems to be broken, thanks for bringing this to our attention.

It used to be broken on older versions too, but apparently not on Monterey or Ventura.

As a workaround you can try adding the JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS=1 definition to your project, which enables an alternative Metal based renderer.

This will only redraw the affected area even on Sonoma, but it’s not guaranteed to be faster overall for your specific application, which is why it’s not enabled by default.

1 Like

Thanks - yeah I have tried the JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS option in the past and was just giving it another shot on Sonoma now that you mentioned it. However, it seems that getRectsBeingDrawn: is returning the full rect of the entire view, so the results appear to be the same in terms of performance and how it is constantly redrawing the entire window.

FYI here is a thread on the apple developer forum about this: Window updates are broken on Sonoma | Apple Developer Forums

For that link you can ignore the discussion about clipsToBounds, because that is a separate issue and is a red herring in this context. But the person has done some interesting research on classes that have changed internally in core graphics - see the discussion about NSViewBackingLayer.

I hope they fix it soon. However, it still appears to be broken in the latest 14.1 beta build which is what I am running.

1 Like

I can confirm that this issue does exist (on macOS 14.1) and JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS=1 works for me. However, it uses dGPU now (IIRC, before macOS 14 it used iGPU).

I reported this issue about one month ago. Thanks for your explanation. Now I understand why the level meter is slow: it tries to repaint the whole window even if only the level meter calls repaint.

getRectsBeingDrawn: is returning the full rect of the entire view

The drawing calls are dispatched to NSViewComponentPeer::renderRect, and in my testing the NSRect argument to this function is just the affected, dirty area. I don’t think the result of getRectsBeingDrawn: will expand this area indirectly.

That said, the performance may very well be the same or worse in your case.

The discussion about JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS is spread over a few places in the forum, so I’ll add a summary here.

In a fairly recent macOS update CoreGraphics started consolidating dirty rectangles. This means that if your GUI has an unfortunate layout, like meters on both sides of your GUI, then rather than invalidating two small rectangles CoreGraphics might invalidate the entire area between the meters too.

Simultaneously, for JUCE 7, we enabled “asynchronous” CoreGraphics drawing. There are no docs that explain exactly what this flag does, but it seems very likely that drawing commands are queued for later execution on the GPU rather than processed immediately on the message thread.

Asynchronous drawing provides a very substantial performance improvement. However if you are suffering from CoreGraphics invalidating much more of your GUI than it needs to then this additional superfluous drawing may be slowing things back down again. Enabling JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS uses a different rendering path that is incompatible with asynchronous drawing, so you will be rendering much more slowly, but you may be rendering much less content. In some cases this is a clear win, in other cases it is not beneficial.

If we are now rendering the entire view each time with the default rendering path then enabling JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS is much more likely to be a performance improvement.


Do you anticipate finding a solution to this recent problem without the need for JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS, because some applications are clearly in a situation where using the option slows performance down, but if not using it drawing on Sonoma is slow too, so it’s a losing game either way for some.

Of course redesigning plugins to avoid what is now an ‘unfortunate layout’ like meters on both sides of it is not much of an option for in-service plugins and we need a suitable technical solution asap. A plugin GUI being slow because it has animated meters on both sides of it after an OS update is not really an acceptable situation for a framework to be in, so I think this is a pretty high priority problem to resolve. Does the Juce team agree?

Thanks for your reply. Here is a screenshot of the plugin. Both input/output meters are on the right side. However, it seems that this layout is also ‘‘unfortunate’’. I attach a logger to the left side curve-plot component and discover that:

  • with JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS=0, the curve-plot repaint itself continuously. However, it should only get repainted when I change parameters (triggered by an APVTS listener).
  • with JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS=1, the curve-plot repaints itself only when I change parameters.

I am afraid that enabling JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS is the only solution for me :melting_face:

Excuse me if my question is too naive, but I had the impression for years that Juce handled clipping rects, dirty flags and all the message driven repainting logic?

Is macOS capable of interfering in this “layer” by invalidating areas itself?

We are investigating. We will report back when we can provide any useful information.


I look forward to a speedy resolution. Issues like this that cause developers and clients real pain are matters that I hope are prioritised for attention over longer term developments like the major upgrades being done for Juce 8. I’d observed this behaviour in various Juce plugins since the first Sonoma beta. It seems worst by far in Logic Pro using an Intel Mac with integrated graphics, with varying levels of badness using discrete GPU Intel Macs that I still have, and seems basically fine on Apple Silicon Macs. Maybe that’s down to driver support, inherent power of the GPU, or some other factor. I had no idea what the cause of it was so didn’t raise it because reports with so little to go on are pretty unhelpful, so I’m delighted @andrewoliverhall seems to have figured out the root cause.

The term ‘unfortunate layout’ has great meme potential, things could get wild around here.

I wonder how many developers are struggling with their unfortunate layout. Knowingly or not.

The resolution is on Apple handle from what it looks like…

This affects all software that uses setNeedsDisplayInRect. If the intention was to invalidate the whole view you would instead call setNeedsDisplay.

Right exactly. I posted the issue here but just to remind everyone that this appears to be an issue on the apple side, not the juce side. I would think it would affect lots of non-juce apps/plugins out there too. I have filed a bug using the feedback assistant using our company team - so others might consider doing the same if you are affected.