[C++] [D3D9] Texturkoordinaten und Skalierung

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] Texturkoordinaten und Skalierung

Beitrag von Goderion »

Hallo.

In bestimmten Zoom/Skalierungsleveln zeigt meine Engine "Renderfehler":
fail.png
fail2.png
Das Direct3DDevice9 wird so auf das 2D-Rendern vorbereitet:

Code: Alles auswählen

const UInt32 D3DFVF_CUSTOM_2D = Direct3D9::D3DFVF_XYZRHW | Direct3D9::D3DFVF_DIFFUSE | Direct3D9::D3DFVF_TEX1;

m_Direct3DDevice9->SetFVF(D3DFVF_CUSTOM_2D);

m_Direct3DDevice9->SetRenderState(Direct3D9::D3DRS_COLORWRITEENABLE, 0x0000000F);
m_Direct3DDevice9->SetRenderState(Direct3D9::D3DRS_LIGHTING, False);
m_Direct3DDevice9->SetRenderState(Direct3D9::D3DRS_ALPHABLENDENABLE, True);
m_Direct3DDevice9->SetRenderState(Direct3D9::D3DRS_CULLMODE, Direct3D9::D3DCULL_NONE);

m_Direct3DDevice9->SetTextureStageState(0, Direct3D9::D3DTSS_ALPHAOP, Direct3D9::D3DTOP_MODULATE);

m_Direct3DDevice9->SetRenderState(Direct3D9::D3DRS_ZENABLE, False);
m_Direct3DDevice9->SetRenderState(Direct3D9::D3DRS_ZWRITEENABLE, False);
m_Direct3DDevice9->SetRenderState(Direct3D9::D3DRS_ALPHATESTENABLE, False);

m_Direct3DDevice9->SetRenderState(Direct3D9::D3DRS_FILLMODE, Direct3D9::D3DFILL_SOLID);

m_Direct3DDevice9->SetRenderState(Direct3D9::D3DRS_SRCBLEND, Direct3D9::D3DBLEND_SRCALPHA);
m_Direct3DDevice9->SetRenderState(Direct3D9::D3DRS_DESTBLEND, Direct3D9::D3DBLEND_INVSRCALPHA);
Die Texturkoordinaten werden aufgrund der Gesamtgröße der Textur und dem zu rendernden Bereich/Bild erzeugt:

Code: Alles auswählen

TextureRect.X1 = ImageRect.X1 / TextureWidth;
TextureRect.Y1 = ImageRect.Y1 / TextureHeight;
TextureRect.X2 = ImageRect.X2 / TextureWidth;
TextureRect.Y2 = ImageRect.Y2 / TextureHeight;
Die 2D-Zielkoordinaten werden um 0.5 reduziert, also X1, Y1, X2 und Y2 -= 0.5f.
Ein Beispiel:
fail3.png
Erste Zeile ist ZielXY (-0.5f fehlt noch), zweite Zeile das Rect vom Bild auf der Textur und die dritte Zeile das TextureRect.

Die Renderfehler treten immer nur dann auf, wenn ZielXY Nachkommastellen hat. (vor dem -0.5f)

Selbst wenn ich aber alle Zielkoordinaten vor der 0.5f-Subtraktion runde, bekomme ich "Lücken":
fail4.png
fail4.png (7.51 KiB) 8441 mal betrachtet
Ich habe bis jetzt zwei Lösungen gefunden, um diese Renderfehler zu vermeiden, bzw. sie "unsichtbar" zu machen.
1. Jedes Bild/Image/Tile hat in der Textur einen ein Pixel breiten transparenten Rahmen.
2. Ich addiere zu den Texturkoordinaten 0.001f hinzu.

Mein Frage dazu:
Ist mein Problem ein allgemeines Problem und man muss eine "Notlösung/Fix" implementieren, oder mache ich vermutlich irgendwas grundlegend falsch?
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] [D3D9] Texturkoordinaten und Skalierung

Beitrag von Krishty »

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] Texturkoordinaten und Skalierung

Beitrag von Goderion »

Vielen Dank für die Antwort!
Krishty hat geschrieben:Hast du das hier gelesen? Directly Mapping Texels to Pixels (Direct3D 9)
Bestimmt irgendwann und das Wissen in dem Artikel ist mir auch bekannt.

Das Problem liegt einfach darin, dass bei bestimmten Zoomleveln Grafiken ZielXY haben, die eigentlich nicht existieren (ungerade).
Will ich einen Pixel an Position 100x100 setzen, so muss ich das als 99.5x99.5 an D3D9 übergeben, so steht es ja auch in dem Microsoft Artikel.
Wenn ich aber jetzt aufgrund von der Skalierung/Zoom einen Pixel bei 99.5x99.5 setzen möchte, muss ich 99x99 an D3D9 übergeben, und D3D9 versucht dass dann irgendwie sinnvoll umzusetzen, da es ja keine 0.5 Pixel gibt.

In meinem Fall übergebe ich eine Position und Texturkoordinaten, die D3D9 nicht richtig/korrekt/1zu1 mappen kann, da die Position nicht wirklich existiert, also muss D3D9 umrechnen. D3D9 rechnet dann aber irgendwie "rückwärts", also verschiebt er die Position, und anscheinend damit auch die Texturkoordinaten, wodurch dann Pixel/Texturteile auf dem Bildschirm landen, die man eigentlich nicht haben will.

Ich habe noch eine dritte Lösung gefunden. Wenn ich die Zielkoordinaten X1 und Y1 (nicht X2 und Y2) abrunde, verschwinden die "Fehler" und es entstehen auch keine Lücken.

Die vermutlich beste Löschung wäre es, in eine extra Textur zu rendern, wo man nicht "krumme" Zielkoordinaten benötigt, um dann die gesamte Textur zu skalieren. Zum Heranzoomen sollte das recht einfach sein, aber beim Herauszoomen wird man schnell in ein Größenlimit der Textur rennen.
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] [D3D9] Texturkoordinaten und Skalierung

Beitrag von Krishty »

Hmm … die dritte Lösung mit dem Runden klingt interessant. Vielleicht findest du dafür irgendeine Garantie in D3D!

Mir ist der Point Filter nur als sehr instabil bekannt, sobald irgendwie transformiert wird – vielleicht durch geringe Präzision auf der GPU, aber das rate ich gerade nur.

Du solltest das auch unbedingt mal auf einer Grafikkarte eines Drittherstellers testen: einer von hier hatte bei seinem Zelda-Klon das Problem, dass die Karten eines Herstellers (S3 oder so?) mit Point Filter grundsätzlich alle Texel verschoben haben und dann alles wie Grütze aussah …
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Schrompf
Moderator
Beiträge: 4838
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: [C++] [D3D9] Texturkoordinaten und Skalierung

Beitrag von Schrompf »

Ich sollte da echt mal ein Paper zu machen.

