Slow Frame Rates on 2017+ Macs

Just keep in-mind regarding JUCE comptability.
This requires macOS 10.8+.
(currently JUCE minimum target is 10.7).

Can I ask what exactly you added where?
I’d like to test this out.

I think I tried the wantsLayer in the past and it didn’t seem to do much (IIRC because top-level windows are always layer backed).
Do you know if it’s the drawsAsynchronously flag that gave you a big speedup?

Wow. Adding:

        [view setWantsLayer: YES];
        [[view layer] setDrawsAsynchronously: YES];

just under the [view setPostsFrameChangedNotifications: YES]; line takes me from about 10fps to 60fps. It’s like night and day.

I have noticed some graphical glitches but they look like they’re to do with dirty rects (or maybe aliasing) that I can probably work around.
It also looks like you can’t have transparent windows with one of those flags (CallOutBoxes etc.) so I’ll need to do some investigation there too.

7 Likes

Thanks @olilarkin and @basteln and everyone else…

I can confirm that the update fixes my issue on my iMac Pro using the iMac Pro Color profile. In fact, I even noticed a bit more detail in the graphics (could be euphoria haha). Resizing is faster and overall it feels like a new plugin graphics-wise.

So adding these flags only to opaque components seems to work very well:

        if (component.isOpaque())
        {
            [view setWantsLayer: YES];
            [[view layer] setDrawsAsynchronously: YES];
        }

I managed to work around my graphical glitch by snapping the lines to 0.5px boundaries.

Would it be possible to get these flags added to JUCE under a special “beta” (JUCE_NSVIEW_DRAWS_ASYNCHRONOUSLY or similar) macro so I can send a version out to beta testers?

I’d like to see if any more glitches are found before committing to this completely.

4 Likes

Digging a little deeper in to the stack traces of the profile, it looks like doing this enables the graphics to be rendered using Metal. It also puts the drawing on background threads which frees up a lot of time on the message thread.

I’m really impressed by this. Kudos to @olilarkin for sharing.

2 Likes

This may have been the biggest find of the year. :grin: :grinning:

Wishful thinking here, but does this translate to iOS as well?

1 Like

Sure! I also read your other posts below this one, and what I did is almost the same. I added this:

view.wantsLayer = YES;
view.layer.drawsAsynchronously = YES;

…in between the view = [createViewInstance() initWithFrame: r]; and the setOwner (view, this);.

Wishful thinking here, but does this translate to iOS as well?.

I’ll try it. On iOS, a CALayer is always used, but the drawsAsynchronously is off by default and can be turned on just as on macOS. We’ll see how much it affects speed on iOS.

1 Like

This is ridiculously effective :star_struck:

Just tried it on iOS, can confirm that just by setting drawsAsynchronously to YES I get a major speedup in our app. The Apple doc for drawsAsynchronously says to measure and compare, but IMO it’s worth trying out :+1:
@olilarkin Thanks so much, once again.

2 Likes

@basteln Now this doesn’t solve our rects painting and drawing the entire interface stuff as per (https://forum.juce.com/t/re-solved-repaint-ignores-opacity-and-repaints-parent-component/35597/2)? Actually, what is going on…Are we now using Metal as per what @dave96 suggested?

I think what happens with the async and layer flags is that:

  1. The window has a CA layer which creates a Metal context to draw on to
  2. In your paint method, instead of directly drawing on to the screen via CoreGraphics, these instructions get queued up
  3. Another thread(s) then dispatch the graphics calls using Metal on to the context

This is just a rough guess from looking at the profile trace though. I’m not a Metal expert so if anyone has more insight I’d be keen to hear.

This has been added on the develop branch in a277256, so you can enable the JUCE_COREGRAPHICS_DRAW_ASYNC flag to test it out.

2 Likes

Thanks @ed95, I think you need to wrap it in an if (component.isOpaque()) check though or window transparency is broken.

Ah OK, will do.

I found this worked on all the transparent components in my UIs, so might be better to disable it a bit more selectively to maximise the performance gains if possible.

1 Like

Yeah, @ed95 worked it out so it works on transparent windows too now.

You have to set the window to a “clear” background which was only being done for newer SDKs. Removing the SDK check seems to resolve it on all targets.

2 Likes

This is awesome ! I’m putting the two magic lines enclosed in a

if (floor(kCFCoreFoundationVersionNumber) >= kCFCoreFoundationVersionNumber10_8)

so that I can still target macOS 10.7, and take advantage of this improvement on later macOS versions.

3 Likes

Just tried and I notice a real difference on my 2019 5k iMac.
@jules You guy should definitely have a look after ADC.

Thanks !

Annoyingly it looks like some plugins have an issue with this flag set. At the moment we’ve had reports of Waves Gold V10 and some Izotope plugins just showing a blank UI with this flag set.

Even more annoyingly though, the most recent versions of these plugins seem to work (Waves V11 etc.) but we have a lot of users still on V10.

It looks like we might have to add a way to disable this on a per-window basis so I can simply not use it for plugins windows. Maybe another ComponentPeer::StyleFlags?