Opengl Objekt Datenstruktur

Hey Leute.
Wieder einmal zurück zur wunderbaren OpenGL ^^

Ich habe in meiner Lib folgende Struktur, in der ich alles anordne:

View [eine art ober-sammlung. zB guiView, worldView]

enthält:

ViewPart [ein teil einer view, verschiedene viewParts = verschiedene ShaderProgramme]

enthält:

RenderObject [halt ein render objekt, welches aus einem asset gebastelt wird.]

enthält:

RenderObject [in einer liste subRenderObjects, um hierarchisch angeordnete objekte basteln zu können]

So. Ich würde mal gerne wissen, was ihr daran nicht ganz glücklich gestaltet findet.
Woran ich von anfang an dachte ist, dass es zum Problem werden könnte, wenn subRenderObjects
einen anderen Shader benutzen wollen als die parents. dann bräuchte ich ja ein anderes viewpart, was dann
ja kein subObject mehr wäre…

Eine andere Sache die mir nicht gefällt, ist die Tatsache, das es umständlich ist so ein renderobject zu löschen.
Im Prinzip bräuchte ich ja irgendwo eine globale liste mit allen objects in den anderen listen dann nur referenzen, und dann daraus irgendwie löschen…

Außerdem… wenn man performance gewinnen will, in dem man alles nach zB texturen oder sonst was sortiert, dann funktioniert dass ja wiederum nicht,
wenn es in verschiedenen bereichen gespeichert ist…

was meint ihr?
was fällt euch noch so auf?

Anhand der Beschreibung in Worten ist es schwer, da etwas dazu zu sagen… Zumindest ein Sample, das zeigt, wie man diese ganzen Klassen dann verwendet, wäre interessant. (Klingt grob nach ähnlichen Fragen, wie ich sie mal in JavaGL Rendering zu beantworten versucht hatte, aber … das Projekt “schläft” jetzt schon eine ganze Weile…)

Ok. Also.

Im Prinzip sind folgende Schritte notwendig, um hier etwas zum laufen zu bekommen.

  1. Es wird eine view erzeugt. Einer view wird auch die Kamera übergene.
