UIScrollView performance and Metal

Hi,


I'm using Metal for to take advantage of the shaders in a regular (non-game app). I'm rendering some video into a MTLTexture and then I have some controls for which I'd like to employ standard UIKit controls that sit on top of the view.


My view hierarchy consists of a container view (UIView), with my metal view (MTKView) sitting in the background at index zero. And then a few UIViews sitting on top. CPU/GPU usage is next to nothing on an iPhone 7 (at least, when run without the Metal debug tools) but I'm experiencing some strange glitches with UIScrollView performance.


I'd like to overlay a UIScrollView on top of my metal view (so I can replicate the familiar UIScrollView physics easily) and drive some elements in my Metal view. However, I'm noticing some stalling of the UIScrollView animation which is causing what looks like dropped frames. As mentioned, CPU/GPU utilisation is next to zero.


Any ideas of how to work around this problem? I've seen mention that this may be due to some CADisplayLink synchronisation problem but that seemed to be Open GL specific.


Thanks!

Replies

I *think* I've found a solution for this.


There's a propery on MTKView called `presentsWithTransaction`. Set that to true and then, rather than calling `present(:)` on your command buffer, delay presentation until your command buffer is comitted and then call `waitUntilScheduled()` followed by `present()` _directly on your drawable_.


So in the simplest case, the draw hook goes from this:


func draw(in view: MTKView) {
   guard
        let commandBuffer = commandQueue?.makeCommandBuffer(),
        let drawable = view.currentDrawable,
        let descriptor = view.currentRenderPassDescriptor,
        let commandEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor)
    else { return }
    
    commandEncoder.endEncoding()
    commandBuffer.present(drawable)
    commandBuffer.commit()
}


To this:

func draw(in view: MTKView) {
   guard
        let commandBuffer = commandQueue?.makeCommandBuffer(),
        let drawable = view.currentDrawable,
        let descriptor = view.currentRenderPassDescriptor,
        let commandEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor)
    else { return }
    
    commandEncoder.endEncoding(
    commandBuffer.commit()
    commandBuffer.waitUntilScheduled()
    drawable.present()
}


Making sure to enable `presentsWithTransaction` on your MTKView.