DX11 Resize von SwapChain bei Multithreading

Für Fragen zu Grafik APIs wie DirectX und OpenGL sowie Shaderprogrammierung.
Antworten
Grinch
Beiträge: 25
Registriert: 16.04.2004, 22:42

DX11 Resize von SwapChain bei Multithreading

Beitrag von Grinch »

Hallo zusammen,
ich bastel gerade wieder etwas mit DirectX11 rum und habe mir gedacht, auch mal etwas in Richtung Multithreading dazuzulernen. Ich weiß, dass die ganze Synchronisierung der Objekte und vor allem in Verbindung mit DirectX kein triviales Thema ist. Aber irgendwann muss man damit ja mal anfangen. Also habe ich eine neue Threadklasse erstellt, die von einem ThreadWrapper gestartet und beendet wird. In dem Thread läuft dann die Renderingmethode.
Soweit so gut. Das ganze eingebaut und das Bild wird sauber gerendert. Nun wollte ich auf das Resizen des Fensters reagieren und habe versucht mich dabei an die Anleitung vom MSDN zu halten:
http://msdn.microsoft.com/en-us/library ... w_Resizing
Mein Code sieht dann so aus:

Code: Alles auswählen

void CRenderWindowDX11::Resize(uint16 width,uint16 height)
{
	m_critSection.Enter();
	if(CRenderTargetDX11::GetCurrentRenderTarget()==m_renderTarget)
	{
		((CRenderEngineDX11*)m_engine)->GetDeviceContext()->OMSetRenderTargets(0, 0, 0);
	}
	((CRenderTargetDX11*)m_renderTarget)->Resize(width,height,m_swapChain);
	if(CRenderTargetDX11::GetCurrentRenderTarget()==m_renderTarget)
	{
		m_renderTarget->Apply();
	}
	m_critSection.Leave();
}

void CRenderTargetDX11::Resize(uint16 width,uint16 height,IDXGISwapChain* swapChain)
{
	m_critSection.Enter();
	ClearRenderTargetViews();
	TextureListIterator it = m_renderTargetTexture.begin();
	for(;it!=m_renderTargetTexture.end();it++)
	{
		((CTextureDX11*)(*it).get())->Release();
	}
	uint16 i=0;
	if(swapChain)
	{
		HRESULT result;
		// Preserve the existing buffer count and format.
		// Automatically choose the width and height to match the client rect for HWNDs.
		result = swapChain->ResizeBuffers(0, width, height, DXGI_FORMAT_UNKNOWN, 0);
		if(FAILED(result))
		{
			return;
		}
		ID3D11Texture2D* backBufferPtr;
		result = swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&backBufferPtr);
		if(FAILED(result))
		{
			return;
		}

		STexture backBuffer = ((CFactoryTextureDX11*)CFactoryTextureDX11::getInstance())->createTextureFromDX11Tex(backBufferPtr);
		BindTexture(0,backBuffer);
		SAFE_RELEASE(backBufferPtr)
		i++;
	}
	for(;i<m_renderTargetTexture.size();i++)
	{
		STexture tex = ((CFactoryTextureDX11*)CFactoryTextureDX11::getInstance())->createTexture(width,height,m_renderTargetTexture[i]->getFormat(),false);
		BindTexture(i,tex);
	}
	m_critSection.Leave();
}
Prinzipiell funktioniert es auch, wenn man Vollbild umschaltet oder Maximiert läuft es wunderbar, wenn ich allerdings am Rand vom Fenster ziehe, läuft es ganz kurz, bis der ganze Rechner stehe oder Windows mit BlueScreen abstürzt. Wenn ich die gleichen Methode ohne eigenen Renderthread benutze, funktioniert auch der Resize.
Ich kann mir auch denken, woran das liegt: Beim Singlethreaded stoppt das Rendern, bis man das Resizen beendet und man bekommt damit die Nachricht nur 1x. Beim Multithreading feuert die WindowProc für jeden Pixel den man die Fenstergröße verändert eine Nachricht, worauf die Resizefunktion aufgerufen wird.
Mir fehlt aber eine gute Idee, wie man damit umgehen soll. Man müsste eigentlich den Thread pausieren, bis das Resizing beendet ist, aber ich komme im Resizing nicht an den Thread heran.
Kann mir jemand sagen, wie ich es besser mache? Oder wie macht ihr das mit dem Resizing? Vielleicht stimmt bei mir auch etwas mit den Threadlocks noch nicht...
Zuletzt geändert von Grinch am 31.05.2013, 22:38, insgesamt 1-mal geändert.
Benutzeravatar
Krishty
Establishment
Beiträge: 8316
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: DX11 Resize von SwapChain bei Multithreading

