Magnum Enginehttps://magnum.graphics/2022-08-02T00:00:00+02:00C++11/C++14 and OpenGL graphics engineC++11/C++14 and OpenGL graphics engineConvenient CPU feature detection and dispatch2022-08-02T00:00:00+02:002022-08-02T00:00:00+02:00Vladimír Vondruštag:blog.magnum.graphics,2022-08-02:/backstage/cpu-feature-detection-dispatch/<p>A new Mag­num fea­ture provides ef­fi­cient com­pile-time and runtime CPU
de­tec­tion and dis­patch on x86, ARM and WebAssembly. The core idea be­hind
al­lows adding new vari­ants without hav­ing to write <em>any</em> dis­patch­ing code.</p>
<p>Among key as­pects dif­fer­en­ti­at­ing between “per­form­ance is a <em>joke</em> for this
pro­ject!” and “wow they really mean it” — be­sides fa­vor­ing data-ori­ented
design over ab­stract fact­ory proxy man­ager del­eg­ates — is use of SIMD
in­struc­tions such as AVX or NEON, and ul­ti­mately de­tect­ing and pick­ing the best
op­tim­ized im­ple­ment­a­tion for giv­en hard­ware at runtime.</p>
<p>For over a dec­ade, Mag­num could af­ford to not both­er, partly be­cause the ac­tu­al
heavy lift­ing such as <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1BulletIntegration.html">phys­ics</a> or
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1BasisImageConverter.html">tex­ture com­pres­sion</a> was of­f­loaded to 3rd
party lib­rar­ies, and partly be­cause if you really needed to per­form things like
fast mul­ti­plic­a­tion of large matrices, you could al­ways
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1EigenIntegration.html">seam­lessly del­eg­ate to Ei­gen</a>. But as all new Mag­num
APIs are data-ori­ented, de­signed with batch op­er­a­tions in mind, I just couldn’t
ig­nore the ex­tra per­form­ance that SIMD in­struc­tions could bring.</p>
<section id="core-idea-inheritance-based-overload-resolution">
<h2><a href="#core-idea-inheritance-based-overload-resolution">Core idea — in­her­it­ance-based over­load res­ol­u­tion</a></h2>
<p>What triggered this whole pro­cess was that I real­ized I could make use of C++
in­her­it­ance to pick among can­did­ate over­loads. Loosely cit­ing the
<a href="https://en.cppreference.com/w/cpp/language/overload_resolution">C++ ref­er­en­ce on Over­load Res­ol­u­tion</a>:</p>
<blockquote>
<p>F1 is de­term­ined to be a bet­ter func­tion than F2 if <span class="m-text m-dim">[…]</span> there
is at least one ar­gu­ment of F1 whose im­pli­cit con­ver­sion is <em>bet­ter</em> than
the cor­res­pond­ing im­pli­cit con­ver­sion for that ar­gu­ment of F2.</p>
<p>If two con­ver­sion se­quences are in­dis­tin­guish­able be­cause they have the
same rank, the fol­low­ing ad­di­tion­al rules ap­ply:</p>
<ul>
<li>If <code>Mid</code> is de­rived (dir­ectly or in­dir­ectly) from <code>Base</code>, and
<code>Derived</code> is de­rived (dir­ectly or in­dir­ectly) from <code>Mid</code>, then
<code>Derived</code> to <code>Mid</code> is bet­ter than <code>Derived</code> to <code>Base</code>.</li>
</ul>
</blockquote>
<p>For a prac­tic­al ex­ample, let’s as­sume we’re about to im­ple­ment a
<a class="m-flat" href="https://man.archlinux.org/man/memrchr.3">mem­r­chr()</a> func­tion,
be­cause, com­pared to <a class="m-flat" href="https://en.cppreference.com/w/cpp/string/byte/memchr">std::mem­chr()</a>, it’s for some reas­on not a widely
avail­able API and we want to have a fast im­ple­ment­a­tion every­where. Tak­ing just
x86 in­to ac­count for sim­pli­city, we’ll have an AVX2 vari­ant, a slightly slower
SSE2 vari­ant and a scal­ar fall­back, dis­tin­guished from each oth­er not by a name
but by a <em>tag</em>, sim­il­arly to what tags like <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/namespaceCorrade.html#a0fc1aca838b7d380102ab12c608b62b6">NoIn­it</a> or <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/namespaceCorrade.html#af3be6d0316a337741e2545bf2b1d7bd6">ValueIn­it</a>
do in <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/classCorrade_1_1Containers_1_1Array.html">Con­tain­ers::Ar­ray</a> con­struct­ors:</p>
<pre class="m-code"><span class="k">struct</span> <span class="n">ScalarT</span> <span class="p">{};</span>
<span class="k">struct</span> <span class="nl">Sse2T</span><span class="p">:</span> <span class="n">ScalarT</span> <span class="p">{};</span>
<span class="err">…</span>
<span class="k">struct</span> <span class="nl">Avx2T</span><span class="p">:</span> <span class="n">AvxT</span> <span class="p">{};</span>
<span class="k">struct</span> <span class="nl">Avx512T</span><span class="p">:</span> <span class="n">Avx2T</span><span class="p">{};</span>
<span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="nf">memrchr</span><span class="p">(</span><span class="n">ScalarT</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">data</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="kt">size_t</span> <span class="n">size</span><span class="p">,</span> <span class="kt">char</span> <span class="n">c</span><span class="p">);</span>
<span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="nf">memrchr</span><span class="p">(</span><span class="n">Sse2T</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">data</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="kt">size_t</span> <span class="n">size</span><span class="p">,</span> <span class="kt">char</span> <span class="n">c</span><span class="p">);</span>
<span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="nf">memrchr</span><span class="p">(</span><span class="n">Avx2T</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">data</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="kt">size_t</span> <span class="n">size</span><span class="p">,</span> <span class="kt">char</span> <span class="n">c</span><span class="p">);</span></pre>
<p>Then, an ap­pro­pri­ate tag would be <code class="cpp m-code"><span class="k">typedef</span></code>‘d de­pend­ing on what
par­tic­u­lar <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/Corrade_8h.html#ac5084f4b6a30b08b6b45d4e4d4063407">COR­RADE_TAR­GET_*</a> pre­pro­cessor vari­ables
are defined, with an in­stance of that tag ex­posed through a <code class="cpp m-code"><span class="k">constexpr</span></code>
vari­able for easy use:</p>
<pre class="m-code"><span class="cp">#ifdef CORRADE_TARGET_AVX512</span>
<span class="k">typedef</span> <span class="n">Avx512T</span> <span class="n">DefaultCpuT</span><span class="p">;</span>
<span class="cp">#elif defined(CORRADE_TARGET_AVX2)</span>
<span class="k">typedef</span> <span class="n">Avx2T</span> <span class="n">DefaultCpuT</span><span class="p">;</span>
<span class="err">…</span>
<span class="cp">#elif defined(CORRADE_TARGET_SSE2)</span>
<span class="k">typedef</span> <span class="n">Sse2T</span> <span class="n">DefaultCpuT</span><span class="p">;</span>
<span class="cp">#else</span>
<span class="k">typedef</span> <span class="n">ScalarT</span> <span class="n">DefaultCpuT</span><span class="p">;</span>
<span class="cp">#endif</span>
<span class="k">constexpr</span> <span class="n">DefaultCpuT</span> <span class="n">DefaultCpu</span><span class="p">;</span></pre>
<p>Ul­ti­mately, to pick the right over­load at com­pile time, the <code>DefaultCpu</code> tag
gets passed along­side oth­er para­met­ers. On an AVX-512 ma­chine the AVX2
im­ple­ment­a­tion gets chosen, as <code>Avx2T</code> is the closest avail­able base of
<code>Avx512T</code>. On an AVX ma­chine, the SSE2 im­ple­ment­a­tion gets chosen in­stead —
<code>Avx2T</code> is a sub­class of <code>AvxT</code>, so it’s ruled out, and the closest base
is <code>Sse2T</code>. In the rare scen­ario where not even SSE2 is present, it falls
back to the <code>ScalarT</code> vari­ant.</p>
<pre class="m-code"><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">found</span> <span class="o">=</span> <span class="n">memrchr</span><span class="p">(</span><span class="n">DefaultCpu</span><span class="p">,</span> <span class="err">…</span><span class="p">);</span></pre>
<p>Pretty neat, huh? This makes com­pile-time dis­patch ex­tremely easy to per­form,
and what I es­pe­cially like about it is that adding a new vari­ant is lit­er­ally
just one more over­load. No re­dund­ant boil­er­plate, no manu­ally man­aged dis­patch
tables, no sur­face for bugs to creep in.</p>
<p>Now, what’s left is “just” the runtime dis­patch.</p>
</section>
<section id="a-year-passed-by">
<h2><a href="#a-year-passed-by">A year passed by …</a></h2>
<p>… dur­ing which I ba­sic­ally aban­doned the whole idea.<a class="m-footnote" href="#id7" id="id1">1</a> Reas­on is that the
very fea­ture that makes it work at com­pile time — dif­fer­ent func­tion
sig­na­tures — is what makes runtime dis­patch <em>really pain­ful</em>. For every such
func­tion, one would have to manu­ally write a snip­pet like this, adding
sig­ni­fic­ant main­ten­ance and runtime over­head. Didn’t I want to avoid
<em>ex­actly this</em> in the first place?</p>
<pre class="m-code"><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="nf">memrchr</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">data</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="kt">size_t</span> <span class="n">size</span><span class="p">,</span> <span class="kt">char</span> <span class="n">c</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="n">targetIsAvx2</span><span class="p">)</span>
<span class="k">return</span> <span class="n">memrchr</span><span class="p">(</span><span class="n">Avx2T</span><span class="p">{},</span> <span class="n">data</span><span class="p">,</span> <span class="n">size</span><span class="p">,</span> <span class="n">c</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="n">targetIsSse2</span><span class="p">)</span>
<span class="k">return</span> <span class="n">memrchr</span><span class="p">(</span><span class="n">Sse2T</span><span class="p">{},</span> <span class="n">data</span><span class="p">,</span> <span class="n">size</span><span class="p">,</span> <span class="n">c</span><span class="p">);</span>
<span class="c1">// plus #ifdefs and branches for ARM NEON, WebAssembly SIMD, ...</span>
<span class="k">return</span> <span class="n">memrchr</span><span class="p">(</span><span class="n">ScalarT</span><span class="p">{},</span> <span class="n">data</span><span class="p">,</span> <span class="n">size</span><span class="p">,</span> <span class="n">c</span><span class="p">);</span>
<span class="p">}</span></pre>
<p>Also, ori­gin­ally I planned to use CPU fea­ture dis­patch for <em>sig­ni­fic­ant</em> chunks
of code like cal­cu­lat­ing mesh nor­mals or res­iz­ing im­ages, to min­im­ize the
im­pact of such dis­patch over­head. But ended up here, want­ing to use it for a
<code class="cpp m-code"><span class="n">memrchr</span><span class="p">()</span></code> im­ple­ment­a­tion! Which means that <em>any</em> sort of over­head that’s
big­ger than a reg­u­lar func­tion call is not go­ing to cut it. Es­pe­cially not a
gi­ant <code class="cpp m-code"><span class="k">if</span></code> cas­cade with a po­ten­tially ex­pens­ive ar­gu­ment passthrough.</p>
</section>
<section id="how-the-grownups-do-it">
<h2><a href="#how-the-grownups-do-it">How the grownups do it</a></h2>
<p>For­tu­nately, dur­ing re­cent perf in­vest­ig­a­tions and code pro­fil­ing ses­sions I
dis­covered the <a href="https://sourceware.org/glibc/wiki/GNU_IFUNC">GNU IFUNC at­trib­ute</a>,
and found out that it’s even <em>The Solu­tion</em>
<a href="https://github.com/bminor/glibc/blob/78fb88827362fbd2cc8aa32892ae5b015106e25c/sysdeps/x86_64/multiarch/ifunc-memcmp.h">used by glibc it­self</a>
to dis­patch to ar­chi­tec­ture-spe­cif­ic vari­ants of <a class="m-flat" href="https://en.cppreference.com/w/cpp/string/byte/memchr">std::mem­chr()</a> or
<a class="m-flat" href="https://en.cppreference.com/w/cpp/string/byte/memcmp">std::mem­cmp()</a>. Can’t really do much bet­ter than that, right?</p>
<p>Which led me to a con­clu­sion that <abbr title="not slower than glibc">the ideal way</abbr>
to per­form runtime CPU fea­ture dis­patch would be to:</p>
<ol>
<li>De­clare a func­tion (point­er) in the head­er, with the ac­tu­al com­plex­ity and
ar­chi­tec­ture-spe­cif­ic code hid­den away in­to a source file. To min­im­ize func­tion call over­head, avoid passing heavy classes with re­dund­ant state
— ideally just built­in types. In case of our ex­ample, it would be
<code class="cpp m-code"><span class="k">extern</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span><span class="p">(</span><span class="o">*</span><span class="n">memrchr</span><span class="p">)(</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="kt">size_t</span><span class="p">,</span> <span class="kt">char</span><span class="p">)</span></code>.</li>
<li>If the func­tion is meant to be called from with­in a high­er-level API and
not dir­ectly (such as in this case, where it’d be ex­posed through
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/classCorrade_1_1Containers_1_1BasicStringView.html#a7452416cc951afb77eefea87cfb8a1c0">Con­tain­ers::StringView::find­Last(char)</a>),
make that API the smal­lest pos­sible wrap­per that’s in­lined in the head­er.
That way the com­piler can in­line the wrap­per on the call site, turn­ing it
in­to just a single call to the func­tion (point­er), in­stead of two nes­ted
calls.</li>
</ol>
<p>Mean­ing, if I want to have a fast dis­patch, I have to find a solu­tion that
doesn’t in­volve ex­pens­ive ar­gu­ment passthrough. Out of des­per­a­tion I even
thought of Em­bra­cing the Dark­ness and <code class="cpp m-code"><span class="k">reinterpret_cast</span><span class="o"><></span></code>‘ing the
vari­ants to a com­mon func­tion point­er type, but dis­carded that idea upon
dis­cov­er­ing that <a href="https://emscripten.org/docs/porting/guidelines/function_pointer_issues.html">WebAssembly checks that a func­tion is only called with a match­ing type</a>,
pre­vent­ing such hack from work­ing there. Not to men­tion the pub­lic hu­mi­li­ation,
ad­dress san­it­izer and stat­ic ana­lys­is com­plaints.</p>
</section>
<p class="m-transition"># # #</p>
<section id="function-currying-to-the-rescue">
<h2><a href="#function-currying-to-the-rescue">Func­tion cur­ry­ing to the res­cue</a></h2>
<p>The <em>Heureka Mo­ment</em> came to me when I was check­ing
<a href="https://vittorioromeo.info/index/blog/cpp17_curry.html">if C++ could do func­tion cur­ry­ing</a>,
and the solu­tion isn’t that much com­plex than the ori­gin­al idea. First let me
show how the <code class="cpp m-code"><span class="n">memrchr</span><span class="p">()</span></code> ex­ample from the top would be re­writ­ten in a way
that <em>ac­tu­ally works</em> with both a com­pile-time and a runtime dis­patch, and uses
an <em>ac­tu­al</em> <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/namespaceCorrade_1_1Cpu.html">Cor­rade::Cpu</a> lib­rary:</p>
<pre class="m-code"><span class="cp">#include</span> <span class="cpf"><Corrade/Cpu.h></span><span class="cp"></span>
<span class="k">using</span> <span class="k">namespace</span> <span class="n">Corrade</span><span class="p">;</span>
<span class="n">CORRADE_ALWAYS_INLINE</span> <span class="k">auto</span> <span class="nf">memrchrImplementation</span><span class="p">(</span><span class="n">Cpu</span><span class="o">::</span><span class="n">ScalarT</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="o">+</span><span class="p">[](</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">data</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="kt">size_t</span> <span class="n">size</span><span class="p">,</span> <span class="kt">char</span> <span class="n">c</span><span class="p">)</span> <span class="p">{</span> <span class="err">…</span> <span class="p">};</span>
<span class="p">}</span>
<span class="n">CORRADE_ALWAYS_INLINE</span> <span class="k">auto</span> <span class="nf">memrchrImplementation</span><span class="p">(</span><span class="n">Cpu</span><span class="o">::</span><span class="n">Sse2T</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="o">+</span><span class="p">[](</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">data</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="kt">size_t</span> <span class="n">size</span><span class="p">,</span> <span class="kt">char</span> <span class="n">c</span><span class="p">)</span> <span class="p">{</span> <span class="err">…</span> <span class="p">};</span>
<span class="p">}</span>
<span class="n">CORRADE_ALWAYS_INLINE</span> <span class="k">auto</span> <span class="nf">memrchrImplementation</span><span class="p">(</span><span class="n">Cpu</span><span class="o">::</span><span class="n">Avx2T</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="o">+</span><span class="p">[](</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">data</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="kt">size_t</span> <span class="n">size</span><span class="p">,</span> <span class="kt">char</span> <span class="n">c</span><span class="p">)</span> <span class="p">{</span> <span class="err">…</span> <span class="p">};</span>
<span class="p">}</span>
<span class="n">CORRADE_CPU_DISPATCHER_BASE</span><span class="p">(</span><span class="n">memrchrImplementation</span><span class="p">)</span></pre>
<p>The only dif­fer­ence com­pared to the pre­vi­ous at­tempt is that the
ar­chi­tec­ture-spe­cif­ic vari­ants are now re­turn­ing a lambda<a class="m-footnote" href="#id8" id="id2">2</a> that con­tains
the ac­tu­al code in­stead … and then there’s an opaque macro. Since
non-cap­tur­ing lambdas are just like reg­u­lar func­tions, there isn’t any ex­tra
over­head from put­ting the code there in­stead. The wrap­per func­tion, tak­ing the
tag, is how­ever marked as <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/Macros_8h.html#a3df4fcf9966b18af3f5d311a04698b56">COR­RADE_AL­WAYS_IN­LINE</a>, mean­ing it op­tim­izes
down to ac­cess­ing a func­tion point­er dir­ectly. Thus per­form­ing</p>
<pre class="m-code"><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">found</span> <span class="o">=</span> <span class="n">memrchrImplementation</span><span class="p">(</span><span class="n">Cpu</span><span class="o">::</span><span class="n">DefaultBase</span><span class="p">)(</span><span class="err">…</span><span class="p">);</span></pre>
<p>— where <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/namespaceCorrade_1_1Cpu.html#a7eb0fdea7fc6203c8abe112c2b58271a">Cpu::De­fault­Base</a> is an ali­as to the highest base in­struc­tion
set en­abled at com­pile time — is equi­val­ent to the pre­vi­ous at­tempt, but with
the im­port­ant dif­fer­ence that it’s pos­sible to sep­ar­ate the com­pile-time
dis­patch from the ac­tu­al func­tion call.</p>
<p>Which is what makes pos­sible to im­ple­ment a <abbr title="again, not slower than glibc">zero-over­head</abbr>
<em>runtime</em> dis­patch as well. And that’s what the
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/Cpu_8h.html#a0b29ab5881e2e0b24ff443100d5aa81e">COR­RADE_CPU_DIS­PATCH­ER­_­BASE()</a> macro is for — here is the x86 vari­ant
of it. It uses <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/classCorrade_1_1Cpu_1_1Features.html">Cpu::Fea­tures</a>, which I didn’t talk about yet, but suf­fice
to say it’s sim­il­ar to a <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/classCorrade_1_1Containers_1_1EnumSet.html">Con­tain­ers::Enum­Set</a>, con­vert­ing the
com­pile-time tags to a bit­field-like value that can be op­er­ated with at
runtime:</p>
<pre class="m-code"><span class="cp">#define CORRADE_CPU_DISPATCHER_BASE(function) \</span>
<span class="cp"> auto function(Cpu::Features features) { \</span>
<span class="cp"> if(features & Cpu::Avx512f) \</span>
<span class="cp"> return function(Cpu::Avx512f); \</span>
<span class="cp"> if(features & Cpu::Avx2) \</span>
<span class="cp"> return function(Cpu::Avx2); \</span>
<span class="cp"> … \</span>
<span class="cp"> if(features & Cpu::Sse2) \</span>
<span class="cp"> return function(Cpu::Sse2); \</span>
<span class="cp"> return function(Cpu::Scalar); \</span>
<span class="cp"> }</span></pre>
<p>The macro just stamps out checks for all pos­sible CPU fea­tures, from most
ad­vanced to least, and then calls the func­tion with <em>each</em> of those tags. Thus
— like be­fore — <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/namespaceCorrade_1_1Cpu.html#ae53a0c8cdcbffd46cf48e443dd1c9ad9">Cpu::Avx2</a> and above branches will re­solve to
<code class="cpp m-code"><span class="n">memrchrImplementation</span><span class="p">(</span><span class="n">Cpu</span><span class="o">::</span><span class="n">Avx2T</span><span class="p">)</span></code>, all branches be­low in­clud­ing
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/namespaceCorrade_1_1Cpu.html#a0824c1dffe74c9af986f5c448334df7e">Cpu::Sse2</a> will re­solve to <code class="cpp m-code"><span class="n">memrchrImplementation</span><span class="p">(</span><span class="n">Cpu</span><span class="o">::</span><span class="n">Sse2T</span><span class="p">)</span></code>, and
the <code class="cpp m-code"><span class="n">memrchrImplementation</span><span class="p">(</span><span class="n">Cpu</span><span class="o">::</span><span class="n">ScalarT</span><span class="p">)</span></code> fall­back gets used if noth­ing
else matches.</p>
<p>Quite a lot of branch­ing, eh? Not really. Be­cause of the
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/Macros_8h.html#a3df4fcf9966b18af3f5d311a04698b56">COR­RADE_AL­WAYS_IN­LINE</a>, this com­piles down to re­turn­ing a set of func­tion
point­ers, and with even the low­est op­tim­iz­a­tions en­abled the com­piler sees that
sev­er­al branches re­turn the same point­er and col­lapses them to­geth­er. To proof
such a wild claim, here’s the as­sembly of the
<code class="cpp m-code"><span class="n">memrchrImplementation</span><span class="p">(</span><span class="n">Cpu</span><span class="o">::</span><span class="n">Features</span><span class="p">)</span></code> func­tion that this macro gen­er­ated,
with GCC and <code>-O1</code>:</p>
<pre class="m-code"><span class="mh">0x00001131</span><span class="o">:</span> <span class="n">lea</span> <span class="mh">0x175</span><span class="p">(</span><span class="o">%</span><span class="n">rip</span><span class="p">),</span><span class="o">%</span><span class="n">rax</span> <span class="o"><</span><span class="n">memrchrImplementation</span><span class="p">(</span><span class="n">Corrade</span><span class="o">::</span><span class="n">Cpu</span><span class="o">::</span><span class="n">Avx2T</span><span class="p">)</span><span class="err">…</span><span class="o">></span>
<span class="mh">0x00001138</span><span class="o">:</span> <span class="n">test</span> <span class="err">$</span><span class="mh">0xc0</span><span class="p">,</span><span class="o">%</span><span class="n">dil</span>
<span class="mh">0x0000113c</span><span class="o">:</span> <span class="n">je</span> <span class="mh">0x113f</span>
<span class="mh">0x0000113e</span><span class="o">:</span> <span class="n">ret</span>
<span class="mh">0x0000113f</span><span class="o">:</span> <span class="n">test</span> <span class="err">$</span><span class="mh">0x3f</span><span class="p">,</span><span class="o">%</span><span class="n">dil</span>
<span class="mh">0x00001143</span><span class="o">:</span> <span class="n">lea</span> <span class="mh">0x15f</span><span class="p">(</span><span class="o">%</span><span class="n">rip</span><span class="p">),</span><span class="o">%</span><span class="n">rax</span> <span class="o"><</span><span class="n">memrchrImplementation</span><span class="p">(</span><span class="n">Corrade</span><span class="o">::</span><span class="n">Cpu</span><span class="o">::</span><span class="n">Sse2T</span><span class="p">)</span><span class="err">…</span><span class="o">></span>
<span class="mh">0x0000114a</span><span class="o">:</span> <span class="n">lea</span> <span class="mh">0x154</span><span class="p">(</span><span class="o">%</span><span class="n">rip</span><span class="p">),</span><span class="o">%</span><span class="n">rdx</span> <span class="o"><</span><span class="n">memrchrImplementation</span><span class="p">(</span><span class="n">Corrade</span><span class="o">::</span><span class="n">Cpu</span><span class="o">::</span><span class="n">ScalarT</span><span class="p">)</span><span class="err">…</span><span class="o">></span>
<span class="mh">0x00001151</span><span class="o">:</span> <span class="n">cmove</span> <span class="o">%</span><span class="n">rdx</span><span class="p">,</span><span class="o">%</span><span class="n">rax</span>
<span class="mh">0x00001155</span><span class="o">:</span> <span class="n">jmp</span> <span class="mh">0x113e</span></pre>
<p>Clang does the same, MS­VC is dif­fer­ent in that the <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/Macros_8h.html#a3df4fcf9966b18af3f5d311a04698b56">COR­RADE_AL­WAYS_IN­LINE</a>
won’t work when op­tim­iz­a­tions are dis­abled, lead­ing to the <code class="cpp m-code"><span class="k">if</span></code> cas­cade
in­deed be­ing a bunch of sad func­tion calls. Yet <em>even that</em> isn’t a prob­lem, as
the dis­patch­er func­tion is meant to only be called once in a pro­gram life­time.
But I’m skip­ping ahead.</p>
</section>
<section id="the-cpu-usually-doesn-t-change-under-our-hands">
<h2><a href="#the-cpu-usually-doesn-t-change-under-our-hands">The CPU usu­ally doesn’t change un­der our hands</a></h2>
<p>Now comes the ac­tu­al runtime CPU fea­ture de­tec­tion. Which, for x86, is done via
the <a href="https://en.wikipedia.org/wiki/CPUID">CPUID</a> in­struc­tion, and ex­posed
through <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/namespaceCorrade_1_1Cpu.html#a9afada18b534309f7700f1cb79eeae28">Cpu::runtime­Fea­tures()</a>. There’s not much else to say, ex­cept
that <em>a lot of curs­ing</em> went in­to mak­ing that code port­able. An im­port­ant
as­sump­tion is that the set of CPU fea­tures doesn’t change dur­ing pro­gram
life­time,<a class="m-footnote" href="#id9" id="id3">3</a> and so we can query them and dis­patch just once, cach­ing the
res­ult. Which is what <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/Cpu_8h.html#a5378d07b7bfa36af2adb7d45003e2c8f">COR­RADE_CPU_DIS­PATCHED_­POINT­ER()</a> is for:</p>
<pre class="m-code"><span class="n">CORRADE_CPU_DISPATCHED_POINTER</span><span class="p">(</span><span class="n">memrchrImplementation</span><span class="p">,</span>
<span class="k">const</span> <span class="kt">char</span><span class="o">*</span><span class="p">(</span><span class="o">*</span><span class="n">memrchr</span><span class="p">)(</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="kt">size_t</span><span class="p">,</span> <span class="kt">char</span><span class="p">))</span></pre>
<p>In­tern­ally, it simply as­signs the res­ult of a call to
<code class="cpp m-code"><span class="n">memrchrImplementation</span><span class="p">(</span><span class="n">Cpu</span><span class="o">::</span><span class="n">Features</span><span class="p">)</span></code>, defined with the
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/Cpu_8h.html#a0b29ab5881e2e0b24ff443100d5aa81e">COR­RADE_CPU_DIS­PATCH­ER­_­BASE()</a> macro above, to a <code>memrchr</code> func­tion
point­er:</p>
<pre class="m-code"><span class="cp">#define CORRADE_CPU_DISPATCHED_POINTER(dispatcher, ...) \</span>
<span class="cp"> __VA_ARGS__ = dispatcher(Corrade::Cpu::runtimeFeatures());</span></pre>
<p>Since this all hap­pens in a glob­al con­struct­or, the <code class="cpp m-code"><span class="n">memrchr</span></code> func­tion
point­er is ready for use without any ex­pli­cit setup call. All that’s needed is
ex­pos­ing the point­er in some pub­lic head­er as an <code class="cpp m-code"><span class="k">extern</span></code>:</p>
<pre class="m-code"><span class="k">extern</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span><span class="p">(</span><span class="o">*</span><span class="n">memrchr</span><span class="p">)(</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="kt">size_t</span><span class="p">,</span> <span class="kt">char</span><span class="p">);</span>
<span class="err">…</span>
<span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">found</span> <span class="o">=</span> <span class="n">memrchr</span><span class="p">(</span><span class="err">…</span><span class="p">);</span></pre>
<section id="the-magic-of-ifunc">
<h3><a href="#the-magic-of-ifunc">The ma­gic of IFUNC</a></h3>
<p>This sounds pretty much ideal already, so what does
<a href="https://sourceware.org/glibc/wiki/GNU_IFUNC">IFUNC</a> ac­tu­ally bring to the
table? In short, it turns the point­er in­to a reg­u­lar func­tion. Which means,
in­stead of first hav­ing to load the value of the func­tion point­er from
<em>some­where</em> and only then call­ing it, it’s like any oth­er dy­nam­ic lib­rary
func­tion call.<a class="m-footnote" href="#id10" id="id4">4</a></p>
<p>Us­age-wise it’s not much dif­fer­ent from the func­tion point­er ap­proach. A
dis­patch­er func­tion is as­so­ci­ated with a func­tion pro­to­type, which the dy­nam­ic
load­er then uses to as­sign the pro­to­type a con­crete func­tion point­er. This all
hap­pens dur­ing early star­tup, so the dis­patch­er code can’t really do much —
es­pe­cially not call­ing in­to ex­tern­al lib­rar­ies, which may not even be there yet
at that point. Prac­tic­ally it means <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/namespaceCorrade_1_1Cpu.html#a9afada18b534309f7700f1cb79eeae28">Cpu::runtime­Fea­tures()</a> has to be
fully in­lined in or­der to be us­able in this con­text.</p>
<p>Here’s how the above would look with <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/Cpu_8h.html#afcbf5e42884c152a1bd6631928dd3e59">COR­RADE_CPU_DIS­PATCHED_I­FUNC()</a>
in­stead:</p>
<pre class="m-code"><span class="n">CORRADE_CPU_DISPATCHED_IFUNC</span><span class="p">(</span><span class="n">memrchrImplementation</span><span class="p">,</span>
<span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">memrchr</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="kt">size_t</span><span class="p">,</span> <span class="kt">char</span><span class="p">))</span></pre>
<p>In the macro im­ple­ment­a­tion, a func­tion is an­not­ated with
<code class="cpp m-code"><span class="n">__attribute__</span><span class="p">((</span><span class="n">ifunc</span><span class="p">))</span></code> car­ry­ing a name of the dis­patch­er func­tion. The
dis­patch­er func­tion gets called with no ar­gu­ments, so the macro cre­ates a
<code class="cpp m-code"><span class="n">memrchrImplementation</span><span class="p">()</span></code> wrap­per that del­eg­ates to
<code class="cpp m-code"><span class="n">memrchrImplementation</span><span class="p">(</span><span class="n">Cpu</span><span class="o">::</span><span class="n">Features</span><span class="p">)</span></code>. What the com­piler manu­al doesn’t
say is that the dis­patch­er func­tion has to have C link­age in or­der to be found.</p>
<pre class="m-code"><span class="cp">#define CORRADE_CPU_DISPATCHED_IFUNC(dispatcher, ...) \</span>
<span class="cp"> extern "C" { static auto dispatcher() { \</span>
<span class="cp"> return dispatcher(Corrade::Cpu::runtimeFeatures()); \</span>
<span class="cp"> }} \</span>
<span class="cp"> __VA_ARGS__ __attribute__((ifunc(#dispatcher)));</span></pre>
<p>The only down­side of this ap­proach is that it’s a glibc-spe­cif­ic fea­ture, thus
mainly just Linux (and An­droid, as I’ll de­tail later). Apart from low-level
glibc code us­ing it, this is also the back­bone of GCC’s and Clang’s
<a href="https://lwn.net/Articles/691932/">func­tion multi-ver­sion­ing</a>. So far I’m not
aware of any­thing sim­il­arly con­veni­ent on ma­cOS or Win­dows, ex­cept maybe for
the <code>cpu_dispatch</code> at­trib­ute in <a href="https://clang.llvm.org/docs/AttributeReference.html#cpu-dispatch">Clang</a>
and <a href="https://www.intel.com/content/www/us/en/develop/documentation/cpp-compiler-developer-guide-and-reference/top/compiler-reference/attributes/cpu-dispatch-cpu-specific.html">ICC</a>.
But that one, as far as I can tell, dis­patches on every call, and at least in
case of ICC is <a href="https://www.agner.org/optimize/blog/read.php?i=49">lim­ited only to In­tel pro­cessors</a>.
No ARM but no AMD either.</p>
</section>
<section id="even-less-overhead-please">
<h3><a href="#even-less-overhead-please">Even less over­head, please?</a></h3>
<p>In cer­tain cases it may be de­sir­able to not go through a dy­nam­ic­ally dis­patched
func­tion at all in or­der to get be­ne­fits from in­ter­pro­ced­ur­al op­tim­iz­a­tions and
LTO. In that case, the dis­patch­er can se­lect an over­load at com­pile time us­ing
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/namespaceCorrade_1_1Cpu.html#a7eb0fdea7fc6203c8abe112c2b58271a">Cpu::De­fault­Base</a>, sim­il­arly to what the very first ex­ample was show­ing:</p>
<pre class="m-code"><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="nf">memrchr</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">data</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="kt">size_t</span> <span class="n">size</span><span class="p">,</span> <span class="kt">char</span> <span class="n">c</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">memrchrImplementation</span><span class="p">(</span><span class="n">Cpu</span><span class="o">::</span><span class="n">DefaultBase</span><span class="p">)(</span><span class="n">data</span><span class="p">,</span> <span class="n">size</span><span class="p">,</span> <span class="n">c</span><span class="p">);</span>
<span class="p">}</span></pre>
<p>In my ex­per­i­ments at least, with com­piler op­tim­iz­a­tions en­abled, the whole
re­turned lambda gets in­lined here, re­mov­ing any re­main­ing ar­gu­ment-passing
over­head. Thus be­ing identic­al to the ideal case where the high-level func­tion
would dir­ectly con­tain op­tim­ized AVX or SSE code.</p>
</section>
<section id="overhead-comparison">
<h3><a href="#overhead-comparison">Over­head com­par­is­on</a></h3>
<p>The fol­low­ing plot com­pares the three ap­proaches. Apart from the weird out­lier
with a func­tion point­er in a dy­nam­ic lib­rary that I can’t ex­plain, it shows
that a reg­u­lar func­tion call is the clear win­ner in case the code can be
com­piled dir­ectly for the hard­ware the it will run on. IFUNC ul­ti­mately isn’t
any faster than reg­u­lar point­ers, but isn’t really slower either, in this
mi­crobench­mark at least. I sup­pose in real-world scen­ari­os it could be­ne­fit at
least from cache loc­al­ity with oth­er dy­nam­ic func­tions.</p>
<div class="m-plot">
<svg viewBox="0 0 576 257.76">
<defs>
<style type="text/css">*{stroke-linejoin: round; stroke-linecap: butt}</style>
</defs>
<g id="figure_1">
<g id="axes_1">
<g id="patch_1">
<path d="M 119.145 214.705312 L 564.12 214.705312 L 564.12 27.136406 L 119.145 27.136406 z" class="m-background"/>
</g>
<g id="plot1-value0-0"><title>1.26 ± 0.06 ns</title>
<path d="M 119.145 35.662266 L 326.110116 35.662266 L 326.110116 55.723111 L 119.145 55.723111 z" clip-path="url(#p86ce701213)" class="m-bar m-success"/>
</g>
<g id="plot1-value0-1"><title>1.51 ± 0.06 ns</title>
<path d="M 119.145 60.738323 L 367.174623 60.738323 L 367.174623 80.799168 L 119.145 80.799168 z" clip-path="url(#p86ce701213)" class="m-bar m-info"/>
</g>
<g id="plot1-value0-2"><title>1.52 ± 0.08 ns</title>
<path d="M 119.145 85.81438 L 368.817204 85.81438 L 368.817204 105.875225 L 119.145 105.875225 z" clip-path="url(#p86ce701213)" class="m-bar m-info"/>
</g>
<g id="plot1-value0-3"><title>1.52 ± 0.07 ns</title>
<path d="M 119.145 110.890437 L 368.817204 110.890437 L 368.817204 130.951282 L 119.145 130.951282 z" clip-path="url(#p86ce701213)" class="m-bar m-info"/>
</g>
<g id="plot1-value0-4"><title>1.25 ± 0.05 ns</title>
<path d="M 119.145 135.966494 L 324.467536 135.966494 L 324.467536 156.027339 L 119.145 156.027339 z" clip-path="url(#p86ce701213)" class="m-bar m-dim"/>
</g>
<g id="plot1-value0-5"><title>1.5 ± 0.06 ns</title>
<path d="M 119.145 161.042551 L 365.532043 161.042551 L 365.532043 181.103396 L 119.145 181.103396 z" clip-path="url(#p86ce701213)" class="m-bar m-info"/>
</g>
<g id="plot1-value0-6"><title>2.5 ± 0.08 ns</title>
<path d="M 119.145 186.118608 L 529.790072 186.118608 L 529.790072 206.179453 L 119.145 206.179453 z" clip-path="url(#p86ce701213)" class="m-bar m-danger"/>
</g>
<g id="matplotlib.axis_1">
<g id="xtick_1">
<g id="line2d_1">
<defs>
<path id="mc4bea1de13" d="M 0 0 L 0 3.5" class="m-line"/>
</defs>
<g>
<use xlink:href="#mc4bea1de13" x="119.145" y="214.705312"/>
</g>
</g>
<g id="text_1">
<text class="m-label" style="text-anchor: middle" x="119.145" y="229.537656" transform="rotate(-0, 119.145, 229.537656)">0.0</text>
</g>
</g>
<g id="xtick_2">
<g id="line2d_2">
<g>
<use xlink:href="#mc4bea1de13" x="201.274014" y="214.705312"/>
</g>
</g>
<g id="text_2">
<text class="m-label" style="text-anchor: middle" x="201.274014" y="229.537656" transform="rotate(-0, 201.274014, 229.537656)">0.5</text>
</g>
</g>
<g id="xtick_3">
<g id="line2d_3">
<g>
<use xlink:href="#mc4bea1de13" x="283.403029" y="214.705312"/>
</g>
</g>
<g id="text_3">
<text class="m-label" style="text-anchor: middle" x="283.403029" y="229.537656" transform="rotate(-0, 283.403029, 229.537656)">1.0</text>
</g>
</g>
<g id="xtick_4">
<g id="line2d_4">
<g>
<use xlink:href="#mc4bea1de13" x="365.532043" y="214.705312"/>
</g>
</g>
<g id="text_4">
<text class="m-label" style="text-anchor: middle" x="365.532043" y="229.537656" transform="rotate(-0, 365.532043, 229.537656)">1.5</text>
</g>
</g>
<g id="xtick_5">
<g id="line2d_5">
<g>
<use xlink:href="#mc4bea1de13" x="447.661058" y="214.705312"/>
</g>
</g>
<g id="text_5">
<text class="m-label" style="text-anchor: middle" x="447.661058" y="229.537656" transform="rotate(-0, 447.661058, 229.537656)">2.0</text>
</g>
</g>
<g id="xtick_6">
<g id="line2d_6">
<g>
<use xlink:href="#mc4bea1de13" x="529.790072" y="214.705312"/>
</g>
</g>
<g id="text_6">
<text class="m-label" style="text-anchor: middle" x="529.790072" y="229.537656" transform="rotate(-0, 529.790072, 229.537656)">2.5</text>
</g>
</g>
<g id="text_7">
<text class="m-label" style="text-anchor: middle" x="341.6325" y="243.625" transform="rotate(-0, 341.6325, 243.625)">ns</text>
</g>
</g>
<g id="matplotlib.axis_2">
<g id="ytick_1">
<g id="line2d_7">
<defs>
<path id="mae1c673641" d="M 0 0 L -3.5 0" class="m-line"/>
</defs>
<g>
<use xlink:href="#mae1c673641" x="119.145" y="45.692688"/>
</g>
</g>
<g id="text_8">
<text class="m-label" style="text-anchor: end" x="112.145" y="49.675032" transform="rotate(-0, 112.145, 49.675032)">Regular function</text>
</g>
</g>
<g id="ytick_2">
<g id="line2d_8">
<g>
<use xlink:href="#mae1c673641" x="119.145" y="70.768745"/>
</g>
</g>
<g id="text_9">
<text class="m-label" style="text-anchor: end" x="112.145" y="74.684917" transform="rotate(-0, 112.145, 74.684917)">Function pointer</text>
</g>
</g>
<g id="ytick_3">
<g id="line2d_9">
<g>
<use xlink:href="#mae1c673641" x="119.145" y="95.844802"/>
</g>
</g>
<g id="text_10">
<text class="m-label" style="text-anchor: end" x="112.145" y="99.760974" transform="rotate(-0, 112.145, 99.760974)">IFUNC function</text>
</g>
</g>
<g id="ytick_4">
<g id="line2d_10">
<g>
<use xlink:href="#mae1c673641" x="119.145" y="120.920859"/>
</g>
</g>
<g id="text_11">
<text class="m-label" transform="translate(35.84625 119.754688)">Regular function</text>
<text class="m-label" transform="translate(112.145 131.618188)"/>
</g>
</g>
<g id="ytick_5">
<g id="line2d_11">
<g>
<use xlink:href="#mae1c673641" x="119.145" y="145.996916"/>
</g>
</g>
<g id="text_12">
<text class="m-label" transform="translate(35.24125 144.869416)">Function pointer</text>
<text class="m-label" transform="translate(112.145 156.523229)"/>
</g>
</g>
<g id="ytick_6">
<g id="line2d_12">
<g>
<use xlink:href="#mae1c673641" x="119.145" y="171.072973"/>
</g>
</g>
<g id="text_13">
<text class="m-label" transform="translate(42.46 169.945473)">IFUNC function</text>
<text class="m-label" transform="translate(112.145 181.599286)"/>
</g>
</g>
<g id="ytick_7">
<g id="line2d_13">
<g>
<use xlink:href="#mae1c673641" x="119.145" y="196.14903"/>
</g>
</g>
<g id="text_14">
<text class="m-label" transform="translate(11.88 194.999187)">Dispatch on every call</text>
<text class="m-label" transform="translate(112.145 206.697687)"/>
</g>
</g>
</g>
<g id="LineCollection_1">
<path d="M 316.254635 45.692688 L 335.965598 45.692688" clip-path="url(#p86ce701213)" class="m-error"/>
<path d="M 357.319142 70.768745 L 377.030105 70.768745" clip-path="url(#p86ce701213)" class="m-error"/>
<path d="M 355.676561 95.844802 L 381.957846 95.844802" clip-path="url(#p86ce701213)" class="m-error"/>
<path d="M 357.319142 120.920859 L 380.315266 120.920859" clip-path="url(#p86ce701213)" class="m-error"/>
<path d="M 316.254635 145.996916 L 332.680437 145.996916" clip-path="url(#p86ce701213)" class="m-error"/>
<path d="M 355.676561 171.072973 L 375.387525 171.072973" clip-path="url(#p86ce701213)" class="m-error"/>
<path d="M 516.64943 196.14903 L 542.930714 196.14903" clip-path="url(#p86ce701213)" class="m-error"/>
</g>
<g id="line2d_14">
<defs>
<path id="m1487d016e7" d="M 0 5 L 0 -5" class="m-error"/>
</defs>
<g clip-path="url(#p86ce701213)">
<use xlink:href="#m1487d016e7" x="316.254635" y="45.692688"/>
<use xlink:href="#m1487d016e7" x="357.319142" y="70.768745"/>
<use xlink:href="#m1487d016e7" x="355.676561" y="95.844802"/>
<use xlink:href="#m1487d016e7" x="357.319142" y="120.920859"/>
<use xlink:href="#m1487d016e7" x="316.254635" y="145.996916"/>
<use xlink:href="#m1487d016e7" x="355.676561" y="171.072973"/>
<use xlink:href="#m1487d016e7" x="516.64943" y="196.14903"/>
</g>
</g>
<g id="line2d_15">
<g clip-path="url(#p86ce701213)">
<use xlink:href="#m1487d016e7" x="335.965598" y="45.692688"/>
<use xlink:href="#m1487d016e7" x="377.030105" y="70.768745"/>
<use xlink:href="#m1487d016e7" x="381.957846" y="95.844802"/>
<use xlink:href="#m1487d016e7" x="380.315266" y="120.920859"/>
<use xlink:href="#m1487d016e7" x="332.680437" y="145.996916"/>
<use xlink:href="#m1487d016e7" x="375.387525" y="171.072973"/>
<use xlink:href="#m1487d016e7" x="542.930714" y="196.14903"/>
</g>
</g>
<g id="text_15">
<text class="m-label m-dim" transform="translate(112.145 119.114084)"/>
<text class="m-label m-dim" transform="translate(21.633906 130.767897)">in a dynamic library</text>
</g>
<g id="text_16">
<text class="m-label m-dim" transform="translate(112.145 144.190141)"/>
<text class="m-label m-dim" transform="translate(21.633906 155.843954)">in a dynamic library</text>
</g>
<g id="text_17">
<text class="m-label m-dim" transform="translate(112.145 169.266198)"/>
<text class="m-label m-dim" transform="translate(21.633906 180.920011)">in a dynamic library</text>
</g>
<g id="text_18">
<text class="m-label m-dim" transform="translate(112.145 194.342255)"/>
<text class="m-label m-dim" transform="translate(21.633906 205.996068)">in a dynamic library</text>
</g>
<g id="text_19">
<text class="m-title" style="text-anchor: middle" x="341.6325" y="21.136406" transform="rotate(-0, 341.6325, 21.136406)">Dispatch overhead, Linux x86-64</text>
</g>
</g>
</g>
<defs>
<clipPath id="p86ce701213">
<rect x="119.145" y="27.136406" width="444.975" height="187.568906"/>
</clipPath>
</defs>
</svg>
</div>
<aside class="m-block m-success">
<h3>Testing considerations</h3>
<p>There’s one hid­den be­ne­fit with the func­tion point­er ap­proach — be­ing
able to change the point­er later at runtime. To­geth­er with pub­licly
ex­pos­ing the <code class="cpp m-code"><span class="n">memrchrImplementation</span><span class="p">(</span><span class="n">Cpu</span><span class="o">::</span><span class="n">Features</span><span class="p">)</span></code> dis­patch­er this is
very use­ful for test­ing, as a test can go through all vari­ants and veri­fy
each without hav­ing to re­com­pile for a dif­fer­ent tar­get. Not only that,
it’s even pos­sible for a bench­mark to sup­ply al­tern­at­ive im­ple­ment­a­tions to
com­pare against the built­in ones. This is how a test case veri­fy­ing all
three al­tern­at­ives could look like with Cor­rade’s
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/classCorrade_1_1TestSuite_1_1Tester.html">Test­Suite</a>:</p>
<pre class="m-code"><span class="k">const</span> <span class="k">struct</span> <span class="p">{</span>
<span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">name</span><span class="p">;</span>
<span class="n">Cpu</span><span class="o">::</span><span class="n">Features</span> <span class="n">features</span><span class="p">;</span>
<span class="p">}</span> <span class="n">TestData</span><span class="p">[]{</span>
<span class="p">{</span><span class="s">"scalar"</span><span class="p">,</span> <span class="n">Cpu</span><span class="o">::</span><span class="n">Scalar</span><span class="p">},</span>
<span class="p">{</span><span class="s">"SSE2"</span><span class="p">,</span> <span class="n">Cpu</span><span class="o">::</span><span class="n">Sse2</span><span class="p">},</span>
<span class="p">{</span><span class="s">"AVX2"</span><span class="p">,</span> <span class="n">Cpu</span><span class="o">::</span><span class="n">Avx2</span><span class="p">},</span>
<span class="p">};</span>
<span class="kt">void</span> <span class="n">MemrchrTest</span><span class="o">::</span><span class="n">test</span><span class="p">()</span> <span class="p">{</span>
<span class="k">auto</span><span class="o">&&</span> <span class="n">data</span> <span class="o">=</span> <span class="n">TestData</span><span class="p">[</span><span class="n">testCaseInstanceId</span><span class="p">()];</span>
<span class="n">setTestCaseDescription</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">name</span><span class="p">);</span>
<span class="n">memrchr</span> <span class="o">=</span> <span class="n">memrchrImplementation</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">features</span><span class="p">);</span>
<span class="err">…</span>
<span class="p">}</span></pre>
</aside>
</section>
</section>
<section id="compiling-different-functions-for-different-targets">
<h2><a href="#compiling-different-functions-for-different-targets">Com­pil­ing dif­fer­ent func­tions for dif­fer­ent tar­gets</a></h2>
<p>In or­der to use in­trins­ics for a par­tic­u­lar CPU in­struc­tion set, GCC and Clang
re­quire the code to be com­piled with a cor­res­pond­ing tar­get op­tion, such as
<code>-mavx2</code> for AVX2. Such re­quire­ment makes sense, as it al­lows the com­piler to
per­form its own op­tim­iz­a­tions on top of the ex­pli­cit in­trins­ics calls. Hav­ing a
sep­ar­ate file for every vari­ant would be quite im­prac­tic­al though, for­tu­nately
one can use <code class="cpp m-code"><span class="n">__attribute__</span><span class="p">((</span><span class="n">target</span><span class="p">))</span></code> to en­able in­struc­tion sets just on
par­tic­u­lar func­tions, al­low­ing all vari­ants to live in the same file.<a class="m-footnote" href="#id11" id="id5">5</a></p>
<p>This is ex­posed via mac­ros such as <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/Cpu_8h.html#aa4dab6243f7b22397fd546518d64a8b0">COR­RADE_EN­ABLE_AVX2</a>, and ad­di­tion­ally
each macro is defined only if com­pil­ing for a match­ing ar­chi­tec­ture and the
com­piler sup­ports giv­en in­struc­tion set. Which can be con­veni­ently used to
guard the code to be only com­piled where it makes sense:</p>
<pre class="m-code"><span class="cp">#ifdef CORRADE_ENABLE_AVX2</span>
<span class="n">CORRADE_ALWAYS_INLINE</span> <span class="k">auto</span> <span class="nf">memrchrImplementation</span><span class="p">(</span><span class="n">Cpu</span><span class="o">::</span><span class="n">Avx2T</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="o">+</span><span class="p">[](</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">data</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="kt">size_t</span> <span class="n">size</span><span class="p">,</span> <span class="kt">char</span> <span class="n">c</span><span class="p">)</span> <span class="n">CORRADE_ENABLE_AVX2</span> <span class="p">{</span> <span class="err">…</span> <span class="p">};</span>
<span class="p">}</span>
<span class="cp">#endif</span></pre>
<p>MS­VC, on the oth­er hand, doesn’t re­quire any op­tion in or­der to use any
in­trins­ics, so there the mac­ros are empty. It how­ever also means that it will
only ap­ply the baseline op­tim­iz­a­tions, so for ex­ample ex­tract­ing all AVX+ code
to a file with <code>/arch:AVX</code> en­abled might have some perf be­ne­fits.<a class="m-footnote" href="#id11" id="id6">5</a></p>
</section>
<p class="m-transition">^ v ^</p>
<section id="and-then-the-reality-hits">
<h2><a href="#and-then-the-reality-hits">And then the real­ity hits</a></h2>
<p>So far, to make the code shine, I was hold­ing back on a cer­tain im­port­ant
as­pect of the in­struc­tion set land­scape. In that it’s <em>def­in­itely not</em> a lin­ear
se­quence of in­struc­tion sets where each is a clear su­per­set of the pre­vi­ous
one. Rather, it looks more like this:</p>
<div class="m-row m-container-inflate">
<div class="m-col-m-12">
<div class="m-graph">
<svg style="width: 55.625rem; height: 21.938rem;" viewBox="0.00 0.00 890.00 351.18">
<g transform="scale(1 1) rotate(0) translate(4 347.18)">
<title>x86 instruction family tree</title>
<g class="m-node">
<title>FMA</title>
<ellipse cx="666" cy="-302.68" rx="21.5" ry="21.5"/>
<text text-anchor="middle" x="666" y="-299.28" style="font-size: 13.0px;">FMA</text>
</g>
<g class="m-node">
<title>F16C</title>
<ellipse cx="576" cy="-321.68" rx="21.5" ry="21.5"/>
<text text-anchor="middle" x="576" y="-318.28" style="font-size: 13.0px;">F16C</text>
</g>
<g class="m-node m-default">
<title>AVX</title>
<ellipse cx="486" cy="-207.68" rx="36" ry="36"/>
<text text-anchor="middle" x="486" y="-203.88">AVX</text>
</g>
<g class="m-edge">
<title>FMA->AVX</title>
<path d="M645.02,-296.9C619.54,-289.05 574.64,-273.55 540,-252.68 533.46,-248.74 526.92,-244 520.77,-239.1"/>
<polygon points="522.6,-236.07 512.67,-232.37 518.13,-241.45 522.6,-236.07"/>
</g>
<g class="m-edge">
<title>F16C->AVX</title>
<path d="M559.75,-307.16C553.37,-300.8 546.06,-293.11 540,-285.68 530,-273.41 519.99,-259.29 511.41,-246.45"/>
<polygon points="514.14,-244.24 505.71,-237.81 508.29,-248.09 514.14,-244.24"/>
</g>
<g class="m-node">
<title>LZCNT</title>
<ellipse cx="216" cy="-28.68" rx="21.5" ry="21.5"/>
<text text-anchor="middle" x="216" y="-25.28" style="font-size: 13.0px;">LZCNT</text>
</g>
<g class="m-node">
<title>POPCNT</title>
<ellipse cx="126" cy="-21.68" rx="21.86" ry="21.86"/>
<text text-anchor="middle" x="126" y="-18.88" style="font-size: 11.0px;">POPCNT</text>
</g>
<g class="m-node m-default">
<title>SSE3</title>
<ellipse cx="126" cy="-97.68" rx="36" ry="36"/>
<text text-anchor="middle" x="126" y="-93.88">SSE3</text>
</g>
<g class="m-node">
<title>BMI1</title>
<ellipse cx="306" cy="-28.68" rx="21.5" ry="21.5"/>
<text text-anchor="middle" x="306" y="-25.28" style="font-size: 13.0px;">BMI1</text>
</g>
<g class="m-node m-default">
<title>SSE2</title>
<ellipse cx="36" cy="-97.68" rx="36" ry="36"/>
<text text-anchor="middle" x="36" y="-93.88">SSE2</text>
</g>
<g class="m-edge m-default">
<title>SSE3->SSE2</title>
<path d="M89.97,-97.68C87.41,-97.68 84.81,-97.68 82.2,-97.68"/>
<polygon points="82.2,-94.18 72.2,-97.68 82.2,-101.18 82.2,-94.18"/>
</g>
<g class="m-node m-default">
<title>SSSE3</title>
<ellipse cx="216" cy="-169.68" rx="36" ry="36"/>
<text text-anchor="middle" x="216" y="-165.88">SSSE3</text>
</g>
<g class="m-edge m-default">
<title>SSSE3->SSE3</title>
<path d="M187.64,-147.29C179.61,-140.73 170.71,-133.45 162.26,-126.53"/>
<polygon points="164.3,-123.68 154.34,-120.05 159.87,-129.1 164.3,-123.68"/>
</g>
<g class="m-node m-default">
<title>SSE41</title>
<ellipse cx="306" cy="-207.68" rx="36" ry="36"/>
<text text-anchor="middle" x="306" y="-203.88">SSE4.1</text>
</g>
<g class="m-edge m-default">
<title>SSE41->SSSE3</title>
<path d="M272.73,-193.75C268.19,-191.79 263.46,-189.75 258.77,-187.72"/>
<polygon points="259.83,-184.36 249.26,-183.61 257.06,-190.79 259.83,-184.36"/>
</g>
<g class="m-node m-default">
<title>SSE42</title>
<ellipse cx="396" cy="-207.68" rx="36" ry="36"/>
<text text-anchor="middle" x="396" y="-203.88">SSE4.2</text>
</g>
<g class="m-edge m-default">
<title>SSE42->SSE41</title>
<path d="M359.97,-207.68C357.41,-207.68 354.81,-207.68 352.2,-207.68"/>
<polygon points="352.2,-204.18 342.2,-207.68 352.2,-211.18 352.2,-204.18"/>
</g>
<g class="m-edge m-default">
<title>AVX->SSE42</title>
<path d="M449.97,-207.68C447.41,-207.68 444.81,-207.68 442.2,-207.68"/>
<polygon points="442.2,-204.18 432.2,-207.68 442.2,-211.18 442.2,-204.18"/>
</g>
<g class="m-node m-default">
<title>AVX2</title>
<ellipse cx="576" cy="-207.68" rx="36" ry="36"/>
<text text-anchor="middle" x="576" y="-203.88">AVX2</text>
</g>
<g class="m-edge m-default">
<title>AVX2->AVX</title>
<path d="M539.97,-207.68C537.41,-207.68 534.81,-207.68 532.2,-207.68"/>
<polygon points="532.2,-204.18 522.2,-207.68 532.2,-211.18 532.2,-204.18"/>
</g>
<g class="m-node m-default">
<title>AVX512F</title>
<ellipse cx="666" cy="-207.68" rx="36" ry="36"/>
<text text-anchor="middle" x="666" y="-211.28" style="font-size: 13.0px;">AVX512F</text>
<text text-anchor="middle" x="666" y="-197.28" style="font-size: 13.0px;">AVX512CD</text>
</g>
<g class="m-edge m-default">
<title>AVX512F->AVX2</title>
<path d="M629.97,-207.68C627.41,-207.68 624.81,-207.68 622.2,-207.68"/>
<polygon points="622.2,-204.18 612.2,-207.68 622.2,-211.18 622.2,-204.18"/>
</g>
<g class="m-node">
<title>AVX512VLDQBW</title>
<ellipse cx="756" cy="-252.68" rx="36" ry="36"/>
<text text-anchor="middle" x="756" y="-261.88" style="font-size: 11.0px;">AVX512VL</text>
<text text-anchor="middle" x="756" y="-249.88" style="font-size: 11.0px;">AVX512DQ</text>
<text text-anchor="middle" x="756" y="-237.88" style="font-size: 11.0px;">AVX512BW</text>
</g>
<g class="m-edge m-default">
<title>AVX512VLDQBW->AVX512F</title>
<path d="M723.73,-236.69C718.47,-234 712.95,-231.18 707.51,-228.4"/>
<polygon points="708.97,-225.21 698.47,-223.77 705.78,-231.44 708.97,-225.21"/>
</g>
<g class="m-node">
<title>VBMI2</title>
<ellipse cx="846" cy="-252.68" rx="36" ry="36"/>
<text text-anchor="middle" x="846" y="-268.78" style="font-size: 8.0px;">VBMI2</text>
<text text-anchor="middle" x="846" y="-259.78" style="font-size: 8.0px;">BITALG</text>
<text text-anchor="middle" x="846" y="-250.78" style="font-size: 8.0px;">VPCLMULQDQ</text>
<text text-anchor="middle" x="846" y="-241.78" style="font-size: 8.0px;">GFNI</text>
<text text-anchor="middle" x="846" y="-232.78" style="font-size: 8.0px;">VAES</text>
</g>
<g class="m-edge">
<title>VBMI2->AVX512VLDQBW</title>
<path d="M809.97,-252.68C807.41,-252.68 804.81,-252.68 802.2,-252.68"/>
<polygon points="802.2,-249.18 792.2,-252.68 802.2,-256.18 802.2,-249.18"/>
</g>
<g class="m-node">
<title>SSE4a</title>
<ellipse cx="216" cy="-93.68" rx="21.5" ry="21.5"/>
<text text-anchor="middle" x="216" y="-90.28" style="font-size: 13.0px;">SSE4a</text>
</g>
<g class="m-edge">
<title>SSE4a->SSE3</title>
<path d="M194.15,-94.63C187.5,-94.93 179.87,-95.28 172.2,-95.63"/>
<polygon points="171.99,-92.13 162.16,-96.08 172.3,-99.13 171.99,-92.13"/>
</g>
<g class="m-node">
<title>SSE5</title>
<ellipse cx="306" cy="-131.68" rx="21.5" ry="21.5"/>
<text text-anchor="middle" x="306" y="-128.28" style="font-size: 13.0px;">SSE5</text>
</g>
<g class="m-edge">
<title>SSE5->SSSE3</title>
<path d="M285.91,-139.93C277.87,-143.4 268.18,-147.58 258.64,-151.7"/>
<polygon points="257,-148.59 249.21,-155.77 259.78,-155.02 257,-148.59"/>
</g>
<g class="m-node">
<title>XOP</title>
<ellipse cx="666" cy="-112.68" rx="21.5" ry="21.5"/>
<text text-anchor="middle" x="666" y="-109.28" style="font-size: 13.0px;">XOP</text>
</g>
<g class="m-edge">
<title>XOP->AVX</title>
<path d="M645.02,-118.46C619.54,-126.32 574.64,-141.81 540,-162.68 533.46,-166.62 526.92,-171.36 520.77,-176.27"/>
<polygon points="518.13,-173.91 512.67,-183 522.6,-179.29 518.13,-173.91"/>
</g>
<g class="m-node">
<title>FMA4</title>
<ellipse cx="576" cy="-93.68" rx="21.5" ry="21.5"/>
<text text-anchor="middle" x="576" y="-90.28" style="font-size: 13.0px;">FMA4</text>
</g>
<g class="m-edge">
<title>FMA4->AVX</title>
<path d="M559.75,-108.2C553.37,-114.56 546.06,-122.25 540,-129.68 530,-141.95 519.99,-156.08 511.41,-168.91"/>
<polygon points="508.29,-167.27 505.71,-177.55 514.14,-171.13 508.29,-167.27"/>
</g>
<g class="m-node">
<title>AVX512ERPF</title>
<ellipse cx="756" cy="-162.68" rx="36" ry="36"/>
<text text-anchor="middle" x="756" y="-166.28" style="font-size: 13.0px;">AVX512ER</text>
<text text-anchor="middle" x="756" y="-152.28" style="font-size: 13.0px;">AVX512PF</text>
</g>
<g class="m-edge">
<title>AVX512ERPF->AVX512F</title>
<path d="M723.73,-178.67C718.47,-181.36 712.95,-184.18 707.51,-186.97"/>
<polygon points="705.78,-183.92 698.47,-191.59 708.97,-190.15 705.78,-183.92"/>
</g>
<g class="m-node">
<title>FMAPS</title>
<ellipse cx="846" cy="-162.68" rx="36" ry="36"/>
<text text-anchor="middle" x="846" y="-171.18" style="font-size: 10.0px;">4FMAPS</text>
<text text-anchor="middle" x="846" y="-160.18" style="font-size: 10.0px;">4VNNIW</text>
<text text-anchor="middle" x="846" y="-149.18" style="font-size: 10.0px;">VPOPCNTDQ</text>
</g>
<g class="m-edge">
<title>FMAPS->AVX512ERPF</title>
<path d="M809.97,-162.68C807.41,-162.68 804.81,-162.68 802.2,-162.68"/>
<polygon points="802.2,-159.18 792.2,-162.68 802.2,-166.18 802.2,-159.18"/>
</g>
</g>
</svg>
</div>
<p class="m-text m-small m-dim m-text-center">Please note there’s <em>many</em> in­struc­tion sets still omit­ted and the hier­archy
likely con­tains severe er­rors.</p>
</div>
</div>
<p>With the passing of time many of these branches for­tu­nately went aban­doned,
how­ever there’s still many cases where any or­der­ing is just im­possible. For
ex­ample, there are AVX CPUs that have <a href="https://en.wikipedia.org/wiki/F16C">F16C</a>
but not <a href="https://en.wikipedia.org/wiki/FMA_instruction_set">FMA</a>, there are
AVX CPUs with FMA but no F16C, and there’s ap­par­ently even a
<a href="https://stackoverflow.com/a/50829580">VIA CPU with AVX2 but no FMA</a>. To
ac­count for all these cases, I ended up dif­fer­en­ti­at­ing between a
<em>base in­struc­tion set</em> that has clear or­der­ing and <em>ex­tra in­struc­tion sets</em>
that have no re­la­tions what­so­ever. For AVX-512 I’m not 100% sure yet, but
I’m hop­ing it’ll <a href="https://news.ycombinator.com/item?id=31533314">even­tu­ally con­verge as well</a>.</p>
<div class="m-row m-container-inflate">
<div class="m-col-m-12">
<div class="m-graph">
<svg style="width: 55.625rem; height: 21.938rem;" viewBox="0.00 0.00 890.00 351.18">
<g transform="scale(1 1) rotate(0) translate(4 347.18)">
<title>x86 instruction family tree</title>
<g class="m-node m-primary">
<title>FMA</title>
<ellipse cx="666" cy="-302.68" rx="21.5" ry="21.5"/>
<text text-anchor="middle" x="666" y="-299.28" style="font-size: 13.0px;">FMA</text>
</g>
<g class="m-node m-primary">
<title>F16C</title>
<ellipse cx="576" cy="-321.68" rx="21.5" ry="21.5"/>
<text text-anchor="middle" x="576" y="-318.28" style="font-size: 13.0px;">F16C</text>
</g>
<g class="m-node m-success">
<title>AVX</title>
<ellipse cx="486" cy="-207.68" rx="36" ry="36"/>
<text text-anchor="middle" x="486" y="-203.88">AVX</text>
</g>
<g class="m-edge m-primary">
<title>FMA->AVX</title>
<path d="M645.02,-296.9C619.54,-289.05 574.64,-273.55 540,-252.68 533.46,-248.74 526.92,-244 520.77,-239.1"/>
<polygon points="522.6,-236.07 512.67,-232.37 518.13,-241.45 522.6,-236.07"/>
</g>
<g class="m-edge m-primary">
<title>F16C->AVX</title>
<path d="M559.75,-307.16C553.37,-300.8 546.06,-293.11 540,-285.68 530,-273.41 519.99,-259.29 511.41,-246.45"/>
<polygon points="514.14,-244.24 505.71,-237.81 508.29,-248.09 514.14,-244.24"/>
</g>
<g class="m-node m-primary">
<title>LZCNT</title>
<ellipse cx="216" cy="-28.68" rx="21.5" ry="21.5"/>
<text text-anchor="middle" x="216" y="-25.28" style="font-size: 13.0px;">LZCNT</text>
</g>
<g class="m-node m-primary">
<title>POPCNT</title>
<ellipse cx="126" cy="-21.68" rx="21.86" ry="21.86"/>
<text text-anchor="middle" x="126" y="-18.88" style="font-size: 11.0px;">POPCNT</text>
</g>
<g class="m-node m-success">
<title>SSE3</title>
<ellipse cx="126" cy="-97.68" rx="36" ry="36"/>
<text text-anchor="middle" x="126" y="-93.88">SSE3</text>
</g>
<g class="m-node m-primary">
<title>BMI1</title>
<ellipse cx="306" cy="-28.68" rx="21.5" ry="21.5"/>
<text text-anchor="middle" x="306" y="-25.28" style="font-size: 13.0px;">BMI1</text>
</g>
<g class="m-node m-success">
<title>SSE2</title>
<ellipse cx="36" cy="-97.68" rx="36" ry="36"/>
<text text-anchor="middle" x="36" y="-93.88">SSE2</text>
</g>
<g class="m-edge m-success">
<title>SSE3->SSE2</title>
<path d="M89.97,-97.68C87.41,-97.68 84.81,-97.68 82.2,-97.68"/>
<polygon points="82.2,-94.18 72.2,-97.68 82.2,-101.18 82.2,-94.18"/>
</g>
<g class="m-node m-success">
<title>SSSE3</title>
<ellipse cx="216" cy="-169.68" rx="36" ry="36"/>
<text text-anchor="middle" x="216" y="-165.88">SSSE3</text>
</g>
<g class="m-edge m-success">
<title>SSSE3->SSE3</title>
<path d="M187.64,-147.29C179.61,-140.73 170.71,-133.45 162.26,-126.53"/>
<polygon points="164.3,-123.68 154.34,-120.05 159.87,-129.1 164.3,-123.68"/>
</g>
<g class="m-node m-success">
<title>SSE41</title>
<ellipse cx="306" cy="-207.68" rx="36" ry="36"/>
<text text-anchor="middle" x="306" y="-203.88">SSE4.1</text>
</g>
<g class="m-edge m-success">
<title>SSE41->SSSE3</title>
<path d="M272.73,-193.75C268.19,-191.79 263.46,-189.75 258.77,-187.72"/>
<polygon points="259.83,-184.36 249.26,-183.61 257.06,-190.79 259.83,-184.36"/>
</g>
<g class="m-node m-success">
<title>SSE42</title>
<ellipse cx="396" cy="-207.68" rx="36" ry="36"/>
<text text-anchor="middle" x="396" y="-203.88">SSE4.2</text>
</g>
<g class="m-edge m-success">
<title>SSE42->SSE41</title>
<path d="M359.97,-207.68C357.41,-207.68 354.81,-207.68 352.2,-207.68"/>
<polygon points="352.2,-204.18 342.2,-207.68 352.2,-211.18 352.2,-204.18"/>
</g>
<g class="m-edge m-success">
<title>AVX->SSE42</title>
<path d="M449.97,-207.68C447.41,-207.68 444.81,-207.68 442.2,-207.68"/>
<polygon points="442.2,-204.18 432.2,-207.68 442.2,-211.18 442.2,-204.18"/>
</g>
<g class="m-node m-success">
<title>AVX2</title>
<ellipse cx="576" cy="-207.68" rx="36" ry="36"/>
<text text-anchor="middle" x="576" y="-203.88">AVX2</text>
</g>
<g class="m-edge m-success">
<title>AVX2->AVX</title>
<path d="M539.97,-207.68C537.41,-207.68 534.81,-207.68 532.2,-207.68"/>
<polygon points="532.2,-204.18 522.2,-207.68 532.2,-211.18 532.2,-204.18"/>
</g>
<g class="m-node m-success">
<title>AVX512F</title>
<ellipse cx="666" cy="-207.68" rx="36" ry="36"/>
<text text-anchor="middle" x="666" y="-211.28" style="font-size: 13.0px;">AVX512F</text>
<text text-anchor="middle" x="666" y="-197.28" style="font-size: 13.0px;">AVX512CD</text>
</g>
<g class="m-edge m-success">
<title>AVX512F->AVX2</title>
<path d="M629.97,-207.68C627.41,-207.68 624.81,-207.68 622.2,-207.68"/>
<polygon points="622.2,-204.18 612.2,-207.68 622.2,-211.18 622.2,-204.18"/>
</g>
<g class="m-node">
<title>AVX512VLDQBW</title>
<ellipse cx="756" cy="-252.68" rx="36" ry="36"/>
<text text-anchor="middle" x="756" y="-261.88" style="font-size: 11.0px;">AVX512VL</text>
<text text-anchor="middle" x="756" y="-249.88" style="font-size: 11.0px;">AVX512DQ</text>
<text text-anchor="middle" x="756" y="-237.88" style="font-size: 11.0px;">AVX512BW</text>
</g>
<g class="m-edge">
<title>AVX512VLDQBW->AVX512F</title>
<path d="M723.73,-236.69C718.47,-234 712.95,-231.18 707.51,-228.4"/>
<polygon points="708.97,-225.21 698.47,-223.77 705.78,-231.44 708.97,-225.21"/>
</g>
<g class="m-node">
<title>VBMI2</title>
<ellipse cx="846" cy="-252.68" rx="36" ry="36"/>
<text text-anchor="middle" x="846" y="-268.78" style="font-size: 8.0px;">VBMI2</text>
<text text-anchor="middle" x="846" y="-259.78" style="font-size: 8.0px;">BITALG</text>
<text text-anchor="middle" x="846" y="-250.78" style="font-size: 8.0px;">VPCLMULQDQ</text>
<text text-anchor="middle" x="846" y="-241.78" style="font-size: 8.0px;">GFNI</text>
<text text-anchor="middle" x="846" y="-232.78" style="font-size: 8.0px;">VAES</text>
</g>
<g class="m-edge">
<title>VBMI2->AVX512VLDQBW</title>
<path d="M809.97,-252.68C807.41,-252.68 804.81,-252.68 802.2,-252.68"/>
<polygon points="802.2,-249.18 792.2,-252.68 802.2,-256.18 802.2,-249.18"/>
</g>
<g class="m-node m-dim">
<title>SSE4a</title>
<ellipse cx="216" cy="-93.68" rx="21.5" ry="21.5"/>
<text text-anchor="middle" x="216" y="-90.28" style="font-size: 13.0px;">SSE4a</text>
</g>
<g class="m-edge m-dim">
<title>SSE4a->SSE3</title>
<path d="M194.15,-94.63C187.5,-94.93 179.87,-95.28 172.2,-95.63"/>
<polygon points="171.99,-92.13 162.16,-96.08 172.3,-99.13 171.99,-92.13"/>
</g>
<g class="m-node m-dim">
<title>SSE5</title>
<ellipse cx="306" cy="-131.68" rx="21.5" ry="21.5"/>
<text text-anchor="middle" x="306" y="-128.28" style="font-size: 13.0px;">SSE5</text>
</g>
<g class="m-edge m-dim">
<title>SSE5->SSSE3</title>
<path d="M285.91,-139.93C277.87,-143.4 268.18,-147.58 258.64,-151.7"/>
<polygon points="257,-148.59 249.21,-155.77 259.78,-155.02 257,-148.59"/>
</g>
<g class="m-node m-dim">
<title>XOP</title>
<ellipse cx="666" cy="-112.68" rx="21.5" ry="21.5"/>
<text text-anchor="middle" x="666" y="-109.28" style="font-size: 13.0px;">XOP</text>
</g>
<g class="m-edge m-dim">
<title>XOP->AVX</title>
<path d="M645.02,-118.46C619.54,-126.32 574.64,-141.81 540,-162.68 533.46,-166.62 526.92,-171.36 520.77,-176.27"/>
<polygon points="518.13,-173.91 512.67,-183 522.6,-179.29 518.13,-173.91"/>
</g>
<g class="m-node m-dim">
<title>FMA4</title>
<ellipse cx="576" cy="-93.68" rx="21.5" ry="21.5"/>
<text text-anchor="middle" x="576" y="-90.28" style="font-size: 13.0px;">FMA4</text>
</g>
<g class="m-edge m-dim">
<title>FMA4->AVX</title>
<path d="M559.75,-108.2C553.37,-114.56 546.06,-122.25 540,-129.68 530,-141.95 519.99,-156.08 511.41,-168.91"/>
<polygon points="508.29,-167.27 505.71,-177.55 514.14,-171.13 508.29,-167.27"/>
</g>
<g class="m-node m-dim">
<title>AVX512ERPF</title>
<ellipse cx="756" cy="-162.68" rx="36" ry="36"/>
<text text-anchor="middle" x="756" y="-166.28" style="font-size: 13.0px;">AVX512ER</text>
<text text-anchor="middle" x="756" y="-152.28" style="font-size: 13.0px;">AVX512PF</text>
</g>
<g class="m-edge m-dim">
<title>AVX512ERPF->AVX512F</title>
<path d="M723.73,-178.67C718.47,-181.36 712.95,-184.18 707.51,-186.97"/>
<polygon points="705.78,-183.92 698.47,-191.59 708.97,-190.15 705.78,-183.92"/>
</g>
<g class="m-node m-dim">
<title>FMAPS</title>
<ellipse cx="846" cy="-162.68" rx="36" ry="36"/>
<text text-anchor="middle" x="846" y="-171.18" style="font-size: 10.0px;">4FMAPS</text>
<text text-anchor="middle" x="846" y="-160.18" style="font-size: 10.0px;">4VNNIW</text>
<text text-anchor="middle" x="846" y="-149.18" style="font-size: 10.0px;">VPOPCNTDQ</text>
</g>
<g class="m-edge m-dim">
<title>FMAPS->AVX512ERPF</title>
<path d="M809.97,-162.68C807.41,-162.68 804.81,-162.68 802.2,-162.68"/>
<polygon points="802.2,-159.18 792.2,-162.68 802.2,-166.18 802.2,-159.18"/>
</g>
</g>
</svg>
</div>
<p class="m-text m-small m-text-center m-noindent"><span class="m-label m-success">base sets</span> · <span class="m-label m-default">ex­pec­ted con­tinu­ation</span> · <span class="m-label m-primary">ex­tra sets</span> · <span class="m-label m-dim">aban­doned sets</span></p>
</div>
</div>
<p>To make this work, the dis­patch has to se­lect among vari­ants with both the base
in­struc­tion set and all ex­tra sets sup­por­ted, and these two pri­or­ity rules:</p>
<ul>
<li>If one vari­ant has the base in­struc­tion set more ad­vanced than an­oth­er,
it’s pre­ferred (which is same as be­fore). For ex­ample, if there’s an
AVX-512 vari­ant and an AVX+FMA vari­ant, AVX-512 gets picked.</li>
<li>Oth­er­wise, if both vari­ants have the same base in­struc­tion set, a vari­ant
that uses more ex­tra in­struc­tion sets is pre­ferred. For ex­ample, if there’s
a plain AVX vari­ant and an AVX+FMA vari­ant, AVX+FMA gets picked.</li>
</ul>
<p>There’s de­lib­er­ately no or­der­ing defined among the ex­tra in­struc­tion sets. Thus
hav­ing for ex­ample a SSE2+POP­CNT and a SSE2+LZCNT vari­ant would lead to an
am­bi­gu­ity if the ma­chine sup­por­ted both <a href="https://en.wikipedia.org/wiki/X86_Bit_manipulation_instruction_set#ABM_(Advanced_Bit_Manipulation)">POP­CNT</a>
and <a href="https://en.wikipedia.org/wiki/X86_Bit_manipulation_instruction_set#ABM_(Advanced_Bit_Manipulation)">LZCNT</a>.
This can only be re­solved by the im­ple­ment­a­tion it­self, by provid­ing a
SSE2+POP­CNT+LZCNT vari­ant and del­eg­at­ing to whichever is the bet­ter op­tion in
that case.</p>
</section>
<section id="now-comes-the-pile-of-nasty-templates">
<h2><a href="#now-comes-the-pile-of-nasty-templates">Now comes the pile of nasty tem­plates</a></h2>
<p>Let’s say there’s a <code class="cpp m-code"><span class="n">Tag</span><span class="o"><</span><span class="n">bits</span><span class="o">></span></code> type that stores the bit­mask of both the
base in­struc­tion set and the ex­tra sets. An <em>ex­tra</em> in­struc­tion set is al­ways
rep­res­en­ted by just a single unique bit, while a <em>base</em> in­struc­tion set
con­tains one unique bit for it­self plus also all bits for the in­struc­tion sets
it’s based on. Thus, for ex­ample, SSE2 would be <code class="cpp m-code"><span class="n">Tag</span><span class="o"><</span><span class="mi">0b00000001</span><span class="o">></span></code>, SSE3
<code class="cpp m-code"><span class="n">Tag</span><span class="o"><</span><span class="mi">0b00000011</span><span class="o">></span></code>, SSSE3 <code class="cpp m-code"><span class="n">Tag</span><span class="o"><</span><span class="mi">0b00000111</span><span class="o">></span></code> and so on, while POP­CNT
would be <code class="cpp m-code"><span class="n">Tag</span><span class="o"><</span><span class="mi">0b01000000</span><span class="o">></span></code> and LZCNT <code class="cpp m-code"><span class="n">Tag</span><span class="o"><</span><span class="mi">0b10000000</span><span class="o">></span></code>. Then,
com­bined, SSE3+POP­CNT would be <code class="cpp m-code"><span class="n">Tag</span><span class="o"><</span><span class="mi">0b01000011</span><span class="o">></span></code>. Fi­nally, the class
provides a con­ver­sion op­er­at­or to an­oth­er <code class="cpp m-code"><span class="n">Tag</span><span class="o"><></span></code> that’s en­abled only if
the res­ult­ing bit mask is a sub­set:</p>
<pre class="m-code"><span class="k">template</span><span class="o"><</span><span class="kt">unsigned</span> <span class="n">bits</span><span class="o">></span> <span class="k">struct</span> <span class="n">Tag</span> <span class="p">{</span>
<span class="k">template</span><span class="o"><</span>
<span class="kt">unsigned</span> <span class="n">otherBits</span><span class="p">,</span>
<span class="k">class</span> <span class="err">= </span><span class="nc">std</span><span class="o">::</span><span class="n">enable_if_t</span><span class="o"><</span><span class="p">(</span><span class="n">otherBits</span> <span class="o">&</span> <span class="n">bits</span><span class="p">)</span> <span class="o">==</span> <span class="n">otherBits</span><span class="o">></span>
<span class="o">></span> <span class="k">operator</span> <span class="n">Tag</span><span class="o"><</span><span class="n">otherBits</span><span class="o">></span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span> <span class="err">…</span> <span class="p">}</span>
<span class="p">};</span></pre>
<p>The pri­or­ity or­der­ing is defined primar­ily by the in­dex of the base in­struc­tion
set, and sec­ond­ar­ily by the count of ex­tra in­struc­tion sets. Which, giv­en an
up­per bound on the count of ex­tra in­struc­tion sets, can be rep­res­en­ted with a
single num­ber — for ex­ample, SSE2 would have a pri­or­ity num­ber <code class="cpp m-code"><span class="mi">100</span></code>,
SSE3 <code class="cpp m-code"><span class="mi">200</span></code>, SSSE3 <code class="cpp m-code"><span class="mi">300</span></code> and so on, SSE2+LZCNT+POP­CNT would be
<code class="cpp m-code"><span class="mi">102</span></code> and SSE3+POP­CNT <code class="cpp m-code"><span class="mi">201</span></code>. Here’s where the core idea of
in­her­it­ance hier­archy gets re­used again, just in a cal­cu­lated man­ner:</p>
<figure class="m-code-figure">
<pre class="m-code"><span class="k">template</span><span class="o"><</span><span class="kt">unsigned</span> <span class="n">i</span><span class="o">></span> <span class="k">struct</span> <span class="nl">Priority</span><span class="p">:</span> <span class="n">Priority</span><span class="o"><</span><span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="o">></span> <span class="p">{};</span>
<span class="k">template</span><span class="o"><></span> <span class="k">struct</span> <span class="n">Priority</span><span class="o"><</span><span class="mi">0</span><span class="o">></span> <span class="p">{};</span></pre>
<p class="m-text m-small m-dim m-noindent">I claim no in­ven­tion here — cred­it goes to <a href="https://github.com/sthalik">@sthalik</a> who
<a href="https://gist.github.com/sthalik/0828be17146ddad36f4a3e11b47cbbcc">sug­ges­ted the pri­or­ity tag idea to me</a>.</p>
</figure>
<p>To make these work to­geth­er, the dis­patch has to use two func­tion ar­gu­ments —
one is where the can­did­ate fil­ter­ing hap­pens, and the oth­er or­ders by pri­or­ity.
In the fol­low­ing ex­ample, call­ing with <code class="cpp m-code"><span class="n">Tag</span><span class="o"><</span><span class="mi">0b11000011</span><span class="o">></span></code> and
<code class="cpp m-code"><span class="n">Priority</span><span class="o"><</span><span class="mi">202</span><span class="o">></span></code> (SSE3+LZCNT+POP­CNT), would pick the SSE3+POP­CNT over­load:</p>
<pre class="m-code"><span class="kt">void</span> <span class="nf">foo</span><span class="p">(</span><span class="n">Tag</span><span class="o"><</span><span class="mi">0b00000000</span><span class="o">></span><span class="p">,</span> <span class="n">Priority</span><span class="o"><</span><span class="mo">000</span><span class="o">></span><span class="p">)</span> <span class="p">{</span> <span class="err">…</span> <span class="p">}</span> <span class="cm">/* scalar */</span>
<span class="kt">void</span> <span class="nf">foo</span><span class="p">(</span><span class="n">Tag</span><span class="o"><</span><span class="mi">0b00000011</span><span class="o">></span><span class="p">,</span> <span class="n">Priority</span><span class="o"><</span><span class="mi">200</span><span class="o">></span><span class="p">)</span> <span class="p">{</span> <span class="err">…</span> <span class="p">}</span> <span class="cm">/* SSE3 */</span>
<span class="kt">void</span> <span class="nf">foo</span><span class="p">(</span><span class="n">Tag</span><span class="o"><</span><span class="mi">0b01000011</span><span class="o">></span><span class="p">,</span> <span class="n">Priority</span><span class="o"><</span><span class="mi">201</span><span class="o">></span><span class="p">)</span> <span class="p">{</span> <span class="err">…</span> <span class="p">}</span> <span class="cm">/* SSE3+POPCNT */</span>
<span class="kt">void</span> <span class="nf">foo</span><span class="p">(</span><span class="n">Tag</span><span class="o"><</span><span class="mi">0b10000111</span><span class="o">></span><span class="p">,</span> <span class="n">Priority</span><span class="o"><</span><span class="mi">301</span><span class="o">></span><span class="p">)</span> <span class="p">{</span> <span class="err">…</span> <span class="p">}</span> <span class="cm">/* SSSE3+LZCNT */</span></pre>
<p>Be­cause I was un­able to fig­ure out a way that wouldn’t in­volve us­ing two
para­met­ers, the user-fa­cing API hides this whole pro­cess be­hind two mac­ros —
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/Cpu_8h.html#a61150aae89029e7693e6f6690fb65a5c">COR­RADE_CPU_­DE­CLARE()</a> and <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/Cpu_8h.html#aac01b038141283c0d628106d50739d56">COR­RADE_CPU_SE­LECT()</a>. To­geth­er with an
abil­ity to com­bine the <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/namespaceCorrade_1_1Cpu.html">Cpu</a> tags with bit­wise op­er­a­tions, the pro­cess of
de­clar­a­tion and com­pile-time dis­patch looks like this:</p>
<pre class="m-code"><span class="kt">void</span> <span class="nf">foo</span><span class="p">(</span><span class="n">CORRADE_CPU_DECLARE</span><span class="p">(</span><span class="n">Cpu</span><span class="o">::</span><span class="n">Scalar</span><span class="p">))</span> <span class="p">{</span> <span class="err">…</span> <span class="p">}</span>
<span class="kt">void</span> <span class="nf">foo</span><span class="p">(</span><span class="n">CORRADE_CPU_DECLARE</span><span class="p">(</span><span class="n">Cpu</span><span class="o">::</span><span class="n">Sse3</span><span class="p">))</span> <span class="p">{</span> <span class="err">…</span> <span class="p">}</span>
<span class="kt">void</span> <span class="nf">foo</span><span class="p">(</span><span class="n">CORRADE_CPU_DECLARE</span><span class="p">(</span><span class="n">Cpu</span><span class="o">::</span><span class="n">Sse3</span><span class="o">|</span><span class="n">Cpu</span><span class="o">::</span><span class="n">Popcnt</span><span class="p">))</span> <span class="p">{</span> <span class="err">…</span> <span class="p">}</span>
<span class="kt">void</span> <span class="nf">foo</span><span class="p">(</span><span class="n">CORRADE_CPU_DECLARE</span><span class="p">(</span><span class="n">Cpu</span><span class="o">::</span><span class="n">Ssse3</span><span class="o">|</span><span class="n">Cpu</span><span class="o">::</span><span class="n">Lzcnt</span><span class="p">))</span> <span class="p">{</span> <span class="err">…</span> <span class="p">}</span>
<span class="err">…</span>
<span class="n">foo</span><span class="p">(</span><span class="n">CORRADE_CPU_SELECT</span><span class="p">(</span><span class="n">Cpu</span><span class="o">::</span><span class="n">Sse3</span><span class="o">|</span><span class="n">Cpu</span><span class="o">::</span><span class="n">Lzcnt</span><span class="o">|</span><span class="n">Cpu</span><span class="o">::</span><span class="n">Popcnt</span><span class="p">));</span></pre>
<p>Fi­nally, the runtime dis­patch­er then has to take the ex­tra in­struc­tion sets
in­to ac­count as well. Im­pli­citly con­sid­er­ing all pos­sible com­bin­a­tions would
how­ever lead to a lot of work for the com­piler. As a tradeoff, since in
prac­tice the vari­ants usu­ally need at most one or two ex­tra sets, the
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/Cpu_8h.html#ad4532aef8ce891c64b8b1612039e872e">COR­RADE_CPU_DIS­PATCH­ER()</a> macro re­quires you to list them ex­pli­citly:</p>
<pre class="m-code"><span class="n">CORRADE_CPU_DISPATCHER</span><span class="p">(</span><span class="n">foo</span><span class="p">,</span> <span class="n">Cpu</span><span class="o">::</span><span class="n">Popcnt</span><span class="p">,</span> <span class="n">Cpu</span><span class="o">::</span><span class="n">Lzcnt</span><span class="p">)</span></pre>
<p>This macro pro­duces a <code class="cpp m-code"><span class="n">foo</span><span class="p">(</span><span class="n">Cpu</span><span class="o">::</span><span class="n">Features</span><span class="p">)</span></code> which can be again used with
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/Cpu_8h.html#a5378d07b7bfa36af2adb7d45003e2c8f">COR­RADE_CPU_DIS­PATCHED_­POINT­ER()</a> or <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/Cpu_8h.html#afcbf5e42884c152a1bd6631928dd3e59">COR­RADE_CPU_DIS­PATCHED_I­FUNC()</a>.
For brev­ity I glossed over the nas­ti­er im­ple­ment­a­tion de­tails, if you’re
in­ter­ested please <a href="https://github.com/mosra/corrade/blob/simd/src/Corrade/Cpu.h">dive in­to the source</a>.</p>
</section>
<p class="m-transition">~ ~ ~</p>
<section id="practical-usability-verification">
<h2><a href="#practical-usability-verification">Prac­tic­al us­ab­il­ity veri­fic­a­tion</a></h2>
<p>To en­sure san­ity of this design, I pro­ceeded with us­ing the <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/namespaceCorrade_1_1Cpu.html">Cpu</a> APIs in
a prac­tic­al scen­ario — im­ple­ment­ing a <a class="m-flat" href="https://en.cppreference.com/w/cpp/string/byte/memchr">std::mem­chr()</a> al­tern­at­ive to use
in <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/classCorrade_1_1Containers_1_1BasicStringView.html#aaa0df864a8b8dc423addf136c41fe0c3">Con­tain­ers::StringView::find()</a>.
Turns out I was lucky, hav­ing not really any pri­or ex­per­i­ence writ­ing
in­trins­ics for any ar­chi­tec­ture, I man­aged to pick a suf­fi­ciently hard prob­lem
where</p>
<ul>
<li>com­piler autovec­tor­iz­a­tion doesn’t do a “good enough” job,</li>
<li>the stand­ard im­ple­ment­a­tion is well op­tim­ized and not easy to beat,</li>
<li>the al­gorithm needs to have spe­cial­ized hand­ling for small and large in­puts
in or­der to be fast,</li>
<li>I get to use also the ex­tra in­struc­tion sets such as <a href="https://en.wikipedia.org/wiki/X86_Bit_manipulation_instruction_set#BMI1_(Bit_Manipulation_Instruction_Set_1">BMI1</a>,</li>
<li>and the ideal way is <em>sig­ni­fic­antly</em> dif­fer­ent between x86, ARM and
WebAssembly.</li>
</ul>
<p>Apart from hav­ing the API us­ab­il­ity con­firmed (and a ton of ugly com­piler bugs
dis­covered), I real­ized that in or­der to really make the most of every
plat­form, it doesn’t make sense to try to come up with an in­struc­tion-level
ab­strac­tion API like is for ex­ample in <a href="https://github.com/simd-everywhere/simde">SIMD every­where</a>.
So that’s some­thing Mag­num will not have. The build­ing blocks need to be much
more high level.</p>
<p>Since I’m very new to all this, I won’t em­bar­rass my­self fur­ther by try­ing to
pre­tend I know what I’m talk­ing about. Hope­fully in a later post — here’s
just a sneak peek at the <a href="https://github.com/mosra/corrade/blob/c17fc86e4234bfab592a11fd40c2493f41c38f60/src/Corrade/Containers/StringView.cpp/issues/L157-L703">code that’s now in Cor­rade mas­ter</a>:</p>
<div class="m-plot">
<svg viewBox="0 0 576 200.16">
<defs>
<style type="text/css">*{stroke-linejoin: round; stroke-linecap: butt}</style>
</defs>
<g id="figure_1">
<g id="axes_1">
<g id="patch_1">
<path d="M 133.118437 157.105313 L 564.12 157.105313 L 564.12 27.136406 L 133.118437 27.136406 z" class="m-background"/>
</g>
<g id="plot2-value0-0"><title>1.0 ± 0.0 time relative to std::memchr()</title>
<path d="M 133.118437 33.044084 L 458.481214 33.044084 L 458.481214 52.736342 L 133.118437 52.736342 z" clip-path="url(#pd82deab164)" class="m-bar m-info"/>
</g>
<g id="plot2-value0-1"><title>1.2098 ± 0.0518 time relative to std::memchr()</title>
<path d="M 133.118437 57.659407 L 526.742324 57.659407 L 526.742324 77.351665 L 133.118437 77.351665 z" clip-path="url(#pd82deab164)" class="m-bar m-warning"/>
</g>
<g id="plot2-value0-2"><title>0.8908 ± 0.072 time relative to std::memchr()</title>
<path d="M 133.118437 82.27473 L 422.951599 82.27473 L 422.951599 101.966989 L 133.118437 101.966989 z" clip-path="url(#pd82deab164)" class="m-bar m-success"/>
</g>
<g id="plot2-value0-3"><title>0.7013 ± 0.0065 time relative to std::memchr()</title>
<path d="M 133.118437 106.890053 L 361.295353 106.890053 L 361.295353 126.582312 L 133.118437 126.582312 z" clip-path="url(#pd82deab164)" class="m-bar m-success"/>
</g>
<g id="plot2-value0-4"><title>0.1369 ± 0.0274 time relative to std::memchr()</title>
<path d="M 133.118437 131.505376 L 177.660602 131.505376 L 177.660602 151.197635 L 133.118437 151.197635 z" clip-path="url(#pd82deab164)" class="m-bar m-success"/>
</g>
<g id="matplotlib.axis_1">
<g id="xtick_1">
<g id="line2d_1">
<defs>
<path id="m6c4cdc507b" d="M 0 0 L 0 3.5" class="m-line"/>
</defs>
<g>
<use xlink:href="#m6c4cdc507b" x="133.118437" y="157.105313"/>
</g>
</g>
<g id="text_1">
<text class="m-label" style="text-anchor: middle" x="133.118437" y="171.937656" transform="rotate(-0, 133.118437, 171.937656)">0.0</text>
</g>
</g>
<g id="xtick_2">
<g id="line2d_2">
<g>
<use xlink:href="#m6c4cdc507b" x="198.190993" y="157.105313"/>
</g>
</g>
<g id="text_2">
<text class="m-label" style="text-anchor: middle" x="198.190993" y="171.937656" transform="rotate(-0, 198.190993, 171.937656)">0.2</text>
</g>
</g>
<g id="xtick_3">
<g id="line2d_3">
<g>
<use xlink:href="#m6c4cdc507b" x="263.263548" y="157.105313"/>
</g>
</g>
<g id="text_3">
<text class="m-label" style="text-anchor: middle" x="263.263548" y="171.937656" transform="rotate(-0, 263.263548, 171.937656)">0.4</text>
</g>
</g>
<g id="xtick_4">
<g id="line2d_4">
<g>
<use xlink:href="#m6c4cdc507b" x="328.336103" y="157.105313"/>
</g>
</g>
<g id="text_4">
<text class="m-label" style="text-anchor: middle" x="328.336103" y="171.937656" transform="rotate(-0, 328.336103, 171.937656)">0.6</text>
</g>
</g>
<g id="xtick_5">
<g id="line2d_5">
<g>
<use xlink:href="#m6c4cdc507b" x="393.408659" y="157.105313"/>
</g>
</g>
<g id="text_5">
<text class="m-label" style="text-anchor: middle" x="393.408659" y="171.937656" transform="rotate(-0, 393.408659, 171.937656)">0.8</text>
</g>
</g>
<g id="xtick_6">
<g id="line2d_6">
<g>
<use xlink:href="#m6c4cdc507b" x="458.481214" y="157.105313"/>
</g>
</g>
<g id="text_6">
<text class="m-label" style="text-anchor: middle" x="458.481214" y="171.937656" transform="rotate(-0, 458.481214, 171.937656)">1.0</text>
</g>
</g>
<g id="xtick_7">
<g id="line2d_7">
<g>
<use xlink:href="#m6c4cdc507b" x="523.553769" y="157.105313"/>
</g>
</g>
<g id="text_7">
<text class="m-label" style="text-anchor: middle" x="523.553769" y="171.937656" transform="rotate(-0, 523.553769, 171.937656)">1.2</text>
</g>
</g>
<g id="text_8">
<text class="m-label" style="text-anchor: middle" x="348.619219" y="186.025" transform="rotate(-0, 348.619219, 186.025)">time relative to std::memchr()</text>
</g>
</g>
<g id="matplotlib.axis_2">
<g id="ytick_1">
<g id="line2d_8">
<defs>
<path id="m74ed764a78" d="M 0 0 L -3.5 0" class="m-line"/>
</defs>
<g>
<use xlink:href="#m74ed764a78" x="133.118437" y="42.890213"/>
</g>
</g>
<g id="text_9">
<text class="m-label" transform="translate(60.962344 41.762713)">std::memchr()</text>
<text class="m-label" transform="translate(126.118437 53.416526)"/>
</g>
</g>
<g id="ytick_2">
<g id="line2d_9">
<g>
<use xlink:href="#m74ed764a78" x="133.118437" y="67.505536"/>
</g>
</g>
<g id="text_10">
<text class="m-label" transform="translate(46.409688 66.383192)">StringView::find()</text>
<text class="m-label" transform="translate(126.118437 78.246692)"/>
</g>
</g>
<g id="ytick_3">
<g id="line2d_10">
<g>
<use xlink:href="#m74ed764a78" x="133.118437" y="92.120859"/>
</g>
</g>
<g id="text_11">
<text class="m-label" transform="translate(46.409688 90.998516)">StringView::find()</text>
<text class="m-label" transform="translate(126.118437 102.862016)"/>
</g>
</g>
<g id="ytick_4">
<g id="line2d_11">
<g>
<use xlink:href="#m74ed764a78" x="133.118437" y="116.736183"/>
</g>
</g>
<g id="text_12">
<text class="m-label" transform="translate(46.409688 115.613839)">StringView::find()</text>
<text class="m-label" transform="translate(126.118437 127.477339)"/>
</g>
</g>
<g id="ytick_5">
<g id="line2d_12">
<g>
<use xlink:href="#m74ed764a78" x="133.118437" y="141.351506"/>
</g>
</g>
<g id="text_13">
<text class="m-label" transform="translate(46.409688 140.229162)">StringView::find()</text>
<text class="m-label" transform="translate(126.118437 152.092662)"/>
</g>
</g>
</g>
<g id="LineCollection_1">
<path d="M 458.481214 42.890213 L 458.481214 42.890213" clip-path="url(#pd82deab164)" class="m-error"/>
<path d="M 509.888532 67.505536 L 543.596116 67.505536" clip-path="url(#pd82deab164)" class="m-error"/>
<path d="M 399.525479 92.120859 L 446.377719 92.120859" clip-path="url(#pd82deab164)" class="m-error"/>
<path d="M 359.180494 116.736183 L 363.410211 116.736183" clip-path="url(#pd82deab164)" class="m-error"/>
<path d="M 168.745662 141.351506 L 186.575542 141.351506" clip-path="url(#pd82deab164)" class="m-error"/>
</g>
<g id="line2d_13">
<defs>
<path id="m3b539238c1" d="M 0 5 L 0 -5" class="m-error"/>
</defs>
<g clip-path="url(#pd82deab164)">
<use xlink:href="#m3b539238c1" x="458.481214" y="42.890213"/>
<use xlink:href="#m3b539238c1" x="509.888532" y="67.505536"/>
<use xlink:href="#m3b539238c1" x="399.525479" y="92.120859"/>
<use xlink:href="#m3b539238c1" x="359.180494" y="116.736183"/>
<use xlink:href="#m3b539238c1" x="168.745662" y="141.351506"/>
</g>
</g>
<g id="line2d_14">
<g clip-path="url(#pd82deab164)">
<use xlink:href="#m3b539238c1" x="458.481214" y="42.890213"/>
<use xlink:href="#m3b539238c1" x="543.596116" y="67.505536"/>
<use xlink:href="#m3b539238c1" x="446.377719" y="92.120859"/>
<use xlink:href="#m3b539238c1" x="363.410211" y="116.736183"/>
<use xlink:href="#m3b539238c1" x="186.575542" y="141.351506"/>
</g>
</g>
<g id="text_14">
<text class="m-label m-dim" transform="translate(126.118437 41.082745)"/>
<text class="m-label m-dim" transform="translate(87.419062 52.736557)">baseline</text>
</g>
<g id="text_15">
<text class="m-label m-dim" transform="translate(126.118437 65.698068)"/>
<text class="m-label m-dim" transform="translate(22.888594 77.351881)">x86 SSE2+BMI1 (Linux)</text>
</g>
<g id="text_16">
<text class="m-label m-dim" transform="translate(126.118437 90.313391)"/>
<text class="m-label m-dim" transform="translate(23.120625 101.967204)">x86 AVX2+BMI1 (Linux)</text>
</g>
<g id="text_17">
<text class="m-label m-dim" transform="translate(126.118437 114.796714)"/>
<text class="m-label m-dim" transform="translate(25.344687 126.714527)">ARM NEON (Apple M1)</text>
</g>
<g id="text_18">
<text class="m-label m-dim" transform="translate(126.118437 139.345866)"/>
<text class="m-label m-dim" transform="translate(11.88 151.263678)">WASM SIMD128 (Node.js)</text>
</g>
<g id="text_19">
<text class="m-title" style="text-anchor: middle" x="348.619219" y="21.136406" transform="rotate(-0, 348.619219, 21.136406)">Character lookup</text>
</g>
</g>
</g>
<defs>
<clipPath id="pd82deab164">
<rect x="133.118437" y="27.136406" width="431.001562" height="129.968906"/>
</clipPath>
</defs>
</svg>
</div>
</section>
<section id="cpu-features-and-dispatch-on-arm-platforms">
<h2><a href="#cpu-features-and-dispatch-on-arm-platforms">CPU fea­tures and dis­patch on ARM plat­forms</a></h2>
<p>In­struc­tion-set-wise, ARM has the ba­sic­ally ubi­quit­ous NEON. To­geth­er with a
few re­cog­nized ex­ten­sions, it’s de­tec­ted as <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/namespaceCorrade_1_1Cpu.html#afb2aa2abe1d692820f60acc7bc839047">Cpu::Neon</a>,
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/namespaceCorrade_1_1Cpu.html#a69ca74be36799b556d04781b78a43a36">Cpu::NeonFma</a> and <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/namespaceCorrade_1_1Cpu.html#adcc7038ce8560bddbe32d7473dcfc5f6">Cpu::NeonFp16</a>. Apart from NEON the main
in­struc­tions sets are SVE and SVE2 and Apple’s own pro­pri­et­ary
<a href="https://gist.github.com/dougallj/7a75a3be1ec69ca550e7c36dc75e0d6f">AMX</a>
(which doesn’t even have any pub­licly avail­able com­piler sup­port yet). I’ll
wire up de­tec­tion for these once they be­come more com­mon.</p>
<p>Com­pared to the x86 CPUID in­struc­tion, runtime de­tec­tion of ARM fea­tures has to
rely on OS-spe­cif­ic calls such as <a class="m-flat" href="https://man.archlinux.org/man/getauxval.3">get­auxv­al()</a>
on Linux or <a class="m-flat" href="https://developer.apple.com/documentation/kernel/1387446-sysctlbyname">sy­sctlby­name()</a>
on Apple plat­forms. What’s nice is that both Linux and An­droid (API 18+)
sup­port IFUNC dis­patch on ARM as well. It’s com­plic­ated by the fact that
<code>getauxval()</code> is a call to an ex­tern­al lib­rary that’s not avail­able at the
time IFUNC point­ers get re­solved so in­stead it’s fed to the re­solv­er from
out­side. An­droid how­ever ad­op­ted such be­ha­vi­or only since API 30 (An­droid 11).
To ac­count for this, the <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/Cpu_8h.html#afcbf5e42884c152a1bd6631928dd3e59">COR­RADE_CPU_DIS­PATCHED_I­FUNC()</a> macro is
spe­cial-cased for ARM and en­abled only on glibc and An­droid 30+. Over­head-wise,
IFUNCs on ARM are com­par­able to x86.</p>
<p>Prac­tic­al us­age of ARM NEON in­trins­ics high­lighted two things. First, it’s a
very dif­fer­ent in­struc­tion set from x86, so na­ively try­ing to emu­late
in­struc­tions like <a href="https://www.felixcloutier.com/x86/pmovmskb">movemask</a>
is not go­ing to be fast. In­stead, <a href="https://twitter.com/Danlark1/status/1539344281336422400">us­ing fea­tures unique to NEON</a>
can yield sig­ni­fic­ant spee­dups. Second, in my ex­per­i­ments at least, GCC seems
to be sig­ni­fic­antly worse than Clang <a href="https://stackoverflow.com/questions/50028458/how-to-treat-result-of-vaddv-u8-in-arm64-as-a-neon-register#comment87123343_50028458">in deal­ing with ARM in­trins­ics</a>.
The stand­ard <a class="m-flat" href="https://en.cppreference.com/w/cpp/string/byte/memchr">std::mem­chr()</a> im­ple­ment­a­tion is usu­ally writ­ten in plain
as­sembly, and while I was able to get my in­trins­ics to a com­par­able speed us­ing
Clang, it was plain im­possible with GCC. I won­der wheth­er it’s the cause or the
con­sequence of An­droid and ma­cOS/iOS, the ma­jor ARM plat­forms, nowadays
ex­clus­ively us­ing Clang. Or maybe I’m just do­ing some­thing wrong, again this is
all new to me.</p>
</section>
<section id="webassembly-128-bit-simd">
<h2><a href="#webassembly-128-bit-simd">WebAssembly 128-bit SIMD</a></h2>
<p>WebAssembly cur­rently provides a single 128-bit SIMD in­struc­tion set, de­tec­ted
as <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/namespaceCorrade_1_1Cpu.html#a5994e7ffbf54ba14ab7ddc95cc5be311">Cpu::Sim­d128</a>. The situ­ation is a bit spe­cif­ic, as se­cur­ity on the
web will al­ways have a pri­or­ity over per­form­ance. WebAssembly mod­ules are
stat­ic­ally veri­fied up­front and if they fail the val­id­a­tion — for ex­ample
be­cause an un­known in­struc­tion was en­countered — they’re re­jec­ted. Since
WebAssembly SIMD is re­l­at­ively new, this means that a bin­ary can either use
any SIMD in­struc­tions any­where, or nowhere at all, to pass val­id­a­tion on VMs
that don’t know about SIMD yet.</p>
<p>While that makes any sort of runtime dis­patch rather use­less, per­haps the more
con­cern­ing is­sue is that runtime dis­patch is <em>pro­hib­it­ively ex­pens­ive</em> —
go­ing through an ar­bit­rary func­tion point­er has a <em>fourty times</em> big­ger
over­head than call­ing a func­tion dir­ectly. The fol­low­ing num­bers are from
Node.js, but both Chro­mi­um and Fire­fox showed a sim­il­ar dis­par­ity.</p>
<div class="m-nopadb m-plot">
<svg viewBox="0 0 576 200.16">
<defs>
<style type="text/css">*{stroke-linejoin: round; stroke-linecap: butt}</style>
</defs>
<g id="figure_1">
<g id="axes_1">
<g id="patch_1">
<path d="M 119.145 157.105313 L 564.12 157.105313 L 564.12 27.136406 L 119.145 27.136406 z" class="m-background"/>
</g>
<g id="plot3-value0-0"><title>0.3 ± 0.04 ns</title>
<path d="M 119.145 33.044084 L 124.820702 33.044084 L 124.820702 52.736342 L 119.145 52.736342 z" clip-path="url(#pe1dc903fce)" class="m-bar m-success"/>
</g>
<g id="plot3-value0-1"><title>0.31 ± 0.05 ns</title>
<path d="M 119.145 57.659407 L 125.009892 57.659407 L 125.009892 77.351665 L 119.145 77.351665 z" clip-path="url(#pe1dc903fce)" class="m-bar m-success"/>
</g>
<g id="plot3-value0-2"><title>0.5 ± 0.03 ns</title>
<path d="M 119.145 82.27473 L 128.604503 82.27473 L 128.604503 101.966989 L 119.145 101.966989 z" clip-path="url(#pe1dc903fce)" class="m-bar m-info"/>
</g>
<g id="plot3-value0-3"><title>21.43 ± 0.51 ns</title>
<path d="M 119.145 106.890053 L 524.579279 106.890053 L 524.579279 126.582312 L 119.145 126.582312 z" clip-path="url(#pe1dc903fce)" class="m-bar m-danger"/>
</g>
<g id="plot3-value0-4"><title>21.76 ± 0.64 ns</title>
<path d="M 119.145 131.505376 L 530.822551 131.505376 L 530.822551 151.197635 L 119.145 151.197635 z" clip-path="url(#pe1dc903fce)" class="m-bar m-danger"/>
</g>
<g id="matplotlib.axis_1">
<g id="xtick_1">
<g id="line2d_1">
<defs>
<path id="m0f8c636808" d="M 0 0 L 0 3.5" class="m-line"/>
</defs>
<g>
<use xlink:href="#m0f8c636808" x="119.145" y="157.105313"/>
</g>
</g>
<g id="text_1">
<text class="m-label" style="text-anchor: middle" x="119.145" y="171.937656" transform="rotate(-0, 119.145, 171.937656)">0</text>
</g>
</g>
<g id="xtick_2">
<g id="line2d_2">
<g>
<use xlink:href="#m0f8c636808" x="213.740026" y="157.105313"/>
</g>
</g>
<g id="text_2">
<text class="m-label" style="text-anchor: middle" x="213.740026" y="171.937656" transform="rotate(-0, 213.740026, 171.937656)">5</text>
</g>
</g>
<g id="xtick_3">
<g id="line2d_3">
<g>
<use xlink:href="#m0f8c636808" x="308.335051" y="157.105313"/>
</g>
</g>
<g id="text_3">
<text class="m-label" style="text-anchor: middle" x="308.335051" y="171.937656" transform="rotate(-0, 308.335051, 171.937656)">10</text>
</g>
</g>
<g id="xtick_4">
<g id="line2d_4">
<g>
<use xlink:href="#m0f8c636808" x="402.930077" y="157.105313"/>
</g>
</g>
<g id="text_4">
<text class="m-label" style="text-anchor: middle" x="402.930077" y="171.937656" transform="rotate(-0, 402.930077, 171.937656)">15</text>
</g>
</g>
<g id="xtick_5">
<g id="line2d_5">
<g>
<use xlink:href="#m0f8c636808" x="497.525102" y="157.105313"/>
</g>
</g>
<g id="text_5">
<text class="m-label" style="text-anchor: middle" x="497.525102" y="171.937656" transform="rotate(-0, 497.525102, 171.937656)">20</text>
</g>
</g>
<g id="text_6">
<text class="m-label" style="text-anchor: middle" x="341.6325" y="186.025" transform="rotate(-0, 341.6325, 186.025)">ns</text>
</g>
</g>
<g id="matplotlib.axis_2">
<g id="ytick_1">
<g id="line2d_6">
<defs>
<path id="m3e62786acc" d="M 0 0 L -3.5 0" class="m-line"/>
</defs>
<g>
<use xlink:href="#m3e62786acc" x="119.145" y="42.890213"/>
</g>
</g>
<g id="text_7">
<text class="m-label" style="text-anchor: end" x="112.145" y="46.806385" transform="rotate(-0, 112.145, 46.806385)">Function</text>
</g>
</g>
<g id="ytick_2">
<g id="line2d_7">
<g>
<use xlink:href="#m3e62786acc" x="119.145" y="67.505536"/>
</g>
</g>
<g id="text_8">
<text class="m-label" style="text-anchor: end" x="112.145" y="71.421708" transform="rotate(-0, 112.145, 71.421708)">Function pointer</text>
</g>
</g>
<g id="ytick_3">
<g id="line2d_8">
<g>
<use xlink:href="#m3e62786acc" x="119.145" y="92.120859"/>
</g>
</g>
<g id="text_9">
<text class="m-label" transform="translate(71.255937 90.993359)">Function</text>
<text class="m-label" transform="translate(112.145 102.647172)"/>
</g>
</g>
<g id="ytick_4">
<g id="line2d_9">
<g>
<use xlink:href="#m3e62786acc" x="119.145" y="116.736183"/>
</g>
</g>
<g id="text_10">
<text class="m-label" transform="translate(35.24125 115.608683)">Function pointer</text>
<text class="m-label" transform="translate(112.145 127.262495)"/>
</g>
</g>
<g id="ytick_5">
<g id="line2d_10">
<g>
<use xlink:href="#m3e62786acc" x="119.145" y="141.351506"/>
</g>
</g>
<g id="text_11">
<text class="m-label" transform="translate(11.88 140.201662)">Dispatch on every call</text>
<text class="m-label" transform="translate(112.145 151.900162)"/>
</g>
</g>
</g>
<g id="LineCollection_1">
<path d="M 124.063941 42.890213 L 125.577462 42.890213" clip-path="url(#pe1dc903fce)" class="m-error"/>
<path d="M 124.063941 67.505536 L 125.955842 67.505536" clip-path="url(#pe1dc903fce)" class="m-error"/>
<path d="M 128.036932 92.120859 L 129.172073 92.120859" clip-path="url(#pe1dc903fce)" class="m-error"/>
<path d="M 514.930587 116.736183 L 534.227972 116.736183" clip-path="url(#pe1dc903fce)" class="m-error"/>
<path d="M 518.714388 141.351506 L 542.930714 141.351506" clip-path="url(#pe1dc903fce)" class="m-error"/>
</g>
<g id="line2d_11">
<defs>
<path id="m1434c070a1" d="M 0 5 L 0 -5" class="m-error"/>
</defs>
<g clip-path="url(#pe1dc903fce)">
<use xlink:href="#m1434c070a1" x="124.063941" y="42.890213"/>
<use xlink:href="#m1434c070a1" x="124.063941" y="67.505536"/>
<use xlink:href="#m1434c070a1" x="128.036932" y="92.120859"/>
<use xlink:href="#m1434c070a1" x="514.930587" y="116.736183"/>
<use xlink:href="#m1434c070a1" x="518.714388" y="141.351506"/>
</g>
</g>
<g id="line2d_12">
<g clip-path="url(#pe1dc903fce)">
<use xlink:href="#m1434c070a1" x="125.577462" y="42.890213"/>
<use xlink:href="#m1434c070a1" x="125.955842" y="67.505536"/>
<use xlink:href="#m1434c070a1" x="129.172073" y="92.120859"/>
<use xlink:href="#m1434c070a1" x="534.227972" y="116.736183"/>
<use xlink:href="#m1434c070a1" x="542.930714" y="141.351506"/>
</g>
</g>
<g id="text_12">
<text class="m-label m-dim" transform="translate(112.145 90.291047)"/>
<text class="m-label m-dim" transform="translate(63.500937 101.94486)">in a library</text>
</g>
<g id="text_13">
<text class="m-label m-dim" transform="translate(112.145 114.906371)"/>
<text class="m-label m-dim" transform="translate(63.500937 126.560183)">in a library</text>
</g>
<g id="text_14">
<text class="m-label m-dim" transform="translate(112.145 139.521694)"/>
<text class="m-label m-dim" transform="translate(63.500937 151.175506)">in a library</text>
</g>
<g id="text_15">
<text class="m-title" style="text-anchor: middle" x="341.6325" y="21.136406" transform="rotate(-0, 341.6325, 21.136406)">Dispatch overhead, Emscripten and WebAssembly</text>
</g>
</g>
</g>
<defs>
<clipPath id="pe1dc903fce">
<rect x="119.145" y="27.136406" width="444.975" height="129.968906"/>
</clipPath>
</defs>
</svg>
</div>
<p class="m-text m-small m-dim m-text-center">Hon­estly I hope I just for­got to en­able some op­tim­iz­a­tion. This is too
much.</p>
<p>While there’s cur­rently no way to per­form runtime de­tec­tion of sup­por­ted CPU
fea­tures, there’s a <a href="https://github.com/WebAssembly/feature-detection/blob/main/proposals/feature-detection/Overview.md">Fea­ture De­tec­tion pro­pos­al</a>
aim­ing to cov­er this gap. But even then, un­less the over­head situ­ation
im­proves, runtime dis­patch will only be use­ful for heav­ier chunks of code —
def­in­itely not for things like <code class="cpp m-code"><span class="n">memchr</span><span class="p">()</span></code>.</p>
<p>Prac­tic­al us­age of WebAssembly SIM­D128 in­trins­ics only fur­ther em­phas­ised that
the dif­fer­ence between x86 and ARM isn’t some­thing to be ig­nored. I have two
vari­ants of my code where us­ing <a class="m-flat" href="https://github.com/WebAssembly/simd/blob/main/proposals/simd/SIMD.md#boolean-horizontal-reductions">was­m_i8x16_any_true()</a>
in­stead of <a class="m-flat" href="https://github.com/WebAssembly/simd/blob/main/proposals/simd/SIMD.md#bitmask-extraction">was­m_i8x16_bit­mask()</a>
makes code 20% faster on ARM, while at the same time mak­ing it al­most 30%
slower on x86. I could use <a href="https://github.com/mozilla/standards-positions/issues/651#issuecomment-1157847992">a hack to dis­tin­guish between x86 and ARM</a>
and then choose an ap­pro­pri­ate vari­ant at runtime, but again, con­sid­er­ing the
over­head of runtime dis­patch this would be far worse than hav­ing just a single
non-dis­patched vari­ant.</p>
<p>For now at least, giv­en there are <a href="https://wonderlandengine.com">users with 99% of their audi­en­ce on mo­bile plat­forms</a>,
I’m con­sid­er­ing provid­ing a com­pile-time op­tion to prefer ARM-friendly
vari­ants. Which would re­use the concept of ex­tra in­struc­tion sets de­scribed
above, for ex­ample as <code class="cpp m-code"><span class="n">Cpu</span><span class="o">::</span><span class="n">Simd128</span><span class="o">|</span><span class="n">Cpu</span><span class="o">::</span><span class="n">WasmArm</span></code>. And once the over­head
situ­ation im­proves, the vari­ants can dir­ectly work with a runtime dis­patch.</p>
</section>
<section id="power-risc-v">
<h2><a href="#power-risc-v">POWER, RISC-V</a></h2>
<p>I … have no idea, sorry. There’s <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/Corrade_8h.html#a1b7f626b036b53c0315f516a8d44df7c">COR­RADE_TAR­GET_­POWER­PC</a> but that’s
about it. It doesn’t how­ever mean the code will stop com­pil­ing on these
plat­forms — since none of the <code>CORRADE_ENABLE_*</code> mac­ros is defined there,
the code will fall back to <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/namespaceCorrade_1_1Cpu.html#ad46bdf541c1b53cbc48b61ada9322763">Cpu::Scal­ar</a> vari­ants, be­hav­ing just like
be­fore the CPU dis­patch was in­tro­duced.</p>
</section>
<section id="next-steps">
<h2><a href="#next-steps">Next steps</a></h2>
<p>While the <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/namespaceCorrade_1_1Cpu.html">Cpu</a> lib­rary can be con­sidered stable and ready for pro­duc­tion
use, Cor­rade it­self has runtime CPU dis­patch en­abled only on Linux if IFUNC is
de­tec­ted to be avail­able. Oth­er plat­forms have it cur­rently off by de­fault,
un­til I’m 100% con­fid­ent about the over­head. In any case, you can use the
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/Corrade_8h.html#a513e068096e1767d87805ded276af0cf">COR­RADE_BUILD_CPU_RUNTIME_DIS­PATCH</a> and <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/Corrade_8h.html#a7dc0a34bc59076520b25e735563cf798">COR­RADE_CPU_USE_I­FUNC</a>
CMake op­tions to toggle this func­tion­al­ity at build time.</p>
<p>For the ac­tu­al SIMD-en­abled al­gorithms, of course the use­ful­ness of <em>nar­rowly</em>
beat­ing <a class="m-flat" href="https://en.cppreference.com/w/cpp/string/byte/memchr">std::mem­chr()</a> is du­bi­ous. But the code can now be trivi­ally
mod­i­fied to im­ple­ment the above-men­tioned
<a class="m-flat" href="https://man.archlinux.org/man/memrchr.3">mem­r­chr()</a> as well as
mak­ing <a href="https://docs.rs/memchr/2.3.0/memchr/fn.memchr3.html">op­tim­ized two- and more-char­ac­ter vari­ants</a>.
That’s where the <em>ac­tu­al</em> gains will be, as multi-char­ac­ter look­up is a com­mon
bot­tle­neck in JSON (glTF) or OBJ pars­ing, and stand­ard lib­rar­ies don’t really
have a ded­ic­ated tool for that.</p>
<p>Among oth­er fea­tures that <span class="m-text m-s">were planned to use SIMD someday</span> were
des­per­ately wait­ing for the CPU dis­patch to get ready are — hier­arch­ic­al
trans­form­a­tion cal­cu­la­tions, batch frust­um cull­ing and oth­er bulk pro­cessing
util­it­ies to make use in your ECS work­flows. Ex­cit­ing times ahead!</p>
</section>
<section id="single-header-implementation">
<h2><a href="#single-header-implementation">Single-head­er im­ple­ment­a­tion</a></h2>
<p>Like with vari­ous <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/namespaceCorrade_1_1Containers.html">Con­tain­ers</a> im­ple­ment­a­tions, this fea­ture is
self-con­tained enough to make sense as a stan­dalone lib­rary. To make it
pos­sible to use it without re­ly­ing on the whole of Cor­rade, it’s provided as a
single-head­er lib­rary in the <a href="https://github.com/mosra/magnum-singles">Mag­num Singles</a>
re­pos­it­ory. To use it, simply down­load the file and <code class="cpp m-code"><span class="cp">#include</span></code> it your
pro­ject:</p>
<table class="m-table m-fullwidth">
<thead>
<tr><th>Lib­rary</th>
<th>LoC</th>
<th>Pre­pro­cessed LoC</th>
<th>De­scrip­tion</th>
</tr>
</thead>
<tbody>
<tr><td><a href="https://github.com/mosra/magnum-singles/blob/master/CorradeCpu.h">Cor­radeCpu.h</a> <span class="m-label m-success">new</span></td>
<td>1646</td>
<td>2085</td>
<td>See the <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/namespaceCorrade_1_1Cpu.html">Cpu</a> namespace docs</td>
</tr>
</tbody>
</table>
<p>To put the head­er size in­to per­spect­ive, just <code class="cpp m-code"><span class="cp">#include</span> <span class="cpf"><immintrin.h></span><span class="cp"></span></code> for
all AVX in­trins­ics yields <em>34 thou­sand</em> pre­pro­cessed lines on GCC. Com­pared to
when <a href="https://blog.magnum.graphics/backstage/lightweight-stl-compatible-unique-pointer/">I was bitch­ing about <memory> be­ing heavy</a>,
this is a whole oth­er level. And hon­estly I don’t know what could fix this,
there’s simply <em>a lot</em> in AVX-512.</p>
<p class="m-transition">. : .</p>
<aside class="m-note m-info">
Ques­tions? <em>Com­plaints?</em> Ex­press your dis­ap­prov­al on so­cial net­works:
<a href="https://twitter.com/czmosra/status/1554485603206569986">Twit­ter</a>,
<a href="https://www.reddit.com/r/cpp/comments/weegza/convenient_cpu_feature_detection_and_dispatch_in/">Red­dit r/cpp</a>,
<a href="https://news.ycombinator.com/item?id=32319750">Hack­er News</a></aside>
<dl class="m-footnote">
<dt id="id7">1</a>.</dt>
<dd><span class="m-footnote"><a href="#id1">^</a></span> The ori­gin­al pro­to­type ap­peared in <a href="https://github.com/mosra/corrade/issues/115">mosra/cor­rade#115</a> in March
2021, which was a spin-off of <a href="https://github.com/mosra/magnum/issues/306">mosra/mag­num#306</a>, dat­ing back to
Janu­ary 2019. Yup, cer­tain fea­tures <em>take time</em> to brew.</dd>
<dt id="id8">2</a>.</dt>
<dd><span class="m-footnote"><a href="#id2">^</a></span> The <code class="cpp m-code"><span class="o">+</span></code> be­fore the lambda “de­cays” it in­to a reg­u­lar func­tion
point­er, and that’s what the func­tion re­turns. I used C++14 for brev­ity,
but the <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/namespaceCorrade_1_1Cpu.html">Cpu</a> lib­rary works in plain C++11 as well, just with a bit
more typ­ing in­stead of the <code class="cpp m-code"><span class="k">auto</span></code> re­turn type. I’m happy be­ing the
cave­man that’s still us­ing C++11.</dd>
<dt id="id9">3</a>.</dt>
<dd><span class="m-footnote"><a href="#id3">^</a></span> Ex­cept for <a href="https://github.com/golang/go/issues/28431">that one case</a> where ARM big.LITTLE
cores each re­por­ted a slightly dif­fer­ent in­struc­tion set, lead­ing to
crashes when you quer­ied CPU fea­tures on the more ad­vanced core and ended
up run­ning ad­vanced in­struc­tions on the core that didn’t have them.</dd>
<dt id="id10">4</a>.</dt>
<dd><span class="m-footnote"><a href="#id4">^</a></span> The ex­plan­a­tion is grossly over­sim­pli­fied, but <em>dy­nam­ic lib­rary func­tion
call</em> is the key point here. Calls in­to dy­nam­ic lib­rar­ies are in­dir­ect,
mean­ing they’re kinda like a func­tion point­er as well. The dif­fer­ence is,
how­ever, that they’re re­solved at load time, con­stant for the ap­plic­a­tion
life­time and all con­tained in a single place, which has the po­ten­tial of
re­du­cing over­head com­pared to hav­ing to fetch a func­tion point­er value from
a ran­dom loc­a­tion be­fore every call.</dd>
<dt id="id11">5.</dt>
<dd><span class="m-footnote">^ <a href="#id5">a</a> <a href="#id6">b</a></span> Again I’m just scratch­ing the sur­face here. En­abling AVX2 for the whole
file may cause AVX2 to be used also in func­tions where you use just SSE2
in­trins­ics and thus crash­ing on ma­chines without AVX2 sup­port. That is
rather ob­vi­ous, but sim­il­ar cases <a href="https://www.reddit.com/r/cpp/comments/eneib0/detecting_sse_features_at_runtime/fdzqbqg?utm_source=share&utm_medium=web2x&context=3">could hap­pen also in case LTO com­bines code from mul­tiple files</a>
or <a href="https://www.reddit.com/r/cpp/comments/eneib0/detecting_sse_features_at_runtime/fdzqbqg?utm_source=share&utm_medium=web2x&context=3">when a single tem­plate func­tion gets com­piled for dif­fer­ent in­struc­tion sets and then the linker de­du­plic­ates it</a>.
Com­puters are hard, ap­par­ently, and I hope most of this got solved since.</dd>
</dl>
</section>
Zero-waste single-pass packing of power-of-two textures2022-06-15T00:00:00+02:002022-06-15T00:00:00+02:00Vladimír Vondruštag:blog.magnum.graphics,2022-06-15:/backstage/pot-array-packing/<p>Rect­angle pack­ing doesn’t ac­tu­ally have to be a NP-hard prob­lem if we
don’t need to solve the most gen­er­al case. In this post I present a simple
yet op­tim­al al­gorithm for pack­ing power-of-two tex­tures in­to a tex­ture
ar­ray.</p>
<aside class="m-note m-info">
Sorry for the lack of up­dates on this site. No, Mag­num is not dead, I’m
just work­ing on too many things at once and none of them is fin­ished enough
to be an­nounced yet. This one is, though.</aside>
<p>I don’t claim that I “in­ven­ted a nov­el al­gorithm” here. It’s so simple that the
com­put­ing pi­on­eers would def­in­itely have it in­ven­ted back in the 1970s already,
but I just couldn’t find any pri­or art. On the oth­er hand, I feel like I need
to risk stat­ing the ob­vi­ous, be­cause oth­er­wise the gen­er­al con­sensus seems to
be that rect­angle pack­ing is <em>hard</em> no mat­ter what the in­put is. Well, not
al­ways.</p>
<section id="background-or-why-even-bother">
<h2><a href="#background-or-why-even-bother">Back­ground, or why even both­er</a></h2>
<p>I have a few hun­dreds of OBJ, COL­LADA, PLY, etc. mod­el files and the goal is to
pack them all in­to a single glTF file op­tim­ized for multi-draw<a class="m-footnote" href="#id11" id="id1">1</a>. Thus,
con­tain­ing a single ver­tex and in­dex buf­fer, with meshes be­ing sub-ranges of
this buf­fer, and a single 2D ar­ray tex­ture us­ing the pro­posed
<a href="https://github.com/KhronosGroup/glTF/issues/1964">KHR_­tex­ture_k­tx</a> ex­ten­sion<a class="m-footnote" href="#id12" id="id2">2</a>, with ma­ter­i­als
ref­er­en­cing (trans­formed) lay­ers of the ar­ray.</p>
<p>For meshes, con­cat­en­a­tion and bak­ing re­l­at­ive trans­forms in­to them was easy
enough with tools like <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1MeshTools.html#ac093061706a7501ca0e8b224b091c959">MeshTools::con­cat­en­ate()</a> or
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1SceneTools.html#a22db9d034175f16217cc131549cd6bbc">SceneTools::flat­t­en­Mesh­H­i­er­archy3D()</a>. For tex­ture pack­ing, how­ever, I
had only <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1TextureTools.html#ac58fb68c19f7c8a31443765201609ebd">Tex­tureTools::at­las()</a>, and that’s the most stu­pid and
in­ef­fi­cient im­ple­ment­a­tion ima­gin­able<a class="m-footnote" href="#id13" id="id3">3</a>. The ob­vi­ous next step was thus to
look in­to re­pla­cing it with some­thing bet­ter — but even the simplest
im­ple­ment­a­tions I know of<a class="m-footnote" href="#id14" id="id4">4</a><a class="m-footnote" href="#id15" id="id5">5</a> are still quite com­plex with a lot of
toggles, heur­ist­ics and tradeoffs.</p>
<p>Fur­ther­more, con­sid­er­ing I have <em>hun­dreds</em> of in­put mod­els of which many have
2048×2048 tex­tures, I’d quickly run of 2D tex­ture size lim­its of even the
beefi­est GPUs. Tex­ture ar­rays, how­ever, can have as many lay­ers as I need.
Which how­ever means al­most all ex­ist­ing rect­angle pack­ing al­gorithms are out of
ques­tion, as they op­er­ate only on a single 2D tex­ture.</p>
</section>
<section id="looking-at-the-data">
<h2><a href="#looking-at-the-data">Look­ing at the data</a></h2>
<p>In­stead of dir­ectly jump­ing onto ad­apt­ing some ex­ist­ing gen­er­al al­gorithm to
work with tex­ture ar­rays, I pro­cras­tin­ated away. Stared at the data. And
real­ized that ba­sic­ally all mod­els in the set I have fol­low the best prac­tices,
and use square power-of-two tex­tures — 64×64, 256×256 and such.
Out­liers that didn’t fol­low this were less than one out of 100, thus not that
im­port­ant. Those will be handled sep­ar­ately<a class="m-footnote" href="#id16" id="id6">6</a>.</p>
<svg class="m-image" style="width: 722.667px; height: 85.3333px;" viewBox="0 0 1084 128" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect x="0" y="111" width="16" height="16" style="fill:#9b0f01"/>
<rect x="20" y="119" width="8" height="8" style="fill:#d02f05"/>
<rect x="32" y="119" width="8" height="8" style="fill:#20c7df"/>
<rect x="44" y="119" width="8" height="8" style="fill:#c6f034"/>
<rect x="56" y="123" width="4" height="4" style="fill:#19e3b9"/>
<rect x="64" y="95" width="32" height="32" style="fill:#1fc9dd"/>
<rect x="100" y="119" width="8" height="8" style="fill:#a9fb39"/>
<rect x="112" y="95" width="32" height="32" style="fill:#4669e0"/>
<rect x="148" y="123" width="4" height="4" style="fill:#3e3891"/>
<rect x="156" y="-1" width="128" height="128" style="fill:#e9d539"/>
<rect x="288" y="95" width="32" height="32" style="fill:#3f3b97"/>
<rect x="324" y="123" width="4" height="4" style="fill:#96fe44"/>
<rect x="332" y="95" width="32" height="32" style="fill:#fc8725"/>
<rect x="368" y="119" width="8" height="8" style="fill:#4680f6"/>
<rect x="380" y="123" width="4" height="4" style="fill:#25c0e7"/>
<rect x="388" y="123" width="4" height="4" style="fill:#2eb4f2"/>
<rect x="396" y="111" width="16" height="16" style="fill:#4771e9"/>
<rect x="416" y="95" width="32" height="32" style="fill:#3b2f80"/>
<rect x="452" y="63" width="64" height="64" style="fill:#7dff56"/>
<rect x="520" y="63" width="64" height="64" style="fill:#1fc9dd"/>
<rect x="588" y="123" width="4" height="4" style="fill:#3f3b97"/>
<rect x="596" y="119" width="8" height="8" style="fill:#30123b"/>
<rect x="608" y="123" width="4" height="4" style="fill:#4040a2"/>
<rect x="616" y="63" width="64" height="64" style="fill:#1ecbda"/>
<rect x="684" y="-1" width="128" height="128" style="fill:#3c3286"/>
<rect x="816" y="111" width="16" height="16" style="fill:#fdac34"/>
<rect x="836" y="63" width="64" height="64" style="fill:#d83706"/>
<rect x="904" y="119" width="8" height="8" style="fill:#3e9bfe"/>
<rect x="916" y="123" width="4" height="4" style="fill:#2ff19b"/>
<rect x="924" y="63" width="64" height="64" style="fill:#3f3b97"/>
<rect x="992" y="111" width="16" height="16" style="fill:#455ed3"/>
<rect x="1012" y="63" width="64" height="64" style="fill:#80ff53"/>
<rect x="1080" y="123" width="4" height="4" style="fill:#df3f08"/>
</svg>
<p>The sim­pler fla­vors of pack­ing al­gorithms first make all im­ages either por­trait
or land­cape, sort them by size on one ax­is, and then, start­ing with the
largest, pack them in­to the out­put, <em>some­how</em>.</p>
<p>In my case, giv­en that I can as­sume power-of-two squares and can just over­flow
to an­oth­er ar­ray lay­er when the pre­vi­ous gets full, the al­gorithm be­comes
pretty clear. Im­port­antly, power-of-two squares also make it pos­sible to do
the pack­ing in a way that makes all lay­ers ex­cept the last one com­pletely full,
with zero wasted space. Such con­straint also helps avoid­ing tex­ture-leak­ing
ar­ti­facts when us­ing block com­pres­sion — ex­cept for the smal­lest sizes, a
4×4 block (or 8×8 for cer­tain ASTC formats) will nev­er span two
in­de­pend­ent tex­tures.</p>
<svg class="m-image" style="width: 349.333px; height: 85.3333px;" viewBox="0 0 524 128" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect x="492" y="48" width="16" height="16" style="fill:#9b0f01"/>
<rect x="460" y="24" width="8" height="8" style="fill:#d02f05"/>
<rect x="468" y="24" width="8" height="8" style="fill:#20c7df"/>
<rect x="460" y="16" width="8" height="8" style="fill:#c6f034"/>
<rect x="484" y="20" width="4" height="4" style="fill:#19e3b9"/>
<rect x="396" y="32" width="32" height="32" style="fill:#1fc9dd"/>
<rect x="468" y="16" width="8" height="8" style="fill:#a9fb39"/>
<rect x="428" y="32" width="32" height="32" style="fill:#4669e0"/>
<rect x="488" y="20" width="4" height="4" style="fill:#3e3891"/>
<rect x="0" y="0" width="128" height="128" style="fill:#e9d539"/>
<rect x="396" y="0" width="32" height="32" style="fill:#3f3b97"/>
<rect x="484" y="16" width="4" height="4" style="fill:#96fe44"/>
<rect x="428" y="0" width="32" height="32" style="fill:#fc8725"/>
<rect x="476" y="24" width="8" height="8" style="fill:#4680f6"/>
<rect x="488" y="16" width="4" height="4" style="fill:#25c0e7"/>
<rect x="460" y="12" width="4" height="4" style="fill:#2eb4f2"/>
<rect x="508" y="48" width="16" height="16" style="fill:#4771e9"/>
<rect x="460" y="32" width="32" height="32" style="fill:#3b2f80"/>
<rect x="264" y="64" width="64" height="64" style="fill:#7dff56"/>
<rect x="328" y="64" width="64" height="64" style="fill:#1fc9dd"/>
<rect x="464" y="12" width="4" height="4" style="fill:#3f3b97"/>
<rect x="484" y="24" width="8" height="8" style="fill:#30123b"/>
<rect x="460" y="8" width="4" height="4" style="fill:#4040a2"/>
<rect x="264" y="0" width="64" height="64" style="fill:#1ecbda"/>
<rect x="132" y="0" width="128" height="128" style="fill:#3c3286"/>
<rect x="492" y="32" width="16" height="16" style="fill:#fdac34"/>
<rect x="328" y="0" width="64" height="64" style="fill:#d83706"/>
<rect x="476" y="16" width="8" height="8" style="fill:#3e9bfe"/>
<rect x="464" y="8" width="4" height="4" style="fill:#2ff19b"/>
<rect x="396" y="64" width="64" height="64" style="fill:#3f3b97"/>
<rect x="508" y="32" width="16" height="16" style="fill:#455ed3"/>
<rect x="460" y="64" width="64" height="64" style="fill:#80ff53"/>
<rect x="468" y="12" width="4" height="4" style="fill:#df3f08"/>
</svg>
</section>
<section id="the-algorithm">
<h2><a href="#the-algorithm">The al­gorithm</a></h2>
<p>The core of this al­gorithm is main­tain­ing know­ledge of the free space left. As
the space is re­curs­ively sub­diving, it looks like a prob­lem quadtrees could
solve, but for­tu­nately as we go lin­early from largest to smal­lest, we don’t
even need that. All info that we need to main­tain is how many im­ages of
cur­rent size can still fit. Best ex­plained with a visu­al ex­ample:</p>
<svg class="m-image" style="max-height:384px;max-width:384px" version="1.1" viewBox="0 0 136 136" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(.33073 .33073)" fill="none" stroke="#dcdcdc" stroke-width=".52917">
<rect x="-.066145" y="67.667" width="67.733" height="67.733" stop-color="#000000"/>
<rect x="67.667" y="67.667" width="67.733" height="67.733" stop-color="#000000"/>
<rect x="-.066145" y="33.801" width="33.867" height="33.867" stop-color="#000000"/>
<rect x="33.801" y="33.801" width="33.867" height="33.867" stop-color="#000000"/>
<rect x="-.066145" y="-.066145" width="33.867" height="33.867" stop-color="#000000"/>
<rect x="33.801" y="-.066145" width="33.867" height="33.867" stop-color="#000000"/>
<rect x="67.667" y="33.801" width="33.867" height="33.867" stop-color="#000000"/>
<rect x="118.47" y="50.734" width="16.933" height="16.933" stop-color="#000000"/>
<rect x="101.53" y="50.734" width="16.933" height="16.933" stop-color="#000000"/>
<rect x="101.53" y="33.801" width="16.933" height="16.933" stop-color="#000000"/>
<rect x="118.47" y="33.801" width="16.933" height="16.933" stop-color="#000000"/>
<rect x="84.601" y="25.334" width="8.4667" height="8.4667" stop-color="#000000"/>
<rect x="93.067" y="25.334" width="8.4667" height="8.4667" stop-color="#000000"/>
<rect x="84.601" y="16.867" width="8.4667" height="8.4667" stop-color="#000000"/>
<rect x="67.667" y="16.867" width="16.933" height="16.933" stop-color="#000000"/>
</g>
<g transform="translate(.26458 .26458)" fill="#dcdcdc" font-family="'Source Sans Pro'" stroke-width=".26458" text-anchor="middle">
<text x="33.866661" y="108.80231" font-size="22.578px" text-align="center" style="font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="33.866661" y="108.80231" fill="#dcdcdc" font-family="'Source Sans Pro'" font-size="22.578px" stroke-width=".26458" text-align="center" text-anchor="middle" style="font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal">0</tspan></text>
<text x="101.36292" y="108.80231" font-size="22.578px" text-align="center" style="font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="101.36292" y="108.80231" fill="#dcdcdc" font-family="'Source Sans Pro'" font-size="22.578px" stroke-width=".26458" text-align="center" text-anchor="middle" style="font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal">1</tspan></text>
<g font-size="11.289px">
<text x="16.814796" y="54.401154" text-align="center" style="font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="16.814796" y="54.401154" fill="#dcdcdc" font-family="'Source Sans Pro'" font-size="11.289px" stroke-width=".26458" text-align="center" text-anchor="middle" style="font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal">20</tspan></text>
<text x="50.681461" y="54.401161" text-align="center" style="font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="50.681461" y="54.401161" fill="#dcdcdc" font-family="'Source Sans Pro'" font-size="11.289px" stroke-width=".26458" text-align="center" text-anchor="middle" style="font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal">21</tspan></text>
<text x="16.814796" y="20.534491" text-align="center" style="font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="16.814796" y="20.534491" fill="#dcdcdc" font-family="'Source Sans Pro'" font-size="11.289px" stroke-width=".26458" text-align="center" text-anchor="middle" style="font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal">22</tspan></text>
<text x="50.681461" y="20.534491" text-align="center" style="font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="50.681461" y="20.534491" fill="#dcdcdc" font-family="'Source Sans Pro'" font-size="11.289px" stroke-width=".26458" text-align="center" text-anchor="middle" style="font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal">23</tspan></text>
<text x="84.548126" y="54.401161" text-align="center" style="font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="84.548126" y="54.401161" fill="#dcdcdc" font-family="'Source Sans Pro'" font-size="11.289px" stroke-width=".26458" text-align="center" text-anchor="middle" style="font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal">30</tspan></text>
</g>
<g font-size="5.6444px">
<text x="110.00739" y="61.067238" text-align="center" style="font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="110.00739" y="61.067238" fill="#dcdcdc" font-family="'Source Sans Pro'" font-size="5.6444px" stroke-width=".26458" text-align="center" text-anchor="middle" style="font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal">310</tspan></text>
<text x="126.94073" y="61.067238" text-align="center" style="font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="126.94073" y="61.067238" fill="#dcdcdc" font-family="'Source Sans Pro'" font-size="5.6444px" stroke-width=".26458" text-align="center" text-anchor="middle" style="font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal">311</tspan></text>
<text x="110.00739" y="44.133911" text-align="center" style="font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="110.00739" y="44.133911" fill="#dcdcdc" font-family="'Source Sans Pro'" font-size="5.6444px" stroke-width=".26458" text-align="center" text-anchor="middle" style="font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal">312</tspan></text>
<text x="126.94073" y="44.133911" text-align="center" style="font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="126.94073" y="44.133911" fill="#dcdcdc" font-family="'Source Sans Pro'" font-size="5.6444px" stroke-width=".26458" text-align="center" text-anchor="middle" style="font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal">313</tspan></text>
</g>
<g font-size="2.8222px">
<text x="88.870369" y="30.533621" text-align="center" style="font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="88.870369" y="30.533621" fill="#dcdcdc" font-family="'Source Sans Pro'" font-size="2.8222px" stroke-width=".26458" text-align="center" text-anchor="middle" style="font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal">3210</tspan></text>
<text x="97.392059" y="30.533621" text-align="center" style="font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="97.392059" y="30.533621" fill="#dcdcdc" font-family="'Source Sans Pro'" font-size="2.8222px" stroke-width=".26458" text-align="center" text-anchor="middle" style="font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal">3211</tspan></text>
<text x="88.9254" y="22.066954" text-align="center" style="font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="88.9254" y="22.066954" fill="#dcdcdc" font-family="'Source Sans Pro'" font-size="2.8222px" stroke-width=".26458" text-align="center" text-anchor="middle" style="font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal">3212</tspan></text>
</g>
<text x="76.140739" y="27.200577" font-size="5.6444px" text-align="center" style="font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="76.140739" y="27.200577" fill="#dcdcdc" font-family="'Source Sans Pro'" font-size="5.6444px" stroke-width=".26458" text-align="center" text-anchor="middle" style="font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal">320</tspan></text>
</g>
</svg>
<ul>
<li>Let’s say the out­put size is 2048×2048, and the largest im­age we have
is 1024×1024. We can fit four of them in­to the out­put be­fore the
lay­er is full and we need to go to the next.</li>
<li>Put­ting aside any place­ment rules for now, we place two 1024×1024
im­ages in­to the out­put (labeled <strong>0</strong> and <strong>1</strong>), which means it’s just two
slots left.</li>
<li>Next im­age in the list is 512×512, which has a four times smal­ler
area than the pre­vi­ous one. Thus, the leftover space quad­ruples, mean­ing
we now have eight slots to fit 512×512 im­ages.</li>
<li>There are five 512×512 im­ages (labeled <strong>20</strong> to <strong>23</strong> and <strong>30</strong>),
after which there’s three slots left.</li>
<li>The next im­age size is 256×256, which has a 4× smal­ler area, and
thus the three 512×512 slots be­come 12 256×256 slots.</li>
<li>…</li>
</ul>
<p>Once an ar­ray lay­er is full, we start a new one, re­cal­cu­lat­ing the num­ber of
free slots based on the ra­tio of its area and area of the next im­age to place.
So, for ex­ample, we can fit 256 128×128 im­ages to the next lay­er of a
2048×2048 out­put.</p>
<section id="placement-rules">
<h3><a href="#placement-rules">Place­ment rules</a></h3>
<p>Cal­cu­lat­ing re­main­ing free space is hope­fully clear, now the ac­tu­al place­ment.
Here’s where I fi­nally make use of quadtree ad­dress­ing, but again without
build­ing any com­plex in-memory struc­ture. As can be seen above, a par­tic­u­lar
square in the quadtree can be thus ad­dressed with a base-4 co­ordin­ate, where
first di­git rep­res­ents loc­a­tion in the top level, second in­side giv­en quad­rant
of the top level and re­curs­ing fur­ther.</p>
<p>The im­port­ant as­pect of this ad­dress­ing scheme is that the base-4 co­ordin­ate of
every im­age is ac­tu­ally the in­dex of the free slot when adding the im­age, and
the way it works guar­an­tees no over­lap. I.e., when we ad­ded the first
256×256 im­age above, labeled <strong>310</strong>, there were 12 slots left, and
<svg class="m-math" style="width: 12.966em; height: 1.455em; vertical-align: -0.431em;" viewBox="0 -9.843428 124.474284 13.96606">
<title>
(\frac{2048}{256})^2 - 12 = 52 = 310_4
</title>
<defs>
<path id='eq1-g0-0' d='M7.878456-2.749689C8.081694-2.749689 8.296887-2.749689 8.296887-2.988792S8.081694-3.227895 7.878456-3.227895H1.41071C1.207472-3.227895 .992279-3.227895 .992279-2.988792S1.207472-2.749689 1.41071-2.749689H7.878456Z'/>
<path id='eq1-g1-48' d='M3.897385-2.542466C3.897385-3.395268 3.809714-3.913325 3.5467-4.423412C3.196015-5.124782 2.550436-5.300125 2.11208-5.300125C1.107846-5.300125 .74122-4.550934 .629639-4.327771C.342715-3.745953 .326775-2.956912 .326775-2.542466C.326775-2.016438 .350685-1.211457 .73325-.573848C1.099875 .01594 1.689664 .167372 2.11208 .167372C2.494645 .167372 3.180075 .047821 3.57858-.74122C3.873474-1.315068 3.897385-2.024408 3.897385-2.542466ZM2.11208-.055791C1.841096-.055791 1.291158-.183313 1.123786-1.020174C1.036115-1.474471 1.036115-2.223661 1.036115-2.638107C1.036115-3.188045 1.036115-3.745953 1.123786-4.184309C1.291158-4.99726 1.912827-5.076961 2.11208-5.076961C2.383064-5.076961 2.933001-4.941469 3.092403-4.216189C3.188045-3.777833 3.188045-3.180075 3.188045-2.638107C3.188045-2.16787 3.188045-1.45056 3.092403-1.004234C2.925031-.167372 2.375093-.055791 2.11208-.055791Z'/>
<path id='eq1-g1-50' d='M2.247572-1.625903C2.375093-1.745455 2.709838-2.008468 2.83736-2.12005C3.331507-2.574346 3.801743-3.012702 3.801743-3.737983C3.801743-4.686426 3.004732-5.300125 2.008468-5.300125C1.052055-5.300125 .422416-4.574844 .422416-3.865504C.422416-3.474969 .73325-3.419178 .844832-3.419178C1.012204-3.419178 1.259278-3.53873 1.259278-3.841594C1.259278-4.25604 .860772-4.25604 .765131-4.25604C.996264-4.837858 1.530262-5.037111 1.920797-5.037111C2.662017-5.037111 3.044583-4.407472 3.044583-3.737983C3.044583-2.909091 2.462765-2.303362 1.522291-1.338979L.518057-.302864C.422416-.215193 .422416-.199253 .422416 0H3.57061L3.801743-1.42665H3.55467C3.53076-1.267248 3.466999-.868742 3.371357-.71731C3.323537-.653549 2.717808-.653549 2.590286-.653549H1.171606L2.247572-1.625903Z'/>
<path id='eq1-g1-52' d='M3.140224-5.156663C3.140224-5.316065 3.140224-5.379826 2.972852-5.379826C2.86924-5.379826 2.86127-5.371856 2.781569-5.260274L.239103-1.570112V-1.307098H2.486675V-.645579C2.486675-.350685 2.462765-.263014 1.849066-.263014H1.665753V0C2.343213-.02391 2.359153-.02391 2.81345-.02391S3.283686-.02391 3.961146 0V-.263014H3.777833C3.164134-.263014 3.140224-.350685 3.140224-.645579V-1.307098H3.985056V-1.570112H3.140224V-5.156663ZM2.542466-4.511083V-1.570112H.518057L2.542466-4.511083Z'/>
<path id='eq1-g1-53' d='M1.115816-4.479203C1.219427-4.447323 1.538232-4.367621 1.872976-4.367621C2.86924-4.367621 3.474969-5.068991 3.474969-5.188543C3.474969-5.276214 3.419178-5.300125 3.379328-5.300125C3.363387-5.300125 3.347447-5.300125 3.275716-5.260274C2.964882-5.140722 2.598257-5.045081 2.16787-5.045081C1.697634-5.045081 1.307098-5.164633 1.060025-5.260274C.980324-5.300125 .964384-5.300125 .956413-5.300125C.852802-5.300125 .852802-5.212453 .852802-5.068991V-2.733748C.852802-2.590286 .852802-2.494645 .980324-2.494645C1.044085-2.494645 1.067995-2.526526 1.107846-2.590286C1.203487-2.709838 1.506351-3.116314 2.183811-3.116314C2.630137-3.116314 2.84533-2.749689 2.917061-2.598257C3.052553-2.311333 3.068493-1.944707 3.068493-1.633873C3.068493-1.338979 3.060523-.908593 2.83736-.557908C2.685928-.318804 2.367123-.071731 1.944707-.071731C1.42665-.071731 .916563-.398506 .73325-.916563C.757161-.908593 .804981-.908593 .812951-.908593C1.036115-.908593 1.211457-1.052055 1.211457-1.299128C1.211457-1.594022 .980324-1.697634 .820922-1.697634C.67746-1.697634 .422416-1.617933 .422416-1.275218C.422416-.557908 1.044085 .167372 1.960648 .167372C2.956912 .167372 3.801743-.605729 3.801743-1.594022C3.801743-2.518555 3.132254-3.339477 2.191781-3.339477C1.793275-3.339477 1.41868-3.211955 1.115816-2.940971V-4.479203Z'/>
<path id='eq1-g1-54' d='M1.099875-2.638107C1.099875-3.299626 1.155666-3.881445 1.44259-4.367621C1.681694-4.766127 2.088169-5.092902 2.590286-5.092902C2.749689-5.092902 3.116314-5.068991 3.299626-4.790037C2.940971-4.774097 2.909091-4.503113 2.909091-4.415442C2.909091-4.176339 3.092403-4.040847 3.283686-4.040847C3.427148-4.040847 3.658281-4.128518 3.658281-4.431382C3.658281-4.909589 3.299626-5.300125 2.582316-5.300125C1.474471-5.300125 .350685-4.24807 .350685-2.526526C.350685-.366625 1.354919 .167372 2.12802 .167372C2.510585 .167372 2.925031 .063761 3.283686-.278954C3.602491-.589788 3.873474-.924533 3.873474-1.617933C3.873474-2.662017 3.084433-3.395268 2.199751-3.395268C1.625903-3.395268 1.283188-3.028643 1.099875-2.638107ZM2.12802-.071731C1.705604-.071731 1.44259-.366625 1.323039-.589788C1.139726-.948443 1.123786-1.490411 1.123786-1.793275C1.123786-2.582316 1.554172-3.172105 2.16787-3.172105C2.566376-3.172105 2.805479-2.964882 2.956912-2.685928C3.124284-2.391034 3.124284-2.032379 3.124284-1.625903S3.124284-.868742 2.964882-.581818C2.757659-.215193 2.478705-.071731 2.12802-.071731Z'/>
<path id='eq1-g1-56' d='M2.646077-2.87721C3.092403-3.092403 3.634371-3.490909 3.634371-4.112578C3.634371-4.869738 2.86127-5.300125 2.12005-5.300125C1.275218-5.300125 .589788-4.718306 .589788-3.969116C.589788-3.674222 .6934-3.403238 .892653-3.172105C1.028144-3.004732 1.060025-2.988792 1.554172-2.677958C.565878-2.239601 .350685-1.657783 .350685-1.211457C.350685-.334745 1.235367 .167372 2.10411 .167372C3.084433 .167372 3.873474-.494147 3.873474-1.338979C3.873474-1.841096 3.602491-2.175841 3.474969-2.311333C3.339477-2.438854 3.331507-2.446824 2.646077-2.87721ZM1.41071-3.626401C1.179577-3.761893 .988294-3.993026 .988294-4.27198C.988294-4.774097 1.538232-5.092902 2.10411-5.092902C2.725778-5.092902 3.235866-4.670486 3.235866-4.112578C3.235866-3.650311 2.87721-3.259776 2.406974-3.028643L1.41071-3.626401ZM1.801245-2.534496C1.833126-2.518555 2.741719-1.960648 2.87721-1.872976C3.004732-1.801245 3.419178-1.546202 3.419178-1.067995C3.419178-.454296 2.773599-.071731 2.12005-.071731C1.41071-.071731 .804981-.557908 .804981-1.211457C.804981-1.809215 1.251308-2.279452 1.801245-2.534496Z'/>
<path id='eq1-g2-40' d='M3.88543 2.905106C3.88543 2.86924 3.88543 2.84533 3.682192 2.642092C2.486675 1.43462 1.817186-.537983 1.817186-2.976837C1.817186-5.296139 2.379078-7.292653 3.765878-8.703362C3.88543-8.810959 3.88543-8.834869 3.88543-8.870735C3.88543-8.942466 3.825654-8.966376 3.777833-8.966376C3.622416-8.966376 2.642092-8.105604 2.056289-6.933998C1.446575-5.726526 1.171606-4.447323 1.171606-2.976837C1.171606-1.912827 1.338979-.490162 1.960648 .789041C2.666002 2.223661 3.646326 3.000747 3.777833 3.000747C3.825654 3.000747 3.88543 2.976837 3.88543 2.905106Z'/>
<path id='eq1-g2-41' d='M3.371357-2.976837C3.371357-3.88543 3.251806-5.36787 2.582316-6.75467C1.876961-8.18929 .896638-8.966376 .765131-8.966376C.71731-8.966376 .657534-8.942466 .657534-8.870735C.657534-8.834869 .657534-8.810959 .860772-8.607721C2.056289-7.400249 2.725778-5.427646 2.725778-2.988792C2.725778-.669489 2.163885 1.327024 .777086 2.737733C.657534 2.84533 .657534 2.86924 .657534 2.905106C.657534 2.976837 .71731 3.000747 .765131 3.000747C.920548 3.000747 1.900872 2.139975 2.486675 .968369C3.096389-.251059 3.371357-1.542217 3.371357-2.976837Z'/>
<path id='eq1-g2-48' d='M5.355915-3.825654C5.355915-4.817933 5.296139-5.786301 4.865753-6.694894C4.375592-7.687173 3.514819-7.950187 2.929016-7.950187C2.235616-7.950187 1.3868-7.603487 .944458-6.611208C.609714-5.858032 .490162-5.116812 .490162-3.825654C.490162-2.666002 .573848-1.793275 1.004234-.944458C1.470486-.035866 2.295392 .251059 2.917061 .251059C3.957161 .251059 4.554919-.37061 4.901619-1.06401C5.332005-1.960648 5.355915-3.132254 5.355915-3.825654ZM2.917061 .011955C2.534496 .011955 1.75741-.203238 1.530262-1.506351C1.398755-2.223661 1.398755-3.132254 1.398755-3.969116C1.398755-4.94944 1.398755-5.834122 1.590037-6.539477C1.793275-7.340473 2.402989-7.711083 2.917061-7.711083C3.371357-7.711083 4.064757-7.436115 4.291905-6.40797C4.447323-5.726526 4.447323-4.782067 4.447323-3.969116C4.447323-3.16812 4.447323-2.259527 4.315816-1.530262C4.088667-.215193 3.335492 .011955 2.917061 .011955Z'/>
<path id='eq1-g2-49' d='M3.443088-7.663263C3.443088-7.938232 3.443088-7.950187 3.203985-7.950187C2.917061-7.627397 2.319303-7.185056 1.08792-7.185056V-6.838356C1.362889-6.838356 1.960648-6.838356 2.618182-7.149191V-.920548C2.618182-.490162 2.582316-.3467 1.530262-.3467H1.159651V0C1.482441-.02391 2.642092-.02391 3.036613-.02391S4.578829-.02391 4.901619 0V-.3467H4.531009C3.478954-.3467 3.443088-.490162 3.443088-.920548V-7.663263Z'/>
<path id='eq1-g2-50' d='M5.260274-2.008468H4.99726C4.961395-1.80523 4.865753-1.147696 4.746202-.956413C4.662516-.848817 3.981071-.848817 3.622416-.848817H1.41071C1.733499-1.123786 2.462765-1.888917 2.773599-2.175841C4.590785-3.849564 5.260274-4.471233 5.260274-5.654795C5.260274-7.029639 4.172354-7.950187 2.785554-7.950187S.585803-6.766625 .585803-5.738481C.585803-5.128767 1.111831-5.128767 1.147696-5.128767C1.398755-5.128767 1.709589-5.308095 1.709589-5.69066C1.709589-6.025405 1.482441-6.252553 1.147696-6.252553C1.0401-6.252553 1.016189-6.252553 .980324-6.240598C1.207472-7.053549 1.853051-7.603487 2.630137-7.603487C3.646326-7.603487 4.267995-6.75467 4.267995-5.654795C4.267995-4.638605 3.682192-3.753923 3.000747-2.988792L.585803-.286924V0H4.94944L5.260274-2.008468Z'/>
<path id='eq1-g2-51' d='M2.199751-4.291905C1.996513-4.27995 1.948692-4.267995 1.948692-4.160399C1.948692-4.040847 2.008468-4.040847 2.223661-4.040847H2.773599C3.789788-4.040847 4.244085-3.203985 4.244085-2.056289C4.244085-.490162 3.431133-.071731 2.84533-.071731C2.271482-.071731 1.291158-.3467 .944458-1.135741C1.327024-1.075965 1.673724-1.291158 1.673724-1.721544C1.673724-2.068244 1.422665-2.307347 1.08792-2.307347C.800996-2.307347 .490162-2.139975 .490162-1.685679C.490162-.621669 1.554172 .251059 2.881196 .251059C4.303861 .251059 5.355915-.836862 5.355915-2.044334C5.355915-3.144209 4.471233-4.004981 3.323537-4.208219C4.363636-4.507098 5.033126-5.379826 5.033126-6.312329C5.033126-7.256787 4.052802-7.950187 2.893151-7.950187C1.697634-7.950187 .812951-7.220922 .812951-6.348194C.812951-5.869988 1.183562-5.774346 1.362889-5.774346C1.613948-5.774346 1.900872-5.953674 1.900872-6.312329C1.900872-6.694894 1.613948-6.862267 1.350934-6.862267C1.279203-6.862267 1.255293-6.862267 1.219427-6.850311C1.673724-7.663263 2.797509-7.663263 2.857285-7.663263C3.251806-7.663263 4.028892-7.483935 4.028892-6.312329C4.028892-6.085181 3.993026-5.415691 3.646326-4.901619C3.287671-4.375592 2.881196-4.339726 2.558406-4.327771L2.199751-4.291905Z'/>
<path id='eq1-g2-53' d='M1.530262-6.850311C2.044334-6.682939 2.462765-6.670984 2.594271-6.670984C3.945205-6.670984 4.805978-7.663263 4.805978-7.830635C4.805978-7.878456 4.782067-7.938232 4.710336-7.938232C4.686426-7.938232 4.662516-7.938232 4.554919-7.890411C3.88543-7.603487 3.311582-7.567621 3.000747-7.567621C2.211706-7.567621 1.649813-7.806725 1.422665-7.902366C1.338979-7.938232 1.315068-7.938232 1.303113-7.938232C1.207472-7.938232 1.207472-7.866501 1.207472-7.675218V-4.124533C1.207472-3.90934 1.207472-3.837609 1.350934-3.837609C1.41071-3.837609 1.422665-3.849564 1.542217-3.993026C1.876961-4.483188 2.438854-4.770112 3.036613-4.770112C3.670237-4.770112 3.981071-4.184309 4.076712-3.981071C4.27995-3.514819 4.291905-2.929016 4.291905-2.47472S4.291905-1.338979 3.957161-.800996C3.694147-.37061 3.227895-.071731 2.701868-.071731C1.912827-.071731 1.135741-.609714 .920548-1.482441C.980324-1.458531 1.052055-1.446575 1.111831-1.446575C1.315068-1.446575 1.637858-1.566127 1.637858-1.972603C1.637858-2.307347 1.41071-2.49863 1.111831-2.49863C.896638-2.49863 .585803-2.391034 .585803-1.924782C.585803-.908593 1.398755 .251059 2.725778 .251059C4.076712 .251059 5.260274-.884682 5.260274-2.402989C5.260274-3.825654 4.303861-5.009215 3.048568-5.009215C2.367123-5.009215 1.841096-4.710336 1.530262-4.375592V-6.850311Z'/>
<path id='eq1-g2-61' d='M8.069738-3.873474C8.237111-3.873474 8.452304-3.873474 8.452304-4.088667C8.452304-4.315816 8.249066-4.315816 8.069738-4.315816H1.028144C.860772-4.315816 .645579-4.315816 .645579-4.100623C.645579-3.873474 .848817-3.873474 1.028144-3.873474H8.069738ZM8.069738-1.649813C8.237111-1.649813 8.452304-1.649813 8.452304-1.865006C8.452304-2.092154 8.249066-2.092154 8.069738-2.092154H1.028144C.860772-2.092154 .645579-2.092154 .645579-1.876961C.645579-1.649813 .848817-1.649813 1.028144-1.649813H8.069738Z'/>
</defs>
<g id='eq1-page1'>
<use x='0' y='0' xlink:href='#eq1-g2-40'/>
<use x='5.74784' y='-4.707126' xlink:href='#eq1-g1-50'/>
<use x='9.982022' y='-4.707126' xlink:href='#eq1-g1-48'/>
<use x='14.216205' y='-4.707126' xlink:href='#eq1-g1-52'/>
<use x='18.450388' y='-4.707126' xlink:href='#eq1-g1-56'/>
<rect x='5.74784' y='-3.227886' height='.478187' width='16.936731'/>
<use x='7.864931' y='4.122632' xlink:href='#eq1-g1-50'/>
<use x='12.099114' y='4.122632' xlink:href='#eq1-g1-53'/>
<use x='16.333297' y='4.122632' xlink:href='#eq1-g1-54'/>
<use x='23.880085' y='0' xlink:href='#eq1-g2-41'/>
<use x='28.43241' y='-4.338437' xlink:href='#eq1-g1-50'/>
<use x='35.821389' y='0' xlink:href='#eq1-g0-0'/>
<use x='47.776549' y='0' xlink:href='#eq1-g2-49'/>
<use x='53.62954' y='0' xlink:href='#eq1-g2-50'/>
<use x='62.803359' y='0' xlink:href='#eq1-g2-61'/>
<use x='75.22884' y='0' xlink:href='#eq1-g2-53'/>
<use x='81.08183' y='0' xlink:href='#eq1-g2-50'/>
<use x='90.25565' y='0' xlink:href='#eq1-g2-61'/>
<use x='102.681131' y='0' xlink:href='#eq1-g2-51'/>
<use x='108.534121' y='0' xlink:href='#eq1-g2-49'/>
<use x='114.387111' y='0' xlink:href='#eq1-g2-48'/>
<use x='120.240102' y='1.793263' xlink:href='#eq1-g1-52'/>
</g>
</svg>. After adding the im­age, there
were just 11 slots left (<svg class="m-math" style="width: 2.270em; height: 0.989em; vertical-align: -0.187em;" viewBox="0 -7.704442 21.793154 9.497705">
<title>
311_4
</title>
<defs>
<path id='eq2-g0-52' d='M3.140224-5.156663C3.140224-5.316065 3.140224-5.379826 2.972852-5.379826C2.86924-5.379826 2.86127-5.371856 2.781569-5.260274L.239103-1.570112V-1.307098H2.486675V-.645579C2.486675-.350685 2.462765-.263014 1.849066-.263014H1.665753V0C2.343213-.02391 2.359153-.02391 2.81345-.02391S3.283686-.02391 3.961146 0V-.263014H3.777833C3.164134-.263014 3.140224-.350685 3.140224-.645579V-1.307098H3.985056V-1.570112H3.140224V-5.156663ZM2.542466-4.511083V-1.570112H.518057L2.542466-4.511083Z'/>
<path id='eq2-g1-49' d='M3.443088-7.663263C3.443088-7.938232 3.443088-7.950187 3.203985-7.950187C2.917061-7.627397 2.319303-7.185056 1.08792-7.185056V-6.838356C1.362889-6.838356 1.960648-6.838356 2.618182-7.149191V-.920548C2.618182-.490162 2.582316-.3467 1.530262-.3467H1.159651V0C1.482441-.02391 2.642092-.02391 3.036613-.02391S4.578829-.02391 4.901619 0V-.3467H4.531009C3.478954-.3467 3.443088-.490162 3.443088-.920548V-7.663263Z'/>
<path id='eq2-g1-51' d='M2.199751-4.291905C1.996513-4.27995 1.948692-4.267995 1.948692-4.160399C1.948692-4.040847 2.008468-4.040847 2.223661-4.040847H2.773599C3.789788-4.040847 4.244085-3.203985 4.244085-2.056289C4.244085-.490162 3.431133-.071731 2.84533-.071731C2.271482-.071731 1.291158-.3467 .944458-1.135741C1.327024-1.075965 1.673724-1.291158 1.673724-1.721544C1.673724-2.068244 1.422665-2.307347 1.08792-2.307347C.800996-2.307347 .490162-2.139975 .490162-1.685679C.490162-.621669 1.554172 .251059 2.881196 .251059C4.303861 .251059 5.355915-.836862 5.355915-2.044334C5.355915-3.144209 4.471233-4.004981 3.323537-4.208219C4.363636-4.507098 5.033126-5.379826 5.033126-6.312329C5.033126-7.256787 4.052802-7.950187 2.893151-7.950187C1.697634-7.950187 .812951-7.220922 .812951-6.348194C.812951-5.869988 1.183562-5.774346 1.362889-5.774346C1.613948-5.774346 1.900872-5.953674 1.900872-6.312329C1.900872-6.694894 1.613948-6.862267 1.350934-6.862267C1.279203-6.862267 1.255293-6.862267 1.219427-6.850311C1.673724-7.663263 2.797509-7.663263 2.857285-7.663263C3.251806-7.663263 4.028892-7.483935 4.028892-6.312329C4.028892-6.085181 3.993026-5.415691 3.646326-4.901619C3.287671-4.375592 2.881196-4.339726 2.558406-4.327771L2.199751-4.291905Z'/>
</defs>
<g id='eq2-page1'>
<use x='0' y='0' xlink:href='#eq2-g1-51'/>
<use x='5.85299' y='0' xlink:href='#eq2-g1-49'/>
<use x='11.705981' y='0' xlink:href='#eq2-g1-49'/>
<use x='17.558971' y='1.793263' xlink:href='#eq2-g0-52'/>
</g>
</svg>), and thus there’s no pos­sib­il­ity for
the al­gorithm to re­turn to earli­er slots like <svg class="m-math" style="width: 2.880em; height: 0.989em; vertical-align: -0.187em;" viewBox="0 -7.704442 27.646144 9.497705">
<title>
3100_4
</title>
<defs>
<path id='eq3-g0-52' d='M3.140224-5.156663C3.140224-5.316065 3.140224-5.379826 2.972852-5.379826C2.86924-5.379826 2.86127-5.371856 2.781569-5.260274L.239103-1.570112V-1.307098H2.486675V-.645579C2.486675-.350685 2.462765-.263014 1.849066-.263014H1.665753V0C2.343213-.02391 2.359153-.02391 2.81345-.02391S3.283686-.02391 3.961146 0V-.263014H3.777833C3.164134-.263014 3.140224-.350685 3.140224-.645579V-1.307098H3.985056V-1.570112H3.140224V-5.156663ZM2.542466-4.511083V-1.570112H.518057L2.542466-4.511083Z'/>
<path id='eq3-g1-48' d='M5.355915-3.825654C5.355915-4.817933 5.296139-5.786301 4.865753-6.694894C4.375592-7.687173 3.514819-7.950187 2.929016-7.950187C2.235616-7.950187 1.3868-7.603487 .944458-6.611208C.609714-5.858032 .490162-5.116812 .490162-3.825654C.490162-2.666002 .573848-1.793275 1.004234-.944458C1.470486-.035866 2.295392 .251059 2.917061 .251059C3.957161 .251059 4.554919-.37061 4.901619-1.06401C5.332005-1.960648 5.355915-3.132254 5.355915-3.825654ZM2.917061 .011955C2.534496 .011955 1.75741-.203238 1.530262-1.506351C1.398755-2.223661 1.398755-3.132254 1.398755-3.969116C1.398755-4.94944 1.398755-5.834122 1.590037-6.539477C1.793275-7.340473 2.402989-7.711083 2.917061-7.711083C3.371357-7.711083 4.064757-7.436115 4.291905-6.40797C4.447323-5.726526 4.447323-4.782067 4.447323-3.969116C4.447323-3.16812 4.447323-2.259527 4.315816-1.530262C4.088667-.215193 3.335492 .011955 2.917061 .011955Z'/>
<path id='eq3-g1-49' d='M3.443088-7.663263C3.443088-7.938232 3.443088-7.950187 3.203985-7.950187C2.917061-7.627397 2.319303-7.185056 1.08792-7.185056V-6.838356C1.362889-6.838356 1.960648-6.838356 2.618182-7.149191V-.920548C2.618182-.490162 2.582316-.3467 1.530262-.3467H1.159651V0C1.482441-.02391 2.642092-.02391 3.036613-.02391S4.578829-.02391 4.901619 0V-.3467H4.531009C3.478954-.3467 3.443088-.490162 3.443088-.920548V-7.663263Z'/>
<path id='eq3-g1-51' d='M2.199751-4.291905C1.996513-4.27995 1.948692-4.267995 1.948692-4.160399C1.948692-4.040847 2.008468-4.040847 2.223661-4.040847H2.773599C3.789788-4.040847 4.244085-3.203985 4.244085-2.056289C4.244085-.490162 3.431133-.071731 2.84533-.071731C2.271482-.071731 1.291158-.3467 .944458-1.135741C1.327024-1.075965 1.673724-1.291158 1.673724-1.721544C1.673724-2.068244 1.422665-2.307347 1.08792-2.307347C.800996-2.307347 .490162-2.139975 .490162-1.685679C.490162-.621669 1.554172 .251059 2.881196 .251059C4.303861 .251059 5.355915-.836862 5.355915-2.044334C5.355915-3.144209 4.471233-4.004981 3.323537-4.208219C4.363636-4.507098 5.033126-5.379826 5.033126-6.312329C5.033126-7.256787 4.052802-7.950187 2.893151-7.950187C1.697634-7.950187 .812951-7.220922 .812951-6.348194C.812951-5.869988 1.183562-5.774346 1.362889-5.774346C1.613948-5.774346 1.900872-5.953674 1.900872-6.312329C1.900872-6.694894 1.613948-6.862267 1.350934-6.862267C1.279203-6.862267 1.255293-6.862267 1.219427-6.850311C1.673724-7.663263 2.797509-7.663263 2.857285-7.663263C3.251806-7.663263 4.028892-7.483935 4.028892-6.312329C4.028892-6.085181 3.993026-5.415691 3.646326-4.901619C3.287671-4.375592 2.881196-4.339726 2.558406-4.327771L2.199751-4.291905Z'/>
</defs>
<g id='eq3-page1'>
<use x='0' y='0' xlink:href='#eq3-g1-51'/>
<use x='5.85299' y='0' xlink:href='#eq3-g1-49'/>
<use x='11.705981' y='0' xlink:href='#eq3-g1-48'/>
<use x='17.558971' y='0' xlink:href='#eq3-g1-48'/>
<use x='23.411961' y='1.793263' xlink:href='#eq3-g0-52'/>
</g>
</svg> to <svg class="m-math" style="width: 2.880em; height: 0.989em; vertical-align: -0.187em;" viewBox="0 -7.704442 27.646144 9.497705">
<title>
3103_4
</title>
<defs>
<path id='eq4-g0-52' d='M3.140224-5.156663C3.140224-5.316065 3.140224-5.379826 2.972852-5.379826C2.86924-5.379826 2.86127-5.371856 2.781569-5.260274L.239103-1.570112V-1.307098H2.486675V-.645579C2.486675-.350685 2.462765-.263014 1.849066-.263014H1.665753V0C2.343213-.02391 2.359153-.02391 2.81345-.02391S3.283686-.02391 3.961146 0V-.263014H3.777833C3.164134-.263014 3.140224-.350685 3.140224-.645579V-1.307098H3.985056V-1.570112H3.140224V-5.156663ZM2.542466-4.511083V-1.570112H.518057L2.542466-4.511083Z'/>
<path id='eq4-g1-48' d='M5.355915-3.825654C5.355915-4.817933 5.296139-5.786301 4.865753-6.694894C4.375592-7.687173 3.514819-7.950187 2.929016-7.950187C2.235616-7.950187 1.3868-7.603487 .944458-6.611208C.609714-5.858032 .490162-5.116812 .490162-3.825654C.490162-2.666002 .573848-1.793275 1.004234-.944458C1.470486-.035866 2.295392 .251059 2.917061 .251059C3.957161 .251059 4.554919-.37061 4.901619-1.06401C5.332005-1.960648 5.355915-3.132254 5.355915-3.825654ZM2.917061 .011955C2.534496 .011955 1.75741-.203238 1.530262-1.506351C1.398755-2.223661 1.398755-3.132254 1.398755-3.969116C1.398755-4.94944 1.398755-5.834122 1.590037-6.539477C1.793275-7.340473 2.402989-7.711083 2.917061-7.711083C3.371357-7.711083 4.064757-7.436115 4.291905-6.40797C4.447323-5.726526 4.447323-4.782067 4.447323-3.969116C4.447323-3.16812 4.447323-2.259527 4.315816-1.530262C4.088667-.215193 3.335492 .011955 2.917061 .011955Z'/>
<path id='eq4-g1-49' d='M3.443088-7.663263C3.443088-7.938232 3.443088-7.950187 3.203985-7.950187C2.917061-7.627397 2.319303-7.185056 1.08792-7.185056V-6.838356C1.362889-6.838356 1.960648-6.838356 2.618182-7.149191V-.920548C2.618182-.490162 2.582316-.3467 1.530262-.3467H1.159651V0C1.482441-.02391 2.642092-.02391 3.036613-.02391S4.578829-.02391 4.901619 0V-.3467H4.531009C3.478954-.3467 3.443088-.490162 3.443088-.920548V-7.663263Z'/>
<path id='eq4-g1-51' d='M2.199751-4.291905C1.996513-4.27995 1.948692-4.267995 1.948692-4.160399C1.948692-4.040847 2.008468-4.040847 2.223661-4.040847H2.773599C3.789788-4.040847 4.244085-3.203985 4.244085-2.056289C4.244085-.490162 3.431133-.071731 2.84533-.071731C2.271482-.071731 1.291158-.3467 .944458-1.135741C1.327024-1.075965 1.673724-1.291158 1.673724-1.721544C1.673724-2.068244 1.422665-2.307347 1.08792-2.307347C.800996-2.307347 .490162-2.139975 .490162-1.685679C.490162-.621669 1.554172 .251059 2.881196 .251059C4.303861 .251059 5.355915-.836862 5.355915-2.044334C5.355915-3.144209 4.471233-4.004981 3.323537-4.208219C4.363636-4.507098 5.033126-5.379826 5.033126-6.312329C5.033126-7.256787 4.052802-7.950187 2.893151-7.950187C1.697634-7.950187 .812951-7.220922 .812951-6.348194C.812951-5.869988 1.183562-5.774346 1.362889-5.774346C1.613948-5.774346 1.900872-5.953674 1.900872-6.312329C1.900872-6.694894 1.613948-6.862267 1.350934-6.862267C1.279203-6.862267 1.255293-6.862267 1.219427-6.850311C1.673724-7.663263 2.797509-7.663263 2.857285-7.663263C3.251806-7.663263 4.028892-7.483935 4.028892-6.312329C4.028892-6.085181 3.993026-5.415691 3.646326-4.901619C3.287671-4.375592 2.881196-4.339726 2.558406-4.327771L2.199751-4.291905Z'/>
</defs>
<g id='eq4-page1'>
<use x='0' y='0' xlink:href='#eq4-g1-51'/>
<use x='5.85299' y='0' xlink:href='#eq4-g1-49'/>
<use x='11.705981' y='0' xlink:href='#eq4-g1-48'/>
<use x='17.558971' y='0' xlink:href='#eq4-g1-51'/>
<use x='23.411961' y='1.793263' xlink:href='#eq4-g0-52'/>
</g>
</svg>
and cause an over­lap.</p>
<p>To con­vert these base-4 num­bers to ac­tu­al tex­ture co­ordin­ates, I don’t have
any­thing tan­gible to refer to<a class="m-footnote" href="#id17" id="id7">7</a>, but when rep­res­en­ted as bin­ary, each odd
bit is an Y co­ordin­ate (lower or up­per) and each even bit is an X co­ordin­ate
(left or right). Then, every two bits the co­ordin­ate range shrinks by half in
both di­men­sions, and the res­ult is a sum of these.</p>
</section>
<section id="full-code">
<h3><a href="#full-code">Full code</a></h3>
<p>For brev­ity, the fol­low­ing snip­pet as­sumes an already-sor­ted in­put with largest
im­ages first, every size be­ing a non-zero power-of-two square, and lay­er size
be­ing at least as large as the largest im­age in the set. Out­puts 3D off­sets in
the ar­ray tex­ture.</p>
<pre class="m-code"><span class="n">Containers</span><span class="o">::</span><span class="n">Array</span><span class="o"><</span><span class="n">Vector3i</span><span class="o">></span> <span class="n">atlasArrayPowerOfTwo</span><span class="p">(</span>
<span class="k">const</span> <span class="n">Vector2i</span><span class="o">&</span> <span class="n">layerSize</span><span class="p">,</span> <span class="n">Containers</span><span class="o">::</span><span class="n">ArrayView</span><span class="o"><</span><span class="k">const</span> <span class="n">Vector2i</span><span class="o">></span> <span class="n">sortedSizes</span>
<span class="p">)</span> <span class="p">{</span>
<span class="n">Containers</span><span class="o">::</span><span class="n">Array</span><span class="o"><</span><span class="n">Vector3i</span><span class="o">></span> <span class="n">output</span><span class="p">{</span><span class="n">NoInit</span><span class="p">,</span> <span class="n">sortedSizes</span><span class="p">.</span><span class="n">size</span><span class="p">()};</span>
<span class="cm">/* Start with the whole first layer free */</span>
<span class="n">Int</span> <span class="n">layer</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">UnsignedInt</span> <span class="n">free</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="n">Vector2i</span> <span class="n">previousSize</span> <span class="o">=</span> <span class="n">layerSize</span><span class="p">;</span>
<span class="k">for</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="kt">size_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">!=</span> <span class="n">sortedSizes</span><span class="p">.</span><span class="n">size</span><span class="p">();</span> <span class="o">++</span><span class="n">i</span><span class="p">)</span> <span class="p">{</span>
<span class="cm">/* No free slots left, go to the next layer. Then, what's free, is one</span>
<span class="cm"> whole layer. */</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="n">free</span><span class="p">)</span> <span class="p">{</span>
<span class="o">++</span><span class="n">layer</span><span class="p">;</span>
<span class="n">free</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="n">previousSize</span> <span class="o">=</span> <span class="n">layerSize</span><span class="p">;</span>
<span class="p">}</span>
<span class="cm">/* Multiply number of free slots based on area difference from previous</span>
<span class="cm"> size. If the size is the same, nothing changes. */</span>
<span class="n">free</span> <span class="o">*=</span> <span class="p">(</span><span class="n">previousSize</span><span class="o">/</span><span class="n">sortedSizes</span><span class="p">[</span><span class="n">i</span><span class="p">]).</span><span class="n">product</span><span class="p">();</span>
<span class="cm">/* Calculate slot index and coordinates from the slot index */</span>
<span class="k">const</span> <span class="n">UnsignedInt</span> <span class="n">sideSlotCount</span> <span class="o">=</span> <span class="n">layerSize</span><span class="p">.</span><span class="n">x</span><span class="p">()</span><span class="o">/</span><span class="n">sortedSizes</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">x</span><span class="p">();</span>
<span class="k">const</span> <span class="n">UnsignedInt</span> <span class="n">layerDepth</span> <span class="o">=</span> <span class="n">Math</span><span class="o">::</span><span class="n">log2</span><span class="p">(</span><span class="n">sideSlotCount</span><span class="p">);</span>
<span class="k">const</span> <span class="n">UnsignedInt</span> <span class="n">slotIndex</span> <span class="o">=</span> <span class="n">sideSlotCount</span><span class="o">*</span><span class="n">sideSlotCount</span> <span class="o">-</span> <span class="n">free</span><span class="p">;</span>
<span class="n">Vector2i</span> <span class="n">coordinates</span><span class="p">;</span>
<span class="k">for</span><span class="p">(</span><span class="n">UnsignedInt</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">layerDepth</span><span class="p">;</span> <span class="o">++</span><span class="n">i</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="n">slotIndex</span> <span class="o">&</span> <span class="p">(</span><span class="mi">1</span> <span class="o"><<</span> <span class="mi">2</span><span class="o">*</span><span class="p">(</span><span class="n">layerDepth</span> <span class="o">-</span> <span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)))</span>
<span class="n">coordinates</span><span class="p">.</span><span class="n">x</span><span class="p">()</span> <span class="o">+=</span> <span class="n">layerSize</span><span class="p">.</span><span class="n">x</span><span class="p">()</span> <span class="o">>></span> <span class="p">(</span><span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="n">slotIndex</span> <span class="o">&</span> <span class="p">(</span><span class="mi">1</span> <span class="o"><<</span> <span class="p">(</span><span class="mi">2</span><span class="o">*</span><span class="p">(</span><span class="n">layerDepth</span> <span class="o">-</span> <span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)))</span>
<span class="n">coordinates</span><span class="p">.</span><span class="n">y</span><span class="p">()</span> <span class="o">+=</span> <span class="n">layerSize</span><span class="p">.</span><span class="n">y</span><span class="p">()</span> <span class="o">>></span> <span class="p">(</span><span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span>
<span class="p">}</span>
<span class="cm">/* Write the final coordinates to the output */</span>
<span class="n">output</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span><span class="n">coordinates</span><span class="p">,</span> <span class="n">layer</span><span class="p">};</span>
<span class="n">previousSize</span> <span class="o">=</span> <span class="n">sortedSizes</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
<span class="o">--</span><span class="n">free</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">output</span><span class="p">;</span>
<span class="p">}</span></pre>
</section>
</section>
<section id="environmental-responsibility-and-waste-management">
<h2><a href="#environmental-responsibility-and-waste-management">En­vir­on­ment­al re­spons­ib­il­ity and waste man­age­ment</a></h2>
<p>The only vari­able af­fect­ing out­put of this al­gorithm is the ar­ray lay­er size.
For the least waste in the last lay­er it’s best to set it to size of the
largest im­age and not more. Gen­er­ally the more lay­ers you have the less
re­l­at­ive waste there is. With 100 lay­ers, if the last lay­er would be con­tain­ing
just one 1×1 im­age (which is a rather un­likely worst-case scen­ario), the
re­l­at­ive waste is still less than 1%. Prac­tic­ally speak­ing how­ever, if you’d
have thou­sands of 64×64 or smal­ler im­ages — say, an icon pack — then
it’s bet­ter to waste a bit and choose a big­ger lay­er size to avoid hit­ting
hard­ware tex­ture size lim­its.</p>
<p>With a lot of in­put data there are also oth­er factors to con­sider. While in my
case there were al­most no non-power-of-two tex­tures, there was <em>quite a few</em>
uni­formly colored 2048×2048 im­ages that could be scaled down to 1×1
without any loss in qual­ity.</p>
</section>
<section id="conclusion">
<h2><a href="#conclusion">Con­clu­sion</a></h2>
<p>I don’t claim any break­through in­ven­tion. And since the al­gorithm is neither
gen­er­ic<a class="m-footnote" href="#id18" id="id8">8</a> nor ML-en­abled<a class="m-footnote" href="#id19" id="id9">9</a><a class="m-footnote" href="#id20" id="id10">10</a>, I don’t think it’s cool enough to
gain much trac­tion.</p>
<p>But here it is, im­ple­men­ted as <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1TextureTools.html#abcd7d0cc9e5baadfca44481d19a83ee7">Tex­tureTools::at­las­Ar­ray­Po­wer­OfT­wo()</a> in
Mag­num. Cheers!</p>
<aside class="m-note m-dim">
Ques­tions? Com­plaints? Share your opin­ion on so­cial net­works: <a href="https://twitter.com/czmosra/status/1537117845351538692">Twit­ter</a></aside>
<dl class="m-footnote">
<dt id="id11">1</a>.</dt>
<dd><span class="m-footnote"><a href="#id1">^</a></span> In an ideal­ist­ic case be­ing able to sub­mit a single draw call for (a
culled sub­set of) the whole scene in­stead of draw­ing every mesh sep­ar­ately,
sig­ni­fic­antly re­du­cing over­head.</dd>
<dt id="id12">2</a>.</dt>
<dd><span class="m-footnote"><a href="#id2">^</a></span> There’s <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1GltfImporter.html#Trade-GltfImporter-behavior-textures-array">ex­per­i­ment­al KHR_­tex­ture_k­tx sup­port in Glt­fIm­port­er</a>
if you are in­ter­ested, avail­able un­der an off-by-de­fault op­tion. A glTF
ex­port­er sup­port­ing this ex­ten­sion is cur­rently in a WIP branch and will
even­tu­ally be merged too.</dd>
<dt id="id13">3</a>.</dt>
<dd><span class="m-footnote"><a href="#id3">^</a></span> The cur­rent im­ple­ment­a­tion, dat­ing back to 2012, di­vides the out­put in­to
a reg­u­lar grid of cells that are all as large as the largest in­put. Yes,
it’s that bad.</dd>
<dt id="id14">4</a>.</dt>
<dd><span class="m-footnote"><a href="#id4">^</a></span> <a href="https://github.com/TeamHypersomnia/rectpack2D">Team­Hy­per­som­nia/rect­pack­2D</a>, or its dis­tilled ver­sion at
<a class="m-link-wrap" href="https://observablehq.com/@mourner/simple-rectangle-packing">https://observablehq.com/@mourner/simple-rectangle-packing</a></dd>
<dt id="id15">5</a>.</dt>
<dd><span class="m-footnote"><a href="#id5">^</a></span> <a href="https://github.com/juj/RectangleBinPack">juj/Rect­angleBin­Pack</a></dd>
<dt id="id16">6</a>.</dt>
<dd><span class="m-footnote"><a href="#id6">^</a></span> Sent back to be re­worked, ideally; if not pos­sible then pad­ded or
res­ampled.</dd>
<dt id="id17">7</a>.</dt>
<dd><span class="m-footnote"><a href="#id7">^</a></span> Back in 2010, when high-speed mo­bile in­ter­net was still some­thing
un­fathom­able, I was re­verse-en­gin­eer­ing satel­lite im­agery in Google Maps to
down­load it for off­line view­ing on my coun­tryside bike trips. Im­age URLs in
one of the lay­ers looked like <code>/elevation/tqrsqrqqtstt.jpg</code> and, hav­ing
no pri­or know­ledge about any­thing graph­ics-re­lated, it took me quite a
while to fig­ure out what the let­ters meant. Since then I have the quadtree
ad­dress­ing scheme forever in­grained in my head.</dd>
<dt id="id18">8</a>.</dt>
<dd><span class="m-footnote"><a href="#id8">^</a></span> Yes, I still need to im­ple­ment a real rect­angle pack­ing al­gorithm for
the gen­er­ic use case. Someday.</dd>
<dt id="id19">9</a>.</dt>
<dd><span class="m-footnote"><a href="#id9">^</a></span> <em>Ranked Re­ward: En­abling Self-Play Re­in­force­ment Learn­ing for
Com­bin­at­or­i­al Op­tim­iz­a­tion</em>, 2018, <a class="m-link-wrap" href="https://arxiv.org/abs/1807.01672">https://arxiv.org/abs/1807.01672</a></dd>
<dt id="id20">10</a>.</dt>
<dd><span class="m-footnote"><a href="#id10">^</a></span> <em>At­tend2Pack: Bin Pack­ing through Deep Re­in­force­ment Learn­ing with
At­ten­tion</em>, 2021, <a class="m-link-wrap" href="https://arxiv.org/abs/2107.04333">https://arxiv.org/abs/2107.04333</a></dd>
</dl>
</section>
A Decade of Magnum2020-12-21T00:00:00+01:002020-12-21T00:00:00+01:00Vladimír Vondruštag:blog.magnum.graphics,2020-12-21:/announcements/a-decade-of-magnum/<p>On 19th Decem­ber 2010, Mag­num saw its first com­mit. A bunch more
com­mits happened since then and I learned some things along the way.</p>
<p>A proven way to start a pro­ject is with a bunch of util­it­ies cre­ated out of
ne­ces­sity to make one’s life a bit less miser­able, and then it­er­at­ing from
there. In my case, in late 2010, the <a href="hhttps://books.slashdot.org/story/10/10/25/134209/opengl-superbible-5th-ed">fifth edi­tion of OpenGL Su­per­Bible came out</a>,
fully re­vised for mod­ern OpenGL and throw­ing away everything re­lated to the
clas­sic fixed-func­tion pipeline. The book was <em>great</em>, how­ever the ac­com­pa­ny­ing
code was not, and it was tak­ing a lot of fun out of the learn­ing ex­per­i­ence. So
<a href="https://github.com/mosra/magnum/commit/09d74422d955223db758f4939302eb8a662c88d9">I star­ted an ex­per­i­ment</a>
to find a bet­ter way … and then kept go­ing.</p>
<p class="m-transition">~ ~ ~</p>
<p><em>A lot</em> can hap­pen in a dec­ade, so I’ll skip the ob­vi­ous dry know­ledge like
“auto­mated tests are good” and fo­cus just on a few juicy bits.</p>
<section id="let-s-try-this-new-thing-called-c-0x">
<h2><a href="#let-s-try-this-new-thing-called-c-0x">Let’s try this new thing called C++0x</a></h2>
<p>Back in the 2000s, C++ was this ven­er­able old lan­guage that wasn’t ex­actly fun
to use. C++11, ori­gin­ally code­named C++0x, ar­rived to change that. I saw a huge
po­ten­tial in the new fea­tures, es­pe­cially scoped enums, move se­mantics and the
short­hand <code class="cpp m-code"><span class="p">{}</span></code>-ini­tial­iz­a­tion — “clas­sic” C++, with non-copy­able
classes al­loc­ated with <code class="cpp m-code"><span class="k">new</span></code>, enums with ugly pre­fixes to avoid name
clashes and <code class="cpp m-code"><span class="n">foo</span><span class="p">(</span><span class="k">const</span> <span class="n">Vector3</span><span class="o">&</span><span class="p">)</span></code> / <code class="cpp m-code"><span class="n">foo</span><span class="p">(</span><span class="kt">float</span><span class="p">,</span> <span class="kt">float</span><span class="p">,</span> <span class="kt">float</span><span class="p">)</span></code>
over­loads for everything sud­denly felt like an ugly and in­ef­fi­cient main­ten­ance
night­mare — and so I de­cided to design the lib­rary with C++11 in mind.</p>
<div class="m-right-m m-col-m-4 m-container-inflate">
<blockquote>
<p class="m-text m-big m-em">“You need to be­come old and wise and delay any new C++ fea­ture ad­op­tion
by at least 5 years.”</p>
<p class="attribution">—<a href="https://twitter.com/bkaradzic/status/623625252447260672">@bkarad­z­ic</a></p>
</blockquote>
</div>
<p>It was a risky de­cision and <em>hard</em> at first be­cause I was com­monly hit­ting
ex­cit­ing yet-un­dis­covered com­piler bugs. In­dustry vet­er­ans re­peatedly told me
us­ing C++11 was not a good idea, nev­er­the­less I’m happy that I didn’t back off
on that. (Even though back­port­ing to GCC 4.4 to run on a
<a href="http://beagleboard.org">Beagle­Board</a> and to MS­VC 2013 to fi­nally run on
Win­dows was truly hellish.) The us­ab­il­ity and clean­ness of the design simply
wouldn’t be there without C++11, though to this day I feel un­easy about us­ing
lambdas be­cause I re­mem­ber how <em>bad</em> the sup­port was in the early days, and I
can still enu­mer­ate all un­fin­ished corners in the GCC 4.7 STL im­ple­ment­a­tion.</p>
<p>Today, Mag­num is <em>still</em> C++11 and I’m not really plan­ning to re­quire a new­er
stand­ard ver­sion any­time soon. While Mag­num users are com­monly on C++17, there
isn’t any­thing nearly as ground­break­ing as was in C++11 to be use­ful <em>on the
lib­rary level</em>. I’m now hap­pily on the “cave­man” side of the spec­trum and users
that chose Mag­num be­cause it was the only thing still com­pil­ing on a Cen­tOS
with GCC 4.8 con­firm that I made the right de­cision.</p>
</section>
<section id="make-upgrades-painless-and-apis-easy-to-deprecate">
<h2><a href="#make-upgrades-painless-and-apis-easy-to-deprecate">Make up­grades pain­less and APIs easy to de­prec­ate</a></h2>
<p>When evolving a lib­rary, there’s al­ways a tradeoff in how much you al­low
your­self to in­nov­ate <a href="https://thenewstack.io/greg-kroah-hartman-lessons-for-developers-from-20-years-of-linux-kernel-work/">without mak­ing users too mad</a>.
If up­grades are pain­ful, users will hes­it­ate to up­grade, which means there will
of­ten be nobody to dis­cov­er bugs in new code. In turn, even less users want
to up­grade, res­ult­ing in a down­wards spir­al of the lib­rary in­creas­ingly los­ing
touch with user ex­pect­a­tions.</p>
<p>I de­cided to not both­er with ABI com­pat­ib­il­ity be­cause it makes it ba­sic­ally
im­possible to ex­per­i­ment, and that’s no good in the fast-chan­ging land­scape of
GPU de­vel­op­ment. On the oth­er hand, caus­ing com­piler fail­ures every time the
users up­grade would mean they even­tu­ally lose pa­tience and leave.</p>
<p>Over time, I real­ized what were the pos­sib­il­it­ies for API-com­pat­ible fea­ture
de­prec­a­tion:</p>
<ul>
<li>A func­tion can have an <code class="cpp m-code"><span class="kr">inline</span></code> ali­as that matches the pre­vi­ous
sig­na­ture as long as the two over­loads are un­am­bigu­ous</li>
<li>A class can be turned in­to a <code class="cpp m-code"><span class="k">typedef</span></code> and vice versa (which is use­ful
when mak­ing a class tem­plated or turn­ing a tem­plate in­to a con­crete class)</li>
<li>A namespace can be re­named and its mem­bers ex­posed un­der the old name via
<code class="cpp m-code"><span class="k">using</span></code>, <code class="cpp m-code"><span class="k">typedef</span></code> or <code class="cpp m-code"><span class="k">namespace</span> <span class="n">Old</span> <span class="o">=</span> <span class="n">New</span></code></li>
<li>Head­ers can be re­named, split or joined and de­prec­ated “prox­ies” kept that
<code class="cpp m-code"><span class="cp">#include</span></code> from the new loc­a­tions</li>
<li>A CMake tar­get can hide <em>a lot</em> of back­wards com­pat­ib­il­ity — link­ing to
lib­rar­ies that were split out of the ori­gin­al tar­get, adding back ob­sol­ete
in­clude paths, modi­fy­ing com­piler flags …</li>
<li>Plu­gin in­ter­faces can be changed freely as long as all the <code class="cpp m-code"><span class="k">virtual</span></code>
func­tions are <a href="http://www.gotw.ca/publications/mill18.htm">kept private</a>,
out of users’ reach. That way plu­gin im­ple­ment­a­tions can be switched to use
the new in­ter­faces and back­wards com­pat­ib­il­ity provided only in the
non-vir­tu­al pub­lic API in­stead of every plu­gin.</li>
</ul>
<p>And what were the lim­its:</p>
<ul>
<li>Plain struc­ture data mem­bers are ba­sic­ally im­possible to de­prec­ate, so if
you de­cide to have giv­en mem­ber cal­cu­lated on-the-fly or change its name /
type, you have no way to provide back­wards com­pat­ib­il­ity. This is why all
Mag­num get­ters are <code class="cpp m-code"><span class="kr">inline</span></code> func­tions, with data mem­bers nev­er ex­posed
dir­ectly.</li>
<li>It’s not gen­er­ally pos­sible to re­order func­tion ar­gu­ments, un­less the types
are dis­tinct and not im­pli­citly con­vert­ible to each oth­er. I’m stuck on
this in a few places (<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/namespaceCorrade_1_1Utility_1_1Directory.html#af3e40dd0b19be0b79e8d2bad631f9009">Util­ity::Dir­ect­ory::write()</a> is one).</li>
<li>Func­tion re­turn types are hard to change, un­less the types are im­pli­citly
con­vert­ible to each oth­er or oth­er­wise com­pat­ible. This is also why I
dis­cour­age users from <code class="cpp m-code"><span class="k">auto</span></code> — if they use a con­crete type,
back­wards-com­pat­ib­il­ity meas­ures en­sure what’s re­turned is im­pli­citly
con­ver­ted to what’s ex­pec­ted. Thanks to that the change from
<a class="m-flat" href="https://en.cppreference.com/w/cpp/memory/unique_ptr">std::unique_ptr</a> to <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/classCorrade_1_1Containers_1_1Pointer.html">Con­tain­ers::Point­er</a> every­where went so
smooth for most of the users.</li>
</ul>
<p>While I want user code to keep build­ing, I also want to <em>nudge</em> them to up­grade
to new­er APIs and drop back­wards-com­pat­ib­il­ity ali­ases and wrap­pers after a
year or two after de­prec­a­tion. Apart from de­prec­ated APIs be­ing clearly marked
as such in the doc­u­ment­a­tion, this is done us­ing the <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/Macros_8h.html#a8bc844974522ec40054a604cadbdb2b2">COR­RADE_DE­PREC­ATED()</a>
fam­ily of mac­ros, which on suf­fi­ciently re­cent com­pilers can add de­prec­a­tion
warn­ings on everything in­clud­ing func­tions, classes, enums, namespaces and
files. One step fur­ther, the users also have an op­tion to
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/Magnum_8h.html#a8fa4463a10327399ce45e0c89e4f5f2f">dis­able back­wards com­pat­ib­il­ity al­to­geth­er</a> and
fix even the spots the com­piler didn’t / couldn’t warn about.</p>
<p>This paid off the most <a href="https://blog.magnum.graphics/announcements/2018.04/">when I made the OpenGL wrap­per op­tion­al in 2018</a>
— while it was ba­sic­ally a com­plete re­write of the most cent­ral parts, the
com­pat­ib­il­ity ali­ases made ex­ist­ing code still com­pile, only with a ton of
warn­ings that told people what to change and how. The up­grade went sur­pris­ingly
well for every­body and I fi­nally re­moved the com­pat­ib­il­ity in­ter­faces earli­er
this year.</p>
<p>I’m happy to re­port that I have sev­er­al long-time users run­ning their
<em>pro­duc­tion</em> code off Mag­num <code>master</code> — be­cause they trust it that much.</p>
</section>
<section id="friendly-vendor-lock-in">
<h2><a href="#friendly-vendor-lock-in">Friendly vendor lock-in</a></h2>
<p>While pro­jects made by in­dustry vet­er­ans with good repu­ta­tion are usu­ally
trus­ted im­pli­citly, a pro­ject from an un­known has to be <em>ex­cep­tion­ally per­fect</em>
to make a dent. Though … why even lower the bar as the repu­ta­tion builds up over time?</p>
<p>Even though many suc­cess­ful pro­jects can do with only a Git­Hub README just
fine, I spent sev­er­al months build­ing a <a href="https://mcss.mosra.cz">whole CSS lay­out frame­work and site theme</a>
from scratch, with a <a href="https://blog.magnum.graphics/meta/implementing-a-fast-doxygen-search/">fast doc­u­ment­a­tion search</a>
ar­riv­ing shortly after. The time wasn’t wasted and it was great to hear
feed­back say­ing that people miss this in oth­er pro­jects, or see people ad­apt­ing
Mag­num’s doc­u­ment­a­tion sys­tem for their pro­jects.</p>
<p>Apart from that there’s vari­ous minor Qual­ity-of-Life fea­tures one gets quickly
used to like the <code class="cpp m-code"><span class="mf">90.0_degf</span></code> or <code class="cpp m-code"><span class="mh">0x3bd267_rgbf<span class="m-code-color" style="background-color: #3bd267;"></span></span></code> lit­er­als, abil­ity to
con­veni­ently print al­most any con­tain­er or enum with
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/classCorrade_1_1Utility_1_1Debug.html">De­bug</a> or as­sert mes­sages that show <em>what ex­actly</em> went
wrong, not just that <em>some­thing</em> wrong happened. To help ad­op­tion, small
re­usable bits of Mag­num were ex­trac­ted to <a href="https://github.com/mosra/magnum-singles">single-head­er lib­rar­ies</a>
so users can eas­ily bring their fa­vor­ite APIs to oth­er pro­jects as well.</p>
</section>
<section id="finding-the-right-amount-of-nih">
<h2><a href="#finding-the-right-amount-of-nih">Find­ing the right amount of NIH</a></h2>
<div class="m-left-m m-col-m-4 m-container-inflate">
<blockquote>
<p class="m-text m-big m-em"><em>If every­one fol­lowed the “Don’t re­in­vent the wheel” say­ing, we’d still
be stuck with wooden wheels today.</em></p>
</blockquote>
</div>
<p>While in­vest­ing time in­to writ­ing my own math lib­rary was worth it as I could
design some­thing from the ground up without be­ing tied to how GLSL works or how
math was done in the C++03 days, at­tempt­ing to write my own phys­ics lib­rary was
<em>a mis­take</em>. It was a use­ful learn­ing ex­per­i­ence tho — next time I made sure
that if I go all ar­chi­tec­ture as­tro­naut on some­thing, I’ll im­ple­ment the
ac­tu­ally use­ful bits (in case of phys­ics, <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1Math_1_1Intersection.html">in­ter­sec­tion al­gorithms</a>)
on a lower level first so when it ul­ti­mately doesn’t work out, I don’t need to
throw away <em>everything</em>.</p>
<p>The be­ne­fits of a layered ap­proach was some­thing that dawned on me very slowly
— ori­gin­ally, to get any­thing on screen, it was man­dat­ory to use a scene
graph, a cam­era ab­strac­tion and a bare­bones GLUT-based ap­plic­a­tion. Today, you
can use for ex­ample just the math lib­rary and out­put to a SVG, without touch­ing
the GPU or open­ing any win­dow, and <a href="https://magnum.graphics/features/">the lib­rary pieces are largely in­de­pend­ent</a>.
As the uses broadened from games to ed­it­ors to re­search demos to heavy­weight
data pro­cessing, it be­came clear that one design can’t fit everything and while
Mag­num APIs <em>can</em> be bet­ter for a cer­tain use case than a com­mon 3rd party lib,
there are also use cases for which the same 3rd party lib is more suited than
Mag­num.</p>
<ul>
<li>The built­in <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1Math.html">Math</a> lib­rary is fea­ture­ful and well doc­u­mented, but if
you need to do heavy cal­cu­la­tions on large sparse matrices, Ei­gen is a
bet­ter fit. <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1EigenIntegration.html">And we can do the type con­ver­sion for you.</a> Or maybe the big­ger half of your pro­ject already uses
GLM? <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1GlmIntegration.html">No wor­ries either.</a></li>
<li>Mag­num has a hier­arch­ic­al <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1SceneGraph.html">SceneGraph</a>, but you can simply not use it,
if you don’t need. <a href="https://github.com/skypjack/entt">Or, if you’re in­to ECS in­stead, use entt</a>.</li>
<li>There’s <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Platform_1_1Sdl2Application.html">Plat­form::Sdl2Ap­plic­a­tion</a> that cov­ers most plat­forms, or
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Platform_1_1GlfwApplication.html">Plat­form::GlfwAp­plic­a­tion</a> if you need just desktop and want to be
lean­er. Or <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Platform_1_1EmscriptenApplication.html">Plat­form::Em­scrip­tenAp­plic­a­tion</a> if you want some­thing
op­tim­ized for the web. Or go com­pletely cus­tom and em­bed Mag­num in GTK,
wx­Wid­gets or Qt — <a href="https://github.com/mosra/magnum-bootstrap">for each there’s a boot­strap pro­ject to get you star­ted</a>.</li>
<li>Want phys­ics? Feel free to use Mag­num with Bul­let, DART, Box2D or for
ex­ample PhysX. <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1DartIntegration.html">In­teg­ra­tions</a>,
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1BulletIntegration.html">help­ers</a> and <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/examples-box2d.html">ex­amples</a>
provided.</li>
<li>Want to use some OpenGL or Vulkan API that’s not wrapped by Mag­num, or
com­bine with 3rd party ren­der­ing? <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/vulkan-wrapping.html">No prob­lem.</a></li>
<li>Don’t like Mag­num’s OpenGL or Vulkan wrap­pers at all, ac­tu­ally? Turn them
off and keep us­ing Mag­num as­set man­age­ment APIs in com­bin­a­tion with
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/examples-triangle-sokol.html">sokol_g­fx</a>, <a href="https://github.com/bkaradzic/bgfx">bg­fx</a>
and oth­er 3rd party ren­der­ers.</li>
</ul>
<p>I have to ad­mit it took quite some ef­fort to swal­low the pride and ac­cept the
fact that it’s simply not hu­manly pos­sible to make Mag­num the best op­tion for
<em>every</em> use case — but in the end I real­ized that if I give the users
con­veni­ent in­teg­ra­tions with 3rd party lib­rar­ies, they will hap­pily stay
be­cause the re­main­ing parts of Mag­num are still worth it for them.</p>
</section>
<section id="it-s-not-good-to-use-the-stl-but-it-s-not-good-to-not-use-it-either">
<h2><a href="#it-s-not-good-to-use-the-stl-but-it-s-not-good-to-not-use-it-either">It’s not good to use the STL but it’s not good to not use it either</a></h2>
<p>Ori­gin­ally I wanted to up­grade to C++14 as soon as it comes out to make use of
<a class="m-flat" href="https://en.cppreference.com/w/cpp/utility/optional">std::op­tion­al</a>, how­ever that con­tain­er got
<abbr title="come on, optional references are just a pointer, really">over­en­gin­eered bey­ond any reas­on</abbr>
and delayed to C++17; then I con­sidered up­dat­ing to C++17 to get
<a class="m-flat" href="https://en.cppreference.com/w/cpp/string/basic_string_view">std::string_view</a> and <code class="cpp m-code"><span class="n">std</span><span class="o">::</span><span class="n">array_view</span></code>, how­ever string views ended
up im­mut­able and use­less and the ar­ray view got re­named to <a class="m-flat" href="https://en.cppreference.com/w/cpp/container/span">std::span</a> and
delayed to C++20, and <code class="cpp m-code"><span class="n">std</span><span class="o">::</span><span class="n">mdspan</span></code> might fi­nally ar­rive in C++23 if
things go well …</p>
<p>One of the oth­er long-term is­sues was <a class="m-flat" href="https://en.cppreference.com/w/cpp/memory/unique_ptr">std::unique_ptr</a>.
<code class="cpp m-code"><span class="cp">#include</span> <span class="cpf"><memory></span><span class="cp"></span></code> had a <em>meas­ur­able im­pact</em> on com­pile times from the
very be­gin­ning, which is why I hes­it­ated to use it in class in­tern­als, mean­ing
a lot of <a href="https://en.wikipedia.org/wiki/Opaque_pointer">PIMPL</a>’d state was
in­stead man­aged (and routinely leaked) us­ing clas­sic <code class="cpp m-code"><span class="k">new</span></code> / <code class="cpp m-code"><span class="k">delete</span></code>.</p>
<p>Even though it was sug­ges­ted nu­mer­ous times, I frowned upon the thought of
“writ­ing my own STL” or switch­ing to STL­port / EASTL, be­cause do­ing so would
mean ali­en­at­ing com­mon users — they would not only need to learn a new
en­gine, but also write ex­tra code to trans­form their <a class="m-flat" href="https://en.cppreference.com/w/cpp/container/vector">std::vec­tor</a>s and
<a class="m-flat" href="https://en.cppreference.com/w/cpp/string/basic_string">std::string</a>s to some­thing the en­gine used. Only
<a href="https://blog.magnum.graphics/backstage/lightweight-stl-compatible-unique-pointer/">re­l­at­ively re­cently</a>
I real­ized that I can design con­tain­ers that are both STL-in­de­pend­ent and
STL-com­pat­ible, so the en­gine can be­ne­fit from faster com­pile times and ex­tra
flex­ib­il­ity like memory own­er­ship trans­fer, but users can still keep us­ing
<a class="m-flat" href="https://en.cppreference.com/w/cpp/container/vector">std::vec­tor</a>, <a class="m-flat" href="https://en.cppreference.com/w/cpp/memory/unique_ptr">std::unique_ptr</a> and the like, of­ten without even
real­iz­ing those are not the types the en­gine nat­ively works with.</p>
</section>
<section id="a-friendly-community-is-what-keeps-the-project-ticking">
<h2><a href="#a-friendly-community-is-what-keeps-the-project-ticking">A friendly com­mu­nity is what keeps the pro­ject tick­ing</a></h2>
<p>And fi­nally, I can’t un­der­es­tim­ate how much this pro­ject owes the com­munity
on <a href="https://github.com/mosra/magnum">Git­Hub</a>, <a href="https://gitter.im/mosra/magnum">Git­ter</a> and
else­where for its ex­ist­ence. I had the luck that over the years I only had to
deal with a single per­son with of­fens­ive be­ha­vi­or, every­one else is go­ing out
of their way to help each oth­er, provide valu­able feed­back and en­cour­age­ment
and con­trib­ute back massive amounts of great code.</p>
<p>I’m happy to see that every ef­fort to help users do their first steps or
re­solve their is­sues has re­turned back nu­mer­ous times and the com­munity brings
a con­tin­ued whole­some ex­per­i­ence.</p>
<p><strong>Thank you, every­body.</strong> Cheers for the next ten years.</p>
<hr />
<p class="m-text m-small m-dim">Cov­er im­age cre­ated with <a href="https://gource.io">Gource</a>, re­flect­ing the
state from Decem­ber 19th, 2020.</p>
<aside class="m-note m-dim">
Dis­cus­sion: <a href="https://twitter.com/czmosra/status/1341174606288330753">Twit­ter</a>, <a href="https://www.reddit.com/r/cpp/comments/ki6zds/a_decade_of_magnum/">Red­dit r/cpp</a></aside>
</section>
Magnum 2020.06 released2020-07-02T00:00:00+02:002020-07-02T00:00:00+02:00Vladimír Vondruštag:blog.magnum.graphics,2020-07-02:/announcements/2020.06/<p>Re­designed geo­metry pipeline to­geth­er with massive ad­di­tions to
im­port­er plu­gins, new de­bug­ging, visu­al­iz­a­tion and pro­fil­ing tools, new
ex­amples in­clud­ing flu­id sim­u­la­tion and raytra­cing, in­stan­cing in built­in
shaders and a gal­lery of cool pro­jects to get in­spired from.</p>
<p>Big re­works take time to fer­ment prop­erly, which is why this re­lease comes more
than six months after the pre­vi­ous one, <a href="https://blog.magnum.graphics/announcements/2019.10/">2019.10</a>. I
dare to say this is the busiest re­lease of Mag­num yet, with work span­ning
across sev­er­al areas of the pro­ject. Fol­low­ing are re­lease high­lights, for a
de­tailed changelog that’s about 100 times big­ger please see
<a href="#full-changelog">links at the bot­tom</a>.</p>
<section id="new-geometry-pipeline">
<h2><a href="#new-geometry-pipeline">New geo­metry pipeline</a></h2>
<p>As­set im­port and geo­metry pro­cessing is one of the cent­ral parts of Mag­num. For
2020.06 it got re­writ­ten from a rather ba­sic dec­ade-old “toy en­gine”
im­ple­ment­a­tion full of nes­ted <a class="m-flat" href="https://en.cppreference.com/w/cpp/container/vector">std::vec­tor</a>s to an ef­fi­cient and
flex­ible GPU-friendly design that’s ready for new paradigms such as mesh
shaders. I won’t be re­it­er­at­ing everything that went in­to it again in this
an­nounce­ment, please go see the in-depth in­tro­duc­tion art­icle for de­tailed
in­form­a­tion:</p>
<div class="m-row">
<div class="m-col-m-8 m-push-m-2">
<figure class="m-figure m-fullwidth m-info">
<a href="https://blog.magnum.graphics/announcements/new-geometry-pipeline/"><img alt="New Geometry Pipeline in Magnum cover image" src="https://static.magnum.graphics/img/blog/announcements/2020.06/new-geometry-pipeline-cover-small.jpg" /></a>
<figcaption><a href="https://blog.magnum.graphics/announcements/new-geometry-pipeline/">New Geo­metry Pipeline in Mag­num</a> <span class="m-label m-flat m-info">May 27, 2020</span><div class="m-figure-description">
Flex­ible and ef­fi­cient mesh rep­res­ent­a­tion, cus­tom at­trib­utes, new
data types and a ton of new pro­cessing, visu­al­iz­a­tion and ana­lyz­ing
tools. GPU-friendly geo­metry stor­age as it should be in the 21st cen­tury.</div>
</figcaption>
</figure>
</div>
</div>
</section>
<section id="let-s-fork-c-further">
<h2><a href="#let-s-fork-c-further">Let’s fork C++ fur­ther</a></h2>
<p>The move away from <a class="m-flat" href="https://en.cppreference.com/w/cpp/container/vector">std::vec­tor</a>, which is vis­ible es­pe­cially in the
re­designed <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1MeshTools.html">MeshTools</a> namespace, means Mag­num had to provide a
re­place­ment. <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/classCorrade_1_1Containers_1_1Array.html">Con­tain­ers::Ar­ray</a> is around for quite a while — but for
the pur­pose of cer­tain im­port­ers and <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1Primitives.html">Prim­it­ives</a> that can’t eas­ily know
the fi­nal in­dex/ver­tex count be­fore­hand — it had to be ex­ten­ded to sup­port
ar­bit­rary grow­ing, like <a class="m-flat" href="https://en.cppreference.com/w/cpp/container/vector">std::vec­tor</a> has.</p>
<div class="m-col-m-6 m-center-m">
<figure class="m-code-figure">
<pre class="m-code"><span class="n">Containers</span><span class="o">::</span><span class="n">Array</span><span class="o"><</span><span class="n">Color3</span><span class="o">></span> <span class="n">palette</span><span class="p">;</span>
<span class="n">arrayAppend</span><span class="o"><</span><span class="n">MyFancyAllocator</span><span class="o">></span><span class="p">(</span><span class="n">palette</span><span class="p">,</span> <span class="p">{</span>
<span class="mh">0xa5c9ea_rgbf<span class="m-code-color" style="background-color: #a5c9ea;"></span></span><span class="p">,</span>
<span class="mh">0x3bd267_rgbf<span class="m-code-color" style="background-color: #3bd267;"></span></span><span class="p">,</span>
<span class="mh">0xc7cf2f_rgbf<span class="m-code-color" style="background-color: #c7cf2f;"></span></span>
<span class="p">});</span></pre>
<p class="m-text m-small m-noindent">A <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/classCorrade_1_1Containers_1_1Array.html">Con­tain­ers::Ar­ray</a> in­stance isn’t tied to a par­tic­u­lar
al­loc­at­or — and you can also switch to a dif­fer­ent al­loc­at­or at any point later.</p>
</figure>
</div>
<p>The design is rather un­con­ven­tion­al in or­der to avoid the well-known
short­com­ings of <a class="m-flat" href="https://en.cppreference.com/w/cpp/container/vector">std::vec­tor</a>, es­pe­cially when it comes to cus­tom
al­loc­at­ors. The im­ple­ment­a­tion present in 2020.06 is still miss­ing ar­bit­rary
in­ser­tion and de­le­tion which is why it’s not ad­vert­ised in more de­tail yet —
once that’s done, ex­pect a ded­ic­ated art­icle to­geth­er with bench­marks and
com­par­is­on to com­mon im­ple­ment­a­tions. Un­til then, see
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/classCorrade_1_1Containers_1_1Array.html#Containers-Array-growable">Grow­able ar­rays</a> for an in­tro­duc­tion.</p>
<p>One of the last re­main­ing pieces of STL that are <span class="m-text m-s">hold­ing us back</span> not as
light­weight & flex­ible as I’d want them to be are <a class="m-flat" href="https://en.cppreference.com/w/cpp/string/basic_string">std::string</a>s. After
the re­lease cut I de­cided that new APIs are not go­ing to use those any­more,
which means a re­place­ment is un­der­way. Ex­ist­ing APIs will get gradu­ally por­ted
away, sim­il­arly as was done with <a class="m-flat" href="https://en.cppreference.com/w/cpp/container/vector">std::vec­tor</a> or <a class="m-flat" href="https://en.cppreference.com/w/cpp/memory/unique_ptr">std::unique_ptr</a>
in the past. Of course, as al­ways, an <em>opt-in</em> com­pat­ib­il­ity of the new APIs
with <a class="m-flat" href="https://en.cppreference.com/w/cpp/string/basic_string">std::string</a> / <a class="m-flat" href="https://en.cppreference.com/w/cpp/string/basic_string_view">std::string_view</a> will be provided — the goal
is not to ali­en­ate users of stand­ard C++, the goal is to be flex­ible and
provide al­tern­at­ives.</p>
</section>
<section id="math-and-algorithm-goodies">
<h2><a href="#math-and-algorithm-goodies">Math and al­gorithm good­ies</a></h2>
<p>Be­cause the new <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1MeshData.html">Trade::Mesh­Data</a> APIs ex­pan­ded a lot on sup­por­ted ver­tex
formats, the math lib­rary re­ceived batch <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1Math.html#ae2b2b053ec0f02d745aa441a7eca8164">Math::pack­In­to()</a> /
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1Math.html#ad3e46c35e3ae7f25f7770cbf2ff2a3a7">un­pack­In­to()</a> func­tions that al­low for ef­fi­cient
con­ver­sion between float­ing-point and 8-/16-bit packed or half-float types.
When no con­ver­sion is needed, <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/namespaceCorrade_1_1Utility.html#a039691d89f3ce26def0c05999735743e">Util­ity::copy()</a> from a new
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/Algorithms_8h.html">Util­ity/Al­gorithms.h</a>
head­er gives you a <a class="m-flat" href="https://en.cppreference.com/w/cpp/string/byte/memcpy">std::mem­cpy()</a> / <a class="m-flat" href="https://en.cppreference.com/w/cpp/algorithm/copy">std::copy()</a> al­tern­at­ive that
works on mul­tiple di­men­sions and sparse data lay­outs as well.</p>
<div class="m-col-m-8 m-center-m">
<figure class="m-code-figure">
<pre class="m-code"><span class="n">ImageView2D</span> <span class="n">src</span><span class="p">,</span> <span class="n">dst</span><span class="p">;</span>
<span class="n">Utility</span><span class="o">::</span><span class="n">copy</span><span class="p">(</span><span class="n">src</span><span class="p">.</span><span class="n">pixels</span><span class="p">().</span><span class="n">flipped</span><span class="o"><</span><span class="mi">1</span><span class="o">></span><span class="p">(),</span> <span class="n">dst</span><span class="p">.</span><span class="n">pixels</span><span class="p">());</span></pre>
<p class="m-text m-small m-noindent">Mir­ror­ing an im­age — in an oneliner, with
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/classCorrade_1_1Containers_1_1StridedArrayView.html">Con­tain­ers::StridedAr­rayView</a> un­der­neath.</p>
</figure>
</div>
<p>The <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1MeshTools.html">MeshTools</a> lib­rary, apart from be­ing ad­ap­ted for the re­designed
work­flow, now con­tains <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1MeshTools.html#ac093061706a7501ca0e8b224b091c959">MeshTools::con­cat­en­ate()</a> for join­ing mul­tiple
meshes to­geth­er, or for ex­ample <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1MeshTools.html#aa2f023dc60f4330316419b362a0f839f">MeshTools::gen­er­ateIn­dices()</a> for
con­vert­ing strips, loops or fans to plain in­dexed meshes.</p>
</section>
<section id="what-s-new-in-plugins">
<h2><a href="#what-s-new-in-plugins">What’s new in plu­gins</a></h2>
<p>The more Mag­num gets used to im­port a in­creas­ingly broad range of data­sets in
vari­ous formats, the more pre­vi­ously un­handled corner cases get dis­covered and
fixed.</p>
<ul>
<li>The <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1AssimpImporter.html">As­simpIm­port­er</a> now cor­rectly im­ports
multi-prim­it­ive meshes, pre­serves al­pha in ma­ter­i­als and ac­counts for Y up
/ Z up ori­ent­a­tion over­ride, if a file defines it.</li>
<li>All im­port­ers now im­port both base col­or and tex­ture in­stead of either one
or the oth­er. In­ter­est­ingly, this was a lim­it­a­tion that ori­gin­ated from the
COL­LADA format — the early <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1Trade.html">Trade</a> APIs and the now-gone
<code>ColladaImporter</code> plu­gin were mod­elled after it, how­ever when re­view­ing
this design de­cision it turned out that COL­LADA is ac­tu­ally the only format
with such re­stric­tion and every oth­er format (OBJ, glTF, OpenGEX, …)
sup­ports com­bin­ing both.</li>
<li><a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1TinyGltfImporter.html">Tiny­Glt­fIm­port­er</a> failed to im­port
in­ter­leaved meshes. This was a short­cut done to make the early
im­ple­ment­a­tion sim­pler. To my sur­prise, ap­par­ently the vast ma­jor­ity of
glTF mod­els is ex­por­ted de-in­ter­leaved and thus in­ef­fi­cient for the GPU,
which ex­plains why this lim­it­a­tion went largely un­noticed since the
ori­gin­al plu­gin re­lease in 2018.</li>
</ul>
<p>Im­age im­port­ers wer­en’t left be­hind either — formats that sup­port it such as
DDS or Basis Uni­ver­sal now can im­port par­tic­u­lar mip levels us­ing
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1AbstractImporter.html#a9624991f2d46d5da6ec17e1c84d620aa">Trade::Ab­strac­tIm­port­er::im­age2D­Level­Count()</a> and the second para­met­er of
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1AbstractImporter.html#ae37dc5b3bc2bc1d5d4c7d020b7358f52">Trade::Ab­strac­tIm­port­er::im­age2D()</a>, with this be­ing prox­ied in­to all
scene im­port­ers as well. Vaguely re­lated to this, <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1DevIlImageImporter.html">Dev­Il­Im­ageIm­port­er</a>
and <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1StbImageImporter.html">StbIm­ageIm­port­er</a> can now im­port frames of
an­im­ated GIFs for a very crude <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/examples-animated-gif.html">video play­back</a>.</p>
<div class="m-right-m m-col-m-6 m-container-inflate">
<figure class="m-figure">
<img src="https://static.magnum.graphics/img/blog/announcements/2020.06/primitives.png" />
Suited mainly for test­ing pur­poses, the whole <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1Primitives.html">Prim­it­ives</a> lib­rary
is now ex­posed through a <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1PrimitiveImporter.html">Prim­it­i­veIm­port­er</a>
plu­gin. This can be used for ex­ample to ex­pose built­in prim­it­ives to
ex­ist­ing <code>Importer</code>-based pipelines without hav­ing to add new code
path for each.</figure>
</div>
<p>As men­tioned in the <a href="https://blog.magnum.graphics/announcements/new-geometry-pipeline/">New Geo­metry Pipeline in Mag­num</a>
art­icle already, there’s sev­er­al ad­di­tions and im­prove­ments that go to­geth­er
with the new <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1MeshData.html">Trade::Mesh­Data</a> APIs:</p>
<ul>
<li><a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1AssimpImporter.html">As­simpIm­port­er</a> and
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1TinyGltfImporter.html">Tiny­Glt­fIm­port­er</a> now im­port tan­gents /
bit­an­gents as well, with sup­port for skin­ning at­trib­utes get­ting ready in
<a href="https://github.com/mosra/magnum/issues/441">mosra/mag­num#441</a></li>
<li><a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1StanfordImporter.html">Stan­fordIm­port­er</a> for the PLY file format
got ex­ten­ded to sup­port nor­mals, tex­ture co­ordin­ates, ver­tex col­ors and
per-face at­trib­utes, be­ing also highly op­tim­ized for near-in­stant im­port
times</li>
<li><a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1TinyGltfImporter.html">Tiny­Glt­fIm­port­er</a> and
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1StanfordImporter.html">Stan­fordIm­port­er</a> now re­cog­nize a
per-ver­tex Ob­ject ID at­trib­ute, used in vari­ous data­sets for se­mant­ic
an­nota­tions. Be­cause it isn’t stand­ard­ized in any way and each data­set
might use it dif­fer­ently, the <code class="m-code"><span class="na">objectIdAttribute</span></code>
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1TinyGltfImporter.html#Trade-TinyGltfImporter-configuration">plu­gin-spe­cif­ic con­fig­ur­a­tion op­tion</a>
can be used to re­cog­nize it un­der a dif­fer­ent name.</li>
<li><a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1TinyGltfImporter.html">Tiny­Glt­fIm­port­er</a> re­ceived sup­port for
sev­er­al new ex­ten­sions in­clud­ing <a href="https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual">KHR_­light­s_­punc­tu­al</a>,
<a href="https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_transform">KHR_­tex­ture_trans­form</a>
and <a href="https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_mesh_quantization/README.md">KHR_mesh_quant­iz­a­tion</a></li>
</ul>
<p>Fi­nally, there is a new scene con­vert­er plu­gin in­ter­face, with
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1MeshOptimizerSceneConverter.html">Mesh­Op­tim­izer­SceneCon­vert­er</a> and
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1StanfordSceneConverter.html">Stan­ford­SceneCon­vert­er</a> be­ing the first
two plu­gins im­ple­ment­ing it. Apart from that, im­age im­port­ers now have a simple
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1IcoImporter.html">IcoIm­port­er</a> for Win­dows <code>*.ico</code> files and there’s
(also a very trivi­al) <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1StlImporter.html">StlImport­er</a> for bin­ary STL
files, com­monly used in 3D print­ing.</p>
<p>All im­port­er and con­vert­er plu­gins re­ceived a flag to en­able verb­ose out­put,
which is also ex­posed as a <code>--verbose</code> op­tion in the
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/magnum-imageconverter.html">mag­num-im­age­con­vert­er</a>,
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/magnum-sceneconverter.html">mag­num-scenecon­vert­er</a> and
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/magnum-player.html">mag­num-play­er</a> util­it­ies. The plu­gins use that to no­ti­fy
you about longer-run­ning op­er­a­tions or pro­cessing stats. It’s prob­ably most
help­ful in case of As­simp, which likes to crash or mis­be­have on cer­tain files.</p>
</section>
<section id="builtin-shader-improvements-new-visualization-tools">
<h2><a href="#builtin-shader-improvements-new-visualization-tools">Built­in shader im­prove­ments, new visu­al­iz­a­tion tools</a></h2>
<p>Per­haps the most sig­ni­fic­ant shader ad­di­tion is in­stan­cing sup­port in
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1Shaders.html#aa8a252d128449a517288dc461d21348e">Shaders::Phong</a> and <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1Shaders.html#a1a71a307d5c94f22c80ad5f8e6515b05">Shaders::Flat</a> — while in­stan­cing alone was
sup­por­ted in the <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1GL.html">GL</a> lib­rary since 2014, the built­in shaders didn’t
im­ple­ment this func­tion­al­ity un­til now. For show­case, the <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/examples-bullet.html">Bul­let Phys­ics</a>
and <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/examples-box2d.html">Box2D</a> ex­amples are now re­im­ple­men­ted us­ing in­stan­cing, each
of them us­ing just a single draw call for the whole scene. Try them out on­line:</p>
<div class="m-row m-container-inflate">
<div class="m-col-m-6">
<figure class="m-figure m-fullwidth">
<a href="https://magnum.graphics/showcase/box2d/"><img alt="Box2D example screenshot" src="https://static.magnum.graphics/showcase/box2d/screenshot.png" /></a>
<figcaption><a href="https://magnum.graphics/showcase/box2d/">Box2D Ex­ample</a>
<span class="m-label m-flat m-success">webgl1</span> <span class="m-label m-warning">phys­ics</span>
<span class="m-label m-primary">in­stan­cing</span><div class="m-figure-description">
Builds a pyr­am­id out of cubes and al­lows you to des­troy it after.</div>
</figcaption>
</figure>
</div>
<div class="m-col-m-6">
<figure class="m-figure m-fullwidth">
<a href="https://magnum.graphics/showcase/bullet/"><img alt="Bullet Physics example screenshot" src="https://static.magnum.graphics/showcase/bullet/screenshot.png" /></a>
<figcaption><a href="https://magnum.graphics/showcase/bullet/">Bul­let Phys­ics Ex­ample</a>
<span class="m-label m-flat m-success">webgl1</span> <span class="m-label m-warning">phys­ics</span>
<span class="m-label m-primary">in­stan­cing</span><div class="m-figure-description">
Shows a ro­tat­ing table full of cubes that you can shoot down.</div>
</figcaption>
</figure>
</div>
</div>
<p>Com­ple­ment­ing the glTF <a href="https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_transform">KHR_­tex­ture_trans­form</a> ex­ten­sion sup­port, there’s now
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Shaders_1_1PhongGL.html#aeb772a18ed5e8e2ecdb9bde4ece67d3d">Shaders::Phong::set­Tex­tureMat­rix()</a>
to­geth­er with abil­ity to have in­stanced tex­ture off­set, and the same in
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1Shaders.html#a1a71a307d5c94f22c80ad5f8e6515b05">Shaders::Flat</a>.</p>
<p>With tan­gent and nor­mal map im­port be­ing done, <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1Shaders.html#aa8a252d128449a517288dc461d21348e">Shaders::Phong</a> nor­mal
map sup­port ad­ded in 2019.10 can fi­nally be fully util­ized. This is closely
tied with <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1Shaders.html#a1e9971bdd252b39180d0381aee1e06b3">Shaders::MeshVisu­al­izer­3D</a> now be­ing able to visu­al­ize not just
wire­frame but also tan­gent, bit­an­gent and nor­mal dir­ec­tion — very use­ful for
de­bug­ging those dreaded light­ing is­sues.</p>
<img class="m-image" src="https://static.magnum.graphics/img/blog/announcements/2020.06/meshvisualizer3d.png" style="width: 256px" />
<p><a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1Shaders.html#aa8a252d128449a517288dc461d21348e">Shaders::Phong</a> / <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1Shaders.html#a1a71a307d5c94f22c80ad5f8e6515b05">Shaders::Flat</a> can now out­put also per-ver­tex
Ob­ject ID at­trib­ute, which means <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1Shaders.html#a1e9971bdd252b39180d0381aee1e06b3">Shaders::MeshVisu­al­izer­3D</a> can visu­al­ize
that one as well, to­geth­er with ver­tex and prim­it­ive ID. This goes hand-in-hand
with a new <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1DebugTools_1_1ColorMap.html">De­bug­Tools::ColorMap</a> namespace that in­cludes also the
<a href="https://twitter.com/antovsky/status/1265711996718080001">very re­cog­niz­able Turbo colormap</a>
by Ant­on Mikhail­ov.</p>
</section>
<section id="certain-gl-drivers-continue-to-be-a-hot-mess">
<h2><a href="#certain-gl-drivers-continue-to-be-a-hot-mess">Cer­tain GL drivers con­tin­ue to be a hot mess</a></h2>
<p>Even if all oth­er new fea­tures shown here wouldn’t be a con­vin­cing reas­on for
you to up­grade, you’ll def­in­itely want to pick up these three work­arounds for
bet­ter driver com­pat­ib­il­ity:</p>
<div class="m-col-m-4 m-right-m m-container-inflate">
<aside class="m-note m-danger">
<h3>Beware: Intel drivers</h3>
<p>Un­like most work­arounds where the en­gine fully shields you from driver
bugs, broken <code class="m-code"><span class="k">layout</span><span class="p">(</span><span class="n">location</span><span class="o">=</span><span class="n">N</span><span class="p">)</span></code> on In­tel Win­dows drivers
af­fects you as well — think twice be­fore us­ing ex­pli­cit uni­form loc­a­tions if you need to sup­port In­tel GPUs on Win­dows. Even though it
makes the shader code sim­pler, the frus­trat­ing de­bug­ging ex­per­i­ence
af­ter­wards is not worth it.</p>
</aside>
</div>
<ul>
<li>A hard-to-re­pro­duce <abbr title="or I assume so">syn­chron­iz­a­tion</abbr> bug on In­tel
Win­dows drivers makes the <a href="https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_direct_state_access.txt">AR­B_­dir­ec­t_state_ac­cess</a> ex­ten­sion
ba­sic­ally un­us­able for any­thing re­lated to buf­fers or VAOs. Usu­ally
mani­fes­ted as flick­er­ing in ImGui-based apps. A sub­set of this work­around
was done for 2019.10 already but due to its semi-ran­dom nature it didn’t
cov­er all cases. This work­around aban­donds all hope and com­pletely dis­ables
DSA for af­fected code paths on these drivers.</li>
<li>In­tel Win­dows drivers don’t really re­spect ex­pli­cit uni­form loc­a­tions but
in­stead only take it as a very vague sug­ges­tion. This bug was most
cer­tainly <a href="https://twitter.com/czmosra/status/1106955396219105285">also present since forever</a>,
but only be­came vis­ible after the latest ad­di­tions of tex­ture trans­form,
nor­mal maps and in­stan­cing to built­in shaders, which caused the uni­form
loc­a­tions to be any­thing but a con­tigu­ous in­creas­ing se­quence. Since
there’s no ap­par­ent rhyme or reas­on in which the drivers al­loc­ate uni­form
IDs, solu­tion was to dis­able the <a href="https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_explicit_uniform_location.txt">AR­B_­ex­pli­cit_uni­form_­loca­tion</a> on
In­tel Win­dows drivers al­to­geth­er.</li>
<li>It’s hard to find bugs in drivers that are cap­able of very little, but even
then — while this bug was prob­ably present ever since Apple re­wrote their
(de­prec­ated) GL 4.1 driver on top of Met­al, it got in­de­pend­ently dis­covered
by two dif­fer­ent users only re­cently. When <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1GL_1_1BufferTexture.html">GL::Buf­fer­Tex­ture</a> is
bound, it causes all buf­fer modi­fic­a­tions to crash due to what I as­sume is
cor­rup­tion of in­tern­al driver state. The work­around avoids the crash by
un­bind­ing the tex­ture when up­dat­ing or map­ping a <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1GL_1_1Buffer.html">GL::Buf­fer</a>.</li>
</ul>
<p>For your amuse­ment, the list of all cur­rent OpenGL driver work­arounds is
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/opengl-workarounds.html">in the doc­u­ment­a­tion</a>.</p>
</section>
<section id="but-there-are-also-some-non-negative-gl-news-even">
<h2><a href="#but-there-are-also-some-non-negative-gl-news-even">But there are also some non-neg­at­ive GL news, even</a></h2>
<div class="m-col-m-5 m-left-m m-container-inflate">
<figure class="m-console-figure">
<pre class="m-console"><span class="g g-AnsiBrightDefault">Last</span><span class="g g-AnsiBrightCyan"> 50</span><span class="g g-AnsiBrightDefault"> frames:</span>
<span class="g g-AnsiBrightDefault"> Frame time:</span><span class="g g-AnsiBrightGreen"> 16.65</span> ms
<span class="g g-AnsiBrightDefault"> CPU duration:</span><span class="g g-AnsiBrightGreen"> 14.72</span> ms
<span class="g g-AnsiBrightDefault"> GPU duration:</span><span class="g g-AnsiBrightGreen"> 10.89</span> ms
<span class="g g-AnsiBrightDefault"> Vertex fetch ratio:</span><span class="g g-AnsiBrightGreen"> 0.24</span>
<span class="g g-AnsiBrightDefault"> Primitives clipped:</span><span class="g g-AnsiBrightGreen"> 59.67</span> %</pre>
<p class="m-text m-small m-noindent">An ex­ample where pipeline stat­ist­ic quer­ies can be use­ful. Out­put from
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1DebugTools_1_1FrameProfiler.html">De­bug­Tools::FramePro­filer</a>.</p>
</figure>
</div>
<p>Work­ing on the geo­metry pipeline and large data­sets re­quired me to do vari­ous
meas­ure­ments, which led to a new <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1GL_1_1PipelineStatisticsQuery.html">GL::Pipelin­eS­tat­ist­ic­sQuery</a> class. It
ex­poses the <a href="https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_pipeline_statistics_query.txt">AR­B_pipeline_s­tat­ist­ic­s_query</a> ex­ten­sion which doesn’t
provide <em>really</em> much, but it’s at least some­thing — and sadly the only
non-pro­pri­et­ary way to get <em>any</em> stats on NVidia drivers. A new
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1DebugTools_1_1FrameProfiler.html">De­bug­Tools::FramePro­filer</a> class uses those quer­ies to give you a
easy-to-in­teg­rate per-frame pro­fil­ing.</p>
<p>Apart from that, vari­ous tiny bits and pieces such as
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1GL_1_1Renderer.html#aac87c961eb53921e17d66bbf146828aead2634099fd47d8495441e48d59e4e296">clip­ping plane sup­port</a> were
ad­ded, mostly on-de­mand based on needs of vari­ous users. See the changelog
links at the bot­tom for the full list.</p>
<p>One quite minor but widely ap­pre­ci­ated change was turn­ing <code class="m-code"><span class="n">mesh</span><span class="p">.</span><span class="n">draw</span><span class="p">(</span><span class="n">shader</span><span class="p">)</span></code>
in­to <code class="m-code"><span class="n">shader</span><span class="p">.</span><span class="n">draw</span><span class="p">(</span><span class="n">mesh</span><span class="p">)</span></code>. The ori­gin­al was a res­ult of how the API evolved
over the years, which is why I was blind to its coun­ter­in­tu­it­ive­ness un­til it
was fi­nally poin­ted out to me. A good takeaway from that is — if you see
any­thing in Mag­num APIs that feels strange or un­ne­ces­sar­ily com­plic­ated, please
com­plain, no mat­ter how “noob” or in­ex­per­i­enced you might feel. Feed­back like
this mat­ters a lot, and if I nev­er hear it, I might nev­er dis­cov­er the prob­lem.</p>
<p>One in­ter­est­ing pro­ject that is mak­ing great pro­gress re­cently is Mesa’s Zink
OpenGL-over-Vulkan driver. Stable Mesa 20.1 doesn’t have it en­abled by de­fault
yet and there it’s just at GL 2.1, but latest com­mits already
<a href="http://www.supergoodcode.com/opengl-3.1/">bring it up to 3.1 sup­port</a>. After
fix­ing some bad as­sump­tions in con­text cre­ation routines in or­der to make pure
GL 2.1 con­texts work again, Mag­num can now work with Zink as well.</p>
<aside class="m-note m-success">
If you want to try Zink, you have to com­pile Mesa from sources. If you are
on Arch­Linux, easi­est is to take the AUR
<a href="https://aur.archlinux.org/packages/mesa-git/">mesa-git</a> pack­age and
add <code>zink</code> to the <code>-D gallium-drivers</code> list. Al­tern­at­ively, if you
don’t want to play with fire <em>that much</em>, modi­fy the
<a href="https://git.archlinux.org/svntogit/packages.git/tree/trunk?h=packages/mesa">stable pack­age</a>
in­stead.</aside>
</section>
<section id="application-improvements">
<h2><a href="#application-improvements">Ap­plic­a­tion im­prove­ments</a></h2>
<p>Thanks to a joint ef­fort from sev­er­al con­trib­ut­ors,
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Platform_1_1GlfwApplication.html">Plat­form::GlfwAp­plic­a­tion</a> and <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Platform_1_1Sdl2Application.html">Plat­form::Sdl2Ap­plic­a­tion</a> now
sup­port curs­or man­age­ment, which is also used by the <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1ImGuiIntegration.html">ImGui­In­teg­ra­tion</a>
lib­rary. Both ap­plic­a­tions can now also set win­dow icon, and if you are on
Win­dows, you can use it to­geth­er with the new <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1IcoImporter.html">IcoIm­port­er</a>
to use one file to set an <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/platforms-windows.html#platform-windows-icon">ex­ecut­able icon</a> and a
win­dow icon as well, op­tion­ally also provid­ing sev­er­al res­ol­u­tions to let the
OS choose from.</p>
<p>The <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1ImGuiIntegration.html">ImGui­In­teg­ra­tion</a> lib­rary was switched to use built­in
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1Shaders.html#ad301c9a16ade2d8169aab9ea2c7c39fb">Shaders::Flat2D</a> in­stead of a cus­tom shader, which re­moved quite some
code <em>and</em> made it work on WebGL 1 as well. This was pos­sible thanks to built­in
shaders re­ceiv­ing ver­tex col­or sup­port in the 2019.10 re­lease.</p>
<p>Win­dow­less apps, which are com­monly used for data pro­cessing or test­ing, got
ex­ten­ded to sup­port con­text shar­ing. The <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Platform_1_1WindowlessEglApplication.html">Plat­form::Win­dow­lessE­glAp­plic­a­tion</a>
sup­ports EGL device se­lec­tion through the <code>--magnum-device</code> op­tion since
2019.10 and now it sup­ports also <code>--magnum-cuda-device</code> for fil­ter­ing only
CUDA devices, if you’re run­ning on a ma­chine with NVidia GPUs.</p>
<p>If you don’t use the built­in ap­plic­a­tion wrap­pers, there’s a new
<a href="https://github.com/mosra/magnum-bootstrap/tree/base-gtkmm">base-gtkmm</a> boot­strap pro­ject to get
you star­ted us­ing GTKmm, join­ing <a href="https://github.com/mosra/magnum-bootstrap/tree/base-wxwidgets">base-wx­wid­gets</a>
ad­ded in the pre­vi­ous re­lease, and with a QtQuick boot­strap be­ing worked on
for the next.</p>
</section>
<section id="a-gigaton-of-cool-new-examples">
<h2><a href="#a-gigaton-of-cool-new-examples">A gigaton of cool new ex­amples</a></h2>
<p>The <em>Con­trib­ut­or of the Year</em> award goes to <a href="https://github.com/ttnghia">Nghia Truong</a> —
sub­mit­ting five ex­tremely in­ter­est­ing ex­amples, each im­ple­ment­ing a dif­fer­ent
al­gorithm com­pletely from scratch, with many more good­ies prom­ised. All of them
are now avail­able as WebGL demos, feel free to try them out:</p>
<div class="m-row m-container-inflate">
<div class="m-col-m-6">
<figure class="m-figure m-fullwidth">
<a href="https://magnum.graphics/showcase/fluidsimulation2d/"><img alt="2D Fluid Simulation example screenshot" src="https://static.magnum.graphics/showcase/fluidsimulation2d/screenshot.png" /></a>
<figcaption><a href="https://magnum.graphics/showcase/fluidsimulation2d/">2D Flu­id Sim­u­la­tion Ex­ample</a>
<span class="m-label m-success">webgl2</span> <span class="m-label m-warning">phys­ics</span> <span class="m-label m-primary">ui</span><div class="m-figure-description">
2D flu­id sim­u­la­tion us­ing the APIC meth­od.</div>
</figcaption>
</figure>
</div>
<div class="m-col-m-6">
<figure class="m-figure m-fullwidth">
<a href="https://magnum.graphics/showcase/fluidsimulation3d/"><img alt="3D Fluid Simulation example screenshot" src="https://static.magnum.graphics/showcase/fluidsimulation3d/screenshot.png" /></a>
<figcaption><a href="https://magnum.graphics/showcase/fluidsimulation3d/">3D Flu­id Sim­u­la­tion Ex­ample</a>
<span class="m-label m-success">webgl2</span> <span class="m-label m-warning">phys­ics</span> <span class="m-label m-primary">ui</span><div class="m-figure-description">
SPH flu­id sim­u­la­tion with a dy­nam­ic bound­ary.</div>
</figcaption>
</figure>
</div>
<div class="m-clearfix-m">
</div>
<div class="m-col-m-6">
<figure class="m-figure m-fullwidth">
<a href="https://magnum.graphics/showcase/raytracing/"><img alt="Ray Tracing example screenshot" src="https://static.magnum.graphics/showcase/raytracing/screenshot.png" /></a>
<figcaption><a href="https://magnum.graphics/showcase/raytracing/">Ray Tra­cing</a>
<span class="m-label m-flat m-success">webgl1</span><div class="m-figure-description">
Simple it­er­at­ive CPU ray tra­cing.</div>
</figcaption>
</figure>
</div>
<div class="m-col-m-6">
<figure class="m-figure m-fullwidth">
<a href="https://magnum.graphics/showcase/octree/"><img alt="Octree example screenshot" src="https://static.magnum.graphics/showcase/octree/screenshot.png" /></a>
<figcaption><a href="https://magnum.graphics/showcase/octree/">Octree</a>
<span class="m-label m-flat m-success">webgl1</span> <span class="m-label m-warning">phys­ics</span>
<span class="m-label m-primary">in­stan­cing</span><div class="m-figure-description">
Loose octree for ac­cel­er­at­ing col­li­sion de­tec­tion</div>
</figcaption>
</figure>
</div>
<div class="m-clearfix-m">
</div>
<div class="m-col-m-6">
<figure class="m-figure m-fullwidth">
<a href="https://magnum.graphics/showcase/arcball/"><img alt="Arc Ball example screenshot" src="https://static.magnum.graphics/showcase/arcball/screenshot.png" /></a>
<figcaption><a href="https://magnum.graphics/showcase/arcball/">Ar­cBall Cam­era Ex­ample</a>
<span class="m-label m-success">webgl2</span><div class="m-figure-description">
Im­ple­ment­a­tion of <a href="https://www.talisman.org/~erlkonig/misc/shoemake92-arcball.pdf">Ken Shoe­make’s ar­cball cam­era</a>
with smooth nav­ig­a­tion fea­ture.</div>
</figcaption>
</figure>
</div>
<div class="m-col-m-6">
<figure class="m-figure m-fullwidth">
<a href="https://magnum.graphics/showcase/webxr/"><img alt="WebXR example screenshot" src="https://static.magnum.graphics/showcase/webxr/screenshot.png" /></a>
<figcaption><a href="https://magnum.graphics/showcase/webxr/">WebXR Ex­ample</a>
<span class="m-label m-flat m-success">webgl1</span> <span class="m-label m-primary">webxr</span><div class="m-figure-description">
A ba­sic demon­stra­tion of how to use the
<a href="https://github.com/VhiteRabbit/emscripten-webxr">Em­scripten WebXR lib­rary</a>
with Mag­num.</div>
</figcaption>
</figure>
</div>
</div>
<p>Apart from these, the ori­gin­al Web­VR API got ob­sol­eted by WebXR, and so fol­lows
our ex­ample, linked above — cur­rently you can try it out in Chrome, and it
also works in the An­droid browser.</p>
</section>
<section id="buildsystem-improvements">
<h2><a href="#buildsystem-improvements">Build­sys­tem im­prove­ments</a></h2>
<p><a href="https://docs.microsoft.com/en-us/cpp/build/clang-support-msbuild?view=vs-2019">Clang-CL</a>
is now an of­fi­cially sup­por­ted and tested com­piler, in case you want to build
for Win­dows but <span class="m-text m-s">hate both</span> neither MinGW nor MS­VC suits your needs. Some
work was done for MinGW Clang sup­port but
<a href="https://github.com/mosra/magnum/issues/439">ser­i­ous un­tackled is­sues</a> still re­main, so GCC is still
the only sup­por­ted com­piler un­der MinGW.</p>
<p>Be­cause de­pend­ency man­age­ment is hard un­less you have a sys­tem-wide pack­age
man­ager or <a href="https://github.com/microsoft/vcpkg">Vcp­kg</a> do­ing the work for you, cer­tain
de­pend­en­cies such as Open­AL, Basis Uni­ver­sal, mesh­op­tim­izer or ImGui can now
be bundled as CMake sub­pro­jects. In ad­di­tion, vari­ous fixes were done in CMake
Find mod­ules for stat­ic­ally-linked de­pend­en­cies that are com­monly used when
dis­trib­ut­ing pro­ject bin­ar­ies.</p>
<p>To bet­ter track ver­sions of your de­pend­en­cies, all Mag­num pro­jects now con­tain
a <code>version.h</code> head­er con­tain­ing the ex­act com­mit the lib­rary was built from.</p>
<p>Built­in CMake An­droid sup­port, which got broken with the in­tro­duc­tion of NDK
r19, is work­ing with CMake 3.16+ again, only with
<abbr title="it's Android, don't expect things to "just work"">min­im­al work­arounds</abbr>.
Build­ing doc­u­ment­a­tion and the <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/platforms-android.html">An­droid troubleshoot­ing guide</a>
were up­dated to re­flect this fact.</p>
</section>
<section id="new-and-improved-tools">
<h2><a href="#new-and-improved-tools">New and im­proved tools</a></h2>
<p>Go­ing with the new <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1AbstractSceneConverter.html">Trade::Ab­stractS­ceneCon­vert­er</a> plu­gin in­ter­face,
there’s a <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/magnum-sceneconverter.html">mag­num-scenecon­vert­er</a> util­ity as well.
Cur­rently the only sup­por­ted out­put is PLY (through <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1StanfordSceneConverter.html">Stan­ford­SceneCon­vert­er</a>)
and the amount of op­er­a­tions is lim­ited, but this area is go­ing to ex­pand over
time, like it did for im­age con­ver­sion plu­gins. In ad­di­tion the tool also
ex­poses vari­ous <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1MeshTools.html">MeshTools</a> al­gorithms such as du­plic­ate re­mov­al or
at­trib­ute fil­ter­ing.</p>
<p><a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/magnum-imageconverter.html">mag­num-im­age­con­vert­er</a> can now
con­sume and pro­duce raw pixel data of a spe­cified format, which is use­ful when
deal­ing with low-level pipelines that don’t un­der­stand high-level im­age
con­tain­er formats. For data dia­gnost­ic and de­bug­ging, both
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/magnum-imageconverter.html">mag­num-im­age­con­vert­er</a> and
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/magnum-sceneconverter.html">mag­num-scenecon­vert­er</a> learned a new <code>--info</code>
op­tion that prints in­form­a­tion about file con­tents, pixel / ver­tex formats and
data lay­out.</p>
<p>The <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/magnum-player.html">mag­num-play­er</a> util­ity is heav­ily used for in­tern­al
test­ing and thus ab­sorbed ba­sic­ally all new en­gine ad­di­tions — it can dis­play
nor­mal maps, ex­poses all visu­al­iz­a­tion fea­tures of <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1Shaders.html#a1e9971bdd252b39180d0381aee1e06b3">Shaders::MeshVisu­al­izer­3D</a> and you can run <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1DebugTools_1_1FrameProfiler.html">De­bug­Tools::FramePro­filer</a>
with the <span class="m-label m-default">P</span> key. Only the nat­ive app has the new ad­di­tions
right now, the web ver­sion wasn’t up­dated yet.</p>
</section>
<section id="magnum-project-spotlight">
<h2><a href="#magnum-project-spotlight">Mag­num Pro­ject Spot­light</a></h2>
<p>To give you an idea what Mag­num is used for, here’s a se­lec­tion of cur­rently
act­ive or re­cently pub­lished Mag­num-based pro­jects, with many more get­ting
ready to ap­pear next. Presen­ted in no par­tic­u­lar or­der:</p>
<div class="m-row">
<div class="m-col-t-6 m-push-t-6 m-col-m-4 m-push-m-6">
<div class="m-image">
<a href="https://ternarii.com"><img src="https://static.magnum.graphics/img/blog/announcements/2020.06/ternarii.png" style="height: 20em" /></a>
</div>
</div>
<div class="m-col-t-6 m-pull-t-6 m-col-m-4 m-pull-m-2">
<p class="m-nopadb">
<br />
</p>
<h3><a href="https://ternarii.com" class="m-flat">Ternarii</a></h3><p>An <em>ad­dict­ive</em> and col­or­ful Tet­ris-in­spired puzzle game, run­ning in a
browser. My highest score so far is 63k, beat me!</p>
<ul>
<li><strong>Au­thor:</strong> <a href="https://github.com/fgoujeon">Flori­an Goujeon</a></li>
<li><strong>Li­cense:</strong> <span class="m-label m-flat m-primary">GPL</span></li>
<li><strong>Git­Hub:</strong> <a href="https://github.com/fgoujeon/ternarii">fgoujeon/tern­arii</a></li>
</ul>
<div class="m-button m-success">
<a href="https://ternarii.com"><div class="m-big">
Play now!</div>
<div class="m-small">
tern­arii.com</div>
</a>
</div>
</div>
</div>
<div class="m-row">
<div class="m-col-m-6">
<div class="m-image">
<a href="https://github.com/Melix19/oberon"><img src="https://static.magnum.graphics/img/blog/announcements/2020.06/oberon.png" /></a>
</div>
</div>
<div class="m-col-m-6">
<p class="m-show-m m-nopadb">
<br />
</p>
<h3><a href="https://github.com/Melix19/oberon" class="m-flat">Oberon</a></h3><p>A work-in-pro­gress light­weight game en­gine with an em­phas­is on
us­ab­il­ity and per­form­ance.</p>
<ul>
<li><strong>Au­thor:</strong> <a href="https://github.com/Melix19">Marco Melorio</a></li>
<li><strong>Li­cense:</strong> <span class="m-label m-flat m-success">MIT</span></li>
<li><strong>Git­Hub:</strong> <a href="https://github.com/Melix19/oberon">Melix19/ober­on</a></li>
</ul>
</div>
</div>
<div class="m-row">
<div class="m-col-m-6 m-push-m-6">
<div class="m-image">
<a href="https://render.otoy.com/forum/viewtopic.php?f=7&t=73278"><img src="https://static.magnum.graphics/img/blog/announcements/2020.06/sculptron.png" /></a>
</div>
</div>
<div class="m-col-m-6 m-pull-m-6">
<p class="m-show-m m-nopadb">
<br />
</p>
<h3><a href="https://render.otoy.com/forum/viewtopic.php?f=7&t=73278" class="m-flat">Sculptron</a></h3><p>GPU-based sculpt/an­im­ate ap­plic­a­tion. Cur­rently in al­pha.</p>
<ul>
<li><strong>Au­thor:</strong> <a href="https://otoy.com">OTOY</a></li>
<li><strong>Li­cense:</strong> <span class="m-label m-flat m-default">Com­mer­cial</span></li>
<li><strong>Pre­view video:</strong> <a class="m-link-wrap" href="https://youtu.be/aEcKpEvTVcc">https://youtu.be/aEcKpEvTVcc</a></li>
<li><a href="https://render.otoy.com/forum/viewtopic.php?f=7&t=73278">For­um link</a></li>
</ul>
</div>
</div>
<div class="m-row">
<div class="m-col-m-6">
<div class="m-image">
<a href="https://twitter.com/vhiterabbitvr"><img src="https://static.magnum.graphics/img/blog/announcements/2020.06/cant-say-the-name.png" /></a>
</div>
</div>
<div class="m-col-m-6">
<p class="m-show-m m-nopadb">
<br />
</p>
<h3><a href="https://twitter.com/vhiterabbitvr" class="m-flat">[redacted]</a></h3><p>Vhite Rab­bit’s yet-un­re­leased Web-fo­cused Game En­gine.</p>
<ul>
<li><strong>Au­thor:</strong> <a href="https://vhiterabbit.com/">Vhite Rab­bit</a></li>
<li><strong>Li­cense:</strong> <span class="m-label m-flat m-dim">Un­re­leased</span></li>
<li><strong>Twit­ter:</strong> <a href="https://twitter.com/vhiterabbitvr">@vhit­erab­bitvr</a></li>
</ul>
</div>
</div>
<div class="m-row">
<div class="m-col-m-6 m-push-m-6">
<div class="m-image">
<a href="https://github.com/JacksonCampolattaro/n_body"><img src="https://static.magnum.graphics/img/blog/announcements/2020.06/n-body.png" /></a>
</div>
</div>
<div class="m-col-m-6 m-pull-m-6">
<p class="m-show-m m-nopadb">
<br />
</p>
<h3><a href="https://github.com/JacksonCampolattaro/n_body" class="m-flat">MINI</a></h3><p>An N-body grav­ity sim­u­lat­or, cur­rently in pro­cess of be­ing por­ted to
Mag­num. <span class="m-text m-dim">This an­nounce­ment cov­er im­age is a still frame from one of the sim­u­la­tions.</span></p>
<ul>
<li><strong>Au­thor:</strong> <a href="https://github.com/JacksonCampolattaro">Jack­son Cam­po­lat­taro</a></li>
<li><strong>Li­cense:</strong> <span class="m-label m-flat m-primary">GPL</span></li>
<li><strong>Git­Hub:</strong> <a href="https://github.com/JacksonCampolattaro/n_body">Jack­son­Cam­po­lat­taro/n_body</a></li>
</ul>
</div>
</div>
<div class="m-row">
<div class="m-col-m-6">
<div class="m-image">
<a href="https://ttnghia.github.io/posts/quadratic-approximation-of-cubic-curves/"><img src="https://static.magnum.graphics/img/blog/announcements/2020.06/cubic.png" /></a>
</div>
</div>
<div class="m-col-m-6">
<h3><a href="https://ttnghia.github.io/posts/quadratic-approximation-of-cubic-curves/" class="m-flat">Quadratic Approximation of Cubic Curves</a></h3><p>A simple de­gree re­duc­tion tech­nique for con­vert­ing piece­wise cu­bic
splines in­to piece­wise quad­rat­ic splines that main­tain para­met­er­iz­a­tion
and <svg class="m-math" style="width: 1.403em; height: 0.987em; vertical-align: -0.000em;" viewBox="0 -9.474739 13.467795 9.474739">
<title>
C^1
</title>
<defs>
<path id='eq1-g1-49' d='M2.502615-5.076961C2.502615-5.292154 2.486675-5.300125 2.271482-5.300125C1.944707-4.98132 1.522291-4.790037 .765131-4.790037V-4.527024C.980324-4.527024 1.41071-4.527024 1.872976-4.742217V-.653549C1.872976-.358655 1.849066-.263014 1.091905-.263014H.812951V0C1.139726-.02391 1.825156-.02391 2.183811-.02391S3.235866-.02391 3.56264 0V-.263014H3.283686C2.526526-.263014 2.502615-.358655 2.502615-.653549V-5.076961Z'/>
<path id='eq1-g0-67' d='M8.930511-8.308842C8.930511-8.416438 8.846824-8.416438 8.822914-8.416438S8.751183-8.416438 8.655542-8.296887L7.830635-7.292653C7.412204-8.009963 6.75467-8.416438 5.858032-8.416438C3.275716-8.416438 .597758-5.798257 .597758-2.988792C.597758-.992279 1.996513 .251059 3.741968 .251059C4.698381 .251059 5.535243-.155417 6.228643-.74122C7.268742-1.613948 7.579577-2.773599 7.579577-2.86924C7.579577-2.976837 7.483935-2.976837 7.44807-2.976837C7.340473-2.976837 7.328518-2.905106 7.304608-2.857285C6.75467-.992279 5.140722-.095641 3.945205-.095641C2.677958-.095641 1.578082-.908593 1.578082-2.606227C1.578082-2.988792 1.697634-5.068991 3.048568-6.635118C3.706102-7.400249 4.829888-8.069738 5.965629-8.069738C7.280697-8.069738 7.866501-6.981818 7.866501-5.762391C7.866501-5.451557 7.830635-5.188543 7.830635-5.140722C7.830635-5.033126 7.950187-5.033126 7.986052-5.033126C8.117559-5.033126 8.129514-5.045081 8.177335-5.260274L8.930511-8.308842Z'/>
</defs>
<g id='eq1-page1'>
<use x='0' y='0' xlink:href='#eq1-g0-67'/>
<use x='9.233612' y='-4.338437' xlink:href='#eq1-g1-49'/>
</g>
</svg> con­tinu­ity.</p>
<ul>
<li><strong>Au­thors:</strong> Nghia Truong, Cem Yuk­sel and Larry Seiler,
<a href="https://www.highperformancegraphics.org/2020/">High-Per­form­ance Graph­ics 2020</a></li>
<li><strong>Pa­per:</strong> <a href="https://ttnghia.github.io/pdf/QuadraticApproximation.pdf">com­ing soon</a></li>
<li><strong>Git­Hub:</strong> <a href="https://github.com/ttnghia/QuadraticApproximation">ttnghia/Quad­rat­icAp­prox­im­a­tion</a></li>
</ul>
</div>
</div>
<div class="m-row">
<div class="m-col-m-6 m-push-m-6">
<div class="m-image">
<a href="https://github.com/alanjfs/sequentity"><img src="https://static.magnum.graphics/img/blog/announcements/2020.06/sequentity.png" /></a>
</div>
</div>
<div class="m-col-m-6 m-pull-m-6">
<p class="m-show-m m-nopadb">
<br />
</p>
<h3><a href="https://github.com/alanjfs/sequentity" class="m-flat">Sequentity</a></h3><p>A single-file, im­me­di­ate-mode se­quen­cer wid­get for C++17, Dear ImGui
and EnTT. Ex­ample powered by Mag­num.</p>
<ul>
<li><strong>Au­thor:</strong> <a href="https://github.com/alanjfs">Alan Jef­fer­son</a></li>
<li><strong>Li­cense:</strong> <a class="m-label m-success m-flat" href="https://github.com/alanjfs/sequentity/blob/08b041798fa2680897a34e894eda1c187f1d02db/Sequentity.h#L1422-L1458">Jef­fer­son Li­cence</a></li>
<li><strong>Git­Hub:</strong> <a href="https://github.com/alanjfs/sequentity">alan­jfs/se­quentity</a></li>
</ul>
</div>
</div>
<div class="m-row">
<div class="m-col-m-6">
<div class="m-image">
<a href="hhttps://ais-bonn.github.io/stillleben/"><img src="https://static.magnum.graphics/img/blog/announcements/2020.06/stillleben.jpg" /></a>
</div>
</div>
<div class="m-col-m-6">
<p class="m-show-m m-nopadb">
<br />
</p>
<h3><a href="https://ais-bonn.github.io/stillleben/" class="m-flat">Stillleben</a></h3><p>Gen­er­ates real­ist­ic ar­range­ments of ri­gid bod­ies and provides vari­ous out­puts that can be used to train deep learn­ing mod­els.</p>
<ul>
<li><strong>Au­thors:</strong> Max Schwarz and Arul Selvam Per­iy­asamy</li>
<li><strong>Pa­per:</strong> <a class="m-link-wrap" href="https://arxiv.org/pdf/2005.05659.pdf">https://arxiv.org/pdf/2005.05659.pdf</a></li>
<li><strong>Git­Hub:</strong> <a href="https://github.com/AIS-Bonn/stillleben">AIS-Bonn/still­leben</a></li>
</ul>
</div>
</div>
<div class="m-row">
<div class="m-col-m-6 m-push-m-6">
<div class="m-image">
<a href="https://github.com/sariug/mpfluid_cave_frontend"><img src="https://static.magnum.graphics/img/blog/announcements/2020.06/mpfluid.jpg" /></a>
</div>
</div>
<div class="m-col-m-6 m-pull-m-6">
<h3><a href="https://github.com/sariug/mpfluid_cave_frontend" class="m-flat">mpFluid CAVE Front End</a></h3><p>In­ter­act­ive Ex­plor­a­tion and Com­pu­ta­tion­al Steer­ing in CAVE-like En­vir­on­ments for High-Per­form­ance Flu­id Sim­u­la­tions.
<a class="m-text m-dim" href="https://blog.magnum.graphics/guest-posts/teaching-vr-with-magnum/">The CAVE-like en­vir­on­ment was fea­tured pre­vi­ously here.</a></p>
<ul>
<li><strong>Au­thor:</strong> <a href="https://github.com/sariug">Uğurc­an Sarı</a></li>
<li><a href="https://www.ugurcansari.com/pdf/MasterArbeit.pdf">Thes­is PDF</a></li>
<li><strong>Git­Hub:</strong> <a href="https://github.com/sariug/mpfluid_cave_frontend">sari­ug/mp­flu­id_cave_­fron­tend</a></li>
</ul>
</div>
</div>
<div class="m-text m-success m-small m-text-center m-center-m m-col-m-8">
If you have a pro­ject that you want to have men­tioned in the next Pro­ject
Spot­light, <a href="mailto:info@magnum.graphics">let</a>
<a href="https://gitter.im/mosra/magnum">us</a>
<a href="https://twitter.com/czmosra">know</a>! For pro­jects hos­ted on Git­Hub don’t
for­get to add the <a href="https://github.com/topics/magnum">#mag­num</a> tag to make
them easi­er to dis­cov­er.</div>
</section>
<section id="full-changelog">
<h2><a href="#full-changelog">Full changelog</a></h2>
<p>There’s so much hap­pen­ing that this art­icle is, as al­ways, just a dis­tilled
ver­sion of the changelog — and I’m sure I for­got to men­tion some of the
hid­den gems:</p>
<ul>
<li><a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/corrade-changelog.html#corrade-changelog-2020-06">Changes in Cor­rade 2020.06</a></li>
<li><a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/changelog.html#changelog-2020-06">Changes in Mag­num 2020.06</a></li>
<li><a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/changelog-plugins.html#changelog-plugins-2020-06">Changes in Mag­num Plu­gins 2020.06</a></li>
<li><a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/changelog-integration.html#changelog-integration-2020-06">Changes in Mag­num In­teg­ra­tion 2020.06</a></li>
<li><a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/changelog-extras.html#changelog-extras-2020-06">Changes in Mag­num Ex­tras 2020.06</a></li>
<li><a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/changelog-examples.html#changelog-examples-2020-06">Changes in Mag­num Ex­amples 2020.06</a></li>
</ul>
</section>
<section id="updating-from-previous-versions">
<h2><a href="#updating-from-previous-versions">Up­dat­ing from pre­vi­ous ver­sions</a></h2>
<p>If you use <a href="https://github.com/mosra/homebrew-magnum">Homebrew</a>,
<a href="https://aur.archlinux.org/packages/?O=0&K=magnum">Arch­Linux AUR</a> or build
your own <code>*.deb</code> pack­ages, the 2020.06 re­lease is already avail­able there.
Vcp­kg pack­age up­date is cur­rently wait­ing for a merge in
<a href="https://github.com/microsoft/vcpkg/issues/12211">mi­crosoft/vcp­kg#12211</a>, MSYS pack­ages are al­most ready in
<a href="https://github.com/msys2/MINGW-packages/issues/6641">msys2/MINGW-pack­ages#6641</a> and Arch­Linux com­munity pack­age up­dates will
fol­low shortly. If you use <a href="https://github.com/mosra/magnum-singles">Mag­num Singles</a>, those
have all ad­di­tions from 2020.06 present.</p>
<p>If you have your code already us­ing the new <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1MeshData.html">Trade::Mesh­Data</a> APIs,
con­grats — you’re 95% there. If not, the new re­lease should <em>mostly</em> com­pile
with your ex­ist­ing code, only emit a lot of de­prec­a­tion warn­ings where each
will tell you what API to use in­stead.</p>
<aside class="m-block m-warning">
<h3>Breaking changes</h3>
<p>While Mag­num tries to avoid break­ing changes if pos­sible, some­times it’s
in­ev­it­able, and those are the ma­jor ones that might bite you:</p>
<ul>
<li>Pre­defined loc­a­tions of gen­er­ic mesh at­trib­utes — in par­tic­u­lar
nor­mals, tan­gents and col­ors — got changed in or­der to ac­co­mod­ate for
new at­trib­ute types . This may break cus­tom shaders if these rely on
gen­er­ic at­trib­ute defin­i­tions or are used to­geth­er with <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1MeshTools.html#a117705a137cfa68945d678a096824648">MeshTools::com­pile()</a>. To avoid such break­ages, you’re ad­vised to
propag­ate the gen­er­ic defin­i­tions to the shader code as shown in the
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/structMagnum_1_1Shaders_1_1GenericGL.html#Shaders-GenericGL-custom">Shaders::Gen­er­ic</a> doc­u­ment­a­tion.</li>
<li><a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/Assert_8h.html#a43f13cdfc5f2ceef611c9ab46c4563fa">COR­RADE_ASS­ER­T_UN­REACH­ABLE()</a> got re­named to
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/Assert_8h.html#ae57d79a33cb035144c8a888125318c04">COR­RADE_IN­TERN­AL_ASS­ER­T_UN­REACH­ABLE()</a> and the ori­gin­al name is
re­used for a macro with a cus­tom er­ror mes­sage. If you were not us­ing
Cor­rade as­serts in your code, this change won’t af­fect you, oth­er­wise
you’ll get a com­pil­a­tion er­ror and need to ad­apt.</li>
<li><a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1ResourceManager.html">Re­source­M­an­ager</a> <code class="m-code"><span class="n">instance</span><span class="p">()</span></code> singleton that got de­prec­ated
in 2019.10 had to be fully re­moved. Usu­ally a de­prec­ated API is kept
for at least a year to give every­one enough time to up­grade, but here
it was severely lim­it­ing mul­ti­th­readed ap­plic­a­tions and so it’s gone
earli­er.</li>
</ul>
</aside>
</section>
<section id="thank-you">
<h2><a href="#thank-you">Thank you</a></h2>
<p>A sig­ni­fic­ant por­tion of the work on Mag­num is be­ing done by ex­tern­al
con­trib­ut­ors, and this re­lease is no ex­cep­tion — thanks, every­body (and
apo­lo­gies to those I for­got):</p>
<ul>
<li><a href="https://github.com/bowling-allie">Al­lie</a> for im­ple­ment­ing
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1IcoImporter.html">IcoIm­port­er</a>, ad­di­tions and fixes to
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1ImGuiIntegration.html">ImGui­In­teg­ra­tion</a> and vari­ous oth­er things</li>
<li><a href="https://github.com/pomeroyb">Brandon Pomeroy</a> for ex­ample cleanup</li>
<li><a href="https://github.com/erikwijmans">Erik Wij­mans</a> for im­ple­ment­ing CUDA device se­lec­tion in
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Platform_1_1WindowlessEglApplication.html">Plat­form::Win­dow­lessE­glAp­plic­a­tion</a></li>
<li><a href="https://github.com/williamjcm">Guil­laume Jac­quemin</a> for con­tin­ued MSYS pack­age
main­ten­ance, bu­gre­ports and fixes</li>
<li><a href="https://github.com/Squareys">Jonath­an Hale</a> for con­tin­ued Vcp­kg pack­age main­ten­ance,
im­ple­ment­ing tex­ture co­ordin­ate set sup­port, Em­scripten fixes, light
sup­port in <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1TinyGltfImporter.html">Tiny­Glt­fIm­port­er</a>, <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1Math.html">Math</a>
and <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1GL.html">GL</a> ad­di­tions and more</li>
<li><a href="https://github.com/costashatz">Kon­stanti­nos Chatzily­ger­oud­is</a> for vari­ous ap­plic­a­tion
fixes and ad­di­tions, con­tin­ued <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1DartIntegration.html">DartInteg­ra­tion</a> main­ten­ance</li>
<li><a href="https://github.com/Melix19">Marco Melorio</a> for adding curs­or man­age­ment to all
ap­plic­a­tions + <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1ImGuiIntegration.html">ImGui­In­teg­ra­tion</a>, <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1Math.html">Math</a> and py­thon bind­ings
ad­di­tions</li>
<li><a href="https://github.com/xqms">Max Schwarz</a> for im­ple­ment­ing multi-prim­it­ive sup­port in
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1AssimpImporter.html">As­simpIm­port­er</a> and/ an ex­treme pa­tience with
As­simp in gen­er­al</li>
<li><a href="https://github.com/ttnghia">Nghia Truong</a> for all the cool ex­amples and re­lated
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1Math.html">Math</a> ad­di­tions</li>
<li><a href="https://github.com/aspioupiou">Stéphane Brard</a> for shared con­text sup­port in win­dow­less
apps</li>
<li><a href="https://github.com/Amphaal">@Am­phaal</a>, <a href="https://github.com/HaroldReyiz">Burak Canik</a>,
<a href="https://github.com/roig">Daniel Guz­man</a>, <a href="https://github.com/dbacchet">Dav­ide Bac­chet</a>,
<a href="https://github.com/JacksonCampolattaro">Jack­son Cam­po­lat­taro</a>,
<a href="https://github.com/Auburns">Jordan Peck</a> and <a href="https://github.com/pezcode">@pez­code</a> for vari­ous fixes,
im­prove­ments and doc­u­ment­a­tion cla­ri­fic­a­tions</li>
</ul>
<aside class="m-note m-dim">
Im­pressed? An­noyed? Bored? Angry? That’s what so­cial net­works are for:
<a href="https://twitter.com/czmosra/status/1278670039005171712">Twit­ter</a>;
Red­dit <a href="https://www.reddit.com/r/cpp/comments/hjwbrb/magnum_engine_202006_released_with_redesigned/">r/cpp</a>,
<a href="https://www.reddit.com/r/gamedev/comments/hjwcal/magnum_engine_202006_released_with_redesigned/">r/game­dev</a>,
<a href="https://www.reddit.com/r/webgl/comments/hjweok/magnum_engine_202006_released_with_redesigned/">r/webgl</a>;
<a href="https://news.ycombinator.com/item?id=23712530">Hack­er News</a></aside>
</section>
New geometry pipeline in Magnum2020-05-27T00:00:00+02:002020-05-27T00:00:00+02:00Vladimír Vondruštag:blog.magnum.graphics,2020-05-27:/announcements/new-geometry-pipeline/<p>Flex­ible and ef­fi­cient mesh rep­res­ent­a­tion, cus­tom at­trib­utes, new
data types and a ton of new pro­cessing, visu­al­iz­a­tion and ana­lyz­ing
tools. GPU-friendly geo­metry stor­age as it should be in the 21st cen­tury.</p>
<p>Dur­ing the past six months, Mag­num had un­der­gone a rather massive re­work of its
very cent­ral parts — mesh data im­port and stor­age. The ori­gin­al (and now
de­prec­ated) <code class="cpp m-code"><span class="n">Trade</span><span class="o">::</span><span class="n">MeshData2D</span></code> / <code class="cpp m-code"><span class="n">Trade</span><span class="o">::</span><span class="n">MeshData3D</span></code> classes stayed
ba­sic­ally in­tact from the early 2010s when Mag­num was noth­ing more than a toy
pro­ject of one bored uni­ver­sity stu­dent, and were over­due for a re­place­ment.</p>
<section id="how-to-not-do-things">
<h2><a href="#how-to-not-do-things">How to not do things</a></h2>
<p>While the <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1GL_1_1Mesh.html">GL::Mesh</a> and <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1GL_1_1Attribute.html">GL::At­trib­ute</a> on the ren­der­er side
provided all ima­gin­able op­tions for data lay­out and ver­tex formats, the
flex­ib­il­ity bot­tle­neck was on the im­port­er side. In­creas­ingly un­happy about the
lim­it­a­tions, I ended up sug­gest­ing people to just sidestep the <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1Trade.html">Trade</a>
APIs and make their own rep­res­ent­a­tion when they needed to do any­thing
non-trivi­al. How­ever, work­ing on the re­place­ment, I dis­covered — the hor­ror
— that Mag­num was far from the only lib­rary with such lim­it­a­tions em­bed­ded in
its design.</p>
<div class="m-col-m-8 m-left-m m-container-inflate">
<figure class="m-code-figure">
<pre class="m-text m-small m-code"><span class="k">explicit</span> <span class="nf">MeshData3D</span><span class="p">(</span><span class="n">MeshPrimitive</span> <span class="n">primitive</span><span class="p">,</span>
<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">UnsignedInt</span><span class="o">></span> <span class="n">indices</span><span class="p">,</span>
<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">Vector3</span><span class="o">>></span> <span class="n">positions</span><span class="p">,</span>
<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">Vector3</span><span class="o">>></span> <span class="n">normals</span><span class="p">,</span>
<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">Vector2</span><span class="o">>></span> <span class="n">textureCoords2D</span><span class="p">,</span>
<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">Color4</span><span class="o">>></span> <span class="n">colors</span><span class="p">,</span>
<span class="k">const</span> <span class="kt">void</span><span class="o">*</span> <span class="n">importerState</span> <span class="o">=</span> <span class="k">nullptr</span><span class="p">);</span></pre>
<p class="m-text m-small m-dim m-noindent">Source: <a href="https://github.com/mosra/magnum/blob/6053cc6fc6761d8e7110d889f01924188106bef8/src/Magnum/Trade/MeshData3D.h/issues/L68-L81">Mag­num/Trade/Mesh­Data3D.h</a>
<span class="m-label m-danger">de­prec­ated</span></p>
</figure>
</div>
<p>Here is the ori­gin­al Mag­num API. While it al­lowed mul­tiple sets of all
at­trib­utes (us­able in mesh morph­ing, for ex­ample), adding a new at­trib­ute type
meant adding an­oth­er vec­tor-of-vec­tors (and up­dat­ing calls to this con­struct­or
every­where), not to men­tion lack of sup­port any sort of cus­tom at­trib­utes or
abil­ity to store dif­fer­ent data types. The <code>importerState</code> is an ex­ten­sion
point that al­lows ac­cess­ing ar­bit­rary ad­di­tion­al data, but it’s
plu­gin-de­pend­ent and thus not us­able in a gen­er­ic way.</p>
<div class="m-col-m-5 m-right-m m-container-inflate">
<figure class="m-code-figure">
<pre class="m-text m-small m-code"><span class="k">struct</span> <span class="n">aiMesh</span>
<span class="p">{</span>
<span class="n">aiVector3D</span><span class="o">*</span> <span class="n">mVertices</span><span class="p">;</span>
<span class="n">aiVector3D</span><span class="o">*</span> <span class="n">mNormals</span><span class="p">;</span>
<span class="n">aiVector3D</span><span class="o">*</span> <span class="n">mTangents</span><span class="p">;</span>
<span class="n">aiVector3D</span><span class="o">*</span> <span class="n">mBitangents</span><span class="p">;</span>
<span class="n">aiColor4D</span><span class="o">*</span> <span class="n">mColors</span><span class="p">[</span><span class="err">…</span><span class="p">];</span>
<span class="n">aiVector3D</span><span class="o">*</span> <span class="n">mTextureCoords</span><span class="p">[</span><span class="err">…</span><span class="p">];</span>
<span class="n">aiFace</span><span class="o">*</span> <span class="n">mFaces</span><span class="p">;</span>
<span class="err">…</span>
<span class="p">};</span></pre>
<p class="m-text m-small m-dim m-noindent">Source: <a href="https://github.com/https://github.com/assimp/assimp/blob/b7de061749ce98ef08f46d961876092f1e450ae5/include/assimp/mesh.h/issues/L557-L724">as­simp/mesh.h</a></p>
</figure>
</div>
<p>Per­haps the most widely used as­set im­port lib­rary, <a href="https://assimp.org/">As­simp</a>,
has it very sim­il­ar. All at­trib­utes are tightly packed and in a fixed type, and
while it sup­ports a few more at­trib­ute types com­pared to the ori­gin­al Mag­num
API, it has no cus­tom at­trib­utes or formats either.</p>
<p>Fixed in­dex and at­trib­ute types mean that in­put data has to be de-in­ter­leaved
and ex­pan­ded to 32-bit ints and floats in or­der to be stored here, only to have
them in­ter­leaved and packed again later to have ef­fi­cient rep­res­ent­a­tion on the
GPU. Both of those rep­res­ent­a­tions also <em>own</em> the data, mean­ing you can’t use
them to ref­er­ence ex­tern­al memory (for ex­ample a memory-mapped file or a GPU
buf­fer).</p>
<p>The ul­ti­mate win­ner of this con­test, how­ever, is <a href="https://libigl.github.io/">libIGL</a>,
with the fol­low­ing func­tion sig­na­ture. Gran­ted, it’s tem­plated to al­low you to
choose a dif­fer­ent in­dex and scal­ar type, but you have to choose the type
up­front and not based on what the file ac­tu­ally con­tains, which kinda de­feats
the pur­pose. What’s the most amaz­ing though is that every po­s­i­tion and nor­mal
is a three-com­pon­ent <a class="m-flat" href="https://en.cppreference.com/w/cpp/container/vector">std::vec­tor</a>, every tex­ture co­ordin­ate a
two-com­pon­ent vec­tor <em>and then</em> each face is rep­res­en­ted by an­oth­er three
vec­tor in­stances. So if you load a 5M-ver­tex mesh with 10M faces (which is not
that un­com­mon if you deal with <a href="https://twitter.com/zeuxcg/status/1210736070129680384">real data</a>),
it’ll be spread across 45 mil­lions of al­loc­a­tions. Even with keep­ing all the
flex­ib­il­ity It could be just a hand­ful<a class="m-footnote" href="#id2" id="id1">1</a>, but why keep your feet on the
ground, right? The <a class="m-flat" href="https://en.cppreference.com/w/cpp/string/basic_string">std::string</a> passed by value is just a nice touch on
top.</p>
<div class="m-col-m-8 m-center-m">
<figure class="m-code-figure">
<pre class="m-text m-small m-code"><span class="k">template</span> <span class="o"><</span><span class="k">typename</span> <span class="n">Scalar</span><span class="p">,</span> <span class="k">typename</span> <span class="n">Index</span><span class="o">></span>
<span class="n">IGL_INLINE</span> <span class="kt">bool</span> <span class="n">readOBJ</span><span class="p">(</span>
<span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">obj_file_name</span><span class="p">,</span>
<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">Scalar</span> <span class="o">></span> <span class="o">></span> <span class="o">&</span> <span class="n">V</span><span class="p">,</span>
<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">Scalar</span> <span class="o">></span> <span class="o">></span> <span class="o">&</span> <span class="n">TC</span><span class="p">,</span>
<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">Scalar</span> <span class="o">></span> <span class="o">></span> <span class="o">&</span> <span class="n">N</span><span class="p">,</span>
<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">Index</span> <span class="o">></span> <span class="o">></span> <span class="o">&</span> <span class="n">F</span><span class="p">,</span>
<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">Index</span> <span class="o">></span> <span class="o">></span> <span class="o">&</span> <span class="n">FTC</span><span class="p">,</span>
<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">Index</span> <span class="o">></span> <span class="o">></span> <span class="o">&</span> <span class="n">FN</span><span class="p">,</span>
<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">std</span><span class="o">::</span><span class="n">tuple</span><span class="o"><</span><span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="p">,</span> <span class="n">Index</span><span class="p">,</span> <span class="n">Index</span> <span class="o">>></span> <span class="o">&</span><span class="n">FM</span>
<span class="p">);</span></pre>
<p class="m-text m-small m-dim m-noindent">Source: <a href="https://github.com/https://github.com/libigl/libigl/blob/3069260f0753df94b1615208ace86eb685b8df0c/include/igl/readOBJ.h/issues/L69-L79">igl/readOBJ.h</a></p>
</figure>
</div>
<dl class="m-footnote">
<dt id="id2">1</a>.</dt>
<dd><span class="m-footnote"><a href="#id1">^</a></span> To be fair, libIGL has an over­load that puts the res­ult
<a href="https://github.com/libigl/libigl/blob/3069260f0753df94b1615208ace86eb685b8df0c/include/igl/readOBJ.h#L104-L118">in­to just six reg­u­larly-shaped Ei­gen matrices</a>.
How­ever, it’s im­ple­men­ted <em>on top of</em> the above (so you still need a <abbr title="battle-hardened, fulfilling orders without judgment">mil­it­ary-grade</abbr>
al­loc­at­or) and it re­quires you to know <em>be­fore­hand</em> that all faces in the file have the same size.</dd>
</dl>
</section>
<section id="can-we-do-better">
<h2><a href="#can-we-do-better">Can we do bet­ter?</a></h2>
<p>The ori­gin­al pipeline (and many im­port­er lib­rar­ies as well) got de­signed with
an as­sump­tion that a file has to be <em>parsed</em> in or­der to get the geo­metry data
out of it. It was a sens­ible de­cision for clas­sic tex­tu­al formats such as OBJ,
COL­LADA or OpenGEX, and there was little point in pars­ing those to any­thing
else than 32-bit floats and in­tegers. For such formats, a re­l­at­ively massive
amount of pro­cessing was needed either way, so a bunch of more cop­ies and data
pack­ing at the end didn’t really mat­ter:</p>
<div class="m-container-inflate">
<img class="m-image" src="https://static.magnum.graphics/img/blog/announcements/new-geometry-pipeline/meshdata-before.svg" style="width: 100%" />
</div>
<p>The new pipeline turns this as­sump­tion up­side down, and in­stead builds on a
simple design goal — be­ing able to un­der­stand any­thing that the GPU can
un­der­stand as well. In­ter­leaved data or not, half-floats, packed formats,
ar­bit­rary pad­ding and align­ment, cus­tom ap­plic­a­tion-spe­cif­ic at­trib­utes and so
on. Then, as­sum­ing a file already has the data ex­actly as we want it, it can
simply copy the bin­ary blob over to the GPU and only parse the metadata
de­scrib­ing off­sets, strides and formats:</p>
<div class="m-container-inflate">
<img class="m-image" src="https://static.magnum.graphics/img/blog/announcements/new-geometry-pipeline/meshdata-after.svg" style="width: 100%" />
</div>
<p>For the tex­tu­al formats (and ri­gidly-de­signed 3rd party im­port­er lib­rar­ies) it
means the im­port­er plu­gin now has to do ex­tra work that in­volves pack­ing the
data in­to a single buf­fer. But that’s an op­tim­iz­a­tion done on the right side
— with in­creas­ing mod­el com­plex­ity it will make less and less sense to store
the data in a tex­tu­al format.</p>
</section>
<section id="enter-the-new-meshdata">
<h2><a href="#enter-the-new-meshdata">Enter the new Mesh­Data</a></h2>
<p>The new <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1MeshData.html">Trade::Mesh­Data</a> class ac­cepts just two memory buf­fers — a
type­less in­dex buf­fer and a type­less ver­tex buf­fer. The rest is sup­plied as a
metadata, with <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/classCorrade_1_1Containers_1_1StridedArrayView.html">Con­tain­ers::StridedAr­rayView</a> power­ing the data ac­cess
(be sure to check out the <a href="https://blog.magnum.graphics/backstage/multidimensional-strided-array-views/">ori­gin­al art­icle on strided views</a>).
This, along with an abil­ity to sup­ply any <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum.html#a0e8c9116ac182d4d2bf5f37b28bc39dc">MeshIndex­Type</a> and
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum.html#a174f1c4f12e98745bee3db77239aa53f">Ver­tex­Format</a> gives you al­most un­lim­ited<a class="m-footnote" href="#id4" id="id3">2</a> free­dom of ex­pres­sion. As
an ex­ample, let’s say you have your po­s­i­tions as half-floats, nor­mals packed in
bytes and a cus­tom per-ver­tex ma­ter­i­al ID at­trib­ute for de­ferred ren­der­ing,
com­plete with pad­ding to en­sure ver­tices are aligned to four-byte ad­dresses:</p>
<pre class="m-inverted m-code"><span class="hll"><span class="k">struct</span> <span class="n">Vertex</span> <span class="p">{</span>
</span><span class="hll"> <span class="n">Vector3h</span> <span class="n">position</span><span class="p">;</span>
</span><span class="hll"> <span class="n">Vector2b</span> <span class="n">normal</span><span class="p">;</span>
</span><span class="hll"> <span class="nl">UnsignedShort</span><span class="p">:</span><span class="mi">16</span><span class="p">;</span>
</span><span class="hll"> <span class="n">UnsignedShort</span> <span class="n">objectId</span><span class="p">;</span>
</span><span class="hll"><span class="p">};</span>
</span>
<span class="n">Containers</span><span class="o">::</span><span class="n">Array</span><span class="o"><</span><span class="kt">char</span><span class="o">></span> <span class="n">indexData</span><span class="p">;</span>
<span class="n">Containers</span><span class="o">::</span><span class="n">Array</span><span class="o"><</span><span class="kt">char</span><span class="o">></span> <span class="n">vertexData</span><span class="p">;</span>
<span class="hll"><span class="n">Trade</span><span class="o">::</span><span class="n">MeshIndexData</span> <span class="n">indices</span><span class="p">{</span><span class="n">MeshIndexType</span><span class="o">::</span><span class="n">UnsignedShort</span><span class="p">,</span> <span class="n">indexData</span><span class="p">};</span>
</span><span class="hll"><span class="n">Trade</span><span class="o">::</span><span class="n">MeshData</span> <span class="n">meshData</span><span class="p">{</span><span class="n">MeshPrimitive</span><span class="o">::</span><span class="n">Triangles</span><span class="p">,</span>
</span><span class="hll"> <span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">indexData</span><span class="p">),</span> <span class="n">indices</span><span class="p">,</span>
</span><span class="hll"> <span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">vertexData</span><span class="p">),</span> <span class="p">{</span>
</span><span class="hll"> <span class="n">Trade</span><span class="o">::</span><span class="n">MeshAttributeData</span><span class="p">{</span><span class="n">Trade</span><span class="o">::</span><span class="n">MeshAttribute</span><span class="o">::</span><span class="n">Position</span><span class="p">,</span>
</span><span class="hll"> <span class="n">VertexFormat</span><span class="o">::</span><span class="n">Vector3h</span><span class="p">,</span> <span class="n">offsetof</span><span class="p">(</span><span class="n">Vertex</span><span class="p">,</span> <span class="n">position</span><span class="p">),</span>
</span><span class="hll"> <span class="n">vertexCount</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">Vertex</span><span class="p">)},</span>
</span><span class="hll"> <span class="n">Trade</span><span class="o">::</span><span class="n">MeshAttributeData</span><span class="p">{</span><span class="n">Trade</span><span class="o">::</span><span class="n">MeshAttribute</span><span class="o">::</span><span class="n">Normal</span><span class="p">,</span>
</span><span class="hll"> <span class="n">VertexFormat</span><span class="o">::</span><span class="n">Vector2bNormalized</span><span class="p">,</span> <span class="n">offsetof</span><span class="p">(</span><span class="n">Vertex</span><span class="p">,</span> <span class="n">normal</span><span class="p">),</span>
</span><span class="hll"> <span class="n">vertexCount</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">Vertex</span><span class="p">)},</span>
</span><span class="hll"> <span class="n">Trade</span><span class="o">::</span><span class="n">MeshAttributeData</span><span class="p">{</span><span class="n">Trade</span><span class="o">::</span><span class="n">MeshAttribute</span><span class="o">::</span><span class="n">ObjectId</span><span class="p">,</span>
</span><span class="hll"> <span class="n">VertexFormat</span><span class="o">::</span><span class="n">UnsignedShort</span><span class="p">,</span> <span class="n">offsetof</span><span class="p">(</span><span class="n">Vertex</span><span class="p">,</span> <span class="n">objectId</span><span class="p">),</span>
</span><span class="hll"> <span class="n">vertexCount</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">Vertex</span><span class="p">)}</span>
</span><span class="hll"> <span class="p">}</span>
</span><span class="hll"><span class="p">};</span>
</span></pre>
<p>The res­ult­ing <code>meshData</code> vari­able is a self-con­tained in­stance con­tain­ing all
ver­tex and in­dex data of the mesh. You can then for ex­ample pass it dir­ectly to
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1MeshTools.html#a117705a137cfa68945d678a096824648">MeshTools::com­pile()</a> — which will up­load the <code>indexData</code> and
<code>vertexData</code> as-is to the GPU without any pro­cessing, and con­fig­ure it so the
built­in shaders can trans­par­ently in­ter­pret the half-floats and nor­mal­ized
bytes as 32-bit floats:</p>
<pre class="m-code"><span class="n">GL</span><span class="o">::</span><span class="n">Mesh</span> <span class="n">mesh</span> <span class="o">=</span> <span class="n">MeshTools</span><span class="o">::</span><span class="n">compile</span><span class="p">(</span><span class="n">meshData</span><span class="p">);</span>
<span class="n">Shaders</span><span class="o">::</span><span class="n">Phong</span><span class="p">{}.</span><span class="n">draw</span><span class="p">(</span><span class="n">mesh</span><span class="p">);</span></pre>
<p>The data isn’t hid­den from you either — us­ing <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1MeshData.html#a293f49afcc3811ce4abec798654e4e1f">in­dices()</a>
or <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1MeshData.html#afd86408d6dd0a557f12532ed33322a03">at­trib­ute()</a> you can dir­ectly ac­cess
the in­dices and par­tic­u­lar at­trib­utes in a match­ing con­crete type …</p>
<pre class="m-code"><span class="n">Containers</span><span class="o">::</span><span class="n">StridedArrayView1D</span><span class="o"><</span><span class="k">const</span> <span class="n">UnsignedShort</span><span class="o">></span> <span class="n">objectIds</span> <span class="o">=</span>
<span class="n">meshData</span><span class="p">.</span><span class="n">attribute</span><span class="o"><</span><span class="n">UnsignedShort</span><span class="o">></span><span class="p">(</span><span class="n">Trade</span><span class="o">::</span><span class="n">MeshAttribute</span><span class="o">::</span><span class="n">ObjectId</span><span class="p">);</span>
<span class="k">for</span><span class="p">(</span><span class="n">UnsignedShort</span> <span class="nl">objectId</span><span class="p">:</span> <span class="n">objectIds</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// …</span>
<span class="p">}</span></pre>
<p>… and be­cause there’s many pos­sible types and not all of them are dir­ectly
us­able (such as the half-floats), there are
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1MeshData.html#adeb7f88868e6e3034890413880ce90ac">in­dicesAs­Ar­ray()</a>,
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1MeshData.html#a7329571e01dd01b0d64e92e35a03d0a6">po­s­i­tion­s3­DAsAr­ray()</a>,
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1MeshData.html#a3dd3842dc8806bd8d4a767968cb7bced">nor­malsAs­Ar­ray()</a> etc. con­veni­ence
ac­cessors that give you the at­trib­ute un­packed to a ca­non­ic­al type so it can
be used eas­ily in con­texts that as­sume 32-bit floats. For ex­ample, cal­cu­lat­ing
an AABB of whatever po­s­i­tion type is just an oneliner:</p>
<pre class="m-code"><span class="n">Range3D</span> <span class="n">aabb</span> <span class="o">=</span> <span class="n">Math</span><span class="o">::</span><span class="n">minmax</span><span class="p">(</span><span class="n">meshData</span><span class="p">.</span><span class="n">positions3DAsArray</span><span class="p">());</span></pre>
<p>Among the <abbr title="as opposed to revolutionary">evol­u­tion­ary</abbr> things, mesh
at­trib­ute sup­port got ex­ten­ded with tan­gents and bit­an­gents (in both
rep­res­ent­a­tions, either a four-com­pon­ent tan­gent that glTF uses or a sep­ar­ate
three-com­pon­ent bit­an­gent that As­simp uses), and <a href="https://github.com/Squareys">@Squareys</a> is work­ing on
adding sup­port for ver­tex weights and joint IDs in <a href="https://github.com/mosra/magnum/issues/441">mosra/mag­num#441</a>.</p>
<dl class="m-footnote">
<dt id="id4">2</a>.</dt>
<dd><span class="m-footnote"><a href="#id3">^</a></span> You still need to obey the lim­it­a­tions giv­en by the GPU, such as the
in­dex buf­fer be­ing con­tigu­ous, all at­trib­utes hav­ing the same in­dex buf­fer
or all faces be­ing tri­angles. <a href="#going-further-custom-attributes-face-and-edge-properties-meshlets">Un­less you go with mesh­lets.</a></dd>
</dl>
</section>
<section id="tools-to-help-you-around">
<h2><a href="#tools-to-help-you-around">Tools to help you around</a></h2>
<p>Of course one doesn’t al­ways have data already packed in an ideal way, and
do­ing so by hand is te­di­ous and er­ror-prone. For that, the <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1MeshTools.html">MeshTools</a>
lib­rary got ex­ten­ded with vari­ous util­it­ies op­er­at­ing dir­ectly on
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1MeshData.html">Trade::Mesh­Data</a>. Here’s how you could use <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1MeshTools.html#a5ab4589fe7d0b805fc2dcfefcdba06e8">MeshTools::in­ter­leave()</a>
to cre­ate the above packed rep­res­ent­a­tion from a bunch of con­tigu­ous ar­rays,
pos­sibly to­geth­er with <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1Math.html#ae2b2b053ec0f02d745aa441a7eca8164">Math::pack­In­to()</a>, <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1Math.html#a967f6258d1a07fce427c36f0413fcb70">Math::pack­HalfInto()</a> and
sim­il­ar. Where pos­sible, the ac­tu­al <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum.html#a174f1c4f12e98745bee3db77239aa53f">Ver­tex­Format</a> is in­ferred from the
passed view type:</p>
<pre class="m-inverted m-code"><span class="n">Containers</span><span class="o">::</span><span class="n">ArrayView</span><span class="o"><</span><span class="k">const</span> <span class="n">Vector3h</span><span class="o">></span> <span class="n">positions</span><span class="p">;</span>
<span class="n">Containers</span><span class="o">::</span><span class="n">ArrayView</span><span class="o"><</span><span class="k">const</span> <span class="n">Vector2b</span><span class="o">></span> <span class="n">normals</span><span class="p">;</span>
<span class="n">Containers</span><span class="o">::</span><span class="n">ArrayView</span><span class="o"><</span><span class="k">const</span> <span class="n">UnsignedShort</span><span class="o">></span> <span class="n">objectIds</span><span class="p">;</span>
<span class="n">Containers</span><span class="o">::</span><span class="n">ArrayView</span><span class="o"><</span><span class="k">const</span> <span class="n">UnsignedShort</span><span class="o">></span> <span class="n">indices</span><span class="p">;</span>
<span class="hll"><span class="n">Trade</span><span class="o">::</span><span class="n">MeshData</span> <span class="n">meshData</span> <span class="o">=</span> <span class="n">MeshTools</span><span class="o">::</span><span class="n">interleave</span><span class="p">(</span>
</span><span class="hll"> <span class="n">Trade</span><span class="o">::</span><span class="n">MeshData</span><span class="p">{</span><span class="n">MeshPrimitive</span><span class="o">::</span><span class="n">Triangles</span><span class="p">,</span>
</span><span class="hll"> <span class="p">{},</span> <span class="n">indices</span><span class="p">,</span> <span class="n">Trade</span><span class="o">::</span><span class="n">MeshIndexData</span><span class="p">{</span><span class="n">indices</span><span class="p">},</span> <span class="n">UnsignedInt</span><span class="p">(</span><span class="n">positions</span><span class="p">.</span><span class="n">size</span><span class="p">())},</span>
</span><span class="hll"> <span class="p">{</span><span class="n">Trade</span><span class="o">::</span><span class="n">MeshAttributeData</span><span class="p">{</span><span class="n">Trade</span><span class="o">::</span><span class="n">MeshAttribute</span><span class="o">::</span><span class="n">Position</span><span class="p">,</span> <span class="n">positions</span><span class="p">},</span>
</span><span class="hll"> <span class="n">Trade</span><span class="o">::</span><span class="n">MeshAttributeData</span><span class="p">{</span><span class="n">Trade</span><span class="o">::</span><span class="n">MeshAttribute</span><span class="o">::</span><span class="n">Normal</span><span class="p">,</span> <span class="n">normals</span><span class="p">},</span>
</span><span class="hll"> <span class="n">Trade</span><span class="o">::</span><span class="n">MeshAttributeData</span><span class="p">{</span><span class="n">Trade</span><span class="o">::</span><span class="n">MeshAttribute</span><span class="o">::</span><span class="n">ObjectId</span><span class="p">,</span> <span class="n">objectIds</span><span class="p">}}</span>
</span><span class="hll"><span class="p">);</span>
</span></pre>
<p>Thanks to the flex­ib­il­ity of <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1MeshData.html">Trade::Mesh­Data</a>, many of his­tor­ic­ally quite
verb­ose op­er­a­tions are now avail­able through single-ar­gu­ment APIs. Tak­ing a
mesh, in­ter­leav­ing its at­trib­utes, re­mov­ing du­plic­ates and fi­nally pack­ing the
in­dex buf­fer to the smal­lest type that can rep­res­ent giv­en range can be done by
chain­ing <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1MeshTools.html#a5ab4589fe7d0b805fc2dcfefcdba06e8">MeshTools::in­ter­leave()</a>,
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1MeshTools.html#ac39584845d0291262e8276e05d53b22e">MeshTools::re­move­Du­plic­ates()</a> and <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1MeshTools.html#abb14599dd50bc7559a6fb7767bd70405">MeshTools::com­pressIn­dices()</a>:</p>
<pre class="m-code"><span class="n">Trade</span><span class="o">::</span><span class="n">MeshData</span> <span class="n">optimized</span> <span class="o">=</span> <span class="n">MeshTools</span><span class="o">::</span><span class="n">compressIndices</span><span class="p">(</span>
<span class="n">MeshTools</span><span class="o">::</span><span class="n">removeDuplicates</span><span class="p">(</span>
<span class="n">MeshTools</span><span class="o">::</span><span class="n">interleave</span><span class="p">(</span><span class="n">mesh</span><span class="p">)));</span></pre>
<p>There’s also <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1MeshTools.html#ac093061706a7501ca0e8b224b091c959">MeshTools::con­cat­en­ate()</a> for mer­ging mul­tiple meshes
to­geth­er, <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1MeshTools.html#aa2f023dc60f4330316419b362a0f839f">MeshTools::gen­er­ateIn­dices()</a> for con­vert­ing strips, loops and
fans to in­dexed lines and tri­angles, and oth­ers. Ex­cept for po­ten­tial
re­stric­tions com­ing from giv­en al­gorithm, each of those works on an ar­bit­rary
in­stance, be it an in­dexed mesh or not, with any kind of at­trib­utes.</p>
<p>Apart from the high-level APIs work­ing on <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1MeshData.html">Trade::Mesh­Data</a> in­stances, the
ex­ist­ing <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1MeshTools.html">MeshTools</a> al­gorithms that work dir­ectly on data ar­rays were
por­ted from <a class="m-flat" href="https://en.cppreference.com/w/cpp/container/vector">std::vec­tor</a> to <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/classCorrade_1_1Containers_1_1StridedArrayView.html">Con­tain­ers::StridedAr­rayView</a>, mean­ing
they can be used on a much broad­er range of in­puts.</p>
</section>
<section id="binary-file-formats-make-the-computer-happy">
<h2><a href="#binary-file-formats-make-the-computer-happy">Bin­ary file formats make the com­puter happy</a></h2>
<p>With a mesh rep­res­ent­a­tion match­ing GPU cap­ab­il­it­ies 1:1, let’s look at a few
ex­amples of bin­ary file formats that could make use of it, their flex­ib­il­ity
and how they per­form.</p>
<section id="gltf">
<h3><a href="#gltf">glTF</a></h3>
<figure class="m-fullwidth m-figure">
<img src="https://static.magnum.graphics/img/blog/announcements/new-geometry-pipeline/format-gltf.svg" />
<p class="m-text-center"><strong>glTF</strong> — <span class="m-label m-warning">in­ter­leaved</span> <span class="m-label m-success">at­trib­utes</span> or
<span class="m-label m-info">not</span>, do what you want as long as <span class="m-label m-dim">in­dices</span> stay
con­tigu­ous</p>
</figure>
<p>The <a href="https://en.wikipedia.org/wiki/GlTF">“JPEG of 3D”</a> and its very flex­ible
bin­ary mesh data rep­res­ent­a­tion was ac­tu­ally the ini­tial trig­ger for this work
— “what if we could simply memory-map the <code>*.glb</code> and render dir­ectly off
it?”. In my opin­ion the cur­rent ver­sion is a bit too lim­ited in the choice of
ver­tex formats (no half-floats, no 10.10.10.2 or float 11.11.10
rep­res­ent­a­tions for nor­mals and qua­ternions), but that’s largely due to its
goal of be­ing fully com­pat­ible with un­ex­ten­ded WebGL 1 and noth­ing an ex­ten­sion
couldn’t fix.</p>
<p>To make use of a broad­er range of new ver­tex formats, Mag­num’s
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1TinyGltfImporter.html">Tiny­Glt­fIm­port­er</a> got ex­ten­ded to sup­port the
<a href="https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization">KHR_mesh_quant­iz­a­tion</a>
glTF ex­ten­sion, to­geth­er with
<a href="https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_transform">KHR_­tex­ture_trans­form</a>,
which it de­pends on. Com­pared to the more in­volved com­pres­sion schemes
quant­iz­a­tion has the ad­vant­age of not re­quir­ing any de­com­pres­sion step, as the
GPU can still un­der­stand the data without a prob­lem. A quant­ized mesh will have
its po­s­i­tions, nor­mals and tex­ture co­ordin­ates stored in the smal­lest pos­sible
type that can still rep­res­ent the ori­gin­al data with­in reas­on­able er­ror bounds.
So for ex­ample tex­ture co­ordin­ates in a range of <svg class="m-math" style="width: 4.340em; height: 1.245em; vertical-align: -0.312em;" viewBox="0 -8.966376 41.662765 11.955168">
<title>
[0.5, 0.8]
</title>
<defs>
<path id='eq1-g0-58' d='M2.199751 -0.573848C2.199751 -0.920548 1.912827 -1.159651 1.625903 -1.159651C1.279203 -1.159651 1.0401 -0.872727 1.0401 -0.585803C1.0401 -0.239103 1.327024 0 1.613948 0C1.960648 0 2.199751 -0.286924 2.199751 -0.573848Z'/>
<path id='eq1-g0-59' d='M2.331258 0.047821C2.331258 -0.645579 2.10411 -1.159651 1.613948 -1.159651C1.231382 -1.159651 1.0401 -0.848817 1.0401 -0.585803S1.219427 0 1.625903 0C1.78132 0 1.912827 -0.047821 2.020423 -0.155417C2.044334 -0.179328 2.056289 -0.179328 2.068244 -0.179328C2.092154 -0.179328 2.092154 -0.011955 2.092154 0.047821C2.092154 0.442341 2.020423 1.219427 1.327024 1.996513C1.195517 2.139975 1.195517 2.163885 1.195517 2.187796C1.195517 2.247572 1.255293 2.307347 1.315068 2.307347C1.41071 2.307347 2.331258 1.422665 2.331258 0.047821Z'/>
<path id='eq1-g1-48' d='M5.355915 -3.825654C5.355915 -4.817933 5.296139 -5.786301 4.865753 -6.694894C4.375592 -7.687173 3.514819 -7.950187 2.929016 -7.950187C2.235616 -7.950187 1.3868 -7.603487 0.944458 -6.611208C0.609714 -5.858032 0.490162 -5.116812 0.490162 -3.825654C0.490162 -2.666002 0.573848 -1.793275 1.004234 -0.944458C1.470486 -0.035866 2.295392 0.251059 2.917061 0.251059C3.957161 0.251059 4.554919 -0.37061 4.901619 -1.06401C5.332005 -1.960648 5.355915 -3.132254 5.355915 -3.825654ZM2.917061 0.011955C2.534496 0.011955 1.75741 -0.203238 1.530262 -1.506351C1.398755 -2.223661 1.398755 -3.132254 1.398755 -3.969116C1.398755 -4.94944 1.398755 -5.834122 1.590037 -6.539477C1.793275 -7.340473 2.402989 -7.711083 2.917061 -7.711083C3.371357 -7.711083 4.064757 -7.436115 4.291905 -6.40797C4.447323 -5.726526 4.447323 -4.782067 4.447323 -3.969116C4.447323 -3.16812 4.447323 -2.259527 4.315816 -1.530262C4.088667 -0.215193 3.335492 0.011955 2.917061 0.011955Z'/>
<path id='eq1-g1-53' d='M1.530262 -6.850311C2.044334 -6.682939 2.462765 -6.670984 2.594271 -6.670984C3.945205 -6.670984 4.805978 -7.663263 4.805978 -7.830635C4.805978 -7.878456 4.782067 -7.938232 4.710336 -7.938232C4.686426 -7.938232 4.662516 -7.938232 4.554919 -7.890411C3.88543 -7.603487 3.311582 -7.567621 3.000747 -7.567621C2.211706 -7.567621 1.649813 -7.806725 1.422665 -7.902366C1.338979 -7.938232 1.315068 -7.938232 1.303113 -7.938232C1.207472 -7.938232 1.207472 -7.866501 1.207472 -7.675218V-4.124533C1.207472 -3.90934 1.207472 -3.837609 1.350934 -3.837609C1.41071 -3.837609 1.422665 -3.849564 1.542217 -3.993026C1.876961 -4.483188 2.438854 -4.770112 3.036613 -4.770112C3.670237 -4.770112 3.981071 -4.184309 4.076712 -3.981071C4.27995 -3.514819 4.291905 -2.929016 4.291905 -2.47472S4.291905 -1.338979 3.957161 -0.800996C3.694147 -0.37061 3.227895 -0.071731 2.701868 -0.071731C1.912827 -0.071731 1.135741 -0.609714 0.920548 -1.482441C0.980324 -1.458531 1.052055 -1.446575 1.111831 -1.446575C1.315068 -1.446575 1.637858 -1.566127 1.637858 -1.972603C1.637858 -2.307347 1.41071 -2.49863 1.111831 -2.49863C0.896638 -2.49863 0.585803 -2.391034 0.585803 -1.924782C0.585803 -0.908593 1.398755 0.251059 2.725778 0.251059C4.076712 0.251059 5.260274 -0.884682 5.260274 -2.402989C5.260274 -3.825654 4.303861 -5.009215 3.048568 -5.009215C2.367123 -5.009215 1.841096 -4.710336 1.530262 -4.375592V-6.850311Z'/>
<path id='eq1-g1-56' d='M3.56264 -4.315816C4.160399 -4.638605 5.033126 -5.188543 5.033126 -6.192777C5.033126 -7.232877 4.028892 -7.950187 2.929016 -7.950187C1.745455 -7.950187 0.812951 -7.07746 0.812951 -5.989539C0.812951 -5.583064 0.932503 -5.176588 1.267248 -4.770112C1.398755 -4.614695 1.41071 -4.60274 2.247572 -4.016936C1.08792 -3.478954 0.490162 -2.677958 0.490162 -1.80523C0.490162 -0.537983 1.697634 0.251059 2.917061 0.251059C4.244085 0.251059 5.355915 -0.729265 5.355915 -1.984558C5.355915 -3.203985 4.495143 -3.741968 3.56264 -4.315816ZM1.936737 -5.391781C1.78132 -5.499377 1.303113 -5.810212 1.303113 -6.396015C1.303113 -7.173101 2.116065 -7.663263 2.917061 -7.663263C3.777833 -7.663263 4.542964 -7.041594 4.542964 -6.180822C4.542964 -5.451557 4.016936 -4.865753 3.323537 -4.483188L1.936737 -5.391781ZM2.49863 -3.849564L3.945205 -2.905106C4.25604 -2.701868 4.805978 -2.331258 4.805978 -1.601993C4.805978 -0.6934 3.88543 -0.071731 2.929016 -0.071731C1.912827 -0.071731 1.0401 -0.812951 1.0401 -1.80523C1.0401 -2.737733 1.721544 -3.490909 2.49863 -3.849564Z'/>
<path id='eq1-g1-91' d='M2.988792 2.988792V2.546451H1.829141V-8.524035H2.988792V-8.966376H1.3868V2.988792H2.988792Z'/>
<path id='eq1-g1-93' d='M1.853051 -8.966376H0.251059V-8.524035H1.41071V2.546451H0.251059V2.988792H1.853051V-8.966376Z'/>
</defs>
<g id='eq1-page1'>
<use x='0' y='0' xlink:href='#eq1-g1-91'/>
<use x='3.251661' y='0' xlink:href='#eq1-g1-48'/>
<use x='9.104652' y='0' xlink:href='#eq1-g0-58'/>
<use x='12.356313' y='0' xlink:href='#eq1-g1-53'/>
<use x='18.209303' y='0' xlink:href='#eq1-g0-59'/>
<use x='23.453462' y='0' xlink:href='#eq1-g1-48'/>
<use x='29.306452' y='0' xlink:href='#eq1-g0-58'/>
<use x='32.558113' y='0' xlink:href='#eq1-g1-56'/>
<use x='38.411104' y='0' xlink:href='#eq1-g1-93'/>
</g>
</svg> will get
packed to a 8-bit range <svg class="m-math" style="width: 3.662em; height: 1.245em; vertical-align: -0.312em;" viewBox="0 -8.966376 35.159442 11.955168">
<title>
[0, 255]
</title>
<defs>
<path id='eq2-g0-59' d='M2.331258 0.047821C2.331258 -0.645579 2.10411 -1.159651 1.613948 -1.159651C1.231382 -1.159651 1.0401 -0.848817 1.0401 -0.585803S1.219427 0 1.625903 0C1.78132 0 1.912827 -0.047821 2.020423 -0.155417C2.044334 -0.179328 2.056289 -0.179328 2.068244 -0.179328C2.092154 -0.179328 2.092154 -0.011955 2.092154 0.047821C2.092154 0.442341 2.020423 1.219427 1.327024 1.996513C1.195517 2.139975 1.195517 2.163885 1.195517 2.187796C1.195517 2.247572 1.255293 2.307347 1.315068 2.307347C1.41071 2.307347 2.331258 1.422665 2.331258 0.047821Z'/>
<path id='eq2-g1-48' d='M5.355915 -3.825654C5.355915 -4.817933 5.296139 -5.786301 4.865753 -6.694894C4.375592 -7.687173 3.514819 -7.950187 2.929016 -7.950187C2.235616 -7.950187 1.3868 -7.603487 0.944458 -6.611208C0.609714 -5.858032 0.490162 -5.116812 0.490162 -3.825654C0.490162 -2.666002 0.573848 -1.793275 1.004234 -0.944458C1.470486 -0.035866 2.295392 0.251059 2.917061 0.251059C3.957161 0.251059 4.554919 -0.37061 4.901619 -1.06401C5.332005 -1.960648 5.355915 -3.132254 5.355915 -3.825654ZM2.917061 0.011955C2.534496 0.011955 1.75741 -0.203238 1.530262 -1.506351C1.398755 -2.223661 1.398755 -3.132254 1.398755 -3.969116C1.398755 -4.94944 1.398755 -5.834122 1.590037 -6.539477C1.793275 -7.340473 2.402989 -7.711083 2.917061 -7.711083C3.371357 -7.711083 4.064757 -7.436115 4.291905 -6.40797C4.447323 -5.726526 4.447323 -4.782067 4.447323 -3.969116C4.447323 -3.16812 4.447323 -2.259527 4.315816 -1.530262C4.088667 -0.215193 3.335492 0.011955 2.917061 0.011955Z'/>
<path id='eq2-g1-50' d='M5.260274 -2.008468H4.99726C4.961395 -1.80523 4.865753 -1.147696 4.746202 -0.956413C4.662516 -0.848817 3.981071 -0.848817 3.622416 -0.848817H1.41071C1.733499 -1.123786 2.462765 -1.888917 2.773599 -2.175841C4.590785 -3.849564 5.260274 -4.471233 5.260274 -5.654795C5.260274 -7.029639 4.172354 -7.950187 2.785554 -7.950187S0.585803 -6.766625 0.585803 -5.738481C0.585803 -5.128767 1.111831 -5.128767 1.147696 -5.128767C1.398755 -5.128767 1.709589 -5.308095 1.709589 -5.69066C1.709589 -6.025405 1.482441 -6.252553 1.147696 -6.252553C1.0401 -6.252553 1.016189 -6.252553 0.980324 -6.240598C1.207472 -7.053549 1.853051 -7.603487 2.630137 -7.603487C3.646326 -7.603487 4.267995 -6.75467 4.267995 -5.654795C4.267995 -4.638605 3.682192 -3.753923 3.000747 -2.988792L0.585803 -0.286924V0H4.94944L5.260274 -2.008468Z'/>
<path id='eq2-g1-53' d='M1.530262 -6.850311C2.044334 -6.682939 2.462765 -6.670984 2.594271 -6.670984C3.945205 -6.670984 4.805978 -7.663263 4.805978 -7.830635C4.805978 -7.878456 4.782067 -7.938232 4.710336 -7.938232C4.686426 -7.938232 4.662516 -7.938232 4.554919 -7.890411C3.88543 -7.603487 3.311582 -7.567621 3.000747 -7.567621C2.211706 -7.567621 1.649813 -7.806725 1.422665 -7.902366C1.338979 -7.938232 1.315068 -7.938232 1.303113 -7.938232C1.207472 -7.938232 1.207472 -7.866501 1.207472 -7.675218V-4.124533C1.207472 -3.90934 1.207472 -3.837609 1.350934 -3.837609C1.41071 -3.837609 1.422665 -3.849564 1.542217 -3.993026C1.876961 -4.483188 2.438854 -4.770112 3.036613 -4.770112C3.670237 -4.770112 3.981071 -4.184309 4.076712 -3.981071C4.27995 -3.514819 4.291905 -2.929016 4.291905 -2.47472S4.291905 -1.338979 3.957161 -0.800996C3.694147 -0.37061 3.227895 -0.071731 2.701868 -0.071731C1.912827 -0.071731 1.135741 -0.609714 0.920548 -1.482441C0.980324 -1.458531 1.052055 -1.446575 1.111831 -1.446575C1.315068 -1.446575 1.637858 -1.566127 1.637858 -1.972603C1.637858 -2.307347 1.41071 -2.49863 1.111831 -2.49863C0.896638 -2.49863 0.585803 -2.391034 0.585803 -1.924782C0.585803 -0.908593 1.398755 0.251059 2.725778 0.251059C4.076712 0.251059 5.260274 -0.884682 5.260274 -2.402989C5.260274 -3.825654 4.303861 -5.009215 3.048568 -5.009215C2.367123 -5.009215 1.841096 -4.710336 1.530262 -4.375592V-6.850311Z'/>
<path id='eq2-g1-91' d='M2.988792 2.988792V2.546451H1.829141V-8.524035H2.988792V-8.966376H1.3868V2.988792H2.988792Z'/>
<path id='eq2-g1-93' d='M1.853051 -8.966376H0.251059V-8.524035H1.41071V2.546451H0.251059V2.988792H1.853051V-8.966376Z'/>
</defs>
<g id='eq2-page1'>
<use x='0' y='0' xlink:href='#eq2-g1-91'/>
<use x='3.251661' y='0' xlink:href='#eq2-g1-48'/>
<use x='9.104652' y='0' xlink:href='#eq2-g0-59'/>
<use x='14.34881' y='0' xlink:href='#eq2-g1-50'/>
<use x='20.201801' y='0' xlink:href='#eq2-g1-53'/>
<use x='26.054791' y='0' xlink:href='#eq2-g1-53'/>
<use x='31.907781' y='0' xlink:href='#eq2-g1-93'/>
</g>
</svg> and off­set + scale needed to
dequant­ize them back to ori­gin­al range is then provided through the tex­ture
trans­form­a­tion mat­rix. The size gains vary from mod­el to mod­el and de­pend on
the ra­tio between tex­ture and ver­tex data. To show some num­bers, here’s a
dif­fer­ence with two mod­els from the <a href="https://github.com/KhronosGroup/glTF-Sample-Models">glTF-Sample-Mod­els</a>
re­pos­it­ory, con­ver­ted us­ing the <code>gltfpack</code> util­ity from <a href="https://github.com/zeux/meshoptimizer">mesh­op­tim­izer</a>
(<a href="#meshoptimizer-and-plugin-interfaces-for-mesh-conversion">more on that be­low</a>):</p>
<div class="m-nopadb m-plot">
<svg viewBox="0 0 576 171.36">
<defs>
<style type="text/css">*{stroke-linejoin: round; stroke-linecap: butt}</style>
</defs>
<g id="figure_1">
<g id="axes_1">
<g id="patch_1">
<path d="M 104.329375 128.305312 L 564.12 128.305312 L 564.12 27.292813 L 104.329375 27.292813 z" class="m-background"/>
</g>
<g id="plot1-value0-0"><title>5.0 kB</title>
<path d="M 104.329375 31.88429 L 104.958536 31.88429 L 104.958536 51.216826 L 104.329375 51.216826 z" clip-path="url(#p5162b9a375)" class="m-bar m-default"/>
</g>
<g id="plot1-value0-1"><title>4.8 kB</title>
<path d="M 104.329375 56.04996 L 104.933369 56.04996 L 104.933369 75.382496 L 104.329375 75.382496 z" clip-path="url(#p5162b9a375)" class="m-bar m-default"/>
</g>
<g id="plot1-value0-2"><title>64.0 kB</title>
<path d="M 104.329375 80.215629 L 112.382632 80.215629 L 112.382632 99.548165 L 104.329375 99.548165 z" clip-path="url(#p5162b9a375)" class="m-bar m-default"/>
</g>
<g id="plot1-value0-3"><title>8.0 kB</title>
<path d="M 104.329375 104.381299 L 105.336032 104.381299 L 105.336032 123.713835 L 104.329375 123.713835 z" clip-path="url(#p5162b9a375)" class="m-bar m-default"/>
</g>
<g id="plot1-value1-0"><title>417.6 kB</title>
<path d="M 104.958536 31.88429 L 157.506036 31.88429 L 157.506036 51.216826 L 104.958536 51.216826 z" clip-path="url(#p5162b9a375)" class="m-bar m-success"/>
</g>
<g id="plot1-value1-1"><title>417.6 kB</title>
<path d="M 104.933369 56.04996 L 157.480869 56.04996 L 157.480869 75.382496 L 104.933369 75.382496 z" clip-path="url(#p5162b9a375)" class="m-bar m-success"/>
</g>
<g id="plot1-value1-2"><title>0.0 kB</title>
<path d="M 112.382632 80.215629 L 112.382632 80.215629 L 112.382632 99.548165 L 112.382632 99.548165 z" clip-path="url(#p5162b9a375)" class="m-bar m-success"/>
</g>
<g id="plot1-value1-3"><title>0.0 kB</title>
<path d="M 105.336032 104.381299 L 105.336032 104.381299 L 105.336032 123.713835 L 105.336032 123.713835 z" clip-path="url(#p5162b9a375)" class="m-bar m-success"/>
</g>
<g id="plot1-value2-0"><title>106.8 kB</title>
<path d="M 157.506036 31.88429 L 170.944908 31.88429 L 170.944908 51.216826 L 157.506036 51.216826 z" clip-path="url(#p5162b9a375)" class="m-bar m-info"/>
</g>
<g id="plot1-value2-1"><title>75.6 kB</title>
<path d="M 157.480869 56.04996 L 166.993779 56.04996 L 166.993779 75.382496 L 157.480869 75.382496 z" clip-path="url(#p5162b9a375)" class="m-bar m-info"/>
</g>
<g id="plot1-value2-2"><title>3416.0 kB</title>
<path d="M 112.382632 80.215629 L 542.225208 80.215629 L 542.225208 99.548165 L 112.382632 99.548165 z" clip-path="url(#p5162b9a375)" class="m-bar m-info"/>
</g>
<g id="plot1-value2-3"><title>2984.0 kB</title>
<path d="M 105.336032 104.381299 L 480.819126 104.381299 L 480.819126 123.713835 L 105.336032 123.713835 z" clip-path="url(#p5162b9a375)" class="m-bar m-info"/>
</g>
<g id="matplotlib.axis_1">
<g id="xtick_1">
<g id="line2d_1">
<defs>
<path id="mc4bea1de13" d="M 0 0 L 0 3.5" class="m-line"/>
</defs>
<g>
<use xlink:href="#mc4bea1de13" x="104.329375" y="128.305312"/>
</g>
</g>
<g id="text_1">
<text class="m-label" style="text-anchor: middle" x="104.329375" y="143.137656" transform="rotate(-0, 104.329375, 143.137656)">0</text>
</g>
</g>
<g id="xtick_2">
<g id="line2d_2">
<g>
<use xlink:href="#mc4bea1de13" x="167.245443" y="128.305312"/>
</g>
</g>
<g id="text_2">
<text class="m-label" style="text-anchor: middle" x="167.245443" y="143.137656" transform="rotate(-0, 167.245443, 143.137656)">500</text>
</g>
</g>
<g id="xtick_3">
<g id="line2d_3">
<g>
<use xlink:href="#mc4bea1de13" x="230.161511" y="128.305312"/>
</g>
</g>
<g id="text_3">
<text class="m-label" style="text-anchor: middle" x="230.161511" y="143.137656" transform="rotate(-0, 230.161511, 143.137656)">1000</text>
</g>
</g>
<g id="xtick_4">
<g id="line2d_4">
<g>
<use xlink:href="#mc4bea1de13" x="293.077579" y="128.305312"/>
</g>
</g>
<g id="text_4">
<text class="m-label" style="text-anchor: middle" x="293.077579" y="143.137656" transform="rotate(-0, 293.077579, 143.137656)">1500</text>
</g>
</g>
<g id="xtick_5">
<g id="line2d_5">
<g>
<use xlink:href="#mc4bea1de13" x="355.993647" y="128.305312"/>
</g>
</g>
<g id="text_5">
<text class="m-label" style="text-anchor: middle" x="355.993647" y="143.137656" transform="rotate(-0, 355.993647, 143.137656)">2000</text>
</g>
</g>
<g id="xtick_6">
<g id="line2d_6">
<g>
<use xlink:href="#mc4bea1de13" x="418.909715" y="128.305312"/>
</g>
</g>
<g id="text_6">
<text class="m-label" style="text-anchor: middle" x="418.909715" y="143.137656" transform="rotate(-0, 418.909715, 143.137656)">2500</text>
</g>
</g>
<g id="xtick_7">
<g id="line2d_7">
<g>
<use xlink:href="#mc4bea1de13" x="481.825783" y="128.305312"/>
</g>
</g>
<g id="text_7">
<text class="m-label" style="text-anchor: middle" x="481.825783" y="143.137656" transform="rotate(-0, 481.825783, 143.137656)">3000</text>
</g>
</g>
<g id="xtick_8">
<g id="line2d_8">
<g>
<use xlink:href="#mc4bea1de13" x="544.741851" y="128.305312"/>
</g>
</g>
<g id="text_8">
<text class="m-label" style="text-anchor: middle" x="544.741851" y="143.137656" transform="rotate(-0, 544.741851, 143.137656)">3500</text>
</g>
</g>
<g id="text_9">
<text class="m-label" style="text-anchor: middle" x="334.224688" y="157.225" transform="rotate(-0, 334.224688, 157.225)">kB</text>
</g>
</g>
<g id="matplotlib.axis_2">
<g id="ytick_1">
<g id="line2d_9">
<defs>
<path id="mae1c673641" d="M 0 0 L -3.5 0" class="m-line"/>
</defs>
<g>
<use xlink:href="#mae1c673641" x="104.329375" y="41.550558"/>
</g>
</g>
<g id="text_10">
<text class="m-label" transform="translate(13.629688 40.423058)">Cesium Milk Truck</text>
<text class="m-label" transform="translate(97.329375 52.07687)"/>
</g>
</g>
<g id="ytick_2">
<g id="line2d_10">
<g>
<use xlink:href="#mae1c673641" x="104.329375" y="65.716228"/>
</g>
</g>
<g id="text_11">
<text class="m-label" transform="translate(13.629688 64.588728)">Cesium Milk Truck</text>
<text class="m-label" transform="translate(97.329375 76.24254)"/>
</g>
</g>
<g id="ytick_3">
<g id="line2d_11">
<g>
<use xlink:href="#mae1c673641" x="104.329375" y="89.881897"/>
</g>
</g>
<g id="text_12">
<text class="m-label" transform="translate(11.88 88.550726)">Reciprocating Saw</text>
<text class="m-label" transform="translate(97.329375 100.414226)"/>
</g>
</g>
<g id="ytick_4">
<g id="line2d_12">
<g>
<use xlink:href="#mae1c673641" x="104.329375" y="114.047567"/>
</g>
</g>
<g id="text_13">
<text class="m-label" transform="translate(11.88 112.716395)">Reciprocating Saw</text>
<text class="m-label" transform="translate(97.329375 124.579895)"/>
</g>
</g>
</g>
<g id="text_14">
<text class="m-label m-dim" transform="translate(97.329375 39.615763)"/>
<text class="m-label m-dim" transform="translate(38.180313 51.269576)">original *.glb</text>
</g>
<g id="text_15">
<text class="m-label m-dim" transform="translate(97.329375 63.886277)"/>
<text class="m-label m-dim" transform="translate(50.995313 75.540089)">quantized</text>
</g>
<g id="text_16">
<text class="m-label m-dim" transform="translate(97.329375 87.947103)"/>
<text class="m-label m-dim" transform="translate(38.180313 99.600915)">original *.glb</text>
</g>
<g id="text_17">
<text class="m-label m-dim" transform="translate(97.329375 112.217616)"/>
<text class="m-label m-dim" transform="translate(50.995313 123.871429)">quantized</text>
</g>
<g id="text_18">
<text class="m-title" style="text-anchor: middle" x="334.224688" y="21.292813" transform="rotate(-0, 334.224688, 21.292813)">Quantization using gltfpack</text>
</g>
</g>
</g>
<defs>
<clipPath id="p5162b9a375">
<rect x="104.329375" y="27.292813" width="459.790625" height="101.0125"/>
</clipPath>
</defs>
</svg>
</div>
<ul class="m-tiny m-unstyled m-block-dot-t m-text-center">
<li><span class="m-label m-default">JSON data size</span></li>
<li><span class="m-label m-success">im­age data size</span></li>
<li><span class="m-label m-info">mesh data size</span></li>
</ul>
<p>While packed at­trib­utes are sup­por­ted by the GPU trans­par­ently, the built­in <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1Shaders.html#aa8a252d128449a517288dc461d21348e">Shaders::Phong</a> and <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1Shaders.html#a1a71a307d5c94f22c80ad5f8e6515b05">Shaders::Flat</a> had to be ex­ten­ded to sup­port
tex­ture trans­form as well.</p>
</section>
<section id="stanford-ply">
<h3><a href="#stanford-ply">Stan­ford PLY</a></h3>
<figure class="m-fullwidth m-figure">
<img src="https://static.magnum.graphics/img/blog/announcements/new-geometry-pipeline/format-ply.svg" />
<p class="m-text-center"><strong>PLY</strong> — in­ter­leaved per-ver­tex <span class="m-label m-warning">po­s­i­tion</span>,
<span class="m-label m-success">nor­mal</span> and <span class="m-label m-info">col­or</span> data, fol­lowed by
<span class="m-label m-primary">size</span> and <span class="m-label m-dim">in­dices</span> of each face</p>
</figure>
<p><a href="https://en.wikipedia.org/wiki/PLY_(file_format)">PLY</a> is a very simple, yet
sur­pris­ingly flex­ible and ex­tens­ible format. Mag­num has the
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1StanfordImporter.html">Stan­fordIm­port­er</a> plu­gin for years, but
fol­low­ing the <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1MeshData.html">Trade::Mesh­Data</a> re­design it gained quite a few new
fea­tures, among which is sup­port for ver­tex col­ors, nor­mals, tex­ture
co­ordin­ates and ob­ject IDs. PLYs also sup­port 8- and 16-bit types for ver­tex
data, and sim­il­arly to glTF’s <a href="https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization">KHR_mesh_quant­iz­a­tion</a> sup­port are now im­por­ted
as-is, without ex­pan­sion to floats.</p>
<p>Be­cause PLYs are so simple <em>and</em> be­cause PLYs are very of­ten used for massive
scanned data­sets (<a href="http://graphics.stanford.edu/data/3Dscanrep/">Stan­ford Bunny</a>
be­ing the most prom­in­ent of them), I took this as an op­por­tun­ity to in­vest­ig­ate
how far can Mag­num re­duce the im­port time, giv­en that it can have the whole
chain un­der con­trol. Plot­ted be­low is im­port time of a 613 MB scan mod­el<a class="m-footnote" href="#id34" id="id5">3</a>
with float po­s­i­tions, 24-bit ver­tex col­ors and a per-face 32-bit ob­ject ID
prop­erty that is pur­posedly ig­nored. Meas­ured times start with the ori­gin­al
state be­fore the <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1MeshData.html">Trade::Mesh­Data</a> re­work, com­pare
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1AssimpImporter.html">As­simpIm­port­er</a> and
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1StanfordImporter.html">Stan­fordIm­port­er</a> con­figured for fast­est
im­port<a class="m-footnote" href="#id14" id="id6">4</a> and show the ef­fect of ad­di­tion­al op­tim­iz­a­tions:</p>
<div class="m-hide-s">
<a class="m-footnote" href="#id15" id="id7">5</a> <a class="m-footnote" href="#id16" id="id8">6</a> <a class="m-footnote" href="#id16" id="id9">6</a> <a class="m-footnote" href="#id17" id="id10">7</a> <a class="m-footnote" href="#id18" id="id11">8</a> <a class="m-footnote" href="#id19" id="id12">9</a> <a class="m-footnote" href="#id19" id="id13">9</a></div>
<div class="m-plot">
<svg viewBox="0 0 576 344.16">
<defs>
<style type="text/css">*{stroke-linejoin: round; stroke-linecap: butt}</style>
</defs>
<g id="figure_1">
<g id="axes_1">
<g id="patch_1">
<path d="M 179.235937 301.105313 L 564.12 301.105313 L 564.12 27.136406 L 179.235937 27.136406 z" class="m-background"/>
</g>
<g id="plot2-value0-0"><title>7.972 seconds</title>
<path d="M 179.235937 39.589538 L 545.792187 39.589538 L 545.792187 59.921183 L 179.235937 59.921183 z" clip-path="url(#p4dba194fcf)" class="m-bar m-danger"/>
</g>
<g id="plot2-value0-1"><title>7.263 seconds</title>
<path d="M 179.235937 65.004094 L 513.192039 65.004094 L 513.192039 85.335738 L 179.235937 85.335738 z" clip-path="url(#p4dba194fcf)" class="m-bar m-danger"/>
</g>
<g id="plot2-value0-2"><title>2.551 seconds</title>
<path d="M 179.235937 90.418649 L 296.532098 90.418649 L 296.532098 110.750293 L 179.235937 110.750293 z" clip-path="url(#p4dba194fcf)" class="m-bar m-warning"/>
</g>
<g id="plot2-value0-3"><title>2.231 seconds</title>
<path d="M 179.235937 115.833204 L 281.81835 115.833204 L 281.81835 136.164849 L 179.235937 136.164849 z" clip-path="url(#p4dba194fcf)" class="m-bar m-warning"/>
</g>
<g id="plot2-value0-4"><title>1.36 seconds</title>
<path d="M 179.235937 141.24776 L 241.769367 141.24776 L 241.769367 161.579404 L 179.235937 161.579404 z" clip-path="url(#p4dba194fcf)" class="m-bar m-info"/>
</g>
<g id="plot2-value0-5"><title>0.875 seconds</title>
<path d="M 179.235937 166.662315 L 219.468843 166.662315 L 219.468843 186.993959 L 179.235937 186.993959 z" clip-path="url(#p4dba194fcf)" class="m-bar m-info"/>
</g>
<g id="plot2-value0-6"><title>0.535 seconds</title>
<path d="M 179.235937 192.07687 L 203.835485 192.07687 L 203.835485 212.408514 L 179.235937 212.408514 z" clip-path="url(#p4dba194fcf)" class="m-bar m-info"/>
</g>
<g id="plot2-value0-7"><title>0.262 seconds</title>
<path d="M 179.235937 217.491426 L 191.282819 217.491426 L 191.282819 237.82307 L 179.235937 237.82307 z" clip-path="url(#p4dba194fcf)" class="m-bar m-success"/>
</g>
<g id="plot2-value0-8"><title>0.13 seconds</title>
<path d="M 179.235937 242.905981 L 185.213398 242.905981 L 185.213398 263.237625 L 179.235937 263.237625 z" clip-path="url(#p4dba194fcf)" class="m-bar m-default"/>
</g>
<g id="plot2-value0-9"><title>0.002 seconds</title>
<path d="M 179.235937 268.320536 L 179.327898 268.320536 L 179.327898 288.65218 L 179.235937 288.65218 z" clip-path="url(#p4dba194fcf)" class="m-bar m-success"/>
</g>
<g id="matplotlib.axis_1">
<g id="xtick_1">
<g id="line2d_1">
<defs>
<path id="m6c4cdc507b" d="M 0 0 L 0 3.5" class="m-line"/>
</defs>
<g>
<use xlink:href="#m6c4cdc507b" x="179.235937" y="301.105313"/>
</g>
</g>
<g id="text_1">
<text class="m-label" style="text-anchor: middle" x="179.235937" y="315.937656" transform="rotate(-0, 179.235937, 315.937656)">0</text>
</g>
</g>
<g id="xtick_2">
<g id="line2d_2">
<g>
<use xlink:href="#m6c4cdc507b" x="225.2164" y="301.105313"/>
</g>
</g>
<g id="text_2">
<text class="m-label" style="text-anchor: middle" x="225.2164" y="315.937656" transform="rotate(-0, 225.2164, 315.937656)">1</text>
</g>
</g>
<g id="xtick_3">
<g id="line2d_3">
<g>
<use xlink:href="#m6c4cdc507b" x="271.196863" y="301.105313"/>
</g>
</g>
<g id="text_3">
<text class="m-label" style="text-anchor: middle" x="271.196863" y="315.937656" transform="rotate(-0, 271.196863, 315.937656)">2</text>
</g>
</g>
<g id="xtick_4">
<g id="line2d_4">
<g>
<use xlink:href="#m6c4cdc507b" x="317.177326" y="301.105313"/>
</g>
</g>
<g id="text_4">
<text class="m-label" style="text-anchor: middle" x="317.177326" y="315.937656" transform="rotate(-0, 317.177326, 315.937656)">3</text>
</g>
</g>
<g id="xtick_5">
<g id="line2d_5">
<g>
<use xlink:href="#m6c4cdc507b" x="363.157789" y="301.105313"/>
</g>
</g>
<g id="text_5">
<text class="m-label" style="text-anchor: middle" x="363.157789" y="315.937656" transform="rotate(-0, 363.157789, 315.937656)">4</text>
</g>
</g>
<g id="xtick_6">
<g id="line2d_6">
<g>
<use xlink:href="#m6c4cdc507b" x="409.138252" y="301.105313"/>
</g>
</g>
<g id="text_6">
<text class="m-label" style="text-anchor: middle" x="409.138252" y="315.937656" transform="rotate(-0, 409.138252, 315.937656)">5</text>
</g>
</g>
<g id="xtick_7">
<g id="line2d_7">
<g>
<use xlink:href="#m6c4cdc507b" x="455.118715" y="301.105313"/>
</g>
</g>
<g id="text_7">
<text class="m-label" style="text-anchor: middle" x="455.118715" y="315.937656" transform="rotate(-0, 455.118715, 315.937656)">6</text>
</g>
</g>
<g id="xtick_8">
<g id="line2d_8">
<g>
<use xlink:href="#m6c4cdc507b" x="501.099178" y="301.105313"/>
</g>
</g>
<g id="text_8">
<text class="m-label" style="text-anchor: middle" x="501.099178" y="315.937656" transform="rotate(-0, 501.099178, 315.937656)">7</text>
</g>
</g>
<g id="xtick_9">
<g id="line2d_9">
<g>
<use xlink:href="#m6c4cdc507b" x="547.07964" y="301.105313"/>
</g>
</g>
<g id="text_9">
<text class="m-label" style="text-anchor: middle" x="547.07964" y="315.937656" transform="rotate(-0, 547.07964, 315.937656)">8</text>
</g>
</g>
<g id="text_10">
<text class="m-label" style="text-anchor: middle" x="371.677969" y="330.025" transform="rotate(-0, 371.677969, 330.025)">seconds</text>
</g>
</g>
<g id="matplotlib.axis_2">
<g id="ytick_1">
<g id="line2d_10">
<defs>
<path id="m74ed764a78" d="M 0 0 L -3.5 0" class="m-line"/>
</defs>
<g>
<use xlink:href="#m74ed764a78" x="179.235937" y="49.75536"/>
</g>
</g>
<g id="text_11">
<text class="m-label" transform="translate(30.418437 48.62786)">AssimpImporter + MeshData3D</text>
<text class="m-label" transform="translate(172.235937 60.281673)"/>
</g>
</g>
<g id="ytick_2">
<g id="line2d_11">
<g>
<use xlink:href="#m74ed764a78" x="179.235937" y="75.169916"/>
</g>
</g>
<g id="text_12">
<text class="m-label" transform="translate(42.661094 74.042416)">AssimpImporter + MeshData</text>
<text class="m-label" transform="translate(172.235937 85.696228)"/>
</g>
</g>
<g id="ytick_3">
<g id="line2d_12">
<g>
<use xlink:href="#m74ed764a78" x="179.235937" y="100.584471"/>
</g>
</g>
<g id="text_13">
<text class="m-label" transform="translate(23.290781 99.523143)">StanfordImporter + MeshData3D</text>
<text class="m-label" transform="translate(172.235937 111.176955)"/>
</g>
</g>
<g id="ytick_4">
<g id="line2d_13">
<g>
<use xlink:href="#m74ed764a78" x="179.235937" y="125.999026"/>
</g>
</g>
<g id="text_14">
<text class="m-label" transform="translate(23.290781 124.937698)">StanfordImporter + MeshData3D</text>
<text class="m-label" transform="translate(172.235937 136.591511)"/>
</g>
</g>
<g id="ytick_5">
<g id="line2d_14">
<g>
<use xlink:href="#m74ed764a78" x="179.235937" y="151.413582"/>
</g>
</g>
<g id="text_15">
<text class="m-label" transform="translate(35.533437 150.352254)">StanfordImporter + MeshData</text>
<text class="m-label" transform="translate(172.235937 162.006066)"/>
</g>
</g>
<g id="ytick_6">
<g id="line2d_15">
<g>
<use xlink:href="#m74ed764a78" x="179.235937" y="176.828137"/>
</g>
</g>
<g id="text_16">
<text class="m-label" transform="translate(35.533437 175.766809)">StanfordImporter + MeshData</text>
<text class="m-label" transform="translate(172.235937 187.420621)"/>
</g>
</g>
<g id="ytick_7">
<g id="line2d_16">
<g>
<use xlink:href="#m74ed764a78" x="179.235937" y="202.242692"/>
</g>
</g>
<g id="text_17">
<text class="m-label" transform="translate(35.533437 201.181364)">StanfordImporter + MeshData</text>
<text class="m-label" transform="translate(172.235937 212.835177)"/>
</g>
</g>
<g id="ytick_8">
<g id="line2d_17">
<g>
<use xlink:href="#m74ed764a78" x="179.235937" y="227.657248"/>
</g>
</g>
<g id="text_18">
<text class="m-label" transform="translate(35.533437 226.59592)">StanfordImporter + MeshData</text>
<text class="m-label" transform="translate(172.235937 238.249732)"/>
</g>
</g>
<g id="ytick_9">
<g id="line2d_18">
<g>
<use xlink:href="#m74ed764a78" x="179.235937" y="253.071803"/>
</g>
</g>
<g id="text_19">
<text class="m-label" transform="translate(72.86125 251.988131)">cat file.ply > /dev/null</text>
<text class="m-label" transform="translate(172.235937 263.686631)"/>
</g>
</g>
<g id="ytick_10">
<g id="line2d_19">
<g>
<use xlink:href="#m74ed764a78" x="179.235937" y="278.486358"/>
</g>
</g>
<g id="text_20">
<text class="m-label" transform="translate(11.88 277.320186)">Magnum's upcoming *.blob format</text>
<text class="m-label" transform="translate(172.235937 289.183686)"/>
</g>
</g>
</g>
<g id="text_21">
<text class="m-label m-dim" transform="translate(172.235937 47.88301)"/>
<text class="m-label m-dim" transform="translate(112.35125 59.536823)">original code</text>
</g>
<g id="text_22">
<text class="m-label m-dim" transform="translate(172.235937 73.402409)"/>
<text class="m-label m-dim" transform="translate(76.8625 85.056222)">new MeshData APIs ⁵</text>
</g>
<g id="text_23">
<text class="m-label m-dim" transform="translate(172.235937 98.712121)"/>
<text class="m-label m-dim" transform="translate(112.35125 110.365933)">original code</text>
</g>
<g id="text_24">
<text class="m-label m-dim" transform="translate(172.235937 123.974739)"/>
<text class="m-label m-dim" transform="translate(100.5125 136.142114)">w/o iostreams ⁶</text>
</g>
<g id="text_25">
<text class="m-label m-dim" transform="translate(172.235937 149.646075)"/>
<text class="m-label m-dim" transform="translate(76.8625 161.299888)">new MeshData APIs ⁵</text>
</g>
<g id="text_26">
<text class="m-label m-dim" transform="translate(172.235937 174.48038)"/>
<text class="m-label m-dim" transform="translate(73.538437 187.085005)">w/ triangle fast path ⁷</text>
</g>
<g id="text_27">
<text class="m-label m-dim" transform="translate(172.235937 199.899061)"/>
<text class="m-label m-dim" transform="translate(56.612187 212.660436)">one less copy on import ⁸</text>
</g>
<g id="text_28">
<text class="m-label m-dim" transform="translate(172.235937 225.313616)"/>
<text class="m-label m-dim" transform="translate(89.163594 238.074991)">zerocopy branch ⁹</text>
</g>
<g id="text_29">
<text class="m-label m-dim" transform="translate(172.235937 251.304296)"/>
<text class="m-label m-dim" transform="translate(95.90625 262.958109)">warm SSD cache</text>
</g>
<g id="text_30">
<text class="m-label m-dim" transform="translate(172.235937 276.718852)"/>
<text class="m-label m-dim" transform="translate(27.221562 288.372664)">meshdata-cereal-killer branch ⁹</text>
</g>
<g id="text_31">
<text class="m-title" style="text-anchor: middle" x="371.677969" y="21.136406" transform="rotate(-0, 371.677969, 21.136406)">Import time, 613 MB Little-Endian PLY, Release</text>
</g>
</g>
</g>
<defs>
<clipPath id="p4dba194fcf">
<rect x="179.235937" y="27.136406" width="384.884063" height="273.968906"/>
</clipPath>
</defs>
</svg>
</div>
<dl class="m-footnote">
<dt id="id14">4.</dt>
<dd><span class="m-footnote">^ <a href="#id6">a</a> <a href="#id20">b</a></span> For <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1AssimpImporter.html">As­simpIm­port­er</a>, the on-by-de­fault
<code class="m-code"><span class="na">JoinIdenticalVertices</span></code>, <code class="m-code"><span class="na">Triangulate</span></code> and <code class="m-code"><span class="na">SortByPType</span></code>
pro­cessing op­tions were turned off, as those in­crease the im­port time
sig­ni­fic­antly for large meshes. To have a fair com­par­is­on, in case of
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1StanfordImporter.html">Stan­fordIm­port­er</a> the
<code class="m-code"><span class="na">perFaceToPerVertex</span></code> op­tion that con­verts per-face at­trib­utes to
per-ver­tex was turned off to match As­simp that ig­nores per-face at­trib­utes com­pletely.</dd>
<dt id="id15">5</a>.</dt>
<dd><span class="m-footnote"><a href="#id7">^</a></span> In case of <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1StanfordImporter.html">Stan­fordIm­port­er</a>, the main
spee­dup comes from all <a class="m-flat" href="https://en.cppreference.com/w/cpp/container/vector/push_back">push_­back()</a>s
re­placed with a <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/namespaceCorrade_1_1Utility.html#a039691d89f3ce26def0c05999735743e">Util­ity::copy()</a>, which is ba­sic­ally a fan­ci­er
<a class="m-flat" href="https://en.cppreference.com/w/cpp/string/byte/memcpy">std::mem­cpy()</a> that works on strided ar­rays as well.
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1AssimpImporter.html">As­simpIm­port­er</a> in­stead
<a class="m-flat" href="https://en.cppreference.com/w/cpp/container/vector/assign">as­sign()</a>ed the whole range at once which
is faster, how­ever the ab­so­lute spee­dup was roughly the same for both.
Un­for­tu­nately not enough for As­simp to be­come sig­ni­fic­antly faster. Com­mit
<a href="https://github.com/mosra/magnum-plugins/commit/79a185b2f63eefffa3f3d7d60b210fc5b37ef713">mosra/mag­num-plu­gins@79a185b</a> and
<a href="https://github.com/mosra/magnum-plugins/commit/e67c217e7a8748049af9eee72a3d86f6432b035e">mosra/mag­num-plu­gins@e67c217</a>.</dd>
<dt id="id16">6.</dt>
<dd><span class="m-footnote">^ <a href="#id8">a</a> <a href="#id9">b</a></span> The ori­gin­al <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1StanfordImporter.html">Stan­fordIm­port­er</a>
im­ple­ment­a­tion was us­ing <a class="m-flat" href="https://en.cppreference.com/w/cpp/string/basic_string/getline">std::get­line()</a> to parse the tex­tu­al head­er
and <a class="m-flat" href="https://en.cppreference.com/w/cpp/io/basic_istream/read">std::is­tream::read()</a> to read the bin­ary con­tents. Load­ing the
whole file in­to a gi­ant ar­ray first and then op­er­at­ing on that proved to be
faster. Com­mit <a href="https://github.com/mosra/magnum-plugins/commit/7d654f10a0eb8610ed486d85963c03172543d4fb">mosra/mag­num-plu­gins@7d654f1</a>.</dd>
<dt id="id17">7</a>.</dt>
<dd><span class="m-footnote"><a href="#id10">^</a></span> PLY al­lows faces to have ar­bit­rary N-gons, which means an im­port­er has
to go through each face, check its ver­tex count and tri­an­gu­late if needed.
I real­ized I could de­tect all-tri­angle files based solely by com­par­ing face
count with file size and then again use <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/namespaceCorrade_1_1Utility.html#a039691d89f3ce26def0c05999735743e">Util­ity::copy()</a> to copy the
sparse tri­angle in­dices to a tightly packed res­ult­ing ar­ray. Com­mit
<a href="https://github.com/mosra/magnum-plugins/commit/885ba490b05c741a46335791cd43d540f93f5a68">mosra/mag­num-plu­gins@885ba49</a>.</dd>
<dt id="id18">8.</dt>
<dd><span class="m-footnote">^ <a href="#id11">a</a> <a href="#id21">b</a> <a href="#id22">c</a></span> To make plu­gin im­ple­ment­a­tion easi­er, if a plu­gin doesn’t provide a
ded­ic­ated <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1AbstractImporter.html#ae627c821d389a3a1ee21dc6f24c5801e">doOpen­File()</a>,
the base im­ple­ment­a­tion reads the file in­to an ar­ray and then passes the
ar­ray to <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1AbstractImporter.html#a81e18009f2c3d40fafad36df6d91604b">doOpenData()</a>.
To­geth­er with as­sump­tions about data own­er­ship it causes an ex­tra copy that
can be avoided by provid­ing a ded­ic­ated <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1AbstractImporter.html#ae627c821d389a3a1ee21dc6f24c5801e">doOpen­File()</a>
im­ple­ment­a­tion. Com­mit <a href="https://github.com/mosra/magnum-plugins/commit/8e21c2f87a8492ba016b289b1d08c88f971223e9">mosra/mag­num-plu­gins@8e21c2f</a>.</dd>
<dt id="id19">9.</dt>
<dd><span class="m-footnote">^ <a href="#id12">a</a> <a href="#id13">b</a></span> If the im­port­er can make a few more as­sump­tions about data own­er­ship,
the re­turned mesh data can be ac­tu­ally a view onto the memory giv­en on
in­put, get­ting rid of an­oth­er copy. There’s still some over­head left from
dein­ter­leav­ing the in­dex buf­fer, so it’s not faster than a plain <code class="m-code">cat</code>.
A cus­tom file format al­lows the im­port to be done in 0.002 seconds, with
the ac­tu­al data read­ing de­ferred to the point where the GPU needs it —
and then feed­ing the GPU straight from a (memory-mapped) SSD. Neither of
those is in­teg­rated in­to <code>master</code> yet, see
<a href="#a-peek-into-the-future-magnum-s-own-memory-mappable-mesh-format">A peek in­to the fu­ture — Mag­num’s own memory-map­pable mesh format</a>
be­low.</dd>
</dl>
</section>
<section id="stl-stereolithography">
<h3><a href="#stl-stereolithography">STL (“ste­re­o­litho­graphy”)</a></h3>
<figure class="m-fullwidth m-figure">
<img src="https://static.magnum.graphics/img/blog/announcements/new-geometry-pipeline/format-stl.svg" />
<p class="m-text-center"><strong>STL</strong> — for each tri­angle a <span class="m-label m-success">nor­mal</span>, three corner
<span class="m-label m-warning">po­s­i­tions</span> and op­tion­al <span class="m-label m-info">col­or</span> data</p>
</figure>
<p>The <a href="https://en.wikipedia.org/wiki/STL_(file_format)">STL format</a> is
ex­tremely simple — just a list of tri­angles, each con­tain­ing a nor­mal and
po­s­i­tions of its corners. It’s com­monly used for 3D print­ing, and thus the
in­ter­net is also full of in­ter­est­ing huge files for test­ing. Un­til re­cently,
Mag­num used <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1AssimpImporter.html">As­simpIm­port­er</a> to im­port STLs, and
to do an­oth­er com­par­is­on I im­ple­men­ted a <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1StlImporter.html">StlImport­er</a>
from scratch. Tak­ing a 104 MB file (<a href="https://www.thingiverse.com/thing:3896745">source</a>,
<a href="https://www.cgtrader.com/3d-print-models/art/sculptures/girl-and-dragon-3d-print-model">al­tern­at­ive</a>), here’s the times —
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1AssimpImporter.html">As­simpIm­port­er</a> is con­figured the same as
above<a class="m-footnote" href="#id14" id="id20">4</a> and sim­il­ar op­tim­iz­a­tions<a class="m-footnote" href="#id18" id="id21">8</a> as in
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1StanfordImporter.html">Stan­fordIm­port­er</a> were done here as well:</p>
<div class="m-hide-s">
<a class="m-footnote" href="#id18" id="id22">8</a> <a class="m-footnote" href="#id24" id="id23">10</a></div>
<div class="m-plot">
<svg viewBox="0 0 576 200.16">
<defs>
<style type="text/css">*{stroke-linejoin: round; stroke-linecap: butt}</style>
</defs>
<g id="figure_1">
<g id="axes_1">
<g id="patch_1">
<path d="M 145.117031 157.105313 L 564.12 157.105313 L 564.12 27.136406 L 145.117031 27.136406 z" class="m-background"/>
</g>
<g id="plot3-value0-0"><title>0.329 seconds</title>
<path d="M 145.117031 33.044084 L 544.167478 33.044084 L 544.167478 52.736342 L 145.117031 52.736342 z" clip-path="url(#pa14ac14d9c)" class="m-bar m-warning"/>
</g>
<g id="plot3-value0-1"><title>0.184 seconds</title>
<path d="M 145.117031 57.659407 L 368.294181 57.659407 L 368.294181 77.351665 L 145.117031 77.351665 z" clip-path="url(#pa14ac14d9c)" class="m-bar m-info"/>
</g>
<g id="plot3-value0-2"><title>0.144 seconds</title>
<path d="M 145.117031 82.27473 L 319.777409 82.27473 L 319.777409 101.966989 L 145.117031 101.966989 z" clip-path="url(#pa14ac14d9c)" class="m-bar m-info"/>
</g>
<g id="plot3-value0-3"><title>0.087 seconds</title>
<path d="M 145.117031 106.890053 L 250.641009 106.890053 L 250.641009 126.582312 L 145.117031 126.582312 z" clip-path="url(#pa14ac14d9c)" class="m-bar m-success"/>
</g>
<g id="plot3-value0-4"><title>0.039 seconds</title>
<path d="M 145.117031 131.505376 L 192.420884 131.505376 L 192.420884 151.197635 L 145.117031 151.197635 z" clip-path="url(#pa14ac14d9c)" class="m-bar m-default"/>
</g>
<g id="matplotlib.axis_1">
<g id="xtick_1">
<g id="line2d_1">
<defs>
<path id="m0f8c636808" d="M 0 0 L 0 3.5" class="m-line"/>
</defs>
<g>
<use xlink:href="#m0f8c636808" x="145.117031" y="157.105313"/>
</g>
</g>
<g id="text_1">
<text class="m-label" style="text-anchor: middle" x="145.117031" y="171.937656" transform="rotate(-0, 145.117031, 171.937656)">0.00</text>
</g>
</g>
<g id="xtick_2">
<g id="line2d_2">
<g>
<use xlink:href="#m0f8c636808" x="205.762996" y="157.105313"/>
</g>
</g>
<g id="text_2">
<text class="m-label" style="text-anchor: middle" x="205.762996" y="171.937656" transform="rotate(-0, 205.762996, 171.937656)">0.05</text>
</g>
</g>
<g id="xtick_3">
<g id="line2d_3">
<g>
<use xlink:href="#m0f8c636808" x="266.40896" y="157.105313"/>
</g>
</g>
<g id="text_3">
<text class="m-label" style="text-anchor: middle" x="266.40896" y="171.937656" transform="rotate(-0, 266.40896, 171.937656)">0.10</text>
</g>
</g>
<g id="xtick_4">
<g id="line2d_4">
<g>
<use xlink:href="#m0f8c636808" x="327.054925" y="157.105313"/>
</g>
</g>
<g id="text_4">
<text class="m-label" style="text-anchor: middle" x="327.054925" y="171.937656" transform="rotate(-0, 327.054925, 171.937656)">0.15</text>
</g>
</g>
<g id="xtick_5">
<g id="line2d_5">
<g>
<use xlink:href="#m0f8c636808" x="387.700889" y="157.105313"/>
</g>
</g>
<g id="text_5">
<text class="m-label" style="text-anchor: middle" x="387.700889" y="171.937656" transform="rotate(-0, 387.700889, 171.937656)">0.20</text>
</g>
</g>
<g id="xtick_6">
<g id="line2d_6">
<g>
<use xlink:href="#m0f8c636808" x="448.346854" y="157.105313"/>
</g>
</g>
<g id="text_6">
<text class="m-label" style="text-anchor: middle" x="448.346854" y="171.937656" transform="rotate(-0, 448.346854, 171.937656)">0.25</text>
</g>
</g>
<g id="xtick_7">
<g id="line2d_7">
<g>
<use xlink:href="#m0f8c636808" x="508.992818" y="157.105313"/>
</g>
</g>
<g id="text_7">
<text class="m-label" style="text-anchor: middle" x="508.992818" y="171.937656" transform="rotate(-0, 508.992818, 171.937656)">0.30</text>
</g>
</g>
<g id="text_8">
<text class="m-label" style="text-anchor: middle" x="354.618516" y="186.025" transform="rotate(-0, 354.618516, 186.025)">seconds</text>
</g>
</g>
<g id="matplotlib.axis_2">
<g id="ytick_1">
<g id="line2d_8">
<defs>
<path id="m3e62786acc" d="M 0 0 L -3.5 0" class="m-line"/>
</defs>
<g>
<use xlink:href="#m3e62786acc" x="145.117031" y="42.890213"/>
</g>
</g>
<g id="text_9">
<text class="m-label" transform="translate(64.050937 41.762713)">AssimpImporter</text>
<text class="m-label" transform="translate(138.117031 53.416526)"/>
</g>
</g>
<g id="ytick_2">
<g id="line2d_9">
<g>
<use xlink:href="#m3e62786acc" x="145.117031" y="67.505536"/>
</g>
</g>
<g id="text_10">
<text class="m-label" transform="translate(84.787656 66.378036)">StlImporter</text>
<text class="m-label" transform="translate(138.117031 78.031849)"/>
</g>
</g>
<g id="ytick_3">
<g id="line2d_10">
<g>
<use xlink:href="#m3e62786acc" x="145.117031" y="92.120859"/>
</g>
</g>
<g id="text_11">
<text class="m-label" transform="translate(84.787656 90.993359)">StlImporter</text>
<text class="m-label" transform="translate(138.117031 102.647172)"/>
</g>
</g>
<g id="ytick_4">
<g id="line2d_11">
<g>
<use xlink:href="#m3e62786acc" x="145.117031" y="116.736183"/>
</g>
</g>
<g id="text_12">
<text class="m-label" transform="translate(84.787656 115.608683)">StlImporter</text>
<text class="m-label" transform="translate(138.117031 127.262495)"/>
</g>
</g>
<g id="ytick_5">
<g id="line2d_12">
<g>
<use xlink:href="#m3e62786acc" x="145.117031" y="141.351506"/>
</g>
</g>
<g id="text_13">
<text class="m-label" transform="translate(41.655625 140.224006)">cat file.stl > /dev/null</text>
<text class="m-label" transform="translate(138.117031 151.877818)"/>
</g>
</g>
</g>
<g id="text_14">
<text class="m-label m-dim" transform="translate(138.117031 41.082745)"/>
<text class="m-label m-dim" transform="translate(48.980937 52.736557)">new MeshData APIs</text>
</g>
<g id="text_15">
<text class="m-label m-dim" transform="translate(138.117031 65.698068)"/>
<text class="m-label m-dim" transform="translate(35.1725 77.351881)">initial implementation</text>
</g>
<g id="text_16">
<text class="m-label m-dim" transform="translate(138.117031 89.737266)"/>
<text class="m-label m-dim" transform="translate(22.493281 102.498641)">one less copy on import ⁸</text>
</g>
<g id="text_17">
<text class="m-label m-dim" transform="translate(138.117031 114.270089)"/>
<text class="m-label m-dim" transform="translate(11.88 127.031464)">per-face normals ignored ¹⁰</text>
</g>
<g id="text_18">
<text class="m-label m-dim" transform="translate(138.117031 139.544037)"/>
<text class="m-label m-dim" transform="translate(61.787344 151.19785)">warm SSD cache</text>
</g>
<g id="text_19">
<text class="m-title" style="text-anchor: middle" x="354.618516" y="21.136406" transform="rotate(-0, 354.618516, 21.136406)">Import time, 104 MB STL, Release</text>
</g>
</g>
</g>
<defs>
<clipPath id="pa14ac14d9c">
<rect x="145.117031" y="27.136406" width="419.002969" height="129.968906"/>
</clipPath>
</defs>
</svg>
</div>
<dl class="m-footnote">
<dt id="id24">10</a>.</dt>
<dd><span class="m-footnote"><a href="#id23">^</a></span> Be­cause the nor­mals are per-tri­angle, turn­ing them in­to per-ver­tex
in­creases the data size roughly by a half (in­stead of 16 floats per
tri­angle it be­comes 24). Dis­abling this (again with a
<code class="m-code"><span class="na">perFaceToPerVertex</span></code> op­tion) sig­ni­fic­antly im­proves im­port time.
Com­mit <a href="https://github.com/mosra/magnum-plugins/commit/e013040d4dd4f6ecd16a713c56d626e60212dbdc">mosra/mag­num-plu­gins@e013040</a>.</dd>
</dl>
</section>
</section>
<section id="meshoptimizer-and-plugin-interfaces-for-mesh-conversion">
<h2><a href="#meshoptimizer-and-plugin-interfaces-for-mesh-conversion">Mesh­Op­tim­izer and plu­gin in­ter­faces for mesh con­ver­sion</a></h2>
<p>While the <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1MeshTools.html">MeshTools</a> lib­rary provides a ver­sat­ile set of APIs for vari­ous
mesh-re­lated tasks, it’ll nev­er be able to suit the needs of <em>every­one</em>. Now
that there’s a flex­ible-enough mesh rep­res­ent­a­tion, it made sense to ex­tend the
built­in en­gine cap­ab­il­it­ies with ex­tern­al mesh con­ver­sion plu­gins.</p>
<p>The first mesh pro­cessing plu­gin is <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1MeshOptimizerSceneConverter.html">Mesh­Op­tim­izer­SceneCon­vert­er</a>,
in­teg­rat­ing <a href="https://github.com/zeux/meshoptimizer">mesh­op­tim­izer</a> by <a href="https://twitter.com/zeuxcg">@zeux­cg</a>.
Au­thor of this lib­rary is also re­spons­ible for the <a href="https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization">KHR_mesh_quant­iz­a­tion</a>
ex­ten­sion and it’s all-round a great piece of tech­no­logy. Un­leash­ing the plu­gin
in its de­fault con­fig on a mesh will per­form the non-de­struct­ive op­er­a­tions —
ver­tex cache op­tim­iz­a­tion, over­draw op­tim­iz­a­tion and ver­tex fetch op­tim­iz­a­tion.
All those op­er­a­tions can be done in-place on an in­dexed tri­angle mesh us­ing
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1AbstractSceneConverter.html#a77ede22362540431f1882f0e6b7a48c1">con­vertIn­Place()</a>:</p>
<pre class="m-code"><span class="n">Containers</span><span class="o">::</span><span class="n">Pointer</span><span class="o"><</span><span class="n">Trade</span><span class="o">::</span><span class="n">AbstractSceneConverter</span><span class="o">></span> <span class="n">meshoptimizer</span> <span class="o">=</span>
<span class="n">manager</span><span class="p">.</span><span class="n">loadAndInstantiate</span><span class="p">(</span><span class="s">"MeshOptimizerSceneConverter"</span><span class="p">);</span>
<span class="n">meshoptimizer</span><span class="o">-></span><span class="n">convertInPlace</span><span class="p">(</span><span class="n">mesh</span><span class="p">);</span></pre>
<p>Okay, now what? This may look like one of those im­possible
<span class="m-label m-flat m-primary">Press to render fast</span> ma­gic but­tons, and since the
op­er­a­tion took about a second at most and didn’t make the out­put smal­ler in any
way, it can’t really do won­ders, right? Well, let’s meas­ure, now with a 179 MB
scan<a class="m-footnote" href="#id34" id="id25">3</a> con­tain­ing 7.5 mil­lion tri­angles with po­s­i­tions and ver­tex col­ors,
how long it takes to render be­fore and after mesh­op­tim­izer looked at it:</p>
<div class="m-row m-container-inflate">
<div class="m-col-m-6">
<div class="m-plot">
<svg viewBox="0 0 324 171.36">
<defs>
<style type="text/css">*{stroke-linejoin: round; stroke-linecap: butt}</style>
</defs>
<g id="figure_1">
<g id="axes_1">
<g id="patch_1">
<path d="M 74.265 128.305312 L 312.12 128.305312 L 312.12 27.136406 L 74.265 27.136406 z" class="m-background"/>
</g>
<g id="plot4-value0-0"><title>62.52 ms</title>
<path d="M 74.265 31.734993 L 300.793571 31.734993 L 300.793571 51.097463 L 74.265 51.097463 z" clip-path="url(#p93e1764265)" class="m-bar m-info"/>
</g>
<g id="plot4-value0-1"><title>20.95 ms</title>
<path d="M 74.265 55.938081 L 150.173087 55.938081 L 150.173087 75.300551 L 74.265 75.300551 z" clip-path="url(#p93e1764265)" class="m-bar m-success"/>
</g>
<g id="plot4-value0-2"><title>11.98 ms</title>
<path d="M 74.265 80.141168 L 117.672106 80.141168 L 117.672106 99.503638 L 74.265 99.503638 z" clip-path="url(#p93e1764265)" class="m-bar m-info"/>
</g>
<g id="plot4-value0-3"><title>9.91 ms</title>
<path d="M 74.265 104.344256 L 110.17188 104.344256 L 110.17188 123.706726 L 74.265 123.706726 z" clip-path="url(#p93e1764265)" class="m-bar m-success"/>
</g>
<g id="matplotlib.axis_1">
<g id="xtick_1">
<g id="line2d_1">
<defs>
<path id="m662ed05794" d="M 0 0 L 0 3.5" class="m-line"/>
</defs>
<g>
<use xlink:href="#m662ed05794" x="74.265" y="128.305312"/>
</g>
</g>
<g id="text_1">
<text class="m-label" style="text-anchor: middle" x="74.265" y="143.137656" transform="rotate(-0, 74.265, 143.137656)">0</text>
</g>
</g>
<g id="xtick_2">
<g id="line2d_2">
<g>
<use xlink:href="#m662ed05794" x="110.497977" y="128.305312"/>
</g>
</g>
<g id="text_2">
<text class="m-label" style="text-anchor: middle" x="110.497977" y="143.137656" transform="rotate(-0, 110.497977, 143.137656)">10</text>
</g>
</g>
<g id="xtick_3">
<g id="line2d_3">
<g>
<use xlink:href="#m662ed05794" x="146.730954" y="128.305312"/>
</g>
</g>
<g id="text_3">
<text class="m-label" style="text-anchor: middle" x="146.730954" y="143.137656" transform="rotate(-0, 146.730954, 143.137656)">20</text>
</g>
</g>
<g id="xtick_4">
<g id="line2d_4">
<g>
<use xlink:href="#m662ed05794" x="182.963931" y="128.305312"/>
</g>
</g>
<g id="text_4">
<text class="m-label" style="text-anchor: middle" x="182.963931" y="143.137656" transform="rotate(-0, 182.963931, 143.137656)">30</text>
</g>
</g>
<g id="xtick_5">
<g id="line2d_5">
<g>
<use xlink:href="#m662ed05794" x="219.196908" y="128.305312"/>
</g>
</g>
<g id="text_5">
<text class="m-label" style="text-anchor: middle" x="219.196908" y="143.137656" transform="rotate(-0, 219.196908, 143.137656)">40</text>
</g>
</g>
<g id="xtick_6">
<g id="line2d_6">
<g>
<use xlink:href="#m662ed05794" x="255.429884" y="128.305312"/>
</g>
</g>
<g id="text_6">
<text class="m-label" style="text-anchor: middle" x="255.429884" y="143.137656" transform="rotate(-0, 255.429884, 143.137656)">50</text>
</g>
</g>
<g id="xtick_7">
<g id="line2d_7">
<g>
<use xlink:href="#m662ed05794" x="291.662861" y="128.305312"/>
</g>
</g>
<g id="text_7">
<text class="m-label" style="text-anchor: middle" x="291.662861" y="143.137656" transform="rotate(-0, 291.662861, 143.137656)">60</text>
</g>
</g>
<g id="text_8">
<text class="m-label" style="text-anchor: middle" x="193.1925" y="157.225" transform="rotate(-0, 193.1925, 157.225)">ms</text>
</g>
</g>
<g id="matplotlib.axis_2">
<g id="ytick_1">
<g id="line2d_8">
<defs>
<path id="m104b56c38f" d="M 0 0 L -3.5 0" class="m-line"/>
</defs>
<g>
<use xlink:href="#m104b56c38f" x="74.265" y="41.416228"/>
</g>
</g>
<g id="text_9">
<text class="m-label" transform="translate(30.775937 40.183884)">Original</text>
<text class="m-label" transform="translate(67.265 52.047384)"/>
</g>
</g>
<g id="ytick_2">
<g id="line2d_9">
<g>
<use xlink:href="#m104b56c38f" x="74.265" y="65.619316"/>
</g>
</g>
<g id="text_10">
<text class="m-label" transform="translate(19.335938 64.491816)">Optimized</text>
<text class="m-label" transform="translate(67.265 76.145628)"/>
</g>
</g>
<g id="ytick_3">
<g id="line2d_10">
<g>
<use xlink:href="#m104b56c38f" x="74.265" y="89.822403"/>
</g>
</g>
<g id="text_11">
<text class="m-label" transform="translate(30.775937 88.590059)">Original</text>
<text class="m-label" transform="translate(67.265 100.453559)"/>
</g>
</g>
<g id="ytick_4">
<g id="line2d_11">
<g>
<use xlink:href="#m104b56c38f" x="74.265" y="114.025491"/>
</g>
</g>
<g id="text_12">
<text class="m-label" transform="translate(19.335938 112.897991)">Optimized</text>
<text class="m-label" transform="translate(67.265 124.551803)"/>
</g>
</g>
</g>
<g id="text_13">
<text class="m-label m-dim" transform="translate(67.265 39.588148)"/>
<text class="m-label m-dim" transform="translate(27.762969 51.24196)">Intel 630</text>
</g>
<g id="text_14">
<text class="m-label m-dim" transform="translate(67.265 63.791236)"/>
<text class="m-label m-dim" transform="translate(27.762969 75.445048)">Intel 630</text>
</g>
<g id="text_15">
<text class="m-label m-dim" transform="translate(67.265 87.889479)"/>
<text class="m-label m-dim" transform="translate(11.88 99.543292)">AMD Vega M</text>
</g>
<g id="text_16">
<text class="m-label m-dim" transform="translate(67.265 112.092567)"/>
<text class="m-label m-dim" transform="translate(11.88 123.74638)">AMD Vega M</text>
</g>
<g id="text_17">
<text class="m-title" style="text-anchor: middle" x="193.1925" y="21.136406" transform="rotate(-0, 193.1925, 21.136406)">Rendering 7.5 M triangles, GPU time</text>
</g>
</g>
</g>
<defs>
<clipPath id="p93e1764265">
<rect x="74.265" y="27.136406" width="237.855" height="101.168906"/>
</clipPath>
</defs>
</svg>
</div>
</div>
<div class="m-col-m-6">
<div class="m-plot">
<svg viewBox="0 0 324 171.36">
<defs>
<style type="text/css">*{stroke-linejoin: round; stroke-linecap: butt}</style>
</defs>
<g id="figure_1">
<g id="axes_1">
<g id="patch_1">
<path d="M 74.265 128.305312 L 312.12 128.305312 L 312.12 27.292813 L 74.265 27.292813 z" class="m-background"/>
</g>
<g id="plot5-value0-0"><title>0.82 vertex shader invocations / all submitted vertices</title>
<path d="M 74.265 31.88429 L 292.798445 31.88429 L 292.798445 51.216826 L 74.265 51.216826 z" clip-path="url(#p7ec317eb79)" class="m-bar m-info"/>
</g>
<g id="plot5-value0-1"><title>0.21 vertex shader invocations / all submitted vertices</title>
<path d="M 74.265 56.04996 L 130.230882 56.04996 L 130.230882 75.382496 L 74.265 75.382496 z" clip-path="url(#p7ec317eb79)" class="m-bar m-success"/>
</g>
<g id="plot5-value0-2"><title>0.85 vertex shader invocations / all submitted vertices</title>
<path d="M 74.265 80.215629 L 300.793571 80.215629 L 300.793571 99.548165 L 74.265 99.548165 z" clip-path="url(#p7ec317eb79)" class="m-bar m-info"/>
</g>
<g id="plot5-value0-3"><title>0.24 vertex shader invocations / all submitted vertices</title>
<path d="M 74.265 104.381299 L 138.226008 104.381299 L 138.226008 123.713835 L 74.265 123.713835 z" clip-path="url(#p7ec317eb79)" class="m-bar m-success"/>
</g>
<g id="matplotlib.axis_1">
<g id="xtick_1">
<g id="line2d_1">
<defs>
<path id="m475952c3eb" d="M 0 0 L 0 3.5" class="m-line"/>
</defs>
<g>
<use xlink:href="#m475952c3eb" x="74.265" y="128.305312"/>
</g>
</g>
<g id="text_1">
<text class="m-label" style="text-anchor: middle" x="74.265" y="143.137656" transform="rotate(-0, 74.265, 143.137656)">0.0</text>
</g>
</g>
<g id="xtick_2">
<g id="line2d_2">
<g>
<use xlink:href="#m475952c3eb" x="127.56584" y="128.305312"/>
</g>
</g>
<g id="text_2">
<text class="m-label" style="text-anchor: middle" x="127.56584" y="143.137656" transform="rotate(-0, 127.56584, 143.137656)">0.2</text>
</g>
</g>
<g id="xtick_3">
<g id="line2d_3">
<g>
<use xlink:href="#m475952c3eb" x="180.866681" y="128.305312"/>
</g>
</g>
<g id="text_3">
<text class="m-label" style="text-anchor: middle" x="180.866681" y="143.137656" transform="rotate(-0, 180.866681, 143.137656)">0.4</text>
</g>
</g>
<g id="xtick_4">
<g id="line2d_4">
<g>
<use xlink:href="#m475952c3eb" x="234.167521" y="128.305312"/>
</g>
</g>
<g id="text_4">
<text class="m-label" style="text-anchor: middle" x="234.167521" y="143.137656" transform="rotate(-0, 234.167521, 143.137656)">0.6</text>
</g>
</g>
<g id="xtick_5">
<g id="line2d_5">
<g>
<use xlink:href="#m475952c3eb" x="287.468361" y="128.305312"/>
</g>
</g>
<g id="text_5">
<text class="m-label" style="text-anchor: middle" x="287.468361" y="143.137656" transform="rotate(-0, 287.468361, 143.137656)">0.8</text>
</g>
</g>
<g id="text_6">
<text class="m-label" style="text-anchor: middle" x="193.1925" y="157.225" transform="rotate(-0, 193.1925, 157.225)">vertex shader invocations / all submitted vertices</text>
</g>
</g>
<g id="matplotlib.axis_2">
<g id="ytick_1">
<g id="line2d_6">
<defs>
<path id="m64697597e0" d="M 0 0 L -3.5 0" class="m-line"/>
</defs>
<g>
<use xlink:href="#m64697597e0" x="74.265" y="41.550558"/>
</g>
</g>
<g id="text_7">
<text class="m-label" transform="translate(30.775937 40.318214)">Original</text>
<text class="m-label" transform="translate(67.265 52.181714)"/>
</g>
</g>
<g id="ytick_2">
<g id="line2d_7">
<g>
<use xlink:href="#m64697597e0" x="74.265" y="65.716228"/>
</g>
</g>
<g id="text_8">
<text class="m-label" transform="translate(19.335938 64.588728)">Optimized</text>
<text class="m-label" transform="translate(67.265 76.24254)"/>
</g>
</g>
<g id="ytick_3">
<g id="line2d_8">
<g>
<use xlink:href="#m64697597e0" x="74.265" y="89.881897"/>
</g>
</g>
<g id="text_9">
<text class="m-label" transform="translate(30.775937 88.649554)">Original</text>
<text class="m-label" transform="translate(67.265 100.513054)"/>
</g>
</g>
<g id="ytick_4">
<g id="line2d_9">
<g>
<use xlink:href="#m64697597e0" x="74.265" y="114.047567"/>
</g>
</g>
<g id="text_10">
<text class="m-label" transform="translate(19.335938 112.920067)">Optimized</text>
<text class="m-label" transform="translate(67.265 124.57388)"/>
</g>
</g>
</g>
<g id="text_11">
<text class="m-label m-dim" transform="translate(67.265 39.720607)"/>
<text class="m-label m-dim" transform="translate(27.762969 51.374419)">Intel 630</text>
</g>
<g id="text_12">
<text class="m-label m-dim" transform="translate(67.265 63.886277)"/>
<text class="m-label m-dim" transform="translate(27.762969 75.540089)">Intel 630</text>
</g>
<g id="text_13">
<text class="m-label m-dim" transform="translate(67.265 87.947103)"/>
<text class="m-label m-dim" transform="translate(11.88 99.600915)">AMD Vega M</text>
</g>
<g id="text_14">
<text class="m-label m-dim" transform="translate(67.265 112.112773)"/>
<text class="m-label m-dim" transform="translate(11.88 123.766585)">AMD Vega M</text>
</g>
<g id="text_15">
<text class="m-title" style="text-anchor: middle" x="193.1925" y="21.292813" transform="rotate(-0, 193.1925, 21.292813)">Rendering 7.5 M triangles, vertex fetch ratio</text>
</g>
</g>
</g>
<defs>
<clipPath id="p7ec317eb79">
<rect x="74.265" y="27.292813" width="237.855" height="101.0125"/>
</clipPath>
</defs>
</svg>
</div>
</div>
</div>
<p>To sim­u­late a real-world scen­ario, the render was de­lib­er­ately done in a
de­fault cam­era loc­a­tion, with a large part of the mod­el be­ing out of the view.
Both meas­ure­ments are done us­ing the (also re­cently ad­ded)
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1DebugTools.html#a98e73c424d08570bb84c0058ebafdaae">De­bug­Tools::GLFramePro­filer</a>, and while GPU time meas­ures the time GPU
spent ren­der­ing one frame, ver­tex fetch ra­tio shows how many times a ver­tex
shader was ex­ecuted com­pared to how many ver­tices were sub­mit­ted in total. For
a non-in­dexed tri­angle mesh the value would be ex­actly 1.0, with in­dexed meshes
the lower the value is the bet­ter is ver­tex re­use from the post-trans­form
ver­tex cache<a class="m-footnote" href="#id27" id="id26">11</a>. The res­ults are vastly dif­fer­ent for dif­fer­ent GPUs, and
while mesh­op­tim­izer helped re­duce the amount of ver­tex shader in­voc­a­tions for
both equally, it helped mainly the In­tel GPU. One con­clu­sion could be that the
In­tel GPU is bot­tle­necked in ALU pro­cessing, while the AMD card not so much and
thus re­du­cing ver­tex shader in­voc­a­tions doesn’t mat­ter that much. That said,
the shader used here was a simple <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1Shaders.html#aa8a252d128449a517288dc461d21348e">Shaders::Phong</a>, and the im­pact could
be likely much big­ger for the AMD card with com­plex PBR shaders.</p>
<dl class="m-footnote">
<dt id="id27">11</a>.</dt>
<dd><span class="m-footnote"><a href="#id26">^</a></span> Un­for­tu­nately the <a href="https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_pipeline_statistics_query.txt">AR­B_pipeline_s­tat­ist­ic­s_query</a> ex­ten­sion
doesn’t provide a way to query the count of in­dices sub­mit­ted, so it’s not
pos­sible to know the <em>over­fetch</em> ra­tio — how many times the ver­tex shader
had to be ex­ecuted for a single ver­tex. This is only pos­sible if the
sub­mit­ted in­dices would be coun­ted on the en­gine side.</dd>
</dl>
<p>Apart from the above, the <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1MeshOptimizerSceneConverter.html">Mesh­Op­tim­izer­SceneCon­vert­er</a>
plu­gin can also op­tion­ally decim­ate meshes. As that is a de­struct­ive op­er­a­tion,
it’s not en­abled by de­fault, but you can en­able and con­fig­ure it us­ing
plu­gin-spe­cif­ic op­tions:</p>
<pre class="m-code"><span class="n">meshoptimizer</span><span class="o">-></span><span class="n">configuration</span><span class="p">().</span><span class="n">setValue</span><span class="p">(</span><span class="s">"simplify"</span><span class="p">,</span> <span class="nb">true</span><span class="p">);</span>
<span class="n">meshoptimizer</span><span class="o">-></span><span class="n">configuration</span><span class="p">().</span><span class="n">setValue</span><span class="p">(</span><span class="s">"simplifyTargetIndexCountThreshold"</span><span class="p">,</span> <span class="mf">0.5f</span><span class="p">);</span>
<span class="n">Containers</span><span class="o">::</span><span class="n">Optional</span><span class="o"><</span><span class="n">Trade</span><span class="o">::</span><span class="n">MeshData</span><span class="o">></span> <span class="n">simplified</span> <span class="o">=</span> <span class="n">meshoptimizer</span><span class="o">-></span><span class="n">convert</span><span class="p">(</span><span class="n">mesh</span><span class="p">);</span></pre>
<p>To­geth­er with the mesh pro­cessing plu­gins, and sim­il­arly to im­age con­vert­ers,
there’s a new <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/magnum-sceneconverter.html">mag­num-scenecon­vert­er</a> com­mand-line
tool that makes it pos­sible to use these plu­gins to­geth­er with vari­ous mesh
tools dir­ectly on scene files. Its use is quite lim­ited at this point as the
only sup­por­ted out­put format is PLY (via
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1StanfordSceneConverter.html">Stan­ford­SceneCon­vert­er</a>) but the tool
will gradu­ally be­come more power­ful, with more out­put formats. As an ex­ample,
here it first prints an info about the mesh, then takes just the first
at­trib­ute, dis­card­ing per-face nor­mals, re­moves du­plic­ate ver­tices, pro­cesses
the data with mesh­op­tim­izer on de­fault set­tings and saves the out­put to a PLY:</p>
<figure class="m-code-figure">
<pre class="m-code">magnum-sceneconverter dragon.stl --info</pre>
<pre class="m-console-wrap m-nopad m-inverted m-console"><span class="go">Mesh 0:</span>
<span class="go"> Level 0: MeshPrimitive::Triangles, 6509526 vertices (152567.0 kB)</span>
<span class="hll"><span class="go"> Offset 0: Trade::MeshAttribute::Position @ VertexFormat::Vector3, stride 24</span>
</span><span class="hll"><span class="go"> Offset 12: Trade::MeshAttribute::Normal @ VertexFormat::Vector3, stride 24</span>
</span></pre>
</figure>
<figure class="m-code-figure">
<pre class="m-code">magnum-sceneconverter dragon.stl dragon.ply <span class="se">\</span>
--only-attributes <span class="s2">"0"</span> <span class="se">\</span>
--remove-duplicates <span class="se">\</span>
--converter MeshOptimizerSceneConverter -v</pre>
<pre class="m-console-wrap m-nopad m-inverted m-console"><span class="go">Trade::AnySceneImporter::openFile(): using StlImporter</span>
<span class="hll"><span class="go">Duplicate removal: 6509526 -> 1084923 vertices</span>
</span><span class="hll"><span class="go">Trade::MeshOptimizerSceneConverter::convert(): processing stats:</span>
</span><span class="hll"><span class="go"> vertex cache:</span>
</span><span class="hll"><span class="go"> 5096497 -> 1502463 transformed vertices</span>
</span><span class="hll"><span class="go"> 1 -> 1 executed warps</span>
</span><span class="hll"><span class="go"> ACMR 2.34879 -> 0.69243</span>
</span><span class="hll"><span class="go"> ATVR 4.69757 -> 1.38486</span>
</span><span class="hll"><span class="go"> vertex fetch:</span>
</span><span class="hll"><span class="go"> 228326592 -> 24462720 bytes fetched</span>
</span><span class="hll"><span class="go"> overfetch 17.5378 -> 1.87899</span>
</span><span class="hll"><span class="go"> overdraw:</span>
</span><span class="hll"><span class="go"> 107733 -> 102292 shaded pixels</span>
</span><span class="hll"><span class="go"> 101514 -> 101514 covered pixels</span>
</span><span class="hll"><span class="go"> overdraw 1.06126 -> 1.00766</span>
</span><span class="go">Trade::AnySceneConverter::convertToFile(): using StanfordSceneConverter</span></pre>
</figure>
<p>The <code>-v</code> op­tion trans­lates to <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1Trade.html#ac3f3c2001df6c6e4761ed6e4e70d700dad4a9fa383ab700c5bdd6f31cf7df0faf">Trade::SceneCon­vert­er­Flag::Verb­ose</a>,
which is an­oth­er new fea­ture that en­ables plu­gins to print ex­ten­ded info about
im­port or pro­cessing. In case of
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1MeshOptimizerSceneConverter.html">Mesh­Op­tim­izer­SceneCon­vert­er</a> it
ana­lyzes the mesh be­fore and after, cal­cu­lat­ing <abbr title="ACMR">av­er­age cache miss ra­tio</abbr>,
over­draw and oth­er use­ful met­rics for mesh ren­der­ing ef­fi­ciency.</p>
</section>
<section id="going-further-custom-attributes-face-and-edge-properties-meshlets">
<h2><a href="#going-further-custom-attributes-face-and-edge-properties-meshlets">Go­ing fur­ther — cus­tom at­trib­utes, face and edge prop­er­ties, mesh­lets</a></h2>
<p>To have the mesh data rep­res­ent­a­tion truly fu­ture-proofed, it isn’t enough to
lim­it its sup­port to just the “clas­sic­al” in­dexed meshes with at­trib­utes of
pre­defined se­mantics and a (broad, but hard­coded) set of ver­tex formats.</p>
<p>Re­gard­ing ver­tex formats, sim­il­arly as is done <a href="https://blog.magnum.graphics/announcements/2018.04/#api-independent-asset-management">since 2018.04 for pixel formats</a>,
a mesh can con­tain any at­trib­ute in an im­ple­ment­a­tion-spe­cif­ic format. One
ex­ample could be nor­mals packed in­to for ex­ample <a href="https://www.khronos.org/registry/vulkan/specs/1.1-extensions/man/html/VkVkFormat.html">VK_­FORM­AT_A2R10G10B10_S­NORM_PACK­32</a>
(which cur­rently doesn’t have a gen­er­ic equi­val­ent in <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum.html#a174f1c4f12e98745bee3db77239aa53f">Ver­tex­Format</a>) —
code that con­sumes the <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1MeshData.html">Trade::Mesh­Data</a> in­stance can then un­wrap the
im­ple­ment­a­tion-spe­cif­ic ver­tex format and pass it dir­ectly to the cor­res­pond­ing
GPU API. Note that be­cause the lib­rary has no way to know any­thing about sizes
of im­ple­ment­a­tion-spe­cif­ic formats, such in­stances have only lim­ited use in
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1MeshTools.html">MeshTools</a> al­gorithms.</p>
<pre class="m-code"><span class="n">Trade</span><span class="o">::</span><span class="n">MeshAttributeData</span> <span class="n">normals</span><span class="p">{</span><span class="n">Trade</span><span class="o">::</span><span class="n">MeshAttribute</span><span class="o">::</span><span class="n">Normal</span><span class="p">,</span>
<span class="n">vertexFormatWrap</span><span class="p">(</span><span class="n">VK_FORMAT_A2R10G10B10_UNORM_PACK32</span><span class="p">),</span> <span class="n">data</span><span class="p">};</span></pre>
<p>Meshes don’t stop with just points, lines or tri­angles any­more. To­geth­er with
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1AbstractImporter.html#a9d5e3f27944ce9fa33d64d49b780fa14">Trade::Ab­strac­tIm­port­er::mesh()</a> al­low­ing a second para­met­er spe­cify­ing
mesh level (sim­il­arly to im­age mip levels), this opens new pos­sib­il­it­ies —
STL and PLY im­port­ers already use it to re­tain per-face prop­er­ties, as shown
be­low on one of the <a href="https://www.pbrt.org/scenes-v3.html">pbrt-v3 sample scenes</a>:</p>
<figure class="m-code-figure">
<pre class="m-code"><span class="c1"># Disabling the perFaceToPerVertex option to keep face properties as-is</span>
magnum-sceneconverter dragon_remeshed.ply --info <span class="se">\</span>
--importer StanfordImporter -i <span class="nv">perFaceToPerVertex</span><span class="o">=</span><span class="nb">false</span></pre>
<pre class="m-console-wrap m-nopad m-inverted m-console"><span class="go">Mesh 0 (referenced by 0 objects):</span>
<span class="go"> Level 0: MeshPrimitive::Triangles, 924422 vertices (10833.1 kB)</span>
<span class="go"> 5545806 indices @ MeshIndexType::UnsignedInt (21663.3 kB)</span>
<span class="go"> Offset 0: Trade::MeshAttribute::Position @ VertexFormat::Vector3, stride 12</span>
<span class="hll"><span class="go"> Level 1: MeshPrimitive::Faces, 1848602 vertices (21663.3 kB)</span>
</span><span class="hll"><span class="go"> Offset 0: Trade::MeshAttribute::Normal @ VertexFormat::Vector3, stride 12</span>
</span></pre>
</figure>
<p>Among oth­er pos­sib­il­it­ies is us­ing <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum.html#a52379a327f55566ec9aa4c72eaacbc0fa484afb8d54ec549700b1fe96417519a9">MeshPrim­it­ive::Edges</a> to store meshes
in half-edge rep­res­ent­a­tion (the end­lessly-flex­ible PLY format even has sup­port
for <a href="https://stackoverflow.com/a/58648176">per-edge data</a>, al­though the
im­port­er doesn’t sup­port that yet), <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum.html#a52379a327f55566ec9aa4c72eaacbc0fa1c7d5bb21f0c351e584f3a4109ecdc76">MeshPrim­it­ive::In­stances</a> to store
in­stance data (for ex­ample to im­ple­ment the pro­posed glTF
<a href="https://github.com/KhronosGroup/glTF/issues/1691">EX­T_mesh_g­pu_in­stan­cing</a> ex­ten­sion) or simply
provide ad­di­tion­al LOD levels (glTF has a <a href="https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/MSFT_lod">MSFT_lod</a>
ex­ten­sion for this).</p>
<p class="m-transition">~ ~ ~</p>
<div class="m-col-m-5 m-right-m m-container-inflate">
<figure class="m-code-figure">
<pre class="m-text m-small m-code"><span class="k">struct</span> <span class="n">meshopt_Meshlet</span> <span class="p">{</span>
<span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">vertices</span><span class="p">[</span><span class="mi">64</span><span class="p">];</span>
<span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">indices</span><span class="p">[</span><span class="mi">126</span><span class="p">][</span><span class="mi">3</span><span class="p">];</span>
<span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">triangle_count</span><span class="p">;</span>
<span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">vertex_count</span><span class="p">;</span>
<span class="p">};</span></pre>
<p class="m-text m-small m-dim m-noindent">Source: <a href="https://github.com/zeux/meshoptimizer/blob/2b103f2409329c023b8b50ce34085b8a9ae4e24b/src/meshoptimizer.h/issues/L358-L376">mesh­op­tim­izer.h</a></p>
</figure>
</div>
<p>Ul­ti­mately, we’re not lim­ited to pre­defined prim­it­ive and at­trib­ute types
either. The most prom­in­ent ex­ample of us­ing this newly gained flex­ib­il­ity is
<a href="https://devblogs.nvidia.com/introduction-turing-mesh-shaders/">mesh shaders and mesh­lets</a>.
Mesh­lets are a tech­nique that is be­com­ing more and more im­port­ant for deal­ing
with heavy geo­metry, and <a href="https://github.com/zeux/meshoptimizer">mesh­op­tim­izer</a> has an
ex­per­i­ment­al sup­port for those<a class="m-footnote" href="#id29" id="id28">12</a>. For giv­en in­put it gen­er­ates a se­quence
of stat­ic­ally-defined <code class="cpp m-code"><span class="n">meshopt_Meshlet</span></code> struc­tures that are then meant to
be fed straight to the GPU.</p>
<p>De­scrib­ing such data in a <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1MeshData.html">Trade::Mesh­Data</a> in­stance is a mat­ter of
de­fin­ing a set of cus­tom at­trib­ute names and list­ing their off­sets, types and
ar­ray sizes, as shown be­low. While a bit verb­ose at first look, an ad­vant­age of
be­ing able to spe­cify the lay­out dy­nam­ic­ally is that the same at­trib­utes can
work for rep­res­ent­a­tions from oth­er tools as well, such as
<a href="https://github.com/JarkkoPFC/meshlete">mesh­lete</a>.</p>
<pre class="m-inverted m-code"><span class="cm">/* Pick any numbers that don't conflict with your other custom attributes */</span>
<span class="k">constexpr</span> <span class="k">auto</span> <span class="n">Meshlet</span> <span class="o">=</span> <span class="n">meshPrimitiveWrap</span><span class="p">(</span><span class="mh">0xabcd</span><span class="p">);</span>
<span class="k">constexpr</span> <span class="k">auto</span> <span class="n">MeshletVertices</span> <span class="o">=</span> <span class="n">Trade</span><span class="o">::</span><span class="n">meshAttributeCustom</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
<span class="k">constexpr</span> <span class="k">auto</span> <span class="n">MeshletIndices</span> <span class="o">=</span> <span class="n">Trade</span><span class="o">::</span><span class="n">meshAttributeCustom</span><span class="p">(</span><span class="mi">2</span><span class="p">);</span>
<span class="k">constexpr</span> <span class="k">auto</span> <span class="n">MeshletTriangleCount</span> <span class="o">=</span> <span class="n">Trade</span><span class="o">::</span><span class="n">meshAttributeCustom</span><span class="p">(</span><span class="mi">3</span><span class="p">);</span>
<span class="k">constexpr</span> <span class="k">auto</span> <span class="n">MeshletVertexCount</span> <span class="o">=</span> <span class="n">Trade</span><span class="o">::</span><span class="n">meshAttributeCustom</span><span class="p">(</span><span class="mi">4</span><span class="p">);</span>
<span class="hll"><span class="n">Trade</span><span class="o">::</span><span class="n">MeshData</span> <span class="n">meshlets</span><span class="p">{</span><span class="n">Meshlet</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">meshletData</span><span class="p">),</span> <span class="p">{</span>
</span><span class="hll"> <span class="n">Trade</span><span class="o">::</span><span class="n">MeshAttributeData</span><span class="p">{</span><span class="n">MeshletVertices</span><span class="p">,</span> <span class="n">VertexFormat</span><span class="o">::</span><span class="n">UnsignedInt</span><span class="p">,</span>
</span><span class="hll"> <span class="n">offsetof</span><span class="p">(</span><span class="n">meshopt_Meshlet</span><span class="p">,</span> <span class="n">vertices</span><span class="p">),</span> <span class="mi">0</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">meshopt_Meshlet</span><span class="p">),</span> <span class="mi">64</span><span class="p">},</span>
</span><span class="hll"> <span class="n">Trade</span><span class="o">::</span><span class="n">MeshAttributeData</span><span class="p">{</span><span class="n">MeshletIndices</span><span class="p">,</span> <span class="n">VertexFormat</span><span class="o">::</span><span class="n">Vector3ub</span><span class="p">,</span>
</span><span class="hll"> <span class="n">offsetof</span><span class="p">(</span><span class="n">meshopt_Meshlet</span><span class="p">,</span> <span class="n">indices</span><span class="p">),</span> <span class="mi">0</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">meshopt_Meshlet</span><span class="p">),</span> <span class="mi">126</span><span class="p">},</span>
</span><span class="hll"> <span class="n">Trade</span><span class="o">::</span><span class="n">MeshAttributeData</span><span class="p">{</span><span class="n">MeshletTriangleCount</span><span class="p">,</span> <span class="n">VertexFormat</span><span class="o">::</span><span class="n">UnsignedByte</span><span class="p">,</span>
</span><span class="hll"> <span class="n">offsetof</span><span class="p">(</span><span class="n">meshopt_Meshlet</span><span class="p">,</span> <span class="n">triangle_count</span><span class="p">),</span> <span class="mi">0</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">meshopt_Meshlet</span><span class="p">)},</span>
</span><span class="hll"> <span class="n">Trade</span><span class="o">::</span><span class="n">MeshAttributeData</span><span class="p">{</span><span class="n">MeshletVertexCount</span><span class="p">,</span> <span class="n">VertexFormat</span><span class="o">::</span><span class="n">UnsignedByte</span><span class="p">,</span>
</span><span class="hll"> <span class="n">offsetof</span><span class="p">(</span><span class="n">meshopt_Meshlet</span><span class="p">,</span> <span class="n">vertex_count</span><span class="p">),</span> <span class="mi">0</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">meshopt_Meshlet</span><span class="p">)},</span>
</span><span class="hll"><span class="p">},</span> <span class="n">meshletCount</span><span class="p">};</span>
</span></pre>
<p>One im­port­ant thing to note is the <em>ar­ray at­trib­utes</em> — those are ac­cessed
with a spe­cial syn­tax, and give you a 2D view in­stead of a 1D one:</p>
<pre class="m-code"><span class="n">Containers</span><span class="o">::</span><span class="n">StridedArrayView1D</span><span class="o"><</span><span class="k">const</span> <span class="n">UnsignedByte</span><span class="o">></span> <span class="n">triangleCount</span> <span class="o">=</span>
<span class="n">meshlets</span><span class="p">.</span><span class="n">attribute</span><span class="o"><</span><span class="n">UnsignedByte</span><span class="o">></span><span class="p">(</span><span class="n">MeshletTriangleCount</span><span class="p">);</span>
<span class="n">Containers</span><span class="o">::</span><span class="n">StridedArrayView2D</span><span class="o"><</span><span class="k">const</span> <span class="n">Vector3ub</span><span class="o">></span> <span class="n">indices</span> <span class="o">=</span>
<span class="n">meshlets</span><span class="p">.</span><span class="n">attribute</span><span class="o"><</span><span class="n">Vector3ub</span><span class="p">[]</span><span class="o">></span><span class="p">(</span><span class="n">MeshletIndices</span><span class="p">);</span>
<span class="k">for</span><span class="p">(</span><span class="n">Vector3ub</span> <span class="nl">triangle</span><span class="p">:</span> <span class="n">indices</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">prefix</span><span class="p">(</span><span class="n">triangleCount</span><span class="p">[</span><span class="n">i</span><span class="p">]))</span> <span class="p">{</span>
<span class="c1">// do something with each triangle of meshlet i …</span>
<span class="p">}</span></pre>
<dl class="m-footnote">
<dt id="id29">12</a>.</dt>
<dd><span class="m-footnote"><a href="#id28">^</a></span> Not men­tioned in the pro­ject README, you need to look dir­ectly
<a href="https://github.com/zeux/meshoptimizer/blob/2b103f2409329c023b8b50ce34085b8a9ae4e24b/src/meshoptimizer.h/issues/L358-L376">in the source</a>.
At the time of writ­ing, mesh­let gen­er­a­tion isn’t in­teg­rated in­to the
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1MeshOptimizerSceneConverter.html">Mesh­Op­tim­izer­SceneCon­vert­er</a>
plu­gin yet — but it’ll be, once I get a hard­ware to test the whole mesh
shader pipeline on. If <em>you</em> want to play with them, there’s a re­cent
<a href="https://www.geeks3d.com/20200519/introduction-to-mesh-shaders-opengl-and-vulkan/">in­tro­duc­tion on Geek­s3D</a>
cov­er­ing both OpenGL and Vulkan.</dd>
</dl>
</section>
<section id="visualize-the-happiness-of-your-data">
<h2><a href="#visualize-the-happiness-of-your-data">Visu­al­ize the hap­pi­ness of your data</a></h2>
<p>When work­ing with mesh data of vary­ing qual­ity and com­plex­ity, it’s of­ten
needed to know not only how the mesh renders, but also <em>why</em> it renders that
way. The <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1Shaders.html#a1e9971bdd252b39180d0381aee1e06b3">Shaders::MeshVisu­al­izer</a> got
ex­ten­ded to have both a 2D and 3D vari­ant and it can now visu­al­ize not just
wire­frame, but also tan­gent space<a class="m-footnote" href="#id35" id="id30">13</a> — use­ful when you need to know why
your light­ing or a nor­mal map is off —, ob­ject ID for se­mant­ic an­nota­tions or
for ex­ample when you have mul­tiple meshes batched to­geth­er<a class="m-footnote" href="#id36" id="id31">14</a>, and fi­nally
two simple but very im­port­ant prop­er­ties — prim­it­ive and ver­tex ID.</p>
<div class="m-imagegrid m-container-inflate">
<div>
<figure style="width: 60.770%">
<a href="https://static.magnum.graphics/img/blog/announcements/new-geometry-pipeline/tbn.jpg"><img src="https://static.magnum.graphics/img/blog/announcements/new-geometry-pipeline/tbn.jpg" /><figcaption>Tan­gent, bit­an­gent and nor­mal visu­al­iz­a­tion</figcaption>
</a>
</figure>
<figure style="width: 39.230%">
<a href="https://static.magnum.graphics/img/blog/announcements/new-geometry-pipeline/object-id.jpg"><img src="https://static.magnum.graphics/img/blog/announcements/new-geometry-pipeline/object-id.jpg" /><figcaption>Ob­ject ID visu­al­iz­a­tion</figcaption>
</a>
</figure>
</div>
<div>
<figure style="width: 33.120%">
<a href="https://static.magnum.graphics/img/blog/announcements/new-geometry-pipeline/primitive-id.jpg"><img src="https://static.magnum.graphics/img/blog/announcements/new-geometry-pipeline/primitive-id.jpg" /><figcaption>Prim­it­ive ID visu­al­iz­a­tion</figcaption>
</a>
</figure>
<figure style="width: 66.880%">
<a href="https://static.magnum.graphics/img/blog/announcements/new-geometry-pipeline/vertex-id.jpg"><img src="https://static.magnum.graphics/img/blog/announcements/new-geometry-pipeline/vertex-id.jpg" /><figcaption>Ver­tex ID visu­al­iz­a­tion</figcaption>
</a>
</figure>
</div>
</div>
<p>On the truck wheel<a class="m-footnote" href="#id37" id="id32">15</a> above you can see a very “rain­bowy” prim­it­ive ID
visu­al­iz­a­tion, which hints that the ver­tices are not rendered in an or­der that
would make good use of the ver­tex cache (and which can <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1MeshOptimizerSceneConverter.html">Mesh­Op­tim­izer­SceneCon­vert­er</a>
help with). Ver­tex ID, on the oth­er hand, can point to dis­con­tinu­it­ies in the
mesh — even though the blanket<a class="m-footnote" href="#id34" id="id33">3</a> above would look like a smooth
con­tinu­ous mesh to a na­ked eye, the visu­al­iz­a­tion un­cov­ers that al­most none of
the tri­angles share a com­mon ver­tex, which will likely cause is­sues for ex­ample
when decim­at­ing the mesh or us­ing it for col­li­sion de­tec­tion.</p>
<p>Sup­ple­ment­ary to the mesh visu­al­izer is a gal­lery of col­or maps for bal­anced
and eas­ily re­cog­niz­able visu­al­iz­a­tions. The above im­ages were cre­ated us­ing the
<a href="https://ai.googleblog.com/2019/08/turbo-improved-rainbow-colormap-for.html">Turbo col­or map</a>
and <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1DebugTools_1_1ColorMap.html">De­bug­Tools::ColorMap</a> provides four more that you can choose from.</p>
<p>Lastly, and as already men­tioned above, you’re en­cour­aged to use
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1DebugTools_1_1FrameProfiler.html">De­bug­Tools::FramePro­filer</a> to meas­ure vari­ous as­pects of mesh ren­der­er,
both on the CPU and GPU side and with built­in sup­port for cus­tom meas­ure­ments
and delayed quer­ies to avoid stalls. Hook­ing up this pro­filer doesn’t mean you
sud­denly need to deal with UI and text ren­der­ing — it can simply print its
out­put to a ter­min­al as well, re­fresh­ing it­self every once in a while:</p>
<pre class="m-console"><span class="g g-AnsiBrightDefault">Last</span><span class="g g-AnsiBrightCyan"> 50</span><span class="g g-AnsiBrightDefault"> frames:</span>
<span class="g g-AnsiBrightDefault"> Frame time:</span><span class="g g-AnsiBrightGreen"> 16.65</span> ms
<span class="g g-AnsiBrightDefault"> CPU duration:</span><span class="g g-AnsiBrightGreen"> 14.72</span> ms
<span class="g g-AnsiBrightDefault"> GPU duration:</span><span class="g g-AnsiBrightGreen"> 10.89</span> ms
<span class="g g-AnsiBrightDefault"> Vertex fetch ratio:</span><span class="g g-AnsiBrightGreen"> 0.24</span>
<span class="g g-AnsiBrightDefault"> Primitives clipped:</span><span class="g g-AnsiBrightGreen"> 59.67</span> %</pre>
<dl class="m-footnote">
<dt id="id34">3.</dt>
<dd><span class="m-footnote">^ <a href="#id5">a</a> <a href="#id25">b</a> <a href="#id33">c</a></span> The <a href="https://niessner.github.io/Matterport/">Mat­ter­port3D in­door en­vir­on­ment scans</a>
were used as a source for vari­ous tim­ings, bench­marks and visu­al­iz­a­tions</dd>
<dt id="id35">13</a>.</dt>
<dd><span class="m-footnote"><a href="#id30">^</a></span> Mod­el source: <a href="https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/Lantern">Lan­tern</a>
from the glTF Sample Mod­els re­pos­it­ory</dd>
<dt id="id36">14</a>.</dt>
<dd><span class="m-footnote"><a href="#id31">^</a></span> Screen­shot from a se­mantics-an­not­ated scan from the
<a href="https://github.com/facebookresearch/Replica-Dataset">Rep­lica data­set</a></dd>
<dt id="id37">15</a>.</dt>
<dd><span class="m-footnote"><a href="#id32">^</a></span> Mod­el source: <a href="https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/CesiumMilkTruck">Cesi­um Milk Truck</a>
from the glTF Sample Mod­els re­pos­it­ory</dd>
</dl>
</section>
<section id="referencing-external-data-avoiding-copies">
<h2><a href="#referencing-external-data-avoiding-copies">Ref­er­en­cing ex­tern­al data, avoid­ing cop­ies</a></h2>
<p>One of the ubi­quit­ous an­noy­ing prob­lems when deal­ing with STL con­tain­ers is
memory man­age­ment in­flex­ib­il­ity — you can’t really<a class="m-footnote" href="#id39" id="id38">16</a> con­vince a
<a class="m-flat" href="https://en.cppreference.com/w/cpp/container/vector">std::vec­tor</a> to ref­er­ence ex­tern­al memory or, con­versely, re­lease its
stor­age and re­use it else­where. The new <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1MeshData.html">Trade::Mesh­Data</a> (and
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1AnimationData.html">Trade::An­im­a­tionData</a> + <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1ImageData.html">Trade::Im­ageData</a> as well, for that mat­ter)
learned from past mis­takes and can act as a non-own­ing ref­er­ence to ex­tern­al
in­dex and ver­tex buf­fers as well as at­trib­ute de­scrip­tions.</p>
<p>For ex­ample it’s pos­sible store in­dex and ver­tex buf­fer for a par­tic­u­lar mod­el
in con­stant memory and make <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1MeshData.html">Trade::Mesh­Data</a> just ref­er­ence it, without
any al­loc­a­tions or cop­ies. In Mag­num it­self this is used by cer­tain prim­it­ives
such as <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1Primitives.html#a3293ada161344543b8341f486b3f62c6">Prim­it­ives::cube­Sol­id()</a> — since a cube is prac­tic­ally al­ways
the same, it doesn’t make sense to build a copy of it in dy­nam­ic memory every
time.</p>
<p>An­oth­er thing the API was ex­pli­citly de­signed for is shar­ing a single large
buf­fer among mul­tiple meshes — ima­gine a glTF file con­tain­ing sev­er­al
dif­fer­ent meshes, but all shar­ing a single buf­fer that you up­load just once:</p>
<pre class="m-code"><span class="cm">/* Shared for all meshes */</span>
<span class="n">Containers</span><span class="o">::</span><span class="n">ArrayView</span><span class="o"><</span><span class="k">const</span> <span class="kt">char</span><span class="o">></span> <span class="n">indexData</span><span class="p">;</span>
<span class="n">Containers</span><span class="o">::</span><span class="n">ArrayView</span><span class="o"><</span><span class="k">const</span> <span class="kt">char</span><span class="o">></span> <span class="n">vertexData</span><span class="p">;</span>
<span class="n">GL</span><span class="o">::</span><span class="n">Buffer</span> <span class="n">indices</span><span class="p">{</span><span class="n">indexData</span><span class="p">};</span>
<span class="n">GL</span><span class="o">::</span><span class="n">Buffer</span> <span class="n">vertices</span><span class="p">{</span><span class="n">indexData</span><span class="p">};</span>
<span class="n">GL</span><span class="o">::</span><span class="n">Mesh</span> <span class="n">chair</span> <span class="o">=</span> <span class="n">MeshTools</span><span class="o">::</span><span class="n">compile</span><span class="p">(</span><span class="n">chairData</span><span class="p">,</span> <span class="n">indices</span><span class="p">,</span> <span class="n">vertices</span><span class="p">);</span>
<span class="n">GL</span><span class="o">::</span><span class="n">Mesh</span> <span class="n">tree</span> <span class="o">=</span> <span class="n">MeshTools</span><span class="o">::</span><span class="n">compile</span><span class="p">(</span><span class="n">treeData</span><span class="p">,</span> <span class="n">indices</span><span class="p">,</span> <span class="n">vertices</span><span class="p">);</span>
<span class="c1">// …</span></pre>
<p>Lastly, noth­ing pre­vents <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1MeshData.html">Trade::Mesh­Data</a> from work­ing in an “in­verse”
way — first use it to up­load a GPU buf­fer, and then use the same at­trib­ute
lay­out to con­veni­ently per­form modi­fic­a­tions when the buf­fer gets mapped back
to CPU memory later.</p>
<dl class="m-footnote">
<dt id="id39">16</a>.</dt>
<dd><span class="m-footnote"><a href="#id38">^</a></span> Stand­ard Lib­rary design ad­voc­ates would men­tion that you can use a
cus­tom al­loc­at­or to achieve that. While that’s tech­nic­ally true, it’s not
a prac­tic­al solu­tion, con­sid­er­ing the sheer amount of code you need to
write for an al­loc­at­or (when all you really need is a cus­tom de­leter).
Also, have fun con­vin­cing 3rd party vendors that you need all their APIs to
ac­cept <a class="m-flat" href="https://en.cppreference.com/w/cpp/container/vector">std::vec­tor</a>s with cus­tom al­loc­at­ors.</dd>
</dl>
</section>
<section id="a-peek-into-the-future-magnum-s-own-memory-mappable-mesh-format">
<h2><a href="#a-peek-into-the-future-magnum-s-own-memory-mappable-mesh-format">A peek in­to the fu­ture — Mag­num’s own memory-map­pable mesh format</a></h2>
<p>Ex­pand­ing fur­ther on the above-men­tioned abil­ity to ref­er­ence ex­tern­al data,
it’s now pos­sible to have <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1MeshData.html">Trade::Mesh­Data</a> point­ing dir­ectly to con­tents
of a memory-mapped file in a com­pat­ible format, achiev­ing a truly zero-copy
as­set load­ing. This is, to some ex­tent, pos­sible with all three — STL, PLY
and glTF — file formats men­tioned above. A work-in-pro­gress PR en­abling this
is <a href="https://github.com/mosra/magnum/issues/240">mosra/mag­num#240</a>, what I still need to fig­ure out is in­ter­ac­tion
between memory own­er­ship and cus­tom file load­ing call­backs; plus in case of
glTF it re­quires writ­ing a new im­port­er plu­gin based on
<a href="https://github.com/jkuhlmann/cgltf">cgltf</a> as <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1TinyGltfImporter.html">Tiny­Glt­fIm­port­er</a>
(and tiny_gltf in par­tic­u­lar) can’t really be con­vinced to work with ex­tern­al
buf­fers due to its heavy re­li­ance on <a class="m-flat" href="https://en.cppreference.com/w/cpp/container/vector">std::vec­tor</a>s.</p>
<p>At some point I real­ized that even with all flex­ib­il­ity that glTF provides,
it’s still not ideal due to its re­li­ance on JSON, which can have a large im­pact
on down­load sizes of WebAssembly builds.</p>
<p>What would a min­im­al­ist file format tailored for Mag­num look like, if we
re­moved everything that can be re­moved? To avoid com­plex pars­ing and data
lo­gist­ics, the file format should be as close to the bin­ary rep­res­ent­a­tion of
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1MeshData.html">Trade::Mesh­Data</a> as pos­sible, al­low­ing the ac­tu­al pay­load to be used
dir­ectly without any pro­cessing, and the deseri­al­iz­a­tion pro­cess be­ing just a
hand­ful of san­ity and range checks. With that, it’s then pos­sible to have
im­port times <em>smal­ler</em> than what would a <code class="m-code">cp file.blob > /dev/null</code> take
(<a href="#stanford-ply">as shown above</a>), be­cause we don’t ac­tu­ally need to read
through all data at first — only when giv­en por­tion of the file is meant to
be up­loaded to the GPU or pro­cessed in some oth­er way:</p>
<pre class="m-code"><span class="cm">/* Takes basically no time */</span>
<span class="n">Containers</span><span class="o">::</span><span class="n">Array</span><span class="o"><</span><span class="kt">char</span><span class="p">,</span> <span class="n">Utility</span><span class="o">::</span><span class="n">Directory</span><span class="o">::</span><span class="n">MapDeleter</span><span class="o">></span> <span class="n">blob</span> <span class="o">=</span>
<span class="n">Utility</span><span class="o">::</span><span class="n">Directory</span><span class="o">::</span><span class="n">mapRead</span><span class="p">(</span><span class="s">"file.blob"</span><span class="p">);</span>
<span class="cm">/* Does a bunch of checks and returns views onto `blob` */</span>
<span class="n">Containers</span><span class="o">::</span><span class="n">Optional</span><span class="o"><</span><span class="n">Trade</span><span class="o">::</span><span class="n">MeshData</span><span class="o">></span> <span class="n">chair</span> <span class="o">=</span> <span class="n">Trade</span><span class="o">::</span><span class="n">MeshData</span><span class="o">::</span><span class="n">deserialize</span><span class="p">(</span><span class="n">blob</span><span class="p">);</span></pre>
<p>An­oth­er as­pect of the format is easy com­pos­ab­il­ity and ex­tens­ib­il­ity —
in­spired by <a href="https://en.wikipedia.org/wiki/Resource_Interchange_File_Format">RIFF</a>
and design of the <a href="https://en.wikipedia.org/wiki/Portable_Network_Graphics#File_header">PNG file head­er</a>,
it’s com­posed of sized chunks that can be ar­bit­rar­ily com­posed to­geth­er,
al­low­ing the con­sumer to pick just a sub­set and ig­nore the rest. Pack­ing a
bunch of meshes of di­verse formats to­geth­er in­to a single file could then look
like this:</p>
<pre class="m-inverted m-code"><span class="hll">magnum-sceneconverter file.blend --mesh <span class="s2">"chair"</span> chair.blob
</span><span class="hll">magnum-sceneconverter scene.glb --mesh <span class="s2">"tree"</span> tree.blob
</span>…
<span class="hll">cat chair.blob tree.blob car.blob > blobs.blob <span class="c1"># because why not</span>
</span></pre>
<p>Ini­tial work­ing im­ple­ment­a­tion of all the above to­geth­er with de­tailed format
spe­cific­a­tion is in <a href="https://github.com/mosra/magnum/issues/427">mosra/mag­num#427</a>, and the end goal is to be able to
de­scribe not just meshes but whole scenes. It’s cur­rently liv­ing in a branch
be­cause the last thing a file format needs is com­pat­ib­il­ity is­sues — it still
needs a few more it­er­a­tions be­fore its design settles down. This then goes
hand-in-hand with ex­tend­ing <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1AbstractSceneConverter.html">Trade::Ab­stractS­ceneCon­vert­er</a> to sup­port
more than just meshes alone, thus also mak­ing it pos­sible to out­put glTF files
with <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/magnum-sceneconverter.html">mag­num-scenecon­vert­er</a>, among oth­er things.</p>
<p class="m-transition">* * *</p>
<p>And that’s it for now. Thanks for read­ing and stay tuned for fur­ther ad­vances
in op­tim­iz­ing the as­set pipeline.</p>
<aside class="m-note m-dim">
Dis­cus­sion: <a href="https://twitter.com/czmosra/status/1265659775926362115">Twit­ter</a>,
<a href="https://www.reddit.com/r/cpp/comments/grld3d/a_new_geometry_pipeline_in_the_magnum_graphics/">Red­dit r/cpp</a>,
<a href="https://www.reddit.com/r/gamedev/comments/grlery/a_new_geometry_pipeline_in_the_magnum_graphics/">Red­dit r/game­dev</a>,
<a href="https://news.ycombinator.com/item?id=23324495">Hack­er News</a></aside>
</section>
Magnum 2019.10 released2019-10-30T00:00:00+01:002019-10-30T00:00:00+01:00Vladimír Vondruštag:blog.magnum.graphics,2019-10-30:/announcements/2019.10/<p>The new re­lease brings Py­thon bind­ings, Basis Uni­ver­sal tex­ture
com­pres­sion, im­proved STL in­ter­op­er­ab­il­ity, bet­ter Uni­code ex­per­i­ence for
Win­dows users, a more ef­fi­cient Em­scripten ap­plic­a­tion im­ple­ment­a­tion,
single-head­er lib­rar­ies, new OpenGL driver work­arounds and much more.</p>
<section id="stl-compatible-container-classes-and-further-work-on-reducing-compile-times">
<h2><a href="#stl-compatible-container-classes-and-further-work-on-reducing-compile-times">STL-com­pat­ible con­tain­er classes and fur­ther work on re­du­cing com­pile times</a></h2>
<p>Con­tinu­ing from <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/classCorrade_1_1Containers_1_1Pointer.html">Con­tain­ers::Point­er</a> and <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/classCorrade_1_1Containers_1_1Optional.html">Con­tain­ers::Op­tion­al</a>
<a href="https://blog.magnum.graphics/backstage/lightweight-stl-compatible-unique-pointer/">in­tro­duced in the pre­vi­ous re­lease</a>,
the 2019.10 re­lease adds STL com­pat­ib­il­ity to its <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/classCorrade_1_1Containers_1_1ArrayView.html">Con­tain­ers::Ar­rayView</a>
classes as well. In prac­tice this means <a class="m-flat" href="https://en.cppreference.com/w/cpp/container/vector">std::vec­tor</a> or <a class="m-flat" href="https://en.cppreference.com/w/cpp/container/array">std::ar­ray</a>
in­stances can be im­pli­citly con­ver­ted to Mag­num ar­ray views, and if you’re on
the bleed­ing edge and use C++2a <a class="m-flat" href="https://en.cppreference.com/w/cpp/container/span">std::span</a>, the con­ver­sion can go both
ways:</p>
<pre class="m-code"><span class="cp">#include</span> <span class="cpf"><Corrade/Containers/ArrayViewStl.h> // enables implicit conversions</span><span class="cp"></span>
<span class="cm">/* Image view backed by a std::vector */</span>
<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="kt">char</span><span class="o">></span> <span class="n">pixels</span> <span class="o">=</span> <span class="err">…</span><span class="p">;</span>
<span class="n">MutableImageView2D</span> <span class="n">image</span><span class="p">{</span><span class="n">PixelFormat</span><span class="o">::</span><span class="n">RGBA8Unorm</span><span class="p">,</span> <span class="p">{</span><span class="mi">32</span><span class="p">,</span> <span class="mi">24</span><span class="p">},</span> <span class="n">pixels</span><span class="p">};</span>
<span class="cm">/* Getting the data back as a span on C++2a */</span>
<span class="n">std</span><span class="o">::</span><span class="n">span</span><span class="o"><</span><span class="kt">char</span><span class="o">></span> <span class="n">span</span> <span class="o">=</span> <span class="n">image</span><span class="p">.</span><span class="n">data</span><span class="p">();</span></pre>
<p>The new re­lease also in­tro­duces a re­worked <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/classCorrade_1_1Containers_1_1StridedArrayView.html">Con­tain­ers::StridedAr­rayView</a>,
now sup­port­ing mul­tiple di­men­sions and zero / neg­at­ive strides, giv­ing it a
fea­ture par­ity with <a class="m-flat" href="https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.html#numpy.ndarray">numpy.ndar­ray</a> in Py­thon. It’s also get­ting used in a
broad­er set of APIs, in­clud­ing <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1MeshTools.html#a26ac55caa2c1391e0043e3d82193090c">MeshTools::gen­er­ateS­mooth­Nor­mals()</a>,
range-tak­ing <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1Math.html#a96d63ce830679d3516755e875a87a23d">Math::min()</a> or the very use­ful <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Image.html#a22ff216a618ce4f1b5cfb56da432b8aa">Im­age::pixels()</a> that
gives raw typed ac­cess to pixel data, and even print them on ter­min­al:</p>
<figure class="m-code-figure">
<pre class="m-code"><span class="n">importer</span><span class="p">.</span><span class="n">openFile</span><span class="p">(</span><span class="s">"mosra.png"</span><span class="p">);</span>
<span class="n">Trade</span><span class="o">::</span><span class="n">ImageData2D</span> <span class="n">image</span> <span class="o">=</span> <span class="n">importer</span><span class="p">.</span><span class="n">image2D</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
<span class="n">Debug</span><span class="p">{}</span> <span class="o"><<</span> <span class="n">Debug</span><span class="o">::</span><span class="n">color</span> <span class="o"><<</span> <span class="n">Debug</span><span class="o">::</span><span class="n">packed</span> <span class="o"><<</span> <span class="n">image</span><span class="p">.</span><span class="n">pixels</span><span class="o"><</span><span class="n">Color3ub</span><span class="o">></span><span class="p">();</span></pre>
<pre class="m-nopad m-text m-tiny m-console"><span style="color: #8eb86f; background-color: #8eb86f">▓▓</span><span style="color: #90ba71; background-color: #90ba71">▓▓</span><span style="color: #92bb73; background-color: #92bb73">▓▓</span><span style="color: #93bb74; background-color: #93bb74">▓▓</span><span style="color: #93bb75; background-color: #93bb75">▓▓</span><span style="color: #a1c77e; background-color: #a1c77e">▓▓</span><span style="color: #566c47; background-color: #566c47">▒▒</span><span style="color: #0a1916; background-color: #0a1916"> </span><span style="color: #263829; background-color: #263829">░░</span><span style="color: #17261e; background-color: #17261e"> </span><span style="color: #081513; background-color: #081513"> </span><span style="color: #051212; background-color: #051212"> </span><span style="color: #041312; background-color: #041312"> </span><span style="color: #041212; background-color: #041212"> </span><span style="color: #000b0e; background-color: #000b0e"> </span><span style="color: #030d0f; background-color: #030d0f"> </span><span style="color: #051212; background-color: #051212"> </span><span style="color: #192b21; background-color: #192b21"> </span><span style="color: #253728; background-color: #253728">░░</span><span style="color: #809d63; background-color: #809d63">▓▓</span><span style="color: #98c178; background-color: #98c178">▓▓</span><span style="color: #90b972; background-color: #90b972">▓▓</span><span style="color: #90b971; background-color: #90b971">▓▓</span><span style="color: #8fb86f; background-color: #8fb86f">▓▓</span>
<span style="color: #96bd77; background-color: #96bd77">▓▓</span><span style="color: #98be79; background-color: #98be79">▓▓</span><span style="color: #9abf7b; background-color: #9abf7b">▓▓</span><span style="color: #9abe7c; background-color: #9abe7c">▓▓</span><span style="color: #a7cb85; background-color: #a7cb85">▓▓</span><span style="color: #627852; background-color: #627852">▒▒</span><span style="color: #091816; background-color: #091816"> </span><span style="color: #1d2e25; background-color: #1d2e25"> </span><span style="color: #0a1716; background-color: #0a1716"> </span><span style="color: #000b0e; background-color: #000b0e"> </span><span style="color: #010d0f; background-color: #010d0f"> </span><span style="color: #020f11; background-color: #020f11"> </span><span style="color: #021111; background-color: #021111"> </span><span style="color: #021313; background-color: #021313"> </span><span style="color: #071514; background-color: #071514"> </span><span style="color: #020d10; background-color: #020d10"> </span><span style="color: #000a0e; background-color: #000a0e"> </span><span style="color: #061413; background-color: #061413"> </span><span style="color: #253629; background-color: #253629">░░</span><span style="color: #516144; background-color: #516144">░░</span><span style="color: #9ebb7a; background-color: #9ebb7a">▓▓</span><span style="color: #9ac07c; background-color: #9ac07c">▓▓</span><span style="color: #98be79; background-color: #98be79">▓▓</span><span style="color: #96bc77; background-color: #96bc77">▓▓</span>
<span style="color: #9ec27f; background-color: #9ec27f">▓▓</span><span style="color: #a0c382; background-color: #a0c382">▓▓</span><span style="color: #a2c383; background-color: #a2c383">▓▓</span><span style="color: #a9cb8a; background-color: #a9cb8a">▓▓</span><span style="color: #90a774; background-color: #90a774">▓▓</span><span style="color: #24342a; background-color: #24342a">░░</span><span style="color: #182721; background-color: #182721"> </span><span style="color: #0f1b19; background-color: #0f1b19"> </span><span style="color: #0e1919; background-color: #0e1919"> </span><span style="color: #13201d; background-color: #13201d"> </span><span style="color: #081617; background-color: #081617"> </span><span style="color: #031113; background-color: #031113"> </span><span style="color: #051415; background-color: #051415"> </span><span style="color: #021214; background-color: #021214"> </span><span style="color: #091918; background-color: #091918"> </span><span style="color: #010e11; background-color: #010e11"> </span><span style="color: #010b10; background-color: #010b10"> </span><span style="color: #00080d; background-color: #00080d"> </span><span style="color: #061314; background-color: #061314"> </span><span style="color: #404e3b; background-color: #404e3b">░░</span><span style="color: #8d9d6d; background-color: #8d9d6d">▓▓</span><span style="color: #a6c686; background-color: #a6c686">▓▓</span><span style="color: #a0c281; background-color: #a0c281">▓▓</span><span style="color: #9fc17f; background-color: #9fc17f">▓▓</span>
<span style="color: #a6c687; background-color: #a6c687">▓▓</span><span style="color: #a8c78a; background-color: #a8c78a">▓▓</span><span style="color: #aac98c; background-color: #aac98c">▓▓</span><span style="color: #b0c98e; background-color: #b0c98e">▓▓</span><span style="color: #516049; background-color: #516049">░░</span><span style="color: #202f28; background-color: #202f28"> </span><span style="color: #2c392f; background-color: #2c392f">░░</span><span style="color: #2d3a30; background-color: #2d3a30">░░</span><span style="color: #0a1819; background-color: #0a1819"> </span><span style="color: #0c191a; background-color: #0c191a"> </span><span style="color: #081417; background-color: #081417"> </span><span style="color: #020e13; background-color: #020e13"> </span><span style="color: #041014; background-color: #041014"> </span><span style="color: #010f13; background-color: #010f13"> </span><span style="color: #041216; background-color: #041216"> </span><span style="color: #041316; background-color: #041316"> </span><span style="color: #041416; background-color: #041416"> </span><span style="color: #031013; background-color: #031013"> </span><span style="color: #00060d; background-color: #00060d"> </span><span style="color: #081617; background-color: #081617"> </span><span style="color: #7d8762; background-color: #7d8762">▒▒</span><span style="color: #b0c68b; background-color: #b0c68b">▓▓</span><span style="color: #a8c68a; background-color: #a8c68a">▓▓</span><span style="color: #a7c487; background-color: #a7c487">▓▓</span>
<span style="color: #adca8f; background-color: #adca8f">▓▓</span><span style="color: #aeca90; background-color: #aeca90">▓▓</span><span style="color: #bad59a; background-color: #bad59a">██</span><span style="color: #92a178; background-color: #92a178">▓▓</span><span style="color: #142321; background-color: #142321"> </span><span style="color: #25312c; background-color: #25312c"> </span><span style="color: #505c4b; background-color: #505c4b">░░</span><span style="color: #0e191b; background-color: #0e191b"> </span><span style="color: #00070e; background-color: #00070e"> </span><span style="color: #00050e; background-color: #00050e"> </span><span style="color: #00050f; background-color: #00050f"> </span><span style="color: #000710; background-color: #000710"> </span><span style="color: #04161b; background-color: #04161b"> </span><span style="color: #000f13; background-color: #000f13"> </span><span style="color: #000b11; background-color: #000b11"> </span><span style="color: #021015; background-color: #021015"> </span><span style="color: #0b1a1c; background-color: #0b1a1c"> </span><span style="color: #071317; background-color: #071317"> </span><span style="color: #00060e; background-color: #00060e"> </span><span style="color: #4f5946; background-color: #4f5946">░░</span><span style="color: #b5bf8c; background-color: #b5bf8c">▓▓</span><span style="color: #b0ca91; background-color: #b0ca91">▓▓</span><span style="color: #aec78e; background-color: #aec78e">▓▓</span>
<span style="color: #b4cd96; background-color: #b4cd96">██</span><span style="color: #b4cc97; background-color: #b4cc97">▓▓</span><span style="color: #c9dba4; background-color: #c9dba4">██</span><span style="color: #566350; background-color: #566350">░░</span><span style="color: #00050d; background-color: #00050d"> </span><span style="color: #1e2c29; background-color: #1e2c29"> </span><span style="color: #27332f; background-color: #27332f"> </span><span style="color: #00050e; background-color: #00050e"> </span><span style="color: #0d1c23; background-color: #0d1c23"> </span><span style="color: #484d4f; background-color: #484d4f">░░</span><span style="color: #6b6867; background-color: #6b6867">▒▒</span><span style="color: #75706e; background-color: #75706e">▒▒</span><span style="color: #787270; background-color: #787270">▒▒</span><span style="color: #9a8c87; background-color: #9a8c87">▓▓</span><span style="color: #62625e; background-color: #62625e">░░</span><span style="color: #031418; background-color: #031418"> </span><span style="color: #000c12; background-color: #000c12"> </span><span style="color: #021015; background-color: #021015"> </span><span style="color: #0b191b; background-color: #0b191b"> </span><span style="color: #000910; background-color: #000910"> </span><span style="color: #142121; background-color: #142121"> </span><span style="color: #929875; background-color: #929875">▒▒</span><span style="color: #bdce99; background-color: #bdce99">██</span><span style="color: #b3cb94; background-color: #b3cb94">▓▓</span>
<span style="color: #bace9b; background-color: #bace9b">██</span><span style="color: #c2d5a2; background-color: #c2d5a2">██</span><span style="color: #b4bf94; background-color: #b4bf94">▓▓</span><span style="color: #121f20; background-color: #121f20"> </span><span style="color: #000910; background-color: #000910"> </span><span style="color: #132223; background-color: #132223"> </span><span style="color: #0d1a1d; background-color: #0d1a1d"> </span><span style="color: #04151d; background-color: #04151d"> </span><span style="color: #73706e; background-color: #73706e">▒▒</span><span style="color: #b9a49b; background-color: #b9a49b">▓▓</span><span style="color: #d4b9ad; background-color: #d4b9ad">██</span><span style="color: #e0c2b6; background-color: #e0c2b6">██</span><span style="color: #e9c8bb; background-color: #e9c8bb">██</span><span style="color: #e8c7ba; background-color: #e8c7ba">██</span><span style="color: #bfa9a0; background-color: #bfa9a0">▓▓</span><span style="color: #394444; background-color: #394444">░░</span><span style="color: #001317; background-color: #001317"> </span><span style="color: #021016; background-color: #021016"> </span><span style="color: #031217; background-color: #031217"> </span><span style="color: #030e15; background-color: #030e15"> </span><span style="color: #00080f; background-color: #00080f"> </span><span style="color: #5f6754; background-color: #5f6754">▒▒</span><span style="color: #bec697; background-color: #bec697">▓▓</span><span style="color: #bbd19d; background-color: #bbd19d">██</span>
<span style="color: #bdd0a0; background-color: #bdd0a0">██</span><span style="color: #d0dfae; background-color: #d0dfae">██</span><span style="color: #747d67; background-color: #747d67">▒▒</span><span style="color: #00010b; background-color: #00010b"> </span><span style="color: #031118; background-color: #031118"> </span><span style="color: #0d1c20; background-color: #0d1c20"> </span><span style="color: #000d15; background-color: #000d15"> </span><span style="color: #1c2b31; background-color: #1c2b31"> </span><span style="color: #988b86; background-color: #988b86">▒▒</span><span style="color: #bea89e; background-color: #bea89e">▓▓</span><span style="color: #c9b0a5; background-color: #c9b0a5">▓▓</span><span style="color: #d2b7ac; background-color: #d2b7ac">██</span><span style="color: #d8bbb0; background-color: #d8bbb0">██</span><span style="color: #d6b9ae; background-color: #d6b9ae">██</span><span style="color: #b8a39a; background-color: #b8a39a">▓▓</span><span style="color: #515557; background-color: #515557">░░</span><span style="color: #01151c; background-color: #01151c"> </span><span style="color: #010f16; background-color: #010f16"> </span><span style="color: #05141a; background-color: #05141a"> </span><span style="color: #030f16; background-color: #030f16"> </span><span style="color: #000a11; background-color: #000a11"> </span><span style="color: #2f3c36; background-color: #2f3c36">░░</span><span style="color: #9ea07f; background-color: #9ea07f">▓▓</span><span style="color: #c5d3a3; background-color: #c5d3a3">██</span>
<span style="color: #c3d3a6; background-color: #c3d3a6">██</span><span style="color: #d2dbae; background-color: #d2dbae">██</span><span style="color: #404a42; background-color: #404a42">░░</span><span style="color: #00030e; background-color: #00030e"> </span><span style="color: #041319; background-color: #041319"> </span><span style="color: #021018; background-color: #021018"> </span><span style="color: #000f18; background-color: #000f18"> </span><span style="color: #2d3a3e; background-color: #2d3a3e">░░</span><span style="color: #9c8e88; background-color: #9c8e88">▓▓</span><span style="color: #c7afa5; background-color: #c7afa5">▓▓</span><span style="color: #d1b6ac; background-color: #d1b6ac">██</span><span style="color: #d8bbb0; background-color: #d8bbb0">██</span><span style="color: #d9bbb0; background-color: #d9bbb0">██</span><span style="color: #cdb3a9; background-color: #cdb3a9">██</span><span style="color: #b39f97; background-color: #b39f97">▓▓</span><span style="color: #585b5b; background-color: #585b5b">░░</span><span style="color: #00121a; background-color: #00121a"> </span><span style="color: #000e15; background-color: #000e15"> </span><span style="color: #132225; background-color: #132225"> </span><span style="color: #051219; background-color: #051219"> </span><span style="color: #000a13; background-color: #000a13"> </span><span style="color: #192929; background-color: #192929"> </span><span style="color: #7d816a; background-color: #7d816a">▒▒</span><span style="color: #c8cfa2; background-color: #c8cfa2">██</span>
<span style="color: #c8d7ab; background-color: #c8d7ab">██</span><span style="color: #ced4aa; background-color: #ced4aa">██</span><span style="color: #2e3835; background-color: #2e3835">░░</span><span style="color: #000610; background-color: #000610"> </span><span style="color: #031118; background-color: #031118"> </span><span style="color: #000f17; background-color: #000f17"> </span><span style="color: #061b23; background-color: #061b23"> </span><span style="color: #636363; background-color: #636363">░░</span><span style="color: #b19e96; background-color: #b19e96">▓▓</span><span style="color: #bfa89f; background-color: #bfa89f">▓▓</span><span style="color: #ccb2a8; background-color: #ccb2a8">▓▓</span><span style="color: #d8bbb0; background-color: #d8bbb0">██</span><span style="color: #dec0b4; background-color: #dec0b4">██</span><span style="color: #cbb1a7; background-color: #cbb1a7">▓▓</span><span style="color: #b7a29a; background-color: #b7a29a">▓▓</span><span style="color: #484f51; background-color: #484f51">░░</span><span style="color: #000d16; background-color: #000d16"> </span><span style="color: #000e16; background-color: #000e16"> </span><span style="color: #1b292b; background-color: #1b292b"> </span><span style="color: #08141b; background-color: #08141b"> </span><span style="color: #000c14; background-color: #000c14"> </span><span style="color: #05171c; background-color: #05171c"> </span><span style="color: #586053; background-color: #586053">░░</span><span style="color: #c2c59d; background-color: #c2c59d">▓▓</span>
<span style="color: #ccd9ad; background-color: #ccd9ad">██</span><span style="color: #c7c9a3; background-color: #c7c9a3">▓▓</span><span style="color: #202c2c; background-color: #202c2c"> </span><span style="color: #000811; background-color: #000811"> </span><span style="color: #020f16; background-color: #020f16"> </span><span style="color: #001018; background-color: #001018"> </span><span style="color: #0d222a; background-color: #0d222a"> </span><span style="color: #515557; background-color: #515557">░░</span><span style="color: #646363; background-color: #646363">░░</span><span style="color: #7d7572; background-color: #7d7572">▒▒</span><span style="color: #a79790; background-color: #a79790">▓▓</span><span style="color: #ac9a92; background-color: #ac9a92">▓▓</span><span style="color: #b19e96; background-color: #b19e96">▓▓</span><span style="color: #b5a199; background-color: #b5a199">▓▓</span><span style="color: #8a827d; background-color: #8a827d">▒▒</span><span style="color: #213137; background-color: #213137">░░</span><span style="color: #001018; background-color: #001018"> </span><span style="color: #011018; background-color: #011018"> </span><span style="color: #0d1d22; background-color: #0d1d22"> </span><span style="color: #020f16; background-color: #020f16"> </span><span style="color: #011017; background-color: #011017"> </span><span style="color: #000d15; background-color: #000d15"> </span><span style="color: #223130; background-color: #223130"> </span><span style="color: #abac8b; background-color: #abac8b">▓▓</span>
<span style="color: #cdd8ac; background-color: #cdd8ac">██</span><span style="color: #bcba98; background-color: #bcba98">▓▓</span><span style="color: #1c292a; background-color: #1c292a"> </span><span style="color: #000811; background-color: #000811"> </span><span style="color: #020e16; background-color: #020e16"> </span><span style="color: #00121a; background-color: #00121a"> </span><span style="color: #41494c; background-color: #41494c">░░</span><span style="color: #1e2d33; background-color: #1e2d33"> </span><span style="color: #081b23; background-color: #081b23"> </span><span style="color: #04141d; background-color: #04141d"> </span><span style="color: #0a1c23; background-color: #0a1c23"> </span><span style="color: #364044; background-color: #364044">░░</span><span style="color: #7a7472; background-color: #7a7472">▒▒</span><span style="color: #333d41; background-color: #333d41">░░</span><span style="color: #19252b; background-color: #19252b"> </span><span style="color: #04131b; background-color: #04131b"> </span><span style="color: #000f18; background-color: #000f18"> </span><span style="color: #011018; background-color: #011018"> </span><span style="color: #08181e; background-color: #08181e"> </span><span style="color: #000e15; background-color: #000e15"> </span><span style="color: #021219; background-color: #021219"> </span><span style="color: #000c14; background-color: #000c14"> </span><span style="color: #182728; background-color: #182728"> </span><span style="color: #a7a485; background-color: #a7a485">▓▓</span>
<span style="color: #cdd2a7; background-color: #cdd2a7">██</span><span style="color: #b3ad8d; background-color: #b3ad8d">▓▓</span><span style="color: #0e1f22; background-color: #0e1f22"> </span><span style="color: #000913; background-color: #000913"> </span><span style="color: #020d15; background-color: #020d15"> </span><span style="color: #001018; background-color: #001018"> </span><span style="color: #74706e; background-color: #74706e">▒▒</span><span style="color: #9a8c87; background-color: #9a8c87">▓▓</span><span style="color: #515657; background-color: #515657">░░</span><span style="color: #1e2e34; background-color: #1e2e34">░░</span><span style="color: #0f2229; background-color: #0f2229"> </span><span style="color: #575a5b; background-color: #575a5b">░░</span><span style="color: #a89790; background-color: #a89790">▓▓</span><span style="color: #00111a; background-color: #00111a"> </span><span style="color: #000d15; background-color: #000d15"> </span><span style="color: #01131b; background-color: #01131b"> </span><span style="color: #010e17; background-color: #010e17"> </span><span style="color: #000e17; background-color: #000e17"> </span><span style="color: #07171d; background-color: #07171d"> </span><span style="color: #010e16; background-color: #010e16"> </span><span style="color: #021118; background-color: #021118"> </span><span style="color: #001118; background-color: #001118"> </span><span style="color: #192828; background-color: #192828"> </span><span style="color: #b0ab8a; background-color: #b0ab8a">▓▓</span>
<span style="color: #c6ca9f; background-color: #c6ca9f">▓▓</span><span style="color: #676d5b; background-color: #676d5b">▒▒</span><span style="color: #000e15; background-color: #000e15"> </span><span style="color: #010c15; background-color: #010c15"> </span><span style="color: #030c15; background-color: #030c15"> </span><span style="color: #000611; background-color: #000611"> </span><span style="color: #606160; background-color: #606160">░░</span><span style="color: #ae9b94; background-color: #ae9b94">▓▓</span><span style="color: #8c827d; background-color: #8c827d">▒▒</span><span style="color: #7d7774; background-color: #7d7774">▒▒</span><span style="color: #8e847f; background-color: #8e847f">▒▒</span><span style="color: #b4a098; background-color: #b4a098">▓▓</span><span style="color: #ac9a93; background-color: #ac9a93">▓▓</span><span style="color: #12262d; background-color: #12262d"> </span><span style="color: #12252d; background-color: #12252d"> </span><span style="color: #02171f; background-color: #02171f"> </span><span style="color: #001119; background-color: #001119"> </span><span style="color: #000c15; background-color: #000c15"> </span><span style="color: #031319; background-color: #031319"> </span><span style="color: #041219; background-color: #041219"> </span><span style="color: #000e16; background-color: #000e16"> </span><span style="color: #021319; background-color: #021319"> </span><span style="color: #06181b; background-color: #06181b"> </span><span style="color: #82856c; background-color: #82856c">▒▒</span>
<span style="color: #9ca17e; background-color: #9ca17e">▓▓</span><span style="color: #0f2223; background-color: #0f2223"> </span><span style="color: #000f14; background-color: #000f14"> </span><span style="color: #010c14; background-color: #010c14"> </span><span style="color: #020c15; background-color: #020c15"> </span><span style="color: #000711; background-color: #000711"> </span><span style="color: #273338; background-color: #273338">░░</span><span style="color: #7a7572; background-color: #7a7572">▒▒</span><span style="color: #a0918b; background-color: #a0918b">▓▓</span><span style="color: #938782; background-color: #938782">▒▒</span><span style="color: #777270; background-color: #777270">▒▒</span><span style="color: #d4b8ad; background-color: #d4b8ad">██</span><span style="color: #cdb3a9; background-color: #cdb3a9">██</span><span style="color: #213136; background-color: #213136">░░</span><span style="color: #162930; background-color: #162930"> </span><span style="color: #03161e; background-color: #03161e"> </span><span style="color: #000f17; background-color: #000f17"> </span><span style="color: #010b15; background-color: #010b15"> </span><span style="color: #010f16; background-color: #010f16"> </span><span style="color: #041319; background-color: #041319"> </span><span style="color: #010e15; background-color: #010e15"> </span><span style="color: #021015; background-color: #021015"> </span><span style="color: #000b11; background-color: #000b11"> </span><span style="color: #2e3c35; background-color: #2e3c35">░░</span>
<span style="color: #1c2e2a; background-color: #1c2e2a"> </span><span style="color: #011417; background-color: #011417"> </span><span style="color: #011015; background-color: #011015"> </span><span style="color: #010c13; background-color: #010c13"> </span><span style="color: #020d14; background-color: #020d14"> </span><span style="color: #000811; background-color: #000811"> </span><span style="color: #0f1f27; background-color: #0f1f27"> </span><span style="color: #7b7673; background-color: #7b7673">▒▒</span><span style="color: #948882; background-color: #948882">▒▒</span><span style="color: #74706d; background-color: #74706d">▒▒</span><span style="color: #6f6c6a; background-color: #6f6c6a">▒▒</span><span style="color: #b49f97; background-color: #b49f97">▓▓</span><span style="color: #cbb1a7; background-color: #cbb1a7">▓▓</span><span style="color: #29373c; background-color: #29373c">░░</span><span style="color: #011820; background-color: #011820"> </span><span style="color: #02121a; background-color: #02121a"> </span><span style="color: #010b15; background-color: #010b15"> </span><span style="color: #010b14; background-color: #010b14"> </span><span style="color: #010c13; background-color: #010c13"> </span><span style="color: #000d14; background-color: #000d14"> </span><span style="color: #000b12; background-color: #000b12"> </span><span style="color: #010c13; background-color: #010c13"> </span><span style="color: #000e13; background-color: #000e13"> </span><span style="color: #031417; background-color: #031417"> </span>
<span style="color: #000e12; background-color: #000e12"> </span><span style="color: #011315; background-color: #011315"> </span><span style="color: #000f14; background-color: #000f14"> </span><span style="color: #010c13; background-color: #010c13"> </span><span style="color: #010c12; background-color: #010c12"> </span><span style="color: #010a13; background-color: #010a13"> </span><span style="color: #000e18; background-color: #000e18"> </span><span style="color: #515657; background-color: #515657">░░</span><span style="color: #726e6c; background-color: #726e6c">▒▒</span><span style="color: #a89790; background-color: #a89790">▓▓</span><span style="color: #8b817c; background-color: #8b817c">▒▒</span><span style="color: #3b4448; background-color: #3b4448">░░</span><span style="color: #303b40; background-color: #303b40">░░</span><span style="color: #071d25; background-color: #071d25"> </span><span style="color: #03171f; background-color: #03171f"> </span><span style="color: #000d16; background-color: #000d16"> </span><span style="color: #010a15; background-color: #010a15"> </span><span style="color: #010a12; background-color: #010a12"> </span><span style="color: #000c12; background-color: #000c12"> </span><span style="color: #000b12; background-color: #000b12"> </span><span style="color: #000b11; background-color: #000b11"> </span><span style="color: #000c12; background-color: #000c12"> </span><span style="color: #010f14; background-color: #010f14"> </span><span style="color: #001013; background-color: #001013"> </span>
<span style="color: #021113; background-color: #021113"> </span><span style="color: #001013; background-color: #001013"> </span><span style="color: #011113; background-color: #011113"> </span><span style="color: #010d12; background-color: #010d12"> </span><span style="color: #010c12; background-color: #010c12"> </span><span style="color: #020c13; background-color: #020c13"> </span><span style="color: #000611; background-color: #000611"> </span><span style="color: #222e34; background-color: #222e34">░░</span><span style="color: #7c7773; background-color: #7c7773">▒▒</span><span style="color: #af9c95; background-color: #af9c95">▓▓</span><span style="color: #c1aba1; background-color: #c1aba1">▓▓</span><span style="color: #867d7a; background-color: #867d7a">▒▒</span><span style="color: #515657; background-color: #515657">░░</span><span style="color: #29383d; background-color: #29383d">░░</span><span style="color: #00121b; background-color: #00121b"> </span><span style="color: #010c15; background-color: #010c15"> </span><span style="color: #010a14; background-color: #010a14"> </span><span style="color: #000a11; background-color: #000a11"> </span><span style="color: #010d12; background-color: #010d12"> </span><span style="color: #010c12; background-color: #010c12"> </span><span style="color: #010a11; background-color: #010a11"> </span><span style="color: #010b11; background-color: #010b11"> </span><span style="color: #000e11; background-color: #000e11"> </span><span style="color: #011113; background-color: #011113"> </span>
<span style="color: #011011; background-color: #011011"> </span><span style="color: #010e11; background-color: #010e11"> </span><span style="color: #011013; background-color: #011013"> </span><span style="color: #000e11; background-color: #000e11"> </span><span style="color: #010c10; background-color: #010c10"> </span><span style="color: #010a11; background-color: #010a11"> </span><span style="color: #000913; background-color: #000913"> </span><span style="color: #041019; background-color: #041019"> </span><span style="color: #505556; background-color: #505556">░░</span><span style="color: #565858; background-color: #565858">░░</span><span style="color: #384043; background-color: #384043">░░</span><span style="color: #243237; background-color: #243237">░░</span><span style="color: #243036; background-color: #243036">░░</span><span style="color: #0e1e26; background-color: #0e1e26"> </span><span style="color: #001018; background-color: #001018"> </span><span style="color: #010b15; background-color: #010b15"> </span><span style="color: #010a13; background-color: #010a13"> </span><span style="color: #010a10; background-color: #010a10"> </span><span style="color: #010e12; background-color: #010e12"> </span><span style="color: #010d11; background-color: #010d11"> </span><span style="color: #010a10; background-color: #010a10"> </span><span style="color: #010b0f; background-color: #010b0f"> </span><span style="color: #000d10; background-color: #000d10"> </span><span style="color: #021314; background-color: #021314"> </span>
<span style="color: #010f10; background-color: #010f10"> </span><span style="color: #000b0e; background-color: #000b0e"> </span><span style="color: #011112; background-color: #011112"> </span><span style="color: #000f11; background-color: #000f11"> </span><span style="color: #000b0f; background-color: #000b0f"> </span><span style="color: #00090e; background-color: #00090e"> </span><span style="color: #010b11; background-color: #010b11"> </span><span style="color: #000812; background-color: #000812"> </span><span style="color: #0c1c24; background-color: #0c1c24"> </span><span style="color: #2a393e; background-color: #2a393e">░░</span><span style="color: #0e2129; background-color: #0e2129"> </span><span style="color: #00151e; background-color: #00151e"> </span><span style="color: #00131c; background-color: #00131c"> </span><span style="color: #001119; background-color: #001119"> </span><span style="color: #010d16; background-color: #010d16"> </span><span style="color: #010a14; background-color: #010a14"> </span><span style="color: #010a10; background-color: #010a10"> </span><span style="color: #010a0f; background-color: #010a0f"> </span><span style="color: #000d10; background-color: #000d10"> </span><span style="color: #010e10; background-color: #010e10"> </span><span style="color: #000b0f; background-color: #000b0f"> </span><span style="color: #010b0e; background-color: #010b0e"> </span><span style="color: #000e0f; background-color: #000e0f"> </span><span style="color: #041514; background-color: #041514"> </span>
<span style="color: #010f10; background-color: #010f10"> </span><span style="color: #000a0e; background-color: #000a0e"> </span><span style="color: #021212; background-color: #021212"> </span><span style="color: #031312; background-color: #031312"> </span><span style="color: #000b0e; background-color: #000b0e"> </span><span style="color: #00090e; background-color: #00090e"> </span><span style="color: #000a0e; background-color: #000a0e"> </span><span style="color: #020b13; background-color: #020b13"> </span><span style="color: #000b15; background-color: #000b15"> </span><span style="color: #17282e; background-color: #17282e"> </span><span style="color: #3a4448; background-color: #3a4448">░░</span><span style="color: #41494c; background-color: #41494c">░░</span><span style="color: #172930; background-color: #172930"> </span><span style="color: #001119; background-color: #001119"> </span><span style="color: #010b16; background-color: #010b16"> </span><span style="color: #000a11; background-color: #000a11"> </span><span style="color: #000a0e; background-color: #000a0e"> </span><span style="color: #00090e; background-color: #00090e"> </span><span style="color: #000b0e; background-color: #000b0e"> </span><span style="color: #011010; background-color: #011010"> </span><span style="color: #000d0f; background-color: #000d0f"> </span><span style="color: #000c0e; background-color: #000c0e"> </span><span style="color: #021111; background-color: #021111"> </span><span style="color: #041513; background-color: #041513"> </span>
<span style="color: #010e0e; background-color: #010e0e"> </span><span style="color: #010e0f; background-color: #010e0f"> </span><span style="color: #021111; background-color: #021111"> </span><span style="color: #000b0d; background-color: #000b0d"> </span><span style="color: #000a0e; background-color: #000a0e"> </span><span style="color: #000a0d; background-color: #000a0d"> </span><span style="color: #00090e; background-color: #00090e"> </span><span style="color: #010b13; background-color: #010b13"> </span><span style="color: #010f18; background-color: #010f18"> </span><span style="color: #0e1c24; background-color: #0e1c24"> </span><span style="color: #1b272e; background-color: #1b272e"> </span><span style="color: #07151e; background-color: #07151e"> </span><span style="color: #000b15; background-color: #000b15"> </span><span style="color: #000a11; background-color: #000a11"> </span><span style="color: #00090d; background-color: #00090d"> </span><span style="color: #000b0e; background-color: #000b0e"> </span><span style="color: #000a0d; background-color: #000a0d"> </span><span style="color: #000d0e; background-color: #000d0e"> </span><span style="color: #011010; background-color: #011010"> </span><span style="color: #010f0f; background-color: #010f0f"> </span><span style="color: #031211; background-color: #031211"> </span><span style="color: #021110; background-color: #021110"> </span><span style="color: #011010; background-color: #011010"> </span>
<span style="color: #000a0c; background-color: #000a0c"> </span><span style="color: #010e0e; background-color: #010e0e"> </span><span style="color: #010d0e; background-color: #010d0e"> </span><span style="color: #000e0e; background-color: #000e0e"> </span><span style="color: #000b0c; background-color: #000b0c"> </span><span style="color: #000a0c; background-color: #000a0c"> </span><span style="color: #000a0e; background-color: #000a0e"> </span><span style="color: #00080f; background-color: #00080f"> </span><span style="color: #00060e; background-color: #00060e"> </span><span style="color: #00080f; background-color: #00080f"> </span><span style="color: #01090d; background-color: #01090d"> </span><span style="color: #00080b; background-color: #00080b"> </span><span style="color: #000a0c; background-color: #000a0c"> </span><span style="color: #000c0d; background-color: #000c0d"> </span><span style="color: #000b0d; background-color: #000b0d"> </span><span style="color: #010e0e; background-color: #010e0e"> </span><span style="color: #000d0e; background-color: #000d0e"> </span><span style="color: #041412; background-color: #041412"> </span><span style="color: #031110; background-color: #031110"> </span><span style="color: #000d0d; background-color: #000d0d"> </span><span style="color: #010f0e; background-color: #010f0e"> </span>
<span style="color: #010c0c; background-color: #010c0c"> </span><span style="color: #010e0d; background-color: #010e0d"> </span><span style="color: #000c0c; background-color: #000c0c"> </span><span style="color: #010d0d; background-color: #010d0d"> </span><span style="color: #000c0c; background-color: #000c0c"> </span><span style="color: #000a0c; background-color: #000a0c"> </span><span style="color: #00090c; background-color: #00090c"> </span><span style="color: #000a0b; background-color: #000a0b"> </span><span style="color: #010a0c; background-color: #010a0c"> </span><span style="color: #01090c; background-color: #01090c"> </span><span style="color: #00090b; background-color: #00090b"> </span><span style="color: #00080a; background-color: #00080a"> </span><span style="color: #00090c; background-color: #00090c"> </span><span style="color: #000a0c; background-color: #000a0c"> </span><span style="color: #000b0c; background-color: #000b0c"> </span><span style="color: #010d0d; background-color: #010d0d"> </span><span style="color: #010e0d; background-color: #010e0d"> </span><span style="color: #081913; background-color: #081913"> </span><span style="color: #02100e; background-color: #02100e"> </span><span style="color: #010e0d; background-color: #010e0d"> </span><span style="color: #02100e; background-color: #02100e"> </span></pre>
</figure>
<p>All this in­ter­op­er­ab­il­ity how­ever doesn’t mean all head­ers sud­denly need to
<code class="cpp m-code"><span class="cp">#include</span> <span class="cpf"><vector></span><span class="cp"></span></code> or the like. On the con­trary — the con­ver­sion is
en­abled by in­clud­ing ded­ic­ated head­ers lis­ted be­low, to­geth­er with the abil­ity
to for­ward-de­clare <em>some</em> STL types when you don’t need the full defin­i­tion.
Put all to­geth­er, this means the code both com­piles faster (when you don’t need
to use STL types) and can in­ter­op­er­ate with STL types bet­ter (when you ac­tu­ally
want that).</p>
<div class="m-col-m-5 m-right-m m-right-l m-container-inflate">
<aside class="m-frame">
<p>This quick over­view barely scratches the sur­face, so be sure to read
more in ded­ic­ated art­icles:</p>
<ul class="m-text m-big">
<li><a href="https://blog.magnum.graphics/backstage/array-view-implementations/">Ar­ray view im­ple­ment­a­tions in Mag­num »</a></li>
<li><a href="https://blog.magnum.graphics/backstage/multidimensional-strided-array-views/">Multi-di­men­sion­al strided ar­ray views in Mag­num »</a></li>
<li><a href="https://blog.magnum.graphics/backstage/forward-declaring-stl-container-types/">For­ward-de­clar­ing STL con­tain­er types »</a></li>
</ul>
</aside>
</div>
<ul>
<li><a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/ArrayViewStl_8h.html">Cor­rade/Con­tain­ers/Ar­rayViewStl.h</a>
makes ar­ray views con­vert­ible from <a class="m-flat" href="https://en.cppreference.com/w/cpp/container/vector">std::vec­tor</a> / <a class="m-flat" href="https://en.cppreference.com/w/cpp/container/array">std::ar­ray</a></li>
<li><a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/ArrayViewStlSpan_8h.html">Cor­rade/Con­tain­ers/Ar­rayViewStlSpan.h</a>
makes ar­ray views con­vert­ible from / to C++2a <a class="m-flat" href="https://en.cppreference.com/w/cpp/container/span">std::span</a></li>
<li><a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/OptionalStl_8h.html">Cor­rade/Con­tain­ers/Op­tion­alStl.h</a>
en­ables con­ver­sion between <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/classCorrade_1_1Containers_1_1Optional.html">Con­tain­ers::Op­tion­al</a> and C++17
<a class="m-flat" href="https://en.cppreference.com/w/cpp/utility/optional">std::op­tion­al</a></li>
<li><a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/PointerStl_8h.html">Cor­rade/Con­tain­ers/Point­erStl.h</a>
en­ables con­ver­sion between <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/classCorrade_1_1Containers_1_1Pointer.html">Con­tain­ers::Point­er</a> and
<a class="m-flat" href="https://en.cppreference.com/w/cpp/memory/unique_ptr">std::unique_ptr</a></li>
<li><a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/DebugStl_8h.html">Cor­rade/Util­ity/De­bugStl.h</a>
is now an opt-in head­er to make <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/classCorrade_1_1Utility_1_1Debug.html">Util­ity::De­bug</a> work with
<a class="m-flat" href="https://en.cppreference.com/w/cpp/string/basic_string">std::string</a> or <a class="m-flat" href="https://en.cppreference.com/w/cpp/utility/tuple">std::tuple</a></li>
<li><a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/FormatStl_8h.html">Cor­rade/Util­ity/FormatStl.h</a>
is now an opt-in head­er to make <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/namespaceCorrade_1_1Utility.html#a6ed9378fb78a4da408fd5154a9d9a308">Util­ity::format()</a> work with
<a class="m-flat" href="https://en.cppreference.com/w/cpp/string/basic_string">std::string</a></li>
<li><a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/StlMath_8h.html">Cor­rade/Util­ity/Stl­Math.h</a>
is <code class="cpp m-code"><span class="cp">#include</span> <span class="cpf"><cmath></span><span class="cp"></span></code> but without drag­ging in all C++17 ad­di­tions that
<a href="https://twitter.com/czmosra/status/1085993965529255936">make your code com­pile twice as slow</a></li>
<li><a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/StlForwardArray_8h.html">Cor­rade/Util­ity/StlFor­wardAr­ray.h</a>,
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/StlForwardString_8h.html">StlFor­ward­String.h</a>,
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/StlForwardTuple_8h.html">StlFor­wardTuple.h</a> and
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/StlForwardVector_8h.html">StlFor­ward­Vec­tor.h</a>
provide for­ward de­clar­a­tions for <a class="m-flat" href="https://en.cppreference.com/w/cpp/container/array">std::ar­ray</a>, <a class="m-flat" href="https://en.cppreference.com/w/cpp/container/vector">std::vec­tor</a>,
<a class="m-flat" href="https://en.cppreference.com/w/cpp/utility/tuple">std::tuple</a> and <a class="m-flat" href="https://en.cppreference.com/w/cpp/container/vector">std::vec­tor</a> where STL im­ple­ment­a­tions make it
pos­sible (and in­clud­ing the full defin­i­tions where not)</li>
</ul>
</section>
<section id="new-power-efficient-application-implementation-for-emscripten">
<h2><a href="#new-power-efficient-application-implementation-for-emscripten">New power-ef­fi­cient ap­plic­a­tion im­ple­ment­a­tion for Em­scripten</a></h2>
<p>While all Mag­num ap­plic­a­tions his­tor­ic­ally de­faul­ted to re­draw­ing and mak­ing
the CPU busy only when needed in or­der to save power, this was not really the
case on the web. Con­trib­uted by <a href="https://github.com/Squareys">@Squareys</a>, there’s a new
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Platform_1_1EmscriptenApplication.html">Plat­form::Em­scrip­tenAp­plic­a­tion</a> that aims to be more ef­fi­cient and
smal­ler to down­load. Be­sides that, the Em­scripten SDL “emu­la­tion” has a lot
of lim­it­a­tions and hav­ing an im­ple­ment­a­tion based dir­ectly off the HTM­L5 APIs
al­lows us to be more flex­ible.</p>
<div class="m-row">
<div class="m-col-m-6">
<figure class="m-flat m-figure">
<img src="https://static.magnum.graphics/img/blog/announcements/new-emscripten-application-implementation/sdl2.png" style="width: 270px" />
<figcaption>Idle <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Platform_1_1Sdl2Application.html">Sdl2Ap­plic­a­tion</a></figcaption>
</figure>
</div>
<div class="m-col-m-6">
<figure class="m-flat m-figure">
<img src="https://static.magnum.graphics/img/blog/announcements/new-emscripten-application-implementation/emscripten.png" style="width: 270px" />
<figcaption>Idle <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Platform_1_1EmscriptenApplication.html">Em­scrip­tenAp­plic­a­tion</a></figcaption>
</figure>
</div>
</div>
<p>The new im­ple­ment­a­tion was also used for an ex­per­i­ment in how far <em>can</em> Mag­num
po­ten­tially get with ex­ecut­able size op­tim­iz­a­tion. A few of those op­tim­iz­a­tions
already made it to 2019.10 and lots more is in the buf­fer for next re­leases —
sub­scribe to <a href="https://github.com/mosra/magnum/issues/293">mosra/mag­num#293</a> for up­dates.</p>
<div class="m-plot">
<svg viewBox="0 0 576 272.16">
<defs>
<style type="text/css">*{stroke-linejoin: round; stroke-linecap: butt}</style>
</defs>
<g id="figure_1">
<g id="axes_1">
<g id="patch_1">
<path d="M 183.0275 229.105313 L 564.12 229.105313 L 564.12 27.396406 L 183.0275 27.396406 z" class="m-background"/>
</g>
<g id="plot1-value0-0"><title>52.1 kB</title>
<path d="M 183.0275 36.564993 L 250.949364 36.564993 L 250.949364 51.534114 L 183.0275 51.534114 z" clip-path="url(#pa8f0f1266f)" class="m-bar m-success"/>
</g>
<g id="plot1-value0-1"><title>36.3 kB</title>
<path d="M 183.0275 55.276394 L 230.351179 55.276394 L 230.351179 70.245515 L 183.0275 70.245515 z" clip-path="url(#pa8f0f1266f)" class="m-bar m-success"/>
</g>
<g id="plot1-value0-2"><title>35.7 kB</title>
<path d="M 183.0275 73.987796 L 229.568969 73.987796 L 229.568969 88.956917 L 183.0275 88.956917 z" clip-path="url(#pa8f0f1266f)" class="m-bar m-success"/>
</g>
<g id="plot1-value0-3"><title>19.4 kB</title>
<path d="M 183.0275 92.699197 L 208.318943 92.699197 L 208.318943 107.668318 L 183.0275 107.668318 z" clip-path="url(#pa8f0f1266f)" class="m-bar m-success"/>
</g>
<g id="plot1-value0-4"><title>14.7 kB</title>
<path d="M 183.0275 111.410598 L 202.191634 111.410598 L 202.191634 126.379719 L 183.0275 126.379719 z" clip-path="url(#pa8f0f1266f)" class="m-bar m-success"/>
</g>
<g id="plot1-value0-5"><title>14.7 kB</title>
<path d="M 183.0275 130.122 L 202.191634 130.122 L 202.191634 145.091121 L 183.0275 145.091121 z" clip-path="url(#pa8f0f1266f)" class="m-bar m-success"/>
</g>
<g id="plot1-value0-6"><title>14.7 kB</title>
<path d="M 183.0275 148.833401 L 202.191634 148.833401 L 202.191634 163.802522 L 183.0275 163.802522 z" clip-path="url(#pa8f0f1266f)" class="m-bar m-success"/>
</g>
<g id="plot1-value0-7"><title>14.7 kB</title>
<path d="M 183.0275 167.544802 L 202.191634 167.544802 L 202.191634 182.513923 L 183.0275 182.513923 z" clip-path="url(#pa8f0f1266f)" class="m-bar m-success"/>
</g>
<g id="plot1-value0-8"><title>14.7 kB</title>
<path d="M 183.0275 186.256203 L 202.191634 186.256203 L 202.191634 201.225325 L 183.0275 201.225325 z" clip-path="url(#pa8f0f1266f)" class="m-bar m-success"/>
</g>
<g id="plot1-value0-9"><title>14.7 kB</title>
<path d="M 183.0275 204.967605 L 202.191634 204.967605 L 202.191634 219.936726 L 183.0275 219.936726 z" clip-path="url(#pa8f0f1266f)" class="m-bar m-success"/>
</g>
<g id="plot1-value1-0"><title>226.3 kB</title>
<path d="M 250.949364 36.564993 L 545.972738 36.564993 L 545.972738 51.534114 L 250.949364 51.534114 z" clip-path="url(#pa8f0f1266f)" class="m-bar m-info"/>
</g>
<g id="plot1-value1-1"><title>224.5 kB</title>
<path d="M 230.351179 55.276394 L 523.027924 55.276394 L 523.027924 70.245515 L 230.351179 70.245515 z" clip-path="url(#pa8f0f1266f)" class="m-bar m-info"/>
</g>
<g id="plot1-value1-2"><title>224.5 kB</title>
<path d="M 229.568969 73.987796 L 522.245715 73.987796 L 522.245715 88.956917 L 229.568969 88.956917 z" clip-path="url(#pa8f0f1266f)" class="m-bar m-info"/>
</g>
<g id="plot1-value1-3"><title>224.5 kB</title>
<path d="M 208.318943 92.699197 L 500.995688 92.699197 L 500.995688 107.668318 L 208.318943 107.668318 z" clip-path="url(#pa8f0f1266f)" class="m-bar m-info"/>
</g>
<g id="plot1-value1-4"><title>83.6 kB</title>
<path d="M 202.191634 111.410598 L 311.1795 111.410598 L 311.1795 126.379719 L 202.191634 126.379719 z" clip-path="url(#pa8f0f1266f)" class="m-bar m-info"/>
</g>
<g id="plot1-value1-5"><title>75.4 kB</title>
<path d="M 202.191634 130.122 L 300.489303 130.122 L 300.489303 145.091121 L 202.191634 145.091121 z" clip-path="url(#pa8f0f1266f)" class="m-bar m-info"/>
</g>
<g id="plot1-value1-6"><title>69.3 kB</title>
<path d="M 202.191634 148.833401 L 292.536839 148.833401 L 292.536839 163.802522 L 202.191634 163.802522 z" clip-path="url(#pa8f0f1266f)" class="m-bar m-info"/>
</g>
<g id="plot1-value1-7"><title>62.6 kB</title>
<path d="M 202.191634 167.544802 L 283.802166 167.544802 L 283.802166 182.513923 L 202.191634 182.513923 z" clip-path="url(#pa8f0f1266f)" class="m-bar m-info"/>
</g>
<g id="plot1-value1-8"><title>56.3 kB</title>
<path d="M 202.191634 186.256203 L 275.588965 186.256203 L 275.588965 201.225325 L 202.191634 201.225325 z" clip-path="url(#pa8f0f1266f)" class="m-bar m-info"/>
</g>
<g id="plot1-value1-9"><title>44.0 kB</title>
<path d="M 202.191634 204.967605 L 259.553669 204.967605 L 259.553669 219.936726 L 202.191634 219.936726 z" clip-path="url(#pa8f0f1266f)" class="m-bar m-info"/>
</g>
<g id="matplotlib.axis_1">
<g id="xtick_1">
<g id="line2d_1">
<defs>
<path id="mc4bea1de13" d="M 0 0 L 0 3.5" class="m-line"/>
</defs>
<g>
<use xlink:href="#mc4bea1de13" x="183.0275" y="229.105313"/>
</g>
</g>
<g id="text_1">
<text class="m-label" style="text-anchor: middle" x="183.0275" y="243.937656" transform="rotate(-0, 183.0275, 243.937656)">0</text>
</g>
</g>
<g id="xtick_2">
<g id="line2d_2">
<g>
<use xlink:href="#mc4bea1de13" x="248.21163" y="229.105313"/>
</g>
</g>
<g id="text_2">
<text class="m-label" style="text-anchor: middle" x="248.21163" y="243.937656" transform="rotate(-0, 248.21163, 243.937656)">50</text>
</g>
</g>
<g id="xtick_3">
<g id="line2d_3">
<g>
<use xlink:href="#mc4bea1de13" x="313.395761" y="229.105313"/>
</g>
</g>
<g id="text_3">
<text class="m-label" style="text-anchor: middle" x="313.395761" y="243.937656" transform="rotate(-0, 313.395761, 243.937656)">100</text>
</g>
</g>
<g id="xtick_4">
<g id="line2d_4">
<g>
<use xlink:href="#mc4bea1de13" x="378.579891" y="229.105313"/>
</g>
</g>
<g id="text_4">
<text class="m-label" style="text-anchor: middle" x="378.579891" y="243.937656" transform="rotate(-0, 378.579891, 243.937656)">150</text>
</g>
</g>
<g id="xtick_5">
<g id="line2d_5">
<g>
<use xlink:href="#mc4bea1de13" x="443.764022" y="229.105313"/>
</g>
</g>
<g id="text_5">
<text class="m-label" style="text-anchor: middle" x="443.764022" y="243.937656" transform="rotate(-0, 443.764022, 243.937656)">200</text>
</g>
</g>
<g id="xtick_6">
<g id="line2d_6">
<g>
<use xlink:href="#mc4bea1de13" x="508.948152" y="229.105313"/>
</g>
</g>
<g id="text_6">
<text class="m-label" style="text-anchor: middle" x="508.948152" y="243.937656" transform="rotate(-0, 508.948152, 243.937656)">250</text>
</g>
</g>
<g id="text_7">
<text class="m-label" style="text-anchor: middle" x="373.57375" y="258.025" transform="rotate(-0, 373.57375, 258.025)">kB</text>
</g>
</g>
<g id="matplotlib.axis_2">
<g id="ytick_1">
<g id="line2d_7">
<defs>
<path id="mae1c673641" d="M 0 0 L -3.5 0" class="m-line"/>
</defs>
<g>
<use xlink:href="#mae1c673641" x="183.0275" y="44.049553"/>
</g>
</g>
<g id="text_8">
<text class="m-label" style="text-anchor: end" x="176.0275" y="47.965725" transform="rotate(-0, 176.0275, 47.965725)">Initial state</text>
</g>
</g>
<g id="ytick_2">
<g id="line2d_8">
<g>
<use xlink:href="#mae1c673641" x="183.0275" y="62.760955"/>
</g>
</g>
<g id="text_9">
<text class="m-label" style="text-anchor: end" x="176.0275" y="66.677127" transform="rotate(-0, 176.0275, 66.677127)">Enabling minimal runtime</text>
</g>
</g>
<g id="ytick_3">
<g id="line2d_9">
<g>
<use xlink:href="#mae1c673641" x="183.0275" y="81.472356"/>
</g>
</g>
<g id="text_10">
<text class="m-label" style="text-anchor: end" x="176.0275" y="85.4547" transform="rotate(-0, 176.0275, 85.4547)">Additional slimming flags</text>
</g>
</g>
<g id="ytick_4">
<g id="line2d_10">
<g>
<use xlink:href="#mae1c673641" x="183.0275" y="100.183757"/>
</g>
</g>
<g id="text_11">
<text class="m-label" style="text-anchor: end" x="176.0275" y="104.166101" transform="rotate(-0, 176.0275, 104.166101)">Disabling filesystem</text>
</g>
</g>
<g id="ytick_5">
<g id="line2d_11">
<g>
<use xlink:href="#mae1c673641" x="183.0275" y="118.895159"/>
</g>
</g>
<g id="text_12">
<text class="m-label" style="text-anchor: end" x="176.0275" y="122.877502" transform="rotate(-0, 176.0275, 122.877502)">Chopping off all C++ stream usage</text>
</g>
</g>
<g id="ytick_6">
<g id="line2d_12">
<g>
<use xlink:href="#mae1c673641" x="183.0275" y="137.60656"/>
</g>
</g>
<g id="text_13">
<text class="m-label" style="text-anchor: end" x="176.0275" y="141.522732" transform="rotate(-0, 176.0275, 141.522732)">Enabling CORRADE_NO_ASSERT</text>
</g>
</g>
<g id="ytick_7">
<g id="line2d_13">
<g>
<use xlink:href="#mae1c673641" x="183.0275" y="156.317961"/>
</g>
</g>
<g id="text_14">
<text class="m-label" style="text-anchor: end" x="176.0275" y="160.344133" transform="rotate(-0, 176.0275, 160.344133)">Removing a single use of std::sort()</text>
</g>
</g>
<g id="ytick_8">
<g id="line2d_14">
<g>
<use xlink:href="#mae1c673641" x="183.0275" y="175.029363"/>
</g>
</g>
<g id="text_15">
<text class="m-label" style="text-anchor: end" x="176.0275" y="178.945535" transform="rotate(-0, 176.0275, 178.945535)">Removing one std::unordered_map</text>
</g>
</g>
<g id="ytick_9">
<g id="line2d_15">
<g>
<use xlink:href="#mae1c673641" x="183.0275" y="193.740764"/>
</g>
</g>
<g id="text_16">
<text class="m-label" style="text-anchor: end" x="176.0275" y="197.723108" transform="rotate(-0, 176.0275, 197.723108)">Using emmalloc instead of dlmalloc</text>
</g>
</g>
<g id="ytick_10">
<g id="line2d_16">
<g>
<use xlink:href="#mae1c673641" x="183.0275" y="212.452165"/>
</g>
</g>
<g id="text_17">
<text class="m-label" style="text-anchor: end" x="176.0275" y="216.478337" transform="rotate(-0, 176.0275, 216.478337)">Removing all printf() usage</text>
</g>
</g>
</g>
<g id="text_18">
<text class="m-title" style="text-anchor: middle" x="373.57375" y="21.396406" transform="rotate(-0, 373.57375, 21.396406)">Download size (*.js, *.wasm)</text>
</g>
</g>
</g>
<defs>
<clipPath id="pa8f0f1266f">
<rect x="183.0275" y="27.396406" width="381.0925" height="201.708906"/>
</clipPath>
</defs>
</svg>
</div>
<aside class="m-frame">
<p class="m-text m-big m-noindent">Read more: <a href="https://blog.magnum.graphics/announcements/new-emscripten-application-implementation/">New Ap­plic­a­tion im­ple­ment­a­tion for Em­scripten »</a></p>
</aside>
</section>
<section id="python-bindings-and-eigen-interoperability">
<h2><a href="#python-bindings-and-eigen-interoperability">Py­thon bind­ings and Ei­gen in­ter­op­er­ab­il­ity</a></h2>
<p>By far the largest part of this re­lease are the new Py­thon bind­ings, made us­ing
<a href="https://github.com/pybind/pybind11">py­bind11</a> and avail­able through a sep­ar­ate re­pos­it­ory at
<a class="m-link-wrap" href="https://github.com/mosra/magnum-bindings">https://github.com/mosra/magnum-bindings</a>. To get a first im­pres­sions, check out
how the ba­sic C++ tu­tori­als <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/python/examples/">look like when re­writ­ten in Py­thon</a>.
Large ef­fort went in­to mak­ing the Py­thon API feel like Py­thon, in­clud­ing
GLSL-like vec­tor swizzles:</p>
<pre class="m-code"><span class="gp">>>> </span><span class="kn">from</span> <span class="nn">magnum</span> <span class="kn">import</span> <span class="o">*</span>
<span class="gp">>>> </span><span class="n">a</span> <span class="o">=</span> <span class="n">Vector4</span><span class="p">(</span><span class="mf">1.5</span><span class="p">,</span> <span class="mf">0.3</span><span class="p">,</span> <span class="o">-</span><span class="mf">1.0</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">)</span>
<span class="gp">>>> </span><span class="n">b</span> <span class="o">=</span> <span class="n">Vector4</span><span class="p">(</span><span class="mf">7.2</span><span class="p">,</span> <span class="mf">2.3</span><span class="p">,</span> <span class="mf">1.1</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">)</span>
<span class="gp">>>> </span><span class="n">a</span><span class="o">.</span><span class="n">wxy</span> <span class="o">=</span> <span class="n">b</span><span class="o">.</span><span class="n">xwz</span>
<span class="gp">>>> </span><span class="n">a</span>
<span class="go">Vector(0, 1.1, -1, 7.2)</span></pre>
<p>The bind­ings are op­tim­ized for zero-copy data trans­fer between C++ and Py­thon
us­ing CPy­thon’s Buf­fer Pro­tocol, which at the very core means the Mag­num ar­ray
view classes got ex­posed as <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/python/corrade/containers/ArrayView/">con­tain­ers.Ar­rayView</a>,
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/python/corrade/containers/StridedArrayView1D/">con­tain­ers.StridedAr­rayView1D</a> etc., with sup­port for the full Py­thon
sli­cing syn­tax and in­ter­op­er­ab­il­ity with <a class="m-flat" href="https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.html#numpy.ndarray">numpy.ndar­ray</a>, <a class="m-flat" href="https://docs.python.org/3/library/stdtypes.html#memoryview">memoryview</a>
and oth­er views and con­tain­ers.</p>
<aside class="m-block m-success">
<h3>Python doc generator</h3>
<p>An im­port­ant side-product of ex­pos­ing Mag­num to Py­thon is a Sphinx-alike
doc­u­ment­a­tion gen­er­at­or with first-class sup­port for py­bind11-gen­er­ated
code, fo­cus on type an­nota­tions and — of course — a blaz­ing-fast search
you’re used to from Mag­num C++ docs. Check them out at
<a class="m-link-wrap" href="https://doc.magnum.graphics/python/">https://doc.magnum.graphics/python/</a>; the gen­er­at­or it­self is doc­u­mented at
<a class="m-link-wrap" href="https://mcss.mosra.cz/documentation/python/">https://mcss.mosra.cz/documentation/python/</a>.</p>
<p>One of the early ad­op­ters for Mag­num Py­thon bind­ings and the Py­thon doc
gen­er­at­or is the <em>AI Hab­it­at</em> pro­ject — <a class="m-link-wrap" href="https://aihabitat.org">https://aihabitat.org</a>. The whole
site and all its doc­u­ment­a­tion is made us­ing the m.css frame­work, only with
a dif­fer­ent theme than Mag­num sites.</p>
</aside>
<aside class="m-frame">
<p class="m-text m-big m-noindent">Read more: <a href="https://blog.magnum.graphics/announcements/introducing-python-bindings/">In­tro­du­cing Mag­num Py­thon Bind­ings »</a></p>
</aside>
<p>While NumPy is used ex­tens­ively by re­search­ers in the Py­thon world, same could
be said about Ei­gen in the C++ world. In 2019.10, <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1EigenIntegration.html">Ei­gen­In­teg­ra­tion</a>
joins the ranks of <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1GlmIntegration.html">GlmIn­teg­ra­tion</a> in bring­ing built­in con­ver­sion between
for­eign and Mag­num math types. Goal for both is not need­ing to worry about
wheth­er mat­rix is row- or column-ma­jor or which or­der qua­ternion com­pon­ents are
stored in:</p>
<pre class="m-code"><span class="cp">#include</span> <span class="cpf"><Magnum/EigenIntegration/Integration.h></span><span class="cp"></span>
<span class="n">Eigen</span><span class="o">::</span><span class="n">Vector3f</span> <span class="n">a</span><span class="p">{</span><span class="mf">1.0f</span><span class="p">,</span> <span class="mf">2.0f</span><span class="p">,</span> <span class="mf">3.0f</span><span class="p">};</span>
<span class="n">Vector3</span> <span class="nf">b</span><span class="p">(</span><span class="n">a</span><span class="p">);</span>
<span class="k">auto</span> <span class="n">c</span> <span class="o">=</span> <span class="n">Matrix4</span><span class="o">::</span><span class="n">rotation</span><span class="p">(</span><span class="n">Vector3</span><span class="p">(</span><span class="n">a</span><span class="p">),</span> <span class="mf">35.0_degf</span><span class="p">);</span></pre>
</section>
<section id="image-api-improvements">
<h2><a href="#image-api-improvements">Im­age API im­prove­ments</a></h2>
<p>With <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum.html#ac8d2058eea925905bfe83f7d5db93c54">Mut­ableIm­ageView2D</a> and friends and new over­loads to
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1GL_1_1AbstractFramebuffer.html#a28a3cbf18d3f28fca98a67bafea007d4">GL::Ab­stract­Frame­buf­fer::read()</a>, <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1GL_1_1Texture.html#a14b7d514304da9fa7ace8a7b39e27318">GL::Tex­ture::im­age()</a> etc. it’s
now pos­sible to read GPU im­ages in­to ex­ist­ing memory, without un­wanted large
memory al­loc­a­tions hap­pen­ing in the back­ground. These new APIs are also ex­posed
to Py­thon, al­low­ing for ef­fi­cient trans­fer of rendered im­ages dir­ectly in­to a
memory buf­fer man­aged by a ma­chine learn­ing frame­work, for ex­ample.</p>
<p>Back in <a href="2018.04.rst">2018.04</a>, Mag­num gained backend-in­de­pend­ent pixel
formats, how­ever the <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum.html#a6aa5c7abd3ed7e272d99436adf5131e0">Com­pressed­Pixel­Format</a> enum was quite neg­lected
un­til now, sup­port­ing just ba­sic S3TC. Now it sup­ports all widely-used
com­pres­sion formats — sR­GB S3TC vari­ants, one/two-chan­nel BC4 and BC5
formats, BC6h and BC7, ETC2 and EAC formats for mo­bile plat­forms, ASTC
(in­clud­ing 3D and HDR) and PVRTC. On the GL side, <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1GL.html#a3d55f4d2412f6a9913220e80820847dd">GL::Com­pressed­Pixel­Format</a>
learned PVRTC formats as well, ex­posed the (3D) ASTC formats for WebGL, and
same was done for the <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1Vk.html#a2479652926689c38bb03bd3243b60e71">Vk::vk­Format()</a> con­ver­sion util­ity. Be­sides GL and
Vulkan, the <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum.html#a0f3e774f6adcfb4b7d06df036c378c24">Pixel­Format</a> / <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum.html#a6aa5c7abd3ed7e272d99436adf5131e0">Com­pressed­Pixel­Format</a> enum
doc­u­ment­a­tion now lists also cor­res­pond­ing D3D and Met­al val­ues to make it
easi­er for people us­ing (or com­ing from) these backends.</p>
<p>These im­prove­ments are the ini­tial batch of new fea­tures be­ing ad­ded, with more
fol­low­ing next — im­proved DDS sup­port (see <a href="https://github.com/mosra/magnum-plugins/issues/67">mosra/mag­num-plu­gins#67</a>),
a <a href="http://github.khronos.org/KTX-Specification/">KTX2</a> im­port­er or, for
ex­ample, mip level se­lec­tion (<a href="https://github.com/mosra/magnum/issues/369">mosra/mag­num#369</a>).</p>
</section>
<section id="basis-universal-texture-compression">
<h2><a href="#basis-universal-texture-compression">Basis Uni­ver­sal tex­ture com­pres­sion</a></h2>
<p>The main reas­on why all the above-lis­ted com­pres­sion formats were ad­ded is
<a href="https://github.com/BinomialLLC/basis_universal">Basis Uni­ver­sal</a>. It’s a
suc­cessor to <a href="https://github.com/binomialLLC/crunch">Crunch</a>, open-sourced a
few months ago thanks to fund­ing from Google. What makes it so re­volu­tion­al is
best ex­plained by the fol­low­ing plot. I took the
<a href="https://static.magnum.graphics/img/blog/announcements/2019.10/cover.jpg">cov­er.jpg</a> used on top of
this art­icle and con­ver­ted it to <a href="https://static.magnum.graphics/img/blog/announcements/2019.10/cover.basis">cov­er.basis</a>
and a bunch of raw block com­pres­sion formats for com­par­is­on:</p>
<div class="m-plot">
<svg viewBox="0 0 576 151.2">
<defs>
<style type="text/css">*{stroke-linejoin: round; stroke-linecap: butt}</style>
</defs>
<g id="figure_1">
<g id="axes_1">
<g id="patch_1">
<path d="M 201.861562 108.145313 L 564.12 108.145313 L 564.12 27.136406 L 201.861562 27.136406 z" class="m-background"/>
</g>
<g id="plot2-value0-0"><title>215.0 kB</title>
<path d="M 201.861562 30.818629 L 216.170345 30.818629 L 216.170345 46.322726 L 201.861562 46.322726 z" clip-path="url(#p18e91720e4)" class="m-bar m-success"/>
</g>
<g id="plot2-value0-1"><title>1296.0 kB</title>
<path d="M 201.861562 50.19875 L 288.113571 50.19875 L 288.113571 65.702847 L 201.861562 65.702847 z" clip-path="url(#p18e91720e4)" class="m-bar m-success"/>
</g>
<g id="plot2-value0-2"><title>269.5 kB</title>
<path d="M 201.861562 69.578871 L 219.797455 69.578871 L 219.797455 85.082968 L 201.861562 85.082968 z" clip-path="url(#p18e91720e4)" class="m-bar m-success"/>
</g>
<g id="plot2-value0-3"><title>154.9 kB</title>
<path d="M 201.861562 88.958993 L 212.170541 88.958993 L 212.170541 104.463089 L 201.861562 104.463089 z" clip-path="url(#p18e91720e4)" class="m-bar m-success"/>
</g>
<g id="plot2-value1-0"><title>0.0 kB</title>
<path d="M 216.170345 30.818629 L 216.170345 30.818629 L 216.170345 46.322726 L 216.170345 46.322726 z" clip-path="url(#p18e91720e4)" class="m-bar m-danger"/>
</g>
<g id="plot2-value1-1"><title>0.0 kB</title>
<path d="M 288.113571 50.19875 L 288.113571 50.19875 L 288.113571 65.702847 L 288.113571 65.702847 z" clip-path="url(#p18e91720e4)" class="m-bar m-danger"/>
</g>
<g id="plot2-value1-2"><title>242.1 kB</title>
<path d="M 219.797455 69.578871 L 235.909809 69.578871 L 235.909809 85.082968 L 219.797455 85.082968 z" clip-path="url(#p18e91720e4)" class="m-bar m-danger"/>
</g>
<g id="plot2-value1-3"><title>0.0 kB</title>
<path d="M 212.170541 88.958993 L 212.170541 88.958993 L 212.170541 104.463089 L 212.170541 104.463089 z" clip-path="url(#p18e91720e4)" class="m-bar m-danger"/>
</g>
<g id="plot2-value2-0"><title>0.0 kB</title>
<path d="M 216.170345 30.818629 L 216.170345 30.818629 L 216.170345 46.322726 L 216.170345 46.322726 z" clip-path="url(#p18e91720e4)" class="m-bar m-warning"/>
</g>
<g id="plot2-value2-1"><title>0.0 kB</title>
<path d="M 288.113571 50.19875 L 288.113571 50.19875 L 288.113571 65.702847 L 288.113571 65.702847 z" clip-path="url(#p18e91720e4)" class="m-bar m-warning"/>
</g>
<g id="plot2-value2-2"><title>508.1 kB</title>
<path d="M 235.909809 69.578871 L 269.725122 69.578871 L 269.725122 85.082968 L 235.909809 85.082968 z" clip-path="url(#p18e91720e4)" class="m-bar m-warning"/>
</g>
<g id="plot2-value2-3"><title>0.0 kB</title>
<path d="M 212.170541 88.958993 L 212.170541 88.958993 L 212.170541 104.463089 L 212.170541 104.463089 z" clip-path="url(#p18e91720e4)" class="m-bar m-warning"/>
</g>
<g id="plot2-value3-0"><title>4969.0 kB</title>
<path d="M 216.170345 30.818629 L 546.869598 30.818629 L 546.869598 46.322726 L 216.170345 46.322726 z" clip-path="url(#p18e91720e4)" class="m-bar m-info"/>
</g>
<g id="plot2-value3-1"><title>0.0 kB</title>
<path d="M 288.113571 50.19875 L 288.113571 50.19875 L 288.113571 65.702847 L 288.113571 65.702847 z" clip-path="url(#p18e91720e4)" class="m-bar m-info"/>
</g>
<g id="plot2-value3-2"><title>276.3 kB</title>
<path d="M 269.725122 69.578871 L 288.113571 69.578871 L 288.113571 85.082968 L 269.725122 85.082968 z" clip-path="url(#p18e91720e4)" class="m-bar m-info"/>
</g>
<g id="plot2-value3-3"><title>1141.1 kB</title>
<path d="M 212.170541 88.958993 L 288.113571 88.958993 L 288.113571 104.463089 L 212.170541 104.463089 z" clip-path="url(#p18e91720e4)" class="m-bar m-info"/>
</g>
<g id="matplotlib.axis_1">
<g id="xtick_1">
<g id="line2d_1">
<defs>
<path id="m6c4cdc507b" d="M 0 0 L 0 3.5" class="m-line"/>
</defs>
<g>
<use xlink:href="#m6c4cdc507b" x="201.861562" y="108.145313"/>
</g>
</g>
<g id="text_1">
<text class="m-label" style="text-anchor: middle" x="201.861562" y="122.977656" transform="rotate(-0, 201.861562, 122.977656)">0</text>
</g>
</g>
<g id="xtick_2">
<g id="line2d_2">
<g>
<use xlink:href="#m6c4cdc507b" x="268.414039" y="108.145313"/>
</g>
</g>
<g id="text_2">
<text class="m-label" style="text-anchor: middle" x="268.414039" y="122.977656" transform="rotate(-0, 268.414039, 122.977656)">1000</text>
</g>
</g>
<g id="xtick_3">
<g id="line2d_3">
<g>
<use xlink:href="#m6c4cdc507b" x="334.966515" y="108.145313"/>
</g>
</g>
<g id="text_3">
<text class="m-label" style="text-anchor: middle" x="334.966515" y="122.977656" transform="rotate(-0, 334.966515, 122.977656)">2000</text>
</g>
</g>
<g id="xtick_4">
<g id="line2d_4">
<g>
<use xlink:href="#m6c4cdc507b" x="401.518991" y="108.145313"/>
</g>
</g>
<g id="text_4">
<text class="m-label" style="text-anchor: middle" x="401.518991" y="122.977656" transform="rotate(-0, 401.518991, 122.977656)">3000</text>
</g>
</g>
<g id="xtick_5">
<g id="line2d_5">
<g>
<use xlink:href="#m6c4cdc507b" x="468.071467" y="108.145313"/>
</g>
</g>
<g id="text_5">
<text class="m-label" style="text-anchor: middle" x="468.071467" y="122.977656" transform="rotate(-0, 468.071467, 122.977656)">4000</text>
</g>
</g>
<g id="xtick_6">
<g id="line2d_6">
<g>
<use xlink:href="#m6c4cdc507b" x="534.623943" y="108.145313"/>
</g>
</g>
<g id="text_6">
<text class="m-label" style="text-anchor: middle" x="534.623943" y="122.977656" transform="rotate(-0, 534.623943, 122.977656)">5000</text>
</g>
</g>
<g id="text_7">
<text class="m-label" style="text-anchor: middle" x="382.990781" y="137.065" transform="rotate(-0, 382.990781, 137.065)">kB</text>
</g>
</g>
<g id="matplotlib.axis_2">
<g id="ytick_1">
<g id="line2d_7">
<defs>
<path id="m74ed764a78" d="M 0 0 L -3.5 0" class="m-line"/>
</defs>
<g>
<use xlink:href="#m74ed764a78" x="201.861562" y="38.570678"/>
</g>
</g>
<g id="text_8">
<text class="m-label" style="text-anchor: end" x="194.861562" y="42.48685" transform="rotate(-0, 194.861562, 42.48685)">JPEG -> RGBA8</text>
</g>
</g>
<g id="ytick_2">
<g id="line2d_8">
<g>
<use xlink:href="#m74ed764a78" x="201.861562" y="57.950799"/>
</g>
</g>
<g id="text_9">
<text class="m-label" style="text-anchor: end" x="194.861562" y="61.866971" transform="rotate(-0, 194.861562, 61.866971)">Uncompressed BC3 DDS -> BC3</text>
</g>
</g>
<g id="ytick_3">
<g id="line2d_9">
<g>
<use xlink:href="#m74ed764a78" x="201.861562" y="77.33092"/>
</g>
</g>
<g id="text_10">
<text class="m-label" style="text-anchor: end" x="194.861562" y="81.247092" transform="rotate(-0, 194.861562, 81.247092)">Compressed BC3 + ETC2 + PVRTC -> BC3</text>
</g>
</g>
<g id="ytick_4">
<g id="line2d_10">
<g>
<use xlink:href="#m74ed764a78" x="201.861562" y="96.711041"/>
</g>
</g>
<g id="text_11">
<text class="m-label" style="text-anchor: end" x="194.861562" y="100.627213" transform="rotate(-0, 194.861562, 100.627213)">Basis Universal -> BC3</text>
</g>
</g>
</g>
<g id="text_12">
<text class="m-title" style="text-anchor: middle" x="382.990781" y="21.136406" transform="rotate(-0, 382.990781, 21.136406)">File size / memory use</text>
</g>
</g>
</g>
<defs>
<clipPath id="p18e91720e4">
<rect x="201.861562" y="27.136406" width="362.258438" height="81.008906"/>
</clipPath>
</defs>
</svg>
</div>
<p>Be­fore Basis, you had ba­sic­ally two ways how to op­tim­ize your as­set size:</p>
<ul>
<li>Either op­tim­ize stor­age size by us­ing lossy com­pres­sion (such as JPEG), but
then hav­ing to fully un­com­press to RGBA8. With the 1536×864 cov­er
im­age it’s a ~200 kB im­age in­flated to over 5 MB of RGBA data.</li>
<li>Or op­tim­ize GPU memory us­age by us­ing vari­ous block com­pres­sion formats
(such as BC3 / DX­T5), which is only 1.3 MB of data in memory; and with a
lossless com­pres­sion on top you’ll get down to a 270 kB file. How­ever,
es­pe­cially on mo­bile devices, each GPU vendor sup­ports a dif­fer­ent format
so you need to ship at least a BCn, ETC and PVRTC vari­ant.</li>
</ul>
<p>With Basis Uni­ver­sal, you get the best of both worlds — data is in­tern­ally
stored in a sub­set of the ETC1 block com­pres­sion format with ad­di­tion­al
com­pres­sion on top, mak­ing it smal­ler than an equi­val­ent JPEG, and then you can
transcode that single file to BCn, ETC2, ASTC or PVRTC de­pend­ing on what the
GPU needs.</p>
<p>Thanks to work done by <a href="https://github.com/Squareys">@Squareys</a>, Mag­num sup­ports both im­port­ing (and
transcod­ing to a de­sired GPU format) via the <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1BasisImporter.html">BasisIm­port­er</a>
plu­gin as well as en­cod­ing im­ages in­to the Basis Uni­ver­sal format us­ing the
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1BasisImageConverter.html">BasisIm­age­Con­vert­er</a>. Com­pared to the
of­fi­cial <code>basisu</code> tool, which works only with PNGs, the
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/magnum-imageconverter.html">mag­num-im­age­con­vert­er</a> util­ity sup­ports any
format that Mag­num can im­port:</p>
<pre class="m-code">magnum-imageconverter image.jpg image.basis</pre>
<p>Of course, all op­tions sup­por­ted by <code>basisu</code> are ex­posed to the plu­gin
con­fig­ur­a­tion as well:</p>
<pre class="m-code">magnum-imageconverter image.jpg --converter BasisImageConverter <span class="se">\</span>
-c <span class="nv">flip_y</span><span class="o">=</span>false,threads<span class="o">=</span><span class="m">8</span> image.basis</pre>
<p>The <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1TinyGltfImporter.html">Tiny­Glt­fIm­port­er</a> sup­ports Basis files
through the un­of­fi­cial <code>GOOGLE_texture_basis</code> ex­ten­sion. There are still some
fea­tures we’re wait­ing on to get merged in or­der to have a full sup­port. One of
them is an abil­ity to Y-flip im­ages dur­ing transcode (in­stead of only in the
en­coder, <a href="https://github.com/BinomialLLC/basis_universal/issues/79">Bi­no­mi­alLLC/basis_uni­ver­sal#79</a>), an­oth­er are build­sys­tem
im­prove­ments (<a href="https://github.com/BinomialLLC/basis_universal/issues/13">Bi­no­mi­alLLC/basis_uni­ver­sal#13</a>) — right now, the soft­ware
can’t be built as a lib­rary on its own and thus is im­possible to pack­age /
dis­trib­ute without re­quir­ing each pro­ject to bundle it. Un­til that’s re­solved,
Basis won’t be en­abled in any Mag­num pack­ages. The only ex­cep­tion is Vcp­kg,
where a Basis fork, based off the above PR, is used.</p>
</section>
<section id="magnum-player-improvements">
<h2><a href="#magnum-player-improvements">Mag­num Play­er im­prove­ments</a></h2>
<p>The <a href="https://magnum.graphics/showcase/player/">Mag­num Play­er</a> util­ity re­ceived quite a
few new fea­tures. It can now auto­mat­ic­ally gen­er­ate smooth nor­mals for mod­els
that don’t have them and you can in­spect mesh to­po­logy by se­lect­ing it us­ing a right-click.</p>
<img class="m-image" src="https://static.magnum.graphics/img/blog/announcements/2019.10/player.png" style="width: 405px" />
<p>Apart from meshes, the play­er can now also open im­ages of all types that Mag­num
can im­port. This in­cludes the above-men­tioned Basis Uni­ver­sal — and the web
ver­sion knows those, too, and transcodes to BCn, ETC, PVRTC, ASTC or plain RGBA
de­pend­ing on what your browser sup­ports.</p>
<div class="m-button m-success">
<a href="https://magnum.graphics/showcase/player/"><div class="m-big">
Mag­num Web Play­er</div>
<div class="m-small">
drag&drop any glTF, JPEG, PNG or Basis file</div>
</a>
</div>
</section>
<section id="dart-integration-and-an-example">
<h2><a href="#dart-integration-and-an-example">DART in­teg­ra­tion and an ex­ample</a></h2>
<p>The <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1DartIntegration.html">DartInteg­ra­tion</a> lib­rary, in­teg­rat­ing the DART An­im­a­tion and Ro­bot­ics
Toolkit, con­trib­uted by <a href="https://github.com/costashatz">@cost­ashatz</a> over a year ago, now re­ceived a
well-pol­ished in­ter­act­ive ex­ample. As a side dish, Cos­t­as wrote a de­tailed
over­view post, ex­plain­ing both the code and the ro­bot­ics back­ground:</p>
<figure class="m-figure">
<a href="https://blog.magnum.graphics/guest-posts/using-dart-to-control-a-robotic-manipulator/"><img src="https://static.magnum.graphics/img/blog/guest-posts/using-dart-to-control-a-robotic-manipulator.png" style="width: 400px" /></a>
<figcaption><a href="https://blog.magnum.graphics/guest-posts/using-dart-to-control-a-robotic-manipulator/">Us­ing DART to con­trol a ro­bot­ic ma­nip­u­lat­or »</a><div class="m-figure-description">
Gues post by Kon­stanti­nos Chatzily­ger­oud­is</div>
</figcaption>
</figure>
</section>
<section id="buildsystem-usability-improvements">
<h2><a href="#buildsystem-usability-improvements">Build­sys­tem us­ab­il­ity im­prove­ments</a></h2>
<p>The 2019.10 re­lease irons out the re­main­ing pain points in us­ing Mag­num
lib­rar­ies as CMake sub­pro­jects. All bin­ar­ies are now put in­to a com­mon
dir­ect­ory in­side the build dir, which means no hassle with DLL paths on Win­dows
any­more — and to help the com­mon use cases even fur­ther, SDL and GLFW DLLs
are auto­mat­ic­ally copied there as well.</p>
<p>Plu­gin us­age with CMake sub­pro­jects is sig­ni­fic­antly im­proved too. Dy­nam­ic
plu­gin bin­ar­ies are put in a cent­ral place in the build dir­ect­ory and the
plu­gin man­agers now look for them re­l­at­ively to loc­a­tion of giv­en plu­gin
in­ter­face lib­rary, re­mov­ing the need to in­stall everything first.</p>
<pre class="m-code"><span class="nb">set</span><span class="p">(</span><span class="s">WITH_TINYGLTFIMPORTER</span> <span class="s">ON</span><span class="p">)</span>
<span class="nb">set</span><span class="p">(</span><span class="s">WITH_STBIMAGEIMPORTER</span> <span class="s">ON</span><span class="p">)</span>
<span class="nb">add_subdirectory</span><span class="p">(</span><span class="s">magnum-plugins</span><span class="p">)</span></pre>
<p>Note that the above bumped the min­im­al CMake ver­sion re­quire­ment from 3.1 to
3.4, al­though we don’t ex­pect any is­sues as the ver­sions cur­rently in
wide­spread use is 3.5. In any case, you can al­ways down­load a pre­b­uilt ver­sion
for your plat­form.</p>
<p>Thanks to ex­tens­ive feed­back from <a href="https://github.com/alanjfs">@alan­jfs</a>, the <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/getting-started.html">Get­ting Star­ted Guide</a>
got re­writ­ten and is now easi­er to fol­low by first-time users on Win­dows, not
re­quir­ing any­body to fiddle with <code class="m-code"><span class="nv">%PATH%</span></code> or in­stalling things to ran­dom
places any­more.</p>
</section>
<section id="windows-specific-goodies">
<h2><a href="#windows-specific-goodies">Win­dows-spe­cif­ic good­ies</a></h2>
<p>Com­pared to 2019.01, there’s an of­fi­cial sup­port for MS­VC 2019. The com­piler
still needs a few work­arounds com­pared to GCC / Clang, but it’s re­l­at­ively
minor things that should not af­fect us­ab­il­ity. Ex­tra­pol­at­ing fur­ther, we ex­pect
the next ver­sion of MS­VC to be fully con­form­ing, and thus not need­ing any
com­piler-spe­cif­ic hand­ling. We’re com­mited to fully sup­port­ing all pre­vi­ous
ver­sions back to MS­VC 2015 for the fore­see­able fu­ture.</p>
<p><a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/main.html">Cor­rade::Main</a> is a new lib­rary that, on Win­dows, adds a shim
around your <code class="cpp m-code"><span class="n">main</span><span class="p">()</span></code> func­tion, sets up UTF-8 ter­min­al en­cod­ing, en­ables
AN­SI col­or es­cape codes and con­verts Uni­code com­mand-line ar­gu­ments to UTF-8 as
well, en­abling you to use the same stand­ards-con­form­ing code on all plat­forms.
Ad­di­tion­ally, it’ll also al­low you to hide the ter­min­al win­dow lurk­ing in
back­ground without for­cing you to im­ple­ment <code class="cpp m-code"><span class="n">WinMain</span><span class="p">()</span></code>. With CMake, this
is all you need to do:</p>
<pre class="m-code"><span class="nb">find_package</span><span class="p">(</span><span class="s">Corrade</span> <span class="s">REQUIRED</span> <span class="s">Main</span><span class="p">)</span>
<span class="nb">add_executable</span><span class="p">(</span><span class="s">my-application</span> <span class="s">WIN32</span> <span class="s">main.cpp</span><span class="p">)</span> <span class="c"># WIN32 turns it into a GUI app</span>
<span class="nb">target_link_libraries</span><span class="p">(</span><span class="s">my-application</span> <span class="s">PRIVATE</span> <span class="s">Corrade::Main</span><span class="p">)</span></pre>
<p>An­oth­er thing worth men­tion­ing is that <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Platform_1_1Sdl2Application.html">Plat­form::Sdl2Ap­plic­a­tion</a> and
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Platform_1_1GlfwApplication.html">Plat­form::GlfwAp­plic­a­tion</a> now have ba­sic DPI aware­ness on Win­dows,
catch­ing up with oth­er plat­forms.</p>
</section>
<section id="opengl-related-improvements">
<h2><a href="#opengl-related-improvements">OpenGL-re­lated im­prove­ments</a></h2>
<p>Com­pared to oth­er parts of the lib­rary, the OpenGL backend was kept on the
back­burn­er in the last few re­leases, not re­ceiv­ing any huge new fea­tures. For
2019.10, it got cleaned up from the old <a href="https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_direct_state_access.txt">EX­T_­dir­ec­t_state_ac­cess</a>
ex­ten­sion, keep­ing just the new­er and bet­ter-de­signed <a href="https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_direct_state_access.txt">AR­B_­dir­ec­t_state_ac­cess</a>
simply be­cause all cur­rent drivers im­ple­ment­ing the EXT vari­ant sup­port the ARB
one as well. On the oth­er hand, there’s a <em>ton</em> of driver bugs re­lated to the
<abbr title="2014 was a mere year ago, right?">new</abbr> ex­ten­sion, es­pe­cially on In­tel
and AMD drivers on Win­dows. To counter that, Mag­num re­ceived
<a href="https://twitter.com/czmosra/status/1106955396219105285">about a dozen new driver work­arounds</a>
to en­sure it be­haves as ex­pec­ted even when the driver doesn’t.</p>
<p>One of the <em>es­sen­tial</em> new things is the <code>--magnum-gpu-validation</code>
com­mand-line op­tion. In­stead of hav­ing to cre­ate a de­bug con­text manu­ally and
then fiddle with <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1GL_1_1DebugOutput.html">GL::De­bugOut­put</a> call­backs, this op­tion will do this for
you. Very use­ful for quick de­bug­ging of the dreaded “why it doesn’t render”
is­sues. For win­dow­less EGL con­texts on Linux, there’s also a new
<code>--magnum-device</code> op­tion giv­ing you an abil­ity to switch between dif­fer­ent
devices. It’s known to work with bin­ary NVidia drivers and Mesa since 19.2 —
there it usu­ally al­lows you to choose between an in­teg­rated GPU, a ded­ic­ated
card or a soft­ware ren­der­er.</p>
<aside class="m-note m-info">
The new op­tion names are de­lib­er­ately chosen to not have any re­la­tion to
the OpenGL API, since the plan is to have the same op­tions af­fect the
up­com­ing Vulkan backend as well.</aside>
</section>
<section id="single-header-libraries">
<h2><a href="#single-header-libraries">Single-head­er lib­rar­ies</a></h2>
<p>Start­ing with this re­lease, a sub­set of Mag­num func­tion­al­ity is be­ing ex­posed
through single-head­er lib­rar­ies over at <a class="m-link-wrap" href="https://github.com/mosra/magnum-singles">https://github.com/mosra/magnum-singles</a>.
These are all <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/acme.html">gen­er­ated from multi-file sources</a> and thus con­tain
the best of both worlds — small foot­print of the gen­er­ated files as all
doc­u­ment­a­tion, com­ments and non-es­sen­tial fea­tures are stripped out, but on the
oth­er hand they in­her­it ex­tens­ive doc­u­ment­a­tion and >95% test cov­er­age of the
ori­gin­al source code.</p>
<p>The lib­rar­ies were gradu­ally in­tro­duced in the past posts, here’s the whole
list:</p>
<table class="m-table m-container-inflate">
<thead>
<tr><th>Lib­rary</th>
<th>LoC</th>
<th>PpLoC<a class="m-footnote" href="#id8" id="id2">1</a></th>
<th>De­scrip­tion</th>
</tr>
</thead>
<tbody>
<tr><td><a href="https://github.com/mosra/magnum-singles/blob/master/CorradeArrayView.h">Cor­rade­Ar­rayView.h</a></td>
<td>644</td>
<td>2489</td>
<td><a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/classCorrade_1_1Containers_1_1ArrayView.html">Con­tain­ers::Ar­rayView</a>
and <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/classCorrade_1_1Containers_1_1StaticArrayView.html">StaticAr­rayView</a>,
light­weight al­tern­at­ives
to <a class="m-flat" href="https://en.cppreference.com/w/cpp/container/span">std::span</a></td>
</tr>
<tr><td><a href="https://github.com/mosra/magnum-singles/blob/master/CorradeStridedArrayView.h">Cor­radeStridedAr­rayView.h</a></td>
<td>594<a class="m-footnote" href="#id9" id="id3">2</a></td>
<td>2923</td>
<td><a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/classCorrade_1_1Containers_1_1StridedArrayView.html">Con­tain­ers::StridedAr­rayView</a>,
light­weight al­tern­at­ive to
pro­posed <a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0009r9.html">std::md­span</a>.</td>
</tr>
<tr><td><a href="https://github.com/mosra/magnum-singles/blob/master/CorradeArray.h">Cor­rade­Ar­ray.h</a></td>
<td>698<a class="m-footnote" href="#id9" id="id4">2</a></td>
<td>3344</td>
<td><a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/classCorrade_1_1Containers_1_1Array.html">Con­tain­ers::Ar­ray</a>
and <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/classCorrade_1_1Containers_1_1StaticArray.html">StaticAr­ray</a>,
light­weight
al­tern­at­ives to
<a class="m-flat" href="https://en.cppreference.com/w/cpp/container/vector">std::vec­tor</a> and
<a class="m-flat" href="https://en.cppreference.com/w/cpp/container/array">std::ar­ray</a></td>
</tr>
<tr><td><a href="https://github.com/mosra/magnum-singles/blob/master/CorradeOptional.h">Cor­rade­Op­tion­al.h</a></td>
<td>330</td>
<td>2736</td>
<td><a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/classCorrade_1_1Containers_1_1Optional.html">Con­tain­ers::Op­tion­al</a>,
a light­weight
al­tern­at­ive to
<a class="m-flat" href="https://en.cppreference.com/w/cpp/utility/optional">std::op­tion­al</a></td>
</tr>
<tr><td><a href="https://github.com/mosra/magnum-singles/blob/master/CorradePointer.h">Cor­rade­Point­er.h</a></td>
<td>263</td>
<td>2312</td>
<td><a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/classCorrade_1_1Containers_1_1Pointer.html">Con­tain­ers::Point­er</a>,
a light­weight
al­tern­at­ive to
<a class="m-flat" href="https://en.cppreference.com/w/cpp/memory/unique_ptr">std::unique_ptr</a></td>
</tr>
<tr><td><a href="https://github.com/mosra/magnum-singles/blob/master/CorradeReference.h">Cor­radeRefer­en­ce.h</a></td>
<td>115</td>
<td>1626</td>
<td><a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/classCorrade_1_1Containers_1_1Reference.html">Con­tain­ers::Ref­er­en­ce</a>,
a light­weight
al­tern­at­ive to
<a class="m-flat" href="https://en.cppreference.com/w/cpp/utility/functional/reference_wrapper">std::ref­er­en­ce_wrap­per</a></td>
</tr>
<tr><td><a href="https://github.com/mosra/magnum-singles/blob/master/CorradeScopeGuard.h">Cor­ra­de­Scope­Guard.h</a></td>
<td>131</td>
<td>34</td>
<td><a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/classCorrade_1_1Containers_1_1ScopeGuard.html">Con­tain­ers::Scope­Guard</a>,
al­tern­at­ive to
<a class="m-flat" href="https://en.cppreference.com/w/cpp/memory/unique_ptr">std::unique_ptr</a>
with a cus­tom de­leter</td>
</tr>
<tr><td><a href="https://github.com/mosra/magnum-singles/blob/master/CorradeStlForwardArray.h">Cor­radeStlFor­wardAr­ray.h</a></td>
<td>67</td>
<td>2436<a class="m-footnote" href="#id10" id="id5">3</a></td>
<td><a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/StlForwardArray_8h.html">For­ward de­clar­a­tion for std::ar­ray</a></td>
</tr>
<tr><td><a href="https://github.com/mosra/magnum-singles/blob/master/CorradeStlForwardString.h">Cor­radeStlFor­ward­String.h</a></td>
<td>74</td>
<td>48</td>
<td><a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/StlForwardString_8h.html">For­ward de­clar­a­tion for std::string</a></td>
</tr>
<tr><td><a href="https://github.com/mosra/magnum-singles/blob/master/CorradeStlForwardTuple.h">Cor­radeStlFor­wardTuple.h</a></td>
<td>78</td>
<td>1601</td>
<td><a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/StlForwardTuple_8h.html">For­ward de­clar­a­tion for std::tuple</a></td>
</tr>
<tr><td><a href="https://github.com/mosra/magnum-singles/blob/master/CorradeStlForwardVector.h">Cor­radeStlFor­ward­Vec­tor.h</a></td>
<td>62</td>
<td>766<a class="m-footnote" href="#id10" id="id6">3</a></td>
<td><a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/StlForwardVector_8h.html">For­ward de­clar­a­tion for std::vec­tor</a></td>
</tr>
<tr><td><a href="https://github.com/mosra/magnum-singles/blob/master/CorradeStlMath.h">Cor­radeStl­Math.h</a></td>
<td>57</td>
<td>2970<a class="m-footnote" href="#id11" id="id7">4</a></td>
<td>Like <code class="cpp m-code"><span class="cp">#include</span> <span class="cpf"><cmath></span><span class="cp"></span></code>, but <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/StlMath_8h.html">without the heavy C++17 ad­di­tions</a></td>
</tr>
</tbody>
</table>
<dl class="m-footnote">
<dt id="id8">1</a>.</dt>
<dd><span class="m-footnote"><a href="#id2">^</a></span> lines of code after a pre­pro­cessor run, with sys­tem in­cludes ex­pan­ded.
Gathered us­ing GCC 9.2 and lib­stdc++, un­less said oth­er­wise.</dd>
<dt id="id9">2.</dt>
<dd><span class="m-footnote">^ <a href="#id3">a</a> <a href="#id4">b</a></span> not a total size due to inter-lib­rary de­pend­en­cies</dd>
<dt id="id10">3.</dt>
<dd><span class="m-footnote">^ <a href="#id5">a</a> <a href="#id6">b</a></span> gathered us­ing Clang 9.0 and libc++, since lib­stdc++ doesn’t have a
for­ward de­clar­a­tion for <a class="m-flat" href="https://en.cppreference.com/w/cpp/container/array">std::ar­ray</a> / <a class="m-flat" href="https://en.cppreference.com/w/cpp/container/vector">std::vec­tor</a></dd>
<dt id="id11">4</a>.</dt>
<dd><span class="m-footnote"><a href="#id7">^</a></span> gathered us­ing GCC 9.2, lib­stdc++ and <code>-std=c++17</code></dd>
</dl>
</section>
<section id="testsuite-improvements-shader-testing">
<h2><a href="#testsuite-improvements-shader-testing">Test­Suite im­prove­ments, shader test­ing</a></h2>
<p>If you’re not yet us­ing <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/classCorrade_1_1TestSuite_1_1Tester.html">Test­Suite</a> for tests in your
Mag­num-based pro­ject (well, or any oth­er), con­sider giv­ing it a try. For this
re­lease, con­tin­ued ef­fort was put on render out­put test­ing —
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1DebugTools_1_1CompareImage.html">De­bug­Tools::Com­pareIm­age</a> re­ceived an abil­ity to save a dia­gnost­ic file
in case of a com­par­is­on fail­ure, and can com­pare against an ar­bit­rary pixel
view in ad­di­tion to files and <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1ImageView.html">Im­ageView</a> in­stances. Tests can be now also
run with verb­ose out­put, show­ing de­tailed info even in case the com­par­is­on
passes:</p>
<figure class="m-code-figure">
<pre class="m-code">./ShadersPhongGLTest -v --only <span class="m">38</span></pre>
<pre class="m-nopad m-console-wrap m-console"><span class="g g-AnsiBrightDefault">Starting Magnum::Shaders::Test::PhongGLTest with 1 test cases...</span>
<span class="g g-AnsiBrightDefault"> INFO</span><span class="g g-AnsiBlue"> [</span><span class="g g-AnsiBrightCyan">38</span><span class="g g-AnsiBlue">]</span><span class="g g-AnsiBrightDefault"> renderShininess(</span>80<span class="g g-AnsiBrightDefault">)</span> at src/Magnum/Shaders/Test/PhongGLTest.cpp on line 966
Images Containers::arrayCast<Color3ub>(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels<Color4ub>()) and Utility::Directory::join({_testDir, "PhongTestFiles", data.expected}) have deltas 1.66667/0.0159375 below threshold 12/0.043. Delta image:
| |
| |
| |
| <span class="g g-AnsiBrightYellow">::</span> <span class="g g-AnsiBrightYellow">:</span> |
| <span class="g g-AnsiBrightYellow">Z:</span> <span class="g g-AnsiBrightYellow">:::</span> |
| <span class="g g-AnsiBrightYellow">:Z</span> <span class="g g-AnsiBrightYellow">Z</span> |
| <span class="g g-AnsiBrightYellow">::</span> <span class="g g-AnsiBrightYellow">Z</span> <span class="g g-AnsiBrightYellow">:</span> <span class="g g-AnsiBrightYellow">:Z:</span> |
| <span class="g g-AnsiBrightYellow">Z</span> <span class="g g-AnsiBrightYellow">:Z:</span> <span class="g g-AnsiBrightYellow">Z:</span> <span class="g g-AnsiBrightYellow">:</span> |
| <span class="g g-AnsiBrightYellow">::</span> <span class="g g-AnsiBrightYellow">+</span> <span class="g g-AnsiBrightYellow">:</span> <span class="g g-AnsiBrightYellow">:</span> <span class="g g-AnsiBrightYellow">:</span> |
| <span class="g g-AnsiBrightYellow">:</span> <span class="g g-AnsiBrightYellow">:</span> <span class="g g-AnsiBrightYellow">:</span> <span class="g g-AnsiBrightYellow">:</span> <span class="g g-AnsiBrightYellow">::</span> <span class="g g-AnsiBrightYellow">:</span> |
| <span class="g g-AnsiBrightYellow">:</span> <span class="g g-AnsiBrightYellow">Z</span> |
| <span class="g g-AnsiBrightYellow">+::</span> <span class="g g-AnsiBrightYellow">:</span> <span class="g g-AnsiBrightYellow">:</span> <span class="g g-AnsiBrightYellow">Z</span> <span class="g g-AnsiBrightYellow">:</span> |
| <span class="g g-AnsiBrightYellow">:</span> <span class="g g-AnsiBrightYellow">++::</span> <span class="g g-AnsiBrightYellow">ZZ</span> <span class="g g-AnsiBrightYellow">:</span> <span class="g g-AnsiBrightYellow">+Z::</span> <span class="g g-AnsiBrightYellow">:</span> <span class="g g-AnsiBrightYellow">::</span> |
| <span class="g g-AnsiBrightYellow">+ZZ:</span> <span class="g g-AnsiBrightYellow">+:</span> <span class="g g-AnsiBrightYellow">:</span> <span class="g g-AnsiBrightYellow">:</span> <span class="g g-AnsiBrightYellow">Z:</span> |
Top 10 out of 189 pixels above max/mean threshold:
[19,23] #f96161, expected #fa6363 (Δ =<span class="g g-AnsiBrightYellow"> 1.66667</span>)
[20,23] #ffa9a9, expected #ffabab (Δ =<span class="g g-AnsiBrightYellow"> 1.33333</span>)
[21,22] #ffd9d9, expected #ffdbdb (Δ =<span class="g g-AnsiBrightYellow"> 1.33333</span>)
[20,22] #ff9090, expected #ff9292 (Δ =<span class="g g-AnsiBrightYellow"> 1.33333</span>)
[13,62] #250707, expected #260808 (Δ =<span class="g g-AnsiBrightYellow"> 1</span>)
[28,56] #2a0808, expected #2b0909 (Δ =<span class="g g-AnsiBrightYellow"> 1</span>)
[11,56] #4d0f0f, expected #4e1010 (Δ =<span class="g g-AnsiBrightYellow"> 1</span>)
[31,54] #2a0808, expected #2b0909 (Δ =<span class="g g-AnsiBrightYellow"> 1</span>)
[19,54] #490f0f, expected #480e0e (Δ =<span class="g g-AnsiBrightYellow"> 1</span>)
[ 8,51] #6b1515, expected #6c1616 (Δ =<span class="g g-AnsiBrightYellow"> 1</span>)
<span class="g g-AnsiBrightDefault">Finished Magnum::Shaders::Test::PhongGLTest with 0 errors out of 2 checks.</span></pre>
</figure>
<p>With these im­prove­ments in place, the whole <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1Shaders.html">Shaders</a> lib­rary has tests
for ren­der­ing out­put. So far, thanks to these, we ironed out a bunch of bugs
in dusty corner cases, but that’s not all — it makes fur­ther modi­fic­a­tions,
op­tim­iz­a­tions and im­prove­ments easi­er to make as re­gres­sions will now be caught
through auto­mat­ic test­ing.</p>
</section>
<section id="reduced-overhead-guaranteed-thread-safety-and-uniqueness-of-globals">
<h2><a href="#reduced-overhead-guaranteed-thread-safety-and-uniqueness-of-globals">Re­duced over­head, guar­an­teed thread safety and unique­ness of glob­als</a></h2>
<p>While glob­als are of­ten a source of im­mense pain, some­times hav­ing a state
glob­al is the most prag­mat­ic de­cision of all. Mag­num cur­rently uses glob­als in
these few places:</p>
<div class="m-col-m-4 m-right-m m-container-inflate">
<aside class="m-note m-danger">
<p>Singletons are bad!!</p>
<p class="m-text m-dim m-text-right">— every­one</p>
</aside>
</div>
<ol>
<li><a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/classCorrade_1_1Utility_1_1Debug.html">Util­ity::De­bug</a> scoped out­put re­dir­ec­tion and col­or­ing</li>
<li>Each com­piled-in <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/classCorrade_1_1Utility_1_1Resource.html">Util­ity::Re­source</a> re­source re­gisters it­self
in­to a glob­al stor­age</li>
<li>Sim­il­arly, stat­ic plu­gins re­gister them­selves in­to <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/namespaceCorrade_1_1PluginManager.html">Plu­gin­Man­ager</a></li>
<li>And be­cause OpenGL (and then Open­AL, which is mod­elled after it) has a
glob­al con­text, it makes sense to have cur­rent <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1GL_1_1Context.html">GL::Con­text</a> /
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Audio_1_1Context.html">Au­dio::Con­text</a> glob­ally ac­cess­ible as well</li>
</ol>
<p>One oth­er us­age of glob­als was in <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1ResourceManager.html">Re­source­M­an­ager</a> (and trans­it­ively in <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1DebugTools_1_1ObjectRenderer.html">De­bug­Tools::Ob­jectRender­er</a> as well), but those APIs are now de­prec­ated
in fa­vor of ex­pli­citly passed ref­er­ences. And, for the up­com­ing Vulkan backend,
there’s no plan to have a GL-like “glob­al con­text” at all.</p>
<p>For this re­lease, all glob­al state was re­writ­ten to be com­pletely
al­loc­a­tion-free (re­gis­tra­tion of re­sources and stat­ic plu­gins is now just
build­ing an in­trus­ive linked list), which means there’s no need to run any
glob­al de­struct­ors for these. Moreover, while already very light­weight, the
auto­mat­ic re­gis­tra­tion can be com­pletely op­ted out of, al­low­ing you to get rid
of glob­al con­struct­ors as well.</p>
<p>All glob­al state that’s read-write is now made <code class="cpp m-code"><span class="k">thread_local</span></code>, mean­ing
every thread will have its own copy of the glob­al data. This makes more sense
than hav­ing the glob­al state ac­cess guarded by a lock. Be­sides be­ing faster,
you might want to re­dir­ect your log out­put to a file in one thread but not in
the oth­er. Apart from these, Mag­num doesn’t do any­thing about thread­ing on its
own — if your app needs to share data across threads, you’re fully
re­spons­ible for guard­ing against data races. Thread-loc­al vari­ables of course
come with some small over­head, and if you don’t need that, you can turn it off
via the <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/Corrade_8h.html#a1ac88f5e5f8c979d585486d09d3f2a76">COR­RADE_BUILD_MUL­TI­TH­READED</a> op­tion.</p>
<p>With the in­tro­duc­tion of Py­thon bind­ings, glob­als posed an­oth­er prob­lem — if
Mag­num is built stat­ic­ally and then linked in­to two dis­tinct Py­thon mod­ules,
the glob­als get du­plic­ated, each mod­ule hav­ing its own copy. On Unix sys­tems
this was eas­ily solved by mark­ing the few glob­als ex­por­ted <em>weak sym­bols</em>,
telling the dy­nam­ic linker to al­ways pick only one of them. On Win­dows there’s
no no­tion of a weak link­ing and ad­di­tion­ally <code class="cpp m-code"><span class="kr">__declspec</span><span class="p">(</span><span class="n">dllexport</span><span class="p">)</span></code>
at­trib­utes can’t be <code class="cpp m-code"><span class="k">thread_local</span></code>, so this got solved by a
<abbr title="it's a shitty workaround">brown ma­gic</abbr> in­volving
<a class="m-flat" href="https://docs.microsoft.com/en-us/cpp/build/getprocaddress?view=vs-2019">Get­P­ro­c­Ad­dress()</a>.</p>
</section>
<section id="audio-related-additions">
<h2><a href="#audio-related-additions">Au­dio-re­lated ad­di­tions</a></h2>
<p><a href="https://github.com/williamjcm">@wil­li­amjcm</a>, who can be now con­sidered our au­dio ex­pert thanks to all his
con­tri­bu­tions, im­ple­men­ted loop points in <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Audio_1_1Buffer.html">Au­dio::Buf­fer</a>, buf­fer queuing
and an MP3 im­port­er in <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Audio_1_1DrMp3Importer.html">DrM­p3Au­di­oIm­port­er</a> — all
MP3-re­lated pat­ents fi­nally ex­pired back in 2017, so there’s shouldn’t be any
leg­al pres­sure against us­ing MP3 files for your au­dio tracks any­more.</p>
<p>Fol­low­ing <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1GL_1_1Context.html">GL::Con­text</a>, the <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Audio_1_1Context.html">Au­dio::Con­text</a> now un­der­stands the
<code>--magnum-disable-extensions</code> and <code>--magnum-log</code> op­tions as well.</p>
</section>
<section id="full-changelog-and-what-s-next">
<h2><a href="#full-changelog-and-what-s-next">Full changelog … and what’s next?</a></h2>
<p>This re­lease took al­most 9 months to make, much more than ini­tially planned,
and a “re­lease cut” had to be made in or­der to keep it from grow­ing
in­def­in­itely. Be­cause of that, there’s a lot of things that didn’t fit in­to
this an­nounce­ment and the changelogs are lar­ger than you might ex­pect:</p>
<ul>
<li><a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/corrade-changelog.html#corrade-changelog-2019-10">Changes in Cor­rade 2019.10</a></li>
<li><a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/changelog.html#changelog-2019-10">Changes in Mag­num 2019.10</a></li>
<li><a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/changelog-plugins.html#changelog-plugins-2019-10">Changes in Mag­num Plu­gins 2019.10</a></li>
<li><a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/changelog-integration.html#changelog-integration-2019-10">Changes in Mag­num In­teg­ra­tion 2019.10</a></li>
<li><a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/changelog-extras.html#changelog-extras-2019-10">Changes in Mag­num Ex­tras 2019.10</a></li>
<li><a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/changelog-examples.html#changelog-examples-2019-10">Changes in Mag­num Ex­amples 2019.10</a></li>
</ul>
<p>For the next ver­sion, apart from im­age-re­lated im­prove­ments
<a href="#image-api-improvements">hin­ted above</a>, well un­der­way is a re­work of
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1MeshData3D.html">Trade::Mesh­Data3D</a>, with sup­port for more ver­tex at­trib­utes, ar­bit­rary
data types and zero-copy data im­port. This one will likely res­ult also in
ad­di­tions to Cor­rade con­tain­er types (grow­able ar­rays) and vari­ous oth­er
things. Sub­scribe to <a href="https://github.com/mosra/magnum/issues/371">mosra/mag­num#371</a> for up­dates.</p>
<p>Hav­ing Py­thon bind­ings out of the way, the Vulkan bind­ings got a pri­or­ity as
well — ex­pect Vulkan-re­lated changes pop­ping up in the next months.</p>
</section>
<section id="updating-from-previous-versions">
<h2><a href="#updating-from-previous-versions">Up­dat­ing from pre­vi­ous ver­sions</a></h2>
<p>If you’re us­ing Homebrew, MSYS pack­ages Arch­Linux AUR or Vcp­kg, 2019.10 is
already in the re­pos­it­or­ies. Arch­Linux com­munity pack­ages are sched­uled for an
up­date, and Ubuntu pack­ages can be built dir­ectly from with­in the cloned
re­pos­it­ory as usu­al.</p>
<p>The lib­rary is con­stantly un­der­go­ing a “head­er hy­giene” in­clude cleanup,
mean­ing you can now get com­piler er­rors re­lated to use of in­com­plete types. The
fix is in most cases in­clud­ing cor­res­pond­ing head­ers — in many cases some of
these:</p>
<pre class="m-code"><span class="cp">#include</span> <span class="cpf"><Corrade/Containers/Reference.h></span><span class="cp"></span>
<span class="cp">#include</span> <span class="cpf"><Corrade/Containers/Optional.h></span><span class="cp"></span>
<span class="cp">#include</span> <span class="cpf"><Corrade/Utility/DebugStl.h></span><span class="cp"></span>
<span class="cp">#include</span> <span class="cpf"><Magnum/Math/Matrix4.h></span><span class="cp"></span></pre>
<p>Since it’s been over a year since the “GL split” in <a href="2018.04.rst">2018.04</a>, 2019.10 re­moves
all com­pat­ib­il­ity ali­ases of <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1GL.html">GL</a> APIs in the root namespace. If you’re
up­grad­ing from older ver­sions, the re­com­men­ded way is as al­ways jump­ing over
all stable re­leases (so 2018.04, 2018.10, 2019.01) and fix­ing up what breaks,
in­stead of dir­ectly try­ing with the latest.</p>
</section>
<section id="thank-you">
<h2><a href="#thank-you">Thank you</a></h2>
<p>A huge part of the work for this re­lease was done by ex­tern­al con­trib­ut­ors —
sin­cere thanks to every­one (and apo­lo­gies to those I for­got):</p>
<ul>
<li><a href="https://github.com/alanjfs">Alan Jef­fer­son</a> for ex­tens­ive us­ab­il­ity feed­back on
tu­tori­als and doc­u­ment­a­tion</li>
<li><a href="https://github.com/bowling-allie">Al­lie</a> for Em­scripten-re­lated us­ab­il­ity im­prove­ments</li>
<li><a href="https://github.com/cegbertOculus">Camer­on Egbert</a> for work on the Win­dows port of the
new Py­thon bind­ings</li>
<li><a href="https://github.com/Bluer01">Daniel Bloor</a> for set­ting old code on fire</li>
<li><a href="https://github.com/roig">Daniel Guz­man</a> for <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1ResourceManager.html">Re­source­M­an­ager</a> im­prove­ments</li>
<li><a href="https://github.com/fgoujeon">Flori­an Goujeon</a> for iOS fixes in the <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1Shaders.html">Shaders</a>
lib­rary</li>
<li><a href="https://github.com/williamjcm">Guil­laume Jac­quemin</a> for vari­ous
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Platform_1_1GlfwApplication.html">Plat­form::GlfwAp­plic­a­tion</a> / <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Platform_1_1Sdl2Application.html">Plat­form::Sdl2Ap­plic­a­tion</a>
im­prove­ments, <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Audio_1_1Buffer.html">Au­dio::Buf­fer</a> queuing and loop point sup­port,
fea­ture par­ity between <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Audio_1_1Context.html">Au­dio::Con­text</a> and <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1GL_1_1Context.html">GL::Con­text</a>,
the <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Audio_1_1DrMp3Importer.html">DrM­p3Au­di­oIm­port­er</a> plu­gin,
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1ImGuiIntegration.html">ImGui­In­teg­ra­tion</a> im­prove­ments, MSYS pack­age main­ten­ance and much
more</li>
<li><a href="https://github.com/ikalevatykh">Ig­or Kal­evatykh</a> for im­prove­ments to
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1AssimpImporter.html">As­simpIm­port­er</a></li>
<li><a href="https://github.com/TheHugeManatee">Jakob Weiss</a> for ini­tial work on mak­ing the
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/namespaceCorrade_1_1Interconnect.html">In­ter­con­nect</a> lib­rary ac­cept state­ful lambdas</li>
<li><a href="https://github.com/Squareys">Jonath­an Hale</a> for <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Platform_1_1EmscriptenApplication.html">Plat­form::Em­scrip­tenAp­plic­a­tion</a>,
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1BasisImporter.html">BasisIm­port­er</a> and
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1BasisImageConverter.html">BasisIm­age­Con­vert­er</a>, Vcp­kg pack­age
main­ten­ance and more</li>
<li><a href="https://github.com/costashatz">Kon­stanti­nos Chatzily­ger­oud­is</a> for con­tin­ued main­ten­ance
of <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1DartIntegration.html">DartInteg­ra­tion</a> and the DART ex­ample</li>
<li><a href="https://github.com/Melix19">Marco Melorio</a> for <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1ImGuiIntegration.html">ImGui­In­teg­ra­tion</a> im­prove­ments and
help with ma­cOS/iOS test­ing</li>
<li><a href="https://github.com/xqms">Max Schwarz</a> for all work done on im­prov­ing as­set man­age­ment,
mul­ti­th­read­ing and gen­er­al sta­bil­ity</li>
<li><a href="https://github.com/NSkelsey">Nick Skel­sey</a> for doc­u­ment­a­tion copy-edit­ing</li>
<li><a href="https://github.com/codewing">Win­fried Bau­mann</a> for ex­ample code cleanup</li>
<li><a href="https://github.com/xiconxi">Cong Xie</a>, <a href="https://github.com/erikwijmans">Erik Wij­mans</a>,
<a href="https://github.com/isc30">Ivan Sanz Carasa</a>, <a href="https://github.com/msbaines">Mandeep Singh Baines</a>,
<a href="https://github.com/mtao">Mi­chael Tao</a>, <a href="https://github.com/pkubaj">@pkubaj</a>, <a href="https://github.com/Selot">@Selot</a>,
<a href="https://github.com/Tryum">Thibault Jo­chem</a> and many oth­ers who con­trib­uted vari­ous fixes
to make things work bet­ter on a broad­er range of plat­forms</li>
</ul>
<aside class="m-note m-dim">
Share your opin­ion on so­cial net­works:
<a href="https://twitter.com/czmosra/status/1189558650957946882">Twit­ter</a>;
Red­dit <a href="https://www.reddit.com/r/cpp/comments/dp80np/magnum_engine_201910_released_with_python/">r/cpp</a>,
<a href="https://www.reddit.com/r/Python/comments/dp86aq/magnum_engine_201910_released_with_python/">r/py­thon</a>,
<a href="https://www.reddit.com/r/gamedev/comments/dp85pv/magnum_engine_201910_released_with_python/">r/game­dev</a>,
<a href="https://www.reddit.com/r/glTF/comments/dp8las/magnum_engine_201910_released_with_python/">r/gltf</a>,
<a href="https://www.reddit.com/r/webgl/comments/dp92oi/magnum_engine_201910_released_with_emscripten/">r/webgl</a>;
<a href="https://news.ycombinator.com/item?id=21398699">Hack­er News</a></aside>
</section>
Introducing Magnum Python Bindings2019-09-17T00:00:00+02:002019-09-18T00:00:00+02:00Vladimír Vondruštag:blog.magnum.graphics,2019-09-17:/announcements/introducing-python-bindings/<p>Dur­ing the past four months, Mag­num began its ad­ven­ture in­to the
Py­thon world. Not just with <em>some</em> auto­gen­er­ated bind­ings and not just with
<em>some</em> auto­gen­er­ated Sphinx docs — that simply wouldn’t be Mag­num enough.
Brace yourselves, this art­icle will show you <em>everything</em>.</p>
<p>The new Mag­num Py­thon bind­ings, while still
<abbr title="to allow breaking API changes">labeled ex­per­i­ment­al</abbr>, already give you
a pack­age us­able in real work­flows — a NumPy-com­pat­ible con­tain­er lib­rary,
graph­ics-ori­ented math classes and func­tions, OpenGL buf­fer, mesh, shader and
tex­ture APIs, im­age and mesh data im­port and a SDL / GLFW ap­plic­a­tion class
with key and mouse events. Head over to the
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/python/building/">in­stall­a­tion doc­u­ment­a­tion</a> to get it your­self; if you
are on Arch­Linux or use Homebrew, pack­ages are already there, wait­ing for you:</p>
<pre class="m-code">brew tap mosra/magnum
brew install --HEAD corrade magnum magnum-plugins magnum-bindings</pre>
<p>And of course it has all good­ies you’d ex­pect from a “Py­thon-nat­ive” lib­rary
— full sli­cing sup­port, er­rors re­por­ted through Py­thon ex­cep­tions in­stead of
re­turn codes (or hard as­serts) and prop­er­ties in­stead of set­ters/get­ters where
it makes sense. To get you a quick over­view of how it looks and how is it used,
the first few ex­amples are por­ted to it:</p>
<div class="m-button m-success">
<a href="https://doc.magnum.graphics/python/examples/"><div class="m-big">
Py­thon Bind­ings</div>
<div class="m-small">
ex­ample code</div>
</a>
</div>
<aside class="m-note m-success">
<h3>Content care</h3>
<dl class="m-diary">
<dt>Sep 18th, 2019</dt>
<dd>Ad­ded a note about <a href="#become-a-10-programmer-with-this-one-weird-trick">-flto=job­serv­er</a>,
about the <a href="#what-python-apis-and-docs-could-learn-from-c">Pyrr math lib­rary</a>
and made the <a href="#everyone-just-uses-sphinx-you">Sphinx al­tern­at­ive</a>
/ m.css Py­thon doc gen­er­at­or more vis­ible.</dd>
<dt>May 17, 2021</dt>
<dd>Cla­ri­fied <a class="m-flat" href="https://en.cppreference.com/w/cpp/string/basic_string">std::string</a> al­loc­a­tions in the sec­tion about
<a href="#let-s-allocate-a-bunch-of-vectors-and-strings-to-do-a-zero-copy-data-transfer">buf­fer pro­tocol</a>.</dd>
</dl>
</aside>
<section id="enter-pybind11">
<h2><a href="#enter-pybind11">Enter py­bind11</a></h2>
<p>I dis­covered <a href="https://github.com/pybind/pybind11">py­bind11</a> by a lucky ac­ci­dent in
<a href="https://github.com/mosra/magnum/issues/228">early 2018</a> and im­me­di­ately had to try it. Learn­ing
the ba­sics and ex­pos­ing some min­im­al mat­rix/vec­tor math took me about
<em>two hours</em>. It was an ex­treme fun and I have to thank all py­bind11 de­velopers
for mak­ing it so straight­for­ward to use.</p>
<figure class="m-code-figure">
<pre class="m-code"><span class="n">py</span><span class="o">::</span><span class="n">class_</span><span class="o"><</span><span class="n">Vector3</span><span class="o">></span><span class="p">(</span><span class="n">m</span><span class="p">,</span> <span class="s">"Vector3"</span><span class="p">)</span>
<span class="p">.</span><span class="n">def_static</span><span class="p">(</span><span class="s">"x_axis"</span><span class="p">,</span> <span class="o">&</span><span class="n">Vector3</span><span class="o">::</span><span class="n">xAxis</span><span class="p">,</span> <span class="n">py</span><span class="o">::</span><span class="n">arg</span><span class="p">(</span><span class="s">"length"</span><span class="p">)</span> <span class="o">=</span> <span class="mf">1.0f</span><span class="p">)</span>
<span class="p">.</span><span class="n">def_static</span><span class="p">(</span><span class="s">"y_axis"</span><span class="p">,</span> <span class="o">&</span><span class="n">Vector3</span><span class="o">::</span><span class="n">yAxis</span><span class="p">,</span> <span class="n">py</span><span class="o">::</span><span class="n">arg</span><span class="p">(</span><span class="s">"length"</span><span class="p">)</span> <span class="o">=</span> <span class="mf">1.0f</span><span class="p">)</span>
<span class="p">.</span><span class="n">def_static</span><span class="p">(</span><span class="s">"z_axis"</span><span class="p">,</span> <span class="o">&</span><span class="n">Vector3</span><span class="o">::</span><span class="n">zAxis</span><span class="p">,</span> <span class="n">py</span><span class="o">::</span><span class="n">arg</span><span class="p">(</span><span class="s">"length"</span><span class="p">)</span> <span class="o">=</span> <span class="mf">1.0f</span><span class="p">)</span>
<span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="n">py</span><span class="o">::</span><span class="n">init</span><span class="o"><</span><span class="n">Float</span><span class="p">,</span> <span class="n">Float</span><span class="p">,</span> <span class="n">Float</span><span class="o">></span><span class="p">())</span>
<span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="n">py</span><span class="o">::</span><span class="n">init</span><span class="o"><</span><span class="n">Float</span><span class="o">></span><span class="p">())</span>
<span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="n">py</span><span class="o">::</span><span class="n">self</span> <span class="o">==</span> <span class="n">py</span><span class="o">::</span><span class="n">self</span><span class="p">)</span>
<span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="n">py</span><span class="o">::</span><span class="n">self</span> <span class="o">!=</span> <span class="n">py</span><span class="o">::</span><span class="n">self</span><span class="p">)</span>
<span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="s">"is_zero"</span><span class="p">,</span> <span class="o">&</span><span class="n">Vector3</span><span class="o">::</span><span class="n">isZero</span><span class="p">)</span>
<span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="s">"is_normalized"</span><span class="p">,</span> <span class="o">&</span><span class="n">Vector3</span><span class="o">::</span><span class="n">isNormalized</span><span class="p">)</span>
<span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="n">py</span><span class="o">::</span><span class="n">self</span> <span class="o">+=</span> <span class="n">py</span><span class="o">::</span><span class="n">self</span><span class="p">)</span>
<span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="n">py</span><span class="o">::</span><span class="n">self</span> <span class="o">+</span> <span class="n">py</span><span class="o">::</span><span class="n">self</span><span class="p">)</span>
<span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="n">py</span><span class="o">::</span><span class="n">self</span> <span class="o">*=</span> <span class="n">Float</span><span class="p">{})</span>
<span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="n">py</span><span class="o">::</span><span class="n">self</span> <span class="o">*</span> <span class="n">Float</span><span class="p">{})</span>
<span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="n">py</span><span class="o">::</span><span class="n">self</span> <span class="o">*=</span> <span class="n">py</span><span class="o">::</span><span class="n">self</span><span class="p">)</span></pre>
<p>That’s what it took to bind a vec­tor class.</p>
</figure>
<p>How­ever, dif­fer­ent things took a pri­or­ity and so the pro­to­type got shelved
un­til it got re­vived again this year. But I learned one main thing — even
just the math classes alone were some­thing <em>so use­ful</em> that I kept the built
Py­thon mod­ule around and used it from time to time as an en­hanced cal­cu­lat­or.
Now, with the <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/python/magnum/math/">mag­num.math</a> mod­ule be­ing al­most com­plete, it’s an every­day
tool I use for quick cal­cu­la­tions. Feel free to do the same.</p>
<div class="m-col-m-5 m-right-m m-container-inflate">
<figure class="m-code-figure">
<pre class="m-code"><span class="gp">>>> </span><span class="kn">from</span> <span class="nn">magnum</span> <span class="kn">import</span> <span class="o">*</span>
<span class="gp">>>> </span><span class="n">Matrix3</span><span class="o">.</span><span class="n">rotation</span><span class="p">(</span><span class="n">Deg</span><span class="p">(</span><span class="mi">45</span><span class="p">))</span>
<span class="go">Matrix(0.707107, -0.707107, 0,</span>
<span class="go"> 0.707107, 0.707107, 0,</span>
<span class="go"> 0, 0, 1)</span></pre>
<p class="m-noindent">Quick, where are the minus signs in a 2D ro­ta­tion mat­rix?</p>
</figure>
</div>
</section>
<section id="what-python-apis-and-docs-could-learn-from-c">
<h2><a href="#what-python-apis-and-docs-could-learn-from-c">What Py­thon APIs (and docs) could learn from C++</a></h2>
<p>Every time someone told me they’re us­ing <a href="https://numpy.org/">numpy</a> for
“do­ing math quickly in Py­thon”, I as­sumed it’s the reas­on­able thing to do —
un­til I ac­tu­ally tried to use it. I get that my use case of 4×4 matrices
<em>at most</em> might not align well with NumPy’s goals, but the prob­lem is, as far
as I know,
<span class="m-text m-s">there’s no full-fea­tured math lib­rary for Py­thon that would give me the whole pack­age</span><a class="m-footnote" href="#id2" id="id1">1</a>
in­clud­ing <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/python/magnum/Quaternion/">Qua­ternion</a>s or <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/python/magnum/Matrix4/">2D/3D trans­form­a­tion matrices</a>.</p>
<p>As an ex­cer­cise, for us­ab­il­ity com­par­is­on I tried to ex­press the ro­ta­tion
mat­rix shown in the box above in SciPy / NumPy. It took me a good half an hour
of star­ing at the docs of <a class="m-flat" href="https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.transform.Rotation.html#scipy.spatial.transform.Rotation">scipy.spa­tial.trans­form.Ro­ta­tion</a> un­til I
ul­ti­mately de­cided it’s not worth my time. The over­arch­ing prob­lem I have with
all those APIs is that it’s not clear at all what types I’m ex­pec­ted to feed to
them and provided ex­ample code looks like I’m sup­posed to do half of the
cal­cu­la­tions my­self any­way.</p>
<figure class="m-code-figure">
<pre class="m-code"><span class="o">>>></span> <span class="kn">from</span> <span class="nn">scipy.spatial.transform</span> <span class="kn">import</span> <span class="n">Rotation</span> <span class="k">as</span> <span class="n">R</span>
<span class="o">>>></span> <span class="n">r</span> <span class="o">=</span> <span class="n">R</span><span class="o">.</span><span class="n">from_quat</span><span class="p">([</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">np</span><span class="o">.</span><span class="n">sin</span><span class="p">(</span><span class="n">np</span><span class="o">.</span><span class="n">pi</span><span class="o">/</span><span class="mi">4</span><span class="p">),</span> <span class="n">np</span><span class="o">.</span><span class="n">cos</span><span class="p">(</span><span class="n">np</span><span class="o">.</span><span class="n">pi</span><span class="o">/</span><span class="mi">4</span><span class="p">)])</span></pre>
<blockquote>
<p class="m-noindent">Ro­ta­tion.from_quat(quat, nor­mal­ized=False)</p>
<p class="m-noindent">Para­met­ers:</p>
<dl>
<dt><strong>quat: ar­ray_­like, shape (N, 4) or (4,)</strong></dt>
<dd>Each row is a (pos­sibly non-unit norm) qua­ternion in scal­ar-last
(x, y, z, w) format.</dd>
</dl>
<p class="m-text m-dim m-small m-text-right attribution">—<a class="m-flat" href="https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.transform.Rotation.from_quat.html#scipy.spatial.transform.Rotation.from_quat">scipy.spa­tial.trans­form.Ro­ta­tion.from_quat()</a></p>
</blockquote>
<p class="m-noindent">Type in­form­a­tion in the SciPy doc­u­ment­a­tion is vague at best.
<em>Also, I’d like some­thing that would make the qua­ternion for me, as well.</em></p>
</figure>
<p>To avoid the type con­fu­sion, with Mag­num Py­thon bind­ings I de­cided to use
strong types where pos­sible — so in­stead of a single dy­nam­ic mat­rix / vec­tor
type akin to <a class="m-flat" href="https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.html#numpy.ndarray">numpy.ndar­ray</a>, there’s a clear dis­tinc­tion between matrices
and vec­tors of dif­fer­ent sizes. So then if you do <code class="m-code"><span class="n">Matrix4x3</span><span class="p">()</span> <span class="o">@</span> <span class="n">Matrix2x4</span><span class="p">()</span></code>,
docs of <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/python/magnum/Matrix4x3/#__matmul__-da39a">Mat­rix4x3.__mat­mul__()</a> will tell you the res­ult is <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/python/magnum/Matrix2x3/">Mat­rix2x3</a>.
For NumPy it­self, there’s a pro­pos­al for im­proved type an­nota­tions at
<a href="https://github.com/numpy/numpy/issues/7370">numpy/numpy#7370</a> which would help a lot, but the doc­u­ment­a­tion tools
<em>have to</em> make use of that. <a href="#everyone-just-uses-sphinx-you">More on that be­low</a>.</p>
<p>One little thing with big im­pact of the C++ API is strongly-typed angles. You
no longer need to re­mem­ber that trig func­tions use ra­di­ans in­tern­ally but HSV
col­ors or Open­AL juggles with de­grees in­stead — simply use whatever you
please. So Py­thon got the <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/python/magnum/Deg/">Deg</a> and <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/python/magnum/Rad/">Rad</a> as well. Py­thon doesn’t
have any user-defined lit­er­als (and I’m not aware of any pro­pos­als to add it),
how­ever <a href="https://stackoverflow.com/a/37204095">there’s a way to make Py­thon re­cog­nize them</a>.
I’m not yet sure if this amount of ma­gic is wise to ap­ply, but I might try it
out once.</p>
<dl class="m-footnote">
<dt id="id2">1</a>.</dt>
<dd><span class="m-footnote"><a href="#id1">^</a></span> As <a href="https://www.reddit.com/r/cpp/comments/d5pilr/how_magnum_engine_exposes_c_to_python/f0nqese/">/u/Ni­hon­Nukite poin­ted out on Red­dit</a>,
there’s <a href="https://github.com/adamlwgriffiths/Pyrr">Pyrr</a> that provides
the above miss­ing func­tion­al­ity, fully in­teg­rated with numpy. The only
po­ten­tial down­side is that it’s all pure Py­thon, not op­tim­ized nat­ive code.</dd>
</dl>
</section>
<section id="hard-things-are-suddenly-easy-if-you-use-a-different-language">
<h2><a href="#hard-things-are-suddenly-easy-if-you-use-a-different-language">Hard things are sud­denly easy if you use a dif­fer­ent lan­guage</a></h2>
<pre class="m-code"><span class="gp">>>> </span><span class="n">a</span> <span class="o">=</span> <span class="n">Vector4</span><span class="p">(</span><span class="mf">1.5</span><span class="p">,</span> <span class="mf">0.3</span><span class="p">,</span> <span class="o">-</span><span class="mf">1.0</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">)</span>
<span class="gp">>>> </span><span class="n">b</span> <span class="o">=</span> <span class="n">Vector4</span><span class="p">(</span><span class="mf">7.2</span><span class="p">,</span> <span class="mf">2.3</span><span class="p">,</span> <span class="mf">1.1</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">)</span>
<span class="gp">>>> </span><span class="n">a</span><span class="o">.</span><span class="n">wxy</span> <span class="o">=</span> <span class="n">b</span><span class="o">.</span><span class="n">xwz</span>
<span class="gp">>>> </span><span class="n">a</span>
<span class="go">Vector(0, 1.1, -1, 7.2)</span></pre>
<p>If you ever used GLSL or any oth­er shader lan­guage, you prob­ably fell in love
with vec­tor swizzles right at the mo­ment you saw them … and then be­came sad
after a real­iz­a­tion that such APIs are <em>prac­tic­ally im­pos­sible</em><a class="m-footnote" href="#id5" id="id3">2</a> to have
in C++. Swizzle op­er­a­tions are nev­er­the­less use­ful and as­sign­ing each com­pon­ent
sep­ar­ately would be a pain, so Mag­num provides <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1Math.html#aeda0faa04b927f5c7aa2bbc3a8794d7d">Math::gath­er()</a> and
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1Math.html#a04d57d5b0e229035590046fed3496502">Math::scat­ter()</a> that al­low you to ex­press the above:</p>
<pre class="m-code"><span class="n">a</span> <span class="o">=</span> <span class="n">Math</span><span class="o">::</span><span class="n">scatter</span><span class="o"><</span><span class="sc">'w'</span><span class="p">,</span> <span class="sc">'x'</span><span class="p">,</span> <span class="sc">'y'</span><span class="o">></span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">Math</span><span class="o">::</span><span class="n">gather</span><span class="o"><</span><span class="sc">'x'</span><span class="p">,</span> <span class="sc">'w'</span><span class="p">,</span> <span class="sc">'z'</span><span class="o">></span><span class="p">(</span><span class="n">b</span><span class="p">));</span></pre>
<p>Verb­ose<a class="m-footnote" href="#id7" id="id4">3</a> but <em>prac­tic­ally</em> pos­sible. Point is, how­ever, that the above is
im­ple­ment­able very eas­ily in Py­thon us­ing <code class="m-code"><span class="fm">__getattr__</span><span class="p">()</span></code> and
<code class="m-code"><span class="fm">__setattr__</span><span class="p">()</span></code> … and a ton of er­ror check­ing on top.</p>
<dl class="m-footnote">
<dt id="id5">2</a>.</dt>
<dd><span class="m-footnote"><a href="#id3">^</a></span> GLM <em>does have</em> those, if you en­able <code class="m-code"><span class="n">GLM_FORCE_SWIZZLE</span></code>, but do­ing
so adds <em>three seconds</em><a class="m-footnote" href="#id8" id="id6">4</a> to com­pil­a­tion time of each file that
in­cludes GLM head­ers. I’d say that makes swizzles pos­sible <em>in the­ory</em> but
such over­head makes them <em>prac­tic­ally</em> use­less.</dd>
<dt id="id7">3</a>.</dt>
<dd><span class="m-footnote"><a href="#id4">^</a></span> Math func­tions are <em>func­tions</em> and so do not mutate their ar­gu­ments,
that’s why the fi­nal self-as­sign­ment. It would of course be bet­ter to be
able to write <code class="m-code"><span class="n">Math</span><span class="o">::</span><span class="n">gather</span><span class="o"><</span><span class="s">"wxy"</span><span class="o">></span><span class="p">(</span><span class="n">b</span><span class="p">)</span></code> or at least
<code class="m-code"><span class="n">Math</span><span class="o">::</span><span class="n">gather</span><span class="o"><</span><span class="err">'</span><span class="n">wxy</span><span class="err">'</span><span class="o">></span><span class="p">(</span><span class="n">b</span><span class="p">)</span></code> but C++ <em>in­sists</em> on the first be­ing
im­possible and the second be­ing un­port­able. And
<a href="https://github.com/SephDB/constexpr-format">cre­at­ing a user-defined lit­er­al</a> just to
spe­cify a swizzle seems ex­cess­ive.</dd>
<dt id="id8">4</a>.</dt>
<dd><span class="m-footnote"><a href="#id6">^</a></span> I did a couple of bench­marks for a yet-to-be-pub­lished art­icle com­par­ing
math lib­rary im­ple­ment­a­tions, and this was a shock­er. The only oth­er
lib­rary that could come close was <a href="https://github.com/boostorg/geometry">Boost.Geo­metry</a>,
with two seconds per file.</dd>
</dl>
<section id="but-on-the-contrary-c-has-it-easier-with-overloads">
<h3><a href="#but-on-the-contrary-c-has-it-easier-with-overloads">… but on the con­trary, C++ has it easi­er with over­loads</a></h3>
<p>I was very de­lighted upon dis­cov­er­ing that py­bind11 sup­ports func­tion over­loads
<em>just like that</em> — if you bind more than one func­tion of the same name, it’ll
take a type­less <code class="m-code"><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span></code> and dis­patch to a cor­rect over­load
based on ar­gu­ment types. It’s prob­ably not blaz­ingly fast (and in some cases
you could prob­ably beat its speed by do­ing the dis­patch you­self), but it’s
there and much bet­ter than hav­ing to in­vent new names for over­loaded func­tions
(and con­struct­ors!). With the <abbr title="well, relatively">new</abbr> <a class="m-flat" href="https://docs.python.org/3/library/typing.html#module-typing">typ­ing</a>
mod­ule, it’s pos­sible to achieve a sim­il­ar thing in pure Py­thon us­ing the
<a class="m-flat" href="https://docs.python.org/3/library/typing.html#typing.overload">@over­load</a> dec­or­at­or — though only for doc­u­ment­a­tion
pur­poses, you’re still re­spons­ible to im­ple­ment the type dis­patch your­self. In
case of <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/python/magnum/math/#dot">math.dot()</a> im­ple­men­ted in pure Py­thon, this could look like
this:</p>
<pre class="m-code"><span class="nd">@overload</span>
<span class="k">def</span> <span class="nf">dot</span><span class="p">(</span><span class="n">a</span><span class="p">:</span> <span class="n">Quaternion</span><span class="p">,</span> <span class="n">b</span><span class="p">:</span> <span class="n">Quaternion</span><span class="p">)</span> <span class="o">-></span> <span class="nb">float</span><span class="p">:</span>
<span class="o">...</span>
<span class="nd">@overload</span>
<span class="k">def</span> <span class="nf">dot</span><span class="p">(</span><span class="n">a</span><span class="p">:</span> <span class="n">Vector2</span><span class="p">,</span> <span class="n">b</span><span class="p">:</span> <span class="n">Vector2</span><span class="p">)</span> <span class="o">-></span> <span class="nb">float</span><span class="p">:</span>
<span class="o">...</span>
<span class="k">def</span> <span class="nf">dot</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">):</span>
<span class="c1"># actual implementation</span></pre>
<p>What was ac­tu­ally <em>hard</em> though, was the fol­low­ing, look­ing com­pletely or­din­ary
to a C++ pro­gram­mer:</p>
<figure class="m-code-figure">
<pre class="m-code"><span class="gp">>>> </span><span class="n">a</span> <span class="o">=</span> <span class="n">Matrix3</span><span class="o">.</span><span class="n">translation</span><span class="p">((</span><span class="mf">4.0</span><span class="p">,</span> <span class="mf">2.0</span><span class="p">))</span>
<span class="gp">>>> </span><span class="n">a</span>
<span class="go">Matrix(1, 0, 4,</span>
<span class="go"> 0, 1, 2,</span>
<span class="go"> 0, 0, 1)</span>
<span class="gp">>>> </span><span class="n">a</span><span class="o">.</span><span class="n">translation</span> <span class="o">=</span> <span class="n">Vector2</span><span class="p">(</span><span class="mf">5.0</span><span class="p">,</span> <span class="mf">3.0</span><span class="p">)</span>
<span class="gp">>>> </span><span class="n">a</span>
<span class="go">Matrix(1, 0, 5,</span>
<span class="go"> 0, 1, 3,</span>
<span class="go"> 0, 0, 1)</span></pre>
<p class="m-noindent">Is the Py­thon lan­guage po­lice go­ing to ar­rest me now?</p>
</figure>
<p>While the case of <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/python/magnum/Matrix3/#scaling-da39a">Mat­rix3.scal­ing()</a> vs. <code class="m-code"><span class="n">mat</span><span class="o">.</span><span class="n">scaling</span><span class="p">()</span></code> — where
the former re­turns a scal­ing <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/python/magnum/Matrix3/">Mat­rix3</a> and lat­ter a scal­ing <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/python/magnum/Vector3/">Vec­tor3</a>
out of a scal­ing mat­rix — was easi­er and could be done just via a dis­patch
based on ar­gu­ment types (“if the first ar­gu­ment is an in­stance of <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/python/magnum/Matrix3/">Mat­rix3</a>,
be­have like the mem­ber func­tion”), in case of <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/python/magnum/Matrix3/#translation">Mat­rix3.trans­la­tion()</a> it’s
either a stat­ic meth­od or an in­stance <em>prop­erty</em>. Ul­ti­mately I man­aged to solve
it by sup­ply­ing a cus­tom meta­class that does a cor­rect dis­patch when
en­coun­ter­ing ac­cess to the <code>translation</code> at­trib­ute.</p>
<p>But yeah, while al­most any­thing is pos­sible in Py­thon, it could give a hand
here — am I the first per­son ever that needs this func­tion­al­ity?</p>
</section>
</section>
<section id="zero-copy-data-transfer">
<h2><a href="#zero-copy-data-transfer">Zero-copy data trans­fer</a></h2>
<p>One very im­port­ant part of Py­thon is the <a href="https://docs.python.org/3/c-api/buffer.html">Buf­fer Pro­tocol</a>.
It al­lows zero-copy shar­ing of ar­bit­rat­ily shaped data between C and Py­thon —
simple tightly-packed lin­ear ar­rays, 2D matrices, or a green chan­nel of a lower
right quarter of an im­age flipped up­side down. Hav­ing a full sup­port for the
buf­fer pro­tocol was among the reas­ony why <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/classCorrade_1_1Containers_1_1StridedArrayView.html">Con­tain­ers::StridedAr­rayView</a>
went through a <a href="https://blog.magnum.graphics/backstage/multidimensional-strided-array-views/">ma­jor re­design earli­er this year</a>.
This strided ar­ray view is now ex­posed to Py­thon as a
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/python/corrade/containers/StridedArrayView1D/">con­tain­ers.StridedAr­rayView1D</a> (or
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/python/corrade/containers/MutableStridedArrayView1D/">Mut­ableStridedAr­rayView1D</a>, and
their 2D, 3D and 4D vari­ants) and thanks to the buf­fer pro­tocol it can be
seam­lessly con­ver­ted from and to <a class="m-flat" href="https://docs.scipy.org/doc/numpy/reference/generated/numpy.array.html#numpy.array">numpy.ar­ray()</a> (and Py­thon’s own
<a class="m-flat" href="https://docs.python.org/3/library/stdtypes.html#memoryview">memoryview</a> as well). Trans­it­ively that means you can un­leash numpy-based
Py­thon al­gorithms dir­ectly on data com­ing out of <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/python/magnum/ImageView2D/#pixels">Im­ageView2D.pixels()</a>
and have the modi­fic­a­tions im­me­di­ately re­flec­ted back in C++.</p>
<p>Be­cause, again, hav­ing a spe­cial­ized type with fur­ther re­stric­tions makes the
code easi­er to reas­on about, <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/python/corrade/containers/ArrayView/">con­tain­ers.Ar­rayView</a> (and its mut­able
vari­ant) is ex­posed as well. This one works only with lin­ear tightly packed
memory and thus is suit­able for tak­ing views onto <a class="m-flat" href="https://docs.python.org/3/library/stdtypes.html#bytes">bytes</a> or
<a class="m-flat" href="https://docs.python.org/3/library/stdtypes.html#bytearray">byte­ar­ray</a>, file con­tents and such. Both the strided and lin­ear ar­ray
views of course sup­port the full Py­thon sli­cing API. As an ex­ample, here’s how
you can read an im­age in Py­thon, pass its con­tents to a Mag­num im­port­er and get
the raw pixel data back:</p>
<pre class="m-code"><span class="kn">from</span> <span class="nn">magnum</span> <span class="kn">import</span> <span class="n">trade</span>
<span class="k">def</span> <span class="nf">consume_pixels</span><span class="p">(</span><span class="n">pixels</span><span class="p">:</span> <span class="n">np</span><span class="o">.</span><span class="n">ndarray</span><span class="p">):</span>
<span class="o">...</span>
<span class="n">importer</span><span class="p">:</span> <span class="n">trade</span><span class="o">.</span><span class="n">AbstractImporter</span> <span class="o">=</span>
<span class="n">trade</span><span class="o">.</span><span class="n">ImporterManager</span><span class="p">()</span><span class="o">.</span><span class="n">load_and_instantiate</span><span class="p">(</span><span class="s1">'AnyImageImporter'</span><span class="p">)</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">filename</span><span class="p">,</span> <span class="s1">'rb'</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="n">importer</span><span class="o">.</span><span class="n">open_data</span><span class="p">(</span><span class="n">f</span><span class="o">.</span><span class="n">readall</span><span class="p">())</span>
<span class="n">image</span><span class="p">:</span> <span class="n">trade</span><span class="o">.</span><span class="n">ImageData2D</span> <span class="o">=</span> <span class="n">importer</span><span class="o">.</span><span class="n">image2d</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="c1"># green channel of a lower right quarter of a 256x256 image flipped upside down</span>
<span class="n">consume_pixels</span><span class="p">(</span><span class="n">image</span><span class="o">.</span><span class="n">pixels</span><span class="p">[</span><span class="mi">128</span><span class="p">:</span><span class="mi">128</span><span class="p">,::</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">:</span><span class="mi">2</span><span class="p">])</span></pre>
<p>Just one ques­tion left — who owns the memory here, then? To an­swer that,
let’s dive in­to Py­thon’s ref­er­ence count­ing.</p>
</section>
<section id="reference-counting">
<h2><a href="#reference-counting">Ref­er­en­ce count­ing</a></h2>
<p>In C++, views are one of the more dan­ger­ous con­tain­ers, as they ref­er­ence data
owned by some­thing else. There you’re ex­pec­ted to en­sure the data own­er is kept
in scope for at least as long as the view on it. A sim­il­ar thing is with oth­er
types — for ex­ample, a <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1GL_1_1Mesh.html">GL::Mesh</a> may ref­er­ence a bunch of
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1GL_1_1Buffer.html">GL::Buf­fer</a>s, or a <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1AbstractImporter.html">Trade::Ab­strac­tIm­port­er</a> loaded from a plu­gin
needs its plu­gin man­ager to be alive to keep the plu­gin lib­rary loaded.</p>
<div class="m-right-m m-container-inflate">
<figure class="m-figure">
<svg class="m-graph" style="width: 17.812rem; height: 10.125rem;" viewBox="0.00 0.00 284.69 162.35">
<g transform="scale(1 1) rotate(0) translate(4 158.35)">
<g class="m-node m-flat">
<title>importer</title>
<ellipse cx="138.69" cy="-120" rx="34" ry="34"/>
<text text-anchor="middle" x="138.69" y="-116.2">importer</text>
</g>
<g class="m-node m-flat">
<title>manager</title>
<ellipse cx="34.35" cy="-120" rx="34.2" ry="34.2"/>
<text text-anchor="middle" x="34.35" y="-116.2">manager</text>
</g>
<g class="m-edge">
<title>importer->manager</title>
<path d="M104.47,-120C96.33,-120 87.51,-120 78.97,-120"/>
<polygon points="78.76,-116.5 68.76,-120 78.76,-123.5 78.76,-116.5"/>
</g>
<g class="m-node m-flat">
<title>f</title>
<ellipse cx="34.35" cy="-34" rx="34" ry="34"/>
<text text-anchor="middle" x="34.35" y="-30.2">f</text>
</g>
<g class="m-edge m-dim">
<title>importer->f</title>
<path stroke-dasharray="5,2" d="M112.13,-98.52C99.07,-87.54 83,-74.04 68.94,-62.23"/>
<polygon points="70.85,-59.26 60.94,-55.5 66.34,-64.62 70.85,-59.26"/>
</g>
<g class="m-node m-success m-flat">
<title>image</title>
<ellipse cx="138.69" cy="-34" rx="34" ry="34"/>
<text text-anchor="middle" x="138.69" y="-30.2">image</text>
</g>
<g class="m-edge m-dim">
<title>image->f</title>
<path stroke-dasharray="5,2" d="M104.47,-34C96.16,-34 87.13,-34 78.42,-34"/>
<polygon points="78.41,-30.5 68.41,-34 78.41,-37.5 78.41,-30.5"/>
</g>
<g class="m-node m-primary m-flat">
<title>pixels</title>
<ellipse cx="242.69" cy="-34" rx="34" ry="34"/>
<text text-anchor="middle" x="242.69" y="-30.2">pixels</text>
</g>
<g class="m-edge">
<title>pixels->image</title>
<path d="M208.58,-34C200.47,-34 191.68,-34 183.17,-34"/>
<polygon points="182.99,-30.5 172.99,-34 182.99,-37.5 182.99,-30.5"/>
</g>
</g>
</svg>
<figcaption>Ref­er­ence hier­archy<div class="m-figure-description">
<p>The dim dashed lines show ad­di­tion­al po­ten­tial de­pend­en­cies that would
hap­pen with fu­ture <a href="https://github.com/mosra/magnum/issues/240">zero-copy plu­gin im­ple­ment­a­tions</a>
— when the file format al­lows it, these would ref­er­ence dir­ectly the
data in <code class="m-code"><span class="n">f</span></code> in­stead of stor­ing a copy them­selves.</p>
</div>
</figcaption>
</figure>
</div>
<p>How­ever, im­pos­ing sim­il­ar con­straints on Py­thon users would be <em>dar­ing too much</em>,
so all ex­posed Mag­num types that refer to ex­tern­al data im­ple­ment ref­er­ence
count­ing un­der the hood. The des­ig­nated way of do­ing this with py­bind11 is
wrap­ping <em>all your everything</em> with <a class="m-flat" href="https://en.cppreference.com/w/cpp/memory/shared_ptr">std::shared_ptr</a>. On the oth­er hand,
Mag­num is free of any shared point­ers by design, and adding them back just to
make Py­thon happy would make every­one else angry in ex­change. What Mag­num does
in­stead is ex­tend­ing the so-called hold­er type in py­bind11 (which doesn’t have
to be <a class="m-flat" href="https://en.cppreference.com/w/cpp/memory/shared_ptr">std::shared_ptr</a>; <a class="m-flat" href="https://en.cppreference.com/w/cpp/memory/unique_ptr">std::unique_ptr</a> or a cus­tom point­er types
is fine as well) and stor­ing ref­er­ences to in­stance de­pend­en­cies in­side it.</p>
<p>The straight­for­ward way of do­ing this would be to take <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1GL_1_1Mesh.html">GL::Mesh</a>,
sub­class it in­to a <code class="m-code"><span class="n">PyMesh</span></code>, store buf­fer ref­er­ences in­side it and then
ex­pose <code class="m-code"><span class="n">PyMesh</span></code> as <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/python/magnum/gl/Mesh/">gl.Mesh</a> in­stead. But com­pared to the hold­er type
ap­proach this has a ser­i­ous dis­ad­vant­age where <em>every</em> API that works with
meshes would sud­denly need to work with <code class="m-code"><span class="n">PyMesh</span></code> in­stead and that’s not
al­ways pos­sible.</p>
<p>For test­ing and de­bug­ging pur­poses, ref­er­ences to memory own­ers or oth­er data
are al­ways ex­posed through the API — see for ex­ample <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/python/magnum/ImageView2D/#owner">Im­ageView2D.own­er</a>
or <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/python/magnum/gl/Mesh/#buffers">gl.Mesh.buf­fers</a>.</p>
<section id="zero-waste-data-slicing">
<h3><a href="#zero-waste-data-slicing">Zero-waste data sli­cing</a></h3>
<p>One thing I got used to, es­pe­cially when writ­ing pars­ers, is to con­tinu­ally
slice the in­put data view as the al­gorithm con­sumes its pre­fix. Con­sider the
fol­low­ing Py­thon code, vaguely re­sem­bling an OBJ pars­er:</p>
<pre class="m-code"><span class="n">view</span> <span class="o">=</span> <span class="n">containers</span><span class="o">.</span><span class="n">ArrayView</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
<span class="k">while</span> <span class="n">view</span><span class="p">:</span>
<span class="c1"># Comment, ignore until EOL</span>
<span class="k">if</span> <span class="n">view</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">==</span> <span class="s1">'#'</span><span class="p">:</span> <span class="k">while</span> <span class="n">view</span> <span class="ow">and</span> <span class="n">view</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">!=</span> <span class="s1">'</span><span class="se">\n</span><span class="s1">'</span><span class="p">:</span> <span class="n">view</span> <span class="o">=</span> <span class="n">view</span><span class="p">[</span><span class="mi">1</span><span class="p">:]</span>
<span class="c1"># Vertex / face</span>
<span class="k">elif</span> <span class="n">view</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">==</span> <span class="s1">'v'</span><span class="p">:</span> <span class="n">view</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">parse_vertex</span><span class="p">(</span><span class="n">view</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">view</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">==</span> <span class="s1">'f'</span><span class="p">:</span> <span class="n">view</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">parse_face</span><span class="p">(</span><span class="n">view</span><span class="p">)</span>
<span class="o">...</span></pre>
<p>On every op­er­a­tion, the <code class="m-code"><span class="n">view</span></code> gets some pre­fix chopped off. While not a
prob­lem in C++, this would gen­er­ate an im­press­ively long ref­er­ence chain in
Py­thon, pre­serving all in­ter­me­di­ate views from all loop it­er­a­tions.</p>
<div class="m-graph">
<svg style="width: 43.750rem; height: 4.750rem;" viewBox="0.00 0.00 700.00 76.00">
<g transform="scale(1 1) rotate(0) translate(4 72)">
<g class="m-node m-flat">
<title>slice4</title>
<ellipse cx="554" cy="-34" rx="34" ry="34"/>
<text text-anchor="middle" x="554" y="-30.2">slice4</text>
</g>
<g class="m-node m-flat">
<title>slice3</title>
<ellipse cx="450" cy="-34" rx="34" ry="34"/>
<text text-anchor="middle" x="450" y="-30.2">slice3</text>
</g>
<g class="m-edge">
<title>slice4->slice3</title>
<path d="M519.88,-34C511.78,-34 502.98,-34 494.47,-34"/>
<polygon points="494.3,-30.5 484.3,-34 494.3,-37.5 494.3,-30.5"/>
</g>
<g class="m-node m-flat">
<title>slice2</title>
<ellipse cx="346" cy="-34" rx="34" ry="34"/>
<text text-anchor="middle" x="346" y="-30.2">slice2</text>
</g>
<g class="m-edge">
<title>slice3->slice2</title>
<path d="M415.88,-34C407.78,-34 398.98,-34 390.47,-34"/>
<polygon points="390.3,-30.5 380.3,-34 390.3,-37.5 390.3,-30.5"/>
</g>
<g class="m-node m-flat">
<title>slice1</title>
<ellipse cx="242" cy="-34" rx="34" ry="34"/>
<text text-anchor="middle" x="242" y="-30.2">slice1</text>
</g>
<g class="m-edge">
<title>slice2->slice1</title>
<path d="M311.88,-34C303.78,-34 294.98,-34 286.47,-34"/>
<polygon points="286.3,-30.5 276.3,-34 286.3,-37.5 286.3,-30.5"/>
</g>
<g class="m-node m-flat">
<title>view</title>
<ellipse cx="138" cy="-34" rx="34" ry="34"/>
<text text-anchor="middle" x="138" y="-30.2">view</text>
</g>
<g class="m-edge">
<title>slice1->view</title>
<path d="M207.88,-34C199.78,-34 190.98,-34 182.47,-34"/>
<polygon points="182.3,-30.5 172.3,-34 182.3,-37.5 182.3,-30.5"/>
</g>
<g class="m-node m-success m-flat">
<title>data</title>
<ellipse cx="34" cy="-34" rx="34" ry="34"/>
<text text-anchor="middle" x="34" y="-30.2">data</text>
</g>
<g class="m-edge">
<title>view->data</title>
<path d="M103.88,-34C95.78,-34 86.98,-34 78.47,-34"/>
<polygon points="78.3,-30.5 68.3,-34 78.3,-37.5 78.3,-30.5"/>
</g>
<g class="m-node m-primary m-flat">
<title>sliceN</title>
<ellipse cx="658" cy="-34" rx="34" ry="34"/>
<text text-anchor="middle" x="658" y="-30.2">sliceN</text>
</g>
<g class="m-edge m-primary">
<title>sliceN->slice4</title>
<path stroke-dasharray="1,5" d="M623.88,-34C615.78,-34 606.98,-34 598.47,-34"/>
<polygon points="598.3,-30.5 588.3,-34 598.3,-37.5 598.3,-30.5"/>
</g>
</g>
</svg>
</div>
<p>While the views are gen­er­ally smal­ler than the data they refer to, with big
files it could eas­ily hap­pen that the over­head of views be­comes lar­ger than the
parsed file it­self. To avoid such end­less growth, sli­cing op­er­a­tions on views
al­ways refer the ori­gin­al data own­er, al­low­ing the in­ter­me­di­ate views to be
col­lec­ted. In oth­er words, for a <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/python/corrade/containers/ArrayView/#owner">con­tain­ers.Ar­rayView.own­er</a>, <code class="m-code"><span class="n">view</span><span class="p">[:]</span><span class="o">.</span><span class="n">owner</span> <span class="ow">is</span> <span class="n">view</span><span class="o">.</span><span class="n">owner</span></code> al­ways holds.</p>
<div class="m-graph">
<svg style="width: 43.750rem; height: 11.062rem;" viewBox="0.00 0.00 700.00 177.12">
<g transform="scale(1 1) rotate(0) translate(4 173.12)">
<g class="m-node m-dim m-flat">
<title>view</title>
<ellipse cx="138" cy="-49.23" rx="34" ry="34"/>
<text text-anchor="middle" x="138" y="-45.43">view</text>
</g>
<g class="m-node m-success m-flat">
<title>data</title>
<ellipse cx="34" cy="-49.23" rx="34" ry="34"/>
<text text-anchor="middle" x="34" y="-45.43">data</text>
</g>
<g class="m-edge m-dim">
<title>view->data</title>
<path d="M103.88,-49.23C95.78,-49.23 86.98,-49.23 78.47,-49.23"/>
<polygon points="78.3,-45.73 68.3,-49.23 78.3,-52.73 78.3,-45.73"/>
</g>
<g class="m-node m-primary m-flat">
<title>sliceN</title>
<ellipse cx="658" cy="-49.23" rx="34" ry="34"/>
<text text-anchor="middle" x="658" y="-45.43">sliceN</text>
</g>
<g class="m-edge m-primary">
<title>sliceN->data</title>
<path d="M629.75,-68.58C617.42,-76.7 602.41,-85.74 588,-92.23 431.44,-162.81 379.68,-166.36 208,-162.23 161.74,-161.12 144.04,-180.43 104,-157.23 78.59,-142.51 61.04,-114.26 50,-90.35"/>
<polygon points="53.17,-88.85 45.95,-81.1 46.76,-91.66 53.17,-88.85"/>
</g>
<g class="m-node m-dim m-flat">
<title>slice4</title>
<ellipse cx="554" cy="-49.23" rx="34" ry="34"/>
<text text-anchor="middle" x="554" y="-45.43">slice4</text>
</g>
<g class="m-edge m-dim">
<title>slice4->data</title>
<path d="M525.83,-68.76C513.52,-76.91 498.5,-85.93 484,-92.23 369.21,-142.14 332.99,-154.83 208,-148.23 161.58,-145.79 144.66,-159.75 104,-137.23 83.71,-126 67.33,-106.09 55.63,-88.05"/>
<polygon points="58.51,-86.05 50.27,-79.39 52.56,-89.73 58.51,-86.05"/>
</g>
<g class="m-node m-dim m-flat">
<title>slice3</title>
<ellipse cx="450" cy="-49.23" rx="34" ry="34"/>
<text text-anchor="middle" x="450" y="-45.43">slice3</text>
</g>
<g class="m-edge m-dim">
<title>slice3->data</title>
<path d="M422.01,-69.2C409.73,-77.44 394.69,-86.4 380,-92.23 265.68,-137.67 218.52,-158.17 104,-113.23 88.15,-107.02 73.78,-95.26 62.3,-83.5"/>
<polygon points="64.56,-80.79 55.19,-75.84 59.42,-85.55 64.56,-80.79"/>
</g>
<g class="m-node m-dim m-flat">
<title>slice2</title>
<ellipse cx="346" cy="-49.23" rx="34" ry="34"/>
<text text-anchor="middle" x="346" y="-45.43">slice2</text>
</g>
<g class="m-edge m-dim">
<title>slice2->data</title>
<path d="M319.14,-70.16C306.85,-78.8 291.47,-87.81 276,-92.23 202.5,-113.26 177.5,-113.26 104,-92.23 91.91,-88.78 79.89,-82.52 69.34,-75.83"/>
<polygon points="71.12,-72.81 60.86,-70.16 67.23,-78.63 71.12,-72.81"/>
</g>
<g class="m-node m-dim m-flat">
<title>slice1</title>
<ellipse cx="242" cy="-49.23" rx="34" ry="34"/>
<text text-anchor="middle" x="242" y="-45.43">slice1</text>
</g>
<g class="m-edge m-dim">
<title>slice1->data</title>
<path d="M215.14,-28.31C202.85,-19.67 187.47,-10.66 172,-6.23 142.94,2.08 133.06,2.08 104,-6.23 91.91,-9.69 79.89,-15.95 69.34,-22.64"/>
<polygon points="67.23,-19.84 60.86,-28.31 71.12,-25.66 67.23,-19.84"/>
</g>
</g>
</svg>
</div>
</section>
</section>
<section id="the-less-than-great-aspects-of-pybind11">
<h2><a href="#the-less-than-great-aspects-of-pybind11">The less-than-great as­pects of py­bind11</a></h2>
<section id="throwing-c-exceptions-is-actually-really-slow">
<h3><a href="#throwing-c-exceptions-is-actually-really-slow">Throw­ing C++ ex­cep­tions is ac­tu­ally really slow</a></h3>
<p>While I was aware there’s some over­head in­volved with C++’s <code class="m-code"><span class="k">throw</span></code>, I
nev­er guessed the over­head would be <em>so big</em>. In most cases, this would not be
a prob­lem as ex­cep­tions are ex­cep­tion­al but there’s one little corner of Py­thon
where you <em>have to</em> use them — it­er­a­tion. In or­der to it­er­ate any­thing,
Py­thon calls <code class="m-code"><span class="fm">__getitem__</span><span class="p">()</span></code> with an in­creas­ing in­dex, and in­stead of
check­ing against <code class="m-code"><span class="fm">__len__</span><span class="p">()</span></code>, simply wait­ing un­til it raises
<a class="m-flat" href="https://docs.python.org/3/library/exceptions.html#IndexError">In­dex­Er­ror</a>. This is also how con­ver­sion from/to lists is used and also
how <a class="m-flat" href="https://docs.scipy.org/doc/numpy/reference/generated/numpy.array.html#numpy.array">numpy.ar­ray()</a> pop­u­lates the ar­ray from a list-like type, un­less the
type sup­ports Buf­fer Pro­tocol. Bind­ings for Mag­num’s <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/python/magnum/Vector3/#__getitem__">Vec­tor3.__­getitem__()</a>
ori­gin­ally looked like this:</p>
<pre class="m-code"><span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="s">"__getitem__"</span><span class="p">,</span> <span class="p">[](</span><span class="k">const</span> <span class="n">T</span><span class="o">&</span> <span class="n">self</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="kt">size_t</span> <span class="n">i</span><span class="p">)</span> <span class="o">-></span> <span class="k">typename</span> <span class="n">T</span><span class="o">::</span><span class="n">Type</span> <span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="n">i</span> <span class="o">>=</span> <span class="n">T</span><span class="o">::</span><span class="n">Size</span><span class="p">)</span> <span class="k">throw</span> <span class="n">pybind11</span><span class="o">::</span><span class="n">index_error</span><span class="p">{};</span>
<span class="k">return</span> <span class="n">self</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
<span class="p">})</span></pre>
<p>Plain and simple and seem­ingly not a perf prob­lem at all … un­til you start
meas­ur­ing:</p>
<div class="m-plot">
<svg viewBox="0 0 576 171.36">
<defs>
<style type="text/css">*{stroke-linejoin: round; stroke-linecap: butt}</style>
</defs>
<g id="figure_1">
<g id="axes_1">
<g id="patch_1">
<path d="M 185.918437 128.305312 L 564.12 128.305312 L 564.12 27.292813 L 185.918437 27.292813 z" class="m-background"/>
</g>
<g id="plot1-value0-0"><title>0.1356 ± 0.0049 µs</title>
<path d="M 185.918437 31.88429 L 199.370578 31.88429 L 199.370578 51.216826 L 185.918437 51.216826 z" clip-path="url(#p1a8dc6bf65)" class="m-bar m-success"/>
</g>
<g id="plot1-value0-1"><title>3.4824 ± 0.1484 µs</title>
<path d="M 185.918437 56.04996 L 531.388443 56.04996 L 531.388443 75.382496 L 185.918437 75.382496 z" clip-path="url(#p1a8dc6bf65)" class="m-bar m-danger"/>
</g>
<g id="plot1-value0-2"><title>2.607 ± 0.1367 µs</title>
<path d="M 185.918437 80.215629 L 444.54476 80.215629 L 444.54476 99.548165 L 185.918437 99.548165 z" clip-path="url(#p1a8dc6bf65)" class="m-bar m-danger"/>
</g>
<g id="plot1-value0-3"><title>0.4181 ± 0.0363 µs</title>
<path d="M 185.918437 104.381299 L 227.39587 104.381299 L 227.39587 123.713835 L 185.918437 123.713835 z" clip-path="url(#p1a8dc6bf65)" class="m-bar m-warning"/>
</g>
<g id="matplotlib.axis_1">
<g id="xtick_1">
<g id="line2d_1">
<defs>
<path id="mc4bea1de13" d="M 0 0 L 0 3.5" class="m-line"/>
</defs>
<g>
<use xlink:href="#mc4bea1de13" x="185.918437" y="128.305312"/>
</g>
</g>
<g id="text_1">
<text class="m-label" style="text-anchor: middle" x="185.918437" y="143.137656" transform="rotate(-0, 185.918437, 143.137656)">0.0</text>
</g>
</g>
<g id="xtick_2">
<g id="line2d_2">
<g>
<use xlink:href="#mc4bea1de13" x="235.520724" y="128.305312"/>
</g>
</g>
<g id="text_2">
<text class="m-label" style="text-anchor: middle" x="235.520724" y="143.137656" transform="rotate(-0, 235.520724, 143.137656)">0.5</text>
</g>
</g>
<g id="xtick_3">
<g id="line2d_3">
<g>
<use xlink:href="#mc4bea1de13" x="285.123011" y="128.305312"/>
</g>
</g>
<g id="text_3">
<text class="m-label" style="text-anchor: middle" x="285.123011" y="143.137656" transform="rotate(-0, 285.123011, 143.137656)">1.0</text>
</g>
</g>
<g id="xtick_4">
<g id="line2d_4">
<g>
<use xlink:href="#mc4bea1de13" x="334.725297" y="128.305312"/>
</g>
</g>
<g id="text_4">
<text class="m-label" style="text-anchor: middle" x="334.725297" y="143.137656" transform="rotate(-0, 334.725297, 143.137656)">1.5</text>
</g>
</g>
<g id="xtick_5">
<g id="line2d_5">
<g>
<use xlink:href="#mc4bea1de13" x="384.327584" y="128.305312"/>
</g>
</g>
<g id="text_5">
<text class="m-label" style="text-anchor: middle" x="384.327584" y="143.137656" transform="rotate(-0, 384.327584, 143.137656)">2.0</text>
</g>
</g>
<g id="xtick_6">
<g id="line2d_6">
<g>
<use xlink:href="#mc4bea1de13" x="433.92987" y="128.305312"/>
</g>
</g>
<g id="text_6">
<text class="m-label" style="text-anchor: middle" x="433.92987" y="143.137656" transform="rotate(-0, 433.92987, 143.137656)">2.5</text>
</g>
</g>
<g id="xtick_7">
<g id="line2d_7">
<g>
<use xlink:href="#mc4bea1de13" x="483.532157" y="128.305312"/>
</g>
</g>
<g id="text_7">
<text class="m-label" style="text-anchor: middle" x="483.532157" y="143.137656" transform="rotate(-0, 483.532157, 143.137656)">3.0</text>
</g>
</g>
<g id="xtick_8">
<g id="line2d_8">
<g>
<use xlink:href="#mc4bea1de13" x="533.134444" y="128.305312"/>
</g>
</g>
<g id="text_8">
<text class="m-label" style="text-anchor: middle" x="533.134444" y="143.137656" transform="rotate(-0, 533.134444, 143.137656)">3.5</text>
</g>
</g>
<g id="text_9">
<text class="m-label" style="text-anchor: middle" x="375.019219" y="157.225" transform="rotate(-0, 375.019219, 157.225)">µs</text>
</g>
</g>
<g id="matplotlib.axis_2">
<g id="ytick_1">
<g id="line2d_9">
<defs>
<path id="mae1c673641" d="M 0 0 L -3.5 0" class="m-line"/>
</defs>
<g>
<use xlink:href="#mae1c673641" x="185.918437" y="41.550558"/>
</g>
</g>
<g id="text_10">
<text class="m-label" transform="translate(122.266719 40.400714)">pure Python</text>
<text class="m-label" transform="translate(178.918437 52.099214)"/>
</g>
</g>
<g id="ytick_2">
<g id="line2d_10">
<g>
<use xlink:href="#mae1c673641" x="185.918437" y="65.716228"/>
</g>
</g>
<g id="text_11">
<text class="m-label" transform="translate(135.796719 64.566384)">pybind11</text>
<text class="m-label" transform="translate(178.918437 76.264884)"/>
</g>
</g>
<g id="ytick_3">
<g id="line2d_11">
<g>
<use xlink:href="#mae1c673641" x="185.918437" y="89.881897"/>
</g>
</g>
<g id="text_12">
<text class="m-label" transform="translate(135.796719 88.732054)">pybind11</text>
<text class="m-label" transform="translate(178.918437 100.430554)"/>
</g>
</g>
<g id="ytick_4">
<g id="line2d_12">
<g>
<use xlink:href="#mae1c673641" x="185.918437" y="114.047567"/>
</g>
</g>
<g id="text_13">
<text class="m-label" transform="translate(88.187344 112.897724)">pybind11 / CPython</text>
<text class="m-label" transform="translate(178.918437 124.596224)"/>
</g>
</g>
</g>
<g id="LineCollection_1">
<path d="M 198.884475 41.550558 L 199.85668 41.550558" clip-path="url(#p1a8dc6bf65)" class="m-error"/>
<path d="M 516.666484 65.716228 L 546.110402 65.716228" clip-path="url(#p1a8dc6bf65)" class="m-error"/>
<path d="M 430.983495 89.881897 L 458.106025 89.881897" clip-path="url(#p1a8dc6bf65)" class="m-error"/>
<path d="M 223.794744 114.047567 L 230.996996 114.047567" clip-path="url(#p1a8dc6bf65)" class="m-error"/>
</g>
<g id="line2d_13">
<defs>
<path id="m1487d016e7" d="M 0 5 L 0 -5" class="m-error"/>
</defs>
<g clip-path="url(#p1a8dc6bf65)">
<use xlink:href="#m1487d016e7" x="198.884475" y="41.550558"/>
<use xlink:href="#m1487d016e7" x="516.666484" y="65.716228"/>
<use xlink:href="#m1487d016e7" x="430.983495" y="89.881897"/>
<use xlink:href="#m1487d016e7" x="223.794744" y="114.047567"/>
</g>
</g>
<g id="line2d_14">
<g clip-path="url(#p1a8dc6bf65)">
<use xlink:href="#m1487d016e7" x="199.85668" y="41.550558"/>
<use xlink:href="#m1487d016e7" x="546.110402" y="65.716228"/>
<use xlink:href="#m1487d016e7" x="458.106025" y="89.881897"/>
<use xlink:href="#m1487d016e7" x="230.996996" y="114.047567"/>
</g>
</g>
<g id="text_14">
<text class="m-label m-dim" transform="translate(178.918437 39.720607)"/>
<text class="m-label m-dim" transform="translate(99.244062 51.374419)">raise IndexError()</text>
</g>
<g id="text_15">
<text class="m-label m-dim" transform="translate(178.918437 63.863933)"/>
<text class="m-label m-dim" transform="translate(40.392344 75.517745)">throw pybind11::index_error{}</text>
</g>
<g id="text_16">
<text class="m-label m-dim" transform="translate(178.918437 88.029603)"/>
<text class="m-label m-dim" transform="translate(11.88 99.683415)">throw pybind11::error_already_set{}</text>
</g>
<g id="text_17">
<text class="m-label m-dim" transform="translate(178.918437 111.980773)"/>
<text class="m-label m-dim" transform="translate(99.11 123.898585)">PyErr_SetString()</text>
</g>
<g id="text_18">
<text class="m-title" style="text-anchor: middle" x="375.019219" y="21.292813" transform="rotate(-0, 375.019219, 21.292813)">Cost of raising an exception</text>
</g>
</g>
</g>
<defs>
<clipPath id="p1a8dc6bf65">
<rect x="185.918437" y="27.292813" width="378.201563" height="101.0125"/>
</clipPath>
</defs>
</svg>
</div>
<p>This is fur­ther blown out of pro­por­tion in case of <a class="m-flat" href="https://docs.scipy.org/doc/numpy/reference/generated/numpy.array.html#numpy.array">numpy.ar­ray()</a> —
look­ing at the sources of <a class="m-flat" href="https://docs.scipy.org/doc/numpy/reference/c-api.array.html#c.PyArray_FROMANY">PyAr­ray_­Fro­mAny()</a>,
it’s ap­par­ently <a href="https://github.com/numpy/numpy/blob/4b4eaa666b18016162c144b7757ba40d8237fdb8/numpy/core/src/multiarray/ctors.c#L1938-L1954">hit­ting the out-of-bounds con­di­tion three times</a>
— first when check­ing for di­men­sions, second when cal­cu­lat­ing a com­mon type
for all ele­ments and third when do­ing an ac­tu­al copy of the data. This was most
prob­ably not worth op­tim­iz­ing as­sum­ing sane ex­cep­tion per­form­ance, how­ever
com­bined with py­bind11, it leads to a <em>massive</em> slow­down:</p>
<div class="m-plot">
<svg viewBox="0 0 576 200.16">
<defs>
<style type="text/css">*{stroke-linejoin: round; stroke-linecap: butt}</style>
</defs>
<g id="figure_1">
<g id="axes_1">
<g id="patch_1">
<path d="M 185.918437 157.105313 L 564.12 157.105313 L 564.12 27.136406 L 185.918437 27.136406 z" class="m-background"/>
</g>
<g id="plot2-value0-0"><title>0.5756 ± 0.0313 µs</title>
<path d="M 185.918437 33.044084 L 197.181163 33.044084 L 197.181163 52.736342 L 185.918437 52.736342 z" clip-path="url(#p4bf0305ff1)" class="m-bar m-success"/>
</g>
<g id="plot2-value0-1"><title>17.2296 ± 1.1786 µs</title>
<path d="M 185.918437 57.659407 L 523.048818 57.659407 L 523.048818 77.351665 L 185.918437 77.351665 z" clip-path="url(#p4bf0305ff1)" class="m-bar m-danger"/>
</g>
<g id="plot2-value0-2"><title>14.2204 ± 0.3782 µs</title>
<path d="M 185.918437 82.27473 L 464.168012 82.27473 L 464.168012 101.966989 L 185.918437 101.966989 z" clip-path="url(#p4bf0305ff1)" class="m-bar m-danger"/>
</g>
<g id="plot2-value0-3"><title>6.3909 ± 0.1217 µs</title>
<path d="M 185.918437 106.890053 L 310.968732 106.890053 L 310.968732 126.582312 L 185.918437 126.582312 z" clip-path="url(#p4bf0305ff1)" class="m-bar m-warning"/>
</g>
<g id="plot2-value0-4"><title>0.6411 ± 0.0368 µs</title>
<path d="M 185.918437 131.505376 L 198.462796 131.505376 L 198.462796 151.197635 L 185.918437 151.197635 z" clip-path="url(#p4bf0305ff1)" class="m-bar m-success"/>
</g>
<g id="matplotlib.axis_1">
<g id="xtick_1">
<g id="line2d_1">
<defs>
<path id="m6c4cdc507b" d="M 0 0 L 0 3.5" class="m-line"/>
</defs>
<g>
<use xlink:href="#m6c4cdc507b" x="185.918437" y="157.105313"/>
</g>
</g>
<g id="text_1">
<text class="m-label" style="text-anchor: middle" x="185.918437" y="171.937656" transform="rotate(-0, 185.918437, 171.937656)">0.0</text>
</g>
</g>
<g id="xtick_2">
<g id="line2d_2">
<g>
<use xlink:href="#m6c4cdc507b" x="234.835763" y="157.105313"/>
</g>
</g>
<g id="text_2">
<text class="m-label" style="text-anchor: middle" x="234.835763" y="171.937656" transform="rotate(-0, 234.835763, 171.937656)">2.5</text>
</g>
</g>
<g id="xtick_3">
<g id="line2d_3">
<g>
<use xlink:href="#m6c4cdc507b" x="283.753088" y="157.105313"/>
</g>
</g>
<g id="text_3">
<text class="m-label" style="text-anchor: middle" x="283.753088" y="171.937656" transform="rotate(-0, 283.753088, 171.937656)">5.0</text>
</g>
</g>
<g id="xtick_4">
<g id="line2d_4">
<g>
<use xlink:href="#m6c4cdc507b" x="332.670414" y="157.105313"/>
</g>
</g>
<g id="text_4">
<text class="m-label" style="text-anchor: middle" x="332.670414" y="171.937656" transform="rotate(-0, 332.670414, 171.937656)">7.5</text>
</g>
</g>
<g id="xtick_5">
<g id="line2d_5">
<g>
<use xlink:href="#m6c4cdc507b" x="381.587739" y="157.105313"/>
</g>
</g>
<g id="text_5">
<text class="m-label" style="text-anchor: middle" x="381.587739" y="171.937656" transform="rotate(-0, 381.587739, 171.937656)">10.0</text>
</g>
</g>
<g id="xtick_6">
<g id="line2d_6">
<g>
<use xlink:href="#m6c4cdc507b" x="430.505065" y="157.105313"/>
</g>
</g>
<g id="text_6">
<text class="m-label" style="text-anchor: middle" x="430.505065" y="171.937656" transform="rotate(-0, 430.505065, 171.937656)">12.5</text>
</g>
</g>
<g id="xtick_7">
<g id="line2d_7">
<g>
<use xlink:href="#m6c4cdc507b" x="479.42239" y="157.105313"/>
</g>
</g>
<g id="text_7">
<text class="m-label" style="text-anchor: middle" x="479.42239" y="171.937656" transform="rotate(-0, 479.42239, 171.937656)">15.0</text>
</g>
</g>
<g id="xtick_8">
<g id="line2d_8">
<g>
<use xlink:href="#m6c4cdc507b" x="528.339716" y="157.105313"/>
</g>
</g>
<g id="text_8">
<text class="m-label" style="text-anchor: middle" x="528.339716" y="171.937656" transform="rotate(-0, 528.339716, 171.937656)">17.5</text>
</g>
</g>
<g id="text_9">
<text class="m-label" style="text-anchor: middle" x="375.019219" y="186.025" transform="rotate(-0, 375.019219, 186.025)">µs</text>
</g>
</g>
<g id="matplotlib.axis_2">
<g id="ytick_1">
<g id="line2d_9">
<defs>
<path id="m74ed764a78" d="M 0 0 L -3.5 0" class="m-line"/>
</defs>
<g>
<use xlink:href="#m74ed764a78" x="185.918437" y="42.890213"/>
</g>
</g>
<g id="text_10">
<text class="m-label" style="text-anchor: end" x="178.918437" y="46.806385" transform="rotate(-0, 178.918437, 46.806385)">from a list</text>
</g>
</g>
<g id="ytick_2">
<g id="line2d_10">
<g>
<use xlink:href="#m74ed764a78" x="185.918437" y="67.505536"/>
</g>
</g>
<g id="text_11">
<text class="m-label" transform="translate(119.463437 66.378036)">from Vector3</text>
<text class="m-label" transform="translate(178.918437 78.031849)"/>
</g>
</g>
<g id="ytick_3">
<g id="line2d_11">
<g>
<use xlink:href="#m74ed764a78" x="185.918437" y="92.120859"/>
</g>
</g>
<g id="text_12">
<text class="m-label" transform="translate(119.463437 90.993359)">from Vector3</text>
<text class="m-label" transform="translate(178.918437 102.647172)"/>
</g>
</g>
<g id="ytick_4">
<g id="line2d_12">
<g>
<use xlink:href="#m74ed764a78" x="185.918437" y="116.736183"/>
</g>
</g>
<g id="text_13">
<text class="m-label" transform="translate(119.463437 115.608683)">from Vector3</text>
<text class="m-label" transform="translate(178.918437 127.262495)"/>
</g>
</g>
<g id="ytick_5">
<g id="line2d_13">
<g>
<use xlink:href="#m74ed764a78" x="185.918437" y="141.351506"/>
</g>
</g>
<g id="text_14">
<text class="m-label" transform="translate(119.463437 140.224006)">from Vector3</text>
<text class="m-label" transform="translate(178.918437 151.877818)"/>
</g>
</g>
</g>
<g id="LineCollection_1">
<path d="M 196.568718 42.890213 L 197.793607 42.890213" clip-path="url(#p4bf0305ff1)" class="m-error"/>
<path d="M 499.987234 67.505536 L 546.110402 67.505536" clip-path="url(#p4bf0305ff1)" class="m-error"/>
<path d="M 456.767799 92.120859 L 471.568225 92.120859" clip-path="url(#p4bf0305ff1)" class="m-error"/>
<path d="M 308.587436 116.736183 L 313.350027 116.736183" clip-path="url(#p4bf0305ff1)" class="m-error"/>
<path d="M 197.742733 141.351506 L 199.182859 141.351506" clip-path="url(#p4bf0305ff1)" class="m-error"/>
</g>
<g id="line2d_14">
<defs>
<path id="m3b539238c1" d="M 0 5 L 0 -5" class="m-error"/>
</defs>
<g clip-path="url(#p4bf0305ff1)">
<use xlink:href="#m3b539238c1" x="196.568718" y="42.890213"/>
<use xlink:href="#m3b539238c1" x="499.987234" y="67.505536"/>
<use xlink:href="#m3b539238c1" x="456.767799" y="92.120859"/>
<use xlink:href="#m3b539238c1" x="308.587436" y="116.736183"/>
<use xlink:href="#m3b539238c1" x="197.742733" y="141.351506"/>
</g>
</g>
<g id="line2d_15">
<g clip-path="url(#p4bf0305ff1)">
<use xlink:href="#m3b539238c1" x="197.793607" y="42.890213"/>
<use xlink:href="#m3b539238c1" x="546.110402" y="67.505536"/>
<use xlink:href="#m3b539238c1" x="471.568225" y="92.120859"/>
<use xlink:href="#m3b539238c1" x="313.350027" y="116.736183"/>
<use xlink:href="#m3b539238c1" x="199.182859" y="141.351506"/>
</g>
</g>
<g id="text_15">
<text class="m-label m-dim" transform="translate(178.918437 65.675724)"/>
<text class="m-label m-dim" transform="translate(40.392344 77.329537)">throw pybind11::index_error{}</text>
</g>
<g id="text_16">
<text class="m-label m-dim" transform="translate(178.918437 90.291047)"/>
<text class="m-label m-dim" transform="translate(11.88 101.94486)">throw pybind11::error_already_set{}</text>
</g>
<g id="text_17">
<text class="m-label m-dim" transform="translate(178.918437 114.691871)"/>
<text class="m-label m-dim" transform="translate(99.11 126.609683)">PyErr_SetString()</text>
</g>
<g id="text_18">
<text class="m-label m-dim" transform="translate(178.918437 139.464631)"/>
<text class="m-label m-dim" transform="translate(109.561719 151.277256)">buffer protocol</text>
</g>
<g id="text_19">
<text class="m-title" style="text-anchor: middle" x="375.019219" y="21.136406" transform="rotate(-0, 375.019219, 21.136406)">Constructing numpy.ndarray</text>
</g>
</g>
</g>
<defs>
<clipPath id="p4bf0305ff1">
<rect x="185.918437" y="27.136406" width="378.201563" height="129.968906"/>
</clipPath>
</defs>
</svg>
</div>
<p>As hin­ted by the plots above, there’s a few pos­sible ways of coun­ter­ing the
in­ef­fi­ciency:</p>
<ol>
<li><p>A lot of over­head in py­bind11 is re­lated to ex­cep­tion trans­la­tion which can
be sidestepped by call­ing <a class="m-flat" href="https://docs.python.org/3/c-api/exceptions.html#c.PyErr_SetString">Py­Er­r_­Set­String()</a> and telling
py­bind11 an er­ror is already set and it only needs to propag­ate it:</p>
<pre class="m-inverted m-code"><span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="s">"__getitem__"</span><span class="p">,</span> <span class="p">[](</span><span class="k">const</span> <span class="n">T</span><span class="o">&</span> <span class="n">self</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="kt">size_t</span> <span class="n">i</span><span class="p">)</span> <span class="o">-></span> <span class="k">typename</span> <span class="n">T</span><span class="o">::</span><span class="n">Type</span> <span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="n">i</span> <span class="o">>=</span> <span class="n">T</span><span class="o">::</span><span class="n">Size</span><span class="p">)</span> <span class="p">{</span>
<span class="hll"> <span class="n">PyErr_SetString</span><span class="p">(</span><span class="n">PyExc_IndexError</span><span class="p">,</span> <span class="s">""</span><span class="p">);</span>
</span><span class="hll"> <span class="k">throw</span> <span class="n">pybind11</span><span class="o">::</span><span class="n">error_already_set</span><span class="p">{};</span>
</span> <span class="p">}</span>
<span class="k">return</span> <span class="n">self</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
<span class="p">})</span></pre>
<p>As seen above, this res­ults in a mod­er­ate im­prove­ment with ex­cep­tions
tak­ing ~1 µs less to throw (though for <a class="m-flat" href="https://docs.scipy.org/doc/numpy/reference/generated/numpy.array.html#numpy.array">numpy.ar­ray()</a> it doesn’t help
much). This is what Mag­num Bind­ings
<a href="https://github.com/mosra/magnum-bindings/commit/f20c5beb76f96bb3177290584681f038eeeb43e9">glob­ally switched to</a>
after dis­cov­er­ing the perf dif­fer­ence, and apart from
<a class="m-flat" href="https://docs.python.org/3/c-api/exceptions.html#c.PyErr_SetString">Py­Er­r_­Set­String()</a>, there’s also
<a class="m-flat" href="https://docs.python.org/3/c-api/exceptions.html#c.PyErr_Format">Py­Er­r_­Format()</a> able to string­i­fy Py­thon ob­jects dir­ectly
us­ing <code class="m-code"><span class="s">"%A"</span></code> — hard to beat that with any third-party solu­tion.</p>
</li>
<li><p>Even with the above, the <code class="m-code"><span class="k">throw</span></code> and the whole ex­cep­tion bub­bling
in­side py­bind is still re­spons­ible for quite a lot, so the next step is to
only call <a class="m-flat" href="https://docs.python.org/3/c-api/exceptions.html#c.PyErr_SetString">Py­Er­r_­Set­String()</a> and re­turn <em>noth­ing</em> to
py­bind to in­dic­ate we want to raise an ex­cep­tion in­stead:</p>
<pre class="m-inverted m-code"><span class="hll"><span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="s">"__getitem__"</span><span class="p">,</span> <span class="p">[](</span><span class="k">const</span> <span class="n">T</span><span class="o">&</span> <span class="n">self</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="kt">size_t</span> <span class="n">i</span><span class="p">)</span> <span class="o">-></span> <span class="n">pybind11</span><span class="o">::</span><span class="n">object</span> <span class="p">{</span>
</span> <span class="k">if</span><span class="p">(</span><span class="n">i</span> <span class="o">>=</span> <span class="n">T</span><span class="o">::</span><span class="n">Size</span><span class="p">)</span> <span class="p">{</span>
<span class="hll"> <span class="n">PyErr_SetString</span><span class="p">(</span><span class="n">PyExc_IndexError</span><span class="p">,</span> <span class="s">""</span><span class="p">);</span>
</span><span class="hll"> <span class="k">return</span> <span class="n">pybind11</span><span class="o">::</span><span class="n">object</span><span class="p">{};</span>
</span> <span class="p">}</span>
<span class="hll"> <span class="k">return</span> <span class="n">pybind11</span><span class="o">::</span><span class="n">cast</span><span class="p">(</span><span class="n">self</span><span class="p">[</span><span class="n">i</span><span class="p">]);</span>
</span><span class="p">})</span></pre>
<p>This res­ults in quite a sig­ni­fic­ant im­prove­ment, re­du­cing the ex­cep­tion
over­head from about 3.5 µs to 0.4 µs. It how­ever re­lies on a patch that’s
not merged yet (see <a href="https://github.com/pybind/pybind11/issues/1853">py­bind/py­bind11#1853</a>) and it re­quires the bound
API to re­turn a type­less <a class="m-flat" href="https://docs.python.org/3/library/functions.html#object">ob­ject</a> in­stead of a con­crete value as
there’s no oth­er way to ex­press a “null” value oth­er­wise.</p>
</li>
<li>If there’s a pos­sib­il­ity to use the <a href="https://docs.python.org/3/c-api/buffer.html">Buf­fer Pro­tocol</a>, pre­fer­ring it over
<code class="m-code"><span class="fm">__getitem__</span><span class="p">()</span></code>. At first I was skep­tic­al about this idea be­cause the
buf­fer pro­tocol setup with point­ers and shapes and formats and sizes and
all re­lated er­ror check­ing cer­tainly <em>feels</em> heav­ier than simply it­er­at­ing
a three-ele­ment ar­ray. But the re­l­at­ive heav­i­ness of ex­cep­tions makes it a
win­ner. Py­bind has a <a href="https://pybind11.readthedocs.io/en/stable/advanced/pycpp/numpy.html#buffer-protocol">built­in sup­port</a>,
so why not use it. Well, ex­cept …</li>
</ol>
</section>
<section id="let-s-allocate-a-bunch-of-vectors-and-strings-to-do-a-zero-copy-data-transfer">
<h3><a href="#let-s-allocate-a-bunch-of-vectors-and-strings-to-do-a-zero-copy-data-transfer">Let’s al­loc­ate a bunch of vec­tors and strings to do a zero-copy data trans­fer</a></h3>
<p>Py­thon’s Buf­fer Pro­tocol, men­tioned above, is a really nice ap­proach for
data trans­fers with min­im­al over­head — if used cor­rectly. Let’s look again at
the case of call­ing <a class="m-flat" href="https://docs.scipy.org/doc/numpy/reference/generated/numpy.array.html#numpy.array">numpy.ar­ray()</a> above:</p>
<div class="m-plot">
<svg viewBox="0 0 576 171.36">
<defs>
<style type="text/css">*{stroke-linejoin: round; stroke-linecap: butt}</style>
</defs>
<g id="figure_1">
<g id="axes_1">
<g id="patch_1">
<path d="M 95.28875 128.305312 L 564.12 128.305312 L 564.12 27.396406 L 95.28875 27.396406 z" class="m-background"/>
</g>
<g id="plot3-value0-0"><title>0.5756 ± 0.0313 µs</title>
<path d="M 95.28875 31.983175 L 474.413733 31.983175 L 474.413733 51.295884 L 95.28875 51.295884 z" clip-path="url(#pa41985c8d1)" class="m-bar m-info"/>
</g>
<g id="plot3-value0-1"><title>0.4766 ± 0.0263 µs</title>
<path d="M 95.28875 56.124061 L 409.206344 56.124061 L 409.206344 75.436771 L 95.28875 75.436771 z" clip-path="url(#pa41985c8d1)" class="m-bar m-info"/>
</g>
<g id="plot3-value0-2"><title>0.6411 ± 0.0368 µs</title>
<path d="M 95.28875 80.264948 L 517.555996 80.264948 L 517.555996 99.577657 L 95.28875 99.577657 z" clip-path="url(#pa41985c8d1)" class="m-bar m-warning"/>
</g>
<g id="plot3-value0-3"><title>0.5552 ± 0.0294 µs</title>
<path d="M 95.28875 104.405835 L 460.977059 104.405835 L 460.977059 123.718544 L 95.28875 123.718544 z" clip-path="url(#pa41985c8d1)" class="m-bar m-success"/>
</g>
<g id="matplotlib.axis_1">
<g id="xtick_1">
<g id="line2d_1">
<defs>
<path id="m0f8c636808" d="M 0 0 L 0 3.5" class="m-line"/>
</defs>
<g>
<use xlink:href="#m0f8c636808" x="95.28875" y="128.305312"/>
</g>
</g>
<g id="text_1">
<text class="m-label" style="text-anchor: middle" x="95.28875" y="143.137656" transform="rotate(-0, 95.28875, 143.137656)">0.0</text>
</g>
</g>
<g id="xtick_2">
<g id="line2d_2">
<g>
<use xlink:href="#m0f8c636808" x="161.1548" y="128.305312"/>
</g>
</g>
<g id="text_2">
<text class="m-label" style="text-anchor: middle" x="161.1548" y="143.137656" transform="rotate(-0, 161.1548, 143.137656)">0.1</text>
</g>
</g>
<g id="xtick_3">
<g id="line2d_3">
<g>
<use xlink:href="#m0f8c636808" x="227.02085" y="128.305312"/>
</g>
</g>
<g id="text_3">
<text class="m-label" style="text-anchor: middle" x="227.02085" y="143.137656" transform="rotate(-0, 227.02085, 143.137656)">0.2</text>
</g>
</g>
<g id="xtick_4">
<g id="line2d_4">
<g>
<use xlink:href="#m0f8c636808" x="292.8869" y="128.305312"/>
</g>
</g>
<g id="text_4">
<text class="m-label" style="text-anchor: middle" x="292.8869" y="143.137656" transform="rotate(-0, 292.8869, 143.137656)">0.3</text>
</g>
</g>
<g id="xtick_5">
<g id="line2d_5">
<g>
<use xlink:href="#m0f8c636808" x="358.75295" y="128.305312"/>
</g>
</g>
<g id="text_5">
<text class="m-label" style="text-anchor: middle" x="358.75295" y="143.137656" transform="rotate(-0, 358.75295, 143.137656)">0.4</text>
</g>
</g>
<g id="xtick_6">
<g id="line2d_6">
<g>
<use xlink:href="#m0f8c636808" x="424.619" y="128.305312"/>
</g>
</g>
<g id="text_6">
<text class="m-label" style="text-anchor: middle" x="424.619" y="143.137656" transform="rotate(-0, 424.619, 143.137656)">0.5</text>
</g>
</g>
<g id="xtick_7">
<g id="line2d_7">
<g>
<use xlink:href="#m0f8c636808" x="490.485049" y="128.305312"/>
</g>
</g>
<g id="text_7">
<text class="m-label" style="text-anchor: middle" x="490.485049" y="143.137656" transform="rotate(-0, 490.485049, 143.137656)">0.6</text>
</g>
</g>
<g id="xtick_8">
<g id="line2d_8">
<g>
<use xlink:href="#m0f8c636808" x="556.351099" y="128.305312"/>
</g>
</g>
<g id="text_8">
<text class="m-label" style="text-anchor: middle" x="556.351099" y="143.137656" transform="rotate(-0, 556.351099, 143.137656)">0.7</text>
</g>
</g>
<g id="text_9">
<text class="m-label" style="text-anchor: middle" x="329.704375" y="157.225" transform="rotate(-0, 329.704375, 157.225)">µs</text>
</g>
</g>
<g id="matplotlib.axis_2">
<g id="ytick_1">
<g id="line2d_9">
<defs>
<path id="m3e62786acc" d="M 0 0 L -3.5 0" class="m-line"/>
</defs>
<g>
<use xlink:href="#m3e62786acc" x="95.28875" y="41.639529"/>
</g>
</g>
<g id="text_10">
<text class="m-label" style="text-anchor: end" x="88.28875" y="45.555701" transform="rotate(-0, 88.28875, 45.555701)">from a list</text>
</g>
</g>
<g id="ytick_2">
<g id="line2d_10">
<g>
<use xlink:href="#m3e62786acc" x="95.28875" y="65.780416"/>
</g>
</g>
<g id="text_11">
<text class="m-label" style="text-anchor: end" x="88.28875" y="69.76276" transform="rotate(-0, 88.28875, 69.76276)">from array.array</text>
</g>
</g>
<g id="ytick_3">
<g id="line2d_11">
<g>
<use xlink:href="#m3e62786acc" x="95.28875" y="89.921303"/>
</g>
</g>
<g id="text_12">
<text class="m-label" transform="translate(28.83375 88.793803)">from Vector3</text>
<text class="m-label" transform="translate(88.28875 100.447615)"/>
</g>
</g>
<g id="ytick_4">
<g id="line2d_12">
<g>
<use xlink:href="#m3e62786acc" x="95.28875" y="114.062189"/>
</g>
</g>
<g id="text_13">
<text class="m-label" transform="translate(28.83375 112.934689)">from Vector3</text>
<text class="m-label" transform="translate(88.28875 124.588502)"/>
</g>
</g>
</g>
<g id="LineCollection_1">
<path d="M 453.79766 41.639529 L 495.029807 41.639529" clip-path="url(#pa41985c8d1)" class="m-error"/>
<path d="M 391.883573 65.780416 L 426.529115 65.780416" clip-path="url(#pa41985c8d1)" class="m-error"/>
<path d="M 493.31729 89.921303 L 541.794702 89.921303" clip-path="url(#pa41985c8d1)" class="m-error"/>
<path d="M 441.61244 114.062189 L 480.341678 114.062189" clip-path="url(#pa41985c8d1)" class="m-error"/>
</g>
<g id="line2d_13">
<defs>
<path id="m1434c070a1" d="M 0 5 L 0 -5" class="m-error"/>
</defs>
<g clip-path="url(#pa41985c8d1)">
<use xlink:href="#m1434c070a1" x="453.79766" y="41.639529"/>
<use xlink:href="#m1434c070a1" x="391.883573" y="65.780416"/>
<use xlink:href="#m1434c070a1" x="493.31729" y="89.921303"/>
<use xlink:href="#m1434c070a1" x="441.61244" y="114.062189"/>
</g>
</g>
<g id="line2d_14">
<g clip-path="url(#pa41985c8d1)">
<use xlink:href="#m1434c070a1" x="495.029807" y="41.639529"/>
<use xlink:href="#m1434c070a1" x="426.529115" y="65.780416"/>
<use xlink:href="#m1434c070a1" x="541.794702" y="89.921303"/>
<use xlink:href="#m1434c070a1" x="480.341678" y="114.062189"/>
</g>
</g>
<g id="text_14">
<text class="m-label m-dim" transform="translate(88.28875 87.988363)"/>
<text class="m-label m-dim" transform="translate(11.88 99.800988)">pybind11::buffer</text>
</g>
<g id="text_15">
<text class="m-label m-dim" transform="translate(88.28875 112.129249)"/>
<text class="m-label m-dim" transform="translate(43.616719 123.941874)">Py_buffer</text>
</g>
<g id="text_16">
<text class="m-title" style="text-anchor: middle" x="329.704375" y="21.396406" transform="rotate(-0, 329.704375, 21.396406)">Creating numpy.array() from a list-like type</text>
</g>
</g>
</g>
<defs>
<clipPath id="pa41985c8d1">
<rect x="95.28875" y="27.396406" width="468.83125" height="100.908906"/>
</clipPath>
</defs>
</svg>
</div>
<p>It’s clear that con­vert­ing a pure Py­thon list to a <a class="m-flat" href="https://docs.scipy.org/doc/numpy/reference/generated/numpy.array.html#numpy.array">numpy.ar­ray()</a> is,
even with all the ex­cep­tions in­volved, still <em>faster</em> than us­ing py­bind’s
buf­fer pro­tocol im­ple­ment­a­tion to con­vert a <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/python/magnum/Vector3/">Vec­tor3</a> to it. In
com­par­is­on, <a class="m-flat" href="https://docs.python.org/3/library/array.html#array.array">ar­ray.ar­ray()</a> (which im­ple­ments Buf­fer Pro­tocol as well,
only nat­ively in plain C) is quite speedy, so there’s def­in­itely some­thing
<em>fishy</em> in py­bind11.</p>
<div class="m-row">
<div class="m-col-m-6 m-push-m-3 m-nopadt">
<figure class="m-code-figure">
<pre class="m-code"><span class="k">struct</span> <span class="n">buffer_info</span> <span class="p">{</span>
<span class="kt">void</span> <span class="o">*</span><span class="n">ptr</span><span class="p">;</span>
<span class="kt">ssize_t</span> <span class="n">itemsize</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">format</span><span class="p">;</span>
<span class="kt">ssize_t</span> <span class="n">ndim</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="kt">ssize_t</span><span class="o">></span> <span class="n">shape</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="kt">ssize_t</span><span class="o">></span> <span class="n">strides</span><span class="p">;</span>
<span class="p">};</span></pre>
<p class="m-noindent">Oh, so <a href="https://pybind11.readthedocs.io/en/stable/advanced/pycpp/numpy.html#buffer-protocol">that’s why</a>.</p>
</figure>
</div>
</div>
<p>The <a class="m-flat" href="https://en.cppreference.com/w/cpp/container/vector">std::vec­tor</a> al­loc­a­tions (and <a class="m-flat" href="https://en.cppreference.com/w/cpp/string/basic_string">std::string</a> pos­sibly as well, if
the format spe­cifier is too long for small string op­tim­iz­a­tion) in
<code>pybind11::buffer_info</code> add up to the over­head, so I de­cided to sidestep
py­bind11 al­to­geth­er and in­ter­face dir­ectly with the un­der­ly­ing Py­thon C API
in­stead. Be­cause the <a class="m-flat" href="https://docs.python.org/3/c-api/buffer.html#c.Py_buffer">Py_buf­fer</a> struc­ture is quite flex­ible, I
ended up <a href="https://github.com/mosra/magnum-bindings/commit/d1d6cb9ec046e824ca6c50fdcf9c41fe005a67f7">point­ing its mem­bers to stat­ic­ally defined data</a>
for each mat­rix / vec­tor type, mak­ing the buf­fer pro­tocol op­er­a­tion com­pletely
al­loc­a­tion-less. In case of <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/python/corrade/containers/ArrayView/">con­tain­ers.Ar­rayView</a> and its strided
equi­val­ent the struc­ture points to their in­tern­al mem­bers, so noth­ing needs to
be al­loc­ated even in case of <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/python/corrade/containers/StridedArrayView4D/">con­tain­ers.StridedAr­rayView4D</a>.
Ad­di­tion­ally, op­er­at­ing dir­ectly with the C API al­lowed me to cor­rectly
propag­ate readonly prop­er­ties and the
<a href="#zero-waste-data-slicing">above-men­tioned data own­er­ship</a> as well.</p>
</section>
<section id="become-a-10-programmer-with-this-one-weird-trick">
<h3><a href="#become-a-10-programmer-with-this-one-weird-trick">Be­come a 10× pro­gram­mer with this one weird trick</a></h3>
<p>Com­pile times with py­bind are some­thing I can’t get used to at all. Maybe this
is noth­ing ex­traordin­ary when you do a lot of Mod­ern C++, but an in­cre­ment­al
build of a single file tak­ing <em>20</em> seconds is a bit too much for my taste. In
com­par­is­on, I can re­com­pile the full Mag­num (without tests) in 15 seconds. This
gets <em>a lot</em> worse when build­ing Re­lease, due to <code>-flto</code> be­ing passed to the
com­piler — then an in­cre­ment­al build of that same file takes <em>90</em> seconds<a class="m-footnote" href="#id11" id="id9">5</a>,
large part of the time spent in Link-Time Op­tim­iz­a­tion.</p>
<p>For­tu­nately, by an­oth­er lucky ac­ci­dent, I re­cently dis­covered that GCC’s <code>-flto</code>
flag has a par­al­lel op­tion<a class="m-footnote" href="#id12" id="id10">6</a> — so if you have 8 cores, <code>-flto=8</code> will
make the LTO step run eight times faster, turn­ing the above 90 seconds in­to
slightly-less-hor­rif­ic 35 seconds. <em>Ima­gine that.</em> This has how­ever a dan­ger­ous
con­sequence — the build­sys­tem is not aware of the LTO par­al­lel­ism, so it’s
in­ev­it­able that it will hap­pily sched­ule 8 par­al­lel­ized link jobs at once,
bring­ing your ma­chine to a grind­ing halt un­less you have 32 GB RAM and most of
those free. If you use Ninja, it has <a href="https://cmake.org/cmake/help/v3.15/prop_gbl/JOB_POOLS.html">job pools</a>
where you can tell it to not fire up more than one such link job at once, but
as far as my un­der­stand­ing of this fea­ture goes, this will not af­fect the
schedul­ing of com­pile and link in par­al­lel.</p>
<aside class="m-block m-warning">
<h3>LTO with a GNU jobserver</h3>
<p>As <a href="https://www.reddit.com/r/cpp/comments/d5pilr/how_magnum_engine_exposes_c_to_python/f0o1oi3/">/u/Doc­tor­Rock­it poin­ted out on Red­dit</a>,
GCC has also <code>-flto=jobserver</code> that aims to help ex­actly with the case
above.</p>
<p>So did a brief test­ing — it sup­posedly works with GNU Make, in case of
Ninja there’s a <a href="https://github.com/ninja-build/ninja/issues/1139">long-run­ning dis­pute over this fea­ture</a>
and it’s un­likely to get merged any­time soon. In the mean­time you can try
<a href="https://github.com/Kitware/ninja/releases">Kit­ware’s fork</a> that
in­teg­rates it; it’s also in AUR as <a href="https://aur.archlinux.org/packages/ninja-kitware/">ninja-kit­ware</a>.
How­ever, even us­ing Kit­ware’s Ninja, GCC didn’t make use of this fea­ture
and still spent 90 seconds on this one link job. Dig­ging fur­ther, for
CMake+Make it ap­par­ently needs <a href="https://stackoverflow.com/questions/41299052/in-cmake-pass-flto-jobserver-to-gcc">hand-patched Make­files</a>
(oh well) and then <a href="https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html">the doc says</a>
that</p>
<blockquote>
This op­tion likely only works if <code>MAKE</code> is GNU make.</blockquote>
<pre class="m-console-wrap m-console"><span class="gp">$</span> <span class="nv">MAKE</span><span class="o">=</span>ninja ninja
<span class="go">[1/1] Linking CXX shared module src/py...magnum.cpython-37m-x86_64-linux-gnu.so</span>
<span class="go">FAILED: src/python/_magnum.cpython-37m-x86_64-linux-gnu.so</span>
<span class="go">: && /usr/bin/c++ -fPIC -march=native -pipe -fstack-protector-strong -fno-plt -O3 -DNDEBUG -flto=jobserver -Wl,-O1,--sort-common,--as-needed,-z,relro,-z,now -shared -o src/python/_magnum.cpython-37m-x86_64-linux-gnu.so src/python/magnum/CMakeFiles/magnum.dir/magnum.cpp.o src/python/magnum/CMakeFiles/magnum.dir/math.cpp.o src/python/magnum/CMakeFiles/magnum.dir/math.matrixfloat.cpp.o src/python/magnum/CMakeFiles/magnum.dir/math.matrixdouble.cpp.o src/python/magnum/CMakeFiles/magnum.dir/math.range.cpp.o src/python/magnum/CMakeFiles/magnum.dir/math.vectorfloat.cpp.o src/python/magnum/CMakeFiles/magnum.dir/math.vectorintegral.cpp.o -flto /usr/lib/libMagnum-d.so /usr/lib/libCorradeUtility-d.so</span>
<span class="go">ninja: error: /tmp/ccKhCeuC.mk:1: lexing error</span>
<span class="go">lto-wrapper: fatal error: ninja returned 1 exit status</span>
<span class="go">compilation terminated.</span>
<span class="go">/usr/bin/ld: error: lto-wrapper failed</span>
<span class="go">collect2: error: ld returned 1 exit status</span>
<span class="go">ninja: build stopped: subcommand failed.</span></pre>
<p>So ex­pli­citly set­ting <code class="m-code"><span class="nv">$MAKE</span></code> makes it fi­nally do some­thing, but then
of course Ninja won’t un­der­stand the Make­file syn­tax. This fea­ture is
<em>ex­actly</em> what I’d need, but prac­tic­ally it’s not there yet.</p>
</aside>
<p>Once Clang 9 is out (and once I get some free time), I want to un­leash the new
<a href="https://aras-p.info/blog/2019/01/16/time-trace-timeline-flame-chart-profiler-for-Clang/">-ftime-trace op­tion</a>
on the py­bind code, to see if there’s any low-hanging fruit. But un­for­tu­nately
in the long term I’m afraid I’ll need to re­place even more parts of py­bind to
bring com­pile times back to sane bounds.</p>
<dl class="m-footnote">
<dt id="id11">5</a>.</dt>
<dd><span class="m-footnote"><a href="#id9">^</a></span> To give a per­spect­ive, the cov­er im­age of this art­icle (<a href="#">on the top</a>)
is gen­er­ated from pre­pro­cessed out­put of the file that takes 90 seconds to
build. About 1%, few faded lines in the front, is the ac­tu­al bind­ings code.
The rest — as far as your eyes can see — is STL and py­bind11 head­ers.</dd>
<dt id="id12">6</a>.</dt>
<dd><span class="m-footnote"><a href="#id10">^</a></span> It’s cur­rently opt-in, but GCC 10 is sched­uled to have it
<a href="https://www.phoronix.com/scan.php?page=news_item&px=GCC-10-LTO-flto-Available-Cores">en­abled by de­fault</a>. If you are on Clang,
it has <a href="http://blog.llvm.org/2016/06/thinlto-scalable-and-incremental-lto.html">Thin­LTO</a>,
how­ever I was not able to con­vince it to run par­al­lel for me.</dd>
</dl>
</section>
</section>
<section id="everyone-just-uses-sphinx-you">
<h2><a href="#everyone-just-uses-sphinx-you">Every­one “just uses Sphinx”. You?</a></h2>
<p>The ob­vi­ous first choice when it comes to doc­u­ment­ing Py­thon code is to use
Sphinx — everything in­clud­ing the stand­ard lib­rary uses it and I don’t even
re­mem­ber see­ing a single Py­thon lib­rary that <em>doesn’t</em>. How­ever, if you clicked
on <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/python/magnum/shaders/Phong/">any of the above doc links</a>, you prob­ably real­ized
that … no, Mag­num is not us­ing it.</p>
<div class="m-button m-primary">
<a href="https://mcss.mosra.cz/documentation/python/"><div class="m-big">
m.css Py­thon doc gen­er­at­or</div>
<div class="m-small">
click and see what is it about</div>
</a>
</div>
<p>Ever since the doc­u­ment­a­tion search
<a href="https://blog.magnum.graphics/meta/implementing-a-fast-doxygen-search/">got in­tro­duced early last year</a>,
many de­velopers quickly be­came <span class="m-text m-s">ad­dicted</span> used to it. Whip­ping up some
Sphinx docs, where both search per­form­ance and res­ult rel­ev­ance is ex­tremely
un­der­whelm­ing, would be ef­fect­ively un­do­ing all us­ab­il­ity pro­gress Mag­num made
un­til now, so the only op­tion was to bring the search to Py­thon as well.</p>
<figure class="m-primary m-figure">
<a href="https://doc.magnum.graphics/python/magnum/math/Matrix4/"><img src="https://static.magnum.graphics/img/blog/announcements/introducing-python-bindings/annotations.png" style="width: 454px" /></a>
<figcaption><a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/python/magnum/Matrix4/">mag­num.Mat­rix4</a> doc­u­ment­a­tion<div class="m-figure-description">
Type an­nota­tions are cent­ral to the Py­thon doc gen­er­at­or.</div>
</figcaption>
</figure>
<p>While at it, I made the doc gen­er­at­or aware of all kinds of type an­nota­tions,
prop­erly cross­link­ing everything to cor­res­pond­ing type defin­i­tions. And not
just loc­al types — sim­il­arly to Doxy­gen’s <a href="http://www.doxygen.nl/manual/external.html">tag­files</a>,
Sphinx has <a href="https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html">In­ter­sphinx</a>, so link­ing to 3rd party lib­rary docs (such as NumPy) or
even the stand­ard lib­rary is pos­sible as well. Con­versely, the m.css Py­thon
doc gen­er­at­or <a href="https://magnum.graphics/doc-downloads/#intersphinx-inventory-file">ex­ports an In­ter­sphinx in­vent­ory file</a>,
so no mat­ter wheth­er you use vanilla Sphinx or m.css, you can link to
m.css-gen­er­ated Py­thon docs from your own doc­u­ment­a­tion as well.</p>
<p>If you want to try it out on your pro­ject, head over to the
<a href="https://mcss.mosra.cz/documentation/python/">m.css web­site</a> for a brief
in­tro­duc­tion. Com­pared to Sphinx or Doxy­gen it be­haves more like Doxy­gen, as it
im­pli­citly browses your mod­ule hier­archy and gen­er­ates a ded­ic­ated page for
each class and mod­ule. The way Sphinx does it was in­ter­est­ing for me at first,
but over the time I real­ized it needs quite a lot of ef­fort from de­veloper side
to or­gan­ize well — and from the doc­u­ment­a­tion read­er side, it can lead to
things be­ing harder to find than they should be (for ex­ample, docs for
<a class="m-flat" href="https://docs.python.org/3/library/stdtypes.html#str.splitlines">str.splitlines()</a> are bur­ied some­where in the middle of a kilo­met­er-long
page doc­u­ment­ing <em>all built­in types</em>).</p>
<p>The doc gen­er­at­or <em>re­sembles</em> Sphinx, but I de­cided to ex­per­i­ment with a clean
slate first in­stead of mak­ing it 100% com­pat­ible — some design de­cisions in
Sphinx it­self are his­tor­ic­al (such as type an­nota­tions in the doc block in­stead
of in the code it­self) and it didn’t make sense for me to port those over.
Well, at least for now, a full Sphinx com­pat­ib­il­ity is not com­pletely out of
ques­tion.</p>
</section>
<section id="what-s-next">
<h2><a href="#what-s-next">What’s next?</a></h2>
<p>The work is far from be­ing done — apart from ex­pos­ing APIs that are not
ex­posed yet (which is just a routine work, mostly), I’m still not quite
sat­is­fied with the per­form­a­ce of bound types, so on my roadmap is try­ing to
ex­pose a ba­sic type us­ing pure C Py­thon APIs the most ef­fi­cient way pos­sible
and then com­par­ing how long does it take to in­stan­ti­ate that type and call
meth­ods on it. One of the things to try is a vec­tor­call call pro­tocol that’s
new in Py­thon 3.8 (<a href="https://www.python.org/dev/peps/pep-0590/">PEP590</a>) and
the re­search couldn’t be com­plete without also try­ing a sim­il­ar thing in
<a href="http://docs.micropython.org/en/latest/develop/cmodules.html">Mi­croPy­thon</a>.</p>
<p class="m-transition">~ ~ ~</p>
<aside class="m-note m-dim">
Ques­tions? Com­plaints? Share your opin­ion on so­cial net­works:
<a href="https://twitter.com/czmosra/status/1174021174013112321">Twit­ter</a>,
Red­dit <a href="https://www.reddit.com/r/Python/comments/d5l7kj/introducing_python_bindings_for_the_magnum/">r/py­thon</a>,
<a href="https://www.reddit.com/r/cpp/comments/d5pilr/how_magnum_engine_exposes_c_to_python/">r/cpp</a>,
<a href="https://www.reddit.com/r/gamedev/comments/d5na3q/introducing_python_bindings_for_the_magnum/">r/game­dev</a>,
<a href="https://news.ycombinator.com/item?id=20998615">Hack­er News</a>,</aside>
</section>
Using DART to control a robotic manipulator2019-07-29T00:00:00+02:002019-07-29T00:00:00+02:00Konstantinos Chatzilygeroudistag:blog.magnum.graphics,2019-07-29:/guest-posts/using-dart-to-control-a-robotic-manipulator/<p>A new ex­ample show­cases cap­ab­il­it­ies of the DART in­teg­ra­tion. Let’s
dive in­to ro­bot­ics, ex­plain what DART is able to do and how it com­pares to
Bul­let.</p>
<section id="what-is-a-robotic-manipulator">
<h2><a href="#what-is-a-robotic-manipulator">What is a ro­bot­ic ma­nip­u­lat­or?</a></h2>
<p>A ro­bot­ic mech­an­ism is con­struc­ted by con­nect­ing ri­gid bod­ies, called links,
to­geth­er by means of joints, so that re­l­at­ive mo­tion between ad­ja­cent links
be­comes pos­sible. Ac­tu­ation of the joints, typ­ic­ally by elec­tric mo­tors, then
causes the ro­bot to move and ex­ert forces in de­sired ways. Ad­di­tion­ally, there
ex­ist sev­er­al sensors that al­lows us to read the cur­rent po­s­i­tion, ve­lo­city or
torque of an ac­tu­at­or.</p>
<p>A ro­bot­ic ma­nip­u­lat­or is a ro­bot­ic mech­an­ism where its links are ar­ranged in
seri­al fash­ion, that is, they form what we call an open-chain. Without go­ing
very deeply in­to the mech­an­ic­al de­tails be­hind the ac­tu­al ma­chines and without
loss of gen­er­al­ity, for the re­main­ing of this post a ro­bot­ic ma­nip­u­lat­or can be
de­scribed fully by the mass and in­er­tia prop­er­ties of its links, its kin­emat­ic
con­fig­ur­a­tion, its joint po­s­i­tions and its joint ve­lo­cit­ies. Moreover, we will
as­sume that our ma­nip­u­lat­ors can be con­trol either via torque or ve­lo­city
com­mands.</p>
</section>
<section id="what-is-this-example-about">
<h2><a href="#what-is-this-example-about">What is this ex­ample about?</a></h2>
<p>Be­fore mov­ing on, let us see what we can do <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/examples-dart.html">in this ex­ample</a>
(and what we will ex­plain in this post):</p>
<video src="https://static.magnum.graphics/img/blog/guest-posts/using-dart-to-control-a-robotic-manipulator/video.mp4" width="768" height="432" controls></video><p>As we can see in the video, our ex­ample world con­sists of a ro­bot­ic ma­nip­u­lat­or
with a grip­per at­tached in its end-ef­fect­or (that is, the last link in the
seri­al chain) and three small colored boxes. The user can con­trol the
ma­nip­u­lat­or with in­tu­it­ive com­mands (e.g., move over the red box or move
up/down) and pick up the small boxes to cre­ate some towers. They can even
des­troy the ones that they cre­ated!</p>
<p>We will split the post in­to two main dir­ec­tions: (a) ex­plain­ing how to use the
DartInteg­ra­tion (and some ba­sic us­age of DART), and (b) ex­plain­ing the ba­sics
of the con­trol­ler de­veloped for this task (from a ro­bot­ics per­spect­ive).</p>
</section>
<section id="using-the-dartintegration">
<h2><a href="#using-the-dartintegration">Us­ing the DartInteg­ra­tion</a></h2>
<p><a href="http://dartsim.github.io/">Dy­nam­ic An­im­a­tion and Ro­bot­ics Toolkit (DART)</a> is
an open-source lib­rary that provides data struc­tures and al­gorithms for
kin­emat­ic and dy­nam­ic ap­plic­a­tions in ro­bot­ics and com­puter an­im­a­tion. Since
its main fo­cus is ro­bot­ics ap­plic­a­tions, every en­tity in a DART world is a
kin­emat­ic skel­et­on, that is, a col­lec­tion of links in­ter­con­nec­ted with joints.
Each joint can be ac­tu­ated with dif­fer­ent types of ac­tu­ation (force or torque,
ve­lo­city, po­s­i­tion) or it can be fixed. DART ad­di­tion­ally sup­port non-ri­gid
bod­ies/links, but we will only con­sider ro­bots with ri­gid body links in this
post. In DART everything be­gins by cre­at­ing an empty world:</p>
<pre class="m-code"><span class="k">auto</span> <span class="n">world</span> <span class="o">=</span> <span class="n">dart</span><span class="o">::</span><span class="n">simulation</span><span class="o">::</span><span class="n">WorldPtr</span><span class="p">(</span><span class="k">new</span> <span class="n">dart</span><span class="o">::</span><span class="n">simulation</span><span class="o">::</span><span class="n">World</span><span class="p">);</span></pre>
<p>In­side this ima­gin­ary world, our ro­bots will live and flour­ish. The next step
is to cre­ate our ro­bots. In DART, even the small boxes that have no ac­tu­ation
are con­sidered as a kin­emat­ic skel­et­on. Let us cre­ate our fist “ro­bot” box (for
more de­tails on how to use DART, please <a href="http://dartsim.github.io/tutorials_introduction.html">fol­low their tu­tori­als</a>):</p>
<figure class="m-code-figure">
<pre class="m-code"><span class="cm">/* The size of our box */</span>
<span class="k">constexpr</span> <span class="n">Double</span> <span class="n">boxSize</span> <span class="o">=</span> <span class="mf">0.06</span><span class="p">;</span>
<span class="cm">/* Calculate the mass of the box */</span>
<span class="k">constexpr</span> <span class="n">Double</span> <span class="n">boxDensity</span> <span class="o">=</span> <span class="mi">260</span><span class="p">;</span> <span class="c1">// kg/m^3</span>
<span class="k">constexpr</span> <span class="n">Double</span> <span class="n">boxMass</span> <span class="o">=</span> <span class="n">boxDensity</span><span class="o">*</span><span class="n">Math</span><span class="o">::</span><span class="n">pow</span><span class="o"><</span><span class="mi">3</span><span class="o">></span><span class="p">(</span><span class="n">boxSize</span><span class="p">);</span>
<span class="cm">/* Create a Skeleton and give it a name */</span>
<span class="n">dart</span><span class="o">::</span><span class="n">dynamics</span><span class="o">::</span><span class="n">SkeletonPtr</span> <span class="n">box</span> <span class="o">=</span> <span class="n">dart</span><span class="o">::</span><span class="n">dynamics</span><span class="o">::</span><span class="n">Skeleton</span><span class="o">::</span><span class="n">create</span><span class="p">(</span><span class="s">"box"</span><span class="p">);</span>
<span class="cm">/* Create a body for the box */</span>
<span class="n">dart</span><span class="o">::</span><span class="n">dynamics</span><span class="o">::</span><span class="n">BodyNodePtr</span> <span class="n">body</span> <span class="o">=</span>
<span class="n">box</span><span class="o">-></span><span class="n">createJointAndBodyNodePair</span><span class="o"><</span><span class="n">dart</span><span class="o">::</span><span class="n">dynamics</span><span class="o">::</span><span class="n">FreeJoint</span><span class="o">></span><span class="p">(</span><span class="k">nullptr</span><span class="p">).</span><span class="n">second</span><span class="p">;</span>
<span class="cm">/* Create a shape for the box */</span>
<span class="k">auto</span> <span class="n">boxShape</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">make_shared</span><span class="o"><</span><span class="n">dart</span><span class="o">::</span><span class="n">dynamics</span><span class="o">::</span><span class="n">BoxShape</span><span class="o">></span><span class="p">(</span>
<span class="n">Eigen</span><span class="o">::</span><span class="n">Vector3d</span><span class="p">{</span><span class="n">box_size</span><span class="p">,</span> <span class="n">box_size</span><span class="p">,</span> <span class="n">box_size</span><span class="p">});</span>
<span class="k">auto</span> <span class="n">shapeNode</span> <span class="o">=</span> <span class="n">body</span><span class="o">-></span><span class="n">createShapeNodeWith</span><span class="o"><</span>
<span class="n">dart</span><span class="o">::</span><span class="n">dynamics</span><span class="o">::</span><span class="n">VisualAspect</span><span class="p">,</span>
<span class="n">dart</span><span class="o">::</span><span class="n">dynamics</span><span class="o">::</span><span class="n">CollisionAspect</span><span class="p">,</span> <span class="n">dart</span><span class="o">::</span><span class="n">dynamics</span><span class="o">::</span><span class="n">DynamicsAspect</span><span class="o">></span><span class="p">(</span><span class="n">box_shape</span><span class="p">);</span>
<span class="cm">/* Give a color to our box */</span>
<span class="n">shapeNode</span><span class="o">-></span><span class="n">getVisualAspect</span><span class="p">()</span><span class="o">-></span><span class="n">setColor</span><span class="p">(</span><span class="n">Eigen</span><span class="o">::</span><span class="n">Vector3d</span><span class="p">{</span><span class="mf">1.0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">});</span>
<span class="cm">/* Set up inertia for the box */</span>
<span class="n">dart</span><span class="o">::</span><span class="n">dynamics</span><span class="o">::</span><span class="n">Inertia</span> <span class="n">inertia</span><span class="p">;</span>
<span class="n">inertia</span><span class="p">.</span><span class="n">setMass</span><span class="p">(</span><span class="n">box_mass</span><span class="p">);</span>
<span class="n">inertia</span><span class="p">.</span><span class="n">setMoment</span><span class="p">(</span><span class="n">box_shape</span><span class="o">-></span><span class="n">computeInertia</span><span class="p">(</span><span class="n">box_mass</span><span class="p">));</span>
<span class="n">body</span><span class="o">-></span><span class="n">setInertia</span><span class="p">(</span><span class="n">inertia</span><span class="p">);</span>
<span class="cm">/* Setup the center of the box properly */</span>
<span class="n">box</span><span class="o">-></span><span class="n">getDof</span><span class="p">(</span><span class="s">"Joint_pos_z"</span><span class="p">)</span><span class="o">-></span><span class="n">setPosition</span><span class="p">(</span><span class="n">box_size</span><span class="o">/</span><span class="mf">2.0</span><span class="p">);</span></pre>
<p class="m-text m-small m-dim m-noindent">See <a href="https://github.com/mosra/magnum-examples/blob/08cddf48fdfcf5ffe2be51a211c387ea2cc8fa9e/src/dart/DartExample.cpp#L82-L112">this code in the mag­num-ex­amples re­pos­it­ory</a>.</p>
</figure>
<p>The are sev­er­al steps that we need to take in or­der to cre­ate a ro­bot (in DART
but in gen­er­al also). Ima­gine if we have to cre­ate a ro­bot with mul­tiple links
and joints! For that reas­on, sev­er­al con­ven­tions have been defined and used in
the ro­bot­ics com­munity. The most pop­u­lar ones are the
<a href="https://en.wikipedia.org/wiki/Denavit%E2%80%93Hartenberg_parameters">De­navit–Harten­berg para­met­ers</a>
and the product of ex­po­nen­tials for­mula (that is based on the screw the­ory; for
more in­form­a­tion please refer to the
<a href="http://hades.mech.northwestern.edu/images/7/7f/MR.pdf">Mod­ern Ro­bot­ics book</a>
by Kev­in M. Lynch and Frank C. Park).</p>
<section id="universal-robot-description-format">
<h3><a href="#universal-robot-description-format">Uni­ver­sal Ro­bot De­scrip­tion Format</a></h3>
<p>Nev­er­the­less, these are still math­em­at­ic­al for­mu­las and they re­quire quite some
work to manu­ally per­form all the com­pu­ta­tions. DART (and most ro­bot­ic
sim­u­lat­ors) already have some of the two con­ven­tions im­ple­men­ted and re­quire
from the user to give only the re­l­at­ive trans­form­a­tion from each link to their
“par­ent” joints and from the joints to their “child” links. This greatly
sim­pli­fies the life of the user, but still writ­ing this in C++ code is
de­mand­ing and er­ror prone (just have a look at the code above to cre­ate a
simple box). For this reas­on, sev­er­al de­scrip­tion formats have arised
over the years that try to make it easi­er and more in­tu­it­ive to cre­ate your
ro­bots.</p>
<p>One of the most pop­u­lar formats is the Uni­ver­sal Ro­bot De­scrip­tion Format
(URDF) that is part of the <a href="https://www.ros.org/">Ro­bot­ics Op­er­at­ing Sys­tem (ROS)</a>
frame­work. URDF is an XML format for rep­res­ent­ing a ro­bot mod­el; for more
de­tails, please have a look at the <a href="http://wiki.ros.org/urdf/XML">ROS wiki</a>.
Here is how a part of the URDF mod­el of the ma­nip­u­lat­or that we are go­ing to
use looks like:</p>
<figure class="m-code-figure">
<pre class="m-code"><span class="c"><!-- link 0 --></span>
<span class="nt"><link</span> <span class="na">name=</span><span class="s">"iiwa_link_0"</span><span class="nt">></span>
<span class="nt"><inertial></span>
<span class="nt"><origin</span> <span class="na">rpy=</span><span class="s">"0 0 0"</span> <span class="na">xyz=</span><span class="s">"-0.1 0 0.07"</span><span class="nt">/></span>
<span class="nt"><mass</span> <span class="na">value=</span><span class="s">"5"</span><span class="nt">/></span>
<span class="nt"><inertia</span> <span class="na">ixx=</span><span class="s">"0.05"</span> <span class="na">ixy=</span><span class="s">"0"</span> <span class="na">ixz=</span><span class="s">"0"</span> <span class="na">iyy=</span><span class="s">"0.06"</span> <span class="na">iyz=</span><span class="s">"0"</span> <span class="na">izz=</span><span class="s">"0.03"</span><span class="nt">/></span>
<span class="nt"></inertial></span>
<span class="nt"><visual></span>
<span class="nt"><origin</span> <span class="na">rpy=</span><span class="s">"0 0 0"</span> <span class="na">xyz=</span><span class="s">"0 0 0"</span><span class="nt">/></span>
<span class="nt"><geometry></span>
<span class="nt"><mesh</span> <span class="na">filename=</span><span class="s">"package://iiwa14/link_0.stl"</span><span class="nt">/></span>
<span class="nt"></geometry></span>
<span class="nt"><material</span> <span class="na">name=</span><span class="s">"Grey"</span><span class="nt">/></span>
<span class="nt"></visual></span>
<span class="nt"><collision></span>
<span class="nt"><origin</span> <span class="na">rpy=</span><span class="s">"0 0 0"</span> <span class="na">xyz=</span><span class="s">"-0.01 0 0.07875"</span><span class="nt">/></span>
<span class="nt"><geometry></span>
<span class="nt"><box</span> <span class="na">size=</span><span class="s">"0.24 0.23 0.1575"</span><span class="nt">/></span>
<span class="nt"></geometry></span>
<span class="nt"><material</span> <span class="na">name=</span><span class="s">"Grey"</span><span class="nt">/></span>
<span class="nt"></collision></span>
<span class="nt"></link></span>
<span class="c"><!-- joint between link 0 and link 1 --></span>
<span class="nt"><joint</span> <span class="na">name=</span><span class="s">"iiwa_joint_1"</span> <span class="na">type=</span><span class="s">"revolute"</span><span class="nt">></span>
<span class="nt"><parent</span> <span class="na">link=</span><span class="s">"iiwa_link_0"</span><span class="nt">/></span>
<span class="nt"><child</span> <span class="na">link=</span><span class="s">"iiwa_link_1"</span><span class="nt">/></span>
<span class="nt"><origin</span> <span class="na">rpy=</span><span class="s">"0 0 0"</span> <span class="na">xyz=</span><span class="s">"0 0 0.1575"</span><span class="nt">/></span>
<span class="nt"><axis</span> <span class="na">xyz=</span><span class="s">"0 0 1"</span><span class="nt">/></span>
<span class="nt"><limit</span> <span class="na">effort=</span><span class="s">"320"</span> <span class="na">lower=</span><span class="s">"-2.96705972"</span> <span class="na">upper=</span><span class="s">"2.96705972"</span> <span class="na">velocity=</span><span class="s">"1.4835298"</span><span class="nt">/></span>
<span class="nt"><dynamics</span> <span class="na">damping=</span><span class="s">"0.5"</span><span class="nt">/></span>
<span class="nt"></joint></span>
<span class="c"><!-- link 1 --></span>
<span class="nt"><link</span> <span class="na">name=</span><span class="s">"iiwa_link_1"</span><span class="nt">></span>
...
<span class="nt"></link></span></pre>
<p class="m-text m-small m-dim m-noindent">See <a href="https://github.com/mosra/magnum-examples/blob/08cddf48fdfcf5ffe2be51a211c387ea2cc8fa9e/src/dart/urdf/iiwa14_simple.urdf">the full file in the mag­num-ex­amples re­pos­it­ory</a>.</p>
</figure>
<p>This is to get an idea of how a URDF file would look like; no need to
un­der­stand it!</p>
</section>
<section id="loading-a-urdf-model-with-dart">
<h3><a href="#loading-a-urdf-model-with-dart">Load­ing a URDF mod­el with DART</a></h3>
<p>As­sum­ing that we have in our pos­ses­sion the URDF mod­el of our ro­bot, here is
how to load it with DART:</p>
<figure class="m-code-figure">
<pre class="m-code"><span class="cm">/* DART: Load Skeletons/Robots */</span>
<span class="n">DartLoader</span> <span class="n">loader</span><span class="p">;</span>
<span class="cm">/* Add packages (needed for URDF loading): this is a ROS-related thing in</span>
<span class="cm"> order to find the resources (e.g., meshes), see the "package://" in the</span>
<span class="cm"> URDF file above */</span>
<span class="n">loader</span><span class="p">.</span><span class="n">addPackageDirectory</span><span class="p">(</span><span class="s">"name_of_package"</span><span class="p">,</span> <span class="s">"path/to/package"</span><span class="p">);</span>
<span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">filename</span> <span class="o">=</span> <span class="s">"path/to/URDF/file"</span><span class="p">;</span>
<span class="cm">/* Load the URDF in a DART Skeleton */</span>
<span class="k">auto</span> <span class="n">manipulator</span> <span class="o">=</span> <span class="n">loader</span><span class="p">.</span><span class="n">parseSkeleton</span><span class="p">(</span><span class="n">filename</span><span class="p">);</span></pre>
<p class="m-text m-small m-dim m-noindent">See <a href="https://github.com/mosra/magnum-examples/blob/08cddf48fdfcf5ffe2be51a211c387ea2cc8fa9e/src/dart/DartExample.cpp#L278-L283">this code in the mag­num-ex­amples re­pos­it­ory</a>.</p>
</figure>
<p>The <a href="https://www.kuka.com/en-ch/products/robotics-systems/industrial-robots/lbr-iiwa">KUKA LBR Ii­wa ma­nip­u­lat­or</a>
URDF that we used is a mod­i­fied ver­sion of the one in the
<a href="https://github.com/epfl-lasa/iiwa_ros">ii­wa_ros</a> pack­age. The
<a href="https://robotiq.com/products/2f85-140-adaptive-robot-gripper?ref=nav_product_new_button">Ro­botiq grip­per 2F-85</a>
URDF that we used is a mod­i­fied ver­sion of the one in the
<a href="https://github.com/a-price/robotiq_arg85_description">ro­botiq_ar­g85_­de­scrip­tion</a>
pack­age. Once we have loaded/cre­ated all our ro­bots, we add them to the DART
world:</p>
<figure class="m-code-figure">
<pre class="m-code"><span class="cm">/* Add the robot/objects in our DART world */</span>
<span class="n">world</span><span class="o">-></span><span class="n">addSkeleton</span><span class="p">(</span><span class="n">manipulator</span><span class="p">);</span>
<span class="n">world</span><span class="o">-></span><span class="n">addSkeleton</span><span class="p">(</span><span class="n">floorSkel</span><span class="p">);</span>
<span class="n">world</span><span class="o">-></span><span class="n">addSkeleton</span><span class="p">(</span><span class="n">redBoxSkel</span><span class="p">);</span>
<span class="n">world</span><span class="o">-></span><span class="n">addSkeleton</span><span class="p">(</span><span class="n">greenBoxSkel</span><span class="p">);</span>
<span class="n">world</span><span class="o">-></span><span class="n">addSkeleton</span><span class="p">(</span><span class="n">blueBoxSkel</span><span class="p">);</span></pre>
<p class="m-text m-small m-dim m-noindent">See <a href="https://github.com/mosra/magnum-examples/blob/08cddf48fdfcf5ffe2be51a211c387ea2cc8fa9e/src/dart/DartExample.cpp#L325-L356">this code in the mag­num-ex­amples re­pos­it­ory</a>.</p>
</figure>
</section>
<section id="how-does-dart-connect-to-magnum">
<h3><a href="#how-does-dart-connect-to-magnum">How does DART con­nect to Mag­num?</a></h3>
<p>DART con­nects to Mag­num through the <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1SceneGraph.html">SceneGraph</a> lib­rary, sim­il­ar to how
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1BulletIntegration.html">Bul­letInteg­ra­tion</a> does it. DART in­teg­ra­tion provides two main classes:
(a) <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1DartIntegration_1_1World.html">DartInteg­ra­tion::World</a>, and (b) <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1DartIntegration_1_1Object.html">DartInteg­ra­tion::Ob­ject</a>. The
most com­mon us­age will be some­thing like the fol­low­ing:</p>
<pre class="m-code"><span class="cm">/* DART world */</span>
<span class="n">dart</span><span class="o">::</span><span class="n">simulation</span><span class="o">::</span><span class="n">WorldPtr</span> <span class="n">dartWorld</span><span class="p">{</span><span class="k">new</span> <span class="n">dart</span><span class="o">::</span><span class="n">simulation</span><span class="o">::</span><span class="n">World</span><span class="p">};</span>
<span class="c1">// ... add robots and objects into DART world ...</span>
<span class="cm">/* Create our DartIntegration object/world */</span>
<span class="k">auto</span> <span class="n">dartObj</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Object3D</span><span class="p">{</span><span class="o">&</span><span class="n">_scene</span><span class="p">};</span>
<span class="k">auto</span> <span class="n">world</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">make_unique</span><span class="o"><</span><span class="n">DartIntegration</span><span class="o">::</span><span class="n">World</span><span class="o">></span><span class="p">(</span><span class="o">*</span><span class="n">dartObj</span><span class="p">,</span> <span class="o">*</span><span class="n">dartWorld</span><span class="p">));</span>
<span class="cm">/* Simulate with time step of 0.001 seconds */</span>
<span class="n">world</span><span class="p">.</span><span class="n">world</span><span class="p">().</span><span class="n">setTimeStep</span><span class="p">(</span><span class="mf">0.001</span><span class="p">);</span>
<span class="k">for</span><span class="p">(</span><span class="n">UnsignedInt</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">simulationSteps</span><span class="p">;</span> <span class="o">++</span><span class="n">i</span><span class="p">)</span> <span class="p">{</span>
<span class="n">world</span><span class="p">.</span><span class="n">step</span><span class="p">();</span>
<span class="cm">/* Update graphics at ~60Hz (15*0.001 ~= 60Hz) */</span>
<span class="k">if</span><span class="p">(</span><span class="n">i</span> <span class="o">%</span> <span class="mi">15</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="n">world</span><span class="p">.</span><span class="n">refresh</span><span class="p">();</span>
<span class="cm">/* Get unused/deleted shapes */</span>
<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">std</span><span class="o">::</span><span class="n">unique_ptr</span><span class="o"><</span><span class="n">DartIntegration</span><span class="o">::</span><span class="n">Object</span><span class="o">>>&</span> <span class="n">unusedObjects</span>
<span class="o">=</span> <span class="n">world</span><span class="p">.</span><span class="n">unusedObjects</span><span class="p">();</span>
<span class="cm">/* The user is expected to handle unused objects. One possible</span>
<span class="cm"> handling would be to remove them from the parent scene. */</span>
<span class="n">deleteObjectsFromScene</span><span class="p">(</span><span class="n">unusedObjects</span><span class="p">);</span>
<span class="cm">/* Get updated shapes -- ones that either the materials or the</span>
<span class="cm"> meshes have changed */</span>
<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">std</span><span class="o">::</span><span class="n">reference_wrapper</span><span class="o"><</span><span class="n">DartIntegration</span><span class="o">::</span><span class="n">Object</span><span class="o">>></span>
<span class="n">updatedObjects</span> <span class="o">=</span> <span class="n">world</span><span class="p">.</span><span class="n">updatedShapeObjects</span><span class="p">();</span>
<span class="n">updateMeshesAndMaterials</span><span class="p">(</span><span class="n">updatedObjects</span><span class="p">);</span>
<span class="cm">/* Clear list of updated objects */</span>
<span class="n">world</span><span class="p">.</span><span class="n">clearUpdatedShapeObjects</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span></pre>
<p>In es­sence, the user needs to handle the de­leted ob­jects and the ones that need
to be up­dated. In our ex­ample, noth­ing should be de­leted and thus we do not
handle the de­leted ob­jects. In or­der to draw the meshes of our ro­bots, we need
to cre­ate a struc­ture. We will as­sume a <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1Shaders.html#aa8a252d128449a517288dc461d21348e">Phong shader</a>
and cre­ate the fol­low­ing class:</p>
<figure class="m-code-figure">
<pre class="m-code"><span class="k">class</span> <span class="nc">DrawableObject</span><span class="o">:</span> <span class="k">public</span> <span class="n">Object3D</span><span class="p">,</span> <span class="n">SceneGraph</span><span class="o">::</span><span class="n">Drawable3D</span> <span class="p">{</span>
<span class="k">public</span><span class="o">:</span>
<span class="k">explicit</span> <span class="n">DrawableObject</span><span class="p">(</span>
<span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">Containers</span><span class="o">::</span><span class="n">Reference</span><span class="o"><</span><span class="n">GL</span><span class="o">::</span><span class="n">Mesh</span><span class="o">>>&</span> <span class="n">meshes</span><span class="p">,</span>
<span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">MaterialData</span><span class="o">>&</span> <span class="n">materials</span><span class="p">,</span> <span class="n">Object3D</span><span class="o">*</span> <span class="n">parent</span><span class="p">,</span>
<span class="n">SceneGraph</span><span class="o">::</span><span class="n">DrawableGroup3D</span><span class="o">*</span> <span class="n">group</span><span class="p">);</span>
<span class="n">DrawableObject</span><span class="o">&</span> <span class="n">setMeshes</span><span class="p">(</span>
<span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">Containers</span><span class="o">::</span><span class="n">Reference</span><span class="o"><</span><span class="n">GL</span><span class="o">::</span><span class="n">Mesh</span><span class="o">>>&</span> <span class="n">meshes</span><span class="p">);</span>
<span class="n">DrawableObject</span><span class="o">&</span> <span class="n">setMaterials</span><span class="p">(</span>
<span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">MaterialData</span><span class="o">>&</span> <span class="n">materials</span><span class="p">);</span>
<span class="n">DrawableObject</span><span class="o">&</span> <span class="n">setSoftBodies</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="kt">bool</span><span class="o">>&</span> <span class="n">softBody</span><span class="p">);</span>
<span class="n">DrawableObject</span><span class="o">&</span> <span class="n">setTextures</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">GL</span><span class="o">::</span><span class="n">Texture2D</span><span class="o">*>&</span> <span class="n">textures</span><span class="p">);</span>
<span class="k">private</span><span class="o">:</span>
<span class="kt">void</span> <span class="n">draw</span><span class="p">(</span><span class="k">const</span> <span class="n">Matrix4</span><span class="o">&</span> <span class="n">transformationMatrix</span><span class="p">,</span> <span class="n">SceneGraph</span><span class="o">::</span><span class="n">Camera3D</span><span class="o">&</span> <span class="n">camera</span><span class="p">)</span>
<span class="k">override</span><span class="p">;</span>
<span class="n">Resource</span><span class="o"><</span><span class="n">Shaders</span><span class="o">::</span><span class="n">Phong</span><span class="o">></span> <span class="n">_colorShader</span><span class="p">;</span>
<span class="n">Resource</span><span class="o"><</span><span class="n">Shaders</span><span class="o">::</span><span class="n">Phong</span><span class="o">></span> <span class="n">_textureShader</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">Containers</span><span class="o">::</span><span class="n">Reference</span><span class="o"><</span><span class="n">GL</span><span class="o">::</span><span class="n">Mesh</span><span class="o">>></span> <span class="n">_meshes</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">MaterialData</span><span class="o">></span> <span class="n">_materials</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="kt">bool</span><span class="o">></span> <span class="n">_isSoftBody</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">GL</span><span class="o">::</span><span class="n">Texture2D</span><span class="o">*></span> <span class="n">_textures</span><span class="p">;</span>
<span class="p">};</span></pre>
<p class="m-text m-small m-dim m-noindent">See <a href="https://github.com/mosra/magnum-examples/blob/08cddf48fdfcf5ffe2be51a211c387ea2cc8fa9e/src/dart/DartExample.cpp#L153-L186">this code in the mag­num-ex­amples re­pos­it­ory</a>.</p>
</figure>
<p>Note that each <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1DartIntegration_1_1Object.html">DartInteg­ra­tion::Ob­ject</a> can con­tain mul­tiple meshes with
col­or or tex­ture ma­ter­i­al. To keep track of which ob­jects are be­ing up­dated
(this should only hap­pen if the visu­al prop­er­ties, that is the mesh or ma­ter­i­al
in­form­a­tion, of a body changes), we have defined a <a class="m-flat" href="https://en.cppreference.com/w/cpp/container/unordered_map">std::un­ordered_map</a>:</p>
<pre class="m-code"><span class="n">std</span><span class="o">::</span><span class="n">unordered_map</span><span class="o"><</span><span class="n">DartIntegration</span><span class="o">::</span><span class="n">Object</span><span class="o">*</span><span class="p">,</span> <span class="n">DrawableObject</span><span class="o">*></span> <span class="n">_drawableObjects</span><span class="p">;</span></pre>
<p>To up­date the in­form­a­tion of our ob­jects (both the trans­form­a­tions but also the
graph­ics part), we per­form the fol­low­ing:</p>
<pre class="m-code"><span class="n">world</span><span class="o">-></span><span class="n">refresh</span><span class="p">();</span></pre>
<p>To get and up­date the new meshes and ma­ter­i­al in­form­a­tion, we per­form some­thing
like the fol­low­ing:</p>
<figure class="m-code-figure">
<pre class="m-code"><span class="cm">/* For each updated object */</span>
<span class="k">for</span><span class="p">(</span><span class="n">DartIntegration</span><span class="o">::</span><span class="n">Object</span><span class="o">&</span> <span class="nl">object</span><span class="p">:</span> <span class="n">world</span><span class="o">-></span><span class="n">updatedShapeObjects</span><span class="p">())</span> <span class="p">{</span>
<span class="cm">/* Get material information */</span>
<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">MaterialData</span><span class="o">></span> <span class="n">materials</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">Containers</span><span class="o">::</span><span class="n">Reference</span><span class="o"><</span><span class="n">GL</span><span class="o">::</span><span class="n">Mesh</span><span class="o">>></span> <span class="n">meshes</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="kt">bool</span><span class="o">></span> <span class="n">isSoftBody</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">L</span><span class="o">::</span><span class="n">Texture2D</span><span class="o">*></span> <span class="n">textures</span><span class="p">;</span>
<span class="k">for</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="kt">size_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">object</span><span class="p">.</span><span class="n">drawData</span><span class="p">().</span><span class="n">meshes</span><span class="p">.</span><span class="n">size</span><span class="p">();</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="kt">bool</span> <span class="n">isColor</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
<span class="n">GL</span><span class="o">::</span><span class="n">Texture2D</span><span class="o">*</span> <span class="n">texture</span> <span class="o">=</span> <span class="k">nullptr</span><span class="p">;</span>
<span class="k">if</span><span class="p">(</span><span class="n">object</span><span class="p">.</span><span class="n">drawData</span><span class="p">().</span><span class="n">materials</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">flags</span><span class="p">()</span> <span class="o">&</span>
<span class="n">Trade</span><span class="o">::</span><span class="n">PhongMaterialData</span><span class="o">::</span><span class="n">Flag</span><span class="o">::</span><span class="n">DiffuseTexture</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">Containers</span><span class="o">::</span><span class="n">Optional</span><span class="o"><</span><span class="n">GL</span><span class="o">::</span><span class="n">Texture2D</span><span class="o">>&</span> <span class="n">entry</span> <span class="o">=</span> <span class="n">object</span><span class="p">.</span><span class="n">drawData</span><span class="p">()</span>
<span class="p">.</span><span class="n">textures</span><span class="p">[</span><span class="n">object</span><span class="p">.</span><span class="n">drawData</span><span class="p">().</span><span class="n">materials</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">diffuseTexture</span><span class="p">()];</span>
<span class="k">if</span><span class="p">(</span><span class="n">entry</span><span class="p">)</span> <span class="p">{</span>
<span class="n">texture</span> <span class="o">=</span> <span class="o">&*</span><span class="n">entry</span><span class="p">;</span>
<span class="n">isColor</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="n">textures</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="n">texture</span><span class="p">);</span>
<span class="n">MaterialData</span> <span class="n">mat</span><span class="p">;</span>
<span class="n">mat</span><span class="p">.</span><span class="n">ambientColor</span> <span class="o">=</span> <span class="n">object</span><span class="p">.</span><span class="n">drawData</span><span class="p">().</span><span class="n">materials</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">ambientColor</span><span class="p">().</span><span class="n">rgb</span><span class="p">();</span>
<span class="k">if</span><span class="p">(</span><span class="n">isColor</span><span class="p">)</span>
<span class="n">mat</span><span class="p">.</span><span class="n">diffuseColor</span> <span class="o">=</span> <span class="n">object</span><span class="p">.</span><span class="n">drawData</span><span class="p">().</span><span class="n">materials</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">diffuseColor</span><span class="p">().</span><span class="n">rgb</span><span class="p">();</span>
<span class="n">mat</span><span class="p">.</span><span class="n">specularColor</span> <span class="o">=</span> <span class="n">object</span><span class="p">.</span><span class="n">drawData</span><span class="p">().</span><span class="n">materials</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">specularColor</span><span class="p">().</span><span class="n">rgb</span><span class="p">();</span>
<span class="n">mat</span><span class="p">.</span><span class="n">shininess</span> <span class="o">=</span> <span class="n">object</span><span class="p">.</span><span class="n">drawData</span><span class="p">().</span><span class="n">materials</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">shininess</span><span class="p">();</span>
<span class="n">mat</span><span class="p">.</span><span class="n">scaling</span> <span class="o">=</span> <span class="n">object</span><span class="p">.</span><span class="n">drawData</span><span class="p">().</span><span class="n">scaling</span><span class="p">;</span>
<span class="cm">/* Get the modified mesh */</span>
<span class="n">meshes</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="n">object</span><span class="p">.</span><span class="n">drawData</span><span class="p">().</span><span class="n">meshes</span><span class="p">[</span><span class="n">i</span><span class="p">]);</span>
<span class="n">materials</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="n">mat</span><span class="p">);</span>
<span class="n">isSoftBody</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="n">object</span><span class="p">.</span><span class="n">shapeNode</span><span class="p">()</span><span class="o">-></span><span class="n">getShape</span><span class="p">()</span><span class="o">-></span><span class="n">getType</span><span class="p">()</span> <span class="o">==</span>
<span class="n">dart</span><span class="o">::</span><span class="n">dynamics</span><span class="o">::</span><span class="n">SoftMeshShape</span><span class="o">::</span><span class="n">getStaticType</span><span class="p">());</span>
<span class="p">}</span>
<span class="cm">/* Check if we already have it and then either add a new one or update</span>
<span class="cm"> the existing. We don't need the mesh / material / texture vectors</span>
<span class="cm"> anywhere else anymore, so move them in to avoid copies. */</span>
<span class="k">auto</span> <span class="n">it</span> <span class="o">=</span> <span class="n">_drawableObjects</span><span class="p">.</span><span class="n">insert</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">make_pair</span><span class="p">(</span><span class="o">&</span><span class="n">object</span><span class="p">,</span> <span class="k">nullptr</span><span class="p">));</span>
<span class="k">if</span><span class="p">(</span><span class="n">it</span><span class="p">.</span><span class="n">second</span><span class="p">)</span> <span class="p">{</span>
<span class="k">auto</span> <span class="n">drawableObj</span> <span class="o">=</span> <span class="k">new</span> <span class="n">DrawableObject</span><span class="p">{</span>
<span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">meshes</span><span class="p">),</span> <span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">materials</span><span class="p">),</span>
<span class="k">static_cast</span><span class="o"><</span><span class="n">Object3D</span><span class="o">*></span><span class="p">(</span><span class="o">&</span><span class="p">(</span><span class="n">object</span><span class="p">.</span><span class="n">object</span><span class="p">())),</span> <span class="o">&</span><span class="n">_drawables</span><span class="p">};</span>
<span class="n">drawableObj</span><span class="o">-></span><span class="n">setSoftBodies</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">isSoftBody</span><span class="p">));</span>
<span class="n">drawableObj</span><span class="o">-></span><span class="n">setTextures</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">textures</span><span class="p">));</span>
<span class="n">it</span><span class="p">.</span><span class="n">first</span><span class="o">-></span><span class="n">second</span> <span class="o">=</span> <span class="n">drawableObj</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="p">(</span><span class="o">*</span><span class="n">it</span><span class="p">.</span><span class="n">first</span><span class="o">-></span><span class="n">second</span><span class="p">)</span>
<span class="p">.</span><span class="n">setMeshes</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">meshes</span><span class="p">))</span>
<span class="p">.</span><span class="n">setMaterials</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">materials</span><span class="p">))</span>
<span class="p">.</span><span class="n">setSoftBodies</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">isSoftBody</span><span class="p">))</span>
<span class="p">.</span><span class="n">setTextures</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">textures</span><span class="p">));</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="n">world</span><span class="o">-></span><span class="n">clearUpdatedShapeObjects</span><span class="p">();</span></pre>
<p class="m-text m-small m-dim m-noindent">See <a href="https://github.com/mosra/magnum-examples/blob/08cddf48fdfcf5ffe2be51a211c387ea2cc8fa9e/src/dart/DartExample.cpp#L415-L470">this code in the mag­num-ex­amples re­pos­it­ory</a>.</p>
</figure>
<p>The <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1DartIntegration_1_1Object.html">DartInteg­ra­tion::Ob­ject</a> stores in memory the mesh, ma­ter­i­al and
tex­ture in­form­a­tion and thus it is ad­vised to keep ref­er­ences or point­ers to
these val­ues. Put­ting everything to­geth­er and with a few ad­di­tion­al things
(e.g., two lights), we can get some­thing like the fol­low­ing:</p>
<div class="m-imagegrid m-container-inflate">
<div>
<figure style="width: 100.000%">
<a href="https://static.magnum.graphics/img/blog/guest-posts/using-dart-to-control-a-robotic-manipulator.png"><img src="https://static.magnum.graphics/img/blog/guest-posts/using-dart-to-control-a-robotic-manipulator.png" /><div>
</div>
</a>
</figure>
</div>
</div>
</section>
<section id="simulating-any-robot-with-the-dartintegration">
<h3><a href="#simulating-any-robot-with-the-dartintegration">Sim­u­lat­ing any ro­bot with the DartInteg­ra­tion</a></h3>
<p>Us­ing the ex­actly the same code (just chan­ging the URDF file), we can load any
ro­bot in DART and render it with Mag­num. Here are a few oth­er ex­amples
ren­der­ing a hu­manoid (<a href="http://www.icub.org/">iCub</a>), a
<a href="https://www.resibots.eu/photos.html#pexod-robot">hexa­pod</a> and quad­ruped
(<a href="https://github.com/ANYbotics/anymal_b_simple_description">ANY­mal</a>) ro­bot:</p>
<div class="m-imagegrid m-container-inflate">
<div>
<figure style="width: 17.714%">
<a href="https://static.magnum.graphics/img/blog/guest-posts/using-dart-to-control-a-robotic-manipulator/icub.png"><img src="https://static.magnum.graphics/img/blog/guest-posts/using-dart-to-control-a-robotic-manipulator/icub.png" /><div>
</div>
</a>
</figure>
<figure style="width: 21.820%">
<a href="https://static.magnum.graphics/img/blog/guest-posts/using-dart-to-control-a-robotic-manipulator/hexapod.png"><img src="https://static.magnum.graphics/img/blog/guest-posts/using-dart-to-control-a-robotic-manipulator/hexapod.png" /><div>
</div>
</a>
</figure>
<figure style="width: 60.466%">
<a href="https://static.magnum.graphics/img/blog/guest-posts/using-dart-to-control-a-robotic-manipulator/anymal.png"><img src="https://static.magnum.graphics/img/blog/guest-posts/using-dart-to-control-a-robotic-manipulator/anymal.png" /><div>
</div>
</a>
</figure>
</div>
</div>
</section>
</section>
<section id="controlling-a-robotic-manipulator">
<h2><a href="#controlling-a-robotic-manipulator">Con­trolling a ro­bot­ic ma­nip­u­lat­or</a></h2>
<p>The ex­ample scene looks (re­l­at­ively) nice, but up un­til now we can­not con­trol
what hap­pens to our ima­gin­ary world. But most of the fun comes when we can
con­trol our ro­bots and make them do things. In this second part of the post,
we will see how we can con­trol our ma­nip­u­lat­or in or­der to be able to give it
in­tu­it­ive com­mands to per­form some every­day tasks.</p>
<p>As we have already dis­cussed, our ro­bot’s state can be de­scribed by its joint
po­s­i­tions and its joint ve­lo­cit­ies. In the ro­bot­ics lit­er­at­ure, we find the
nota­tion <svg class="m-math" style="width: 0.585em; height: 0.778em; vertical-align: -0.243em;" viewBox="0 -5.147373 5.61916 7.47198">
<title>
q
</title>
<defs>
<path id='eq1-g0-113' d='M5.272229 -5.152677C5.272229 -5.212453 5.224408 -5.260274 5.164633 -5.260274C5.068991 -5.260274 4.60274 -4.829888 4.375592 -4.411457C4.160399 -4.94944 3.789788 -5.272229 3.275716 -5.272229C1.924782 -5.272229 0.466252 -3.526775 0.466252 -1.75741C0.466252 -0.573848 1.159651 0.119552 1.972603 0.119552C2.606227 0.119552 3.132254 -0.358655 3.383313 -0.633624L3.395268 -0.621669L2.940971 1.171606L2.833375 1.601993C2.725778 1.960648 2.546451 1.960648 1.984558 1.972603C1.853051 1.972603 1.733499 1.972603 1.733499 2.199751C1.733499 2.283437 1.80523 2.319303 1.888917 2.319303C2.056289 2.319303 2.271482 2.295392 2.438854 2.295392H3.658281C3.837609 2.295392 4.040847 2.319303 4.220174 2.319303C4.291905 2.319303 4.435367 2.319303 4.435367 2.092154C4.435367 1.972603 4.339726 1.972603 4.160399 1.972603C3.598506 1.972603 3.56264 1.888917 3.56264 1.793275C3.56264 1.733499 3.574595 1.721544 3.610461 1.566127L5.272229 -5.152677ZM3.58655 -1.422665C3.526775 -1.219427 3.526775 -1.195517 3.359402 -0.968369C3.096389 -0.633624 2.570361 -0.119552 2.008468 -0.119552C1.518306 -0.119552 1.243337 -0.561893 1.243337 -1.267248C1.243337 -1.924782 1.613948 -3.263761 1.841096 -3.765878C2.247572 -4.60274 2.809465 -5.033126 3.275716 -5.033126C4.064757 -5.033126 4.220174 -4.052802 4.220174 -3.957161C4.220174 -3.945205 4.184309 -3.789788 4.172354 -3.765878L3.58655 -1.422665Z'/>
</defs>
<g id='eq1-page1'>
<use x='0' y='0' xlink:href='#eq1-g0-113'/>
</g>
</svg> for joint po­s­i­tions and <svg class="m-math" style="width: 0.585em; height: 1.069em; vertical-align: -0.243em;" viewBox="0 -7.934555 5.61916 10.259162">
<title>
\dot{q}
</title>
<defs>
<path id='eq2-g0-113' d='M5.272229 -5.152677C5.272229 -5.212453 5.224408 -5.260274 5.164633 -5.260274C5.068991 -5.260274 4.60274 -4.829888 4.375592 -4.411457C4.160399 -4.94944 3.789788 -5.272229 3.275716 -5.272229C1.924782 -5.272229 0.466252 -3.526775 0.466252 -1.75741C0.466252 -0.573848 1.159651 0.119552 1.972603 0.119552C2.606227 0.119552 3.132254 -0.358655 3.383313 -0.633624L3.395268 -0.621669L2.940971 1.171606L2.833375 1.601993C2.725778 1.960648 2.546451 1.960648 1.984558 1.972603C1.853051 1.972603 1.733499 1.972603 1.733499 2.199751C1.733499 2.283437 1.80523 2.319303 1.888917 2.319303C2.056289 2.319303 2.271482 2.295392 2.438854 2.295392H3.658281C3.837609 2.295392 4.040847 2.319303 4.220174 2.319303C4.291905 2.319303 4.435367 2.319303 4.435367 2.092154C4.435367 1.972603 4.339726 1.972603 4.160399 1.972603C3.598506 1.972603 3.56264 1.888917 3.56264 1.793275C3.56264 1.733499 3.574595 1.721544 3.610461 1.566127L5.272229 -5.152677ZM3.58655 -1.422665C3.526775 -1.219427 3.526775 -1.195517 3.359402 -0.968369C3.096389 -0.633624 2.570361 -0.119552 2.008468 -0.119552C1.518306 -0.119552 1.243337 -0.561893 1.243337 -1.267248C1.243337 -1.924782 1.613948 -3.263761 1.841096 -3.765878C2.247572 -4.60274 2.809465 -5.033126 3.275716 -5.033126C4.064757 -5.033126 4.220174 -4.052802 4.220174 -3.957161C4.220174 -3.945205 4.184309 -3.789788 4.172354 -3.765878L3.58655 -1.422665Z'/>
<path id='eq2-g1-95' d='M2.199751 -7.364384C2.199751 -7.723039 1.912827 -7.950187 1.625903 -7.950187C1.291158 -7.950187 1.0401 -7.687173 1.0401 -7.364384C1.0401 -7.053549 1.303113 -6.790535 1.613948 -6.790535C1.972603 -6.790535 2.199751 -7.07746 2.199751 -7.364384Z'/>
</defs>
<g id='eq2-page1'>
<use x='2.159246' y='0' xlink:href='#eq2-g1-95'/>
<use x='0' y='0' xlink:href='#eq2-g0-113'/>
</g>
</svg> for joint
ve­lo­cit­ies.</p>
<section id="actuator-types">
<h3><a href="#actuator-types">Ac­tu­at­or types</a></h3>
<p>Ro­bots are mech­an­ic­al struc­tures that are in­ter­con­nec­ted and con­trolled via
elec­tric­al com­pon­ents. This means that the low­est level con­trol sig­nal hap­pens
in cur­rent. Be­cause hand­ling cur­rent is dif­fi­cult and non-in­tu­it­ive, there
are two types of mo­tors widely used to provide a high­er level of ab­strac­tion:</p>
<ul>
<li>Torque-con­trolled ac­tu­at­ors; these ac­tu­at­ors are equipped with a
force/torque sensor and the high­er level con­trol sig­nals are the de­sired
torques/forces (de­pend­ing on the type of the mech­an­ic­al con­nec­tion).</li>
<li>Servo ac­tu­at­ors; these ac­tu­at­ors are equipped with an en­coder sensor (that
meas­ures the dis­place­ment or cur­rent po­s­i­tion of the joint) and the high­er
level con­trol sig­nals are the de­sired joint po­s­i­tions.</li>
</ul>
</section>
<section id="controlling-in-joint-space">
<h3><a href="#controlling-in-joint-space">Con­trolling in joint space</a></h3>
<p>The easi­est type of con­trol is to con­trol the ro­bot in what we call the
joint-space. This ba­sic­ally means that we have some de­sired <svg class="m-math" style="width: 1.026em; height: 1.080em; vertical-align: -0.243em;" viewBox="0 -8.046759 9.853339 10.371367">
<title>
q^*
</title>
<defs>
<path id='eq3-g0-3' d='M3.291656 -1.052055C3.363387 -1.004234 3.387298 -1.004234 3.427148 -1.004234C3.55467 -1.004234 3.666252 -1.107846 3.666252 -1.251308C3.666252 -1.40274 3.58655 -1.43462 3.466999 -1.490411C2.933001 -1.737484 2.741719 -1.825156 2.351183 -1.984558L3.283686 -2.406974C3.347447 -2.430884 3.498879 -2.502615 3.56264 -2.526526C3.642341 -2.574346 3.666252 -2.654047 3.666252 -2.725778C3.666252 -2.82142 3.618431 -2.972852 3.379328 -2.972852L2.231631 -2.191781L2.343213 -3.371357C2.359153 -3.506849 2.343213 -3.706102 2.11208 -3.706102C1.968618 -3.706102 1.857036 -3.58655 1.880946 -3.474969V-3.379328L1.992528 -2.191781L0.932503 -2.925031C0.860772 -2.972852 0.836862 -2.972852 0.797011 -2.972852C0.669489 -2.972852 0.557908 -2.86924 0.557908 -2.725778C0.557908 -2.574346 0.637609 -2.542466 0.757161 -2.486675C1.291158 -2.239601 1.482441 -2.15193 1.872976 -1.992528L0.940473 -1.570112C0.876712 -1.546202 0.72528 -1.474471 0.661519 -1.45056C0.581818 -1.40274 0.557908 -1.323039 0.557908 -1.251308C0.557908 -1.107846 0.669489 -1.004234 0.797011 -1.004234C0.860772 -1.004234 0.876712 -1.004234 1.075965 -1.147696L1.992528 -1.785305L1.872976 -0.502117C1.872976 -0.342715 2.008468 -0.270984 2.11208 -0.270984S2.351183 -0.342715 2.351183 -0.502117C2.351183 -0.581818 2.319303 -0.836862 2.311333 -0.932503C2.279452 -1.203487 2.255542 -1.506351 2.231631 -1.785305L3.291656 -1.052055Z'/>
<path id='eq3-g1-113' d='M5.272229 -5.152677C5.272229 -5.212453 5.224408 -5.260274 5.164633 -5.260274C5.068991 -5.260274 4.60274 -4.829888 4.375592 -4.411457C4.160399 -4.94944 3.789788 -5.272229 3.275716 -5.272229C1.924782 -5.272229 0.466252 -3.526775 0.466252 -1.75741C0.466252 -0.573848 1.159651 0.119552 1.972603 0.119552C2.606227 0.119552 3.132254 -0.358655 3.383313 -0.633624L3.395268 -0.621669L2.940971 1.171606L2.833375 1.601993C2.725778 1.960648 2.546451 1.960648 1.984558 1.972603C1.853051 1.972603 1.733499 1.972603 1.733499 2.199751C1.733499 2.283437 1.80523 2.319303 1.888917 2.319303C2.056289 2.319303 2.271482 2.295392 2.438854 2.295392H3.658281C3.837609 2.295392 4.040847 2.319303 4.220174 2.319303C4.291905 2.319303 4.435367 2.319303 4.435367 2.092154C4.435367 1.972603 4.339726 1.972603 4.160399 1.972603C3.598506 1.972603 3.56264 1.888917 3.56264 1.793275C3.56264 1.733499 3.574595 1.721544 3.610461 1.566127L5.272229 -5.152677ZM3.58655 -1.422665C3.526775 -1.219427 3.526775 -1.195517 3.359402 -0.968369C3.096389 -0.633624 2.570361 -0.119552 2.008468 -0.119552C1.518306 -0.119552 1.243337 -0.561893 1.243337 -1.267248C1.243337 -1.924782 1.613948 -3.263761 1.841096 -3.765878C2.247572 -4.60274 2.809465 -5.033126 3.275716 -5.033126C4.064757 -5.033126 4.220174 -4.052802 4.220174 -3.957161C4.220174 -3.945205 4.184309 -3.789788 4.172354 -3.765878L3.58655 -1.422665Z'/>
</defs>
<g id='eq3-page1'>
<use x='0' y='0' xlink:href='#eq3-g1-113'/>
<use x='5.619156' y='-4.338437' xlink:href='#eq3-g0-3'/>
</g>
</svg> joint
po­s­i­tions and some de­sired <svg class="m-math" style="width: 1.026em; height: 1.080em; vertical-align: -0.243em;" viewBox="0 -8.046759 9.853339 10.371367">
<title>
\dot{q}^*
</title>
<defs>
<path id='eq4-g0-3' d='M3.291656 -1.052055C3.363387 -1.004234 3.387298 -1.004234 3.427148 -1.004234C3.55467 -1.004234 3.666252 -1.107846 3.666252 -1.251308C3.666252 -1.40274 3.58655 -1.43462 3.466999 -1.490411C2.933001 -1.737484 2.741719 -1.825156 2.351183 -1.984558L3.283686 -2.406974C3.347447 -2.430884 3.498879 -2.502615 3.56264 -2.526526C3.642341 -2.574346 3.666252 -2.654047 3.666252 -2.725778C3.666252 -2.82142 3.618431 -2.972852 3.379328 -2.972852L2.231631 -2.191781L2.343213 -3.371357C2.359153 -3.506849 2.343213 -3.706102 2.11208 -3.706102C1.968618 -3.706102 1.857036 -3.58655 1.880946 -3.474969V-3.379328L1.992528 -2.191781L0.932503 -2.925031C0.860772 -2.972852 0.836862 -2.972852 0.797011 -2.972852C0.669489 -2.972852 0.557908 -2.86924 0.557908 -2.725778C0.557908 -2.574346 0.637609 -2.542466 0.757161 -2.486675C1.291158 -2.239601 1.482441 -2.15193 1.872976 -1.992528L0.940473 -1.570112C0.876712 -1.546202 0.72528 -1.474471 0.661519 -1.45056C0.581818 -1.40274 0.557908 -1.323039 0.557908 -1.251308C0.557908 -1.107846 0.669489 -1.004234 0.797011 -1.004234C0.860772 -1.004234 0.876712 -1.004234 1.075965 -1.147696L1.992528 -1.785305L1.872976 -0.502117C1.872976 -0.342715 2.008468 -0.270984 2.11208 -0.270984S2.351183 -0.342715 2.351183 -0.502117C2.351183 -0.581818 2.319303 -0.836862 2.311333 -0.932503C2.279452 -1.203487 2.255542 -1.506351 2.231631 -1.785305L3.291656 -1.052055Z'/>
<path id='eq4-g2-95' d='M2.199751 -7.364384C2.199751 -7.723039 1.912827 -7.950187 1.625903 -7.950187C1.291158 -7.950187 1.0401 -7.687173 1.0401 -7.364384C1.0401 -7.053549 1.303113 -6.790535 1.613948 -6.790535C1.972603 -6.790535 2.199751 -7.07746 2.199751 -7.364384Z'/>
<path id='eq4-g1-113' d='M5.272229 -5.152677C5.272229 -5.212453 5.224408 -5.260274 5.164633 -5.260274C5.068991 -5.260274 4.60274 -4.829888 4.375592 -4.411457C4.160399 -4.94944 3.789788 -5.272229 3.275716 -5.272229C1.924782 -5.272229 0.466252 -3.526775 0.466252 -1.75741C0.466252 -0.573848 1.159651 0.119552 1.972603 0.119552C2.606227 0.119552 3.132254 -0.358655 3.383313 -0.633624L3.395268 -0.621669L2.940971 1.171606L2.833375 1.601993C2.725778 1.960648 2.546451 1.960648 1.984558 1.972603C1.853051 1.972603 1.733499 1.972603 1.733499 2.199751C1.733499 2.283437 1.80523 2.319303 1.888917 2.319303C2.056289 2.319303 2.271482 2.295392 2.438854 2.295392H3.658281C3.837609 2.295392 4.040847 2.319303 4.220174 2.319303C4.291905 2.319303 4.435367 2.319303 4.435367 2.092154C4.435367 1.972603 4.339726 1.972603 4.160399 1.972603C3.598506 1.972603 3.56264 1.888917 3.56264 1.793275C3.56264 1.733499 3.574595 1.721544 3.610461 1.566127L5.272229 -5.152677ZM3.58655 -1.422665C3.526775 -1.219427 3.526775 -1.195517 3.359402 -0.968369C3.096389 -0.633624 2.570361 -0.119552 2.008468 -0.119552C1.518306 -0.119552 1.243337 -0.561893 1.243337 -1.267248C1.243337 -1.924782 1.613948 -3.263761 1.841096 -3.765878C2.247572 -4.60274 2.809465 -5.033126 3.275716 -5.033126C4.064757 -5.033126 4.220174 -4.052802 4.220174 -3.957161C4.220174 -3.945205 4.184309 -3.789788 4.172354 -3.765878L3.58655 -1.422665Z'/>
</defs>
<g id='eq4-page1'>
<use x='2.159246' y='0' xlink:href='#eq4-g2-95'/>
<use x='0' y='0' xlink:href='#eq4-g1-113'/>
<use x='5.619156' y='-4.338437' xlink:href='#eq4-g0-3'/>
</g>
</svg> joint ve­lo­cit­ies (usu­ally set to
zero) and we want to find the con­trol sig­nal to achieve them.</p>
<p>When con­trolling in joint-space, the most clas­sic­al con­trol­ler is a
<a href="https://en.wikipedia.org/wiki/PID_controller">Pro­por­tion­al–In­teg­ral–De­riv­at­ive (PID) con­trol­ler</a>.
In es­sence, the de­sired con­trol sig­nal (no mat­ter the type) is com­puted as
fol­lows:</p>
<div class="m-math">
<svg style="width: 25.836em; height: 3.001em;" viewBox="69.659051 -28.812865 248.029397 28.812865">
<title>
\tau = K_p (q^*-q) + K_i \int_0^t (q^*-q)dt + K_d\frac{d(q^*-q)}{dt}
</title>
<defs>
<path id='eq5-g5-48' d='M3.897385 -2.542466C3.897385 -3.395268 3.809714 -3.913325 3.5467 -4.423412C3.196015 -5.124782 2.550436 -5.300125 2.11208 -5.300125C1.107846 -5.300125 0.74122 -4.550934 0.629639 -4.327771C0.342715 -3.745953 0.326775 -2.956912 0.326775 -2.542466C0.326775 -2.016438 0.350685 -1.211457 0.73325 -0.573848C1.099875 0.01594 1.689664 0.167372 2.11208 0.167372C2.494645 0.167372 3.180075 0.047821 3.57858 -0.74122C3.873474 -1.315068 3.897385 -2.024408 3.897385 -2.542466ZM2.11208 -0.055791C1.841096 -0.055791 1.291158 -0.183313 1.123786 -1.020174C1.036115 -1.474471 1.036115 -2.223661 1.036115 -2.638107C1.036115 -3.188045 1.036115 -3.745953 1.123786 -4.184309C1.291158 -4.99726 1.912827 -5.076961 2.11208 -5.076961C2.383064 -5.076961 2.933001 -4.941469 3.092403 -4.216189C3.188045 -3.777833 3.188045 -3.180075 3.188045 -2.638107C3.188045 -2.16787 3.188045 -1.45056 3.092403 -1.004234C2.925031 -0.167372 2.375093 -0.055791 2.11208 -0.055791Z'/>
<path id='eq5-g0-90' d='M1.243337 26.026401C1.625903 26.002491 1.829141 25.739477 1.829141 25.440598C1.829141 25.046077 1.530262 24.854795 1.255293 24.854795C0.968369 24.854795 0.669489 25.034122 0.669489 25.452553C0.669489 26.062267 1.267248 26.564384 1.996513 26.564384C3.813699 26.564384 4.495143 23.766874 5.34396 20.299875C6.264508 16.522042 7.041594 12.708344 7.687173 8.870735C8.129514 6.324284 8.571856 3.93325 8.978331 2.391034C9.121793 1.80523 9.528269 0.263014 9.994521 0.263014C10.365131 0.263014 10.66401 0.490162 10.711831 0.537983C10.31731 0.561893 10.114072 0.824907 10.114072 1.123786C10.114072 1.518306 10.412951 1.709589 10.68792 1.709589C10.974844 1.709589 11.273724 1.530262 11.273724 1.111831C11.273724 0.466252 10.628144 0 9.97061 0C9.062017 0 8.392528 1.303113 7.734994 3.741968C7.699128 3.873474 6.073225 9.874969 4.758157 17.693649C4.447323 19.52279 4.100623 21.519303 3.706102 23.181071C3.490909 24.053798 2.940971 26.30137 1.972603 26.30137C1.542217 26.30137 1.255293 26.026401 1.243337 26.026401Z'/>
<path id='eq5-g3-100' d='M4.28792 -5.292154C4.29589 -5.308095 4.319801 -5.411706 4.319801 -5.419676C4.319801 -5.459527 4.28792 -5.531258 4.192279 -5.531258C4.160399 -5.531258 3.913325 -5.507347 3.730012 -5.491407L3.283686 -5.459527C3.108344 -5.443587 3.028643 -5.435616 3.028643 -5.292154C3.028643 -5.180573 3.140224 -5.180573 3.235866 -5.180573C3.618431 -5.180573 3.618431 -5.132752 3.618431 -5.061021C3.618431 -5.0132 3.55467 -4.750187 3.514819 -4.590785L3.124284 -3.036613C3.052553 -3.172105 2.82142 -3.514819 2.335243 -3.514819C1.3868 -3.514819 0.342715 -2.406974 0.342715 -1.227397C0.342715 -0.398506 0.876712 0.079701 1.490411 0.079701C2.000498 0.079701 2.438854 -0.326775 2.582316 -0.486177C2.725778 0.063761 3.267746 0.079701 3.363387 0.079701C3.730012 0.079701 3.913325 -0.223163 3.977086 -0.358655C4.136488 -0.645579 4.24807 -1.107846 4.24807 -1.139726C4.24807 -1.187547 4.216189 -1.243337 4.120548 -1.243337S4.008966 -1.195517 3.961146 -0.996264C3.849564 -0.557908 3.698132 -0.143462 3.387298 -0.143462C3.203985 -0.143462 3.132254 -0.294894 3.132254 -0.518057C3.132254 -0.669489 3.156164 -0.757161 3.180075 -0.860772L4.28792 -5.292154ZM2.582316 -0.860772C2.183811 -0.310834 1.769365 -0.143462 1.514321 -0.143462C1.147696 -0.143462 0.964384 -0.478207 0.964384 -0.892653C0.964384 -1.267248 1.179577 -2.12005 1.354919 -2.470735C1.586052 -2.956912 1.976588 -3.291656 2.343213 -3.291656C2.86127 -3.291656 3.012702 -2.709838 3.012702 -2.614197C3.012702 -2.582316 2.81345 -1.801245 2.765629 -1.594022C2.662017 -1.219427 2.662017 -1.203487 2.582316 -0.860772Z'/>
<path id='eq5-g3-105' d='M2.375093 -4.97335C2.375093 -5.148692 2.247572 -5.276214 2.064259 -5.276214C1.857036 -5.276214 1.625903 -5.084932 1.625903 -4.845828C1.625903 -4.670486 1.753425 -4.542964 1.936737 -4.542964C2.14396 -4.542964 2.375093 -4.734247 2.375093 -4.97335ZM1.211457 -2.048319L0.781071 -0.948443C0.74122 -0.828892 0.70137 -0.73325 0.70137 -0.597758C0.70137 -0.207223 1.004234 0.079701 1.42665 0.079701C2.199751 0.079701 2.526526 -1.036115 2.526526 -1.139726C2.526526 -1.219427 2.462765 -1.243337 2.406974 -1.243337C2.311333 -1.243337 2.295392 -1.187547 2.271482 -1.107846C2.088169 -0.470237 1.761395 -0.143462 1.44259 -0.143462C1.346949 -0.143462 1.251308 -0.183313 1.251308 -0.398506C1.251308 -0.589788 1.307098 -0.73325 1.41071 -0.980324C1.490411 -1.195517 1.570112 -1.41071 1.657783 -1.625903L1.904857 -2.271482C1.976588 -2.454795 2.072229 -2.701868 2.072229 -2.83736C2.072229 -3.235866 1.753425 -3.514819 1.346949 -3.514819C0.573848 -3.514819 0.239103 -2.399004 0.239103 -2.295392C0.239103 -2.223661 0.294894 -2.191781 0.358655 -2.191781C0.462267 -2.191781 0.470237 -2.239601 0.494147 -2.319303C0.71731 -3.076463 1.083935 -3.291656 1.323039 -3.291656C1.43462 -3.291656 1.514321 -3.251806 1.514321 -3.028643C1.514321 -2.948941 1.506351 -2.83736 1.42665 -2.598257L1.211457 -2.048319Z'/>
<path id='eq5-g3-112' d='M0.414446 0.964384C0.350685 1.219427 0.334745 1.283188 0.01594 1.283188C-0.095641 1.283188 -0.191283 1.283188 -0.191283 1.43462C-0.191283 1.506351 -0.119552 1.546202 -0.079701 1.546202C0 1.546202 0.03188 1.522291 0.621669 1.522291C1.195517 1.522291 1.362889 1.546202 1.41868 1.546202C1.45056 1.546202 1.570112 1.546202 1.570112 1.39477C1.570112 1.283188 1.458531 1.283188 1.362889 1.283188C0.980324 1.283188 0.980324 1.235367 0.980324 1.163636C0.980324 1.107846 1.123786 0.541968 1.362889 -0.390535C1.466501 -0.207223 1.713574 0.079701 2.14396 0.079701C3.124284 0.079701 4.144458 -1.052055 4.144458 -2.207721C4.144458 -2.996762 3.634371 -3.514819 2.996762 -3.514819C2.518555 -3.514819 2.13599 -3.188045 1.904857 -2.948941C1.737484 -3.514819 1.203487 -3.514819 1.123786 -3.514819C0.836862 -3.514819 0.637609 -3.331507 0.510087 -3.084433C0.326775 -2.725778 0.239103 -2.319303 0.239103 -2.295392C0.239103 -2.223661 0.294894 -2.191781 0.358655 -2.191781C0.462267 -2.191781 0.470237 -2.223661 0.526027 -2.430884C0.629639 -2.83736 0.773101 -3.291656 1.099875 -3.291656C1.299128 -3.291656 1.354919 -3.108344 1.354919 -2.917061C1.354919 -2.83736 1.323039 -2.646077 1.307098 -2.582316L0.414446 0.964384ZM1.880946 -2.454795C1.920797 -2.590286 1.920797 -2.606227 2.040349 -2.749689C2.343213 -3.108344 2.685928 -3.291656 2.972852 -3.291656C3.371357 -3.291656 3.52279 -2.901121 3.52279 -2.542466C3.52279 -2.247572 3.347447 -1.39477 3.108344 -0.924533C2.901121 -0.494147 2.518555 -0.143462 2.14396 -0.143462C1.601993 -0.143462 1.474471 -0.765131 1.474471 -0.820922C1.474471 -0.836862 1.490411 -0.924533 1.498381 -0.948443L1.880946 -2.454795Z'/>
<path id='eq5-g3-116' d='M1.761395 -3.172105H2.542466C2.693898 -3.172105 2.789539 -3.172105 2.789539 -3.323537C2.789539 -3.435118 2.685928 -3.435118 2.550436 -3.435118H1.825156L2.11208 -4.566874C2.14396 -4.686426 2.14396 -4.726276 2.14396 -4.734247C2.14396 -4.901619 2.016438 -4.98132 1.880946 -4.98132C1.609963 -4.98132 1.554172 -4.766127 1.466501 -4.407472L1.219427 -3.435118H0.454296C0.302864 -3.435118 0.199253 -3.435118 0.199253 -3.283686C0.199253 -3.172105 0.302864 -3.172105 0.438356 -3.172105H1.155666L0.67746 -1.259278C0.629639 -1.060025 0.557908 -0.781071 0.557908 -0.669489C0.557908 -0.191283 0.948443 0.079701 1.370859 0.079701C2.223661 0.079701 2.709838 -1.044085 2.709838 -1.139726C2.709838 -1.227397 2.638107 -1.243337 2.590286 -1.243337C2.502615 -1.243337 2.494645 -1.211457 2.438854 -1.091905C2.279452 -0.70934 1.880946 -0.143462 1.39477 -0.143462C1.227397 -0.143462 1.131756 -0.255044 1.131756 -0.518057C1.131756 -0.669489 1.155666 -0.757161 1.179577 -0.860772L1.761395 -3.172105Z'/>
<path id='eq5-g6-40' d='M3.88543 2.905106C3.88543 2.86924 3.88543 2.84533 3.682192 2.642092C2.486675 1.43462 1.817186 -0.537983 1.817186 -2.976837C1.817186 -5.296139 2.379078 -7.292653 3.765878 -8.703362C3.88543 -8.810959 3.88543 -8.834869 3.88543 -8.870735C3.88543 -8.942466 3.825654 -8.966376 3.777833 -8.966376C3.622416 -8.966376 2.642092 -8.105604 2.056289 -6.933998C1.446575 -5.726526 1.171606 -4.447323 1.171606 -2.976837C1.171606 -1.912827 1.338979 -0.490162 1.960648 0.789041C2.666002 2.223661 3.646326 3.000747 3.777833 3.000747C3.825654 3.000747 3.88543 2.976837 3.88543 2.905106Z'/>
<path id='eq5-g6-41' d='M3.371357 -2.976837C3.371357 -3.88543 3.251806 -5.36787 2.582316 -6.75467C1.876961 -8.18929 0.896638 -8.966376 0.765131 -8.966376C0.71731 -8.966376 0.657534 -8.942466 0.657534 -8.870735C0.657534 -8.834869 0.657534 -8.810959 0.860772 -8.607721C2.056289 -7.400249 2.725778 -5.427646 2.725778 -2.988792C2.725778 -0.669489 2.163885 1.327024 0.777086 2.737733C0.657534 2.84533 0.657534 2.86924 0.657534 2.905106C0.657534 2.976837 0.71731 3.000747 0.765131 3.000747C0.920548 3.000747 1.900872 2.139975 2.486675 0.968369C3.096389 -0.251059 3.371357 -1.542217 3.371357 -2.976837Z'/>
<path id='eq5-g6-43' d='M4.770112 -2.761644H8.069738C8.237111 -2.761644 8.452304 -2.761644 8.452304 -2.976837C8.452304 -3.203985 8.249066 -3.203985 8.069738 -3.203985H4.770112V-6.503611C4.770112 -6.670984 4.770112 -6.886177 4.554919 -6.886177C4.327771 -6.886177 4.327771 -6.682939 4.327771 -6.503611V-3.203985H1.028144C0.860772 -3.203985 0.645579 -3.203985 0.645579 -2.988792C0.645579 -2.761644 0.848817 -2.761644 1.028144 -2.761644H4.327771V0.537983C4.327771 0.705355 4.327771 0.920548 4.542964 0.920548C4.770112 0.920548 4.770112 0.71731 4.770112 0.537983V-2.761644Z'/>
<path id='eq5-g6-61' d='M8.069738 -3.873474C8.237111 -3.873474 8.452304 -3.873474 8.452304 -4.088667C8.452304 -4.315816 8.249066 -4.315816 8.069738 -4.315816H1.028144C0.860772 -4.315816 0.645579 -4.315816 0.645579 -4.100623C0.645579 -3.873474 0.848817 -3.873474 1.028144 -3.873474H8.069738ZM8.069738 -1.649813C8.237111 -1.649813 8.452304 -1.649813 8.452304 -1.865006C8.452304 -2.092154 8.249066 -2.092154 8.069738 -2.092154H1.028144C0.860772 -2.092154 0.645579 -2.092154 0.645579 -1.876961C0.645579 -1.649813 0.848817 -1.649813 1.028144 -1.649813H8.069738Z'/>
<path id='eq5-g4-28' d='M3.431133 -4.507098H5.415691C5.571108 -4.507098 5.965629 -4.507098 5.965629 -4.889664C5.965629 -5.152677 5.738481 -5.152677 5.523288 -5.152677H2.235616C1.960648 -5.152677 1.554172 -5.152677 1.004234 -4.566874C0.6934 -4.220174 0.310834 -3.58655 0.310834 -3.514819S0.37061 -3.419178 0.442341 -3.419178C0.526027 -3.419178 0.537983 -3.455044 0.597758 -3.526775C1.219427 -4.507098 1.841096 -4.507098 2.139975 -4.507098H3.132254L1.888917 -0.406476C1.829141 -0.227148 1.829141 -0.203238 1.829141 -0.167372C1.829141 -0.035866 1.912827 0.131507 2.15193 0.131507C2.52254 0.131507 2.582316 -0.191283 2.618182 -0.37061L3.431133 -4.507098Z'/>
<path id='eq5-g4-75' d='M5.977584 -4.829888C5.965629 -4.865753 5.917808 -4.961395 5.917808 -4.99726C5.917808 -5.009215 5.929763 -5.021171 6.133001 -5.176588L7.292653 -6.085181C8.894645 -7.328518 9.420672 -7.746949 10.245579 -7.81868C10.329265 -7.830635 10.448817 -7.830635 10.448817 -8.033873C10.448817 -8.105604 10.412951 -8.16538 10.31731 -8.16538C10.185803 -8.16538 10.042341 -8.141469 9.910834 -8.141469H9.456538C9.085928 -8.141469 8.691407 -8.16538 8.332752 -8.16538C8.249066 -8.16538 8.105604 -8.16538 8.105604 -7.950187C8.105604 -7.830635 8.18929 -7.81868 8.261021 -7.81868C8.392528 -7.806725 8.547945 -7.758904 8.547945 -7.591532C8.547945 -7.352428 8.18929 -7.065504 8.093649 -6.993773L3.407223 -3.347447L4.399502 -7.292653C4.507098 -7.699128 4.531009 -7.81868 5.379826 -7.81868C5.606974 -7.81868 5.71457 -7.81868 5.71457 -8.045828C5.71457 -8.16538 5.595019 -8.16538 5.535243 -8.16538C5.32005 -8.16538 5.068991 -8.141469 4.841843 -8.141469H3.431133C3.21594 -8.141469 2.952927 -8.16538 2.737733 -8.16538C2.642092 -8.16538 2.510585 -8.16538 2.510585 -7.938232C2.510585 -7.81868 2.618182 -7.81868 2.797509 -7.81868C3.526775 -7.81868 3.526775 -7.723039 3.526775 -7.591532C3.526775 -7.567621 3.526775 -7.49589 3.478954 -7.316563L1.865006 -0.884682C1.75741 -0.466252 1.733499 -0.3467 0.896638 -0.3467C0.669489 -0.3467 0.549938 -0.3467 0.549938 -0.131507C0.549938 0 0.657534 0 0.729265 0C0.956413 0 1.195517 -0.02391 1.422665 -0.02391H2.82142C3.048568 -0.02391 3.299626 0 3.526775 0C3.622416 0 3.753923 0 3.753923 -0.227148C3.753923 -0.3467 3.646326 -0.3467 3.466999 -0.3467C2.737733 -0.3467 2.737733 -0.442341 2.737733 -0.561893C2.737733 -0.645579 2.809465 -0.944458 2.857285 -1.135741L3.323537 -2.988792L5.140722 -4.411457C5.487422 -3.646326 6.121046 -2.116065 6.611208 -0.944458C6.647073 -0.872727 6.670984 -0.800996 6.670984 -0.71731C6.670984 -0.358655 6.192777 -0.3467 6.085181 -0.3467S5.858032 -0.3467 5.858032 -0.119552C5.858032 0 5.989539 0 6.025405 0C6.443836 0 6.886177 -0.02391 7.304608 -0.02391H7.878456C8.057783 -0.02391 8.261021 0 8.440349 0C8.51208 0 8.643587 0 8.643587 -0.227148C8.643587 -0.3467 8.53599 -0.3467 8.416438 -0.3467C7.974097 -0.358655 7.81868 -0.454296 7.639352 -0.884682L5.977584 -4.829888Z'/>
<path id='eq5-g4-100' d='M6.01345 -7.998007C6.025405 -8.045828 6.049315 -8.117559 6.049315 -8.177335C6.049315 -8.296887 5.929763 -8.296887 5.905853 -8.296887C5.893898 -8.296887 5.308095 -8.249066 5.248319 -8.237111C5.045081 -8.225156 4.865753 -8.201245 4.65056 -8.18929C4.351681 -8.16538 4.267995 -8.153425 4.267995 -7.938232C4.267995 -7.81868 4.363636 -7.81868 4.531009 -7.81868C5.116812 -7.81868 5.128767 -7.711083 5.128767 -7.591532C5.128767 -7.519801 5.104857 -7.424159 5.092902 -7.388294L4.363636 -4.483188C4.23213 -4.794022 3.90934 -5.272229 3.287671 -5.272229C1.936737 -5.272229 0.478207 -3.526775 0.478207 -1.75741C0.478207 -0.573848 1.171606 0.119552 1.984558 0.119552C2.642092 0.119552 3.203985 -0.394521 3.53873 -0.789041C3.658281 -0.083686 4.220174 0.119552 4.578829 0.119552S5.224408 -0.095641 5.439601 -0.526027C5.630884 -0.932503 5.798257 -1.661768 5.798257 -1.709589C5.798257 -1.769365 5.750436 -1.817186 5.678705 -1.817186C5.571108 -1.817186 5.559153 -1.75741 5.511333 -1.578082C5.332005 -0.872727 5.104857 -0.119552 4.614695 -0.119552C4.267995 -0.119552 4.244085 -0.430386 4.244085 -0.669489C4.244085 -0.71731 4.244085 -0.968369 4.327771 -1.303113L6.01345 -7.998007ZM3.598506 -1.422665C3.53873 -1.219427 3.53873 -1.195517 3.371357 -0.968369C3.108344 -0.633624 2.582316 -0.119552 2.020423 -0.119552C1.530262 -0.119552 1.255293 -0.561893 1.255293 -1.267248C1.255293 -1.924782 1.625903 -3.263761 1.853051 -3.765878C2.259527 -4.60274 2.82142 -5.033126 3.287671 -5.033126C4.076712 -5.033126 4.23213 -4.052802 4.23213 -3.957161C4.23213 -3.945205 4.196264 -3.789788 4.184309 -3.765878L3.598506 -1.422665Z'/>
<path id='eq5-g4-113' d='M5.272229 -5.152677C5.272229 -5.212453 5.224408 -5.260274 5.164633 -5.260274C5.068991 -5.260274 4.60274 -4.829888 4.375592 -4.411457C4.160399 -4.94944 3.789788 -5.272229 3.275716 -5.272229C1.924782 -5.272229 0.466252 -3.526775 0.466252 -1.75741C0.466252 -0.573848 1.159651 0.119552 1.972603 0.119552C2.606227 0.119552 3.132254 -0.358655 3.383313 -0.633624L3.395268 -0.621669L2.940971 1.171606L2.833375 1.601993C2.725778 1.960648 2.546451 1.960648 1.984558 1.972603C1.853051 1.972603 1.733499 1.972603 1.733499 2.199751C1.733499 2.283437 1.80523 2.319303 1.888917 2.319303C2.056289 2.319303 2.271482 2.295392 2.438854 2.295392H3.658281C3.837609 2.295392 4.040847 2.319303 4.220174 2.319303C4.291905 2.319303 4.435367 2.319303 4.435367 2.092154C4.435367 1.972603 4.339726 1.972603 4.160399 1.972603C3.598506 1.972603 3.56264 1.888917 3.56264 1.793275C3.56264 1.733499 3.574595 1.721544 3.610461 1.566127L5.272229 -5.152677ZM3.58655 -1.422665C3.526775 -1.219427 3.526775 -1.195517 3.359402 -0.968369C3.096389 -0.633624 2.570361 -0.119552 2.008468 -0.119552C1.518306 -0.119552 1.243337 -0.561893 1.243337 -1.267248C1.243337 -1.924782 1.613948 -3.263761 1.841096 -3.765878C2.247572 -4.60274 2.809465 -5.033126 3.275716 -5.033126C4.064757 -5.033126 4.220174 -4.052802 4.220174 -3.957161C4.220174 -3.945205 4.184309 -3.789788 4.172354 -3.765878L3.58655 -1.422665Z'/>
<path id='eq5-g4-116' d='M2.402989 -4.805978H3.502864C3.730012 -4.805978 3.849564 -4.805978 3.849564 -5.021171C3.849564 -5.152677 3.777833 -5.152677 3.53873 -5.152677H2.486675L2.929016 -6.898132C2.976837 -7.065504 2.976837 -7.089415 2.976837 -7.173101C2.976837 -7.364384 2.82142 -7.47198 2.666002 -7.47198C2.570361 -7.47198 2.295392 -7.436115 2.199751 -7.053549L1.733499 -5.152677H0.609714C0.37061 -5.152677 0.263014 -5.152677 0.263014 -4.925529C0.263014 -4.805978 0.3467 -4.805978 0.573848 -4.805978H1.637858L0.848817 -1.649813C0.753176 -1.231382 0.71731 -1.111831 0.71731 -0.956413C0.71731 -0.394521 1.111831 0.119552 1.78132 0.119552C2.988792 0.119552 3.634371 -1.625903 3.634371 -1.709589C3.634371 -1.78132 3.58655 -1.817186 3.514819 -1.817186C3.490909 -1.817186 3.443088 -1.817186 3.419178 -1.769365C3.407223 -1.75741 3.395268 -1.745455 3.311582 -1.554172C3.060523 -0.956413 2.510585 -0.119552 1.817186 -0.119552C1.458531 -0.119552 1.43462 -0.418431 1.43462 -0.681445C1.43462 -0.6934 1.43462 -0.920548 1.470486 -1.06401L2.402989 -4.805978Z'/>
<path id='eq5-g1-3' d='M3.291656 -1.052055C3.363387 -1.004234 3.387298 -1.004234 3.427148 -1.004234C3.55467 -1.004234 3.666252 -1.107846 3.666252 -1.251308C3.666252 -1.40274 3.58655 -1.43462 3.466999 -1.490411C2.933001 -1.737484 2.741719 -1.825156 2.351183 -1.984558L3.283686 -2.406974C3.347447 -2.430884 3.498879 -2.502615 3.56264 -2.526526C3.642341 -2.574346 3.666252 -2.654047 3.666252 -2.725778C3.666252 -2.82142 3.618431 -2.972852 3.379328 -2.972852L2.231631 -2.191781L2.343213 -3.371357C2.359153 -3.506849 2.343213 -3.706102 2.11208 -3.706102C1.968618 -3.706102 1.857036 -3.58655 1.880946 -3.474969V-3.379328L1.992528 -2.191781L0.932503 -2.925031C0.860772 -2.972852 0.836862 -2.972852 0.797011 -2.972852C0.669489 -2.972852 0.557908 -2.86924 0.557908 -2.725778C0.557908 -2.574346 0.637609 -2.542466 0.757161 -2.486675C1.291158 -2.239601 1.482441 -2.15193 1.872976 -1.992528L0.940473 -1.570112C0.876712 -1.546202 0.72528 -1.474471 0.661519 -1.45056C0.581818 -1.40274 0.557908 -1.323039 0.557908 -1.251308C0.557908 -1.107846 0.669489 -1.004234 0.797011 -1.004234C0.860772 -1.004234 0.876712 -1.004234 1.075965 -1.147696L1.992528 -1.785305L1.872976 -0.502117C1.872976 -0.342715 2.008468 -0.270984 2.11208 -0.270984S2.351183 -0.342715 2.351183 -0.502117C2.351183 -0.581818 2.319303 -0.836862 2.311333 -0.932503C2.279452 -1.203487 2.255542 -1.506351 2.231631 -1.785305L3.291656 -1.052055Z'/>
<path id='eq5-g2-0' d='M7.878456 -2.749689C8.081694 -2.749689 8.296887 -2.749689 8.296887 -2.988792S8.081694 -3.227895 7.878456 -3.227895H1.41071C1.207472 -3.227895 0.992279 -3.227895 0.992279 -2.988792S1.207472 -2.749689 1.41071 -2.749689H7.878456Z'/>
</defs>
<g id='eq5-page1'>
<use x='69.659051' y='-10.793007' xlink:href='#eq5-g4-28'/>
<use x='79.398822' y='-10.793007' xlink:href='#eq5-g6-61'/>
<use x='91.824303' y='-10.793007' xlink:href='#eq5-g4-75'/>
<use x='101.783416' y='-8.999744' xlink:href='#eq5-g3-112'/>
<use x='106.544326' y='-10.793007' xlink:href='#eq5-g6-40'/>
<use x='111.096651' y='-10.793007' xlink:href='#eq5-g4-113'/>
<use x='116.715808' y='-15.729193' xlink:href='#eq5-g1-3'/>
<use x='124.104786' y='-10.793007' xlink:href='#eq5-g2-0'/>
<use x='136.059947' y='-10.793007' xlink:href='#eq5-g4-113'/>
<use x='141.679103' y='-10.793007' xlink:href='#eq5-g6-41'/>
<use x='148.888092' y='-10.793007' xlink:href='#eq5-g6-43'/>
<use x='160.649407' y='-10.793007' xlink:href='#eq5-g4-75'/>
<use x='170.608521' y='-8.999744' xlink:href='#eq5-g3-105'/>
<use x='175.98229' y='-27.065451' xlink:href='#eq5-g0-90'/>
<use x='187.937488' y='-23.91061' xlink:href='#eq5-g3-116'/>
<use x='182.62407' y='0' xlink:href='#eq5-g5-48'/>
<use x='191.493641' y='-10.793007' xlink:href='#eq5-g6-40'/>
<use x='196.045967' y='-10.793007' xlink:href='#eq5-g4-113'/>
<use x='201.665123' y='-15.729193' xlink:href='#eq5-g1-3'/>
<use x='209.054102' y='-10.793007' xlink:href='#eq5-g2-0'/>
<use x='221.009262' y='-10.793007' xlink:href='#eq5-g4-113'/>
<use x='226.628419' y='-10.793007' xlink:href='#eq5-g6-41'/>
<use x='231.180744' y='-10.793007' xlink:href='#eq5-g4-100'/>
<use x='237.263437' y='-10.793007' xlink:href='#eq5-g4-116'/>
<use x='244.147261' y='-10.793007' xlink:href='#eq5-g6-43'/>
<use x='255.908576' y='-10.793007' xlink:href='#eq5-g4-75'/>
<use x='265.867689' y='-8.999744' xlink:href='#eq5-g3-100'/>
<use x='271.918652' y='-18.880766' xlink:href='#eq5-g4-100'/>
<use x='278.001345' y='-18.880766' xlink:href='#eq5-g6-40'/>
<use x='282.553671' y='-18.880766' xlink:href='#eq5-g4-113'/>
<use x='288.172827' y='-23.219202' xlink:href='#eq5-g1-3'/>
<use x='295.561805' y='-18.880766' xlink:href='#eq5-g2-0'/>
<use x='307.516966' y='-18.880766' xlink:href='#eq5-g4-113'/>
<use x='313.136122' y='-18.880766' xlink:href='#eq5-g6-41'/>
<rect x='271.918652' y='-14.020893' height='0.478187' width='45.769785'/>
<use x='289.648628' y='-2.592345' xlink:href='#eq5-g4-100'/>
<use x='295.731321' y='-2.592345' xlink:href='#eq5-g4-116'/>
</g>
</svg></div>
<p>The user here will need to tune the para­met­ers <svg class="m-math" style="width: 5.507em; height: 1.199em; vertical-align: -0.350em;" viewBox="0 -8.169366 52.865156 11.512374">
<title>
K_p,K_i,K_d
</title>
<defs>
<path id='eq6-g0-100' d='M4.28792 -5.292154C4.29589 -5.308095 4.319801 -5.411706 4.319801 -5.419676C4.319801 -5.459527 4.28792 -5.531258 4.192279 -5.531258C4.160399 -5.531258 3.913325 -5.507347 3.730012 -5.491407L3.283686 -5.459527C3.108344 -5.443587 3.028643 -5.435616 3.028643 -5.292154C3.028643 -5.180573 3.140224 -5.180573 3.235866 -5.180573C3.618431 -5.180573 3.618431 -5.132752 3.618431 -5.061021C3.618431 -5.0132 3.55467 -4.750187 3.514819 -4.590785L3.124284 -3.036613C3.052553 -3.172105 2.82142 -3.514819 2.335243 -3.514819C1.3868 -3.514819 0.342715 -2.406974 0.342715 -1.227397C0.342715 -0.398506 0.876712 0.079701 1.490411 0.079701C2.000498 0.079701 2.438854 -0.326775 2.582316 -0.486177C2.725778 0.063761 3.267746 0.079701 3.363387 0.079701C3.730012 0.079701 3.913325 -0.223163 3.977086 -0.358655C4.136488 -0.645579 4.24807 -1.107846 4.24807 -1.139726C4.24807 -1.187547 4.216189 -1.243337 4.120548 -1.243337S4.008966 -1.195517 3.961146 -0.996264C3.849564 -0.557908 3.698132 -0.143462 3.387298 -0.143462C3.203985 -0.143462 3.132254 -0.294894 3.132254 -0.518057C3.132254 -0.669489 3.156164 -0.757161 3.180075 -0.860772L4.28792 -5.292154ZM2.582316 -0.860772C2.183811 -0.310834 1.769365 -0.143462 1.514321 -0.143462C1.147696 -0.143462 0.964384 -0.478207 0.964384 -0.892653C0.964384 -1.267248 1.179577 -2.12005 1.354919 -2.470735C1.586052 -2.956912 1.976588 -3.291656 2.343213 -3.291656C2.86127 -3.291656 3.012702 -2.709838 3.012702 -2.614197C3.012702 -2.582316 2.81345 -1.801245 2.765629 -1.594022C2.662017 -1.219427 2.662017 -1.203487 2.582316 -0.860772Z'/>
<path id='eq6-g0-105' d='M2.375093 -4.97335C2.375093 -5.148692 2.247572 -5.276214 2.064259 -5.276214C1.857036 -5.276214 1.625903 -5.084932 1.625903 -4.845828C1.625903 -4.670486 1.753425 -4.542964 1.936737 -4.542964C2.14396 -4.542964 2.375093 -4.734247 2.375093 -4.97335ZM1.211457 -2.048319L0.781071 -0.948443C0.74122 -0.828892 0.70137 -0.73325 0.70137 -0.597758C0.70137 -0.207223 1.004234 0.079701 1.42665 0.079701C2.199751 0.079701 2.526526 -1.036115 2.526526 -1.139726C2.526526 -1.219427 2.462765 -1.243337 2.406974 -1.243337C2.311333 -1.243337 2.295392 -1.187547 2.271482 -1.107846C2.088169 -0.470237 1.761395 -0.143462 1.44259 -0.143462C1.346949 -0.143462 1.251308 -0.183313 1.251308 -0.398506C1.251308 -0.589788 1.307098 -0.73325 1.41071 -0.980324C1.490411 -1.195517 1.570112 -1.41071 1.657783 -1.625903L1.904857 -2.271482C1.976588 -2.454795 2.072229 -2.701868 2.072229 -2.83736C2.072229 -3.235866 1.753425 -3.514819 1.346949 -3.514819C0.573848 -3.514819 0.239103 -2.399004 0.239103 -2.295392C0.239103 -2.223661 0.294894 -2.191781 0.358655 -2.191781C0.462267 -2.191781 0.470237 -2.239601 0.494147 -2.319303C0.71731 -3.076463 1.083935 -3.291656 1.323039 -3.291656C1.43462 -3.291656 1.514321 -3.251806 1.514321 -3.028643C1.514321 -2.948941 1.506351 -2.83736 1.42665 -2.598257L1.211457 -2.048319Z'/>
<path id='eq6-g0-112' d='M0.414446 0.964384C0.350685 1.219427 0.334745 1.283188 0.01594 1.283188C-0.095641 1.283188 -0.191283 1.283188 -0.191283 1.43462C-0.191283 1.506351 -0.119552 1.546202 -0.079701 1.546202C0 1.546202 0.03188 1.522291 0.621669 1.522291C1.195517 1.522291 1.362889 1.546202 1.41868 1.546202C1.45056 1.546202 1.570112 1.546202 1.570112 1.39477C1.570112 1.283188 1.458531 1.283188 1.362889 1.283188C0.980324 1.283188 0.980324 1.235367 0.980324 1.163636C0.980324 1.107846 1.123786 0.541968 1.362889 -0.390535C1.466501 -0.207223 1.713574 0.079701 2.14396 0.079701C3.124284 0.079701 4.144458 -1.052055 4.144458 -2.207721C4.144458 -2.996762 3.634371 -3.514819 2.996762 -3.514819C2.518555 -3.514819 2.13599 -3.188045 1.904857 -2.948941C1.737484 -3.514819 1.203487 -3.514819 1.123786 -3.514819C0.836862 -3.514819 0.637609 -3.331507 0.510087 -3.084433C0.326775 -2.725778 0.239103 -2.319303 0.239103 -2.295392C0.239103 -2.223661 0.294894 -2.191781 0.358655 -2.191781C0.462267 -2.191781 0.470237 -2.223661 0.526027 -2.430884C0.629639 -2.83736 0.773101 -3.291656 1.099875 -3.291656C1.299128 -3.291656 1.354919 -3.108344 1.354919 -2.917061C1.354919 -2.83736 1.323039 -2.646077 1.307098 -2.582316L0.414446 0.964384ZM1.880946 -2.454795C1.920797 -2.590286 1.920797 -2.606227 2.040349 -2.749689C2.343213 -3.108344 2.685928 -3.291656 2.972852 -3.291656C3.371357 -3.291656 3.52279 -2.901121 3.52279 -2.542466C3.52279 -2.247572 3.347447 -1.39477 3.108344 -0.924533C2.901121 -0.494147 2.518555 -0.143462 2.14396 -0.143462C1.601993 -0.143462 1.474471 -0.765131 1.474471 -0.820922C1.474471 -0.836862 1.490411 -0.924533 1.498381 -0.948443L1.880946 -2.454795Z'/>
<path id='eq6-g1-59' d='M2.331258 0.047821C2.331258 -0.645579 2.10411 -1.159651 1.613948 -1.159651C1.231382 -1.159651 1.0401 -0.848817 1.0401 -0.585803S1.219427 0 1.625903 0C1.78132 0 1.912827 -0.047821 2.020423 -0.155417C2.044334 -0.179328 2.056289 -0.179328 2.068244 -0.179328C2.092154 -0.179328 2.092154 -0.011955 2.092154 0.047821C2.092154 0.442341 2.020423 1.219427 1.327024 1.996513C1.195517 2.139975 1.195517 2.163885 1.195517 2.187796C1.195517 2.247572 1.255293 2.307347 1.315068 2.307347C1.41071 2.307347 2.331258 1.422665 2.331258 0.047821Z'/>
<path id='eq6-g1-75' d='M5.977584 -4.829888C5.965629 -4.865753 5.917808 -4.961395 5.917808 -4.99726C5.917808 -5.009215 5.929763 -5.021171 6.133001 -5.176588L7.292653 -6.085181C8.894645 -7.328518 9.420672 -7.746949 10.245579 -7.81868C10.329265 -7.830635 10.448817 -7.830635 10.448817 -8.033873C10.448817 -8.105604 10.412951 -8.16538 10.31731 -8.16538C10.185803 -8.16538 10.042341 -8.141469 9.910834 -8.141469H9.456538C9.085928 -8.141469 8.691407 -8.16538 8.332752 -8.16538C8.249066 -8.16538 8.105604 -8.16538 8.105604 -7.950187C8.105604 -7.830635 8.18929 -7.81868 8.261021 -7.81868C8.392528 -7.806725 8.547945 -7.758904 8.547945 -7.591532C8.547945 -7.352428 8.18929 -7.065504 8.093649 -6.993773L3.407223 -3.347447L4.399502 -7.292653C4.507098 -7.699128 4.531009 -7.81868 5.379826 -7.81868C5.606974 -7.81868 5.71457 -7.81868 5.71457 -8.045828C5.71457 -8.16538 5.595019 -8.16538 5.535243 -8.16538C5.32005 -8.16538 5.068991 -8.141469 4.841843 -8.141469H3.431133C3.21594 -8.141469 2.952927 -8.16538 2.737733 -8.16538C2.642092 -8.16538 2.510585 -8.16538 2.510585 -7.938232C2.510585 -7.81868 2.618182 -7.81868 2.797509 -7.81868C3.526775 -7.81868 3.526775 -7.723039 3.526775 -7.591532C3.526775 -7.567621 3.526775 -7.49589 3.478954 -7.316563L1.865006 -0.884682C1.75741 -0.466252 1.733499 -0.3467 0.896638 -0.3467C0.669489 -0.3467 0.549938 -0.3467 0.549938 -0.131507C0.549938 0 0.657534 0 0.729265 0C0.956413 0 1.195517 -0.02391 1.422665 -0.02391H2.82142C3.048568 -0.02391 3.299626 0 3.526775 0C3.622416 0 3.753923 0 3.753923 -0.227148C3.753923 -0.3467 3.646326 -0.3467 3.466999 -0.3467C2.737733 -0.3467 2.737733 -0.442341 2.737733 -0.561893C2.737733 -0.645579 2.809465 -0.944458 2.857285 -1.135741L3.323537 -2.988792L5.140722 -4.411457C5.487422 -3.646326 6.121046 -2.116065 6.611208 -0.944458C6.647073 -0.872727 6.670984 -0.800996 6.670984 -0.71731C6.670984 -0.358655 6.192777 -0.3467 6.085181 -0.3467S5.858032 -0.3467 5.858032 -0.119552C5.858032 0 5.989539 0 6.025405 0C6.443836 0 6.886177 -0.02391 7.304608 -0.02391H7.878456C8.057783 -0.02391 8.261021 0 8.440349 0C8.51208 0 8.643587 0 8.643587 -0.227148C8.643587 -0.3467 8.53599 -0.3467 8.416438 -0.3467C7.974097 -0.358655 7.81868 -0.454296 7.639352 -0.884682L5.977584 -4.829888Z'/>
</defs>
<g id='eq6-page1'>
<use x='0' y='0' xlink:href='#eq6-g1-75'/>
<use x='9.959113' y='1.793263' xlink:href='#eq6-g0-112'/>
<use x='14.720023' y='0' xlink:href='#eq6-g1-59'/>
<use x='19.964182' y='0' xlink:href='#eq6-g1-75'/>
<use x='29.923295' y='1.793263' xlink:href='#eq6-g0-105'/>
<use x='33.304566' y='0' xlink:href='#eq6-g1-59'/>
<use x='38.548725' y='0' xlink:href='#eq6-g1-75'/>
<use x='48.507839' y='1.793263' xlink:href='#eq6-g0-100'/>
</g>
</svg>. In
ro­bot­ics, a vari­ation of this con­trol is widely used (usu­ally named
PD-con­trol­ler):</p>
<div class="m-math">
<svg style="width: 15.155em; height: 1.282em;" viewBox="121.525481 -12.309385 145.492022 12.309385">
<title>
\tau = K_p (q^*-q) + K_d (\dot{q}^*-\dot{q})
</title>
<defs>
<path id='eq7-g1-0' d='M7.878456 -2.749689C8.081694 -2.749689 8.296887 -2.749689 8.296887 -2.988792S8.081694 -3.227895 7.878456 -3.227895H1.41071C1.207472 -3.227895 0.992279 -3.227895 0.992279 -2.988792S1.207472 -2.749689 1.41071 -2.749689H7.878456Z'/>
<path id='eq7-g0-3' d='M3.291656 -1.052055C3.363387 -1.004234 3.387298 -1.004234 3.427148 -1.004234C3.55467 -1.004234 3.666252 -1.107846 3.666252 -1.251308C3.666252 -1.40274 3.58655 -1.43462 3.466999 -1.490411C2.933001 -1.737484 2.741719 -1.825156 2.351183 -1.984558L3.283686 -2.406974C3.347447 -2.430884 3.498879 -2.502615 3.56264 -2.526526C3.642341 -2.574346 3.666252 -2.654047 3.666252 -2.725778C3.666252 -2.82142 3.618431 -2.972852 3.379328 -2.972852L2.231631 -2.191781L2.343213 -3.371357C2.359153 -3.506849 2.343213 -3.706102 2.11208 -3.706102C1.968618 -3.706102 1.857036 -3.58655 1.880946 -3.474969V-3.379328L1.992528 -2.191781L0.932503 -2.925031C0.860772 -2.972852 0.836862 -2.972852 0.797011 -2.972852C0.669489 -2.972852 0.557908 -2.86924 0.557908 -2.725778C0.557908 -2.574346 0.637609 -2.542466 0.757161 -2.486675C1.291158 -2.239601 1.482441 -2.15193 1.872976 -1.992528L0.940473 -1.570112C0.876712 -1.546202 0.72528 -1.474471 0.661519 -1.45056C0.581818 -1.40274 0.557908 -1.323039 0.557908 -1.251308C0.557908 -1.107846 0.669489 -1.004234 0.797011 -1.004234C0.860772 -1.004234 0.876712 -1.004234 1.075965 -1.147696L1.992528 -1.785305L1.872976 -0.502117C1.872976 -0.342715 2.008468 -0.270984 2.11208 -0.270984S2.351183 -0.342715 2.351183 -0.502117C2.351183 -0.581818 2.319303 -0.836862 2.311333 -0.932503C2.279452 -1.203487 2.255542 -1.506351 2.231631 -1.785305L3.291656 -1.052055Z'/>
<path id='eq7-g2-100' d='M4.28792 -5.292154C4.29589 -5.308095 4.319801 -5.411706 4.319801 -5.419676C4.319801 -5.459527 4.28792 -5.531258 4.192279 -5.531258C4.160399 -5.531258 3.913325 -5.507347 3.730012 -5.491407L3.283686 -5.459527C3.108344 -5.443587 3.028643 -5.435616 3.028643 -5.292154C3.028643 -5.180573 3.140224 -5.180573 3.235866 -5.180573C3.618431 -5.180573 3.618431 -5.132752 3.618431 -5.061021C3.618431 -5.0132 3.55467 -4.750187 3.514819 -4.590785L3.124284 -3.036613C3.052553 -3.172105 2.82142 -3.514819 2.335243 -3.514819C1.3868 -3.514819 0.342715 -2.406974 0.342715 -1.227397C0.342715 -0.398506 0.876712 0.079701 1.490411 0.079701C2.000498 0.079701 2.438854 -0.326775 2.582316 -0.486177C2.725778 0.063761 3.267746 0.079701 3.363387 0.079701C3.730012 0.079701 3.913325 -0.223163 3.977086 -0.358655C4.136488 -0.645579 4.24807 -1.107846 4.24807 -1.139726C4.24807 -1.187547 4.216189 -1.243337 4.120548 -1.243337S4.008966 -1.195517 3.961146 -0.996264C3.849564 -0.557908 3.698132 -0.143462 3.387298 -0.143462C3.203985 -0.143462 3.132254 -0.294894 3.132254 -0.518057C3.132254 -0.669489 3.156164 -0.757161 3.180075 -0.860772L4.28792 -5.292154ZM2.582316 -0.860772C2.183811 -0.310834 1.769365 -0.143462 1.514321 -0.143462C1.147696 -0.143462 0.964384 -0.478207 0.964384 -0.892653C0.964384 -1.267248 1.179577 -2.12005 1.354919 -2.470735C1.586052 -2.956912 1.976588 -3.291656 2.343213 -3.291656C2.86127 -3.291656 3.012702 -2.709838 3.012702 -2.614197C3.012702 -2.582316 2.81345 -1.801245 2.765629 -1.594022C2.662017 -1.219427 2.662017 -1.203487 2.582316 -0.860772Z'/>
<path id='eq7-g2-112' d='M0.414446 0.964384C0.350685 1.219427 0.334745 1.283188 0.01594 1.283188C-0.095641 1.283188 -0.191283 1.283188 -0.191283 1.43462C-0.191283 1.506351 -0.119552 1.546202 -0.079701 1.546202C0 1.546202 0.03188 1.522291 0.621669 1.522291C1.195517 1.522291 1.362889 1.546202 1.41868 1.546202C1.45056 1.546202 1.570112 1.546202 1.570112 1.39477C1.570112 1.283188 1.458531 1.283188 1.362889 1.283188C0.980324 1.283188 0.980324 1.235367 0.980324 1.163636C0.980324 1.107846 1.123786 0.541968 1.362889 -0.390535C1.466501 -0.207223 1.713574 0.079701 2.14396 0.079701C3.124284 0.079701 4.144458 -1.052055 4.144458 -2.207721C4.144458 -2.996762 3.634371 -3.514819 2.996762 -3.514819C2.518555 -3.514819 2.13599 -3.188045 1.904857 -2.948941C1.737484 -3.514819 1.203487 -3.514819 1.123786 -3.514819C0.836862 -3.514819 0.637609 -3.331507 0.510087 -3.084433C0.326775 -2.725778 0.239103 -2.319303 0.239103 -2.295392C0.239103 -2.223661 0.294894 -2.191781 0.358655 -2.191781C0.462267 -2.191781 0.470237 -2.223661 0.526027 -2.430884C0.629639 -2.83736 0.773101 -3.291656 1.099875 -3.291656C1.299128 -3.291656 1.354919 -3.108344 1.354919 -2.917061C1.354919 -2.83736 1.323039 -2.646077 1.307098 -2.582316L0.414446 0.964384ZM1.880946 -2.454795C1.920797 -2.590286 1.920797 -2.606227 2.040349 -2.749689C2.343213 -3.108344 2.685928 -3.291656 2.972852 -3.291656C3.371357 -3.291656 3.52279 -2.901121 3.52279 -2.542466C3.52279 -2.247572 3.347447 -1.39477 3.108344 -0.924533C2.901121 -0.494147 2.518555 -0.143462 2.14396 -0.143462C1.601993 -0.143462 1.474471 -0.765131 1.474471 -0.820922C1.474471 -0.836862 1.490411 -0.924533 1.498381 -0.948443L1.880946 -2.454795Z'/>
<path id='eq7-g3-28' d='M3.431133 -4.507098H5.415691C5.571108 -4.507098 5.965629 -4.507098 5.965629 -4.889664C5.965629 -5.152677 5.738481 -5.152677 5.523288 -5.152677H2.235616C1.960648 -5.152677 1.554172 -5.152677 1.004234 -4.566874C0.6934 -4.220174 0.310834 -3.58655 0.310834 -3.514819S0.37061 -3.419178 0.442341 -3.419178C0.526027 -3.419178 0.537983 -3.455044 0.597758 -3.526775C1.219427 -4.507098 1.841096 -4.507098 2.139975 -4.507098H3.132254L1.888917 -0.406476C1.829141 -0.227148 1.829141 -0.203238 1.829141 -0.167372C1.829141 -0.035866 1.912827 0.131507 2.15193 0.131507C2.52254 0.131507 2.582316 -0.191283 2.618182 -0.37061L3.431133 -4.507098Z'/>
<path id='eq7-g3-75' d='M5.977584 -4.829888C5.965629 -4.865753 5.917808 -4.961395 5.917808 -4.99726C5.917808 -5.009215 5.929763 -5.021171 6.133001 -5.176588L7.292653 -6.085181C8.894645 -7.328518 9.420672 -7.746949 10.245579 -7.81868C10.329265 -7.830635 10.448817 -7.830635 10.448817 -8.033873C10.448817 -8.105604 10.412951 -8.16538 10.31731 -8.16538C10.185803 -8.16538 10.042341 -8.141469 9.910834 -8.141469H9.456538C9.085928 -8.141469 8.691407 -8.16538 8.332752 -8.16538C8.249066 -8.16538 8.105604 -8.16538 8.105604 -7.950187C8.105604 -7.830635 8.18929 -7.81868 8.261021 -7.81868C8.392528 -7.806725 8.547945 -7.758904 8.547945 -7.591532C8.547945 -7.352428 8.18929 -7.065504 8.093649 -6.993773L3.407223 -3.347447L4.399502 -7.292653C4.507098 -7.699128 4.531009 -7.81868 5.379826 -7.81868C5.606974 -7.81868 5.71457 -7.81868 5.71457 -8.045828C5.71457 -8.16538 5.595019 -8.16538 5.535243 -8.16538C5.32005 -8.16538 5.068991 -8.141469 4.841843 -8.141469H3.431133C3.21594 -8.141469 2.952927 -8.16538 2.737733 -8.16538C2.642092 -8.16538 2.510585 -8.16538 2.510585 -7.938232C2.510585 -7.81868 2.618182 -7.81868 2.797509 -7.81868C3.526775 -7.81868 3.526775 -7.723039 3.526775 -7.591532C3.526775 -7.567621 3.526775 -7.49589 3.478954 -7.316563L1.865006 -0.884682C1.75741 -0.466252 1.733499 -0.3467 0.896638 -0.3467C0.669489 -0.3467 0.549938 -0.3467 0.549938 -0.131507C0.549938 0 0.657534 0 0.729265 0C0.956413 0 1.195517 -0.02391 1.422665 -0.02391H2.82142C3.048568 -0.02391 3.299626 0 3.526775 0C3.622416 0 3.753923 0 3.753923 -0.227148C3.753923 -0.3467 3.646326 -0.3467 3.466999 -0.3467C2.737733 -0.3467 2.737733 -0.442341 2.737733 -0.561893C2.737733 -0.645579 2.809465 -0.944458 2.857285 -1.135741L3.323537 -2.988792L5.140722 -4.411457C5.487422 -3.646326 6.121046 -2.116065 6.611208 -0.944458C6.647073 -0.872727 6.670984 -0.800996 6.670984 -0.71731C6.670984 -0.358655 6.192777 -0.3467 6.085181 -0.3467S5.858032 -0.3467 5.858032 -0.119552C5.858032 0 5.989539 0 6.025405 0C6.443836 0 6.886177 -0.02391 7.304608 -0.02391H7.878456C8.057783 -0.02391 8.261021 0 8.440349 0C8.51208 0 8.643587 0 8.643587 -0.227148C8.643587 -0.3467 8.53599 -0.3467 8.416438 -0.3467C7.974097 -0.358655 7.81868 -0.454296 7.639352 -0.884682L5.977584 -4.829888Z'/>
<path id='eq7-g3-113' d='M5.272229 -5.152677C5.272229 -5.212453 5.224408 -5.260274 5.164633 -5.260274C5.068991 -5.260274 4.60274 -4.829888 4.375592 -4.411457C4.160399 -4.94944 3.789788 -5.272229 3.275716 -5.272229C1.924782 -5.272229 0.466252 -3.526775 0.466252 -1.75741C0.466252 -0.573848 1.159651 0.119552 1.972603 0.119552C2.606227 0.119552 3.132254 -0.358655 3.383313 -0.633624L3.395268 -0.621669L2.940971 1.171606L2.833375 1.601993C2.725778 1.960648 2.546451 1.960648 1.984558 1.972603C1.853051 1.972603 1.733499 1.972603 1.733499 2.199751C1.733499 2.283437 1.80523 2.319303 1.888917 2.319303C2.056289 2.319303 2.271482 2.295392 2.438854 2.295392H3.658281C3.837609 2.295392 4.040847 2.319303 4.220174 2.319303C4.291905 2.319303 4.435367 2.319303 4.435367 2.092154C4.435367 1.972603 4.339726 1.972603 4.160399 1.972603C3.598506 1.972603 3.56264 1.888917 3.56264 1.793275C3.56264 1.733499 3.574595 1.721544 3.610461 1.566127L5.272229 -5.152677ZM3.58655 -1.422665C3.526775 -1.219427 3.526775 -1.195517 3.359402 -0.968369C3.096389 -0.633624 2.570361 -0.119552 2.008468 -0.119552C1.518306 -0.119552 1.243337 -0.561893 1.243337 -1.267248C1.243337 -1.924782 1.613948 -3.263761 1.841096 -3.765878C2.247572 -4.60274 2.809465 -5.033126 3.275716 -5.033126C4.064757 -5.033126 4.220174 -4.052802 4.220174 -3.957161C4.220174 -3.945205 4.184309 -3.789788 4.172354 -3.765878L3.58655 -1.422665Z'/>
<path id='eq7-g4-40' d='M3.88543 2.905106C3.88543 2.86924 3.88543 2.84533 3.682192 2.642092C2.486675 1.43462 1.817186 -0.537983 1.817186 -2.976837C1.817186 -5.296139 2.379078 -7.292653 3.765878 -8.703362C3.88543 -8.810959 3.88543 -8.834869 3.88543 -8.870735C3.88543 -8.942466 3.825654 -8.966376 3.777833 -8.966376C3.622416 -8.966376 2.642092 -8.105604 2.056289 -6.933998C1.446575 -5.726526 1.171606 -4.447323 1.171606 -2.976837C1.171606 -1.912827 1.338979 -0.490162 1.960648 0.789041C2.666002 2.223661 3.646326 3.000747 3.777833 3.000747C3.825654 3.000747 3.88543 2.976837 3.88543 2.905106Z'/>
<path id='eq7-g4-41' d='M3.371357 -2.976837C3.371357 -3.88543 3.251806 -5.36787 2.582316 -6.75467C1.876961 -8.18929 0.896638 -8.966376 0.765131 -8.966376C0.71731 -8.966376 0.657534 -8.942466 0.657534 -8.870735C0.657534 -8.834869 0.657534 -8.810959 0.860772 -8.607721C2.056289 -7.400249 2.725778 -5.427646 2.725778 -2.988792C2.725778 -0.669489 2.163885 1.327024 0.777086 2.737733C0.657534 2.84533 0.657534 2.86924 0.657534 2.905106C0.657534 2.976837 0.71731 3.000747 0.765131 3.000747C0.920548 3.000747 1.900872 2.139975 2.486675 0.968369C3.096389 -0.251059 3.371357 -1.542217 3.371357 -2.976837Z'/>
<path id='eq7-g4-43' d='M4.770112 -2.761644H8.069738C8.237111 -2.761644 8.452304 -2.761644 8.452304 -2.976837C8.452304 -3.203985 8.249066 -3.203985 8.069738 -3.203985H4.770112V-6.503611C4.770112 -6.670984 4.770112 -6.886177 4.554919 -6.886177C4.327771 -6.886177 4.327771 -6.682939 4.327771 -6.503611V-3.203985H1.028144C0.860772 -3.203985 0.645579 -3.203985 0.645579 -2.988792C0.645579 -2.761644 0.848817 -2.761644 1.028144 -2.761644H4.327771V0.537983C4.327771 0.705355 4.327771 0.920548 4.542964 0.920548C4.770112 0.920548 4.770112 0.71731 4.770112 0.537983V-2.761644Z'/>
<path id='eq7-g4-61' d='M8.069738 -3.873474C8.237111 -3.873474 8.452304 -3.873474 8.452304 -4.088667C8.452304 -4.315816 8.249066 -4.315816 8.069738 -4.315816H1.028144C0.860772 -4.315816 0.645579 -4.315816 0.645579 -4.100623C0.645579 -3.873474 0.848817 -3.873474 1.028144 -3.873474H8.069738ZM8.069738 -1.649813C8.237111 -1.649813 8.452304 -1.649813 8.452304 -1.865006C8.452304 -2.092154 8.249066 -2.092154 8.069738 -2.092154H1.028144C0.860772 -2.092154 0.645579 -2.092154 0.645579 -1.876961C0.645579 -1.649813 0.848817 -1.649813 1.028144 -1.649813H8.069738Z'/>
<path id='eq7-g4-95' d='M2.199751 -7.364384C2.199751 -7.723039 1.912827 -7.950187 1.625903 -7.950187C1.291158 -7.950187 1.0401 -7.687173 1.0401 -7.364384C1.0401 -7.053549 1.303113 -6.790535 1.613948 -6.790535C1.972603 -6.790535 2.199751 -7.07746 2.199751 -7.364384Z'/>
</defs>
<g id='eq7-page1'>
<use x='121.525481' y='-3.343009' xlink:href='#eq7-g3-28'/>
<use x='131.265251' y='-3.343009' xlink:href='#eq7-g4-61'/>
<use x='143.690732' y='-3.343009' xlink:href='#eq7-g3-75'/>
<use x='153.649845' y='-1.549746' xlink:href='#eq7-g2-112'/>
<use x='158.410755' y='-3.343009' xlink:href='#eq7-g4-40'/>
<use x='162.96308' y='-3.343009' xlink:href='#eq7-g3-113'/>
<use x='168.582237' y='-8.279195' xlink:href='#eq7-g0-3'/>
<use x='175.971215' y='-3.343009' xlink:href='#eq7-g1-0'/>
<use x='187.926376' y='-3.343009' xlink:href='#eq7-g3-113'/>
<use x='193.545532' y='-3.343009' xlink:href='#eq7-g4-41'/>
<use x='200.754521' y='-3.343009' xlink:href='#eq7-g4-43'/>
<use x='212.515836' y='-3.343009' xlink:href='#eq7-g3-75'/>
<use x='222.47495' y='-1.549746' xlink:href='#eq7-g2-100'/>
<use x='227.330399' y='-3.343009' xlink:href='#eq7-g4-40'/>
<use x='234.041971' y='-3.343009' xlink:href='#eq7-g4-95'/>
<use x='231.882725' y='-3.343009' xlink:href='#eq7-g3-113'/>
<use x='237.501881' y='-8.279195' xlink:href='#eq7-g0-3'/>
<use x='244.89086' y='-3.343009' xlink:href='#eq7-g1-0'/>
<use x='259.005266' y='-3.343009' xlink:href='#eq7-g4-95'/>
<use x='256.84602' y='-3.343009' xlink:href='#eq7-g3-113'/>
<use x='262.465177' y='-3.343009' xlink:href='#eq7-g4-41'/>
</g>
</svg></div>
<p>Us­ing these simple con­trol­lers, one can do some very amaz­ing and in­ter­est­ing things.</p>
</section>
<section id="controlling-in-task-space">
<h3><a href="#controlling-in-task-space">Con­trolling in task space</a></h3>
<p>How­ever, most of the in­ter­est­ing things hap­pen in the Cartesian 6D space. This
is the space where the end-ef­fect­or of our ma­nip­u­lat­or lives in. Moreover, this
is the space where most of the real world tasks can be very in­tu­it­ively
de­scribed. For this reas­on, we call it the task-space.</p>
<section id="jacobian">
<h4><a href="#jacobian">Jac­obi­an</a></h4>
<p>Jac­obi­an matrices are very use­ful, and heav­ily used in ro­bot­ics en­gin­eer­ing and
re­search. Ba­sic­ally, a Jac­obi­an defines the dy­nam­ic re­la­tion­ship between two
dif­fer­ent rep­res­ent­a­tions of a sys­tem. Form­ally, the Jac­obi­an mat­rix is the
first-or­der par­tial de­riv­at­ives of a func­tion with re­spect to some vari­ables.
Let’s take as an ex­ample our ro­bot­ic ma­nip­u­lat­or. As we have already, we can
de­scribe its con­fig­ur­a­tion by the joint po­s­i­tions <svg class="m-math" style="width: 0.585em; height: 0.778em; vertical-align: -0.243em;" viewBox="0 -5.147373 5.61916 7.47198">
<title>
q
</title>
<defs>
<path id='eq8-g0-113' d='M5.272229 -5.152677C5.272229 -5.212453 5.224408 -5.260274 5.164633 -5.260274C5.068991 -5.260274 4.60274 -4.829888 4.375592 -4.411457C4.160399 -4.94944 3.789788 -5.272229 3.275716 -5.272229C1.924782 -5.272229 0.466252 -3.526775 0.466252 -1.75741C0.466252 -0.573848 1.159651 0.119552 1.972603 0.119552C2.606227 0.119552 3.132254 -0.358655 3.383313 -0.633624L3.395268 -0.621669L2.940971 1.171606L2.833375 1.601993C2.725778 1.960648 2.546451 1.960648 1.984558 1.972603C1.853051 1.972603 1.733499 1.972603 1.733499 2.199751C1.733499 2.283437 1.80523 2.319303 1.888917 2.319303C2.056289 2.319303 2.271482 2.295392 2.438854 2.295392H3.658281C3.837609 2.295392 4.040847 2.319303 4.220174 2.319303C4.291905 2.319303 4.435367 2.319303 4.435367 2.092154C4.435367 1.972603 4.339726 1.972603 4.160399 1.972603C3.598506 1.972603 3.56264 1.888917 3.56264 1.793275C3.56264 1.733499 3.574595 1.721544 3.610461 1.566127L5.272229 -5.152677ZM3.58655 -1.422665C3.526775 -1.219427 3.526775 -1.195517 3.359402 -0.968369C3.096389 -0.633624 2.570361 -0.119552 2.008468 -0.119552C1.518306 -0.119552 1.243337 -0.561893 1.243337 -1.267248C1.243337 -1.924782 1.613948 -3.263761 1.841096 -3.765878C2.247572 -4.60274 2.809465 -5.033126 3.275716 -5.033126C4.064757 -5.033126 4.220174 -4.052802 4.220174 -3.957161C4.220174 -3.945205 4.184309 -3.789788 4.172354 -3.765878L3.58655 -1.422665Z'/>
</defs>
<g id='eq8-page1'>
<use x='0' y='0' xlink:href='#eq8-g0-113'/>
</g>
</svg>. This con­fig­ur­a­tion
also gives a 6D po­s­i­tion (that is, trans­la­tion and ori­ent­a­tion) for the
end-ef­fect­or, let’s call it <svg class="m-math" style="width: 2.227em; height: 1.245em; vertical-align: -0.312em;" viewBox="0 -8.966376 21.375895 11.955168">
<title>
x(q)
</title>
<defs>
<path id='eq9-g1-40' d='M3.88543 2.905106C3.88543 2.86924 3.88543 2.84533 3.682192 2.642092C2.486675 1.43462 1.817186 -0.537983 1.817186 -2.976837C1.817186 -5.296139 2.379078 -7.292653 3.765878 -8.703362C3.88543 -8.810959 3.88543 -8.834869 3.88543 -8.870735C3.88543 -8.942466 3.825654 -8.966376 3.777833 -8.966376C3.622416 -8.966376 2.642092 -8.105604 2.056289 -6.933998C1.446575 -5.726526 1.171606 -4.447323 1.171606 -2.976837C1.171606 -1.912827 1.338979 -0.490162 1.960648 0.789041C2.666002 2.223661 3.646326 3.000747 3.777833 3.000747C3.825654 3.000747 3.88543 2.976837 3.88543 2.905106Z'/>
<path id='eq9-g1-41' d='M3.371357 -2.976837C3.371357 -3.88543 3.251806 -5.36787 2.582316 -6.75467C1.876961 -8.18929 0.896638 -8.966376 0.765131 -8.966376C0.71731 -8.966376 0.657534 -8.942466 0.657534 -8.870735C0.657534 -8.834869 0.657534 -8.810959 0.860772 -8.607721C2.056289 -7.400249 2.725778 -5.427646 2.725778 -2.988792C2.725778 -0.669489 2.163885 1.327024 0.777086 2.737733C0.657534 2.84533 0.657534 2.86924 0.657534 2.905106C0.657534 2.976837 0.71731 3.000747 0.765131 3.000747C0.920548 3.000747 1.900872 2.139975 2.486675 0.968369C3.096389 -0.251059 3.371357 -1.542217 3.371357 -2.976837Z'/>
<path id='eq9-g0-113' d='M5.272229 -5.152677C5.272229 -5.212453 5.224408 -5.260274 5.164633 -5.260274C5.068991 -5.260274 4.60274 -4.829888 4.375592 -4.411457C4.160399 -4.94944 3.789788 -5.272229 3.275716 -5.272229C1.924782 -5.272229 0.466252 -3.526775 0.466252 -1.75741C0.466252 -0.573848 1.159651 0.119552 1.972603 0.119552C2.606227 0.119552 3.132254 -0.358655 3.383313 -0.633624L3.395268 -0.621669L2.940971 1.171606L2.833375 1.601993C2.725778 1.960648 2.546451 1.960648 1.984558 1.972603C1.853051 1.972603 1.733499 1.972603 1.733499 2.199751C1.733499 2.283437 1.80523 2.319303 1.888917 2.319303C2.056289 2.319303 2.271482 2.295392 2.438854 2.295392H3.658281C3.837609 2.295392 4.040847 2.319303 4.220174 2.319303C4.291905 2.319303 4.435367 2.319303 4.435367 2.092154C4.435367 1.972603 4.339726 1.972603 4.160399 1.972603C3.598506 1.972603 3.56264 1.888917 3.56264 1.793275C3.56264 1.733499 3.574595 1.721544 3.610461 1.566127L5.272229 -5.152677ZM3.58655 -1.422665C3.526775 -1.219427 3.526775 -1.195517 3.359402 -0.968369C3.096389 -0.633624 2.570361 -0.119552 2.008468 -0.119552C1.518306 -0.119552 1.243337 -0.561893 1.243337 -1.267248C1.243337 -1.924782 1.613948 -3.263761 1.841096 -3.765878C2.247572 -4.60274 2.809465 -5.033126 3.275716 -5.033126C4.064757 -5.033126 4.220174 -4.052802 4.220174 -3.957161C4.220174 -3.945205 4.184309 -3.789788 4.172354 -3.765878L3.58655 -1.422665Z'/>
<path id='eq9-g0-120' d='M5.66675 -4.877709C5.284184 -4.805978 5.140722 -4.519054 5.140722 -4.291905C5.140722 -4.004981 5.36787 -3.90934 5.535243 -3.90934C5.893898 -3.90934 6.144956 -4.220174 6.144956 -4.542964C6.144956 -5.045081 5.571108 -5.272229 5.068991 -5.272229C4.339726 -5.272229 3.93325 -4.554919 3.825654 -4.327771C3.550685 -5.224408 2.809465 -5.272229 2.594271 -5.272229C1.374844 -5.272229 0.729265 -3.706102 0.729265 -3.443088C0.729265 -3.395268 0.777086 -3.335492 0.860772 -3.335492C0.956413 -3.335492 0.980324 -3.407223 1.004234 -3.455044C1.41071 -4.782067 2.211706 -5.033126 2.558406 -5.033126C3.096389 -5.033126 3.203985 -4.531009 3.203985 -4.244085C3.203985 -3.981071 3.132254 -3.706102 2.988792 -3.132254L2.582316 -1.494396C2.402989 -0.777086 2.056289 -0.119552 1.422665 -0.119552C1.362889 -0.119552 1.06401 -0.119552 0.812951 -0.274969C1.243337 -0.358655 1.338979 -0.71731 1.338979 -0.860772C1.338979 -1.099875 1.159651 -1.243337 0.932503 -1.243337C0.645579 -1.243337 0.334745 -0.992279 0.334745 -0.609714C0.334745 -0.107597 0.896638 0.119552 1.41071 0.119552C1.984558 0.119552 2.391034 -0.334745 2.642092 -0.824907C2.833375 -0.119552 3.431133 0.119552 3.873474 0.119552C5.092902 0.119552 5.738481 -1.446575 5.738481 -1.709589C5.738481 -1.769365 5.69066 -1.817186 5.618929 -1.817186C5.511333 -1.817186 5.499377 -1.75741 5.463512 -1.661768C5.140722 -0.609714 4.447323 -0.119552 3.90934 -0.119552C3.490909 -0.119552 3.263761 -0.430386 3.263761 -0.920548C3.263761 -1.183562 3.311582 -1.374844 3.502864 -2.163885L3.921295 -3.789788C4.100623 -4.507098 4.507098 -5.033126 5.057036 -5.033126C5.080946 -5.033126 5.415691 -5.033126 5.66675 -4.877709Z'/>
</defs>
<g id='eq9-page1'>
<use x='0' y='0' xlink:href='#eq9-g0-120'/>
<use x='6.652087' y='0' xlink:href='#eq9-g1-40'/>
<use x='11.204413' y='0' xlink:href='#eq9-g0-113'/>
<use x='16.823569' y='0' xlink:href='#eq9-g1-41'/>
</g>
</svg>. This re­la­tion­ship is known as the
<em>for­ward kin­emat­ics</em> and is usu­ally non-lin­ear. The Jac­obi­an mat­rix (which is a
func­tion), <svg class="m-math" style="width: 2.323em; height: 1.245em; vertical-align: -0.312em;" viewBox="0 -8.966376 22.300927 11.955168">
<title>
J(q)
</title>
<defs>
<path id='eq10-g1-40' d='M3.88543 2.905106C3.88543 2.86924 3.88543 2.84533 3.682192 2.642092C2.486675 1.43462 1.817186 -0.537983 1.817186 -2.976837C1.817186 -5.296139 2.379078 -7.292653 3.765878 -8.703362C3.88543 -8.810959 3.88543 -8.834869 3.88543 -8.870735C3.88543 -8.942466 3.825654 -8.966376 3.777833 -8.966376C3.622416 -8.966376 2.642092 -8.105604 2.056289 -6.933998C1.446575 -5.726526 1.171606 -4.447323 1.171606 -2.976837C1.171606 -1.912827 1.338979 -0.490162 1.960648 0.789041C2.666002 2.223661 3.646326 3.000747 3.777833 3.000747C3.825654 3.000747 3.88543 2.976837 3.88543 2.905106Z'/>
<path id='eq10-g1-41' d='M3.371357 -2.976837C3.371357 -3.88543 3.251806 -5.36787 2.582316 -6.75467C1.876961 -8.18929 0.896638 -8.966376 0.765131 -8.966376C0.71731 -8.966376 0.657534 -8.942466 0.657534 -8.870735C0.657534 -8.834869 0.657534 -8.810959 0.860772 -8.607721C2.056289 -7.400249 2.725778 -5.427646 2.725778 -2.988792C2.725778 -0.669489 2.163885 1.327024 0.777086 2.737733C0.657534 2.84533 0.657534 2.86924 0.657534 2.905106C0.657534 2.976837 0.71731 3.000747 0.765131 3.000747C0.920548 3.000747 1.900872 2.139975 2.486675 0.968369C3.096389 -0.251059 3.371357 -1.542217 3.371357 -2.976837Z'/>
<path id='eq10-g0-74' d='M6.38406 -7.340473C6.479701 -7.699128 6.503611 -7.81868 7.089415 -7.81868C7.280697 -7.81868 7.400249 -7.81868 7.400249 -8.033873C7.400249 -8.16538 7.292653 -8.16538 7.244832 -8.16538C7.041594 -8.16538 6.814446 -8.141469 6.599253 -8.141469H5.941719C5.439601 -8.141469 4.913574 -8.16538 4.411457 -8.16538C4.303861 -8.16538 4.160399 -8.16538 4.160399 -7.950187C4.160399 -7.830635 4.25604 -7.830635 4.25604 -7.81868H4.554919C5.511333 -7.81868 5.511333 -7.723039 5.511333 -7.543711C5.511333 -7.531756 5.511333 -7.44807 5.463512 -7.256787L4.088667 -1.793275C3.777833 -0.573848 2.976837 0.011955 2.402989 0.011955C1.996513 0.011955 1.422665 -0.179328 1.279203 -0.812951C1.327024 -0.800996 1.3868 -0.789041 1.43462 -0.789041C1.829141 -0.789041 2.139975 -1.135741 2.139975 -1.482441C2.139975 -1.673724 2.020423 -1.924782 1.661768 -1.924782C1.446575 -1.924782 0.944458 -1.80523 0.944458 -1.028144C0.944458 -0.274969 1.566127 0.251059 2.426899 0.251059C3.514819 0.251059 4.686426 -0.573848 4.97335 -1.709589L6.38406 -7.340473Z'/>
<path id='eq10-g0-113' d='M5.272229 -5.152677C5.272229 -5.212453 5.224408 -5.260274 5.164633 -5.260274C5.068991 -5.260274 4.60274 -4.829888 4.375592 -4.411457C4.160399 -4.94944 3.789788 -5.272229 3.275716 -5.272229C1.924782 -5.272229 0.466252 -3.526775 0.466252 -1.75741C0.466252 -0.573848 1.159651 0.119552 1.972603 0.119552C2.606227 0.119552 3.132254 -0.358655 3.383313 -0.633624L3.395268 -0.621669L2.940971 1.171606L2.833375 1.601993C2.725778 1.960648 2.546451 1.960648 1.984558 1.972603C1.853051 1.972603 1.733499 1.972603 1.733499 2.199751C1.733499 2.283437 1.80523 2.319303 1.888917 2.319303C2.056289 2.319303 2.271482 2.295392 2.438854 2.295392H3.658281C3.837609 2.295392 4.040847 2.319303 4.220174 2.319303C4.291905 2.319303 4.435367 2.319303 4.435367 2.092154C4.435367 1.972603 4.339726 1.972603 4.160399 1.972603C3.598506 1.972603 3.56264 1.888917 3.56264 1.793275C3.56264 1.733499 3.574595 1.721544 3.610461 1.566127L5.272229 -5.152677ZM3.58655 -1.422665C3.526775 -1.219427 3.526775 -1.195517 3.359402 -0.968369C3.096389 -0.633624 2.570361 -0.119552 2.008468 -0.119552C1.518306 -0.119552 1.243337 -0.561893 1.243337 -1.267248C1.243337 -1.924782 1.613948 -3.263761 1.841096 -3.765878C2.247572 -4.60274 2.809465 -5.033126 3.275716 -5.033126C4.064757 -5.033126 4.220174 -4.052802 4.220174 -3.957161C4.220174 -3.945205 4.184309 -3.789788 4.172354 -3.765878L3.58655 -1.422665Z'/>
</defs>
<g id='eq10-page1'>
<use x='0' y='0' xlink:href='#eq10-g0-74'/>
<use x='7.577119' y='0' xlink:href='#eq10-g1-40'/>
<use x='12.129445' y='0' xlink:href='#eq10-g0-113'/>
<use x='17.748602' y='0' xlink:href='#eq10-g1-41'/>
</g>
</svg>, gives us the fol­low­ing re­la­tion­ship:</p>
<div class="m-math">
<svg style="width: 10.288em; height: 7.941em;" viewBox="144.891441 -77.574711 98.760086 76.229718">
<title>
\begin{aligned}
J(q) &= \frac{\partial{x(q)}}{\partial{q}}\\
&= \frac{\partial{x(q)}}{\partial{t}}\frac{\partial{t}}{\partial{q}}\Rightarrow\\
\dot{x} &= J(q)\dot{q}
\end{aligned}
</title>
<defs>
<path id='eq11-g0-41' d='M8.679452 -3.90934C9.181569 -3.478954 9.791283 -3.16812 10.185803 -2.988792C9.755417 -2.797509 9.169614 -2.486675 8.679452 -2.068244H1.08792C0.884682 -2.068244 0.657534 -2.068244 0.657534 -1.829141S0.872727 -1.590037 1.075965 -1.590037H8.141469C7.567621 -1.0401 6.945953 0.011955 6.945953 0.167372C6.945953 0.298879 7.10137 0.298879 7.173101 0.298879C7.268742 0.298879 7.352428 0.298879 7.400249 0.203238C7.651308 -0.251059 7.986052 -0.884682 8.763138 -1.578082C9.588045 -2.307347 10.389041 -2.630137 11.01071 -2.809465C11.213948 -2.881196 11.225903 -2.893151 11.249813 -2.917061C11.273724 -2.929016 11.273724 -2.964882 11.273724 -2.988792S11.273724 -3.036613 11.261768 -3.060523L11.225903 -3.084433C11.201993 -3.096389 11.190037 -3.108344 10.962889 -3.180075C9.348941 -3.658281 8.153425 -4.746202 7.483935 -6.025405C7.352428 -6.264508 7.340473 -6.276463 7.173101 -6.276463C7.10137 -6.276463 6.945953 -6.276463 6.945953 -6.144956C6.945953 -5.989539 7.555666 -4.94944 8.141469 -4.387547H1.075965C0.872727 -4.387547 0.657534 -4.387547 0.657534 -4.148443S0.884682 -3.90934 1.08792 -3.90934H8.679452Z'/>
<path id='eq11-g1-64' d='M5.427646 -3.993026C5.355915 -4.662516 4.937484 -5.463512 3.861519 -5.463512C2.175841 -5.463512 0.454296 -3.753923 0.454296 -1.853051C0.454296 -1.123786 0.956413 0.251059 2.582316 0.251059C5.403736 0.251059 6.611208 -3.861519 6.611208 -5.499377C6.611208 -7.220922 5.642839 -8.547945 4.112578 -8.547945C2.379078 -8.547945 1.865006 -7.029639 1.865006 -6.706849C1.865006 -6.599253 1.936737 -6.336239 2.271482 -6.336239C2.689913 -6.336239 2.86924 -6.718804 2.86924 -6.922042C2.86924 -7.292653 2.49863 -7.292653 2.343213 -7.292653C2.833375 -8.177335 3.741968 -8.261021 4.064757 -8.261021C5.116812 -8.261021 5.786301 -7.424159 5.786301 -6.085181C5.786301 -5.32005 5.559153 -4.435367 5.439601 -3.993026H5.427646ZM2.618182 -0.071731C1.494396 -0.071731 1.303113 -0.956413 1.303113 -1.458531C1.303113 -1.984558 1.637858 -3.21594 1.817186 -3.658281C1.972603 -4.016936 2.654047 -5.224408 3.897385 -5.224408C4.985305 -5.224408 5.236364 -4.27995 5.236364 -3.634371C5.236364 -2.749689 4.459278 -0.071731 2.618182 -0.071731Z'/>
<path id='eq11-g1-74' d='M6.38406 -7.340473C6.479701 -7.699128 6.503611 -7.81868 7.089415 -7.81868C7.280697 -7.81868 7.400249 -7.81868 7.400249 -8.033873C7.400249 -8.16538 7.292653 -8.16538 7.244832 -8.16538C7.041594 -8.16538 6.814446 -8.141469 6.599253 -8.141469H5.941719C5.439601 -8.141469 4.913574 -8.16538 4.411457 -8.16538C4.303861 -8.16538 4.160399 -8.16538 4.160399 -7.950187C4.160399 -7.830635 4.25604 -7.830635 4.25604 -7.81868H4.554919C5.511333 -7.81868 5.511333 -7.723039 5.511333 -7.543711C5.511333 -7.531756 5.511333 -7.44807 5.463512 -7.256787L4.088667 -1.793275C3.777833 -0.573848 2.976837 0.011955 2.402989 0.011955C1.996513 0.011955 1.422665 -0.179328 1.279203 -0.812951C1.327024 -0.800996 1.3868 -0.789041 1.43462 -0.789041C1.829141 -0.789041 2.139975 -1.135741 2.139975 -1.482441C2.139975 -1.673724 2.020423 -1.924782 1.661768 -1.924782C1.446575 -1.924782 0.944458 -1.80523 0.944458 -1.028144C0.944458 -0.274969 1.566127 0.251059 2.426899 0.251059C3.514819 0.251059 4.686426 -0.573848 4.97335 -1.709589L6.38406 -7.340473Z'/>
<path id='eq11-g1-113' d='M5.272229 -5.152677C5.272229 -5.212453 5.224408 -5.260274 5.164633 -5.260274C5.068991 -5.260274 4.60274 -4.829888 4.375592 -4.411457C4.160399 -4.94944 3.789788 -5.272229 3.275716 -5.272229C1.924782 -5.272229 0.466252 -3.526775 0.466252 -1.75741C0.466252 -0.573848 1.159651 0.119552 1.972603 0.119552C2.606227 0.119552 3.132254 -0.358655 3.383313 -0.633624L3.395268 -0.621669L2.940971 1.171606L2.833375 1.601993C2.725778 1.960648 2.546451 1.960648 1.984558 1.972603C1.853051 1.972603 1.733499 1.972603 1.733499 2.199751C1.733499 2.283437 1.80523 2.319303 1.888917 2.319303C2.056289 2.319303 2.271482 2.295392 2.438854 2.295392H3.658281C3.837609 2.295392 4.040847 2.319303 4.220174 2.319303C4.291905 2.319303 4.435367 2.319303 4.435367 2.092154C4.435367 1.972603 4.339726 1.972603 4.160399 1.972603C3.598506 1.972603 3.56264 1.888917 3.56264 1.793275C3.56264 1.733499 3.574595 1.721544 3.610461 1.566127L5.272229 -5.152677ZM3.58655 -1.422665C3.526775 -1.219427 3.526775 -1.195517 3.359402 -0.968369C3.096389 -0.633624 2.570361 -0.119552 2.008468 -0.119552C1.518306 -0.119552 1.243337 -0.561893 1.243337 -1.267248C1.243337 -1.924782 1.613948 -3.263761 1.841096 -3.765878C2.247572 -4.60274 2.809465 -5.033126 3.275716 -5.033126C4.064757 -5.033126 4.220174 -4.052802 4.220174 -3.957161C4.220174 -3.945205 4.184309 -3.789788 4.172354 -3.765878L3.58655 -1.422665Z'/>
<path id='eq11-g1-116' d='M2.402989 -4.805978H3.502864C3.730012 -4.805978 3.849564 -4.805978 3.849564 -5.021171C3.849564 -5.152677 3.777833 -5.152677 3.53873 -5.152677H2.486675L2.929016 -6.898132C2.976837 -7.065504 2.976837 -7.089415 2.976837 -7.173101C2.976837 -7.364384 2.82142 -7.47198 2.666002 -7.47198C2.570361 -7.47198 2.295392 -7.436115 2.199751 -7.053549L1.733499 -5.152677H0.609714C0.37061 -5.152677 0.263014 -5.152677 0.263014 -4.925529C0.263014 -4.805978 0.3467 -4.805978 0.573848 -4.805978H1.637858L0.848817 -1.649813C0.753176 -1.231382 0.71731 -1.111831 0.71731 -0.956413C0.71731 -0.394521 1.111831 0.119552 1.78132 0.119552C2.988792 0.119552 3.634371 -1.625903 3.634371 -1.709589C3.634371 -1.78132 3.58655 -1.817186 3.514819 -1.817186C3.490909 -1.817186 3.443088 -1.817186 3.419178 -1.769365C3.407223 -1.75741 3.395268 -1.745455 3.311582 -1.554172C3.060523 -0.956413 2.510585 -0.119552 1.817186 -0.119552C1.458531 -0.119552 1.43462 -0.418431 1.43462 -0.681445C1.43462 -0.6934 1.43462 -0.920548 1.470486 -1.06401L2.402989 -4.805978Z'/>
<path id='eq11-g1-120' d='M5.66675 -4.877709C5.284184 -4.805978 5.140722 -4.519054 5.140722 -4.291905C5.140722 -4.004981 5.36787 -3.90934 5.535243 -3.90934C5.893898 -3.90934 6.144956 -4.220174 6.144956 -4.542964C6.144956 -5.045081 5.571108 -5.272229 5.068991 -5.272229C4.339726 -5.272229 3.93325 -4.554919 3.825654 -4.327771C3.550685 -5.224408 2.809465 -5.272229 2.594271 -5.272229C1.374844 -5.272229 0.729265 -3.706102 0.729265 -3.443088C0.729265 -3.395268 0.777086 -3.335492 0.860772 -3.335492C0.956413 -3.335492 0.980324 -3.407223 1.004234 -3.455044C1.41071 -4.782067 2.211706 -5.033126 2.558406 -5.033126C3.096389 -5.033126 3.203985 -4.531009 3.203985 -4.244085C3.203985 -3.981071 3.132254 -3.706102 2.988792 -3.132254L2.582316 -1.494396C2.402989 -0.777086 2.056289 -0.119552 1.422665 -0.119552C1.362889 -0.119552 1.06401 -0.119552 0.812951 -0.274969C1.243337 -0.358655 1.338979 -0.71731 1.338979 -0.860772C1.338979 -1.099875 1.159651 -1.243337 0.932503 -1.243337C0.645579 -1.243337 0.334745 -0.992279 0.334745 -0.609714C0.334745 -0.107597 0.896638 0.119552 1.41071 0.119552C1.984558 0.119552 2.391034 -0.334745 2.642092 -0.824907C2.833375 -0.119552 3.431133 0.119552 3.873474 0.119552C5.092902 0.119552 5.738481 -1.446575 5.738481 -1.709589C5.738481 -1.769365 5.69066 -1.817186 5.618929 -1.817186C5.511333 -1.817186 5.499377 -1.75741 5.463512 -1.661768C5.140722 -0.609714 4.447323 -0.119552 3.90934 -0.119552C3.490909 -0.119552 3.263761 -0.430386 3.263761 -0.920548C3.263761 -1.183562 3.311582 -1.374844 3.502864 -2.163885L3.921295 -3.789788C4.100623 -4.507098 4.507098 -5.033126 5.057036 -5.033126C5.080946 -5.033126 5.415691 -5.033126 5.66675 -4.877709Z'/>
<path id='eq11-g2-40' d='M3.88543 2.905106C3.88543 2.86924 3.88543 2.84533 3.682192 2.642092C2.486675 1.43462 1.817186 -0.537983 1.817186 -2.976837C1.817186 -5.296139 2.379078 -7.292653 3.765878 -8.703362C3.88543 -8.810959 3.88543 -8.834869 3.88543 -8.870735C3.88543 -8.942466 3.825654 -8.966376 3.777833 -8.966376C3.622416 -8.966376 2.642092 -8.105604 2.056289 -6.933998C1.446575 -5.726526 1.171606 -4.447323 1.171606 -2.976837C1.171606 -1.912827 1.338979 -0.490162 1.960648 0.789041C2.666002 2.223661 3.646326 3.000747 3.777833 3.000747C3.825654 3.000747 3.88543 2.976837 3.88543 2.905106Z'/>
<path id='eq11-g2-41' d='M3.371357 -2.976837C3.371357 -3.88543 3.251806 -5.36787 2.582316 -6.75467C1.876961 -8.18929 0.896638 -8.966376 0.765131 -8.966376C0.71731 -8.966376 0.657534 -8.942466 0.657534 -8.870735C0.657534 -8.834869 0.657534 -8.810959 0.860772 -8.607721C2.056289 -7.400249 2.725778 -5.427646 2.725778 -2.988792C2.725778 -0.669489 2.163885 1.327024 0.777086 2.737733C0.657534 2.84533 0.657534 2.86924 0.657534 2.905106C0.657534 2.976837 0.71731 3.000747 0.765131 3.000747C0.920548 3.000747 1.900872 2.139975 2.486675 0.968369C3.096389 -0.251059 3.371357 -1.542217 3.371357 -2.976837Z'/>
<path id='eq11-g2-61' d='M8.069738 -3.873474C8.237111 -3.873474 8.452304 -3.873474 8.452304 -4.088667C8.452304 -4.315816 8.249066 -4.315816 8.069738 -4.315816H1.028144C0.860772 -4.315816 0.645579 -4.315816 0.645579 -4.100623C0.645579 -3.873474 0.848817 -3.873474 1.028144 -3.873474H8.069738ZM8.069738 -1.649813C8.237111 -1.649813 8.452304 -1.649813 8.452304 -1.865006C8.452304 -2.092154 8.249066 -2.092154 8.069738 -2.092154H1.028144C0.860772 -2.092154 0.645579 -2.092154 0.645579 -1.876961C0.645579 -1.649813 0.848817 -1.649813 1.028144 -1.649813H8.069738Z'/>
<path id='eq11-g2-95' d='M2.199751 -7.364384C2.199751 -7.723039 1.912827 -7.950187 1.625903 -7.950187C1.291158 -7.950187 1.0401 -7.687173 1.0401 -7.364384C1.0401 -7.053549 1.303113 -6.790535 1.613948 -6.790535C1.972603 -6.790535 2.199751 -7.07746 2.199751 -7.364384Z'/>
</defs>
<g id='eq11-page1'>
<use x='144.891441' y='-60.520576' xlink:href='#eq11-g1-74'/>
<use x='152.46856' y='-60.520576' xlink:href='#eq11-g2-40'/>
<use x='157.020886' y='-60.520576' xlink:href='#eq11-g1-113'/>
<use x='162.640043' y='-60.520576' xlink:href='#eq11-g2-41'/>
<use x='170.513194' y='-60.520576' xlink:href='#eq11-g2-61'/>
<use x='184.134189' y='-68.608335' xlink:href='#eq11-g1-64'/>
<use x='190.964057' y='-68.608335' xlink:href='#eq11-g1-120'/>
<use x='197.616144' y='-68.608335' xlink:href='#eq11-g2-40'/>
<use x='202.16847' y='-68.608335' xlink:href='#eq11-g1-113'/>
<use x='207.787626' y='-68.608335' xlink:href='#eq11-g2-41'/>
<rect x='184.134189' y='-63.748462' height='0.478187' width='28.205752'/>
<use x='192.012558' y='-52.319914' xlink:href='#eq11-g1-64'/>
<use x='198.842426' y='-52.319914' xlink:href='#eq11-g1-113'/>
<use x='170.513194' y='-28.956127' xlink:href='#eq11-g2-61'/>
<use x='184.134189' y='-37.043886' xlink:href='#eq11-g1-64'/>
<use x='190.964057' y='-37.043886' xlink:href='#eq11-g1-120'/>
<use x='197.616144' y='-37.043886' xlink:href='#eq11-g2-40'/>
<use x='202.16847' y='-37.043886' xlink:href='#eq11-g1-113'/>
<use x='207.787626' y='-37.043886' xlink:href='#eq11-g2-41'/>
<rect x='184.134189' y='-32.184013' height='0.478187' width='28.205752'/>
<use x='192.708556' y='-20.755465' xlink:href='#eq11-g1-64'/>
<use x='199.538424' y='-20.755465' xlink:href='#eq11-g1-116'/>
<use x='215.426966' y='-37.043886' xlink:href='#eq11-g1-64'/>
<use x='222.256835' y='-37.043886' xlink:href='#eq11-g1-116'/>
<rect x='214.730968' y='-32.184013' height='0.478187' width='12.449013'/>
<use x='214.730968' y='-20.755465' xlink:href='#eq11-g1-64'/>
<use x='221.560836' y='-20.755465' xlink:href='#eq11-g1-113'/>
<use x='231.696324' y='-28.956127' xlink:href='#eq11-g0-41'/>
<use x='162.565656' y='-4.333785' xlink:href='#eq11-g2-95'/>
<use x='160.540277' y='-4.333785' xlink:href='#eq11-g1-120'/>
<use x='170.513194' y='-4.333785' xlink:href='#eq11-g2-61'/>
<use x='182.938675' y='-4.333785' xlink:href='#eq11-g1-74'/>
<use x='190.515794' y='-4.333785' xlink:href='#eq11-g2-40'/>
<use x='195.06812' y='-4.333785' xlink:href='#eq11-g1-113'/>
<use x='200.687276' y='-4.333785' xlink:href='#eq11-g2-41'/>
<use x='207.398848' y='-4.333785' xlink:href='#eq11-g2-95'/>
<use x='205.239602' y='-4.333785' xlink:href='#eq11-g1-113'/>
</g>
</svg></div>
<p>With this last equa­tion, we have an ana­lyt­ic­al equa­tion that relates the joint
ve­lo­cit­ies, <svg class="m-math" style="width: 0.585em; height: 1.069em; vertical-align: -0.243em;" viewBox="0 -7.934555 5.61916 10.259162">
<title>
\dot{q}
</title>
<defs>
<path id='eq12-g0-113' d='M5.272229 -5.152677C5.272229 -5.212453 5.224408 -5.260274 5.164633 -5.260274C5.068991 -5.260274 4.60274 -4.829888 4.375592 -4.411457C4.160399 -4.94944 3.789788 -5.272229 3.275716 -5.272229C1.924782 -5.272229 0.466252 -3.526775 0.466252 -1.75741C0.466252 -0.573848 1.159651 0.119552 1.972603 0.119552C2.606227 0.119552 3.132254 -0.358655 3.383313 -0.633624L3.395268 -0.621669L2.940971 1.171606L2.833375 1.601993C2.725778 1.960648 2.546451 1.960648 1.984558 1.972603C1.853051 1.972603 1.733499 1.972603 1.733499 2.199751C1.733499 2.283437 1.80523 2.319303 1.888917 2.319303C2.056289 2.319303 2.271482 2.295392 2.438854 2.295392H3.658281C3.837609 2.295392 4.040847 2.319303 4.220174 2.319303C4.291905 2.319303 4.435367 2.319303 4.435367 2.092154C4.435367 1.972603 4.339726 1.972603 4.160399 1.972603C3.598506 1.972603 3.56264 1.888917 3.56264 1.793275C3.56264 1.733499 3.574595 1.721544 3.610461 1.566127L5.272229 -5.152677ZM3.58655 -1.422665C3.526775 -1.219427 3.526775 -1.195517 3.359402 -0.968369C3.096389 -0.633624 2.570361 -0.119552 2.008468 -0.119552C1.518306 -0.119552 1.243337 -0.561893 1.243337 -1.267248C1.243337 -1.924782 1.613948 -3.263761 1.841096 -3.765878C2.247572 -4.60274 2.809465 -5.033126 3.275716 -5.033126C4.064757 -5.033126 4.220174 -4.052802 4.220174 -3.957161C4.220174 -3.945205 4.184309 -3.789788 4.172354 -3.765878L3.58655 -1.422665Z'/>
<path id='eq12-g1-95' d='M2.199751 -7.364384C2.199751 -7.723039 1.912827 -7.950187 1.625903 -7.950187C1.291158 -7.950187 1.0401 -7.687173 1.0401 -7.364384C1.0401 -7.053549 1.303113 -6.790535 1.613948 -6.790535C1.972603 -6.790535 2.199751 -7.07746 2.199751 -7.364384Z'/>
</defs>
<g id='eq12-page1'>
<use x='2.159246' y='0' xlink:href='#eq12-g1-95'/>
<use x='0' y='0' xlink:href='#eq12-g0-113'/>
</g>
</svg> to end-ef­fect­or ve­lo­cit­ies <svg class="m-math" style="width: 0.693em; height: 0.827em; vertical-align: -0.000em;" viewBox="0 -7.934555 6.652087 7.934555">
<title>
\dot{x}
</title>
<defs>
<path id='eq13-g0-120' d='M5.66675 -4.877709C5.284184 -4.805978 5.140722 -4.519054 5.140722 -4.291905C5.140722 -4.004981 5.36787 -3.90934 5.535243 -3.90934C5.893898 -3.90934 6.144956 -4.220174 6.144956 -4.542964C6.144956 -5.045081 5.571108 -5.272229 5.068991 -5.272229C4.339726 -5.272229 3.93325 -4.554919 3.825654 -4.327771C3.550685 -5.224408 2.809465 -5.272229 2.594271 -5.272229C1.374844 -5.272229 0.729265 -3.706102 0.729265 -3.443088C0.729265 -3.395268 0.777086 -3.335492 0.860772 -3.335492C0.956413 -3.335492 0.980324 -3.407223 1.004234 -3.455044C1.41071 -4.782067 2.211706 -5.033126 2.558406 -5.033126C3.096389 -5.033126 3.203985 -4.531009 3.203985 -4.244085C3.203985 -3.981071 3.132254 -3.706102 2.988792 -3.132254L2.582316 -1.494396C2.402989 -0.777086 2.056289 -0.119552 1.422665 -0.119552C1.362889 -0.119552 1.06401 -0.119552 0.812951 -0.274969C1.243337 -0.358655 1.338979 -0.71731 1.338979 -0.860772C1.338979 -1.099875 1.159651 -1.243337 0.932503 -1.243337C0.645579 -1.243337 0.334745 -0.992279 0.334745 -0.609714C0.334745 -0.107597 0.896638 0.119552 1.41071 0.119552C1.984558 0.119552 2.391034 -0.334745 2.642092 -0.824907C2.833375 -0.119552 3.431133 0.119552 3.873474 0.119552C5.092902 0.119552 5.738481 -1.446575 5.738481 -1.709589C5.738481 -1.769365 5.69066 -1.817186 5.618929 -1.817186C5.511333 -1.817186 5.499377 -1.75741 5.463512 -1.661768C5.140722 -0.609714 4.447323 -0.119552 3.90934 -0.119552C3.490909 -0.119552 3.263761 -0.430386 3.263761 -0.920548C3.263761 -1.183562 3.311582 -1.374844 3.502864 -2.163885L3.921295 -3.789788C4.100623 -4.507098 4.507098 -5.033126 5.057036 -5.033126C5.080946 -5.033126 5.415691 -5.033126 5.66675 -4.877709Z'/>
<path id='eq13-g1-95' d='M2.199751 -7.364384C2.199751 -7.723039 1.912827 -7.950187 1.625903 -7.950187C1.291158 -7.950187 1.0401 -7.687173 1.0401 -7.364384C1.0401 -7.053549 1.303113 -6.790535 1.613948 -6.790535C1.972603 -6.790535 2.199751 -7.07746 2.199751 -7.364384Z'/>
</defs>
<g id='eq13-page1'>
<use x='2.025379' y='0' xlink:href='#eq13-g1-95'/>
<use x='0' y='0' xlink:href='#eq13-g0-120'/>
</g>
</svg>.
Moreover, this is a lin­ear re­la­tion­ship. Us­ing the prop­erty of con­ser­va­tion of
en­ergy (from tra­di­tion­al phys­ics) and some math­em­at­ic­al ma­nip­u­la­tion, we can
gen­er­al­ize this re­la­tion­ship to forces and torques (we drop the ar­gu­ments in
<svg class="m-math" style="width: 0.789em; height: 0.851em; vertical-align: -0.000em;" viewBox="0 -8.169366 7.577123 8.169366">
<title>
J
</title>
<defs>
<path id='eq14-g0-74' d='M6.38406 -7.340473C6.479701 -7.699128 6.503611 -7.81868 7.089415 -7.81868C7.280697 -7.81868 7.400249 -7.81868 7.400249 -8.033873C7.400249 -8.16538 7.292653 -8.16538 7.244832 -8.16538C7.041594 -8.16538 6.814446 -8.141469 6.599253 -8.141469H5.941719C5.439601 -8.141469 4.913574 -8.16538 4.411457 -8.16538C4.303861 -8.16538 4.160399 -8.16538 4.160399 -7.950187C4.160399 -7.830635 4.25604 -7.830635 4.25604 -7.81868H4.554919C5.511333 -7.81868 5.511333 -7.723039 5.511333 -7.543711C5.511333 -7.531756 5.511333 -7.44807 5.463512 -7.256787L4.088667 -1.793275C3.777833 -0.573848 2.976837 0.011955 2.402989 0.011955C1.996513 0.011955 1.422665 -0.179328 1.279203 -0.812951C1.327024 -0.800996 1.3868 -0.789041 1.43462 -0.789041C1.829141 -0.789041 2.139975 -1.135741 2.139975 -1.482441C2.139975 -1.673724 2.020423 -1.924782 1.661768 -1.924782C1.446575 -1.924782 0.944458 -1.80523 0.944458 -1.028144C0.944458 -0.274969 1.566127 0.251059 2.426899 0.251059C3.514819 0.251059 4.686426 -0.573848 4.97335 -1.709589L6.38406 -7.340473Z'/>
</defs>
<g id='eq14-page1'>
<use x='0' y='0' xlink:href='#eq14-g0-74'/>
</g>
</svg> for clar­ity) as</p>
<div class="m-math">
<svg style="width: 5.107em; height: 1.082em;" viewBox="169.759398 -10.382422 49.024201 10.382422">
<title>
\tau = J^TW
</title>
<defs>
<path id='eq15-g0-84' d='M3.602491 -4.821918C3.674222 -5.108842 3.682192 -5.124782 4.008966 -5.124782H4.614695C5.443587 -5.124782 5.539228 -4.861768 5.539228 -4.463263C5.539228 -4.26401 5.491407 -3.921295 5.483437 -3.881445C5.467497 -3.793773 5.459527 -3.722042 5.459527 -3.706102C5.459527 -3.602491 5.531258 -3.57858 5.579078 -3.57858C5.66675 -3.57858 5.69863 -3.626401 5.72254 -3.777833L5.937733 -5.276214C5.937733 -5.387796 5.842092 -5.387796 5.69863 -5.387796H1.004234C0.804981 -5.387796 0.789041 -5.387796 0.73325 -5.220423L0.247073 -3.841594C0.231133 -3.801743 0.207223 -3.737983 0.207223 -3.690162C0.207223 -3.626401 0.263014 -3.57858 0.326775 -3.57858C0.414446 -3.57858 0.430386 -3.618431 0.478207 -3.753923C0.932503 -5.029141 1.163636 -5.124782 2.375093 -5.124782H2.685928C2.925031 -5.124782 2.933001 -5.116812 2.933001 -5.053051C2.933001 -5.029141 2.901121 -4.869738 2.893151 -4.837858L1.841096 -0.653549C1.769365 -0.350685 1.745455 -0.263014 0.916563 -0.263014C0.661519 -0.263014 0.581818 -0.263014 0.581818 -0.111582C0.581818 -0.103611 0.581818 0 0.71731 0C0.932503 0 1.482441 -0.02391 1.697634 -0.02391H2.375093C2.598257 -0.02391 3.156164 0 3.379328 0C3.443088 0 3.56264 0 3.56264 -0.151432C3.56264 -0.263014 3.474969 -0.263014 3.259776 -0.263014C3.068493 -0.263014 3.004732 -0.263014 2.797509 -0.278954C2.542466 -0.302864 2.510585 -0.334745 2.510585 -0.438356C2.510585 -0.470237 2.518555 -0.502117 2.542466 -0.581818L3.602491 -4.821918Z'/>
<path id='eq15-g1-28' d='M3.431133 -4.507098H5.415691C5.571108 -4.507098 5.965629 -4.507098 5.965629 -4.889664C5.965629 -5.152677 5.738481 -5.152677 5.523288 -5.152677H2.235616C1.960648 -5.152677 1.554172 -5.152677 1.004234 -4.566874C0.6934 -4.220174 0.310834 -3.58655 0.310834 -3.514819S0.37061 -3.419178 0.442341 -3.419178C0.526027 -3.419178 0.537983 -3.455044 0.597758 -3.526775C1.219427 -4.507098 1.841096 -4.507098 2.139975 -4.507098H3.132254L1.888917 -0.406476C1.829141 -0.227148 1.829141 -0.203238 1.829141 -0.167372C1.829141 -0.035866 1.912827 0.131507 2.15193 0.131507C2.52254 0.131507 2.582316 -0.191283 2.618182 -0.37061L3.431133 -4.507098Z'/>
<path id='eq15-g1-74' d='M6.38406 -7.340473C6.479701 -7.699128 6.503611 -7.81868 7.089415 -7.81868C7.280697 -7.81868 7.400249 -7.81868 7.400249 -8.033873C7.400249 -8.16538 7.292653 -8.16538 7.244832 -8.16538C7.041594 -8.16538 6.814446 -8.141469 6.599253 -8.141469H5.941719C5.439601 -8.141469 4.913574 -8.16538 4.411457 -8.16538C4.303861 -8.16538 4.160399 -8.16538 4.160399 -7.950187C4.160399 -7.830635 4.25604 -7.830635 4.25604 -7.81868H4.554919C5.511333 -7.81868 5.511333 -7.723039 5.511333 -7.543711C5.511333 -7.531756 5.511333 -7.44807 5.463512 -7.256787L4.088667 -1.793275C3.777833 -0.573848 2.976837 0.011955 2.402989 0.011955C1.996513 0.011955 1.422665 -0.179328 1.279203 -0.812951C1.327024 -0.800996 1.3868 -0.789041 1.43462 -0.789041C1.829141 -0.789041 2.139975 -1.135741 2.139975 -1.482441C2.139975 -1.673724 2.020423 -1.924782 1.661768 -1.924782C1.446575 -1.924782 0.944458 -1.80523 0.944458 -1.028144C0.944458 -0.274969 1.566127 0.251059 2.426899 0.251059C3.514819 0.251059 4.686426 -0.573848 4.97335 -1.709589L6.38406 -7.340473Z'/>
<path id='eq15-g1-87' d='M10.795517 -6.838356C11.070486 -7.304608 11.333499 -7.746949 12.050809 -7.81868C12.158406 -7.830635 12.266002 -7.84259 12.266002 -8.033873C12.266002 -8.16538 12.158406 -8.16538 12.12254 -8.16538C12.09863 -8.16538 12.014944 -8.141469 11.225903 -8.141469C10.867248 -8.141469 10.496638 -8.16538 10.149938 -8.16538C10.078207 -8.16538 9.934745 -8.16538 9.934745 -7.938232C9.934745 -7.830635 10.030386 -7.81868 10.102117 -7.81868C10.34122 -7.806725 10.723786 -7.734994 10.723786 -7.364384C10.723786 -7.208966 10.675965 -7.12528 10.556413 -6.922042L7.292653 -1.207472L6.862267 -7.436115C6.862267 -7.579577 6.993773 -7.806725 7.663263 -7.81868C7.81868 -7.81868 7.938232 -7.81868 7.938232 -8.045828C7.938232 -8.16538 7.81868 -8.16538 7.758904 -8.16538C7.340473 -8.16538 6.898132 -8.141469 6.467746 -8.141469H5.846077C5.66675 -8.141469 5.451557 -8.16538 5.272229 -8.16538C5.200498 -8.16538 5.057036 -8.16538 5.057036 -7.938232C5.057036 -7.81868 5.140722 -7.81868 5.34396 -7.81868C5.893898 -7.81868 5.893898 -7.806725 5.941719 -7.07746L5.977584 -6.647073L2.881196 -1.207472L2.438854 -7.376339C2.438854 -7.507846 2.438854 -7.806725 3.251806 -7.81868C3.383313 -7.81868 3.514819 -7.81868 3.514819 -8.033873C3.514819 -8.16538 3.407223 -8.16538 3.335492 -8.16538C2.917061 -8.16538 2.47472 -8.141469 2.044334 -8.141469H1.422665C1.243337 -8.141469 1.028144 -8.16538 0.848817 -8.16538C0.777086 -8.16538 0.633624 -8.16538 0.633624 -7.938232C0.633624 -7.81868 0.729265 -7.81868 0.896638 -7.81868C1.458531 -7.81868 1.470486 -7.746949 1.494396 -7.364384L2.020423 -0.02391C2.032379 0.179328 2.044334 0.251059 2.187796 0.251059C2.307347 0.251059 2.331258 0.203238 2.438854 0.02391L6.001494 -6.204732L6.443836 -0.02391C6.455791 0.179328 6.467746 0.251059 6.611208 0.251059C6.73076 0.251059 6.766625 0.191283 6.862267 0.02391L10.795517 -6.838356Z'/>
<path id='eq15-g2-61' d='M8.069738 -3.873474C8.237111 -3.873474 8.452304 -3.873474 8.452304 -4.088667C8.452304 -4.315816 8.249066 -4.315816 8.069738 -4.315816H1.028144C0.860772 -4.315816 0.645579 -4.315816 0.645579 -4.100623C0.645579 -3.873474 0.848817 -3.873474 1.028144 -3.873474H8.069738ZM8.069738 -1.649813C8.237111 -1.649813 8.452304 -1.649813 8.452304 -1.865006C8.452304 -2.092154 8.249066 -2.092154 8.069738 -2.092154H1.028144C0.860772 -2.092154 0.645579 -2.092154 0.645579 -1.876961C0.645579 -1.649813 0.848817 -1.649813 1.028144 -1.649813H8.069738Z'/>
</defs>
<g id='eq15-page1'>
<use x='169.759398' y='0' xlink:href='#eq15-g1-28'/>
<use x='179.499169' y='0' xlink:href='#eq15-g2-61'/>
<use x='191.92465' y='0' xlink:href='#eq15-g1-74'/>
<use x='199.501769' y='-4.936186' xlink:href='#eq15-g0-84'/>
<use x='206.10626' y='0' xlink:href='#eq15-g1-87'/>
</g>
</svg></div>
<p class="m-noindent">where <svg class="m-math" style="width: 1.321em; height: 0.851em; vertical-align: -0.000em;" viewBox="0 -8.169366 12.67734 8.169366">
<title>
W
</title>
<defs>
<path id='eq16-g0-87' d='M10.795517 -6.838356C11.070486 -7.304608 11.333499 -7.746949 12.050809 -7.81868C12.158406 -7.830635 12.266002 -7.84259 12.266002 -8.033873C12.266002 -8.16538 12.158406 -8.16538 12.12254 -8.16538C12.09863 -8.16538 12.014944 -8.141469 11.225903 -8.141469C10.867248 -8.141469 10.496638 -8.16538 10.149938 -8.16538C10.078207 -8.16538 9.934745 -8.16538 9.934745 -7.938232C9.934745 -7.830635 10.030386 -7.81868 10.102117 -7.81868C10.34122 -7.806725 10.723786 -7.734994 10.723786 -7.364384C10.723786 -7.208966 10.675965 -7.12528 10.556413 -6.922042L7.292653 -1.207472L6.862267 -7.436115C6.862267 -7.579577 6.993773 -7.806725 7.663263 -7.81868C7.81868 -7.81868 7.938232 -7.81868 7.938232 -8.045828C7.938232 -8.16538 7.81868 -8.16538 7.758904 -8.16538C7.340473 -8.16538 6.898132 -8.141469 6.467746 -8.141469H5.846077C5.66675 -8.141469 5.451557 -8.16538 5.272229 -8.16538C5.200498 -8.16538 5.057036 -8.16538 5.057036 -7.938232C5.057036 -7.81868 5.140722 -7.81868 5.34396 -7.81868C5.893898 -7.81868 5.893898 -7.806725 5.941719 -7.07746L5.977584 -6.647073L2.881196 -1.207472L2.438854 -7.376339C2.438854 -7.507846 2.438854 -7.806725 3.251806 -7.81868C3.383313 -7.81868 3.514819 -7.81868 3.514819 -8.033873C3.514819 -8.16538 3.407223 -8.16538 3.335492 -8.16538C2.917061 -8.16538 2.47472 -8.141469 2.044334 -8.141469H1.422665C1.243337 -8.141469 1.028144 -8.16538 0.848817 -8.16538C0.777086 -8.16538 0.633624 -8.16538 0.633624 -7.938232C0.633624 -7.81868 0.729265 -7.81868 0.896638 -7.81868C1.458531 -7.81868 1.470486 -7.746949 1.494396 -7.364384L2.020423 -0.02391C2.032379 0.179328 2.044334 0.251059 2.187796 0.251059C2.307347 0.251059 2.331258 0.203238 2.438854 0.02391L6.001494 -6.204732L6.443836 -0.02391C6.455791 0.179328 6.467746 0.251059 6.611208 0.251059C6.73076 0.251059 6.766625 0.191283 6.862267 0.02391L10.795517 -6.838356Z'/>
</defs>
<g id='eq16-page1'>
<use x='0' y='0' xlink:href='#eq16-g0-87'/>
</g>
</svg> is the wrench (force and torque) ap­plied on the end-ef­fect­or.
Us­ing this equa­tion we can plan our de­sired tra­ject­or­ies in the end-ef­fect­or,
write some con­trol­ler in that space (like a PD con­trol­ler that we saw
pre­vi­ously), and then trans­form them to joint-space (the space that we can
ac­tu­ally con­trol).</p>
<p><em>But how do we get this Jac­obi­an mat­rix?</em> We will not go in­to the de­tails, but
once we have the for­ward kin­emat­ics of our ro­bot (this is either meas­ured or
giv­en by the man­u­fac­turer), then we just take the par­tial de­riv­at­ives and we
get our Jac­obi­an.</p>
</section>
<section id="using-dart-to-perform-task-space-control">
<h4><a href="#using-dart-to-perform-task-space-control">Us­ing DART to per­form task-space con­trol</a></h4>
<p>DART has built-in func­tions that com­pute the Jac­obi­an of any link of our ro­bot,
and thus con­trolling our ro­bots in the task-space is quite easy. Here’s what we
do:</p>
<figure class="m-code-figure">
<pre class="m-code"><span class="cm">/* Get joint velocities of manipulator */</span>
<span class="n">Eigen</span><span class="o">::</span><span class="n">VectorXd</span> <span class="n">dq</span> <span class="o">=</span> <span class="n">_model</span><span class="o">-></span><span class="n">getVelocities</span><span class="p">();</span>
<span class="cm">/* Get full (with orientation) Jacobian of our end-effector */</span>
<span class="n">Eigen</span><span class="o">::</span><span class="n">MatrixXd</span> <span class="n">J</span> <span class="o">=</span> <span class="n">_model</span><span class="o">-></span><span class="n">getBodyNode</span><span class="p">(</span><span class="s">"iiwa_link_ee"</span><span class="p">)</span><span class="o">-></span><span class="n">getWorldJacobian</span><span class="p">();</span>
<span class="cm">/* Get current state of the end-effector */</span>
<span class="n">Eigen</span><span class="o">::</span><span class="n">MatrixXd</span> <span class="n">currentWorldTransformation</span> <span class="o">=</span> <span class="n">_model</span><span class="o">-></span><span class="n">getBodyNode</span><span class="p">(</span><span class="s">"iiwa_link_ee"</span><span class="p">)</span>
<span class="o">-></span><span class="n">getWorldTransform</span><span class="p">().</span><span class="n">matrix</span><span class="p">();</span>
<span class="n">Eigen</span><span class="o">::</span><span class="n">VectorXd</span> <span class="n">currentWorldPosition</span> <span class="o">=</span>
<span class="n">currentWorldTransformation</span><span class="p">.</span><span class="n">block</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
<span class="n">Eigen</span><span class="o">::</span><span class="n">MatrixXd</span> <span class="n">currentWorldOrientation</span> <span class="o">=</span>
<span class="n">currentWorldTransformation</span><span class="p">.</span><span class="n">block</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">3</span><span class="p">);</span>
<span class="n">Eigen</span><span class="o">::</span><span class="n">VectorXd</span> <span class="n">currentWorldSpatialVelocity</span> <span class="o">=</span> <span class="n">_model</span><span class="o">-></span><span class="n">getBodyNode</span><span class="p">(</span><span class="s">"iiwa_link_ee"</span><span class="p">)</span>
<span class="o">-></span><span class="n">getSpatialVelocity</span><span class="p">(</span><span class="n">dart</span><span class="o">::</span><span class="n">dynamics</span><span class="o">::</span><span class="n">Frame</span><span class="o">::</span><span class="n">World</span><span class="p">(),</span>
<span class="n">dart</span><span class="o">::</span><span class="n">dynamics</span><span class="o">::</span><span class="n">Frame</span><span class="o">::</span><span class="n">World</span><span class="p">());</span>
<span class="cm">/* Compute desired forces and torques */</span>
<span class="n">Eigen</span><span class="o">::</span><span class="n">VectorXd</span> <span class="n">linearError</span> <span class="o">=</span> <span class="n">_desiredPosition</span> <span class="o">-</span> <span class="n">currentWorldPosition</span><span class="p">;</span>
<span class="cm">/* PD controller in end-effector position */</span>
<span class="n">Eigen</span><span class="o">::</span><span class="n">VectorXd</span> <span class="n">desiredForces</span> <span class="o">=</span> <span class="n">_pLinearGain</span><span class="o">*</span><span class="n">linearError</span> <span class="o">-</span>
<span class="n">_dLinearGain</span><span class="o">*</span><span class="n">currentWorldSpatialVelocity</span><span class="p">.</span><span class="n">tail</span><span class="p">(</span><span class="mi">3</span><span class="p">);</span>
<span class="cm">/* Special care needs to be taken to compute orientation differences.</span>
<span class="cm"> We use the angle-axis representation */</span>
<span class="n">Eigen</span><span class="o">::</span><span class="n">VectorXd</span> <span class="n">orientationError</span> <span class="o">=</span>
<span class="n">dart</span><span class="o">::</span><span class="n">math</span><span class="o">::</span><span class="n">logMap</span><span class="p">(</span><span class="n">_desiredOrientation</span><span class="o">*</span><span class="n">currentWorldOrientation</span><span class="p">.</span><span class="n">transpose</span><span class="p">());</span>
<span class="cm">/* PD controller in end-effector orientation */</span>
<span class="n">Eigen</span><span class="o">::</span><span class="n">VectorXd</span> <span class="n">desiredTorques</span> <span class="o">=</span> <span class="n">_pOrientationGain</span><span class="o">*</span><span class="n">orientationError</span> <span class="o">-</span>
<span class="n">_dOrientationGain</span><span class="o">*</span><span class="n">currentWorldSpatialVelocity</span><span class="p">.</span><span class="n">head</span><span class="p">(</span><span class="mi">3</span><span class="p">);</span>
<span class="cm">/* Combine forces and torques in one vector */</span>
<span class="n">Eigen</span><span class="o">::</span><span class="n">VectorXd</span> <span class="n">tau</span><span class="p">(</span><span class="mi">6</span><span class="p">);</span>
<span class="n">tau</span><span class="p">.</span><span class="n">head</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span> <span class="o">=</span> <span class="n">desiredTorques</span><span class="p">;</span>
<span class="n">tau</span><span class="p">.</span><span class="n">tail</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span> <span class="o">=</span> <span class="n">desiredForces</span><span class="p">;</span>
<span class="cm">/* Compute final forces + gravity compensation + regularization */</span>
<span class="n">forces</span> <span class="o">=</span> <span class="n">J</span><span class="p">.</span><span class="n">transpose</span><span class="p">()</span><span class="o">*</span><span class="n">tau</span> <span class="o">+</span> <span class="n">_model</span><span class="o">-></span><span class="n">getCoriolisAndGravityForces</span><span class="p">()</span> <span class="o">-</span>
<span class="n">_dRegularization</span><span class="o">*</span><span class="n">dq</span><span class="p">;</span>
<span class="cm">/* Send commands to the robot */</span>
<span class="n">_model</span><span class="o">-></span><span class="n">setCommands</span><span class="p">(</span><span class="n">forces</span><span class="p">);</span></pre>
<p class="m-text m-small m-dim m-noindent">See <a href="https://github.com/mosra/magnum-examples/blob/08cddf48fdfcf5ffe2be51a211c387ea2cc8fa9e/src/dart/DartExample.cpp#L536-L584">this code in the mag­num-ex­amples re­pos­it­ory</a>.</p>
</figure>
<p>For mak­ing the ex­ample, we had to do a few more things, like adding a few
boxes, keep­ing track of their loc­a­tion and up­dat­ing the de­sired end-ef­fect­or
loc­a­tion giv­en the user in­put. Moreover, we ad­ded to our ma­nip­u­lat­or a grip­per
so that it can grasp the boxes. In real­ity, most com­mer­cially avail­able
grip­pers are po­s­i­tion con­trolled (we can­not send torques as we were de­scrib­ing
so far, but we need to give de­sired po­s­i­tions): to sim­u­late that we use
ve­lo­city con­trol for the grip­per and what we have been de­scrib­ing so far for
the rest of the ma­nip­u­lat­or. For this reas­on, we need to keep a mod­el of our
ma­nip­u­lat­or without the grip­per so that DART can gives us all the in­form­a­tion
we need cleanly and eas­ily. There is a way to do it without a copy, but
in­volves keep­ing track of frames and re­mov­ing spe­cif­ic lines from the Jac­obi­an,
and thus, I chose the copy to be more clear.</p>
</section>
</section>
</section>
<section id="why-dart-and-not-bullet">
<h2><a href="#why-dart-and-not-bullet">Why DART and not Bul­let?</a></h2>
<p><a href="https://pybullet.org/wordpress/">Bul­let Phys­ics</a> is one of the most pop­u­lar
open-source phys­ics en­gines avail­able and widely used in the graph­ics and game
in­dustry. Al­though choos­ing which phys­ics lib­rary to use for a pro­ject de­pends
on quite many para­met­ers and factors, I am go­ing to list a few reas­ons why I am
us­ing DART more act­ively than Bul­let:</p>
<ul>
<li>DART is ori­ented to­wards com­puter an­im­a­tion and ro­bot­ics. This means that
it already has im­ple­men­ted a wide range of func­tion­al­ity that is needed for
these areas. A few ex­amples con­sist:<ul>
<li>For­ward/In­verse kin­emat­ics,</li>
<li>For­ward dy­nam­ics,</li>
<li>Jac­obi­ans and their de­riv­at­ives,</li>
<li>Ro­bot dy­nam­ics quant­it­ies (like mass mat­rix, cori­ol­is and grav­ity
forces, etc.),</li>
<li>Ef­fi­cient im­ple­ment­a­tion for ri­gid body chains (al­though Bul­let has
re­cently ad­ded this fea­ture),</li>
<li>Mo­tion plan­ning.</li>
</ul>
</li>
<li>Mod­ern C++11/14 code,</li>
<li>Ac­cur­ate and stable dy­nam­ics for ri­gid body chains (ro­bot­ic sys­tems).</li>
</ul>
<p>Al­though Bul­let is mov­ing to­wards sup­port­ing most of the fea­tures that DART
already has, to me as a ro­boti­cist I find DART more in­tu­it­ive and closer to my
mind of think­ing. Nev­er­the­less, if someone is aim­ing for big open worlds (e.g.,
like in <em>The Eld­er Scrolls: Skyrim</em>) or many ob­jects in­ter­act­ing with each
oth­er, DART is not de­signed for this type of situ­ations and Bul­let will be
sig­ni­fic­antly faster (pos­sibly less ac­cur­ate though).</p>
<p class="m-transition">~ ~ ~</p>
<p>Thank you for read­ing! Be sure to check out the
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/examples-dart.html">full ex­ample code</a>, build it on your ma­chine and play
around with its con­trols.</p>
<aside class="m-note m-dim">
Ques­tions? Share your opin­ion on so­cial net­works: <a href="https://twitter.com/czmosra/status/1155855657842741249">Twit­ter</a></aside>
</section>
Being productive with your tools2019-07-08T00:00:00+02:002019-08-03T00:00:00+02:00Vladimír Vondruštag:blog.magnum.graphics,2019-07-08:/meta/being-productive-with-your-tools/<p>Mag­num is de­veloped with a <a href="https://magnum.graphics/features/#our-zen-garden-philosophy">“Zen Garden” philo­sophy</a>
in mind, fo­cus­ing on pro­ductiv­ity, pre­dict­ab­il­ity and ease of use. Let’s
see how that can ex­tend bey­ond just the lib­rary it­self — in­to your daily
work­flow.</p>
<p>Triggered by an (ad­mit­tedly very harsh) <a href="https://www.positech.co.uk/cliffsblog/2019/07/03/how-to-10x-your-indie-game-development-process/">re­cent art­icle by @cliff­ski</a>,
a re­cent need to re­boot in­to Win­dows 10 in or­der to fix MS­VC 2019 sup­port in
Mag­num (to be hor­ri­fied from the UX of Win­dows 10), and a res­ult­ing dis­cus­sion
on <a href="https://gitter.im/mosra/magnum/archives/2019/07/06?at=5d20d62d95e4122da5c220c2">our Git­ter chat</a>,
I de­cided to share what I think is use­ful for be­ing a pro­duct­ive de­veloper. In
this case with Linux and the KDE Plasma 5 desktop be­ing my sys­tem of choice.</p>
<aside class="m-note m-success">
<h3>Content care: Aug 3, 2019</h3>
<p>Ex­ten­ded the art­icle to men­tion vari­ous al­tern­at­ives for non-Linux users
thanks to sug­ges­tions by <a href="https://twitter.com/Squareys">@Squareys</a> and
<a href="https://twitter.com/freling">@frel­ing</a>.</p>
</aside>
<aside class="m-note m-primary">
The pur­pose of this art­icle is <em>not</em> to say that the tools, OS or lib­rar­ies
you use are <em>bad</em>. I’m only shar­ing the pos­it­ive view, why I think the
tools, OS and lib­rar­ies I use are <em>good</em>.</aside>
<section id="git-is-a-friend-not-a-despised-enemy">
<h2><a href="#git-is-a-friend-not-a-despised-enemy">Git is a friend, not a des­pised en­emy</a></h2>
<p>Think of Git not as of the an­noy­ing ob­scure last-mile tool to “push your code
to cus­tom­ers”, but a tool that can act­ively keep you fo­cused, or­gan­ized, help
you re­view your changes, have your edit his­tory safe and let you eas­ily pick
up in­ter­rup­ted work from yes­ter­day.</p>
<p>I’m reg­u­larly us­ing <code>git status</code> and <code>git diff</code> as a “scope guard” — to
avoid the set of changes in my work­ing copy grow­ing too large. Then I’m us­ing
the in­ter­act­ive <a href="https://git-scm.com/book/en/v2/Git-Tools-Interactive-Staging">git add -i</a>
to pick rel­ev­ant changes and com­mit them in­to as many sep­ar­ate com­mits as
de­sir­able, of­ten us­ing <a href="https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History">git re­base -i</a>
to back-in­teg­rate fixup patches to ex­ist­ing com­mits.</p>
<p>There’s noth­ing easi­er than do­ing a quick <code>git stash</code> to see if your latest
modi­fic­a­tions are to blame for broken tests or if it’s some­thing else.</p>
<p>Some people ar­gue that <a href="https://twitter.com/meshula/status/1121965695909027840">“[their] job is cod­ing, not mak­ing beau­ti­ful com­mit logs”</a>,
but from my ex­per­i­ence scop­ing up a task in­to a se­quence of clean com­mits helps
<em>a lot</em> — if the diff is self-con­tained, makes sense and doesn’t con­tain
un­re­lated changes, you can re­view your work much easi­er and <em>be sure</em> you
didn’t mess any­thing up. It pays off later when in­vest­ig­at­ing bugs as well,
<code>git blame</code> to­geth­er with <code>git bisect</code> are power­ful tools for pin­ning down
is­sues in large or un­fa­mil­i­ar code­bases. But they need a clean his­tory to work
prop­erly.</p>
</section>
<section id="get-to-know-your-editor-shell-and-keyboard-layout">
<h2><a href="#get-to-know-your-editor-shell-and-keyboard-layout">Get to know your ed­it­or, shell and key­board lay­out</a></h2>
<p>While every ed­it­or is dif­fer­ent, there are a few fea­tures I ab­so­lutely <em>de­mand</em>
to con­sider it us­able. If the ed­it­or starts slow, has a typ­ing lag, can’t ap­ply
a con­sist­ent in­dent­a­tion and whitespace policy, doesn’t know block edit­ing or
com­ment­ing/un­com­ment­ing code isn’t a key short­cut, then it’s use­less for me.
<em>Kate</em> from KDE (and everything based on it — KWrite, KDevel­op) is my ed­it­or
of choice. I have to ad­mit I nev­er bothered to learn Vim — mostly be­cause the
cur­rent way I edit files is fast enough, the bot­tle­neck be­ing my brain and not
“how fast I can type”.</p>
<p>Know­ing how to nav­ig­ate with key­board is es­sen­tial as well — far too of­ten I
see seni­or pro­gram­mers with 20+ years of ex­per­i­ence mov­ing across words or
lines only by re­peatedly press­ing ar­row keys like if there was no oth­er way.
<span class="m-label m-warning">Ctrl</span> <span class="m-label m-default">Ar­row</span> skips whole words and there’s
<span class="m-label m-default">Home</span> / <span class="m-label m-default">End</span>, use them! On Mac, how­ever, I was
nev­er able to have those work con­sist­ently across apps and it’s one of reas­ons
why I’d rather SSH in­to a Mac than use its key­board dir­ectly.</p>
<p>Pick a shell where you can work fast — 90% of what you do there is re­peatedly
ex­ecut­ing short com­mands, so it’s es­sen­tial to have this op­tim­ized. The plain
Win­dows <code>cmd.exe</code> is <em>ab­so­lutely ter­rible</em> at that, but most oth­er shells can
be made a joy to work with if you con­fig­ure their his­tory hand­ling. If you are
on Win­dows and can’t or don’t want to use Git Bash (which con­tains <code>readline</code>
and thus can be made us­able), have a look at <a href="http://mridgers.github.io/clink/">Clink</a>.</p>
<figure class="m-code-figure">
<pre class="m-code">"\e[A":history-search-backward
"\e[B":history-search-forward</pre>
<p class="m-noindent">Paste this in­to your <code>/etc/inputrc</code>, open a new ter­min­al, type a com­mand
pre­fix and pres <span class="m-label m-default">Up</span> to auto­com­plete it from typed his­tory.
Simple and in­tu­it­ive. You can thank me later.</p>
</figure>
</section>
<section id="have-your-desktop-work-for-you">
<h2><a href="#have-your-desktop-work-for-you">Have your desktop work for you</a></h2>
<p>I have a single ex­tern­al 27” 4K screen. It prac­tic­ally cov­ers my field of view
and fits everything I can fo­cus on in a single mo­ment, but <em>not more</em>. By
choice — I cur­rently don’t have any de­sire to use more than one screen. What
I usu­ally see else­where is people hav­ing one screen with an IDE and the oth­er
with a single Slack win­dow, an e-mail or a browser, with all the mes­sages,
an­im­ated ads and no­ti­fic­a­tions con­stantly beg­ging for at­ten­tion, only mak­ing
fo­cused work miser­able.</p>
<p>What I have in­stead is six vir­tu­al desktops — one ded­ic­ated for the browser, an­oth­er three for work, one desktop solely for file / pack­age man­age­ment and
one with a mu­sic play­er. This makes it pos­sible to have each vir­tu­al desktop a
single area of fo­cus, com­pared to a pile of win­dows the IDE won’t dis­tract you
when read­ing a PDF and a browser win­dow won’t dis­tract from cod­ing. It’s
im­port­ant that the taskbar shows only win­dows from the cur­rent desktop and
noth­ing else. Re­cently I even turned off all browser push no­ti­fic­a­tions so
activ­ity from one vir­tu­al desktop doesn’t leak in­to oth­ers in any way.</p>
<figure class="m-figure">
<img src="https://static.magnum.graphics/img/blog/meta/being-productive-with-your-tools/taskbar.png" style="width: 595px" />
<figcaption>Desktop switch­er in the taskbar<div class="m-figure-description">
<span class="m-label m-warning">Ctrl</span> <span class="m-label m-default">F1</span>–<span class="m-label m-default">F6</span> short­cuts
for switch­ing. <span class="m-label m-warning">Ctrl</span> <span class="m-label m-default">F1</span> is al­ways the
browser desktop.</div>
</figcaption>
</figure>
<p>Im­port­ant for desktop switch­ing short­cuts is that they’re ab­so­lute (so I don’t
have to think about <em>dir­ec­tion</em>, just the <em>des­tin­a­tion</em>) and that there’s no
an­im­a­tion — if the brain is fo­cused on a par­tic­u­lar screen area, quick
switch­ing to an­oth­er desktop and back will not cause it to lose con­text. That’s
also why I nev­er use <span class="m-label m-warning">Alt</span> <span class="m-label m-default">Tab</span>, it has an
un­pre­dict­able or­der and causes so much visu­al noise that los­ing con­text is
in­ev­it­able. An­oth­er es­sen­tial fea­ture is an abil­ity to make a win­dow stay
al­ways on top or be present on all vir­tu­al desktops — a float­ing con­sole
win­dow with a long-run­ning op­er­a­tion, for ex­ample.</p>
<figure class="m-figure">
<img src="https://static.magnum.graphics/img/blog/meta/being-productive-with-your-tools/always-on-top.png" style="width: 653px" />
<figcaption>Al­ways on top<div class="m-figure-description">
A diff opened in <code>gitk</code> stays on top while edit­ing code in a full­screen
IDE be­low; a “rolled-up” con­sole win­dow with a long-run­ning op­er­a­tion above
it.</div>
</figcaption>
</figure>
</section>
<section id="your-computer-can-be-a-power-house">
<h2><a href="#your-computer-can-be-a-power-house">Your com­puter can be a power-house</a></h2>
<p>It’s com­mon for me to have a browser with 100+ tabs open, two IDEs with ~50
files each, sev­er­al con­sole win­dows each with mul­tiple tabs, file man­ager with
five split tabs, a dozen of PD­Fs open on top and a spread­sheet for
pro­cras­tin­at­ing on my taxes. When I fin­ish my work, I put the laptop to sleep
and when I re­sume work the next day, it’s all there, ex­actly how I left it.
Up­time of 90 days isn’t any­thing ex­traordin­ary either.</p>
<p>A laptop with 16 GB of RAM, of­ten run­ning only at 800 MHz, has no prob­lem
keep­ing up with all that. But it’s im­port­ant that I can <em>rely</em> on the sys­tem to
not do any shady busi­ness in the back­ground — hog­ging the CPU with an
an­ti­vir­us check or down­load­ing giga­bytes of sys­tem up­dates <em>un­less I tell it
to</em> (and then ran­domly re­boot­ing) would be an ab­so­lute showstop­per.</p>
</section>
<section id="little-big-things">
<h2><a href="#little-big-things">Little Big Things</a></h2>
<p>On KDE Plasma, if I press <span class="m-label m-warning">Alt</span> <span class="m-label m-default">F2</span>, <em>KRun­ner</em>,
a popup search win­dow, ap­pears. It can open apps, search tabs in my browser, do
simple cal­cu­la­tions but also has <a href="https://github.com/domschrei/krunner-symbols">a plu­gin</a>
that gives me ac­cess to a data­base of pre-defined sym­bols — wheth­er I need an
em-dash for a tweet, a trade­mark char­ac­ter or a ¯\_(ツ)_/¯ re­sponse for a
chat. A crit­ic­al re­quire­ment is that it has to work <em>pre­dict­ably</em> and without
any delay; typ­ing a known pre­fix and press­ing <span class="m-label m-default">Enter</span> will
<em>al­ways</em> give the same res­ult, no mat­ter how fast I type.</p>
<img class="m-image" src="https://static.magnum.graphics/img/blog/meta/being-productive-with-your-tools/fliptable.png" style="width: 566px" />
<p>An­oth­er very handy thing is a glob­al key­board his­tory. More of­ten than not, you
need to copy sev­er­al things at once, not just one. Or you ac­ci­dent­ally copy
some­thing and lose the pre­cious clip­board con­tents. Es­pe­cially when you need to
switch win­dows or desktops to copy mul­tiple things, the visu­al noise will make
your brain go out of <em>the zone</em> very quickly. With <em>Klip­per</em> I can use
<span class="m-label m-warning">Ctrl</span> <span class="m-label m-warning">Alt</span> <span class="m-label m-default">Up</span> or
<span class="m-label m-default">Down</span> to pick a dif­fer­ent entry from the clip­board his­tory.</p>
</section>
<section id="python-is-the-best-calculator-shell-and-knife">
<h2><a href="#python-is-the-best-calculator-shell-and-knife">Py­thon is the best cal­cu­lat­or, shell and knife</a></h2>
<p>It’s a good idea to have a pen and a piece of pa­per on your desk, es­pe­cially
when you are cod­ing visu­al things. Us­ing it to cal­cu­late a dot product by hand
<em>isn’t</em>. A ter­min­al win­dow with an in­ter­act­ive Py­thon in­stance is a much bet­ter
tool. And with Mag­num now get­ting <a href="https://doc.magnum.graphics/python/">Py­thon bind­ings</a>,
it has everything needed.</p>
<figure class="m-code-figure">
<pre class="m-code"><span class="gp">>>> </span><span class="kn">from</span> <span class="nn">magnum</span> <span class="kn">import</span> <span class="o">*</span>
<span class="gp">>>> </span><span class="n">Matrix3</span><span class="o">.</span><span class="n">rotation</span><span class="p">(</span><span class="n">Deg</span><span class="p">(</span><span class="mi">45</span><span class="p">))</span>
<span class="go">Matrix(0.707107, -0.707107, 0,</span>
<span class="go"> 0.707107, 0.707107, 0,</span>
<span class="go"> 0, 0, 1)</span></pre>
<p class="m-noindent">Quick, where are the minus signs in a 2D ro­ta­tion mat­rix?</p>
</figure>
<p>Py­thon is the go-to choice also for all string-pro­cessing shell scripts longer
than one line — in­stead of <abbr title="trying to Google">try­ing to re­mem­ber</abbr> how
to use <code>awk</code> and <code>cut</code> in­side a <code class="sh m-code"><span class="k">while</span></code> loop in Bash, whip that up in
Py­thon. It’ll be easi­er to de­bug, ex­tend and you wouldn’t need to learn the
ob­scure tools again a week later.</p>
</section>
<section id="fast-iteration-times-are-key">
<h2><a href="#fast-iteration-times-are-key">Fast it­er­a­tion times are key</a></h2>
<p>There’s no worse pro­ductiv­ity killer than a tool that makes me wait un­til an
op­er­a­tion is done. That’s a forced in­ter­rup­tion and my brain im­me­di­ately gives
up on all con­text. I can it­er­ate or core APIs in Mag­num ba­sic­ally without
in­ter­rup­tion, in­cre­ment­al com­pil­a­tion tak­ing few seconds at most. Then, with
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/classCorrade_1_1Utility_1_1Tweakable.html">Util­ity::Tweak­able</a>, I can
<a href="https://twitter.com/czmosra/status/1059841188583366656">live-edit con­stants in a run­ning app</a>
for even faster turn­around times.</p>
<p>In con­trast, Mag­num’s <a href="https://doc.magnum.graphics/python/">Py­thon bind­ings</a> are done with py­bind11, which ex­poses
a very simple API do­ing very com­plex things un­der­neath. How­ever I soon got in­to
a state where the it­er­a­tion time of a single com­pile & link got to al­most a
minute — the whole en­gine with 800 tar­gets com­piles from scratch faster than
that. To stay oc­cu­pied dur­ing this “down­time”, I tem­por­ar­ily switch to an­oth­er
task, but the con­text switch over­head slowly makes the fo­cus dis­ap­pear.</p>
</section>
<section id="have-a-stack-you-can-trust">
<h2><a href="#have-a-stack-you-can-trust">Have a stack you can trust …</a></h2>
<p>With Mag­num not far from be­ing a dec­ade old, I have the lux­ury of re­ly­ing on a
ma­ture, stable and well-tested code­base. De­vel­op­ing new things on top of a
trus­ted stack is a breeze, be­cause com­bin­ing well-tested and well-un­der­stood
build­ing blocks most of­ten leads to the res­ult be­hav­ing cor­rectly as well —
with any de­bug­ging hap­pen­ing only on the sur­face level.</p>
<p>This ex­tends to provid­ing sup­port as well — know­ing the in­tern­als well I can
quickly nar­row down a re­por­ted prob­lem, re­motely dia­gnose it by ask­ing just a
few ques­tions and provide either a solu­tion or a work­around al­most im­me­di­ately.</p>
</section>
<section id="and-several-alternatives-for-the-stacks-you-can-t">
<h2><a href="#and-several-alternatives-for-the-stacks-you-can-t">… and sev­er­al al­tern­at­ives for the stacks you can’t</a></h2>
<p>Not everything is a “Zen Garden”, though — there’s the OS, GPU drivers, third
party lib­rar­ies, com­pilers and hard­ware, each at a vari­ous state of sta­bil­ity.
For those it’s im­port­ant to al­ways have an al­tern­at­ive im­ple­ment­a­tion to test
on — if an im­age fails to load with one plu­gin, try with an­oth­er. If a shader
works flaw­lessly on one GPU, it might as well crash and burn on an­oth­er.</p>
<p>Try to primar­ily de­vel­op against the most con­form­ing im­ple­ment­a­tion (of a
com­piler, stand­ard lib­rary, GPU driver, file format load­er) and reg­u­larly test
on at least one oth­er, to veri­fy your as­sump­tions. In­vest­ing a week (or even a
month) of your time in­to set­ting up a CI test mat­rix that does auto­mat­ic
test­ing for you on sev­er­al dif­fer­ent plat­forms, ideally in­clud­ing GPU code,
will pay back mul­tiple times.</p>
<figure class="m-figure">
<img src="https://static.magnum.graphics/img/blog/meta/being-productive-with-your-tools/build-status.png" style="width: 396px" />
<figcaption>Build Mat­rix<div class="m-figure-description">
And that’s <a href="https://magnum.graphics/build-status/">just the top half</a>.</div>
</figcaption>
</figure>
</section>
<section id="web-is-unfortunately-just-too-damn-slow">
<h2><a href="#web-is-unfortunately-just-too-damn-slow">Web is un­for­tu­nately just too damn slow</a></h2>
<p>Ever since I made the light­weight Mag­num web­site and docs, the rest of the
In­ter­net com­par­at­ively star­ted to feel <em>much slower</em>. While I can jump to a
doc­u­ment­a­tion of <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/namespaceMagnum_1_1MeshTools.html#a26ac55caa2c1391e0043e3d82193090c">MeshTools::gen­er­ateS­mooth­Nor­mals()</a> in a frac­tion of a
second, nav­ig­at­ing to a par­tic­u­lar is­sue of a par­tic­u­lar pro­ject through the
Git­Hub UI to write a reply is <em>so slow</em> that it’s faster for me to just re­call
its num­ber and <em>type the whole ad­dress out</em>.</p>
<p>For ex­tern­al lib­rar­ies I’m us­ing, I of­ten end up re­gen­er­at­ing the docs my­self
us­ing <a href="https://mcss.mosra.cz/documentation/">m.css</a>. The search func­tion­al­ity
of <em>any</em> Sphinx-gen­er­ated docs is a <strong>bad joke</strong> and Googling the ac­tu­al
be­ha­vi­or of Py­thon’s <code>splitlines()</code> isn’t nearly as straight­for­ward as it
should be either. I ended up build­ing my own <em>search­able</em> copy of
<a href="https://tmp.mosra.cz/eigen-docs/">Ei­gen doc­u­ment­a­tion</a>, did a sim­il­ar thing
for An­droid NDK and
<a href="https://twitter.com/czmosra/status/1151938326003363840">I’m plan­ning to do that for Py­thon stand­ard lib­rary as well</a>.</p>
<p>Worth men­tion­ing is <a href="https://zealdocs.org/">Zeal</a>, provid­ing off­line docs for
vari­ous lib­rar­ies. I nev­er used it my­self, so can’t com­ment fur­ther.</p>
<aside class="m-note m-dim">
Ques­tions? Com­plaints? Share your opin­ion on so­cial net­works:
<a href="https://twitter.com/czmosra/status/1148279430084775939">Twit­ter</a>,
Red­dit <a href="https://www.reddit.com/r/programming/comments/canws2/on_being_productive_with_your_tools/">r/pro­gram­ming</a>,
<a href="https://www.reddit.com/r/linux/comments/cao1w8/on_being_productive_with_your_tools/">r/linux</a>,
<a href="https://www.reddit.com/r/kde/comments/caoo6c/on_being_productive_with_your_tools/">r/kde</a>,
<a href="https://news.ycombinator.com/item?id=20384640">Hack­er News</a></aside>
</section>
New Application implementation for Emscripten2019-06-05T00:00:00+02:002019-06-05T00:00:00+02:00Vladimír Vondruštag:blog.magnum.graphics,2019-06-05:/announcements/new-emscripten-application-implementation/<p>If you build your Mag­num apps for the web, you can now make use of
a new fea­ture-packed, smal­ler and more power-ef­fi­cient ap­plic­a­tion
im­ple­ment­a­tion. It is us­ing the Em­scripten HTM­L5 APIs dir­ectly in­stead of
go­ing through com­pat­ib­il­ity lay­ers.</p>
<p>Un­til now, the <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Platform_1_1Sdl2Application.html">Plat­form::Sdl2Ap­plic­a­tion</a> was the go-to solu­tion for most
plat­forms in­clud­ing the web and mo­bile. How­ever, not every­body needs all the
fea­tures SDL provides and, es­pe­cially on Em­scripten, apart from sim­pli­fy­ing
port­ing it doesn’t really add any­thing ex­tra on top. On the con­trary, the
ad­di­tion­al lay­er of trans­la­tion between HTM­L5 and SDL APIs in­creases the
ex­ecut­able size and makes some fea­tures un­ne­ces­sar­ily hard to ac­cess.</p>
<p>To solve that, the new <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Platform_1_1EmscriptenApplication.html">Plat­form::Em­scrip­tenAp­plic­a­tion</a>, con­trib­uted in
<a href="https://github.com/mosra/magnum/issues/300">mosra/mag­num#300</a> by <a href="https://github.com/Squareys">@Squareys</a>, is us­ing Em­scripten HTM­L5 APIs
dir­ectly, open­ing new pos­sib­il­it­ies while mak­ing the code smal­ler and more
ef­fi­cient.</p>
<section id="sdl2-vs-sdl2">
<h2><a href="#sdl2-vs-sdl2">“SDL2” vs SDL2</a></h2>
<p>Since there’s some con­fu­sion about SDL among Em­scripten users, let’s cla­ri­fy
that first. Us­ing SDL in Em­scripten is ac­tu­ally pos­sible in two ways — the
im­pli­cit sup­port, im­ple­men­ted in <a href="https://github.com/emscripten-core/emscripten/blob/incoming/src/library_sdl.js">lib­rar­y_sdl.js</a>,
gives you a slightly strange hy­brid of SDL1 and SDL2 in a re­l­at­ively small
pack­age. Not all SDL2 APIs are present there, on the oth­er hand it has enough
from SDL2 to make it a vi­able al­tern­at­ive to the SDL2 every­one is used to. This
is what <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Platform_1_1Sdl2Application.html">Plat­form::Sdl2Ap­plic­a­tion</a> is us­ing.</p>
<p>The oth­er way is a “full SDL2”, avail­able if you pass <code>-s USE_SDL=2</code> to the
linker. Two years ago we tried to re­move all Em­scripten-spe­cif­ic work­arounds
from <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Platform_1_1Sdl2Application.html">Plat­form::Sdl2Ap­plic­a­tion</a> by switch­ing to this full SDL2, but
quickly real­ized it was a bad de­cision — in total it re­moved 30 lines of
code, but caused the res­ult­ing code to be al­most 600 kB lar­ger. The size
in­crease was so ser­i­ous that it didn’t war­rant the very minor im­prove­ments in
code main­tain­ab­il­ity. For the re­cord, the ori­gin­al pull re­quest is archived at
<a href="https://github.com/mosra/magnum/issues/218">mosra/mag­num#218</a>.</p>
</section>
<section id="the-sdl-free-emscriptenapplication">
<h2><a href="#the-sdl-free-emscriptenapplication">The SDL-free Em­scrip­tenAp­plic­a­tion</a></h2>
<p>All ap­plic­a­tion im­ple­ment­a­tions in Mag­num strive for al­most full API
com­pat­ib­il­ity, with the goal of mak­ing it pos­sible to use an im­ple­ment­a­tion
op­tim­al for chosen plat­form and use case. This was already the case with
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Platform_1_1GlfwApplication.html">Plat­form::GlfwAp­plic­a­tion</a> and <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Platform_1_1Sdl2Application.html">Plat­form::Sdl2Ap­plic­a­tion</a>, where
switch­ing from one to the oth­er is in 90% cases just a mat­ter of us­ing a
dif­fer­ent <code class="m-code"><span class="cp">#include</span></code> and passing a dif­fer­ent com­pon­ent to CMake’s
<code class="m-code"><span class="nb">find_package</span><span class="p">()</span></code>.</p>
<p>The new <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Platform_1_1EmscriptenApplication.html">Plat­form::Em­scrip­tenAp­plic­a­tion</a> con­tin­ues in this fash­ion and we
por­ted all ex­ist­ing ex­amples and tools that formerly used
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Platform_1_1Sdl2Application.html">Plat­form::Sdl2Ap­plic­a­tion</a> to it to en­sure it works in broad use cases.
Apart from that, the new im­ple­ment­a­tion fixes some of the long-stand­ing is­sues
like mis­cal­cu­lated event co­ordin­ates on mo­bile web browsers or the
<span class="m-label m-default">De­lete</span> key leak­ing through text in­put events.</p>
<aside class="m-note m-info">
Only two widely used APIs are miss­ing from it right now — the
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Platform_1_1Sdl2Application.html#ab6e25f5a4a990a9329115ebd0335561a">Plat­form::Sdl2Ap­plic­a­tion::tick­Event()</a> and
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Platform_1_1Sdl2Application.html#a745b3b3793fb1a65e30d1fce72c0625e">Plat­form::Sdl2Ap­plic­a­tion::set­Swap­In­ter­val()</a>. The former will get
ad­ded to­geth­er with an equi­val­ent in GLFW ap­plic­a­tion, while the second
will be ex­posed dif­fer­ently, al­low­ing to use the ex­ten­ded browser APIs.
Right now it’s enough to <code class="m-code"><span class="cp">#ifdef</span></code> around it, as browsers, un­like
most desktop plat­forms, en­able VSync by de­fault.</aside>
</section>
<section id="power-efficient-idle-behavior">
<h2><a href="#power-efficient-idle-behavior">Power-ef­fi­cient idle be­ha­vi­or</a></h2>
<p>Since the very be­gin­ning, all Mag­num ap­plic­a­tion im­ple­ment­a­tions de­fault to
re­draw­ing only when needed in or­der to save power — be­cause Mag­num is not
just for games that have to an­im­ate some­thing every frame, it doesn’t make
sense to use up all sys­tem re­sources by de­fault. While this is simple to
im­ple­ment ef­fi­ciently on desktop apps where the ap­plic­a­tion has the full
con­trol over the main loop (and thus can block in­def­in­itely wait­ing for an
in­put event), it’s harder in the call­back-based browser en­vir­on­ment.</p>
<p>The ori­gin­al <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Platform_1_1Sdl2Application.html">Plat­form::Sdl2Ap­plic­a­tion</a> makes use of
<a class="m-flat" href="https://emscripten.org/docs/api_reference/emscripten.h.html#c.emscripten_set_main_loop">em­scripten_­set_­main_loop()</a>,
which peri­od­ic­ally calls <a class="m-flat" href="https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame">win­dow.re­quest­An­im­a­tion­Frame()</a>
in or­der to main­tain a steady frame rate. For apps that need to re­draw only
when needed this means the call­back will be called 60 times per second only to
be a no-op. While that’s still sig­ni­fic­antly more ef­fi­cient than draw­ing
everything each time, it still means the browser has to wake up 60 times per
second to do noth­ing.</p>
<p><a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Platform_1_1EmscriptenApplication.html">Plat­form::Em­scrip­tenAp­plic­a­tion</a> in­stead makes use of
<a class="m-flat" href="https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame">re­quest­An­im­a­tion­Frame()</a>
dir­ectly — the next an­im­a­tion frame is im­pli­citly sched­uled, but can­celled
again after the draw event if the app doesn’t wish to re­draw im­me­di­ately again.
That takes the best of both worlds — re­draws are still VSync’d, but the
browser is not loop­ing need­lessly if the app just wants to wait with a re­draw
for the next in­put event. To give you some num­bers, be­low is a ten-second
out­put of Chrome’s per­form­ance mon­it­or com­par­ing SDL and Em­scripten app
im­ple­ment­a­tion wait­ing for an in­put event. You can re­pro­duce this with the
<a href="https://magnum.graphics/showcase/player/">Mag­num Play­er</a> — no mat­ter how com­plex
an­im­ated scene you throw at it, if you pause the an­im­a­tion it will use as much
CPU as a plain stat­ic text web page.</p>
<div class="m-row">
<div class="m-col-m-6">
<figure class="m-flat m-figure">
<img src="https://static.magnum.graphics/img/blog/announcements/new-emscripten-application-implementation/sdl2.png" style="width: 270px" />
<figcaption>Idle <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Platform_1_1Sdl2Application.html">Sdl2Ap­plic­a­tion</a></figcaption>
</figure>
</div>
<div class="m-col-m-6">
<figure class="m-flat m-figure">
<img src="https://static.magnum.graphics/img/blog/announcements/new-emscripten-application-implementation/emscripten.png" style="width: 270px" />
<figcaption>Idle <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Platform_1_1EmscriptenApplication.html">Em­scrip­tenAp­plic­a­tion</a></figcaption>
</figure>
</div>
</div>
</section>
<section id="dpi-awareness-revisited">
<h2><a href="#dpi-awareness-revisited">DPI aware­ness re­vis­ited</a></h2>
<p>Ar­gu­ably to sim­pli­fy port­ing, the Em­scripten SDL emu­la­tion re­cal­cu­lates all
in­put event co­ordin­ates to match frame­buf­fer pixels. The ac­tu­al DPI scal­ing
(or device pixel ra­tio) is then be­ing ex­posed through <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Platform_1_1Sdl2Application.html#a0684881f21328c4966a12d0220e3dfa9">dpiS­cal­ing()</a>,
mak­ing it be­have the same as Linux, Win­dows and An­droid on high-DPI screens. In
con­trast, HTM­L5 APIs be­have like ma­cOS / iOS and
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Platform_1_1EmscriptenApplication.html">Plat­form::Em­scrip­tenAp­plic­a­tion</a> fol­lows that be­ha­vi­or —
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Platform_1_1EmscriptenApplication.html#afa506715580500ad830656aa2a739b11">frame­buf­fer­Size()</a>
thus matches device pixels while <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Platform_1_1EmscriptenApplication.html#afc078e274c216441c8f7689e8a8d8093">win­dowSize()</a>
(to which all events are re­lated) is smal­ler on HiDPI sys­tems. For more
in­form­a­tion, check out the <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Platform_1_1EmscriptenApplication.html#Platform-EmscriptenApplication-dpi">DPI aware­ness</a> docs.</p>
<aside class="m-note m-success">
It’s im­port­ant to note that even though dif­fer­ent plat­forms ex­pose DPI
aware­ness in a dif­fer­ent way, Mag­num APIs are de­signed in a way that makes
it pos­sible to have the same code be­have cor­rectly every­where. The
sep­ar­a­tion in­to <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Platform_1_1Sdl2Application.html#a0684881f21328c4966a12d0220e3dfa9">dpiS­cal­ing()</a>,
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Platform_1_1Sdl2Application.html#a2d5a3fb1e725a268cf43294a0f7a31f1">frame­buf­fer­Size()</a> and
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Platform_1_1Sdl2Application.html#a878988745a29773a4b7d224ef53f425c">win­dowSize()</a> prop­er­ties
is mainly for a more fine-grained con­trol where needed.</aside>
</section>
<section id="executable-size-savings">
<h2><a href="#executable-size-savings">Ex­ecut­able size sav­ings</a></h2>
<p>Be­cause we didn’t end up us­ing the heavy­weight “full SDL2” in the first place,
the dif­fer­ence in ex­ecut­able size is noth­ing ex­treme — in total, in a Re­lease
WebAssembly build, the JS size got smal­ler by about 20 kB, while the WASM file
stays roughly the same.</p>
<div class="m-plot">
<svg viewBox="0 0 576 142.56">
<defs>
<style type="text/css">*{stroke-linejoin: round; stroke-linecap: butt}</style>
</defs>
<g id="figure_1">
<g id="axes_1">
<g id="patch_1">
<path d="M 123.978125 99.505313 L 564.12 99.505313 L 564.12 27.396406 L 123.978125 27.396406 z" class="m-background"/>
</g>
<g id="plot1-value0-0"><title>111.9 kB</title>
<path d="M 123.978125 30.674084 L 179.613931 30.674084 L 179.613931 49.40367 L 123.978125 49.40367 z" clip-path="url(#pc11ea89710)" class="m-bar m-success"/>
</g>
<g id="plot1-value0-1"><title>74.4 kB</title>
<path d="M 123.978125 54.086066 L 160.969224 54.086066 L 160.969224 72.815652 L 123.978125 72.815652 z" clip-path="url(#pc11ea89710)" class="m-bar m-success"/>
</g>
<g id="plot1-value0-2"><title>52.1 kB</title>
<path d="M 123.978125 77.498049 L 149.881838 77.498049 L 149.881838 96.227635 L 123.978125 96.227635 z" clip-path="url(#pc11ea89710)" class="m-bar m-success"/>
</g>
<g id="plot1-value1-0"><title>731.2 kB</title>
<path d="M 179.613931 30.674084 L 543.160863 30.674084 L 543.160863 49.40367 L 179.613931 49.40367 z" clip-path="url(#pc11ea89710)" class="m-bar m-info"/>
</g>
<g id="plot1-value1-1"><title>226.3 kB</title>
<path d="M 160.969224 54.086066 L 273.483817 54.086066 L 273.483817 72.815652 L 160.969224 72.815652 z" clip-path="url(#pc11ea89710)" class="m-bar m-info"/>
</g>
<g id="plot1-value1-2"><title>226.0 kB</title>
<path d="M 149.881838 77.498049 L 262.247274 77.498049 L 262.247274 96.227635 L 149.881838 96.227635 z" clip-path="url(#pc11ea89710)" class="m-bar m-info"/>
</g>
<g id="matplotlib.axis_1">
<g id="xtick_1">
<g id="line2d_1">
<defs>
<path id="mc4bea1de13" d="M 0 0 L 0 3.5" class="m-line"/>
</defs>
<g>
<use xlink:href="#mc4bea1de13" x="123.978125" y="99.505313"/>
</g>
</g>
<g id="text_1">
<text class="m-label" style="text-anchor: middle" x="123.978125" y="114.337656" transform="rotate(-0, 123.978125, 114.337656)">0</text>
</g>
</g>
<g id="xtick_2">
<g id="line2d_2">
<g>
<use xlink:href="#mc4bea1de13" x="173.697344" y="99.505313"/>
</g>
</g>
<g id="text_2">
<text class="m-label" style="text-anchor: middle" x="173.697344" y="114.337656" transform="rotate(-0, 173.697344, 114.337656)">100</text>
</g>
</g>
<g id="xtick_3">
<g id="line2d_3">
<g>
<use xlink:href="#mc4bea1de13" x="223.416564" y="99.505313"/>
</g>
</g>
<g id="text_3">
<text class="m-label" style="text-anchor: middle" x="223.416564" y="114.337656" transform="rotate(-0, 223.416564, 114.337656)">200</text>
</g>
</g>
<g id="xtick_4">
<g id="line2d_4">
<g>
<use xlink:href="#mc4bea1de13" x="273.135783" y="99.505313"/>
</g>
</g>
<g id="text_4">
<text class="m-label" style="text-anchor: middle" x="273.135783" y="114.337656" transform="rotate(-0, 273.135783, 114.337656)">300</text>
</g>
</g>
<g id="xtick_5">
<g id="line2d_5">
<g>
<use xlink:href="#mc4bea1de13" x="322.855002" y="99.505313"/>
</g>
</g>
<g id="text_5">
<text class="m-label" style="text-anchor: middle" x="322.855002" y="114.337656" transform="rotate(-0, 322.855002, 114.337656)">400</text>
</g>
</g>
<g id="xtick_6">
<g id="line2d_6">
<g>
<use xlink:href="#mc4bea1de13" x="372.574222" y="99.505313"/>
</g>
</g>
<g id="text_6">
<text class="m-label" style="text-anchor: middle" x="372.574222" y="114.337656" transform="rotate(-0, 372.574222, 114.337656)">500</text>
</g>
</g>
<g id="xtick_7">
<g id="line2d_7">
<g>
<use xlink:href="#mc4bea1de13" x="422.293441" y="99.505313"/>
</g>
</g>
<g id="text_7">
<text class="m-label" style="text-anchor: middle" x="422.293441" y="114.337656" transform="rotate(-0, 422.293441, 114.337656)">600</text>
</g>
</g>
<g id="xtick_8">
<g id="line2d_8">
<g>
<use xlink:href="#mc4bea1de13" x="472.01266" y="99.505313"/>
</g>
</g>
<g id="text_8">
<text class="m-label" style="text-anchor: middle" x="472.01266" y="114.337656" transform="rotate(-0, 472.01266, 114.337656)">700</text>
</g>
</g>
<g id="xtick_9">
<g id="line2d_9">
<g>
<use xlink:href="#mc4bea1de13" x="521.73188" y="99.505313"/>
</g>
</g>
<g id="text_9">
<text class="m-label" style="text-anchor: middle" x="521.73188" y="114.337656" transform="rotate(-0, 521.73188, 114.337656)">800</text>
</g>
</g>
<g id="text_10">
<text class="m-label" style="text-anchor: middle" x="344.049062" y="128.425" transform="rotate(-0, 344.049062, 128.425)">kB</text>
</g>
</g>
<g id="matplotlib.axis_2">
<g id="ytick_1">
<g id="line2d_10">
<defs>
<path id="mae1c673641" d="M 0 0 L -3.5 0" class="m-line"/>
</defs>
<g>
<use xlink:href="#mae1c673641" x="123.978125" y="40.038877"/>
</g>
</g>
<g id="text_11">
<text class="m-label" transform="translate(44.022344 38.911377)">Sdl2Application</text>
<text class="m-label" transform="translate(116.978125 50.565189)"/>
</g>
</g>
<g id="ytick_2">
<g id="line2d_11">
<g>
<use xlink:href="#mae1c673641" x="123.978125" y="63.450859"/>
</g>
</g>
<g id="text_12">
<text class="m-label" transform="translate(44.022344 62.323359)">Sdl2Application</text>
<text class="m-label" transform="translate(116.978125 73.977172)"/>
</g>
</g>
<g id="ytick_3">
<g id="line2d_12">
<g>
<use xlink:href="#mae1c673641" x="123.978125" y="86.862842"/>
</g>
</g>
<g id="text_13">
<text class="m-label" style="text-anchor: end" x="116.978125" y="90.779014" transform="rotate(-0, 116.978125, 90.779014)">EmscriptenApplication</text>
</g>
</g>
</g>
<g id="text_14">
<text class="m-label m-dim" transform="translate(116.978125 38.171242)"/>
<text class="m-label m-dim" transform="translate(53.540781 49.825054)">-s USE_SDL=2</text>
</g>
<g id="text_15">
<text class="m-label m-dim" transform="translate(116.978125 61.583224)"/>
<text class="m-label m-dim" transform="translate(53.540781 73.237037)">-s USE_SDL=1</text>
</g>
<g id="text_16">
<text class="m-title" style="text-anchor: middle" x="344.049062" y="21.396406" transform="rotate(-0, 344.049062, 21.396406)">Download size (*.js, *.wasm)</text>
</g>
</g>
</g>
<defs>
<clipPath id="pc11ea89710">
<rect x="123.978125" y="27.396406" width="440.141875" height="72.108906"/>
</clipPath>
</defs>
</svg>
</div>
</section>
<section id="minimal-runtime-or-brain-surgery-with-a-chainsaw">
<h2><a href="#minimal-runtime-or-brain-surgery-with-a-chainsaw">Min­im­al runtime, or brain sur­gery with a chain­saw</a></h2>
<p>On the oth­er hand, since the new ap­plic­a­tion doesn’t use any of the <code class="m-code"><span class="n">emscripten_set_main_loop</span><span class="p">()</span></code> APIs from <code>library_browser.js</code>, it makes it
a good can­did­ate for play­ing with the re­l­at­ively re­cent
<a href="https://github.com/emscripten-core/emscripten/issues/7923">MIN­IM­AL_RUNTIME</a> fea­ture of Em­scripten.
Now, while Mag­num is mov­ing in the right dir­ec­tion, it’s not yet in a state
where this would “just work”. Sup­port­ing <code>MINIMAL_RUNTIME</code> re­quires either
mov­ing fast and break­ing lots of things or have the APIs slowly evolve in­to a
state that makes it pos­sible. Be­cause re­li­able back­wards com­pat­ib­il­ity and
pain­less up­grade path is a valu­able as­set in our port­fo­lio, we chose the
lat­ter — it will even­tu­ally hap­pen, but not right now. An­oth­er reas­on is that
while Mag­num it­self can be highly op­tim­ized to be com­pat­ible with min­im­al
runtime, the usu­al ap­plic­a­tion code is not able to sat­is­fy those re­quire­ments
without re­mov­ing and re­writ­ing most third-party de­pend­en­cies.</p>
<p>That be­ing said, why not spend one af­ter­noon with a chain­saw and try
de­mol­ish­ing the code to see what <em>could</em> come out? It’s how­ever im­port­ant to
note that <code>MINIMAL_RUNTIME</code> is still a very fresh fea­ture and thus it’s very
likely that a lot of code will simply <em>not work</em> with it. All the dis­covered
prob­lems are lis­ted be­low be­cause at this point there are <em>no res­ults at all</em>
when googling them, so hope­fully this helps oth­er people stuck in sim­il­ar
places:</p>
<ul>
<li><a class="m-flat" href="https://en.cppreference.com/w/cpp/utility/program/getenv">std::getenv()</a> or the <code class="m-code"><span class="n">environ</span></code> vari­able (used by
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/classCorrade_1_1Utility_1_1Arguments.html">Util­ity::Ar­gu­ments</a>) res­ults in <code class="m-code"><span class="nx">writeAsciiToMemory</span><span class="p">()</span></code> be­ing
called, which is right now <a href="https://github.com/juj/emscripten/blob/458cc9bfcd42eccfd6294dbd00c373d2dab748f5/src/runtime_strings.js#L2">ex­pli­citly dis­abled</a>
for min­im­al runtime (and thus you either get a fail­ure at runtime or the
Clos­ure Com­piler com­plain­ing about these names be­ing un­defined). Since
Em­scripten’s en­vir­on­ment is just a bunch of hard­coded val­ues and Mag­num is
us­ing Node.js APIs to get the real val­ues for com­mand-line apps any­way,
solu­tion is to simply not use those func­tions.</li>
<li>Right now, Mag­num is us­ing C++ iostreams on three isol­ated places
(<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/classCorrade_1_1Utility_1_1Debug.html">Util­ity::De­bug</a> be­ing the most prom­in­ent) and those uses are
gradu­ally be­ing phased off. On Em­scripten, us­ing any­thing that even
re­motely touches them will make the backend emit calls to
<code class="m-code"><span class="n">llvm_stacksave</span><span class="p">()</span></code> and
<code class="m-code"><span class="n">llvm_stackrestore</span><span class="p">()</span></code>. The <a href="https://github.com/emscripten-core/emscripten/blob/8b518967f71bdeee4d57a98f3bb9b78c0158e931/src/library.js#L1679-L1692">JavaS­cript im­ple­ment­a­tions</a>
then call <code class="m-code"><span class="nx">stackSave</span><span class="p">()</span></code> and <code class="m-code"><span class="nx">stackRestore</span><span class="p">()</span></code> which how­ever do not
get pulled in in <code>MINIMAL_RUNTIME</code>, again res­ult­ing in either a runtime
er­ror every time you call in­to JS (so also all
<code class="m-code"><span class="n">emscripten_set_mousedown_callback</span><span class="p">()</span></code> func­tions) or when you use the
Clos­ure Com­piler. After wast­ing a few hours try­ing to con­vince Em­scripten
to emit these two by adding <code class="m-code"><span class="nx">_llvm_stacksave__deps</span><span class="o">:</span> <span class="p">[</span><span class="s1">'$stackSave'</span><span class="p">]</span></code> the
ul­ti­mate solu­tion was to kill everything stream-re­lated. Con­sid­er­ing
every­one who’s in­ter­ested in <code>MINIMAL_RUNTIME</code> prob­ably did that already,
it ex­plains why this is an­oth­er un­google­able er­ror.</li>
<li>If you use C++ streams, the gen­er­ated JS driver file con­tains a full
JavaS­cript im­ple­ment­a­tion of <code class="m-code"><span class="n">strftime</span><span class="p">()</span></code> and the only way to get rid
of it is re­mov­ing all stream us­age as well. Grep your JS file for <code>Monday</code>
— if it’s there, you have a prob­lem.</li>
<li>JavaS­cript Em­scripten APIs like <code class="m-code"><span class="nx">dynCall</span><span class="p">()</span></code> or <code class="m-code"><span class="nx">allocate</span><span class="p">()</span></code> are not
avail­able and put­ting them in­to either <code class="m-code"><span class="nx">EXTRA_EXPORTED_RUNTIME_METHODS</span></code>
or <code class="m-code"><span class="nx">RUNTIME_FUNCS_TO_IMPORT</span></code> either didn’t do any­thing or moved the
er­ror in­to a dif­fer­ent place. For the former it was pos­sible to work around
it by dir­ectly call­ing one of its spe­cial­iz­a­tions (in that par­tic­u­lar case
<code class="m-code"><span class="nx">dynCall_ii</span><span class="p">()</span></code>), the second res­ul­ted in a frus­trated table­flip and the
rel­ev­ant piece of code get­ting cut off.</li>
</ul>
<p>Be­low is a break­down of vari­ous op­tim­iz­a­tions on a min­im­al ap­plic­a­tion that
does just a frame­buf­fer clear, each step chop­ping an­oth­er bit off the total
down­load size. All sizes are un­com­pressed, built in Re­lease mode with <code>-Oz</code>,
<code>--llvm-lto 1</code> and <code>--closure 1</code>. Later on in the pro­cess,
<a href="https://github.com/google/bloaty">Bloaty McBloat­Face</a> ex­per­i­ment­al
<a href="https://groups.google.com/forum/#!topic/emscripten-discuss/jyLD-D09JUw">WebAssembly sup­port</a> was used to dis­cov­er what func­tions con­trib­ute the most to fi­nal
code size.</p>
<table class="m-table">
<thead>
<tr><th>Op­er­a­tion</th>
<th>JS size</th>
<th>WASM size</th>
</tr>
</thead>
<tbody>
<tr><td>Ini­tial state</td>
<td>52.1 kB</td>
<td>226.3 kB</td>
</tr>
<tr><td>En­abling min­im­al runtime <a class="m-footnote" href="#id8" id="id1">1</a></td>
<td>36.3 kB</td>
<td>224.5 kB</td>
</tr>
<tr><td>Ad­di­tion­al slim­ming flags <a class="m-footnote" href="#id9" id="id2">2</a></td>
<td>35.7 kB</td>
<td>224.5 kB</td>
</tr>
<tr><td>Dis­abling filesys­tem <a class="m-footnote" href="#id10" id="id3">3</a></td>
<td>19.4 kB</td>
<td>224.5 kB</td>
</tr>
<tr><td>Chop­ping off all C++ stream us­age</td>
<td>14.7 kB</td>
<td>83.6 kB</td>
</tr>
<tr><td>En­abling <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/Assert_8h.html#a588d67a03f847239f809eda035d73730">COR­RADE_NO_ASSERT</a></td>
<td>14.7 kB</td>
<td>75.4 kB</td>
</tr>
<tr><td>Re­mov­ing a single use of <a class="m-flat" href="https://en.cppreference.com/w/cpp/algorithm/sort">std::sort()</a> <a class="m-footnote" href="#id11" id="id4">4</a></td>
<td>14.7 kB</td>
<td>69.3 kB</td>
</tr>
<tr><td>Re­mov­ing one <a class="m-flat" href="https://en.cppreference.com/w/cpp/container/unordered_map">std::un­ordered_map</a> <a class="m-footnote" href="#id11" id="id5">4</a></td>
<td>14.7 kB</td>
<td>62.6 kB</td>
</tr>
<tr><td>Us­ing em­mal­loc in­stead of dlmal­loc <a class="m-footnote" href="#id12" id="id6">5</a></td>
<td>14.7 kB</td>
<td>56.3 kB</td>
</tr>
<tr><td>Re­mov­ing all <a class="m-flat" href="https://en.cppreference.com/w/cpp/io/c/fprintf">printf()</a> us­age <a class="m-footnote" href="#id13" id="id7">6</a></td>
<td>14.7 kB</td>
<td>44 kB <em>(es­tim­ate)</em></td>
</tr>
</tbody>
</table>
<div class="m-plot">
<svg viewBox="0 0 576 272.16">
<defs>
<style type="text/css">*{stroke-linejoin: round; stroke-linecap: butt}</style>
</defs>
<g id="figure_1">
<g id="axes_1">
<g id="patch_1">
<path d="M 183.0275 229.105313 L 564.12 229.105313 L 564.12 27.396406 L 183.0275 27.396406 z" class="m-background"/>
</g>
<g id="plot2-value0-0"><title>52.1 kB</title>
<path d="M 183.0275 36.564993 L 250.949364 36.564993 L 250.949364 51.534114 L 183.0275 51.534114 z" clip-path="url(#pe377403d4b)" class="m-bar m-success"/>
</g>
<g id="plot2-value0-1"><title>36.3 kB</title>
<path d="M 183.0275 55.276394 L 230.351179 55.276394 L 230.351179 70.245515 L 183.0275 70.245515 z" clip-path="url(#pe377403d4b)" class="m-bar m-success"/>
</g>
<g id="plot2-value0-2"><title>35.7 kB</title>
<path d="M 183.0275 73.987796 L 229.568969 73.987796 L 229.568969 88.956917 L 183.0275 88.956917 z" clip-path="url(#pe377403d4b)" class="m-bar m-success"/>
</g>
<g id="plot2-value0-3"><title>19.4 kB</title>
<path d="M 183.0275 92.699197 L 208.318943 92.699197 L 208.318943 107.668318 L 183.0275 107.668318 z" clip-path="url(#pe377403d4b)" class="m-bar m-success"/>
</g>
<g id="plot2-value0-4"><title>14.7 kB</title>
<path d="M 183.0275 111.410598 L 202.191634 111.410598 L 202.191634 126.379719 L 183.0275 126.379719 z" clip-path="url(#pe377403d4b)" class="m-bar m-success"/>
</g>
<g id="plot2-value0-5"><title>14.7 kB</title>
<path d="M 183.0275 130.122 L 202.191634 130.122 L 202.191634 145.091121 L 183.0275 145.091121 z" clip-path="url(#pe377403d4b)" class="m-bar m-success"/>
</g>
<g id="plot2-value0-6"><title>14.7 kB</title>
<path d="M 183.0275 148.833401 L 202.191634 148.833401 L 202.191634 163.802522 L 183.0275 163.802522 z" clip-path="url(#pe377403d4b)" class="m-bar m-success"/>
</g>
<g id="plot2-value0-7"><title>14.7 kB</title>
<path d="M 183.0275 167.544802 L 202.191634 167.544802 L 202.191634 182.513923 L 183.0275 182.513923 z" clip-path="url(#pe377403d4b)" class="m-bar m-success"/>
</g>
<g id="plot2-value0-8"><title>14.7 kB</title>
<path d="M 183.0275 186.256203 L 202.191634 186.256203 L 202.191634 201.225325 L 183.0275 201.225325 z" clip-path="url(#pe377403d4b)" class="m-bar m-success"/>
</g>
<g id="plot2-value0-9"><title>14.7 kB</title>
<path d="M 183.0275 204.967605 L 202.191634 204.967605 L 202.191634 219.936726 L 183.0275 219.936726 z" clip-path="url(#pe377403d4b)" class="m-bar m-success"/>
</g>
<g id="plot2-value1-0"><title>226.3 kB</title>
<path d="M 250.949364 36.564993 L 545.972738 36.564993 L 545.972738 51.534114 L 250.949364 51.534114 z" clip-path="url(#pe377403d4b)" class="m-bar m-info"/>
</g>
<g id="plot2-value1-1"><title>224.5 kB</title>
<path d="M 230.351179 55.276394 L 523.027924 55.276394 L 523.027924 70.245515 L 230.351179 70.245515 z" clip-path="url(#pe377403d4b)" class="m-bar m-info"/>
</g>
<g id="plot2-value1-2"><title>224.5 kB</title>
<path d="M 229.568969 73.987796 L 522.245715 73.987796 L 522.245715 88.956917 L 229.568969 88.956917 z" clip-path="url(#pe377403d4b)" class="m-bar m-info"/>
</g>
<g id="plot2-value1-3"><title>224.5 kB</title>
<path d="M 208.318943 92.699197 L 500.995688 92.699197 L 500.995688 107.668318 L 208.318943 107.668318 z" clip-path="url(#pe377403d4b)" class="m-bar m-info"/>
</g>
<g id="plot2-value1-4"><title>83.6 kB</title>
<path d="M 202.191634 111.410598 L 311.1795 111.410598 L 311.1795 126.379719 L 202.191634 126.379719 z" clip-path="url(#pe377403d4b)" class="m-bar m-info"/>
</g>
<g id="plot2-value1-5"><title>75.4 kB</title>
<path d="M 202.191634 130.122 L 300.489303 130.122 L 300.489303 145.091121 L 202.191634 145.091121 z" clip-path="url(#pe377403d4b)" class="m-bar m-info"/>
</g>
<g id="plot2-value1-6"><title>69.3 kB</title>
<path d="M 202.191634 148.833401 L 292.536839 148.833401 L 292.536839 163.802522 L 202.191634 163.802522 z" clip-path="url(#pe377403d4b)" class="m-bar m-info"/>
</g>
<g id="plot2-value1-7"><title>62.6 kB</title>
<path d="M 202.191634 167.544802 L 283.802166 167.544802 L 283.802166 182.513923 L 202.191634 182.513923 z" clip-path="url(#pe377403d4b)" class="m-bar m-info"/>
</g>
<g id="plot2-value1-8"><title>56.3 kB</title>
<path d="M 202.191634 186.256203 L 275.588965 186.256203 L 275.588965 201.225325 L 202.191634 201.225325 z" clip-path="url(#pe377403d4b)" class="m-bar m-info"/>
</g>
<g id="plot2-value1-9"><title>44.0 kB</title>
<path d="M 202.191634 204.967605 L 259.553669 204.967605 L 259.553669 219.936726 L 202.191634 219.936726 z" clip-path="url(#pe377403d4b)" class="m-bar m-info"/>
</g>
<g id="matplotlib.axis_1">
<g id="xtick_1">
<g id="line2d_1">
<defs>
<path id="m6c4cdc507b" d="M 0 0 L 0 3.5" class="m-line"/>
</defs>
<g>
<use xlink:href="#m6c4cdc507b" x="183.0275" y="229.105313"/>
</g>
</g>
<g id="text_1">
<text class="m-label" style="text-anchor: middle" x="183.0275" y="243.937656" transform="rotate(-0, 183.0275, 243.937656)">0</text>
</g>
</g>
<g id="xtick_2">
<g id="line2d_2">
<g>
<use xlink:href="#m6c4cdc507b" x="248.21163" y="229.105313"/>
</g>
</g>
<g id="text_2">
<text class="m-label" style="text-anchor: middle" x="248.21163" y="243.937656" transform="rotate(-0, 248.21163, 243.937656)">50</text>
</g>
</g>
<g id="xtick_3">
<g id="line2d_3">
<g>
<use xlink:href="#m6c4cdc507b" x="313.395761" y="229.105313"/>
</g>
</g>
<g id="text_3">
<text class="m-label" style="text-anchor: middle" x="313.395761" y="243.937656" transform="rotate(-0, 313.395761, 243.937656)">100</text>
</g>
</g>
<g id="xtick_4">
<g id="line2d_4">
<g>
<use xlink:href="#m6c4cdc507b" x="378.579891" y="229.105313"/>
</g>
</g>
<g id="text_4">
<text class="m-label" style="text-anchor: middle" x="378.579891" y="243.937656" transform="rotate(-0, 378.579891, 243.937656)">150</text>
</g>
</g>
<g id="xtick_5">
<g id="line2d_5">
<g>
<use xlink:href="#m6c4cdc507b" x="443.764022" y="229.105313"/>
</g>
</g>
<g id="text_5">
<text class="m-label" style="text-anchor: middle" x="443.764022" y="243.937656" transform="rotate(-0, 443.764022, 243.937656)">200</text>
</g>
</g>
<g id="xtick_6">
<g id="line2d_6">
<g>
<use xlink:href="#m6c4cdc507b" x="508.948152" y="229.105313"/>
</g>
</g>
<g id="text_6">
<text class="m-label" style="text-anchor: middle" x="508.948152" y="243.937656" transform="rotate(-0, 508.948152, 243.937656)">250</text>
</g>
</g>
<g id="text_7">
<text class="m-label" style="text-anchor: middle" x="373.57375" y="258.025" transform="rotate(-0, 373.57375, 258.025)">kB</text>
</g>
</g>
<g id="matplotlib.axis_2">
<g id="ytick_1">
<g id="line2d_7">
<defs>
<path id="m74ed764a78" d="M 0 0 L -3.5 0" class="m-line"/>
</defs>
<g>
<use xlink:href="#m74ed764a78" x="183.0275" y="44.049553"/>
</g>
</g>
<g id="text_8">
<text class="m-label" style="text-anchor: end" x="176.0275" y="47.965725" transform="rotate(-0, 176.0275, 47.965725)">Initial state</text>
</g>
</g>
<g id="ytick_2">
<g id="line2d_8">
<g>
<use xlink:href="#m74ed764a78" x="183.0275" y="62.760955"/>
</g>
</g>
<g id="text_9">
<text class="m-label" style="text-anchor: end" x="176.0275" y="66.677127" transform="rotate(-0, 176.0275, 66.677127)">Enabling minimal runtime</text>
</g>
</g>
<g id="ytick_3">
<g id="line2d_9">
<g>
<use xlink:href="#m74ed764a78" x="183.0275" y="81.472356"/>
</g>
</g>
<g id="text_10">
<text class="m-label" style="text-anchor: end" x="176.0275" y="85.4547" transform="rotate(-0, 176.0275, 85.4547)">Additional slimming flags</text>
</g>
</g>
<g id="ytick_4">
<g id="line2d_10">
<g>
<use xlink:href="#m74ed764a78" x="183.0275" y="100.183757"/>
</g>
</g>
<g id="text_11">
<text class="m-label" style="text-anchor: end" x="176.0275" y="104.166101" transform="rotate(-0, 176.0275, 104.166101)">Disabling filesystem</text>
</g>
</g>
<g id="ytick_5">
<g id="line2d_11">
<g>
<use xlink:href="#m74ed764a78" x="183.0275" y="118.895159"/>
</g>
</g>
<g id="text_12">
<text class="m-label" style="text-anchor: end" x="176.0275" y="122.877502" transform="rotate(-0, 176.0275, 122.877502)">Chopping off all C++ stream usage</text>
</g>
</g>
<g id="ytick_6">
<g id="line2d_12">
<g>
<use xlink:href="#m74ed764a78" x="183.0275" y="137.60656"/>
</g>
</g>
<g id="text_13">
<text class="m-label" style="text-anchor: end" x="176.0275" y="141.522732" transform="rotate(-0, 176.0275, 141.522732)">Enabling CORRADE_NO_ASSERT</text>
</g>
</g>
<g id="ytick_7">
<g id="line2d_13">
<g>
<use xlink:href="#m74ed764a78" x="183.0275" y="156.317961"/>
</g>
</g>
<g id="text_14">
<text class="m-label" style="text-anchor: end" x="176.0275" y="160.344133" transform="rotate(-0, 176.0275, 160.344133)">Removing a single use of std::sort()</text>
</g>
</g>
<g id="ytick_8">
<g id="line2d_14">
<g>
<use xlink:href="#m74ed764a78" x="183.0275" y="175.029363"/>
</g>
</g>
<g id="text_15">
<text class="m-label" style="text-anchor: end" x="176.0275" y="178.945535" transform="rotate(-0, 176.0275, 178.945535)">Removing one std::unordered_map</text>
</g>
</g>
<g id="ytick_9">
<g id="line2d_15">
<g>
<use xlink:href="#m74ed764a78" x="183.0275" y="193.740764"/>
</g>
</g>
<g id="text_16">
<text class="m-label" style="text-anchor: end" x="176.0275" y="197.723108" transform="rotate(-0, 176.0275, 197.723108)">Using emmalloc instead of dlmalloc</text>
</g>
</g>
<g id="ytick_10">
<g id="line2d_16">
<g>
<use xlink:href="#m74ed764a78" x="183.0275" y="212.452165"/>
</g>
</g>
<g id="text_17">
<text class="m-label" style="text-anchor: end" x="176.0275" y="216.478337" transform="rotate(-0, 176.0275, 216.478337)">Removing all printf() usage</text>
</g>
</g>
</g>
<g id="text_18">
<text class="m-title" style="text-anchor: middle" x="373.57375" y="21.396406" transform="rotate(-0, 373.57375, 21.396406)">Download size (*.js, *.wasm)</text>
</g>
</g>
</g>
<defs>
<clipPath id="pe377403d4b">
<rect x="183.0275" y="27.396406" width="381.0925" height="201.708906"/>
</clipPath>
</defs>
</svg>
</div>
<dl class="m-footnote">
<dt id="id8">1</a>.</dt>
<dd><span class="m-footnote"><a href="#id1">^</a></span> <code>-s MINIMAL_RUNTIME=2 -s ENVIRONMENT=web -lGL</code> plus tem­por­ar­ily
en­abling also <code>-s IGNORE_CLOSURE_COMPILER_ERRORS=1</code> in or­der to make
Clos­ure Com­piler sur­vive un­defined vari­able er­rors due to iostreams and
oth­er, men­tioned above</dd>
<dt id="id9">2</a>.</dt>
<dd><span class="m-footnote"><a href="#id2">^</a></span> <code>-s SUPPORT_ERRNO=0 -s GL_EMULATE_GLES_VERSION_STRING_FORMAT=0 -s GL_EXTENSIONS_IN_PREFIXED_FORMAT=0 -s GL_SUPPORT_AUTOMATIC_ENABLE_EXTENSIONS=0 -s GL_TRACK_ERRORS=0 -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1</code>
— ba­sic­ally dis­abling what’s en­abled by de­fault. In par­tic­u­lar, the
<code>GL_EXTENSIONS_IN_PREFIXED_FORMAT=0</code> is not sup­por­ted by Mag­num right
now, caus­ing it to not re­port any ex­ten­sions, but that can be eas­ily fixed.
The res­ult of dis­abling all these is … un­der­whelm­ing.</dd>
<dt id="id10">3</a>.</dt>
<dd><span class="m-footnote"><a href="#id3">^</a></span> <code>-s FILESYSTEM=0</code>, makes Em­scripten not emit any filesys­tem-re­lated
code. Mag­num provides filesys­tem ac­cess through vari­ous APIs
(<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/corrade/namespaceCorrade_1_1Utility_1_1Directory.html">Util­ity::Dir­ect­ory</a>, <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1GL_1_1Shader.html#af2352ecf59303ac5e20ab1b6090e4dea">GL::Shader::add­File()</a>,
<a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1AbstractImporter.html#a36e7675be7b7b8ef3c7b40f000969844">Trade::Ab­strac­tIm­port­er::open­File()</a>, …) and at the mo­ment there’s
no pos­sib­il­ity to com­pile all these out, so this is a nuc­le­ar op­tion that
works.</dd>
<dt id="id11">4.</dt>
<dd><span class="m-footnote">^ <a href="#id4">a</a> <a href="#id5">b</a></span> <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1GL_1_1Context.html">GL::Con­text</a> uses a <a class="m-flat" href="https://en.cppreference.com/w/cpp/algorithm/sort">std::sort()</a> and a
<a class="m-flat" href="https://en.cppreference.com/w/cpp/container/unordered_map">std::un­ordered_map</a> to check for ex­ten­sion pres­ence and print their
list in the en­gine star­tup log. It was fright­en­ing to see a re­mov­al of a
single <a class="m-flat" href="https://en.cppreference.com/w/cpp/algorithm/sort">std::sort()</a> caus­ing a 10% drop in ex­ecut­able size — since
WebGL has roughly two dozens ex­ten­sions (com­pared to > 200 on desktop and
ES), maybe a space-ef­fi­cient al­tern­at­ive im­ple­ment­a­tion could be done
for this tar­get in­stead.</dd>
<dt id="id12">5</a>.</dt>
<dd><span class="m-footnote"><a href="#id6">^</a></span> <a href="https://en.wikipedia.org/wiki/C_dynamic_memory_allocation#dlmalloc">Doug Lea</a>‘s
<a class="m-flat" href="https://en.cppreference.com/w/cpp/memory/c/malloc">mal­loc()</a> is a gen­er­al-pur­pose al­loc­at­or, used by
glibc among oth­ers. It’s very per­form­ant and a good choice for code that
does many small al­loc­a­tions (<a class="m-flat" href="https://en.cppreference.com/w/cpp/container/unordered_map">std::un­ordered_map</a>, I’m look­ing at
you). The down­side is its lar­ger size, and code do­ing few­er lar­ger
al­loc­a­tions might want to use <code>-s MALLOC=emmalloc</code> in­stead. We don’t
pre­tend Mag­num is at that state yet, but oth­er pro­jects
<a href="https://github.com/BinomialLLC/basis_universal/issues/7">su­cess­fully switched to it</a>, shav­ing
more bytes off the down­load size.</dd>
<dt id="id13">6</a>.</dt>
<dd><span class="m-footnote"><a href="#id7">^</a></span> After re­mov­ing all of the above, <a class="m-flat" href="https://en.cppreference.com/w/cpp/io/c/fprintf">std::printf()</a> in­tern­als star­ted
ap­pear­ing at the top of Bloaty’s size re­port, totalling at about 10% of the
ex­ecut­able size. Mag­num doesn’t use it any­where dir­ectly and all trans­it­ive
us­age of it was killed to­geth­er with iostreams; fur­ther dig­ging re­vealed
that it gets called from libc++’s <a href="https://github.com/emscripten-core/emscripten/blob/6e4b98636618989bcd99308391e51aa1b81f4c61/system/lib/libcxxabi/src/abort_message.cpp#L25-L48">abort_mes­sage()</a>,
for ex­ample when abort­ing due to a pure vir­tu­al func­tion call. In­de­pend­ent
meas­ure­ment showed that <a class="m-flat" href="https://en.cppreference.com/w/cpp/io/c/fprintf">std::printf()</a> is around 12 kB of ad­di­tion­al
code com­pared to <a class="m-flat" href="https://en.cppreference.com/w/cpp/io/c/puts">std::puts()</a>, mainly due to the in­her­ent com­plex­ity
of float­ing-point string con­ver­sion. It’s planned to use the much
sim­pler and smal­ler <a href="https://dl.acm.org/citation.cfm?id=3192369">Ryū al­gorithm</a>
for Mag­num’s <a class="m-flat" href="https://en.cppreference.com/w/cpp/io/c/fprintf">std::printf()</a> re­place­ment, ad­di­tion­ally en­sur­ing that
float-to-string con­ver­sions can be <abbr title="dead code elimination">DCE</abbr>-d
when not used. We might be look­ing in­to patch­ing Em­scripten’s libc++ to not
use the ex­pens­ive im­ple­ment­a­tion in its abort mes­sages.</dd>
</dl>
<p>While all of the above size re­duc­tions were done in a hack-and-slash man­ner,
the fi­nal ex­ecut­able still ini­tial­izes and ex­ecutes prop­erly, clear­ing the
frame­buf­fer and re­act­ing to in­put events. For ref­er­ence, check out diffs of
the <code>chainsaw-surgery</code> branches in <a href="https://github.com/mosra/corrade/tree/chainsaw-surgery">cor­rade</a>
and <a href="https://github.com/mosra/magnum/tree/chainsaw-surgery">mag­num</a>.</p>
<p>The above is <em>def­in­itely not</em> all that can be done — es­pe­cially con­sid­er­ing
that re­mov­ing <em>two</em> uses of semi-heavy STL APIs led to al­most 20% save in code
size, there are most prob­ably more of such low hanging fruits. The above tasks
were ad­ded to <a href="https://github.com/mosra/magnum/issues/293">mosra/mag­num#293</a> (if not there already) and will get
gradu­ally in­teg­rated in­to <code>master</code>.</p>
</section>
<section id="conclusion">
<h2><a href="#conclusion">Con­clu­sion</a></h2>
<p>Bright times ahead! The new <a class="m-flat m-text m-strong" href="https://doc.magnum.graphics/magnum/classMagnum_1_1Platform_1_1EmscriptenApplication.html">Plat­form::Em­scrip­tenAp­plic­a­tion</a> is the first
step to truly min­im­al WebAssembly builds and the above hints that it’s pos­sible
to have down­load sizes not too far from <a href="https://floooh.github.io/2018/05/01/cpp-to-c-size-reduction.html">code care­fully writ­ten in plain C</a>.
To give a fair com­par­is­on, the ba­sic frame­buf­fer clear sample from
<a href="https://github.com/floooh">@floooh</a>‘s <a href="https://floooh.github.io/sokol-html5/">Sokol Samples</a> is 42
kB in total, while the above equi­val­ent is roughly 59 kB. Us­ing C++(11), but
not over­us­ing it — and that’s just the be­gin­ning.</p>
<aside class="m-note m-dim">
Ques­tions? Com­plaints? Share your opin­ion on so­cial net­works:
<a href="https://twitter.com/czmosra/status/1136419181442846720">Twit­ter</a>,
Red­dit <a href="https://www.reddit.com/r/cpp/comments/bx9qwl/optimizing_emscripten_c_apps_for_powerefficient/">r/cpp</a>,
<a href="https://www.reddit.com/r/WebAssembly/comments/bx9uze/optimizing_emscripten_c_apps_for_powerefficient/">r/WebAssembly</a>,
<a href="https://news.ycombinator.com/item?id=20110251">Hack­er News</a></aside>
</section>