다음을 통해 공유


간단한 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 애플리케이션에서 찾은 메서드와 동일합니다. 유일한 예외는 여러 Direct2D 리소스를 만드는 CreateDeviceIndependentResources 메서드(다음 부분에서 정의할)를 호출하는 Initialize 메서드입니다.

  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. 창을 만들고, 표시하고, DemoApp::CreateDeviceIndependentResources 메서드를 호출하는 Initialize 메서드를 구현합니다. 다음 섹션에서 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, 클래스의 instance 초기화하고 메시지 루프를 시작합니다.

    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. 이 메서드는 반복적으로 호출되므로 렌더링 대상(m_pRenderTarget)이 이미 있는지 여부를 검사 문을 추가 if 합니다. 다음 코드에서는 전체 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 콘텐츠 렌더링

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

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

    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. 루프 및 렌더링 대상의 DrawLine 메서드를 for 사용하여 그리드 배경을 그려 일련의 선을 그립니다.

    // 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단계에서 시작한 문의 scope 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를 반환하고 메서드의 scope 닫습니다.

        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 컴파일되는지 확인합니다. Windows SDK에서 및 d2d1.lib 를 찾을 d2d1.h 수 있습니다.

요약

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