Search code examples
iosuikitrenderingtexturesmetal

Render UIView contents into MTLTexture


I have an (animated) UIView-Hierarchy and I want to periodically render the UIView content into a MTLTexture for further processing.

What I have tried, is to subclass my parent UIView and

override public class var layerClass: Swift.AnyClass {
  return CAMetalLayer.self
}

but the texture from nextDrawable() is black and does not show the view content.

Any ideas how to get a MTLTexture containing the view content ?


Solution

  • Thanks to Matthijs Hollemanns who pointed me into the right direction with some code, I came up with the following UIView extension, which does the job in about 12 ms per frame on an iPhone8plus for a full screen resolution.

    extension UIView {
    
       func takeTextureSnapshot(device: MTLDevice) -> MTLTexture? {
          let width = Int(bounds.width)
          let height = Int(bounds.height)
    
          if let context = CGContext(data: nil,
                                     width: width,
                                     height: height,
                                     bitsPerComponent: 8,
                                     bytesPerRow: 0,
                                     space: CGColorSpaceCreateDeviceRGB(),
                                     bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue),
            let data = context.data {
    
            layer.render(in: context)
    
            let desc = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .rgba8Unorm,
                                                                width: width,
                                                                height: height,
                                                                mipmapped: false)
            if let texture = device.makeTexture(descriptor: desc) {
              texture.replace(region: MTLRegionMake2D(0, 0, width, height),
                              mipmapLevel: 0,
                              withBytes: data,
                              bytesPerRow: context.bytesPerRow)
              return texture
            }
          }
          return nil
        }
    }