Beitrag von Krishty »

(Damit die Formatierung nicht verloren geht, kannst du [ code=cpp ]-Tags benutzen)

Welcher Thread besitzt das Fenster und welcher rendert? Für mich klingt das, als ob sie getrennt sind.

Bis auf den Deferred Context darfst du keine Direct3D- oder DXGI-Aufrufe aus einem anderen Thread als dem Render-Thread vornehmen:
http://msdn.microsoft.com/en-us/library/windows/desktop/ff476891(v=vs.85).aspx#DXGI hat geschrieben:Only one thread at a time should use the immediate context. However, your application should also use that same thread for Microsoft DirectX Graphics Infrastructure (DXGI) operations, especially when the application makes calls to the IDXGISwapChain::Present method.
Demnach darf dein Problem garnicht auftreten weil du das Resizing im selben Thread vornehmen sollst, in dem du auch renderst. Jetzt sagt eine andere Seite aber weiter:
http://msdn.microsoft.com/en-us/library/windows/desktop/bb205075(v=vs.85).aspx#Multithread_Considerations hat geschrieben:Be careful that you never have the message-pump thread wait on the render thread when you use full-screen swap chains. For instance, calling IDXGISwapChain1::Present1 (from the render thread) may cause the render thread to wait on the message-pump thread. When a mode change occurs, this scenario is possible if Present1 calls ::SetWindowPos() or ::SetWindowStyle() and either of these methods call ::SendMessage(). In this scenario, […]
Das klingt für mich nach: Du darfst die DXGI-Swap-Chain nur von dem selben Thread aus verändern oder anzeigen, aus dem du auch renderst. Du darfst außerdem deine Swap Chain nur von dem Thread aus anzeigen, der die Nachrichten des Zielfensters verarbeitet. Der Thread, der die Nachrichten eines Fensters verarbeitet, muss AFAIK in der WinAPI immer derjenige sein, der das Fenster auch erzeugt hat. Benutzt du den Immediate Context also in einem eigenen Thread, um nebenläufig zu rendern, muss dieser Thread auch das Render-Fenster selber anlegen und verwalten. Oder du benutzt den Render-Thread ausschließlich, um via Deferred Context Befehlslisten zu befüllen und sie vom Haupt-Thread abarbeiten zu lassen.

Ich spreche aber leider nicht aus Erfahrung, sondern nur aus Interesse an der Materie.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Grinch
Beiträge: 25
Registriert: 16.04.2004, 22:42

Re: DX11 Resize von SwapChain bei Multithreading

Beitrag von Grinch »

Danke für die schnelle Antwort und den Tipp mit der Formatierung. Aber du liegst glaube ich gar nicht so falsch.
Mein Aufbau ist derzeit folgender:

Code: Alles auswählen

main
	create Window
	create GameClass
		create Engine mit D3DDevice und D3DDeviceContext
		create RenderingClass mit SwapChain, RenderWindow, RenderTarget, Objekten und Hook auf WM_SIZE
		create Thread(RenderingClass)
				Run()
Das heißt mein SwapChain liegt im Mainthread und der Hook auf die Windowmessages ist auch im Mainthread. Der Resize müsste also theoretisch aus dem Mainthread kommen. Im Thread läuft aber während dessen das Rendern weiter und hier wird das Problem sein.
Reicht es dann, wenn ich es so umbaue? Das würde wieder der zweiten Aussage widersprechen, dass die Messages vom Fensterthread verarbeitet werden müssen.

