使用 Metal 渲染 ARMeshGeometry 时出现设备加载错误

Device Load errors when rendering an ARMeshGeometry using Metal

我在 scene reconstruction and need to rendered the captured scene geometry in metal. I can access this geometry through the ARMeshAnchor.geometry, which is a ARMeshGeometry 中使用 ARKit。然而,当我尝试使用我的自定义金属渲染管道渲染它时,没有任何渲染,我得到了一堆这样的错误:

Invalid device load executing vertex function "myVertex" encoder: "0", draw: 3, at offset 4688

这是我用于调试的代码的高度简化版本:

struct InOut {
    float4 position [[position]];
};

vertex InOut myVertex(
    uint vid [[vertex_id]],
    const constant float3* vertexArray [[buffer(0)]])
{
    TouchInOut out;

    const float3 in = vertexArray[vid];
    out.position = float4(in.position, 1);
}

fragment float4 myFragment(TouchInOut in [[stage_in]]){
    return float4(1, 0, 0, 1);
}
// Setup MTLRenderPipelineDescriptor
let pipelineDescriptor = MTLRenderPipelineDescriptor()
pipelineDescriptor.colorAttachments[0].pixelFormat = .rgba8Unorm
pipelineDescriptor.sampleCount = 1

pipelineDescriptor.vertexFunction = defaultLibrary.makeFunction(name: "myVertex")
pipelineDescriptor.fragmentFunction = defaultLibrary.makeFunction(name: "myFragment")

let vertexDescriptor = MTLVertexDescriptor()
vertexDescriptor.attributes[0].format = .float3
vertexDescriptor.attributes[0].offset = 0
vertexDescriptor.attributes[0].bufferIndex = 0
        
vertexDescriptor.layouts[0].stride = MemoryLayout<SIMD3<Float>>.stride

pipelineDescriptor.vertexDescriptor = vertexDescriptor
func render(arMesh: ARMeshAnchor) -> void {

   // snip... — Setting up command buffers

    let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor)!
    renderEncoder.setViewport(MTLViewport(originX: 0, originY: 0, width: 512, height: 512, znear: 0, zfar: 1))
 
        
    renderEncoder.setRenderPipelineState(renderPipelineState)
        
    let vertices = arMesh.geometry.vertices
    let faces = arMesh.geometry.faces
            
    renderEncoder.setVertexBuffer(vertices.buffer, offset: 0, index: 0)
            
    renderEncoder.drawIndexedPrimitives(type: .triangle, indexCount: faces.count * 3, indexType: .uint32, indexBuffer: buffer, indexBufferOffset: 0)
    
    renderEncoder.endEncoding()

   // snip... — Clean up
}

我不明白为什么这段代码会导致金属异常。如果我将着色器中的 vid 限制在 100 左右,它就会停止抛出,但它仍然无法正确绘制任何东西

这是怎么回事?为什么我的代码会产生错误,我该如何解决?

这里的问题是顶点数据的alignment/packing。

ARMeshGeometry.vertices中的每个顶点由 3 个 float 组件组成,总大小为 12 字节。上面的代码假定这意味着数据是 float3 / SIMD3<Float>,但是 ARMeshGeometry 中的 vertices 实际上是紧密打包的。因此,虽然 SIMD3<Float> 的步幅为 16,但实际顶点数据的步幅为 12.

float3 (16) 的较大大小与 vertices 缓冲区 (12) 中元素的实际大小相比,导致金属试图访问 [=15= 末尾的数据] 缓冲区,产生错误。

这里有两个重要的修复:

  1. 确保 MTLVertexDescriptor 的步幅正确:
let exampleMeshGeometry: ARMeshGeometry = ...

vertexDescriptor.layouts[0].stride = exampleMeshGeometry.vertices.stride
  1. 在着色器中,使用 packed_float3 而不是 float3
vertex InOut myVertex(
    uint vid [[vertex_id]],
    const constant packed_float3* vertexArray [[buffer(0)]])
{
    ...
}

解决这些问题后,您应该能够将 ARMeshGeometry 缓冲区正确传输到金属着色器