The jump to DirectX11 : Setup questions

Started by
13 comments, last by backstep 10 years, 5 months ago
I'm at a loss, did the breakpoint on the call to backbufferRenderTarget->Release() in the system destructor only get hit once and error on that first call to it?
Advertisement

I'm at a loss, did the breakpoint on the call to backbufferRenderTarget->Release() in the system destructor only get hit once and error on that first call to it?

If I use this as my clean up method and variable declares (Not sure If I posted this)


//Variable declares
System wyvern;
SpriteBatcher *batcher;

//Clean up method 
void cleanUp()
{
	if(batcher != NULL)
	{
		delete batcher;
		batcher = NULL;
	}
}

And then have this as my start up / main init method


bool startup(HINSTANCE hInstance, WNDPROC winProc)
{
	if(wyvern.initSystem(hInstance, winProc, "Application Window", false) == false)
		return false;

	return true;
}

The window with the clear color shows and etc. When the window is closed, no break occurs

If I have this as my start up / main init method


bool startup(HINSTANCE hInstance, WNDPROC winProc)
{
	if(wyvern.initSystem(hInstance, winProc, "Application Window", false) == false)
		return false;

	batcher = new SpriteBatcher(wyvern.device, wyvern.deviceContext);

	return true;
}

The break occurs in my System.CPP's deconstructor sad.png


/* Other clean up items  in the deconstructor*/

if(backbufferRenderTarget != NULL)
{
	backbufferRenderTarget->Release(); //<--Breaks here
	backbufferRenderTarget = NULL;
}

I just don't understand because I only create / set the render target in the initSystem call

Now what I find interesting is that if I do this as my clean up


void cleanUp()
{
	wyvern.~System();

	if(batcher != NULL)
	{
		delete batcher;
		batcher = NULL;
	}
}

My application breaks in the SpriteBatcher.CPP here


SpriteBatcher::~SpriteBatcher()
{
	if(batDevice != NULL)
	{
		batDevice->Release(); <-- Breaks here
		batDevice = NULL;
	}

	if(batDeviceContext != NULL)
	{
		batDeviceContext->Release();
		batDeviceContext = NULL;
	}

        /* Other clean up items */
}

So does this mean that releasing my device / device context also releases the render target?

Ok just to break that down into the three situations, as you presented them:

1. If you never instantiate batcher during startup, it's destructor is never called, so any COM object release calls only happen in the automatic call of the System object destructor and when that's called there are no errors. Exits cleanly.

2. If you do instantiate batcher, and it's destructor is called without first manually calling the system destructor, then there are no errors from the spritebatch destructor. Then, when the system object is destroyed automatically, it's destructor gives an exception when you try to release the backbufferRenderTarget COM object. It looks like the backbufferRenderTarget COM object was destroyed along with the device and context by the spritebatch destructor, despite it having that pointer's external reference from the system instance.

3. If you do the same as in 2. but first manually call the system destructor, then that call to the system destructor gives no errors, but the following automatic spritebatcher destructor will error when it tries to release the id3d11device a second time (since system's destructor already released and destroyed it).

So situation 1 is normal, and how it should work, only the pointers with references to COM objects call release on them, everything is happy. So all you really want is to instantiate batcher while retaining the object management of situation 1, right?

In situation 2, The error is caused by releasing pointers that never addref'd the pointed COM objects (batdevice and batdevcontext). I'm honestly not really certain why it's breaking the way that it does. It should give a runtime warning, but I don't understand why it full on errors on the backbufferRenderTarget. It's to do with the chain of internal references between the device, the swapchain, the ID311Texture2D of the backbuffer, and finally the ID3D11RenderTargetView that errors when you release. I'd expect the open external reference from backbufferRenderTarget would keep all the objects alive in that internal reference chain, but it does not. I don't understand the internal relationships of the DXGI/D3D runtimes well enough to say what exactly is going on. I can say how you'd fix it though, either addref the device and context in the spritebatcher constructor, or never call release on them in the spritebatch destructor.

In situation 3 it's the same problem, you're calling release when you never addref spritebatcher's pointers to the device and context, so in this case those objects no longer exist to call release on (since the manual call of the system destructor released and destroyed them first). So once more, either addref the spritebatcher device and context pointers, or never call release on them, and the problem goes away, but also never manually call the system destructor like that. Even if you fix the problem with spritebatcher, you're in a situation where the system destructor gets called twice, once manually, once automatically, for a single instance of the system. Your checks in the destructor will probably prevent an error, but manually calling destructors is a bad idea in almost all if not absolutely all cases.

I hope it makes sense that the only pointers that call release on a COM object are those which have added an external reference to them. The pointer passed into their creation automatically has it's reference added, so that's the only pointer you really need to call release with. When you create a copy of that pointer, no reference was added automatically, and if you didn't call addref yourself, then why would the copy need to release a reference it never held.

Like I said before, it would be best to consider ownership of the COM objects when you handle pointers to them. You can make it so the pointer that was used in their creation is the sole owner and only this pointer ever calls release on the object. Any other pointers to the same COM object will never call addref or release, only the initial owner pointer will call release once when the object is no longer needed. This is easiest.

A second choice is share ownership of the COM object's between all pointers to them, and to do that make sure every time you copy a COM object pointer that you addref once using the copy, and always call release before that copy goes out of scope or is deleted. This guarantees object lifetime to all pointers but is also more susceptible to mistakes (if you ever forget to addref or release one pointer then the system breaks down, also it's a ton more boilerplate code).

