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 glGenBuffersund glCreateShaderetc. Diese Rückgabetypen von GLuintdenen 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. ShaderProgramist meine Klasse, die die Bindung von a GLuintan 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
WorldEntityerstellen (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
WorldEntityBedürfnisse schafft, muss wissen, wasShaderProgrames braucht - Dies erfordert wahrscheinlich eine Art Gulp-
EntityManagerKlasse, die weiß, welche Instanz von wasShaderPrograman verschiedene Entitäten übergeben werden soll.
Jetzt, da es eine gibt, müssen sich Managerdie Klassen entweder EntityManagerzusammen mit der benötigten ShaderProgramInstanz bei der registrieren , oder ich brauche einen Big-Ass switchim Manager, den ich für jeden neuen WorldEntityabgeleiteten Typ aktualisieren muss .
Mein erster Gedanke war, eine ShaderManagerKlasse zu erstellen (ich weiß, Manager sind schlecht), die ich als Referenz oder Zeiger auf die WorldEntityKlassen übergebe, damit sie erstellen können, was ShaderProgramsie wollen, über die ShaderManagerund die ShaderManagerbereits vorhandenen ShaderPrograms 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 ShaderPrograms über den Hash der Dateinamen des ShaderProgramtatsächlichen Quellcodes speichern. )
Also jetzt:
- Ich übergebe jetzt Zeiger auf
ShaderManagerstattShaderProgram, daher gibt es immer noch viele Parameter - Ich brauche keine
EntityManager, die Entitäten selbst wissen, welche InstanzShaderProgramerstellt werden soll, undShaderManagerkümmern sich um die tatsächlichenShaderPrograms. - Aber jetzt weiß ich nicht, wann ich
ShaderManagersicher löschen kann,ShaderProgramwas es enthält.
Jetzt habe ich meiner ShaderProgramKlasse eine Referenzzählung hinzugefügt , die das interne GLuintVia löscht, glDeleteProgramund ich verzichte darauf ShaderManager.
Also jetzt:
- Ein Objekt kann alles erstellen, was
ShaderProgrames benötigt - Aber jetzt gibt es Duplikate,
ShaderProgramweil 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 ShaderPrograms zu erstellen . Es führt eine interne Verfolgung von ShaderPrograms basierend auf einem Hash der Dateinamen durch - dies bedeutet, dass ich keine Zeiger oder Verweise mehr auf ShaderPrograms oder ShaderManagers übergeben muss, also weniger Parameter - WorldEntitiesdie alle Kenntnisse über die Instanz haben, die ShaderProgramsie erstellen möchten
Dieses neue static ShaderManagermuss:
- Zählen Sie, wie oft a verwendet
ShaderProgramwird, und ich macheShaderProgramkein kopierbares ODER ShaderPrograms zählen ihre Referenzen und rufenglDeleteProgramihren Destruktor nur auf, wenn die Zählung0UND ist, und suchenShaderManagerregelmäß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
glXFunktionen erstellt werden. Möglicherweise wird also einWorldEntityerstellt und versucht,ShaderProgramvor 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
EngineKlasse, 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
ShaderPrograms 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 ShaderShaderProgramgelöscht wurde, aber dann ein neuer ihnWorldEntityanfordert. aber ich bin sicher, es gibt einen besseren Weg.
2. Eine bessere Methode
Darum bitte ich hier
WorldEntitys zu konstruieren . Verschiebt das nicht einen Teil des Problems? Denn jetzt muss die WorldFactory-Klasse jeder WolrdEntity das richtige ShaderProgramm übergeben.