Wie kann die Leistung dieses Shaders verbessert werden?

8

Ich habe eine Szene mit 150000 Instanzen. Ich benutze glsl und opengl 4.0. Shader A ist 2-mal langsamer als Shader BIe, wobei Shader AI 20 fps und Shader BI durchschnittlich 40 fps erreicht. Was kann ich tun, um Shader A zu verbessern?

Shader A:

#version 400

struct Light {
   vec3 position;
   vec3 intensities; //a.k.a the color of the light
   float ambientCoefficient;
   float attenuation;
};

uniform bool useLight;
uniform mat4 modelMatrix;
uniform bool useTex;
uniform sampler2D tex;
uniform Light light;
uniform vec4 diffuseColor;

in vec2 fragTexCoord;
in vec3 fragNormal;
in vec3 fragVert;

out vec4 finalColor;

void main() {
    vec3 normal = normalize(transpose(inverse(mat3(modelMatrix))) * fragNormal);
    vec3 surfacePos = vec3(modelMatrix * vec4(fragVert, 1));

    vec4 surfaceColor = vec4(0,0,0,1);

    if(useTex) {
        surfaceColor = texture(tex, fragTexCoord);
    }
    else {
        //surfaceColor = diffuseColor;
        surfaceColor = vec4(0,1,0,1);
    }

    if(useLight) {
        vec3 surfaceToLight = normalize(light.position - surfacePos);

        //ambient
        vec3 ambient = light.ambientCoefficient * surfaceColor.rgb * light.intensities;

        //diffuse
        float diffuseCoefficient = max(0.0, dot(normal, surfaceToLight));
        vec3 diffuse = diffuseCoefficient * surfaceColor.rgb * light.intensities;

        //attenuation
        float distanceToLight = length(light.position - surfacePos);
        float attenuation = 1.0 / (1.0 + light.attenuation * pow(distanceToLight, 2));

        //linear color (color before gamma correction)
        vec3 linearColor = ambient + attenuation*(diffuse);

        //final color (after gamma correction)
        vec3 gamma = vec3(1.0/2.2);
        finalColor = vec4(pow(linearColor, gamma), surfaceColor.a);
    }
    else {
        finalColor = surfaceColor;
    }
}

Shader B:

#version 400

struct Light {
   vec3 position;
   vec3 intensities; //a.k.a the color of the light
   float ambientCoefficient;
   float attenuation;
};

uniform bool useLight;
uniform mat4 modelMatrix;
uniform bool useTex;
uniform sampler2D tex;
uniform Light light;
uniform vec4 diffuseColor;

in vec2 fragTexCoord;
in vec3 fragNormal;
in vec3 fragVert;

out vec4 finalColor;

void main() {
    finalColor = vec4(0,0,0.7,1);
}
user68854
quelle
1
Dies kann eine interessante Frage für CodeReview.SE
LukeG
1
@LukeG Ich stimme zu, aber ich wäre nicht im geringsten überrascht, wenn es hier viel mehr Traktion bekommen würde. OpenGL ist dort vielleicht eine kleine Nische gegen Brot und Butter hier. Analog würde um Rat zu einem Shell-Skript unter Unix SE bitten.
Jared Smith
@LukeG - es ist auch so, dass an diesem Code nichts spezielles falsch ist , wenn er isoliert überprüft wird. Man muss auch die Plattform, auf der es ausgeführt wird, eine GPU und die Leistungsmerkmale dieser Plattform berücksichtigen, um ein vollständigeres Bild zu erhalten.
Maximus Minimus
Ich könnte mir hier etwas vermissen, wenn ja, bitte vergib mir. Aber fragen Sie sich nur, warum der Code mit erheblich weniger Operationen schneller ist als der andere?
Doddy

Antworten:

15

Zunächst sollten Sie so viele Daten wie möglich vorberechnen und vermeiden, für jedes Pixel dieselben Werte zu berechnen.

Du hast so ein Fragment:

