With near­ly three years of de­vel­op­ment since the pre­vi­ous re­lease, I’m ex­cit­ed to an­nounce the re­lease of Mag­num 2018.02. Lots of things hap­pened, so I’ll fo­cus on the most prom­i­nent fea­tures, for a de­tailed list fol­low the changel­og links at the end. This re­lease al­so in­tro­duces a new ver­sion­ing scheme. Orig­i­nal­ly this was meant to be 1.0, but af­ter much thought I de­cid­ed to go with year/month in­stead, as it fits more in­to the rolling re­lease phi­los­o­phy of Mag­num.

Win­dows im­prove­ments Vis­ual Stu­dio 2015, 2017, Win­dows Store/Phone, AN­GLE, Vcp­kg Win­dows sup­port did a huge leap for­ward com­pared to the pre­vi­ous ver­sion. Thanks to big im­prove­ments in C++11 sup­port in re­cent Vis­ual Stu­dio ver­sions it’s now pos­si­ble to build Mag­num with MSVC 2015 and 2017 and al­so tar­get the UWP plat­form us­ing the AN­GLE OpenGL-to-D3D trans­la­tion lay­er. On the oth­er hand, sup­port for MSVC 2013 and MinG­W32 (the non-w64 ver­sion) was dropped to sim­pli­fy main­te­nance ef­forts. To make de­vel­op­ment and de­pen­den­cy han­dling on Win­dows even eas­i­er, Mag­num now has Vcp­kg pack­ages, which al­low you to in­stall it to­geth­er with all de­pen­den­cies us­ing a sin­gle com­mand: vcpkg install magnum Pack­ages in­stalled us­ing Vcp­kg can be used straight away in Vis­ual Stu­dio — all you need to do is to #include the head­ers you want, the buildsys­tem will do all need­ed li­brary link­ing and set­up be­hind the scenes au­to­mat­i­cal­ly. (Cool, isn’t it?) It’s of course al­so pos­si­ble to use Vcp­kg-in­stalled pack­ages with CMake. See the doc­u­men­ta­tion for de­tails. UTF-8 ev­ery­where Among oth­er no­table things is im­proved Uni­code sup­port on Win­dows. In line with the UTF-8 Man­i­fes­to, all Mag­num APIs were de­signed to ex­pect strings and paths en­cod­ed in UTF-8. This is now work­ing prop­er­ly al­so on Win­dows — as long as you use Mag­num APIs such as Util­i­ty::Di­rec­to­ry or Trade::Ab­strac­tIm­porter to ac­cess the filesys­tem, Win­dows wide-char APIs will be used be­hind the scenes to en­sure prop­er path en­cod­ing.

mac­OS im­prove­ments, iOS port Mag­num is be­ing used in a project that’s tight­ly cou­pled to Ap­ple plat­forms, which means the code now runs on iOS as well. Plat­form::Sdl2Ap­pli­ca­tion was up­dat­ed for Reti­na sup­port and oth­er iOS-spe­cif­ic fea­tures. There’s al­so a new Plat­form::Win­dow­lessIos­Ap­pli­ca­tion that’s use­ful for run­ning head­less OpenGL tests on iOS. The Test­Suite li­brary now pro­vides in­te­gra­tion with Xcode and XCTest so you can run tests di­rect­ly from the IDE. The cor­rade_ad­d_test() macro is al­so able to do all the nec­es­sary boil­er­plate to al­low you to run your tests di­rect­ly on an iOS de­vice or sim­u­la­tor sim­ply by run­ning CMake ctest from the com­mand-line. To make life eas­i­er for mac­OS users, there are now Home­brew pack­ages that you can in­stall sim­ply by typ­ing brew install mosra/magnum/magnum You can al­so add all Mag­num pack­ages as a tap, see the doc­u­men­ta­tion for more in­for­ma­tion. We’re ful­ly aware of Ap­ple de­ci­sion to aban­don OpenGL sup­port and push­ing for Met­al in­stead. This will be, along with Vulkan, among the main top­ics for the fol­low­ing Mag­num re­leas­es.