Das -0.5 nach innen bringt Dir nur was auf der höchsten MipMap-Stufe. Und versaut Dir nebenbei die Texel-Zu-Pixel-Zuordnung. Bei perfektem 1:1-Mapping und Point Sampling fällt das evtl. noch nicht auf, aber spätestens mit leichter Rotation/Skalierung oder linearer Interpolation werden Deine Sprites dann matschig. Sobald Deine Grafik ein bisschen kleiner als Orginalgröße auf dem Rendertarget scheint, reicht -0.5 Texel vom Rand nicht mehr aus.

Hatte das gleiche Problem, hab ich vor Ewigkeiten mal in Splatter implementiert:

Im VertexShader berechnest Du Dir die Ausdehnung auf dem Rendertarget relativ zur Größe der Quellgrafik auf dem Texturatlas. Da kommt dann z.B. raus: das Ding erscheint in der Breite 0.673f mal so groß wie das Orginal, in der Höhe sogar nur 0.375f mal so groß wie das Orginal. Du weißt also, dass der Filter schlimmstenfalls das zweite MipMap-Level rausholt. Denn: der kleinere der beiden Werte ist zwei Zweierpotenzen unter der Eins, also zweites MipMap-Level. Und diese Erkenntnis habe ich das genutzt, um meinem PixelShader ein zusätzliches Attribut namens Clamping-Grenze mitzugeben. Zweites MipMap-Level bedeutet, dass ich ab 0.5f * 2^(Zweites MipMap-Level) Texeln Nähe zum Grafikrand nicht mehr samplen darf, sonst zieht der Texturfilter Farbmüll von außerhalb der Grafik mit rein. Ich habe dem PixelShader also die ursprünglichen Texturkoordinaten (vom Rand der Grafik) als Texturkoordinaten mitgegeben und zusätzlich eine um diese Ausdehnung nach innen versetzte Texturgrenze als zusätzliches Attribut. Im PixelShader clampe ich dann die Texturkoords auf diese Texturgrenze und sample erst dann.

Das Ergebnis: pixelperfektes Mapping bei Sollgröße, korrektes Interpolieren bei Vergrößerung, absolut sauberes Bild ohne Fehlfarben bei Rotation und Verkleinerung. Zur Nachahmung empfohlen.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
Goderion
Beiträge: 82
Registriert: 16.09.2012, 12:02

Re: [C++] [D3D9] Texturkoordinaten und Skalierung

Beitrag von Goderion »

Vielen Dank für die Antworten!
Krishty hat geschrieben:Hmm … die dritte Lösung mit dem Runden klingt interessant. Vielleicht findest du dafür irgendeine Garantie in D3D!
Um Missverständnisse zu vermeiden: Die Rundung nehme ich vor der 0.5f-Subtraktion vor.
Krishty hat geschrieben:Mir ist der Point Filter nur als sehr instabil bekannt, sobald irgendwie transformiert wird – vielleicht durch geringe Präzision auf der GPU, aber das rate ich gerade nur.
Wie meinst Du das, instabil? Der Point Filter (D3DTEXF_POINT) erzeugt halt oft "Pixelgrütze", sobald die Werte nicht optimal sind.
Krishty hat geschrieben:Du solltest das auch unbedingt mal auf einer Grafikkarte eines Drittherstellers testen: einer von hier hatte bei seinem Zelda-Klon das Problem, dass die Karten eines Herstellers (S3 oder so?) mit Point Filter grundsätzlich alle Texel verschoben haben und dann alles wie Grütze aussah …
Guter Hinweis!
Schrompf hat geschrieben:Ich sollte da echt mal ein Paper zu machen.
Her damit! ;-)
Schrompf hat geschrieben:Das -0.5 nach innen bringt Dir nur was auf der höchsten MipMap-Stufe. Und versaut Dir nebenbei die Texel-Zu-Pixel-Zuordnung. Bei perfektem 1:1-Mapping und Point Sampling fällt das evtl. noch nicht auf, aber spätestens mit leichter Rotation/Skalierung oder linearer Interpolation werden Deine Sprites dann matschig. Sobald Deine Grafik ein bisschen kleiner als Orginalgröße auf dem Rendertarget scheint, reicht -0.5 Texel vom Rand nicht mehr aus.

Hatte das gleiche Problem, hab ich vor Ewigkeiten mal in Splatter implementiert:

Im VertexShader berechnest Du Dir die Ausdehnung auf dem Rendertarget relativ zur Größe der Quellgrafik auf dem Texturatlas. Da kommt dann z.B. raus: das Ding erscheint in der Breite 0.673f mal so groß wie das Orginal, in der Höhe sogar nur 0.375f mal so groß wie das Orginal. Du weißt also, dass der Filter schlimmstenfalls das zweite MipMap-Level rausholt. Denn: der kleinere der beiden Werte ist zwei Zweierpotenzen unter der Eins, also zweites MipMap-Level. Und diese Erkenntnis habe ich das genutzt, um meinem PixelShader ein zusätzliches Attribut namens Clamping-Grenze mitzugeben. Zweites MipMap-Level bedeutet, dass ich ab 0.5f * 2^(Zweites MipMap-Level) Texeln Nähe zum Grafikrand nicht mehr samplen darf, sonst zieht der Texturfilter Farbmüll von außerhalb der Grafik mit rein. Ich habe dem PixelShader also die ursprünglichen Texturkoordinaten (vom Rand der Grafik) als Texturkoordinaten mitgegeben und zusätzlich eine um diese Ausdehnung nach innen versetzte Texturgrenze als zusätzliches Attribut. Im PixelShader clampe ich dann die Texturkoords auf diese Texturgrenze und sample erst dann.

Das Ergebnis: pixelperfektes Mapping bei Sollgröße, korrektes Interpolieren bei Vergrößerung, absolut sauberes Bild ohne Fehlfarben bei Rotation und Verkleinerung. Zur Nachahmung empfohlen.
Das klingt alles sehr interessant, wo bleibt das Paper?! ;-)
Wenn ich das richtig verstanden habe, interpolierst Du im Pixelshader selber?
Ich benutze aktuell (noch) kein Mip Mapping und auch (noch) keine Pixelshader.

Ich habe nochmal ein wenig rum experimentiert. Sobald ich einen Texturfilter einstelle, der nicht D3DTEXF_NONE oder D3DTEXF_POINT ist und irgendwie skaliert werden muss, kommt folgendes raus:
fail5.png
Wo kommt da dieses Pink/Lila her? Alle Bilder/Tiles haben innerhalb der Textur transparente Ränder.

Was mich etwas wundert, ist, dass ich keinen Unterschied zwischen den Texturfiltern sehe. Es gibt nur zwei unterschiedliche Ergebnisse.

Ergebnis 1: D3DTEXF_NONE und D3DTEXF_POINT = Bei passenden Werten, wie z.B. Skalierung von 1, 2, 3, 4, usw. perfekt. "Krumme" Skalierung, wie z.B. 1,5, ergibt "schlechte Interpolation". Bewegt man die Karte, "zuckelt" das Bild.

