在 WinForm 中使用 Direct2D


在 C# 的 WinForm 應用中,界面的繪制使用的是 GDI+。不過在一些特別的應用中,可能需要用硬件加速來提高繪制的效率。下面就來介紹兩種在 WinForm 應用中嵌入 Direct2D 的方法。

這里所謂的“嵌入”,指的是只有窗口的某一部分應用 Direct2D 繪制(用一些控件承載),而不是整個窗口都使用 Direct2D 繪制。這是一種混合方案,需要用硬件加速的部分由自己來繪制,其它部分仍然可以使用現有的 WinForm 技術。

至於 Direct2D 的類庫,我仍然使用 SharpDX 類庫,使用 SharpDX.Windows.RenderControl 控件承載 Direct2D 渲染。

一、使用 HwndRenderTarget

HwndRenderTarget(ID2D1HwndRenderTarget interface),在 SharpDX 中對應的類是 WindowRenderTarget,是將窗口句柄(hwnd)作為渲染目標的類,利用它可以非常容易的在窗口中嵌入 Direct2D 渲染。

它的用法非常簡單,只要先創建一個 Direct2D 工廠(SharpDX.Direct2D1.Factory),接下來直接創建 WindowRenderTarget 實例,然后就可以使用了。其核心代碼如下所示:

// 創建 Direct2D 單線程工廠。
Factory factory = new Factory(FactoryType.SingleThreaded);
// 渲染參數。
RenderTargetProperties renderProps = new RenderTargetProperties
{
	PixelFormat = D2PixelFormat,
	Usage = RenderTargetUsage.None,
	Type = RenderTargetType.Default
};
// 渲染目標屬性。
HwndRenderTargetProperties hwndProps = new HwndRenderTargetProperties()
{
	// 承載控件的句柄。
	Hwnd = hwndRenderControl.Handle,
	// 控件的尺寸。
	PixelSize = new Size2(hwndRenderControl.ClientSize.Width, hwndRenderControl.ClientSize.Height),
	PresentOptions = PresentOptions.None
};
// 渲染目標。
hwndRenderTarget = new WindowRenderTarget(factory, renderProps, hwndProps)
{
	AntialiasMode = AntialiasMode.PerPrimitive
};

一般的用法,就是在控件的 Paint 事件中,調用 hwndRenderTarget 的相關方法進行繪制。需要特別注意的是,如果控件的大小發生了改變,必須調用 WindowRenderTarget.Resize 方法,重新調整渲染目標的尺寸才可以,否則會導致繪制結果被拉伸,引起失真。

這個方法的優點就是非常簡單易用,而且基本上只要操作系統是 Windows 7 及更高版本,都可以正常繪制(在第 8 行設置 Type = RenderTargetType.Default,會根據情況自動選擇硬件渲染或軟件渲染),適用范圍很廣。

其缺點首先是不能在 Windows 8 的 Store app 中使用,其次是與 Direct2D 的一些高級功能不能很好的結合。Direct2D 的很多高級功能,如渲染特效,都是需要與 DeviceContext 結合使用的,而 HwndRenderTarget 不能直接使用 DeviceContext 的渲染結果。

二、使用 DeviceContext

DeviceContext 則是一個比較“萬能”的類,它可以將結果繪制到任意的 Image 上,在離線渲染與多線程渲染中是非常有用的。

使用 DeviceContext 來繪制 Direct2D 的做法則要復雜很多,由於 DeviceContext 並不能直接將結果渲染到窗口句柄上,因此需要在 DeviceContext 和 hwnd 之間建立起聯系,這里使用的是 DXGI 的 SwapChain

大致的步驟,是先利用 DeviceContext,將結果繪制到一塊緩沖區中(BackBuffer 后台緩沖區),然后由 SwapChain 將后台緩沖區的內容,呈現到 hwnd 上,完成一次繪制。

創建時,需要創建 Direct3D Device、DXGI Device 和 Diect2D Device,這樣才能創建 DeviceContext。接着再創建 SwapChain 和相應的 Surface 作為緩沖區,才能正常使用。相應的代碼如下所示,由於會有很多重名類,因此我用 using 語句定義了很多類型別名,代碼看起來會亂一些:

// 創建 Dierect3D 設備。
D3D11Device d3DDevice = new D3D11Device(DriverType.Hardware, DeviceCreationFlags.BgraSupport);
DXGIDevice dxgiDevice = d3DDevice.QueryInterface<D3D11Device1>().QueryInterface<DXGIDevice>();
// 創建 Direct2D 設備和工廠。
D2D1Device d2DDevice = new D2D1Device(dxgiDevice);
this.deviceContext = new DeviceContext(d2DDevice, DeviceContextOptions.None);

// 創建 DXGI SwapChain。
SwapChainDescription swapChainDesc = new SwapChainDescription()
{
	BufferCount = 1,
	Usage = Usage.RenderTargetOutput,
	OutputHandle = dcRenderControl.Handle,
	IsWindowed = true,
	// 這里寬度和高度都是 0,表示自動獲取。
	ModeDescription = new ModeDescription(0, 0, new Rational(60, 1), Format),
	SampleDescription = new SampleDescription(1, 0),
	SwapEffect = SwapEffect.Discard
};
this.swapChain = new SwapChain(dxgiDevice.GetParent<Adapter>().GetParent<DXGIFactory>(),
	d3DDevice, swapChainDesc);
// 創建 BackBuffer。
this.backBuffer = Surface.FromSwapChain(this.swapChain, 0);
// 從 BackBuffer 創建 DeviceContext 可用的目標。
this.targetBitmap = new Bitmap1(this.deviceContext, backBuffer);
this.deviceContext.Target = targetBitmap;

繪制同樣是在控件的 Paint 事件中繪制的,但要記得在最后調用 swapChain.Present 方法,令 SwapChain 將結果呈現在 hwnd 中。

在改變控件大小時,也要復雜很多。因為在調用 SwapChain 的 ResizeBuffers 方法之前,需要先將與 SwapChain 相關的資源全部釋放掉,才能調整緩沖區的尺寸,最后還要重建相關的資源。

使用 DiviceContext 的優點很多,包括:

  • 可以用於 Windows Store apps。
  • DeviceContext 可以繪制到其它目標,只要簡單的改變 Target 屬性即可。
  • 可以創建 Direct2D 特效。
  • 可以使用多個 DeviceContext 來進行多線程繪制,可以參見這里

在下面的 Demo 中,我也簡單的演示了其它 DeviceContext 的繪制結果,可以直接被繪制到界面中。

該方法的缺點,就是創建略微復雜,而且需要電腦在一定程度上支持 Direct3D 才可以(具體要支持到什么程度,還不清楚~)。

關於 Direct2D 特效的應用,可以參見《C# 使用 Direct2D 實現斜角效果》。Direct2D 在 WinForm 中的實際應用,可以參見我的拼圖游戲

所有源代碼和用到的類庫都可以在這里下載。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM