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