Feature guide » OpenGL wrapping layer

Overview of the base OpenGL wrapper API.

The purpose of the GL library is to simplify interaction with the OpenGL API using type-safe C++11 features and RAII, abstracting away extension and platform differences, tracking the state for optimum performance and selecting the best available code path for given driver.

Magnum provides wrappers for most native OpenGL objects like buffers, textures, meshes, queries, transform feedback objects, shaders etc., but makes it possible to use raw GL calls or combine Magnum with third-party OpenGL libraries if the user wants to.

OpenGL object wrapper instances

By default, all wrapper classes follow RAII – underlying OpenGL objects are created in the class constructor and deleted in the wrapper class destructor. All OpenGL objects are movable (but not copyable) and moves are destructive — the moved-from instance does not have any associated OpenGL object and is thus in an invalid state. Calling anything on instances in a moved-from state may thus result in OpenGL errors being generated, in some cases even application crashes.

If you need to preserve the underlying OpenGL object after destruction, you can call release(). It returns ID of the underlying object, the instance is then equivalent to the moved-from state and you are responsible for proper deletion of the returned OpenGL object (note that it is possible to just query ID of the underlying without releasing it using id()). It is also possible to do the opposite — wrapping an existing OpenGL object ID into a Magnum object instance using wrap():

/* Transferring the instance to external library */
{
    GL::Buffer buffer;
    buffer.setData(someData, GL::BufferUsage::StaticDraw);
    GLuint id = buffer.release();
    externalLib.setSomeBuffer(id); /* The library is responsible for deletion */
}

/* Acquiring an instance from external library */
{
    GLuint id = externalLib.someBuffer();
    GL::Buffer buffer = GL::Buffer::wrap(id, GL::ObjectFlag::DeleteOnDestruction);
    /* The buffer instance now handles deletion */
}

The wrap() and release() functions are available for all OpenGL classes except for GL::Shader, instances of which are rather short-lived and thus wrapping external instances makes less sense.

Delayed context creation and NoCreate constructors

Constructing a Magnum GL object requires an active GL::Context instance. By default, this instance is automatically created by Platform::*Application constructors, however if you use delayed context creation or are directly interfacing with custom platform toolkits, this is not the case. If OpenGL functionality gets called before the GL::Context instance is created (or after it has been destroyed), you may end up with the following assertion:

GL::Context::current(): no current context

In the common case of delayed context creation, this problem can be circumvented by constructing the OpenGL objects using the NoCreate tag first and populating them with live instances once the context is ready. For example:

class MyApplication: public Platform::Application {
    

    private:
        /* Placeholders without an underlying GL object */
        GL::Mesh _mesh{NoCreate};
        Shaders::PhongGL _shader{NoCreate};
        
};

MyApplication::MyApplication(const Arguments& arguments):
    Platform::Application{arguments, NoCreate}
{
    
    create();

    /* GL context is ready, now it's safe to populate the GL objects */
    _mesh = GL::Mesh{};
    _shader = Shaders::PhongGL{};
}

Please note that objects constructed using the NoCreate tag are equivalent to the moved-from state, and thus again calling anything on these may result in OpenGL errors being generated or even application crashes — you're responsible for correctly initializing the objects before use. If you are fine with trading some overhead for extra safety checks, wrap the objects in an Containers::Optional instead of using the NoCreate constructor.

The NoCreate constructor is available for all OpenGL classes, including builtin shaders.

State tracking and interaction with third-party code

It is possible (and encouraged) to combine Magnum with third-party libraries or even raw OpenGL calls — trying out features that are not yet implemented in Magnum, using some specialized GUI library etc. But bear in mind that in order to improve performance and avoid redundant state changes, Magnum internally tracks OpenGL state such as currently bound objects, activated renderer features etc. When combining Magnum with third-party code, the internal state tracker may get confused and you need to reset it using GL::Context::resetState():

GL::Buffer buffer;
GL::Mesh mesh;
// ...
someShader.draw(mesh);

{
    /* Entering a section with 3rd-party OpenGL code -- clean up all state that
       could cause accidental modifications of our objects from outside */
    GL::Context::current().resetState(GL::Context::State::EnterExternal);

    /* Raw OpenGL calls */
    glBindBuffer(GL_ARRAY_BUFFER, buffer.id());
    glBufferStorage(GL_ARRAY_BUFFER, 32768, nullptr, GL_MAP_READ_BIT|GL_MAP_WRITE_BIT);
    // ...

    /* Exiting a section with 3rd-party OpenGL code -- reset our state tracker */
    GL::Context::current().resetState(GL::Context::State::ExitExternal);
}

/* Use the buffer through Magnum again */
auto data = buffer.map(0, 32768, GL::Buffer::MapFlag::Read|GL::Buffer::MapFlag::Write);
// ...

Note that by design it's not possible to reset all state touched by Magnum to previous values — it would involve impractically large amount of queries and state switches with serious performance impact. It's thus expected that third-party code either does no state tracking or provides similar means to reset their state tracker (for example Qt has QQuickWindow::resetOpenGLState() that's advised to call before giving the control back to Qt).

Extension-dependent functionality

While the majority of Magnum API stays the same on all platforms and driver capabilities, large portion of the functionality needs to be realized under the hood using various different OpenGL API calls based on available extensions. If required extension is not available, there are two possible outcomes — either given API is simply not available or it is emulated using older functionality.

In the first case, to avoid performance overhead, Magnum does not check that you use only the APIs that are implemented in the driver — you are expected to do the checks. Documentation of each type, function and enum value explicitly states whether the functionality is available everywhere or whether particular GL version/extension is required. The information is also aggregated on Version and extension requirements documentation page. Use GL::Context::isVersionSupported() or GL::Context::isExtensionSupported():

GL::TextureFormat format;
if(GL::Context::current().isExtensionSupported<GL::Extensions::ARB::depth_buffer_float>())
    format = GL::TextureFormat::DepthComponent32F;
else
    format = GL::TextureFormat::DepthComponent24;

Some functionality can be emulated by Magnum — it detects available extensions and selects best possible code path for optimal performance. On startup, the application prints list of extensions that were used to improve the default functionality. The most prominent feature is ARB_direct_state_access (part of OpenGL 4.3). This extension makes it possible to modify OpenGL objects without explicitly binding them, reducing the amount of needed API calls. Magnum API is designed around direct state access as it is far easier to use and less error-prone, but if this extension is not available, the functionality is emulated through classic bind-to-edit approach. Other examples of extension-dependent functionality is debug output which is simply no-op when required extensions are not available, GL::Texture::setStorage() emulation on platforms that don't support it etc. The goal is to abstract away the (mostly unimportant) differences for easier porting.

GL::Texture2D texture;

/* - on OpenGL 4.5+/ARB_direct_state_access this calls glTextureStorage2D()
   - on OpenGL 4.2+/ARB_texture_storage and OpenGL ES 3.0+ calls glTexStorage2D()
   - on OpenGL ES 2.0 with EXT_texture_storage calls glTexStorage2DEXT()
   - otherwise emulated using a sequence of four glTexImage2D() calls */
texture.setStorage(4, GL::TextureFormat::RGBA8, {256, 256});