🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

D3D12 screen flicker

Started by
2 comments, last by XGT08 3 years, 11 months ago

Hello everyone,

I have been banging my head against the wall with this issue I am having. I am working on a hobby game engine and I decided to give DirectX 12 a try (I already have experience with DirectX 11). I am currently implementing the render backend, and I have arrived at the point where I can draw 2 cubes on the screen. However, the cubes seem to flicker and I have no idea what could be causing this issue. I suppose it's a CPU/GPU sync issue, but I simply can not detect the problem. I was hoping somebody here could help me out.

Here is the video that shows the flickering in action:

Now allow me to share the code and explain what I am doing. So I have a DX12GfxDevice class which implements the rendering functionality. There are 3 main functions of interest here and I am going to list them below:

Note: I am perfectly aware that the following code may not be optimal. I am just trying to get to the point where I can happily draw stuff on the screen before I think about optimization.

Void DX12GfxDevice::beginFrame()
{
	auto rtvDescHandle = DX12API::cpuDescHandle(_rtvDescHeap, _currentSwapChainBuffer, _rtvDescSize);
	auto dsvDescHandle = DX12API::cpuDescHandle(_dsvDescHeap, 0, _dsvDescSize);

	_beginCmd.completeReset();
	_beginCmd.list->ResourceBarrier(1, &DX12API::transitionBarrier(_swapChainBuffers[_currentSwapChainBuffer], D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET));
	_beginCmd.list->ClearRenderTargetView(rtvDescHandle, (Float*)&clearColor(), 0, nullptr);
	_beginCmd.list->ClearDepthStencilView(dsvDescHandle, D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 1.0f, 0, 0, nullptr);
	_beginCmd.execute();
	_beginCmd.flushQueue();
}  

Void DX12GfxDevice::endFrame()
{
	_endCmd.completeReset();
	_endCmd.list->ResourceBarrier(1, &DX12API::transitionBarrier(_swapChainBuffers[_currentSwapChainBuffer], D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT));
	_endCmd.execute();
	_dxgiSwapChain->Present(0, 0);
	_endCmd.flushQueue();

	_currentSwapChainBuffer = (_currentSwapChainBuffer + 1) % _numSwapChainBuffers;
}

Void DX12GfxDevice::draw(const CameraData& cameraData, const DrawItem* drawItems, UInt32 numDrawItems)
{
	auto rtvDescHandle = DX12API::cpuDescHandle(_rtvDescHeap, _currentSwapChainBuffer, _rtvDescSize);
	auto dsvDescHandle = DX12API::cpuDescHandle(_dsvDescHeap, 0, _dsvDescSize);
	DX12API::setResourceData(_cameraCB->buffer, (Void*)&cameraData, sizeof(CameraData));

	for (UInt32 itemIndex = 0; itemIndex < numDrawItems; ++itemIndex)
	{
 		const DrawItem& drawItem = drawItems[itemIndex];
 		DX12IndexBuffer* indexBuffer = _indexBufferPool[drawItem.indexBuffer];
 		DX12SurfaceShader* surfaceShader = _surfaceShaderPool[drawItem.surfaceShader];
 		DX12API::setResourceData(_objectCB->buffer, (Void*)&drawItem.objectData, sizeof(ObjectData));

 		_drawCmd.completeReset();
 		_drawCmd.list->OMSetRenderTargets(1, &rtvDescHandle, true, &amp;amp;dsvDescHandle);
 		_drawCmd.list->RSSetViewports(1, &_viewport);
 		_drawCmd.list->RSSetScissorRects(1, &_scissorRect);

 		_drawCmd.list->IASetVertexBuffers(0, 1, &_vertexBufferPool[drawItem.vertexBuffer]->view);
     		_drawCmd.list->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
 		_drawCmd.list->IASetIndexBuffer(&indexBuffer->view);
 		_drawCmd.list->SetPipelineState(surfaceShader->activePso);
 		_drawCmd.list->SetDescriptorHeaps(1, &_cbvDescHeap);
 		_drawCmd.list->SetGraphicsRootSignature(surfaceShader->rootSig);
 		_drawCmd.list->SetGraphicsRootDescriptorTable(0, _cbvDescHeap->GetGPUDescriptorHandleForHeapStart());
 		_drawCmd.list->DrawIndexedInstanced(indexBuffer->numIndices, indexBuffer->numPrimitives, 0, 0, 0);

 		_drawCmd.execute();
 		_drawCmd.flushQueue();
	}
}

There are a few helper functions and structs that I am using here. For example, I have a DX12Commander class which holds a combination of command list, command allocator, fence and command queue. In the code above, these are beginCmd, drawCmd and _endCmd.

These classes have the following functions:

-completeReset() →resets the allocator and command list;

-execute() → closes the command list and executes the command list on the associated queue;

-flushQueue()→uses the fence to signal the queue and make the CPU wait until the fence value is set.

I am also using the setResourceData function to write data to the constant buffer. This is simply Map/writeData/Unmap.

The implementation of the flushQueue function goes something like this:

++value;
queue->Signal(fence, value);
if (fence->GetCompletedValue() != value)
{
	HANDLE fenceEvent = CreateEventEx(nullptr, nullptr, 0, EVENT_ALL_ACCESS);
	fence->SetEventOnCompletion(value, fenceEvent);
	WaitForSingleObject(fenceEvent, INFINITE);
	CloseHandle(fenceEvent);
}

So, in the beginFrame function, I simply clear the render target and depth stencil. I execute the list and flush the queue to make sure that the clear operations are completed before moving further.

In the endFrame function, I call Present, flush the queue, and switch to the next swap chain buffer. I am using 2 swap chain buffers.

In the draw function, I simply loop through all the DrawItem instances, to perform any necessary drawing. Each DrawItem contains a vertex buffer, index buffer and surface shader. Note that in this case I am flushing the queue after each draw call to make sure that the contents of the constant buffer have been used up before updating it for the next item.

Now, the interesting thing is this. If I throw away the drawCmd commander from the draw function and use _beginCmd to perform the drawing without any flushing during the draw and if I flush _beginCmd only in the EndFrame function, there is no more flickering. However, in that case, only one cube is rendered. I guess, because I am not waiting for the constant buffer to be used by by the draw call, the next item will override the contents of the buffer.

All commanders (beginCmd,endCmd and drawCmd use the same queue). I need to do this because otherwise I get debug errors telling me that the queue associated with the command list must be the same queue that was passed inside the CreateSwapChainForHwnd function.

Any help would be greatly appreciated.

Thanks, Andrew

Advertisement

What's going on when you do call DX12API::setResourceData? Is that ultimately filling out two distinct constant buffers, one for each cube that you're drawing? Or are both cubes sharing the same constant buffer?

DX12::setResourceData does the following:

Void* mappedData = nullptr;
D3D12_RANGE emptyRange{};	// Empty range to indicate we are not reading
if (FAILED(resource->Map(0, &emptyRange, (Void**)&mappedData))) return false;
memcpy(mappedData, data, (size_t)numBytes);
resource->Unmap(0, nullptr);

return true;

So it's a simple Map/memcpy/Unmap wrapper.

The same constant buffer is used for both cubes. This is why I am executing and flushing the queue after each draw call.

This topic is closed to new replies.

Advertisement