Building A Direct2D Framework

Goals

I am starting out with the goal to make a simple video game called "Anna's Afterlife". I also want to use it for rich client application development: some educational software for my son and other kids, and maybe as a front end to an accounting system for work. Therefor, my architecture should have these goals.

Architecture

To meet these goals, I've created a very simple architecture. Essentially, the user of this framework will create an instance of a directApplication, and feed one or more boards into it. A board literally means a board like in a game - it has a hooks for getting keystrokes and mouse movements and also also knows how to draw itself. boards are designed to be written portably, and the direct2dApplication works through those boards.

LayerClassesPurpose
Renderingdirect2dContextIs the place where Direct 2d renders to.
WindowdirectApplicationContains the Window handling, message loop, application lifetime.
Data TransferpointDto,solidBrushDto, etcprovides a platform neutral set of types that boards can use to talk to the application with.
Board board portable rendering and event handling logic

Right now, there's just a testBoard, that draws a square and spins the name around in code. Eventually, there will be a derived board class that has a scene graph and serialization in it.


Direct 2d Setup

To get rolling with Direct2d, first you need a Direct2d factory and a DirectWrite factory, as shown below.

direct2dContext::direct2dContext(  ) : 
	d2DFactory( NULL ), wicFactory( NULL ), dWriteFactory( NULL ), renderTarget( NULL )
{
	CoInitialize( NULL );
	HRESULT hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &d2DFactory);

	if (SUCCEEDED(hr))
		hr = CoCreateInstance( CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&wicFactory) );

	if (SUCCEEDED(hr)) 			// Create a DirectWrite factory.       
		hr = DWriteCreateFactory( DWRITE_FACTORY_TYPE_SHARED, __uuidof(dWriteFactory), reinterpret_cast<IUnknown **>(&dWriteFactory) );
}
Now we need a render target. The rendertarget is just place for the system to write to. In our case, we want the render target to be part of a Window.
bool direct2dContext::createRenderTarget( HWND hwnd )
{
	RECT rc;
	GetClientRect( hwnd, &rc);

	D2D1_SIZE_U size = D2D1::SizeU( rc.right - rc.left, rc.bottom - rc.top );

	HRESULT hr = d2DFactory->CreateHwndRenderTarget( D2D1::RenderTargetProperties(), D2D1::HwndRenderTargetProperties(hwnd, size), &renderTarget );
	return SUCCEEDED(hr);
}

Direct 2d Object LifeTimes

In Direct2d it is more efficient to create objects once, and paint with them repeatedly. However, Direct2d has an implementation quirk, in that, these objects can go away out from underneath you. So, you have to periodically recreate your objects so that you can use your brushes and geometries and others, repeatedly. To help with mananging this, the direct2d application creates a cache of the various object types and manages them on the board's behalf. This takes place in two steps.

First, we look at the end paint of our render target and see if we have to recreate it. If we do, we blow away the Direct2d side of things. Note the use of the new C++0x lambda expressions in Visual C++ 2010.

void directApplication::endDraw() {
	if (!currentBoard)
		return;
	HRESULT hr = renderTarget->EndDraw();
	if (hr == D2DERR_RECREATE_TARGET) {
		std::for_each( brushes.begin(), brushes.end(), [ this ]( std::pair<std::string, deviceDependentAssetBase *> ib ) {
			ib.second->release();
		});

Then, recreate the items in the board when the render target doesn't exist.

void directApplication::beginDraw(  )
{
	if (!currentBoard)
		return;
	if (!renderTarget && createRenderTarget( hwnd )) {
		std::for_each( bitmaps.begin(), bitmaps.end(), [ this ]( std::pair<std::string, deviceDependentAssetBase *> ib ) {
			ib.second->create(this);
		});
		std::for_each( brushes.begin(), brushes.end(), [ this ]( std::pair<std::string, deviceDependentAssetBase *> ib ) {
			ib.second->create(this);
		});

Taken together, these items allow us to present to the board a consistent view of the brushes and items in the system. I'll delve more into the specifics of Direct2d objects in the next article.


Direct2d PeekMessage Windows Loop

Finally, our direct2dApplication is a window, and manages the Direct2d binding to it, and handles the message loop, and forwards items to the board.

Our message loop looks like

	setBoard( _firstBoard );
	::ShowWindow(hwnd, SW_SHOWNORMAL );
	::UpdateWindow( hwnd );

	::QueryPerformanceFrequency( (LARGE_INTEGER *)&performanceFrequency );
	::QueryPerformanceCounter( (LARGE_INTEGER *)&lastCounter );

	while (true)  {
		if (::PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE)) {
			if (!::GetMessage( &msg, NULL, 0, 0 ))
				break;
			::TranslateMessage( &msg );
			::DispatchMessage( &msg );
		} else {
			__int64 counter;
			::QueryPerformanceCounter( (LARGE_INTEGER *)&counter );
			double elapsedSeconds = (double)( counter - lastCounter ) / (double)performanceFrequency;
			double totalSeconds = (double)( counter - startCounter ) / (double)performanceFrequency;
			lastCounter = counter;
			if (currentBoard->update( elapsedSeconds, totalSeconds )) {
				::InvalidateRect( hwnd, NULL, TRUE );
				::UpdateWindow( hwnd );
			}
		}
	}

Handling the messages themselves delegates to the board, and does some rendertarget bookkeeping.

	case WM_PAINT:
		beginDraw();
		if (currentBoard)
			currentBoard->drawFrame();
		endDraw();
		::ValidateRect( hwnd, NULL );
		return 0;

		// <------------mouse management------------------->

	case WM_LBUTTONDOWN:
		point.x = GET_X_LPARAM(lParam); 
		point.y = GET_Y_LPARAM(lParam); 
		if (currentBoard)
			currentBoard->mouseClick( &point );
		break;

	case WM_MOUSEMOVE:
		point.x = GET_X_LPARAM(lParam); 
		point.y = GET_Y_LPARAM(lParam); 
		if (currentBoard)
			currentBoard->mouseMove( &point );
		break;

Wrap up

So, we've got a basic architecture of our system, with a working object cache and message pump. In the next article, I'll talk about how creating Direct2d objects actually works for each kind, from an implementation perspective, and the board's.