CIImage display MTKView vs GLKView performance
Asked Answered
G

0

8

I have a series of UI Images (made from incoming jpeg Data from server) that I wish to render using MTKView. Problem is it is too slow compared to GLKView. There is lot of buffering and delay when I have a series of images to display in MTKView but no delay in GLKView.

Here is MTKView display code:

 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()
}

And here is code for GLKView which is lag free and fast:

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()
}

I really want to find out where is bottleneck in Metal code. Is Metal not capable of displaying 640x360 UIImages 20 times per second?

EDIT: Setting colorPixelFormat of MTKView to rgba16Float solves the delay issue, but the reproduced colors are not accurate. So seems like colorspace conversion issue with core image. But how does GLKView renders so fast delay but not MTKView?

EDIT2: Setting colorPixelFormat of MTKView to bgra_xr10 mostly solves the delay issue. But the problem is we can not use CIRenderDestination API with this pixel color format.

Still wondering how GLKView/CIContext render the images so quickly without any delay but in MTKView we need to set colorPixelFormat to bgra_xr10 for increasing performance. And settings bgra_xr10 on iPad Mini 2 causes a crash:

  -[MTLRenderPipelineDescriptorInternal validateWithDevice:], line 2590: error 'pixelFormat, for color render target(0), is not a valid MTLPixelFormat.
Ga answered 7/9, 2019 at 13:42 Comment(23)
Two thoughts. (1) Why is your CIContext declared as lazy? I' not saying that's wrong, just that it may result in extra contexts being created - and that is wrong. (2) Have you looked into CIRenderDestination? #54774033Unsuitable
Little better but the delay still exists. I have no idea what glkView is doing better. Rendering to context is the problem.Ga
If so, then I still wonder if it's how your declaring the context. Particularly as lazy. I'm not seeing that in your GLKView code....Unsuitable
I removed lazy, no difference. I ported glkView code to Swift too. No delay at all! Will update code for GLKView() in Swift.Ga
Maybe you can check out this question/answer: #55770112Premillenarian
@FrankSchlegel I did try that answer as well, but surprisingly nothing works. There is hell lot of delay in rendering images, which means lot of buffering. But GLKView is buffer free, delay free. What is it that makes context.drawImage in GLKView so fast , but context.render(in texture) in Metal so slow?Ga
The code in Metal has scaling btw, but no scaling is their in GLKView if that makes a difference. I tried removing scaling, but delay is still there.Ga
It's a bit hard to tell without more context. But have benchmarked the different parts of the draw method for their timing behavior? From just looking at it I would guess that syncQueue.sync inside the draw would cause a huge performance hit. Have you tried without it for benchmarking?Premillenarian
I need to try benchmarking. But I got to know one thing. Setting colorPixelFormat = .rgba16Float removes delay. .bgra8Unorm is the problem.Ga
I'm also using a queue for syncing, but I gave mine a very high priority: let syncQueue = DispatchQueue(label: "Display Sync Queue", qos: .userInitiated, attributes: [], autoreleaseFrequency: .workItem).Premillenarian
To be clear, these ciImages are made from jpegdata. ColorPixel format is playing a role, why?Ga
Are you testing on a device with a wide gamut (P3) screen? In this case backing the view with a .rgba16Float buffer is probably avoiding color sync operations that CI would perform otherwise.Premillenarian
I am testing on iPhone 6s. I think only iPhone 7 & above support P3.Ga
These images are coming from iPhone XR which supports P3 as jpeg data. iPhone 6s is rendering that incoming jpeg data. GLKView no issues, MTKView with bgra8Unorm is the problem, has lot of delay in processing.Ga
You are right @FrankSchlegel, RGBA16 somehow avoids colorsync function and so the reproduced colors are not original. But the color space on iPhone XR which is transmitting jpeg is sRGB as far as I see. How is GLKView avoiding this issue and reproducing faithful colors?Ga
Have you tried .bgra8Unorm_srgb and .bgra10_xr? Also, have you tried changing the outputColorSpace of the context? There might be some linear-to-non-linear conversion happening. But honestly, any kind of color sync operation shouldn't cause significant lag...Premillenarian
I tried bgra8Unorm_srgb, no effect. Will try bgra10_xr.Ga
bgra10_xr does wonders, performance is so better than GLKView!!!! How????Ga
Would you please compare to bgra10_xr_srgb for me? My guess is that .bgra10_xr is as close (or equal) to the format that is actually used by the framebuffer of the display so that no conversion is necessary.Premillenarian
Performance wise, bgra10_xr_srgb is little bit slow but colors are not close.Ga
Oh no, sometimes delay comes with bgra10_xr, although much less than bgra8Unorm. Hmm!Ga
bgra10_xr causes crash on devices not supporting 10 bit, such as iPad Mini 2.Ga
@FrankRupprecht Are you aware of this Core Image issue involving stitchable kernels in XCode 16?Ga

© 2022 - 2024 — McMap. All rights reserved.