Unity上でSharpDXする

Unityは[Low-level Native Plugin Interface](https://docs.unity3d.com/Manual/NativePluginInterface.html)によりD3D11にアクセスできるが、[SharpDX](http://sharpdx.org/)でも同様のことをすることができた。言うなればHigh-level Native Plugin Interface？

# 重大な弱点
これEditorで実行するとmonoのリソース回収時(Editor終了時または、2回目以降のPlay時)にデッドロックで固まるのが発覚したのですが、回避方法が見つかりました。

```c#
    [DllImport("mono")]
    static extern IntPtr mono_thread_current();

    [DllImport("mono")]
    static extern IntPtr mono_thread_detach(IntPtr p);

    void OnRender(int eventID)
    {
        try
        {
            // 処理
        }
        finally
        {
            mono_thread_detach(mono_thread_current());
        }
    }
```

OnRenderの最後で``mono_thread_detach``をコールする。

後で詳しく書く。

https://speakerdeck.com/ueshita/unmanagedthreadfalsesusume

# 環境

* Windows10 64bit(CreatorsUpdate適用済み)
* Unity5.6.0f3
* VisualStudio2017

コード

* https://github.com/ousttrue/UniSharpDX

dllの配置。
![folder.png](https://qiita-image-store.s3.amazonaws.com/0/59664/54ab6c8f-193f-2662-9091-c7f3df265911.png)

# その１。適当に.Net3.5プロジェクトを作成してSharpDXの.Net3.5対応バージョンを探る
* VisualStudio2015で新規プロジェクトをクラシックデスクトップのどれか、.Netバージョン3.5で作成する。
* nugetでSharpDXの動作するバージョンを探す。
* SharpDX-2.6.3ならいけた。

これもUnityのランタイムが.Net4互換にアップデートするまでの措置だがいつになることやら。

# その２。Unityのプロジェクトを作成して上記のプロジェクトからSharpDXのdllを投入する

* SharpDX.dll
* SharpDX.DXGI.dll
* SharpDX.Direct3D11.dll

![standalone.png](https://qiita-image-store.s3.amazonaws.com/0/59664/37c35ff8-2d01-d86d-086c-40c737a60cff.png)

こういう感じのテストコード。

```csharp
using UnityEngine;

public class SharpDx : MonoBehaviour
{
    void Start()
    {
        var tex = new Texture2D(640, 480);

        // テクスチャのポインタをSharpDXに渡してID3D11Texture2Dとして扱う
        using (var t = new SharpDX.Direct3D11.Texture2D(tex.GetNativeTexturePtr()))
        {
            // 実験
            var desc=t.Description;
            Debug.Log(desc.Width);
        }
    }
}
```

ちゃんとDescription(D3D11_TEXTURE2D_DESC)が取れた。

# その2.5。しかしデバッガがアタッチできない

```
warning MSB3268: プライマリ参照 "SharpDX" は、現在ターゲットされているフレームワークで解決できなかったフレームワーク アセンブリ "System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" に間接的に依存するため、解決できませんでした。".NETFramework,Version=v3.5,Profile=Unity Subset v3.5"。この問題を解決するには、参照 "SharpDX" を削除するか、"System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" を含むフレームワーク バージョンにアプリケーションを再ターゲットしてください。
```

追加で

* C:\Windows\Microsoft.NET\Framework64\v2.0.50727\System.Drawing.dll
* C:\Windows\Microsoft.NET\Framework64\v2.0.50727\System.Windows.Forms.dll

をpluginsに投入する。

# その３。ならばUWPだ

* 最新版のSharpDX-3.1.1
* SharpDX.dll
* SharpDX.DXGI.dll
* SharpDX.Direct3D11.dll

Placeholderをセットするべし

![uwp.png](https://qiita-image-store.s3.amazonaws.com/0/59664/766e77f9-0108-45f6-6716-8e00a40c20a6.png)

Windows10をターゲットにしてプロジェクトをエクスポート。

![wsa.png](https://qiita-image-store.s3.amazonaws.com/0/59664/61788310-60db-c628-ef61-87615379961d.png)

Debug, X86, ローカルマシーンを指定してビルド実行すると以下のエラーが出た。

```
error CS0012: 型 'ComObject' は、参照されていないアセンブリに定義されています。アセンブリ 'SharpDX, Version=3.1.1.0, Culture=neutral, PublicKeyToken=b4dcf0f35e5521f1' に参照を追加する必要があります。
error CS1674: 'Texture2D': using ステートメントで使用される型は、暗黙的に 'System.IDisposable' への変換が可能でなければなりません。
```

うぅむ。
Assembly-CSharpにSharpDXをnugetすることで動いた。
コンパイルエラーにならないのでわかりにくい。

![uwp-sharpdx.png](https://qiita-image-store.s3.amazonaws.com/0/59664/1401ffff-3aeb-5cea-bbb9-73cea0cec638.png)

```json:APP_EXPORT_DIR/GeneratedProjects/UWP/Assembly-CSharp/project.json
{
  "dependencies": {
    "Microsoft.NETCore.UniversalWindowsPlatform": "5.0.0",
    "SharpDX": "3.1.1",
    "SharpDX.Direct3D11": "3.1.1",
    "SharpDX.DXGI": "3.1.1"
  },
  "frameworks": {
    "uap10.0": {}
  },
  "runtimes": {
    "win10-arm": {},
    "win10-arm-aot": {},
    "win10-x86": {},
    "win10-x86-aot": {},
    "win10-x64": {},
    "win10-x64-aot": {}
  }
}
```

# その４。公式のLowLevelNativePluginのデモプロジェクトを移植する

なるほど、わりと動きそう。
もう少し大きい例を試してみようということでLowLevelNativePluginの内容をSharpDXに移植してみよう。
これ。

* https://bitbucket.org/Unity-Technologies/graphicsdemos

## できた

![ss.png](https://qiita-image-store.s3.amazonaws.com/0/59664/b6f7941e-1f44-879d-1e2a-b41cef72ffb2.png)

* https://github.com/ousttrue/UniSharpDX

だいたい動いている

## ID3D11Deviceを渡す
以下の関数はC#で作ったとしてもUnityから呼んでもらうことができないので

```cpp
void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API UnityPluginLoad(IUnityInterfaces* unityInterfaces);
void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API UnityPluginUnload();
```

別の方法でDeviceを渡す。

```cpp
ID3D11Texture2D::GetDevice
```

を使う。
SetTextureFromUnity関数でDeviceもいただく。

```csharp
    static IntPtr g_TextureHandle;
    static int g_TextureWidth = 0;
    static int g_TextureHeight = 0;
    public static void SetTextureFromUnity(System.IntPtr texture, int w, int h)
    {
        g_TextureHandle = texture;
        g_TextureWidth = w;
        g_TextureHeight = h;

        using (var dxTexture = new SharpDX.Direct3D11.Texture2D(texture))
        {
            var device=dxTexture.Device;
        }
    }
```

## ID3D11Deviceを返す

```
UnityPluginUnload()
```

が無いので

```csharp
OnApplicationQuit
```

で参照を返せるようにIDisposableなクラスを作ることにした。

## ToDo

* 深度バッファが有効にならない
* エディターの終了時に固まる(一度でもGL.IssuePluginEventを呼び出すとそうなる様子。回避するためにUnityPluginLoadとUnityPluginUnload受付用のpluginを作る必要があるかもしれない)
* わりとクラッシュする(GCでポインターが移動したときのような気がするが・・・)
* SharpDX関連がDisposeすべきか否かよくわからない(初期化時に作って使い回したほうがいいかも。きれいに終了できないのだけども)

# まとめ
UnityとSharpDXを組み合わせることでc++無用でNativePluginのように直接D3D11にアクセスできた。
Android、iOS等のクロスプラットフォームが不要で、そこまでCPU側のパフォーマンスにシビアではない用途で使い道があると思う。
画素や頂点を計算して更新するようなタイプはさすがにC++で書いた方がいいだろうけど、別のAPIやネットワークとのグルーコードなどはスレッドに乗せて少々遅かろうがスクリプトスレッドの邪魔をしなければよろしいという観点とC++めんどくせぇ(堕落)、ましてUWP向けのC++やC++/CX・・・という気持ちです。

具体的には、

* MediaFoundation(SharpDXに実装がある)からのテクスチャ更新
* Direct2D(SharpDXに実装がある)からのテクスチャ更新

や

* レンダリング結果をCPUに戻して使う(動画なら30FPSでOKなど)

等の用途をC#で完結させることを見込んでいる。
そして・・・

![hololens.png](https://qiita-image-store.s3.amazonaws.com/0/59664/49a86ee9-f2fd-8760-0be9-27682d84f8e4.png)

Hololensでも動いたよ。