Ich schreibe ein Spiel in C ++ mit OpenGL.
Für diejenigen , die Sie mit der OpenGL API nicht kennen, machen eine Menge Anrufe , um Dinge wie glGenBuffers
und glCreateShader
etc. Diese Rückgabetypen von GLuint
denen sind eindeutige Kennungen zu dem, was Sie gerade erstellt haben . Das erstellte Objekt lebt im GPU-Speicher.
Da der GPU-Speicher manchmal begrenzt ist, möchten Sie nicht zwei Dinge erstellen, die gleich sind, wenn sie von mehreren Objekten verwendet werden sollen.
Zum Beispiel Shader. Sie verknüpfen ein Shader-Programm und haben dann ein GLuint
. Wenn Sie mit dem Shader fertig sind, sollten Sie anrufen glDeleteShader
(oder etwas in diesem Sinne).
Nehmen wir an, ich habe eine flache Klassenhierarchie wie:
class WorldEntity
{
public:
/* ... */
protected:
ShaderProgram* shader;
/* ... */
};
class CarEntity : public WorldEntity
{
/* ... */
};
class PersonEntity: public WorldEntity
{
/* ... */
};
Jeder Code, den ich jemals gesehen habe, würde erfordern, dass alle Konstruktoren eine ShaderProgram*
Übergabe an ihn haben, um in der gespeichert zu werden WorldEntity
. ShaderProgram
ist meine Klasse, die die Bindung von a GLuint
an den aktuellen Shader-Status im OpenGL-Kontext sowie einige andere hilfreiche Dinge, die Sie mit Shadern tun müssen, kapselt.
Das Problem, das ich damit habe, ist:
- Es sind viele Parameter erforderlich, um ein zu
WorldEntity
erstellen (bedenken Sie, dass es möglicherweise ein Netz, einen Shader, eine Reihe von Texturen usw. gibt, die alle gemeinsam genutzt werden können, sodass sie als Zeiger übergeben werden). - Was auch immer die
WorldEntity
Bedürfnisse schafft, muss wissen, wasShaderProgram
es braucht - Dies erfordert wahrscheinlich eine Art Gulp-
EntityManager
Klasse, die weiß, welche Instanz von wasShaderProgram
an verschiedene Entitäten übergeben werden soll.
Jetzt, da es eine gibt, müssen sich Manager
die Klassen entweder EntityManager
zusammen mit der benötigten ShaderProgram
Instanz bei der registrieren , oder ich brauche einen Big-Ass switch
im Manager, den ich für jeden neuen WorldEntity
abgeleiteten Typ aktualisieren muss .
Mein erster Gedanke war, eine ShaderManager
Klasse zu erstellen (ich weiß, Manager sind schlecht), die ich als Referenz oder Zeiger auf die WorldEntity
Klassen übergebe, damit sie erstellen können, was ShaderProgram
sie wollen, über die ShaderManager
und die ShaderManager
bereits vorhandenen ShaderProgram
s verfolgen können , damit dies möglich ist Geben Sie eine bereits vorhandene zurück oder erstellen Sie bei Bedarf eine neue.
(Ich könnte das ShaderProgram
s über den Hash der Dateinamen des ShaderProgram
tatsächlichen Quellcodes speichern. )
Also jetzt:
- Ich übergebe jetzt Zeiger auf
ShaderManager
stattShaderProgram
, daher gibt es immer noch viele Parameter - Ich brauche keine
EntityManager
, die Entitäten selbst wissen, welche InstanzShaderProgram
erstellt werden soll, undShaderManager
kümmern sich um die tatsächlichenShaderProgram
s. - Aber jetzt weiß ich nicht, wann ich
ShaderManager
sicher löschen kann,ShaderProgram
was es enthält.
Jetzt habe ich meiner ShaderProgram
Klasse eine Referenzzählung hinzugefügt , die das interne GLuint
Via löscht, glDeleteProgram
und ich verzichte darauf ShaderManager
.
Also jetzt:
- Ein Objekt kann alles erstellen, was
ShaderProgram
es benötigt - Aber jetzt gibt es Duplikate,
ShaderProgram
weil kein externer Manager den Überblick behält
Schließlich komme ich, um eine von zwei Entscheidungen zu treffen:
1. Statische Klasse
A static class
, das aufgerufen wird, um ShaderProgram
s zu erstellen . Es führt eine interne Verfolgung von ShaderProgram
s basierend auf einem Hash der Dateinamen durch - dies bedeutet, dass ich keine Zeiger oder Verweise mehr auf ShaderProgram
s oder ShaderManager
s übergeben muss, also weniger Parameter - WorldEntities
die alle Kenntnisse über die Instanz haben, die ShaderProgram
sie erstellen möchten
Dieses neue static ShaderManager
muss:
- Zählen Sie, wie oft a verwendet
ShaderProgram
wird, und ich macheShaderProgram
kein kopierbares ODER ShaderProgram
s zählen ihre Referenzen und rufenglDeleteProgram
ihren Destruktor nur auf, wenn die Zählung0
UND ist, und suchenShaderManager
regelmäßig nachShaderProgram
's mit einer Zählung von 1 und verwerfen sie.
Die Nachteile dieses Ansatzes, die ich sehe, sind:
Ich habe eine globale statische Klasse, die ein Problem sein könnte. Der OpenGL-Kontext muss vor dem Aufrufen von
glX
Funktionen erstellt werden. Möglicherweise wird also einWorldEntity
erstellt und versucht,ShaderProgram
vor der OpenGL-Kontexterstellung einen zu erstellen , was zu einem Absturz führt.Der einzige Weg, dies zu umgehen, besteht darin, alles als Zeiger / Referenzen weiterzugeben oder eine globale GLContext-Klasse zu haben, die abgefragt werden kann, oder alles in einer Klasse zu halten, die den Kontext bei der Erstellung erstellt. Oder vielleicht nur ein globaler Boolescher Wert
IsContextCreated
, der überprüft werden kann. Aber ich mache mir Sorgen, dass ich dadurch überall hässlichen Code bekomme.Was ich sehen kann, ist:
- Die große
Engine
Klasse, in der jede andere Klasse versteckt ist, damit sie die Bau- / Dekonstruktionsreihenfolge angemessen steuern kann. Dies scheint ein großes Durcheinander von Schnittstellencode zwischen dem Benutzer der Engine und der Engine zu sein, wie ein Wrapper über einem Wrapper - Eine ganze Reihe von "Manager" -Klassen, die Instanzen verfolgen und Dinge löschen, wenn dies erforderlich ist. Dies könnte ein notwendiges Übel sein?
- Die große
UND
- Wann soll man eigentlich
ShaderProgram
s aus dem räumenstatic ShaderManager
? Alle paar Minuten? Jede Spielschleife? Ich kümmere mich ordnungsgemäß um das Neukompilieren eines Shaders für den Fall, dass ein ShaderShaderProgram
gelöscht wurde, aber dann ein neuer ihnWorldEntity
anfordert. aber ich bin sicher, es gibt einen besseren Weg.
2. Eine bessere Methode
Darum bitte ich hier
WorldEntity
s zu konstruieren . Verschiebt das nicht einen Teil des Problems? Denn jetzt muss die WorldFactory-Klasse jeder WolrdEntity das richtige ShaderProgramm übergeben.