引子
任何一門語言的第一個教程幾乎都是Hello,world。我們也不例外,但是這里不是教大家打印Hello,world,而是編寫一個簡單的D2D繪制程序,讓大家對Direct2D的程序結構及編程方法有一個基本的認識。下面我們來看如何一步一步繪制一個矩形。
基本概念
在開始之前,還是先介紹一些基本的概念,有助於大家理解程序,這些概念包括,Brush(畫刷),Render target(渲染目標),Geometry(幾何圖形),它們會貫穿整個教程,所以越早介紹越好,對於有Windows GDI基礎的人來說,理解這些概念很容易。沒有基礎的也沒關系,我們可以先了解一下,隨着學習的深入,會有更加深刻的認識。
Brush
Brush-畫刷,畫刷是繪圖的工具,它管理圖形的顏色,虛實,畫刷可以繪制幾何圖形,也可以繪制位圖。
Render target
Render target-渲染目標(姑且這么翻譯吧)是繪制的場所,其實這就是一個surface,一個表面,再具體點就是一塊內存,可以是顯存,也可能是系統內存。所有的繪圖操作都在這里完成。
Geometry, Bitmap, Text
Geometry-幾何圖形,Bitmap-位圖, Text-文本。這三者是要繪制的內容,幾何圖形包括矩形,圓角矩形,橢圓等,當然Direct2D除了可以繪制幾何圖形之外,還可繪制位圖和文本,D2D沒有提供加載位圖的接口,所以對位圖的加載都是使用WIC來實現的。而對文本的繪制則是通過DirectWrite來實現的,DirectWrite在分類上屬於D2D,但是目前已經獨立成一個組件了。為了便於理解以上三者之間的關系,大家可以想象一個畫家,他在作畫的時候,都需要那些東西呢?第一,他需要一支畫筆用來繪制,這相當於上面的畫刷,第二,他需要一張紙或者一張畫布用來承載繪制的東西,這就是上面的Render target,最后,他想畫什么呢?山水?花鳥?亦或是人物?這就是繪制的內容,相當於上面的幾何圖形,位圖或者文本。
Resource
在Direct2D中主要有兩種資源,一是設備無關的資源,另一個是設備相關的資源。所謂設備無關,是指該資源不與特定的硬件渲染設備相關聯,所以設備無關的資源都分配在CPU中,而設備相關是指該資源與特定的渲染硬件相關聯,比如當硬件加速可用時,使用GPU渲染,否則使用CPU渲染。
設備無關的資源
- ID2D1DrawingStateBlock
- ID2D1Factory
- ID2D1Geometry及繼承自它的接口
- ID2D1GeometrySink和ID2D1SimplifiedGeometrySink
- ID2D1StrokeStyle
除了ID2D1RenderTarget之外,所有使用ID2D1Factory創建的資源都是設備無關的。
設備相關的資源
- ID2D1Brush及繼承自它的接口
- ID2D1Layer
- ID2D1RenderTarget及繼承自它的接口
一般來說,使用ID2D1RenderTarget創建的資源都是設備相關的。
程序框架
一個簡單的D2D程序大致包含下面三個核心函數。
1 創建資源(CreateResources) - 設備無關的資源可以一次性創建,永久使用,而設備相關的資源則需要隨着設備改變而相應的改變。
2 渲染(Render) - 響應WM_PAINT消息進行繪制。
3 清理資源(Cleanup) - DX是基於COM的,所有COM對象在使用完畢時,都要釋放。
為了便於大家理解,我畫了一張圖,這個圖簡單描述了D2D程序的基本渲染流程。
需要說明的是,Direct2D的渲染時機與D3D有些不同,D3D是在沒有消息處理時進行渲染,而D2D則是響應WM_PAINT消息進行渲染。下面的代碼中會有詳細的解釋。
代碼
添加頭文件
除了Win32編程需要的頭文件(比如Windows.h)之外,任何D2D程序都需要頭文件d2d1.h。
#include <D2D1.h>
聲明全局變量
首先我們需要一個ID2D1Factory*類型的對象,也就是D2D工廠接口,這個接口是所有D2D程序的起始點,幾乎所有的D2D資源都是由這個接口創建的,其次我們需要一個渲染的場所,也就是Render Target,在D2D中有多種類型的Render Target,這里我們選擇ID2D1HwndRenderTarget類型,用來在窗口中進行渲染。最后我們定義一個畫刷,用來繪制圖形,這里選擇固定顏色的畫刷,即ID2D1SolidColorBrush。
ID2D1HwndRenderTarget* pRenderTarget = NULL; // Render target
ID2D1SolidColorBrush* pBlackBrush = NULL ; // A black brush, reflect the line color
RECT rc ; // Render area
HWND g_Hwnd ; // Window handle
創建D2D工廠
接下來創建D2D工廠對象,有了這個對象才能創建后續的資源,這個函數有兩個參數,第一個參數是工廠的類型,這里只有單線程和多線程兩類,如果是單線程的話,意味着D2D不會為所創建的工廠的對象以及由這個對象創建的子對象提供同步機制,也就是說,如果有多個線程訪問了這個資源,那么需要自己提供同步機制。如果是多線程類型,那么D2D會為你提供同步機制。第二個參數用來接收創建的工廠。
hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &pD2DFactory) ;
if (FAILED(hr))
{
MessageBox(hWnd, "Create D2D factory failed!", "Error", 0) ;
return ;
}
創建Render target
有了工廠對象以后,開始創建RenderTarget,CreateHwndRenderTarget函數有三個參數,第一個參數是Render target屬性,包括渲染模式,象素格式,DPI等,D2D提供了一個函數D2D1::RenderTargetProperties(),可以用來生成默認的屬性,我們這里直接使用這個函數。第二個參數是Hwnd類型的Render target屬性,它包含三個參數,第一個是窗口句柄,第二個是Render target的大小,第三個參數是Present選項,這個參數有個默認值,這里我們使用它的默認值。CreateHwndRenderTarget函數的最后一個參數用來接收創建的Render target。
hr = pD2DFactory->CreateHwndRenderTarget(
D2D1::RenderTargetProperties(),
D2D1::HwndRenderTargetProperties(
hWnd,
D2D1::SizeU(rc.right - rc.left,rc.bottom - rc.top)
),
&pRenderTarget
) ;
if (FAILED(hr))
{
MessageBox(hWnd, "Create render target failed!", "Error", 0) ;
return ;
}
創建畫刷
有了Render target,再使用函數CreateSolidColorBrush創建畫刷,這里創建一個固定顏色的畫刷,第一個參數是畫刷的顏色,第二個參數接收創建的畫刷,畫刷的顏色就是繪制線條所用的顏色,比如這里創建一個紅色的畫刷,那么后面繪制的矩形就是紅色的。
hr = pRenderTarget->CreateSolidColorBrush(
D2D1::ColorF(D2D1::ColorF::Red),
&pBlackBrush
) ;
if (FAILED(hr))
{
MessageBox(hWnd, "Create brush failed!", "Error", 0) ;
return ;
}
繪制矩形
萬事俱備,只欠渲染!渲染的代碼很簡單,首先調用CreateD2DResource函數來創建資源,這是個自定義函數,用來創建資源,比如Render target,畫刷之類的,該函數包含了上面提到的代碼。繪制的代碼要放在BeginDraw和EndDraw函數之間,調用Clear函數可以將Render target清除為指定的背景色。DrawRectangle函數用來繪制矩形,它有兩個參數,第一個是被繪制的矩形,第二個是繪制所用的畫刷。函數EndDraw的返回值標識了渲染是否成功。
{
CreateD2DResource(g_Hwnd) ;
pRenderTarget->BeginDraw() ;
// Clear background color white
pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White));
// Draw Rectangle
pRenderTarget->DrawRectangle(
D2D1::RectF(100.f, 100.f, 500.f, 500.f),
pBlackBrush
);
HRESULT hr = pRenderTarget->EndDraw() ;
if (FAILED(hr))
{
MessageBox(NULL, "Draw failed!", "Error", 0) ;
return ;
}
清理資源
最后,在程序退出時,清理程序資源,每個COM對象都有一個Release方法,用來釋放自己,這里我們定義一個宏來釋放COM對象。
{
SAFE_RELEASE(pRenderTarget) ;
SAFE_RELEASE(pBlackBrush) ;
SAFE_RELEASE(pD2DFactory) ;
}
釋放COM對象的宏。
效果圖如下
Happy Coding
== THE END ==