GLSL Compute Shader

Für Fragen zu Grafik APIs wie DirectX und OpenGL sowie Shaderprogrammierung.
Antworten
Benutzeravatar
Jonathan
Establishment
Beiträge: 2654
Registriert: 04.08.2004, 20:06
Kontaktdaten:

GLSL Compute Shader

Beitrag von Jonathan »

Hi,

ich probiere gerade ein wenig Compute Shader in OpenGL aus. Ansich habe ich einen Vertex-Buffer mit Partikelpositionen und will nun die Partikel im Shader animieren. Aber trotz der Lektüre diverser Quellen habe ich bisher damit keinen Erfolg gehabt. Oft habe ich das Gefühl, man hat zwar tolle Detail-Beschreibungen aller OpenGL Funktionen, aber niemand sagt einem, wie man sie jetzt zusammen zu benutzen hat.

Die Partikel ansich funktionieren schon, ich will nur noch die Animation hinzufügen. Der VertexBuffer ist als GL_ARRAY_BUFFER erstellt, dazu lege ich noch ein VertexArrayObject an (was IMO übrigens ein grausiger, weil nichtssagender Name ist...), an das ich mittels glVertexAttribPointer die SubBuffer binde. Dabei benutze ich jedoch nur einen Buffer in dem pro Eintrag eine Position und eine Farbe steht und setze den Stride entsprechend. Das klappt soweit alles ganz gut.

Jetzt lese ich im Zusammenhang mit Compute Shadern ständig etwas von GL_SHADER_STORAGE_BUFFERn. Der Sinn scheint zu sein, direkt die ganze Struktur benutzen zu können. Ehrlich gesagt habe ich kein Beispiel gefunden, in dem die nicht benutzt werden, aber es sagt auch niemand, dass es zwingend erforderlich sein.
Hier geht jetzt eigentlich schon ein bisschen die Raterei los. Ich habe bisher versucht den Buffer an einen leeren Compute-Shader zu binden, aber sobald ich auch nur das mache, sind die Partikel schon vollkommen kaputt, obwohl der Compute Shader rein gar nichts macht. Da ich nach langem rumprobieren die Raterei satt bin, kommen hier meine Fragen:

- Muss ich im Compute Shader Shader-Storage-Buffer verwenden? Bzw. ist alles andere eine dumme Idee?
- Wenn ich Storage-Buffer verwende, kann ich die Partikel dann weiterhin so verwenden wie bisher?
- Benötige ich immer noch ein VAO? Kann ich das selbe benutzen wie fürs Rendern, oder muss ich ein neues anlegen?
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
Spiele Programmierer
Establishment
Beiträge: 426
Registriert: 23.01.2013, 15:55

Re: GLSL Compute Shader

Beitrag von Spiele Programmierer »

Ich habe den Eindruck, du bist schon ein wenig frustriert von OpenGL.
Für die Informationssuche würde ich dir das OpenGL Wiki nahelegen. Da sollten auch einige der bestehenden Fragen geklärt werden.
Muss ich im Compute Shader Shader-Storage-Buffer verwenden? Bzw. ist alles andere eine dumme Idee?
Nein und ja.
Du kannst im Compute Shader zum Beispiel auch Uniforms, Atomic Counter oder Uniform Buffer verwenden. Im Compute Shader besteht aber leider natürlich kein Zugriff auf Vertex oder Index Buffer, da diese speziell zum Rendern definiert sind. Uniform Buffer wären theoretisch auch möglich, besitzen aber einen großen Nachteil gegenüber Shader-Storage-Buffern: Sie sind Readonly. Außerdem sind sie meistens in ihrere Größe stark beschränkt. Das liegt vermutlich daran, weil es in einiger Hardware speziellen Konstantenspeicher oder aber wenigstens schnell zugreifbaren Shared Memory gibt, der von Uniform Buffern möglicherweise genutzt wird.
Wie auch immer, wenn du Daten schreiben willst, kommst du um Shader Storage ohnehin nicht rum.
Wenn ich Storage-Buffer verwende, kann ich die Partikel dann weiterhin so verwenden wie bisher?
Ja klar. Du solltest den Buffer einfach an mehrere Targets binden können und abwechelnd als Vertexbuffer und Storagebuffer verwenden können.
Benötige ich immer noch ein VAO?
Ja. Zum Rendern benötigst du ein VAO. Vermutlich ist daran keine weitere Änderung notwendig.

Ich vermute für den Compute Shader benötigst du kein VAO und der gebundene Storagebuffer wird darin auch nicht gespeichert. 100% sicher bin ich mir da im Moment nicht. Ich kenne das auch. Häufig entstehen dann Detailfragen die in der Spezifikation nicht oder nicht verständlich geklärt sind.