transpose(inverse(mat3(modelMatrix))

Dies invertiert die Matrix, was keine so triviale Operation ist, und trotz der Tatsache, dass die Eingabedaten für jedes Pixel gleich sind (die Ergebnisse sind also gleich), wird dies für jedes Pixel neu berechnet. Berechnen Sie es einmal vor dem Rendern und übergeben Sie das Ergebnis als eine andere Matrix, wie Sie es mit dem tun modelMatrix.

Später normalisieren Sie auch den (light.position - surfacePos)Vektor, berechnen lengthihn aber auch, sodass zwei sqrtOperationen anstelle von einer ausgeführt werden.

Abhängig von Ihrer Hardware kann es außerdem vorkommen, dass die Verwendung if'sin einem Pixel-Shader Ihre Leistung beeinträchtigt. In diesem Fall können Sie einige unterschiedliche Versionen Ihres Shaders vorbereiten und Ihre Instanzen je nach useLightund useTexEigenschaften stapeln.

BEARBEITEN:

Sie können auch versuchen, die in Shadern definierte OpenGL-Version zu verringern, um die niedrigste Version zu sein, die Ihre Funktionen unterstützt. Theoretisch sollte es nicht viel bewirken, aber je nach Treiber und HW-Anbieter kann die Praxis unterschiedlich sein ... (Wenn Ihre GPU OGL 4.0 unterstützt, bedeutet dies häufig, dass es in OGL 3.0 schnell, in 4.0 jedoch sehr langsam ist, aber Sie müssen testen es auf bestimmten Fall).

kolenda
quelle
Wenn vec3 normal nur von Fragmentvariablen abhängt, würde die GPU es dann nicht wissen und redundante Berechnungen vermeiden?
user68854
@ user68854 - GPUs funktionieren im Allgemeinen nicht so: Sie pflügen normalerweise nur durch die Arbeit. Ihr Shader-Compiler kann dies möglicherweise identifizieren, aber möglicherweise nicht. Ob dies der Fall ist oder nicht, wird durch die GL-Spezifikation definiert. Mit anderen Worten, Sie sollten sich nicht darauf verlassen.
Maximus Minimus
@ user68854 Das glaube ich nicht. Während Ihr Shader-Compiler möglicherweise Code für Sie optimiert, kann er dies nicht zwischen verschiedenen Ausführungen Ihres Shaders tun, verfügt jedoch einfach nicht über ein 'Notizblock', in das diese allgemeinen Daten eingefügt werden können. Aber selbst wenn sich herausstellt, dass das Ergebnis von Ihnen inversekonstant ist, gibt es auf der GPU keinen Platz zum Speichern dieser Daten. Es funktioniert einfach nicht so (AFAIK).
Kolenda
@kolenda Es könnte interessant sein, auf computergraphics.stackexchange.com zu fragen, ob eine solche Optimierung möglich ist.
Porglezomp
1
Das verwirrt mich: transpose(inverse(mat3(modelMatrix))Sollte die Transponierung einer 3x3-Rotationsmatrix nicht schon umgekehrt sein?
Sidar
3

Betrachtet man insbesondere diese Zeile:

vec3 normal = normalize(transpose(inverse(mat3(modelMatrix))) * fragNormal);

Das Ausarbeiten der Umkehrung einer Matrix ist sehr anstrengend und sollte von der CPU vorberechnet werden, anstatt die GPU zu zwingen, Zeit damit zu verschwenden, damit herumzuspielen.

Huxellberger
quelle
3

Sie können nicht viel tun. Die einfache Realität ist, dass Shader A mehr Arbeit leistet als Shader B, sodass er immer langsamer läuft.

Wir können die Lücke jedoch etwas schließen. Ich kann Ihnen keine eindeutigen Zahlen dafür geben, wie viel davon abhängt. Dies hängt von den Leistungsmerkmalen des restlichen Programms ab. Behandeln Sie diese also als allgemeine bewährte Verfahren.

transpose(inverse(mat3(modelMatrix)))

Das sind viele Inversionen und Transpositionen pro Frame. Tun Sie dies stattdessen nur einmal auf der CPU (oder zumindest nur, wenn sich modelMatrix ändert) und senden Sie die inverse / transponierte Matrix als zusätzliche Uniform. Wenn ALU-Operationen ein Engpass für Sie sind, sollte dies Ihnen den größten Anstieg bringen.

if(useTex) {

Verzweigen ist nicht der Tod, der es früher war, aber Sie können es hier trotzdem vermeiden (und einen einheitlichen Schlitz speichern), indem Sie eine 1x1-Textur (der entsprechenden Farbe) erstellen und diese stattdessen binden.

if(useLight) {

Mehr Verzweigung. In diesem Fall gibt es keine offensichtliche Alternative (wie die 1x1-Textur), daher würde ich Sie ermutigen, diese Bedingung in einen dritten Shader aufzuteilen und beide zu vergleichen (dh 2 Shader mit einem Zweig gegenüber 3 Shadern ohne). Je nachdem, wie oft Sie die Shader wechseln müssen, kann es im Vergleich zur Verzweigung zu Leistungsunterschieden kommen oder auch nicht.

Maximus Minimus
quelle
1x1 Textur scheint eine experimentelle Lösung zu sein. Besser einen anderen Shader machen. Wie auch immer, warum das Verzweigen teuer ist? Sprung mit Bedingung auf GPU-Kern ist teuer?
user68854
@ user68854 - GPUs funktionieren nicht wie CPUs. Ältere GPUs, insbesondere, hatten keine native Unterstützung für die Verzweigung überhaupt , sondern beide Seiten der Branche dann eine Schritt - Anleitung des richtigen auszuwählen verwendet ausgeführt. Heutzutage ist das Verzweigen billiger (aber immer noch nicht kostenlos), daher lohnt es sich zu prüfen, ob ein Shaderwechsel billiger ist oder nicht. Die Verwendung einer 1x1-Textur ist ein bekannter Trick. Weitere Informationen finden Sie beispielsweise unter stackoverflow.com/questions/22703166/… .
Maximus Minimus
Verzweigungen können aufgrund von Pipeline-Optimierungen kostspielig sein. Wenn Ihre GPU eine Operation ausführt, bereitet sie sich tatsächlich auf einige nächste vor. Wenn es auf einen trifft if, kann es sich nur auf einen Zweig vorbereiten. Wenn Sie den zweiten auswählen, wird diese Arbeit verschwendet und muss neu gestartet werden. Das gleiche passiert auf CPUs. Der Trick mit 1x1-Textur kann helfen oder nicht - alles hängt von einem bestimmten Shader, GPU-Hersteller, Architektur, Treibern usw. ab, sodass Sie ihn nur selbst testen müssen.
Kolenda