5
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

C# WPFでDirect2D描画

Last updated at Posted at 2022-01-21

#やりたいこと
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を詰め込み、それを描画対象としました。
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>
  • そして相互作用ロジックコード側
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();を呼び出せばよいです。

5
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?