Wenn es immernoch nicht funktioniert, würde ich dir empfehlen, einfach mal den Code zu zeigen.
Benutzeravatar
Jonathan
Establishment
Beiträge: 2654
Registriert: 04.08.2004, 20:06
Kontaktdaten:

Re: GLSL Compute Shader

Beitrag von Jonathan »

Ok, vielen Dank schonmal, zumindest glaube ich jetzt, dass mein Ansatz tatsächlich richtig ist. Laufen tut es allerdings noch nicht.

Hier mal der Code. Ist zusammenkopiert aus den einzelnen Klassen, und ein wenig geändert damit man besser versteht, was passiert.

Code: Alles auswählen

Initialisierung:

// Vertex Struktur
struct P
{
	vec3 Position;
	vec4 Color = vec4(0, 1, 1, 0.1);
};

// Buffer anlegen
glGenVertexArrays(1, &m_VertexArray);
glGenBuffers(1, &m_Buffer);
glBindVertexArray(m_VertexArray);
glBindBuffer(GL_ARRAY_BUFFER, m_Buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(tVertexFormat)*data.size(), data.data(), GL_STATIC_DRAW);
m_NumVertices = data.size();

// Attribute setzen: (wird zweimal gemacht, für Position und Farbe)
glVertexAttribPointer(location, size, GL_FLOAT, GL_FALSE, stride, (void*)offset);
glEnableVertexAttribArray(location);

//Compute Shader laden
glCreateShader(...)
glShaderSource(...)
glCompileShader(...)
glCreateProgram(...)
glAttachShader(m_ProgramObject, m_ShaderObject);
glLinkProgram(m_ProgramObject);


//Compute Shader ausführen:
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT | GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, m_Buffer.GetBuffer());
glUseProgram(m_ProgramObject);
glDispatchCompute(m_NumParticles/64, 1, 1);
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT | GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT);


// Rendern
m_Shader.Set();
m_Shader.SetUniform(0, cameraMatrix);
m_Shader.SetUniform(1, projectionMatrix);
m_Shader.BindTexture(m_Texture, 0);
glBindVertexArray(m_VertexArray);
glDrawArrays(primitiveType, 0, m_NumVertices);


// Compute Shader
#version 430 core


struct P
{
	vec3 Position;
	vec4 Color;
};

layout(local_size_x=64) in;
layout(std140, binding=0) buffer PositionBuffer
{
	P p[];
};

void main()
{
	//p[gl_GlobalInvocationID​].Position.z += 0.001;
}
Im Moment sieht es so aus, dass wenn ich den Compute Shader ausführe (mach ich direkt bevor ich die Partikel render, deshalb hatte ich mal die MemoryBarriers eingefügt), alles was ich danach Rendere flickert (in einem Frame wirds gezeichnet, im nächsten wieder nicht, scheint allerdings ein wenig zufällig zu sein). Und wenn ich versuche die Partikel zu bewegen (auskommentierter Code im Shader), bekomme ich einen Speicherzugriffsfehler.
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
Spiele Programmierer
Establishment
Beiträge: 426
Registriert: 23.01.2013, 15:55

Re: GLSL Compute Shader

Beitrag von Spiele Programmierer »

Ich habe mir den Code nur grob angeschaut, allerdings keinen offensichtlichen Fehler gefunden.
Wenn du verschiedene Befehle bei "Compute Shader ausführen" auskommentierst, wann tritt der Fehler auf? Was passiert zum Beispiel, wenn du nur "glDispatchCompute" auskommentierst? Es wäre auch schön zu sehen, was du genau an "glVertexAttribPointer" übergibst.

Außerdem noch zwei Anmerkungen:
  1. Ich würde die Daten unbedingt datenorientiert ablegen und je ein Array pro Attribut. Das sollte bessere Leistung erzielen, weil gleichzeitig benötigte Daten zusammenliegen. Außerdem bin ich nicht sicher, wie sich das bei der Struct bei dir momentan mit den Alignment verhält. Ich halte es für möglich, dass das auch das die Ursache für den Zugriffsfehler ist. Ich glaube nämlich, dass "vec3"s bei "std140" immer zu 16 Byte alignment werden. Vielleicht also besser gleich "vec4"s nutzen und den 4. Wert zum Beispiel für den Radius verwenden.
  2. "m_NumParticles/64" wird die letzen 0 bis 63 Partikel potentiell nicht verarbeiten, wenn "m_NumParticles" kein Vielfaches von 64 ist.
    Die Empfehlung dazu von Nvidia ist eine Schleife im Computeshader. Das hat auch den Vorteil, dass die tatsächliche Anzahl Partikel leicht auf der GPU bleiben kann, ohne gleich mit "DispatchComputeIndirect" kommen zu müssen.
