간단한 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를 사용하도록 애플리케이션을 설정합니다. 또한 이 자습서의 뒷부분에서 사용할 메서드 및 데이터 멤버를 선언합니다.
애플리케이션 헤더 파일에 자주 사용되는 다음과 같은 헤더를 포함합니다.
// 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>
인터페이스를 해제하기 위한 추가 함수와 모듈의 기본 주소를 오류 처리 및 검색하기 위한 매크로를 선언합니다.
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
클래스 초기화, 리소스 만들기 및 삭제, 메시지 루프 처리, 콘텐츠 렌더링 및 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 ); };
클래스 멤버로서 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 리소스를 만듭니다.
클래스 구현 파일에서 클래스 생성자 및 소멸자를 구현합니다. 생성자는
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); }
메시지를 변환하고 디스패치하는 DemoApp::RunMessageLoop 메서드를 구현합니다.
void DemoApp::RunMessageLoop() { MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } }
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; }
애플리케이션 진입점 역할을 하는 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는 애플리케이션 기간 동안 지속될 수 있는 디바이스 독립적 리소스와 디바이스 종속 리소스의 두 가지 유형의 리소스를 제공합니다. 디바이스 종속 리소스는 특정 렌더링 디바이스와 연결되며 해당 디바이스가 제거되면 작동이 중단됩니다.
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; }
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);
렌더링 대상을 사용하여 회색 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 ); }
이 메서드는 반복적으로 호출되므로
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; }
DemoApp::D iscardDeviceResources 메서드를 구현합니다. 이 메서드에서는 DemoApp::CreateDeviceResources 메서드에서 만든 렌더링 대상과 두 개의 브러시를 해제합니다.
void DemoApp::DiscardDeviceResources() { SafeRelease(&m_pRenderTarget); SafeRelease(&m_pLightSlateGrayBrush); SafeRelease(&m_pCornflowerBlueBrush); }
4부: Direct2D 콘텐츠 렌더링
이 부분에서는 창 프로시저, OnRender 메서드(콘텐츠를 그리는 메서드) 및 OnResize 메서드(창 크기가 조정될 때 렌더링 대상의 크기를 조정함)를 구현합니다.
DemoApp::WndProc 메서드를 구현하여 창 메시지를 처리합니다. WM_SIZE 메시지의 경우 DemoApp::OnResize 메서드를 호출하고 새 너비와 높이를 전달합니다. WM_PAINT 및 WM_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; }
DemoApp::OnRender 메서드를 구현합니다. 먼저 HRESULT정의합니다. 그런 다음 CreateDeviceResource 메서드를 호출합니다. 이 메서드는 창을 그릴 때마다 호출됩니다. 3부 4단계에서 렌더링 대상이 이미 있는 경우 메서드가 작업을 수행하지 못하도록 하는
if
문을 추가했습니다.HRESULT DemoApp::OnRender() { HRESULT hr = S_OK; hr = CreateDeviceResources();
CreateDeviceResource 메서드가 성공했는지 확인합니다. 그렇지 않은 경우 그리기를 하지 마십시오.
if (SUCCEEDED(hr)) {
방금 추가한
if
문 내에서 렌더링 대상의 BeginDraw 메서드를 호출하여 그리기를 시작합니다. 렌더링 대상의 변환을 ID 매트릭스로 설정하고 창을 지웁니다.m_pRenderTarget->BeginDraw(); m_pRenderTarget->SetTransform(D2D1::Matrix3x2F::Identity()); m_pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White));
그리기 영역의 크기를 검색합니다.
D2D1_SIZE_F rtSize = m_pRenderTarget->GetSize();
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 ); }
화면 가운데에 있는 두 개의 사각형 기본 형식을 만듭니다.
// 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 );
렌더링 대상의 FillRectangle 메서드를 사용하여 회색 브러시로 첫 번째 사각형의 내부를 그립니다.
// Draw a filled rectangle. m_pRenderTarget->FillRectangle(&rectangle1, m_pLightSlateGrayBrush);
렌더링 대상의 DrawRectangle 메서드를 사용하여 옥수수 꽃 파랑 브러시로 두 번째 사각형의 윤곽선을 그립니다.
// Draw the outline of a rectangle. m_pRenderTarget->DrawRectangle(&rectangle2, m_pCornflowerBlueBrush);
렌더링 대상의 EndDraw 메서드를 호출합니다. EndDraw 메서드는 그리기 작업이 성공했는지 여부를 나타내는 HRESULT 반환합니다. 3단계에서 시작한
if
문의 범위를 종료합니다.hr = m_pRenderTarget->EndDraw(); }
EndDraw에서 반환된 HRESULT을 확인합니다. 렌더링 대상을 다시 만들어야 한다고 표시되면 DemoApp::D iscardDeviceResources 메서드를 호출하여 해제합니다. 다음에 창에서 WM_PAINT 또는 WM_DISPLAYCHANGE 메시지를 받을 때 다시 만들어지게 됩니다.
if (hr == D2DERR_RECREATE_TARGET) { hr = S_OK; DiscardDeviceResources(); }
HRESULT반환하고 메서드의 범위를 닫습니다.
return hr; }
렌더링 대상의 크기를 창의 새 크기로 조정하도록 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.h
및 d2d1.lib
을 Windows SDK에서 찾을 수 있습니다.
요약
이 자습서에서는 Direct2D 리소스를 만들고 기본 셰이프를 그리는 방법을 알아보았습니다. 또한 리소스 생성을 최소화하여 성능을 향상시키기 위해 애플리케이션을 구성하는 방법도 알아보았습니다.