UIKit and Metal Rendering Synchronization Issue

Hello everyone,

I am currently in the process of developing an app that integrates both UIKit and Metal. Within my app, I have a UIKit View and overlaying it, a transparent Metal View. My primary goal is to sync UIKit and Metal rendering. I've adopted the following configuration for my MetalLayer:

func setupMetalLayer() {
        metalLayer = CAMetalLayer()
        metalLayer.device = MetalEngine.device
        metalLayer.framebufferOnly = true
        metalLayer.drawableSize = CGSize(width: frame.width * scale, height: frame.height * scale)
        metalLayer.frame = self.frame
        metalLayer.delegate = metalLayerDelegate
        metalLayer.needsDisplayOnBoundsChange = true
        metalLayer.isOpaque = false
        metalLayer.presentsWithTransaction = true
        metalLayer.maximumDrawableCount = 3
    }

In particular, setting metalLayer.presentsWithTransaction = true appears to be critical.

Here is a simplified version of my rendering function:

func render() {
        
        self.frameBoundarySemaphore.wait()
        
        self.currentFrameIndex = (self.currentFrameIndex + 1) % Renderer.kMaxInflightBuffers
        
        let commandBuffer = MetalEngine.commandQueue.makeCommandBuffer()
        guard let drawable = metalView?.metalLayer.nextDrawable(),
              let commandBuffer = commandBuffer,
              let metalView = metalView
        else {
            fatalError("Rendering failed")
        }
        
        let rpd = makeRenderPassDescriptor(drawableTexture: drawable.texture)
        let renderCommandEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: rpd)!
        renderCommandEncoder.setViewport(MTLViewport(originX: 0.0, originY: 0.0,
                                              width: Double(metalView.viewportSize.x) * metalView.scale,
                                              height: Double(metalView.viewportSize.y) * metalView.scale,
                                              znear: 0.0, zfar: 1.0))
        commandBuffer.addCompletedHandler { _ in
            self.frameBoundarySemaphore.signal()
        }
        for objects in renderObjects {
            objects.render(renderEncoder: renderCommandEncoder)
        }
        renderCommandEncoder.endEncoding()
        commandBuffer.commit()
        commandBuffer.waitUntilScheduled()
        drawable.present()
    }

This configuration performs smoothly when I update my UIKit View and call the render method simultaneously. However, I've run into an issue when I use (in my case) the Apple Pencil to only update the Metal content. If I call my render function, the content isn't always rendered. It does update if I perform an action that triggers UIKit updates (note that setNeedsDisplay() is insufficient). However, if I call render() twice in a row, the update does occur.

An alternate solution I've found is to temporarily set metalLayer.presentsWithTransaction to false. This workaround seems viable, but I'm uncertain if I might end up rendering an outdated frame when I call render().

I am seeking insights on how presentsWithTransaction operates and how to trigger a content update effectively. The existing documentation hasn't been able to address my query satisfactorily.

I would appreciate any insights or solutions from anyone who has experienced a similar issue or understands more about this topic.

Thanks in advance

UIKit and Metal Rendering Synchronization Issue
 
 
Q