Advertisement

D3D12 Unnamed Command Queue being final-released while still in use by the GPU

Started by January 19, 2019 04:43 AM
7 comments, last by SoldierOfLight 6 years ago

I encountered this problem when releasing D3D12 resources. I didn't use ComPtr so I have to release everything manually. After enabling debug layer, I saw this error:

D3D12 ERROR: ID3D12CommandQueue::<final-release>: A Command Queue (0x000002EBE2F3C7A0:'Unnamed ID3D12CommandQueue Object') is being final-released while still in use by the GPU.  This is invalid and can lead to application instability. [ EXECUTION ERROR #921: OBJECT_DELETED_WHILE_STILL_IN_USE]

I wanted to figure out which queue is it so I enabled debug device and used ReportLiveDeviceObjects trying to identify the queue. But it showed the same error. All my queues had names and ReportLiveDeviceObjects worked on other resources. After googling around, I found this page. It was a similar problem and it seemed it had something to do with finishing the unfinished frames.

Before I made any changes, my clean up code looks like this:


void Cleanup()
{
	// wait for the gpu to finish all frames
	for (int i = 0; i < FrameBufferCount; ++i)
	{
		frameIndex = i;
		//fenceValue[i]++; //------------------------------------------> FIRST COMMENTED CODE SNIPPET 
		//commandQueue->Signal(fence[i], fenceValue[i]); //------------> SECOND COMMENTED CODE SNIPPET 
		WaitForPreviousFrame(i);
	}
  
  	//////////////////////////////////////////////////////////////////////////////////////////////////////
	// FROM HERE ON, CODES HAVE NOTHING TO DO WITH THE QUESTION, THEY ARE HERE FOR THE SAKE OF COMPLETENESS
  	//////////////////////////////////////////////////////////////////////////////////////////////////////
  
	// close the fence event
	CloseHandle(fenceEvent);
      
	// release gpu resources in the scene
	mRenderer.Release();
	mScene.Release();

	// imgui stuff
	ImGui_ImplDX12_Shutdown();
	ImGui_ImplWin32_Shutdown();
	ImGui::DestroyContext();
	SAFE_RELEASE(g_pd3dSrvDescHeap);

	// direct input stuff
	DIKeyboard->Unacquire();
	DIMouse->Unacquire();
	DirectInput->Release();
      
  	// other stuff ...
}

Obviously it didn't work that's why I googled the problem. After seeing what is proposed on that page, I added the first and the second commented code snippet. The problem was immediately solved. The error above completely disappeared. Then I tried to comment the first code snippet out and only use the second code snippet. It also worked. So I am wondering what happened.

1.Why do I have to signal it? Isn't WaitForPreviousFrame enough?

2.What does that answer on that page mean? What does operating system have anything to do with this?

For you information, the WaitForPreviousFrame function looks like this:


void WaitForPreviousFrame(int frameIndexOverride = -1)
{
	HRESULT hr;

	// swap the current rtv buffer index so we draw on the correct buffer
	frameIndex = frameIndexOverride < 0 ? swapChain->GetCurrentBackBufferIndex() : frameIndexOverride;

	// if the current fence value is still less than "fenceValue", then we know the GPU has not finished executing
	// the command queue since it has not reached the "commandQueue->Signal(fence, fenceValue)" command
	if (fence[frameIndex]->GetCompletedValue() < fenceValue[frameIndex])
	{
		// we have the fence create an event which is signaled once the fence's current value is "fenceValue"
		hr = fence[frameIndex]->SetEventOnCompletion(fenceValue[frameIndex], fenceEvent);
		if (FAILED(hr))
		{
			Running = false;
		}

		// We will wait until the fence has triggered the event that it's current value has reached "fenceValue". once it's value
		// has reached "fenceValue", we know the command queue has finished executing
		WaitForSingleObject(fenceEvent, INFINITE);
	}

	// increment fenceValue for next frame
	fenceValue[frameIndex]++;
}

 

I believe there was a bug in current releases regarding outputting the names of final released objects, except resources. That was fixed relatively recently.

The easiest way to know that a given command queue is idle is to signal a fence on it and then wait for that signal to complete. You can either do this on an existing fence with a higher value that you know hasn't been queued yet, or by creating a new fence and signaling it to 1 greater than its initial value.

Advertisement
2 hours ago, SoldierOfLight said:

The easiest way to know that a given command queue is idle is to signal a fence on it and then wait for that signal to complete.

Yes, I agree with this. But why do I have to signal again? At the end of every frame I put a commandQueue->Signal right after commandQueue->ExecuteCommandLists. This means when I upload work to GPU, there will always be a Signal at the end of it. So why do I have to signal again when I want to clean up. Why can't I just wait for that Signal I put after the ExecuteCommandList? Is it caused by the extra system call thing mentioned on this page?

My code is here

Uncomment this in main.cpp to enable debug layer.


// debug layer
if (!EnableDebugLayer())
{
	MessageBox(0, L"Failed to enable debug layer", L"Error", MB_OK);
	return 1;
}

Uncomment the above two code snippet to see this error.

5 hours ago, acerskyline said:

Is it caused by the extra system call thing mentioned on this page?

Possibly. There is work submitted to the queue (which you pass to creation of a swapchain) by the Present API. Are you signaling after that or before?

1 hour ago, SoldierOfLight said:

Possibly. There is work submitted to the queue (which you pass to creation of a swapchain) by the Present API. Are you signaling after that or before?

I've taken note of this. Alwways signaled before present. Seems like it's safer to signal after present in the event I want to prepare for the next frame? ?

5 hours ago, SoldierOfLight said:

Are you signaling after that or before?

I was signaling before present. Once I moved the signaling after present the error was gone. 

The way I understand it is this:

1.Present will submit work to the queue

2.We can only assure the work before the Signal is done when the Signal is triggered

Therefore when we insert Signal before Present, we can't guarantee all the work on the queue are done even the Signal is triggered because of the work submitted by Present may still be there on the queue waiting for execution.

Now I have the same question as John321:

4 hours ago, John321 said:

Seems like it's safer to signal after present in the event I want to prepare for the next frame? ?

So, Signal before Present, or Signal after Present? Which one is the best approach? Pros and Cons?

The official example seems to always put Signal before Present, so I guess we should stick to this rule and when it comes to releasing command queues, always do an extra Signal to avoid "command queues being final-released while still in use by the GPU"?

Advertisement

I think if you use DXGI_SWAP_EFFECT_FLIP_DISCARD in swap chain, then you need to signal after present because it doesnt block cpu thread . I m guessing the other options allow blocking and a signal after present wouldnt be a requirement?

Any GPU work issued by a Present is relatively lightweight, and it's good practice to signal the end of a frame after that operation. Those samples should probably be updated.

This topic is closed to new replies.

Advertisement