Chris
Chris

Reputation: 1447

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 ?

Upvotes: 3

Views: 1890

Answers (2)

aheze
aheze

Reputation: 30506

I tried @Chris's answer but it didn't render in full resolution. Scaling by UIScreen.main.scale worked.

extension UIView {
    func takeTextureSnapshot(device: MTLDevice) -> MTLTexture? {
        let width = Int(bounds.width * UIScreen.main.scale)
        let height = Int(bounds.height * UIScreen.main.scale)
        
        if
            let context = CGContext(
                data: nil,
                width: width,
                height: height,
                bitsPerComponent: 8,
                bytesPerRow: 0,
                space: CGColorSpaceCreateDeviceRGB(),
                bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
            ),
            let data = context.data
        {

            // don't forget this!
            context.scaleBy(x: UIScreen.main.scale, y: UIScreen.main.scale)
            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
    }
}

Upvotes: 0

Chris
Chris

Reputation: 1447

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
    }
}

Upvotes: 7

Related Questions