使用LiDAR相机采集深度的Swift示例代码GPU超时崩溃求助
我在苹果开发者官网下载了《使用LiDAR相机采集深度》的Swift示例项目,在iPhone 14 Pro上运行时,应用会间歇性崩溃,抛出错误:
‘Execution of the command buffer was aborted due to an error during execution. Caused GPU Timeout Error (00000002:kIOGPUCommandBufferCallbackErrorTimeout)’
崩溃时间不定,有时几秒内就崩溃,有时需十几分钟甚至更久。
我尝试了多种方法排查,也通过Feedback Assistant向苹果提交了反馈,但始终无法通过Xcode定位崩溃代码行。经过多小时排查,终于发现错误出在MetalTextureViewColor.swift文件的draw方法中,具体是encoder.endEncoding()这一行,报错信息为:
‘MTLCommandBuffer execution failed. The commands associated with an encoder caused an error’
完整的draw函数代码如下:
override func draw(in view: MTKView) { guard parent.capturedData.colorY != nil && parent.capturedData.colorCbCr != nil else { print("There's no content to display.") return } guard let commandBuffer = metalCommandQueue.makeCommandBuffer() else { return } guard let passDescriptor = view.currentRenderPassDescriptor else { return } guard let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: passDescriptor) else { return } // Vertex and Texture coordinates data (x,y,u,v) * 4 ordered for triangle strip. let vertexData: [Float] = [-1, -1, 1, 1, 1, -1, 1, 0, -1, 1, 0, 1, 1, 1, 0, 0] encoder.setVertexBytes(vertexData, length: vertexData.count * MemoryLayout<Float>.stride, index: 0) encoder.setFragmentTexture(parent.capturedData.colorY!, index: 0) encoder.setFragmentTexture(parent.capturedData.colorCbCr!, index: 1) encoder.setDepthStencilState(depthState) encoder.setRenderPipelineState(pipelineState) encoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4) // error happening on the line below encoder.endEncoding() commandBuffer.present(view.currentDrawable!) commandBuffer.commit() //commandBuffer.waitUntilCompleted() }
请问是否有办法捕获该错误并忽略,让应用继续运行?
1. 捕获Command Buffer执行错误
Metal的MTLCommandBuffer支持添加完成回调,可在回调中捕获执行阶段的错误,避免应用崩溃。同时对encoder.endEncoding()做容错处理:
override func draw(in view: MTKView) { guard parent.capturedData.colorY != nil && parent.capturedData.colorCbCr != nil else { print("There's no content to display.") return } guard let commandBuffer = metalCommandQueue.makeCommandBuffer() else { return } // 添加命令缓冲区完成回调,捕获执行错误 commandBuffer.addCompletedHandler { buffer in if let error = buffer.error { print("Command Buffer执行错误: \(error.localizedDescription)") // 可在此添加错误统计、重试逻辑,或直接忽略 return } } guard let passDescriptor = view.currentRenderPassDescriptor else { return } guard let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: passDescriptor) else { return } let vertexData: [Float] = [-1, -1, 1, 1, 1, -1, 1, 0, -1, 1, 0, 1, 1, 1, 0, 0] encoder.setVertexBytes(vertexData, length: vertexData.count * MemoryLayout<Float>.stride, index: 0) encoder.setFragmentTexture(parent.capturedData.colorY!, index: 0) encoder.setFragmentTexture(parent.capturedData.colorCbCr!, index: 1) encoder.setDepthStencilState(depthState) encoder.setRenderPipelineState(pipelineState) encoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4) // 尝试安全结束编码 do { try encoder.endEncoding() } catch { print("Encoder结束编码错误: \(error.localizedDescription)") // 即使出错也尝试结束编码,避免资源泄漏 encoder.endEncoding() return } commandBuffer.present(view.currentDrawable!) commandBuffer.commit() }
2. 根源优化:减少GPU超时概率
虽然捕获错误能避免崩溃,但从根源优化可彻底解决问题:
- 确保
colorY和colorCbCr纹理在渲染周期内未被释放或异步修改,可添加纹理引用计数检查 - 将
vertexData改为类级静态常量,避免每次draw都重新创建数组 - 关闭Xcode的Metal Validation等调试选项,这些会增加GPU负载
- 确保
pipelineState在初始化时预创建,不要在draw方法内重复初始化
3. 全局容错兜底(不推荐)
如果想做最极端的容错,可将draw逻辑封装到单独方法并添加全局异常捕获,但可能导致未知渲染状态:
override func draw(in view: MTKView) { do { try performRender(in: view) } catch { print("渲染过程出错: \(error)") } } private func performRender(in view: MTKView) throws { // 原draw方法的完整逻辑 guard parent.capturedData.colorY != nil && parent.capturedData.colorCbCr != nil else { print("There's no content to display.") return } guard let commandBuffer = metalCommandQueue.makeCommandBuffer() else { return } guard let passDescriptor = view.currentRenderPassDescriptor else { return } guard let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: passDescriptor) else { return } let vertexData: [Float] = [-1, -1, 1, 1, 1, -1, 1, 0, -1, 1, 0, 1, 1, 1, 0, 0] encoder.setVertexBytes(vertexData, length: vertexData.count * MemoryLayout<Float>.stride, index: 0) encoder.setFragmentTexture(parent.capturedData.colorY!, index: 0) encoder.setFragmentTexture(parent.capturedData.colorCbCr!, index: 1) encoder.setDepthStencilState(depthState) encoder.setRenderPipelineState(pipelineState) encoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4) encoder.endEncoding() commandBuffer.present(view.currentDrawable!) commandBuffer.commit() }
内容的提问来源于stack exchange,提问作者N D