Broad­er test­ing ca­pa­bil­i­ties Mag­num was in­volved in a project that was all about pro­cess­ing of im­age and spa­tial da­ta. Thanks to that, the Test­Suite li­brary re­ceived loads of im­prove­ments to make au­to­mat­ed test­ing a breeze. The test out­put is now col­ored to make it eas­i­er to spot fail­ures and it gained bench­mark­ing ca­pa­bil­i­ties so you can com­pare how your al­go­rithms per­form against base­line im­ple­men­ta­tions — us­ing CPU time, wall clock time, in­struc­tion counter or any cus­tom mea­sured quan­ti­ty such as amount of al­lo­cat­ed mem­o­ry. Starting InvSqrtBenchmark with 4 test cases... BENCH [ 1 ] 8.24 ± 0.19 ns naive()@499x1000000 (wall time) BENCH [ 2 ] 8.27 ± 0.19 ns naive()@499x1000000 (CPU time) BENCH [ 3 ] 0.31 ± 0.01 ns fast()@499x1000000 (wall time) BENCH [ 4 ] 0.31 ± 0.01 ns fast()@499x1000000 (CPU time) Finished InvSqrtBenchmark with 0 errors out of 0 checks. Be­sides the al­ready men­tioned iOS test­ing, there are sim­i­lar im­prove­ments for An­droid — the cor­rade_ad­d_test() can em­ploy adb to up­load the test ex­e­cutable to­geth­er with all bun­dled files to a de­vice or em­u­la­tor, run it there and re­trieve the re­sults just as if you would be run­ning the tests on your lo­cal ma­chine. The test suite is now able to han­dle in­stanced tests (or, in oth­er words, da­ta-driv­en tests). Lots of at­ten­tion was put in­to fuzzy com­par­i­son — from sim­ple nu­mer­ic com­par­i­son us­ing Test­Suite::Com­pare::Around to com­par­ing im­age da­ta to a ground truth with er­ror thresh­olds us­ing De­bug­Tools::Com­pareIm­age. Be­cause prop­er vi­su­al­iza­tion of large da­ta is es­sen­tial for pro­duc­tiv­i­ty, the lat­ter is able to print ASCII art vi­su­al­iza­tion of the dif­fer­ence so you can see what’s wrong di­rect­ly from your CI log: Starting ProcessingTest with 1 test cases... FAIL [ 1 ] process() at …/debugtools-compareimage.cpp on line 77 Images actual and expected have max delta above threshold, actual 189 but at most 170 expected. Mean delta 13.5776 is below threshold 96. Delta image: | | | | | ~ 8070D NMN 8$ZD 7 | | ? I0 : :++~. . I0Z | | 7 I ? $D8ZZ0 D Z8 , +? | | ~+ + I , 7 N ZZ$ | | : ~ | | . . | | , : | | ~. +. + ID8 ?. | | ?. . Z0 : + 0I : 7 | | . $$ . ~ D8$Z0DZ . = Z + | | = 8$DI =,. .:+ ZDI$ | | :7 0D NMN D$ +. | | | | | Top 10 out of 66 pixels above max/mean threshold: [16,5] #000000ff, expected #fcfcfcff (Δ = 189 ) [16,27] #fbfbfbff, expected #000000ff (Δ = 188.25 ) [15,27] #f2f2f2ff, expected #000000ff (Δ = 181.5 ) [17,5] #000000ff, expected #f1f1f1ff (Δ = 180.75 ) [15,5] #000000ff, expected #efefefff (Δ = 179.25 ) [17,27] #eeeeeeff, expected #000000ff (Δ = 178.5 ) [22,20] #000000ff, expected #e7e7e7ff (Δ = 173.25 ) [18,23] #060606ff, expected #eaeaeaff (Δ = 171 ) [18,9] #e5e5e5ff, expected #040404ff (Δ = 168.75 ) [21,26] #efefefff, expected #0f0f0fff (Δ = 168 ) Finished ProcessingTest with 1 errors out of 1 checks. Base class for tests re­quir­ing OpenGL con­text is now avail­able in a ded­i­cat­ed OpenGLTester li­brary and it gained sup­port for GPU time bench­marks. For eas­i­er test­ing on OpenGL ES and We­bGL, there are now De­bug­Tools::buf­fer­Sub­Da­ta() and De­bug­Tools::tex­ture­SubIm­age() helper util­i­ties that sup­ple­ment the lack of Buf­fer::da­ta() and Tex­ture::im­age() on those plat­forms. To en­sure sta­bil­i­ty and make main­te­nance eas­i­er, Mag­num and all its li­braries are now al­so com­piled and test­ed with code cov­er­age on all sup­port­ed plat­forms — see the Build Sta­tus page for the whole test­ing ma­trix. This al­so means that ev­ery sub­mit­ted pull re­quest gets au­to­mat­i­cal­ly test­ed for re­gres­sions, stream­lin­ing the whole re­view process. There’s much more to men­tion re­gard­ing test­ing — it’ll be a part of a more de­tailed blog post in the fu­ture, stay tuned!

