다음을 통해 공유


간단한 Direct2D 애플리케이션 만들기

이 항목에서는 창을 만들고 Direct2D를 사용하여 콘텐츠를 그리는 DemoApp 클래스를 만드는 과정을 안내합니다. 이 자습서에서는 Direct2D 리소스를 만들고 기본 셰이프를 그리는 방법을 알아봅니다. 또한 리소스 생성을 최소화하여 성능을 향상시키기 위해 애플리케이션을 구성하는 방법도 알아봅니다.

자습서를 수행하려면 Microsoft Visual Studio를 사용하여 Win32 프로젝트를 만든 다음 주 애플리케이션 헤더 및 .cpp 파일의 코드를 이 자습서에 설명된 코드로 바꿀 수 있습니다.

또한 GitHub Simple Direct2D 애플리케이션 샘플 앱을 참조하세요.

메모

Direct2D를 사용하는 UWP(유니버설 Windows 플랫폼) 앱을 만들려면 Windows 8 항목에 대한 Direct2D 빠른 시작을 참조하세요.

Direct2D 콘텐츠를 만드는 데 사용할 수 있는 인터페이스에 대한 개요는 Direct2D API 개요참조하세요.

자습서가 완료되면 DemoApp 클래스는 다음 그림에 표시된 출력을 생성합니다.

그리드 배경에 있는 두 사각형의 삽화

1부: DemoApp 헤더 만들기

이 단계에서는 필요한 헤더와 매크로를 추가하여 Direct2D를 사용하도록 애플리케이션을 설정합니다. 또한 이 자습서의 뒷부분에서 사용할 메서드 및 데이터 멤버를 선언합니다.

  1. 애플리케이션 헤더 파일에 자주 사용되는 다음과 같은 헤더를 포함합니다.

    // 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. 인터페이스를 해제하기 위한 추가 함수와 모듈의 기본 주소를 오류 처리 및 검색하기 위한 매크로를 선언합니다.

    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. 클래스 초기화, 리소스 만들기 및 삭제, 메시지 루프 처리, 콘텐츠 렌더링 및 Windows 프로시저에 대한 메서드를 선언합니다.

    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. 클래스 멤버로서 ID2D1Factory 개체, ID2D1HwndRenderTarget 개체 및 두 개의 ID2D1SolidColorBrush 개체에 대한 포인터를 선언합니다.

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

2부: 클래스 인프라 구현

