Direct2D quickstart for Windows 8
Direct2D is a native-code, immediate-mode API for creating 2D graphics. This topic illustrates how to use Direct2D to draw to a Windows::UI::Core::CoreWindow.
This topic contains the following sections:
- Drawing a Simple Rectangle
- Step 1: Include Direct2D Header
- Step 2: Create an ID2D1Factory1
- Step 3: Create an ID2D1Device and an ID2D1DeviceContext
- Step 4: Create a Brush
- Step 5: Draw the Rectangle
- Example code
Drawing a Simple Rectangle
To draw a rectangle using GDI, you could handle the WM_PAINT message, as shown in the following code.
switch(message)
{
case WM_PAINT:
{
PAINTSTRUCT ps;
BeginPaint(hwnd, &ps);
// Obtain the size of the drawing area.
RECT rc;
GetClientRect(
hwnd,
&rc
);
// Save the original object
HGDIOBJ original = NULL;
original = SelectObject(
ps.hdc,
GetStockObject(DC_PEN)
);
// Create a pen.
HPEN blackPen = CreatePen(PS_SOLID, 3, 0);
// Select the pen.
SelectObject(ps.hdc, blackPen);
// Draw a rectangle.
Rectangle(
ps.hdc,
rc.left + 100,
rc.top + 100,
rc.right - 100,
rc.bottom - 100);
DeleteObject(blackPen);
// Restore the original object
SelectObject(ps.hdc, original);
EndPaint(hwnd, &ps);
}
return 0;
// Code for handling other messages.
The code for drawing the same rectangle with Direct2D is similar: it creates drawing resources, describes a shape to draw, draws the shape, then releases the drawing resources. The sections that follow describe each of these steps in detail.
Step 1: Include Direct2D Header
In addition to the headers required for the application, include the d2d1.h and d2d1_1.h headers.
Step 2: Create an ID2D1Factory1
One of the first things that any Direct2D example does is create an ID2D1Factory1.
DX::ThrowIfFailed(
D2D1CreateFactory(
D2D1_FACTORY_TYPE_SINGLE_THREADED,
__uuidof(ID2D1Factory1),
&options,
&m_d2dFactory
)
);
The ID2D1Factory1 interface is the starting point for using Direct2D; use an ID2D1Factory1 to create Direct2D resources.
When you create a factory, you can specify whether it is multi- or single-threaded. (For more information about multi-threaded factories, see the remarks on the ID2D1Factory reference page.) This example creates a single-threaded factory.
In general, your application should create the factory once and retain it for the life of the application.
Step 3: Create an ID2D1Device and an ID2D1DeviceContext
After you create a factory, use it to create a Direct2D device and then use the device to create a Direct2D device context. In order to create these Direct2D objects, you must have a Direct3D 11 device , a DXGI device, and a DXGI swap chain. See Devices and Device Contexts for info on creating the necessary prerequisites.
// Obtain the underlying DXGI device of the Direct3D11.1 device.
DX::ThrowIfFailed(
m_d3dDevice.As(&dxgiDevice)
);
// Obtain the Direct2D device for 2-D rendering.
DX::ThrowIfFailed(
m_d2dFactory->CreateDevice(dxgiDevice.Get(), &m_d2dDevice)
);
// And get its corresponding device context object.
DX::ThrowIfFailed(
m_d2dDevice->CreateDeviceContext(
D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
&m_d2dContext
)
);
A device context is a device that can perform drawing operations and create device-dependent drawing resources such as brushes. You also use the device context to link a ID2D1Bitmap to a DXGI surface to use as a render target. The device context can render to different types of targets.
The code here declares the properties for bitmap that links to a DXGI swap chain that renders to a CoreWindow. The ID2D1DeviceContext::CreateBitmapFromDxgiSurface method gets a Direct2D surface from the DXGI surface. This makes it so anything rendered to the target ID2D1Bitmap is rendered to the surface of the swap chain.
Once you have the Direct2D surface, use the ID2D1DeviceContext::SetTarget method to set it as the active render target.
// Now we set up the Direct2D render target bitmap linked to the swapchain.
// Whenever we render to this bitmap, it will be directly rendered to the
// swapchain associated with the window.
D2D1_BITMAP_PROPERTIES1 bitmapProperties =
BitmapProperties1(
D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW,
PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED),
m_dpi,
m_dpi
);
// Direct2D needs the dxgi version of the backbuffer surface pointer.
ComPtr<IDXGISurface> dxgiBackBuffer;
DX::ThrowIfFailed(
m_swapChain->GetBuffer(0, IID_PPV_ARGS(&dxgiBackBuffer))
);
// Get a D2D surface from the DXGI back buffer to use as the D2D render target.
DX::ThrowIfFailed(
m_d2dContext->CreateBitmapFromDxgiSurface(
dxgiBackBuffer.Get(),
&bitmapProperties,
&m_d2dTargetBitmap
)
);
// So now we can set the Direct2D render target.
m_d2dContext->SetTarget(m_d2dTargetBitmap.Get());
Step 4: Create a Brush
Like a factory, a device context can create drawing resources. In this example, the device context creates a brush.
ComPtr<ID2D1SolidColorBrush> pBlackBrush;
DX::ThrowIfFailed(
m_d2dContext->CreateSolidColorBrush(
D2D1::ColorF(D2D1::ColorF::Black),
&pBlackBrush
)
);
A brush is an object that paints an area, such as the stroke of a shape or the fill of a geometry. The brush in this example paints an area with a predefined solid color, black.
Direct2D also provides other types of brushes: gradient brushes for painting linear and radial gradients, a bitmap brush for painting with bitmaps and patterns, and starting in Windows 8, an image brush for painting with a rendered image.
Some drawing APIs provide pens for drawing outlines and brushes for filling shapes. Direct2D is different: it does not provide a pen object but uses a brush for drawing outlines and filling shapes. When drawing outlines, use the ID2D1StrokeStyle interface, or starting in Windows 8 the ID2D1StrokeStyle1 interface, with a brush to control path stroking operations.
A brush can only be used with the render target that created it and with other render targets in the same resource domain. In general, you should create brushes once and retain them for the life of the render target that created them. ID2D1SolidColorBrush is the lone exception; because it is relatively inexpensive to create, you can create a ID2D1SolidColorBrush every time you draw a frame, without any noticeable performance hit. You can also use a single ID2D1SolidColorBrush and just change its color or opacity every time you use it.
Step 5: Draw the Rectangle
Next, use the device context to draw the rectangle.
m_d2dContext->BeginDraw();
m_d2dContext->DrawRectangle(
D2D1::RectF(
rc.left + 100.0f,
rc.top + 100.0f,
rc.right - 100.0f,
rc.bottom - 100.0f),
pBlackBrush);
DX::ThrowIfFailed(
m_d2dContext->EndDraw()
);
DX::ThrowIfFailed(
m_swapChain->Present1(1, 0, ¶meters);
);
The DrawRectangle method takes two parameters: the rectangle to be drawn, and the brush to be used to paint the rectangle's outline. Optionally, you can also specify the stroke width, dash pattern, line join, and end cap options.
You must call the BeginDraw method before issuing any drawing commands, and you must call the EndDraw method after you've finished issuing drawing commands. The EndDraw method returns an HRESULT that indicates whether the drawing commands were successful. If it is not successful, the ThrowIfFailed helper function will throw an exception.
The IDXGISwapChain::Present method swaps the buffer surface with the on screen surface to display the result.
Example code
The code in this topic shows the basic elements of a Direct2D application. For brevity, the topic omits the application framework and error handling code that is characteristic of a well-written application.