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

Direct2D: Hardware Acceleration in Windows 7 Plotting

0.00/5 (No votes)
10 May 2009 2  
Draw your vectors using a new, ActiveX based hardware accelerated interface.

Introduction

GDI+ has many features all right, but it is somewhat slow. Microsoft introduced Direct2D in Windows 7, a featured hardware-accelerated API that you can now use for applications. My first usage of Direct2D was in my Turbo Play project, which without it takes 28ms to draw screen. With it, it takes < 1 ms!

The good thing about Direct2D is that it can be easily combined with GDI, GDI+, or Direct3D.

You Need

Overview

Direct2D is an ActiveX object that can draw to an HWND or to an HDC. For this to work, the steps are:

  • Instantiate an ID2D1Factory by creating D2D1CreateFactory() from D2D1.DLL (I suggest you link to this dynamically using LoadLibrary/GetProcAddress so your code runs in any Windows version).
  • Call ID2D1Factory :: CreateHwndRenderTarget() to target an HWND, or call ID2D1Factory :: CreateDCRenderTarget() to target an HDC.
  • Use the returned ID2D1RenderTarget interface to draw:
    • Call ID2D1RenderTarget::BeginDraw()
    • Draw stuff
    • Call ID2D1RenderTarget::EndDraw()

Selecting a Brush to Draw

The following function creates a Direct2D Brush from a given ARGB color:

ID2D1SolidColorBrush* GetD2SolidBrush(ID2D1RenderTarget* pTR,unsigned long c)
{
    if (!pRT)
        return 0;
    ID2D1SolidColorBrush* b = 0;
    D2D1_COLOR_F cc;
    cc.a = GetAValue(c)/255.0f;
    if (cc.a == 0)
        cc.a = 1.0f; 
    cc.r = GetRValue(c)/255.0f;
    cc.g = GetGValue(c)/255.0f;
    cc.b = GetBValue(c)/255.0f;
    pRT->CreateSolidColorBrush(cc,&b);
    return b;
}

Draw Lines, Rectangles, Ellipses

Use the functions exported from ID2D1RenderTarget, like DrawEllipse(), DrawRectangle(), DrawLine(), FillEllipse(), and FillRectangle().

Draw Polygons

The following function demonstrates how to create a polygon from a given set of POINT*s. You simply create a path geometry (ID2DFactory :: CreatePathGeometry()), open it (ID2D1PathGeometry::Open()) to get its sink, call ID2D1GeometrySink ::BeginFigure, add items (Line, Lines, Bezier curves etc.), call ID2D1GeometrySink::EndFigure(), then ID2D1PathGeometry::Close(), and finally, you pass the geometry object to ID2D1RenderTarget::DrawGeometry.

