Windows FormsでSharpDX

簡単なツールを作るには依然としてWindowsFormがお手軽で、3Dのビューを持ったツールを作りたいのです。

C#には `ShaprDX` があって `D3D` ができて、`SharpDX.Windows.Form` でWindowを作れる。
しかし、これだとWindow全部が3Dビューになるのでゲームとかを作るにはいいのだが、
ツリービューやリストビューがあって一部に3DのViewがあるWindowを作れない。

PanelとかPictureBoxみたいな3Dビューを作りたいので、やってみる。

こんな感じのやつ。
![sample.jpg](https://qiita-image-store.s3.amazonaws.com/0/59664/6d88f806-9083-09da-7d7e-33b846343492.jpeg)

動くコード
https://github.com/ousttrue/SharpDXSample

# ユーザーコントロールを作る
![user_control.jpg](https://qiita-image-store.s3.amazonaws.com/0/59664/e58c7e5d-172e-507d-3ab6-9ee6063b7739.jpeg)

ユーザーコントロールをビルドすると、プロジェクトのツールボックスに自作のコントロールが出てくる。

![toolbox.jpg](https://qiita-image-store.s3.amazonaws.com/0/59664/42c49a88-89ea-1554-8686-e56f01b482c2.jpeg)

貼り付けた。

![paste.jpg](https://qiita-image-store.s3.amazonaws.com/0/59664/e4b7295a-77eb-b71a-4413-58f7737289eb.jpeg)

# NugetでSharpDXをインストールする
`SharpDX.Direct3D11` をインストールした。
`SharpDX` と `SharpDX.DXGI` は依存関係でインストールされた。

![nuget.jpg](https://qiita-image-store.s3.amazonaws.com/0/59664/6f74adc5-5a6f-3f19-d034-eacafb725641.jpeg)

# ユーザーコントロールの実装
https://github.com/sharpdx/SharpDX-Samples/blob/f4fb0fe0f12e3edc4eda3f6307d135a66fa172cd/Desktop/Direct3D11/MiniTri/Program.cs

SharpDXのサンプルのコード(ビルドの仕方が分からないんだけど)を見ると `Form.Handle` からCreateDeviceしていると分かった。

UserControlからもHandleが取れるのでこれでやってみる。

```csharp
using System;
using System.Windows.Forms;


namespace SharpDXSample
{
    public partial class D3D11Panel : UserControl
    {
        D3D11Renderer m_renderer = new D3D11Renderer();
        public D3D11Renderer Renderer
        {
            get { return m_renderer; }
        }

        public D3D11Panel()
        {
            InitializeComponent();
            m_renderer.ClearColor = new SharpDX.Color4(0, 0, 0.5f, 1.0f);
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            m_renderer.BeginRendering(Handle); // HWND
            m_renderer.EndRendering();
        }

        protected override void OnSizeChanged(EventArgs e)
        {
            m_renderer.Resize(ClientSize.Width, ClientSize.Height);
            Invalidate(); // 再描画
        }

        public void Close()
        {
            if (m_renderer != null)
            {
                m_renderer.Dispose();
                m_renderer = null;
            }
        }
    }
}

```

```csharp
using SharpDX;
using SharpDX.Direct3D;
using SharpDX.Direct3D11;
using SharpDX.DXGI;
using System;


namespace SharpDXSample
{
    public class D3D11Renderer : IDisposable
    {
        public int Width { get; private set; }
        public int Height { get; private set; }
        Viewport Viewport
        {
            get
            {
                return new Viewport(0, 0, Width, Height, 0.0f, 1.0f);
            }
        }

        public SharpDX.Color4 ClearColor { get; set; }

        SharpDX.Direct3D11.Device m_device;
        public SharpDX.Direct3D11.Device Device
        {
            get { return m_device; }
        }

        DeviceContext m_context;
        public DeviceContext Context
        {
            get { return m_context; }
        }

        DXGISwapChain m_swapChain;

        public void Dispose()
        {
            if (m_swapChain != null)
            {
                m_swapChain.Dispose();
                m_swapChain = null;
            }
            if (m_context != null)
            {
                m_context.Dispose();
                m_context = null;
            }
            if (m_device != null)
            {
                m_device.Dispose();
                m_device = null;
            }
        }

        void CreateDevice(IntPtr hWnd)
        {
            if (m_device != null)
            {
                return;
            }

            SharpDX.Configuration.EnableObjectTracking = true;

            // SwapChain description
            var desc = new SwapChainDescription()
            {
                BufferCount = 1,
                ModeDescription = new ModeDescription(Width, Height,
                    new Rational(60, 1), Format.R8G8B8A8_UNorm),
                IsWindowed = true,
                OutputHandle = hWnd,
                SampleDescription = new SampleDescription(1, 0),
                SwapEffect = SwapEffect.Discard,
                Usage = Usage.RenderTargetOutput
            };

            // Create Device and SwapChain
            SwapChain swapChain;
            SharpDX.Direct3D11.Device.CreateWithSwapChain(DriverType.Hardware,
                DeviceCreationFlags.Debug,
                desc,
                out m_device, out swapChain);
            m_context = m_device.ImmediateContext;

            // Ignore all windows events
            using (var factory = swapChain.GetParent<Factory>())
            {
                factory.MakeWindowAssociation(hWnd, WindowAssociationFlags.IgnoreAll);
            }

            m_swapChain = new DXGISwapChain(swapChain);
        }

        public void Resize(int w, int h)
        {
            if (w == Width && h == Height)
            {
                return;
            }
            Width = w;
            Height = h;
            if (m_swapChain != null)
            {
                m_swapChain.Resize(w, h);
            }
        }

        public void BeginRendering(IntPtr hWnd)
        {
            CreateDevice(hWnd);

            var rtv = m_swapChain.GetRenderTargetView(m_device);
            m_context.ClearRenderTargetView(rtv, ClearColor);

            m_context.OutputMerger.SetTargets(rtv);
            m_context.Rasterizer.SetViewport(Viewport);
        }

        public void EndRendering()
        { 
            m_swapChain.Present();
        }
    }
}
```

![form.jpg](https://qiita-image-store.s3.amazonaws.com/0/59664/7a1a5e70-828d-8682-8ff5-c360b96e6c4e.jpeg)
RenderTargetをクリアするところまでできた。

# ShaderとVertexBuffer

https://github.com/sharpdx/SharpDX-Samples/blob/f4fb0fe0f12e3edc4eda3f6307d135a66fa172cd/Desktop/Direct3D11/MiniTri/Program.cs

Shaderを作って、`context.Draw` をコールする。
要するに下記の一連の呼び出しをする。
必要なリソースを全部作る。
今回の例では、 `vertex shader`, `pixel shader`, `input layout`, `buffer` の４つだけ。

```csharp
context.ClearRenderTargetView(m_renderView, clear); // バックバッファのクリア

{ // ここから
context.OutputMerger.SetTargets(renderView); // SwapChainから作成済み
context.Rasterizer.SetViewport(new Viewport(0, 0, form.ClientSize.Width, form.ClientSize.Height, 0.0f, 1.0f));
context.VertexShader.Set(vertexShader); // 作る 1
context.PixelShader.Set(pixelShader); // 作る 2

context.InputAssembler.InputLayout = layout; // 作る 3
context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertices, 32, 0)); // 作る 4
context.Draw(3, 0);
} // ここまで

swapChain.Present(0, PresentFlags.None); // 画面に反映
```

## nugetで追加インストール
Shaderのコンパイルに必要な `SharpDX.D3DCompiler` と、`Vector4` 等の入った `SharpDX.Mathematics` をインストールした。

## VertexShaderとInputLayoutとPixelShader

`Device` と `string` から作成する。リソースの作成には、`Device` を使う。

Shader

https://github.com/sharpdx/SharpDX-Samples/blob/f4fb0fe0f12e3edc4eda3f6307d135a66fa172cd/Desktop/Direct3D11/MiniTri/MiniTri.fx

###コンパイル
これはDeviceとは関係なくできる。

```csharp
// compile
m_vsCompiled = ShaderBytecode.Compile(m_source, "VS", "vs_4_0", ShaderFlags.None, EffectFlags.None);
m_psCompiled = ShaderBytecode.Compile(m_source, "PS", "ps_4_0", ShaderFlags.None, EffectFlags.None);
```

###作る

InputLayoutはVertexShaderの入力レイアウトの通りに作るだけなので、Shaderリフレクションで自動化できる。
後で調べる。

```csharp
m_vs = new VertexShader(device, m_vsCompiled);
m_layout = new InputLayout(
                    device,
                    ShaderSignature.GetInputSignature(m_vsCompiled),
                    new[]
                        {
                        new InputElement("POSITION", 0, SharpDX.DXGI.Format.R32G32B32A32_Float, 0, 0),
                        new InputElement("COLOR", 0, SharpDX.DXGI.Format.R32G32B32A32_Float, 16, 0)
                        });
m_ps = new PixelShader(device, m_psCompiled);
```

### 使う

```
context.VertexShader.Set(m_vs);
context.PixelShader.Set(m_ps);
context.InputAssembler.InputLayout = m_layout;
```

## VertexBuffer

`Device` と `Vector4[]` から作成する。

### 作る

```csharp
m_buffer = SharpDX.Direct3D11.Buffer.Create(device, BindFlags.VertexBuffer, m_vertices);
```

### 使う

```csharp
context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(m_buffer, s_stride, 0));
context.Draw(m_vertices.Length, 0);
```

# SharpDXでDispose漏れをチェックする

以下のコードでアクティブなオブジェクトの一覧を表示する。

```csharp
Console.WriteLine(SharpDX.Diagnostics.ObjectTracker.ReportActiveObjects());
```

アプリの終了時に呼ぶことで `Dispose` を忘れていないか確認できる。

```csharp
    static class Program
    {
        /// <summary>
        /// アプリケーションのメイン エントリ ポイントです。
        /// </summary>
        [STAThread]
        static void Main()
        {
            SharpDX.Configuration.EnableObjectTracking = true; // Object追跡を有効にする
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
            Console.WriteLine(SharpDX.Diagnostics.ObjectTracker.ReportActiveObjects()); // 一覧表示
        }
    }
```

# 開発中の少し複雑なやつ

https://github.com/ousttrue/DXGLTF