opengl
Encapsulation d'objets OpenGL avec C ++ RAII
Recherche…
Introduction
Exemples de différentes manières d’utiliser les objets OpenGL avec C ++ RAII.
Remarques
L'encapsulation RAII des objets OpenGL présente des dangers. Le plus inévitable est que les objets OpenGL sont associés au contexte OpenGL qui les a créés. La destruction d'un objet C ++ RAII doit donc se faire dans un contexte OpenGL qui partage la propriété de l'objet OpenGL géré par cet objet C ++.
Cela signifie également que si tous les contextes qui possèdent l'objet sont détruits, tous les objets OpenGL encapsulés dans RAII existants tenteront de détruire les objets qui n'existent plus.
Vous devez prendre des mesures manuelles pour traiter les problèmes de contexte comme celui-ci.
En C ++ 98/03
L'encapsulation d'un objet OpenGL dans C ++ 98/03 nécessite d'obéir à la règle C ++ de 3. Cela signifie qu'il faut ajouter un constructeur de copie, un opérateur d'attribution de copie et un destructeur.
Cependant, les constructeurs de copie doivent copier logiquement l'objet. Et copier un objet OpenGL est une entreprise non triviale. Tout aussi important, c'est presque certainement quelque chose que l'utilisateur ne souhaite pas faire.
Nous allons donc rendre l'objet non copiable:
class BufferObject
{
public:
BufferObject(GLenum target, GLsizeiptr size, const void *data, GLenum usage)
{
glGenBuffers(1, &object_);
glBindBuffer(target, object_);
glBufferData(target, size, data, usage);
glBindBuffer(target, 0);
}
~BufferObject()
{
glDeleteBuffers(1, &object_);
}
//Accessors and manipulators
void Bind(GLenum target) const {glBindBuffer(target, object_);}
GLuint GetObject() const {return object_;}
private:
GLuint object_;
//Prototypes, but no implementation.
BufferObject(const BufferObject &);
BufferObject &operator=(const BufferObject &);
};
Le constructeur va créer l'objet et initialiser les données de l'objet tampon. Le destructeur détruira l'objet. En déclarant le constructeur / affectation de copie sans les définir, le lieur génère une erreur si un code tente de les appeler. Et en les déclarant privés, seuls les membres de BufferObject
pourront même les appeler.
Notez que BufferObject
ne conserve pas la target
transmise au constructeur. En effet, un objet tampon OpenGL peut être utilisé avec n'importe quelle cible, pas seulement celle avec laquelle il a été initialement créé. Ceci est différent des objets de texture, qui doivent toujours être liés à la cible avec laquelle ils ont été initialement créés.
Comme OpenGL est très dépendant de la liaison d'objets au contexte à des fins diverses, il est souvent utile d'avoir également une liaison d'objets de type RAII. Étant donné que différents objets ont des besoins de liaison différents (certains ont des cibles, d'autres non), nous devons en implémenter un pour chaque objet individuellement.
class BindBuffer
{
public:
BindBuffer(GLenum target, const BufferObject &buff) : target_(target)
{
buff.Bind(target_);
}
~BindBuffer()
{
glBindBuffer(target_, 0);
}
private:
GLenum target_;
//Also non-copyable.
BindBuffer(const BindBuffer &);
BindBuffer &operator=(const BindBuffer &);
};
BindBuffer
n'est pas copiable, car la copier n'a aucun sens. Notez qu'il ne conserve pas l'accès au BufferObject
qu'il lie. C'est parce que c'est inutile.
En C ++ 11 et versions ultérieures
C ++ 11 offre des outils qui améliorent les fonctionnalités des objets OpenGL encapsulés dans RAII. Sans les fonctionnalités C ++ 11 telles que la sémantique de déplacement, ces objets devraient être dynamiquement alloués si vous voulez les faire circuler, car ils ne peuvent pas être copiés. Le support de déplacement leur permet de passer comme des valeurs normales, mais pas en les copiant:
class BufferObject
{
public:
BufferObject(GLenum target, GLsizeiptr size, const void *data, GLenum usage)
{
glGenBuffers(1, &object_);
glBindBuffer(target, object_);
glBufferData(target, size, data, usage);
glBindBuffer(target, 0);
}
//Cannot be copied.
BufferObject(const BufferObject &) = delete;
BufferObject &operator=(const BufferObject &) = delete;
//Can be moved
BufferObject(BufferObject &&other) noexcept : object_(other.Release())
{}
//Self-assignment is OK with this implementation.
BufferObject &operator=(BufferObject &&other) noexcept
{
Reset(other.Release());
}
//Destroys the old buffer and claims ownership of a new buffer object.
//It's OK to call glDeleteBuffers on buffer object 0.
GLuint Reset(GLuint object = 0)
{
glDeleteBuffers(1, &object_);
object_ = object;
}
//Relinquishes ownership of the object without destroying it
GLuint Release()
{
GLuint ret = object_;
object_ = 0;
return ret;
}
~BufferObject()
{
Reset();
}
//Accessors and manipulators
void Bind(GLenum target) const {glBindBuffer(target, object_);}
GLuint GetObject() const {return object_;}
private:
GLuint object_;
};
Un tel type peut être renvoyé par une fonction:
BufferObject CreateStaticBuffer(GLsizeiptr byteSize) {return BufferObject(GL_ARRAY_BUFFER, byteSize, nullptr, GL_STATIC_DRAW);}
Ce qui vous permet de les stocker dans vos propres types (implicitement en déplacement uniquement):
struct Mesh
{
public:
private:
//Default member initializer.
BufferObject buff_ = CreateStaticBuffer(someSize);
};
Une classe de classeur de portée peut également avoir une sémantique de déplacement, permettant ainsi au classeur d'être renvoyé par des fonctions et stocké dans des conteneurs de bibliothèque standard C ++:
class BindBuffer
{
public:
BindBuffer(GLenum target, const BufferObject &buff) : target_(target)
{
buff.Bind(target_);
}
//Non-copyable.
BindBuffer(const BindBuffer &) = delete;
BindBuffer &operator=(const BindBuffer &) = delete;
//Move-constructible.
BindBuffer(BindBuffer &&other) noexcept : target_(other.target_)
{
other.target_ = 0;
}
//Not move-assignable.
BindBuffer &operator=(BindBuffer &&) = delete;
~BindBuffer()
{
//Only unbind if not moved from.
if(target_)
glBindBuffer(target_, 0);
}
private:
GLenum target_;
};
Notez que l'objet est déplaçable mais non déplaçable. L'idée est d'empêcher la reliure d'une liaison de tampon de portée. Une fois qu'il est défini, la seule chose qui peut le désélectionner est d'être déplacé.