According to this answer by ldoogy, setting the drawsAsynchronously property of CALayer to true enables a Metal based renderer vastly improving performance.
This webpage affirms Idoogy's claim.
However, I do not see any performance difference when drawsAsynchronously is set to true or false.
let layer = CALayer()
let drawsAsynchronously = true //Makes no difference set to true or false
shapeLayer.drawsAsynchronously = drawsAsynchronously
let f = CGRect(x: 0.0, y: 0.0, width: 1024.0, height: 1024.0)
let cgColor = UIColor.orange.cgColor
var lines: [CGPath] // populated with several hundred paths, some with hundreds of points
let start = CFAbsoluteTimeGetCurrent()
for path in lines {
let pathLayer = CAShapeLayer()
pathLayer.path = path
pathLayer.strokeColor = cgColor
pathLayer.fillColor = nil
pathLayer.lineWidth = 1.0
pathLayer.drawsAsynchronously = drawsAsynchronously
layer.addSublayer(pathLayer)
}
UIGraphicsBeginImageContext(f.size)
let ctx = UIGraphicsGetCurrentContext()
layer.render(in: ctx!)
let newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext()
//time: 0.05170309543609619
print("time: \(CFAbsoluteTimeGetCurrent() - start)")
Idoogy specifies renderInContext which is what I am using above. render(in:) has replaced renderInContext with modern Swift.
Surprisingly, UIGraphicsImageRenderer is much slower (nearly 300%), but also makes no difference if drawsAsynchronously is set to true or false:
let renderer = UIGraphicsImageRenderer(size: f.size)
let capturedImage = renderer.image { (ctx) in
return layer.render(in: ctx.cgContext)
}
// time: 0.13654804229736328
print("time: \(CFAbsoluteTimeGetCurrent() - start)")
Is there something I've missed to enable hardware accelerated rendering with drawsAsynchronously enabled?
EDIT:
I tried using drawRect method too, since Idoogy mentions ContextStrokePath, but it was the slowest yet, and made no difference if drawsAsynchronously is enabled or not.
class LineView: UIView {
var lines: [CGPath] //populated with several hundred paths, some with hundreds of points
override func draw(_ rect: CGRect) {
let color = UIColor.orange.cgColor
if let context = UIGraphicsGetCurrentContext() {
for path in lines {
context.saveGState()
context.addPath(path)
context.setStrokeColor(color)
context.setLineWidth(1.0)
context.strokePath()
context.restoreGState()
}
}
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let drawsAsynchronously = true //Makes no difference set to true or false
view.layer.drawsAsynchronously = drawsAsynchronously
let f = CGRect(x: 0.0, y: 0.0, width: 1024.0, height: 1024.0)
let lineView = LineView(frame: f)
lineView.layer.drawsAsynchronously = drawsAsynchronously
view.addSubview(lineView)
let start = CFAbsoluteTimeGetCurrent()
UIGraphicsBeginImageContext(f.size)
let ctx = UIGraphicsGetCurrentContext()
lineView.layer.render(in: ctx!)
let newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext()
//time: 0.14092397689819336
print("time: \(CFAbsoluteTimeGetCurrent() - start)")
}
Since it is so much slower than using CAShapeLayer (3x slower), I suspect that maybe using CAShapeLayer is using the GPU for rendering to image. I'd still like to get the method described by Idoogy working as none of the 3 methods I've tried show any difference using it.
Several things to talk about...
First, the speed difference you observed between
UIGraphicsGetImageFromCurrentImageContextandUIGraphicsImageRendereris due to the fact that you are rendering different size images.Assuming you are on a
@3xdevice,UIGraphicsImageRendereris rendering a 1024 x 1024UIImage, but its.scaleis 3, so it's actually a 3072 x 3072 pixel image.To get equivalent images, change that code block to this:
now
UIGraphicsImageRendererwill produce the same 1024 x 1024 pixel image asUIGraphicsGetImageFromCurrentImageContext.Next, you're timing blocks of code which are not directly related to
layer.drawsAsynchronously-- creating and adding sublayers, generating objects, etc.Apple's docs on this are not what I would call "in-depth" -- but Improving Animation Performance we find:
Important to note -- the docs are talking (mainly) about animation performance... not "single-rendering" tasks.
From some testing...
.drawsAsynchronously = true.drawsAsynchronously = falseSo, let's look at some actual example code that will demonstrate the difference. Too much to try and detail here, but the in-line comments should make it clear what's going on:
custom
CALayersubclass:"Swift Bird" path:
Looks like this if
inRectis (roughly) 200x200:Test View Controller class:
When running, we don't see anything on the screen (just yellow background so we know the app is "live").
It starts a timer, rendering a 1024x1024 image every second, alternating between
.drawsAsynchronouslytrue/false, and prints timing stats to the debug console.Tapping anywhere toggles between rendering
manySimplePathsorfewComplexPaths-- both produce the exact same output image.You should see something similar to this in the debug console:
The rendered 1024x1024 image should look like this: