UnityのTextureのポインタをSwiftだけで作ったNative Pluginに渡して処理する

# UnityのTextureのポインタをSwiftだけで作ったNative Pluginに渡して処理する
* Swift だけで、Unity の iOS の Native Plugin を作る方法を紹介しました

https://qiita.com/fuziki/items/955c2b35514bcfc37969

* Unity の Texture をネイティブで処理したい場合があります
* 例えば、[Texture を mp4 で録画](https://qiita.com/fuziki/items/042c3c5a9333beee6a07)する Native Plugin を実装することが可能です
* Unity には、[Texture.GetNativeTexturePtr](https://docs.unity3d.com/ScriptReference/Texture.GetNativeTexturePtr.html) 関数が用意されており、Texture2D や RenderTexture からも直接 Native で処理できる Texture のポイントを取得することが可能です
* iOS では、`id<MTLTexture>` を取得することが可能であり、今回はこれを Swift で受け取って、処理する方法の紹介です

# 今回のサンプル
* リポジトリはこちらです

https://github.com/fuziki/UnityPluginXcodeTemplate

* [Swift だけで、Unity の iOS の Native Plugin を作る](Swift だけで、Unity の iOS の Native Plugin を作る方法) の内容を前提知識にしています
* 今回は、Unity の Texture をアルバムに保存する機能を作ります
* 左の画像の(小さいですが)真ん中のボタンを押すと、右の画像が保存されます
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214313/4de1350b-fdfc-6b16-a7d5-97c6447f52a4.png" width="200"> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214313/21a0d155-3551-10fb-b9ed-8997d2f46247.jpeg" width="200">



# 処理の流れ
1. Unity で適当な Texture を用意する
1. Texture.GetNativeTexturePtr() を使って、Native で処理するポインタを取得する  
   == ここまで C# ここから Swift ===
1. void ポインタを受け取るので、MTLTexture 型にキャストする
1. MTLTexture -> UIImage 変換する
1. アルバムに保存する

# Unity の実装
* 任意の Texture が用意できれば良いです
* ここでは、RenderTexture のパターンで進めます
* すでに、存在する Texture を利用する場合は、`RenderTexture の用意` の章は読み飛ばしてください
* `Native の Texture のポインタを取得して、Native に渡す` から固有の処理です

## RenderTexture の用意
* RenderTexture を作成します
* Color Format が R8G8B8A8_UNorm になっていることを確認します
* サイズは任意のサイズで大丈夫です

![make-render-tex.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214313/0ee07d7b-9c93-9feb-ccd7-653f3fedcdc9.png)

* 新規のカメラを追加し、先ほど作成した、RenderTexture を Target に設定します

![set-target-texture.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214313/a4f4e89f-f460-625e-72af-4ce09f3923df.png)

* 新規の Button を追加し、下記のスクリプトを設定します

```c#
using System;
using System.Runtime.InteropServices;
using UnityEngine;

public class Button : MonoBehaviour
{
#if UNITY_IOS
    [DllImport("__Internal")]
    private static extern void swiftPmPlugin_saveImage(IntPtr mtlTexture);
#endif

    public RenderTexture texture;

    public void onTap()
    {
        Debug.Log("Button tapped!");

#if UNITY_IOS
        swiftPmPlugin_saveImage(texture.GetNativeTexturePtr());
#endif
    }
}
```

* スクリプトをアタッチしたボタンの onClick と Texture を設定します
![set-button.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214313/e5292bb7-6664-a568-00bd-099d3021cd32.png)

## Native の Texture のポインタを取得して、Native に渡す
* Native の関数を宣言します
* 今回は、iOS のみサポートです
* `[DllImport("__Internal")]` をつけた、static extern な関数を定義します

```c#
#if UNITY_IOS
    [DllImport("__Internal")]
    private static extern void swiftPmPlugin_saveImage(IntPtr mtlTexture);
#endif
```

* C# では、この関数に `texture.GetNativeTexturePtr()` を渡すだけです

```c#
#if UNITY_IOS
        swiftPmPlugin_saveImage(texture.GetNativeTexturePtr());
#endif
```

# Swift の実装
## C# (c) の呼び出し口を作る
* `@_cdecl` を付けて Swift で関数を定義します
* C# の定義的に、void* なので、UnsafeRawPointer 型として受け取ります
* MTLTexture にキャストして、保存する関数に渡します

```swift
@_cdecl("swiftPmPlugin_saveImage")
public func swiftPmPlugin_saveImage(_ texturePtr: UnsafeRawPointer?) {
    guard let texturePtr = texturePtr else { return }
    let mtlTexture: MTLTexture = __bridge(texturePtr)
    SwiftPmPlugin.saveImage(mtlTexture: mtlTexture)
}
```

## MTLTexture -> UIImage
* デフォルトの pixel format は rgba8Unorm なのですが、Unity は srgb でレンダリングしているので、フォーマットを修正します
* MTLTexture -> CIImage -> CGImage -> UIImage に変換します
* UIImageWriteToSavedPhotosAlbum を使ってアルバムに保存します

```swift
class SwiftPmPlugin {
    static func saveImage(mtlTexture: MTLTexture) {
        let mtlTexture2 = mtlTexture.makeTextureView(pixelFormat: .rgba8Unorm_srgb)!
        let ci = CIImage(mtlTexture: mtlTexture2, options: nil)!
        let context = CIContext()
        let cg = context.createCGImage(ci, from: ci.extent)!
        let ui = UIImage(cgImage: cg)

        UIImageWriteToSavedPhotosAlbum(ui, nil, nil, nil)
    }
}
```

# Framework の出力と設定

* こちらの記事の `Framework でビルドする` の章以降と同じ手順で実行ビルド可能です

https://qiita.com/fuziki/items/955c2b35514bcfc37969#framework-%E3%81%A7%E3%83%93%E3%83%AB%E3%83%89%E3%81%99%E3%82%8B

* アルバムの追加のために、Unity でビルドした後に、Xcode でアプリをビルドする前に、Info.plist に`Privacy - Photo Library Additions Usage Description` の設定をします
* ビルドしたアプリで、ボタンを押した時に、画像がアルバムに保存されているはずです

# おわりに
* Unity と Swift の Texture のやりとりは、GetNativeTexturePtr を使うことで簡易に実現可能です
* Texture のサイズやフォーマット、mipmap などの情報も、MTLTexture に入っているので、個別に取得する必要がないのが便利です
* また、Texture2D に書き写して、ReadPixels したバイト配列をコピーするよりポインタ渡しの方が経済的なのかなと思っています
* Unity の Texture を Swift で処理するのは、応用の幅が広いので、活用して行きたいです