Здравствуйте, уважаемые читатели.
В этом коротком посте я хочу поделиться с вами некоторыми моментами, с которыми столкнулся при разработке одного из своих приложений (читалка для Windows). Речь пойдет о DirectX и, как мне показалось, странных утечках памяти.
Как я создал себе проблему?
Для отображения содержимого страниц я решил использовать DirectX. Задумка была проста: сначала создаю 2D-текстуру с текстом, а потом отображаю 3D модель с использованием подготовленных ранее текстур. Это дает мне возможность делать анимацию 3D перелистывания страниц.
Как-то так:
В момент выпуска приложения в магазин я ожидал всеобщего восхищения. Но не тут-то было. Пользователи оказались недовольны. Анализ ситуации показал, что течет память. И очень хорошо течет. Но почему? Этого я долго не мог понять.
С учетом того, что приложения в Windows 8.1 и Windows Phone 8.1 полностью не выгружаются при «закрытии», утечки памяти накапливались.
Сам процесс поиска утечки оказался совсем не интересным. А вот результат показался мне странным.
Что мне показалось странным?
Освобождение ресурсов render-target (не знаю как перевести) и буфера глубины
Для отображения одного кадра 3D сцены используются следующие объекты:
Microsoft::WRL::ComPtr<ID3D11RenderTargetView> m_renderTargetView;
Microsoft::WRL::ComPtr<ID3D11DepthStencilView> m_depthStencilView;
Далее, где-то в методе отображения сцены, мы будем использовать примерно такой код:
ID3D11RenderTargetView *const targets[1] = { m_renderTargetView.Get() };
m_d3dContext->OMSetRenderTargets(1, targets, m_depthStencilView.Get());
Ресурсы надо освобождать. И вот здесь таится одна из проблем, которая стала причиной этого поста.
Просто так, написать:
m_renderTargetView = nullptr;
m_depthStencilView = nullptr;
недостаточно. У m_d3dContext где-то внутри остаются ссылки на render-target и буфер глубины.
Т.е. надо нашему контексту указать ничто в качестве цели.
m_d3dContext->OMSetRenderTargets(0, nullptr, nullptr);
Беда заключается в том, что мы получим утечку памяти если сделаем вот так:
m_d3dContext->OMSetRenderTargets(0, nullptr, nullptr);
m_renderTargetView = nullptr;
m_depthStencilView = nullptr;
Чтобы исправить ситуацию, достаточно изменить порядок инструкций:
m_renderTargetView = nullptr;
m_depthStencilView = nullptr;
m_d3dContext->OMSetRenderTargets(0, nullptr, nullptr);
Это первая «странность», которую я не понимаю. Я буду рад, если кто-нибудь расскажет почему так происходит.
Освобождение ресурсов текстур
Вторая «странность» связана с текстурами. А точнее с шейдерными ресурсами.
Дело в том, что если мы делаем вот так:
Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> texture = ...
m_d3dContext->PSSetShaderResources(0, 1, &texture);
То должны сделать и так:
ID3D11ShaderResourceView* empty = NULL;
d3dContext->PSSetShaderResources(0, 1, &empty);
В противном случае текстура освобождена не будет, даже если вы её «удалите».
texture = nullptr;
Иными словами перед удалением (release) шейдерных ресурсов их надо освободить (unbind).
В принципе, после осознания первой «странности», вторая уже и не кажется таковой.
И тем не менее, мне это показалось не очевидным.
P.S.: Сейчас проблема с утечкой памяти исправлена и гнев пользователей постепенно меняется на милость.
Автор: leschenko