[C++] [D3D9] Input Lag - Fullscreen - VSync VS SelfSync

Für Fragen zu Grafik APIs wie DirectX und OpenGL sowie Shaderprogrammierung.
Antworten
Benutzeravatar
Goderion
Beiträge: 82
Registriert: 16.09.2012, 12:02

[C++] [D3D9] Input Lag - Fullscreen - VSync VS SelfSync

Beitrag von Goderion »

Hallo.

In meiner Anwendung protokolliere ich jedes Frame und kann damit z.B. bei aktivem VSync das nächste Present sehr gut planen. Das ermöglicht es mir, den Input Lag sehr stark zu reduzieren.
Ein einfaches Beispiel.
- Bei 60Hz muss ich 60 FPS erreichen und damit alle 16,6ms ein neues Bild zur Verfügung stellen.
- Ich brauche für ein Bild 4ms zum Zeichnen.
- Ich verarbeite also 10ms lang Benutzereingaben.
- Danach fange ich erst an das nächste Bild zu zeichnen, und schicke es per Present zum Monitor.
- 2 bis 3ms lasse ich immer über, falls das Zeichnen doch etwas länger dauert, als vorhergesehen.

Durch diese Methode entspricht der Input Lag meistens der Zeit zum Rendern + 2 bis 3ms. In dem obigen Beispiel, wo ein Frame 4ms benötigt, liegt der Input Lag zwischen 6 und 7ms.

Im Vollbildmodus mit aktivem VSync und Input Lag Reduktion fühlt sich die Maus aber immer noch sehr schwammig an, was mich stutzig gemacht hat.
Ich habe mir dann die Funktion IDirect3DDevice9::GetRasterStatus genauer angeschaut und damit eine Möglichkeit gefunden, selber auf die nächste Bildsynchronisation zu warten. Ich habe noch nicht den optimalen Weg gefunden, den Zeitpunkt für das nächste Present festzulegen, was öfter zu Tearing am oberen oder unteren Bildschirmrand führt, aber ich sehe, bzw. vermute da eine Möglichkeit, das VSync selber zu übernehmen. Viel wichtiger ist aber die Feststellung, dass der spürbare Input Lag deutlich abgenommen hat.

Ich verstehe das aber überhaupt nicht. Die Diagramme zum Frameverlauf/Frametimes und Input Lag sehen identisch aus, zwischen VSync und SelfSync sind keine Unterschiede zu erkennen. SelfSync fühlt sich richtig gut an und VSync total schwammig. Ich zeichne selber einen Mauscursor und lasse den normalen Mauscursor aktiviert. Bei VSync erreiche ich leicht mehrere cm zwischen Hardware -und Softwarecursor. Bei SelfSync klebt der Softwarecursor fast am Hardwarecursor. Ich habe auch schon die NVidia-Grafikeinstellungen zurückgesetzt und geguckt, ob da irgendwas komisches eingestellt ist.

Was ist denn da los? Habe ich VSync komplett falsch verstanden?
DerAlbi
Establishment
Beiträge: 269
Registriert: 20.05.2011, 05:37

Re: [C++] [D3D9] Input Lag - Fullscreen - VSync VS SelfSync

Beitrag von DerAlbi »

Haste Triple Buffering an? :-O
Benutzeravatar
Krishty
Establishment
Beiträge: 8316
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] [D3D9] Input Lag - Fullscreen - VSync VS SelfSync

Beitrag von Krishty »

Genau; wie viele Buffers rendert denn die GPU voraus?
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Goderion
Beiträge: 82
Registriert: 16.09.2012, 12:02

Re: [C++] [D3D9] Input Lag - Fullscreen - VSync VS SelfSync

Beitrag von Goderion »

Daran habe ich auch schon gedacht, so anfühlen tut es sich auch. Aber eigentlich sollte es nur einen Backbuffer geben. Die Nvidia-Einstellungen habe ich ja auch schon gecheckt.

D3DPRESENT_PARAMETERS mit VSync
D3DPP_VSYNC.jpg
D3DPRESENT_PARAMETERS ohne VSync
D3DPP_NOVSYNC.jpg
Wie man sieht, gibt es nur beim PresentationInterval einen Unterschied.
D3DPRESENT_INTERVAL_ONE für VSync
D3DPRESENT_INTERVAL_IMMEDIATE ohne VSync