As­set man­age­ment im­prove­ments A large new fea­ture is sup­port for com­pressed im­ages — load­ing them from files, up­load­ing and down­load­ing them from tex­tures; plus there are new in­ter­faces in Trade::Ab­strac­tIm­age­Con­vert­er ready for in­te­grat­ing var­i­ous GPU com­pres­sion li­braries. To­geth­er with com­pressed im­ages Mag­num gained sup­port for var­i­ous pix­el stor­age op­tions in the Pix­el­Stor­age class to al­low di­rect ma­nip­u­la­tion of im­age sub-rec­tan­gles, with fur­ther con­ve­nience fea­tures such as Image::slice() planned for the next re­leas­es. There’s al­so a new util­i­ty called mag­num-im­age­con­vert­er that sim­ply ex­pos­es all ex­ist­ing *Im­porter and *Im­age­Con­vert­er plug­ins on a com­mand line. To­geth­er with a new Any­Im­age­Con­vert­er plug­in it’s able to au­tode­tect source and des­ti­na­tion file for­mats based on ex­ten­sion, so you can eas­i­ly use it in your pipe­line for da­ta con­ver­sion, for ex­am­ple: magnum-imageconverter image.bmp image.png Many ex­ter­nal con­tri­bu­tions went in­to as­set man­age­ment and con­ver­sion — you can now use the As­simpIm­porter plug­in to load about 40 new scene for­mats us­ing the As­simp li­brary; the Dev­Il­Im­ageIm­porter plug­in us­es Dev­IL to load 40 new im­age for­mats. Ini­tial work went in­to cam­era and light prop­er­ty im­port, with sup­port in the As­simp and OpenGEX im­porter. Among the oth­ers there’s a pos­si­bil­i­ty to load DXT-com­pressed tex­tures us­ing DdsIm­porter or con­vert im­ages to PNG and EXR us­ing PngIm­age­Con­vert­er and MiniExrIm­age­Con­vert­er. The StbIm­age­Con­vert­er can now use stb_im­age_write to out­put HDR and BMP for­mats in ad­di­tion to PNG.

