The jump to DirectX11 : Setup questions

Started by
13 comments, last by backstep 10 years, 5 months ago

I got a ton of questions about how to do proper set up for Direct x 11, maily because I'm not sure what needs to be checked for and the tutorials on this seem to be a little lackluster. So if I leave anything out please let me know.

Currently I do the following

1. Create a factory

2. Use the facotry to get all the available display adapters

3. Based on the first display adapter we find, get all the available display modes that are of Scaling type

4. Create the device

5. Create the swapchian based on the 800 x 600 diplsay mode found

6. Create the backbuffer render target and viewport

Now I have some issues:

Question 1

When getting the available display adapters I use this code:


	for(UINT i = 0; deviceFactory->EnumAdapters(i, &currentDeviceAdapter) != DXGI_ERROR_NOT_FOUND; ++i)
	{
		availableDeviceAdapters.push_back(currentDeviceAdapter);
	}
	currentDeviceAdapter = availableDeviceAdapters[0];

	currentDeviceAdapter->GetDesc(&currentAdapterDescription);
	std::wcout<<"Available Adapter:"<<currentAdapterDescription.Description<<std::endl;
	std::cout<<"Finished getting adapters"<<std::endl;
	getchar();

When I do clean up though using:


	//Works on clean up
        if(currentDeviceAdapter != NULL)
		currentDeviceAdapter->Release();
	
	
	//Breaks in here
	for(std::vector<IDXGIAdapter*>::iterator i = availableDeviceAdapters.begin(); i != availableDeviceAdapters.end(); i++)
	{
            //Breaks here even when checking for NULL
	    if((*i) != NULL)
		(*i)->Release();
	}
	availableDeviceAdapters.clear();
	

I get a accesss violation, how am I supposed to properly clean this up?

My variables are declared like so


IDXGIAdapter *currentDeviceAdapter;
std::vector<IDXGIAdapter*> availableDeviceAdapters;

Question 2

In Direct X 9 I got Direct X ready first and then created my window based on the resolution I wanted to use.

Eg: Loop through all teh supported resolutions and pick 800 x 600; If it was unavailable fallback to a 640 x 480 resolution.

It seems this can not be done in Direct X 11, unless I create window first the the swapchain creation fails. Which sort of makes sense since the OutputWindow cannot be NULL according to the documentation. But is there away arround this? Or do I need to be adjusting my window size after direct x is setup? Do I have any special checks in the window's callback function for this to happen?

Question 3

My final question... for now at least

What happened to checking device caps in Direct X 11? How do I check if the adapter I use is able to support feature X or use something Y?

Eg: The format used when creating the swap chain. Right now I use DXGI_FORMAT_R8G8B8A8_UNORM. But what if someone can't support this?

Advertisement

