[StartD2D-10] 디바이스 로스트( Device Lost ) 처리하기

DirectX 11 2011. 11. 23. 08:00 Posted by 알 수 없는 사용자

앞서 작성했던 예제 샘플들은 사실 완전한 상태의 코드가 아닙니다. ( 죄송...^^ )
바로 이 디바이스-로스트와 관련한 상황이 있어야, 안정성을 향상시킬 수 있습니다.

디바이스-로스트의 처리는 다음과 같은 절차를 진행해야 합니다.

1. 디바이스-로스트가 발생함을 체크
2. 디바이스 의존적 리소스를 모두 제거
3. 디바이스 의존적 리소스를 재할당


이 세가지를 처리하는 것이 디바이스-로스트 상황에 대처하는 것입니다.
Direct2D 의 리소스와 관련한 내용은
http://vsts2010.net/593 글에서 제가 언급했었습니다.^^

그러면 하나씩 살펴보겠습니다.
우리가 진행했던 일반적인 렌더링 작업은 아래와 같습니다.


여기서 우리의 첫번째 단계를 처리합니다.
바로 hr = ::g_ipRT->EndDraw(); 부분입니다.
EndDraw()는 렌더링 작업의 결과를 리턴합니다.
리턴 값이 D2DERR_RECREATE_TARGET 이면, 바로 디바이스-로스트 상황입니다.
이름에서 유추할 수 있듯이 "에러가 났으니, 다시 생성하라" 입니다.

이번에 제가 사용할 샘플은 바로 이전 시간에 했던 알파이미지를 렌더링하는 샘플입니다.
이 샘플에서 디바이스 의존적인 리소스는 렌더 타겟과 비트맵입니다.
이 샘플에서 디바이스-로스트 상황이 발생한다면,
렌더타겟과 비트맵 리소스는 메모리에서 제거했다가 다시 생성해 주어야 합니다.

그래서 이번 샘플에서는 이들 리소스를 한번에 생성/삭제 하는 함수를 만들었습니다.


그리고 또 하나 고려해야 하는 부분이 있습니다.
바로 윈도우 사이즈의 변경입니다.
이 경우는 디바이스-로스트가 발생하지는 않습니다만,
순간적으로 화면이 깜빡이는 현상을 보이게 됩니다.
이 곳에도 역시 적절한(?) 처리를 해야 합니다.


렌더타겟의 리사이즈 작업이 실패하면,
역시 디바이스 의존적 리소스들을 모두 제거해 버립니다.

이제 WM_PAINT 이벤트를 위와 관련된 작업들과 연계해서 수정해야 합니다.


렌더타겟이 없는 경우는 디바이스-로스트 상황이거나 초기화 상태로 인식하고,
관련된 리소스를 생성합니다.
그리고 렌더타겟의 CheckWindowState()를 통해서 해당 윈도우가 가려져 있는지를 체크하고,
가려져 있지 않다면 렌더링 작업을 수행합니다.

렌더링 작업의 마지막에는 디바이시 로스트 상황을 체크해서
디바이스 의존적 리소스를 제거하고 있습니다. ( 앞서 언급했었죠..^^ )

이제 샘플이 약간은 안정성이 향상되었습니다.^^
이상으로 디바이스-로스트와 관련한 작업을 마치겠습니다.^^



디바이스 로스트( Device Lost ) 라는 용어에 대해서 친숙한 분도 계실 것이고,
그렇지 않은 분도 계실 것입니다.
아마 Direct3D 를 다루어 보신 분들은 이 용어에 무척이나 친숙할 것입니다.

디바이스 로스트라는 것은 특정 상황을 얘기하는 것입니다.
이 상황은 지금까지 확보하고 있던 메모리 같은 리소스들이 모두 사라져서,
아무 작업도 할 수 없는 상황을 얘기합니다.
즉, 시스템 리소스가 모두 무효화 되어버린 상황이라 할 수 있습니다.

GDI 를 사용해서 렌더링 한다면, 갑자기 HDC 가 비활성화 되어버린 것이라 생각할 수 있습니다.
게임 개발에서 주로 사용하는 Direct3D를 사용하는 경우에는 Alt + Tab 문제라던지,
실시간으로 화면 사이즈를 조절하게 되었을 때 주로 나타납니다.
Direct3D로 개발하는 경우에는 예전부터 이 문제에 친숙했기 때문에,
이 상황에 대해서 리소스들을 복구하는 코드를 개발자들이 직접 작성해 주었습니다.

Direct2D도 DirectX의 하나이기 때문에 바로 이 디바이스 로스트 상황이 발생을 합니다.
그래서 개발자들이 이 상황에 대해서 직접 적절한 처리를 해주어야 합니다.

그런데 중요한 것은 현재의 디바이스 로스트 상황이 예전과는 다르다는 것입니다.
이 차이를 설명하려면 디스플레이 드라이버 모델까지 언급을 해야 합니다.

Windows XP 시대와 현재의 Windows 7 시대는 디스플레이 드라이버 모델이 다릅니다.
XP 시대는 XPDM 이라는 드라이버 모델을 사용하고 있으며,
윈도우 7 시대에는 WDDM( Windows Display Driver Model ) 라는 모델을 사용하고 있습니다.
( 모니터에 그리기까지의 과정이 다르다고 생각하시면 좋을 것 같습니다.^^ )
이 모델의 차이가 디바이스 로스트에서도 차이를 만들어 냅니다.

XPDM 은 XP때까지 Windows OS가 발전시켜온 드라이버 모델입니다.
즉, 옛 것을 꾸준히 업데이트 한 결과라 할 수 있습니다.
아무래도 너무 오래되다보니 복잡하고 난해한 구조가 되었고,
그 복잡성 때문에 버그들이 드라이버 내부에 있었다고 합니다.

또한 하드웨어의 발전에도 빠르게 대응하는 것에 굉장한 한계가 있었다고 합니다.

그래서 Windows Vista 때 이 문제를 극복하기 위해서 도입된
새로운 디스플레이 드라이버 모델이 바로 WDDM 입니다.
WDDM 은 기존의 복잡성을 최대한 버려서 심플한 구성을 가지도록 설계되었습니다.
기존의 XPDM 이 커널모드에서 대부분의 처리를 수행했던 것을
커널모드와 사용자 모드로 분리를 시켜서 이 디바이스 로스트에 대한 상황에 대한
대응을 용이하게 했습니다.

XPDM의 경우에는 커널에서 문제가 발생하면,
이 때문에 시스템 전체를 다시 시작해야 하는 불상사(?)가 꽤 있었습니다.
MS 통계에 따르면 우리가 흔히 보던 블루스크린 상황의 20% 정도가
바로 이 디스플레이 드라이버가 원인이였다고 합니다.
그렇기 때문에 간략화 된 커널 모드의 드라이버를 제공하고,
계산이 복잡한 대부분의 기능은 사용자 모드 드라이버로 이동시킴으로써
문제를 해당 애플리케이션 하나로 국한 시킬 수 있습니다.
이는 블루스크린 상황을 감소시킬 수 있는 하나의 방법이기도 합니다.

XP OS의 디스플레이 드라이버 모델 교체가 어렵기 때문에
WDDM 은 Vista 이상의 OS에서만 지원합니다.
결국 이는 최신의 DirectX 들이 XP OS 에서 정상적으로 작동하지 못하는
가장 근본적인 이유입니다.

WDDM과 XPDM 의 차이는 바로 GPU 활용에 있습니다.
XPDM 의 경우에는 GPU 관련 처리를 할 수 없었습니다.
왜냐하면 옛것을 꾸준히 계승시켜 발전시킨 모델이였기 때문에,
GPU 처리와 같은 큰 패러다임의 전환은 이 모델 자체를 변경하는 일이기 때문입니다.
현재 Vista 이후의 Windows OS에서는 대부분의 그래픽 작업과 윈도우 관리에
바로 이 GPU가 활용되고 있습니다.

XP 시대에서 DirectX를 사용하는 것은 GPU를 독점적으로 사용하는 작업이였습니다.
만약에 GPU 작업을 실행하는 중에 다른 애플리케이션에서 GPU를 사용하려 한다면,
GPU의 제어 권한을 빼앗기게 됩니다.
XP 시대에서는 GPU를 여러 애플리케이션에서 동시에 공유할 수가 없었습니다.

하지만 현 세대의 Windows OS에서는 GPU 메모리 관리자가 비디오 메모리를 관리하고 있으며,
GPU 스케줄러에 의해서 스케줄을 조정합니다.
우리가 '빠르다' 라고 하는 작업의 뒤에 바로 이런 작업들이 이루어지고 있습니다.

