opengl
Kapsla in OpenGL-objekt med C ++ RAII
Sök…
Introduktion
Exempel på olika sätt att få OpenGL-objekt att fungera med C ++ RAII.
Anmärkningar
RAII-kapsling av OpenGL-objekt har faror. Det mest oundvikliga är att OpenGL-objekt är associerade med OpenGL-kontexten som skapade dem. Så förstörelsen av ett C ++ RAII-objekt måste göras i ett OpenGL-sammanhang som delar ägandet till OpenGL-objektet som hanteras av det C ++ -objektet.
Detta innebär också att om alla sammanhang som äger objektet förstörs, så kommer alla befintliga RAII-inkapslade OpenGL-objekt att försöka förstöra objekt som inte längre finns.
Du måste vidta manuella steg för att hantera sammanhangsfrågor som detta.
I C ++ 98/03
Inkapsling av ett OpenGL-objekt i C ++ 98/03 kräver lydelse av C ++-regeln från 3. Detta innebär att man lägger till en kopieringskonstruktör, en kopieringsuppdragsoperatör och destruktor.
Kopieringskonstruktörer bör dock logiskt kopiera objektet. Och att kopiera ett OpenGL-objekt är ett icke-trivialt åtagande. Lika viktigt är det nästan säkert något som användaren inte vill göra.
Så vi kommer istället att göra objektet inte kopierbart:
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 &);
};
Konstruktören skapar objektet och initialiserar buffertobjektets data. Förstöraren förstör föremålet. Genom att deklarera kopieringskonstruktören / uppdraget utan att definiera dem, kommer länken att ge ett fel om någon kod försöker ringa dem. Och genom att förklara dem privata kan bara medlemmar av BufferObject
och med kunna ringa dem.
Observera att BufferObject
inte behåller target
skickas till konstruktören. Det beror på att ett OpenGL-buffertobjekt kan användas med alla mål, inte bara det som det ursprungligen skapades med. Detta är till skillnad från texturobjekt, som alltid måste vara bundna till det mål de ursprungligen skapades med.
Eftersom OpenGL är mycket beroende av att binda objekt till sammanhanget för olika ändamål är det ofta användbart att ha bindningsobjekt i RAII-stil också. Eftersom olika objekt har olika bindande behov (vissa har mål, andra inte), måste vi implementera ett för varje objekt individuellt.
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
är inte kopierbar eftersom det inte är vettigt att kopiera. Observera att det inte behåller åtkomst till BufferObject
det binder. Det beror på att det är onödigt.
I C ++ 11 och senare
C ++ 11 erbjuder verktyg som förbättrar funktionaliteten hos RAII-inkapslade OpenGL-objekt. Utan C ++ 11-funktioner som flytta semantik, skulle sådana objekt måste tilldelas dynamiskt om du vill skicka dem runt, eftersom de inte kan kopieras. Flyttstöd gör det möjligt att skicka fram och tillbaka som normala värden, men inte genom att kopiera:
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_;
};
En sådan typ kan returneras av en funktion:
BufferObject CreateStaticBuffer(GLsizeiptr byteSize) {return BufferObject(GL_ARRAY_BUFFER, byteSize, nullptr, GL_STATIC_DRAW);}
Som gör att du kan lagra dem i dina egna (implicit flyttbara) typer:
struct Mesh
{
public:
private:
//Default member initializer.
BufferObject buff_ = CreateStaticBuffer(someSize);
};
En scoped-bindemedelsklass kan också ha flyttande semantik, vilket gör att bindemedlet kan returneras från funktioner och lagras i C ++ -bibliotekets containrar:
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_;
};
Observera att objektet är rörligt konstruerbart men inte flyttbart. Tanken med detta är att förhindra återuppbörd av en scoped buffertbindning. När den är klar flyttas det enda som kan återställa det.