在后台线程上通过 CARenderer 渲染 CALayer -> MTLTexture 时闪烁

Flickering while rendering a CALayer -> MTLTexture, via CARenderer on background thread

提问人:scornflake 提问时间:10/19/2023 最后编辑:scornflake 更新时间:10/25/2023 访问量:67

问:

我正在尝试通过 CARenderer 渲染图层树,最好是在后台线程上,但遇到了奇怪的渲染/闪烁问题


截至10月25日的进展情况

不知道问题到底在哪里出现(我的 CARenderer -> MTLTexture 创建者?视图?转换为 CMSampleBuffer 以编写 vidoe?),我编写了一个简单的着色器来计算每帧的粉红色像素数,该着色器几乎在调用 CARenderer.render() 后直接执行。

然后,我习惯于触发将“坏/有问题”帧写入文件。这是我看到的那种事情:

Weird half baked render

注意:渲染系统(请参阅代码)现在将 MTLHeap 用于其纹理,因此应用程序中的其他线程/任务应该不可能并行重用和覆盖该纹理。

“将 CALayer 呈现为 MTLTexture”代码的关键部分是:

    if let renderCommandBuffer: MTLCommandBuffer = queue.makeCommandBuffer() {
        let renderCommandEncoder: MTLRenderCommandEncoder = renderCommandBuffer.makeRenderCommandEncoder(descriptor: currentDescriptor)!
        renderCommandEncoder.label = "Clear Target"
        renderCommandEncoder.endEncoding()
        renderCommandBuffer.commit()
        renderCommandBuffer.waitUntilScheduled()

        rendererToUse.beginFrame(atTime: CACurrentMediaTime(), timeStamp: nil)
        rendererToUse.addUpdate(rendererToUse.bounds)
        rendererToUse.render()
        rendererToUse.endFrame()

请注意缺少“rendererToUse.waitForMeToBeDoneThanksVeryMuch()” 而“rendererToUse”是一个 CARenderer。

因此,在我看来,“rendererToUse”(CARenderer 实例)在我使用 MTLTexture 时可能仍在执行(要么计算它的粉红色像素,要么在视图中显示/保存到电影中)。

因此,我将 MTLHeap 更改为使用 .tracked 的 MTLHazardTrackingMode。 到目前为止,这似乎已经解决了问题。 这似乎很沉重 - 因为同步显然是在堆级别(我认为这意味着对堆提供的任何内容的所有访问都是序列化的)。我还不知道这是否意味着我不能同时从堆中写入两个/三个纹理,或者这是否意味着对堆中每个独立提供的纹理的访问是独立序列化的。

因此,我认为我现在的问题可以归结为:

假设我在这里甚至走在正确的轨道上:你如何强制 CARenderer 执行相当于 .waitUntilComplete() 的工作?鉴于您无法获得命令缓冲区,也无法获取它可能正在创建/使用的任何编码器。MTLHeap 中的纹理是 .private,因此您无法对我可以看到的它们执行任何 blit 同步(所有尝试对我来说都失败了,具有各种 storageMode 验证断言),并且 MTLHeap 无法提供我知道的 .shared 或 .managed 纹理(尝试过,验证断言失败)

我仍然不能100%确定我是对的。但到目前为止,hazard=.tracked 的“非常大的锤子”方法似乎可以做到这一点。

我是否错过了更明显的东西? 鉴于我的金属经验很低——这是很有可能的。任何提示/问题或想法都值得赞赏。

---- 原帖

当我在非主线程上运行 render 方法时,我看到轻拂(请参阅 renderLayerToMTLTexture)。如果我在主线程上运行,它会更好,但仍然有闪烁,只是少得多。

enter image description here

有关视频,请参阅:https://www.dropbox.com/s/7uhg7nyjde3h1rt/Purple%20flicker%20of%20doom.mov?dl=0

  • 注意:背景被清除为粉红色,使其更加明显。

生成纹理的代码:https://gist.github.com/scornflake/0f42841e377e99440910c43f7424f0a5

上下文是正在生成事件(限制为 60fps),请求渲染给定层(该层是其他层的组合)的纹理。这些纹理要么绘制到屏幕上(在预览模式下),要么发送给视频编写者(以创建电影)。

轻拂既发生在银幕上,也发生在电影中。因此,到目前为止,我假设闪烁不是我的纹理预览 MTKView 的结果,而是我的 CALayerToMetalRenderer 中的问题。

到目前为止,我尝试过:

  • 双倍/三重缓冲(有一点帮助,但即使使用三重缓冲器仍然存在问题)
  • 在纹理资源上执行 blit 同步(似乎根本没有效果)
  • 使用 .private 的 storageMode 创建纹理 - 因为我永远不需要 CPU 可用的纹理。在这样做时,我必须删除同步 blit,我猜是因为纹理现在不再可供 CPU 访问,因此同步无效。
  • 将我自己的 MTLCommandQueue 队列传递给 CARenderer(构造函数中的选项),以查看在纹理资源上手动同步是否有效。没有变化。

我不是金属专家,但在我看来,初始渲染命令(清除纹理)正在工作,但 CARenderer .render 有时不/有时可能迟到?(是的,在这里抓一根稻草)

有什么想法吗?

斯威夫特 ·卡莱尔 金属

评论

0赞 Jeshua Lacock 10/21/2023
您是否查看了捕获的金属帧以验证它是否及时渲染?它还有漂亮的图表来显示时间。您是否尝试过较低的帧速率,例如 30 帧?
0赞 scornflake 10/23/2023
@JeshuaLacock - 不,我没有抓住他们。我试过了,但是 xcode 在捕获后崩溃了,所以我无法查看它(在英特尔和我的 m1 上)
0赞 scornflake 10/23/2023
@JeshuaLacock - 实际上。是的。设法做到了——除了我对金属的了解不够,无法理解它。框架看起来不错。我还没有捕捉到纯粉色(我的调试颜色)帧。我可以看到这些阶段在做我认为他们应该做的事情。
0赞 Jeshua Lacock 10/23/2023
如果您发布一些屏幕截图,很高兴查看。
0赞 scornflake 10/24/2023
好的 - 我拍了一些照片+视频。1. 我取样 2.[遍历] 示例(dropbox.com/scl/fi/w7dcck28izhl1caacj761/...)3.如果你愿意,我可以发送屏幕截图,但至少对于视频,你希望看到更多。

答: 暂无答案