是否需要在主线程上绘制到 MTKView 或 CAMetalLayer?

Posted

技术标签:

【中文标题】是否需要在主线程上绘制到 MTKView 或 CAMetalLayer?【英文标题】:Is drawing to an MTKView or CAMetalLayer required to take place on the main thread? 【发布时间】:2019-01-19 15:08:54 【问题描述】:

众所周知,更新AppKitUIKit 中的用户界面需要在主线程上进行。在提交drawable 时,Metal 是否有相同的要求?

在我一直在使用的层托管NSView 中,我注意到我可以从dispatch_queue 调用[CAMetalLayer nextDrawable],而不是main_queue。然后我可以像往常一样更新该drawable的纹理并呈现它。

似乎工作正常,但我觉得这很可疑。除非我忽略了文档中的某些内容,否则我找不到任何提及 Metal 的主线程要求(支持或反对)。

(我正在 macOS 10.13 上进行测试,但我认为 ios 的主线程要求也相同......?)

【问题讨论】:

【参考方案1】:

在后台线程上绘制是安全的。 docs for -nextDrawable 说:

调用此方法会阻塞当前 CPU 线程,直到有新的可绘制对象可用。

(强调。)如果它只能在主线程上调用,那可能不会那么概括。此外,Apple 的一般建议是避免阻塞主线程,所以你会认为他们会在这里以某种方式指出这个事实,例如建议你不要调用它,除非你很确定它不会阻塞。

关于如何使用(而不是获取)drawable,请注意一个典型的用例是调用命令缓冲区的-presentDrawable: 方法。该方法便于添加调度的处理程序块(如通过-addScheduledHandler:),然后将在可绘制对象上调用-present。未指定将在哪个线程或队列上调用处理程序块,这表明对可绘制对象的 -present 调用不会发生在主线程上。

即使在那之后,drawable 在屏幕上的实际呈现在对-present 的调用中也不是同步的。可绘制对象等待直到渲染或写入其纹理的任何命令完成,然后才呈现到屏幕上。没有具体说明如何实现这种异步性,但它进一步表明调用哪个线程 -present 并不重要。

Metal Programming Guide 中有一些关于多线程的讨论,尽管它并不像人们希望的那样直接。尤其见the section on Multiple Threads, Command Buffers, and Command Encoders。请注意,这里讨论了由后台线程填充的命令缓冲区,并且没有关于使用可绘制对象的具体警告。同样,这是一种缺乏证据的论点,但我认为这很清楚。他们确实指出,一次只能有一个线程作用于给定的命令缓冲区,因此他们正在考虑线程安全问题。

【讨论】:

我倾向于同意,即使这与我多年来的 AppKit 规则背道而驰。 MTKView 的自动显示循环只调用drawRect,这确实发生在主线程上。如果我滚动自己的“渲染循环”并输出到可能托管在NSView 中的CAMetalLayer,听起来我可以渲染到该金属层的nextDrawable,而无需同步回主线程。相反,您将如何强制 Metal 确保当前呈现的纹理 与 UI 同步? (例如当图层的内容需要准确反映鼠标拖动的位置时。) 对于该问题,请参阅-[CAMetalLayer presentsWithTransaction] 的文档。将其设置为 true,不要调用 -[MTLCommandBuffer presentDrawable:],而是先调用 -waitUntilScheduled,然后再调用 -[MTLDrawable present]。在这种情况下,您必须在主线程上执行此操作以与其CATransaction同步。 啊,我在做 waitUntilScheduledpresent 部分,但不是 presentsWithTransaction 部分。我会添加它并玩更多。

以上是关于是否需要在主线程上绘制到 MTKView 或 CAMetalLayer?的主要内容,如果未能解决你的问题,请参考以下文章

在 draw() 循环中重新使用 currentDrawable.texture 时的 MTKView 混合问题

将 Metal MTKView 实时捕获为电影?

java Android - 检查是否在主线程上运行

java Android - 检查是否在主线程上运行

java Android - 检查是否在主线程上运行

java Android - 检查是否在主线程上运行