Graphics slow on El Capitan

It’s a large multi-function app. If we can’t figure it out soon, I will try to strip out the relevant sections into a small project to share.

I think AppNap was present in Mavericks as well as El Capitan. All worked fine with this app in Mavericks, so I’m thinking that’s not it.

[quote=228694:@Sam Rowlands]It’s my understanding that Quartz 2D is hardware accelerated.
Interesting… Is double buffering enabled for the canvas? Are you using any vibrancy on your window? Any Layer-backed Views (setWantsLayer)?

On El Capitan, I’ve not noticed in speed degradation with 2D drawing. Core Image (for processing images) is busted on 32-Bit (for some machines), and slow on 64-Bit.[/quote]
Double-buffering - Not enabled. Same speed with or without.
Vibrancy - No
Layer backed Views - None

FWIW - I’m not using any Declares or Third-party libraries.

[quote=228695:@Sam Rowlands]Some other things (off the top of my head).

• When using invalidate( left, top, width, height, false ) make sure the last parameter is false.
• Set eraseBackground, doubleBuffer & transparent on the canvas to false.
• Make sure the canvas isn’t overlapping any other control.
• Another way to enable Layer Backed Views is to use the StyleMask “NSFullSizeContentViewWindowMask = 32768” when removing the titlebar of the window. I logged this as a bug with Apple, but it’s not gained any traction.
• When controls don’t overlap, there is the declare to speed it up (although I never noticed any real difference).

Declare Sub mUseOptimizedDrawing lib "AppKit" selector "useOptimizedDrawing:" ( inWindow as WindowPtr, inValue as boolean )

• Have you tried 64-Bit? 32-Bit seems to be getting left further and further behind (with the removal of some functions, some are broken and new functionality seems to be 64-Bit only).[/quote]
Lots of great suggestions! Unfortunately…

  • Setting last parameter of Invalidate for false - I had been letting it default to true, but setting false had no impact.
  • Set eraseBackground, doubleBuffer & transparent on the canvas to false -> No change.
  • Make sure the canvas isn’t overlapping any other control - It’s not.
  • Another way to enable Layer Backed Views… - I’m not using Layer Backed Views.
  • … there is the declare to speed it up… - Does this automatically propagate to all controls on the window? Since it was plenty fast on Mavericks, would like to identify what’s wrong now, but will try this if can’t find problem.
  • Have you tried 64-Bit? - Same problem as in 32-bit.

I’m not using any Core Image calls. Just 2-D drawing on the Graphics component of a Canvas.

FYI: this is deprecated from OSX10.10

Stripped-down Project

I really appreciate all the great ideas. I wish one of them was the solution.
I have posted a stripped-down version of the project here:

The main window is TriggerTestW. The action happens in it’s only Canvas (GraphCanvas). There is a timer called SampleTimer that fires every 5 milliseconds and gets a new data point for a simple simulated blood pressure curve. It Invalidates a small rectangular portion of GraphCanvas. GraphCanvas’s Paint event calls PlotDataSamples to draw the point on the curve.

The plot should appear with 70 pulses per minute, i.e. less than 1 second between peaks. It used to do that just fine. Now it requires about 3.5 seconds between peaks. Profiling shows that PlotDataSamples is taking >14 milliseconds, well below the 1-2 milliseconds it used to take.

HELP!!

5 milliseconds is 200 calls per second.
Most (all) mac screens redraw at 60 fps, which is 16 2/3 milliseconds per frame.

What usually happens is multiple calls to invalidate are coalesced into a single Paint event. Also, when invalidating rects these may be merged into a single rect, which is happening in your case. For example you have this in the Paint event

if (UBound(areas)>=0) then PlotDataSamples(g, areas(0)) end if

You’re only processing areas(0) which is actually a merged rect of 2, 3 or maybe 4 invalidaterects.

All of this is to preface that maybe Invalidate coalescing isn’t working anymore, like it’s blocking until the next screen redraw. It’s just that 14ms seems a suspect time.

If coalescing isn’t happening you’ll see the same number of calls to GraphCanvas.Paint as there are sampleTimer.Actions in the Profile results. For example, in 10.10 I see 406 Paints and 1303 timer Actions (haven’t installed 10.11 yet to test).

Another thing you can try is commenting out all of PlotDataSamples and see what the times are. If it goes to near 0 then slowly uncomment lines and find where the time jumps.

