Metal / UIKit de-synchronisation using .presentsWithTransaction

Hi,


This is a follow-up post to a couple of previous posts made before (including https://forums.developer.apple.com/thread/92791) on an issue I have noticed attempting to synchronise Metal with UIKit. I'm posting again as I've isolated the issue further since and am hoping therefore it may be easier to find a solution.


Specifically, when using the method recommended in the documentation (https://developer.apple.com/documentation/quartzcore/cametallayer/1478157-presentswithtransaction) to synchronise Metal and UIKit using `.presentsWithTransaction` on a CAMetalLayer, if a spike in CPU usage occurs the render loop can become starved of drawables – apparently desynchronised – until subsequent CPU spike knocks it back into sync.


I have created an example project that demonstrates the issue in nearly the simplest Metal project possible (drawing a single quad to screen) and simulating a typical background load (a short wait on each frame of the render loop).


The example project is attached to rdar://43568815 in my latest comment.


In usual app operation, the duration for the CPU to submit and schedule its work on the GPU is ~1ms. However, when a CPU spike occurs due to arbitrary work required elsewhere by the system/app, desynchronisation may take place after which this can rise to around ~8ms. (In the example project, this can be simulated by pressing the 'do heavy work button' which will force a short delay on the main thread.) Observing this behaviour in the Metal instruments panel, we can see that the render loop is becoming blocked waiting for a drawable on each frame.


A subsequent CPU spike can knock the render loop back into sync.


This wait on the main thread seems to cause issues elsewhere in UIKit. In the example project, you can see that when the loop is desynchronised dragging the circle appears jerky – touch events appear delayed.


Desynchronisation only seems to occur if there is some quantity of work occuring on the main thread. On an iPhone X, 6ms worth seems to expose the issue, but this may need to be tweaked for other devices. Usually, pressing the 'do heavy work' button a couple of times will cause a desync/resync to occur, but occasionally requires a few more taps.


Would I be able to share the example project with someone at Apple who could perhaps take a look? Either confirming the issue or ideally suggesting a workaround? 🙂