Frequent repaints cause high CPU load on macOS

I’m working on a plugin with visuals that I repaint frequently (currently with 20fps), and on macOS it’s eating a lot of CPU (~25% on both M1 Macbook Air and a 2015 Macbook Pro). On Windows (a rather old i7) it takes less than 5%.

I’ve profiled and optimized around a bit and it doesn’t seem like my drawing code is exceptionally slow. The CPU time is burned outside my code and the JUCE layer, in the Profiler I see mostly CoreGraphics and other system libraries in the hottest paths… Even when I disable all my drawing code and just issue the repaint() frequently on only one component, which ends up drawing nothing, I get this high base load. And no, it’s not coming from the DSP part. When I remove the repaint() from my timer callback, I get <5% load.

Here’s the top of my (inverted) call tree in Instruments CPU Profiler

And here’s the hottest path expanded:

Any idea what might be causing this? Any tricks I can try to mitigate this? Do I have to learn OpenGL now to draw some rectangles at 20fps?

Edit: another interesting observation - when my UI is out of focus, CPU load goes up even more (to ~32%).

Edit2: Tried on both JUCE 6.1.5 and current develop branch.

I flaneured a bit through some drawing code which I mostly don’t understand and came across an obscure define called JUCE_COREGRAPHICS_DRAW_ASYNC. It’s nowhere defined and only referenced twice in juce_mac_NSViewComponentPeer.m.

I felt adventurous and enabled it. For some reason that helped and now I’m <10% for the Standalone app and according to profiler that’s nothing to do with drawing.

I’m kinda happy, but I hate not knowing what I’m doing. Any idea what I just did?

2 Likes

The JUCE_COREGRAPHICS_DRAW_ASYNC turns on asynchronous drawing for the underlying NSView. What happens is that the draw calls are offloaded to a background thread. The drawing is done using Metal on a different thread and thus freeing the message thread of that load. It’s a great feature to enable. I remember people experiencing some glitches in specific situations (search around on the forum) and this is why there is a way to actually turn on async drawing per component (probably componentpeer) using the setComponentAsyncLayerBackedViewDisabled method.

1 Like

More information can be found here:
https://developer.apple.com/documentation/quartzcore/calayer/1410974-drawsasynchronously

Ah cool, thanks a lot! I’ll experiment a bit more and watch out for any glitches.

I had the opposite experience: enabling JUCE_COREGRAPHICS_DRAW_ASYNC does increase perceived smoothness/frame rate, but at the cost of much higher CPU usage.

I have a peak meter on the UI that I try to refresh at 60Hz.

JUCE 6.1.3
Reaper 6.5
4K display running at 200% scale
Mac mini 2018 i3

Here are a few figures I got (not DSP related, as CPU usage drops when the editor is not open):

  • Windows 10:
    CPU usage around 1% in normal use, up to 7% when interacting with the UI
    very good perceived smoothness

  • MacOS with JUCE_COREGRAPHICS_DRAW_ASYNC=0 and a 10-bit color profile:
    CPU usage around 30% in normal use, up to 90% when interacting with the UI
    bad perceived smoothness

  • MacOS with JUCE_COREGRAPHICS_DRAW_ASYNC=0 and a 8-bit color profile:
    CPU usage around 20% in normal use, up to 40% when interacting with the UI
    good perceived smoothness

  • MacOS with JUCE_COREGRAPHICS_DRAW_ASYNC=1 and a 10-bit color profile:
    CPU usage around 60% in normal use, up to 60% when interacting with the UI
    good perceived smoothness

  • MacOS with JUCE_COREGRAPHICS_DRAW_ASYNC=1 and a 8-bit color profile:
    CPU usage around 60% in normal use, up to 60% when interacting with the UI
    good perceived smoothness

MacOS is painfully heavy on CPU compared to Windows10
Will JUCE 7 address this issue?

It’s possible that macOS is repainting more than required. Please see the discussion about the new rendering options in JUCE 7 and give JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS a go to see if it addresses the problem.

1 Like

What’s up with people who want to display meters at 60 fps?

Do yourself a favour and read about visual perception… it can save you a lot of CPU cycles.