2011-05-15 12 views
5

Encontré una fuga en una aplicación Direct3D mía, y terminé corrigiéndola, pero creo que la causa de la fuga se debió a mi incomprensión de cómo Direct3D maneja su memoria e interfaces.¿Cómo funciona exactamente el manejo de la memoria (es decir, la función Release) con Direct3D?

no he sido capaz de encontrar un artículo/tutorial definitivo sobre el mismo (indique uno si tiene uno), pero por lo que he reunido, funciona como tal:

  • Cada vez llama al método Get, se incrementa el número de referencias para el objeto devuelto. Entonces, si llamo al GetRenderTarget, la superficie que se está procesando tiene su recuento de referencia incrementado.
  • Llamar a Release en la interfaz disminuye su número de referencia. Estos dos primeros puntos combinados significan esencialmente: cada vez que obtienes una interfaz, libérala una vez que hayas terminado con ella.
  • Cuando el recuento de referencias llega a 0, la instancia se elimina.

No estoy del todo seguro de si esto es correcto, pero parece funcionar en la práctica. Si alguien pudiera aclarar/confirmar cómo funciona, sería genial.

P.S, ¿hay alguna protección implementada en la liberación de interfaces? Llamar al Release en cualquier número de veces en el búfer posterior no parece causar ningún daño (lo cual es bueno, pero no estoy seguro de por qué no lo hace).

Respuesta

7

Direct3D se basa en COM, que es una tecnología que tiene al menos 15 años de antigüedad. Parece que muchas personas afirman que COM está muerto y por esa razón muchos lo pasan por alto, pero la realidad es que hay muchas cosas en Windows Direct3D y la nueva Media Foundation de MS, todas basadas en COM.

Sugiero que eche un vistazo a la programación general de COM.Hay muchos libros y recursos, pero muchos de ellos son bastante antiguos, pero está bien porque la raíz de la tecnología no ha cambiado durante mucho tiempo.

Básicamente lo que ha observado es el recuento de referencias de la interfaz. COM se basa exclusivamente en el acceso a objetos a través de interfaces, que todas derivan de la interfaz base, IUnknown. IUnknown implementa los métodos AddRef() y Release() y es responsabilidad de su aplicación llamar a AddRef() siempre que almacene una copia local de un puntero y llame a Release() cuando esa copia local ya no sea necesaria.

Cuando tiene métodos con parámetros de interfaz de salida (es decir, IFoo ** ppObj), eso significa que el destinatario le devuelve una interfaz y ahora que la tiene, sigue siendo su responsabilidad llamar a Release() cuando sea hecho con eso

Una vez que lo domine, le sugiero que comience a utilizar la clase inteligente CComPtr para almacenar variables locales y miembros (aún pase valores crudos de interfaces entre llamadas a funciones, sin necesidad de tipos de parámetros de punteros inteligentes). Se encargará de todo tu conteo de referencias. Tampoco se convierta en una práctica de llamar a la versión "cualquier número" de veces. Podría funcionar hoy porque el objeto se implementa como un singleton, o tal vez algo más se está aferrando a él, pero eso podría cambiar con el próximo parche o la próxima versión. Siempre sigue las reglas. Si tiene una interfaz, cuando no la necesita, llame a Release() exactamente una vez. Si hizo una copia del puntero de la interfaz, asegúrese de llamar a AddRef() exactamente una vez.

+0

Excelente respuesta, gracias. Nunca volvería a llamar a 'Release' más de una vez en la práctica, solo estaba preguntando por qué no se rompió el programa cuando estaba tratando de resolver esto y lo intenté. – Jengerer

2

Los objetos D3D son objetos COM y utilizan un sistema básico de conteo de referencias para administrar la vida útil del objeto. (Ver Wikipedia para obtener más información acerca de la Component Object Model, o el artículo de MSDN Managing Object Lifetimes)

El recuento de referencia se modifica puramente a través de los métodos AddRef/Release, y ciertas otras funciones llamar a esos métodos.

Creación del objeto, así como llamar a ciertas Get métodos que devuelven un objeto derivado de la clase IUnknown llamará AddRef internamente para incrementar el contador de referencia, por lo que tendrá que llamar Release para cada llamada cuando haya terminado con el objeto .

Si pasa el objeto a otra función o clase que almacena una copia del punto (incluso temporalmente) esa clase/función debe llamar al AddRef para asegurarse de que el objeto no se libera mientras lo está utilizando (y Release para señal se hace).

Cuando el contador de referencia llega a 0 desde una llamada al Release, se señala al objeto que puede ser un buen momento para eliminar los recursos retenidos, pero puede que no ocurra inmediatamente. Tampoco hay protección para llamar a Release varias veces. El contador de referencia no se volverá negativo, pero no realizará ninguna otra comprobación de cordura (porque realmente no puede), por lo que puede causar inestabilidad de la aplicación al intentar liberar las referencias que no posee.

4

La aplicación de la semántica addref/release es mucho más amplia que la tecnología COM. No es simple regla unoCreateObject() (o CreateTexture o GetRenderTarget o GetBackBuffer, etc ...) tiene que ser confrontado con una Release(), uno AddRef() tienen que ser confrontado con una Release().

En COM IUnknown::Release() devuelve el número de referencias al objeto. Puede engañarte y puedes pensar: "Hm ... Acabo de llamar al Release() hasta que vuelva a cero y no tendré fugas." BENEFICIO !!!!! 111 "< - ¡Eso está mal! AddRef podría ser llamado por Direct3D mismo o por la biblioteca 3rd_party a la que le pasa este objeto, o algo más fuera de su aplicación. UnoRelease para unoAddRef. Debe llamar al Release cuando ya no necesite más objetos, no desperdicie los recursos del sistema. Dijiste:

Llamada de lanzamiento cualquier número de veces en el búfer de reserva no parece hacer ningún daño

Eso no significa nada. Puede ser el Universo como tú o simplemente demasiado afortunado para no obtener excepciones de D3D.

Punteros inteligentes (como CComPtr) podrían hacer su vida mucho más fácil si los va a usar. En este caso, no necesita llamar al Release explícitamente, se llama en CComPtr dtor si está asignado a algún objeto.

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 

También es posible que #define D3D_DEBUG_INFO antes de incluir cabeceras Direct 3D y cambiar a tiempo de ejecución de depuración D3D. Es útil para encontrar fugas en la aplicación d3d.

Que el CComPtr Force te acompañe.

+0

Muchas gracias por la respuesta. No quise decir que pensé que llamar a 'Release' cien veces era una buena idea o que alguna vez pensaría en hacerlo, solo tenía curiosidad de por qué no lo rompió. :) – Jengerer

1

Sí, estás en lo cierto. Esto se denomina recuento de referencias y garantiza que los objetos estén vivos mientras se estén utilizando, y no más. Puede utilizar una variedad de punteros inteligentes para hacer cumplir esta regla; tanto shared_ptr como (C++ 11) unique_ptr permiten que los modificadores personalizados llamen al Release(). Esto facilita el control de la vida útil de los objetos de Direct3D tal como lo haría con cualquier otro objeto en su aplicación. No necesita comenzar a incluir bibliotecas ATL y CComPtr para usar punteros inteligentes con interfaces COM.

Cuestiones relacionadas