C# WPFでDirect2D描画

#やりたいこと
WPFのグラフィックスモデルはとにかく遅いので、Direct2Dを使ってUWPの様にさらさら描画したい。そして、プログラム言語はC#を使いたい。巷にはC++でゴリコリ書いたものをInteropで呼び出すサンプルは見かけるのですが、それは面倒なのと敷居も高いのでなんとかしようとした内容です。

##使ったもの
- Visual Studio 2019
- Microsoft.Wpf.Interop.Directx-x64  (NuGet Package) Microsoft謹製ですが、ベータ版でソースにはTODOとか残ってます。それでも今回の目的には十分な働きをしてくれます。
- SharpDX, SharpDX.DXGI, SharpDX.Direct3D11, SharpDX.Direct2D1, SharpDX.Mathematics  (NuGet Package)

##Sample Code
- Visual StudioでWPF .NetFrameworkのアプリケーションを作成します。
- プロジェクトのNuGetパッケージの管理から上記NuGetパッケージをインストールします。
- D3D11Imageのしばりにより、プラットフォームはx64とします。
- ディフォルトで作成されるMainWindow.xamlに描画したい領域を作成します。今回はMicrosoftのD3D11Imageのサンプルと同様にGridの中にImageを詰め込み、それを描画対象としました。

```xml:MainWindow.xaml
<Window x:Class="SampleWpfSharpDX.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:SampleWpfSharpDX"
        xmlns:DXExtensions="clr-namespace:Microsoft.Wpf.Interop.DirectX;assembly=Microsoft.Wpf.Interop.DirectX"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid  x:Name="D3DImgHost">
        <Image Stretch="Fill">
            <Image.Source>
                <DXExtensions:D3D11Image x:Name="D3DImg"/>
            </Image.Source>
        </Image>
    </Grid>
</Window>
```

- そして相互作用ロジックコード側

