D3D11 Rendering in other thread, window in Main thread

Started by
10 comments, last by Erik Rufelt 10 years, 3 months ago

I have an issue I want to solve:

When I move/drag/resize a window, my message pump get stuck and window content is not updated (well known problem with DefWIndowProc). I want to create another thread and make rendering there, but I don`t know how to correctly do that, because if I start rendering loop in other thread, messages seems not to reach window or something like that.

Here is some code. I use C++/CLI mixed mode and WPF window:

This is main. Here I start my engine




[STAThreadAttribute]
int main()
{
	try
	{

		ManagedEngine^ managed = gcnew ManagedEngine();
		managed->InitializeSystem();
		ThreadStart^ threadDelegate = gcnew ThreadStart(managed, &ManagedEngine::StartEngine);
		Thread^ newThread = gcnew Thread(threadDelegate);
		newThread->Start();
		//managed->StartEngine();
		managed->Stop();

	}
	catch (Exception^ exception)
	{
		Console::WriteLine(exception->Message + exception->StackTrace + exception->TargetSite);
	}
}

In initializeSystem() I do initialization of D3D, Camera, DirectInput

Rendering thread is in method Run()




void Engine::Run()
{
	MSG msg;
	bool done, result;

	//////?????????????? ????????? MSG
	ZeroMemory(&msg, sizeof(MSG));

	//??????? ????, ??????? ????? ???????? ?? ??? ??? ???? ?? ???????
	//????????? ?????????? ?????? ?? ???? ??? ?? ????????????
	done = false;
	int counter = 0;
	while(!done)
	{
		Console::WriteLine("????? ? ???? ??????????");
		//???????????? ????????? Windows
		if (PeekMessage(&msg, NULL, 0, 0,PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		
		//???? ?????? ????????? ??????? ??????????, ????? ???????
		if (msg.message == WM_QUIT)
		{
			done = true;
		}
		else
		{
			//????? ???????????? ????
			result = Frame();
			Console::WriteLine(counter);
			if (!result)
			{
				done = true;
			}
			counter++;
		}
	}
}

I did my engine structure almost exactly as in rastertek lessons if you see them.

So, As I understand the main my problem is that I have message pump run in another thread, but window is main thread, so messages cant be read. That lead window to get stuck in that time when rendering goes well.

Now I want to understand how to organize thread to leave message pump outside (in main thread), but listen this messages inside second thread.

Correct me if I'm wrong, please. I need your help!

Advertisement

Is it possible to implement with DirectX 11?

Yes. Try doing a normal loop on the main-thread with GetMessage instead of PeekMessage, and then on the other thread just something like:


while(true) {
  render();
  swapChain->present();
}

And then some synchronization to stop the rendering on exit and maybe sleep on alt-tab and such.

So, I need to make 2 loops?

One in main thread:


while(!done)
	{
		Console::WriteLine("????? ? ???? ??????????");
		//???????????? ????????? Windows
		if (Getssage(&msg, NULL, 0, 0,PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		
		//???? ?????? ????????? ??????? ??????????, ????? ???????
		if (msg.message == WM_QUIT)
		{
			done = true;
		}
		
	}

Second in other thread:


while (true)
{
    Frame();
}

Am I right?

Yes, but GetMessage works a bit differently. It only returns when there is a message available, and returns 0 on WM_QUIT.

So:


while(!done) {
	MSG msg;
	BOOL bRet = GetMessage(&msg, NULL, 0, 0);
	if(bRet > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	else {
		done = true;
	}
}

Erik Rufelt

Thanks, It works, But I have now another problem:

I implement resizing swapchain method and now when i resize window, my rendering crash in BeginScene method in line:

d3d11_DeviceContext->ClearDepthStencilView(d3d11_DepthStencilView, D3D11_CLEAR_DEPTH, 1.0f, 0);




void D3D11::BeginScene(float red, float green, float blue, float alpha)
{
	
		//Console::WriteLine("windowResizing = false");
		float color[4];

		//????????????? ?????, ????? ???????? ?????
		color[0] = red;
		color[1] = green;
		color[2] = blue;
		color[3] = alpha;

		//??????? ?????? ?????
		d3d11_DeviceContext->ClearRenderTargetView(d3d11_RenderTargetView, color);

		//??????? ????? ???????
		d3d11_DeviceContext->ClearDepthStencilView(d3d11_DepthStencilView, D3D11_CLEAR_DEPTH, 1.0f, 0);
	
}

And this is my method which I call when window resizing:




void D3D11::ResizeSwapChain()
{
	//DeinitializeD2D11();
	HRESULT result;
	if (swapChain)
	{
		RECT rc;
		GetClientRect(hwnd, &rc);
		UINT width = rc.right - rc.left;
		UINT height = rc.bottom - rc.top;

	d3d11_DeviceContext->OMSetRenderTargets(0,0,0);
	d3d11_RenderTargetView->Release();
	
	swapChain->ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN, 0);
	swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&backBufferPtr);
	d3d11_Device->CreateRenderTargetView(backBufferPtr, NULL, &d3d11_RenderTargetView);


	//?????????????? ???????? ????????? Depth_Buffer
	ZeroMemory(&depthBufferDescription, sizeof(depthBufferDescription));

	//????????? ???????? ?????????
	depthBufferDescription.Width = width;
	depthBufferDescription.Height = height;
	depthBufferDescription.MipLevels = 1;
	depthBufferDescription.ArraySize = 1;
	depthBufferDescription.Format = DXGI_FORMAT_D32_FLOAT;
	depthBufferDescription.SampleDesc.Count = 4;
	depthBufferDescription.SampleDesc.Quality = 0;
	depthBufferDescription.Usage = D3D11_USAGE_DEFAULT;
	depthBufferDescription.BindFlags = D3D11_BIND_DEPTH_STENCIL;
	depthBufferDescription.CPUAccessFlags = 0;
	depthBufferDescription.MiscFlags = 0;

	d3d11_DepthStencilBuffer->Release();
	d3d11_DepthStencilBuffer = NULL;

	//??????? ???????? ??? ?????? ??????? ????????? ??????????? ?????????
	result = d3d11_Device->CreateTexture2D(&depthBufferDescription, NULL, &d3d11_DepthStencilBuffer);

	//?????? ????????? Depth Stencil State
	d3d11_DeviceContext->OMSetDepthStencilState(d3d11_DepthStencilState, 1);

	//?????????????? ????????? Depth Stencil View
	ZeroMemory (&depthStencilViewDescription, sizeof(depthStencilViewDescription));

	//????????? ???????? ????????? Depth_Stencil_View
	depthStencilViewDescription.Format = DXGI_FORMAT_D32_FLOAT;
	depthStencilViewDescription.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2DMS;
	depthStencilViewDescription.Texture2D.MipSlice = 0;

	d3d11_DepthStencilView->Release();
	d3d11_DepthStencilView = NULL;

	//?????? ???????? ????????? Depth_Stencil_View
	result = d3d11_Device->CreateDepthStencilView(d3d11_DepthStencilBuffer, &depthStencilViewDescription, &d3d11_DepthStencilView);

	//??????????? ????????? ?? ?????? ?????, ??? ??? ?? ?????? ?? ?????
	backBufferPtr->Release();
	backBufferPtr = 0;
	
	d3d11_DeviceContext->OMSetRenderTargets(1, &d3d11_RenderTargetView, d3d11_DepthStencilView);
	
	//d3d11_DeviceContext->OMSetRenderTargets(1, &d3d11_RenderTargetView, NULL);
	
	//????????????? ??????? ??? ??????????
	D3D11_VIEWPORT viewPort;

	viewPort.Width = (float)width;
	viewPort.Height = (float)height;
	viewPort.MinDepth = 0.0f;
	viewPort.MaxDepth = 1.0f;
	viewPort.TopLeftX = 0.0f;
	viewPort.TopLeftY = 0.0f;

	//??????? ViewPort
	d3d11_DeviceContext->RSSetViewports(1,&viewPort);

	//InitializeD2D11();
	}
}

I found out that app crash because of resizing window, but I have no idea why. Before it works fine.

Do you resize on the main thread when you get a resize message?

That won't work as the render thread might be busy with the back-buffer. You should atomically set width and height variables, and then at the beginning of every frame on the render thread check if the swap-chain needs to be resized, or similar. You shouldn't touch the same device-context on multiple threads simultaneously.

Yes, you was completely right about different threads and now I solve my issues. Thanks a lot for help!

@Erik Rufelt

But one more question regarding this theme:

How I can get rid of that blinking effect when I resizing the window? It becomes white for a few milliseconds and then painted properly again.

Is it possible to make background not became white even on those milliseconds?

How do you create your window and window-class?

My guess is you set a white brush to the window-class hbrBackground, and DefWindowProc uses it to erase the window. If set to NULL, then the window will not be erased. You can also handle WM_ERASEBKGND and return non-zero from your WndProc.

If you use CS_HREDRAW or CS_VREDRAW for your window-class I would also remove those, as those tell Windows to redraw the window when resized.

This topic is closed to new replies.

Advertisement