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.

This ar­ti­cle is from 2013. While great care is tak­en to keep in­for­ma­tion up-to-date, please note that not ev­ery­thing in this ar­ti­cle might re­flect cur­rent state of the Mag­num project, ex­ter­nal links might be dead and con­tent might be pre­served in its orig­i­nal form for archival pur­pos­es. Even the ty­pos.

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:

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 ; a . assignFrom ( data ); 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 */ glEnableVertexAttribArray ( SHADER_POSITION ); glEnableVertexAttribArray ( SHADER_NORMAL ); glEnableVertexAttribArray ( SHADER_TEXCOORDS ); glEnableVertexAttribArray ( SHADER_WEIGHT ); 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 { public : 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 }) . rotate ( 35.0_degf ); 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 */ framebuffer . clear ( FramebufferClear :: Color | FramebufferClear :: Depth ); 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.