```cs:MainWindow.xaml.cs
using SharpDX;
using SharpDX.Direct2D1;
using SharpDX.Direct3D11;
using SharpDX.DXGI;
using SharpDX.Mathematics.Interop;
using System;
using System.Windows;
using System.Windows.Interop;

namespace SampleWpfSharpDX
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        private SharpDX.Direct3D11.Device _Device3D11;
        private SwapChain _SwapChain;
        private SwapChainDescription _SwapChainDescription;
        private SharpDX.Direct2D1.Factory _Factory2D1;
        private RenderTarget _RenderTarget;

        public MainWindow()
        {
            InitializeComponent();
            D3D11ImgHost.Loaded += new RoutedEventHandler(ImageHost_Loaded);
            D3D11ImgHost.SizeChanged += new SizeChangedEventHandler(ImageHost_SizeChangedEventHandler);
        }

        /// <summary>
        /// ここに自分が描きたいコードを書きます
        /// 描画方法はSharpDX.Direct2D1の使い方を調べましょう
        /// </summary>
        /// <param name="rt">Direct2DのRenderTarget</param>
        private void OnRenderD2D(RenderTarget rt)
        {
            var width = D3D11ImgHost.ActualWidth;
            var height = D3D11ImgHost.ActualHeight;

            var rnd = new Random();
            rt.Clear(new RawColor4(0.0f, 0.0f, 0.0f, 1.0f));
            for (var i = 0; i < 10000; i++)
            {
                float r = (float)rnd.NextDouble();
                float g = (float)rnd.NextDouble();
                float b = (float)rnd.NextDouble();

                float x0 = (float)(rnd.NextDouble() * width);
                float y0 = (float)(rnd.NextDouble() * height);
                float x1 = (float)(rnd.NextDouble() * width);
                float y1 = (float)(rnd.NextDouble() * height);
                var lineColor = new RawColor4(r, g, b, 1.0f);
                var brush = new SolidColorBrush(rt, lineColor);
                rt.DrawLine(new RawVector2(x0, y0), new RawVector2(x1, y1), brush);
            }
        }


        /// <summary>
        /// 描画領域関連のインスタンスができてHWNDを取得できるようになったので
        /// DirectXのデバイス等を作成します
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ImageHost_Loaded(object sender, RoutedEventArgs e)
        {
            int width = (int)D3D11ImgHost.ActualWidth;
            int height = (int)D3D11ImgHost.ActualHeight;

            if (width < 8)
            {
                width = 8;
            }
            if (height < 8)
            {
                height = 8;
            }
            var window = GetWindow(this);
            D3D11Img.WindowOwner = new WindowInteropHelper(window).Handle;
            D3D11Img.OnRender = D3D11Img_OnRender;

            HwndSource source = (HwndSource)PresentationSource.FromVisual(this);
            _SwapChainDescription = new SwapChainDescription()
            {
                BufferCount = 1,
                ModeDescription = new ModeDescription(width, height, new Rational(60, 1), Format.B8G8R8A8_UNorm),
                IsWindowed = true,
                OutputHandle = source.Handle,
                SampleDescription = new SampleDescription(1, 0),
                SwapEffect = SwapEffect.Discard,
                Usage = Usage.RenderTargetOutput
            };

            SharpDX.Direct3D11.Device.CreateWithSwapChain(
                SharpDX.Direct3D.DriverType.Hardware,
                DeviceCreationFlags.BgraSupport,
                new[] { SharpDX.Direct3D.FeatureLevel.Level_11_0 },
                _SwapChainDescription,
                out _Device3D11,
                out _SwapChain);

            _Factory2D1 = new SharpDX.Direct2D1.Factory();

            D3D11Img.RequestRender();
        }

        /// <summary>
        /// 描画領域のサイズ変更
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ImageHost_SizeChangedEventHandler(object sender, SizeChangedEventArgs e)
        {
            // 最小サイズは適当です。そもそも不要かもしれません。
            int width = (int)D3D11ImgHost.ActualWidth;
            int height = (int)D3D11ImgHost.ActualHeight;
            if (width < 8)
            {
                width = 8;
            }
            if (height < 8)
            {
                height = 8;
            }
            D3D11Img.SetPixelSize(width, height);
        }


        /// <summary>
        /// D3D11Imageからコールバックされるルーチン
        /// </summary>
        /// <param name="pIUnknown">D3D11ImageからはIUnknownが渡されます</param>
        /// <param name="isNewSurface">Surface再作成の要否</param>
        private void D3D11Img_OnRender(IntPtr pIUnknown, bool isNewSurface)
        {
            if (isNewSurface)
            {
                // Direct2Dを使うためのDirect3D11のSurfaceを
                // WPFのDirect3D9と共有させて作成する
                int width = (int)D3D11ImgHost.ActualWidth;
                int height = (int)D3D11ImgHost.ActualHeight;

                if (width < 8)
                {
                    width = 8;
                }
                if (height < 8)
                {
                    height = 8;
                }
                _SwapChainDescription.ModeDescription.Width = width;
                _SwapChainDescription.ModeDescription.Height = height;
                var dxgiResource = ComObject.As<SharpDX.DXGI.Resource>(pIUnknown);
                var tmpResource = _Device3D11.OpenSharedResource<SharpDX.Direct3D11.Resource>(dxgiResource.SharedHandle);
                var outputResource = tmpResource.QueryInterface<Texture2D>();
                var surface = outputResource.QueryInterface<Surface>();
                _RenderTarget = new RenderTarget(_Factory2D1, surface,
                    new RenderTargetProperties(
                        new PixelFormat(Format.B8G8R8A8_UNorm, SharpDX.Direct2D1.AlphaMode.Premultiplied)));
                _RenderTarget.AntialiasMode = AntialiasMode.PerPrimitive;
                _RenderTarget.TextAntialiasMode = TextAntialiasMode.Cleartype;
            }

            // SurfaceにDirect2Dで描画
            _RenderTarget.BeginDraw();
            OnRenderD2D(_RenderTarget);
            _RenderTarget.EndDraw();
            _SwapChain.Present(0, PresentFlags.None);
            _Device3D11.ImmediateContext.Flush();
        }
    }
}
```

描画量が多すぎると、リサイズした時に真っ黒になったりしますが、通常の量なら大丈夫そうです。
即時モードで描画しているので、任意のタイミングで再描画させる場合、ID3D11Img.RequestRender();を呼び出せばよいです。