The new Magnum milestone brings WebGL 2.0 and WebAssembly, VR support, lots of niceties for Windows users, iOS port, new experimental UI library, improved testing capabilities, support for over 80 new asset formats, new examples and much more.
With nearly three years of development since the previous release, I’m excited to announce the release of Magnum 2018.02. Lots of things happened, so I’ll focus on the most prominent features, for a detailed list follow the changelog links at the end. This release also introduces a new versioning scheme. Originally this was meant to be 1.0, but after much thought I decided to go with year/month instead, as it fits more into the rolling release philosophy of Magnum.
Brand new website and documentation theme
Until very recently, Magnum lacked a central place with consistent design for showcasing the features, providing documentation and keeping you updated. You can read more in the website announcement post, be sure to also subscribe to the blog Atom feed to not miss any updates. The documentation got an overhaul in a similar style, with extra effort put into being fast, easy-to-read and with first-class search functionality. There’s a blog post dedicated to explaining how the new search works, if you want to learn about the inner workings.
One notable non-obvious feature is an ability to search using OpenGL or OpenAL API names — very handy in case you are porting existing OpenGL code to Magnum:
Magnum wouldn’t be anything without the great community that formed around it and community involvement is important also for the website content — if you want to share a succes story with Magnum or have something interesting to say, submit a Guest Post and we’ll be happy to publish it.
If you didn’t notice yet, there’s also a Magnum chat room on Gitter.im, full of people that are happy to help if you have questions or need a bit of support. You can join simply with your GitHub or Twitter account.
First-class WebGL 2.0 support, WebAssembly
An area that received great care is the WebGL port. WebGL 2.0 is now fully supported and there is a set of new dependency-less plugins so you can now more easily play audio, convert images or render text on the web. The StbImageConverter and StbTrueTypeFont plugins are one of them. The BulletIntegration code and example now runs on the web and the magnum-info / Magnum OpenAL Info utilities were ported to provide information about your WebGL implementation as well.
A bunch of code size optimizations is already in place and further size improvements are on the roadmap. Compilation to WebAssembly for better performance is now possible, which in fact replaced the unmaintained NaCl port. See the post about WebAssembly support for more information.
Virtual Reality, WebVR
Thanks to years of hard work by @Squareys, Magnum now provides seamless integration of the Oculus VR SDK in the OvrIntegration library, together with a simple example that showcases a basic use. Besides that, there’s a live example showing WebVR integration in Emscripten:
Windows improvements
Visual Studio 2015, 2017, Windows Store/Phone, ANGLE, Vcpkg
Windows support did a huge leap forward compared to the previous version. Thanks to big improvements in C++11 support in recent Visual Studio versions it’s now possible to build Magnum with MSVC 2015 and 2017 and also target the UWP platform using the ANGLE OpenGL-to-D3D translation layer. On the other hand, support for MSVC 2013 and MinGW32 (the non-w64 version) was dropped to simplify maintenance efforts.
To make development and dependency handling on Windows even easier, Magnum now has Vcpkg packages, which allow you to install it together with all dependencies using a single command:
vcpkg install magnum
Packages installed using Vcpkg can be used straight away in Visual Studio —
all you need to do is to #include
the headers you want, the buildsystem
will do all needed library linking and setup behind the scenes automatically.
(Cool, isn’t it?)
It’s of course also possible to use Vcpkg-installed packages with CMake. See the documentation for details.
UTF-8 everywhere
Among other notable things is improved Unicode support on Windows. In line with the UTF-8 Manifesto, all Magnum APIs were designed to expect strings and paths encoded in UTF-8. This is now working properly also on Windows — as long as you use Magnum APIs such as Utility::Directory or Trade::AbstractImporter to access the filesystem, Windows wide-char APIs will be used behind the scenes to ensure proper path encoding.
macOS improvements, iOS port
Magnum is being used in a project that’s tightly coupled to Apple platforms, which means the code now runs on iOS as well. Platform::Sdl2Application was updated for Retina support and other iOS-specific features.
There’s also a new Platform::WindowlessIosApplication that’s useful for
running headless OpenGL tests on iOS. The TestSuite library now provides
integration with Xcode and XCTest so you can run tests directly from the IDE.
The corrade_add_test() macro is also able to do
all the necessary boilerplate to allow you to run your tests directly on an iOS
device or simulator simply by running CMake ctest
from the command-line.
To make life easier for macOS users, there are now Homebrew packages that you can install simply by typing
brew install mosra/magnum/magnum
You can also add all Magnum packages as a tap, see the documentation for more information.
Broader testing capabilities
Magnum was involved in a project that was all about processing of image and spatial data. Thanks to that, the TestSuite library received loads of improvements to make automated testing a breeze. The test output is now colored to make it easier to spot failures and it gained benchmarking capabilities so you can compare how your algorithms perform against baseline implementations — using CPU time, wall clock time, instruction counter or any custom measured quantity such as amount of allocated memory.
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.
Besides the already mentioned iOS testing, there are similar improvements for
Android — the corrade_add_test() can employ
adb
to upload the test executable together with all bundled files to a
device or emulator, run it there and retrieve the results just as if you would
be running the tests on your local machine.
The test suite is now able to handle instanced tests (or, in other words, data-driven tests). Lots of attention was put into fuzzy comparison — from simple numeric comparison using TestSuite::Compare::Around to comparing image data to a ground truth with error thresholds using DebugTools::CompareImage. Because proper visualization of large data is essential for productivity, the latter is able to print ASCII art visualization of the difference so you can see what’s wrong directly 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: | | | | | ~8070DNMN8$ZD7 | | ?I0: :++~. .I0Z | | 7I ?$D8ZZ0DZ8, +? | | ~+ +I ,7NZZ$ | | : ~ | | . . | | , : | | ~. +. +ID8?. | | ?. .Z0: +0I :7 | | .$$. ~D8$Z0DZ. =Z+ | | =8$DI=,. .:+ZDI$ | | :70DNMND$+. | | | | | 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 requiring OpenGL context is now available in a dedicated OpenGLTester library and it gained support for GPU time benchmarks. For easier testing on OpenGL ES and WebGL, there are now DebugTools::bufferSubData() and DebugTools::textureSubImage() helper utilities that supplement the lack of Buffer::data() and Texture::image() on those platforms.
To ensure stability and make maintenance easier, Magnum and all its libraries are now also compiled and tested with code coverage on all supported platforms — see the Build Status page for the whole testing matrix. This also means that every submitted pull request gets automatically tested for regressions, streamlining the whole review process.
There’s much more to mention regarding testing — it’ll be a part of a more detailed blog post in the future, stay tuned!
Experimental UI library
Some years ago I was responsible for UI rendering in a project that focused on visualizing large amounts of textual and plot data. The new experimental Magnum::Ui library builds on the knowledge gained during that project and its design goal is being ale to render huge UIs (such as editors) fast while staying fully customizable and easy to use for quick prototyping.
Due to its heavy use of uniform buffers and instancing it’s requiring at least OpenGL ES 3.0 (or WebGL 2.0). The current state is very experimental — it will gradually stabilize, gain more widgets and get documented in the next months. You can see how it looks in the UI Widget Gallery, it was also used to provide some knobs and toggles for the Area Lights demo:
Asset management improvements
A large new feature is support for compressed images — loading them from
files, uploading and downloading them from textures; plus there are new
interfaces in Trade::AbstractImageConverter ready for integrating
various GPU compression libraries. Together with compressed images Magnum
gained support for various pixel storage options in the PixelStorage
class to allow direct manipulation of image sub-rectangles, with further
convenience features such as Image::slice()
planned for the next
releases.
There’s also a new utility called magnum-imageconverter that simply exposes all existing *Importer and *ImageConverter plugins on a command line. Together with a new AnyImageConverter plugin it’s able to autodetect source and destination file formats based on extension, so you can easily use it in your pipeline for data conversion, for example:
magnum-imageconverter image.bmp image.png
Many external contributions went into asset management and conversion — you can now use the AssimpImporter plugin to load about 40 new scene formats using the Assimp library; the DevIlImageImporter plugin uses DevIL to load 40 new image formats. Initial work went into camera and light property import, with support in the Assimp and OpenGEX importer.
Among the others there’s a possibility to load DXT-compressed textures using DdsImporter or convert images to PNG and EXR using PngImageConverter and MiniExrImageConverter. The StbImageConverter can now use stb_image_write to output HDR and BMP formats in addition to PNG.
Audio library goodies
The Audio library received HRTF support that’s very important for immersive audio in VR, besides that it supports many new buffer formats. The HRTF support is accompanied with a new example and the whole Audio library was updated to work on the web as well:
Many new dependency-less import plugins were contributed — StbVorbisAudioImporter for loading OGG Vorbis files using stb_vorbis, DrWavAudioImporter and DrFlacAudioImporter for loading WAV and FLAC files using dr_wav/dr_flac. The WavAudioImporter plugin that’s maintained directly in Magnum received various updates, broader buffer format support and better error handling.
Full control over initialization and ownership
While all types in Magnum are by default initialized to a defined value, it’s now possible to override the behavior for greater flexibility. For example if you will be overwriting all values in a container anyway; to avoid unnecessary memory zeroing instructions in a tight loop; or to defer OpenGL object initialization to a point where a context is ready without introducing additional indirection using pointers or optional objects:
/* 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{};
Together with this it’s now possible to transfer ownership of all underlying resources — wrapping externally allocated arrays and providing custom deleters, or, on the other hand, using Containers::Array::release() to release ownership of the allocated memory. There are also two new classes, Containers::StaticArray and Containers::StaticArrayView, for handling stack-allocated, compile-time-sized arrays and views on them. New APIs like Containers::arrayCast() or Containers::arraySize() provide further convenience utilities:
/* 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 library additions
The Math library received a class representing Bezier curves and a Frustum structure. The Math::Geometry::Intersection namespace is extended with frustum culling algorithms and further additions are already being worked on.
There’s a new Half type for representing 16-bit half-float numbers. It’s
just a storage type, providing conversions from and to float
types. In
addition to the existing _deg
/_rad
literals there is a new _h
literal for half-floats and various literals for
entering hexadecimal RGB(A) colors in linear 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 convenience and better feature parity with GLSL, check out the complete changelog at the end of the article.
Better interoperability with 3rd-party code
One of design goals of Magnum is to be a collection of useful non-intrusive tools that works well with third-party libraries — not being a big monolithic engine that takes over everything and forces a particular workflow. With this and the following releases this design goal is being pushed further than ever before.
One common case is that Magnum is not the only library accessing the OpenGL context — for example it’s only taking care of data visualization in a bigger application that’s written in Qt. Because OpenGL is a lot of global state, care must be taken so libraries know what state can be trusted and what not — that’s handled with Context::resetState(). It’s also possible to wrap externally created OpenGL objects using *::wrap() and, conversely, release their ownership with release(). A reduced example showing rendering into QQuickFramebufferObject:
#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; };
Sometimes you may need to access the underlying APIs that Magnum is wrapping
— for example to test out a new feature that hasn’t been exposed yet. Both
Platform::Sdl2Application and Platform::GlfwApplication now
provide access to the underlying toolkit structures. If you need to go even
deeper, you can ditch the *Application
classes completely and simply attach
Magnum to an existing OpenGL context. How to do that with GLFW is shown on a
simple triangle rendering example.
Similar thing is with asset importers — if you need to access particular
Assimp feature directly or parse an OpenGEX extension structure, it’s now
available through type-erased importerState()
accessors. In case of Assimp
it’s also possible to take over an existing Assimp instance using
openState(). See related sections
in AssimpImporter and
OpenGexImporter plugin documentation for
details.
Conversion of math structures from and to external types was extended to all structures in the Math namespace, so with proper boilerplate header included it is possible to have seamless integration with, for example, 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()};
Even more new stuff
The list of new things doesn’t stop here. It’s important to note that all example code is now put into public domain (or UNLICENSE) to free you from legal obstacles when using the code in your apps. There’s a new example showing how to use Parallel Split / Cascaded Shadow Maps and also a simple Object Picking example:
There’s also a new experimental DartIntegration library being worked on — it integrates the DART Dynamic Animation and Robotics Toolkit into Magnum in a similar way that’s done with the Bullet Physics library. A first draft is already merged with second revision being currently in the works. This library alone deserves a blog post on its own — stay tuned!
Upgrading from previous versions
Because there was no milestone since 2015, great care was taken to maintain backwards compatibility with the 2015.05 version. Depending on how big your upgrade jump is, upgrading to 2018.02 should be largely source compatible, with deprecation warnings issued for APIs that were replaced with new functionality.
In contrast, immediately following this release there’s some cleanup work scheduled — purging of APIs deprecated for a long time and doing some reorganization to make space for new features, such as Vulkan and SPIR-V support.
Complete changelog
If you survived all the way down here, congrats! There’s much more and it’s not possible to fit everything in this announcement. You can find a detailed list of changes in version 2018.02 in the documentation:
Special thanks
This release wouldn’t be possible without extreme dedication of numerous volunteers:
- Jonathan Hale (@Squareys) — Oculus VR integration, WebVR example, HRTF support in the Audio library; DDS Assimp and Vorbis importers, Area Lights example, GLFW application, frustum culling and much much more
- Alice Margatroid (@Alicemargatroid) — multi-channel support in Audio library, standard conformance, huge improvements in the WAV importer plugin, dr_wav and dr_flac importer plugins, DevIL importer plugin, various bug reports, feature suggestions and much more
- Konstantinos Chatzilygeroudis (@costashatz) — ongoing work on the DART integration library and related functionality
- Bill Robinson (@wivlaro) — shadow mapping example, various bug reports and suggestions
- Ashwin Ravichandran (@ashrko619) — Bézier curve implementation
- Michael Dietschi (@mdietsch) — colored output in Windows console, numerous bug reports
- Gerhard de Clercq — initial Windows RT (Store/Phone) port
- @sigman78 — initial MSVC 2017 port, initial version of Vcpkg packages
- Joel Clay (@jclay) — updates to Vcpkg packages
This list is not exhaustive — sorry if I forgot about somebody! Over the time there were many more people reporting issues, helping with bugfixes, giving encouraging and constructive feedback or just being happy to help each other on the Gitter chat. Thank you, everybody, and cheers!