Main thread blocking in Metal

I'm experiencing an issue here in Metal that's a bit of a show-stopper for me.


Let's consider using Apple's Game of Life sample found here:

https://developer.apple.com/library/content/samplecode/MetalGameOfLife/Introduction/Intro.html


This compiles and runs fine. However, if one begins manipulating the app's menu items one will see that, when one "lets up" on a given menu item there's a slight pause in the rendering loop. Likewise, for any popup menus in the UI (or new windows as they open/close), lag in the render loop is also an issue.


When one debugs the code, it's apparent that the Metal rendering loop is running on the main thread. This seems typical of all of Apple's sample Metal code. This thread is being blocked during those "dissolve" animations in the menu items.


This really is affecting the viability of my project where I can't have any stuttering of the rendering loop -- for me, it's an AVPlayer's video frames being run through an MTKView. I've gone through the trouble of moving my frame acquistion methods into a CVDisplayLink (ie. off the main thread), only be to flummoxed by this issue.


Is there a way around this? Can the rendering loop be safely moved to a background thread, say, with explicit calls therein to its draw method (eg. with an MTKView's isPaused=YES and enableSetNeedsDisplay=YES).


Any direction on this matter would be appreciated.

Replies

I think it can be done - not that I did it myself. Main problem would be that (I believe) all Cocoa stuff _has_ to be done on main loop. So what I'd do is to create custom UIView (on iOS) or NSView (on Mac), with CAMetalLayer inside. This can be done like this for NSView:

- ( id )initWithCoder: ( NSCoder * )coder device: ( id< MTLDevice > )device
{
    self = [ super initWithCoder: coder ];
    [ self setWantsLayer: YES ];
    [ self setLayerContentsRedrawPolicy: NSViewLayerContentsRedrawOnSetNeedsDisplay ]; // You'll probably want other
    CAMetalLayer * metalLayer = [ CAMetalLayer layer ];
    [ metalLayer setDevice: device ];
    NSColorSpace * colorSpace = [ NSColorSpace genericRGBColorSpace ];
    [ metalLayer setColorspace: [ colorSpace CGColorSpace ] ];
    [ metalLayer setPixelFormat: MTLPixelFormatBGRA8Unorm ];
    [ metalLayer setFramebufferOnly: YES ];
    [ self setLayer: metalLayer ];

Or like that on UIView, by providing custom class method:

+ ( Class )layerClass
{
    return [ CAMetalLayer class ];
}


Then for the rendering I'd use CAMetalLayer functions only for things like getting a drawable and so on. I guess all these could be submitted from other thread than main thread, for example DisplayLink thread. And I am guessing it could work. You'll probably need some sync for stuff like "main thread resized your view and now you want to communicate new dimensions to rendering thread". But I never tried it myself, so be warned.

Regards

Michal

I ended up getting around my issue as follows....


Rather than using the MTKView's internal timer, I put the view in explicit draw mode (ie. isPaused=YES and enableSetNeedsDisplay=NO).


Then, from within my CVDisplayLink callback, I do a GCD dispatch_async on a global queue to explicitly draw the contents of the view.


Works like a charm.