Big reworks take time to ferment properly, which is why this release comes more than six months after the previous one, 2019.10. I dare to say this is the busiest release of Magnum yet, with work spanning across several areas of the project. Following are release highlights, for a detailed changelog that’s about 100 times bigger please see links at the bottom.
Asset import and geometry processing is one of the central parts of Magnum. For 2020.06 it got rewritten from a rather basic decade-old “toy engine” implementation full of nested std::vectors to an efficient and flexible GPU-friendly design that’s ready for new paradigms such as mesh shaders. I won’t be reiterating everything that went into it again in this announcement, please go see the in-depth introduction article for detailed information:
The move away from std::vector, which is visible especially in the redesigned MeshTools namespace, means Magnum had to provide a replacement. Containers::Array is around for quite a while — but for the purpose of certain importers and Primitives that can’t easily know the final index/vertex count beforehand — it had to be extended to support arbitrary growing, like std::vector has.
The design is rather unconventional in order to avoid the well-known shortcomings of std::vector, especially when it comes to custom allocators. The implementation present in 2020.06 is still missing arbitrary insertion and deletion which is why it’s not advertised in more detail yet — once that’s done, expect a dedicated article together with benchmarks and comparison to common implementations. Until then, see Growable arrays for an introduction.
One of the last remaining pieces of STL that are holding us back not as lightweight & flexible as I’d want them to be are std::strings. After the release cut I decided that new APIs are not going to use those anymore, which means a replacement is underway. Existing APIs will get gradually ported away, similarly as was done with std::vector or std::unique_ptr in the past. Of course, as always, an opt-in compatibility of the new APIs with std::string / std::string_view will be provided — the goal is not to alienate users of standard C++, the goal is to be flexible and provide alternatives.
Because the new Trade::MeshData APIs expanded a lot on supported vertex formats, the math library received batch Math::packInto() / unpackInto() functions that allow for efficient conversion between floating-point and 8-/16-bit packed or half-float types. When no conversion is needed, Utility::copy() from a new Utility/Algorithms.h header gives you a std::memcpy() / std::copy() alternative that works on multiple dimensions and sparse data layouts as well.
The MeshTools library, apart from being adapted for the redesigned workflow, now contains MeshTools::concatenate() for joining multiple meshes together, or for example MeshTools::generateIndices() for converting strips, loops or fans to plain indexed meshes.
The more Magnum gets used to import a increasingly broad range of datasets in various formats, the more previously unhandled corner cases get discovered and fixed.
- The AssimpImporter now correctly imports multi-primitive meshes, preserves alpha in materials and accounts for Y up / Z up orientation override, if a file defines it.
- All importers now import both base color and texture instead of either one
or the other. Interestingly, this was a limitation that originated from the
COLLADA format — the early Trade APIs and the now-gone
ColladaImporterplugin were modelled after it, however when reviewing this design decision it turned out that COLLADA is actually the only format with such restriction and every other format (OBJ, glTF, OpenGEX, …) supports combining both.
- TinyGltfImporter failed to import interleaved meshes. This was a shortcut done to make the early implementation simpler. To my surprise, apparently the vast majority of glTF models is exported de-interleaved and thus inefficient for the GPU, which explains why this limitation went largely unnoticed since the original plugin release in 2018.
Image importers weren’t left behind either — formats that support it such as DDS or Basis Universal now can import particular mip levels using Trade::AbstractImporter::image2DLevelCount() and the second parameter of Trade::AbstractImporter::image2D(), with this being proxied into all scene importers as well. Vaguely related to this, DevIlImageImporter and StbImageImporter can now import frames of animated GIFs for a very crude video playback.
- AssimpImporter and TinyGltfImporter now import tangents / bitangents as well, with support for skinning attributes getting ready in mosra/magnum#441
- StanfordImporter for the PLY file format got extended to support normals, texture coordinates, vertex colors and per-face attributes, being also highly optimized for near-instant import times
- TinyGltfImporter and
StanfordImporter now recognize a
per-vertex Object ID attribute, used in various datasets for semantic
annotations. Because it isn’t standardized in any way and each dataset
might use it differently, the
objectIdAttributeplugin-specific configuration option can be used to recognize it under a different name.
- TinyGltfImporter received support for several new extensions including KHR_lights_punctual, KHR_texture_transform and KHR_mesh_quantization
Finally, there is a new scene converter plugin interface, with
StanfordSceneConverter being the first
two plugins implementing it. Apart from that, image importers now have a simple
IcoImporter for Windows
*.ico files and there’s
(also a very trivial) StlImporter for binary STL
files, commonly used in 3D printing.
All importer and converter plugins received a flag to enable verbose output,
which is also exposed as a
--verbose option in the
magnum-player utilities. The plugins use that to notify
you about longer-running operations or processing stats. It’s probably most
helpful in case of Assimp, which likes to crash or misbehave on certain files.
Perhaps the most significant shader addition is instancing support in Shaders::Phong and Shaders::Flat — while instancing alone was supported in the GL library since 2014, the builtin shaders didn’t implement this functionality until now. For showcase, the Bullet Physics and Box2D examples are now reimplemented using instancing, each of them using just a single draw call for the whole scene. Try them out online:
Complementing the glTF KHR_texture_transform extension support, there’s now Shaders::Phong::setTextureMatrix() together with ability to have instanced texture offset, and the same in Shaders::Flat.
With tangent and normal map import being done, Shaders::Phong normal map support added in 2019.10 can finally be fully utilized. This is closely tied with Shaders::MeshVisualizer3D now being able to visualize not just wireframe but also tangent, bitangent and normal direction — very useful for debugging those dreaded lighting issues.
Shaders::Phong / Shaders::Flat can now output also per-vertex Object ID attribute, which means Shaders::MeshVisualizer3D can visualize that one as well, together with vertex and primitive ID. This goes hand-in-hand with a new DebugTools::ColorMap namespace that includes also the very recognizable Turbo colormap by Anton Mikhailov.
Even if all other new features shown here wouldn’t be a convincing reason for you to upgrade, you’ll definitely want to pick up these three workarounds for better driver compatibility:
- A hard-to-reproduce synchronization bug on Intel Windows drivers makes the ARB_direct_state_access extension basically unusable for anything related to buffers or VAOs. Usually manifested as flickering in ImGui-based apps. A subset of this workaround was done for 2019.10 already but due to its semi-random nature it didn’t cover all cases. This workaround abandonds all hope and completely disables DSA for affected code paths on these drivers.
- Intel Windows drivers don’t really respect explicit uniform locations but instead only take it as a very vague suggestion. This bug was most certainly also present since forever, but only became visible after the latest additions of texture transform, normal maps and instancing to builtin shaders, which caused the uniform locations to be anything but a contiguous increasing sequence. Since there’s no apparent rhyme or reason in which the drivers allocate uniform IDs, solution was to disable the ARB_explicit_uniform_location on Intel Windows drivers altogether.
- It’s hard to find bugs in drivers that are capable of very little, but even then — while this bug was probably present ever since Apple rewrote their (deprecated) GL 4.1 driver on top of Metal, it got independently discovered by two different users only recently. When GL::BufferTexture is bound, it causes all buffer modifications to crash due to what I assume is corruption of internal driver state. The workaround avoids the crash by unbinding the texture when updating or mapping a GL::Buffer.
For your amusement, the list of all current OpenGL driver workarounds is in the documentation.
Working on the geometry pipeline and large datasets required me to do various measurements, which led to a new GL::PipelineStatisticsQuery class. It exposes the ARB_pipeline_statistics_query extension which doesn’t provide really much, but it’s at least something — and sadly the only non-proprietary way to get any stats on NVidia drivers. A new DebugTools::FrameProfiler class uses those queries to give you a easy-to-integrate per-frame profiling.
Apart from that, various tiny bits and pieces such as clipping plane support were added, mostly on-demand based on needs of various users. See the changelog links at the bottom for the full list.
One quite minor but widely appreciated change was turning
shader.draw(mesh). The original was a result of how the API evolved
over the years, which is why I was blind to its counterintuitiveness until it
was finally pointed out to me. A good takeaway from that is — if you see
anything in Magnum APIs that feels strange or unnecessarily complicated, please
complain, no matter how “noob” or inexperienced you might feel. Feedback like
this matters a lot, and if I never hear it, I might never discover the problem.
One interesting project that is making great progress recently is Mesa’s Zink OpenGL-over-Vulkan driver. Stable Mesa 20.1 doesn’t have it enabled by default yet and there it’s just at GL 2.1, but latest commits already bring it up to 3.1 support. After fixing some bad assumptions in context creation routines in order to make pure GL 2.1 contexts work again, Magnum can now work with Zink as well.
Thanks to a joint effort from several contributors, Platform::GlfwApplication and Platform::Sdl2Application now support cursor management, which is also used by the ImGuiIntegration library. Both applications can now also set window icon, and if you are on Windows, you can use it together with the new IcoImporter to use one file to set an executable icon and a window icon as well, optionally also providing several resolutions to let the OS choose from.
The ImGuiIntegration library was switched to use builtin Shaders::Flat2D instead of a custom shader, which removed quite some code and made it work on WebGL 1 as well. This was possible thanks to builtin shaders receiving vertex color support in the 2019.10 release.
Windowless apps, which are commonly used for data processing or testing, got
extended to support context sharing. The Platform::WindowlessEglApplication
supports EGL device selection through the
--magnum-device option since
2019.10 and now it supports also
--magnum-cuda-device for filtering only
CUDA devices, if you’re running on a machine with NVidia GPUs.
If you don’t use the builtin application wrappers, there’s a new base-gtkmm bootstrap project to get you started using GTKmm, joining base-wxwidgets added in the previous release, and with a QtQuick bootstrap being worked on for the next.
The Contributor of the Year award goes to Nghia Truong — submitting five extremely interesting examples, each implementing a different algorithm completely from scratch, with many more goodies promised. All of them are now available as WebGL demos, feel free to try them out:
Apart from these, the original WebVR API got obsoleted by WebXR, and so follows our example, linked above — currently you can try it out in Chrome, and it also works in the Android browser.
Clang-CL is now an officially supported and tested compiler, in case you want to build for Windows but hate both neither MinGW nor MSVC suits your needs. Some work was done for MinGW Clang support but serious untackled issues still remain, so GCC is still the only supported compiler under MinGW.
Because dependency management is hard unless you have a system-wide package manager or Vcpkg doing the work for you, certain dependencies such as OpenAL, Basis Universal, meshoptimizer or ImGui can now be bundled as CMake subprojects. In addition, various fixes were done in CMake Find modules for statically-linked dependencies that are commonly used when distributing project binaries.
To better track versions of your dependencies, all Magnum projects now contain
version.h header containing the exact commit the library was built from.
Builtin CMake Android support, which got broken with the introduction of NDK r19, is working with CMake 3.16+ again, only with minimal workarounds. Building documentation and the Android troubleshooting guide were updated to reflect this fact.
Going with the new Trade::AbstractSceneConverter plugin interface, there’s a magnum-sceneconverter utility as well. Currently the only supported output is PLY (through StanfordSceneConverter) and the amount of operations is limited, but this area is going to expand over time, like it did for image conversion plugins. In addition the tool also exposes various MeshTools algorithms such as duplicate removal or attribute filtering.
magnum-imageconverter can now
consume and produce raw pixel data of a specified format, which is useful when
dealing with low-level pipelines that don’t understand high-level image
container formats. For data diagnostic and debugging, both
magnum-sceneconverter learned a new
option that prints information about file contents, pixel / vertex formats and
The magnum-player utility is heavily used for internal testing and thus absorbed basically all new engine additions — it can display normal maps, exposes all visualization features of Shaders::MeshVisualizer3D and you can run DebugTools::FrameProfiler with the P key. Only the native app has the new additions right now, the web version wasn’t updated yet.
To give you an idea what Magnum is used for, here’s a selection of currently active or recently published Magnum-based projects, with many more getting ready to appear next. Presented in no particular order:
A simple degree reduction technique for converting piecewise cubic splines into piecewise quadratic splines that maintain parameterization and continuity.
Interactive Exploration and Computational Steering in CAVE-like Environments for High-Performance Fluid Simulations. The CAVE-like environment was featured previously here.
There’s so much happening that this article is, as always, just a distilled version of the changelog — and I’m sure I forgot to mention some of the hidden gems:
If you use Homebrew,
ArchLinux AUR or build
*.deb packages, the 2020.06 release is already available there.
Vcpkg package update is currently waiting for a merge in
microsoft/vcpkg#12211, MSYS packages are almost ready in
msys2/MINGW-packages#6641 and ArchLinux community package updates will
follow shortly. If you use Magnum Singles, you’re
not forgotten either — up-to-date generated single-header libs are on the
checklist as well.
If you have your code already using the new Trade::MeshData APIs, congrats — you’re 95% there. If not, the new release should mostly compile with your existing code, only emit a lot of deprecation warnings where each will tell you what API to use instead.
A significant portion of the work on Magnum is being done by external contributors, and this release is no exception — thanks, everybody (and apologies to those I forgot):
- Allie for implementing IcoImporter, additions and fixes to ImGuiIntegration and various other things
- Brandon Pomeroy for example cleanup
- Erik Wijmans for implementing CUDA device selection in Platform::WindowlessEglApplication
- Guillaume Jacquemin for continued MSYS package maintenance, bugreports and fixes
- Jonathan Hale for continued Vcpkg package maintenance, implementing texture coordinate set support, Emscripten fixes, light support in TinyGltfImporter, Math and GL additions and more
- Konstantinos Chatzilygeroudis for various application fixes and additions, continued DartIntegration maintenance
- Marco Melorio for adding cursor management to all applications + ImGuiIntegration, Math and python bindings additions
- Max Schwarz for implementing multi-primitive support in AssimpImporter and/ an extreme patience with Assimp in general
- Nghia Truong for all the cool examples and related Math additions
- Stéphane Brard for shared context support in windowless apps
- @Amphaal, Burak Canik, Daniel Guzman, Davide Bacchet, Jackson Campolattaro, Jordan Peck and @pezcode for various fixes, improvements and documentation clarifications