Der Quellcode zum Erstellen vom Direct3DDevice9 ist folgender:

Code: Alles auswählen

	Direct3D9::Direct3DCreate9(&m_Direct3D9, Direct3D9::D3D_SDK_VERSION);

	Direct3D9::StructD3DPRESENT_PARAMETERS D3DPP;
	ZeroMemory(&D3DPP, sizeof(D3DPP));

	if (VideoDisplayModeFullscreen == m_VideoSettings.DisplayMode)
	{
		D3DPP.SwapEffect = Direct3D9::D3DSWAPEFFECT_DISCARD;
		D3DPP.Windowed = False;
		D3DPP.FullScreen_RefreshRateInHz = m_VideoSettings.RefreshRate;
	}
	else
	{
		D3DPP.SwapEffect = Direct3D9::D3DSWAPEFFECT_DISCARD;
		D3DPP.Windowed = True;
	}

	D3DPP.BackBufferWidth = m_VideoSettings.FrontWidth;
	D3DPP.BackBufferHeight = m_VideoSettings.FrontHeight;
	D3DPP.BackBufferCount = 1;
	D3DPP.BackBufferFormat = Direct3D9::D3DFMT_X8R8G8B8;

	if (False == m_VideoSettings.VerticalSync)
	{
		D3DPP.PresentationInterval = Direct3D9::D3DPRESENT_INTERVAL_IMMEDIATE;
	}
	else
	{
		D3DPP.PresentationInterval = Direct3D9::D3DPRESENT_INTERVAL_ONE;
		//D3DPP.PresentationInterval = Direct3D9::D3DPRESENT_INTERVAL_DEFAULT;
	}

	if (True == m_VideoSettings.LockableBackbuffer)
	{
		D3DPP.Flags = Direct3D9::D3DPRESENTFLAG_LOCKABLE_BACKBUFFER;
	}

	if (True == m_VideoSettings.ZBuffer)
	{
		D3DPP.EnableAutoDepthStencil = True;
		D3DPP.AutoDepthStencilFormat = Direct3D9::D3DFMT_D16;
	}

	D3DPP.hDeviceWindow = m_Window->GetHandle();

	m_Direct3D9->CreateDevice(&m_Direct3DDevice9, m_VideoSettings.Adapter, Direct3D9::D3DDEVTYPE_HAL, m_Window, Direct3D9::D3DCREATE_HARDWARE_VERTEXPROCESSING, &D3DPP);

	//m_Direct3DDevice9->GetSwapChain(0, &m_Direct3DSwapChain9);

	m_Direct3DDevice9->GetRenderTarget(0, &m_OriginalRenderTargetSurface);
	m_OriginalRenderTargetSurface->GetDesc(&m_OriginalRenderTargetSurfaceDesc);

	m_RenderTarget = m_OriginalRenderTarget;

	m_ActiveRenderMode = VideoDeviceRenderModeNone;

	ApplyRenderMode();

	return Direct3D9::D3D_OK;
Ich habe auch getestet, ob es mehrere Backbuffer gibt. Ich habe einem Gui-Element einen RenderCount verpasst, und wenn dieser auf 2 geht, werden 10 Sekunden gewartet. Das führt dazu, dass das erste Frame ordnungsgemäß gezeichnet wird, aber das zweite benötigt mehr als 10 Sekunden. Gäbe es mehr als einen Backbuffer, dürfte ich das Steuerelement erst nach 10 Sekunden sehen, ich sehe es aber sofort, und erst dann kommt der 10 Sekunden-Freeze.

Ich werde wohl nicht drum herum kommen, eine kleine eigenständig D3D9-Anwendung zu programmieren, um wirklich auszuschließen, dass es nicht doch irgendein komischer Bug in meiner Anwendung ist.
Benutzeravatar
Krishty
Establishment
Beiträge: 8316
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] [D3D9] Input Lag - Fullscreen - VSync VS SelfSync

Beitrag von Krishty »

Lohnt sich das denn für D3D 9? Wenn du auf D3D 11 umsteigst, kannst du in der API direkt erzwingen, dass beim nächsten Scan-out angezeigt wird, statt selber was programmieren zu müssen. Bei D3D 12 musst du AFAIK sogar darauf hinarbeiten, weil das Programmiermodell halt synchron ist.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Goderion
Beiträge: 82
Registriert: 16.09.2012, 12:02