Code: Alles auswählen

main
	create Window
	create GameClass
		create Engine mit D3DDevice und D3DDeviceContext
		create RenderingClass
		create Thread(RenderingClass)
			create RenderingClass mit SwapChain, RenderWindow, RenderTarget, Objekten und Hook auf WM_SIZE
			Run()
Wahrscheinlich muss es so sein

Code: Alles auswählen

main
	create Window
	create GameClass
		create Engine mit D3DDevice und D3DDeviceContext
		create RenderingClass mit Hook auf WM_SIZE
		create Thread(RenderingClass)
			create RenderingClass mit SwapChain, RenderWindow, RenderTarget, Objekten
			Run()
Ich habe mit den WindowMessages noch etwas rumprobiert und diese Methode funktioniert derzeit mit fast allen Konstellationen außer Fenster an den Rand ziehen, so dass es den halben Bildschirm einnimmt:

Code: Alles auswählen

LRESULT CRenderWindow::HookingWndProc(HWND wnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
	switch(msg)
	{
		case WM_ENTERSIZEMOVE:
			m_isSizeMoving=true;
		break;
		case WM_SIZE:
			switch(wParam)
			{
				case SIZE_MAXIMIZED:
					Resize(0,0);
				break;
				case SIZE_RESTORED:
					if(!m_isSizeMoving)
					{
						Resize(0,0);
						m_isSizing=false;
					}
					else
					{
						m_isSizing=m_isSizeMoving;
					}
				break;
				default:
					m_isSizing=m_isSizeMoving;
			}
		break;
		case WM_EXITSIZEMOVE:
			if(m_isSizeMoving && m_isSizing)
			{
				Resize(0,0);
			}
			m_isSizeMoving=false;
			m_isSizing=false;
		break;
	}
	return CallWindowProc(m_origWndProc,wnd,msg,wParam,lParam);
}
Mit dem DeferredContext habe ich mich noch nicht genau befasst, mein Grundproblem ist aber denke ich nicht konkret auf DirectX11 bezogen. Viele haben mit DirectX9 auch schon in einem extra Thread gerendert, zumindest wenn man den Präsentationen von diversen Spieleentwicklern glauben darf. Aber es muss doch möglich sein, dass ich bei einem Editor z.B. 4 Fenster habe und jedes Fenster hat einen eigenen Thread, der dessen Inhalt rendert.
Grinch
Beiträge: 25
Registriert: 16.04.2004, 22:42

Re: DX11 Resize von SwapChain bei Multithreading

Beitrag von Grinch »

Ich glaube ich hab es hinbekommen, zumindest kann ich derzeit wild die Größe des Fensters ändern, ohne BlueScreen oder Grakatreiberreset.
Ich habe es wie folgt gemacht:
In der WindowProc werden immer noch die SizingMessages abgearbeitet, aber anstatt dann direkt das Resize aufzurufen, setze ich nur eine Variable "m_mustResize" und die neue Breite und Höhe des Fensters.
In meiner ShowMethode frage ich diesen Member ab und führe dann das Resizing durch. Damit ist der Aufruf direkt im Renderthread und wird nicht mehr von außen beeinflusst:

Code: Alles auswählen

void CRenderWindowDX11::Show()
{
	m_critSection.Enter();
	HRESULT result;
	if(m_rwDesc.vsync)
		result = m_swapChain->Present(1,0);
	else
		result = m_swapChain->Present(0,0);
	if(result==DXGI_ERROR_DEVICE_RESET)
	{
		//TODO
	}
	if(m_mustResize)
	{
		Resize(m_resizeWidth,m_resizeHeight);
		m_mustResize=false;
	}
	m_critSection.Leave();
}
Und wen es noch interessiert, folgende WindowMessages benutze ich, ob die Größenveränderungen abzufragen:

