О том, как память текла, а я не мог понять, почему

в 17:12, , рубрики: c++, DirectX, memory leaks, разработка под windows, разработка под windows phone

Здравствуйте, уважаемые читатели.

В этом коротком посте я хочу поделиться с вами некоторыми моментами, с которыми столкнулся при разработке одного из своих приложений (читалка для 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

Источник


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js