본문 바로가기

Library/Windows Programming

Direct2D : Your New Windows 2D Rendering API

윈도우 1.0이 세상에 나온지 25년이 지났지만, 아직도 초기와 비교해 크게 달라진 것이 없으며, 현역으로 사용되고 있는 것을 꼽으라면 단연 GDI일 것이다. GDI는 XP까지도 윈도우의 2D 프레젠테이션 엔진이었고, GDI+라는 확장된 버전이 작성되기도 했지만 근본적으로 2D 클리핑 기반 엔진이라는 한계를 가지고 있었다.

GDI는 윈도우를 서로 겹칠 때 그 단점을 가장 뚜렷하게 관찰할 수 있다. 예를 들어 투명 효과를 구현하다면 복잡한 일이 될 수 밖에 없다. 직접 전체 화면에 대한 DC를 얻어내어 해당 위치에 대한 픽셀값을 계산해야 했기 때문이다. 또, 근본적으로 래스터라이즈된 이미지를 기반으로 동작하기 때문에, 확대, 축소 시에 이미지 손실이 발생한다. .NET에 탑재된 WPF는 벡터 기반으로 작성되었기 때문에 이런 문제를 극복했지만, .NET 환경이 아니라면 쉽게 접근하기 어렵다는 문제가 있다.

.NET은 다른 언어들에 대해서 개방적인 환경을 가지고 있지만, 안타깝게도 .NET의 언어 중립적인 특성은, 나쁘게 말하면 각 개발 언어들의 특성을 제한하는 것이다. 특히, C++의 경우, 컴파일 시점에 코드를 조작하는 템플릿을 제대로 사용할 수 없다. C#의 generics가 템플릿 대용으로 쓸 수 있다고는 하지만, C++ 템플릿은 근본적으로 바이트 코드로 컴파일되는 .NET과 잘 맞지 않는다. generics로는 제대로 메타 프로그래밍을 할 수 없다.

여튼, 이런 저런 이유로 .NET 환경으로 넘어가지 않는 C/C++를 기반으로 하는 윈도우 프로그래머들에게 .NET의 WPF는 '좋지만 쓰기 힘든' 무엇이었다. Direct3D는 일반적인 2D 렌더링에 사용하기에는 너무 복잡한 물건이었고, .NET 환경에 포함된 WPF는 .NET이 아니라면, 쉽고, 빠르게 작성할 수 있다는 장점을 살리기 힘들다. 이 두 극단 사이에서 대부분의 C/C++ 윈도우  프로그래머들은 '낡았지만 그런대로 굴러가는' GDI/GDI+를 사용하여 코드를 작성해왔다. 그러나, 이런 이들을 위해 멋진 물건 하나가 등장했는데, 그것이 Direct2D이다. Direct2D는 윈도우 비스타 SP2 이상, 윈도우 7 환경에서 사용 가능하며, GDI를 대체하는 새로운 2D 프리젠테이션 엔진이다. 기존의 GDI, GDI+와 잘 동작하고, 무엇보다 Direct3D 기반 위에서 작성되었기 때문에 GDI보다 강력하게 하드웨어 가속을 지원받을 수 있다.




Direct2D는 COM 기반이기는 하지만, Direct3D와 마찬가지로 경량화된 COM 스펙을 자체 구현하고 있기 때문에, COM 컴포넌트를 초기화하는 코드를 직접 작성하지 않아도 된다. 이것은 COM 아파트먼트나 프락시와 같은 것을 신경쓰지 않아도 된다는 뜻이다. Direct3D를 사용하는 것과 마찬가지로 단순히 인터페이스를 호출하고, 그 수명을 적절히 관리해주기만 하면 그만이다. 이런 점에서 ATL/WTL과 궁합이 잘 맞는 것도 매우 마음에 든다.

Direct2D는 디바이스와 자원을 초기화하고, WM_PAINT 메세지를 받을 때 렌더링 코드를 호출해주면 그만인데, 이 과정에서 굳이 DC를 사용할 필요는 없다. Direct3D처럼, 렌더 타겟을 지정하고 여기에 렌더링 결과물을 출력하면 된다. Direct2D는 그 출력 품질에 있어서도 기존의 GDI/GDI+와 비교할 수 없이 훌륭하다. 다음은 MSDN에서 예를 든 샘플이다.




