Как именно обработка памяти (то есть функция Release) работает с Direct3D? - PullRequest
5 голосов
/ 15 мая 2011

Я столкнулся с утечкой в ​​моем приложении Direct3D и в итоге исправил ее, но я думаю, что причина утечки была в том, что я неправильно понял, как Direct3D обрабатывает свою память и интерфейсы.

Мне не удалось найти конкретную статью / учебное пособие (пожалуйста, предоставьте ее, если она у вас есть), но из того, что я собрал, она работает следующим образом:

  • Каждый раз, когда вывызовите метод Get, количество ссылок для возвращаемого объекта увеличивается.Поэтому, если я вызываю GetRenderTarget, то для отображаемой поверхности увеличивается счетчик ссылок.
  • Вызов Release в интерфейсе уменьшает счетчик ссылок.Эти первые две точки в совокупности по сути означают: каждый раз, когда вы получаете интерфейс, отпускайте его после того, как с ним покончено.
  • Когда счетчик ссылок достигает 0, экземпляр удаляется.

Я не совсем уверен, правильно ли это, но, похоже, это работает на практике.Если бы кто-то мог уточнить / подтвердить, как это работает, это было бы замечательно.

PS, существуют ли какие-либо меры предосторожности при выпуске интерфейсов?Вызов Release любое количество раз в обратном буфере, похоже, не наносит ущерба (что хорошо, но я не уверен, почему это не так).

Ответы [ 4 ]

7 голосов
/ 15 мая 2011

Direct3D основан на COM, технологии, которой не менее 15 лет.Похоже, многие утверждают, что COM мертв, и по этой причине многие упускают его из виду, но реальность такова, что в Windows есть много вещей, включая Direct3D и новый Media Foundation MS, которые основаны на COM.

Я настоятельно рекомендую вам принятьвзгляд на общее программирование COM.Есть много книг и ресурсов, но многие из них довольно старые, но это нормально, потому что корень технологии не менялся в течение очень долгого времени.

В основном вы наблюдали подсчет ссылок на интерфейс,COM основан исключительно на доступе к объектам через интерфейсы, которые все происходят из базового интерфейса, IUnknown.IUnknown реализует методы AddRef () и Release (), и ваше приложение должно вызывать AddRef () всякий раз, когда вы сохраняете локальную копию указателя, и вызывать Release () всякий раз, когда эта локальная копия больше не нужна.

Если у вас есть методы с параметрами интерфейса out (например, IFoo ** ppObj), это означает, что вызываемый абонент возвращает вам интерфейс, и теперь, когда у вас есть его, вы все равно обязаны вызывать Release (), когда вы закончите.с ним.

Как только вы освоите его, я бы предложил вам начать использовать умный класс CComPtr для хранения локальных переменных и переменных-членов (по-прежнему передавать необработанные значения интерфейса между вызовами функций, нет необходимости в типах параметров интеллектуального указателя)).Он позаботится о подсчете всех ваших ссылок.Также не практикуйте называние релиза «любым числом» раз.Это может сработать сегодня, потому что объект реализован как одноэлементный, или, возможно, что-то еще удерживает его, но это может измениться со следующим патчем или следующим выпуском.Всегда следуйте правилам.Если у вас есть интерфейс, когда он вам не нужен, вызывайте Release () ровно один раз.Если вы сделали копию указателя интерфейса, обязательно вызовите AddRef () ровно один раз.

4 голосов
/ 15 мая 2011

Применение семантики addref / release намного шире, чем технология COM.Существует простое правило: one CreateObject() (или CreateTexture, или GetRenderTarget, или GetBackBuffer, и т. Д.) Необходимо сопоставлять с one Release(), one AddRef() необходимо сопоставить с one Release().