Ergebnis 2: D3DTEXF_LINEAR, D3DTEXF_ANISOTROPIC, D3DTEXF_PYRAMIDALQUAD, D3DTEXF_GAUSSIANQUAD, D3DTEXF_CONVOLUTIONMONO = Bei einer Skalierung von 1 identisch mit D3DTEXF_NONE und D3DTEXF_POINT. Entspricht die Skalierung nicht 1, sieht es wie im obigen Bild aus, kompletter Käse.

Wenn ich jetzt alle Informationen, die ich durch Tests und eure Aussagen zusammentrage, sieht es wohl so aus, dass ich nicht um Pixelshader drumrum komme, wenn ich "vernünfiges" Zoomen unterstützen möchte. Warum sind eigentlich alle D3D9 Texturfilter so schlecht im Interpolieren?! Selbst wenn ich alles erst "sauber" in eine Textur rendere und das dann z.B. per D3DTEXF_LINEAR skaliere, sieht das inakzeptabel aus.

Ich geh mal auf die Suche nach guten D3D9 Pixelshader Tutorials. ^^
Benutzeravatar
Schrompf
Moderator
Beiträge: 4838
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: [C++] [D3D9] Texturkoordinaten und Skalierung

Beitrag von Schrompf »

Goderion hat geschrieben: Wenn ich das richtig verstanden habe, interpolierst Du im Pixelshader selber?
Nein, anders herum: ich begrenze (== clamp()) im PixelShader die Texturkoordinaten auf ein Rechteck, dass in jede Richtung ein Stück vom ursprünglichen Rechteck der Quelltextur "nach innen" gerückt wurde. Mist, jetzt hab ich mich generdsnipet. Ich muss wieder los. Später mehr dazu.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
Schrompf
Moderator
Beiträge: 4838
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: [C++] [D3D9] Texturkoordinaten und Skalierung

Beitrag von Schrompf »

Ok, doch nicht. Es ist absurd viel Arbeit, die ganzen Bilder zu pinseln, um zu zeigen, wo genau die GPU samplet und wie sie aus Rendertarget-Koordinaten die Texturkoordinaten berechnet. Guck Dir mal Pixelshader an, dann reden wir weiter. Denn ich vermute, sobald Du ein sicheres Bild von der Arbeitsweise der GPU hast, reicht Dir der obige Fließtext aus.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
Goderion
Beiträge: 82
Registriert: 16.09.2012, 12:02

Re: [C++] [D3D9] Texturkoordinaten und Skalierung

Beitrag von Goderion »

Vielen Dank für die Antworten!

Ich habe mich heute ein wenig mit Vertex -und Pixelshadern beschäftigt und sie auch gleich in die Engine implementiert. Heute mittag gleich direkt mal eine Stunde sinnlos verbraten, weil ich D3DXAssembleShaderFromFile statt D3DXCompileShaderFromFile benutzt habe. Informationen zu den beiden Funktionen sind mal wieder dürftig oder ich bin blind. Generell fand ich es recht schwer "gute" Tutorials zu dem Thema zu finden, aber ich glaube langsam, heute ist einfach nicht mein Tag.

Jedenfalls funktioniert es jetzt und ich kann in realtime die Shader anpassen. In jedem Frame wird einfach D3DXCompileShaderFromFile wieder aufgerufen, und kommen Fehler, wird kein Shader genutzt, bis wieder was funktionierendes in den Dateien steht. Zum Testen reicht es. Für den Release möchte ich die bereits compilierten Shader nutzen, damit ich kein D3DX nutzen muss.
Schrompf hat geschrieben:Im VertexShader berechnest Du Dir die Ausdehnung auf dem Rendertarget relativ zur Größe der Quellgrafik auf dem Texturatlas. Da kommt dann z.B. raus: das Ding erscheint in der
Breite 0.673f mal so groß wie das Orginal, in der Höhe sogar nur 0.375f mal so groß wie das Orginal.
Wie funktioniert das? Ich bräuchte doch in einem Aufruf vom Vertexshader alle Quellkoordinaten, die Größe der Quelltextur, und die Zielkoordinaten, damit ich die Skalierung im Vertexshader ermitteln kann, um diese dann an den Pixelshader weiterzureichen.

Ich sehe schon, da muss ich noch viel lesen, bis ich das umsetzen kann.... Google hasst mich heute, glaub ich, ich finde nur so unglaublich schlechten Rotz zu dem Thema, grml...
Benutzeravatar
Schrompf
Moderator
Beiträge: 4838
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: [C++] [D3D9] Texturkoordinaten und Skalierung

Beitrag von Schrompf »

Goderion hat geschrieben:Jedenfalls funktioniert es jetzt und ich kann in realtime die Shader anpassen. In jedem Frame wird einfach D3DXCompileShaderFromFile wieder aufgerufen, und kommen Fehler, wird kein Shader genutzt, bis wieder was funktionierendes in den Dateien steht. Zum Testen reicht es. Für den Release möchte ich die bereits compilierten Shader nutzen, damit ich kein D3DX nutzen muss.
Sehr gut. Live Patching ist ein großer Gewinn, wenn man an sowas wie Shadern schraubt.
Wie funktioniert das? Ich bräuchte doch in einem Aufruf vom Vertexshader alle Quellkoordinaten, die Größe der Quelltextur, und die Zielkoordinaten, damit ich die Skalierung im Vertexshader ermitteln kann, um diese dann an den Pixelshader weiterzureichen.
Das stimmt, und die Grafikkarte gibt Dir das nicht. Das habe ich vergessen zu erwähnen, sorry. Erklärung:

Du schreibst aktuell wahrscheinlich 2x3 Vertizes pro Sprite in einen VertexBuffer und renderst den dann. Das ist für Dein Spiel wahrscheinlich auch völlig ausreichend. Andere Szenarien wollen dann aber irgendwann mal rotieren, skalieren, verfärben, ein- und ausblenden und wasweißichnoch. Dann lohnt es sich, die finalen Vertizes erst auf der Grafikkarte auszurechnen, und nur ein Platzhalter-Set an Vertizes hochzuladen, in denen alle Parameter des Sprites drinstehen. Dann hast Du in den Shadern sowieso alle Daten da, und kannst daraus auch die Skalierung im VertexShader ermitteln.

Ich löse das aktuell mittels Instancing. Der primäre VertexBuffer und IndexBuffer enthält genau zwei Dreiecke mit (0,0), (1,0), (0,1) und (1,1). Kannst Du auch aus der VertexID im VertexShader live berechnen. Pro Instanz gebe ich dann einen Satz Parameter mit:

Code: Alles auswählen

