RAII und OpenGL Objekte. Wie?
-
In OpenGL gibt es viele Objekte die mit einer glGen*/glCreate* Funktion erstellt und mit glDelete* zerstört werden. Das Handle auf diese Objekte ist i.d.R. nur ein GLuint, also keine Objeke im Sinne der OOP.
Mit dem Thema RAII im Hinterkopf bin ich auf diesen Post gestoßen, dabei bitte die akzeptierte Antwort beachten: http://stackoverflow.com/questions/17161013/raii-wrapper-for-opengl-objects
Der Vorschlag dort ist:
struct VertexArrayObjectTraits //Entsprechende Traits für Texturen, VBOs u.s.w. { typedef GLuint value_type; static value_type Create(); static void Destroy(value_type); }; template<typename T> class OpenGLObject { public: OpenGLObject() : m_obj(T::Create()) {} ~OpenGLObject() {T::Destroy(m_obj);} operator typename T::value_type() {return m_obj;} private: typename T::value_type m_obj; };und dann einfach ein solches Objekt anlegen:
OpenGLObject<VertexArrayObjectTraits> obj;Das klingt super, macht aber eigentlich nur Kopfschmerzen.
Ein solches Objekt darf nicht einfach bedenkenlos kopiert und zerstört werden, jeder unbedachte Destruktoraufruf einer temporären Kopie würde das OpenGL-Objekt unbrauchbar machen. Also eine NoCopyable-Basisklasse geschrieben, Copyconstructor und Zuweisungsoperator privat gemacht, davon abgeleitet und gut.Aber, was kann ich nun mit dem Objekt noch anfangen?
Es reicht ja nicht aus, dass ein solches Objekt erzeugt wird, danach finden immer weitere Schritte statt, die man gerne auslagern möchte um sich die Codeverdoppelung zu ersparen.
Nehmen wir mal Vertex Buffer Objects (das sind einfach Speicherbereiche für Vertexdaten), gemäß dem obigen Ansatz mit Traits:
VertexBufferObjectTraits::value_type VertexBufferObjectTraits::Create() { GLuint buffer; glGenBuffers(1, &buffer); return buffer; } void VertexBufferObjectTraits::Destroy(value_type value) { if(glIsBuffer(value)) glDeleteBuffers(1, &value); }Und dazu möchte man doch gerne eine Art Factory-Funktion, die solche Objekte erstellt und den Buffer beschreibt:
template<typename T> ??? CreateArrayBuffer(const std::vector<T>& vec) { //GLuint buffer; //so wars früher //glGenBuffers(1, &buffer); OpenGLObject<VertexBufferObjectTraits> buffer; glBindBuffer(GL_ARRAY_BUFFER, buffer); glBufferData(GL_ARRAY_BUFFER, sizeof(T) * vec.size(), vec.data(), GL_DYNAMIC_DRAW); return buffer; }Offensichtlich kann man das NonCopyable-Objekt nicht mehr zurückgeben.
Was nun?
Als Lösungsansätze kommen mir nur Dinge wie shared_ptr oder unique_ptr und Move-Semantik in den Sinn, womit ich evtl diese Probleme in den Griff kriegen könnte.Ich habe das Gefühl, je mehr ich solche Prinzipien von modernem C++ befolge, desto mehr Probleme handele ich mir ein nur um diesen Prinzipien überhaupt gerecht zu werden.
Wahrscheinlich verstehe ich die Hintergründe einfach noch nicht genug. Vielleicht kann mich jemand auf die richtige Spur bringen?Vielleicht noch ganz interessant wie es nun dazu kam, dass ich zerstörbare OpenGL-Objekte mit RAII behandeln wollte. Erstmals hatte ich eine Klasse "GeoObject". Diese nimmt im Konstruktor Daten entgegen (z.b. Koordinaten (Position, Textur, Farbe)), erstellt ein Vertex Array Object, mehrere Vertex Buffer für die eigentlichen Daten:
{ GeoObject::GeoObject(const GeoDataSOA& geoDataSOA) : vao(0), vboInd(0), vboPos(0), vboCol(0), vboTex(0) { glGenVertexArrays(1, &vao); glBindVertexArray(vao); positionLength = geoDataSOA.Position.size(); indicesLength = geoDataSOA.Indices.size(); vboPos = CreateArrayBuffer(geoDataSOA.Position); SetAttrib(ShaderAttribute::Vertex, 3); if (geoDataSOA.Color.size() > 0) { vboCol = CreateArrayBuffer(geoDataSOA.Color); SetAttrib(ShaderAttribute::Color, 3); } if (geoDataSOA.Tex.size() > 0) { vboTex = CreateArrayBuffer(geoDataSOA.Tex); SetAttrib(ShaderAttribute::Tex0, 2); } glBindBuffer(GL_ARRAY_BUFFER, 0); vboInd = CreateElementBuffer(geoDataSOA.Indices); glBindVertexArray(0); }Und der Destruktor war dieser:
GeoObject::~GeoObject() { glDeleteVertexArrays(1, &vao); glDeleteBuffers(1, &vboPos); glDeleteBuffers(1, &vboInd); if(glIsBuffer(vboCol)) glDeleteBuffers(1, &vboCol); if(glIsBuffer(vboTex)) glDeleteBuffers(1, &vboTex); }Von eben diesen Lowlevel OpenGL-Objekten, den ganzen GLunits, wollte ich abstrahieren, um sie nicht mehr von Hand mit glDeleteX löschen zu müssen.
Und später dann auf Texturen und anderes ähnlich verwalten.Aber ja wie gesagt, das verursacht nur mehr Probleme
-
Nimm doch einfach unique_ptr, anstatt deine Ressourcenverwalter von Hand zu programmieren. Das macht genau das gleiche, aber garantiert fehlerfrei und sollte mit einem Schlag all deine Probleme lösen.
Ich habe das Gefühl, je mehr ich solche Prinzipien von modernem C++ befolge, desto mehr Probleme handele ich mir ein nur um diesen Prinzipien überhaupt gerecht zu werden.
Wahrscheinlich verstehe ich die Hintergründe einfach noch nicht genug. Vielleicht kann mich jemand auf die richtige Spur bringen?Ich habe irgendwie den Eindruck, dass du immer noch versuchst, C zu programmieren. Zwar nicht das gefürchtete "C mit Klassen", sondern "C mit RAII", aber das ist eben immer noch nicht C++.
-
SeppJ schrieb:
Nimm doch einfach unique_ptr, anstatt deine Ressourcenverwalter von Hand zu programmieren. Das macht genau das gleiche, aber garantiert fehlerfrei und sollte mit einem Schlag all deine Probleme lösen.
Kannst du das bitte etwas ausführen?
Mir ist unique_ptr bekannt, aber offenbar nicht die adäquate Anwendung auf mein Problem.
-
SeppJ schrieb:
Ich habe irgendwie den Eindruck, dass du immer noch versuchst, C zu programmieren. Zwar nicht das gefürchtete "C mit Klassen", sondern "C mit RAII", aber das ist eben immer noch nicht C++.
Nicht C, ich komme aus der C#-Welt.
"C mit RAII" aber kein richtiges C++, nun, bitte verliere noch ein paar Worte dazu.
-
Salzwasser schrieb:
Kannst du das bitte etwas ausführen?
Mir ist unique_ptr bekannt, aber offenbar nicht die adäquate Anwendung auf mein Problem.struct vbo_deleter { using pointer = GLuint; void operator()(pointer p) noexcept { // cleanup } }; std::unique_ptr<GLuint, vbo_deleter> ptr(a_vbo);Edit: Ich hoffe für sowas wirklich auf dieses Proposal o.ä http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2014/n3949.pdf.
-
Die Frage ist, ob sich ein generischer Ansatz ueberhaupt lohnt. Ueblicherweise braucht man eine OpenGL version und muss keinen Wrapper um die gesamte API mit allem legacy-kram schreiben. Das sind aber nur jeweils ein paar verschiedene Objekte, bei OpenGL 3 bleibt man vermutlich unter 10 und findet auch vielleicht mal eine Ausnahme, die sich nicht exakt in gen- und delete-funktion aufteilen laesst.
-
Nathan schrieb:
struct vbo_deleter { using pointer = GLuint; void operator()(pointer p) noexcept { // cleanup } }; std::unique_ptr<GLuint, vbo_deleter> ptr(a_vbo);Konkret so?
struct del { void operator()(GLuint* p) { cout<<"deleter C\n"; glDeleteBuffers(1, p); } }; void trashtest() { GLuint b; glGenBuffers(1, &b); unique_ptr<GLuint, del> ptr(&b); }Die Konstruktion sieht etwas schräg aus.
Oder habe ich was falsch verstanden?