Re: [C++] [D3D9] Input Lag - Fullscreen - VSync VS SelfSync

Beitrag von Goderion »

Krishty hat geschrieben:Lohnt sich das denn für D3D 9?
Was genau? Ein Testprogramm zu entwickeln oder eine eigene VSync-Funktion?

Testprogramm habe ich schon fertig hingeklatscht:

Code: Alles auswählen

#include <Windows.h>
#include <d3d9.h>


#pragma comment(lib, "Winmm.lib")
#pragma comment(lib, "d3d9.lib")


const wchar_t* WindowClassName = L"Direct3D9Test";
const int Width = 2560;
const int Height = 1440;
const int RefreshRate = 60;
const int ScanLineThreshold = Height * 95 / 100;
//const UINT PresentationInterval = D3DPRESENT_INTERVAL_ONE;
const UINT PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;


LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
	case WM_ERASEBKGND:
		return 0xFFFFFFFF;

	case WM_PAINT:
		ValidateRect(hWnd, nullptr);
		break;

	case WM_KEYDOWN:
	case WM_LBUTTONDOWN:
		DestroyWindow(hWnd);
		break;

	case WM_DESTROY:
		PostQuitMessage(0);
		break;

	default:
		return DefWindowProc(hWnd, message, wParam, lParam);

	}

	return 0;
}

ATOM MyRegisterClass(HINSTANCE hInstance)
{
	WNDCLASSEXW wcex;

	ZeroMemory(&wcex, sizeof(wcex));
	wcex.cbSize = sizeof(WNDCLASSEX);

	wcex.style = CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc = WndProc;
	wcex.cbClsExtra = 0;
	wcex.cbWndExtra = 0;
	wcex.hInstance = hInstance;
	//wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WIN32PROJECT1));
	wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
	wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
	//wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_WIN32PROJECT1);
	wcex.lpszClassName = WindowClassName;
	//wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

	return RegisterClassExW(&wcex);
}

