Create a simple Direct2D application

Posted by WilliamNz on Sat, 19 Feb 2022 06:38:45 +0100

This article will lead you through the creation of the DemoApp class, which will create a window and draw a mesh and two rectangles using Direct2D. In this tutorial, you will learn how to create Direct2D resources and draw basic images. You will also learn how to build an application and improve its performance by minimizing the creation of resources.

Demo download path: https://download.csdn.net/download/xiaoyafang123/17507434

This tutorial includes the following sections:

  • Part 1: creating the declaration part of the DemoApp class
  • Part 2: methods of implementing DemoApp class
  • Division 3: resources required to create Direct2D
  • Part 4: drawing graphics through Direct2D
  • summary

After completing the above operations, you will get the following graphics:

Part 1: creating the declaration part of the DemoApp class

In this section, you will first add the header files and macro definitions that Direct2D depends on. Then you also need to declare the data members and methods of DemoApp class.

1. Add necessary header files

// Windows Header Files:
    #include <windows.h>

    // C RunTime Header Files:
    #include <stdlib.h>
    #include <malloc.h>
    #include <memory.h>
    #include <wchar.h>
    #include <math.h>

    #include <d2d1.h>
    #include <d2d1helper.h>
    #include <dwrite.h>
    #include <wincodec.h>

2. Define the release function and necessary macros

template<class Interface>
    inline void SafeRelease(
        Interface **ppInterfaceToRelease
        )
    {
        if (*ppInterfaceToRelease != NULL)
        {
            (*ppInterfaceToRelease)->Release();

            (*ppInterfaceToRelease) = NULL;
        }
    }


    #ifndef Assert
    #if defined( DEBUG ) || defined( _DEBUG )
    #define Assert(b) do {if (!(b)) {OutputDebugStringA("Assert: " #b "\n");}} while(0)
    #else
    #define Assert(b)
    #endif //DEBUG || _DEBUG
    #endif



    #ifndef HINST_THISCOMPONENT
    EXTERN_C IMAGE_DOS_HEADER __ImageBase;
    #define HINST_THISCOMPONENT ((HINSTANCE)&__ImageBase)
    #endif

3. Declare methods for initializing classes, creating and discarding resources, handling message loops, rendering content and windows procedures.

class DemoApp
    {
    public:
        DemoApp();
        ~DemoApp();

        // Register the window class and call methods for instantiating drawing resources
        HRESULT Initialize();

        // Process and dispatch messages
        void RunMessageLoop();

    private:
        // Initialize device-independent resources.
        HRESULT CreateDeviceIndependentResources();

        // Initialize device-dependent resources.
        HRESULT CreateDeviceResources();

        // Release device-dependent resource.
        void DiscardDeviceResources();

        // Draw content.
        HRESULT OnRender();

        // Resize the render target.
        void OnResize(
            UINT width,
            UINT height
            );

        // The windows procedure.
        static LRESULT CALLBACK WndProc(
            HWND hWnd,
            UINT message,
            WPARAM wParam,
            LPARAM lParam
            );

    
};

4. Declare the pointers of one ID2D1Factory object, one ID2D1HwndRenderTarget object and two ID2D1SolidColorBrush objects as class members.

private:
    HWND m_hwnd;
    ID2D1Factory* m_pDirect2dFactory;
    ID2D1HwndRenderTarget* m_pRenderTarget;
    ID2D1SolidColorBrush* m_pLightSlateGrayBrush;
    ID2D1SolidColorBrush* m_pCornflowerBlueBrush;

Part 2: methods of implementing DemoApp class

In this section, you will implement the DemoApp constructor and destructor, its initialization and message loop methods, and the WinMain function. Most of these methods look the same as those in other Win32 applications. The only exception is the initialization method, which calls the CreateDeviceIndependentResources method (which you will see in the next section), which creates several Direct2D resources.

1. Implement the constructor and destructor of the class. Constructor initializes its members to NULL. The destructor frees any interface that is a member of the class.

DemoApp::DemoApp() :
        m_hwnd(NULL),
        m_pDirect2dFactory(NULL),
        m_pRenderTarget(NULL),
        m_pLightSlateGrayBrush(NULL),
        m_pCornflowerBlueBrush(NULL)
    {
    }

    
DemoApp::~DemoApp()
    {
        SafeRelease(&m_pDirect2dFactory);
        SafeRelease(&m_pRenderTarget);
        SafeRelease(&m_pLightSlateGrayBrush);
        SafeRelease(&m_pCornflowerBlueBrush);

    }

2. Implement the DemoApp::RunMessageLoop method