이 부분에서는 DemoApp 생성자 및 소멸자, 초기화 및 메시지 반복 메서드 및 WinMain 함수를 구현합니다. 이러한 메서드의 대부분은 다른 Win32 애플리케이션에서 찾은 방법과 동일하게 보입니다. 유일한 예외는 Initialize 메서드입니다. 이 메서드는 CreateDeviceIndependentResources 메서드(다음 부분에서 정의)를 호출하여 여러 Direct2D 리소스를 만듭니다.

  1. 클래스 구현 파일에서 클래스 생성자 및 소멸자를 구현합니다. 생성자는 NULL멤버를 초기화해야 합니다. 소멸자는 클래스 멤버로 저장된 모든 인터페이스를 해제해야 합니다.

    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. 메시지를 변환하고 디스패치하는 DemoApp::RunMessageLoop 메서드를 구현합니다.

    void DemoApp::RunMessageLoop()
    {
        MSG msg;
    
        while (GetMessage(&msg, NULL, 0, 0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    
  3. Initialize 메서드를 구현합니다. 이 메서드는 창을 만들고, 표시하고, DemoApp::CreateDeviceIndependentResources 메서드를 호출합니다. 다음 섹션에서 CreateDeviceIndependentResources 메서드를 구현합니다.

    HRESULT DemoApp::Initialize()
    {
        HRESULT hr;
    
        // Initialize device-independent 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);
    
            // In terms of using the correct DPI, to create a window at a specific size
            // like this, the procedure is to first create the window hidden. Then we get
            // the actual DPI from the HWND (which will be assigned by whichever monitor
            // the window is created on). Then we use SetWindowPos to resize it to the
            // correct DPI-scaled size, then we use ShowWindow to show it.
    
            m_hwnd = CreateWindow(
                L"D2DDemoApp",
                L"Direct2D demo application",
                WS_OVERLAPPEDWINDOW,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                0,
                0,
                NULL,
                NULL,
                HINST_THISCOMPONENT,
                this);
    
            if (m_hwnd)
            {
                // Because the SetWindowPos function takes its size in pixels, we
                // obtain the window's DPI, and use it to scale the window size.
                float dpi = GetDpiForWindow(m_hwnd);
    
                SetWindowPos(
                    m_hwnd,
                    NULL,
                    NULL,
                    NULL,
                    static_cast<int>(ceil(640.f * dpi / 96.f)),
                    static_cast<int>(ceil(480.f * dpi / 96.f)),
                    SWP_NOMOVE);
                ShowWindow(m_hwnd, SW_SHOWNORMAL);
                UpdateWindow(m_hwnd);
            }
        }
    
        return hr;
    }
    
  4. 애플리케이션 진입점 역할을 하는 WinMain 메서드를 구현합니다. DemoApp인스턴스를 초기화하고 메시지 루프를 시작합니다.

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

3부: Direct2D 리소스 만들기

이 부분에서는 그리는 데 사용하는 Direct2D 리소스를 만듭니다. Direct2D는 애플리케이션 기간 동안 지속될 수 있는 디바이스 독립적 리소스와 디바이스 종속 리소스의 두 가지 유형의 리소스를 제공합니다. 디바이스 종속 리소스는 특정 렌더링 디바이스와 연결되며 해당 디바이스가 제거되면 작동이 중단됩니다.

  1. DemoApp::CreateDeviceIndependentResources 메서드를 구현합니다. 이 메서드에서 다른 Direct2D 리소스를 만들기 위한 디바이스 독립적 리소스인 ID2D1Factory만듭니다. m_pDirect2DdFactory 클래스 멤버를 사용하여 팩터리를 저장합니다.

    HRESULT DemoApp::CreateDeviceIndependentResources()
    {
        HRESULT hr = S_OK;
    
        // Create a Direct2D factory.
        hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &m_pDirect2dFactory);
    
        return hr;
    }
    
  2. DemoApp::CreateDeviceResources 메서드를 구현합니다. 이 메서드는 창의 디바이스 종속 리소스, 렌더링 대상 및 두 개의 브러시를 만듭니다. 클라이언트 영역의 크기를 가져오고 창의 HWND에 렌더링할 수 있도록 동일한 크기의 ID2D1HwndRenderTarget 객체를 만듭니다. 렌더링 대상을 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);
    
  3. 렌더링 대상을 사용하여 회색 ID2D1SolidColorBrush 및 옥수수 꽃 파란색 ID2D1SolidColorBrush만듭니다.

    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. 이 메서드는 반복적으로 호출되므로 if 문을 추가하여 렌더링 대상(m_pRenderTarget)이 이미 있는지 확인합니다. 다음 코드는 CreateDeviceResources 메서드의 완전한 구현을 보여 줍니다.

    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. DemoApp::D iscardDeviceResources 메서드를 구현합니다. 이 메서드에서는 DemoApp::CreateDeviceResources 메서드에서 만든 렌더링 대상과 두 개의 브러시를 해제합니다.

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

4부: Direct2D 콘텐츠 렌더링

