I am trying to draw a few hundred static paths to an image as fast as possible. So far the fastest solution I've come up with is using CAShapeLayers
:
let layer = CALayer()
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)")
Drawing the lines takes about 0.001 a second, producing an image is what is taking most the time. Also, the above method is about the same speed or perhaps a bit faster than using CARenderer
. So I've tried using a CAMetalLayer
and generating an image with Metal
instead of CoreGraphics
.
I can verify that the lines are drawn to the layer, either by adding the layer to a view or rendering it with the above method, but I get a blank image out using CAMetalLayer
's mtlTexture
.
let layer = CAMetalLayer()
let f = CGRect(x: 0.0, y: 0.0, width: 1024.0, height: 1024.0)
shapeLayer.device = MTLCreateSystemDefaultDevice()!
shapeLayer.bounds = f
shapeLayer.frame = f
shapeLayer.pixelFormat = .bgra8Unorm
shapeLayer.framebufferOnly = false
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)
}
let outputImage = CIImage(mtlTexture: layer.nextDrawable()!.texture)
I tried using a MTLRenderPassDescriptor
but the image is blank as well:
public func render(caLayer: CAMetalLayer) -> UIImage? {
let passDescriptor = MTLRenderPassDescriptor()
let tex = caLayer.nextDrawable()?.texture
passDescriptor.colorAttachments[0].texture = tex
passDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0, 0.5, 1, 0.05);
passDescriptor.colorAttachments[0].loadAction = .clear;
passDescriptor.colorAttachments[0].storeAction = .store;
guard let device = MTLCreateSystemDefaultDevice(),
let commandQueue = device.makeCommandQueue(),
let commandBuffer = commandQueue.makeCommandBuffer() else { return nil }
let renderCommandEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: passDescriptor)
renderCommandEncoder?.endEncoding()
commandBuffer.commit()
commandBuffer.waitUntilCompleted()
let ciImage: CIImage = CIImage(mtlTexture: tex!)!
let img = UIImage(ciImage: ciImage)
return img
}