CIImage显示MTKView与GLKView的性能

Dee*_*rma 5 opengl-es core-image ios glkview metalkit

我有一系列UIImage(由来自服务器的传入jpeg数据制成),希望使用MTKView进行渲染。问题是,与GLKView相比,它太慢了。当我要在MTKView中显示一系列图像但在GLKView中没有延迟时,会有很多缓冲和延迟。

这是MTKView显示代码:

 private lazy var context: CIContext = {
    return CIContext(mtlDevice: self.device!, options: [CIContextOption.workingColorSpace : NSNull()])
}()

 var ciImg: CIImage? {
    didSet {
        syncQueue.sync {
            internalCoreImage = ciImg
        }
    }
}

 func displayCoreImage(_ ciImage: CIImage) {
    self.ciImg = ciImage
}

  override func draw(_ rect: CGRect) {
     var ciImage: CIImage?

    syncQueue.sync {
        ciImage = internalCoreImage
    }

    drawCIImage(ciImg)

}

 func drawCIImage(_ ciImage:CIImage?) {
    guard let image = ciImage,
        let currentDrawable = currentDrawable,
        let commandBuffer = commandQueue?.makeCommandBuffer()
        else {
            return
    }
    let currentTexture = currentDrawable.texture
    let drawingBounds = CGRect(origin: .zero, size: drawableSize)

    let scaleX = drawableSize.width / image.extent.width
    let scaleY = drawableSize.height / image.extent.height
    let scaledImage = image.transformed(by: CGAffineTransform(scaleX: scaleX, y: scaleY))

    context.render(scaledImage, to: currentTexture, commandBuffer: commandBuffer, bounds: drawingBounds, colorSpace: CGColorSpaceCreateDeviceRGB())


    commandBuffer.present(currentDrawable)
    commandBuffer.commit()
}
Run Code Online (Sandbox Code Playgroud)

这里是GLKView的代码,它是无延迟且快速的:

private var videoPreviewView:GLKView!
private var eaglContext:EAGLContext!
private var context:CIContext!

override init(frame: CGRect) {
    super.init(frame: frame)
    initCommon()
}

required init?(coder: NSCoder) {
    super.init(coder: coder)
    initCommon()
}

func initCommon() {
    eaglContext = EAGLContext(api: .openGLES3)!
    videoPreviewView = GLKView(frame: self.bounds, context: eaglContext)
    context = CIContext(eaglContext: eaglContext, options: nil)

    self.addSubview(videoPreviewView)

    videoPreviewView.bindDrawable()
    videoPreviewView.clipsToBounds = true
    videoPreviewView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
}

 func displayCoreImage(_ ciImage: CIImage) {
    let sourceExtent = ciImage.extent

    let sourceAspect = sourceExtent.size.width / sourceExtent.size.height

    let videoPreviewWidth = CGFloat(videoPreviewView.drawableWidth)
    let videoPreviewHeight = CGFloat(videoPreviewView.drawableHeight)

    let previewAspect = videoPreviewWidth/videoPreviewHeight

       // we want to maintain the aspect radio of the screen size, so we clip the video image
    var drawRect = sourceExtent

    if sourceAspect > previewAspect
    {
        // use full height of the video image, and center crop the width
        drawRect.origin.x = drawRect.origin.x + (drawRect.size.width - drawRect.size.height * previewAspect) / 2.0
        drawRect.size.width = drawRect.size.height * previewAspect
    }
    else
    {
        // use full width of the video image, and center crop the height
        drawRect.origin.y = drawRect.origin.y + (drawRect.size.height - drawRect.size.width / previewAspect) / 2.0
        drawRect.size.height = drawRect.size.width / previewAspect
    }

    var videoRect = CGRect(x: 0, y: 0, width: videoPreviewWidth, height: videoPreviewHeight)

    if sourceAspect < previewAspect
       {
           // use full height of the video image, and center crop the width
           videoRect.origin.x += (videoRect.size.width - videoRect.size.height * sourceAspect) / 2.0;
           videoRect.size.width = videoRect.size.height * sourceAspect;
       }
       else
       {
           // use full width of the video image, and center crop the height
           videoRect.origin.y += (videoRect.size.height - videoRect.size.width / sourceAspect) / 2.0;
           videoRect.size.height = videoRect.size.width / sourceAspect;
       }

    videoPreviewView.bindDrawable()

    if eaglContext != EAGLContext.current() {
        EAGLContext.setCurrent(eaglContext)
    }

    // clear eagl view to black
    glClearColor(0, 0, 0, 1)
    glClear(GLbitfield(GL_COLOR_BUFFER_BIT))

    glEnable(GLenum(GL_BLEND))
    glBlendFunc(GLenum(GL_ONE), GLenum(GL_ONE_MINUS_SRC_ALPHA))

    context.draw(ciImage, in: videoRect, from: sourceExtent)
    videoPreviewView.display()
}
Run Code Online (Sandbox Code Playgroud)

我真的很想找出Metal代码的瓶颈。Metal是否无法每秒显示640x360 UIImage 20次?

编辑:将MTKView的colorPixelFormat设置为rgba16Float可解决延迟问题,但再现的颜色不准确。因此似乎核心图像出现色彩空间转换问题。但是GLKView如何呈现这么快的延迟而不是MTKView?

EDIT2:将MTKView的colorPixelFormat设置为bgra_xr10大部分可以解决延迟问题。但是问题是我们不能将CIRenderDestination API与这种像素颜色格式一起使用。

仍然想知道GLKView / CIContext如何如此迅速地渲染图像而没有任何延迟,但是在MTKView中,我们需要将colorPixelFormat设置为bgra_xr10以提高性能。在iPad Mini 2上设置bgra_xr10会导致崩溃:

  -[MTLRenderPipelineDescriptorInternal validateWithDevice:], line 2590: error 'pixelFormat, for color render target(0), is not a valid MTLPixelFormat.
Run Code Online (Sandbox Code Playgroud)