Windows 10 UWP에서 D3D객체들이 완전히 해제되었는지 확인하기.

Windows 10 UWP((Windows 8.x Store App포함하여)에선 종료 개념이 없다.
따라서 메모리 leak체크가 약간 곤란하다. 그래도_CrtMemCheckpoint()와 _CrtMemDumpAllObjectsSince()를 사용하여 내가 작성한 코드에서 메모리가 새는지 여부는 체크할 수 있다.

그런데 Direct3D를  사용하는 경우는 D3D 오브젝트들을 정확히 해제하고 있는지 확신하는것이 어렵다.
win32 데스크탑 어플리케이션에선에선 종료시점에 해제되지 않은 D3D객체들을 확인할 수 있다.
하지만 WinRT앱에선 종료시점이 없으므로 불가능하다.

WinRT에선 HWND가 아닌 Page의 SwapChainPanel을 D3D의 SwapChain에 바인드 하는데 이 경우 D3D의 SwapChain의 ref count가 증가한다. 따라서 D3D의 SwapChain을 제거하려면 Page의 SwapChainPanel과의 연결을 완전히 끊어야한다.

여러모로 디버깅하기가 거지같다. 하지만 방법이 없는 것은 아니다.
Windows 10 UWP(WinRT)환경에서 에서 D3D 객체가 완전히 제거되었는지 확인할 수 있다.
다음과 같은 방법을 사용하면 된다. 약간 꿀꿀하지만 디버그 모드에서만 사용하면 되니까 문제 될것은 없다.

1.임의의 종료시점 설정.
디버그 모드에 한해서 종료버튼을 만들어넣거나 suspend 이벤트를 종료 처리 시점으로 간주한다.
단 D3D를 사용할때는 WinRT측 SwapChainPanel을 소유하고 있는 Page와 D3D의 IDXGISwapChain1이 상호 참조하기 때문에 결국 Page를 제거하지 않으면 D3D의 오브젝트를 깔끔하게 제거할 수 없다.
그래서 suspend이벤트를 종료시점으로 간주하는것은 적합하지 않다. 디버그 모드에서만 보이는 종료버튼을 하나 넣어주도록 한다.

2. 종료시점에서 SwapChainPanel 을 소유하고 있는 Page 제거.
제거를 어떻게 하느나면…아무 페이지나 하나 만들어서 그쪽으로 Navigate시킨다. 그러면 해당 페이지의 소멸자가 호출된다.

3. WinRT측 SwapChainPanel을 소유하고 있는 Page의 소멸자가 호출되면 SwapChainPanel객체에 nullptr을 대입한다.
WinRT ref class객체들은 auto ref counting 방식으로 작동한다. nullptr을 대입하면 ref count를 떨어뜨려 Release()를 호출한 것과 같은 효과를 얻을 수 있다.
WinRT측 SwapChainPanel객체가 D3D의 IDXGISwapChain1 객체를 참조하고 있기 때문에 SwapChainPanel 객체를 제거하지 않으면 D3D측 IDXGISwapChain1의 ref count를 0으로 만들 수 없다.

4. 위와 같이 소멸자에서 SwapChainPanel을 제거하고 나면  D3D 클린업 코드를 호출한다.

5. D3D에서 SwapChain을 설정할때 ISwapChainPanelNative객체를 얻어와서SetSwapChain()을 호출했을것이다. ISwapChainPanelNative객체를 Release()하기 전에 SetSwapChain(NULL)을 반드시 해주도록 한다.
즉 초기화 시에 아래와 같은 코드를 호출했을 것이고

reinterpret_cast<IUnknown*>(swapChainPanel)->QueryInterface(__uuidof(ISwapChainPanelNative), (void**) &m_pSwapChainPanelNative);
hr = m_pSwapChainPanelNative->SetSwapChain(m_pSwapChain);

종료시에는 IDXGISwapChain1객체를 해제하기 전에 아래와 같은 코드를 꼭 호출한다.

ULONG ref_count = 0;
if (m_pSwapChainPanelNative)
{

m_pSwapChainPanelNative->SetSwapChain(NULL);
ref_count = m_pSwapChainPanelNative->Release();
m_pSwapChainPanelNative = NULL;

 }

6. 모든 D3D객체를 다 해제했다면, 마지막으로 IDirect3DDeivce객체를 해제할때 정확하게 0이 리턴된다. 리턴값은 현재 참조 카운트이다. 0이 아니라면 어딘가 해제되지 않은 D3D객체가 있는 것이다.

[샘플코드]
// 임의의 종료버튼을 눌렀을 때 호출되는 핸들러
void MainPage::ExitButton_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
{

auto dispatcher = Windows::ApplicationModel::Core::CoreApplication::MainView->CoreWindow->Dispatcher;
auto frame = this->Frame;

dispatcher->RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal,ref new Windows::UI::Core::DispatchedHandler([frame]()
{

 Sleep(100); // 이벤트 핸들러 코드가 리턴된 후에야 이 페이지가 제거될 것이므로 약간 시간을 벌어준다.꿀꿀하지만 디버그모드에서만 사용할거니까..
frame->Navigate(TypeName(BlankPage::typeid), nullptr);

 }));

}

// 소멸자 코드
MainPage::~MainPage()
{

 swapChainPanel = nullptr; // xaml코드에서 넣어준 SwapChainPanel이다.

auto dispatcher = Windows::ApplicationModel::Core::CoreApplication::MainView->CoreWindow->Dispatcher;

dispatcher->RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal,ref new Windows::UI::Core::DispatchedHandler([]()
{

CleanupD3D();

 }));

 }

[예외사항]
Optimus기술(nvidia + intel hd graphic)이 적용된 노트북에서는 약간 문제가 있다.
Optimus장비인 경우 정확하게 모든 D3D객체들을 해제했음에도 불구하고 D3D디바이스를 Release()할때 ref count가 2남는 현상이 발생한다.
D3D11CreateDevice()를 호출해서 D3D디바이스를 생성할때 일반적으로 그래픽장치를 지정하지 않고 null을 대입할텐데, 이 경우 Optimus설정을 따른다.
내 경우는 Optimus기술이 적용된 노트북에서 global설정으로 항상 nvidia칩을 선택하도록 했다 그래서 언제나 nvidia 디바이스가 선택됐고 문제가 발생했다.
외장 그래픽카드만 장착되어있거나 내장 그래픽칩만 장착된 장비에서는 문제가 없다.
Optimus장비에서도 intel 디바이스를 지정하거나 MS Basic디바이스를 지정하면 이런 현상이 일어나지 않는다.
또한 win32 데스크탑 프로젝트에서도 이런 현상은 없다. ‘WinRT + D3D + Optimus장비의 nvidia디바이스 선택’ 상황에서만 발생한다.
D3D11CreateDevice()에서 내장그래픽을 지정하면 이 문제를 피할 수 있다.
이 글을 작성하는 시점에서 Windows 10 Preview 10066빌드가 최신이고 드라이버도 아직 검증되지 않았기 때문에 이 문제는 곧 해결되리라 생각한다.


답글 남기기

댓글을 게시하려면 다음의 방법 중 하나를 사용하여 로그인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

%s에 연결하는 중