이러한 GPU 활용의 적극적인 도입은 결국 퀄리티의 향상까지 연결됩니다.
XP 시대의 XPDM 의 경우에는 GDI로 렌더링 작업을 할 때,
화면에 직접 그리는 개념으로 작업을 했습니다.
그렇기 때문에 윈도우를 이동하거나 사이즈를 조절하게 되면,
화면이 깜빡이는 것을 확인 할 수 있었습니다.
( 티어링과 같은 현상도 확인할 수 있었습니다.^^ )

또한 이 때 대량의 WM_PAINT 메시지가 발생해서 시스템에 상당한 부하를 주기도 했었습니다.
이는 XPDM 의 경우 모니터 화면에 직접 렌더링 작업을 했었기 때문입니다.

비디오 메모리가 풍족해진 현 세대에서는 오프-스크린 버퍼를 두어서,
화면이 아닌, 다른 버퍼에 렌더링 작업을 수행합니다.
( 흔히들 얘기하시는 더블-버퍼링 기법입니다.^^ )

이러한 것을 담당하는 것이 DWM( Desktop Window Manager ) 입니다.
이는 OS 에서 자동적으로 실행하기 때문에 쉽게 확인할 수 있습니다.


DWM 은 간단히 말해서 DirectX 애플리케이션입니다.
DWM 은 비디오 메모리를 만들어서 각 애플리케이션 화면을 모아서
우리에게 화면을 보여주는 역할을 합니다.
그렇기 때문에 DWM 은 오프-스크린 버퍼를 관리하는 기능을 가지고 있습니다.

WDDM 에 대해서 논할 내용도 상당히 많이 있습니다.
Vista 시절에는 WDDM 1.0 이였고,
Windows 7 에서는 WDDM 1.1 이 사용되고 있습니다.
역시 버전이 업데이트 되면서, 더 좋아진 부분이 있습니다.
1.1 에 관한 개선 사항은 아래의 링크로 대신합니다.^^
http://jacking.tistory.com/442


GPU를 활용한 많은 기능들이 현세대의 Windows OS에 기본적으로 탑재가 되어 있습니다.
그렇기 때문에 디바이스-로스트 같은 GPU 관련한 예외 상황들에 대한 처리를
개발자들이 해주어야 합니다.
XPDM의 경우와 개념도 다르고, 발생되는 상황도 다릅니다.
Direct2D의 경우 디바이스-로스트란,

그래픽카드가 일정시간 동안 응답을 하지 않을 때를 의미합니다
.
여기서 일정 시간이란 기본적으로 2초로 설정되어 있다고 합니다.
Direct2D 에서 디바이스-로스트는 잘 발생되지는 않습니다.^^

이와 관련한 실제 처리는 다음 시간에 계속하겠습니다.^^

[StartD2D-8] 투명 이미지 다루기

DirectX 11 2011. 9. 28. 08:00 Posted by 알 수 없는 사용자


이번 시간에 다룰 것은 투명 이미지 입니다.
얼마 전 댓글로 문의하신 내용인데 답변을 달기가 조금 부족한 듯 싶어서,
이렇게 별도로 아티클(?)로 남깁니다.

예전에 Win32 API 로 알파가 있는 이미지를 표현하는 작업은 무척 번거로운 작업이였습니다.
이미지 색상에 알파가 고려되어지면, 각 색상 성분마다 알파 연산을 해주어야 합니다.
또한 RGB 각 성분이 8비트씩 사용하는데 A성분이 추가되어지면서,
다시 8비트의 추가 데이터들이 각 색상값들에 필요하게 됩니다.
용량이 커지면 성능에 문제가 생기게 되는 것은 당연한 일입니다.

그래서 이를 흉내내기 위한 대안으로 마련된 것이 ColorKey 라고 불리는 기법입니다.
이 기법은 이미지 내의 특정 색상을 표현하지 않음으로써 구현됩니다.
아래의 그림을 예로 들어보겠습니다.



이미지 내에서 배경이 모두 붉은 색으로 되어있습니다.
이런 경우에 붉은 색을 ColorKey로 지정해서 데이터를 읽지 않는 것입니다.
그러면, 캐릭터 관련 색상만 메모리에 기록되게 됩니다.
이 방법을 사용하면 24비트 비트맵만으로 캐릭터를 표현할 수 있습니다.
주의해야 할 점은,
ColorKey 에 해당하는 색상 값을 아티스트들에게 사용하지 말 것에 대한
사전 협의가 있어야 겠지요.

아쉽게도(?) Direct2D에서 이 ColorKey 사용에 대한 API를 찾지 못했습니다.
( Direct3D 에는 있습니다.^^ )

사실 ColorKey 방식이 널리 이용되긴 하지만,
근본적으로 알파 처리를 이용하며 관련 효과를 모두 구현할 수 있습니다.
그렇기 때문에, 굳이 ColorKey 를 염두할 필요성은 없습니다.

이번에 샘플에 사용한 이미지는 알파가 있습니다.

 


우측의 흑백 이미지가 알파 성분만 표현한 이미지 입니다.
당연히 검은 부분은 알파 성분이 0 이기 때문에
해당 영역은 화면에 표현되지 않을 것입니다.

이번에 살펴볼 샘플은 아래와 같습니다.



똑같은 이미지 파일에서 데이터를 읽었지만,
위의 그림은 알파 처리가 되어서 동물 부분만 출력이 되었습니다.
하지만 아래 부분은 알파처리가 이루어지지 않아서 이미지 영역이 모두 출력되었습니다.

이번 결과의 차이는 WIC를 이용한 것입니다.
우리가 지금껏 무심코(?) 지나쳤던 WIC의 컨버터를 기억하십니까?


제가 이번 샘플을 위해서 약간 개량을 했습니다.
두 사용법에 별 차이는 없지만, GUID_WICPixelFormat... 부분이 보일 것입니다.
이 인자가 바로 두 이미지의 차이를 만들어낸 부분입니다.
즉, 컨버터에서 데이터를 어떤 포맷으로 읽어들일지를 설정하는 부분입니다.

첫번째는 알파처리를 수행하는 방법으로 데이터를 읽어들입니다.
GUID_WICPixelFormat32bppPBGRA는
4개의 색상 채널을 가지고 채널당 8개의 비트를 가지고 있으며,
픽셀 당 32비트를 표현하며 UINT로 각 색상이 저장되어 있는 포맷을 의미합니다.

두번째 알파처리를 하지 않는 GUID_WICPixelFormat32bppBGR은
3개의 색상 채널과 8비트로 각 채널을 표현하는 32비트 픽셀 포맷을 의미합니다.
이 포맷도 UINT로 각 색상 성분을 표현합니다.
이 경우에는 알파 채널이 존재하지만, 실제로 읽어들이지 않습니다.
즉, 알파 채널을 무시하는 것입니다.

관련 포맷이 매우 방대하기 때문에, 여기서 더 이상 자세히 다루지 않습니다.
중요한 것은 이 컨버터 덕분에 별다른 수고 없이 알파 처리를 쉽게 수행할 수 있습니다.

Direct2D 가 사실 많은 부분을 우리가 모르게 자동적으로 처리하는 부분이 많이 있습니다.
Direct2D에서는 프로퍼티를 생성하는 부분들이 많이 있습니다.
예를 들면 다음과 같은 것들입니다.



사실 이 프로퍼티 정보들을 별도로 설정하지 않으면,
자동적으로 Direct2D에서 처리를 해버립니다.
혹은 디폴트 생성자들이 모두 들어있습니다.
자동적으로 처리되는 부분들 이외의 기능이 필요하다면,
이들을 잘 제어해야만 하겠지요..^^

부족하지만 샘플을 참조해 드립니다..^^

[StartD2D-7] 변환의 중심과 확대/축소/찌그러짐

DirectX 11 2011. 9. 26. 08:00 Posted by 알 수 없는 사용자


< 변환의 중심 점은 어디인가? >

Direct2D의 변환과 관련된 API는 모두 변환을 수행할 중심점을 함수 인자로 요구를 합니다.
그렇다면, 이 중심점은 스크린 기준에서 정의되어지는 것일까요?

다음과 같은 다람쥐 그림이 있다고 가정해 보겠습니다.


이 그림은 ( 100, 100 ) 의 크기로 그려지기를 원한다고 가정해 보겠습니다.

 


그리고 우리의 모니터에 ( 300, 200 ) 위치에 그려지기 원하도록 설정하겠습니다.
그러면, 우리에게 변환을 위한 중점을 설정하기 위한 기준 좌표는 어떻게 설정되어야 할까요?
그림의 좌측 상단을 변환의 중점으로 원한다면 ( 300, 200 )으로 설정하면 될까요?
그림의 좌측 상단이 변환의 중점이 된다는 말의 의미를 잘 되새겨 보시기 바랍니다.

