#やりたいこと
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();を呼び出せばよいです。