Hi,
Are there any plans for integrating Metal/MetalKit with SwiftUI?
Is that correct that current recommended way of the integration is to use UIViewControllerRepresentable like in the tutorial?
Thank you!
Hi,
Are there any plans for integrating Metal/MetalKit with SwiftUI?
Is that correct that current recommended way of the integration is to use UIViewControllerRepresentable like in the tutorial?
Thank you!
Hi,
Same question here :
I have successfully displayed a SceneKit View in SwifUI (aka SCNView) using UIViewRepresentable, but failed to do this with MetalKit View (aka MTKView).
Thank you in advance if someone as an idea how to do that.
Here's a condensed implementation without doing that much other than rendering CIFilter on images. I have another MTKView that's actually running a compute pipeline and I banged my head a bit figuring out how to organize the code. Mainly the pattern sorta expect you to instantiate everything and leaving the Coordinator to handle actual delegation. However, for an MTKView, you are stuck either instantiating everything within the Coordinator itself or writing an additional Renderer Coordinator class to instantiate everything within. I didn't feel like going down the separate renderer route, so I just passed MTKView straight into the Coordinator and do the typical setup there.
struct MTKMapView: UIViewRepresentable {
typealias UIViewType = MTKView
var mtkView: MTKView
func makeCoordinator() -> Coordinator {
Coordinator(self, mtkView: mtkView)
}
func makeUIView(context: UIViewRepresentableContext<MetalMapView>) -> MTKView {
mtkView.delegate = context.coordinator
mtkView.preferredFramesPerSecond = 60
mtkView.backgroundColor = context.environment.colorScheme == .dark ? UIColor.white : UIColor.white
mtkView.isOpaque = true
mtkView.enableSetNeedsDisplay = true
return mtkView
}
func updateUIView(_ uiView: MTKView, context: UIViewRepresentableContext<MetalMapView>) {
}
class Coordinator : NSObject, MTKViewDelegate {
var parent: MetalMapView
var ciContext: CIContext!
var metalDevice: MTLDevice!
var metalCommandQueue: MTLCommandQueue!
var mtlTexture: MTLTexture!
var startTime: Date!
init(_ parent: MetalMapView, mtkView: MTKView) {
self.parent = parent
if let metalDevice = MTLCreateSystemDefaultDevice() {
mtkView.device = metalDevice
self.metalDevice = metalDevice
}
self.ciContext = CIContext(mtlDevice: metalDevice)
self.metalCommandQueue = metalDevice.makeCommandQueue()!
super.init()
mtkView.framebufferOnly = false
mtkView.clearColor = MTLClearColor(red: 0, green: 0, blue: 0, alpha: 0)
mtkView.drawableSize = mtkView.frame.size
mtkView.enableSetNeedsDisplay = true
}
func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
}
func draw(in view: MTKView) {
guard let drawable = view.currentDrawable else {
return
}
let commandBuffer = metalCommandQueue.makeCommandBuffer()
let inputImage = CIImage(mtlTexture: mtlTexture)!
var size = view.bounds
size.size = view.drawableSize
size = AVMakeRect(aspectRatio: inputImage.extent.size, insideRect: size)
let filteredImage = inputImage.transformed(by: CGAffineTransform(
scaleX: size.size.width/inputImage.extent.size.width,
y: size.size.height/inputImage.extent.size.height))
let x = -size.origin.x
let y = -size.origin.y
self.mtlTexture = drawable.texture
ciContext.render(filteredImage,
to: drawable.texture,
commandBuffer: commandBuffer,
bounds: CGRect(origin:CGPoint(x:x, y:y), size: view.drawableSize),
colorSpace: CGColorSpaceCreateDeviceRGB())
commandBuffer?.present(drawable)
commandBuffer?.commit()
}
func getUIImage(texture: MTLTexture, context: CIContext) -> UIImage?{
let kciOptions = [CIImageOption.colorSpace: CGColorSpaceCreateDeviceRGB(),
CIContextOption.outputPremultiplied: true,
CIContextOption.useSoftwareRenderer: false] as! [CIImageOption : Any]
if let ciImageFromTexture = CIImage(mtlTexture: texture, options: kciOptions) {
if let cgImage = context.createCGImage(ciImageFromTexture, from: ciImageFromTexture.extent) {
let uiImage = UIImage(cgImage: cgImage, scale: 1.0, orientation: .downMirrored)
return uiImage
}else{
return nil
}
}else{
return nil
}
}
}
}
Same thing here. It would be great if metal kit was more naturally integrated with SwiftUI, without having to go through Coordinators and the likes
Can you please share the MetalMapView code? Is it some kind of SwiftUI View with a UIHostingController?..
Or is it a typo and you meant MTKMapView instead of MetalMapView ?
Code Block swift var body: some View { MetalView() }
Code Block swift import MetalKit struct MetalView: NSViewRepresentable { func makeCoordinator() -> Coordinator { Coordinator(self) } func makeNSView(context: NSViewRepresentableContext<MetalView>) -> MTKView { let mtkView = MTKView() mtkView.delegate = context.coordinator mtkView.preferredFramesPerSecond = 60 mtkView.enableSetNeedsDisplay = true if let metalDevice = MTLCreateSystemDefaultDevice() { mtkView.device = metalDevice } mtkView.framebufferOnly = false mtkView.clearColor = MTLClearColor(red: 0, green: 0, blue: 0, alpha: 0) mtkView.drawableSize = mtkView.frame.size mtkView.enableSetNeedsDisplay = true return mtkView } func updateNSView(_ nsView: MTKView, context: NSViewRepresentableContext<MetalView>) { } class Coordinator : NSObject, MTKViewDelegate { var parent: MetalView var metalDevice: MTLDevice! var metalCommandQueue: MTLCommandQueue! init(_ parent: MetalView) { self.parent = parent if let metalDevice = MTLCreateSystemDefaultDevice() { self.metalDevice = metalDevice } self.metalCommandQueue = metalDevice.makeCommandQueue()! super.init() } func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) { } func draw(in view: MTKView) { guard let drawable = view.currentDrawable else { return } let commandBuffer = metalCommandQueue.makeCommandBuffer() let rpd = view.currentRenderPassDescriptor rpd?.colorAttachments[0].clearColor = MTLClearColorMake(0, 1, 0, 1) rpd?.colorAttachments[0].loadAction = .clear rpd?.colorAttachments[0].storeAction = .store let re = commandBuffer?.makeRenderCommandEncoder(descriptor: rpd!) re?.endEncoding() commandBuffer?.present(drawable) commandBuffer?.commit() } } }
Code Block mtkView.isPaused =false
Code Block func makeUIView(context: UIViewRepresentableContext<MetalMapView>) -> MTKView { mtkView.delegate = context.coordinator ... }
(of course the same is also true in the case of NSViewRepresentable, makeNSView )SwiftUI calls this method before calling the makeUIView(context:) method.
Code Block var aspect = Float(mtkView.drawableSize.width / mtkView.drawableSize.height)
Code Block class ViewController: UIViewController { var mtkView: MTKView! ... override func viewDidLoad() { super.viewDidLoad() guard let mtkViewTemp = self.view as? MTKView else {...} mtkView = mtkViewTemp ... }
Code Block Swift func ciExposure (inputImage: CIImage, inputEV: Double) -> CIImage { let filter = CIFilter(name: "CIExposureAdjust")! filter.setValue(inputImage, forKey: kCIInputImageKey) filter.setValue(inputEV, forKey: kCIInputEVKey) return filter.outputImage! }