그림의 중심을 기준으로 중점을 설정해 두는 것이 훨씬 쉬운 개념으로 변환할 수 있습니다.
즉, 위의 그림에서 변환의 중심이 되는 좌측 상단은 좌표는 ( 0, 0 ) 이 되는 것입니다.
만약, 우측 하단이 변환이 중심이 되길 원한다면 ( 100, 100 )을 설정하면 됩니다.

앞서 살펴 보았던 샘플에는 이 의미를 그냥 넘겼지만
이번에 제공되는 샘플에서는 이를 고려해서 모두 작성했으니,
유심히 살펴보시기 바랍니다.^^

우리는 이 개념을 다음과 같이 지금까지 사용하고 있었습니다.


D2D1_RECT_F 정의의 변수가 보이시나요?
바로 이 dxArea 를 기준으로 변환이 되는 중점을 설정하는 것이 이해하기가 편리합니다.^^

이번 샘플은 지난 번 샘플의 연장에 있습니다.
지난 번 샘플에서 이번 내용과 관련된 부분을 추가 했습니다.

 




< Scale( 확대/축소 ) >

확대/축소 작업은 1.0을 기준으로 이루어집니다.
1.0 보다 작으면 축소가, 1.0보다 크다면 확대가 이루어 집니다.
이 작업은 D2D1::Matrix3x2F::Scale() API를 통해서 이루어 지는데,
역시나 변환의 중점을 요구합니다.^^



만약 좌측 상단을 기준으로 확대를 하면 다음과 같은 개념입니다.






< Skew( 찌그러뜨리기 ) >

마지막 변환은 찌그러뜨리기 작업입니다.
이는 Matrix3x2F::Skew() API를 통해서 설정할 수 있습니다.
함수 인자로 받는 것은 X축과 Y축의 찌그러질 각도와 역시나 기준이 되는 중점입니다.



역시나 변환이 되는 중점에 따라 결과가 변합니다.
아래의 그림은 30도씩 찌끄러뜨린 결과입니다.
첫번째는 X축만 적용한 것이고, 두번째는 Y축을,
세번째는 X와 Y축 모두를 30도씩 찌그러뜨린 결과입니다.



부족하지만, 제가 작성한 샘플을 첨부해 드립니다.^^


 

[StartD2D-6] 이동/회전 변환 이해하기

DirectX 11 2011. 9. 21. 08:00 Posted by 알 수 없는 사용자


 

행렬의 등장

기존의 윈도우 애플리케이션 개발자가 Direct2D를 접할 때,
가장 생소한 개념은 행렬( Matrix ) 일 것입니다.

즉, 아래의 API 이겠죠?

<코드>

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

</코드>

우리는 항상 렌더링 작업을 하기 전에, 이 작업을 해주었습니다.
이것은 과연 무슨 의미를 가지고 있는 것일까요?  

불행하게도(?) Direct2D에서 각종 변환 작업을 위해서는 행렬( Matrix )을 사용합니다.
저는 고등학교 2학년 학기 초에 배웠던 기억이 있습니다.
그 때는 주로 연립 방정식의 해를 구하기 위해서 풀이 방법을 익혔습니다.
아마 대부분 저와 비슷하실 것이라 생각이 듭니다.

왜 뜬금없이 행렬이 등장했는지를 언급하고자 하면, 상당히 고통스럽습니다.^^
저는 이 행렬에 대한 깊은 이해를 원하지 않습니다.
제가 원하는 것은 이 행렬 관련 API를 이용해서 화면 상에 나오는 오브젝트들의
이동이나 회전 등과 같은 변환을 표현 할 수 있다는 것만 아셨으면 좋겠습니다.^^


Direct2D 에서는 3ⅹ2 행렬을 사용합니다.
행렬의 각 성분들은 아래와 같습니다.  

M11

Default : 1.0

M12

Default : 0.0

M21

Default : 0.0

M22

Default : 1.0

M31

OffsetX : 0.0

M32

OffsetY : 0.0

 

이 행렬은 D2D1_MATRIX_3X2 라는 구조체로 표현됩니다.
Direct2D에서는 이 행렬을 손쉽게 다루기 위해서 Matrix3x2F 라는 유틸리티 기능을 지원합니다.
우리는 주로 이 Matrix3x2F 의 기능을 이용해서 변환 정보를 설정할 것입니다.
행렬의 M11, M12, M21, M22 성분은 회전과 확대/축소와 관련된 수치가 입력됩니다.
M31과 M32에는 이동과 관련된 수치가 입력됩니다.
행렬의 수치를 통해서 우리가 원하는 변형을 하고자 할 때,
이들 수치를 직접 입력해서 행렬을 제어하기는 무척 어렵습니다.

그래서 Direct2D 에서 제공하는 개발자 편의를 위한 여러 클래스들을 사용해서 이들 수치를 제어합니다.

 

스크린 좌표계  

변환의 이해를 위해서는 좌표계에 대한 이해가 필수적입니다.
우리가 가장 많이 사용하는 좌표계는 당연히 2차원 좌표계입니다.
바로 모니터가 2차원이기 때문입니다.
하지만 이전에도 언급드린 적이 있지만,
컴퓨터 모니터의 좌표계는 우리가 일반적으로 알고 있는
2차원 좌표계가 아닙니다.

 

좌측이 우리가 흔히 학창시절에 배우는 2차원 데카르트 좌표계( 2D Cartesian coordinate system ) 입니다.
우측이
컴퓨터 모니터가 사용하는 스크린 좌표계입니다.
이점 숙지 하시고, 다음 내용을 보시기 바랍니다.^^

 

이번에 제공되는 샘플의 결과는 다음과 같습니다.

 

이동( Translate )

먼저 살펴 볼 것은 이동( Translate ) 입니다.
가장 위에 있는 이미지가 바로 이동만 적용된 형태입니다.
샘플에서는 단순히 ( X,Y ) 축으로 +20씩만 움직였습니다

<코드>
D2D1::Matrix3x2F matTranslation; 
matTranslation = ::D2D1::Matrix3x2F::Translation( 20.0f, 20.0f );
::g_ipRT->SetTransform( matTranslation );                    
::g_ipRT->DrawBitmap( ::g_ipD2DBitmap, dxArea );    
</코드 

D2D1::Matrix3x2F::Translation() 를 통해서 우리가 원하는 이동을 수행하고 있습니다.
쉽죠? 이런 식으로 하면 행렬에 대한 깊은 이해도 필요하지 않을 것이라 생각합니다.^^  


회전( Rotate ) 

두 번째는 회전입니다.
샘플에서는 두 번째 그림과 세 번째 그림이 바로 회전 변환에 대한 결과물입니다.
사실 회전은 행렬의 성분들을 굉장히 복잡한 수치로 변경시킵니다.
왜냐하면 이들 회전 수치들은 삼각함수 값들이기 때문입니다.

하지만, 두려워하지 마시기 바랍니다.
Direct2D 에서는 이를 편리하게 수행하기 위해
::D2D1::Matrix3x2F::Rotation() 를 제공하고 있습니다.
회전 시킬 때, API 에 입력되는 값이 회전량과 회전 중점입니다.
회전량의 경우에는 라디안 값이 아닌, 각도 값이 입력됨을 주의하시기 바랍니다. 
즉, Degree 값입니다.
그리고 이 회전량의 경우 음의 값과 양의 값을 모두 입력이 가능합니다.
음의 값이 입력이 되면 시계 반대 방향으로 회전을 수행하게 됩니다.
( 양수면 시계방향으로 회전 합니다. )

 

또한 회전 중점의 경우도 유의해야 하는데, 아래의 그림을 보시기 바랍니다.

 

좌측 그림의 경우에는 물체의 중점 위치를 기준으로 회전을 수행한 것입니다.
반면에, 우측 그림의 경우에는 물체의 좌측 상단을 기준으로 회전을 수행한 것입니다.
결과물이 다르죠?
이처럼 회전 변환을 할 때는 이 두 가지를 잘 고려해서 수행해야 합니다.  

<코드>
matRot = ::D2D1::Matrix3x2F::Rotation( -30.0f,
                                       D2D1::Point2F( 120.0f, 320.0f ) );
 

::g_ipRT->SetTransform( matRot );                           
::g_ipRT->DrawBitmap( ::g_ipD2DBitmap, dxArea );    
</코드>


이동과 회전의 결합 

