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, abstracting away extension and platform differences, tracking the state for optimum performance and selecting the best available code path for given system.

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 underlying OpenGL objects are created in wrapper class constructor and deleted in wrapper class destructor. Constructing an object using default constructor requires active GL::Context instance. All OpenGL objects are movable (but not copyable), although for performance reasons (and contrary to standard C++11 practice), the moved-from instance does not have any associated OpenGL object and is thus in invalid state. Using instance in moved-from state may result in OpenGL errors being generated, in some cases even application crashes.

Besides the default behavior, it is possible to construct the object without creating the underlying OpenGL object using the NoCreate tag. Constructing the object this way does not require any active context and its state is then equivalent to the moved-from state. It is useful in case you need to construct the object before creating context (such as class members) or if you know you would overwrite it later with another object:

GL::Mesh mesh{NoCreate};
GL::Buffer vertices{NoCreate}, indices{NoCreate};
std::tie(mesh, vertices, indices) = importSomeMesh();

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 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 existing OpenGL object ID into 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 NoCreate constructor, wrap() and release() functions are available for all OpenGL classes except GL::Shader, where wrapping external instances makes less sense.

Note that interaction with third-party OpenGL code as shown above usually needs special attention:

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;
// ...
mesh.draw(someShader);

{
    /* 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) and its predecessor EXT_direct_state_access. These extensions make 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 these extensions are 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()
   - if EXT_direct_state_access is available, calls glTextureStorage2DEXT()
   - 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});