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