【问题标题】:Multiple renders to single texture without blocking MTLCommandBuffer在不阻塞 MTLCommandBuffer 的情况下多次渲染到单个纹理
【发布时间】:2021-11-03 08:32:14
【问题描述】:

我正在尝试将 3 个单独的东西渲染到 Metal 中的一个纹理。 我有一个 MTLTexture 用作 3 个不同 MTLCommandBuffers 中的目的地。我一个接一个地提交它们。每个 MTLCommandBuffer 渲染到纹理的一个单独部分 - 首先绘制 0 - 1/3 部分,第二个绘制中间 1/3 - 2/3,最后一个绘制 2/3 - 1。

id<MTLTexture> dst_texture = ...;
id<MTLCommandBuffer> buffer1 = [self drawToTexture:dst_texture];
[buffer1 commit];
id<MTLCommandBuffer> buffer2 = [self drawToTexture:dst_texture];
[buffer2 commit];
id<MTLCommandBuffer> buffer3 = [self drawToTexture:dst_texture];
[buffer3 commit];

问题在于,我似乎无法在不同的命令缓冲区中共享目标纹理 - 我出现故障,有时我只能看到目标纹理的部分结果... 在drawToTexture 里面我是这样使用 dst_texture 的:

_renderPassDescriptor.colorAttachments[0].texture = dst_texture;
_renderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionLoad;

当我在每次单独提交后调用 [buffer waitUntilCompleted] 时问题得到解决,但我认为它会影响性能,我希望它没有阻塞/等待。

这行得通:

id<MTLTexture> dst_texture = ...;
id<MTLCommandBuffer> buffer1 = [self drawToTexture:dst_texture];
[buffer1 commit];
[buffer1 waitUntilCompleted];
id<MTLCommandBuffer> buffer2 = [self drawToTexture:dst_texture];
[buffer2 commit];
[buffer2 waitUntilCompleted];
id<MTLCommandBuffer> buffer3 = [self drawToTexture:dst_texture];
[buffer3 commit];
[buffer3 waitUntilCompleted];

我还能做些什么来避免waitUntilCompleted 来电?

【问题讨论】:

  • 您是否使用相同的MTLCommandQueue 创建命令缓冲区?
  • @FrankSchlegel - 是的,所有 3 个命令缓冲区都相同
  • 您使用单独的命令缓冲区有什么原因吗?你能不能不使用一个命令缓冲区和 3 个编码器或一个编码器和 3 个绘制调用.. 不知道 drawToTexture 做了什么,但听起来它可能只是一个绘制调用,所以你只需要一个命令缓冲区和一个编码器..
  • @Gary 也许我应该研究一下。我正在做的是使用相同的着色器渲染相同的几何图形,但为每个命令缓冲区使用一组不同的制服。
  • 在这种情况下,您绝对应该使用一个命令缓冲区和一个编码器,因此从 commandBuffer 创建您的 renderPassDescriptor 附加您的纹理,创建您的编码器对第一次绘制进行编码(设置缓冲区进行绘制调用),然后不要调用 endEncoding,重置统一缓冲区然后再次绘制,然后再次绘制 endEncoding。然后一切都会如你所愿,这是多次绘制一个纹理的标准方式。

标签: objective-c macos metal render-to-texture


【解决方案1】:

要回答“我正在尝试在金属的一个纹理中渲染 3 个单独的东西”和“我还可以做些什么来避免 waitUntilCompleted 调用?”的问题。 (Hamid 已经解释了问题发生的原因),是您不应该使用多个命令缓冲区进行具有多个绘制调用的基本渲染。如果要渲染到一个纹理,则需要一个命令缓冲区,用于创建一个将纹理附加到的 renderPassDescriptor。然后,您需要从 renderPassDescriptor 创建一个编码器,您可以在其中编码所有绘制调用和缓冲区状态的更改等。所以正如我在评论中所说,您绑定着色器设置缓冲区等然后绘制,然后不调用 endEncoding 但设置着色器和缓冲区一次又一次地用于您想要多少次绘制调用和缓冲区更改。 如果您想绘制多个纹理,那么您通常会创建多个 renderPassDescriptor(但仍使用一个命令缓冲区)。通常,您每帧使用一个命令缓冲区,或者用于一组屏幕外渲染通道。

【讨论】:

    【解决方案2】:

    只需要手动同步:

    • 对于未跟踪的资源。
    • 跨多个设备。
    • GPU 和 CPU 之间。
    • 单独的命令队列之间。

    否则,metal 会自动在命令缓冲区之间同步(跟踪)资源,即使它们是并行运行的。

    如果命令缓冲区包含对给定MTLTexture 的写入或读取操作,您必须确保这些操作在读取或写入MTLTexture 内容之前完成。您可以使用addCompletedHandler: 方法、waitUntilCompleted 方法或自定义信号量来表示命令缓冲区已完成执行。

    【讨论】:

    • 鉴于他正在尝试做的“我正在尝试将 3 个单独的东西渲染到 Metal 中的一个纹理”,他不应该使用多个命令缓冲区,而是使用一个命令缓冲区和一个 renderPassDescriptor 和一个编码器,使用 waitUntilCompleted 或信号量来确保执行,而可能对他的需求来说是荒谬的。
    • 我同意。我首先澄清了为什么会出现这个问题。
    • 当然,但我认为用户理解工具的使用方式很重要,如果他认为每个绘图调用都需要一个单独的命令缓冲区,这会严重限制他的选择并让他失望在错误的轨道上。
    猜你喜欢
    • 2012-09-15
    • 1970-01-01
    • 2017-04-09
    • 2012-09-25
    • 2021-12-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-26
    相关资源
    最近更新 更多