从顶点函数向缓冲区写入Metal

9

我正在开发一个使用Metal渲染2D几何图形的应用程序。

目前,顶点的位置是在顶点函数内部解决的。我希望能够从同一顶点函数中将解决后的位置写回到缓冲区。

我认为这是可能的,尽管我第一次尝试做到这一点时:

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;
}

我遇到了这个编译时错误:

enter image description here

好的,我脑海中浮现出几个解决方案 - 我可以在第一次 - 非光栅化 - 通过一个顶点函数求解顶点位置,该函数声明如下:

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

然后将这些已解决的顶点输入到一个现在简化了的 - 光栅化 - 顶点函数中。

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

那么,在这种情况下最好的方法是什么?从我的一点研究中,我发现Transform Feedback中做同样程序的过程,但我没有找到苹果文档/示例代码或其他网站上面对这种问题的最佳实践(除了问题开头的链接)。


你可以将数据写入到“常量”缓冲区,而不是设备。 - gpu3d
@Marius constant 地址空间是只读的。请参见此处 - 函数、变量和限定符 - jameslintaylor
1
Metal没有像通常理解的变换反馈;而您发现的(使用非光栅管道和编写到缓冲区的顶点函数)是最接近的事情。计算版本会基本执行相同的工作,而且编写起来非常容易;我建议您尝试两种方式并查看哪种方式能够提供更好的性能。我没有发布这个作为答案,因为(a)我没有实证证据支持任何一种方法,并且(b)我希望您在调查后自己回答 :) - warrenm
好的,再次感谢@warrenm的反馈。根据您的建议,一旦我调查出不同的解决方案,我会自己回答这个问题。 - jameslintaylor
@jameslintaylor,你查明使用计算管线是否比渲染管线更快/更慢了吗? - TJez
2个回答

9

好的,事实证明使用非光栅化顶点函数是正确的方法。但需要注意以下几点,以备他人参考:

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

vertex void non_rasterizing_vertex(...) { }

执行非光栅化的“渲染”通道时,仍然需要为MTLRenderPassDescriptor设置纹理 - 例如在MTLRenderPassDescriptorcolorAttachments [0] .texture中 - 我不知道原因(我认为这只是由于GPU编程的固定性质)。 MTLRenderPipelineState需要将其rasterizationEnabled属性设置为false,然后可以将非光栅化顶点函数分配给其vertexFunction属性。如预期那样,fragmentFunction属性可以保持nil。
实际执行通道时,仍然需要在配置的MTLRenderCommandEncoder上调用drawPrimitives:方法之一(其命名可能会misleading)。我最终呼叫了渲染MTLPrimitiveType.Point,因为这似乎是最合理的。

通过完成所有这些步骤,可以设置“渲染”逻辑,准备从顶点函数写回到顶点缓冲区 - 只要它们位于device地址空间中:

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

这个“答案”更像是一篇博客文章,但我希望它对未来的参考仍然有用。
待办事项:
我仍然想调查在计算管线和渲染管线之间执行此类计算工作的性能权衡。一旦我有更多时间来做这件事,我会更新这个答案。

0

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

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

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

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] = ...
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接