float3 instZielPos; // Zielposition aufm RenderTarget in Pixeln
float2 instGroesse; // Zielgröße aufm RenderTarget in Pixeln
float4 instGrafik;   // Quell-Grafikausschnitt - .xy links oben, .zw Größe in Pixeln
float4 instFarbe;    // Farbmodulation
float2 instParam;   // Parameter - .x Rotation in Radian, .y Griffpunkt 
Das ist natürlich nur ein Vorschlag. Du schleifst da durch, was auch immer Du an Parametern brauchst, und verwurstest die erst im VertexShader zu tatsächlichen Bildschirmpositionen. Und zu beliebigen Parametern, die Du Dir zum PixelShader durchreichst. Lass Dich nicht von den Oldschool-Bezeichnern wie NORMAL oder COLOR0 oder sowas beeindrucken. Das sind nur Namen für Menschen. Du kannst einfach alles als TEXTURE0 bis TEXTURE7 bezeichnen. Nur ein einziges POSITION0 muss Dein VertexShader haben, weil das einer der letzten noch nicht frei programmierbaren Teile der GPU braucht, nämlich der Rasterizer. Alles andere sind beliebig nutzbare Vektoren, und Du solltest sie so auch nutzen.

Wenn Du Instancing nicht haben willst, kannst Du auch die Sprite-Daten auch in jeden Vertex kopieren. Du hast dann halt pro Sprite vier dicke Vertizes (0, 0, <Instanzdaten>), (1, 0, <Instanzdaten), ... - aber egal, funktioniert auch, verbraucht nur minimal mehr Speicher. Später kannst Du dann die VertexPosition aus der VertexID berechnen, die Dreiecke erst im GeometryShader erzeugen oder wasweißich. Da kann man kreativ werden. Ist aber alles erst später wichtig, zuerst muss man mal artefakt-frei rotieren und skalieren können.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] [D3D9] Texturkoordinaten und Skalierung

Beitrag von Krishty »

Goderion hat geschrieben:Ich habe mich heute ein wenig mit Vertex -und Pixelshadern beschäftigt und sie auch gleich in die Engine implementiert. Heute mittag gleich direkt mal eine Stunde sinnlos verbraten, weil ich D3DXAssembleShaderFromFile statt D3DXCompileShaderFromFile benutzt habe. Informationen zu den beiden Funktionen sind mal wieder dürftig oder ich bin blind. Generell fand ich es recht schwer "gute" Tutorials zu dem Thema zu finden, aber ich glaube langsam, heute ist einfach nicht mein Tag.

Jedenfalls funktioniert es jetzt und ich kann in realtime die Shader anpassen. In jedem Frame wird einfach D3DXCompileShaderFromFile wieder aufgerufen, und kommen Fehler, wird kein Shader genutzt, bis wieder was funktionierendes in den Dateien steht. Zum Testen reicht es. Für den Release möchte ich die bereits compilierten Shader nutzen, damit ich kein D3DX nutzen muss.
Off-Topic: Kompilierte Shader sind auch schneller zu entwickeln. Speicher sie mit der Endung .hlsl und füge sie deinem Visual C++-Projekt hinzu. Dann werden sie beim Kompilieren des Projekts von Visual Studio mitkompiliert und liegen als fertige .dxbc-Dateien vor. Falls es dir zu aufwändig ist, die Dateien zu laden, kannst du in den Projekteinstellungen Header mit dem fertigen Bytecode als Array erzeugen lassen, die du im Projekt #includeieren und direkt an CreateVertexShader() & Co. übergeben kannst. Mit D3DX musst du überhaupt nichts zu tun haben (deshalb ist auch die Dokumentation so schwach).
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Zudomon
Establishment
Beiträge: 2253
Registriert: 25.03.2009, 07:20
Kontaktdaten:

Re: [C++] [D3D9] Texturkoordinaten und Skalierung

Beitrag von Zudomon »

Hey, ich hab jetzt ab der Hälfte nur noch grob überflogen.
Ich möchte auch nur etwas einwerfen, wo ich glaube, dass das noch nicht erwähnt wurde.
Bei SQ mache ich viel über Daten auf Texturen und das auch per Atlas. Und da MUSS ich die Daten immer an der exakten Stelle samplen, da es sonst Fehler gibt.
Meine Kenntnisse darüber sind neben dem Lesen der bereits hier geposteten Quellen viel try&error.

Nun aber der kleine Einwand:
Zunächst solltest du probieren Dreiecke an exakten Pixelpositionen mit exakten Werten zu rendern, z.B. Texturkoordinaten als Farbwert von 0 - 1.
Erst dann lohnt es sich, weiter zu gehen:
Und zwar ist bei den Texturkoordinaten noch zu beachten, dass du nicht einfach mit der Texturbreite multiplizierst. Wenn du z.B. eine 1024 Textur hast, dann sind die adressierbaren Pixel von 0 - 1023. Benutzt du einen Texturatlas und unterteilst deine 1024 Textur in 8 Blöcke pro Zeile von je 128 Pixel, dann muss die Texturkoordinate mit 127 multipliziert werden. Also das ganze muss natürlich auch wieder zurück skaliert werden, also so:
Texturkoordinate = Texturkoordinate * (Grafikbreite - 1) / Grafikbreite

Soweit ich weiß fällt übrigens diese 0.5 Texelverschiebung bei Pointsampling weg. Also da muss man auch genau drauf achten, ob man Point Filtert oder Linear. Bei mir verwende ich Pointsampling, um auch nur die kleinsten Rundungsfehler nicht als lineare Interpolation der Daten zu verschandeln.
MipMaps sind dann nochmal eine Stufe härter. Denn dieses multiplizieren mit der ((Grafikbreite - 1) / Grafikbreite) der Texturkoordinaten pflanzt sich natürlich auf den Mipmapstufen fort. Da muss man dann schauen, ob es zu sichtbaren Fehlern kommt.
Benutzeravatar
Goderion
Beiträge: 82
Registriert: 16.09.2012, 12:02

Re: [C++] [D3D9] Texturkoordinaten und Skalierung

Beitrag von Goderion »

Vielen Dank für die Antworten!
Schrompf hat geschrieben:Du schreibst aktuell wahrscheinlich 2x3 Vertizes pro Sprite in einen VertexBuffer und renderst den dann. Das ist für Dein Spiel wahrscheinlich auch völlig ausreichend. Andere Szenarien wollen dann aber irgendwann mal rotieren, skalieren, verfärben, ein- und ausblenden und wasweißichnoch. Dann lohnt es sich, die finalen Vertizes erst auf der Grafikkarte auszurechnen, und nur ein Platzhalter-Set an Vertizes hochzuladen, in denen alle Parameter des Sprites drinstehen. Dann hast Du in den Shadern sowieso alle Daten da, und kannst daraus auch die Skalierung im VertexShader ermitteln.
Ich benutze aktuell folgendes:

Code: Alles auswählen

const UInt32 D3DFVF_CUSTOM_2D = Direct3D9::D3DFVF_XYZRHW | Direct3D9::D3DFVF_DIFFUSE | Direct3D9::D3DFVF_TEX1;

