2012년 5월 21일 월요일

DX9/11 에서 COM 스마트 포인터 쓰기?


원래 쓰려고 했던 글...
최근에 만들고 있는 real-time 소프트웨어가 있는데(게임은 아님), 여기에 사용할 매우 얇은(thin) DX9 렌더링 엔진을 만들었었습니다. 근데 이게 OpenCL과 같이 사용하기가 만만치 않아서 이걸 다시 DX11로 포팅을 했죠. 이 소프트웨어는 윈도우 PC용으로 제작중인데, 좀 편하더군요. 지난 6+ 년동안 다뤘던 하드웨어인 현세대 콘솔 게임기(엑박 360, 플스3 등) 보다훨씬 파워풀한 하드웨어에서 도니까요.

이번에 코딩을 하다가 깨달은게... Direct3D는 COM인데 게임 렌더링 엔진에서 COM 스마트 포인터를 쓰는걸 본적이 없더라구요. 제작에 참여한 게임 수만해도 15개가 넘고, 다뤄본 게임엔진만도 7개가 넘는데 말이죠. 아마도 성능이 딸릴 걸 걱정해서 그러는거 같은데 이젠 그냥 기우가 아닌가 하고 생각을 하게 되었습니다. 아무래도 5년전 하고만 비교해도 컴퓨터 성능이 엄청나게 떳으니까요. 그리고 최근 몇 년 동안의 동향이 게임코드에서 스마트 포인터를 많이 사용하는 거였거든요. (물론 렌더링 엔진은 아직도 예외인듯..) 그리고 게임코드에서 사용하는 스마트 포인터때문에 성능저하가 일어나는 걸 본 것도 몇 번 안되구요. (뭐 생기더라도 고치는 것도 어렵지 않았음). 그래서 'D3D 오브젝트에서 COM 스마트 포인터를 써도 상관 없을 것 같은데...?' 라는 생각이 듭니다. 혹시 상용게임에서 이거 써보신 분 계신가요? 그렇다면 어땠는지 좀 알려주시죠 ^_^?

그래도 뭔가 유용한 정보를 써야....
이렇게 글을 끝내기엔 뭔가 남에게 도움이 되지 않은 거 같지 않아 죄책감이 듭니다...(이렇게 허접한 글을 쓰시는 분들도 있지만... 전 양심에 걸림... -_-). 그래서 DX9하고 DX11 에서 COM 스마트 포인터를 쓰는 법을 소개하기로.... 쿨럭쿨럭... -_-

스마트 포인터를 안쓴다면?
D3D를 비롯한 COM 개체의 라이프사이클은 참조카운터(reference counter)에 따라 좌우됩니다. 예를 들어 CreateTexture() 함수를 통해 생성한 텍스처는 참조카운터가 1입니다. 이제 D3D가 내부적으로 이 텍스처를 사용할 떄마다 참조카운터를 1씩 증가시켰다가 사용을 끝마치면 1씩 감소시키죠. 프로그래머가 일일이 참조카운터를 증가/감소시킬 수도 있습니다. AddRef()함수와 Release()함수를 통해서요. 나중에 이 텍스처가 더이상 필요없어서 지우고 싶을 때도 그냥 Release() 함수를 호출합니다. D3D 가 내부적으로 아직 이 텍스처를 이용하고 있을지도 모르니 delete를 호출해서 곧바로 지워버리면 안되지요. 참조카운터가 0으로 떨어지면 D3D가 알아서 지워줍니다.

따라서 렌더링 엔진의 destructor()를 보면 Release()를 호출해주는 코드가 꽤 많죠. 뭔가 귀찮아 보이죠? 네 -_-... 그래서 이걸 자동적으로 되게 하려는 시도가 COM 스마트 포인터입니다. (더 자세한건 구글형님께 물어보세요. -_-) 그럼 다음은 각 DX 버전별로 스마트 포인터를 쓰는 법을...

DX9:
DX9에서 스마트포인터 형을 선언하려면 d3d9.h를 인클루드 하기전에 comdef.h를 인클루드 하시면 됩니다.

#incldue <comdef.h> 
#include <d3d9.h>

이러면 모든 ID3D형에 대해 ~Ptr 접미사를 붙인 스마트 포인터가 선언됩니다. 이제 D3D 디바이스의 스마트 포인터를 선언하려면 이렇게 하면 되죠.

IDirect3DDevice9Ptr mDevice;

