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

김포프 2012-05-21

원래 쓰려고 했던 글…

최근에 만들고 있는 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()를 호출해주는 코드가 꽤 많죠.</span> 뭔가 귀찮아 보이죠? 네 -_-… 그래서 이걸 자동적으로 되게 하려는 시도가 COM 스마트 포인터입니다. (더 자세한 건 구글형님께 물어보세요. -_-) 그럼 다음은 각 DX 버전별로 스마트 포인터를 쓰는 법을…

DX9

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

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

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

IDirect3DDevice9Ptr mDevice;

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

DX11

`d3d11.h</span>은 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. 오랜만에 글 쓴 포프였습니다. 물론 여전히 꽃미남 입니다 -_-;