Using Direct2D

In today's post, we're going to walk through a simple demonstration of Direct2D. We're not going to cover advanced features, such as interop with GDI/GDI+ or Direct3D. More on that in upcoming posts.

 

Direct2D integrates seamlessly into the familiar Win32 programming paradigm, and follows a usage pattern which is similar to Direct3D. First, you create a factory...

ID2D1FactoryPtr m_spD2DFactory;

HRESULT hr = D2D1CreateFactory(

    D2D1_FACTORY_TYPE_SINGLE_THREADED,

    &m_spD2DFactory

    );

 

Next, you use the factory to create resources that you need.  One important distinction from GDI/GDI+ is that, since Direct2D is a lower-level API (like Direct3D), you need to be aware that some resources (eg. render targets, bitmaps, brushes, gradient stop collections, layers, etc) have a close association with the device on which they were created, while others (eg. geometries, meshes, stroke style, geometry sinks, tessellation sinks, etc) are not associated with a device.  Also, like Direct3D, device-dependent resources need to be recreated in cases where the device is lost or undergoes a state change. Okay, now let's create a simple geometry. Geometries are examples of device-independent resources which can be used with any render target.

   

ID2D1RectangleGeometryPtr m_spRectangleGeometry;

IFR(m_spD2DFactory->CreateRectangleGeometry(

            D2D1::Rect(20.f, 20.f, 30.f, 50.f),

            &m_spRectangleGeometry));

 

Before we can draw this geometry, we're going to need a few other things. Let's create a render target . Direct2D will attempt to create a hardware render target and, if hardware isn't available, it will fall back to a software render target. We need to create a red brush to draw the geometry. Also, we're going to use Windows Imaging Codecs (WIC) to load an image from disk, convert it to 32bppPBGRA, and then create a Direct2D bitmap from it. Note that brushes and bitmaps are device-dependent resources that are associated with a render target.

HRESULT Application::CreateDeviceResources()

{

   HRESULT hr = S_OK;

   if (!m_spRT)

   {

       IWICImagingFactoryPtr spWICFactory;

       IWICBitmapDecoderPtr spDecoder;

       IWICFormatConverterPtr spConverter;

       RECT rc;

       GetClientRect(

           m_hwnd,

           &rc);

       D2D1_SIZE_U size = D2D1::SizeU(

           rc.right - rc.left,

           rc.bottom - rc.top);

       //create a D2D render target

       hr = spD2DFactory->CreateHwndRenderTarget(

           D2D1::RenderTargetProperties(),

           D2D1::HwndRenderTargetProperties(

               hwnd,

               size),

   &spRT);

//create a red brush

IFR(spRT->CreateSolidColorBrush(

    D2D1::ColorF(1.0f, 0.0f, 0.0f, 1.0f),

    &spRedBrush));

//create WIC factory

IFR(CoCreateInstance(

    CLSID_WICImagingFactory,

    NULL,

    CLSCTX_INPROC_SERVER,

    IID_IWICImagingFactory,

    reinterpret_cast<void **>(&spWICFactory)

    ));

//load image using WIC

IFR(spWICFactory->CreateDecoderFromFilename(

    L"tiger.jpg",

    NULL,

    GENERIC_READ,

    WICDecodeMetadataCacheOnLoad,

    &spDecoder));

//get the initial frame

IFR(spDecoder->GetFrame(

    0,

    &spSource));

//format convert to 32bppPBGRA -- which D2D expects

IFR(spWICFactory->CreateFormatConverter(

    &spConverter));

//initialize the format converter

IFR(spConverter->Initialize(

    spSource,

    GUID_WICPixelFormat32bppPBGRA,

    WICBitmapDitherTypeNone,

    NULL,

    0.f,

    WICBitmapPaletteTypeMedianCut));

//create a D2D bitmap from the WIC bitmap.

IFR(spRT->CreateBitmapFromWicBitmap(

    spConverter,

    NULL,

    &m_spBitmap));

}

return S_OK;

}

At this point, we have the basic resources that we need to draw. What we need is a place to do it. Note that we're drawing in response to a WM_PAINT message, but we aren't using a GDI HDC at all.

   

        case WM_PAINT:

        case WM_DISPLAYCHANGE:

            {

                PAINTSTRUCT ps;

                BeginPaint(hwnd, &ps);

                OnRender(ps.rcPaint);

                EndPaint(hwnd, &ps);

            }

 

Here is our render function. You've already seen the CreateDeviceResources function above. This is where you create any device-dependent resources. Note that creation of device resources is only done once. After a render target and its associated resources have been created, CreateDeviceResources does nothing. Our render function checks to see whether the render target is occluded (aka covered). This is an optimization which prevents unnecessary drawing in cases where the window is hidden from view. We call BeginDraw on the render target to initiate drawing. All drawing instructions must be bracketed between BeginDraw and EndDraw calls.

 

Next, we set an identity transform, which means that anything drawn will be relative to the origin in the top left hand corner of the render target. Next, we clear the render target with a white color. You need to clear the target; otherwise, the render target will be initialized with the content from the previous drawing operations. If none have been performed yet, the result is undefined. We draw a bitmap, and then set a transform and draw our red rectangle geometry. We call EndDraw on the render target to signify that drawing operations are complete. Finally,we check the return code from EndDraw to determine whether there was any kind of failure condition.

HRESULT Application::OnRender(const RECT &rcPaint)

{

  HRESULT hr = S_OK;

  //this is where we create device resources if they don't already

  //exist (eg. m_spRT, m_spBitmap)

 

  IFR(CreateDeviceResources());

  if (!(m_spRT->CheckWindowState() & D2D1_WINDOW_STATE_OCCLUDED))

  {

    m_spRT->BeginDraw();

    m_spRT->SetTransform(D2D1::Matrix3x2F::Identity());

    m_spRT->Clear(D2D1::ColorF(D2D1::ColorF::White));

    D2D1_SIZE_F size = m_spBitmap->GetSize();

    m_spRT->DrawBitmap(

       m_spBitmap,

       D2D1::Rect<float>(0.0f, 0.0f, size.width, size.height));

    m_spRT->SetTransform(

        D2D1::Matrix3x2F::Translation(rtSize.width - 200, 0));

    m_spRT->FillGeometry(

        m_spRectangleGeometry,

        m_spRedBrush);

    hr = m_spRT->EndDraw();

}

if (hr == D2DERR_RECREATE_TARGET)

{

    //if the device is lost, we need to discard all of the resources

    //associated with that device (eg. m_spRT, m_spBitmap, etc). We will

    //recreate the next time we need to paint

DiscardDeviceResources();

}

return hr;

}    

 

As mentioned above, Direct2D is a lower-level API, and there are scenarios under which the display device can be lost (eg. adapter removed, display resolution changed, etc). GDI handles these device lost scenarios transparently but, with Direct2D (as with Direct3D), you need to be aware of and handle these conditions. If the device is lost for any reason, Direct2D will let you know, and you should free any  resources that are associated with that render target. Keep in mind that you don't have to release device-independent resources (eg. m_spRectangleGeometry).

  

void Application::DiscardDeviceResources()

{

m_spRT.Release();

m_spBitmap.Release();

m_spRedBrush.Release();

}

    

 

 

Render Target Interfaces

 

 

 

Device-Independent Resource Interfaces

 

 

 

Device-Dependent Resource Interfaces