void DemoApp::RunMessageLoop()
    {
        MSG msg;

        while (GetMessage(&msg, NULL, 0, 0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

3. Use the Initialize method to create a window, display the window, and call the DemoApp::CreateDeviceIndependentResources method. You will implement the CreateDeviceIndependentResources method in the next section.

HRESULT DemoApp::Initialize()
    {
        HRESULT hr;

        // Initialize device-indpendent resources, such
        // as the Direct2D factory.
        hr = CreateDeviceIndependentResources();

        if (SUCCEEDED(hr))
        {
            // Register the window class.
            WNDCLASSEX wcex = { sizeof(WNDCLASSEX) };
            wcex.style         = CS_HREDRAW | CS_VREDRAW;
            wcex.lpfnWndProc   = DemoApp::WndProc;
            wcex.cbClsExtra    = 0;
            wcex.cbWndExtra    = sizeof(LONG_PTR);
            wcex.hInstance     = HINST_THISCOMPONENT;
            wcex.hbrBackground = NULL;
            wcex.lpszMenuName  = NULL;
            wcex.hCursor       = LoadCursor(NULL, IDI_APPLICATION);
            wcex.lpszClassName = L"D2DDemoApp";

            RegisterClassEx(&wcex);


            // Because the CreateWindow function takes its size in pixels,
            // obtain the system DPI and use it to scale the window size.
            FLOAT dpiX, dpiY;

            // The factory returns the current system DPI. This is also the value it will use
            // to create its own windows.
            m_pDirect2dFactory->GetDesktopDpi(&dpiX, &dpiY);


            // Create the window.
            m_hwnd = CreateWindow(
                L"D2DDemoApp",
                L"Direct2D Demo App",
                WS_OVERLAPPEDWINDOW,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                static_cast<UINT>(ceil(640.f * dpiX / 96.f)),
                static_cast<UINT>(ceil(480.f * dpiY / 96.f)),
                NULL,
                NULL,
                HINST_THISCOMPONENT,
                this
                );
            hr = m_hwnd ? S_OK : E_FAIL;
            if (SUCCEEDED(hr))
            {
                ShowWindow(m_hwnd, SW_SHOWNORMAL);
                UpdateWindow(m_hwnd);
            }
        }

        return hr;
    }

4. Create the WinMain method as the entry point of the application. Initialize an instance of the DemoApp class and start its message loop.

int WINAPI WinMain(
        HINSTANCE /* hInstance */,
        HINSTANCE /* hPrevInstance */,
        LPSTR /* lpCmdLine */,
        int /* nCmdShow */
        )
    {
        // Use HeapSetInformation to specify that the process should
        // terminate if the heap manager detects an error in any heap used
        // by the process.
        // The return value is ignored, because we want to continue running in the
        // unlikely event that HeapSetInformation fails.
        HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);

        if (SUCCEEDED(CoInitialize(NULL)))
        {
            {
                DemoApp app;

                if (SUCCEEDED(app.Initialize()))
                {
                    app.RunMessageLoop();
                }
            }
            CoUninitialize();
        }

        return 0;
    }

Division 3: resources required to create Direct2D

In this section, you will create Direct2D resources for drawing. Direct2D provides two types of resources: device independent resources that can be retained throughout the application life cycle; Equipment related resources need to be bound with specific equipment. If the equipment is removed, the resources will become invalid.

1. Implement the DemoApp::CreateDeviceIndependentResources method. In this method, a device independent asset (ID2D1Factory) is created, which is used to create other Direct2D assets. Use m_pDirect2DdFactory class members.

HRESULT DemoApp::CreateDeviceIndependentResources()
    {
        HRESULT hr = S_OK;

        // Create a Direct2D factory.
        hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &m_pDirect2dFactory);

        return hr;
    }

2. Implement the DemoApp::CreateDeviceResources method. This method creates the device related resources of the window: one render target and two brushes. Obtain the client area of the window through the known window handle, and then create a client area of the same size ID2D1HwndRenderTarget Render the target object and use M_ The prendertarget member variable holds the render target object.

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
  );

3. Create a gray ID2D1SolidColorBrush object and a daisy blue ID2D1SolidColorBrush object using the render target object.

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

4. Because this method will be called repeatedly, add an if statement to check whether the rendering target object (m_pRenderTarget) already exists. The following code shows the complete create devices method.

HRESULT DemoApp::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
                    );
            }
            if (SUCCEEDED(hr))
            {
                // Create a blue brush.
                hr = m_pRenderTarget->CreateSolidColorBrush(
                    D2D1::ColorF(D2D1::ColorF::CornflowerBlue),
                    &m_pCornflowerBlueBrush
                    );
            }
        }

        return hr;
    }

5. Implement the DemoApp::DiscardDeviceResources method. In this method, the render target object and two brush objects created in the DemoApp::CreateDeviceResources method are released.

    void DemoApp::DiscardDeviceResources()
    {
        SafeRelease(&m_pRenderTarget);
        SafeRelease(&m_pLightSlateGrayBrush);
        SafeRelease(&m_pCornflowerBlueBrush);
    }

Part 4: drawing graphics through Direct2D

In this section, you will implement the window procedure, the OnRender method to draw the content, and the OnResize method to resize the rendering target object when the window size changes.

