Introducing Magnum, a multiplatform 2D/3D graphics engine

Af­ter near­ly three years of se­mi-pub­lic hy­per­ac­tive de­vel­op­ment, I think it’s the time to re­lease this beast in­to wild. Say hel­lo to Mag­num, mod­u­lar graph­ics en­gine writ­ten in C++11 and OpenGL.

Mag­num start­ed as a sim­ple wrap­per to sim­pli­fy vec­tor/ma­trix op­er­a­tions so I could learn and play with OpenGL API with­out writ­ing too much boil­er­plate code. Over the time it ex­pand­ed in­to ac­tu­al­ly us­able graph­ics en­gine. Its goal is to sim­pli­fy low-lev­el graph­ics de­vel­op­ment and in­ter­ac­tion with OpenGL us­ing re­cent C++11 fea­tures and to ab­stract away plat­form-spe­cif­ic is­sues.

Mag­num is cur­rent­ly port­ed to these plat­forms:

  • OpenGL 2.1 through 4.4, core pro­file func­tion­al­i­ty and mod­ern ex­ten­sions
  • OpenGL ES 2.0, 3.0 and ex­ten­sions to match desk­top OpenGL func­tion­al­i­ty
  • Lin­ux and em­bed­ded Lin­ux (na­tive­ly us­ing GLX/EGL and Xlib or through GLUT or SDL2 tool­kit)
  • Win­dows (through GLUT or SDL2 tool­kit)
  • Google Chrome (through Na­tive Client, both newlib and glibc toolchains are sup­port­ed)

What it of­fers you

Use C++11 to sim­pli­fy com­mon work­flow and OpenGL in­ter­ac­tion

Mag­num makes ex­ten­sive use of C++11. Most of the new fea­tures are used in the in­ter­nals to make the li­brary more pow­er­ful and you can on­ly guess their pres­ence, but the best fea­tures are on ev­ery cor­ner to sim­pli­fy your life.

C++11 list-ini­tial­iza­tion and com­pile-time checks al­low for eas­i­er and safer struc­ture ini­tial­iza­tion. With constexpr you can even do some lim­it­ed vec­tor math at com­pile-time.

Int* data = new Int{2, 5, -1, 10, 0,                          /* using C++03 */
                    3, 53, -60, -27, // oops
                    9, 0, 4, 7, 135};

Math::Matrix<3, 5, Int> a;
Math::Matrix<3, 5, Int> a({2, 5, -1, 10, 0},                  /* using C++11 */
                          {3, 53, -60, -27, 25},
                          {9, 0, 4, 7, 135});

Vari­adic func­tion tem­plates great­ly sim­pli­fy repet­i­tive things and avoid mis­takes, how­ev­er you are not lim­it­ed to do this at com­pile-time on­ly. It is pos­si­ble to do the equiv­a­lent in run-time, but at the cost of more ver­bose code.

/* Shader properties using C++03 and pure OpenGL */
const int SHADER_POSITION = 0; // three-component
const int SHADER_NORMAL = 1; // three-component
const int SHADER_TEXCOORDS = 2; // two-component
const int SHADER_WEIGHT = 3; // one-component

/* Mesh configuration */

glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
int offset = 4238;
glVertexAttribPointer(SHADER_POSITION, 3, GL_FLOAT, GL_FALSE, 40, static_cast<GLvoid*>(offset));
glVertexAttribPointer(SHADER_NORMAL, 3, GL_FLOAT, GL_FALSE, 40, static_cast<GLvoid*>(offset + 12));
glVertexAttribPointer(SHADER_TEXCOORD, 2, GL_FLOAT, GL_FALSE, 40, static_cast<GLvoid*>(offset + 24));
glVertexAttribPointer(SHADER_WEIGHT, 2, GL_FLOAT, GL_FALSE, 40, static_cast<GLvoid*>(offset + 32)); // oops
/* Type-safe shader definition using C++11 and Magnum */
class Shader: public AbstractShaderProgram {
        typedef Attribute<0, Vector3> Position;
        typedef Attribute<1, Vector2> Normal;
        typedef Attribute<2, Vector3> TextureCoordinates;
        typedef Attribute<3, Float> Weight;

