Seite 1 von 1

Direct3D9 - fertige Scene mit hqx vergrößern

Verfasst: 20.01.2015, 03:26
von Goderion
Hallo.

Ich möchte eine gerenderte Scene mit dem hqx-Filter vergrößern.
Der Filter wird oft auch von Emulatoren genutzt: http://en.wikipedia.org/wiki/Hqx

In meiner aktuellen Version funktioniert das auch schon, aber nur irre langsam,
da LockRect viel Zeit frisst, bis zu 10ms, und ich weiß nicht warum.

Die Scene wird in ein zuvor erstelltes RenderTarget gemalt.
Da ich da aber nicht direkt dran komme, kopier ich es erst in ein extra Surface:

Code: Alles auswählen

// Ich habe Direct3D9 komplett gekapselt und nutze sowas wie SmartPointer, daher sieht es etwas komisch aus

m_Direct3DDevice9->CreateOffscreenPlainSurface(m_RealWidth, m_RealHeight, D3DFMT_X8R8G8B8, D3DPOOL_SYSTEMMEM, &m_TempSurface, Null);

m_Direct3DDevice9->GetRenderTargetData(m_RenderTargetSurface, m_TempSurface);

Direct3D9::StructD3DLOCKED_RECT D3DLockedRect;

m_TempSurface->LockRect(&D3DLockedRect, pRect, D3DLOCK_NO_DIRTY_UPDATE | D3DLOCK_NOSYSLOCK | D3DLOCK_DONOTWAIT);

// jetzt wende ich den hqx-Filter an

m_TempSurface->UnlockRect();

m_Direct3DDevice9->UpdateSurface(m_TempSurface, Null, m_RenderTargetSurface, Null);
Das geht alles sehr schnell, jeder Aufruf in der Regel unter 100 Mikrosekunden, bis auf das LockReckt, das braucht bis zu 10 Millisekunden.

Jemand eine Idee wie ich das Ganze schneller bekomme?

Re: Direct3D9 - fertige Scene mit hqx vergrößern

Verfasst: 20.01.2015, 09:16
von dot
Goderion hat geschrieben:Jemand eine Idee wie ich das Ganze schneller bekomme?
Wende den Filter per FragmentShader an, anstatt LockRect() und dann auf der CPU rechnen...

Re: Direct3D9 - fertige Scene mit hqx vergrößern

Verfasst: 20.01.2015, 11:03
von Goderion
Hallo und danke für die Antwort.

Von FragmentShadern (Pixel-Shader?) hab ich so gut wie keine Ahnung, was ich, wenn es nicht zu aufwändig ist, ändern lasst.
Ich weiß auch nicht, ob es überhaupt möglich ist, den hqx-Filter als FragmentShader zu nutzen.
Ich nutze den hqx-Filter aktuell über die libhqx-1.dll.

Ich werde mal Google bemühen und mir FragmentShader/Pixel-Shader genauer angucken.

Mal abgesehen davon, muss es doch eine Möglichkeit geben, den Inhalt eines Direct3DSurface9 zu verändern, ohne dabei ein dutzend Millisekunden zu verlieren?
Ich verstehe auch nicht, was Direct3D9 da treibt. Das Surface ist per D3DPOOL_SYSTEMMEM erstellt worden, sollte also im Arbeitsspeicher und nicht im Grafikkartenspeicher liegen,
warum dauert dann ein LockReckt so lange? Was habe ich übersehen, bzw. missverstanden?

Re: Direct3D9 - fertige Scene mit hqx vergrößern

Verfasst: 20.01.2015, 11:35
von Schrompf
Nein, es ist eben nicht so einfach möglich. Du verlierst auf jeden Fall ein paar Dutzend Millisekunden, nur bei welchem Deiner Schritte ist abhängig vom Treiber. Denn: die Grafikkarte läuft nebenher. Die ist ein eigener Prozessor. Alles, was Du an Direct3D-Funktionen aufruft, wird nur in eine lange Command Queue gesteckt und von dort *bei Gelegenheit* von der GPU abgeholt und abgearbeitet. Je nach Last auf der Grafikkarte kann diese Verzögerung von ein paar ms bis hoch zu zwei drei ganzen Frames sein.