struct StructCustomVertex2D
{
	Float32 x; // Bildschirm-X
	Float32 y; // Bildschirm-Y
	Float32 z; // immer 0
	Float32 rhw; // immer 1
	UInt32 color; // Farbe und Transparenz
	Float32 u; // Texture-X (0 bis 1)
	Float32 v; // Texture-Y (0 bis 1)
};
Rotation und Skalierung berechne ich selber.
Schrompf hat geschrieben:Ich löse das aktuell mittels Instancing. Der primäre VertexBuffer und IndexBuffer enthält genau zwei Dreiecke mit (0,0), (1,0), (0,1) und (1,1). Kannst Du auch aus der VertexID im VertexShader live berechnen. Pro Instanz gebe ich dann einen Satz Parameter mit:
Instancing? Sowas: http://www.rastertek.com/dx11tut37.html
Da wird ein "shader-constant buffer" erzeugt (ID3D11Device::CreateBuffer).
Unter D3D9 müsste ich dazu dann SetVertexShaderConstantF usw. nutzen.
Schrompf hat geschrieben:Das ist natürlich nur ein Vorschlag. Du schleifst da durch, was auch immer Du an Parametern brauchst, und verwurstest die erst im VertexShader zu tatsächlichen Bildschirmpositionen. Und zu beliebigen Parametern, die Du Dir zum PixelShader durchreichst. Lass Dich nicht von den Oldschool-Bezeichnern wie NORMAL oder COLOR0 oder sowas beeindrucken. Das sind nur Namen für Menschen. Du kannst einfach alles als TEXTURE0 bis TEXTURE7 bezeichnen. Nur ein einziges POSITION0 muss Dein VertexShader haben, weil das einer der letzten noch nicht frei programmierbaren Teile der GPU braucht, nämlich der Rasterizer. Alles andere sind beliebig nutzbare Vektoren, und Du solltest sie so auch nutzen.
Ich könnte also z.B. folgendes machen:

Code: Alles auswählen

const UInt32 D3DFVF_CUSTOM_2D = Direct3D9::D3DFVF_XYZ | Direct3D9::D3DFVF_DIFFUSE | Direct3D9::D3DFVF_TEX1 | Direct3D9::D3DFVF_TEX2 | Direct3D9::D3DFVF_TEX3;

struct StructCustomVertex2D
{
	Float32 x;
	Float32 y;
	Float32 z;
	UInt32 color; 
	Float32 u;
	Float32 v;
	Float32 u2;
	Float32 v2;
	Float32 u3;
	Float32 v3;
};
u2, v2, u3 und v3 kann ich dann mit was auch immer vollklatschen... unendliche Möglichkeiten...
Schrompf hat geschrieben:Wenn Du Instancing nicht haben willst, kannst Du auch die Sprite-Daten auch in jeden Vertex kopieren. Du hast dann halt pro Sprite vier dicke Vertizes (0, 0, <Instanzdaten>), (1, 0, <Instanzdaten), ... - aber egal, funktioniert auch, verbraucht nur minimal mehr Speicher. Später kannst Du dann die VertexPosition aus der VertexID berechnen, die Dreiecke erst im GeometryShader erzeugen oder wasweißich. Da kann man kreativ werden. Ist aber alles erst später wichtig, zuerst muss man mal artefakt-frei rotieren und skalieren können.
Das "Instancing" macht man doch nur, um bestimmte Daten nicht jedem Vertex mitgeben zu müssen? Ich arbeite gerade ohne IndexBuffer, muss also pro Sprite 6 Vertices an D3D9 übergeben (mit IndexBuffer wären es nur 4). Ich übergebe also Informationen wie z.B. Rotation, Texturgesamtgröße, usw. entweder pro Sprite einmalig mit "Instancing" oder 6 mal in den Vertexdaten. Der Speicherverbrauch sollte egal sein, am Ende wird wohl die Performance entscheiden, was von beiden Varianten sinnvoller ist.
Krishty hat geschrieben:Off-Topic: Kompilierte Shader sind auch schneller zu entwickeln. Speicher sie mit der Endung .hlsl und füge sie deinem Visual C++-Projekt hinzu. Dann werden sie beim Kompilieren des Projekts von Visual Studio mitkompiliert und liegen als fertige .dxbc-Dateien vor. Falls es dir zu aufwändig ist, die Dateien zu laden, kannst du in den Projekteinstellungen Header mit dem fertigen Bytecode als Array erzeugen lassen, die du im Projekt #includeieren und direkt an CreateVertexShader() & Co. übergeben kannst. Mit D3DX musst du überhaupt nichts zu tun haben (deshalb ist auch die Dokumentation so schwach).
Vielen Dank für den Hinweis!
Zudomon hat geschrieben:Und zwar ist bei den Texturkoordinaten noch zu beachten, dass du nicht einfach mit der Texturbreite multiplizierst. Wenn du z.B. eine 1024 Textur hast, dann sind die adressierbaren Pixel von 0 - 1023. Benutzt du einen Texturatlas und unterteilst deine 1024 Textur in 8 Blöcke pro Zeile von je 128 Pixel, dann muss die Texturkoordinate mit 127 multipliziert werden. Also das ganze muss natürlich auch wieder zurück skaliert werden, also so:
Texturkoordinate = Texturkoordinate * (Grafikbreite - 1) / Grafikbreite
Das verstehe ich nicht ganz, vor allem die letzte Formel.
Bei mir liegen mehrere Sprites mit unterschiedlicher Größe auf einer Textur:
0206.png
0206.png (10.59 KiB) 8116 mal betrachtet
Die Texturkoordinaten (0 bis 1) ermittel ich einfach durch das Teilen der Spritekoordinaten durch die Texturbreite -und Höhe.
Beispiel:

Code: Alles auswählen

Texture.SizeX = 256;
Texture.SizeY = 128;
Sprite.X1 = 123;
Sprite.Y1 = 33;
Sprite.X2 = 146;
Sprite.Y2 = 74;
TexCoord.X1 = 123 / 256 = 0.4805;
TexCoord.Y1 = 33 / 128 = 0.2578;
TexCoord.X2 = 146 / 256 = 0.5703;
TexCoord.Y2 = 74 / 128 = 0.5781;
Zudomon hat geschrieben:Soweit ich weiß fällt übrigens diese 0.5 Texelverschiebung bei Pointsampling weg. Also da muss man auch genau drauf achten, ob man Point Filtert oder Linear. Bei mir verwende ich Pointsampling, um auch nur die kleinsten Rundungsfehler nicht als lineare Interpolation der Daten zu verschandeln.
Mmmmh... Ich benutze auch den Pointfilter (D3DTEXF_POINT), wenn ich aber bei den Ziel/Screenkoordinaten nicht 0.5f abziehe, sieht das bei mir so aus:
nosubz5.png
Zudomon hat geschrieben:MipMaps sind dann nochmal eine Stufe härter. Denn dieses multiplizieren mit der ((Grafikbreite - 1) / Grafikbreite) der Texturkoordinaten pflanzt sich natürlich auf den Mipmapstufen fort. Da muss man dann schauen, ob es zu sichtbaren Fehlern kommt.
Mip Mapping schon ausprobiert, da zerhaut es alles, sobald er nicht mehr die "erste/0" Textur nimmt. Das werde ich angehen, wenn ich die Shader (Vertex/Pixel) verstanden und umgesetzt habe.