int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)
{
	timeBeginPeriod(1);

	MyRegisterClass(hInstance);

	HWND hWnd = CreateWindowEx(0, WindowClassName, L"Direct3D9Text", WS_POPUP, 0, 0, Width, Height, nullptr, nullptr, hInstance, nullptr);
	ShowWindow(hWnd, SW_SHOWNORMAL);

	IDirect3D9* pDirect3D9 = Direct3DCreate9(D3D_SDK_VERSION);

	D3DPRESENT_PARAMETERS D3DPP;
	ZeroMemory(&D3DPP, sizeof(D3DPP));

	D3DPP.AutoDepthStencilFormat = D3DFMT_UNKNOWN;
	D3DPP.BackBufferCount = 1;
	D3DPP.BackBufferFormat = D3DFMT_X8R8G8B8;
	D3DPP.BackBufferHeight = Height;
	D3DPP.BackBufferWidth = Width;
	D3DPP.EnableAutoDepthStencil = false;
	D3DPP.Flags = 0;
	D3DPP.FullScreen_RefreshRateInHz = RefreshRate;
	D3DPP.hDeviceWindow = hWnd;
	D3DPP.MultiSampleQuality = 0;
	D3DPP.MultiSampleType = D3DMULTISAMPLE_NONE;
	D3DPP.PresentationInterval = PresentationInterval;
	D3DPP.SwapEffect = D3DSWAPEFFECT_DISCARD;
	D3DPP.Windowed = false;

	LARGE_INTEGER PerformanceFrequency;
	QueryPerformanceFrequency(&PerformanceFrequency);

	INT64 Frametime = PerformanceFrequency.QuadPart / RefreshRate;
	INT64 Waittime = 3000 * PerformanceFrequency.QuadPart / 1000000;

	LARGE_INTEGER NextPresent;
	NextPresent.QuadPart = 0;

	LARGE_INTEGER LastPresent;
	LastPresent.QuadPart = 0;

	IDirect3DDevice9* pDirect3DDevice9 = nullptr;
	pDirect3D9->CreateDevice(0, D3DDEVTYPE_HAL, hWnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, &D3DPP, &pDirect3DDevice9);

	while(IsWindow(hWnd))
	{
		for (;;)
		{
			MSG Message;
			while (PeekMessage(&Message, nullptr, 0, 0, PM_REMOVE))
			{
				TranslateMessage(&Message);
				DispatchMessage(&Message);
			}

			LARGE_INTEGER Now;
			QueryPerformanceCounter(&Now);

			if (NextPresent.QuadPart > Now.QuadPart)
			{
				INT64 FreeTime = NextPresent.QuadPart - Now.QuadPart;
				if (FreeTime >= Waittime)
				{
					Sleep(1);
				}
			}
			else
			{
				break;
			}
		}

		pDirect3DDevice9->BeginScene();

		D3DRECT Rect;
		Rect.x1 = 0;
		Rect.y1 = 0;
		Rect.x2 = Width;
		Rect.y2 = Height;

		pDirect3DDevice9->Clear(1, &Rect, D3DCLEAR_TARGET, 0xFFFFFFFF, 0, 0);

		POINT P;
		GetCursorPos(&P);

		Rect.x1 = P.x - 4;
		Rect.y1 = P.y - 4;
		Rect.x2 = P.x + 4;
		Rect.y2 = P.y + 4;

		pDirect3DDevice9->Clear(1, &Rect, D3DCLEAR_TARGET, 0xFF000000, 0, 0);

		pDirect3DDevice9->EndScene();

		if (D3DPRESENT_INTERVAL_IMMEDIATE == D3DPP.PresentationInterval)
		{
			for (;;)
			{
				D3DRASTER_STATUS RasterStatus;
				pDirect3DDevice9->GetRasterStatus(0, &RasterStatus);

				if (1 == RasterStatus.InVBlank)
				{
					break;
				}
				else if (RasterStatus.ScanLine > ScanLineThreshold)
				{
					break;
				}
			}
		}

		pDirect3DDevice9->Present(nullptr, nullptr, hWnd, nullptr);

		QueryPerformanceCounter(&LastPresent);

		NextPresent.QuadPart = LastPresent.QuadPart + Frametime;
	}

	timeEndPeriod(1);

	return 0;
}
Selbst beim Testprogramm ist der Input Lag mit VSync gefühlt deutlich größer, als bei der Variante ohne VSync und selber warten.
Allerdings fühlt sich der Input Lag in der eigentlichen Anwendung bei VSync noch schlechter an, als in der Testversion.
Es wird noch absurder. Der Input Lag fühlt sich im Fenstermodus geringer an, als im Vollbildmodus. Es müsste eigentlich umgekehrt sein.

Ich könnte mir noch vorstellen, dass Direct3D9 irgendwas komisches macht, wenn man das Direct3DDevice9 aus mehreren Thread heraus nutzt. Aber das Nutzen von D3DCREATE_MULTITHREADED, oder die Verschiebung aller Zugriffe auf Direct3D9 in einen Thread, ändern leider nichts.
Krishty hat geschrieben:Wenn du auf D3D 11 umsteigst, kannst du in der API direkt erzwingen, dass beim nächsten Scan-out angezeigt wird, statt selber was programmieren zu müssen.
Ist das nicht das Gleiche wie bei D3D9 D3DPRESENT_INTERVAL_ONE?
Krishty hat geschrieben:Bei D3D 12 musst du AFAIK sogar darauf hinarbeiten, weil das Programmiermodell halt synchron ist.
Eigentlich will ich vorerst bei D3D9 bleiben. D3D11/D3D12 kenne ich überhaupt nicht. D3D9 läuft auch einfach überall auf jeder Grafikkarte, auch auf einer uralt Windows XP Mühle.
Benutzeravatar
Krishty
Establishment
Beiträge: 8316
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] [D3D9] Input Lag - Fullscreen - VSync VS SelfSync

Beitrag von Krishty »

Goderion hat geschrieben:
Krishty hat geschrieben:Wenn du auf D3D 11 umsteigst, kannst du in der API direkt erzwingen, dass beim nächsten Scan-out angezeigt wird, statt selber was programmieren zu müssen.
Ist das nicht das Gleiche wie bei D3D9 D3DPRESENT_INTERVAL_ONE?
Nein, eigentlich nicht.

Bei D3D 9 läuft es ungefähr so. Nehmen wir an,
  • CPU braucht 10 ms zum Rendern
  • GPU braucht 20 ms zum Rendern
  • Refresh Rate ist 40 ms (sehr niedrig, aber ich will hier runde Zahlen haben)
