关于CIContext、OpenGL和Metal的混淆(SWIFT)。CIContext默认使用CPU还是GPU?

3

我正在开发一款应用程序,其中一些主要功能是将CIFilters应用于图像。

let context = CIContext()
let context = CIContext(eaglContext: EAGLContext(api: .openGLES3)!)
let context = CIContext(mtlDevice: MTLCreateSystemDefaultDevice()!)

所有这些都会在我的CameraViewController上给我大约相同的CPU使用率(70%),其中我对帧应用滤镜并更新imageview。所有这些似乎以完全相同的方式工作,这使我认为我缺少一些关键信息。
例如,使用AVFoundation,我从相机获取每个帧,将滤镜应用于该帧,并使用新图像更新imageview。
let context = CIContext()

func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
    connection.videoOrientation = orientation
    connection.isVideoMirrored = !cameraModeIsBack
    let videoOutput = AVCaptureVideoDataOutput()
    videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue.main)

    let sharpenFilter = CIFilter(name: "CISharpenLuminance")
    let saturationFilter = CIFilter(name: "CIColorControls")
    let contrastFilter = CIFilter(name: "CIColorControls")
    let pixellateFilter = CIFilter(name: "CIPixellate")

    let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)
    var cameraImage = CIImage(cvImageBuffer: pixelBuffer!)

    saturationFilter?.setValue(cameraImage, forKey: kCIInputImageKey)
    saturationFilter?.setValue(saturationValue, forKey: "inputSaturation")
    var cgImage = context.createCGImage((saturationFilter?.outputImage!)!, from: cameraImage.extent)!
    cameraImage = CIImage(cgImage: cgImage)

    sharpenFilter?.setValue(cameraImage, forKey: kCIInputImageKey)
    sharpenFilter?.setValue(sharpnessValue, forKey: kCIInputSharpnessKey)
    cgImage = context.createCGImage((sharpenFilter?.outputImage!)!, from: (cameraImage.extent))!
    cameraImage = CIImage(cgImage: cgImage)

    contrastFilter?.setValue(cameraImage, forKey: "inputImage")
    contrastFilter?.setValue(contrastValue, forKey: "inputContrast")
    cgImage = context.createCGImage((contrastFilter?.outputImage!)!, from: (cameraImage.extent))!
    cameraImage = CIImage(cgImage: cgImage)

    pixellateFilter?.setValue(cameraImage, forKey: kCIInputImageKey)
    pixellateFilter?.setValue(pixelateValue, forKey: kCIInputScaleKey)
    cgImage = context.createCGImage((pixellateFilter?.outputImage!)!, from: (cameraImage.extent))!
    applyChanges(image: cgImage)

}

另一个例子是我如何仅对普通图像进行更改(我使用滑块完成所有这些操作)。
   func imagePixelate(sliderValue: CGFloat){
    let cgImg = image?.cgImage
    let ciImg = CIImage(cgImage: cgImg!)
    let pixellateFilter = CIFilter(name: "CIPixellate")
    pixellateFilter?.setValue(ciImg, forKey: kCIInputImageKey)
    pixellateFilter?.setValue(sliderValue, forKey: kCIInputScaleKey)
    let outputCIImg = pixellateFilter?.outputImage!
    let outputCGImg = context.createCGImage(outputCIImg!, from: (outputCIImg?.extent)!)
    let outputUIImg = UIImage(cgImage:outputCGImg!, scale:(originalImage?.scale)!, orientation: originalOrientation!)
    imageSource[0] = ImageSource(image: outputUIImg)
    slideshow.setImageInputs(imageSource)
    currentFilteredImage = outputUIImg
}

基本上:

  1. 从UiImg创建CgImg
  2. 从CgImg创建CiImg
  3. 使用上下文将滤镜应用于图像并将其转换回UiImg
  4. 使用新的UiImg更新任何视图

这个过程在我的iPhone X上运行良好,也在iPhone 6上出乎意料地运行良好。因为我的应用程序几乎完成了,所以我想尽可能地对它进行优化。我查看了很多有关使用OpenGL和Metal进行处理的文档,但似乎无法弄清如何开始。