Wenn Du jetzt das Ergebnis eines Rendertargets auf die CPU zurückladen willst, ist der Treiber schlau genug, so lange zu warten, bis alle Renderoperationen auf dem Rendertarget zu Ende gekommen sind. Soweit ich weiß, sind die Treiber gar nicht so schlau, sondern warten an der Stelle einfach, bis die GPU alles abgearbeitet hat und leer läuft. Dann bekommst Du die Daten. Und diese Wartezeit, genannt GPU Sync, sind die Millisekunden, die Du beobachtet hast. Alle Funktionsaufrufe vorher enden nur als Kommandos in der Queue, da kannst Du Dir also das Zeitmessen komplett sparen. Und Du erkennst jetzt wahrscheinlich auch, warum es prinzipiell unmöglich ist, diese Wartezeit zu vermeiden.

Daher ist die von dot vorgeschlagene Lösung die einzig mögliche. Das bedeutet allerdings, dass Du die lib nicht mehr verwenden kannst, sondern im FragmentShader selbst implementieren musst. Dafür wirst Du dann belohnt mit mindestens dutzendfacher Performance des Filters selbst und keiner Wartezeit mehr, weil die Daten nirgends die GPU verlassen müssen.

Re: Direct3D9 - fertige Scene mit hqx vergrößern

Verfasst: 20.01.2015, 15:26
von Goderion
Vielen Dank für die Antwort und die kleine Erklärung.

So etwas habe ich schon ungefähr vermutet. Meine Theorie:
Das GetRenderTargetData hat mein TempSurface vermutlich nur markiert, daß dort jetzt Daten aus dem RenderTargetSurface hingehören,
aber erst, wenn ich auf die Daten im TempSurface zugreife, wird tatsächlich versucht, die Daten vom RenderTargetSurface zum TempSurface zu kopieren,
da kommt dann der GPU-Sync und ich muss warten.

Mmmmh... so ganz kann das aber auch nicht stimmen.
Das GetRenderTargetData braucht zu lange, als das ich davon ausgehen könnte, das es nur als Befehl in einer Warteliste verschwindet.
Wenn ich vor dem LockRect 100 Millisekunden warte, ändert sich an der Dauer vom LockRect nichts.

Ich kann mir hier nur vorstellen, daß das TempSurface wie bereits gesagt, nur zum kopieren markiert wird,
aber das was dann lange dauert, ist das Kopieren der Daten und nicht das Warten auf die GPU.

Wenn ich beim TempSurface zweimal hintereinander LockRect aufrufe, dauert nur das erste LockRect lange,
der zweite LockReckt braucht nur 0 Mikrosekunden, also quasi instant.

Code: Alles auswählen

	ClassPerformanceCounter PC;
	m_Direct3DDevice9->GetRenderTargetData(m_Direct3DSurface9, m_TempSurface);
	UInt64 DurationGetRenderTargetData = PC.GetDurationInMicroseconds(); // 54

	PC.Start();
	Direct3D9::StructD3DLOCKED_RECT LR;
	m_TempSurface->LockRect(&LR, pRect, D3DLOCK_NO_DIRTY_UPDATE | D3DLOCK_NOSYSLOCK | D3DLOCK_DONOTWAIT | D3DLOCK_DISCARD);
	UInt64 DurationLR = PC.GetDurationInMicroseconds(); // 7107
	m_TempSurface->UnlockRect();

	PC.Start();
	Direct3D9::StructD3DLOCKED_RECT D3DLockedRect;
	m_TempSurface->LockRect(&D3DLockedRect, pRect, D3DLOCK_NO_DIRTY_UPDATE | D3DLOCK_NOSYSLOCK | D3DLOCK_DONOTWAIT);
	UInt64 DurationLockRect = PC.GetDurationInMicroseconds(); // 0
Auf Direct3D9 wird immer nur von einem Thread gleichzeigt zugegriffen.

Wenn meine Vermutung stimmt, dauert das Kopieren vom RenderTargetSurface zum TempSurface so lange.
Verkleinere ich beide, geht es schneller. Das gleiche umgekehrt, z.B. bei QHD 2560x1440 dauert es immer ca. 30ms.
Ob ich in das RenderTarget male oder nicht, scheint keine spürbaren Auswirkungen auf das LockRect zu haben.
Ziemlich schwach, braucht fast pro Megabyte eine Millisekunde.

