Flickering while rendering a CALayer -> MTLTexture, via CARenderer on background thread

94 Views Asked by At

I'm trying to render a layer tree, via CARenderer, ideally on a background thread, and running into weird rendering/flicker issues


Progress as of 25th Oct

Not knowing exactly WHERE the issue was cropping up (my CARenderer -> MTLTexture creator? the view? the conversion to a CMSampleBuffer for writing vidoe?), I wrote a simple shader to count the number of pink pixels per frame, that executed almost directly after the CARenderer.render() was called.

I then used to this to trigger writes of "bad/problematic" frames to files. This is the kind of thing I saw:

Weird half baked render

Note: the rendering system (see the code) is now using a MTLHeap for its textures, so it should not be possible that some other thread/task in the app is reusing and overwriting that texture in parallel.

The key parts to the "render a CALayer to a MTLTexture" code is this:

    if let renderCommandBuffer: MTLCommandBuffer = queue.makeCommandBuffer() {
        let renderCommandEncoder: MTLRenderCommandEncoder = renderCommandBuffer.makeRenderCommandEncoder(descriptor: currentDescriptor)!
        renderCommandEncoder.label = "Clear Target"
        renderCommandEncoder.endEncoding()
        renderCommandBuffer.commit()
        renderCommandBuffer.waitUntilScheduled()

        rendererToUse.beginFrame(atTime: CACurrentMediaTime(), timeStamp: nil)
        rendererToUse.addUpdate(rendererToUse.bounds)
        rendererToUse.render()
        rendererToUse.endFrame()

Note the lack of a "rendererToUse.waitForMeToBeDoneThanksVeryMuch()" And "rendererToUse" is a CARenderer.

So it seemed to me that the "rendererToUse" (a CARenderer instance) was possibly still executing at the time I used the MTLTexture (either to count it's pink pixels, show in a view / save to a movie).

So I changed the MTLHeap to use a MTLHazardTrackingMode of .tracked. This seems so far, to have solved the problem. It seems heavy handed tho - since the synchronization is apparently at the HEAP level (I presume that to mean all access to anything vended by the heap is serialized). I don't know yet if that means that I can't be writing to two/three textures from the heap at the same time, or if it means access to each invidually vended texture from the heap is independently serialized.

So my question I think now morphs to this:

Assuming I'm even on the right track here: How do you force a CARenderer to do an equivalent of .waitUntilComplete()? Given that you can't get hold of the command buffer, nor any encoders that it might be creating/using. The textures from the MTLHeap are .private, so you can't perform any blit synchronization on them that I can see (all attempts at that fail for me, with various storageMode validation assertions), and a MTLHeap can't vend .shared or .managed textures that I know of (tried that, failed with validation assertions)

I'm still not 100% certain I'm right. But so far, the "very large hammer" approach of hazard=.tracked for the textures coming from the heap seems to do it.

Have I missed something perhaps more obvious? Given my Metal experience is low - that's quite possible. Any hints / questions or ideas appreciated.

---- Original Post

I'm seeing flicking when I run the render method on a non-main thread (see renderLayerToMTLTexture). If I run on a main thread its better but there is still flicker, just much less.

enter image description here

For video, see: https://www.dropbox.com/s/7uhg7nyjde3h1rt/Purple%20flicker%20of%20doom.mov?dl=0

  • note: the background is being cleared to pink to make it more obvious.

The code producing the texture: https://gist.github.com/scornflake/0f42841e377e99440910c43f7424f0a5

The context is that events are being generated (throttled to 60fps), requesting render to texture for a given layer (that layer being a composition of other layers). These textures are either painted to the screen (in preview mode) or sent to a video writer (to create a movie).

Flicking occurs both on screen & in the movie. Thus I am so far presuming that the flickering is not a result of my texture preview MTKView, but rather a problem in my CALayerToMetalRenderer.

What I've tried so far:

  • double/tripple buffering (helps a bit, but problem still there even with tripple buffers)
  • doing a blit to synchronize on the texture resource (seems to have no effect at all)
  • creating the texture with a storageMode of .private - since I never need the texture available to the CPU. In doing this I have to remove the synchronization blit, I'm guessing because the texture is now no longer CPU accessible thus sync is invalid.
  • passing my own MTLCommandQueue queue to CARenderer (options, in constructor), to see if manually synchronizing on the texture resources works. No change.

I'm no metal expert, but it feels to me like the initial render command (to clear the texture) is working, but the CARenderer .render is sometimes not / or perhaps sometimes late? (yes, grasping at straws here a bit)

Any ideas?

0

There are 0 best solutions below