지금까지 이동과 회전을 각각 수행해 보았습니다.
만약에 이동 작업과 회전 작업이 모두 한 오브젝트에 필요하다면 어떻게 해야 할까요?
그런 경우가 필요하다면 바로 행렬의 곱셈 작업을 적용합니다.
그러면 이 두 가지 작업을 모두 표현 할 수 있습니다.
그런데 이 때 주의 할 부분이 있습니다.
행렬은 교환 법칙이 성립하지 않습니다.
즉 행렬 A, B 가 있을 때, A * B != B * A 라는 것입니다.

<코드>
matTranslation = ::D2D1::Matrix3x2F::Translation( 20.0f, 220.0f );         matRot = ::D2D1::Matrix3x2F::Rotation( -30.0f,
                                       D2D1::Point2F( 120.0f, 320.0f ) );
matTM = matTranslation * matRot;            
::g_ipRT->SetTransform( matTM );
                          
::g_ipRT->DrawBitmap( ::g_ipD2DBitmap, dxArea );     

 
matTranslation = ::D2D1::Matrix3x2F::Translation( 300.0f, 220.0f );         matRot = ::D2D1::Matrix3x2F::Rotation( -30.0f, 
                                       D2D1::Point2F( 400.0f, 320.0f ) );

matTM = matRot * matTranslation;                        
::g_ipRT->SetTransform( matTM );
::g_ipRT->DrawBitmap( ::g_ipD2DBitmap, dxArea ); 
</코드>

 

이 코드가 우리의 샘플에서 두 번째와 세 번째 그림의 차이를 보여주는 것입니다.
두 번째 그림은 이동을 한 후에 회전 작업을 수행한 것이고,
세 번째 그림은 회전 작업을 수행한 후에 이동 작업을 수행한 것입니다.  

실제로 우리가 수행하는 모든 변환은 좌표계 변환입니다.
위에서 우리가 했던 변환은 모두 실제로 오브젝트들의 좌표계를 변환시켜서 얻은 결과물들입니다.
이 개념들은 분명히 이해하기 어려운 부분들입니다.
굳이 이렇게 어려운 부분까지 이해하실 필요는 없습니다.
샘플을 올려드리니, 수치를 바꿔보면서 여러 결과들을 눈으로 확인해 보시기 바랍니다.^^


 

 

 

[StartD2D-4] WIC 를 이용한 이미지 작업하기

DirectX 11 2011. 7. 4. 08:30 Posted by 알 수 없는 사용자

 

  

이번 시간에는 직접 이미지를 화면에 표현하는 방법에 대해서 언급합니다.

Win32 API를 이용할 때, 우리는 '비트맵(Bitmap)' 이라는

그래픽 데이터 포맷을 읽어서 화면에 그려주었습니다.

사람마다 차이는 있겠지만, 일반적으로 다음과 같은 순서를 따라서 구성했을 것입니다.

 

  1. 비트맵을 읽기 위해서 파일을 오픈한다.

  2. 파일에서 헤더를 읽어 들인다.


  3. 비트맵 헤더의 정보를 통해서 관련 메모리를 생성하고,

    파일에서 색상 데이터에 대한 정보를 읽는다.


  4. DIBSection 을 생성하고, 실제 데이터를 읽는다.
  5. 그리고 마무리 한다.

 

이 순서는 수 많은 방법 중에 하나일 뿐이지만,

기본적으로 파일을 열어서 헤더를 먼저 읽고, 관련 메모리를 생성하고,

이후에 실제 데이터를 채우게 되는 순서는 공통된 작업입니다.

Direct2D 에서도 이와 같이 작업을 해도 되지만,

이미 편의를 위해 만들어진 라이브러리를 사용해서 조금 더 확장성 있는 작업을 할 필요가 있습니다.

지금부터는 WIC를 이용한 간단한 이미지 뷰어 작업을 해보겠습니다.

  

WIC( Windows Imaging Component )

 

DirectX 가 윈도우 운영체제 전반으로 광범위하게 활용되면서,

이들과 관련한 내용들을 분리할 필요가 있었습니다.

과거까지는 DirectX 는 게임 개발자들의 전유물에 가까웠기 때문에,

다른 개발자들도 손쉬운 개념으로 접근할 수 있는 그런 분류가 필요했습니다.


결과적으로 아래와 같이 분류가 되었습니다.  

 

WIC는 모든 이미지를 쉽게 처리할 수 있도록 만들어낸 COM 기반의 프레임워크입니다.

그림에서 보듯이 WIC도 하나의 큰 영역으로서 자리 잡고 있습니다.

( 참고로 DXVA는 영상 처리를 위한 프레임워크입니다. )

 

WIC를 이용한 이미지 처리는 앞서 GDI 기반에서 작성했던 것과는 완전히 다릅니다.

WIC는 PNG, JPG, GIF 등과 같은 거의 모든 주요한 이미지 형식을 포함하고,

기본 코덱들을 지원하고 있습니다.

 

말이 참 어렵죠?

쉽게 말해서, Direct2D 기반에서 이미지 처리를 하려면 WIC를 사용하면 쉽게 할 수 있다는 것입니다.

우리는 이것을 사용하는 순서와 방법에 대해서 배우기 위해서,

윈도우 화면에 이미지를 그려주는 간단한 애플리케이션을 만들어 볼 것입니다.

 

 

기본 WIC 프로그래밍

 

애플리케이션 마법사로 새로운 프로젝트를 만들고, stdax.h 에 다음을 추가를 합니다.

 

'WindowsCodecs.lib'와 'wincodec.h' 가 바로 WIC를 사용하기 위해 추가시킨 것입니다.

눈치 빠른 분들이라면, 이름에서 약간 앞으로의 작업 방향을 예측할 수 있을 것입니다.

 

이번 프로젝트에서 사용할 전역 변수들은 아래와 같습니다.

 

 

익숙한 개념이 눈에 보이지 않으십니까?

바로 IWICImagingFactory 입니다. 네 그렇습니다~

WIC 도 바로 팩토리 형태로 생성이 됩니다.

 

추가된 변수들은 아래와 같은 절차에 의해 값이 채워집니다.

즉, 아래는 WIC의 처리 과정입니다.

 

  1. WIC 팩토리를 만든다.

  2. 파일 경로를 기반으로 해서 디코더를 만든다.

  3. 디코딩된 프레임을 가져온다.

  4. 변환기에 넣어서 Direct2D 형식으로 변환한다.

  5. Direct2D 비트맵을 생성하고, 이를 렌더링한다.

 

이제 위의 절차를 실제로 어떻게 처리하는지를 차근차근 살펴보겠습니다.

 

가장 먼저하는 초기화 작업입니다.

앞서 언급했듯이, WIC 도 팩토리 개념으로 생성됩니다.

COM 기반이기 때문에 API 인자들이 굉장히 어려워 보일 수도 있지만, 관심을 둘 부분은 아닙니다.

위와 같은 방법으로만 하면, WIC가 생성 되어진다는 개념으로만 인식하고 다음 단계로 넘어갑니다.

 

 

 

다음 단계는 디코더를 만들고, 이를 기반으로 해서 Direct2D 형식으로 데이터를 변환하는 것입니다.

이를 위해서 가장 먼저 해야 하는 일은

이미지를 읽어들이기 위한 디코더( Decoder )를 만드는 일입니다.


갑자기 등장한 생소한 용어에 조금 혼란스러울 것 같습니다.

우리가 사용하는 모든 멀티미디어 파일( 이미지, 영상, 사운드 등 )들은

굉장히 어려운 방법으로 압축이 되어있습니다.


이들에 대한 원리나 형식을 이해하는 것도 중요한 일일 수도 있지만,

이는 간단하게 본 페이지에서 설명할 수 있는 내용이 아닙니다.

물론 저도 이와 관련한 전문가는 더더욱 아닙니다.

우리는 단지 API만으로 이들에 대한 고민을 해결할 수 있습니다.

바로 그것이 WIC의 존재 이유 중 하나 일 것입니다.^^

 

즉, 우리는 이미 만들어진 API를 이용해서 손쉽게 이미지 파일을 읽어올 수 있습니다.

그런 역할을 하는 것이 바로 디코더입니다.

아래는 디코더를 가지고 실제 작업을 하는 부분입니다.

 

 

디코더는 WIC 팩토리 멤버함수로써 생성이 되어집니다.

우리가 사용했던 이 API의 원형은 다음과 같습니다.

<코드>

HRESULT CreateDecoderFromFilename(

[in] LPCWSTR wzFilename,

[in] const GUID *pguidVendor,

[in] DWORD dwDesiredAccess,

[in] WICDecodeOptions metadataOptions,

[out, retval] IWICBitmapDecoder **ppIDecoder

);

</코드>

 

이 API는 주어진 이미지 파일을 기반으로 해서 디코더를 생성해 줍니다.

