Edited at
MetalDay 17

MTKViewとCAMetalLayerの実装差分

More than 1 year has passed since last update.

海外のMetalの解説書を読むと、大抵の場合、画面への描画にはまずCAMetalLayerを使う。MTKViewではなく。

この理由はわかる。MTKViewはラッパークラスであって、内部的にはCAMetalLayerを使っている。ので、まずはCAMetalLayerから学んでいこう、と。


An MTKView object is backed by a CAMetalLayer object


https://developer.apple.com/library/content/documentation/3DDrawing/Conceptual/MTLBestPracticesGuide/Drawables.html

それは正しいと思うが、個人的にはGPUプログラミング初心者はMTKViewから始めたほうがいいと思っている。新しい概念がたくさん登場するので、ラッパーで簡単化できる部分があるのならその恩恵にあずかったほうが挫折の可能性が下がるかなと。

で、MTKViewは、CAMetalLayerを直に使うことに比べて、具体的に何をどのように簡単化してくれているのだろうか。そのへんを整理するため、実装差分をこの記事で書き出してみる。


初期化



  • MTKViewの場合

let mtkView = MTKView(frame: frame)



  • CAMetalLayerの場合

metalLayer = CAMetalLayer()

metalLayer.frame = frame

内部ではいろいろとあるかもしれないが、API的にはframeをイニシャライザでセットできるかどうかの違いしかない。


セットアップ



  • MTKViewの場合

mtkView.device = device

view.addSubview(mtkView)

mtkView.delegate = self



  • CAMetalLayerの場合

metalLayer.device = device

view.layer.addSublayer(metalLayer)

let link = CADisplayLink(target: self, selector: #selector(onDisplayLink))
link.add(to: .current, forMode: .defaultRunLoopMode)

MTKViewはdelegateでループを処理するが、CAMetalLayerの場合は自分でループのしくみを用意する。


ピクセルフォーマットを合わせる

これは同じ

metalLayer.pixelFormat = texture.pixelFormat

mtkView.colorPixelFormat = texture.pixelFormat


レンダリングループの実装



  • MTKViewの場合

func draw(in: view) {

autoreleasepool {
render()
}
}



  • CAMetalLayerの場合

@objc func onDisplayLink() {

autoreleasepool {
render()
}
}


まとめ

あれ・・・結果としては、そんなに変わらない感じだった。レンダリングループを自前実装するか、delegateを使うか、という差はあるが、そんなに労力が減っている感はない。

簡単になるのは上で省略した、レンダリング時の、ドローアブルとかrender pass descriptorのハンドリングなのかもしれない。


Using an MTKView object is the preferred way to interact with drawables. An MTKView object is backed by a CAMetalLayer object and provides the currentDrawable property to acquire the drawable for the current frame. The current frame renders into this drawable and the presentDrawable: method schedules the actual presentation to occur at the next display refresh interval. The currentDrawable property is automatically updated at the end of every frame.

An MTKView object also provides the currentRenderPassDescriptor convenience property that references the current drawable’s texture; use this property to create a render command encoder that renders into the current drawable. A call to the currentRenderPassDescriptor property implicitly acquires the drawable for the current frame, which is then stored in the currentDrawable property.

If you create your own UIView or NSView subclass that is backed by a CAMetalLayer object, you must explicitly acquire a drawable and use its texture to configure a render pass descriptor. You can also do this for your own MTKView object, but it is much easier to simply use the currentRenderPassDescriptor convenience property. For an example of how to acquire a drawable from a UIView or NSView subclass, see the MetalBasic3D sample.


https://developer.apple.com/library/content/documentation/3DDrawing/Conceptual/MTLBestPracticesGuide/Drawables.html)