void Polygon(POINT*p,int n,bool Close)
{    
    // Convert POINT to D2D1_POINT_2F
    D2D1_POINT_2F* pt =  new D2D1_POINT_2F[n];
    for(int i = 0 ; i < n ; i++)
    {
        pt[i].x = (FLOAT)p[i].x;
        pt[i].y = (FLOAT)p[i].y;
    }
 
    ID2D1SolidColorBrush* b = GetD2SolidBrush(c);
    ID2D1PathGeometry* pg = 0;
    ID2D1GeometrySink* pgs = 0;
    pD2DFactory->CreatePathGeometry(&pg);
    if (pg)
    {
        pg->Open(&pgs);
        if (pgs)
        {
            D2D1_POINT_2F fb;
            fb.x = (FLOAT)pt[0].x;
            fb.y = (FLOAT)pt[0].y;
            // Use D2D1_FIGURE_BEGIN_FILLED for filled
            D2D1_FIGURE_BEGIN fg = D2D1_FIGURE_BEGIN_HOLLOW;
            D2D1_FIGURE_END fe;
            if (Close)
                fe = D2D1_FIGURE_END_CLOSED;
            else 
                fe = D2D1_FIGURE_END_OPEN;
            pgs->BeginFigure(fb,fg);
            for(int i = 1 ; i < n ; i++)
            {
                D2D1_POINT_2F fu;
                fu.x = pt[i].x;
                fu.y = pt[i].y;
                pgs->AddLine(fu);
            }
            pgs->EndFigure(fe);
            pgs->Close();
            pgs->Release();
        }
        if (b)
            pRT->DrawGeometry(pg,b,1);
        pg->Release();
        if (b)
            b->Release();
        delete[] pt;
    }

Draw Images

Direct2D loads a bitmap from a Windows Imaging component. The simple function below will use an existing HBITMAP (which must be a 32-bit one!) to draw:

void Image(int x1,int y1,HBITMAP hB,float Op = 1.0f);
{
    BITMAP bo;
    GetObject(hB,sizeof(bo),&bo);
    WICBitmapAlphaChannelOption ao = WICBitmapUseAlpha;
    IWICBitmap* wb = 0;
    pImageFactory->CreateBitmapFromHBITMAP(hB,0,ao,&wb);
    if (!wb)
        return;
    ID2D1Bitmap* b = 0;
    pRT->CreateBitmapFromWicBitmap(wb,0,&b);
    if (!b)
    {
        // Convert it
        IWICFormatConverter* spConverter = 0;
        pImageFactory->CreateFormatConverter(&spConverter);
        if (spConverter)
        {
            spConverter->Initialize(wb,GUID_WICPixelFormat32bppPBGRA, 
              WICBitmapDitherTypeNone,NULL,0.f, 
              WICBitmapPaletteTypeMedianCut);
            pRT->CreateBitmapFromWicBitmap(spConverter,0,&b);
            spConverter->Release();
        }
        wb->Release();
    }
    if (b)
    {
        D2D1_RECT_F r;
        r.left = (FLOAT)x1;
        r.top = (FLOAT)y1;
        r.right = (FLOAT)(x1 + bo.bmWidth);
        r.bottom = (FLOAT)(y1 + bo.bmHeight);
        pRT->DrawBitmap(b,r,Op);
        b->Release();
    }
}

Draw Text

Text in Direct2D is written through DirectWrite (to be continued...), another new Direct* API in Windows 7. The steps are:

  • Have the IDWriteFactory (DWriteCreateFactory from DWRITE.DLL).
  • Set the font by calling IDWriteFactory::CreateTextFormat() with font parameters, to get the IDWriteTextFormat*. (See my SetFont() function.)
  • Call IDWriteTextFormat members to set the format - my code calls SetTextAlignment/SetParagraphAlignment to set the alignment.
  • Call ID2D1RenderTarget::DrawText.

To measure the text dimensions, you have to create IDWriteTextLayout* (IDWriteFactory::CreateTextLayout) which represents the formatted text's attributes (for more, see my TextSize() function).

The Code

The code is part of my cross-platform drawing library, which is not complete (GDI/GDI+ stuff has been stripped from it to focus on the D2D code). You can use the mydraw.h functions:

// Members
void Line(int x1,int y1,int x2,int y2,int th = 1,unsigned long c = AXRGB(0xFF,0,0,0), 
          unsigned int PenStyle = PS_SOLID);
void Rect(RECT&ar,int th = 1,unsigned long c = AXRGB(0xFF,0,0,0), 
          unsigned int PenStyle = PS_SOLID,bool Elp = false);
void FilledRect(RECT&ar,unsigned long c = AXRGB(0xFF,0,0,0),bool Elp = false);
void Polygon(POINT*p,int n,bool Close,int th = 1, 
             unsigned long c = AXRGB(0xFF,0,0,0), 
             unsigned int PenStyle = PS_SOLID);
void FilledPolygon(POINT*p,int n,bool Close, 
                   unsigned long c = AXRGB(0xFF,0,0,0));
void Ellipse(RECT&ar,int th = 1,unsigned long c = AXRGB(0xFF,0,0,0), 
             unsigned int PenStyle = PS_SOLID);
void FilledEllipse(RECT&ar,unsigned long c = AXRGB(0xFF,0,0,0));
void Rect(int x1,int y1,int wi,int he,int th = 1, 
          unsigned long c = AXRGB(0xFF,0,0,0), 
          unsigned int PenStyle = PS_SOLID,bool Elp = false);
void Ellipse(int x1,int y1,int wi,int he,int th = 1, 
             unsigned long c = AXRGB(0xFF,0,0,0), 
             unsigned int PenStyle = PS_SOLID);
void FilledRect(int x1,int y1,int wi,int he, 
                unsigned long c = AXRGB(0xFF,0,0,0),bool Elp = false);
void FilledEllipse(int x1,int y1,int wi,int he, 
                   unsigned long c = AXRGB(0xFF,0,0,0));
unsigned long TextSize(const wchar_t* txt,int l, 
                       unsigned long al,unsigned long lal);
void DrawText(const wchar_t* txt,int l,int x,int y,int wi,int he, 
              unsigned long al,unsigned long lal, 
              unsigned long c = AXRGB(0xFF,0,0,0),int BreakMode = 1);
void DrawText(const wchar_t* txt,int l,RECT&,unsigned long al, 
              unsigned long lal,unsigned long c = AXRGB(0xFF,0,0,0), 
              int BreakMode = 1);
void SetFont(HFONT hF1);
void Image(int x1,int y1,LPWSTR fil,float Op = 1.0f);
void Image(int x1,int y1,HINSTANCE h,LPWSTR n, 
           LPWSTR typ = RT_BITMAP,float Op = 1.0f);
void Image(int x1,int y1,HBITMAP hB,float Op = 1.0f, bool HasAlpha = 0);
void Image(int x1,int y1,Gdiplus::Bitmap* b,bool HasAlpha = 0);
HRESULT LoadResourceImage(HINSTANCE h,PCWSTR resourceName, 
        PCWSTR resourceType,void**ppBitmap);
HRESULT LoadFileImage(const LPWSTR f,void**ppBitmap);

to draw stuff. Load test32.sln and compile it, and select the drawing mode from the menu. A performance counter is also included.

Todo...

  • Add interoperability between Direct2D, Direct3D , GDI+, and GDI
  • Add brush strokes
  • Add complex geometry

History

  • 08 - 5 - 2009: Update for RC1.
  • 25 - 4 - 2009: First release.

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