CGBitmapContext slow to draw with wide color

For a drawing app I'm making I create bitmap contexts that contain the old strokes of the drawing. I draw those bitmaps to the screen in UIView's drawRect. Then only the most recent strokes need to be rendered on top of those bitmaps.


This strategy is pretty fast on most devices. The drawing scrollable and I'm able to scroll at 55fps on an iPhone 6. However, on devices with Wide Color it is pretty slow - about 15fps.


I believe the slowness is because the pixel format of the wide color bitmap context doesn't match the screen. I used the time profiler to see where the bottleneck is. On non-wide-color devices I'm able to get good performance because the drawImage call mostly uses _platform_memmove. On the wide color devices the drawImage call mostly uses RGBAf16_sample_RGBAf_inner.


In an earlier version of my code it was slow on all devices because the location I was drawing the image wasn't pixel aligned. Instead of using _platform_memmove it used argb32_sample_argb32. Getting everything pixel-aligned sped things up tons.


How can I create the bitmap context to be fast on wide color devices? How can I get the call to context.draw(image:in:) to use the fast-path memmove?


This is how I create the bitmap contexts:


func createBitmap(width: Int, height: Int, templateContext: CGContext) -> CGContext {
    return CGContext(data: nil,
                     width: width,
                     height: height,
                     bitsPerComponent: templateContext.bitsPerComponent, // This is 16 on wide color devices
                     bytesPerRow: 0,
                     space: templateContext.colorSpace!,
                     bitmapInfo: templateContext.bitmapInfo.rawValue)! // This is 4353 on wide color devices
}

override func draw(_ rect: CGRect) {
    guard let context = UIGraphicsGetCurrentContext() else {
        return
    }
    .....
    if (bitmapNeedsCreating) {
       bitmap = createBitmap(width: width, height: height, templateContext: context)
    }
    // Update the bitmap if needed
    ....
    if let image = bitmap.context.makeImage() {
        context.setBlendMode(.copy)
        context.interpolationQuality = .none // No interpolation should be necessary because the image is the same size
        context.draw(image, in: CGRect(x: 0, y: bitmapTopInSelf, width: self.bounds.width, height: layerHeightInSelf))
   }
    // Draw the most recent strokes on top
   ...
}


Thank you!

Bridger Maxwell

Replies

I wasn't able to solve this particular issue, but it turns out in my application I don't need Wide Color so I was able to workaround this issue by disabling Wide Color in my view.


However, the API for disabling wide color is bizarre.


self.layer.contentsFormat = kCAContenstFormatRGBA8UInt // This doesn't work to disable Wide Color


The way that I was able to accidentally disable wide color is by making an empty implementation of layerWillDraw. Weird!

override func layerWillDraw(_ layer: CALayer) {
// You don't have to do anything in this function. Just having it in the view will disable Wide Color. To be
// safe for future iOS versions you probably want to set the contentsFormat in here anyway.
}


I filed a Radar on that API issue: http://www.openradar.me/radar?id=5002200048730112


I'd still be interested to know how I could have got CGBitmapContext working quickly on a Wide Color device, though. Just curious.