opengl
Инкапсулирование объектов OpenGL с помощью C ++ RAII
Поиск…
Вступление
Примеры различных способов работы OpenGL-объектов с C ++ RAII.
замечания
Инкапсуляция объектов OpenGL в RAII имеет свои опасности. Наиболее неизбежным является то, что объекты OpenGL связаны с контекстом OpenGL, который их создал. Таким образом, уничтожение объекта C ++ RAII должно выполняться в контексте OpenGL, который разделяет право собственности на объект OpenGL, управляемый этим объектом C ++.
Это также означает, что если все контексты, которые владеют объектом, будут уничтожены, то любые существующие объекты с открытым содержимым, содержащиеся в RAII, будут пытаться уничтожить объекты, которые больше не существуют.
Вы должны предпринять ручные шаги для решения таких проблем контекста, как это.
В C ++ 98/03
Инкапсуляция объекта OpenGL в C ++ 98/03 требует подчинения правилу C ++ в 3. Это означает добавление конструктора копирования, оператора назначения копирования и деструктора.
Однако конструкторы копирования должны логически копировать объект. И копирование объекта OpenGL - это нетривиальное обязательство. Не менее важно то, что пользователь почти не хочет этого делать.
Поэтому мы вместо этого сделаем объект несовместимым:
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 &);
};
Конструктор создаст объект и инициализирует данные объекта буфера. Деструктор уничтожит объект. Объявив конструктор / назначение копии без их определения, компоновщик выдаст ошибку, если какой-либо код попытается вызвать их. И, объявив их закрытыми, только члены BufferObject
смогут даже назвать их.
Обратите внимание, что BufferObject
не сохраняет target
переданную конструктору. Это связано с тем, что объект буфера OpenGL может использоваться с любой целью, а не только с той, с которой она была первоначально создана. Это не похоже на объекты текстуры, которые всегда должны быть привязаны к цели, с которой они были созданы.
Поскольку OpenGL очень зависит от привязки объектов к контексту для различных целей, часто бывает полезно иметь привязку к объекту с расширением RAII. Поскольку разные объекты имеют разные потребности в привязке (некоторые из них имеют целевые объекты, другие - нет), мы должны реализовать их для каждого объекта индивидуально.
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
не копируется, поскольку копирование не имеет смысла. Обратите внимание, что он не сохраняет доступ к BufferObject
он связывает. Это потому, что это не нужно.
В C ++ 11 и более поздних версиях
C ++ 11 предлагает инструменты, которые улучшают функциональность объектов OpenGL, инкапсулированных RAII. Без возможностей C ++ 11, таких как перемещение семантики, такие объекты должны быть динамически распределены, если вы хотите передать их, поскольку они не могут быть скопированы. Поддержка перемещения позволяет им передавать назад и вперед, как обычные значения, но не путем копирования:
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_;
};
Такой тип может быть возвращен функцией:
BufferObject CreateStaticBuffer(GLsizeiptr byteSize) {return BufferObject(GL_ARRAY_BUFFER, byteSize, nullptr, GL_STATIC_DRAW);}
Это позволяет хранить их в своих (неявно перемещаемых) типах:
struct Mesh
{
public:
private:
//Default member initializer.
BufferObject buff_ = CreateStaticBuffer(someSize);
};
Области с привязкой к связующему могут также иметь семантику перемещения, что позволяет вернуть связующее из функций и хранить в стандартных библиотечных контейнерах 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_;
};
Обратите внимание, что объект перемещается конструктивно, но не может быть назначен переносом. Идея заключается в том, чтобы предотвратить повторное связывание привязки буфера с привязкой. После того, как он установлен, единственное, что может его отключить, перемещается из него.