Full con­trol over ini­tial­iza­tion and own­er­ship While all types in Mag­num are by de­fault ini­tial­ized to a de­fined val­ue, it’s now pos­si­ble to over­ride the be­hav­ior for greater flex­i­bil­i­ty. For ex­am­ple if you will be over­writ­ing all val­ues in a con­tain­er any­way; to avoid un­nec­es­sary mem­o­ry ze­ro­ing in­struc­tions in a tight loop; or to de­fer OpenGL ob­ject ini­tial­iza­tion to a point where a con­text is ready with­out in­tro­duc­ing ad­di­tion­al in­di­rec­tion us­ing point­ers or op­tion­al ob­jects: /* Generate grid positions */ Containers :: Array < Vector3 > positions { Containers :: NoInit , 16 * 16 }; for ( auto & i : positions ) i = ...; /* Zero-init the matrix instead of setting it to identity */ Matrix3x3 m { Math :: ZeroInit }; /* Defer buffer initialization for later when GL context is ready */ Buffer buffer { NoCreate }; // ... buffer = Buffer {}; To­geth­er with this it’s now pos­si­ble to trans­fer own­er­ship of all un­der­ly­ing re­sources — wrap­ping ex­ter­nal­ly al­lo­cat­ed ar­rays and pro­vid­ing cus­tom deleters, or, on the oth­er hand, us­ing Con­tain­ers::Ar­ray::re­lease() to re­lease own­er­ship of the al­lo­cat­ed mem­o­ry. There are al­so two new class­es, Con­tain­ers::Stati­cAr­ray and Con­tain­ers::Stati­cAr­rayView, for han­dling stack-al­lo­cat­ed, com­pile-time-sized ar­rays and views on them. New APIs like Con­tain­ers::ar­ray­Cast() or Con­tain­ers::ar­ray­Size() pro­vide fur­ther con­ve­nience util­i­ties: /* custom allocator functions */ char * allocate ( std :: size_t ); void deallocate ( char * , std :: size_t ); // ... /* Allocate the data and wrap them in a RAII container */ Containers :: Array < char > data { allocate ( 128 ), 128 , []( char * data , std :: size_t size ) { deallocate ( data , size ); }}; /* The allocated array is in fact 16 four-component float vectors */ Containers :: ArrayView < Vector4 > vectors = Containers :: arrayCast < Vector4 > ( data ); for ( Vector4 & i : vectors ) // ...

Math li­brary ad­di­tions The Math li­brary re­ceived a class rep­re­sent­ing Bezi­er curves and a Frus­tum struc­ture. The Math::Ge­om­e­try::In­ter­sec­tion names­pace is ex­tend­ed with frus­tum culling al­go­rithms and fur­ther ad­di­tions are al­ready be­ing worked on. There’s a new Half type for rep­re­sent­ing 16-bit half-float num­bers. It’s just a stor­age type, pro­vid­ing con­ver­sions from and to float types. In ad­di­tion to the ex­ist­ing _deg / _rad lit­er­als there is a new _h lit­er­al for half-floats and var­i­ous lit­er­als for en­ter­ing hexa­dec­i­mal RGB(A) col­ors in lin­ear RGB or sRGB: Color3ub a = 0x33b27f_rgb ; // {0x33, 0xb2, 0x7f} Color4 c = 0x33b27fcc_srgbaf ; // {0.0331048f, 0.445201f, 0.212231f, 0.8f} There’s much more added for more con­ve­nience and bet­ter fea­ture par­i­ty with GLSL, check out the com­plete changel­og at the end of the ar­ti­cle.