첫 번째 인자로 파일명이 들어갑니다.

이 파일을 기반으로 해서 적합한 디코더를 생성해 주게 되는 것입니다.

예를 들어, PNG 파일이면 PNG에 대한 디코더가 필요하다고 인식하고,

그에 맞는 디코더를 자동적으로 생성해 주는 것입니다.

 

두 번째 인자는 선호하는 디코더 벤더(vendor)의 GUID를 입력해야 하는데, 지금은 NULL을 사용합니다.

 

세 번째 인자로는 디코더에 대한 접근 방법을 명시합니다.

읽기(read), 쓰기(write), 혹은 둘 다 가능한지를 넣어주면,

가장 최적화된 방법과 메모리 위치를 가지는 디코드를 생성해 줍니다.

위의 예제에서는 읽기용으로만 디코더를 만들었습니다.

 

네 번째 인자는 디코더의 캐시 관련 옵션입니다.

우리가 인자로 넘긴 WICDecodeMetadataCacheOnDemand는

필요한 이미지 정보만 캐시 하도록 옵션을 준 것입니다.

다음 번에 언급할 지도 모르지만, 하나의 이미지 파일에는 여러 이미지들을 포함하고 있을 수 있습니다.

예를 들면 GIF 애니메이션 이미지 같은 것들이다.

이런 경우에 유용하게 캐시하려면, 다른 옵션을 주어야 할 것입니다.

 

마지막 인자는 생성된 디코더를 저장할 디코더의 포인터입니다.

여기까지 작업하면, 우린 이제 파일을 읽은 디코더를 소유하게 되는 것입니다.

뭔가 절차 상으로 굉장히 복잡한 것처럼 느껴지죠?

 

 

다음으로 할 작업은 프레임(frame) 작업입니다.

프레임이라는 것은 실제 픽셀 데이터를 가지고 있는 비트맵입니다.

앞서 잠깐 언급했듯이, 하나의 이미지 파일은 여러 장의 이미지가 존재할 수 있습니다.

그런 경우를 대비해서 체크를 해야겠지만,

우린 여기서 단 하나의 프레임만이 존재한다고 가정할 것입니다.

디코더의 멤버 함수인 GetFrame()를 통해서 우린 가장 첫 번째 프레임을 얻을 수 있습니다.

이 프레임을 얻는다는 것은 우리가 화면에 표현할 수 있는 이미지를 얻었다는 것입니다.

 

이제 우리는 디코더를 통해서 이미지를 Direct2D에서 표현할 수 있도록

적절하게 변환을 해주어야
합니다.

CreateFormatConverter() API는 이를 위해서 컨버터를 만들어줍니다.

그리고 이 컨버터를 우리가 원하는 형태로 초기화를 시켜 줍니다.

컨버터의 멤버함수 Initialize() 는 이미지를 컨버팅 하면서

픽셀 정보를 보정해 줄 수 있는 많은 옵션을 가지고 있습니다.

이들 옵션에 대한 세부 설정을 하지 않았습니다.

그래서 위에 인자들 형태로 주면, 별다른 이미지의 수정 없이 32비트 포맷으로 남게 됩니다.

 

이제 마지막으로 실제 렌더링 가능한 형태의 메모리를 생성해야 합니다.

렌더타겟의 멤버함수인 CreateBitmapFromWicBitmap() API를 통해서 이 작업을 하게 됩니다.

여기까지 하면, 이미지를 렌더링 하기 위한 준비작업이 모두 끝난 것입니다.

 

저는 여기에 모든 옵션들을 나열하지 않습니다.

( 기본 목적인 이미지를 띄우는데 충실하고자 합니다.^^ )

 

 

생소한 API들이 눈에 많이 띄지만, 이들은 일련의 절차에 지나지 않습니다.

중요한 개념은 이미지를 읽어 들일 디코더를 만들고,

이 이미지 데이터를 Direct2D가 표현할 수 있는 픽셀 데이터로 변환하는 것입니다.

그리고 이 데이터를 렌더타겟에서 표현할 수 있는 비트맵으로 만들어서

렌더링 가능한 상태로 만듭니다.

위의 코드는 바로 이 개념들을 표현하고 있는 것입니다.

 

그러면 실제 WM_PAINT 메시지를 통해서 이들이 어떻게 화면에 그려야 하는지 살펴보겠습니다.

 

WM_PAINT 메시지에서는 렌더타겟이 존재하지 않는 경우, 렌더타겟을 생성합니다.

렌더 타겟이 존재한다면, 비트맵을 그리고 있습니다.

렌더타겟의 렌더링 작업도 BeginDraw() / EndDraw() 의 매커니즘 내부에서

특정 상태를 기반으로 작업을 수행하게 됩니다.

우리는 Clear() 라는 API를 통해서 렌더타겟의 메모리를 흰색으로 채우고 있습니다.

그리고 현재 우리가 이미지를 (0,0) 위치에 (300,300) 크기로 렌더링 합니다.

 

마법의 함수 DrawBitmap()

 

앞선 작업을 통해서 우린 Direct2D를 이용해서 이미지를 화면에 그릴 수 있었습니다.

만약 우리가 읽어 들인 이미지의 일부분만을 화면에 그리고 싶다면 어떻게 해야 할까요?

혹은 흐릿한 효과를 주고 싶다면 어떻게 해야 할까요?

굉장히 어려운 일들 같지만, 이들 기능은 DrawBitmap() 에 모두 옵션 인자로서 존재하고 있습니다.

( 무척 고마운 일이지요..^^ )

그렇기 때문에, 우리는 이 함수를 잘 사용할 수 있어야 합니다.

API의 원형은 다음과 같습니다.

 

<코드>

virtual void DrawBitmap(

[in] ID2D1Bitmap *bitmap,

[in, optional] const D2D1_RECT_F *destinationRectangle = NULL,

         FLOAT opacity = 1.0f,

D2D1_BITMAP_INTERPOLATION_MODE interpolationMode =

D2D1_BITMAP_INTERPOLATION_MODE_LINEAR

,

[in, optional] const D2D1_RECT_F *sourceRectangle = NULL

) = 0;

</코드>

 

첫 번째 인자는 우리가 렌더링 작업을 수행할 이미지입니다.

 

두 번째 인자부터는 옵션적으로 설정할 수 있다.

두 번째 인자는 렌더링 작업을 수행할 화면의 영역을 설정합니다.

NULL 로 설정한다면, 렌더타겟의 원점에 그리게 됩니다.

만약 이미지 크기보다 크게 설정된다면, 자동적으로 이미지를 확대해서 보여주게 됩니다.

 

세 번째 인자는 투명도를 설정합니다.

범위는 0.0~1.0 사이의 값으로 0.0은 투명한 상태를 나타내고 1.0은 불투명한 상태를 나타냅니다.

 

네 번째 인자는 우리가 렌더링하는 이미지가 회전을 하거나 크기가 조정되었을 때,

어떻게 부드럽게 보일 것인가에 대한 옵션을 설정하는 부분입니다.

즉, 보간( interpolation ) 옵션입니다.

 

마지막 인자는 원본 이미지에서 일정 영역을 보여주고 싶을 때 영역을 입력하는 옵션입니다.

이 때 단위는 해당 이미지 파일의 사이즈를 기준으로 영역을 설정해 주면 됩니다.

 

그러면, 간단하게 실제로 이미지의 일부 영역을 약간 투명하게 보여지는 것을 프로그램으로 구현해자면,

앞서 작성했던, 이미지 뷰어의 기능에서 DrawBitmap()만 변경해주면 됩니다.

 

<코드>

HRESULT hr = E_FAIL;

::g_ipRT->BeginDraw();

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

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

                            

if( ::g_ipD2DBitmap != nullptr )

{

    ::D2D1_RECT_F dxArea = ::D2D1::RectF( 0.0f, 0.0f, 500.0f, 500.0f );

    ::D2D1_RECT_F dxSrc = D2D1::RectF( 0.0f, 0.0f, 250.0f, 250.0f );

    ::g_ipRT->DrawBitmap( ::g_ipD2DBitmap, dxArea, 0.3f,

D2D1_BITMAP_INTERPOLATION_MODE_LINEAR, &dxSrc );

                

}

hr = ::g_ipRT->EndDraw();                

</코드>

 

우리는 간단하게 DrawBitmap() 의 인자들만 변경해주는 것만으로 이미지의 일부 영역만을 보여주고,

투명도를 조절할 수 있음을 확인해 보았습니다.

각각의 값을 변경시키면서, 여러가지 아이디어를 구상해 보기 바랍니다. ^^


아래 소스코드를 첨부합니다..


[StartD2D-3] Direct2D 프로그래밍 시작하기!!!