    // ...

/* Mesh configuration */
Buffer vertexBuffer;
Mesh mesh;
mesh.addVertexBuffer(vertexBuffer, 4238, Shader::Position(), Shader::Normal(),
    Shader::TextureCoordinates(), Shader::Weight(), 3);

Ini­tial­iz­er lists and us­er-de­fined lit­er­als will save you typ­ing and avoid nasty mis­takes with units in un­ob­tru­sive way:

Object3D object;                                              /* using C++03 */
object.translate(Vector3(1.5f, 0.3f, -1.0f))
    .rotate(35.0f); // this function accepts degrees, right? (it doesn't)
Object3D object;                                              /* using C++11 */
object.translate({1.5f, 0.3f, -1.0f})

Strong­ly typed enums and type-safe Enum­Set class pre­vent hard-to-spot er­rors with im­prop­er enum val­ues and en­able prop­er IDE au­to­com­ple­tion for enu­mer­a­tion val­ues, sav­ing pre­cious time:

/* Using pure OpenGL, the errors are catched at run-time */
glClear(GL_COLOR|GL_DEPTH); // oops
/* Using C++11 and Magnum, the errors are catched at compile-time */

Mag­num us­es RAII prin­ci­ple, has OpenGL state track­ing and trans­par­ent sup­port for EX­T_­di­rec­t_s­tate_ac­cess. With au­to­mat­ic fall­back to core func­tion­al­i­ty for un­sup­port­ed ex­ten­sions it al­lows you to just cre­ate an ob­ject and call a func­tion on it with­out any boil­er­plate code. You don’t need to han­dle any ex­plic­it ini­tial­iza­tion and fi­nal­iza­tion, save and re­store the pre­vi­ous state or both­er about ex­ten­sion avail­abil­i­ty:

GLint texture;                                          /* using pure OpenGL */
glGenTextures(1, &texture);
GLint previous;
glGetIntegerv(GL_TEXTURE_BINDING_2D, &previous);
glBindTexture(GL_TEXTURE_2D, texture);

if(/* ARB_texture_storage supported, faster code path */) {
    glTexStorage2D(GL_TEXTURE_2D, 4, GL_RGBA8, 256, 256);
} else {
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 128, 128, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    glTexImage2D(GL_TEXTURE_2D, 2, GL_RGBA8, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    glTexImage2D(GL_TEXTURE_2D, 3, GL_RGBA8, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

glBindTexture(GL_TEXTURE_2D, previous);

// ...

glDeleteTextures(1, &texture);
Texture2D texture;                                           /* using Magnum */
texture.setStorage(4, TextureFormat::RGBA8, {256, 256});

These fea­tures re­quire com­pil­er with good enough sup­port for C++11. Of­fi­cialy sup­port­ed ones are GCC 4.6+ and Clang 3.1+. There is al­so com­pat­i­bil­i­ty branch with sup­port for GCC 4.4 and 4.5 (and prob­a­bly Vis­ual Stu­dio 2012, when I get to test it). Some­times the miss­ing fea­tures are heav­i­ly worked around, which might case some is­sues, thus this com­pat­i­bil­i­ty is not part of the main­line code.

Mod­u­lar and ex­ten­si­ble scene graph

On top of core li­brary tak­ing care of math and OpenGL there are var­i­ous op­tion­al li­braries, which you can, but don’t have to use. One of them is scene graph im­ple­men­ta­tion for both 2D and 3D scenes. The scene graph is tem­plat­ed on trans­for­ma­tion im­ple­men­ta­tion, thus you are free to use ma­tri­ces, du­al quater­nions, du­al com­plex num­bers or even roll your own trans­for­ma­tion im­ple­men­ta­tion. Ob­jects in scene graph are not in any lin­ear fea­ture hi­er­ar­chy and par­tic­u­lar fea­tures are at­tached to giv­en ob­ject in­stead, ei­ther dy­nam­i­cal­ly or us­ing mul­ti­ple in­her­i­tace. This ap­proach al­lows greater flex­i­bil­i­ty com­pared to lin­ear hi­er­ar­chy and avoids bub­ble-up ef­fect (like hav­ing func­tion for set­ting wheel count on base ob­ject).

You can learn more about scene graph in the doc­u­men­ta­tion.

In­te­gra­tion with oth­er soft­ware, plug­ins for da­ta ma­nip­u­la­tion

Mag­num li­brary it­self is kept light­weight and with­out any ex­ter­nal de­pen­den­cies to make port­ing and us­age in em­bed­ded sys­tems eas­i­er. How­ev­er, in re­al world us­age, you of­ten need the abil­i­ty to im­port da­ta in var­i­ous for­mats. Mag­num has sup­port for both stat­ic and dy­nam­ic plug­ins and con­tains plug­in in­ter­face for im­port­ing mesh­es, im­ages, au­dio files and for do­ing for­mat con­ver­sions. Sep­a­rate plug­in re­pos­i­to­ry con­tains JPEG, PNG, TGA, COL­LA­DA and WAV im­porter plug­ins.

Mag­num has al­so builtin plug­in-based text lay­out­ing and ren­der­ing li­brary. Plug­in repos­i­to­ry con­tains FreeType font en­gine sup­port, Harf­Buzz text lay­outer, raster font sup­port and al­so abil­i­ty to con­vert be­tween font for­mats.

It is of­ten de­sir­able to use ex­ter­nal (math, physics) li­brary. I’m not go­ing to boast, Mag­num’s math li­brary is pret­ty lim­it­ed in com­par­i­son with most oth­er math li­braries. Mag­num pro­vides in­ter­face for con­vert­ing from and to ex­ter­nal rep­re­sen­ta­tion of math­e­mat­ic struc­tures, which in the end is pre­sent­ed to us­er as sim­ple ex­plic­it con­ver­sion. In­te­gra­tion re­pos­i­to­ry con­tains ini­tial in­te­gra­tion of Bul­let Physics li­brary.

Mag­num doesn’t con­tain its own full-fea­tured win­dow and event han­dling ab­strac­tion li­brary, in­stead it is able to hook in­to var­i­ous mul­ti­plat­form tool­kits like GLUT or SDL2 and al­so light­weight plat­form-spe­cif­ic tool­kits such as Xlib with GLX/EGL or PPA­PI.

Ex­ten­sive doc­u­men­ta­tion and ex­am­ples

Doc­u­men­ta­tion is es­sen­tial part of the en­gine. Each mod­ule and class has in­tro­duc­tion­al chap­ter and ex­am­ple us­age, each OpenGL sup­port class pro­vides de­tailed in­for­ma­tion about re­lat­ed OpenGL calls and ex­ten­sion de­pen­dence. There is al­so ex­am­ple re­pos­i­to­ry con­tain­ing ful­ly doc­u­ment­ed ex­am­ples to ease your learn­ing even more. The doc­u­men­ta­tion al­so has a thor­ough guide how to start us­ing Mag­num in your project, pro­vid­ing even ready-to-build boot­strap code.

More fea­tures

There are many more things worth men­tion­ing, you can read through the near­ly ex­haus­tive fea­ture list for more in­for­ma­tion. The project page con­tains al­so ra­tio­nale and de­sign goals.

What it won’t do

Mag­num is de­signed for peo­ple who love cod­ing and stands up­on in­te­gra­tion with ex­ter­nal tools. Don’t ex­pect any GameMak­er-like GUI, vis­ual shaders, builtin ed­i­tors or ded­i­cat­ed IDE. Spe­cial­ized soft­ware will al­ways be bet­ter at that job than any in­te­grat­ed ed­i­tor and this way you can use any tool you want.

Mag­num tries to be mod­u­lar, light­weight and doesn’t want to put any re­stric­tions or lim­i­ta­tions on the us­er. There is no en­gine-spe­cif­ic mesh for­mat or ef­fect frame­work, as it is near­ly im­pos­si­ble to cre­ate a for­mat which will suit all imag­in­able use cas­es.


Mag­num is cur­rent­ly used in one small game and one big­ger, yet un­na­nounced one and the func­tion­al­i­ty is demon­strat­ed in var­i­ous ex­am­ples. See show­case page for im­ages and live ap­pli­ca­tions.

Push The Box

Where can you get it

Be­cause the li­brary is meant to be used by de­vel­op­ers and not end users, it is dis­trib­uted pure­ly as source code, avail­able on GitHub. The doc­u­men­ta­tion is avail­able for on­line view­ing, you can al­so gen­er­ate it di­rect­ly from the source code, see the build­ing doc­u­men­ta­tion for more in­for­ma­tion. Be sure to read al­so the thor­ough Get­ting Start­ed Guide.