使用 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= 末尾的数据] 缓冲区,产生错误。
这里有两个重要的修复:
- 确保
MTLVertexDescriptor
的步幅正确:
let exampleMeshGeometry: ARMeshGeometry = ...
vertexDescriptor.layouts[0].stride = exampleMeshGeometry.vertices.stride
- 在着色器中,使用
packed_float3
而不是 float3
vertex InOut myVertex(
uint vid [[vertex_id]],
const constant packed_float3* vertexArray [[buffer(0)]])
{
...
}
解决这些问题后,您应该能够将 ARMeshGeometry
缓冲区正确传输到金属着色器
我在 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= 末尾的数据] 缓冲区,产生错误。
这里有两个重要的修复:
- 确保
MTLVertexDescriptor
的步幅正确:
let exampleMeshGeometry: ARMeshGeometry = ...
vertexDescriptor.layouts[0].stride = exampleMeshGeometry.vertices.stride
- 在着色器中,使用
packed_float3
而不是float3
vertex InOut myVertex(
uint vid [[vertex_id]],
const constant packed_float3* vertexArray [[buffer(0)]])
{
...
}
解决这些问题后,您应该能够将 ARMeshGeometry
缓冲区正确传输到金属着色器