DirectX 10 and 11 removed caps, therefore ALL features are supported (there's list of minimal requirements, but hardware might support better ones), however it seems caps are back in 11.1 and/or 11.2 but I hope they'll be gone in DirectX 12 (I prefer to think 11.1 and 11.2 are experiments).

The reason for the crash is that you're releasing availableDeviceAdapters[0] twice. Once is enough.

For a list of supported DX11 texture and render target formats look at http://msdn.microsoft.com/en-us/library/windows/desktop/ff471325%28v=vs.85%29.aspx and the other similar pages linked in the left column for DX10 class hardware.

DirectX 10 and 11 removed caps, therefore ALL features are supported (there's list of minimal requirements, but hardware might support better ones), however it seems caps are back in 11.1 and/or 11.2 but I hope they'll be gone in DirectX 12 (I prefer to think 11.1 and 11.2 are experiments).

Hmmm good to know, I assume this includes shader versions? Does this mean if I wanted to use Shader 3.0 I could and be ok with targeting DX 9, DX 10, DX 11?

The reason for the crash is that you're releasing availableDeviceAdapters[0] twice. Once is enough.

I believe you are right, seeing as if I flip the code to roll through the adapter vector first the application breaks on the the release call of the currentDeviceAdapter.

But what I don't understand is when the break is being thrown even if I check for NULL.

I updated the code to use a For Loop based on the adapter vectors size, because for some odd reason I was getting a vector iterator incompatible assertion break. Even if there was nothing in the For Loop. I'm not sure if this is the best way to do it or it's exactly the same thing as basing the For Loop on a iterator

Here is the updated code:


	if(currentDeviceAdapter != NULL)
	{
		currentDeviceAdapter->Release();
		currentDeviceAdapter = NULL;
	}
	
	//Clean up the vector of adapters
	for(UINT i = 0; i < availableDeviceAdapters.size(); i++)
	{
		if(availableDeviceAdapters[i] != NULL)
		{
			availableDeviceAdapters[i]->Release(); //<------ Still breaks here
			availableDeviceAdapters[i] = NULL;
		}
	}

Any ideas why it's breaking even though I check for NULL? It just says applcaition.exe has triggered a break point

Any ideas why it's breaking even though I check for NULL? It just says applcaition.exe has triggered a break point


If I'm reading your code correctly, both currentDeviceAdapter and availableDeviceAdapter[0] are IDXGIAdapter* pointers, that point to the same COM object.

Releasing currentDeviceAdapter releases the single COM object that they both point to. So when you loop through availableDeviceAdapters, then the first entry [0] still points to the same COM object you already just released. The pointer isn't NULL, so the check passes, but the pointed object is already released so the release fails.

Either addref when you assign availableDeviceAdapters[0] to currentDeviceAdapter, so that the number of releases makes sense, or just release only using your availableDeviceAdapters loop, and simply set currentDeviceAdapter = NULL without calling release on it.

Edit:

Probably best to explain how release and addref work. When you create a COM object like IDXGIAdapter, you pass in a pointer that gets assigned the address of the COM object. The COM object that is pointed to automatically has it's reference count incremented during creation, since by default it has that one pointer referencing it. The reason for the reference count is to manage the lifetime of the object, since the object is only needed so long as something is referencing it (either the runtime itself, or an external pointer in your application).

when you call release on a COM object it's reference count is decremented by one. When the reference count reaches zero, the COM object is destroyed. Any pointers that pointed to the object now point to memory that doesn't contain a COM object anymore.

In your case you have two pointers referencing the same COM object, but the object's reference count is only 1, which was incremented during object creation. So to resolve the situation you can either decide one of the pointers is the sole owner of the COM object and only that is responsible for releasing it (I suggested the availableDeviceAdapters array above), or you can addref the COM object when you make a second pointer also reference it (which will increment it's reference count to 2).

In the latter case, your currentDeviceAdapter release call would decrement the object's ref count from 2 to 1, then your availableDeviceAdapter release call will decrement the ref count from 1 to 0 and the COM object is destroyed.
< Super awesome explanation >

Hmm I find this interesting. I thought we you would release something it would also make the pointer NULL.

Now I have classes and other parts of my code where other COM objects are referenced. Like this:


//In Header file
SpriteBatcher(ID3D11Device *&device, ID3D11DeviceContext *&deviceContext);
ID3D11Device *batDevice;
ID3D11DeviceContext *batDeviceContext;

//Constructor of CPP file
SpriteBatcher::SpriteBatcher(ID3D11Device *&device, ID3D11DeviceContext *&deviceContext)
{
	batDevice = device;
	batDeviceContext = deviceContext;
}

//Deconstrcutor of CPP file
SpriteBatcher::~SpriteBatcher()
{
	if(batDevice != NULL)
	{
		batDevice->Release();
		batDevice = NULL;
	}

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

        /* Other clean up items */
}

//In the MAIN CPP variable section
SpriteBatcher *batcher;

//In the MAIN CPP startup method
batcher = new SpriteBatcher(system.device, system.deviceContext);

//In the MAIN CPP Clean Up
if(batcher != NULL)
{
   delete batcher;
   batcher = NULL;
}

Now if I run the code it breaks on the backbuffer object in my system clean up


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

Should it not the device and device context clean up instead?

What is backbuffer, an ID3D11Texture2D* ?

Retrieved with swapChain->GetBuffer(0,__uuidof(ID3D11Texture2D),(LPVOID*)&backbuffer) ?

If more than one pointer points to the same address as backbuffer, and you've already released that pointer, it's the same problem as before and so backbuffer is calling release on a destroyed object.

If backbuffer is the only pointer that points to that address in your app, I'm not sure. If no pointers are referencing the backbuffer's texture2D COM object, it's usually destroyed when your swapchain object is destroyed.

I'd double check how many times you call release on that backbuffer pointer in your entire app, and if you ever copy backbuffer to another pointer in the entire app.

I'm guessing that once you fix the issue with the backbuffer, the device and context will error, since like you say, you've already destroyed them with the release calls in the spritebatcher destructor (assuming you never addref'd them when you copied the system.device and system.context pointers to the spritebatcher member pointers, the actual COM objects only have 1 ref count).

What is backbuffer, an ID3D11Texture2D* ?

Retrieved with swapChain->GetBuffer(0,__uuidof(ID3D11Texture2D),(LPVOID*)&backbuffer) ?

If more than one pointer points to the same address as backbuffer, and you've already released that pointer, it's the same problem as before and so backbuffer is calling release on a destroyed object.

If backbuffer is the only pointer that points to that address in your app, I'm not sure. If no pointers are referencing the backbuffer's texture2D COM object, it's usually destroyed when your swapchain object is destroyed.

I'd double check how many times you call release on that backbuffer pointer in your entire app, and if you ever copy backbuffer to another pointer in the entire app.

I'm guessing that once you fix the issue with the backbuffer, the device and context will error, since like you say, you've already destroyed them with the release calls in the spritebatcher destructor (assuming you never addref'd them when you copied the system.device and system.context pointers to the spritebatcher member pointers, the actual COM objects only have 1 ref count).

Sorry about that, I knew I forgot to mention something, backbuffer is a ID3D11RenderTargetView. I have changed it to backbufferRenderTarget.

Currently it is only used by the device and device context. It's created and used like in my systems init method like this:


ID3D11Texture2D *pBackbuffer;
swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*) &pBackbuffer);
device->CreateRenderTargetView(pBackbuffer, NULL, &backbufferRenderTarget);
pBackbuffer->Release();

deviceContext->OMSetRenderTargets(1, &backbufferRenderTarget, NULL);

The System.CPP is the only true place where it is touched in terms of Releasing. It's used in the render method:


wyvern.deviceContext->ClearRenderTargetView(wyvern.backbufferRenderTarget, D3DXCOLOR(0.0f, 0.2f, 0.4f, 1.0f));

But I don't think this is an issue

Sorry about that, I knew I forgot to mention something, backbuffer is a ID3D11RenderTargetView. I have changed it to backbufferRenderTarget.

Currently it is only used by the device and device context. It's created and used like in my systems init method like this:
ID3D11Texture2D *pBackbuffer;
swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*) &pBackbuffer);
device->CreateRenderTargetView(pBackbuffer, NULL, &backbufferRenderTarget);
pBackbuffer->Release();

