OpenGL Compute Shader generiert Dreiecksindizes: Wie erhalte ich die richtige Elementanzahl für glDrawElementsIndirect?

7

Ich habe eine Folge von Compute-Shadern, die ein indiziertes Netz erzeugen. Der letzte davon schreibt die generierten Indizes wie folgt:

void addTriangle (uint i0, uint i1, uint i2) {
    uint ic = atomicCounterIncrement(indirectIndexCount);
    meshIndices[ic*3+0] = i0;
    meshIndices[ic*3+1] = i1;
    meshIndices[ic*3+2] = i2;
}

Nachdem das Netz generiert wurde, wird es mit glDrawElementsIndirect gezeichnet . Der indirekteIndexCount im obigen Code ist ein atomic_uint- Zähler an Position 0 innerhalb des GL_DRAW_INDIRECT_BUFFER (siehe die Struktur DrawElementsIndirectCommand ). Dieser Zähler ist jetzt offensichtlich um den Faktor drei zu klein, da er für jedes Dreieck nur einmal erhöht wurde. Derzeit multipliziere ich es kurz vor dem Ausgeben des Draw-Aufrufs mit 3.

(Im Moment geschieht dies durch Zuordnen des Puffers und Multiplizieren auf der CPU, was natürlich Unsinn ist, aber zeigt, dass das Ganze im Grunde funktioniert. Alles ist korrekt gezeichnet. Ich könnte es mit einem Aufruf eines einzelnen 1x1x1x1x1x1-Compute-Shaders tun , aber das scheint nur etwas weniger albern.)

Wie werde ich diesen zusätzlichen Multiplikationsschritt los?

Da dies ein offensichtliches Problem zu sein scheint, wenn Netze mit variabler Indexanzahl generiert werden, muss es wohl eine einfache Lösung geben, die ich übersehen habe?

Tasse
quelle
1
Können Sie dem Zähler 3 atomar hinzufügen, anstatt ihn zu erhöhen?
Nathan Reed
1
Nein, Atomzähler können nur abgefragt, inkrementiert (um 1) oder dekrementiert (um 1) werden. Siehe den Artikel im opengl-Wiki. Sie sind nicht dasselbe wie Atomic Add etc on Images. Sie haben nur 8 (oder so) von ihnen und sie sind angeblich viel schneller, wenn sie extrem oft aufgerufen werden (wie beim Generieren (und damit Zählen) von Tausenden von ... Dingen.)
cupe
Ja, ich denke, Sie müssten es von einem "Atomzähler" zu einer Variablen in einem SSBO machen. Es wäre interessant zu sehen, ob das tatsächlich langsamer ist (je nach HW möglicherweise nicht). Abgesehen davon kann ich mir nur vorstellen, wie Sie gesagt haben, einen Compute-Shader auszuführen, um den Wert mit 3 zu multiplizieren.
Nathan Reed,
Hallo Tasse, ist es möglich, irgendwo einen Blick auf den Code zu werfen? Ps: prüfst du auch auf mehrere Einträge?
Wählen Sie den

Antworten:

7

Wenn Sie Zugriff auf glsl 4.3+ (oder glsl ES 3.1) haben, können Sie atomicAdd verwenden

Die nächste Option besteht darin, a zu verwenden, barrier()nachdem alle Scheitelpunkte in der Berechnung generiert wurden, und dann den Wert im Zähler zu multiplizieren:

main(){

    // generate vertices

    barrier();
    if(gl_localInvocationID == vec3(0)){
        indirectCount = atomicCounter(indirectIndexCount)*3;
    }
}

Dies emuliert das Ausführen eines anderen 1x1x1-Compute-Shaders, um den Index zu multiplizieren.

Andernfalls können Sie ein zweites Atom verwenden, um die Scheitelpunktzahl zu speichern:

void addTriangle (uint i0, uint i1, uint i2) {
    uint ic = atomicCounterIncrement(indirectIndex);
    meshIndices[ic*3+0] = i0;
    meshIndices[ic*3+1] = i1;
    meshIndices[ic*3+2] = i2;
    atomicCounterIncrement(indirectIndexCount);
    atomicCounterIncrement(indirectIndexCount);
    atomicCounterIncrement(indirectIndexCount);
}

Beide indirectIndexund indirectIndexCountwerden auf 0 initialisiert und indirectIndexCountan übergeben glDrawElementsIndirect.

Ratschenfreak
quelle
Ich mag die zweite Idee, weil sie klar macht, dass ich zwei verschiedene Dinge brauche: Eine Möglichkeit, Dreiecke atomar hinzuzufügen und eine Möglichkeit, die endgültige Indexanzahl zu erhalten. Muss nicht der gleiche Mechanismus sein. Und ja, ich werde es auch mit atomicAdd versuchen. Wie Nathan oben in einem Kommentar vorgeschlagen hat, wäre es interessant zu sehen, ob es wirklich langsamer ist. Ich werde mit den Ergebnissen berichten.
Cupe
1
Kann bestätigen, dass atomicAdd für diesen Anwendungsfall auf nvidia kepler tatsächlich nicht langsamer ist (~ 1,5k-Indizes: 0,1 ms - benötigt wahrscheinlich eine größere Größe für einen aussagekräftigen Benchmark). Das Binden des indirekten Befehlspuffers als SSBO und das Schreiben in die Uint an Position 0 funktioniert einwandfrei. Manchmal sollte man einfach zuerst die einfachen Lösungen ausprobieren ...
cupe