【问题标题】:Metal write to buffer from vertex function从顶点函数金属写入缓冲区
【发布时间】:2016-06-15 11:50:33
【问题描述】:

我正在构建一个在 Metal 中渲染 2D 几何图形的应用程序。

现在,顶点的位置是从顶点函数中解决的。我想要的是从同一个顶点函数内部将解决的位置写回缓冲区。

我的印象是这是possible,尽管这是我第一次尝试这样做,即:

vertex VertexOut basic_vertex(device VertexIn *vertices [[ buffer(0) ]],
                              device VertexOut *solvedVertices [[ buffer(1) ]],
                              vid [[ vertex_id ]])
{
    VertexIn in vertices[vid];
    VertexOut out;
    out.position = ... // Solve the position of the vertex 

    solvedVertices[vid] = out // Write to the buffer later to be read by CPU

    return out;
}

我很高兴看到这个编译时错误:

好的,所以我想到了一些解决方案 - 我可以首先解决顶点位置 - 非光栅化 - 通过声明如下的顶点函数:

vertex void solve_vertex(device VertexIn *unsolved [[ buffer(0) ]],
                         device VertexOut *solved [[ buffer(1) ]],
                         vid [[ vertex_id ]])
{
    solved[vid] = ... 
}

然后将这些已解决的顶点通过管道传输到现在更简单的 - 光栅化 - 顶点函数。

另一种可行但似乎不太吸引人的解决方案是在计算函数中解决它们。

那么,在这种情况下,最好的方法是什么?从我的一点点研究中,我可以追踪到在Transform Feedback 中完成了同样的程序,但我没有运气(除了问题乞求时的link)在Apple的文档中找到示例/示例代码或网络上的其他地方,以便在遇到此类问题时获得最佳实践。

【问题讨论】:

  • 你可以写入constant缓冲区而不是device
  • @Marius constant 地址空间是只读的。看这里 - Functions, Variables, and Qualifiers
  • Metal 并没有通常理解的变换反馈;您发现的(使用带有写入缓冲区的顶点函数的非光栅化管道)是最接近的。计算版本基本上可以完成相同的工作,并且非常容易编写;我会尝试两者,看看哪个能给你更好的性能。我没有将此作为答案发布,因为(a)我没有经验证据支持这两种方法,并且(b)我希望您在调查后自行回答:)
  • 好的,再次感谢@warrenm 的反馈。正如您所建议的,一旦我调查了不同的解决方案,我会自行回答。
  • @jameslintaylor 您是否发现使用计算管道比渲染管道更快/更慢?

标签: metal


【解决方案1】:

好的,事实证明使用非光栅化顶点函数是可行的方法。但是有一些事情需要注意,以供其他人将来参考:

非光栅化顶点函数只是一个返回 void 的顶点函数,即:

vertex void non_rasterizing_vertex(...) { }

在执行非光栅化“渲染”过程时,MTLRenderPassDescriptor 仍然需要有一个纹理集 - 例如在 MTLRenderPassDescriptorcolorAttachments[0].texture 中 - 原因我不知道(我假设它只是由于 GPU 编程的固定性质)。

MTLRenderPipelineState 需要将其rasterizationEnabled 属性设置为false,然后您可以将非光栅化顶点函数分配给它的vertexFunction 属性。 fragmentFunction 属性可以按预期保持为零。

在实际执行pass时,仍然需要在配置的MTLRenderCommandEncoder上调用drawPrimitives:方法之一(其命名可能为misleading)。我最终接到了渲染MTLPrimitiveType.Points 的电话,因为这似乎是最合理的。

执行所有这些设置“渲染”逻辑准备好从顶点函数写回顶点缓冲区 - 只要它们位于 device 地址空间中:

vertex void non_rasterizing_vertex(device float *writeableBuffer [[ buffer(0) ]],
                                   uint vid [[ vertex_id ]])
{
    writeableBuffer[vid] = 42; // Write away!
}

这个“答案”最终更像是一篇博文,但我希望它对未来的参考仍然有用。

待办事项

我仍然想研究在计算管道中进行此类计算工作与在上述渲染管道中进行此类计算之间的性能权衡。一旦我有更多时间这样做,我会更新这个答案。

【讨论】:

    【解决方案2】:

    正确的解决方案是将任何写入缓冲区的代码移至计算内核。

    您将失去大量性能写入顶点函数中的缓冲区。它针对光栅化进行了优化,而不是针对计算。

    您只需要使用计算命令编码器。

    guard let computeBuffer = commandQueue.makeCommandBuffer() else { return }
    guard let computeEncoder = computeBuffer.makeComputeCommandEncoder() else { return }
    computeEncoder.setComputePipelineState(solveVertexPipelineState)
    
    
    kernel void solve_vertex(device VertexIn *unsolved [[ buffer(0) ]],
                         device VertexOut *solved [[ buffer(1) ]],
                         vid [[ instance ]])
    {
        solved[vid] = ...
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-03-21
      • 2023-03-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多