我一直以为我是在CPU上运行这些进程,但使用OpenGL和Metal创建上下文并没有提供任何改进。我需要使用MetalKit视图或GLKit视图吗(eaglContext似乎完全被淘汰了)?我该如何转换?苹果的文档似乎不够好。

2个回答

4
我开始将这个作为评论,但我认为自从WWDC'18以来,这是最好的答案。我会在其他更专业的人发表评论时进行编辑,并愿意删除整个答案,如果这是正确的做法。
你正在正确的轨道上-尽可能利用GPU并且它是一个很好的适配器。CoreImage和Metal虽然是“低级”技术,通常使用GPU,但如果需要,可以使用CPU。CoreGraphics?它使用CPU渲染事物。
图像。UIImage和CGImage是实际的图像。然而,CIImage不是。最好的方法是将其视为图像的“食谱”。

我通常 - 稍后我会解释 - 在处理滤镜时使用 CoreImage、CIFilters、CIImages 和 GLKViews。使用 GLKView 对 CIImage 进行处理意味着使用 OpenGL 和单个 CIContext 和 EAGLContext。它的性能几乎与使用 MetalKit 或 MTKViews 相当。

至于使用 UIKit 和它的 UIImage 和 UIImageView,我只在必要时使用 - 保存/共享/上传等情况。在那之前,请坚持使用 GPU。

....

这里开始变得复杂了。
Metal是苹果专有的API。由于他们拥有硬件 - 包括CPU和GPU - 他们已经为自己进行了优化。它的“流水线”与OpenGL有些不同。没有什么主要问题,只是不同而已。
直到WWDC'18,使用GLKit,包括GLKView,都是可以的。但所有与OpenGL相关的内容都被弃用了,苹果正在将它们转移到Metal上。虽然性能提高(目前)并不是很大,但如果您想做一些新的东西,最好使用MTKView、Metal和CIContext。
请查看@matt在这里给出的答案,以了解使用MTKViews的好方法。

1
谢谢提供这个资源!我成功地实现了我想要做的事情。在我的iPhoneX上表现出色,但在我的iPhone6上运行的mtkView无法使用!通过每秒渲染图像30次来将图像呈现到ImageView中,我获得了更好的用户体验。看起来是iPhone 6中A8芯片的限制。 - Alex Barbulescu
很高兴我能帮到你。至于iPhone 6?一个月后我们就要落后三代了。是的,我真的希望苹果公司能够更加一致...但我想这就是我们付出的代价。 - user7014451

2

一些独立的要点:

  • 分析你的应用程序,找出它花费 CPU 时间的地方。
  • 如果图形工作实际上并不是很难 - 也就是说,如果你的应用程序没有 GPU 瓶颈 - 优化 GPU 工作可能不会帮助整体性能。
  • 尽量避免在 CPU 和 GPU 之间来回移动数据。不要为每个滤镜输出创建 CGImage。Core Image 的一个主要特性是能够链接过滤器而不进行每个渲染,然后一次性渲染所有效果。此外,正如 dfd 在他的答案中所说,如果直接呈现到屏幕而不是创建一个 UIImage 显示在图像视图中,那么会更好。
  • 避免冗余工作。不要每次都重新创建你的 CIFilter 对象。如果参数没有改变,不要每次重新配置它们。

是的,我刚意识到我没有有效地链接或创建过滤器,谢谢!有趣的是,我实现了MetalKitView,在我的iPhone X上,我将CPU负载降低了约35%,但在我的iPhone 6上,它非常糟糕。虽然在mtkView上渲染只使用约17%的CPU,但它非常卡顿和不稳定。从理论上讲,性能更好,但在实践中无法使用。 - Alex Barbulescu
@RahishMontegomery 我的问题恰好相反。我的 iPhone 6s Plus 表现出色,电池消耗低,但我的 iPhone XS 的电池消耗很高,尽管两部手机的相机拍摄和渲染的图像大小相同。 - Chewie The Chorkie

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