[quote=229044:@Will Shank]5 milliseconds is 200 calls per second.
Most (all) mac screens redraw at 60 fps, which is 16 2/3 milliseconds per frame.[/quote]
Very interesting thoughts, Will. Profiling consistently shows PlotDataSamples taking just a fraction over 14 ms, never as long as 16. I’m running on a MacBook Air (strange platform for coding, I know). I have no idea what the display refresh rate is on my particular machine.

Again, a very interesting chain of thought. Certainly, you are correct that several Paint areas must be coalescing when this runs normally. I will check to see if the are more areas than one being passed to the Paint event. If so, I could draw them, but then things will be even slower
.[quote=229044:@Will Shank]If coalescing isn’t happening you’ll see the same number of calls to GraphCanvas.Paint as there are sampleTimer.Actions in the Profile results. For example, in 10.10 I see 406 Paints and 1303 timer Actions (haven’t installed 10.11 yet to test).[/quote]
Aha! The number of Timer Actions EXACTLY matches the number of Paint events. That certainly seems to indicate that you are correct - no coalescing is occurring.

The OS surely must do this kind of coalescing. Surely this would cause problems for lots of things - games, dragging things rapidly on the desktop, etc. I would think there would be a lot of noise about that. Is it possible that XoJo is doing the coalescing in this case??

Nope. On El Capitan it appears that a timer’s action indirectly causes invalidated views to be redrawn and flushed to the screen. Given how rapid your timer is set to fire and the fact that it invalidates views, things happen a lot.

Yes, and the drawing kept up with the data on Yosemite and on Mavericks. I’m sure that many Paint areas are/were being combined behind the scenes. The net result (in the past) was that the plotting kept up with the data.

If previous OS’s did this, but El Capitan doesn’t, shouldn’t this affect lots of things were Invalidate/Refresh events are occurring faster then the screen can redraw?

[quote=229058:@Matt McHugh]Yes, and the drawing kept up with the data on Yosemite and on Mavericks. I’m sure that many Paint areas are/were being combined behind the scenes. The net result (in the past) was that the plotting kept up with the data.

If previous OS’s did this, but El Capitan doesn’t, shouldn’t this affect lots of things were Invalidate/Refresh events are occurring faster then the screen can redraw?[/quote]

Sorry, I meant El Capitan. My memory can’t keep up with the OS releases.

Ahh, yes, @Joe Ranieri. It does seem like OS X 10.11.1 (I prefer the numbers) is drawing every Invalidate event independently, causing a huge slowdown.

I’m wondering, though. Doesn’t that mean that Invalidate is acting identically to Refresh now? If that’s the case, that would suggest a problem in the Xojo code for Invalidate, at least on the current OS.

No. Invalidate behaves exactly as it always has. The difference is that a timer’s action now causes the invalidated views to be drawn and flushed to screen.

I’m confused. Are you saying that any timer going off will cause a screen update? If I caused Invalidate events to happen faster than screen refreshes using a method other than timers, would they not get flushed but coalesced instead?

Is this definitely a change that occurred only at 10.11?

If there are any views that have been invalidated and need displaying, yes.

Depends on the mechanism.

Yes.

Thank you, @Joe Ranieri. Do timer actions now directly cause the OS to look for invalidated views? Why would Apple do that?

I tried drawing the points in groups of 10 (i.e. 50 milliseconds of data) and it works beautifully! Average execution time for PlotDataSample has gone from >14 ms down to 0.4 ms. With a few optimizations, I think I can even cut that in time. Plus, now PlotDataSample is only called one-tenth as often. So total speed-up is 2 orders of magnitude!

Thank you once again to all who have helped on this, especially @Joe Ranieri. I never would have figured this out without your help!

Is this a Xojo specific issue that can be fixed or a new OSX thing? I’ve been coalescing invalid rects myself and calling invalidate from a timer (not even realizing the side-effect I was side-stepping, just that it was way smoother)

From the 10.11 AppKit release notes:

To implement this, Apple used a CFRunLoopObserver that fires at the end of each iteration of the main run loop. Doing this allows all of the work for layout and display to be done at the very end of event processing without ordering issues between them.

The issue is that a run loop iteration isn’t the same as an event loop iteration. Every time the main CFRunLoop wakes up for any reason, like timer callouts, the observer also gets called.

Normally this isn’t a problem because views either aren’t invalidated during a timer callout or the timer periods are long enough that drawing will be handled as usual. Having a timer that invalidates controls and is faster than the display rate is about the only time this starts becoming a ‘problem’.