Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Programming Direct2D – Part 1

0.00/5 (No votes)
6 Nov 2012 2  
Introduction to Direct2D API

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 ); // ctor
	~Direct2DHandler(void); // dtor
	HRESULT Initialize(); // Initialize the rendering
	HRESULT OnRender(); // Called from OnPaint function
	void OnResize(UINT width, UINT height);

private:
	HRESULT CreateDeviceResources(); // Create resources for drawing
	void DiscardDeviceResources(); // Release resources for drawing

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;

    // Create a Direct2D factory.
    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)
    {
        // Note: This method can fail, but it's okay to ignore the
        // error here, because the error will be returned again
        // the next time EndDraw is called.
        m_pRenderTarget->Resize(D2D1::SizeU(width, height));
    }
}

OnRender

function does the following

  1. Create the device resource for drawing (if not created)
  2. Render the scene to the Window
  3. 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();
		// Draw a grid background.
		int width = static_cast<int>(rtSize.width);
		int height = static_cast<int>(rtSize.height);

		// Draw two rectangles.
		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
			);

		// Draw the outline of a rectangle.
		m_pRenderTarget->FillRectangle(&rectangle2, m_pLinearGradientBrush);

		// Draw a filled rectangle.
		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
            );

        // Create a Direct2D render target.
        hr = m_pDirect2dFactory->CreateHwndRenderTarget(
            D2D1::RenderTargetProperties(),
            D2D1::HwndRenderTargetProperties(m_hWnd, size),
            &m_pRenderTarget
            );

        if (SUCCEEDED(hr))
        {
            // Create a gray brush.
            hr = m_pRenderTarget->CreateSolidColorBrush(
                D2D1::ColorF(D2D1::ColorF::LightSlateGray),
                &m_pLightSlateGrayBrush
                );
        }

		// Create an array of gradient stops to put in the gradient stop
		// collection that will be used in the gradient brush.
		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;
		// Create the ID2D1GradientStopCollection from a previously
		// declared array of D2D1_GRADIENT_STOP structs.
		hr = m_pRenderTarget->CreateGradientStopCollection(
			gradientStops,
			2,
			D2D1_GAMMA_2_2,
			D2D1_EXTEND_MODE_CLAMP,
			&pGradientStops
			);
		// The line that determines the direction of the gradient starts at
		// the upper-left corner of the square and ends at the lower-right corner.

		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;//CDialogEx::OnEraseBkgnd(pDC);
}

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())
    {
        // code for handling when window is minimized. Check original CPP file attached.
    }
    else
    {
        // CDialogEx::OnPaint(); no need to call this.
        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.

 

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here