So, vernünftige Infos zu HLSL sind wohl wirklich selten im Netz. Die Doku finde ich grausam schlecht.
Die Koordinaten, die ich im Vertex Shader zurückgeben muss, erscheinen auf den ersten Blick auch recht seltsam.
Die Bildschirm-Mitte scheint 0.0, 0.0 zu sein,
oben links ist -1.0, 1.0
und unten rechts ist 1.0, -1.0 ...

Es hat euch etwas gedauert, bis ich auf die Idee gekommen bin, die TEXCOORD0 im Vertex Shader durchzureichen, damit ich was von den Texturen sehe. Bis jetzt ist mir noch nichts begegnet, was sich so mühsam recherchieren lässt, wie Shader... narf?!

Ich habe jetzt erstmal zum Testen und zum verstehen einen Vertex Shader gebastelt, mit dem ich kein Direct3D9::D3DFVF_XYZRHW mehr brauche:

Code: Alles auswählen

struct VS_INPUT
{
	float4 Position : POSITION;
	float4 Color : COLOR;
	float4 TexCoord0 : TEXCOORD0;
};

struct VS_OUTPUT
{
	float4 Position : POSITION;
	float4 Color : COLOR;
	float4 TexCoord0 : TEXCOORD0;
};

VS_OUTPUT VertexShader(VS_INPUT Parameter)
{
	VS_OUTPUT Result;

	Result.Color = Parameter.Color;

	Result.Position[0] = Parameter.Position[0] / 640;
	Result.Position[1] = (720 - Parameter.Position[1]) / 360;
	Result.Position[2] = 0;
	Result.Position[3] = 1;

	Result.Position[0] -= 1;
	Result.Position[1] -= 1;

	Result.TexCoord0 = Parameter.TexCoord0;

	return Result;
}
... und ja, sobald die Auflösung nicht 1280x720 entspricht, funktioniert der nicht mehr. Hier muss ich irgendwie die Größe vom Rendertarget verfügbar machen.

Und noch ein Meisterwerk der Shaderkunst! ;-)

Code: Alles auswählen

struct PS_INPUT
{
	float4 TexCoord0 : TEXCOORD0;
	float4 Color : COLOR;

};

struct PS_OUTPUT
{
	float4 Color : COLOR;
};


sampler2D samp;


PS_OUTPUT PixelShader(PS_INPUT Parameter)
{
	PS_OUTPUT Result;

	Result.Color = tex2D(samp, Parameter.TexCoord0) * Parameter.Color;

	return Result;
}
Ich benutze zum Zeichnen/Rendern von Flächen (FillRect) und Rahmen (FrameRect) IDirect3DDevice9::DrawPrimitive. Aktuell wird dazu einfach nur die Textur entfernt, SetTexture(Null). Mein Pixel Shader macht da jetzt Unsinn und versucht immer eine Textur zu samplen, wo dann anscheinend, wenn keine Textur gesetzt ist, immer 0 bei rauskommt. Die beste Lösung ist vermutlich, für diese Fälle einen gesonderten, bzw. gar keinen Pixel Shader zu nutzen. Gibt es aber eine Möglichkeit im Pixel Shader festzustellen, ob überhaupt eine Textur gesetzt ist?
Benutzeravatar
Zudomon
Establishment
Beiträge: 2253
Registriert: 25.03.2009, 07:20
Kontaktdaten:

Re: [C++] [D3D9] Texturkoordinaten und Skalierung

Beitrag von Zudomon »

Meine Formeln bezogen sich darauf, wenn du eine Textur in gleichgroße Quads unterteilst. Wenn du die Sprites aber direkt per Koordinaten vorliegen hast, dann entfällt mein Einwand, glaub ich...
Benutzeravatar
Goderion
Beiträge: 82
Registriert: 16.09.2012, 12:02

Re: [C++] [D3D9] Texturkoordinaten und Skalierung

Beitrag von Goderion »

Yo!

Mein altes Rendersystem war darauf ausgelegt ausschließlich mit dem Vertexformat "XYZRHW | DIFFUSE | TEX1" zu arbeiten und konnte auch keine Shader verwalten. Aus diesem Grund habe ich das Rendersystem komplett überarbeitet, um problemlos andere Vertexformate und Shader zu nutzen. Mit dem neuen System kann ich während das Programm läuft zwischen verschiedenen Rendermodi hin- und herschalten.

Beim Programmieren und Testen vom neuen Rendersystem sind mir mehrere Sachen aufgefallen:

1. Wenn ich im Test (ca. 60.000 Sprites, ca. 2.000 SetTexture) jedesmal beim SetTexture auch zusätzlich SetVertexShaderConstantF aufrufe, um z.B. die Texturgröße zu setzen, fallen die FPS von 330 auf 90. Wieso auch immer scheint der Aufruf von SetVertexShaderConstantF extrem langsam zu sein und ich werde alle nötigen Daten, die oft wechseln, wie z.B. hier bei jeder Textur, mit in die Vertexdaten packen müssen. SetVertexShaderConstantF rufe ich auch beim Wechsel vom RenderTarget (SetRenderTarget) auf, allerdings wechsel ich das RenderTarget pro Frame nur drei mal und es hat daher keine erwähnenswerte Auswirkung auf die FPS.

2. Ich habe zwei Rendermodi, die eigentlich das gleiche machen, aber unterschiedliche FPS liefern. Den einen nenne ich "Image", den anderen "ImagePro". Der Rendermodus "Image" nutzt "XYZRHW | DIFFUSE | TEX1" und keine Shader. Der Rendermodus "ImagePro" nutzt "XYZ | DIFFUSE | TEX1" und einen VertexShader.

Code: Alles auswählen

float4 rti : register(c0);

struct VS_INPUT
{
	float4 Position : POSITION;
	float4 Color : COLOR;
	float2 TexCoord0 : TEXCOORD0;
};

struct VS_OUTPUT
{
	float4 Position : POSITION;
	float4 Color : COLOR;
	float2 TexCoord0 : TEXCOORD0;
};

VS_OUTPUT VertexShader(VS_INPUT Parameter)
{
	VS_OUTPUT Result;

	Result.Color = Parameter.Color;

	Result.Position[0] = Parameter.Position[0] / rti[2];
	Result.Position[1] = (rti[1] - Parameter.Position[1]) / rti[3];
	Result.Position[2] = 0;
	Result.Position[3] = 1;

	Result.Position[0] -= 1;
	Result.Position[1] -= 1;

	Result.TexCoord0 = Parameter.TexCoord0;

	return Result;
}
Im Rendermodus "Image" überlasse ich Direct3D9 durch das Vertexformat XYZRHW die Transformation der Vertices. Im Rendermodus "ImagePro" transformiere ich die Vertices selber durch den VertexShader. Das Ergebnis beider Rendermodi ist identisch, getestet auch in verschiedenen Zoomstufen. Mit "Image" erreiche ich ca. 330 FPS, mit "ImagePro" ca. 390 FPS. Warum?!

