How exactly does memory handling (i.e, the function Release) work with Direct3D?
Asked Answered
L

4

5

I came across a leak in a Direct3D application of mine, and I ended up correcting it, but I think the cause of the leak was due to my misunderstanding of how Direct3D handles its memory and interfaces.

I haven't been able to find a definitive article/tutorial on it (please provide one if you have one), but from what I've gathered, it works as such:

  • Every time you call a Get method, the number of references for the object returned is incremented. So if I call GetRenderTarget, the surface being rendered to has its reference count incremented.
  • Calling Release on the interface decrements its reference count. These first two points combined essentially mean: every time you get an interface, release it after you're done with it.
  • When the reference count reaches 0, the instance is deleted.

I'm not entirely sure if this is correct, but it seems to work in practice. If someone could clarify/confirm how it works, that'd be great.

P.S, are there any safeguards implemented in releasing interfaces? Calling Release any number of times on the back buffer doesn't seem to do any damage (which is a good thing, but I'm not sure why it doesn't).

Lawyer answered 15/5, 2011 at 7:0 Comment(0)
C
8

Direct3D is based on COM, which is a technology that's at least 15 years old. Seems many people claim COM is dead and for that reason many overlook it, but reality is that there are many things in windows including Direct3D and MS's new Media Foundation that are all based on COM.

I strongly suggest you take a look at general COM programming. There are plenty of books and resources, but many of them are rather old but that's ok because the root of the technology hasn't changed for a very long time.

Basically what you've observed is interface reference counting. COM is based purely on accessing objects via interfaces, which all derive from the base interface, IUnknown. IUnknown implements methods AddRef() and Release() and it is the responsibility of your application to call AddRef() whenever you store a local copy of a pointer and to call Release() whenever that local copy is no longer needed.

When you have methods with interface out parameters (i.e. IFoo** ppObj ), that means the callee is giving you back an interface and now that you have it, it is still your responsibility to call Release() whenever you are done with it.

Once you get the hang of it, I'd suggest you start using CComPtr smart class for storing local and member variables (still pass raw interface values between function calls, no need for smart pointer parameter types). It will take care of all your reference counting. Also don't make it a practice of calling release "any number" of times. It might work today because the object is implemented as a singleton, or maybe something else is holding on to it, but that might change with next patch or next release. Always follow the rules. If you have an interface, when you don't need it call Release() exactly once. If you made a copy of interface pointer, make sure to call AddRef() exactly once.

Chadwick answered 15/5, 2011 at 7:38 Comment(1)
Great answer, thanks. I would never call Release more than once in practice, I was just asking why it didn't tear the program apart when I was trying to figure this out and tried it.Lawyer
Y
4

The application of addref/release semantics is much wider than COM technology. There is simple rule one CreateObject() (or CreateTexture, or GetRenderTarget, or GetBackBuffer, etc...) have to be confronted with one Release(), one AddRef() have to be confronted with one Release().

In COM IUnknown::Release() returns number of references to object. It may delude you and you can think: "Hm... I just call Release() until it return 0 and I will have no leaks. ???? PROFIT!!!!!111" <-- That is wrong! AddRef might be called by Direct3D itself or by 3rd_party library you pass this object to, or something else outside your app. One Release for one AddRef. You should call Release when you don't need object anymore, don't waste system resources. You said:

Calling Release any number of times on the back buffer doesn't seem to do any damage

That means nothing. May be The Universe like you so much or you just too lucky to not get exceptions from D3D.

Smart pointers (such as CComPtr) could make your life much easier if you will use them. In this case you don't need to call Release explicitly, it is called in CComPtr dtor if it is assigned to some object.

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

Also you may #define D3D_DEBUG_INFObefore including direct 3d headers and switch to debug d3d runtime. It is helpful in finding leaks in d3d app.

May the CComPtr Force be with you.

Yandell answered 15/5, 2011 at 12:7 Comment(1)
Thanks a lot for the answer. I didn't mean that I thought calling Release a hundred times was a good idea or that I'd ever think about doing it, I was just curious why it didn't break it. :)Lawyer
P
2

D3D objects are COM objects, and they use a basic reference counting system to manage the lifetime of the object. (See wikipedia for more info about the Component Object Model, or the MSDN article Managing Object Lifetimes)

The reference count is modified purely through the AddRef/Release methods, and certain other functions call those methods.

Creating the object as well as calling certain Get methods that return an object derived from the IUnknown class will call AddRef internally to increment the reference count, so you will need to call Release for each call when you are finished with the object.

If you pass the object to another function or class that stores a copy of the point (even temporarily) that class/function should call AddRef to ensure that the object is not freed while it is using it (and Release to signal it is done).

When the reference counter reaches 0 from a call to Release the object is signalled that it may be a good time to delete the held resources, but it may not happen immediately. There is also no protection for calling Release multiple times. The reference counter will not become negative, but it will not perform any other sanity checking (because it can't really) so you can cause application instability by trying to release references you don't hold.

Paleozoology answered 15/5, 2011 at 7:34 Comment(0)
C
1

Yes, you are correct. This is called reference counting and it ensures that objects are alive as long as they are being used, and no longer. You can use a variety of smart pointers to enforce this rule- both shared_ptr and (C++11) unique_ptr allow for custom deleters to call Release(). This makes it easy to control the lifetime of Direct3D objects just like you would for any other object in your application. You don't need to start including ATL libraries and CComPtr to use smart pointers with COM interfaces.

Coffey answered 15/5, 2011 at 12:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.