.
Benutzeravatar
Jonathan
Establishment
Beiträge: 2654
Registriert: 04.08.2004, 20:06
Kontaktdaten:

Re: GLSL Compute Shader

Beitrag von Jonathan »

Ich habe einfach mal das Bewegen der Partikel vom Rendern getrennt, und da ging es. Keine Ahnung, vielleicht war irgend ein State irgendwie falsch gesetzt oder so. Ich habe halt lange hin und her probiert und ohne genau sagen zu können, was den Ausschlag gegeben hat, läuft mein Shader jetzt und ich kann auch Parameter ändern und somit Partikel animieren.

Deine zwei Punkte sind übrigens gut gewesen. Das mit dem Alignment ist mir auch aufgefallen und ich habe es korrigiert, es hatte dazu geführt dass statt der z Position für jeden Partikel ein zufälliger Parameter verändert wurde, was ganz schön lustig, aber leider falsch aussah. Die Frage ist jetzt: Sind getrennte Buffer wirklich schneller? Wenn ich beim iterieren über Partikel auf alle Werte Zugreife (Position und Farbe verändern z.B.) müsste damit jeder Thread nur von einer Speicherstelle die Werte lesen. Ohne jetzt viel über Caches auf Grafikkarten zu wissen, hört sich das für mich effizienter an, als die Parameter aus verschiedenen Stellen zusammen zu suchen.

Das mit dem Iterieren hört sich auch nett an. Jedoch hatte ich bisher sicher gestellt, dass die Partikelanzahl immer durch die Größe teilbar ist, was man auch später ganz gut sicherstellen können sollte. Immerhin sind Partikel eine diffuse Menge von irgendwas wo weder die konkrete Position noch die Anzahl der Teilchen wirklich eine Rolle spielt.

Aber vielen Dank nochmal für die Hilfe, ich bin jetzt so weit, dass die Grundlagen laufen und ich beginnen kann, mit den Details rum zu spielen.
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
Spiele Programmierer
Establishment
Beiträge: 426
Registriert: 23.01.2013, 15:55

Re: GLSL Compute Shader

Beitrag von Spiele Programmierer »

Die Frage ist jetzt: Sind getrennte Buffer wirklich schneller? Wenn ich beim iterieren über Partikel auf alle Werte Zugreife (Position und Farbe verändern z.B.) müsste damit jeder Thread nur von einer Speicherstelle die Werte lesen. Ohne jetzt viel über Caches auf Grafikkarten zu wissen, hört sich das für mich effizienter an, als die Parameter aus verschiedenen Stellen zusammen zu suchen.
Es gibt auch noch die Möglichkeit, alles in einem Buffer zu packen, aber datenorientiert zu organisieren. Also erst alle Positionen, dann alle Farben in einem Buffer.
Mit 100% Sicherheit kann man zur Geschwindigkeit natürlich nur etwas sagen, nachdem man es ausprobiert hat.
Allerdings zwei Dinge:
  1. Ich glaube nicht, dass du am Ende sowohl bei der Grafik als auch beim Compute wirklich alle Attribute brauchst. Das wird mit extrem hoher wahrscheinlichkeit irgendwann nicht mehr der Fall sein.
  2. Du solltest auch daran denken, dass alle Threads in einem Thread vollständig gleichzeitig ausgeführt werden sollten. Das heißt, dass gleichzeitig zum Beispiel zur Zeit 16 Byte lädst und wieder 16 Byte nicht benötigst. Auch wenn die jeweils 2. 16 Byte kurz darauf gebraucht werden, sehe ich da potentiell einen Nachteil gegenüber einer gleichmäßigen Speicheranforderung. (Zumindests Nvidia lädt in der übrigens anscheinend tatsächlich 16 Bytes gleichzeitig. Quelle)
Das mit der Partikelzahl ist halt nicht mehr sicherzustellen, wenn du Partikel auf der Grafikkarte löschen möchtest.
Mir scheint das ein ziemlich unumgängliches Feature eines jeden Partikelsystems zu sein. Und dann ist es nicht mehr immer durch 64 teilbar und es wäre auch ungünstig, die Zahl wieder auf die CPU zurückzulesen. Nvidia redet außerdem doch von höherer Performance weil bestehende Threads länger etwas zu tun haben.
Antworten