Wie genau das Folgende jetzt zutrifft, müsste einer der Treiberentwickler hier sagen (*hust* Jörg? *hust*), aber machen wir mal:
  • +10 ms: CPU hat Frame #1 fertig und schickt ihn via Present() zum Rendern. Der Command Buffer der GPU ist nicht voll, also entscheidet sich der Treiber, nichts zu tun, und kehrt direkt zurück.
  • +20 ms: CPU schickt via Present() Frame #2 zum Rendern. Command Buffer ist immer noch nicht voll; nichts passiert.
  • +30 ms: CPU schickt via Present() Frame #3 zum Rendern. Der Command Buffer ist nun voll. Der Treiber startet das Rendering. Present() kehrt erstmal nicht zurück.
  • +50 ms: GPU ist fertig mit Rendering von Frame #1. Würde nun anfangen, Frame #2 zu rendern, aber du hast bloß einen Back Buffer und der wartet auf VSync.
  • +80 ms: VSync. Frame #1 wird wird auf dem Bildschirm sichtbar. Back Buffer ist wieder frei für Rendering. GPU startet Rendering von Frame #2. Present() kehrt zurück.
  • +90 ms: CPU schickt via Present() Frame #4 zum Rendern. GPU arbeitet noch an Frame #2.
  • +100 ms: GPU hat Frame #2 fertiggerendert. Würde nun anfangen, Frame #3 zu rendern, aber du hast bloß einen Back Buffer und der wartet auf VSync.
  • +120 ms: VSync. Frame #2 wird auf dem Bildschirm sichtbar. Back Buffer ist wieder frei für Rendering. GPU startet Rendering von Frame #3. Present() von Frame #4 kehrt zurück.
In dem Szenario hättest du 70 ms Lag bei VSync mit einem einzigen Back Buffer.

Wenn du manuell auf VSync wartest, rendert die GPU nichts voraus. Denn du wartest ja auf Present(), bevor du den nächsten Frame schickst.

Wenn du D3D 11.2 auf Windows 8.1 oder höher benutzt, und IDXGISwapChain2::SetMaximumFrameLatency() nutzt, hast du die gleiche Wirkung, nur dass du nichts selber synchronisieren musst. Bei D3D 12 wahrscheinlich ähnlich.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Goderion
Beiträge: 82
Registriert: 16.09.2012, 12:02

Re: [C++] [D3D9] Input Lag - Fullscreen - VSync VS SelfSync

Beitrag von Goderion »

Goderion hat geschrieben:Allerdings fühlt sich der Input Lag in der eigentlichen Anwendung bei VSync noch schlechter an, als in der Testversion.
Die Aussage muss ich zurücknehmen. Der Grund für diese Vermutung war, dass der Input Lag im Vollbildmodus+VSync deutlich stärker auftritt, er wird gefühlt verdoppelt, wenn nicht sogar verdreifacht. Das riecht wieder nach mehr als einem Backbuffer. In der Testanwendung hatte ich den Inputlag auf unter 1ms gedrückt. In der normalen Anwendung liegt dieser aber bei 5 bis 7 ms.
Es scheint aber wirklich nur einen Backbuffer zu geben. Ich cleare den Backbuffer alle 120 Frames grün statt weiß und warte dann direkt nach dem Present für eine Sekunde. Das grüne Bild bleibt auch für eine Sekunde. Bei mehr als einem Backbuffer dürfte ich das grüne Bild nur 16,6ms sehen.
Goderion hat geschrieben:Es wird noch absurder. Der Input Lag fühlt sich im Fenstermodus geringer an, als im Vollbildmodus. Es müsste eigentlich umgekehrt sein.
Das bestätigt sich weiterhin.
Krishty hat geschrieben:[*]+10 ms: CPU hat Frame #1 fertig und schickt ihn via Present() zum Rendern. Der Command Buffer der GPU ist nicht voll, also entscheidet sich der Treiber, nichts zu tun, und kehrt direkt zurück.
Spekulation: Das Verhalten wäre sehr komisch und vermutlich kontraproduktiv. Bei Direct3D9 mit D3DPRESENT_INTERVAL_ONE wird beim Present auf den VSync gewartet und dann der Frontbuffer mit dem Backbuffer vertauscht. Wenn der VSync erfolgt, muss das Bild im Backbuffer fertig sein. Vor dem Present wird EndScene aufgerufen, was Direct3D9 im Prinzip mitteilt, dass nun alles nötige gemacht wurde und nichts mehr weiter zu erwarten ist. Warum sollte da Direct3D9/Treiber auf irgendwas warten? Ich sehe keinen Grund nicht gleich beim EndScene alles fertig zu zeichnen.

