7

It's well known that updating the user interface in AppKit or UIKit is required to take place on the main thread. Does Metal have the same requirement when it comes to presenting a drawable?

In a layer-hosted NSView that I've been playing around with, I've noticed that I can call [CAMetalLayer nextDrawable] from a dispatch_queue that is not the main_queue. I can then update that drawable's texture as usual and present it.

This appears to work properly, but I find that rather suspicious. Unless I've overlooked something in the documentation, I can find no mention of Metal's main thread requirements (either for, or against).

(I'm testing on macOS 10.13, but I would assume the main thread requirements would be the same for iOS as well...?)

kennyc
  • 5,490
  • 5
  • 34
  • 57

1 Answers1

6

It is safe to draw on background threads. The docs for -nextDrawable say:

Calling this method blocks the current CPU thread until a new drawable is available.

(Emphasis added.) If it could only be called on the main thread, that would probably not be so generalized. Also, Apple's general advice is to avoid blocking the main thread, so you'd think they would call out that fact in some way here, such as advising you not to call it unless you're pretty sure it won't block.

For how the drawable is used (rather than obtained), note that a typical use case is to call the command buffer's -presentDrawable: method. That method is a convenience for adding a scheduled handler block (as via -addScheduledHandler:) which will then call -present on the drawable. It is unspecified what thread or queue the handler blocks will be called on, which suggests that there's no promise that the -present call on the drawable will happen on the main thread.

And even after that, the actual presentation of the drawable to the screen is not synchronous within the call to -present. The drawable waits until any commands that render or write to its texture are completed and only then presents to the screen. It's not specified how that asynchronicity is achieved, but it further suggests that it doesn't matter what thread -present is called on.

There's a bit of discussion about multi-threading in the Metal Programming Guide, although it's not quite as direct as one might hope. See especially the section on Multiple Threads, Command Buffers, and Command Encoders. Note that there's a discussion of command buffers being filled by background threads and no specific warning about working with drawables. Again, it's sort of argument by lack of evidence, but I think it's clear. They do call out that only a single thread may act on a given command buffer at a time, so they are considering thread safety questions.

Ken Thomases
  • 88,520
  • 7
  • 116
  • 154
  • I tend to agree, even if it's counter to my years of AppKit rules. MTKView's automatic display loop just calls `drawRect`, which does happen on the main thread. If I roll my own "rendering loop" and output to a `CAMetalLayer` that might be hosted in an `NSView`, it sounds like I can render to that metal layer's `nextDrawable` without having to sync back to the main thread. Inversely, how would you force Metal to ensure that the currently presented texture *is* in sync with the UI? (Like when the layer's contents need to accurately reflect a mouse drag's location.) – kennyc Aug 13 '18 at 09:41
  • 1
    For that question, see the docs for [`-[CAMetalLayer presentsWithTransaction]`](https://developer.apple.com/documentation/quartzcore/cametallayer/1478157-presentswithtransaction). Set that to true, don't call `-[MTLCommandBuffer presentDrawable:]`, call `-waitUntilScheduled` and then `-[MTLDrawable present]`, instead. In this case, you **would** have to do this on the main thread to synchronize with its `CATransaction`. – Ken Thomases Aug 13 '18 at 10:12
  • Ah, I was doing the `waitUntilScheduled` and `present` parts but not the `presentsWithTransaction` part. I'll add that in and play around a bit more. – kennyc Aug 13 '18 at 13:01