Before I begin I just want to say that your machine is more than powerful enough to do what you need it to do. I develop far more power-hungry stuff on older hardware with less ram on OSX.
And, as a disclaimer, I’m not tracing through the source.
I spent the morning researching this as best I could. Specifically, I found this in the documentation:
Apple Docs: Cocoa: Optimizing Performance: Avoiding Synchronous Updates
If you are creating animated content, you should also be careful not to trigger visual updates more frequently than the screen refresh rate allows. Updating faster than the refresh rate results in your code drawing frames that are never seen by the user. In addition, updating faster than the refresh rate is not allowed in OS X v10.4 and later. If you try to update the screen faster than the refresh rate, the window server may block the offending thread until the next update cycle.
Now, I don’t think that this is specifically the issue. But it does demonstrate that the Windowing Server has the ability to block repaint messages that it deems to cause performance issues.
The windowing server in question is the Quartz Compositor which also handles the event queue (mouse, timers, etc…). When I started developing on OSX years ago, I noticed that I get FAR MORE mouse events than on Windows. It was enough of a problem that I started throwing events away. IIRC, dragging the mouse was the primary culprit (it’s been quite a while ago).
This video demonstrates the difference in Windowing performance on Mavericks and El Capitan: https://www.youtube.com/watch?v=LlOdeSeNLcs
I have fixed a couple bugs related to ‘upgrades’ in the Windowing system to make me think there might have been significant rewrites. During the rewrites they could have certainly prioritized user responsiveness over redraw.
Long story short: one day OSX will do all drawing/composition on GPU in a separate thread (probably on a Metal renderer). It was probably meant that El Capitan be that release but slipped.
What does this mean for you?
Well, I suspect that the Quartz upgrades includes the ability to throw away repaint requests. At least delay them for longer intervals while the mouse is dragging. It might also delay requests based on frequency instead of draw time. Julian has addressed this issue in the past with aggregation, but with high precision mouse drag events, it might still be affecting the frequency of repaints.
Or they might just throw away repaints altogether while mouse dragging. And force update at lower intervals. There’s nothing you can do if Quartz is straight-up throwing away repaints during drag operation.
Where to Go?
Use the Quartz Debugger and enable QuartzGL drawing. If this fixes your problem, it might tell you something. Remember to disable again to do any profiling.
Use Instruments to profile your App EXACTLY when you are scrolling. Pause the profiling, clear the data, restart profiling… Then scroll your app a bunch and pause the profiling again. See if there are any sections that seem to spend a lot of time in.
It’s harder to diagnose if OSX is choking your redraw. In fact, we can simply assume that it is and see if it’s
- Time based (too much drawing)
- Frequency Based (too many update requests)
- Fixed Function (OSX simply throwing away repaints during mouse drag)
If #1 is the problem, you should catch it in Instruments as a lot of time spent in drawing code. Also, caching should have some significant improvements. The Quartz Debugger speedometer would peak and then drop as Quartz starts throwing away repaints.
If it’s #2 or #3, then the Quartz Debugger speedometer should unexpectedly drop when scrolling. Like as soon as you start scrolling. There might be a small upward hitch if it’s frequency based, but if it’s fixed function then it would drop straight away.
If it’s #1, then you should be able to cache some of the more complex components. If it’s #2 or #3 then you might need to switch to an OpenGL renderer (JUCE OpenGL runs on a separate thread).
If the theory is completely wrong and Quartz isn’t throwing away repaints then the speedometer will peg out while scrolling your app. Be sure to run your tests with QuartzGL turned off.