Ich hatte mal ein Video gesehen mit John Carmack, wo er sowas andeutet.
The process of updating a textures on the PC is on the order of “tens of thousands of times slower” than on the Xbox 360 and PS3
http://www.pcper.com/reviews/Editorial/ ... s-and-more
Das Interview auf Youtube: https://www.youtube.com/watch?v=hapCuhAs1nA

Nun gut, weiter Analysieren wird mir wohl nix bringen, da muss ich mich wohl mit Pixel-Shadern rumschlagen.
Das ist natürlich ein ganz schön großer Brocken. Der hqx-Quellcode umfasst ca. 400 Kilobyte,
das in einen Pixel-Shader umzuprogrammieren, wird sicher kein Kinderspiel.

Re: Direct3D9 - fertige Scene mit hqx vergrößern

Verfasst: 20.01.2015, 16:26
von Schrompf
1ms pro Megabyte ist ein Gigabyte pro Sekunde. Ich finde das schon verdammt schnell. Ansonsten stimme ich Dir zu: ich bin von sehr viel kleineren Rendertargets ausgegangen, und da wäre der GPU-Sync tatsächlich der größte Packen gewesen. Bei so großen Datenmengen wird aber dann irgendwann der reine Transfer zum Bottleneck.

Nuja, das Endergebnis bleibt das Gleiche: vermeide es, Daten von der GPU zurück auf die CPU zu laden. Und das bedeutet, dass man üblicherweise das Spiel als Einbahnstraße CPU -> GPU -> Bildschirm benutzt. Was der Effekt nun genau tut, weiß ich nicht, aber sonderlich komplex kann es nicht sein, wenn es vorher auf der CPU in Echtzeit ging.

[edit] Falls Du von dem da redest: http://en.wikipedia.org/wiki/Hqx dann sollte das stressfrei auf der GPU gehen. Es wird aber ordentlich Arbeit, das zu portieren. Evtl. googelst Du mal, ob das schon jemand gemacht hat. PostProcessing-Antialiasing-Effekte wie FXAA ( http://en.wikipedia.org/wiki/Fast_appro ... i-aliasing ) tun eigentlich auch nur sowas wie HQX.

Re: Direct3D9 - fertige Scene mit hqx vergrößern

Verfasst: 20.01.2015, 17:38
von Spiele Programmierer
Ich stimme deinen generellen Aussagen zu - 1GB/s ist aber meines Erachtens nicht wirklich viel. Im CUDA SDK ist ein Beispielprogramm beigelegt("bandwidthtest"), das unter anderem die Übertragungsrate von GPU <-> CPU zu ermitteln kann. Ich komme dort mit meiner Nvidia Karte in beiden Richtungen auf knapp über 12GB/s...

Re: Direct3D9 - fertige Scene mit hqx vergrößern

Verfasst: 21.01.2015, 02:45
von Goderion
Hallo und Danke für die Antworten.

Ich habe jetzt halbwegs PixelShader in meine Engine integriert.
Ich gehe mal davon aus, daß StretchRect vom PixelShader nicht beeinflusst wird?
Also muss ...

Code: Alles auswählen

	m_Direct3DDevice9->CreateRenderTarget(m_RealWidth, m_RealHeight, Desc.Format, Desc.MultiSampleType, Desc.MultiSampleQuality, False, &m_Direct3DSurface9, Null);
... raus und ...

Code: Alles auswählen

	m_Direct3DDevice9->CreateTexture(m_RealWidth, m_RealHeight, 1, D3DUSAGE_RENDERTARGET, Desc.Format, D3DPOOL_DEFAULT, &m_Direct3DTexture9, Null);
	m_Direct3DTexture9->GetSurfaceLevel(0, &m_Direct3DSurface9);
... rein,
damit ich das RenderTarget dann z.B. per DrawPrimitive zeichnen kann, oder geht das einfacher?

Re: Direct3D9 - fertige Scene mit hqx vergrößern

Verfasst: 21.01.2015, 11:27
von Schrompf
Nein, das geht genau so. Textur erzeugen mit USAGE_RENDERTARGET, SurfaceLevel holen, da reinrendern. Dann den PostFX-Shader rausholen, Rendertarget umsetzen auf welches Ziel auch immer, vorheriges Rendertarget als Textur anklemmen, Fullscreen-Quad zeichnen.