이 부분에서는 창 프로시저, OnRender 메서드(콘텐츠를 그리는 메서드) 및 OnResize 메서드(창 크기가 조정될 때 렌더링 대상의 크기를 조정함)를 구현합니다.

  1. DemoApp::WndProc 메서드를 구현하여 창 메시지를 처리합니다. WM_SIZE 메시지의 경우 DemoApp::OnResize 메서드를 호출하고 새 너비와 높이를 전달합니다. WM_PAINTWM_DISPLAYCHANGE 메시지의 경우 DemoApp::OnRender 메서드를 호출하여 창을 그립니다. 이후 단계에서 OnRender 메서드 및 OnResize 메서드를 구현할 것입니다.

    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. DemoApp::OnRender 메서드를 구현합니다. 먼저 HRESULT정의합니다. 그런 다음 CreateDeviceResource 메서드를 호출합니다. 이 메서드는 창을 그릴 때마다 호출됩니다. 3부 4단계에서 렌더링 대상이 이미 있는 경우 메서드가 작업을 수행하지 못하도록 하는 if 문을 추가했습니다.

    HRESULT DemoApp::OnRender()
    {
        HRESULT hr = S_OK;
    
        hr = CreateDeviceResources();
    
  3. CreateDeviceResource 메서드가 성공했는지 확인합니다. 그렇지 않은 경우 그리기를 하지 마십시오.

    if (SUCCEEDED(hr))
    {
    
  4. 방금 추가한 if 문 내에서 렌더링 대상의 BeginDraw 메서드를 호출하여 그리기를 시작합니다. 렌더링 대상의 변환을 ID 매트릭스로 설정하고 창을 지웁니다.

    m_pRenderTarget->BeginDraw();
    m_pRenderTarget->SetTransform(D2D1::Matrix3x2F::Identity());
    m_pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White));
    
  5. 그리기 영역의 크기를 검색합니다.

    D2D1_SIZE_F rtSize = m_pRenderTarget->GetSize();
    
  6. for 루프와 렌더링 대상의 DrawLine 메서드를 사용하여 그리드 배경을 그려 일련의 선을 그립니다.

    // 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. 화면 가운데에 있는 두 개의 사각형 기본 형식을 만듭니다.

    // 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. 렌더링 대상의 FillRectangle 메서드를 사용하여 회색 브러시로 첫 번째 사각형의 내부를 그립니다.

    // Draw a filled rectangle.
    m_pRenderTarget->FillRectangle(&rectangle1, m_pLightSlateGrayBrush);
    
  9. 렌더링 대상의 DrawRectangle 메서드를 사용하여 옥수수 꽃 파랑 브러시로 두 번째 사각형의 윤곽선을 그립니다.

    // Draw the outline of a rectangle.
    m_pRenderTarget->DrawRectangle(&rectangle2, m_pCornflowerBlueBrush);
    
  10. 렌더링 대상의 EndDraw 메서드를 호출합니다. EndDraw 메서드는 그리기 작업이 성공했는지 여부를 나타내는 HRESULT 반환합니다. 3단계에서 시작한 if 문의 범위를 종료합니다.

        hr = m_pRenderTarget->EndDraw();
    }
    
  11. EndDraw에서 반환된 HRESULT을 확인합니다. 렌더링 대상을 다시 만들어야 한다고 표시되면 DemoApp::D iscardDeviceResources 메서드를 호출하여 해제합니다. 다음에 창에서 WM_PAINT 또는 WM_DISPLAYCHANGE 메시지를 받을 때 다시 만들어지게 됩니다.

    if (hr == D2DERR_RECREATE_TARGET)
    {
        hr = S_OK;
        DiscardDeviceResources();
    }
    
  12. HRESULT반환하고 메서드의 범위를 닫습니다.

        return hr;
    }
    
  13. 렌더링 대상의 크기를 창의 새 크기로 조정하도록 DemoApp::OnResize 메서드를 구현합니다.

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

이제 자습서를 완료했습니다.

메모

Direct2D를 사용하려면 애플리케이션에 d2d1.h 헤더 파일이 포함되어 있는지 확인하고 d2d1.lib 라이브러리에 대해 컴파일합니다. d2d1.hd2d1.libWindows SDK에서 찾을 수 있습니다.

요약

이 자습서에서는 Direct2D 리소스를 만들고 기본 셰이프를 그리는 방법을 알아보았습니다. 또한 리소스 생성을 최소화하여 성능을 향상시키기 위해 애플리케이션을 구성하는 방법도 알아보았습니다.