3. Ich habe auch nochmal Vertex -und Indexbuffer getestet. Es scheint so zu sein, das Vertex -und Indexbuffer nur sinnvoll sind, wenn man sie inhaltlich nicht in jedem Frame ändern muss, was bei mir aber leider der Fall ist. DrawPrimitiveUP ist bei mir deutlich schneller, als DrawPrimitive mit Vertexbuffer. Ich habe auch schon überlegt, ob ich es irgendwie erreichen kann, dass Daten in einem Vertexbuffer länger "gültig" bleiben. Ich muss mich im Level ja nur einen Pixel bewegen, und schon sind alle Vertexdaten "veraltet". Dazu kommen noch Animationen, die dafür sorgen, dass sich die Texturkoordinaten alle paar Frames ändern.

4. Mit dem VertexFormat "XYZ | DIFFUSE | TEX1" erreiche ich ca. 390 FPS. Mit dem VertexFormat "XYZ | DIFFUSE | TEX1 | TEX2 | TEX3 | TEX4" fallen die FPS auf 300. Die VertexDaten für TEX2, TEX3 und TEX4 werden nicht gesetzt oder im VertexShader verarbeitet. Schon etwas erschreckend, dass 24 Byte mehr pro Vertex so auf die Performance gehen.

5. Laut Microsoft ist TEXCOORD[n] ein float4 (https://msdn.microsoft.com/en-us/librar ... 5).aspx#VS). Nutze ich das so, kommt bei mir bei der Verwendung mehrerer TEXCOORD[n] im VertexShader Blödsinn raus. In vielen Beispielen zu VertexShadern, die ich durch Googlen gefunden habe, wird TEXCOORD[n] aber als float2 genutzt. Stelle ich in meinen TestVertexShadern das auch auf float2, funktioniert alles wie erwartet. In der Struktur vom Vertex werden pro TEXCOORD[n] ja auch nur zwei float übergeben. Was ist hier los? Fehler von Microsoft oder habe ich was überlesen?
Schrompf hat geschrieben:Im VertexShader berechnest Du Dir die Ausdehnung auf dem Rendertarget relativ zur Größe der Quellgrafik auf dem Texturatlas. Da kommt dann z.B. raus: das Ding erscheint in der Breite 0.673f mal so groß wie das Orginal, in der Höhe sogar nur 0.375f mal so groß wie das Orginal. Du weißt also, dass der Filter schlimmstenfalls das zweite MipMap-Level rausholt. Denn: der kleinere der beiden Werte ist zwei Zweierpotenzen unter der Eins, also zweites MipMap-Level. Und diese Erkenntnis habe ich das genutzt, um meinem PixelShader ein zusätzliches Attribut namens Clamping-Grenze mitzugeben. Zweites MipMap-Level bedeutet, dass ich ab 0.5f * 2^(Zweites MipMap-Level) Texeln Nähe zum Grafikrand nicht mehr samplen darf, sonst zieht der Texturfilter Farbmüll von außerhalb der Grafik mit rein. Ich habe dem PixelShader also die ursprünglichen Texturkoordinaten (vom Rand der Grafik) als Texturkoordinaten mitgegeben und zusätzlich eine um diese Ausdehnung nach innen versetzte Texturgrenze als zusätzliches Attribut. Im PixelShader clampe ich dann die Texturkoords auf diese Texturgrenze und sample erst dann.

Das Ergebnis: pixelperfektes Mapping bei Sollgröße, korrektes Interpolieren bei Vergrößerung, absolut sauberes Bild ohne Fehlfarben bei Rotation und Verkleinerung. Zur Nachahmung empfohlen.
Hast Du dazu noch die Vertex/PixelShader und könntest mir bitte zeigen, wie Du das gelöst hast?
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: [C++] [D3D9] Texturkoordinaten und Skalierung

Beitrag von dot »

Statt FVF Codes, die selbst in D3D9 damals schon veraltet waren, schau dir mal VertexDeclarations an: https://msdn.microsoft.com/en-us/librar ... 06335.aspx ;)
Goderion hat geschrieben:5. Laut Microsoft ist TEXCOORD[n] ein float4 (https://msdn.microsoft.com/en-us/librar ... 5).aspx#VS).
Das ist eine Shader Semantic, nicht zu verwechseln mit dem input vertex format. Wenn du FVF verwendest und nicht explizit per D3DFVF_TEXCOORDSIZEN die Größe der Texcoords änderst, dann hast du 2D input coordinates.
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] [D3D9] Texturkoordinaten und Skalierung

Beitrag von Krishty »

Goderion hat geschrieben:1. Wenn ich im Test (ca. 60.000 Sprites, ca. 2.000 SetTexture) jedesmal beim SetTexture auch zusätzlich SetVertexShaderConstantF aufrufe, um z.B. die Texturgröße zu setzen, fallen die FPS von 330 auf 90. Wieso auch immer scheint der Aufruf von SetVertexShaderConstantF extrem langsam zu sein und ich werde alle nötigen Daten, die oft wechseln, wie z.B. hier bei jeder Textur, mit in die Vertexdaten packen müssen.
Gut möglich; siehe die Tabelle hier ganz unten: Accurately Profiling Direct3D API Calls (Direct3D 9) Ab D3D 10 müsste das durch Constant Buffers viel schneller sein.
Goderion hat geschrieben:2. Ich habe zwei Rendermodi, die eigentlich das gleiche machen, aber unterschiedliche FPS liefern. […] Mit "Image" erreiche ich ca. 330 FPS, mit "ImagePro" ca. 390 FPS. Warum?!
Irgendein Slow Path im Treiber; die wenigsten Spiele nutzen ausgiebig XYZRHW.
Goderion hat geschrieben:3. Ich habe auch nochmal Vertex -und Indexbuffer getestet. Es scheint so zu sein, das Vertex -und Indexbuffer nur sinnvoll sind, wenn man sie inhaltlich nicht in jedem Frame ändern muss, was bei mir aber leider der Fall ist.
Genau; die sind da nutzlos. D3D 10 und höher bieten auch keinen Ersatz für DrawPrimitiveUP(); ab D3D 12 kannst du immerhin synchron in Vertex Buffers schreiben.
Goderion hat geschrieben:4. Mit dem VertexFormat "XYZ | DIFFUSE | TEX1" erreiche ich ca. 390 FPS. Mit dem VertexFormat "XYZ | DIFFUSE | TEX1 | TEX2 | TEX3 | TEX4" fallen die FPS auf 300. Die VertexDaten für TEX2, TEX3 und TEX4 werden nicht gesetzt oder im VertexShader verarbeitet. Schon etwas erschreckend, dass 24 Byte mehr pro Vertex so auf die Performance gehen.
Äh … von Caches und Speicherbandbreite hast du aber schon gehört, oder? Wenn du statt 24 B jetzt 48 überträgst, ist die Übertragung halt nur noch halb so schnell … und dass die ein Flaschenhals ist, weißt du, seit Vertex Buffers nichts gebracht haben.
Goderion hat geschrieben:5. Laut Microsoft ist TEXCOORD[n] ein float4 (https://msdn.microsoft.com/en-us/librar ... 5).aspx#VS). Nutze ich das so, kommt bei mir bei der Verwendung mehrerer TEXCOORD[n] im VertexShader Blödsinn raus. In vielen Beispielen zu VertexShadern, die ich durch Googlen gefunden habe, wird TEXCOORD[n] aber als float2 genutzt. Stelle ich in meinen TestVertexShadern das auch auf float2, funktioniert alles wie erwartet. In der Struktur vom Vertex werden pro TEXCOORD[n] ja auch nur zwei float übergeben. Was ist hier los? Fehler von Microsoft oder habe ich was überlesen?
MSDN, Texture Coordinate Formats (Direct3D 9) hat geschrieben:The D3DFVF_TEXTUREFORMAT1 through D3DFVF_TEXTUREFORMAT4 flags describe the number of elements in a texture coordinate in a set, but these flags aren't used by themselves. Rather, the D3DFVF_TEXCOORDSIZEN set of macros use these flags to create bit patterns that describe the number of elements used by a particular set of texture coordinates in the vertex format.
Alternativ sind Vertex Declarations deutlich angenehmer zu benutzen.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: [C++] [D3D9] Texturkoordinaten und Skalierung

Beitrag von dot »

Krishty hat geschrieben:
Goderion hat geschrieben:2. Ich habe zwei Rendermodi, die eigentlich das gleiche machen, aber unterschiedliche FPS liefern. […] Mit "Image" erreiche ich ca. 330 FPS, mit "ImagePro" ca. 390 FPS. Warum?!
Irgendein Slow Path im Treiber; die wenigsten Spiele nutzen ausgiebig XYZRHW.
Abgesehen davon, bedeutet XYZRHW doch, dass du deine Vertices auf der CPU transformieren und dann ständig neu auf die GPU laden musst!?
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] [D3D9] Texturkoordinaten und Skalierung

Beitrag von Krishty »

Wenn man Spiele emuliert, die vor der T&L-Ära entwickelt wurden, ist das aber nötig. Ich mache das bei meinem Flugsimulator genauso.
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] Texturkoordinaten und Skalierung