deviceContext->OMSetRenderTargets(1, &backbufferRenderTarget, NULL);
The System.CPP is the only portion of the application where it is used and referenced


No worries. smile.png

So far as I know the only way to get an RTV to error on release like that, is to try to release it after it's destroyed. It gains one reference on creation ( device->CreateRenderTargetView(pBackbuffer, NULL, &backbufferRenderTarget); ), are you certain it's not released anywhere else in your code?

Even though release was called on the device and context beforehand, they should still exist, because the device's internal reference counter was incremented when you created the ID3D11RenderTargetView that backbufferRenderTarget points to. Internal reference counters are the same as the external reference counters you call addref and release on, except they're only accessible by other COM objects, rather than external pointers, hence the name internal reference counter. The render target view is a child of the device, and won't decrement the device's internal reference count until it's destroyed.

What that means is I don't think the spritebatcher destructor calls to release the device and context are causing your backbufferRenderTarget->Release() exception. The ID3D11RenderTargetView object is somehow already destroyed, meaning it's external ref count was already 0 at some point beforehand.


Just noticed your edit, is wyvern a singleton of your system object? So that backbufferRenderTarget is the same pointer as wyvern.backbufferRenderTarget, only in a different scope (same memory location)? Or is the value of backbufferRenderTarget copied to wyvern.backbufferRenderTarget? (copy constructor of the system object perhaps).

I'll take a wild guess that you're passing the initial instance of your system object into your render function as an argument, or passing it to the render function's parent render object somehow, and that's why it's got an odd function local name like wyvern. The system.cpp destructor gets called when wyvern is destroyed, either when the render function returns, or the render object is destroyed (depending how you passed the system object), and that is when your render target view is actually getting released and destroyed. When the real system object is later destroyed, and the destructor gets called for the second time, the render target view is already released and destroyed by the earlier call to the system.cpp destructor, so you get an exception.

Put a breakpoint in the system.cpp destructor that calls release on the render target view, see how many times it gets hit (my guess is twice, second time the exception happens).

Sorry about that, I knew I forgot to mention something, backbuffer is a ID3D11RenderTargetView. I have changed it to backbufferRenderTarget.

Currently it is only used by the device and device context. It's created and used like in my systems init method like this:
ID3D11Texture2D *pBackbuffer;
swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*) &pBackbuffer);
device->CreateRenderTargetView(pBackbuffer, NULL, &backbufferRenderTarget);
pBackbuffer->Release();

deviceContext->OMSetRenderTargets(1, &backbufferRenderTarget, NULL);
The System.CPP is the only portion of the application where it is used and referenced


No worries. smile.png

So far as I know the only way to get an RTV to error on release like that, is to try to release it after it's destroyed. It gains one reference on creation ( device->CreateRenderTargetView(pBackbuffer, NULL, &backbufferRenderTarget); ), are you certain it's not released anywhere else in your code?

Even though release was called on the device and context beforehand, they should still exist, because the device's internal reference counter was incremented when you created the ID3D11RenderTargetView that backbufferRenderTarget points to. Internal reference counters are the same as the external reference counters you call addref and release on, except they're only accessible by other COM objects, rather than external pointers, hence the name internal reference counter. The render target view is a child of the device, and won't decrement the device's internal reference count until it's destroyed.

What that means is I don't think the spritebatcher destructor calls to release the device and context are causing your backbufferRenderTarget->Release() exception. The ID3D11RenderTargetView object is somehow already destroyed, meaning it's external ref count was already 0 at some point beforehand.


Just noticed your edit, is wyvern a singleton of your system object? So that backbufferRenderTarget is the same pointer as wyvern.backbufferRenderTarget, only in a different scope (same memory location)? Or is the value of backbufferRenderTarget copied to wyvern.backbufferRenderTarget? (copy constructor of the system object perhaps).

I'm not seeing any other portion of the code the where a release is called except in the deconstructor for my System object.

wyvern is a singleton of my System object:


//In public area of my Systsme header ffile
ID3D11RenderTargetView  *backbufferRenderTarget;

//In main CPP
System wyvern;

//Used like so
if(wyvern.initSystem(hInstance, winProc, "App Window", false) == false)
		return false;

This topic is closed to new replies.

Advertisement