海外のMetalの解説書を読むと、大抵の場合、画面への描画にはまずCAMetalLayer
を使う。MTKView
ではなく。
この理由はわかる。MTKView
はラッパークラスであって、内部的にはCAMetalLayer
を使っている。ので、まずはCAMetalLayer
から学んでいこう、と。
An MTKView object is backed by a CAMetalLayer object
それは正しいと思うが、個人的には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.