如何将 Metal Performance Shader 与 MTLBlitCommandEncoder 同步?

Posted

技术标签:

【中文标题】如何将 Metal Performance Shader 与 MTLBlitCommandEncoder 同步?【英文标题】:How do you synchronize a Metal Performance Shader with an MTLBlitCommandEncoder? 【发布时间】:2019-01-31 02:51:47 【问题描述】:

我试图更好地理解使用Metal Performance ShadersMTLBlitCommandEncoder 时的同步要求。

我有一个MTLCommandBuffer,设置如下:

使用MTLBlitCommandEncoder 将纹理 A 的区域复制到纹理 B。纹理 A 大于纹理 B。我正在从纹理 A 中提取“平铺”并将其复制到纹理 B。

使用MPSImageBilinearScale 金属性能着色器,纹理 B 作为源纹理,第三个纹理纹理 C 作为目标。此金属性能着色器将缩放并可能将纹理 B 的内容转换为纹理 C。

在金属性能着色器开始尝试缩放纹理 B 之前,如何确保 blit 编码器完全完成将数据从纹理 A 复制到纹理 B?我是否甚至需要担心这一点,或者命令缓冲区的串行特性是否已经为我解决了这个问题?

Metal 有栅栏的概念,使用MTLFence 来同步对资源的访问,但我不认为有金属性能着色器在栅栏上等待。 (而 waitForFence: 出现在编码器上。)

如果我不能使用栅栏并且我确实需要同步,推荐的做法是将 blit 编码器排入队列,然后在将着色器排入队列并再次调用 waitUntilCompleted 之前在命令缓冲区上调用 waitUntilCompleted?例如:

id<MTLCommandBuffer> commandBuffer;

// Enqueue blit encoder to copy Texture A -> Texture B
id<MTLBlitCommandEncoder> blitEncoder = [commandBuffer blitCommandEncoder];
[blitEncoder copyFromTexture:...];
[blitEncoder endEncoding];

// Wait for blit encoder to complete.
[commandBuffer commit];
[commandBuffer waitUntilCompleted];

// Scale Texture B -> Texture C
MPSImageBilinearScale *imageScaleShader = [[MPSImageBilinearScale alloc] initWithDevice:...];  
[imageScaleShader encodeToCommandBuffer:commandBuffer...];

// Wait for scaling shader to complete.
[commandBuffer commit];
[commandBuffer waitUntilCompleted];

我认为我需要将中间副本复制到纹理 B 的原因是因为 MPSImageBilinearScale 似乎会缩放其整个源纹理。 clipOffset 对输出很有用,但不适用于实际的缩放或变换。所以需要将贴图从纹理 A 中提取到与贴图本身大小相同的纹理 B 中。然后缩放和变换将“有意义”。忽略这个脚注,因为我忘记了一些基本的数学原理,并且已经弄清楚如何使缩放变换的平移属性与 clipRect 一起使用。 p>

【问题讨论】:

【参考方案1】:

Metal 会为您解决这个问题。驱动程序和 GPU 在命令缓冲区中执行命令,就像以串行方式一样。 (“好像”允许并行运行或乱序运行以提高效率,但前提是结果与串行运行时相同。)

当 CPU 和 GPU 处理相同的对象时,会出现同步问题。还可以在屏幕上呈现纹理。 (您不应该渲染到屏幕上呈现的纹理。)

有一个section of the Metal Programming Guide 处理着色器对资源的读写访问,这并不完全相同,但应该让你放心:

内存屏障

命令编码器之间

在给定命令编码器中执行的所有资源写入都是可见的 在下一个命令编码器中。渲染和计算都是如此 命令编码器。

在渲染命令编码器中

对于缓冲区,原子写入对后续原子读取可见 跨多个线程。

对于纹理,textureBarrier 方法确保写入 在给定的绘图调用中执行的后续读取对 下一个绘图调用。

在计算命令编码器中

在给定内核函数中执行的所有资源写入都是可见的 在下一个核函数中。

【讨论】:

谢谢肯。我有理由相信顺序 Blit、Compute 和 Render 命令编码器可以安全使用,但我不确定这个结论是否也适用于 Metal Performance Shaders,因为它们不是继承自 MTLCommandEncoder 而是继承自 MPSKernel .我猜MPSUnaryImageKernel 上的encodeToCommandBuffer: 方法属于您上面引用的内存屏障部分之一。 encodeToCommandBuffer: 内部创建了自己的计算命令编码器,因此上述第一点适用于任何后续工作。 在使用基于MPSTemporaryImageMTLHeap 的资源时仍然如此吗?我正在尝试来自MPSTemporaryImage 的 blit 结果,它充当MPSGaussianBlur 的目的地,但运气不好。当我将 MPSTemporaryImage 替换为常规的 MTLTexture 时,它会起作用。 @warrenm @ken thomases MPSTemporaryImages 是基于堆的资源,因此此处提供的答案不起作用。我认为您将需要使用 MTLEvents。可能插入一个虚拟计算命令编码器来添加您需要的栅栏可能会起作用——还没有尝试过。 Apple 的错误报告要求 MPS 中的围栏支持将是及时的。尽早并经常投票!【参考方案2】:

MPS 位于 Metal 之上(大部分情况下)。它不会取代它(大部分)。您可以假设它使用的是您正在使用的常用命令编码器。

有一些领域需要 MTLFences,尤其是在与渲染编码器和 MTLHeaps 互操作时。如果可用,请使用 MPSImage 和缓冲区类型上的同步方法,而不是自己滚动。

【讨论】:

以上是关于如何将 Metal Performance Shader 与 MTLBlitCommandEncoder 同步?的主要内容,如果未能解决你的问题,请参考以下文章

将数据附加到 Metal 中 MTLBuffer 的现有内容

使用 Metal 绘制位图

iOS Metal:启用用户交互

Metal(iOS)中的多重采样/锯齿状边缘

将 Metal 缓冲区传递给 SceneKit 着色器

如何使用 Metal 绘制弧线?