이제 이 스마트 포인터를 사용하는 건 그다지 어렵지 않습니다. 다른 스마트 포인터랑 비슷해요. 특별한 함정도 없죠. (DX11엔 함정이 있음 -_-)

DX11:
d3d11.h은 D3D 인터페이스마다 COM 스마트 포인터형을 선언하는 preprocessor 매크로가 없어요. 따라서 전 atlbase의 COM 스마트 포인터를 써야했답니다.

일단 atlbase.h를 인클루드 하시구요.

#include <atlbase.h>

이제 다음과 같이 일일이 스마트 포인터를 선언해주시면 됩니다.

CComPtr<ID3D11Device> mDevice;

사용법은 DX9의 COM 스마트 포인터와 비슷한데요.. 한가지 함정이 있죠. CComPtr<>의 속이 비어있지 않은데 주소를 구해오려면 assert가 발생합니다. 그리고 디렉트X를 사용해 보신 분이라면 얼마나 자주 포인터의 포인터(**)를 매개변수로 전달해줘야 하는지 아시죠? (렌더타겟 텍스처를 만들 때라던가가 아주 좋은 예죠.) 따라서 이런 상황이라면 & 연산자를 쓰기전에 우선 스마트 포인터의 속을 비워줘야 합니다. 간단히 nullptr를 대입해주면 되네요.

mRenderTarget = nullptr;

여기서 한가지 함정.... 사실 전 CComPtr<>의 detach() 함수가 nullptr를 대입해 주는 것과 똑같은 일을 하는 줄 알았거든요. 근데 아니더군요..... detach()를 호출하면 내부적으로 Release()를 호출하지 않은 채 그냥 포인터를 내던져요... 그래서 메모리 누수가 생기게 되죠 GPU상에.... 켕 -_-; detach() 쓰지 마시고 nullptr대입하세요.


자, 이정도면 그나마 쓸만한 정보를 제공해드린 듯 하니.. 전 이만 뱌뱌....


p.s. 오랜만에 글 쓴 포프였습니다. 물론 여전히 꽃미남 입니다 -_-;


댓글 5개:

  1. 음, 저는 보통 랜더링 엔진쪽은 오픈소스인 WildMagic게임 엔진을 자주 참고하는데요 요번에 새로 나온 WildMagic엔진에서는 전버전과 달리 다이렉트x 스마트 포인터를 사용하더군요. 그래서 이번에 새로 들어갈 학교 게임프로젝트에 제 엔진을 스마트 포인터로 바꿀까 생각하고 있는데 크게 문제 없지 않을까 싶습니다. 이건 그냥 저의 생각...(사실 다이렉트x9 기반으로 하다보니 디바이스 로스트 실수로 체크 안했다가 메모리 릭나면 또 그게 누적되다보면 컴퓨터 리부팅이 귀찮아서..ㅜ 스마트 포인터 써볼 생각입니다)~ㅎ

    답글삭제
    답글
    1. 릭나면 컴터 리부팅 까지 해야하나요? 건 몰랐네 ㅎㅎㅎ..

      스마트 포인터... 써도 잘못쓰면 다 릭 납니다 -_-;

      삭제
  2. 왜 수정모드가 없는거지.. 다시 써야하다니..ㅜ
    정말 사소한 3d 파티클에서 텍스트 파일 릴리즈를 안시켜주더니 메모리 릭이 꽤 나던것 같은데, 제가 노트북에서 작업해서 모르겠는데 그런게 누적되니 작업하는데 좀 짜증이 나서 재부팅을 하곤 했거든요. 스마트 포인터를 잘못써도 릭이 나나요? 제가 다이렉트x에 있는 걸 써보지는 않았지만, 스마트 포인터는 스코프를 벗어나면 릭 해제가 일어나지 않나요? 클레스 멤버변수로 있으면 프로그램 끝날때에서 적어도 릴리즈는 시켜줄것 같은데... 아닌가?ㅜ

    답글삭제
    답글
    1. 스마트 포인터도 잘못쓰면 당연히 릭이 납니다... 정확한 디테일 구현마다 틀리고.. 당장 생각나는게 없으니 패스 -_-; 대부분은 안나죠... 먼가 어이없게 쓰면 나는데.. 그때 이걸 찾는게 쥐약이라는.....

      삭제
    2. 크흑... 그렇군요. 으흠 조심해서 써야겠네요 ㅜㅜ

      삭제