В COM IUnknown::Release() возвращает количество ссылок на объект.Это может ввести вас в заблуждение, и вы можете подумать: "Хм ... Я просто звоню Release(), пока не вернусь 0, и у меня не будет утечек. ???? ПРИБЫЛЬ !!!!! 111" <- Это неправильно!<code>AddRef может вызываться самим Direct3D или библиотекой 3rd_party, в которую вы передаете этот объект, или чем-то еще вне вашего приложения. Один Release для один AddRef.Вы должны звонить Release, когда вам больше не нужен объект, не тратьте системные ресурсы.Вы сказали:

Calling Release любое количество раз в обратном буфере, кажется, не наносит ущерба

Это ничего не значит.Может быть, Вселенная вас так сильно любит, или вам просто повезло, что вы не получили исключений из D3D.

Умные указатели (такие как CComPtr) могут сделать вашу жизнь намного проще, если вы будете их использовать.В этом случае вам не нужно вызывать Release явно, он вызывается в CComPtr dtor, если он назначен какому-либо объекту.

void get_surface(IDirect3DDevice9 *pDevice)
{
  IDirect3DSurface9 *surf0;
  IDirect3DSurface9 *surf1;
  CComPtr<IDirect3DSurface9> surf2;
  CComPtr<IDirect3DSurface9> surf3;
  CComPtr<IDirect3DSurface9> surf4;

  pDevice->GetRenderTarget( 0, surf0 ); // surface reference counter incremented, you should call Release() for this
  surf1 = surf0; // surface reference count is not incremented, you shouldn't call Release() for this

  pDevice->GetRenderTarget( 0, surf2 ); // surface reference counter incremented
  CComPtr<IDirect3DSurface9> surf3 = surf0; // surface reference counter incremented

  surf0->Release(); // release for pDevice->GetRenderTarget( 0, surf0 );
  surf2.Release();  // .Release() used not ->Release() - it is important
  surf4.Release();  // nothing happens because surf4 == 0
} // surf3.Release() is called in surf3 destructor

Также вы можете #define D3D_DEBUG_INFO перед включением прямого 3dЗаголовки и переключиться в режим отладки d3d.Это полезно для обнаружения утечек в приложении d3d.

Пусть CComPtr Force будет с вами.

2 голосов
/ 15 мая 2011

D3D-объекты являются COM-объектами, и они используют базовую систему подсчета ссылок для управления временем жизни объекта.(См. Википедию для получения дополнительной информации о объектной модели компонентов или статье MSDN Управление временами жизни объектов )

Счетчик ссылок изменяется только через AddRef /Методы Release и некоторые другие функции вызывают эти методы.

Создание объекта, а также вызов определенных методов Get, которые возвращают объект, полученный из класса IUnknown, будут вызывать AddRef внутри для увеличениясчетчик ссылок, поэтому вам нужно будет вызывать Release для каждого вызова, когда вы закончите с объектом.

Если вы передадите объект другой функции или классу, который хранит копию точки (даже временно)) этот класс / функция должен вызывать AddRef, чтобы гарантировать, что объект не освобождается во время его использования (и Release, чтобы сигнализировать об этом).

Когда счетчик ссылок достигает 0 от вызоваRelease объекту сообщается, что это может быть подходящее время для удаления удерживаемых ресурсов, но это может произойти не сразу.Также нет защиты для вызова Release несколько раз.Счетчик ссылок не станет отрицательным, но он не будет выполнять какую-либо другую проверку работоспособности (потому что на самом деле он не может), поэтому вы можете вызвать нестабильность приложения, пытаясь освободить ссылки, которые у вас нет.

1 голос
/ 15 мая 2011

Да, вы правы. Это называется подсчетом ссылок, и он гарантирует, что объекты живы, пока они используются, и больше не используются. Вы можете использовать различные интеллектуальные указатели для обеспечения соблюдения этого правила - и shared_ptr, и (C ++ 11) unique_ptr позволяют пользовательским удалителям вызывать Release(). Это позволяет легко управлять временем жизни объектов Direct3D так же, как и для любого другого объекта в вашем приложении. Вам не нужно начинать включать библиотеки ATL и CComPtr, чтобы использовать интеллектуальные указатели с интерфейсами COM.

...