Bet­ter in­ter­op­er­abil­i­ty with 3rd-par­ty code One of de­sign goals of Mag­num is to be a col­lec­tion of use­ful non-in­tru­sive tools that works well with third-par­ty li­braries — not be­ing a big mono­lith­ic en­gine that takes over ev­ery­thing and forces a par­tic­u­lar work­flow. With this and the fol­low­ing re­leas­es this de­sign goal is be­ing pushed fur­ther than ev­er be­fore. One com­mon case is that Mag­num is not the on­ly li­brary ac­cess­ing the OpenGL con­text — for ex­am­ple it’s on­ly tak­ing care of da­ta vi­su­al­iza­tion in a big­ger ap­pli­ca­tion that’s writ­ten in Qt. Be­cause OpenGL is a lot of glob­al state, care must be tak­en so li­braries know what state can be trust­ed and what not — that’s han­dled with Con­text::re­set­State(). It’s al­so pos­si­ble to wrap ex­ter­nal­ly cre­at­ed OpenGL ob­jects us­ing *::wrap() and, con­verse­ly, re­lease their own­er­ship with re­lease(). A re­duced ex­am­ple show­ing ren­der­ing in­to QQuick­Frame­buf­fer­Ob­ject: #include <Magnum/Context.h> #include <Magnum/Framebuffer.h> #include <Magnum/Mesh.h> #include <Magnum/Shaders/Phong.h> #include <QQuickFramebufferObject> struct MagnumRenderer : QQuickFramebufferObject :: Renderer { // ... QOpenGLFramebufferObject * createFramebufferObject ( const QSize & size ) override { /* Create Qt framebuffer object and wrap it to use with Magnum APIs */ auto fb = new QOpenGLFramebufferObject ( size , ...); _fb = Framebuffer :: wrap ( fb -> handle (), {{}, { size . width (), size . height ()}}); return fb ; } void render () override { /* Reset Magnum state tracker after Qt did its job */ Context :: resetState ( Context :: State :: ExitExternal ); /* Clear the framebuffer and draw a mesh */ _fb . clear ( FramebufferClear :: Color | FramebufferClear :: Depth ); _mesh . draw ( _shader ); /* Clean up to avoid Magnum state being modified by Qt */ Context :: resetState ( Context :: State :: EnterExternal ); } Framebuffer _fb { NoInit }; Mesh _mesh ; Shaders :: Phong _shader ; }; Some­times you may need to ac­cess the un­der­ly­ing APIs that Mag­num is wrap­ping — for ex­am­ple to test out a new fea­ture that hasn’t been ex­posed yet. Both Plat­form::Sdl2Ap­pli­ca­tion and Plat­form::GlfwAp­pli­ca­tion now pro­vide ac­cess to the un­der­ly­ing tool­kit struc­tures. If you need to go even deep­er, you can ditch the *Application class­es com­plete­ly and sim­ply at­tach Mag­num to an ex­ist­ing OpenGL con­text. How to do that with GLFW is shown on a sim­ple tri­an­gle ren­der­ing ex­am­ple. Sim­i­lar thing is with as­set im­porters — if you need to ac­cess par­tic­u­lar As­simp fea­ture di­rect­ly or parse an OpenGEX ex­ten­sion struc­ture, it’s now avail­able through type-erased importerState() ac­ces­sors. In case of As­simp it’s al­so pos­si­ble to take over an ex­ist­ing As­simp in­stance us­ing open­State(). See re­lat­ed sec­tions in As­simpIm­porter and OpenGex­Im­porter plug­in doc­u­men­ta­tion for de­tails. Con­ver­sion of math struc­tures from and to ex­ter­nal types was ex­tend­ed to all struc­tures in the Math names­pace, so with prop­er boil­er­plate head­er in­clud­ed it is pos­si­ble to have seam­less in­te­gra­tion with, for ex­am­ple, GLM: glm :: gtc :: quaternion a { 4.0f , 1.0f , 2.0f , 3.0f }; Quaternion q { a }; Debug {} << q . vector (); // {1.0, 2.0, 3.0} glm :: gtc :: quaternion b { q . normalized ()};

New OpenGL-re­lat­ed func­tion­al­i­ty The OpenGL wrap­ping lay­er re­ceived up­dates for full com­pute shad­er sup­port with SS­BOs and im­age load/store, there’s a new Trans­form­Feed­back class and com­plet­ed uni­form buf­fer sup­port, among many oth­er things. Im­prove­ments were made to make it pos­si­ble to have mul­ti­ple in­de­pen­dent thread-lo­cal in­stances of Mag­num, for ex­am­ple in case where one Mag­num con­text is used as a da­ta pro­cess­ing back­end in a back­ground thread and an­oth­er for ap­pli­ca­tion UI ren­der­ing on the main thread. In or­der to make de­bug­ging eas­i­er, it’s now pos­si­ble to black­list cer­tain OpenGL ex­ten­sions or driv­er work­arounds from the com­mand-line and en­vi­ron­ment. See the doc­u­men­ta­tion for more in­for­ma­tion.

Up­grad­ing from pre­vi­ous ver­sions Be­cause there was no mile­stone since 2015, great care was tak­en to main­tain back­wards com­pat­i­bil­i­ty with the 2015.05 ver­sion. De­pend­ing on how big your up­grade jump is, up­grad­ing to 2018.02 should be large­ly source com­pat­i­ble, with dep­re­ca­tion warn­ings is­sued for APIs that were re­placed with new func­tion­al­i­ty. In con­trast, im­me­di­ate­ly fol­low­ing this re­lease there’s some cleanup work sched­uled — purg­ing of APIs dep­re­cat­ed for a long time and do­ing some re­or­ga­ni­za­tion to make space for new fea­tures, such as Vulkan and SPIR-V sup­port.