DirectX 11 2011. 4. 15. 08:00 Posted by 알 수 없는 사용자

 

첫 번째 Direct2D 프로그래밍~ 
 

지난 시간을 통해서 Direct2D의 필요성에 대해서, 제가 열심히(?) 언급해 드렸습니다.^^

이번 시간에는 Direct2D 프로그래밍의 세계에 대해서 들어가기 전에,

간단하게 프로그램을 작성해 볼 것입니다.

부끄럽지만, 저는 박식한 이론 내용 없이도 많은 프로그래밍 작업을 했었습니다.^^

그 만큼, 직접 프로그램을 작성하면 쉽게 이해할 수 있는 부분이 많이 있을 것입니다.

기본적으로 Direct2D는 2차원 그래픽을 만들기 위한 API입니다.

기존의 GDI를 이용한 프로그램의 일부분을 Direct2D로 대체를 하는 것만으로도 성능을 향상시킬 수 있습니다.

 

기본적으로 Direct2D로 작업하는 순서는 다음과 같습니다.

  1. Direct2D 팩토리를 생성한다.
  2. 팩토리에서 렌더타겟을 생성한다.
  3. 렌더타겟에서 리소스들을 생성한다.
  4. 생성 되어진 리소스들을 이용해서 그리기 작업을 수행한다.

 

Direct2D의 모든 작업은 위의 순서를 따릅니다.

이제 이들 순서를 어떻게 API로 표현하는지 살펴보겠습니다.

 

화면 작업을 위해 준비하기

 

첫 번째로 작성해볼 프로그램은 특정 색상으로 화면을 채우는 작업을 하는 것입니다.

먼저 마법사로 프로젝트를 생성하고, "stdafx.h" 헤더파일에 Direct2D와 관련된 선언을 추가시켜주기 바랍니다.

위의 내용은 Direct2D와 관련된 라이브러리와 헤더파일을 선언해 준 것입니다.

 

그리고 작업을 수행할 .cpp 파일에 전역 변수를 두 개 선언합니다.

 

Direct2D 프로그래밍을 위해서 가장 먼저 해야 하는 일은 ID2D1Factory 를 생성하는 일입니다.

 

D2D1CreateFactory()의 첫 번째 인자는 멀티 스레드 지원 여부를 설정합니다.

이번 내용에서는 싱글 스레드만을 사용합니다.( 멀티스레드 어려워요~~)

두 번째 인자는 팩토리가 생성되어서 결과를 반환 받을 수 있는 팩토리 포인터를 넘겨줍니다.

이것이 성공하면, Direct2D 와 관련된 작업을 할 수 있게 됩니다.( 참~~ 쉽죠잉!! )

 

우리가 만들려고 하는 프로그램이 화면에 어떤 내용을 그리는 것입니다.

화면에 무엇인가를 그린다는 개념은 하드웨어 입장에서 봤을 때는 메모리에 값을 쓰는 것입니다.

여러분들이 보고 있는 모니터 화면은 거대한 메모리에 색상 값이 기록되어 있는 것입니다.

 

이번에 할 일은 바로 이 메모리 영역을 생성하는 일입니다.

Direct2D의 가장 큰 장점이 바로 이 메모리 영역에 값을 기록하는 작업( 이하 렌더링 )이

GDI를 이용하는 것보다 훨씬 빠르다는 것입니다.

왜냐하면, 바로 이 메모리 영역이 그래픽 카드에 있기 때문입니다.

 

그리기 명령을 수행할 메모리 영역을 생성하기 위해서 다음과 같이 코딩을 합니다.

바로 이 메모리 영역을 렌더타겟( RenderTarget ) 이라 합니다.

 

 

CreateHwndRenderTarget() 의 첫번째 인자는 화면에 대한 정보를 설정합니다.

픽셀 포맷이나 DPI 등의 많은 플래그와 옵션이 있지만, 현재는 디폴트 정보로 넘겨주었습니다.

두 번째 인자는 하드웨어 가속을 받는 렌더링에 대한 옵션을 설정합니다.

간단하게 크기 정보만 넘겨주는 것으로 마무리했습니다.

마지막으로는 이 API 호출이 성공했을 때 리턴되어지는 렌더타겟의 포인터를 저장할 변수를 넣어주었습니다.

이렇게 함으로써 간단하게 우리는 하드웨어 가속을 받을 수 있는 렌더타겟을 생성할 수 있습니다.

 

옵션이나 인자에 대한 설명을 충분히 드리면 좋겠지만, 너무 많습니다.^^

중요한 인자나 옵션에 대해서만 설명 드리는 점 양해 부탁 드립니다.

 

이제 우리는 렌더타겟을 가지고 있으니 이 메모리 영역에 값을 쓰면, 모니터로 결과를 확인할 수 있습니다.

윈도우가 화면에 그리기 위해서 발생하는 메시지가 WM_PAINT 입니다.

저는 이 메시지를 처리해서, 원하는 색상으로 렌더타겟의 색상을 채울 것입니다.

다음과 같이 코딩을 합니다.

 

이번 코드를 실행시키면, 파란색으로 칠해진 윈도우 프로그램을 만나게 될 것입니다.

더 정확하게 얘기하면, 메모리 영역( 렌더타겟 )이 파란색 색상데이터로 채워진 것입니다.

샘플을 올려둡니다.( SimpleDraw.zip )

도움이 되셨으면 좋겠습니다.^^

 


GDI vs Direct2D 비교해 보기.  

제가 아무리 Direct2D가 GDI보다 좋다고 혼자 말하는 것 보다, 여러분들이 직접 결과를 확인하는 것이 좋습니다.

그래서 이번에는 GDI와 Direct2D를 이용해서 타원을 렌더링 할 것입니다.

그리고 결과로 나오는 것을 보고, 여러분들이 직접 확인해 보기 바랍니다.

 

GDI 로 작업하기.

 

GDI를 이용하는 것은 전통적인 윈도우 프로그래밍에서 사용되던 방식입니다.

주변에서 이와 관련한 많은 내용들을 접할 수 있어서, 내용에 대한 자세한 설명은 생략하겠습니다.

Windows 운영체제에서 모든 드로잉(Drawing) 작업은

디바이스 컨텍스트 오브젝트( device-context object )를 통해서 실행이 됩니다.

이를 줄여서 'DC' 라고 줄여서 얘기합니다.( 다 아시죠? ^^ )

DC란, 윈도우즈 운영체제에서 컴퓨터 모니터나 프린터에 그리기 명령을 수행하기 위한

여러 속성 정보들을 구조화한 데이터입니다.

우리는 Windows API를 이용해서 DC를 이용한 그리기 작업을 수행할 수 있습니다.

DC를 이용하면, 우리는 어떠한 하드웨어 장치와는 관련이 없이 공통된 형식으로 화면에 그릴 수 있습니다.

이것이 가능한 이유는 DC는 CPU가 그리기 작업을 처리해 주기 때문입니다.

 

<코드>

HDC hDC;

HBRUSH hBrush,

hOldBrush;

 

if (!(hDC = GetDC (hwnd)))

return;

 

hBrush = CreateSolidBrush (RGB(0, 255, 255));

hOldBrush = SelectObject (hDC, hBrush);

 

Rectangle (hDC, 0, 0, 100, 200);

SelectObject (hDC, hOldBrush);

DeleteObject (hBrush);

ReleaseDC (hwnd, hDC);

</코드>

 

위의 코드는 간단히 DC를 이용해서 사각형을 그리는 코드입니다.

위에서 보는 것과 같이,

GetDC() 라는 API 를 통해서 현재 윈도우 애플리케이션에 대한 DC를 얻어서 작업을 수행합니다.

DC와 관련된 코드를 살펴보면, SelectXXX() 형식을 자주 볼 수 있습니다.

이는 DC가 일종의 상태 정보를 유지하고 있기 때문입니다.

예를 들면, 빨간 펜과 파란 펜으로 두 개 동시에 작업을 할 수는 없습니다.

하나의 펜으로 먼저 작업을 한 후에, 작업한 펜을 제거하고 다음 펜을 선택한 후에 작업을 해야 한다는 얘기입니다.

이점을 잘 고려해서 작업을 해야 하는 것이죠.

 

이것은 DC를 이용하는 하나의 방법일 뿐입니다.

만약 WM_PAINT 메시지 내부에서 처리하고자 한다면, 아래와 같은 구조를 취할 수 있습니다.

<코드>

PAINTSTRUCT ps;

 

case WM_PAINT :

hdc = BeginPaint(hWnd, &ps);

{

DoSomething()

}

EndPaint(hWnd, &ps);

break;

</코드>

 

PAINTSTRUCT 는 내부에 DC 관련 멤버 변수를 가지고 있습니다.

BeginPaint()를 통해서 DC 정보가 채워지게 되는 것입니다.

이러한 DC를 활용하는 것이 GDI 를 이용하는 것의 핵심입니다.

이와 관련된 내용을 더 언급하고 싶지만, 이 정도에서 정리하겠습니다.

이미 많은 분들이 저 보다는 훨씬 많은 지식을 가지고 있을 것이며,

관련 자료들도 이미 훌륭히 찾아 볼 수 있기 때문입니다.^^

 

우리가 지금 하려는 것은 GDI와 Direct2D의 비교입니다.

프로젝트를 만들고, 시스템을 셋팅하시기 바랍니다.( Direct2D 오브젝트도 생성해 주어야 합니다. )

WM_PAINT 메시지에 이 두 가지 방법을 사용해서 렌더링 하는 함수를 호출했습니다.

( 제가 정의한 함수들입니다. )

 

 

먼저, Direct2D를 이용해서 타원을 렌더링 하는 코드를 살펴보겠습니다.

 

이 방법은 뒤에도 설명을 하겠지만, Direct2D에서 DC를 활용하는 렌더링 방법입니다.

몇몇 생소한 개념들이 보이지만, 이들에 대해서는 차후에 설명을 할 것입니다.

 

GDI를 활용해서 타원을 렌더링 하는 코드는 다음과 같습니다.

자, 이제 이 둘의 결과는 예제 코드를 실행시키면, 다음과 같이 확인할 수 있습니다.

 

 

 

이는 타원의 일부를 캡쳐한 화면입니다.

좌측은 Direct2D의 결과이고, 우측은 GDI를 이용한 결과입니다.

겉으로 보기에는 차이가 없어 보이지만,

자세히 보면 우측의 경우는 울퉁불퉁한 계단 현상이 심한 것을 알 수 있습니다.

( 잘 보이지 않는다면, 샘플을 실행시켜보시기 바랍니다.^^ )

분명히 같은 기능을 하는 두 함수이지만, 결과에서는 이렇게 차이가 납니다.

샘플 파일( BasicRender.zip )을 같이 첨부했으니, 도움이 되셨으면 좋겠습니다.^^


[StartD2D-2] 왜 GPU 인가?

DirectX 11 2011. 3. 25. 08:00 Posted by 알 수 없는 사용자

 

우리가 예전에 생각했던 PC는 어떤 모습 이였을까요?

앞서 언급했듯이, 아주 오래 전의 PC들은 하나의 CPU를 통해서 연산을 수행하고

결과를 저장하는 구조를 가지고 있었습니다.

또한 오늘 날의 그래픽 처리를 위한 GPU라는 개념도 초창기에는 상상하기 힘든 개념 이였습니다.

하지만, 오늘 날의 PC는 CPU는 여러 개이며, GPU의 성능 또한 아주 막강합니다.

거기다 멀티 GPU인 상황이기도 합니다.

이런 변화들은 Windows 운영체제 차원에서 많은 변화를 요구하게 되었습니다.

사실 현재의 개발 환경은 굉장히 과도기적인 상태라고 할 수 있습니다.

이제 막 이러한 패러다임의 변화들에 대해서 많은 소프트웨어적인 기술들이 공개되고,

개발자들의 선택을 기다리고 있는 상황입니다.

( 대표적으로는, TBB나 CUDA 같은 기술들이 있을 것입니다.^^ )

 

XP 시대까지는 많은 부분들이 전통적인 아키텍쳐 구조들을 기반으로 해서 구현되었고,

꾸준히 개선되어 왔습니다.

즉, XP 시대까지는 싱글코어 기반으로 대부분의 아키텍쳐들이 설계되었습니다.

그래서 Windows XP가 안정적이고 훌륭한 운영체제로 평가 받는 것입니다.

 

하지만, Windows 7 운영체제를 시작으로 앞으로는 많은 수의 CPU를 활용한 구조와

GPU를 활용하는 구조로 변경되고 있으며, 빠르게 XP세대를 대체해 나갈 것입니다.

( 정확하게는 Windows Vista 운영체제부터 시작되었습니다.^^ )

 

앞서 DirectX의 탄생의 과정에 대해서 짧게 살펴보았습니다.

DirectX의 가장 큰 장점은 그래픽 하드웨어의 지원을 받아서

빠른 성능으로 고품질의 결과를 처리할 수 있다는 것이다.

마이크로소프트는 DirectX를 이용해서 고속으로 드라이버에 접근할 수 있는 구조를 만들었습니다.

이를 HAL 이라고 합니다.

 

결론을 얘기하자면,

DirectX 이용한 렌더링( rendering ) 작업이 GDI를 이용한 작업보다 훨씬 더 빠르고 뛰어납니다.

품질은 비교해 보면, 아래와 같습니다.

 

 

현 세대의 PC들은 막강한 성능의 GPU를 탑재하고 있으며,

이들은 대부분 게임과 같은 멀티미디어 관련 애플리케이션을 실행하지 않는 이상은

거의 사용되지 못하고 있었습니다.

그래서 Windows 7 운영체제는 이를 활용하기 위해서 화면에 그리는 작업 패러다임을

완전히 변경해 버렸습니다.( 물론 비스타도 포함됩니다.^^ )

아래의 그림이 이제는 윈도우즈 운영체제 환경의 기본 추상화 계층입니다.

 

 

 

위의 그림에서 보이는 것처럼, 이제 화면에 무엇인가를 그리는 모든 작업은

DirectX를 이용해서 수행하게 되었습니다.( DXGI가 바로 DirectX 입니다. )

이 말은 즉, 기본적으로 GPU를 활용한 한다는 의미입니다.

그렇다고 현재 GDI 가 당장 사라져 버린 것은 아닙니다.

아직까지는 호환성 유지를 위해서 상당기간 공존할 것입니다.

하지만 DirectX 를 활용하는 이 방법은 빠르게 GDI를 대체해 나갈 것이다.

 

 

CPU는 범용 목적으로 설계되었기 때문에,

렌더링 목적으로 설계된 GPU 보다는 렌더링에 대한 작업만큼은 느릴 수 밖에 없습니다.

왜냐하면 GPU는 복잡하고, 많은 수치 연산에 특화된 구조이기 때문입니다.

 

이 DirectX를 강력함을 사용하기 때문에 XP 세대의 운영체제보다 Windows 7이 좋습니다.

( 왠지 홍보하는 것처럼 들리겠지만, 부인할 수 없는 사실이랍니다. ^^ )

 

앞으로 윈도우 프로그래밍에서도 이 DirectX를 이용하는 것이 보편화 될 것입니다.

이제 윈도우 프로그래밍 세계도 큰 변화가 예고되고 있습니다.

 

왜 GPU인가?

 

오늘 날, 프로세싱 유닛의 관점에서 컴퓨터를 바라보면 아래와 같습니다.

 

 

위의 그림에서 CPU는 4개입니다. 이 말은 연산 처리가 가능한 유닛이 4개라는 얘기입니다.

오른쪽 그림은 그래픽 카드를 표현한 것인데,

그래픽 카드는 SIMD 형태로 데이터를 병렬적으로 처리할 수 있는 유닛이 매우 많이 존재합니다.

직관적으로 평가해도 좌측의 CPU 4개 보다는 훨씬 많아 보입니다.

컴퓨터에 CPU 3GHz 가 4개 구동되고 있다면, 초당 연산을 하는 횟수가 48~96GFlops 라고 합니다. ( GFlops 는 109 Flops입니다. )

반면 1GHz GPU 1개가 처리할 수 있는 연산 횟수는 1TeraFlops 입니다. ( TeraFlops는 1012 Flops 입니다. )
GPU는 실수(float) 처리와 병렬처리에 이미 최적화 된 유닛이기 때문에 이것이 가능합니다.

반면 CPU는 범용 목적으로 설계된 유닛입니다.

그래서, If 문과 같은 조건 분기 명령어들은 GPU보다 CPU가 훨씬 빠르게 처리할 수 있습니다.

지금까지는 GPU는 그래픽 처리만을 위해서 존재했었습니다.

특히나 게임과 같은 대용량의 그래픽 처리를 필요로 하는 경우에는
이들의 역할이 절대적 이였습니다.

하지만, 그 이외의 경우는 사용되고 있었을까요?

대답은 '아니다' 입니다.

게임과 같은 경우 DirectX를 통해서 이들을 활용할 수 있었지만,

일반 애플리케이션의 경우에는 이 GPU를 활용할 방법이 없었습니다.

즉, 일반 애플리케이션에서 GPU는 거의 아무 일도 하지 않고 방치되어 있는 것입니다.

CPU의 일을 GPU에게 분담해서 CPU의 부담을 줄이고,

GPU의 활용 능력을 극대화 하면 자연스럽게 최적화가 가능합니다.

그래서 GPU를 활용하는 것이 현재 Windows 7 운영체제에서는

하나의 중요한 이슈로서 자리 잡고 있습니다.

[StartD2D-1] Good-bye~~ GDI…

DirectX 11 2011. 3. 17. 08:00 Posted by 알 수 없는 사용자

 

'윈도우즈 7 운영체제가 왜 좋은 것일까?'

 

이 물음에 프로그래머라면, 어떤 대답을 할 수 있을까요?

여러 가지 대답들이 존재하겠지만, 그 중에 하나인 부분을 지금부터 진행하려 합니다.

지금부터 언급하는 내용은 모니터에 무엇인가를 표현하는 것과 관련이 있습니다.

즉, 드로잉( Drawing ) 작업에 관한 내용입니다.

 

혹시 여러분들은 'GDI' 와 'DirectX' 이들 단어에 대해서 들어본 적이 있습니까?

컴퓨터를 가지고 작업을 경험했던 이들이라면, 들어봤을 법한 용어들입니다.

 

여러분들이 사용하는 컴퓨터의 모니터는 어떤 과정을 거쳐서 우리에게 전달되는 것일까요?

분명히 이것은 하드웨어적인 영역일 수도 있습니다.

조금 더 애플리케이션 프로그래머 관점으로 이 호기심을 확장해 보겠습니다.

'어떻게 하면 모니터 화면을 제어할 수 있을까?'

'어떻게 하면 내가 원하는 모습으로 모니터의 화면에 나타낼 수 있을까?'

'윈도우즈 운영체제에서 화면을 무엇인가를 그리는 시스템은 어떻게 구성되어 있을까?'

 

 

위의 그림은 XP를 포함한 이전 세대의 Windows 운영체제( 이하 XP세대 )가

화면을 제어하는 시스템 구조입니다.

즉, 하드웨어 추상화 계층이라고도 합니다.

XP세대의 운영체제에서는 화면에 무엇인가를 그리기 위해서는

GDI 와 DirectX API, 이 두 가지를 통해서 제어가 가능했습니다.

최종적으로 GDI 와 DirectX API 는 드라이버와 통신을 하면서,

우리의 명령어를 처리해서, 하드웨어로 결과를 보내주게 됩니다.

 

그런데 왜 동일한 일을 하는 구조가 2개씩이나 존재하는 것일까요?

그것은 컴퓨터의 탄생과 발전이 예상을 벗어났기 때문에 생긴 일입니다.

초창기 컴퓨터의 구조는 굉장히 간단했습니다.

아래의 그림을 살펴보겠습니다.

 

 

컴퓨터라는 것은 결국 CPU 에게 연산을 부탁해서 처리된 결과를 메모리에 저장을 합니다.

그리고 이들에 대한 제어를 우리는 I/O 장치들을 통해서 수행하고 확인합니다.

초창기 컴퓨터는 프로세싱 유닛이 CPU 오직 하나였습니다.

그래서 API들은 프로세싱 유닛이 CPU 하나임을 전제로 개발되었습니다.

우리가 지금 배우고 있는 Windows API 역시 CPU가 한나인 경우에

포커스를 두어서 개발되었던 API 였습니다.

 

하지만 오늘날의 컴퓨터는 어떠합니까?

컴퓨터에서 CPU의 수는 2개 이상이고, 앞으로도 그 개수는 증가할 것입니다.

거기다 이제는 프로세싱 유닛이 CPU 뿐만 아니라, GPU 도 활용이 가능한 시대입니다.

메모리는 또 얼마나 발전하고 있습니까?

굉장히 방대한 용량의 메모리를 여러 개 꽂아서 사용하고 있으며,

그래픽 카드에는 비디오 메모리라는 것도 별도로 존재합니다.

컴퓨터의 큰 구조는 변한 것이 없지만, 기능이 점점 세분화 되고 있는 것입니다.

 

이런 하드웨어들의 변화들은 결국 API를 만드는 사람들의 개발 패러다임을 변화시킵니다.

그러한 패러다임의 변화는 애플리케이션 개발자들에게 새로운 학습을 요구합니다.

즉, 여러분들은 그런 변화에 맞추어서 더 많은 공부를 해야 하는 것입니다.T.T

 

 

초창기 Windows 운영체제 차원에서 화면에 그리기 위한 방법은,

GDI 관련 API 를 사용하는 것뿐 이였습니다.

운영체제 차원에서 모든 것을 관리하고 싶었기 때문에,

MS측에서도 별다른 문제를 느끼지 못했을 것입니다.

그런데 예측을 빗나간 부분이 있었는데, 그것은 게임 이였습니다.

게임 개발자들은 GDI 라는 훌륭한 모델이 있음에도 불구하고,

여전히 MS-DOS 기반으로 화면을 제어하고 있었던 것 이였습니다.

이유는 간단했습니다.

GDI 를 활용하면 분명히 쉬운 방법으로 개발을 할 수 있었지만,

DOS 기반의 방법보다 상대적으로 느렸습니다.

MS-DOS를 활용하면 하드웨어를 개발자가 직접 제어할 수 있기 때문에,

Windows 운영체제의 관리를 받는 GDI 보다는 훨씬 빨랐던 것입니다.

( 이 당시만 해도, CPU 사이클을 줄이는 것이 큰 최적화 이슈였습니다. )

MS-DOS를 빨리 벗어나려 했던 MS 입장에서는 적지 않은 충격(?)을 받게 됩니다.

물론 이런 문제는 게임뿐만 아니라,

멀티미디어 관련 개발에서도 나타났던 문제였습니다.^^

 

그렇게 해서 등장한 대한이 바로 DirectX 입니다.

처음 명칭이 GameSDK 였음에서 알 수 있듯이 게임 개발자를 위한 API 였습니다.

이 DirectX는 Windows 95 운영체제부터 추가되어서 활용되기 시작했습니다.

어쩌면, GDI 모델을 완전히 수정할 여유나 계획은 없었을지도 모릅니다.

어찌되었든 현재까지 GDI 와 DirectX는 이렇게 공존하면서 XP 운영체제까지 이르게 되었습니다.

 

XP 세대까지는 화면에 무엇인가를 그리는 작업을 기준으로 봤을 때는,

애플리케이션은 DirectX를 활용하는 애플리케이션과 GDI를 활용하는 애플리케이션으로 나눌 수 있었습니다.

 

GDI( Graphics Device Interface ) 라는 용어에서 보듯이,

일반적인 윈도우 애플리케이션에서 모니터 화면에 무엇인가를 출력하기 위해서는

GDI 라는 것을 이용해서 구현합니다.

이는 2D API 라고 볼 수 있습니다.

GDI를 통해서 애플리케이션과 디바이스 드라이버( Device driver ) 사이를
제어
할 수 있습니다.

XP 세대에서 화면에 보여지는 대부분의 구성은 바로 이 GDI 를 활용한 것입니다.

GDI는 소프트웨어적으로 처리되기 때문에,

그래픽 하드웨어와 상관없이 처리할 수 있다는 큰 장점이 있습니다.

즉, CPU가 그래픽 처리에 필요한 연산을 수행합니다.

 

일반적인 윈도우 애플리케이션은 그래픽 하드웨어를 직접적으로 접근( access )할 수 없습니다.

반드시 GDI를 통해서 디바이스 드라이버에 접근해야 합니다.

GDI 의 최대 강점은 바로 디바이스 독립적( device-dependent ) 이라는 것입니다.

세상의 모든 PC가 동일한 사양을 갖추고 있으면 좋겠지만,

PC들은 저마다 다른 성능과 사양을 갖추고 있기 때문에,

이를 모두 제어하는 것은 굉장히 어렵고 힘든 일입니다.

하지만, GDI 코드는 어떠한 장치에 상관없이 화면에 공통된 그래픽을 표현할 수 있습니다.

왜냐하면 CPU가 이들에 대한 처리를 모두 수행하기 때문입니다.

GDI는 Windows 운영체제의 매우 오래된 기술 중 하나이며,

현재까지도 가장 널리 이용되는 윈도우 그래픽 API 라고 할 수 있습니다.

하지만 최근의 멀티코어 환경과 GPU의 발전은 많은 변화들을 가져왔으며,

GDI도 이러한 변화에 예외가 될 수 없었습니다.

급기야 이제는 DirectX가 GDI를 서서히 대체해 나가는 방향으로
가는 길이 정해지고 말았습니다.T.T

Good-bye!! GDI~~