Code: Alles auswählen

	switch(msg)
	{
		case WM_ENTERSIZEMOVE:
			m_Resizing=true;
		break;
		case WM_SIZE:
			switch(wParam)
			{
				case SIZE_MINIMIZED:
					m_Minimized=true;
					m_Maximized=false;
				break;
				case SIZE_MAXIMIZED:
					m_Minimized=false;
					m_Maximized=true;
					if(!m_isSizing)
						SetResizing(0,0);
				break;
				case SIZE_RESTORED:
					if(m_Minimized)
					{
						SetResizing(0,0);
						m_Minimized=false;
					} else if(m_Maximized)
					{
						if(!m_isSizing)
							SetResizing(0,0);
						m_Maximized=false;
					} else if(!m_Resizing)
					{
						if(m_isSizing)
							SetResizing(0,0);
					} else
					{
						m_isSizing=true;
					}
				break;
			}
		break;
		case WM_EXITSIZEMOVE:
			m_Resizing=false;
			if(m_isSizing)
			{
				SetResizing(0,0);
				m_isSizing=false;
			}
		break;
	}
Tobiking
Beiträge: 16
Registriert: 27.02.2010, 23:55

Re: DX11 Resize von SwapChain bei Multithreading

Beitrag von Tobiking »

Ich habe mir vor ein paar Tagen auch den Kopf wegen dem Resize und Nebenläufigkeit den Kopf zerbrochen. Dabei bin ich zu dem Schluss gekommen, dass ich gar keine Nebenläufigkeit von Message-Pump und Rendering will:

- Während des Renderns (zwischen Clear und Present) führe ich kein Resize durch, da ich dabei den aktuellen Zustand verlieren würde. Da das Rendern eh zügig ablaufen sollte, ist es kein Problem, dass die Windowsnachrichten solange erstmal gesammelt werden.

- Während das Fenster größer/kleiner gezogen wird, soll nicht gerendert werden. Das Ergebnis wäre eh nur eine verzogene Variante, da ich ein Resize der Anzeige erst durchführen möchte wenn, das Fenster die Größe angenommen hat.

Das sind genau die Situationen, die genau dann auftreten wenn message pump und Rendering parallel laufen. Ich muss allerdings dazu sagen, dass ich noch nicht weiß wie das mit Benutzereingaben (Tastatur/Maus) aussieht. Soweit ich weiß kommen die (z.B. bei RawInput) auch über Windowsnachrichten. Wenn da nun insgesamt sehr sehr sehr viele Windowsnachrichten kommen, bremst das sicherlich das Rendering aus. Ich schätze das aber bisher als unrealistisch ein.
Grinch hat geschrieben:Aber es muss doch möglich sein, dass ich bei einem Editor z.B. 4 Fenster habe und jedes Fenster hat einen eigenen Thread, der dessen Inhalt rendert.
Soweit ich weiß sind die Swap-Chains fest an das jeweilige Fenster gebunden. Wenn du also wirklich 4 "echte Fenster" nutzen willst, brauchst du auch 4 Swap-Chains und 4 Message-Pumps. Da würde ich dann je eine Message-Pump und eine Swap-Chain zusammen in einen Thread packen und hätte wieder die Threadlokalität gesichert.

Ich denke aber in so einer Situation willst du eher mehrere Viewports verwenden. Praktisch habe ich da zwar keine Erfahrung, aber besonders wenn es sich um die gleiche Szene aus verschiedenen Ansichten geht, soll das recht effizient sein. Falls nötig lässt kann da wohl auch im Geometry Shader ausgegeben werden um welchen Viewport es sich handelt und entsprechend in den folgenden Shadern darauf reagieren.

Alternativ gingen wohl auch mehrere Rendertargets. Da käme wieder dein Multithreading vor, allerdings so wie Krishty das mit den Deferred Context erwähnt hat. Für jede Ansicht erzeugst du einen Thread mit dazugehörigem Deferred Context. Die Deferred Contexts rendern dann in separate Rendertargets (im Prinzip erzeugen die nur eine Liste an Befehlen). Im eigentlichem Renderthread werden die Deferred-Contexts zusammengesammelt und in den Ausgabebuffer gerendert. Und hierbei ist das Rendern auch wieder mit der Message-Pump in einem Thread.
Benutzeravatar
Krishty
Establishment
Beiträge: 8316
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: DX11 Resize von SwapChain bei Multithreading

