WPFのコントロールにDirectX12で描画する

# モチベーション
モーショングラフィックスエンジン的なものを作りたい。
グラフィックスの描画はDXRを見越してDirectX12が使いたい。
でも、周りのUIはグラフィックスとは分離して、MVC的なフレームワークで作りたい。

Unityでいいじゃんって話なのですが…

# やりたいこと

WPFで色々なパラメータやリソースを表示編集できるWindowの中に、DirextXが描画するコントロールを置きたい。
![コメント 2020-02-27 144421.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280975/e3b01a18-427e-3e09-8226-e325a59a7651.png)

ポリ一枚表示しただけですし、スライダーで動くわけでもなければリサイズにも対応していませんが、
一応上記画像のようにできました。

# D3DImage/D3D11Image（今回は不採用）

WPFには`D3DImage`/`D3D11Image`というクラスが用意されており、それぞれDirextX9/11向けのRenderTargetを提供しています。
[WPFDXInterop](https://github.com/microsoft/WPFDXInterop/tree/master/samples/D3D11Image)

DirectX12向けのクラスは提供されていませんが、DirectX11-12間でテクスチャリソースの共有が可能です。
DirectX11のWPFDXInteropでは、WPFControlから`IDXGIResource`が手に入るので、それから`ID3D11Texture2D`を取得し、RenderTargetViewを作成し描画します。
DirectX12の場合も同様に`IDXGIResource`から`ID3D12Resource1`を取得してRenderTargetViewを作成して描画すればいいはずです。
（DirectX12ではTextureやConstantBufferなどのインターフェイスとしての垣根がなくなり、`ID3D12Resource1`に統一されました。）

しかし、いざ実装してみようとすると、SwapChain使わない場合のやり方などがよくわからなかったので、確実な`HwndHost`を使ってみました。
`ID3D12CommandQueue::ExecuteCommandLists `の後、`IDXGISwapChain::Present`を呼ばずに、描画完了も待たずにリターンすればいいのでしょうか？WPFコントロールから渡される`IDXGIResource`はバックバッファなんですかね？とすると、描き切れなかったら半端なRTが表示されちゃう？？？

# HwndHost（WPF Control）とは

DirectX12のサンプルなどでは生Win32でGUIを生成して、ルートのWindowに対して描画をしています。
Win32のGUIというのは、以下のようなもので前半部がWindowの初期化、後半部がいわゆるUpdateやイベント（メッセージ）処理です。
HWNDという変数が出てきますが、これはFormやWPFでいう`Window`/`Form`やControlに相当するもので文字通りのウインドウからボタンまでWindowとして扱い、HWNDで管理します。
DirectXにHWNDを指定して描画させる場合、このWindowの矩形領域内に描画することになります。

```cpp:main.cpp
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, int nCmdShow)
{
    WNDCLASS winc; // WNDCLASSの設定
    HWND hwnd = CreateWindow(
        TEXT("MYCLASSNAME"), TEXT("Title"),
        WS_OVERLAPPEDWINDOW | WS_VISIBLE,
        CW_USEDEFAULT, CW_USEDEFAULT,
        CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, NULL, hInstance, NULL
    );

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0))
    {
        DispatchMessage(&msg);
        theApp.Render();
    }
    return msg.wParam;
}
```

それで、`HwndHost`はこの`WINAPI`の枠を提供してくれます。
実質的にはWindowの矩形領域を提供してくれる＆`HWND`を保持管理してくれます。

```cs
class MyControl : HwndHost
{
    protected virtual HandleRef BuildWindowCore(HandleRef hwndParent)
    {
        // 初期化処理（ただし、この時点ではまだ矩形情報が取れない！）
        // CreateWindowとかして、帰ってきたHWNDを返す
    }
    protected virtual IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        // Win32の後半のメッセージループの部分
        // DirectXのUpdate（Render）処理はここで
    }
    protected virtual void DestroyWindowCore(HandleRef hwnd)
    {
        // 終了処理
    }
}
```

ここにうまいことDirectX12の初期化処理とUpdate(Render)処理を挟みます。
この方法の最大の利点は、Win32で作る場合とほとんど変わらない…というかWin32との互換性維持のための機能という点です。
DirextXはそもそもゲームを作るためのAPIなので外部GUIは不要でWindowいっぱいに描けばいいので、手に入るサンプルやノウハウも大抵の場合は生Win32のルートWindowに描いています。それらを参考にできます。

ということで、この`HwndHost`とWin32とDirectX12合体させてみたのがこちらになります。
DirectX12の処理は「DirectX12 Programming Vol.1」の付録のサンプルコードを流用させていただきました。

```cs:DX.cs
class DX : HwndHost
{
    [Flags] enum WindowStyle : int { /* 省略 */ }

    IntPtr app = IntPtr.Zero;

    protected override HandleRef BuildWindowCore(HandleRef hwndParent)
    {
        // Win32のWindowの初期化
        IntPtr hwnd = CreateWindowEx(
            0, "STATIC", "",
            WindowStyle.WS_CHILD | WindowStyle.WS_VISIBLE,
            0, 0,
            (int)ActualWidth, (int)ActualHeight,
            hwndParent.Handle,
            (IntPtr)WindowStyle.HOST_ID,
            IntPtr.Zero, 0);
        return new HandleRef(this, hwnd);
    }

    protected override IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        if (app == IntPtr.Zero)
        {
            // DirectX12の初期化
            // BuildWindowCoreでInitしたかったが、は矩形が0のままなのでDepthBufferが作れない。
            // 本当はリサイズも考慮してデバイスのInitとRenderTarger/DepthBufferの生成を分けるべき。
            app = Init(hwnd, (int)ActualWidth, (int)ActualHeight);
        }

        // DirectX12の描画（のリクエスト）処理
        Render(app);

        handled = false;
        return IntPtr.Zero;
    }

    protected override void DestroyWindowCore(HandleRef hwnd)
    {
        // Win32のWindowとDirectX12の終了処理
        DestroyWindow(hwnd.Handle);
        Dispose(app);
    }

    [DllImport("user32.dll")]
    static extern IntPtr CreateWindowEx( /* 省略 */ );
    [DllImport("user32.dll")]
    static extern bool DestroyWindow(IntPtr hwnd);

    [DllImport("02_SimpleTriangle.dll")]
    static extern IntPtr Init(IntPtr hwnd, int width, int height);
    [DllImport("02_SimpleTriangle.dll")]
    static extern void Render(IntPtr app);
    [DllImport("02_SimpleTriangle.dll")]
    static extern IntPtr Dispose(IntPtr app);
}
```
```cpp:Export.cpp
TriangleApp* Init(HWND hwnd, int width, int height)
{
    auto app = new TriangleApp(); // TriangleApp.h
    app->Initialize(hwnd, width, height);
    return app;
}

void Render(TriangleApp* app)
{
    app->Render();
}

void Dispose(TriangleApp* app)
{
    app->Cleanup();
    delete app;
}
```
```xml:MainWindow.xaml
<Window> <!-- 属性は省略 -->
    <Grid>
        <local:DX Margin="18,43,342,122"></local:DX>
        <WrapPanel Margin="527,101,53,281">
            <TextBlock Text="Hogehoge"></TextBlock>
        </WrapPanel>
        <Slider Margin="517,149,39,231"></Slider>
    </Grid>
</Window>
```

DLLImportするときはImportするDLLが参照しているDLLもexeのディレクトリに並べないと`DllNotFoundException`が出ます。
この例では、`02_SimpleTriangle.dll`が`dxcompiler.dll`などを参照していてハマりました。

全文は [こちら（Github）](https://github.com/wallstudio/d3d12_book_1/tree/wpfViaWindowHwnd_2/WpfApp2)

# 技術領域問題

WPFのコントロールは一枚のDirectX9で描かれています。そこに無理やりWin32のウインドウを乗っけてそこにDirextX12で描画している訳です。
なのでWPFコントロールでサンドイッチすることができません。

詳しくは [技術領域の概要（MSDN）](https://docs.microsoft.com/ja-jp/dotnet/framework/wpf/advanced/technology-regions-overview?redirectedfrom=MSDN)

# 余談

DirectX12の情報…少なすぎ！！
最近のMSの動向としてはDirectXを触るのは本当に限られた場合だけで、基本的にはUnityとか使ってね！君たちはDirectXなんて知らなくていいよ！ってスタンスですし、いまだにDirectX9が現役（？）ですからね。ゲームの専門学校などでもいまだにDirectX9と聞きます。確かにシンプルで初学向けなのかもしれませんが。
MSDNは情報「量」だけは結構ありますし、「DirectX12 Programming Vol.1」のおかげで基本的な使い方は理解できましたが、少し外れたことを、応用したいと思うと似たような例が見つかりませんね。

# 参考
* [DirectX12 Programming Vol.1（Booth）](https://booth.pm/ja/items/1286096)
* [WPFでOpenGLの描画はめ込み（すらりん日記）](https://blog.techlab-xe.net/post-4959/)
* [チュートリアル: WPF での Win32 コントロールのホスト（MSDN）](https://docs.microsoft.com/ja-jp/dotnet/framework/wpf/advanced/walkthrough-hosting-a-win32-control-in-wpf?redirectedfrom=MSDN)
* [HwndHost クラス（MSDN）](https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.interop.hwndhost?view=netframework-4.8)
* [WPFDXInterop（microsoft）](https://github.com/microsoft/WPFDXInterop/tree/master/samples/D3D11Image)