【iOS】Metal Best Practicesの解説（3） リソースオプション

[Metal Best Practices](https://developer.apple.com/library/archive/documentation/3DDrawing/Conceptual/MTLBestPracticesGuide/index.html#//apple_ref/doc/uid/TP40016642-CH27-SW1)は、iOS／MacOS／tvOSのAPIであるMetalを用いた設計のベストプラクティスガイドです。

本稿では、何回かに分けてこのガイドを読み解き、コード上での実験を交えて解説していきます。
読んでそのまま理解できそうなところは飛ばしますので、原文を読みながら原文のガイドとしてご利用下さい。
また、iOSの記事なので他のOS(MacOS, tvOS)についての記載は割愛します。

他の記事の一覧は、初回記事より参照下さい。

https://qiita.com/TokyoYoshida/items/521619d6d8dd8d8ef9e4

### [Resource Options (リソースオプション)](https://developer.apple.com/library/archive/documentation/3DDrawing/Conceptual/MTLBestPracticesGuide/ResourceOptions.html#//apple_ref/doc/uid/TP40016642-CH17-SW1)

ベストプラクティス：適切なリソースストレージモードとテクスチャ使用オプションを設定します。

#### リソースストレージモードについて

iOS, tvOSの場合は次の２種類があります。このように、CPUとGPUがメモリを共有するモデルをユニファイドメモリモデルといいます。

|モード|説明|
| :-- | :-- |
|Shared モード|CPU と GPU の両方にアクセス可能|
|Private モード|GPU のみにアクセス可能|

**【iOS, tvOSのリソースストレージモードの概要図】**

<img width="300px" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116010/4d6e302e-1d03-1daa-6fcc-29674b0996bb.png">

そして、iOSにおけるリソースストレージモードの選択は、通常はSharedモードが正しい選択であるとのこと。プライベートモードは、CPU がリソースにアクセスしない場合にのみ選択する。


### 適切なテクスチャ使用フラグの設定

Metalは、その使用目的に基づいて、特定のテクスチャのGPU操作をできる。事前に知っている場合は、必ず明示的なテクスチャ使用オプションを宣言します。

Unknownオプションに依存しないこと。このオプションはテクスチャに最も柔軟性を提供するが、かなりのパフォーマンスコストが発生します。

#### コードで検証してみる

テクスチャの使用フラグの設定によってどの程度パフォーマンスに違いが出るのか検証してみます。
こちらのリポジトリにサンプルコードがあります。

https://github.com/TokyoYoshida/MetalExamples

サンプルコードの中に、[Particle](https://github.com/TokyoYoshida/MetalExamples/tree/main/MetalExamples/Samples/Particle)というサンプルがあるのでこれを改変しながら計測していきます。

(実行イメージ)
<img width="200px" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116010/0a662430-3b57-8186-d023-9c4608cf5603.gif">

テクスチャの使用フラグを設定する箇所の抜粋です。コメントにあるとおり、texDesc.usageの設定をUnknownとする場合と明示的に指定する場合の両方を用意し、実行するときに片方をコメントアウトしながら計測します。

```swift:ParticleMetalView.swift
func initTexture() {
    func makeRenderTexture() -> MTLTexture {
        let texDesc = MTLTextureDescriptor()
        texDesc.width =  100//(parent.mtkView.currentDrawable?.texture.width)!
        texDesc.height =  100//(parent.mtkView.currentDrawable?.texture.height)!
        texDesc.depth = 1
        texDesc.textureType = MTLTextureType.type2D

        texDesc.storageMode = .private
        texDesc.pixelFormat = .bgra8Unorm

        texDesc.usage = .unknown // Unknown版
        texDesc.usage = [MTLTextureUsage.renderTarget, MTLTextureUsage.shaderRead] // 明示指定版

        return metalDevice.makeTexture(descriptor: texDesc)!
    }
    texture = makeRenderTexture()
}

```

計測は、Metal System Traceを使います。使い方はこちらをご覧ください。

https://qiita.com/TokyoYoshida/items/c904e79daf0a1a72375a

アプリを実行し、一定時間計測してから、GPUのサマリーでFragmentとVertexの所要時間を見てみましょう。

**＜Unknown版の結果＞**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116010/a5537180-9105-c044-2f15-4e5a81f02161.png)

**＜明示指定版の結果＞**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116010/fda3ad25-9f65-2a4e-27c7-09ea7d05e3fe.png)

「Avg CPU to GPU Latency」を見てください。VertexはなぜかUnknown版の方が早いですが、Fragmentは明示指定版の方が、1ms近くなっています。トータルでも明示指定版の方が0.77ms早い結果となりました。

設定を1つ変えるだけで1msも早くなるのなら、労力に対して結構いい感じの成果なんじゃないでしょうか😄


### 結論

ベストプラクティスにあるとおり、テクスチャ使用フラグを明示するほうが効率的ということがわかりました。

### 最後に

iOSを使ったARやML、音声処理などの作品やサンプル、技術情報を発信しています。
作品ができたらTwitterで発信していきますのでフォローをお願いします🙏

Twitterは作品や記事のリンクを貼っています。
https://twitter.com/jugemjugemjugem

Qiitaは、iOS開発、とくにARや機械学習、グラフィックス処理、音声処理について発信しています。
https://qiita.com/TokyoYoshida

Noteでは、連載記事を書いています。
https://note.com/tokyoyoshida

Zennは機械学習が多めです。
https://zenn.dev/tokyoyoshida