Most of the native Windows Programmers are familiar with GDI, the graphics API provided under Windows. GDI is widely used in windows applications. Later, Microsoft had introduced class-based, better services through GDI+ under Windows. Both Native programmers and Managed developers are making use of GDI/GDI+ classes and APIs.
But GDI was too old and was introduced with the initial versions of the Windows and over the time the capabilities of graphics hardware has grown and so does the demand for creating Visually Rich UI. With Windows 7, Microsoft has introduced a totally revamped 2D graphics subsystem called Direct2D based on their prominent Direct3D platform to create high quality 2D rendering. This is supposed to replace the GDI APIs in the coming years. Direct2D is not only available under Windows 7 but Microsoft has made it available in Window Vista with the latest service pack upgrade. If you know or not, using DirectX platform, Windows Aero system is already taking advantage of graphics hardware since Windows Vista.
The major advantages of the Direct2D APIs are it’s hardware accelerated and provides high quality 2D rendering. It enables the user to create visually rich applications by paying less effort, when comparing to conventional APIs .
GDI uses Pixel graphics but Direct2D can supports vector graphics as well, in which mathematical formulas are used to draw the lines and curves. Vector graphics provides high quality rendering independent of resolution of the device, while the pixelated graphics has dependency with resolution which may results in choppy graphics.
Most of the GDI APIs are not using anti-aliasing and transparency. Ofcrouse there are functions to do so but always there’s programming cost for taking advantage of these features. Also if we apply transparency and anti-aliasing, the computations are done using CPU. Direct2D can take advantage of graphics hardware and delegate the computationally intensive tasks to GPU.
Direct2D built on top of Direct3D components. The layered architecture is described below
You can see the underlying layers are Direct3D which make use of DXGI(DirectX Graphics Infrastructure), which manages the low level graphics related tasks that are independent of DirectX graphics runtime. DXGI provides common framework for graphics components. Simultaneously a high performance software rasterizer is available when the hardware acceleration is not possible. Another advantage of Direct2D API is, it’s using lightweight COM. It’s almost like simple C++ class. No BSTRS, COM Variants, Interfaces, Apartments etc.
Let’s start analyzing a simple Direct2D Program
There are 3 main components inevitable for enabling D2D rendering your application.
The below example provides steps for enabling Direct2D Rendering in your MFC Application. It’s hard to describe the parameters of all APIs. Please refer them in MSDN.
Create your Window
Here I’m taking a dialog based application for rendering. The functions and messages may change if you’re using any other type of applications like SDI or MDI. No need to reinvent the wheel, allow the wizard to create your main dialog.
Prepare your Rendering Engine
Let’s delegate all the rendering tasks to the other window instead of managing everything in your dialog class. I call this class, Direct2DHandler.
The Direct2DHandler declaration goes like below and it exposes the following functions.
#pragma once
#include <d2d1.h>
#include <d2d1helper.h>
class Direct2DHandler
{
public:
Direct2DHandler( HWND hWnd ); ~Direct2DHandler(void); HRESULT Initialize(); HRESULT OnRender(); void OnResize(UINT width, UINT height);
private:
HRESULT CreateDeviceResources(); void DiscardDeviceResources();
private:
HWND m_hWnd;
ID2D1Factory* m_pDirect2dFactory;
ID2D1HwndRenderTarget* m_pRenderTarget;
ID2D1SolidColorBrush* m_pLightSlateGrayBrush;
ID2D1LinearGradientBrush* m_pLinearGradientBrush;
};
The constructor
does nothing other than calling CoInitialize() function and initialize the member variables( mostly to NULL).
The Initialize function
creates the Direct2D factor object, which similar the HDC (context variable) in GDI.
HRESULT Direct2DHandler::Initialize()
{
HRESULT hr = S_OK;
hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &m_pDirect2dFactory);
return hr;
}
OnResize
function handles the rendering area for the render target. We will be using whole window area in this example.
void Direct2DHandler::OnResize(UINT width, UINT height)
{
if (m_pRenderTarget)
{
m_pRenderTarget->Resize(D2D1::SizeU(width, height));
}
}
OnRender
function does the following
- Create the device resource for drawing (if not created)
- Render the scene to the Window
- Release the resources if error occurred
If you’re experienced 3D programming probably you might be knowing about the role of matrix and how the affects the rendering object. The matrix can be used for representing the objects coordinates system. We can apply rotation, translation (movement) etc (collectively called transformation). on a matrix and this can be used for specifying how object must be rendered. Here we’re using D2D1::Matrix3x2F::Identity() helper function which creates an identity matrix(object will be rendered at origin without any transformation)
m_pRenderTarget->SetTransform(D2D1::Matrix3x2F::Identity());
We can clear the rendering area with a default color (as the dialog background filled with some default color according to Window’s theme. Here I selected white color
m_pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White));
The important thing is, whatever you draw for the scene must be done between BeginDraw() and EndDraw() APIs. The other part of this code is simply straight forward and you can easily grasp it. See the source below.
HRESULT Direct2DHandler::OnRender()
{
HRESULT hr = S_OK;
hr = CreateDeviceResources();
if (SUCCEEDED(hr))
{
m_pRenderTarget->BeginDraw();
m_pRenderTarget->SetTransform(D2D1::Matrix3x2F::Identity());
m_pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White));
D2D1_SIZE_F rtSize = m_pRenderTarget->GetSize();
int width = static_cast<int>(rtSize.width);
int height = static_cast<int>(rtSize.height);
D2D1_RECT_F rectangle1 = D2D1::RectF(
rtSize.width/2 - 50.0f,
rtSize.height/2 - 50.0f,
rtSize.width/2 + 50.0f,
rtSize.height/2 + 50.0f
);
D2D1_RECT_F rectangle2 = D2D1::RectF(
rtSize.width/2 - 100.0f,
rtSize.height/2 - 100.0f,
rtSize.width/2 + 100.0f,
rtSize.height/2 + 100.0f
);
m_pRenderTarget->FillRectangle(&rectangle2, m_pLinearGradientBrush);
m_pRenderTarget->FillRectangle(&rectangle1, m_pLightSlateGrayBrush);
hr = m_pRenderTarget->EndDraw();
}
if (hr == D2DERR_RECREATE_TARGET)
{
hr = S_OK;
DiscardDeviceResources();
}
return hr;
}
CreateDeviceResources
is slightly a lengthy function to initialize the required resources for rendering, like we create pens, brushes, bitmaps etc. Here mainly I’m creating a light colored solid brush and linear gradient brush to render as depicted in above picture. The comments are inlined and please read it along with the code. It will be interesting to learn about gradient brushes. If you’ve created a gradient brush in photoshop things will be far more easy. We can put stop spots to controls the end of gradients also line can be used for representing flow and angle for the gradient. Simple isn’t it? Like we create a normal gradient. See the code below.
HRESULT Direct2DHandler::CreateDeviceResources()
{
HRESULT hr = S_OK;
if (!m_pRenderTarget)
{
RECT rc;
GetClientRect(m_hWnd, &rc);
D2D1_SIZE_U size = D2D1::SizeU(
rc.right - rc.left,
rc.bottom - rc.top
);
hr = m_pDirect2dFactory->CreateHwndRenderTarget(
D2D1::RenderTargetProperties(),
D2D1::HwndRenderTargetProperties(m_hWnd, size),
&m_pRenderTarget
);
if (SUCCEEDED(hr))
{
hr = m_pRenderTarget->CreateSolidColorBrush(
D2D1::ColorF(D2D1::ColorF::LightSlateGray),
&m_pLightSlateGrayBrush
);
}
ID2D1GradientStopCollection *pGradientStops = NULL;
D2D1_GRADIENT_STOP gradientStops[2];
gradientStops[0].color = D2D1::ColorF(D2D1::ColorF::Maroon, 1);
gradientStops[0].position = 0.0f;
gradientStops[1].color = D2D1::ColorF(D2D1::ColorF::Red, 1);
gradientStops[1].position = 1.0f;
hr = m_pRenderTarget->CreateGradientStopCollection(
gradientStops,
2,
D2D1_GAMMA_2_2,
D2D1_EXTEND_MODE_CLAMP,
&pGradientStops
);
if (SUCCEEDED(hr))
{
hr = m_pRenderTarget->CreateLinearGradientBrush(
D2D1::LinearGradientBrushProperties(
D2D1::Point2F(0, 0),
D2D1::Point2F(300, 300)),
pGradientStops,
&m_pLinearGradientBrush
);
}
}
return hr;
}
DiscardDeviceResources
Just releases the allocated resouces. It simply uses helper macro SafeRelease described below the code.
void Direct2DHandler::DiscardDeviceResources()
{
SafeRelease(&m_pRenderTarget);
SafeRelease(&m_pLightSlateGrayBrush);
SafeRelease(&m_pLinearGradientBrush);
}
template<class Interface>
inline void SafeRelease(
Interface **ppInterfaceToRelease
)
{
if (*ppInterfaceToRelease != NULL)
{
(*ppInterfaceToRelease)->Release();
(*ppInterfaceToRelease) = NULL;
}
}
Initializing Rendering in Dialog class
In the
OnInitDialog function,
allocate memory for the Direct2DHandler class and Initialize it. You can call this from OnCreate function as well but ensure that there’s a valid Window handle is there.
m_pRender = new Direct2DHandler( m_hWnd );
m_pRender->Initialize();
Disable Default Background Drawing
Disable the default OnErasBkgrnd function because it may cause flickering while drawing. Also we’re occupying full client area in the dialog and the background is clearing is OnRender function itself.
BOOL CDirect2DDemoDlg::OnEraseBkgnd(CDC* pDC)
{
return FALSE;}
OnSize (WM_SIZE)
function handles the Window size events. It will resize render target according to the Window Size.
void CDirect2DDemoDlg::OnSize(UINT nType, int cx, int cy)
{
CDialogEx::OnSize(nType, cx, cy);
if( m_pRender )
m_pRender->OnResize( cx, cy );
}
OnPaint function
calls OnRender function of Direct2DHandler to draw the scene.
void CDirect2DDemoDlg::OnPaint()
{
if (IsIconic())
{
}
else
{
if( m_pRender )
m_pRender->OnRender();
}
}
Linking
The Application must link to d2d1.lib through project settings or using #pragma comment( lib, “d2d1.lib” ) in the source file
d2d1helper
d2d1helper.h contains some useful functions to help the drawing. See the header for more details.
OK that’s the basics of creating Direct2D rendering for your Windows Application. Go ahead with the attached source code.
The project file is in Visual Studio 2010 format, but basically you only need to have Direct2DHandler class. You can also use other prior version of Visual Studio with Windows 7/Vista SDK.