Meine Beobachtungen lassen sogar noch eher vermuten, dass unabhängig vom D3DPRESENT_INTERVAL, die DrawCalls sofort ausgeführt werden. Ich messe auch die Dauer von IDirect3DDevice9::DrawPrimitive, und die ist einfach zu hoch, als das man davon ausgehen könnte, dass dort nur irgendwelche Commands in einen Buffer gepackt werden.

Die Messungen von DrawPrimitive unterscheiden sich nicht von D3DPRESENT_INTERVAL_ONE zu D3DPRESENT_INTERVAL_IMMEDIATE.
Present benötigt mit D3DPRESENT_INTERVAL_IMMEDIATE nur wenige µs.

Auf der anderen Seite scheint er ja bei Vollbild+VSync irgendwas komisches zu machen, was den Input Lag signifikant in die Höhe treibt.
Ich wüsste einfach nicht, warum man die Commands ewig sammeln sollte, vor allem erst recht nicht, wenn die GPU gerade nutzlos rumgammelt.
Benutzeravatar
Krishty
Establishment
Beiträge: 8316
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] [D3D9] Input Lag - Fullscreen - VSync VS SelfSync

Beitrag von Krishty »

Goderion hat geschrieben:
Krishty hat geschrieben:[*]+10 ms: CPU hat Frame #1 fertig und schickt ihn via Present() zum Rendern. Der Command Buffer der GPU ist nicht voll, also entscheidet sich der Treiber, nichts zu tun, und kehrt direkt zurück.
Spekulation: Das Verhalten wäre sehr komisch und vermutlich kontraproduktiv.
Ich wüsste einfach nicht, warum man die Commands ewig sammeln sollte, vor allem erst recht nicht, wenn die GPU gerade nutzlos rumgammelt.
Nein, im Gegenteil.
  • Es erlaubt dem Treiber, die Befehlsfolge zu optimieren, weil die der Anwendung selten optimal ist
  • Command Buffer Flushes sind unglaublich teuer, weil sie (unter dem D3D 9er-Modell) Kernel Mode Transitions bewirken, und die reduziert man damit auf ein Minimum (idealerweise weniger als einmal pro Frame)
  • Es stellt 100 % Auslastung sicher
  • Es vermeidet Thrashing, wenn mehrere Anwendungen rendern (was im Fenstermodus immer der Fall ist)
Goderion hat geschrieben:Bei Direct3D9 mit D3DPRESENT_INTERVAL_ONE wird beim Present auf den VSync gewartet und dann der Frontbuffer mit dem Backbuffer vertauscht. Wenn der VSync erfolgt, muss das Bild im Backbuffer fertig sein.
Nein. Falls ein Frame fertig vorliegt, wird er in den Front Buffer geschoben. Nichts erzwingt, dass der Frame auch fertig sein muss (das geht ja auch gar nicht, wenn die GPU für einen Frame länger braucht, als das VSync-Intervall dauert!). Du stellst mit D3DPRESENT_INTERVAL_ONE sicher, dass die Anwendung den nächstmöglichen VSync nimmt, statt auf den 2. oder 3. zu warten (was die Framerate glätten würde, falls die GPU zu langsam ist.) Du stellst aber nicht sicher, dass nicht mehr als ein Frame in der Warteschlange für den Treiber liegt. AFAIK hält D3D oft zwei bis vier Frames vor, bevor sie gezeichnet werden.
Goderion hat geschrieben:Vor dem Present wird EndScene aufgerufen, was Direct3D9 im Prinzip mitteilt, dass nun alles nötige gemacht wurde und nichts mehr weiter zu erwarten ist. Warum sollte da Direct3D9/Treiber auf irgendwas warten? Ich sehe keinen Grund nicht gleich beim EndScene alles fertig zu zeichnen.
BeginScene()/EndScene() sind da, damit D3D einzelne Frames erkennt. Falls die CPU signifikant schneller Frames einreiht als die GPU sie rendern kann, müssen nämlich Frames weggeschmissen werden – und der Treiber muss wissen, was dabei zu einem Frame gehört und was nicht. Das teilt man ihm via BeginFrame()/EndFrame() mit. (Neue Treibermodelle nutzen dafür Abhängigkeitsbäume; darum gibt’s die Funktionen ab D3D 10 nicht mehr.)

