📝 Added another blog sub
This commit is contained in:
parent
b31a2d3e1a
commit
4a6bc39fe4
2 changed files with 473 additions and 0 deletions
|
|
@ -54,3 +54,4 @@ file://./rss/bigdinosaur_blog.rss
|
|||
file://./rss/justingarrison.xml
|
||||
file://./rss/keithjgrant.xml
|
||||
file://./rss/mark_manson.xml
|
||||
file://./rss/rosenzweig.xml
|
||||
|
|
|
|||
472
.config/newsboat/rss/rosenzweig.xml
Normal file
472
.config/newsboat/rss/rosenzweig.xml
Normal file
|
|
@ -0,0 +1,472 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0"><channel><title>On Life and Lisp</title><link>https://rosenzweig.io/</link><description>Software freedom, graphics, and gay</description><docs>http://www.rssboard.org/rss-specification</docs><generator>python-feedgen</generator><language>en</language><lastBuildDate>Mon, 22 May 2023 03:13:01 +0000</lastBuildDate><item><title>Growing up Alyssa</title><link>https://rosenzweig.io/blog/growing-up-alyssa.html</link><description><p>When I was 10, I came out as transgender. I was a girl and I knew it.</p>
|
||||
<p>I was one of the lucky ones.</p>
|
||||
<p>After four painful years, I was fortunate enough to access gender-affirming health care. First testosterone blockers. Later estrogen, the stuff my peers soaked in for years while I threw myself into software development to distract from pain.</p>
|
||||
<p>Despite being old enough to go through the wrong puberty and suffer its permanent changes, it took four years to access the medical fix. Four years of gender therapy, hard talks with doctors, and a lot of determination.</p>
|
||||
<p>There’s a vicious myth that kids just walk into clinics and leave with hormones. Quite the opposite.</p>
|
||||
<p>I was lucky: my parents supported me, and by then we lived near San Francisco, where a gender clinic was willing to take me as patient.</p>
|
||||
<p>I’m 21 now. I’ll be blunt: if not for gender-affirming care, I don’t know if I would be around. If there would be FOSS graphics drivers for Mali-T860 or the Apple M1.</p>
|
||||
<p>If I were a few years younger, lived in the wrong part of the US, that may well be the reality, because gender-affirming care is banned for minors in conservative areas across the United States. Texas, for example, would threaten to take me from my loving parents under Greg Abbott’s directive.</p>
|
||||
<p>Even now, I’m lucky I don’t live in the wrong place: the medication I’m prescribed is banned for <em>adults</em> in several American states.</p>
|
||||
<p>I fear the 2024 election. How long until there’s a ban nationwide?</p>
|
||||
<p>In high school, I knew this day might come. I applied to Canadian universities. Canada isn’t perfect, far from it. But stripping trans rights isn’t on the ballot yet.</p>
|
||||
<p>Growing up, we liked visiting Florida.</p>
|
||||
<p>Now there are travel advisories against it.</p>
|
||||
<p>One recent Florida law threatens jail time if a trans person uses the bathroom - <em>any</em> bathroom - in a public space. I remember in high school, arguing back against “bathroom bills” designed to marginalize trans people. They seem tame next to the vile attacks on trans people championed by Ron DeSantis.</p>
|
||||
<p>What’s next?</p>
|
||||
<p>Does anybody remember the Nuremberg laws?</p>
|
||||
<p>I was raised Jewish. Growing up, we were haunted by the spectre of the Holocaust. I knew queer Germans were in the cross-hairs alongside Jews. I didn’t know that Berlin was a queer centre before Hitler came to power.</p>
|
||||
<p>In high school, I understood if fascists came to power in the United States, I might be first to go. Nazis had a special symbol for people like me: a pink triangle superimposed on a yellow triangle. I was 16 when I wondered if one day I would be forced to wear it.</p>
|
||||
<p>In 2020, Donald Trump used the Nazi’s symbol for political prisoners – forced to be worn in camps – to threaten leftists in a campaign ad.</p>
|
||||
<p>Subtle.</p>
|
||||
<p>You don’t need to like Democrats, but I need you to understand that if you vote Republican in 2024, you vote erasure. You vote oppression. You vote fascism.</p>
|
||||
<p>Maybe you “just have some concerns” about trans kids.</p>
|
||||
<p>I was a trans kid, and I want you to know that DeSantis, Abbott, and Trump were my nightmares. Their policies will lead to the deaths of transgender Americans. With hundreds of GOP-sponsored anti-trans bills and laws simultaneously sweeping the United States, it’s hard to believe this isn’t by design.</p>
|
||||
<p>It doesn’t have to be that way.</p>
|
||||
<p>The trans experience isn’t inherently defined by suffering. Not for trans kids, not for trans adults.</p>
|
||||
<p>When treated with respect, allowed to transition, when we can access the medication we know we need, life can be great.</p>
|
||||
<p>Personally, I have felt virtually no gender-related discomfort in years now.</p>
|
||||
<p>I once recoiled at my reflection. Now I look in the mirror and smile at the cute woman smiling back at me. I’m surrounded by lovely friends, and we support each other. Laugh together. Cry together. Text endless stickers of cartoon sharks together. Past the shared struggle, there is immense trans joy.</p>
|
||||
<p>When we are made to suffer – by banning our medication, arresting us for peeing, legislating our identities out of existence on the road to establishing a theocratic state – that is a policy choice.</p>
|
||||
<p>We’re not asking for much. We don’t want special treatment. We just want respect. Life, liberty, and the pursuit of happiness.</p>
|
||||
<p>Right now I want legislators to get the fuck out of our doctors’ office.</p>
|
||||
<p>I’m on the board overseeing Linux graphics. Half of us are trans. If all you care about is Linux, resist the attacks on trans people.</p>
|
||||
<p>If you have any decency, fight back.</p>
|
||||
<p>It’s your choice.</p>
|
||||
<hr />
|
||||
<p>Selected reading:</p>
|
||||
<ul>
|
||||
<li><p><a href="https://www.erininthemorning.com/p/may-anti-trans-legislative-risk-map">May Anti-Trans Legislative Risk Map</a></p></li>
|
||||
<li><p><a href="https://time.com/5953047/lgbtq-holocaust-stories/">Why It Took Decades for LGBTQ Stories to Be Included in Holocaust History</a></p></li>
|
||||
<li><p><a href="https://www.vox.com/recode/2020/6/18/21295226/facebook-trump-campaign-nazi-symbol-antifa">Facebook takes down another Trump campaign ad, this time for Nazi imagery</a></p></li>
|
||||
<li><p><a href="https://naacp.org/articles/naacp-issues-travel-advisory-florida">NAACP Issues Travel Advisory in Florida</a></p></li>
|
||||
<li><p><a href="https://www.cnn.com/2022/03/01/us/texas-transgender-family-investigation-lawsuit/index.html">Texas begins investigating parents of transgender teens for child abuse, according to a lawsuit. One parent works in the department involved in the investigations</a></p></li>
|
||||
</ul>
|
||||
<hr />
|
||||
</description><guid isPermaLink="true">https://rosenzweig.io/blog/growing-up-alyssa.html</guid><pubDate>Sun, 21 May 2023 00:00:00 -0500</pubDate></item><item><title>Passing the reins on Panfrost</title><link>https://rosenzweig.io/blog/passing-reins-panfrost.html</link><description><p>Today is my last day at <a href="https://www.collabora.com/">Collabora</a> and my last day leading the <a href="https://docs.mesa3d.org/drivers/panfrost.html">Panfrost</a> driver.</p>
|
||||
<p>It’s been a wild ride.</p>
|
||||
<p>In 2017, I began work on the <code>chai</code> driver for Mali T (Midgard). <code>chai</code> would later be merged into <a href="https://queer.party/@Lyude">Lyude Paul</a>’s and Connor Abbott’s BiOpenly project for Mali G (Bifrost) to form Panfrost.</p>
|
||||
<p>In 2019, I joined Collabora to accelerate work on the driver stack. The initial goal was to run GNOME on a Mali-T860 Chromebook.</p>
|
||||
<p><a href="https://www.collabora.com/news-and-blog/blog/2019/06/26/gnome-meets-panfrost/">Huge success</a>.</p>
|
||||
<p><img src="https://rosenzweig.io/glmark-gears-gnome-panfrost-crop.webp" alt="GNOME running on Panfrost in 2019" /><br />
|
||||
</p>
|
||||
<p>Today, Panfrost supports a broad spectrum of Mali GPUs, conformant to the OpenGL ES 3.1 specification on Mali-G52 and Mali-G57. It’s hard to overstate how far we’ve come. I’ve had the thrills of architecting several backend shader compilers as well as the Gallium-based OpenGL driver, while my dear colleague Boris Brezillon has put together a proof-of-concept Vulkan driver which I think you’ll hear more about soon.</p>
|
||||
<p>Lately, my focus has been ensuring the project can stand on its own four legs. I have every confidence in other Collaborans hacking on Panfrost, including Boris and Italo Nicola. The project has a bright future. It’s time for me to pass the reins.</p>
|
||||
<p>I’m still alive. I plan to continue working on Mesa drivers for a long time, including the common infrastructure upon which Panfrost relies. And I’ll still send the odd Panfrost patch now and then. That said, my focus will shift.</p>
|
||||
<p>I’m not ready to announce what’s in store yet… but maybe you can read between the lines!</p>
|
||||
</description><guid isPermaLink="true">https://rosenzweig.io/blog/passing-reins-panfrost.html</guid><pubDate>Mon, 10 Apr 2023 00:00:00 -0500</pubDate></item><item><title>Apple GPU drivers now in Asahi Linux</title><link>https://rosenzweig.io/blog/asahi-gpu-part-7.html</link><description><p><a href="https://rosenzweig.io/Quake3.png"><img src="https://rosenzweig.io/Quake3.webp" /></a></p>
|
||||
<p>We’re excited to announce our first Apple GPU driver release!</p>
|
||||
<p>We’ve been working hard over the past two years to bring this new driver to everyone, and we’re really proud to finally be here. This is still an alpha driver, but it’s already good enough to run a smooth desktop experience and some games.</p>
|
||||
<p>Read on to find out more about the state of things today, how to install it (it’s an opt-in package), and how to report bugs!</p>
|
||||
<h2 id="status">Status</h2>
|
||||
<p>This release features work-in-progress OpenGL 2.1 and OpenGL ES 2.0 support for all current Apple M-series systems. That’s enough for hardware acceleration with desktop environments, like GNOME and KDE. It’s also enough for older 3D games, like Quake3 and Neverball. While there’s always room for improvement, the driver is fast enough to run all of the above at 60 frames per second at 4K.</p>
|
||||
<p>Please note: these drivers have not yet passed the OpenGL (ES) conformance tests. There will be bugs!</p>
|
||||
<p>What’s next? Supporting more applications. While OpenGL (ES) 2 suffices for some applications, newer ones (especially games) demand more OpenGL features. OpenGL (ES) 3 brings with it a slew of new features, like multiple render targets, multisampling, and transform feedback. Work on these features is well under way, but they will each take a great deal of additional development effort, and all are needed before OpenGL (ES) 3.0 is available.</p>
|
||||
<p>What about Vulkan? We’re working on it! Although we’re only shipping OpenGL right now, we’re designing with Vulkan in mind. Most of the work we’re putting toward OpenGL will be reused for Vulkan. We estimated that we could ship working OpenGL 2 drivers much sooner than a working Vulkan 1.0 driver, and we wanted to get hardware accelerated desktops into your hands as soon as possible. For the most part, those desktops use OpenGL, so supporting OpenGL first made more sense to us than diving into the Vulkan deep end, only to use Zink to translate OpenGL 2 to Vulkan to run desktops. Plus, there is a large spectrum of OpenGL support, with OpenGL 2.1 containing a fraction of the features of OpenGL 4.6. The same is true for Vulkan: the baseline Vulkan 1.0 profile is roughly equivalent to OpenGL ES 3.1, but applications these days want Vulkan 1.3 with tons of extensions and “optional” features. Zink’s “layering” of OpenGL on top of Vulkan isn’t magic: it can only expose the OpenGL features that the underlying Vulkan driver has. A baseline Vulkan 1.0 driver isn’t even enough to get OpenGL 2.1 on Zink! Zink itself advertises support for OpenGL 4.6, but of course that’s only when paired with Vulkan drivers that support the equivalent of OpenGL 4.6… and that gets us back to a tremendous amount of time and effort.</p>
|
||||
<p>When will OpenGL 3 support be ready? OpenGL 4? Vulkan 1.0? Vulkan 1.3? In community open source projects, it’s said that every time somebody asks when a feature will be done, it delays that feature by a month. Well, a lot of people have been asking…</p>
|
||||
<p>At any rate, for a sneak peek… here is SuperTuxKart’s deferred renderer running at full speed, making liberal use of OpenGL ES 3 features like multiple render targets~</p>
|
||||
<p><a href="https://rosenzweig.io/SuperTuxKart-Deferred.png"><img src="https://rosenzweig.io/SuperTuxKart-Deferred.webp" /></a></p>
|
||||
<h2 id="anatomy-of-a-gpu-driver">Anatomy of a GPU driver</h2>
|
||||
<p>Modern GPUs consist of many distinct “layered” parts. There is…</p>
|
||||
<ul>
|
||||
<li>a memory management unit and an interface to submit memory-mapped work to the hardware</li>
|
||||
<li>fixed-function 3D hardware to rasterize triangles, perform depth/stencil testing, and more</li>
|
||||
<li>programmable “shader cores” (like little CPUs with bespoke instruction sets) with work dispatched by the fixed-function hardware</li>
|
||||
</ul>
|
||||
<p>This “layered” hardware demands a “layered” graphics driver stack. We need…</p>
|
||||
<ul>
|
||||
<li>a kernel driver to map memory and submit memory-mapped work</li>
|
||||
<li>a userspace driver to translate OpenGL and Vulkan calls into hardware-specific data structures in graphics memory</li>
|
||||
<li>a compiler translating shading programming languages like GLSL to the hardware’s instruction set</li>
|
||||
</ul>
|
||||
<p>That’s a lot of work, calling for a team effort! Fortunately, that layering gives us natural boundaries to divide work among our small team.</p>
|
||||
<ul>
|
||||
<li><a href="https://social.treehouse.systems/@alyssa"><strong>Alyssa Rosenzweig</strong></a> is writing the OpenGL driver and compiler.</li>
|
||||
<li><a href="https://vt.social/@lina"><strong>Asahi Lina</strong></a> is writing the kernel driver and helping with OpenGL.</li>
|
||||
<li><a href="https://mastodon.social/@dougall"><strong>Dougall Johnson</strong></a> is reverse-engineering the instruction set with Alyssa.</li>
|
||||
</ul>
|
||||
<p>Meanwhile, <a href="https://tech.lgbt/@ella"><strong>Ella Stanforth</strong></a> is working on a Vulkan driver, reusing the kernel driver, the compiler, and some code shared with the OpenGL driver.</p>
|
||||
<p>Of course, we couldn’t build an OpenGL driver in under two years just ourselves. Thanks to the power of free and open source software, we stand on the shoulders of FOSS giants. The compiler implements a “NIR” backend, where NIR is a powerful intermediate representation, including GLSL to NIR translation. The kernel driver users the “Direct Rendering Manager” (DRM) subsystem of the Linux kernel to minimize boilerplate. Finally, the OpenGL driver implements the “Gallium3D” API inside of <a href="https://mesa3d.org/">Mesa</a>, the home for open source OpenGL and Vulkan drivers. Through Mesa and Gallium3D, we benefit from thirty years of OpenGL driver development, with common code translating OpenGL into the much simpler Gallium3D. Thanks to the incredible engineering of NIR, Mesa, and Gallium3D, our ragtag team of reverse-engineers can focus on what’s left: the Apple hardware.</p>
|
||||
<h2 id="installation-instructions">Installation instructions</h2>
|
||||
<p>To get the new drivers, you need to run the <code>linux-asahi-edge</code> kernel and also install the <code>mesa-asahi-edge</code> Mesa package.</p>
|
||||
<pre class="shell"><code>$ sudo pacman -Syu
|
||||
$ sudo pacman -S linux-asahi-edge mesa-asahi-edge
|
||||
$ sudo update-grub</code></pre>
|
||||
<p>Since only one version of Mesa can be installed at a time, pacman will prompt you to replace <code>mesa</code> with <code>mesa-asahi-edge</code>. This is normal!</p>
|
||||
<p>We also recommend running Wayland instead of Xorg at this point, so if you’re using the KDE Plasma environment, make sure to install the Wayland session:</p>
|
||||
<pre class="shell"><code>$ sudo pacman -S plasma-wayland-session</code></pre>
|
||||
<p>Then reboot, pick the Wayland session at the top of the login screen (SDDM), and enjoy! You might want to adjust the screen scale factor in <em>System Settings → Display and Monitor</em> (Plasma Wayland defaults to 100% or 200%, while 150% is often nicer). If you have “Force font DPI” enabled under <em>Appearance → Fonts</em>, you should disable that (it is saved separately for Wayland and Xorg, and shouldn’t be necessary on Wayland sessions). Log out and back in for these changes to fully apply.</p>
|
||||
<p>Xorg and Xorg-based desktop environments should work, but there are a few known issues:</p>
|
||||
<ul>
|
||||
<li>Expect screen tearing (this might be fixed <a href="https://gitlab.freedesktop.org/xorg/xserver/-/merge_requests/1006">soon</a>)</li>
|
||||
<li>VSync does not work (some KDE animations will be too fast, and GL apps will not limit their FPS even with VSync enabled). This is a limitation of Xorg on the Apple DCP display controllers, which do not support VBlank interrupts.</li>
|
||||
<li>There are still driver bugs triggered by Xorg/KWin. We’re looking into this.</li>
|
||||
</ul>
|
||||
<p>The <code>linux-asahi-edge</code> kernel can be installed side-by-side with the standard <code>linux-asahi</code> package, but both versions should be kept in sync, so make sure to always update your packages together! You can always pick the <code>linux-asahi</code> kernel in the GRUB boot menu, which will disable GPU acceleration and the DCP display driver.</p>
|
||||
<p>When the packages are updated in the future, it’s possible that graphical apps will stop starting up after an update until you reboot, or they may fall back to software rendering. This is normal. Until the UAPI is stable, we’ll have to break compatibility between Mesa and the kernel every now and then, so you will need to reboot to make things work after updates. In general, if apps <em>do</em> keep working with acceleration after any particular Mesa update, then it’s probably safe not to reboot, but you should still do it to make sure you’re running the latest kernel!</p>
|
||||
<h2 id="reporting-bugs">Reporting bugs</h2>
|
||||
<p>Since the driver is still in development, there are lots of known issues and we’re still working hard on improving conformance test results. Please don’t open new bugs for random apps not working! It’s still the early days and we know there’s a lot of work to do. Here’s a quick guide of how to report bugs:</p>
|
||||
<ul>
|
||||
<li>If you find an app that does not start up at all, please don’t report it as a bug. Lots of apps won’t work because they require a newer GL version than what we support. Please set the <code>LIBGL_ALWAYS_SOFTWARE=1</code> environment variable for those apps to fall back to software rendering. If it is a popular app that is part of the Arch Linux ARM repository, you can make a comment on <a href="https://github.com/AsahiLinux/linux/issues/73">this issue</a> instead, so we can add Mesa quirks to workaround.</li>
|
||||
<li>If you run into issues caused by <code>linux-asahi-edge</code> unrelated to the GPU, please add a comment to <a href="https://github.com/AsahiLinux/linux/issues/70">this issue</a>. This includes display output issues! (Resolutions, backlight control, display power control, etc.)</li>
|
||||
<li>If the GPU locks up and all GPU apps stop working, run <code>asahi-diagnose</code> (for example, from an SSH session), open a new bug on <a href="https://github.com/AsahiLinux/linux">the AsahiLinux/linux repository</a>, attach the file generated by that command, and tell us what you were doing that caused the lockup.</li>
|
||||
<li>For other GPU issues (rendering glitches, apps that crash after starting up correctly, and things like that), run <code>asahi-diagnose</code> and make a comment on <a href="https://github.com/AsahiLinux/linux/issues/72">this issue</a>, attaching the file generated by that command. Don’t forget to tell us about your environment!</li>
|
||||
<li>In the future, if a driver update causes a regression (rendering problems or crashes for apps that previously worked properly), you can open a bug <a href="https://gitlab.freedesktop.org/asahi/mesa/-/issues">directly in the Mesa tracker</a>.</li>
|
||||
</ul>
|
||||
<p>We hope you enjoy our driver! Remember, things are still moving quickly, so make sure to update your packages regularly to get updates and bug fixes!</p>
|
||||
<p><em>Co-written with Asahi Lina. Can you tell who wrote what?</em></p>
|
||||
</description><guid isPermaLink="true">https://rosenzweig.io/blog/asahi-gpu-part-7.html</guid><pubDate>Wed, 07 Dec 2022 00:00:00 -0500</pubDate></item><item><title>Clip control on the Apple GPU</title><link>https://rosenzweig.io/blog/asahi-gpu-part-6.html</link><description><figure>
|
||||
<img src="/Neverball.webp" alt="" /><figcaption>Neverball rendered on the Apple M1 GPU with an open source OpenGL driver</figcaption>
|
||||
</figure>
|
||||
<p>After a year in development, <a href="https://docs.mesa3d.org/drivers/asahi.html">the open source “Asahi” driver for the Apple GPU</a> is running real games. There’s more to do, but <a href="https://neverball.org/">Neverball</a> is already playable (and a lot of fun!).</p>
|
||||
<p>Neverball uses legacy “fixed function” OpenGL. Rather than supply programmable shaders like OpenGL 2, old OpenGL 1 applications configure a fixed set of graphics effects like fog and alpha testing. Modern GPUs don’t implement these features in hardware. Instead, the driver synthesizes shaders implementing the desired graphics. This translation is complicated, but we get it for “free” as an open source driver in Mesa. If we implement the modern shader pipeline, Mesa will handle fixed function OpenGL for us transparently. That’s a win for open source drivers, and a win for GPU acceleration on <a href="https://asahilinux.org/">Asahi Linux</a>.</p>
|
||||
<p>To implement the modern OpenGL features, we rely on reverse-engineering the behaviour of Apple’s Metal driver, as we don’t have hardware documentation. Although Metal uses the same shader pipeline as OpenGL, it doesn’t support all the OpenGL features that the hardware does, which puts us in bind. In the past, I’ve relied on <a href="https://rosenzweig.io/blog/asahi-gpu-part-4.html">educated guesswork</a> to bridge the gap, but there’s another solution… and it’s a doozy.</p>
|
||||
<p>For motivation, consider the <em>clip space</em> used in OpenGL. In every other API on the planet, the Z component (depth) of points in the 3D world range from 0 to 1, where 0 is “near” and 1 is “far”. In OpenGL, however, Z ranges from <em>negative 1</em> to 1. As Metal uses the 0/1 clip space, implementing OpenGL on Metal requires emulating the -1/1 clip space by inserting extra instructions into the vertex shader to transform the Z coordinate. Although this emulation adds overhead, it works for <a href="https://github.com/google/angle">ANGLE</a>’s open source implementation of OpenGL ES on Metal.</p>
|
||||
<p>Like ANGLE, Apple’s OpenGL driver internally translates to Metal. Because Metal uses the 0 to 1 clip space, it should require this emulation code. Curiously, when we disassemble shaders compiled with their OpenGL implementation, we don’t see any such emulation. That means Apple’s GPU must support -1/1 clip spaces in addition to Metal’s preferred 0/1. The problem is figuring out how to use this other clip space.</p>
|
||||
<p>We expect that there’s a bit toggling between these clip spaces. The logical place for such a bit is the viewport packet, but there’s no obvious difference between the viewport packets emitted by Metal and OpenGL-on-Metal. Ordinarily, we would identify the bit by toggling the clip space in Metal and comparing memory dumps. However, according to Apple’s documentation, there’s no way to change the clip space in Metal.</p>
|
||||
<p>That’s an apparently contradiction. There’s no way to use the -1/1 clip space with Metal, but Apple’s OpenGL-on-Metal translator uses uses the -1/1 clip space. What gives?</p>
|
||||
<p>Here’s a little secret: there are two graphics APIs called “Metal”. There’s the Metal you know, a limited API that Apple documents for App Store developers, an API that lacks useful features supported by OpenGL and Vulkan.</p>
|
||||
<p>And there’s the Metal that Apple uses themselves, an internal API adding back features that Apple doesn’t want you using. While ANGLE implements OpenGL ES on the documented Metal, Apple can implement OpenGL on the secret Metal.</p>
|
||||
<p>Apple does not publish documentation or headers for this richer Metal API, but if we’re lucky, we can catch a glimpse behind the curtain. The undocumented classes and methods making up the internal Metal API are still available in the production Metal binaries. To use them, we only need the missing headers. Fortunately, Objective-C symbols contain enough information to reconstruct header files, allowing us to experiment with undocumented methods with “extra” functionality inherited from OpenGL.</p>
|
||||
<p>Compared to the desktop GPUs found in Intel Macs, Apple’s own GPU implements a slim, modern feature set mapping well to Metal. Most of the “extra” functionality is emulated. It is interesting to know the emulation happens in their Metal driver instead of their OpenGL frontend, but that’s unsurprising, as it allows their Metal drivers for Intel and AMD GPUs to implement the functionality natively. While this information is fascinating for “macOS hermeneutics”, it won’t help us with our Apple GPU mystery.</p>
|
||||
<p>What <em>will</em> help us are the catch-all mystery methods named <code>setOpenGLModeEnabled</code>, apparently enabling “OpenGL mode”.</p>
|
||||
<p>Mystery methods named like just <em>beg</em> to be called.</p>
|
||||
<p>The render pipeline descriptor has such a method. That descriptor contains state that can change every draw. In some graphics APIs, like OpenGL with <a href="https://registry.khronos.org/OpenGL/extensions/ARB/ARB_clip_control.txt"><code>ARB_clip_control</code></a> and Vulkan with <a href="https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_EXT_depth_clip_control.html"><code>VK_EXT_depth_clip_control</code></a>, the application can change the clip space every draw. Ideally, the clip space state would be part of this descriptor.</p>
|
||||
<p>We can test this optimistic guess by augmenting our Metal test bench to call <code>[MTLRenderPipelineDescriptorInternal setOpenGLModeEnabled: YES]</code>.</p>
|
||||
<p>It feels strange to call this hidden method. It’s stranger when the code compiles and runs just fine.</p>
|
||||
<p>We can then compare traces between OpenGL mode and the normal Metal mode. Seemingly, enabling OpenGL mode toggles a plethora of random unknown bits. Even if one of them is what we want, it’s a bit unsatisfying that the “real” Metal would lack a proper <code>[setClipSpace: MTLMinusOneToOne]</code> method, rather than this blunt hack reconfiguring a pile of loosely related API behaviours.</p>
|
||||
<p>Alas, for all the random changes in “OpenGL mode”, none seem to affect clipping behaviour.</p>
|
||||
<p>Hope is not yet lost. There’s another <code>setOpenGLModeEnabled</code> method, this time in the render pass descriptor. Rather than pipeline state that can change every draw, this descriptor’s state can only change in between render passes. Changing that state in between draws would require an expensive flush to main memory, similar to <a href="https://rosenzweig.io/blog/asahi-gpu-part-5.html">the partial renders seen elsewhere with the Apple GPU</a>. Nevertheless, it’s worth a shot.</p>
|
||||
<p>Changing our test bench to call <code>[MTLRenderPassDescriptorInternal setOpenGLModeEnabled: YES]</code>, we find another collection of random bits changed. Most of them are in hardware packets, and none of those seem to control clip space, either.</p>
|
||||
<p>One bit does stand out. It’s not a hardware bit.</p>
|
||||
<p>In addition to the packets that the userspace driver prepares for the hardware, userspace passes to the <em>kernel</em> a large block of render pass state describing everything from tile size to the depth/stencil buffers. Such a design is unusual. Ordinarily, GPU kernel drivers are only concerned with memory management and scheduling, remaining oblivious of 3D graphics. By contrast, Apple processes this state in the kernel forwarding the state to the GPU’s firmware to configure the actual hardware.</p>
|
||||
<p>Comparing traces, the render pass “OpenGL mode” sets an unknown bit in this kernel-processed block. If we set the same bit in our OpenGL driver, we find the clip space changes to -1/1. Victory, right?</p>
|
||||
<p>Almost. Because this bit is render pass state, we can’t use it to change the clip space between draws. That’s okay for baseline OpenGL and Vulkan, but it prevents us from efficiently implementing the <code>ARB_clip_control</code> and <code>VK_EXT_depth_clip_control</code> extensions. There <em>are</em> at least three (inefficient) implementations.</p>
|
||||
<p>The first is ignoring the hardware support and emulating one of the clip spaces by inserting extra instructions into the vertex shader when the “wrong” clip space is used. In addition to extra overhead, that requires <em>shader variants</em> for the different clip spaces.</p>
|
||||
<p>Shader variants are terrible.</p>
|
||||
<p>In new APIs like Vulkan, Metal, and D3D12, everything needed to compile a shader is known up-front as part of a monolithic pipeline. That means pipelines are compiled when they’re created, not when they’re used, and are never recompiled. By contrast, older APIs like OpenGL and D3D11 allow using the same shader with different API states, requiring some drivers to recompile shaders on the fly. Compiling shaders is slow, so shader variants can cause unpredictable drops in an application’s frame rate, familiar to desktop gamers as stuttering. If we use this approach in our OpenGL driver, switching clip modes could cause stuttering due to recompiling shaders. In bad circumstances, that stutter could even happen long after the mode is switched.</p>
|
||||
<p>That option is undesirable, so the second approach is <em>always</em> inserting emulation instructions that read the desired clip space at run-time, reserving a uniform (push constant) for the transformation. That way, the same shader is usable with either clip space, eliminating shader variants. However, that has even higher overhead than the first method. If an application frequently changes clip spaces within a render pass, this approach will be the most efficient of the three. If it does not, this approach adds constant overhead to <em>every</em> application. Knowing which approach is better requires the driver to have a magic crystal ball.<a href="#fn1" class="footnote-ref" id="fnref1" role="doc-noteref"><sup>1</sup></a></p>
|
||||
<p>The final option is <em>using</em> the hardware clip space bit and splitting the render pass when the clip space is changed. Here, the shaders are optimal and do not require variants. However, splitting the render pass wastes tremendous memory bandwidth if the application changes clip spaces frequently. Nevertheless, this approach has some support from the <code>ARB_clip_control</code> specification:</p>
|
||||
<blockquote>
|
||||
<p>Some [OpenGL] implementations may introduce a flush when changing the clip control state. Hence frequent clip control changes are not recommended.</p>
|
||||
</blockquote>
|
||||
<p>Each approach has trade-offs. For now, the easiest “option” is sticking our head in the sand and giving up on <code>ARB_clip_control</code> altogether. The OpenGL extension is optional until we get to OpenGL 4.5. Apple doesn’t implement it in their OpenGL stack. Because <code>ARB_clip_control</code> is primarily for porting Direct3D games, native OpenGL games are happy without it. Certainly, Neverball doesn’t mind. For now, we can use the hardware bit to use the -1/1 clip space unconditionally in OpenGL and 0/1 unconditionally in Vulkan. That does not require any emulation or flushing, though it prevents us from advertising the extensions.</p>
|
||||
<p>That’s enough to run Neverball on macOS, using our userspace OpenGL driver in Mesa, and Apple’s proprietary kernel driver. There’s a catch: Neverball has to present with the deprecated X11 server on macOS. Years ago, Apple engineers<a href="#fn2" class="footnote-ref" id="fnref2" role="doc-noteref"><sup>2</sup></a> contributed Mesa support for X11 on macOS (XQuartz), allowing us to run X11 applications with our Mesa driver. However, there’s no support for Apple’s own Cocoa windowing system, meaning native macOS applications won’t work with our driver. There’s also no easy way to run Linux’s newer Wayland display server on macOS. Nevertheless, Neverball does not use Cocoa directly. Instead, it uses the cross-platform <a href="https://github.com/libsdl-org/SDL">SDL2</a> library to create its window, which internally uses Cocoa, X11, or Wayland as appropriate for the operating system. With enough sweat and tears, we can build an macOS/X11 version of SDL2 and link Neverball with that.</p>
|
||||
<p>This Neverball/macOS/X11 port was frustrating, especially when the game is one <code>apt install</code> away on Linux. That’s a job for <a href="https://twitter.com/linaasahi">Asahi Lina</a>, who has been hard at work writing a Linux kernel driver for Apple’s GPU. When our work converges, my userspace Mesa driver will run on Linux with her kernel driver to implement a full open source graphics stack for 3D acceleration on Asahi Linux.</p>
|
||||
<p>Please temper your expectations: even with hardware documentation, an optimized Vulkan driver stack (with enough features to layer OpenGL 4.6 with Zink) requires many years of full time work. At least for now, nobody is working on this driver full time<a href="#fn3" class="footnote-ref" id="fnref3" role="doc-noteref"><sup>3</sup></a>. Reverse-engineering slows the process considerably. We won’t be playing AAA games any time soon.</p>
|
||||
<p>That said, thanks to the tremendous shared code in Mesa, a basic OpenGL driver is doable by a single person. I’m optimistic that we’ll have native OpenGL 2.1 in Asahi Linux by the end of the year. That’s enough to accelerate your desktop environment and browser. It’s also enough to play older games (like Neverball). Even without fancy features, GPU acceleration means smooth animations and better battery life.</p>
|
||||
<p>In that light, the Asahi Linux future looks bright.</p>
|
||||
<p><img src="/Neverball2.webp" /></p>
|
||||
<section class="footnotes" role="doc-endnotes">
|
||||
<hr />
|
||||
<ol>
|
||||
<li id="fn1" role="doc-endnote"><p>This crystal ball is called “Vulkan, Metal, or D3D12”, and it has its own problems.<a href="#fnref1" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
|
||||
<li id="fn2" role="doc-endnote"><p>Hi Jeremy!<a href="#fnref2" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
|
||||
<li id="fn3" role="doc-endnote"><p>I work full-time at <a href="https://collabora.com">Collabora</a> on my baby, the open source Panfrost driver for Mali GPUs.<a href="#fnref3" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
|
||||
</ol>
|
||||
</section>
|
||||
</description><guid isPermaLink="true">https://rosenzweig.io/blog/asahi-gpu-part-6.html</guid><pubDate>Mon, 22 Aug 2022 00:00:00 -0500</pubDate></item><item><title>The Apple GPU and the Impossible Bug</title><link>https://rosenzweig.io/blog/asahi-gpu-part-5.html</link><description><p>In late 2020, Apple debuted the M1 with Apple’s GPU architecture, AGX, rumoured to be derived from Imagination’s PowerVR series. Since then, <a href="https://asahilinux.org">we’ve</a> been reverse-engineering AGX and building open source graphics drivers. Last January, I <a href="https://rosenzweig.io/blog/asahi-gpu-part-2.html">rendered a triangle</a> with my own code, but there has since been a heinous bug lurking:</p>
|
||||
<p>The driver fails to render large amounts of geometry.</p>
|
||||
<p>Spinning a cube is fine, low polygon geometry is okay, but detailed models won’t render. Instead, the GPU renders only part of the model and then faults.</p>
|
||||
<figure>
|
||||
<img src="/PartialPhong.webp" alt="" /><figcaption>Partially rendered bunny</figcaption>
|
||||
</figure>
|
||||
<p>It’s hard to pinpoint how much we can render without faults. It’s not just the geometry complexity that matters. The same geometry can render with simple shaders but fault with complex ones.</p>
|
||||
<p>That suggests rendering detailed geometry with a complex shader “takes too long”, and the GPU is timing out. Maybe it renders only the parts it finished in time.</p>
|
||||
<p>Given the hardware architecture, this explanation is unlikely.</p>
|
||||
<p>This hypothesis is easy to test, because we can control for timing with a shader that takes as long as we like:</p>
|
||||
<div class="sourceCode" id="cb1"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true"></a><span class="cf">for</span> (<span class="dt">int</span> i = <span class="dv">0</span>; i &lt; LARGE_NUMBER; ++i) {</span>
|
||||
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true"></a> <span class="co">/* some work to prevent the optimizer from removing the loop */</span></span>
|
||||
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true"></a>}</span></code></pre></div>
|
||||
<p>After experimenting with such a shader, we learn…</p>
|
||||
<ul>
|
||||
<li>If shaders have a time limit to protect against infinite loops, it’s astronomically high. There’s no way our bunny hits that limit.</li>
|
||||
<li>The symptoms of timing out differ from the symptoms of our driver rendering too much geometry.</li>
|
||||
</ul>
|
||||
<p>That theory is out.</p>
|
||||
<p>Let’s experiment more. Modifying the shader and seeing where it breaks, we find the only part of the shader contributing to the bug: the amount of data interpolated per vertex. Modern graphics APIs allow specifying “varying” data for each vertex, like the colour or the surface normal. Then, for each triangle the hardware renders, these “varyings” are interpolated across the triangle to provide smooth inputs to the fragment shader, allowing efficient implementation of common graphics techniques like Blinn-Phong shading.</p>
|
||||
<p>Putting the pieces together, what matters is the <em>product</em> of the number of vertices (geometry complexity) <em>times</em> amount of data per vertex (“shading” complexity). That product is “total amount of per-vertex data”. The GPU faults if we use too much <em>total</em> per-vertex data.</p>
|
||||
<p>Why?</p>
|
||||
<p>When the hardware processes each vertex, the vertex shader produces per-vertex data. That data has to <em>go</em> somewhere. How this works depends on the hardware architecture. Let’s consider common GPU architectures.<a href="#fn1" class="footnote-ref" id="fnref1" role="doc-noteref"><sup>1</sup></a></p>
|
||||
<p>Traditional <strong>immediate mode renderers</strong> render directly into the framebuffer. They first run the vertex shader for each vertex of a triangle, then run the fragment shader for each pixel in the triangle. Per-vertex “varying” data is passed almost directly between the shaders, so immediate mode renderers are efficient for complex scenes.</p>
|
||||
<p>There is a drawback: rendering directly into the framebuffer requires tremendous amounts of memory access to constantly write the results of the fragment shader and to read out back results when blending. Immediate mode renderers are suited to discrete, power-hungry desktop GPUs with dedicated video RAM.</p>
|
||||
<p>By contrast, <strong>tile-based deferred renderers</strong> split rendering into two passes. First, the hardware runs all vertex shaders for the entire frame, not just for a single model. Then the framebuffer is divided into small tiles, and dedicated hardware called a <em>tiler</em> determines which triangles are in each tile. Finally, for each tile, the hardware runs all relevant fragment shaders and writes the final blended result to memory.</p>
|
||||
<p>Tilers reduce memory traffic required for the framebuffer. As the hardware renders a single tile at a time, it keeps a “cached” copy of that tile of the framebuffer (called the “tilebuffer”). The tilebuffer is small, just a few kilobytes, but tilebuffer access is <em>fast</em>. Writing to the tilebuffer is cheap, and unlike immediate renderers, blending is almost free. Because main memory access is expensive and mobile GPUs can’t afford dedicated video memory, tilers are suited to mobile GPUs, like Arm’s Mali, Imaginations’s PowerVR, and Apple’s AGX.</p>
|
||||
<p>Yes, AGX is a <em>mobile</em> GPU, designed for the iPhone. The M1 is a screaming fast desktop, but its unified memory and tiler GPU have roots in mobile phones. Tilers work well on the desktop, but there are some drawbacks.</p>
|
||||
<p>First, at the start of a frame, the contents of the tilebuffer are undefined. If the application needs to preserve existing framebuffer contents, the driver needs to load the framebuffer from main memory and store it into the tilebuffer. This is expensive.</p>
|
||||
<p>Second, because all vertex shaders are run before any fragment shaders, the hardware needs a buffer to store the outputs of all vertex shaders. In general, there is much more data required than space inside the GPU, so this buffer must be in main memory. This is also expensive.</p>
|
||||
<p><strong>Ah-ha</strong>. Because AGX is a tiler, it requires a buffer of <em>all</em> per-vertex data. We fault when we use too <em>much</em> total per-vertex data, overflowing the buffer.</p>
|
||||
<p>…So how do we allocate a larger buffer?</p>
|
||||
<p>On some tilers, like older versions of Arm’s Mali GPU, the userspace driver computes how large this “varyings” buffer should be and allocates it.<a href="#fn2" class="footnote-ref" id="fnref2" role="doc-noteref"><sup>2</sup></a> To fix the faults, we can try increasing the sizes of all buffers we allocate, in the hopes that one of them contains the per-vertex data.</p>
|
||||
<p>No dice.</p>
|
||||
<p>It’s prudent to observe what Apple’s Metal driver does. We can cook up a Metal program drawing variable amounts of geometry and trace all GPU memory allocations that Metal performs while running our program. Doing so, we learn that increasing the amount of geometry drawn does <em>not</em> increase the sizes of any allocated buffers. In fact, it doesn’t change <em>anything</em> in the command buffer submitted to the kernel, except for the single “number of vertices” field in the draw command.</p>
|
||||
<p>We <em>know</em> that buffer exists. If it’s not allocated by userspace – and by now it seems that it’s not – it must be allocated by the kernel or firmware.</p>
|
||||
<p>Here’s a funny thought: maybe we don’t specify the size of the buffer at all. Maybe it’s <em>okay</em> for it to overflow, and there’s a way to handle the overflow.</p>
|
||||
<p>It’s time for a little reconnaissance. Digging through what little public documentation exists for AGX, we learn from one <a href="https://developer.apple.com/videos/play/wwdc2020/10602/">WWDC presentation</a>:</p>
|
||||
<blockquote>
|
||||
<p>The Tiled Vertex Buffer stores the Tiling phase output, which includes the post-transform vertex data…</p>
|
||||
<p>But it may cause a Partial Render if full. A Partial Render is when the GPU splits the render pass in order to flush the contents of that buffer.</p>
|
||||
</blockquote>
|
||||
<p>Bullseye. The buffer we’re chasing, the “tiled vertex buffer”, can overflow. To cope, the GPU stops accepting new geometry, renders the existing geometry, and restarts rendering.</p>
|
||||
<p>Since partial renders hurt performance, Metal application developers need to know about them to optimize their applications. There should be <em>performance counters</em> flagging this issue. Poking around, we find two:</p>
|
||||
<ul>
|
||||
<li>Number of partial renders.</li>
|
||||
<li>Number of bytes used of the parameter buffer.</li>
|
||||
</ul>
|
||||
<p>Wait, what’s a “parameter buffer”?</p>
|
||||
<p>Remember the rumours that AGX is derived from PowerVR? The public PowerVR <a href="https://docs.imgtec.com/Profiling_and_Optimisations/PerfRec/topics/c_PerfRec_parameter_buffer.html">optimization</a> <a href="https://github.com/powervr-graphics/WebGL_SDK/blob/4.0/Documentation/Architecture%20Guides/PowerVR.Performance%20Recommendations.pdf">guides</a> explain:</p>
|
||||
<blockquote>
|
||||
<p>[The] list containing pointers to each vertex passed in from the application… is called the <strong>parameter buffer</strong> (PB) and is stored in system memory along with the vertex data.</p>
|
||||
<p>Each varying requires additional space in the parameter buffer.</p>
|
||||
</blockquote>
|
||||
<p>The Tiled Vertex Buffer <em>is</em> the Parameter Buffer. PB is the PowerVR name, TVB is the public Apple name, and PB is still an internal Apple name.</p>
|
||||
<p>What happens when PowerVR overflows the parameter buffer?</p>
|
||||
<p>An old <a href="http://imgtec.eetrend.com/sites/imgtec.eetrend.com/files/download/201402/1458-2110-1385011428.pdf">PowerVR presentation</a> says that when the parameter buffer is full, the “render is flushed”, meaning “flushed data must be retrieved from the frame buffer as successive tile renders are performed”. In other words, it performs a partial render.</p>
|
||||
<p>Back to the Apple M1, it seems the hardware is failing to perform a partial render. Let’s revisit the broken render.</p>
|
||||
<figure>
|
||||
<img src="/PartialPhong.webp" alt="" /><figcaption>Partially rendered bunny, again</figcaption>
|
||||
</figure>
|
||||
<p>Notice <em>parts</em> of the model are correctly rendered. The parts that are not only have the black clear colour of the scene rendered at the start. Let’s consider the logical order of events.</p>
|
||||
<p>First, the hardware runs vertex shaders for the bunny until the parameter buffer overflows. This works: the partial geometry is correct.</p>
|
||||
<p>Second, the hardware rasterizes the partial geometry and runs the fragment shaders. This works: the shading is correct.</p>
|
||||
<p>Third, the hardware flushes the partial render to the framebuffer. This must work for us to see anything at all.</p>
|
||||
<p>Fourth, the hardware runs vertex shaders for the rest of the bunny’s geometry. This ought to work: the configuration is identical to the original vertex shaders.</p>
|
||||
<p>Fifth, the hardware rasterizes and shades the rest of the geometry, blending with the old partial render. Because AGX is a tiler, to preserve that existing partial render, the hardware needs to load it back into the tilebuffer. We have no idea how it does this.</p>
|
||||
<p>Finally, the hardware flushes the render to the framebuffer. This should work as it did the first time.</p>
|
||||
<p>The only problematic step is <em>loading the framebuffer back into the tilebuffer after a partial render</em>. Usually, the driver supplies two “extra” fragment shaders. One clears the tilebuffer at the start, and the other flushes out the tilebuffer contents at the end.</p>
|
||||
<p>If the application needs the existing framebuffer contents preserved, instead of writing a clear colour, the “load tilebuffer” program instead reads from the framebuffer to reload the contents. Handling this requires quite a bit of code, but it works in our driver.</p>
|
||||
<p>Looking closer, AGX requires more auxiliary programs.</p>
|
||||
<p>The “store” program is supplied <em>twice</em>. I noticed this when initially bringing up the hardware, but the reason for the duplication was unclear. Omitting each copy separately and seeing what breaks, the reason becomes clear: one program flushes the <em>final</em> render, and the other flushes a <em>partial render</em>.<a href="#fn3" class="footnote-ref" id="fnref3" role="doc-noteref"><sup>3</sup></a></p>
|
||||
<p>…What about the program that loads the framebuffer into the tilebuffer?</p>
|
||||
<p>When a partial render is possible, there are two “load” programs. One writes the clear colour or loads the framebuffer, depending on the application setting. We understand this one. The other <em>always</em> loads the framebuffer.</p>
|
||||
<p>…Always loads the framebuffer, as in, for loading back with a partial render even if there is a clear at the start of the frame?</p>
|
||||
<p>If this program is the issue, we can confirm easily. Metal must require it to draw the same bunny, so we can write a Metal application drawing the bunny and stomp over its GPU memory to replace this auxiliary load program with one always loading with black.</p>
|
||||
<figure>
|
||||
<img src="/Metal-Artefacts.webp" alt="" /><figcaption>Metal drawing the bunny, stomping over its memory.</figcaption>
|
||||
</figure>
|
||||
<p>Doing so, Metal fails in a similar way. That means we’re at the root cause. Looking at our own driver code, we don’t specify <em>any</em> program for this partial render load. Up until now, that’s worked okay. If the parameter buffer is never overflowed, this program is unused. As soon as a partial render is required, however, failing to provide this program means the GPU dereferences a null pointer and faults. That explains our GPU faults at the beginning.</p>
|
||||
<p>Following Metal, we supply our own program to load back the tilebuffer after a partial render…</p>
|
||||
<figure>
|
||||
<img src="/BrokenDepth.webp" alt="" /><figcaption>Bunny with the fix</figcaption>
|
||||
</figure>
|
||||
<p>…which does <em>not</em> fix the rendering! Cursed, this GPU. The faults go away, but the render still isn’t quite right for the first few frames, indicating partial renders are still broken. Notice the weird artefacts on the feet.</p>
|
||||
<p>Curiously, the render “repairs itself” after a few frames, suggesting the parameter buffer stops overflowing. This implies the parameter buffer can be resized (by the kernel or by the firmware), and the system is growing the parameter buffer after a few frames in response to overflow. This mechanism makes sense:</p>
|
||||
<ul>
|
||||
<li>The hardware can’t allocate more parameter buffer space itself.</li>
|
||||
<li>Overflowing the parameter buffer is expensive, as partial renders require tremendous memory bandwidth.</li>
|
||||
<li>Overallocating the parameter buffer wastes memory for applications rendering simple geometry.</li>
|
||||
</ul>
|
||||
<p>Starting the parameter buffer small and growing in response to overflow provides a balance, reducing the GPU’s memory footprint and minimizing partial renders.</p>
|
||||
<p>Back to our misrendering. There are actually <em>two</em> buffers being used by our program, a colour buffer (framebuffer)… and a depth buffer. The depth buffer isn’t directly visible, but facilitates the “depth test”, which discards far pixels that are occluded by other close pixels. While the partial render mechanism discards geometry, the depth test discards pixels.</p>
|
||||
<p>That would explain the missing pixels on our bunny. The depth test is broken with partial renders. Why? The depth test depends on the depth buffer, so the depth buffer must <em>also</em> be stored after a partial render and loaded back when resuming. Comparing a trace from our driver to a trace from Metal, looking for any relevant difference, we eventually stumble on the configuration required to make depth buffer flushes work.</p>
|
||||
<p>And with that, we get our bunny.</p>
|
||||
<figure>
|
||||
<img src="/Final.webp" alt="" /><figcaption>The final Phong shaded bunny</figcaption>
|
||||
</figure>
|
||||
<section class="footnotes" role="doc-endnotes">
|
||||
<hr />
|
||||
<ol>
|
||||
<li id="fn1" role="doc-endnote"><p>These explanations are massive oversimplifications of how modern GPUs work, but it’s good enough for our purposes here.<a href="#fnref1" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
|
||||
<li id="fn2" role="doc-endnote"><p>This is a worse idea than it sounds. Starting with the new Valhall architecture, Mali allocates varyings much more efficiently.<a href="#fnref2" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
|
||||
<li id="fn3" role="doc-endnote"><p>Why the duplication? I have not yet observed Metal using different programs for each. However, for front buffer rendering, partial renders need to be flushed to a temporary buffer for this scheme to work. Of course, you may as well use double buffering at that point.<a href="#fnref3" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
|
||||
</ol>
|
||||
</section>
|
||||
</description><guid isPermaLink="true">https://rosenzweig.io/blog/asahi-gpu-part-5.html</guid><pubDate>Fri, 13 May 2022 00:00:00 -0500</pubDate></item><item><title>Software freedom isn't about licenses -- it's about power.</title><link>https://rosenzweig.io/blog/software-freedom-isnt-about-licenses-its-about-power.html</link><description><p>A restrictive end-user license agreement is one way a company can exert power over the user. When the free software movement was founded thirty years ago, these restrictive licenses were the primary user-hostile power dynamic, so permissive and copyleft licenses emerged as synonyms to software freedom. Licensing does matter; user autonomy is lost with subscription models, revocable licenses, binary-only software, and onerous legal clauses. Yet these issues pertinent to desktop software do not scratch the surface of today’s digital power dynamics.</p>
|
||||
<p>Today, companies exert power over their users by: tracking, selling data, psychological manipulation, intrusive advertising, planned obsolescence, and hostile Digital “Rights” Management (DRM) software. These issues affect every digital user, technically inclined or otherwise, on desktops and smartphones alike.</p>
|
||||
<p>The free software movement promised to right these wrongs via free licenses on the source code, with adherents arguing free licenses provide immunity to these forms of malware since users could modify the code. Unfortunately most users lack the resources to do so. While the most egregious violations of user freedom come from companies publishing proprietary software, these ills can remain unchecked even in open source programs, and not all proprietary software exhibits these issues. The modern browser is nominally free software containing the trifecta of telemetry, advertisement, and DRM; a retro video game is proprietary software but relatively harmless.</p>
|
||||
<p>As such, it’s not enough to look at the license. It’s not even enough to consider the license and a fixed set of issues endemic to proprietary software; the context matters. Software does not exist in a vacuum. Just as proprietary software tends to integrate with other proprietary software, free software tends to integrate with other free software. Software freedom <em>in context</em> demands a gentle nudge towards software in user interests, rather than corporate interests.</p>
|
||||
<p>How then should we conceptualize software freedom?</p>
|
||||
<p>Consider the three adherents to free software and open source: hobbyists, corporations, and activists. Individual hobbyists care about tinkering with the software of their choice, emphasizing freely licensed source code. These concerns do not affect those who do not make a sport out of modifying code. There is <em>nothing wrong</em> with this, but it will never be a household issue.</p>
|
||||
<p>For their part, large corporations claim to love “open source”. No, they do not care about the social movement, only the cost reduction achieved by taking advantage of permissively licensed software. This corporate emphasis on licensing is often to the detriment of software freedom in the broader context. In fact, it is this irony that motivates software freedom beyond the license.</p>
|
||||
<p>It is the activist whose ethos must apply to everyone regardless of technical ability or financial status. There is no shortage of open source software, often of corporate origin, but this is insufficient – it is the power dynamic we must fight.</p>
|
||||
<p>We are not alone. Software freedom is intertwined with contemporary social issues, including copyright reform, privacy, sustainability, and Internet addiction. Each issue arises as a hostile power dynamic between a corporate software author and the user, with complicated interactions with software licensing. Disentangling each issue from licensing provides a framework to address nuanced questions of political reform in the digital era.</p>
|
||||
<p>Copyright reform generalizes the licensing approaches of the free software and free culture movements. Indeed, free licenses empower us to freely use, adapt, remix, and share media and software alike. However, proprietary licenses micromanaging the core of human community and creativity are doomed to fail. Proprietary licenses have had little success preventing the proliferation of the creative works they seek to “protect”, and the rights to adapt and remix media have long been exercised by dedicated fans of proprietary media, producing volumes of fanfiction and fan art. The same observation applies to software: proprietary end-user license agreements have stopped neither file sharing nor reverse-engineering. In fact, a unique creative fandom around proprietary software has emerged in video game modding communities. Regardless of legal concerns, the human imagination and spirit of sharing persists. As such, we need not judge anyone for proprietary software and media in their life; rather, we must work towards copyright reform and free licensing to protect them from copyright overreach.</p>
|
||||
<p>Privacy concerns are also traditional in software freedom discourse. True, secure communications software can never be proprietary, given the possibility of backdoors and impossibility of transparent audits. Unfortunately, the converse fails: there are freely licensed programs that inherently compromise user privacy. Consider third-party clients to centralized unencrypted chat systems. Although two users of such a client privately messaging one another are using only free software, if their messages are being data mined, there is still harm. The need for context is once more underscored.</p>
|
||||
<p>Sustainability is an emergent concern, tying to software freedom via the electronic waste crisis. In the mobile space, where deprecating smartphones after a few short years is the norm and lithium batteries are hanging around in landfills indefinitely, we see the paradox of a freely licensed operating system with an abysmal social track record. A curious implication is the need for free device drivers. Where proprietary drivers force devices into obsolescence shortly after the vendor abandons them in favour of a new product, free drivers enable long-term maintenance. As before, licensing is not enough; the code must also be upstreamed and mainlined. Simply throwing source code over a wall is insufficient to resolve electronic waste, but it is a prerequisite. At risk is the right of a device owner to continue use of a device they have already purchased, even after the manufacturer no longer wishes to support it. Desired by climate activists and the dollar conscious alike, we cannot allow software to override this right.</p>
|
||||
<p>Beyond copyright, privacy, and sustainability concerns, no software can be truly “free” if the technology itself shackles us, dumbing us down and driving us to outrage for clicks. Thanks to television culture spilling onto the Internet, the typical citizen has less to fear from government wiretaps than from themselves. For every encrypted message broken by an intelligence agency, thousands of messages are willingly broadcast to the public, seeking instant gratification. Why should a corporation or a government bother snooping into our private lives, if we present them on a silver platter? Indeed, popular open source implementations of corrupt technology do not constitute success, an issue epitomized by free software responses to social media. No, even without proprietary software, centralization, or cruel psychological manipulation, the proliferation of social media still endangers society.</p>
|
||||
<p>Overall, focusing on concrete software freedom issues provides room for nuance, rather than the traditional binary view. End-users may make more informed decisions, with awareness of technologies’ trade-offs beyond the license. Software developers gain a framework to understand how their software fits into the bigger picture, as a free license is necessary but not sufficient for guaranteeing software freedom today. Activists can divide-and-conquer.</p>
|
||||
<p>Many outside of our immediate sphere understand and care about these issues; long-term success requires these allies. Claims of moral superiority by licenses are unfounded and foolish; there is no success backstabbing our friends. Instead, a nuanced approach broadens our reach. While abstract moral philosophies may be intellectually valid, they are inaccessible to all but academics and the most dedicated supporters. Abstractions are perpetually on the political fringe, but these concrete issues are already understood by the general public. Furthermore, we cannot limit ourselves to technical audiences; understanding network topology cannot be a prerequisite to private conversations. Overemphasizing the role of source code and under-emphasizing the power dynamics at play is a doomed strategy; for decades we have tried and failed. In a post-Snowden world, there is too much at stake for more failures. Reforming the specific issues paves the way to software freedom. After all, social change is harder than writing code, but with incremental social reform, licenses become the easy part.</p>
|
||||
<p>The nuanced analysis even helps individual software freedom activists. Purist attempts to refuse non-free technology categorically are laudable, but outside a closed community, going against the grain leads to activist burnout. During the day, employers and schools invariably mandate proprietary software, sometimes used to facilitate surveillance. At night, popular hobbies and social connections today are mediated by questionable software, from the DRM in a video game to the surveillance of a chat with a group of friends. Cutting ties with friends and abandoning self-care as a <em>prerequisite</em> to fighting powerful organizations seems noble, but is futile. Even without politics, there remain technical challenges to using only free software. Layering in other concerns, or perhaps foregoing a mobile smartphone, only amplifies the risk of software freedom burnout.</p>
|
||||
<p>As an application, this approach to software freedom brings to light disparate issues with the modern web raising alarm in the free software community. The traditional issue is proprietary JavaScript, a licensing question, yet considering only JavaScript licensing prompts both imprecise and inaccurate conclusions about web “applications”. Deeper issues include rampant advertising and tracking; the Internet is the largest surveillance network in human history, largely for commercial aims. To some degree, these issues are mitigated by script, advertisement, and tracker blockers; these may be pre-installed in a web browser for harm reduction in pursuit of a gentler web. However, the web’s fatal flaw is yet more fundamental. By design, when a user navigates to a URL, their browser executes <em>whatever</em> code is piped on the wire. Effectively, the web implies an automatic auto-update, regardless of the license of the code. Even if the code is benign, it is still every year more expensive to run, forcing a hardware upgrade cycle deprecating old hardware which would work if only the web weren’t bloated by corporate interests. A subtler point is the “attention economy” tied into the web. While it’s hard to become addicted to reading in a text-only browser, binge-watching DRM-encumbered television is a different story. Half-hearted advances like “Reading Mode” are limited by the ironic distribution of documents over an app store. On the web, disparate issues of DRM, forced auto-update, privacy, sustainability, and psychological dark patterns converge to a single worst case scenario for software freedom. The licenses were only the beginning.</p>
|
||||
<p>Nevertheless, there is cause for optimism. Framed appropriately, the fight for software freedom <em>is</em> winnable. To fight for software freedom, fight for privacy. Fight for copyright reform. Fight for sustainability. Resist psychological dark patterns. At the heart of each is a software freedom battle – keep fighting and we can win.</p>
|
||||
<h2 id="see-also">See also</h2>
|
||||
<p><a href="https://techautonomy.org/">Declaration of Digital Autonomy</a></p>
|
||||
<p><a href="https://www.inkandswitch.com/local-first.html">Local-first software: You own your data, in spite of the cloud</a></p>
|
||||
<p><a href="https://www.gnu.org/philosophy/wwworst-app-store.html">The WWWorst App Store</a></p>
|
||||
</description><guid isPermaLink="true">https://rosenzweig.io/blog/software-freedom-isnt-about-licenses-its-about-power.html</guid><pubDate>Sun, 28 Mar 2021 00:00:00 -0500</pubDate></item><item><title>Fun and Games with Exposure Notifications</title><link>https://rosenzweig.io/blog/fun-and-games-with-exposure-notifications.html</link><description><p><a href="https://en.wikipedia.org/wiki/Exposure_Notification"><em>Exposure Notifications</em></a> is a protocol developed by Apple and Google for facilitating COVID-19 contact tracing on <em>mobile phones</em> by exchanging codes with nearby phones over <a href="https://en.wikipedia.org/wiki/Bluetooth">Bluetooth</a>, implemented within the Android and iOS operating systems, now available here in Toronto.</p>
|
||||
<p>Wait – phones? Android and iOS only? Can’t my <a href="https://debian.org">Debian</a> laptop participate? It has a recent Bluetooth chip. What about phones running GNU/Linux distributions like the <a href="https://en.wikipedia.org/wiki/PinePhone">PinePhone</a> or <a href="https://en.wikipedia.org/wiki/Librem_5">Librem 5</a>?</p>
|
||||
<p>Exposure Notifications breaks down neatly into three sections: a Bluetooth layer, some cryptography, and integration with local public health authorities. Linux is up to the task, via <a href="http://www.bluez.org/">BlueZ</a>, <a href="https://en.wikipedia.org/wiki/OpenSSL">OpenSSL</a>, and some <a href="https://en.wikipedia.org/wiki/Python_(programming_language)">Python</a>.</p>
|
||||
<p>Given my background, will this build to be a <a href="/blog/my-name-is-cafe-beverage.html">reverse-engineering epic</a> resulting in a novel open stack for a closed system?</p>
|
||||
<p>…</p>
|
||||
<p>Not at all. The specifications for the Exposure Notifications are available for both the <a href="https://covid19-static.cdn-apple.com/applications/covid19/current/static/contact-tracing/pdf/ExposureNotification-BluetoothSpecificationv1.2.pdf?1">Bluetooth protocol</a> and the <a href="https://covid19-static.cdn-apple.com/applications/covid19/current/static/contact-tracing/pdf/ExposureNotification-CryptographySpecificationv1.2.pdf?1">underlying cryptography</a>. A <a href="https://github.com/google/exposure-notifications-internals">partial reference implementation</a> is available for Android, as is an independent Android implementation in <a href="https://github.com/microg/android_packages_apps_GmsCore">microG</a>. In Canada, the key servers run an <a href="https://github.com/cds-snc/covid-alert-server">open source stack</a> originally built by Shopify and now maintained by the <a href="https://digital.canada.ca/">Canadian Digital Service</a>, including open <a href="https://github.com/cds-snc/covid-alert-server/tree/master/proto">protocol documentation</a>.</p>
|
||||
<p>All in all, this is looking to be a smooth-sailing weekend<a href="#fn1" class="footnote-ref" id="fnref1" role="doc-noteref"><sup>1</sup></a> project.</p>
|
||||
<p>The devil’s in the details.</p>
|
||||
<h2 id="bluetooth">Bluetooth</h2>
|
||||
<p>Exposure Notifications operates via Bluetooth Low Energy “advertisements”. Scanning for other devices is as simple as scanning for advertisements, and broadcasting is as simple as advertising ourselves.</p>
|
||||
<p>On an Android phone, this is handled deep within Google Play Services. Can we drive the protocol from userspace on a regular GNU/Linux laptop? It depends. Not all laptops support Bluetooth, not all Bluetooth implementations support Bluetooth Low Energy, and I hear not all Bluetooth Low Energy implementations properly support undirected transmissions (“advertising”).</p>
|
||||
<p>Luckily in my case, I develop on an Debianized Chromebook with a Wi-Fi/Bluetooth module. I’ve never used the Bluetooth, but it turns out the module has full support for advertisements, verified with the <code>lescan</code> (<strong>L</strong>ow <strong>E</strong>nergy <strong>Scan</strong>) command of the <code>hcitool</code> Bluetooth utility.</p>
|
||||
<p><code>hcitool</code> is a part of BlueZ, the standard Linux library for Bluetooth. Since <code>lescan</code> is able to detect nearby phones running Exposure Notifications, pouring through its source code is a good first step to our implementation. With some minor changes to <code>hcitool</code> to dump packets as raw hex and to filter for the Exposure Notifications protocol, we can print all nearby Exposure Notifications advertisements. So far, so good.</p>
|
||||
<p>That’s about where the good ends.</p>
|
||||
<p>While scanning is simple with reference code in <code>hcitool</code>, advertising is complicated by BlueZ’s lack of an interface at the time of writing. While a general “enable advertising” routine exists, routines to set advertising parameters and data per the Exposure Notifications specification are unavailable. This is not a showstopper, since BlueZ is itself an open source userspace library. We can drive the Bluetooth module the same way BlueZ does internally, filling in the necessary gaps in the API, while continuing to use BlueZ for the heavy-lifting.</p>
|
||||
<p>Some care is needed to multiplex scanning and advertising within a single thread while remaining power efficient. The key is that advertising, once configured, is handled entirely in hardware without CPU intervention. On the other hand, scanning does require CPU involvement, but it is <em>not</em> necessary to scan continuously. Since COVID-19 is thought to transmit from <em>sustained</em> exposure, we only need to scan every few minutes. (Food for thought: how does this connect to the sampling theorem?)</p>
|
||||
<p>Thus we can order our operations as:</p>
|
||||
<ul>
|
||||
<li>Configure advertising</li>
|
||||
<li>Scan for devices</li>
|
||||
<li>Wait for several minutes</li>
|
||||
<li>Repeat.</li>
|
||||
</ul>
|
||||
<p>Since most of the time the program is asleep, this loop is efficient. It additionally allows us to reconfigure advertising every ten to fifteen minutes, in order to change the Bluetooth address to prevent tracking.</p>
|
||||
<p>All of the above amounts to a few hundred lines of C code, treating the Exposure Notifications packets themselves as opaque random data.</p>
|
||||
<h2 id="cryptography">Cryptography</h2>
|
||||
<p>Yet the data is far from random; it is the result of a series of operations in terms of secret keys defined by the Exposure Notifications cryptography specification. Every day, a “temporary exposure key” is generated, from which a “rolling proximity identifier key” and an “associated encrypted metadata key” are derived. These are used to generate a “rolling proximity identifier” and the “associated encrypted metadata”, which are advertised over Bluetooth and changed in lockstep with the Bluetooth random addresses.</p>
|
||||
<p>There are lots of moving parts to get right, but each derivation reuses a common encryption primitive: HKDF-SHA256 for key derivation, AES-128 for the rolling proximity identifier, and AES-128-CTR for the associated encrypted metadata. Ideally, we would grab a state-of-the-art library of cryptography primitives like <a href="https://nacl.cr.yp.to/"><code>NaCl</code></a> or <a href="https://doc.libsodium.org/"><code>libsodium</code></a> and wire everything up.</p>
|
||||
<p>First, some good news: once these routines are written, we can reliably unit test them. Though the specification states that “test vectors… are available upon request”, it isn’t clear <em>who</em> to request from. But Google’s reference implementation is itself unit-tested, and sure enough, it contains a <a href="https://github.com/google/exposure-notifications-internals/blob/a0394e69c51aa118f5000b8a2c2f15f1f9aedb7d/app/src/androidTest/java/com/google/samples/exposurenotification/testing/TestVectors.java"><code>TestVectors.java</code></a> file, from which we can grab the vectors for a complete set of unit tests.</p>
|
||||
<p>After patting ourselves on the back for writing unit tests, we’ll need to pick a library to implement the cryptography. Suppose we try <code>NaCl</code> first. We’ll quickly realize the primitives we need are missing, so we move onto <code>libsodium</code>, which is backwards-compatible with NaCl. For a moment, this will work – <code>libsodium</code> has upstream support for HKDF-SHA256. Unfortunately, the version of <code>libsodium</code> shipping in Debian testing is too old for HKDF-SHA256. Not a big problem – we can backwards port the implementation, written in terms of the underlying HMAC-SHA256 operations, and move on to the AES.</p>
|
||||
<p>AES is a standard symmetric cipher, so <code>libsodium</code> has excellent support… for some modes. However standard, AES is not <em>one</em> cipher; it is a family of ciphers with different key lengths and operating modes, with dramatically different security properties. “AES-128-CTR” in the Exposure Notifications specification is clearly 128-bit AES in CTR (<strong>C</strong>oun<strong>t</strong>e<strong>r</strong>) mode, but what about “AES-128” alone, stated to operate on a “single AES-128 block”?</p>
|
||||
<p>The mode implicitly specified is known as ECB (<strong>E</strong>lectronic <strong>C</strong>ode<strong>b</strong>ook) mode and is known to have fatal security flaws in most applications. Because AES-ECB is generally insecure, <code>libsodium</code> does not have any support for this cipher mode. Great, now we have <em>two</em> problems – we have to rewrite our cryptography code against a new library, and we have to consider if there is a vulnerability in Exposure Notifications.</p>
|
||||
<p>ECB’s crucial flaw is that for a given key, identical plaintext will always yield identical ciphertext, regardless of position in the stream. Since AES is block-based, this means identical blocks yield identical ciphertext, leading to <a href="https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#ECB">trivial cryptanalysis</a>.</p>
|
||||
<p>In Exposure Notifications, ECB mode is used only to derive rolling proximity identifiers from the rolling proximity identifier key and the timestamp, by the equation:</p>
|
||||
<pre><code>RPI_ij = AES_128_ECB(RPIK_i, PaddedData_j)</code></pre>
|
||||
<p>…where <code>PaddedData</code> is a function of the quantized timestamp. Thus the issue is avoided, as every plaintext will be unique (since timestamps are monotonically increasing, unless you’re trying to contact trace <em>Back to the Future</em>).</p>
|
||||
<p>Nevertheless, <code>libsodium</code> doesn’t know that, so we’ll need to resort to a ubiquitous cryptography library that doesn’t, uh, take security quite so seriously…</p>
|
||||
<p>I’ll leave <a href="https://en.wikipedia.org/wiki/Heartbleed">the implications</a> up to your imagination.</p>
|
||||
<h2 id="database">Database</h2>
|
||||
<p>While the Bluetooth and cryptography sections are governed by upstream specifications, making sense of the data requires tracking a significant amount of state. At <em>minimum</em>, we must:</p>
|
||||
<ul>
|
||||
<li>Record received packets (the Rolling Proximity Identifier and the Associated Encrypted Metadata).</li>
|
||||
<li>Query received packets for diagnosed identifiers.</li>
|
||||
<li>Record our Temporary Encryption Keys.</li>
|
||||
<li>Query our keys to upload if we are diagnosed.</li>
|
||||
</ul>
|
||||
<p>If we were so inclined, we could handwrite all the serialization and concurrency logic and hope we don’t have a bug that results in COVID-19 mayhem.</p>
|
||||
<p>A better idea is to grab <a href="https://sqlite.org/index.html">SQLite</a>, perhaps <a href="https://sqlite.org/mostdeployed.html">the most deployed software in the world</a>, and express these actions as SQL queries. The database persists to disk, and we can even express natural unit tests with a synthetic in-memory database.</p>
|
||||
<p>With this infrastructure, we’re now done with the primary daemon, recording Exposure Notification identifiers to the database and broadcasting our own identifiers. That’s not interesting if we never <em>do</em> anything with that data, though. Onwards!</p>
|
||||
<h2 id="key-retrieval">Key retrieval</h2>
|
||||
<p>Once per day, Exposure Notifications implementations are expected to query the server for Temporary Encryption Keys associated with diagnosed COVID-19 cases. From these keys, the cryptography implementation can reconstruct the associated Rolling Proximity Identifiers, for which we can query the database to detect if we have been exposed.</p>
|
||||
<p>Per Google’s <a href="https://developers.google.com/android/exposure-notifications/exposure-key-file-format">documentation</a>, the servers are expected to return a <code>zip</code> file containing two files:</p>
|
||||
<ul>
|
||||
<li><code>export.bin</code>: a container serialized as <a href="https://en.wikipedia.org/wiki/Protocol_Buffers">Protocol Buffers</a> containing Diagnosis Keys</li>
|
||||
<li><code>export.sig</code>: a signature for the export with the public health agency’s key</li>
|
||||
</ul>
|
||||
<p>The signature is not terribly interesting to us. On Android, it appears the system pins the public keys of recognized public health agencies as an integrity check for the received file. However, this public key is given directly to Google; we don’t appear to have an easy way to access it.</p>
|
||||
<p>Does it matter? For our purposes, it’s unlikely. The Canadian key retrieval server is already transport-encrypted via HTTPS, so tampering with the data would already require compromising a certificate authority in addition to intercepting the requests to <a href="https://canada.ca" class="uri">https://canada.ca</a>. Broadly speaking, that limits attackers to nation-states, and since Canada has no reason to attack its own infrastructure, that limits our threat model to foreign nation-states. International intelligence agencies probably have better uses of resources than getting people to take extra COVID tests.</p>
|
||||
<p>It’s worth noting other countries’ implementations could serve this zip file over plaintext HTTP, in which case this signature check becomes important.</p>
|
||||
<p>Focusing then on <code>export.bin</code>, we may import the relevant protocol buffer definitions to extract the keys for matching against our database. Since this requires only read-only access to the database and executes infrequently, we can safely perform this work from a separate process written in a higher-level language like Python, interfacing with the cryptography routines over the Python <a href="https://docs.python.org/3/library/ctypes.html">foreign function interface <code>ctypes</code></a>. Extraction is easy with the Python protocol buffers implementation, and downloading should be as easy as a <code>GET</code> request with the standard library’s <code>urllib</code>, right?</p>
|
||||
<p>Here we hit a gotcha: the retrieval endpoint is guarded behind an <a href="https://en.wikipedia.org/wiki/HMAC">HMAC</a>, requiring authentication to download the <code>zip</code>. The protocol documentation states:</p>
|
||||
<blockquote>
|
||||
<p>Of course there’s no reliable way to truly authenticate these requests in an environment where millions of devices have immediate access to them upon downloading an Application: this scheme is purely to make it much more difficult to casually scrape these keys.</p>
|
||||
</blockquote>
|
||||
<p>Ah, security by obscurity. Calculating the HMAC itself is simple given the documentation, but it requires a “secret” HMAC key specific to the server. As the documentation is aware, this key is hardly secret, but it’s not available on the Canadian Digital Service’s <a href="https://github.com/cds-snc/covid-alert-app">official repositories</a>. Interoperating with the upstream servers would require some “extra” tricks.</p>
|
||||
<p>From purely academic interest, we can write and debug our implementation without any such authorization by running our own sandbox server. Minus the configuration, the server source is available, so after spinning up a virtual machine and fighting with Go versioning, we can test our Python script.</p>
|
||||
<p>Speaking of a personal sandbox…</p>
|
||||
<h2 id="key-upload">Key upload</h2>
|
||||
<p>There is one essential edge case to the contact tracing implementation, one that we <em>can’t</em> test against the Canadian servers. And edge cases matter. In effect, the entire Exposure Notifications infrastructure is designed for the edge cases. If you don’t care about edge cases, you don’t care about digital contact tracing (so please, stay at home.)</p>
|
||||
<p>The key feature – and key edge case – is uploading Temporary Exposure Keys to the Canadian key server in case of a COVID-19 diagnosis. This upload requires an alphanumeric code generated by a healthcare provider upon diagnosis, so if we used the shared servers, we couldn’t test an implementation. With our sandbox, we can generate as many alphanumeric codes as we’d like.</p>
|
||||
<p>Once sandboxed, there isn’t much to the implementation itself: the keys are snarfed out of the SQLite database, we handshake with the server over protocol buffers marshaled over POST requests, and we throw in some public-key cryptography via the Python bindings to <code>libsodium</code>.</p>
|
||||
<p>This functionality neatly fits into a second dedicated Python script which does <em>not</em> interface with the main library. It’s exposed as a command line interface with flow resembling that of the mobile application, adhering reasonably to the UNIX philosophy. Admittedly I’m not sure wrestling with the command line is top on the priority list of a Linux hacker ill with COVID-19. Regardless, the interface is suitable for higher-level (graphical) abstractions.</p>
|
||||
<p>Problem solved, but of course there’s a gotcha: if the request is malformed, an error should be generated as a key robustness feature. Unfortunately, while developing the script against my sandbox, a bug led the request to be dropped unexpectedly, rather than returning with an error message. On the server implemented in <a href="https://en.wikipedia.org/wiki/Go_(programming_language)">Go</a>, there was an apparent <code>nil</code> dereference. Oops. Fixing this isn’t necessary for this project, but it’s still a bug, even if it requires a COVID-19 diagnosis to trigger. So I went and did the Canadian thing and sent a pull request.</p>
|
||||
<h2 id="conclusion">Conclusion</h2>
|
||||
<p>All in all, we end up with a Linux implementation of Exposure Notifications functional in Ontario, Canada. What’s next? Perhaps supporting contact tracing systems elsewhere in the world – patches welcome. Closer to home, while functional, the aesthetics are not (yet) anything to write home about – perhaps we could write a touch-based Linux interface for mobile Linux interfaces like <a href="https://en.wikipedia.org/wiki/KDE_Plasma_5#Plasma_Mobile">Plasma Mobile</a> and <a href="https://developer.puri.sm/Librem5/Software_Reference/Environments/Phosh.html">Phosh</a>, maybe even running it on a Android flagship flashed with <a href="https://en.wikipedia.org/wiki/PostmarketOS">postmarketOS</a> to go full circle.</p>
|
||||
<p><a href="https://gitlab.freedesktop.org/alyssa/liben">Source code for <code>liben</code> is available</a> for any one who dares go near. Compiling from source is straightforward but necessary at the time of writing. As for packaging?</p>
|
||||
<p>Here’s hoping COVID-19 contact tracing will be obsolete by the time <code>liben</code> hits Debian stable.</p>
|
||||
<section class="footnotes" role="doc-endnotes">
|
||||
<hr />
|
||||
<ol>
|
||||
<li id="fn1" role="doc-endnote"><p>Today (Monday) is Labour Day, so this is a 3-day weekend. But I started on Saturday and posted this today, so it <em>technically</em> counts.<a href="#fnref1" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
|
||||
</ol>
|
||||
</section>
|
||||
</description><guid isPermaLink="true">https://rosenzweig.io/blog/fun-and-games-with-exposure-notifications.html</guid><pubDate>Mon, 07 Sep 2020 00:00:00 -0500</pubDate></item><item><title>The Federation Fallacy</title><link>https://rosenzweig.io/blog/the-federation-fallacy.html</link><description><p>Throughout the free software community, an unbridled aura of justified mistrust fills the air: mistrust of large corporations, mistrust of governments, and of course, mistrust of proprietary software. Each mistrust is connected by a critical thread: centralisation.</p>
|
||||
<p>Thus, permeating the community are calls for decentralisation. To attack the information silos, corporate conglomerates, and governmental surveillance, decentralisation calls for <em>individuals</em> to host servers for their own computing, rather than defaulting on the servers of those rich in data.</p>
|
||||
<p>In the decentralised dream, every user hosts their own server. Every toddler and grandmother is required to become their own system administrator. This dream is an accessibility nightmare, for if advanced technical skills are the price to privacy, all but the technocratic elite are walled off from freedom.</p>
|
||||
<p>Federation is a compromise. Rather than everyone hosting their own systems, ideally every technically able person would host a system for themselves and for their friends, and everyone’s systems could connect. If I’m technically able, I can host an “instance” not only for myself but also my loved ones around me. In theory, through federation my friends and family could take back their computing from the conglomerates, by trusting me and ceding power to me to cover the burden of their system administration.</p>
|
||||
<p>What a dream.</p>
|
||||
<p>Federated systems are all around us. The classic example is e-mail. I host my own email server, so I have the privilege of managing my own email address. According to the ideas of decentralisation, for an e-mail user to be fully free, they should host their own e-mail server.</p>
|
||||
<p>But do <em>you</em> host your own mail server? Do your friends? Does your <em>grandmother</em>? Setting up a mail server often is time-consuming, ad hoc, and brittle; despite technical literacy and the hours I poured in, I continue to have problems with my e-mail delivery. Of course, e-mail is <em>technically</em> federated, but for pragmatic reasons, most people’s personal e-mail is controlled by a centralised service like Google Gmail. Is Google a small individual you know personally?</p>
|
||||
<p>There’s no surprise large companies administer most e-mail accounts; it is the expected consequence of <em>economies of scale</em>. Due to the overhead of running a mail server, it makes economic sense to centralise. Decentralisation, while certainly possible, is impractical for email; accordingly, there are considerable privacy and censorship risks for many e-mail users, submitting to the rules of a massive service provider.</p>
|
||||
<p>But maybe, due to its configuration complexity, e-mail could be an outlier. What about the federated chat protocol, XMPP?</p>
|
||||
<p>Well? How many people do <em>you</em> know who run their own XMPP server?</p>
|
||||
<p>The protocol family claims over one billion users; you may unwittingly be one of them. But almost every one of those users is connecting not to the federated, open paradise, but rather to a walled garden. Of course, a few users connect to their personal server via a free software client, but most connect to <em>Facebook’s</em> walled garden, via <em>Facebook</em>’s app: WhatsApp.</p>
|
||||
<p>Yes, <em>internally</em> the ever-popular, ever-proprietary WhatsApp was once a federated protocol. <a href="https://en.wikipedia.org/wiki/WhatsApp#Technical">Inside WhatsApp is XMPP</a>, but WhatsApp users are isolated from non-WhatsApp users. Likewise, users preferring free software for practical or ideological reasons are isolated from their friends on WhatsApp. Federation was baked into the genes of this protocol, but while the original “freedom-preserving” chat system claims a few technically advanced adherents, the freedom of the masses was unfortunately lost to corporate interests.</p>
|
||||
<p>Chat protocols aside, arguably the web itself is a classic, if overlooked, example of a federated system. The web, as a collection of interlinked websites backed by the <a href="https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol">Hypertext Transfer Protocol</a>, contains the two essential features of a federated, decentralised system. Every author is their own publisher with their own web server, decentralising the web. Similarly, authors are encouraged to link their pages to other pages on other parts of the Internet, hosted by other authors, and in exchange, other pages tend to link to them, creating an interconnected – federated? – system. The fabric of the Internet itself follows the decentralised technical structure.</p>
|
||||
<p>Yet the web is no friend of freedom. The overwhelming majority of web traffic is to commercial silos without users’ best interests at heart. It is certainly possible, if complicated, to host a web server oneself. But practically, the English-speaking web is undeniably centralised around Silicon Valley enterprises, the same enterprises that pose considerable threats to user freedom without the possibility of informed consent. Decentralisation specifically seeks to push <em>back</em> against these Internet masters, yet these centralised giants are operating within the technical framework of a decentralised system.</p>
|
||||
<p>Admittedly, the web is nebulous as an example of federation. What do we make of <a href="https://joinmastodon.org/">Mastodon</a>, a freedom-respecting federated alternative to Twitter, whose network of servers hosted by diverse individuals and organizations is lovingly known as the “fediverse”?</p>
|
||||
<p>Mastodon is perhaps the most promising example of a federated system staying true to its grassroots ideals, so far lacking the tell-tale signs of large-scale corporate tampering. If any federated system can work, let it be Mastodon.</p>
|
||||
<p>But let’s look at the data first. In comparison to some other federated systems, a comprehensive list of federated Mastodon instances (servers) is available for browsing online at <a href="https://instances.social">instances.social</a>. Some non-Mastodon microblogging services are mixed in, as they federate with Mastodon, knitting together the larger “fediverse”. Nevertheless, some simple scripting allows the machine-readable list to be downloaded and processed.</p>
|
||||
<p>Analysing the data, after filtering out instances with no users, we see there are 3070 listed instances. So far, sounds good – that is 3070x more theoretically independent instances than Twitter.</p>
|
||||
<p>Looking at user count, we see there are just under two-million user accounts registered. In other words, there is an average of 642 users per instance. Already the federation fantasy is feeling shaky. How could one system administrator maintain close friendships with over six-hundred people? How could so many people trust a single administrator with their private status updates and direct messages? It seems improbable, if not impossible. With the amount of time required to maintain intimacy with 640 people, there would be no time left in a day to maintain a large Mastodon instance!</p>
|
||||
<p>But what’s really striking about the data is the <em>distribution</em> of users across the instances. In the federation fantasy, given 3,070 instances, we hope that 50% of the user base is spread across 1,535 instances, and the other 50% across the other 1535 instances. That is, in fantasy land, every instance would host an equal number of users (642).</p>
|
||||
<p>In reality, guess how many instances encompass half of the user base. Maybe 1,000? Alright, there are some big instances in there, so perhaps 100? Well, there are a lot of really tiny instances mixed in, so possibly only 20?</p>
|
||||
<p>The answer?</p>
|
||||
<p><strong>Three</strong>.</p>
|
||||
<p>Just <em>three</em> instances encompass 50.8% of users.</p>
|
||||
<p>The most popular instance in the Mastodon universe sports over a <em>half a million users</em>. The runner up is <a href="https://mastodon.social">mastodon.social</a>, the flagship instance run by the developer, clocking in at just over three-hundred thousand. These two instances alone encompass 41.2% of users.</p>
|
||||
<p>It is true that many Mastodon users have multiple accounts spread out across different instances, so it may be more reliable to check by a metric like number of status updates. Unfortunately, results there are similar: half of reported status updates correspond to a mere five instances.</p>
|
||||
<p>Overall, Mastodon certainly encourages people to use its <em>technical</em> ability to host their own instances for themselves, their friends, and their shared interest groups. True, federation technically allows all 3,070 instances to connect to each other, subject to the policies of each instance owner. But practically, what have we achieved when the typical Mastodon user has an account on one of just <em>three</em> instances?</p>
|
||||
<p>If we define its success by decentralisation, unfortunately – unsurprisingly – Mastodon has failed. Unless you use an obscure micro instance, what good is decentralisation anyway?</p>
|
||||
<p><a href="/Mastodon-Users.png"><img src="/Mastodon-Users.png" alt="Mastodon users per instance" /></a></p>
|
||||
<p>Visually, the above graph shows user count on the Y-axis, where the X-axis corresponds to the instance popularity ranking. So, the most popular instance is the left-most point, the second most popular instance is a hair to the right of it, and all the way on the right is the tiniest instance. In total, the plot shows the distribution of users on (primarily Mastodon) instances</p>
|
||||
<p>In the federated ideal, where all instances are created equal, the graph should be a horizontal line showing each instance with 642 people.</p>
|
||||
<p>Of course, due to substantial inequality between instance size, we expect to see a <a href="https://en.wikipedia.org/wiki/Power_law">power distribution</a>, with a spike on the left that quickly falls and tapers out. Power laws govern much of the real world; many phenomena behave according to this unequal distribution.</p>
|
||||
<p>But look at that graph. Calling this distribution a power law would be generous to say the least. There is a <em>massive</em> spike corresponding to just a few instances, and the rest of the graph is nearly invisible to the naked eye, so tiny and so overshadowed by just a few giants. Frankly, this distribution is closer to the <a href="https://en.wikipedia.org/wiki/Dirac_delta_function">Dirac delta function</a> than a power law.</p>
|
||||
<p>Ultimately, there are two types of production-quality networked systems: those designed for centralisation, and those designed for federation. But from the tales of e-mail, XMPP, the web, and Mastodon, it is clear that <em>federation does not mean decentralisation</em>. Each federated system analyzed above only became production-quality and accessible to the masses at the immense cost of <em>centralisation</em>. Each service retains the theoretical ability to federate with tiny self-hosted servers, but the vast majority of users are <em>de facto</em> concentrated about a few major servers. E-mail concentrated on Google, XMPP concentrated on Facebook, the web concentrated on Silicon Valley, and Mastodon concentrated onto a few flagship instances.</p>
|
||||
<p>Indeed, it seems all networked systems tend towards centralisation as the natural consequence of growth. Some systems, both legitimate and illegitimate, are intentionally designed for centralisation. Other systems, like those in the Mastodon universe, are specifically designed to avoid centralisation, but even these succumb to the centralised black hole as their user bases grow towards the event horizon.</p>
|
||||
<p>Consequently, it is not enough to build systems that “can” federate: all four case studies above <em>can</em> federate but de facto stay centralised. Nor is it enough to “save ourselves”, self-hosting our own decentralised digital islands, while ignoring the reality of the masses. We cannot close our eyes and rest, content with freedom in our personal bubble, ignoring the reality of our non-technical friends and family who do not enjoy the same luxuries of privacy and free speech. We cannot ignore their struggles after resolving our own, justifying our behaviour to ourselves given that there is no technical obstacle to their digital freedom “if only” they abandoned their convenience and dedicated themselves to learning system administration and software debugging. With or without decentralised free software, within the technocracy our non-technical loved ones are barred from liberty over their own lives. Decentralisation is certainly better than dependence on centralised corporate conglomerates, but <em>for whom?</em> A society free for the few is a society in chains.</p>
|
||||
<p>Nevertheless, as we mourn the centralised fates of these promising systems and yearn to improve the future, we must understand that <em>centralisation is inevitable</em>. For those of us immersed in the decentralised dream, this fact is uncomfortable to face, but until we do, we will never move on to build systems that are truly free. The fact is, as networked systems designers our choice is rarely “concentrated power or dispersed power?”, but rather “where will power centralise?” If we quixotically opt to cede power over our creations via decentralisation, inevitably someone else will fill the power gap.</p>
|
||||
<p>Alas, naive decentralisation is an experiment in anarchy. Anarchy, whether real or cyber, is a zone where life is “nasty, brutish, and short”; there is no guarantee of true freedom. In any anarchy, soon the power hungry will fill in the void. Remember, history teaches these new despots often establish totalitarian regimes no better than those originally overturned. Did microblogging suffer a parallel fate? If the explosion of Mastodon dethroned Twitter, it did so at the cost of establishing a new instance as the new king.</p>
|
||||
<p>For freedom’s sake, face the facts: federation is dead.</p>
|
||||
<p>But there is hope. In political history, the birth of free nations typically contains three stages: dictatorship, briefly overthrown to anarchy, reformed to democracy. Democracy balances the will of the people with the efficiency of centralisation. Democratic freedom is incompatible with an oligarchic authority, accumulating power imposing their will on others. But neither is democracy a free-for-all; some degree of centralisation is acceptable and even useful for protecting liberty, provided it’s centralised around a legitimate, democratic institution.</p>
|
||||
<p>So it is in cyberspace. As the Internet blossomed into chains, the lucrative, exploitative practices of the Silicon Valley giants created digital totalitarianism, a system offering profits for the few at the expense of freedom of the many: <strong>information dictatorship</strong>.</p>
|
||||
<p>The decentralisation movement understands this oppression as a consequence of unjust organized power, and platforms like Mastodon are successfully overthrowing this information dictatorship, in its absence creating <strong>information anarchy</strong>.</p>
|
||||
<p>While the story does not end with anarchy, it need not end with a return to the status quo or to the aristocracy of the technical elite. No, in the power vacuum left by the victory of decentralisation lies the opportunity to create something new, something beautiful, something promising true digital freedom for everyone. After the fall of the information dictatorship, balancing in our fingertips is the precious opportunity to create <strong>information democracy</strong>.</p>
|
||||
<p>Like its real world counterpart, information democracy is partially centralised for efficiency but encourages community participation for legitimacy. This primarily centralised democratic model is the only realistic option, evident from the structure of free nations in the real world. Yes, there is centralisation required, but we cannot continue to paint centralisation as the virtual bogeyman. Spreading fear is a natural knee jerk reaction to the illegitimate corporatocracy, but we cannot succumb to fear. Whether physical or virtual, centralisation is <em>not evil</em>; it is merely morally neutral.</p>
|
||||
<p>That said, when we ultimately do centralise, we must ensure that the central organization belongs to us, the users, not to special interests. On this point, both corporate and decentralised services alike fail. Yes, corporate interests are oligarchies, bowing to money, but so too is the decentralised technocracy, bowing to arcane technical know-how. In a truly democratic digital space, participation must be accessible to <em>everyone</em>, not just those with money or expertise. If my grandmother cannot participate in the administration of the technology she uses, her best interests may not be adequately represented by the technology. Whether she is beholden to a corporation or to a system administrator, that is not digital freedom.</p>
|
||||
<p>These features distinguish information democracy from its predecessors, creating a system not only more practical but also freer than the federated anarchy. In any democracy, centralised power is wielded to protect freedom, but in decentralised anarchy, no power is wielded at all. Anarchy fantasizes that freedom protects itself; inevitably, life in anarchy is life in chains, for any freedom gained is temporary as the system collapses under the <a href="https://en.wikipedia.org/wiki/Tragedy_of_the_commons">tragedy of the commons</a> and the <a href="https://en.wikipedia.org/wiki/Paradox_of_tolerance">paradox of tolerance</a>.</p>
|
||||
<p>Granted, information democracy is not a perfect system; in a virtual world composed primarily of information oligarchies, it is natural to be wary of its potential to stray from its ideals. Nevertheless, the few flaws of information democracy directly mirror the flaws of physical democracy; most objections to information democracy were raised centuries ago during the spread of real-world democracies. Indeed, democracy is not perfect, but it is the only just system we have. Fear cannot blind us, painting perfect as the enemy of good. However flawed, democracy is the only option for a free world.</p>
|
||||
<p>Without further ado, if proprietary services are dictatorships and decentralised services oligarchic anarchies, imagine democratic microblogging: a flagship instance with a clear set of liberal rules, reached by mutual consensus in the community. When necessary, these rules are enforced by an elected moderation team voted in by the community, though generally bureaucracy should be minimized. The platform is entirely free software, so anyone technical can contribute via code. The server exposes a public API, allowing third-party clients to flourish without encumbering progress on the “official” client. Similarly, although the service is primarily centralised, the servers may permit federation if relevant. Ideally, when sensitive data like private messages is involved, communications are end-to-end encrypted and possibly even peer-to-peer, minimizing the centralised server’s power.</p>
|
||||
<p>Sound good? These criteria are non-exhaustive but illustrate the potential for information democracy. More importantly, sound familiar? Many of these ideals are <em>already embodied by Mastodon</em>. Mastodon may not have lived up to its federated fantasy, and judging it by its own meter stick of decentralisation, it failed. But judging it by the standards of an information democracy, regardless of its inadvertent centralisation, Mastodon is <em>a success story</em>.</p>
|
||||
<p>True, Mastodon is de facto centralised, but despite the size of the largest instances, it retains the ability to federate with other Mastodon instances. Further, Mastodon is able to federate with other free software friendly networks via a pair of common protocols, creating the familial fabric of the “fediverse”. Centralization and federation can certainly co-exist in harmony to improve efficiency while retaining user choice.</p>
|
||||
<p>Nevertheless, the most successful information democracy is orders of magnitude larger than Mastodon. A centralised site with a documented set of rules reached by community consensus, hosted by a non-profit funded by donations from the user base itself. A community governed by administrators elected by participants in the community. A site whose source code is licensed as free software, allowing specialized adaptations to flourish, complementing rather than harming the main instance. A site that embraces <a href="https://en.wikipedia.org/wiki/Free_culture_movement">free culture</a> to ensure its fruits are accessible to all of humanity in perpetuity.</p>
|
||||
<p>Wikipedia.</p>
|
||||
<p>The Wikipedia projects embody the ideals of information democracy. Yes, Wikipedia is centralised; indeed, its software does not directly federate with other smaller wikis, many of which mirror Wikipedia articles – and that’s okay. Wikipedia derives its user freedom not from decentralisation, but from participatory democracy structures. Anyone can edit; with some exceptions to keep logistics manageable, a new user’s first edit is valued as much an administrator’s one-millionth. Typically, conflicts are resolved not from a top-down dictatorship, despite the centralisation, but rather from bottom-up consensus seeking. <a href="https://en.wikipedia.org/wiki/WP:DEMOCRACY">Wikipedia is not an experiment in democracy</a>, but it nevertheless operates as an information democracy, no experimentation needed.</p>
|
||||
<p>To protect the digital freedom of <em>everyone</em>, not just the wealthy or the technically inclined, we need <strong>information democracy</strong>. To see the future of Internet liberty for the few, look not to Silicon Valley, for overwhelming commercial interests can never adequately protect the user. To see the future of liberty for the many, look not to the obscurity of XMPP, for arcane technical voodoo can never be wielded by those who need it most. To see the free future, look to Wikipedia.</p>
|
||||
<p>Whether online or in the real world, rejecting dictatorships is not enough for freedom.</p>
|
||||
<p>We must endorse democracy.</p>
|
||||
<p><em>Thank you to April, Connor, Florrie, and Natalia for soundboarding and reading early drafts.</em></p>
|
||||
</description><guid isPermaLink="true">https://rosenzweig.io/blog/the-federation-fallacy.html</guid><pubDate>Sun, 03 Mar 2019 00:00:00 -0500</pubDate></item><item><title>Hilariously Fast Volume Computation with the Divergence Theorem</title><link>https://rosenzweig.io/blog/hilariously-fast-volume-computation-with-the-divergence-theorem.html</link><description><p>(No, there won’t be jokes.)</p>
|
||||
<p>The following presents a fast algorithm for volume computation of a simple, closed, triangulated 3D mesh. This assumption is a consequence of the divergence theorem. Further extensions may generalise to other meshes as well, although that is presently out of scope.</p>
|
||||
<p>We begin with the definition of volume as the triple integral over a region of the constant one:</p>
|
||||
<p><span class="math display">\[V = \iiint_R 1 \mathrm{d}V\]</span></p>
|
||||
<p>Let <span class="math inline">\(\mathbf{F}\)</span> be a function in <span class="math inline">\(\mathbb{R}^3\)</span> such that its divergence is equal to one. For the purposes of this paper, we choose:</p>
|
||||
<p><span class="math display">\[\mathbf{F}(x, y, z) = &lt;x, 0, 0&gt;\]</span></p>
|
||||
<p>It can easily be verified that</p>
|
||||
<p><span class="math display">\[\mathrm{div} \mathbf{F} = \frac{\partial F}{\partial x} + \frac{\partial F}{\partial y} + \frac{\partial F}{\partial z} = 1 + 0 + 0 = 1\]</span></p>
|
||||
<p>Therefore,</p>
|
||||
<p><span class="math display">\[V = \iiint_R 1 dV = \iiint_R \mathrm{div} \mathbf{F}(x, y, z) \mathrm{d}V\]</span></p>
|
||||
<p>By the Divergence Theorem, this is equal to the surface integral:</p>
|
||||
<p><span class="math display">\[V = \iint_S \mathbf{F}(x, y, z) \mathrm{d}\mathbf{S}\]</span></p>
|
||||
<p>This surface integral, defined over the surface S of the 3D mesh, is equal to the sum of its piecewise triangle parts. Let <span class="math inline">\(T_i\)</span> denote the surface of the <span class="math inline">\(i\)</span>’th triangle in the mesh. Then,</p>
|
||||
<p><span class="math display">\[V = \sum_{i = 0} \iint_{T_i} \mathbf{F}(x, y, z) \mathrm{d}\mathbf{S}\]</span></p>
|
||||
<p>Let <span class="math inline">\(T_{in}\)</span> represent the <span class="math inline">\(n\)</span>’th vertex of the <span class="math inline">\(i\)</span>’th triangle. Let <span class="math inline">\(\Delta_1\)</span> equal the vector difference between <span class="math inline">\(T_{i1}\)</span> and <span class="math inline">\(T_{i0}\)</span>, and <span class="math inline">\(\Delta_2\)</span> likewise equal to <span class="math inline">\(T_{i2} - T{i0}\)</span>. Each individual triangle <span class="math inline">\(T_i\)</span> may thus be parametrised as:</p>
|
||||
<p><span class="math display">\[\mathbf{r}(u, v) = T_{i0} + u\Delta_1 + v\Delta_2\]</span></p>
|
||||
<p>Then, simple differentiation yields:</p>
|
||||
<p><span class="math display">\[\mathbf{r}_u = \Delta_1\]</span> <span class="math display">\[\mathbf{r}_v = \Delta_2\]</span></p>
|
||||
<p>Therefore,</p>
|
||||
<p><span class="math display">\[\mathbf{r}_u \times \mathbf{r}_v = \Delta_1 \times \Delta_2\]</span></p>
|
||||
<p>Thus, the surface integral can be rewritten in terms of this parametrisation, substituting in the definition of <span class="math inline">\(\mathbf{F}\)</span> as needed:</p>
|
||||
<p><span class="math display">\[V = \sum_{i = 0} \iint_{T_i} \mathbf{F}(x, y, z) (\mathbf{r}_u \times \mathbf{r}_v) dA\]</span> <span class="math display">\[= \sum_{i = 0} \iint_{T_i} \mathbf{F}(x, y, z) \dot (\Delta_{i1} \times \Delta_{i2}) dA\]</span> <span class="math display">\[= \sum_{i = 0} \iint_{T_i} &lt;x, 0, 0&gt; \dot (\Delta_{i1} \times \Delta_{i2}) dA\]</span></p>
|
||||
<p>This cross product is constant throughout the triangle and easy to calculate from the vertex data. Only the X component of the cross product should be calculated; the others are equal to zero due to the dot product with the zero components of <span class="math inline">\(\mathbf{F}\)</span>. <span class="math inline">\(V\)</span> can be thus be rewritten as:</p>
|
||||
<p><span class="math display">\[V = \sum_{i = 0} (\Delta_{i1} \times \Delta_{i2})_x \iint_{T_i} x dA\]</span></p>
|
||||
<p>We now focus on the surface integral <span class="math inline">\(\iint_{T_i} x dA\)</span>. Expanding with the parametrisation yields:</p>
|
||||
<p><span class="math display">\[\iint_{T_i} x dA = \int_{0}^{1} \int_{0}^{u} x dv du = \int_{0}^{1} \int_{0}^{u} (T_{i0x} + u \Delta_{i1x} + v \Delta_{i2x}) dv du\]</span></p>
|
||||
<p>This integral can be directly evaluated, treating vertex data as constants:</p>
|
||||
<p><span class="math display">\[\int_{0}^{1} \int_{0}^{1-u} (T_{i0x} + u \Delta_{i1x} + v \Delta_{i2x}) dv du\]</span> <span class="math display">\[= T_{i0x} \int_{0}^{1} \int_{0}^{1-u} dv du + \Delta_{i1x} \int_{0}^{1} \int_{0}^{1-u} u dv du + \Delta_{i2x}) \int_{0}^{1} \int_{0}^{1-u} v dv du\]</span> <span class="math display">\[= T_{i0x} (\frac{1}{2}) + \Delta_{i1x} (\frac{1}{6}) + \Delta_{i2x} (\frac{1}{6})\]</span> <span class="math display">\[= T_{i0x} (\frac{1}{2}) + (T_{i1x} - T_{i0x})(\frac{1}{6}) + (T_{i2x} - T_{i0x})(\frac{1}{6})\]</span> <span class="math display">\[= T_{i0x} (\frac{1}{6}) + (T_{i1x})(\frac{1}{6}) + (T_{i2x})(\frac{1}{6})\]</span> <span class="math display">\[= \frac{1}{6}(T_{i0x} + T_{i1x} + T_{i2x})\]</span></p>
|
||||
<p>Substituting into the original sum and pulling out a constant factor of <span class="math inline">\(\frac{1}{6}\)</span> to avoid the inner loop division, this yields the following compact formula for the volume:</p>
|
||||
<p><span class="math display">\[V = \frac{1}{6} \sum_{i = 0} (\Delta_{i1} \times \Delta_{i2})_x (T_{i0x} + T_{i1x} + T_{i2x})\]</span></p>
|
||||
<h2 id="performance-analysis">Performance analysis</h2>
|
||||
<p>The final algorithm contains no numerical integration nor differentiation. In contrast to common naive algorithms for volume, which are equivalent to rendering the mesh and then sampling the render, an expensive operation, there is only a single loop in this algorithm, over the triangles. Thus, this algorithm for volume computation is O(n) to the number of the triangles. Furthermore, the per-triangle calculation is similarly efficient: given the natural expansion of the cross product, the inner part contains seven additions and three multiplications. On the outside of the loop is only a single multiplication. Thus, for a mesh of <span class="math inline">\(n\)</span> triangles, the algorithm requires <span class="math inline">\(8n - 1\)</span> additions and <span class="math inline">\(3n + 1\)</span> multiplications, or <span class="math inline">\(11n\)</span> floating point operations. This is <em>very</em> fast.</p>
|
||||
<p>For a ballpark number, if volume needs to be calculated every frame in a high-performance 60 frames per second application, without the aid of a GPU, only using the CPU capabilities of a <a href="https://raspberrypi.stackexchange.com/questions/55862/what-is-the-performance-and-the-performance-per-watt-of-raspberry-pi-3-in-gflops">$35 Raspberry Pi</a>, around 30 million triangles could be measured every frame.</p>
|
||||
<h2 id="motivation">Motivation</h2>
|
||||
<p>The vector calculus exam is soon, and I need to study. Plus, who doesn’t love 3D graphics?!</p>
|
||||
<p><del>I would be (pleasantly) surprised if the algorithm is novel.</del> Further research <em>after</em> posting reveals the paper <a href="http://chenlab.ece.cornell.edu/Publication/Cha/icip01_Cha.pdf">Efficient Feature Extraction for 2D/3D Objects in Mesh Representation</a> by Cha Zheng and Tsuhan Chen, which appears to describe the same algorithm, although the derivation is different. It was fun while it lasted!</p>
|
||||
</description><guid isPermaLink="true">https://rosenzweig.io/blog/hilariously-fast-volume-computation-with-the-divergence-theorem.html</guid><pubDate>Fri, 16 Feb 2018 00:00:00 -0500</pubDate></item></channel></rss>
|
||||
Loading…
Add table
Add a link
Reference in a new issue