윈도우 UI 코드는 GDI 개체를 다루는 코드가 많은 부분을 차지하는데, Direct2D를 사용함으로서 크게 달라지는 것은 바로 이 부분이다. 즉, Direct3D를 사용하는 것과 비슷하게 렌더링 함수를 구성하고, WM_PAINT 메세지를 받으면 이 렌더링 함수를 호출한다. 물론, 렌더링 함수에 진입하기 전에 ID2D1Factory라는 인터페이스를 사용하여 디바이스와 자원을 생성해야 한다. 렌더링 코드는 이 생성된 개체들을 사용하여 함수를 호출하며, BeginDraw(), EndDraw()를 사용하여 렌더링 코드를 감싸주어야 한다. Direct2D가 Direct3D에서 파생되었기 때문에, 이들은 이와 같은 비슷한 면을 가지고 있다.

Direct2D를 사용하는 것은 복잡할 것 같지만 ATL/WTL과 같은 현대적인 윈도우 라이브러리, 특히 COM 인터페이스를 관리할 수 있는 스마트 포인터를 갖춘 환경에서는 코드 작성이 대단히 간편하다. 특히, 윈도우 프로그램에서 난잡했던 GDI 코드들을 제거하고 다른 COM 활용 코드들과 일관성을 갖출 수 있기 때문에 훨씬 나은 것 같다. MSDN에서 제시하는 가이드를 따라 샘플 코드를 작성해봤다. 다음은 그 결과이다.




클릭하면 좀 더 큰 화면을 볼 수 있다. 확실히, GDI를 사용했을 때보다 계단 현상이 줄어든 것을 알 수 있으며, 화면의 갱신 속도, 반응 속도 모두 과거와 비교할 바가 아니다.




그러나, 새로운 기술은 마냥 좋기만 한 것이 아니다. Direct2D는 그 구성 방식부터 완전히 새롭기 때문에, 예전의 프로그램들은 Direct2D가 제공하는 혜택을 볼 수 없다. Direct2D를 제대로 활용하기 위해서는 새로 컴파일을 하고 링크하는 것으로 충분하지 않으며, 코드를 완전히 재작성해야 한다.

과거의, 작성된지 20년을 바라보는 코드가 아무런 수정없이 현대의 하드웨어를 제대로 활용할 수 없는 노릇이다. 사실, 윈도우가 바이너리 수준에서 상당한 수준의 하위 호환성을 제공한다는 것은 그만큼의 메모리를 추가로 요구하고, 구현 복잡도는 더욱 상승한다는 뜻이다. 실제로, 윈도우 커널 모델은 극도록 복잡하다. 이런 상황에서 대부분의 소프트웨어 호환성을 제공하는 것은 기적에 가깝다(좋은 뜻이 아니다). 유닉스가 가볍고 단순하게 코드를 관리할 수 있는 것은, 인터페이스를 중시하고 소스 코드를 공유하는 태생적인 문화 때문이다. 따라서, 바이너리 그 자체의 재사용성을 추구하는 윈도우와 단순 비교하는 것은 무리가 있다.

물론, 윈도우 자체가 운영체제 전체를 관통하는 메타포가 없으며, 이로 인해서 윈도우 세대에 따라 개발 방법이 완전히 뒤바뀌는 것은 가장 심각한 문제이다. 그러나, OLE 2.0에 사용되었던 COM과, 이를 기반 기술로 응용하는 .NET은 확고한 윈도우의 개발 방법론으로 자리 잡은 듯 하다. 이를 바탕으로 과거의 유산들을 털어내는 과정을 무조건 비난할 일은 아닌 듯 하다.

여담으로, Direct2D를 적극 활용한다고 공언한, 초기 Direct2D의 대표적인 구현이 될 IE9은 무척 기대된다. 웹 브라우저의 렌더링 코드는 종류를 막론하고 스파게티 잡탕식으로 유명한데, Firefox의 카이로(Cairo)에 맞서 Direct2D는 어떻게 활용될지 궁금하다.