GPUs sind immer auf Durchsatz optimiert, nicht auf Latenz (für den User ist die Zahl „60 FPS Durchsatz“ wichtiger als „100 ms Lag“). Wie gesagt gibt es ab DXGI 1.2 auf Windows 8 die Möglichkeit, das zu beeinflussen; aber auf älteren Systemen musst du das VSync von Hand abwarten.

Du könntest mal probieren, so wie hier https://msdn.microsoft.com/en-us/librar ... r_Sequence unter „The Query Mechanism“ den Command Buffer zu flushen bevor du anzeigst. Wahrscheinlich bewirkt das aber nur, dass die Framerate sinkt.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Goderion
Beiträge: 82
Registriert: 16.09.2012, 12:02

Re: [C++] [D3D9] Input Lag - Fullscreen - VSync VS SelfSync

Beitrag von Goderion »

Krishty, vielen lieben Dank für die Infos und den Link.
Meine Direct3D9-Welt ist zwar gerade am einstürzen ;), aber jetzt habe ich wenigstens eine Erklärung für so manch komisches Verhalten von Direct3D9.

Warum Direct3D9 sich so komisch verhält, verstehe ich aber trotzdem nicht so richtig. Ich versuche das nochmal kurz zusammenzufassen:

Direct3D9 mit D3DPRESENT_INTERVAL_ONE entzieht einem quasi fast die Kontrolle über die Anzeige. Alle Kommandos, die man an ein Direct3DDevice9 schickt, wie z.B. SetTexture und DrawPrimitive, werden nur im Hintergrund von Direct3D9 gesammelt und erst wenn Direct3D9 es für richtig/sinnvoll hält, wird das Frame tatsächlich gezeichnet und angezeigt. Ohne das Leeren vom command buffer, weiß ich nicht, was beim Present tatsächlich passiert. Meine Beobachtungen bestätigen dieses Verhalten, es fühlt sich an, als hätte man mehr als einen backbuffer. Das manuelle Leeren vom command buffer, z.B. direkt nach EndScene, sorgt dann dafür, das beim nächsten Present tatsächlich das Bild angezeigt wird, was man unmittelbar zuvor an Direct3DDevice9 geschickt hat.

Bei D3DPRESENT_INTERVAL_IMMEDIATE scheint sich Direct3D9 komplett anders zu verhalten. Hier bekomme ich den Eindruck, dass tatsächlich das, was ich gerade zuvor an das Direct3DDevice9 geschickt habe, auch mit dem nächsten Present angezeigt wird. Der Input Lag ist hier im vergleich zu D3DPRESENT_INTERVAL_ONE deutlich geringer.

Ich verstehe ja halbwegs den Sinn von D3DPRESENT_INTERVAL_ONE. Die Befehle der Anwendung werden von Direct3D9 schön zwischengespeichert. Damit entkoppelt man quasi die Anwendung von der Grafikausgabe. Holpert die Anwendung mal, oder rendert generell "ungünstig", kann man dass durch den command buffer ausgleichen und hat trotzdem eine flüssige Bildwiedergabe. Da passt diese Aussage auch gut rein:
Krishty hat geschrieben:GPUs sind immer auf Durchsatz optimiert, nicht auf Latenz (für den User ist die Zahl „60 FPS Durchsatz“ wichtiger als „100 ms Lag“).
Warum gibt es kein D3DPRESENT_INTERVAL_IMMEDIATE mit integriertem Warten auf den nächsten VSync? D3DPRESENT_INTERVAL_IMMEDIATE scheint so zu arbeiten, wie ich es brauche. Der command buffer wird entweder sofort verarbeitet (EndScene) oder ist deutlich kürzer und beim Present kommt gefühlt das raus, was ich gerade raus haben möchte, und nicht irgendein uraltes Bild, was noch im command buffer rumlag.

Meine Erfahrungen mit dem Leeren des command buffers:
- Fenstermodus: Nutzlos, keine spürbaren positiven Veränderungen, egal ob mit oder ohne VSync.