Re: Direct3D9 - fertige Scene mit hqx vergrößern

Verfasst: 22.01.2015, 03:15
von Goderion
Danke für die Antwort. Hab es ein wenig getestet, StretchRect und DrawPrimitive nehmen sich nichts, klappt sehr gut.

Ich habe einen PixelShader gefunden, der passen könnte, allerdings bekomme ich ihn nicht richtig geladen:
https://github.com/Armada651/hqx-shader

Beim Single-Pass bekomme ich diesen Fehler:
E:\hqx-shader-master\single-pass\shader-files\hqx.inc(148): error X3014: incorrect number of arguments to numeric-type constructor
Beim anderen bekomme ich folgenden Fehler:
E:\hqx-shader-master\shader-files\pass2.inc(32): error X3047: 'texture_size': structure members cannot be declared 'uniform'
E:\hqx-shader-master\shader-files\pass2.inc(33): error X3082: Object types are not allowed in structs
So versuche ich den PixelShader zu erstellen, bzw. zu compilieren:

Code: Alles auswählen

	FileName = "E:\\hqx-shader-master\\shader-files\\hq2x.cg";
	Function = "main_fragment";
	Profile = "ps_2_0";

	D3DXCompileShaderFromFile(FileName, Null, Null, Function, Profile, 0, &Code, Null, Null);
Ist da jetzt wirklich im Quellcode vom PixelShader Murks oder mache ich was falsch?

Beim nicht single-pass PixelShader soll man eine LookUpTable als Texture laden.
Wie muss ich das verstehen? IDirect3DDevice9->CreateTexture und dann die Daten da rein, und dann?

Re: Direct3D9 - fertige Scene mit hqx vergrößern

Verfasst: 22.01.2015, 11:32
von Schrompf
Also: Du wirst *nicht* das Zeug so einfach zum Laufen bekommen, ohne Dich mit dem Thema "Shader" mal auseinander zu setzen. Ich weiß nicht genau, was Du eigentlich vor hast und in welchem Kontext Du arbeitest, aber falls Du *irgendwas* mit Grafikkarten machst, seien es Spiele, Bildbearbeitung oder wissenschaftliche Simulationen, solltest Du Dich mal mit Shadern beschäftigen.

Dann: Der zitierte Fehler will Dir sagen, dass der half4()-Konstruktor auf Zeile 148 vier Parameter nimmt, nicht einen. Du könntest es also auf half4(1, 1, 1, 1) umschreiben, um das Problem zu beheben. Mich wundert das allerdings, weil DirectX das nach meinem Wissen eigentlich akzeptieren müsste. OpenGL wird bei sowas schnell trotzig mit gewissen Herstellern. Korrigiert ist es jedenfalls exakter.

Und schraub mal Profile auf "ps_3_0" auf. Alle Grafikkarten seit 2004 können mindestens das.

Und: LookUpTables sind ja nur Tabellen, die anhand eines Index irgendeinen vorberechneten Wert ausgeben. Du kannst also einen Zugriff array[index] auch umbiegen als texture2D(TexArray, float2(index / width, 0.0f)).

Re: Direct3D9 - fertige Scene mit hqx vergrößern

Verfasst: 22.01.2015, 15:00
von Goderion
Vielen Dank für die Antwort.

Ich arbeite an meiner 2D-Spiele-Engine. Wie man schon gesehen hat, nutze ich zum Rendern Direct3D9.
Ich wollte eigentlich nur mal "schnell" mein gerendertes Level per hqx vergrößern und mich nicht so viel mit Shadern auseinandersetzen,
aber ich sehe schon, das es komplizierter wird und sich sogar lohnen kann, sich die genauer anzugucken.

Ich habe in der hqx.inc das half4 angepasst, danach kommt ein anderer Fehler:
error X3502: 'main_fragment': input parameter 'VAR' missing semantics
Gibt es denn keinen Weg, den hqx-Shader schnell einsatzbereit zu machen?
Später, spätestens bei der Berechnung des Lichtes, werde ich mir Shader genauer angucken und auch selber welche schreiben.

EDIT: Kann jemand Online-Tutorials zu PixelShadern empfehlen? Ich werd auch mal bei Amazon gucken, da bekommt man recht häufig günstige gebrauchte Fachliteratur.