Metal / UIKit synchronisation with .presentsWithTransaction

The docs for

CAMetalLayer
state that the property
presentsWithTransaction
is:

A Boolean value that determines whether the layer presents its content using a Core Animation transaction

As I'm using UIKit to drive some metal animations (similar to the method Apple suggests for OpenGL in this WWDC 2012 session), I'm assuming this is the right time to enable it. I have a Metal "background" view, overlayed with some UIKit components (which also animate) and so this sounds very much like the applicable use-case:

By default [

.presentsWithTransaction
] is false:
CAMetalLayer
displays the output of a rendering pass to the display as quickly as possible and asynchronously to any Core Animation transactions. However, if your game or app combines Metal and Core Animation content, it's not guaranteed that your Metal content will arrive in the same frame as your Core Animation content. This could be an issue if, for example, your app draws UIKit content (such as labels with a target position and time) over the top of your
CAMetalLayer
and the two domains need to be synchronized.

Certainly, without that setting enabled, scrolling appears jerky. With

presentsWithTransaction
enabled I'm having some limited success but neither of two routes I've tried with the setting enabled are perfect. The first method I've tried follows the instructions within the docs for
presentsWithTransaction
. So, within my
MTKViewDelegate
I have the following method:


func draw(in view: MTKView) {
    guard let commandBuffer = commandQueue.makeCommandBuffer() else { return }

    updateState(device: device, library: library) // update positions, etc.
    render(with: commandBuffer, in: view) // drawing code

    commandBuffer.commit()
    commandBuffer.waitUntilScheduled()
    view.currentDrawable?.present()
}


I also set

view.isPaused = false view.enableSetNeedsDisplay = false
on my
MTKView
. This seems to synchronise more successfully than the usual method (
present(:)
on
MTLCommandBuffer
with default
MTKView
settings).

It still has a tendency to de-synchronise on occasion however, causing a characteristic shudder driving a scrolling animation via a

UIScrollView
for example. The whole idea of
presentsWithTransaction
is to avoid exactly this, so perhaps I'm doing something wrong here.


The second method makes use of

addScheduledHandler
on the command buffer:
func draw(in view: MTKView) {
    guard let commandBuffer = commandQueue.makeCommandBuffer() else { return }

    updateState(device: device, library: library)  // update positions, etc.
    render(with: commandBuffer, in: view) // drawing code
    
    if let drawable = view.currentDrawable {
          commandBuffer.addScheduledHandler { _ in
              DispatchQueue.main.async { drawable.present() }
          }
    }
    
    commandBuffer.commit()
}


This method appears to stay in sync, but causes some horrendous CPU hangs (2 secs or more), especially when the app becomes active after being in the background.


Is there any way to get the best of both worlds?

Replies

Hi Tricky,


I've been discussing this with the CoreAnimation team. They would like to see a sample to find out what's going on. Can you create a bug report and attach a sample showing the problem?

Hi Dan, thanks for the reply. I think I've resolved my issue for now.


After some further investigation (and watching the Metal Performance Optimization Techniques WWDC 15 video) I was able to analyse my code a little better. It turned out that my synchronisation issues were actually due to dropped frames. My command buffer would sometimes take ~30ms to fill. The specific issue for me was using CoreImage to render a CVPixelBuffer rather than CVMetalTextureCacheCreateTextureFromImage. The Core Image method is relatively resource intensive and revealed some underlying contention on resources.


I'm not entirely sure why changing the method used to synchronise with Core Animation would impact the way in which this occured but I'm guessing it was just a case of certain timings being more prone to exposing the contention than others.