Beitrag von Krishty »

Tobiking hat geschrieben:Ich muss allerdings dazu sagen, dass ich noch nicht weiß wie das mit Benutzereingaben (Tastatur/Maus) aussieht. Soweit ich weiß kommen die (z.B. bei RawInput) auch über Windowsnachrichten. Wenn da nun insgesamt sehr sehr sehr viele Windowsnachrichten kommen, bremst das sicherlich das Rendering aus. Ich schätze das aber bisher als unrealistisch ein.
Dafür würde ich sowieso ein eigenes unsichtbares Fenster mit eigenen Thread erzeugen. Der Haken ist nur, dass Windows kein Echtzeit-Betriebssystem ist und du ohne ausreichende Anzahl an Kernen keine Garantie hast, dass nicht ein Thread (davon hast du nun schon drei, plus den deiner Audio-API, deinen Thread Pool, und die vier bis acht deines Grafiktreibers) verhungert.
Tobiking hat geschrieben:
Grinch hat geschrieben:Aber es muss doch möglich sein, dass ich bei einem Editor z.B. 4 Fenster habe und jedes Fenster hat einen eigenen Thread, der dessen Inhalt rendert.
Soweit ich weiß sind die Swap-Chains fest an das jeweilige Fenster gebunden. Wenn du also wirklich 4 "echte Fenster" nutzen willst, brauchst du auch 4 Swap-Chains und 4 Message-Pumps. Da würde ich dann je eine Message-Pump und eine Swap-Chain zusammen in einen Thread packen und hätte wieder die Threadlokalität gesichert.
So einfach ist das afaik nicht. Du würdest dann wieder aus vier Threads nebenläufig DXGI-Ressourcen auf demselben DXGI-Device anlegen, was du nicht darfst, falls ich die MSDN richtig interpretiere.
Tobiking hat geschrieben:- Während das Fenster größer/kleiner gezogen wird, soll nicht gerendert werden. Das Ergebnis wäre eh nur eine verzogene Variante, da ich ein Resize der Anzeige erst durchführen möchte wenn, das Fenster die Größe angenommen hat.
Ja. Man darf aber nicht vergessen, dass das Vergrößern / Verkleinern unter Windows in einer modalen Schleife passiert. Spiele sollten unbedingt darauf achten, dass die Spiellogik dabei entweder pausiert wird oder unabhängig weiterläuft, denn meistens kann man schlecht damit umgehen, dass die Aktualierungsrate von 60 Hz auf 0,1 und wieder zurück springt.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Grinch
Beiträge: 25
Registriert: 16.04.2004, 22:42

Re: DX11 Resize von SwapChain bei Multithreading

Beitrag von Grinch »

@tobiking: Die Pufferung der Windowsnachrichten mache ich ja jetzt auch, in dem ich in der MessageProc nur das Flag setze und dieses nach dem Present prüfe.
Die Eingliederung der MessageProc in den Thread wird sich aber in der Praxis nahezu nie bewerkstelligen lassen. Wenn man z.B. einen Editor baut, dann erstellt man mit MFC oder WPF oder ähnlichem seine Oberfläche. An diese hängt man dann entsprechend die Renderingthreads an. Damit ist man aber wieder an der Stelle, dass MessageProc und Rendering in 2 verschiedenen Threads sind. Ich denke meine Lösung ist gar nicht mal so falsch. Weiterer Vorteil ist, dass ich mich nur mit Hook an die WindowProc anhänge. Das Fenster kann in der Applikation immer noch auf alle Nachrichten reagieren und ich fange mir nur die für die Engine wichtigen heraus, ebenfalls Maus und Tastatur. Außerdem habe ich so auch noch die Möglichkeit Zusatzaktionen im Thread zu machen, falls sich die Größe geändert hat. Das nutze ich, um meine RenderTargets vom Deferred Rendering auch gleich noch mit anzupassen.

Die Lösung mit einem SwapChain mit Desktopgröße und dann verschiedenen Viewports halte ich eher für eine Quick & Dirty Lösung.
Antworten