Szukaj…


Wprowadzenie

Przykłady różnych sposobów współpracy obiektów OpenGL z C ++ RAII.

Uwagi

Hermetyzacja obiektów OpenGL przez RAII wiąże się z niebezpieczeństwami. Najbardziej nieuniknione jest to, że obiekty OpenGL są powiązane z kontekstem OpenGL, który je utworzył. Tak więc zniszczenie obiektu RAII C ++ musi odbywać się w kontekście OpenGL, który dzieli własność obiektu OpenGL zarządzanego przez ten obiekt C ++.

Oznacza to również, że jeśli wszystkie konteksty, które są właścicielem obiektu, zostaną zniszczone, wszelkie istniejące obiekty OpenGL w obudowie RAII spróbują zniszczyć obiekty, które już nie istnieją.

Musisz podjąć ręczne kroki, aby poradzić sobie z takimi problemami kontekstowymi.

W C ++ 98/03

Hermetyzowanie obiektu OpenGL w C ++ 98/03 wymaga przestrzegania reguły C ++ wynoszącej 3. Oznacza to dodanie konstruktora kopii, operatora przypisania kopii i destruktora.

Jednak konstruktory kopiowania powinny logicznie kopiować obiekt. A kopiowanie obiektu OpenGL jest nietrywialnym przedsięwzięciem. Co równie ważne, prawie na pewno jest to coś, czego użytkownik nie chce robić.

Zamiast tego sprawimy, że obiekt nie będzie można kopiować:

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 &);
};

Konstruktor utworzy obiekt i zainicjuje dane obiektu bufora. Destruktor zniszczy obiekt. Deklarując konstruktor / przypisanie kopii bez ich definiowania, linker wyświetli błąd, jeśli jakiś kod spróbuje je wywołać. A uznając ich za prywatnych, tylko członkowie BufferObject będą mogli do nich zadzwonić.

Zauważ, że BufferObject nie zachowuje target przekazanego do konstruktora. Wynika to z faktu, że obiekt bufora OpenGL może być używany z dowolnym celem, a nie tylko tym, z którego pierwotnie utworzono. W przeciwieństwie do obiektów tekstur, które zawsze muszą być powiązane z celem, z którym zostały pierwotnie utworzone.

Ponieważ OpenGL jest bardzo zależny od wiązania obiektów do kontekstu do różnych celów, często przydatne jest również powiązanie obiektów o zasięgu RAII. Ponieważ różne obiekty mają różne potrzeby wiązania (niektóre mają cele, inne nie), musimy zaimplementować po jednym dla każdego obiektu indywidualnie.

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 nie może być kopiowany, ponieważ kopiowanie nie ma sensu. Zauważ, że nie zachowuje on dostępu do obiektu BufferObject który wiąże. Jest tak, ponieważ jest to niepotrzebne.

W C ++ 11 i nowszych

C ++ 11 oferuje narzędzia, które zwiększają funkcjonalność obiektów OpenGL w obudowie RAII. Bez funkcji C ++ 11, takich jak semantyka przenoszenia, takie obiekty musiałyby być dynamicznie przydzielane, jeśli chcesz je przekazywać, ponieważ nie można ich skopiować. Obsługa przenoszenia umożliwia przekazywanie ich tam iz powrotem jak normalne wartości, ale nie przez kopiowanie:

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_;
};

Taki typ może zostać zwrócony przez funkcję:

BufferObject CreateStaticBuffer(GLsizeiptr byteSize) {return BufferObject(GL_ARRAY_BUFFER, byteSize, nullptr, GL_STATIC_DRAW);}

Co pozwala przechowywać je we własnych typach (domyślnie tylko ruch):

struct Mesh
{
public:
private:
    //Default member initializer.
    BufferObject buff_ = CreateStaticBuffer(someSize);
};

Klasa spoiwa o zasięgu może również mieć semantykę przenoszenia, co umożliwia zwracanie spoiwa z funkcji i przechowywanie w standardowych kontenerach bibliotek 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_;
};

Zauważ, że obiekt jest ruchomy, ale nie można go przypisać. Chodzi o to, aby zapobiec ponownemu wiązaniu powiązanego bufora o określonym zasięgu. Po ustawieniu jedyną rzeczą, która może go rozbroić, jest przenoszony.



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow