CATextLayer not rendering properly on SCNNode

294 Views Asked by At

I have a hierarchy of CALayers that I am setting as the diffuse property of my SCNNode's material. I taking snapshots of the current state of the scene (that only has the one node) to save as a PNG to a file using this code:

scene.rootNode.addChildNode(node)
node.geometry?.firstMaterial?.diffuse.contents = self.createLayer() // CALayer

// Set transform of node.

let renderTime = CACurrentMediaTime() + 1
let size = CGSize(width: 600, height: 600)

let renderer = SCNRenderer(
  device: MTLCreateSystemDefaultDevice(),
  options: nil) 
let image = self.renderer.snapshot(
  atTime: renderTime,
  with: size,
  antialiasingMode: .multisampling4X)

return image

Often this works, rendering the node as expected, but about 25-50% of the time, any CATextLayer I have as a sublayer of the layer returned in self.createLayer() does not render the text. All other layers seem to be rendered just fine every time.

For example, an image that is supposed to look as such:

correct

Ends up missing the text "N": incorrect

The layer itself is being rendered, as I can confirm by changing the background color of the text layer:

enter image description here

This seems to only be an issue that occurs immediately after creating the layers. If I dispatch the rendering code asynchronously, even without adding any delay, everything renders as expected:

scene.rootNode.addChildNode(node)
node.geometry?.firstMaterial?.diffuse.contents = self.createLayer() // CALayer
// Set transform of node.
let renderTime = CACurrentMediaTime() + 1

let size = CGSize(width: 600, height: 600)
DispatchQueue.main.async {
  let image = self.renderer.snapshot(
    atTime: renderTime,
    with: size,
    antialiasingMode: .multisampling4X)

  completion(image)
}

The above workaround seems hacky and I don't trust it to be reliable. Also, I'd rather not force my callsites to call the method in an asynchronous manner.

Is there a property or method in SceneKit or CoreAnimation that I'm missing that I can use to make sure the layer is completely rendered before trying to render it to an image?

2

There are 2 best solutions below

1
On BEST ANSWER

I can understand it is not reliable using DispatchMain as you are not sure the node has been rendered well. So there is a function tell you if the rendering has complete or not. Call snapshot inside that completion handle.

 scene.rootNode.addChildNode(node)
    node.geometry?.firstMaterial?.diffuse.contents = self.createLayer() // CALayer

    let renderer = SCNRenderer(
        device: MTLCreateSystemDefaultDevice(),
        options: nil)

----renderer.prepare([node]) { (success) in  // this line is useful
        if (success){
            let renderTime = CACurrentMediaTime() + 1
            let size = CGSize(width: 600, height: 600)
            let  image = renderer.snapshot(
                atTime: renderTime,
                with: size,
                antialiasingMode: .multisampling4X)
            complete(image)
        }
    }

You may add other nodes to the array if you think it takes too long to load.

1
On

I've had success making PNG snapshots by just invoking SCNTransaction.flush() before the invoking sceneView.snapshot().