A third choice is to use ComPtr's which will handle calling release and addref for you. They're like smart pointers for COM objects. I personally use ComPtr's to handle ownership, and pass raw pointers when I need to share them, and I never call release or addref on any raw pointers. That's not necessarily advised, but it works for me. This is more complicated to learn, but easiest to use, and if you give up using raw pointers entirely then it guarantees object lifetime just as well as the second choice, but without the risk of mistakes or extra code required.

To be clear I recommend that right now you follow the first choice until you're more comfortable using D3D11 and COM objects. Only look into using ComPtr once you have a handle on how to use COM objects, since ComPtr's have their own wrinkles (assigning by copy, manually releasing them etc) that will only add complication you don't need when starting out.

So situation 1 is normal, and how it should work, only the pointers with references to COM objects call release on them, everything is happy. So all you really want is to instantiate batcher while retaining the object management of situation 1, right?

I hope it makes sense that the only pointers that call release on a COM object are those which have added an external reference to them. The pointer passed into their creation automatically has it's reference added, so that's the only pointer you really need to call release with. When you create a copy of that pointer, no reference was added automatically, and if you didn't call addref yourself, then why would the copy need to release a reference it never held.

Like I said before, it would be best to consider ownership of the COM objects when you handle pointers to them. You can make it so the pointer that was used in their creation is the sole owner and only this pointer ever calls release on the object. Any other pointers to the same COM object will never call addref or release, only the initial owner pointer will call release once when the object is no longer needed. This is easiest.

This to me would be the ideal situtation. I only want my System object to have / retain all ownership of the device and device context COM objects.

The spritebatcher in my opinion should only have access to the device and device context COM objects, not any type of ownership.

The spritebatcher only needs to use those objects to draw stuff to the screen and etc. Also I do not want to create a copies of the device and device context objects. The spritebatcher just needs to know that they exist. Something that I thought I could do with just passing a ref value:


//Spritebatcher.h prototype
SpriteBatcher(ID3D11Device *&device, ID3D11DeviceContext *&deviceContext);

//Spritebatcher.cpp construcotr
SpriteBatcher::SpriteBatcher(ID3D11Device *&device, ID3D11DeviceContext *&deviceContext)
{
    batDevice = device;
    batDeviceContext = deviceContext;

    /*Other constructor code */
}

But I guess this is not the case? How can I make situtation 1 possible?

Just change the constructor so you're copying the system device pointer value, rather than currently making a pointer-to-pointer. You'd only be copying the pointers, not the actual COM objects pointed to, it's still only one COM object, that now has two pointers pointing to it.

//Spritebatcher.h prototype
SpriteBatcher(ID3D11Device *device, ID3D11DeviceContext *deviceContext);

//Spritebatcher.cpp construcotr
SpriteBatcher::SpriteBatcher(ID3D11Device *device, ID3D11DeviceContext *deviceContext)
{
    batDevice = device;
    batDeviceContext = deviceContext;

    /*Other constructor code */
}
then just pass your original device and deviceContext pointers from your system object into the spritebatcher constructor. Make sure the spritebatcher destructor doesn't release them, since it doesn't own them.

If it helps, you could think of the COM objects like heap allocated memory, usually the pointer that was initialized by the new allocation is the one that calls delete. Even if you copy that pointer, you only call delete once for each new. however many times you copy that pointer, only one pointer to that heap memory will call delete on it.

Oh also, try to make sure the swapchain, device, and context are the last released objects when you shutdown. That way you'll avoid situations like #2 in my previous post where you're relying on their internal references to keep them alive until you release their child objects (textures, views, buffers etc).

This topic is closed to new replies.

Advertisement