1. Implement DemoApp::WndProc method for processing window messages: WM received_ When the size message, call the DemoApp::OnResize method and pass the new width and height. Received WM_PAINT and WM_ When the displaychange message, the DemoApp::OnRender method is called to draw the window. You can see the implementation of OnRender and OnResize methods in the following steps.

    LRESULT CALLBACK DemoApp::WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM                                                     
    lParam)
    {
        LRESULT result = 0;

        if (message == WM_CREATE)
        {
            LPCREATESTRUCT pcs = (LPCREATESTRUCT)lParam;
            DemoApp *pDemoApp = (DemoApp *)pcs->lpCreateParams;

            ::SetWindowLongPtrW(
                hwnd,
                GWLP_USERDATA,
                reinterpret_cast<LONG_PTR>(pDemoApp)
                );

            result = 1;
        }
        else
        {
            DemoApp *pDemoApp = reinterpret_cast<DemoApp *>(static_cast<LONG_PTR>(
                ::GetWindowLongPtrW(
                    hwnd,
                    GWLP_USERDATA
                    )));

            bool wasHandled = false;

            if (pDemoApp)
            {
                switch (message)
                {
                case WM_SIZE:
                    {
                        UINT width = LOWORD(lParam);
                        UINT height = HIWORD(lParam);
                        pDemoApp->OnResize(width, height);
                    }
                    result = 0;
                    wasHandled = true;
                    break;

                case WM_DISPLAYCHANGE:
                    {
                        InvalidateRect(hwnd, NULL, FALSE);
                    }
                    result = 0;
                    wasHandled = true;
                    break;

                case WM_PAINT:
                    {
                        pDemoApp->OnRender();
                        ValidateRect(hwnd, NULL);
                    }
                    result = 0;
                    wasHandled = true;
                    break;

                case WM_DESTROY:
                    {
                        PostQuitMessage(0);
                    }
                    result = 1;
                    wasHandled = true;
                    break;
                }
            }

            if (!wasHandled)
            {
                result = DefWindowProc(hwnd, message, wParam, lParam);
            }
        }

        return result;
    }

2. Implement the DemoApp::OnRender method. First, define an HRESULT. Then call the CreateDeviceResource method. This method is called every time a window is drawn. Recall that in step 4 of Part 3, you added an IF statement to prevent the method from performing any work if the render target object already exists.

    HRESULT DemoApp::OnRender()
    {
        HRESULT hr = S_OK;

        hr = CreateDeviceResources();

3. Verify whether CreateDeviceResource method is called successfully. If not, do not draw any graphics.

        if (SUCCEEDED(hr))
        {

4. In the If statement just now, start rendering by calling the BeginDraw method of the rendering target object, then set a unit matrix to the rendering target object for transformation, and empty the window.

            m_pRenderTarget->BeginDraw();

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

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

5. Get the size of the drawing area.

D2D1_SIZE_F rtSize = m_pRenderTarget->GetSize();

6. Through a for loop and rendering the target object DrawLine Method to draw a grid background.

            // Draw a grid background.
            int width = static_cast<int>(rtSize.width);
            int height = static_cast<int>(rtSize.height);

            for (int x = 0; x < width; x += 10)
            {
                m_pRenderTarget->DrawLine(
                    D2D1::Point2F(static_cast<FLOAT>(x), 0.0f),
                    D2D1::Point2F(static_cast<FLOAT>(x), rtSize.height),
                    m_pLightSlateGrayBrush,
                    0.5f
                    );
            }

            for (int y = 0; y < height; y += 10)
            {
                m_pRenderTarget->DrawLine(
                    D2D1::Point2F(0.0f, static_cast<FLOAT>(y)),
                    D2D1::Point2F(rtSize.width, static_cast<FLOAT>(y)),
                    m_pLightSlateGrayBrush,
                    0.5f
                    );
            }

7. Create two rectangular images in the center of the screen.

            // 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
                );

8. Use the to render the target object FillRectangle Method and gray brush object to fill the interior of the first rectangle.

            // Draw a filled rectangle.
            m_pRenderTarget->FillRectangle(&rectangle1, m_pLightSlateGrayBrush);

9. Use the to render the target object DrawRectangle Method and a daisy blue brush to draw the outline of the second rectangle.

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

10. Call EndDraw method of rendering target object. The EndDraw method returns an HRESULT to indicate whether the drawing operation is successful. End the if statement that started in step 3.

    hr = m_pRenderTarget->EndDraw();
 }

11. Check the HRESULT returned by EndDraw. If d2derr is returned_ RECREATE_ Target value, indicating that the rendering target object needs to be recreated. Call DemoApp::DiscardDeviceResources method to release it; It will receive WM in the next window_ Paint or WM_ Recreate when displaychange message.

        if (hr == D2DERR_RECREATE_TARGET)
        {
            hr = S_OK;
            DiscardDeviceResources();
        }

12. Return HRESULT and end the method.

    return hr;
}

13. Implement the DemoApp::OnResize method to adjust the rendering target object to the new size of the window.

    void DemoApp::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));
        }
    }

You have completed this tutorial.

Note: to use Direct2D, make sure your application contains d2d1 H header file, and added to d2d1 Lib library dependency. You can find d2d1 in the Windows SDK H and d2d1 lib .

summary

In this tutorial, you learned how to create Direct2D resources and draw basic graphics. You also learned how to build an application and improve its performance by minimizing the creation of resources.