Beitrag von Goderion »

Vielen Dank für die Antworten!
dot hat geschrieben:Statt FVF Codes, die selbst in D3D9 damals schon veraltet waren, schau dir mal VertexDeclarations an: https://msdn.microsoft.com/en-us/librar ... 06335.aspx ;)
Krishty hat geschrieben:Alternativ sind Vertex Declarations deutlich angenehmer zu benutzen.
Ah, habe die schon oft in Beispielen und Erklärungen zu Vertexshadern gesehen. Direkt implementiert und SetFVF rausgeschmissen. Jetzt kann ich auch genau festlegen/sehen, wo welcher Wert in der Vertexstruktur zu liegen hat.
Krishty hat geschrieben:Gut möglich; siehe die Tabelle hier ganz unten: Accurately Profiling Direct3D API Calls (Direct3D 9) Ab D3D 10 müsste das durch Constant Buffers viel schneller sein.
Sehr interessante Tabelle/Liste, danke!
Krishty hat geschrieben:Äh … von Caches und Speicherbandbreite hast du aber schon gehört, oder? Wenn du statt 24 B jetzt 48 überträgst, ist die Übertragung halt nur noch halb so schnell … und dass die ein Flaschenhals ist, weißt du, seit Vertex Buffers nichts gebracht haben.
Ja klar, so gesehen ist das schon verständlich. Aber ich finde es trotzdem immer wieder etwas überraschend. Es geht um 60.000 x 6 x 24 Byte, also insgesamt 8,64 MB pro Frame. Bei 400 FPS wären das grob zusätzlich 3,5 GB/s. PCIe 3.0 x16 soll 32 GB/s schaffen. D3D9 ist da aber wohl bekannter Weise nicht so gut umgesetzt. Neuere D3D Versionen wie z.B. D3D10 würden das vermutlich besser "wegstecken", oder?
dot hat geschrieben:Abgesehen davon, bedeutet XYZRHW doch, dass du deine Vertices auf der CPU transformieren und dann ständig neu auf die GPU laden musst!?
Leider korrekt. Bei 3D-Titeln wird vermutlich erst immer ein statischer Teil, der den Großteil der Levelgeometrie/Leveldaten ausmacht und konstant bleibt, einmalig in einen Vertexbuffer gepackt und aufgrund von der Kameraposition/View durch die GPU transformiert. Die dynamischen Teile im Level, wie z.B. Animationen, wo mindestens die Texturkoordinaten alle paar Frames wechseln, werden dann am Ende gerendert und der Z-Buffer sorgt dafür, dass das auch alles korrekt passt. Man sorgt also dafür, das wirklich nur das Nötigste pro Frame neu zur GPU gesendet wird. So ein Konzept muss ich mir für meine 2D-Engine überlegen. Ein Z-Buffer finde ich dort schwieriger nutzbar zu machen, da die Objekte keine Distanz zu einer Kamera haben, sondern eher eine Renderordnung. Bei Ultima 7 ist das z.B. aufgrund von der Pseudo-Vogelperspektive völlig irre. Ich überlege da aber schon länger und vielleicht werde ich bald ein Konzept haben, wie ich wenigstens den Boden, bzw. dessen tatsächlichen konstanten Teile, per VertexBuffer rendern kann.
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] [D3D9] Texturkoordinaten und Skalierung

Beitrag von Krishty »

Goderion hat geschrieben:
Krishty hat geschrieben:Äh … von Caches und Speicherbandbreite hast du aber schon gehört, oder? Wenn du statt 24 B jetzt 48 überträgst, ist die Übertragung halt nur noch halb so schnell … und dass die ein Flaschenhals ist, weißt du, seit Vertex Buffers nichts gebracht haben.
Ja klar, so gesehen ist das schon verständlich. Aber ich finde es trotzdem immer wieder etwas überraschend. Es geht um 60.000 x 6 x 24 Byte, also insgesamt 8,64 MB pro Frame. Bei 400 FPS wären das grob zusätzlich 3,5 GB/s. PCIe 3.0 x16 soll 32 GB/s schaffen. D3D9 ist da aber wohl bekannter Weise nicht so gut umgesetzt. Neuere D3D Versionen wie z.B. D3D10 würden das vermutlich besser "wegstecken", oder?
Keine Ahnung. Die 3.5 GB müssen ja mehrfach kopiert werden – erstmal erzeugst du sie auf dem Stack. Dann werden sie von DrawPrimitiveUP() in den Befehlspuffer des Treibers kopiert. Dann vom Befehlspuffer über PCIe auf die GPU. Da du die Vertices nicht auf einen Schlag zur Verfügung stellst, sondern auf viele Draw Calls verteilt, geht zwischendurch auch die Lokalität flöten.

Mit D3D 12 oder Vulkan kannst du direkt kopieren, also dir den Treiber-Puffer sparen. Das habe ich aber nur gehört und noch nicht selber umgesetzt. Und es wird sicherlich mindestens 20 Mal so viele Zeilen Code erfordern.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Antworten