- Volldbild mit D3DPRESENT_INTERVAL_ONE: Der Input Lag wird stark reduziert. Die Auslastung (CPU/GPU) bleibt gleich, aber die Schwelle, wo die FPS einbrechen (unter die Hz fallen) ist etwas schneller erreicht.

- Volldbild mit D3DPRESENT_INTERVAL_IMMEDIATE und SelfSync: Tearing wird sehr stark reduziert. Ohne das Leeren vom command buffer sieht man oberen Bildschirmrand Tearing. Je stärker die Grafik-Last ist, desto weiter rutscht das Tearing nach unten. Ich vermute daher, dass bei D3DPRESENT_INTERVAL_IMMEDIATE der command buffer beim Present geleert/verarbeitet wird. Hat man dies nicht selber schon vorher manuell getan, verpasst man das VSync und man sieht Tearing.

- Volldbild mit D3DPRESENT_INTERVAL_IMMEDIATE: Scheint komplett sinnlos zu sein. FPS brechen stark ein, teilweise um über 50%.

Nochmals vielen vielen Dank an Krishty! Ohne deine Hinweise würde ich vermutlich immer noch rumrätseln oder hätte mir einen Wolf gegooglet.
Benutzeravatar
Krishty
Establishment
Beiträge: 8316
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] [D3D9] Input Lag - Fullscreen - VSync VS SelfSync

Beitrag von Krishty »

Gern! Habe ich richtig verstanden, dass du den Command Buffer manuell via Query Mechanism leerst? Oder nutzt du einen anderen Weg?
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Goderion
Beiträge: 82
Registriert: 16.09.2012, 12:02

Re: [C++] [D3D9] Input Lag - Fullscreen - VSync VS SelfSync

Beitrag von Goderion »

Krishty hat geschrieben:Gern! Habe ich richtig verstanden, dass du den Command Buffer manuell via Query Mechanism leerst? Oder nutzt du einen anderen Weg?
Ja, ich mache das direkt nach dem EndScene. So sieht der Quellcode aus:

Code: Alles auswählen

if (Null == m_Direct3DQuery9)
{
	m_Direct3DDevice9->CreateQuery(Direct3D9::D3DQUERYTYPE_EVENT, &m_Direct3DQuery9);
}

m_Direct3DQuery9->Issue(Direct3D9::D3DISSUE_END);

for (;;)
{
	UInt32 rVal = m_Direct3DQuery9->GetData(Null, 0, Direct3D9::D3DGETDATA_FLUSH);
	if (S_FALSE != rVal)
	{
		break;
	}

	Sleep(1);
}
Das Sleep(1) ist zwingend nötig, da die Schleife je nach Situation oft/lange durchläuft und sonst die CPU sinnlos auslastet. Es scheint auch Direct3D9 zu behindern, wenn ich das Sleep(1) weg lasse.
Benutzeravatar
Krishty
Establishment
Beiträge: 8316
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] [D3D9] Input Lag - Fullscreen - VSync VS SelfSync

Beitrag von Krishty »

Du kannst SwitchToThread() ausprobieren. Das sagt dem Scheduler, „Falls du mich unterbrechen willst, dann mach’s jetzt sofort.“. Hat vielleicht weniger negative Wirkung als Sleep(1) (das deinen Time Slice zwingend abgibt).

Ansonsten kannst du auch YieldProcessor() nutzen. Das ist der CPU-spezifische Befehl, der die CPU zwar nicht freigibt, aber zumindest keine unnötige Energie verbraucht (normalerweise für Spin Locks).

Ist das alles noch für den Ultima-Klon?
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Goderion
Beiträge: 82
Registriert: 16.09.2012, 12:02

Re: [C++] [D3D9] Input Lag - Fullscreen - VSync VS SelfSync

Beitrag von Goderion »

SwitchToThread() und YieldProcessor() sorgen leider auch für eine hohe CPU-Last.
Krishty hat geschrieben:Ist das alles noch für den Ultima-Klon?
Ja.
Benutzeravatar
Krishty
Establishment
Beiträge: 8316
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] [D3D9] Input Lag - Fullscreen - VSync VS SelfSync

Beitrag von Krishty »

Okay. Präsentier irgendwann mal Bilder, oder Gameplay-Footage (mit Controller in der Bildecke, damit man die Input-Reaktionszeit bestaunen kann ;) )!
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Antworten