AGLView world = new AGLView(camera);```

2. Als nächstes wird ein viewPart, und ein dazu benötigtes shader programm gebaut

```//attributes sind die glsl attributes, resource eine datei in der die beiden shader mit "###" getrennt stehen
AGLShaderProgram program = new AGLShaderProgram(resource, attributes);
AGLViewPart part = new AGLViewPart();
world.addViewPart(part);```

3. Nun können RenderObjekte zum viewPart hinzugefügt werden. Ein RenderObject wird aus einem Asset gebastelt:

```AGLMeshData data = new AGLMeshData(floatarray-resource-oderwasauchimmer, drawmode);
//es ist vielleicht nicht sinnvoll, aber mein mesh braucht die attributes, da im mesh ein vbo erzeugt wird; dieses braucht die attributes
AGLMesh mesh = new AGLMesh(meshData, attributes);
AGLAsset asset = new AGLAsset(mesh);
AGLRenderObject object = new AGLRenderObject(asset);```

4. zum schluss wird die view gebindet

```AGLRenderController.bindViews(world);```

der render durchlauf macht also grob:

-in AGLRenderController: `for(AGLView v : views) v.render();`
-in AGLView: `for(AGLViewPart p : parts) p.render();`
-in AGLViewPart: `for(AGLRenderObject r : objects) o.render();`
-in AGLRenderObject: `asset.render();`

Das ist so mein grober aufbau.

Hm. Diese Aussage jetzt bitte NICHT als ein „Das ist gut so“ überinterpretieren, aber: Das sieht nicht komplett unsinnig aus.

Kamera, Shaderprogramm, Geometrie (bzw Mesh) - es gibt sicher etliche Libs, die irgendwas mit OpenGL machen, und sich solche oder ähnliche Klassen erstellt haben. In diesem klassischen Quartett fehlen mir im Moment noch die Texturen, aber … vielleicht sind die irgendwo in den Attributes versteckt?

Das mit den „durch ### getrennten Shadern“ erscheint etwas suspekt. Warum nicht einfach zwei Dateien, für Vertex und Fragment? (Und Compute und Tesselation und Geometry und … ;-))

Ganz allgemein stellt sich, wenn man so eine Lib/Utility-Klassen erstellen will, die Frage, wie viel Funktionalität man nach draußen durchrouten will - und damit verbunden die Frage, ob eine API, die die gleiche Funktionalität liefert, wie OpenGL, überhaupt einfacher sein kann, als OpenGL :wink: Aber das ist ja wohl nicht das Ziel.

Wie genau die Verdrahtung zwischen Mesh/Attributen/Shader-Inputs abläuft wäre ganz interessant. Ich hatte da, als ich noch aktiver an der Rendering-Lib gearbeitet habe, mal ein paar Threads dazu erstellt, und @Fancy hatte da immer was dazu gesagt. Es ist halt etwas „lästig“, dass man das ganze einerseits komplett generisch machen könnte, aber … dadurch, dass der Shader komplett frei programmierbar ist, erzwingt man eine absurde Abhängigkeit: Wenn man einen Shader verwenden will, muss man wissen, welche Daten er (unter welchem Variablennamen!) als Eingabe erwartet. Das kann man kaum praktikabel programmatisch lösen, d.h. jeder neue Shader birgt das Potential, eine Änderung am Library-Code zu erzwingen (oder man läßt sich auf irgendwas halbgares ein).

Vielleicht beruhigt es dich (MICH hat es im Nachhinein zumindest etwas beruhigt), dass auch „die Großen“ sich da leicht mal verhaspeln: Program doesn't know attributes · Issue #152 · KhronosGroup/glTF · GitHub :smiley:

Die explizite Vergabe von Locations in Shadern in den neueren GL-Versionen könnte das Problem abschwächen, aber … das muss ich mir auch erst noch genauer ansehen…

Moin,

imho könnte man auch überlegen eine interne Datenstruktur über zwei verschiedene Schnittstellen erreichbar zu machen. Einmal eine Schnittstelle die aus Softwaresicht sauber und abstrakt definiert ist. Und zum anderen eine Schnittstelle welche die Daten genau so liefert wie sie OpenGL benötigt.

Für letzteres könnte man für die RenderObjects eine Ordnung definieren, welche die verwendeten Shader und Texturen (usw.) berücksichtigt. Schmeißt man diese Objekte alle in ein SortedSet (ConcurrentSkipListSet oder TreeSet) kann man in der Renderschleife einfach über dieses Set iterieren und die Zustandsänderungen so minimieren.

Viele Grüße
Fancy

Also grob gesagt wie ich meinte, alles zwei mal speichern, das eine für den nutzer das andere für opengl?

Nein, eine Datenstruktur und zwei Schnittstellen. Für OpenGL ist es wichtig das die Daten schnell, unterbrechungsfrei und mit minimalen Zustandsänderungen kommen. Wenn das z.B. als ConcurrentSkipListSet realisiert ist, reicht es einfach mit dem OpenGL Thread über diese Liste zu iterieren (zumindest bei single pass). Andere Threads können diese Liste ebenfalls manipulieren, z.B. wenn ein neues Objekt erscheinen soll. Die Schnittstelle dazu kann dann durchaus sauber implementiert werden, da dort nicht mehr auf die OpenGL Eigenheiten Rücksicht genommen werden muss.

Gruß
Fancy

Jetzt mal ganz blöd gefragt; in welcher reihenfolge sollte man die Zustandsänderungen minimieren?
Also welche sind so die “teuersten” ?

Ich würde zB sortieren nach:

  1. Shader
  2. Mesh, also Vao
  3. Textur

In meiner Struktur die ich oben erklärt habe ist nach Shader ja sowieso Automatisch “sortiert” (gruppiert eher),
dann bliebe noch nach dem mesh und der textur sortieren…

Sie Dir mal diesen Vortrag ab Minute 27:40 an. Da gibt es unter anderem eine Folie die beschreibt wie teuer die einzelnen Zustandsänderungen sind und eine Folie mit der Reihenfolge in der Unreal Engine 4. Und wie man das noch weiter optimieren kann.

Gruß
Fancy