<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Jeremy Cline's Blog</title>
    <description>Blog about various things open source
</description>
    <link>https://www.jcline.org/</link>
    <atom:link href="https://www.jcline.org/feed.xml" rel="self" type="application/rss+xml"/>
    <lastBuildDate>Thu, 09 Jul 2026 19:43:54 +0000</lastBuildDate>
    <generator>Jekyll v4.4.1</generator>
    
      <item>
        <title>Flock 2026</title>
        <description>&lt;p&gt;Another Flock to Fedora conference has come and gone, and like last year, this one was held in Prague. Unlike last year, I was not in the middle of moving across the country (again) so I was able to attend, thanks to my employer.&lt;/p&gt;

&lt;p&gt;As always, it was great to see so many familiar faces and meet new folks face-to-face. To those of you who weren’t able to make it, you were missed. And, as always, I spent a lot of time in the hallway track talking to people, getting a sense of what everyone was working on and interested in.&lt;/p&gt;

&lt;h2 id=&quot;day--1&quot;&gt;Day -1&lt;/h2&gt;

&lt;p&gt;The day before the conference started, there was a sponsorship dinner. Although Bex did all the work getting the paperwork to the correct people at Microsoft, he wasn’t able to make it to Prague in time for the dinner so I was sent. I arrived on Saturday morning after a quick connection through Dublin, which gave me plenty of time to get settled in and resist taking a nap. I spent the dinner chatting with Kevin Fenzi and Jef Spaleta, and while I can’t remember all the topics, curling was definitely mentioned.&lt;/p&gt;

&lt;h2 id=&quot;day-0&quot;&gt;Day 0&lt;/h2&gt;

&lt;p&gt;I volunteered to help at the check-in desk the morning of the first day, which I felt went very smoothly (the new label makers were a nice addition). It was nice to help out, but it was also a great way to match names I’ve seen on Matrix to faces as folks arrived. After my shift, I got sucked into the hallway track until lunch.&lt;/p&gt;

&lt;p&gt;After lunch and a bit more hallway track, I went to the “PR-based Gating for Fedora: Can We Make It Work?” workshop from František Lachman. There was a lot of discussion in and around the Fedora contribution workflow which I have lots of thoughts about, but I felt there was a rather widespread desire to make things better (even if the &lt;em&gt;exact&lt;/em&gt; way we do that isn’t clear). Lots of people who were not me brought up keeping the specfiles in one repository rather than forty thousand or however many git repositories we’re up to. In any case, I’d really like a nice pull request workflow for Fedora where I can’t mess up updates, and where we can all share the tooling we build around packaging.&lt;/p&gt;

&lt;p&gt;I spent the rest of the day in the hallway track, doing some last minute preparations for my talk on signing, and preparing for the joint Microsoft talk with Reuben and Bex. I was happy to meet some of the Red Hat folks working on cryptography and signing, and I’m hopefully somewhere down the line we can all do approximately the same thing for signing content.&lt;/p&gt;

&lt;p&gt;I got dinner with Bex and Reuben at some place that served North Carolina style BBQ, and it was pretty good (especially with kimchi on top!).&lt;/p&gt;

&lt;h2 id=&quot;day-1&quot;&gt;Day 1&lt;/h2&gt;

&lt;p&gt;This was the first day of recorded presentations. I went to the usual “State of Fedora” address, followed by the Fedora Council and FESCo panels. I thought it was interesting (but sadly unsurprising) to see downward trend of contributors, and I’d be interested to see a further breakdown of who’s leaving. I have plenty of not-backed-by-hard-data ideas about why this is happening, but I do hope it leads to a stronger focus on (and acceptance of) improving the contribution experience - the general feeling in the hallways, as I mentioned earlier, makes me somewhat optimistic.&lt;/p&gt;

&lt;p&gt;After lunch and a bit of hallway track, I went to the “Secure by Design: Aligning Fedora with the EU Cyber Resilience Act (CRA)” workshop by Jaroslav Řezník and Roman Zhukov. A good portion of it was a run down of what the CRA entailed and how the roles it describes map into Fedora. After that I spent a bit of time preparing for my talk. &lt;a href=&quot;https://youtu.be/ulz7AhNRQBE?t=7071&quot;&gt;My talk&lt;/a&gt; went well, I think, except the live demo didn’t entirely work (gpg2 + gpg-agent + gnupg-pkcs11-scd is very finicky and I forgot a setup step). With that stressful event out of the way, I was able to relax a bit at the dinner party, chat with numerous folks, and fill up on the “appetizers” they brought out in vast quantities. Big props to the event organizers, the weather was great and I really appreciated the open space and variety of food options.&lt;/p&gt;

&lt;h2 id=&quot;day-2&quot;&gt;Day 2&lt;/h2&gt;

&lt;p&gt;It was hard to believe it was already the final day of the conference, but I think at this point I was also feeling pretty worn out. I went to Justin’s “State of the Fedora Kernel” talk, and was glad to hear that the GitLab workflow I helped build before I left wasn’t absolutely terrible. I made some last minute edits to the slides for our “Two Years In: Accelerating Microsoft Contributions to Fedora” talk (where I was happy to have Bex and Reuben do most of the talking), then helped present that talk. Afterwards I went to the “What’s new in Fedora CoreOS” talk and managed to chat with Jean-Baptiste Trystram and Joel Capitao about signing and Konflux, which we’ll hopefully get sorted out in the next couple weeks (in the staging environment, anyway). Hopefully we’ll also be able to get Fedora CoreOS images into the Azure community gallery alongside the Cloud images.&lt;/p&gt;

&lt;p&gt;The lightning talks were all enjoyable, and I’m really impressed some folks even managed to make up slides for theirs and nothing went terribly wrong (great work everyone). There was time for a bit more hallway track, and then I went to the Contributor Recognition Program, which concluded the presentations for Flock 2026. I spent the evening catching up with old and new friends, chatting about ideas on improving various bits of Fedora infrastructure, and how to make the contributor experience better. People were already leaving for DevConf (or home) at this point, so if I didn’t get a chance to say goodbye, I’m sorry and I hope we’ll see each other next year!&lt;/p&gt;

&lt;h2 id=&quot;day-3&quot;&gt;Day 3&lt;/h2&gt;

&lt;p&gt;It was an uneventful trip back home, thankfully.&lt;/p&gt;

&lt;p&gt;I’m looking forward to put all the work I’ve done on improving Fedora’s signing infrastructure through its paces, to get support for PQC done, and I have a few ideas on what to work on next. Hopefully some of them work out and don’t lead to too many people screaming at me. Flock is a great event to get excited about the next year of work and to test the waters on wild ideas, so I’m really glad I was able to make it this year.&lt;/p&gt;
</description>
        <pubDate>Thu, 09 Jul 2026 16:14:00 +0000</pubDate>
        <link>https://www.jcline.org/blog/fedora/2026/07/09/flock-2026.html</link>
        <guid isPermaLink="true">https://www.jcline.org/blog/fedora/2026/07/09/flock-2026.html</guid>
        
        
        <category>blog</category>
        
        <category>fedora</category>
        
      </item>
    
      <item>
        <title>Fedora signing: draw the rest of the owl</title>
        <description>&lt;p&gt;Way back in September &lt;a href=&quot;/blog/fedora/signing/2025/09/18/fedora-artifact-signing-p4.html&quot;&gt;I apologized&lt;/a&gt; for the gap since my last update. I am, once again, sorry it’s been so long since I posted an update. I recently presented at talk at &lt;a href=&quot;https://cfp.fedoraproject.org/flock-to-fedora-2026/talk/BESUGX/&quot;&gt;Flock 2026&lt;/a&gt; - if you prefer consuming updates via video where live demos fail, you can find that &lt;a href=&quot;https://youtu.be/ulz7AhNRQBE?t=7071&quot;&gt;on YouTube&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This post is something of a summary of that talk, but the short version is Siguldry has all the features Sigul has, plus some. That means we can start deploying it in the coming weeks.&lt;/p&gt;

&lt;h2 id=&quot;pkcs11&quot;&gt;PKCS#11&lt;/h2&gt;

&lt;p&gt;In the last post, I noted I had implemented server-side support for PGP signing. That’s gone now, I’m pleased to say, because I came across a much neater approach. I implemented a small PKCS#11 module inventively called &lt;a href=&quot;https://github.com/fedora-infra/siguldry/tree/siguldry-pkcs11-2.1.0/siguldry-pkcs11&quot;&gt;siguldry-pkcs11&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I can’t take credit for any of the ideas I’m about to describe, since I stole them from multiple other projects. Our friends over in &lt;a href=&quot;https://www.flatcar.org/&quot;&gt;Flatcar&lt;/a&gt; have to sign things as well, and they use a key stored in Azure Key Vault. The way they use it is that they implemented a minimal &lt;a href=&quot;https://github.com/jepio/azure-keyvault-pkcs11&quot;&gt;PKCS#11 module&lt;/a&gt;. That was my primary inspiration, but I also recently spent some time building a drop-in replacement for pesign-daemon and the idea of exposing the signing interface over a Unix socket that can be mounted into isolated build environments was another concept I borrowed.&lt;/p&gt;

&lt;p&gt;For those who aren’t familiar, &lt;a href=&quot;https://docs.oasis-open.org/pkcs11/pkcs11-spec/v3.2/pkcs11-spec-v3.2.html&quot;&gt;PKCS#11&lt;/a&gt; is a standard that is made up of a C interface. To implement it, you create a shared object file with a few well-known C functions used to discover your implementations of the interface. Users load your shared library, call a well-known function to get a table of functions where you provide your implementations for the various features. What’s nice about this is that common libraries like OpenSSL can speak to PKCS#11 modules, so you can make any tool that uses those common cryptography libraries use your implementations.&lt;/p&gt;

&lt;p&gt;PKCS#11 is a fairly broad specification that covers not just signing, but also encryption/decryption, digest calculation, random number generation, and key management (creating, modifying, deleting, etc). What I realized while poking around Flatcar’s implementation is that you actually don’t need to implement everything. You can, instead, stub out all the functions you don’t want to implement and have them return the handy “function not supported” error code. The list of functions I opted to not support &lt;a href=&quot;https://github.com/fedora-infra/siguldry/blob/siguldry-pkcs11-2.1.0/siguldry-pkcs11/src/unsupported.rs&quot;&gt;is extensive&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In fact, all I implemented was the functions to authenticate, list keys, and sign. However, that’s enough to make it possible to use the Siguldry server with tools including (but definitely not limited to): &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rpmsign&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ostree gpg-sign&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cosign&lt;/code&gt; (if built with PKCS#11 support), &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gpg2&lt;/code&gt; (with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gnupg-pkcs11-scd&lt;/code&gt; service), &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sq&lt;/code&gt; (once it merges its PKCS#11 support), &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;systemd-measure&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;systemd-sbsign&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pesign&lt;/code&gt;, and perhaps most humorously, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ssh&lt;/code&gt;. All these tools know how to speak to PKCS#11 modules so they “just work” with Siguldry. Any new tool people make will also likely just work.&lt;/p&gt;

&lt;p&gt;One thing that’s neat about this approach is that we sit between the tool requesting the signature and the signing server. This means we can hash the content client-side and requests to the server are very small: just a hash, the algorithm used for the hash, and the key name. No more sending a 4GiB ISO over the network only to have the server hash it, that all is done before the request starts.&lt;/p&gt;

&lt;p&gt;The last thing that’s &lt;em&gt;really&lt;/em&gt; cool about this approach is that if Fedora decides it needs a new signing server and spends some big bucks on a fancy hardware security module, it will definitely ship with a PKCS#11 module of its own so we can start using it with our existing tooling, no development required. If Siguldry stops being the right choice for Fedora, it’s really easy to swap it out.&lt;/p&gt;

&lt;h3 id=&quot;unix-socket-api&quot;&gt;Unix Socket API&lt;/h3&gt;

&lt;p&gt;The other thing I did was to implement a Siguldry client that binds a Unix socket and proxies requests from the local system to the remote Siguldry server. I did this because you can expose the socket into locked down environments, such as RPM build environments, install the PKCS#11 module into that environment, and sign things. Right now Fedora uses this general approach for SecureBoot, except it’s specific to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pesign&lt;/code&gt; utility. While I’d prefer to disconnect the signing event from the building of a package, it’s important to support the current workflows and this was a neat way to do that which is generic across signing tools.&lt;/p&gt;

&lt;p&gt;It has another advantage. Since the Unix socket is set up using a systemd socket unit, we can sandbox the client proxy, and also configure it with the necessary credentials to connect to the Siguldry server and unlock signing keys. This lets you, if necessary, allow anyone to sign content without needing to manage secrets themselves. The secrets can be encrypted with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;systemd-creds&lt;/code&gt; and bound to hardware. All we have to do is ensure the keys that are configured that way get a special flag when queried via PKCS#11: the “protected authentication path” flag.&lt;/p&gt;

&lt;h2 id=&quot;siguldry-fedora-autopen&quot;&gt;siguldry-fedora-autopen&lt;/h2&gt;

&lt;p&gt;The primary way Fedora uses its signing server is via an AMQP consumer that automatically signs things. &lt;a href=&quot;https://pagure.io/robosignatory&quot;&gt;Robosignatory&lt;/a&gt; is the current tool used, but it needs some significant changes to work with this new workflow. In particular, we don’t want to sign content serially, but the fedora-messaging Python library commonly used to interact with the AMQP broker doesn’t handle concurrency well. And, since we now call out to the particular tool used to sign content rather than sending it to the server, the consumer is primarily a mapping between message topics and those various tools.&lt;/p&gt;

&lt;p&gt;All those reasons led me to re-implement it, and since I’ve got no imagination with names, it’s called &lt;a href=&quot;https://github.com/fedora-infra/siguldry/tree/siguldry-fedora-autopen-0.1.0/siguldry-fedora-autopen&quot;&gt;siguldry-fedora-autopen&lt;/a&gt;. It uses the &lt;a href=&quot;https://crates.io/crates/lapin&quot;&gt;lapin&lt;/a&gt; AMQP client with Tokio to manage hundreds or thousands of RPMs/ISOs/containers/etc signing operations at the same time. This means builds should no longer languish in the signing queue for many hours.&lt;/p&gt;

&lt;h2 id=&quot;ima&quot;&gt;IMA&lt;/h2&gt;

&lt;p&gt;All this probably (hopefully) sounds great, but as with all things there are a few problems. Well, really just one big problem. Back in the Fedora 37 days, we enabled IMA signing for RPMs. The short version of how that works is that each file inside an RPM gets signed. And, unfortunately, the PKCS#11 interface is synchronous. This means that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rpmsign&lt;/code&gt; requests a signature when it creates an OpenPGP signature on the RPM header. It then requests a signature for each file, waiting for each signature request to finish before starting the next one. Now, when the RPM only has dozens or a couple hundred files that happens fairly quickly. A signature might only take a couple milliseconds and the request/response latency might be a couple dozen more milliseconds (or a lot less depending on how close the server is from the client).&lt;/p&gt;

&lt;p&gt;Of course, not all RPMs only have a few hundred files. Some have many, many thousand. For these, a simple OpenGPG signature takes less than a second (even if the RPM is, say, 10GiB). The IMA signing can take many minutes, depending on how many files it has. I’ve been running a client locally, with a server in a nearby datacenter, and the largest time I saw for a signing operation was around 40 minutes. This is really unfortunate, but it is important to remember that we can now do thousands of signing operations at the same time, so even when these slow builds are signed, other builds can be serviced in a timely manner.&lt;/p&gt;

&lt;p&gt;There’s a couple ways we can make this better, but the “easiest” involve a custom signing implementation that avoids using the PKCS#11 path.&lt;/p&gt;

&lt;h2 id=&quot;whats-next&quot;&gt;What’s Next?&lt;/h2&gt;

&lt;p&gt;In the next few weeks I’m hopeful we’ll have enough time to sit down and deploy Siguldry to Fedora’s staging environment. I want to really kick the tires and ensure it’s rock solid before we move it to production, but I think it’s reasonable to hope for that in the next couple of months.&lt;/p&gt;

&lt;p&gt;While that’s happening, I’m going to add support for ML-DSA signing. That will allow us to produce signatures that are safe in a post-quantum cryptography world. After that, I’ll investigate OpenPGP’s hybrid signature schemes, although given recent developments I wonder if we want to bother with those. I’ll defer to what the professional cryptographers have to say about that.&lt;/p&gt;

&lt;p&gt;Sometime in the next few Fedora releases, though, you should expect to see new signature algorithms in use (pending FESCo approval etc etc).&lt;/p&gt;

&lt;h2 id=&quot;comments-and-feedback&quot;&gt;Comments and Feedback&lt;/h2&gt;

&lt;p&gt;Thoughts, comments, or feedback greatly welcomed on &lt;a href=&quot;https://hachyderm.io/@jcline/116784346110417786&quot;&gt;Mastodon&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Sat, 20 Jun 2026 19:17:00 +0000</pubDate>
        <link>https://www.jcline.org/blog/fedora/signing/2026/06/20/fedora-artifact-signing-p5.html</link>
        <guid isPermaLink="true">https://www.jcline.org/blog/fedora/signing/2026/06/20/fedora-artifact-signing-p5.html</guid>
        
        
        <category>blog</category>
        
        <category>fedora</category>
        
        <category>signing</category>
        
      </item>
    
      <item>
        <title>Fedora's aarch64 images support Secure Boot</title>
        <description>&lt;p&gt;About seven years ago, &lt;a href=&quot;https://forge.fedoraproject.org/infra/tickets/issues/7361&quot;&gt;a ticket was
filed&lt;/a&gt; noting aarch64 systems were
shipping with Secure Boot enabled, and that Fedora should start signing its boot path to support
these devices out of the box.&lt;/p&gt;

&lt;p&gt;I’m pleased to say that today’s Fedora Rawhide images - what will be Fedora 45 - finally does this
thanks to the work of a whole bunch of people.&lt;/p&gt;

&lt;p&gt;This means you can grab the latest Rawhide images and boot them on your favorite aarch64 laptop
without turning off Secure Boot, or launch VMs in any of the major clouds with Secure Boot on. For
example, I’m able to start a VM in Azure with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TrustedLaunch&lt;/code&gt; security type:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;❯ az group create &lt;span class=&quot;nt&quot;&gt;--name&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;jcline-aarch64-secureboot&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--location&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;eastus2&quot;&lt;/span&gt;
❯ az vm create &lt;span class=&quot;nt&quot;&gt;--location&lt;/span&gt; eastus2 &lt;span class=&quot;nt&quot;&gt;--name&lt;/span&gt; fedora &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--resource-group&lt;/span&gt; jcline-aarch64-secureboot &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--image&lt;/span&gt; /CommunityGalleries/Fedora-5e266ba4-2250-406d-adad-5d73860d958f/Images/Fedora-Cloud-Rawhide-Arm64/Versions/latest &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--security-type&lt;/span&gt; TrustedLaunch &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--size&lt;/span&gt; Standard_D2plds_v6 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--accept-term&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--ssh-key-values&lt;/span&gt; @/home/jcline/.ssh/id_ed25519.pub
❯ ssh jcline@20.12.69.183
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;jcline@fedora ~]&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;mokutil &lt;span class=&quot;nt&quot;&gt;--sb-state&lt;/span&gt;
SecureBoot enabled
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;why-now&quot;&gt;Why Now?&lt;/h2&gt;

&lt;p&gt;The way Fedora used to sign UEFI applications for Secure Boot was delightfully simple (for some
value of simple). The keys were in a smart card, plugged into a special build host, and anything
that needed a signature was routed to be built on that host. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pesign&lt;/code&gt;, one of the common utilities
to sign PE applications, has a mode where it can run as a daemon and sign anything provided to it
over a Unix socket. That Unix socket is threaded into the build environment, where builds can access
it to sign PE applications with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pesign-client&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Unfortunately, that host was x86_64 so when aarch64 started shipping with Secure Boot
enabled, an alternative approach was needed.&lt;/p&gt;

&lt;p&gt;Ultimately we moved the smart card to the signing server we use for RPMs and other things. The
tricky bit about the whole process is that Fedora signs each bit of the boot chain during the build.
Each time any of the UEFI applications in the boot chain is built it needs to be signed. One way to
do this is to build the application in Fedora’s infrastructure, and then have a second build which
uses the output of the first build along with a signature as input to construct a signed final
version. However, this means you’ve got two specfiles which you have to keep in sync, and there’s
probably other painful aspects I’ve not considered. In any case, that’s not what Fedora does.&lt;/p&gt;

&lt;p&gt;Instead, Fedora signs the UEFI applications during the build. Since we want the signing key to be
stored in a remote server, this implies some sort of networking, but builds aren’t permitted network
access. Nor can the build environment provide the necessary secrets to authenticate with the signing
service. In order to handle this, I wrote a small service that pretends to be the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pesign&lt;/code&gt; Unix
socket, and that can be exposed to the build environment in the same way. However, it just shovels
anything it gets to the signing server and returns whatever the signing server does.&lt;/p&gt;

&lt;p&gt;That service got deployed last week, and after a little bit of debugging it even worked. In fact,
everything was signed for aarch64 last week, except for the fallback UEFI application that adds a
boot entry for Fedora if it’s not there, which happens on first boot. Without that, booting new
images would fail unless you explicitly added the correct Fedora boot entry manually. Yesterday,
shim got rebuilt and everything works.&lt;/p&gt;

&lt;h2 id=&quot;stable-when&quot;&gt;Stable When?&lt;/h2&gt;

&lt;p&gt;It’s possible this will eventually work in Fedora 44 Cloud images. Shim in Fedora 44 hasn’t (yet)
been rebuilt and we’re in the final freeze for Fedora 44, so unfortunately we &lt;em&gt;just&lt;/em&gt; missed it, but
if it does get rebuilt later, Cloud images will be updated and will start working.&lt;/p&gt;

&lt;p&gt;For Fedora 43 and older, the version of shim shipped doesn’t include the version signed for aarch64.
I’m not sure it’s worth the risk to update it, as much as I’d like it to work there, as well.&lt;/p&gt;

&lt;p&gt;Anyway, Fedora 45 will be upon us before you know it, and after seven years, six more months isn’t
so bad, right?&lt;/p&gt;

&lt;h2 id=&quot;comments-and-feedback&quot;&gt;Comments and Feedback&lt;/h2&gt;

&lt;p&gt;Thoughts, comments, or feedback greatly welcomed on &lt;a href=&quot;https://hachyderm.io/@jcline/116331456293724262&quot;&gt;Mastodon&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Wed, 01 Apr 2026 08:51:00 +0000</pubDate>
        <link>https://www.jcline.org/blog/fedora/2026/04/01/fedora-aarch64-secureboot.html</link>
        <guid isPermaLink="true">https://www.jcline.org/blog/fedora/2026/04/01/fedora-aarch64-secureboot.html</guid>
        
        
        <category>blog</category>
        
        <category>fedora</category>
        
      </item>
    
      <item>
        <title>Fedora Signing Update, September edition</title>
        <description>&lt;p&gt;It’s been a while since &lt;a href=&quot;/blog/fedora/signing/2025/07/29/fedora-artifact-signing-p3.html&quot;&gt;my last update&lt;/a&gt; about content signing in Fedora, so I
figured I should write one before the end of this month. While I’ve not been
able to devote all my time to working on Siguldry, I have made enough progress
I think it’s worth covering, and I also had the opportunity to chat with
Miloslav Trmač, the original author of Sigul, about its design.&lt;/p&gt;

&lt;h2 id=&quot;history-lesson-for-sigul&quot;&gt;History Lesson for Sigul&lt;/h2&gt;

&lt;p&gt;In my post about &lt;a href=&quot;/blog/fedora/signing/2025/07/09/fedora-artifact-signing-p2.html&quot;&gt;various protocol tweaks&lt;/a&gt; I covered most of the changes I
wanted to make to Sigul’s protocol and my reasons for doing so. A major theme
of the changes was pushing more of the content-related “smarts” to the client.
For example, in sigul, the server receives a container file and prepares a JSON
document based off it to sign with GPG. In my proposed approach, the client
should handle that and simply asks the server to sign the given document.&lt;/p&gt;

&lt;p&gt;Miloslav was able to provide me with some history on Sigul and why it pushes
the responsibility of dealing with the content to the bridge and server, and I
think it’s worth recording here. In the original design, signing didn’t happen
automatically: a Fedora contributor would need to request a signature using the
Sigul client. In this scenario, there are a &lt;em&gt;lot&lt;/em&gt; more Sigul clients and
they’re running on machines that Fedora infrastructure doesn’t manage. Thus,
the bridge and server needed to make every effort to ensure clients didn’t
request signatures for things they shouldn’t - this is why the bridge is able
to integrate with the Fedora Account System, and why it also contacts Koji when
RPM signatures are requested: it has every reason to expect the client to be
malicious.&lt;/p&gt;

&lt;p&gt;These days, there’s only a few Sigul clients. Fedora’s infrastructure admins
have management accounts, and the robosignatory service, which runs in Fedora’s
infrastructure, also acts as a client. While we should be cautious in our
design, the deployment is significantly different from Sigul’s original
expectations so we can make different trade-offs.&lt;/p&gt;

&lt;h2 id=&quot;siguldry-progress&quot;&gt;Siguldry Progress&lt;/h2&gt;

&lt;p&gt;Back in July, I completed the protocol implementation. Since then, I’ve worked
on actually doing things on top of it.&lt;/p&gt;

&lt;h3 id=&quot;binding-keys-to-hardware&quot;&gt;Binding Keys to Hardware&lt;/h3&gt;

&lt;p&gt;One feature I largely ignored in Sigul because it seemed complicated was its
ability to encrypt signing keys using a combination of user-provided passwords
and hardware (a Yubikey or TPM, for example). This means that if you were to
obtain a copy of the Sigul database and you also managed to get the user’s
password to access a signing key, you still wouldn’t be able to access the
private keys without getting access to the signing machine’s TPM or a Yubikey
that Fedora’s infrastructure team has access to.&lt;/p&gt;

&lt;p&gt;I have partially re-implemented this particular feature in Siguldry. I say
partially because Sigul had a number of different ways to bind keys to hardware
on both the server and client side, and I have opted to implement only what
Fedora infrastructure currently uses. You can use any key accessible via PKCS
#11 to encrypt signing keys in addition to user passwords. You can even use
multiple keys, and any one key is enough to unlock things (so you can have a
backup Yubikey or three).&lt;/p&gt;

&lt;p&gt;On the client side, the expectation is that user passwords and certificates
will be bound to the TPM via systemd-credentials.&lt;/p&gt;

&lt;h3 id=&quot;the-server-supports-pgp-signing&quot;&gt;The Server Supports PGP Signing&lt;/h3&gt;

&lt;p&gt;I’ve implemented creating PGP keys as well as signing content with them. The
interface supports detached signatures, the
&lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9580.html#section-7&quot;&gt;cleartext&lt;/a&gt; format you
see, for example, with
&lt;a href=&quot;https://dl.fedoraproject.org/pub/fedora/linux/releases/42/Workstation/aarch64/iso/Fedora-Workstation-42-1.1-aarch64-CHECKSUM&quot;&gt;CHECKSUM&lt;/a&gt;
files, and inline signatures.&lt;/p&gt;

&lt;p&gt;This covers all current PGP signature types used by Fedora. However, more work
is needed on the client side to use this interface to sign RPMs. The client
needs to extract the header from the RPM, have the server produce a detached
signature, and then send that signature to Koji.&lt;/p&gt;

&lt;h3 id=&quot;the-server-supports-rsassa-pkcs1-v1_5-and-ecdsa-signatures&quot;&gt;The Server Supports RSASSA-PKCS1-v1_5 and ECDSA Signatures&lt;/h3&gt;

&lt;p&gt;These are the signatures you get with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;openssl-dgst&lt;/code&gt; and related commands, and
they’re used for Secure Boot signatures, container signatures with
&lt;a href=&quot;https://github.com/fedora-infra/siguldry/issues/49&quot;&gt;cosign&lt;/a&gt;, and
&lt;a href=&quot;https://sourceforge.net/p/linux-ima/wiki/Home/&quot;&gt;IMA&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;whats-next&quot;&gt;What’s Next&lt;/h2&gt;

&lt;p&gt;Siguldry now has all the primitive interfaces to implement signing in the
client for various types of content (RPM, containers, git tags, etc). It’s now
time to work on implementing those, figure out what server interfaces need
changing, and then add a whole lot of polish: a comprehensive end-to-end test
suite for all content types, API documentation, an admin guide, and a set of
tools to migrate the Sigul database to Siguldry.&lt;/p&gt;

&lt;h2 id=&quot;comments-and-feedback&quot;&gt;Comments and Feedback&lt;/h2&gt;

&lt;p&gt;Thoughts, comments, or feedback greatly welcomed on &lt;a href=&quot;https://hachyderm.io/@jcline/115226068075225348&quot;&gt;Mastodon&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Thu, 18 Sep 2025 15:34:00 +0000</pubDate>
        <link>https://www.jcline.org/blog/fedora/signing/2025/09/18/fedora-artifact-signing-p4.html</link>
        <guid isPermaLink="true">https://www.jcline.org/blog/fedora/signing/2025/09/18/fedora-artifact-signing-p4.html</guid>
        
        
        <category>blog</category>
        
        <category>fedora</category>
        
        <category>signing</category>
        
      </item>
    
      <item>
        <title>Fedora signing protocol tweaks</title>
        <description>&lt;p&gt;In &lt;a href=&quot;/blog/fedora/signing/2025/07/09/fedora-artifact-signing-p2.html&quot;&gt;my last post&lt;/a&gt; on
Fedora’s signing infrastructure, I ended with some protocol changes I would be
interested in making. Over the last few weeks, I’ve tried them all in a
proof-of-concept project and I’m fairly satisfied with most of them. In this
post I’ll cover the details of the new protocol, as well as what’s next.&lt;/p&gt;

&lt;h2 id=&quot;sigul-protocol-20&quot;&gt;Sigul Protocol 2.0&lt;/h2&gt;

&lt;p&gt;The major change to the protocol, as I mentioned in the last post, is that all
communication between the client and the server happens over the nested TLS
session. Since the bridge cannot see any of the traffic, its role is reduced
significantly and it is now a proxy server that requires client authentication.&lt;/p&gt;

&lt;p&gt;I considered an approach where there was no communication from the client or
server to the bridge directly (beyond the TLS handshake). However, adding a
handshake between the bridge and the server/client makes it possible for all
three to share a few pieces of data useful for debugging and error cases. While
it feels a little silly to add this complexity primarily for debugging
purposes, the services are designed to be isolated and there’s no opportunity
for live debugging.&lt;/p&gt;

&lt;h3 id=&quot;the-handshake&quot;&gt;The handshake&lt;/h3&gt;

&lt;p&gt;After a server or client connects to the bridge, it sends a message in the
outer TLS session to the bridge. The message contains the protocol version the
server/client will use on the connection, as well as its role (client or
server). The bridge listens on two different ports for client and server
connections, so the role is only included to catch mis-configurations where the
server connects to the client port or vice versa.&lt;/p&gt;

&lt;p&gt;The bridge responds with a message that includes a status code to indicate it
accepts the connection (or not), and a UUID to identify the connection.&lt;/p&gt;

&lt;p&gt;The bridge sends the same UUID to both the client and server so it can be used
to identify the inner TLS session on the client, bridge, and server. This makes
it easy to, for example, collect logs from all three services for a connection.
It also can be used during development with OpenTelemetry to &lt;a href=&quot;https://opentelemetry.io/docs/concepts/signals/traces/&quot;&gt;trace a
request&lt;/a&gt; across all
three services.&lt;/p&gt;

&lt;p&gt;Reasons the bridge might reject the connection include (but is not limited
to): the protocol version is not supported, the role the connection announced
is not the correct role for the port it connected to, or the client certificate
does not include a valid Common Name field (which is used for the username).&lt;/p&gt;

&lt;p&gt;After the bridge responds with an “OK” status, servers accept incoming inner
TLS session on the socket, and clients connect. All further communication is
opaque to the bridge.&lt;/p&gt;

&lt;h3 id=&quot;clientserver&quot;&gt;Client/Server&lt;/h3&gt;

&lt;p&gt;In the last blog post, I discussed requests and responds being JSON
dictionaries, and content that needed to be signed could be base64 encoded, but
I didn’t go into the details of RPM header signatures. While I’d still love to
have everything be JSON, after examining the size of RPM headers, I opted to
use a request/response format closer to what Sigul 1.2 currently uses. The
reason is that base64-encoding data increases its size by around 33%. After
poking at a few RPMs with the
&lt;a href=&quot;https://github.com/fedora-iot/rpm-head-signing&quot;&gt;rpm-head-signing&lt;/a&gt; tool, I
concluded that headers were still too large (cloud-init’s headers were hundreds
of kilobytes) to pay a 33% tax for a &lt;em&gt;slightly&lt;/em&gt; simpler protocol.&lt;/p&gt;

&lt;p&gt;So, the way a request or response works is like this: first, a frame is sent.
This is two unsigned 64-bit integers. The first &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;u64&lt;/code&gt; is the size of the JSON,
and the second is the size of the arbitrary binary payload that follows it.
What that binary is depends on the command specified in the JSON. The
alternative was to send a single &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;u64&lt;/code&gt; describing the JSON size alone, so the
added complexity here is minimal. The binary size can also be 0 for commands
that don’t use it, just like the Sigul 1.2 protocol.&lt;/p&gt;

&lt;p&gt;Unlike Sigul 1.2, none of the messages need HMAC signatures since they all
happen inside the inner TLS session. Additionally, the protocol does not allow
any further communication on the outer TLS session, so the implementation to
parse the incoming requests is delightfully straightforward.&lt;/p&gt;

&lt;h3 id=&quot;authentication&quot;&gt;Authentication&lt;/h3&gt;

&lt;p&gt;Both the client and server authenticate to the bridge using TLS certificates.
Usernames are provided in the Common Name field of the client certificate. For
servers, this is not terribly useful, although the bridge could have an
allowlist of names that can connect to the server socket.&lt;/p&gt;

&lt;p&gt;Clients and servers &lt;em&gt;also&lt;/em&gt; mutually authenticate via TLS certificates on the
inner TLS session. Technically, the server and client could use a separate set
of TLS certificates for authentication, but in the current proof-of-concept it
uses the same certificates for both authenticating with the bridge and with the
server/client. I’m not sure there’s any benefit to introducing additional sets
of certificates, either.&lt;/p&gt;

&lt;p&gt;For clients, commands no longer need to include the “user”: it’s pulled from
the certificate by the server. Additionally, there’s no user passwords. Users
that want a password in addition to their client certificate can encrypt their
client key with a password. If this doesn’t sit well with users (mostly Fedora
Infrastructure) we can add passwords back, of course.&lt;/p&gt;

&lt;p&gt;Users &lt;em&gt;do&lt;/em&gt; still need to exist in the database or their requests will be
rejected, so administrators will need to create the users via the command-line
interface on the server or via a client authenticated as an admin user.&lt;/p&gt;

&lt;h3 id=&quot;proof-of-concept&quot;&gt;Proof of concept&lt;/h3&gt;

&lt;p&gt;Everything I’ve described has been implemented in my &lt;a href=&quot;https://github.com/jeremycline/siguldry/tree/sigul-protocol-2&quot;&gt;proof-of-concept
branch&lt;/a&gt;. I’m in
the process of cleaning it up to be merge-able, and command-line interfaces
still need to be added for the bridge and client. There’s plenty of TODOs still
sprinkled around, and I expect I’ll reorganize the APIs a bit. I’ve also made
no effort to make things fast. Still, it will happily process hundreds or
thousands of connections concurrently. I plan to add benchmarks to the test
suite prior to merging this so I can be a little less handwave-y about how
capable the service is.&lt;/p&gt;

&lt;h2 id=&quot;whats-next&quot;&gt;What’s next?&lt;/h2&gt;

&lt;p&gt;Now that the protocol changes are done (although there’s still time to tweak
things), it’s time to turn our attention to what we actually want to do:
managing keys and signing things.&lt;/p&gt;

&lt;h3 id=&quot;management&quot;&gt;Management&lt;/h3&gt;

&lt;p&gt;In the current Sigul implementation, some management is possible remotely using
commands, while some are only possible from on the server. One thing I would
like to consider is moving much of the uncommon or destructive management tasks
to be done locally on the server rather than exposing them as remote commands.
These tasks could include:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;All user management (adding, removing, altering)&lt;/li&gt;
  &lt;li&gt;Removing signing keys&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Users could still do any non-destructive actions remotely. This includes
creating signing keys, read operations on users and keys, granting and revoking
access to keys for other users, and of course signing content.&lt;/p&gt;

&lt;p&gt;Moving this to be local to the server makes the permission model for requests
simpler. I would love feedback on whether this would be inconvenient from
anyone in the Fedora Release Engineering or Infrastructure teams.&lt;/p&gt;

&lt;h3 id=&quot;signing&quot;&gt;Signing&lt;/h3&gt;

&lt;p&gt;When Sigul was first written, Fedora was not producing so many different kinds
of things that needed signatures. Mostly, it was RPMs. Since then, many other
things have needed signatures. For some types of content (containers, for
example), we still don’t sign them. For other types, they aren’t signed in an
automated fashion.&lt;/p&gt;

&lt;p&gt;Even the RPM case needs to be reevaluated. RPM 6.0 is on the horizon with a new
format that supports multiple signatures - something we will want to support
post-quantum algorithms. However, we still need to sign the old v4 format for
EPEL 8.0. We probably don’t want to continue to shell out to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rpmsign&lt;/code&gt; on the
server.&lt;/p&gt;

&lt;p&gt;Ideally, the server should not need to be aware of all these details. Instead,
we can push that complexity to the client and offer a few signature types. We
can, for example, produce SecureBoot signatures and container signatures &lt;a href=&quot;https://github.com/fedora-infra/siguldry/issues/49&quot;&gt;the
same way&lt;/a&gt;. I need to
understand the various signature formats and specifications, as well as the
content we sign. The goal is to offer broad support without requiring
constantly teaching the server about new types of content.&lt;/p&gt;

&lt;h2 id=&quot;comments-and-feedback&quot;&gt;Comments and Feedback&lt;/h2&gt;

&lt;p&gt;Thoughts, comments, or feedback greatly welcomed on &lt;a href=&quot;https://hachyderm.io/@jcline/114937982823195105&quot;&gt;Mastodon&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Tue, 29 Jul 2025 14:01:00 +0000</pubDate>
        <link>https://www.jcline.org/blog/fedora/signing/2025/07/29/fedora-artifact-signing-p3.html</link>
        <guid isPermaLink="true">https://www.jcline.org/blog/fedora/signing/2025/07/29/fedora-artifact-signing-p3.html</guid>
        
        
        <category>blog</category>
        
        <category>fedora</category>
        
        <category>signing</category>
        
      </item>
    
      <item>
        <title>Re-designing signing in Fedora</title>
        <description>&lt;p&gt;Over the past few months I’ve spent some time on-and-off working on
&lt;a href=&quot;https://pagure.io/sigul&quot;&gt;Sigul&lt;/a&gt; and some related tools. In particular, I
implemented most of a new &lt;a href=&quot;https://github.com/fedora-infra/siguldry/tree/siguldry-0.3.1/siguldry&quot;&gt;Sigul
client&lt;/a&gt;,
primarily to enable the sigul-pesign-bridge to run on recent Fedora releases
(since the sigul client relies on python-nss, which is not in Fedora anymore).&lt;/p&gt;

&lt;p&gt;At this point, I have a reasonably good understanding of how Sigul works.
Originally, my plan was to completely re-implement the client, then the bridge,
and finally the server using the existing Sigul protocol, version 1.2, as
defined by the Python implementation. However, as I got more familiar with the
implementation, I felt that it would be better to use this opportunity to also
change the protocol. In this post I’m going to cover the issues I have with
the current protocol and how I’d like to address them.&lt;/p&gt;

&lt;p&gt;Note: I assume you’ve read &lt;a href=&quot;https://www.jcline.org/blog/fedora/signing/2025/03/28/fedora-artifact-signing-p1.html&quot;&gt;how artifacts are signed in
Fedora&lt;/a&gt;
in the post.&lt;/p&gt;

&lt;h2 id=&quot;mixing-tls-sessions&quot;&gt;Mixing TLS sessions&lt;/h2&gt;

&lt;p&gt;In protocol version 1.2, the client and server start “outer” TLS sessions with
the bridge, and then the client starts a nested “inner” TLS session with the
server. Data is sent in chunks which indicate how big the chunk is and whether
it’s part of the “outer” session (and destined for the bridge) or the “inner”
session. While it’s perfectly doable to parse the two streams out, it’s a
complication. Maybe we can introduce some rules to make it easier?&lt;/p&gt;

&lt;p&gt;After looking at the implementation, every command follows the same pattern. The client would:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Open a connection to the bridge and send the &lt;em&gt;bridge&lt;/em&gt; the command to pass on to the server.&lt;/li&gt;
  &lt;li&gt;Open the inner TLS session and send some secrets to the server (a key to use
for HMAC and a key passphrase to unlock a signing key, typically).&lt;/li&gt;
  &lt;li&gt;Close the inner TLS session.&lt;/li&gt;
  &lt;li&gt;Send HMAC-signed messages to the bridge, which it relays to the server.&lt;/li&gt;
  &lt;li&gt;Receive HMAC-signed messages from the server, via the bridge.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Critically, the inner TLS session was only used to exchange secrets so the
bridge couldn’t see them, and was never used again. One option would be to only
allow the inner TLS session once, right at the beginning of the connection.&lt;/p&gt;

&lt;p&gt;However, the whole point of the HMAC-signed messages is that the client and
server don’t seem to really “trust” the bridge won’t have tampered with the
messages. Why not just use the inner TLS session exclusively so that we get
confidentiality in addition to integrity?&lt;/p&gt;

&lt;h2 id=&quot;homegrown-serialization&quot;&gt;Homegrown serialization&lt;/h2&gt;

&lt;p&gt;When Sigul was originally written things like JSON weren’t part of the Python
standard library. It implemented its own, limited format. Now, however, there
are a number of widely supported serialization options. JSON is the obvious
choice as it is fairly human-readable, ubiquitous, and fairly simple. The
downside is that for signing requests, the client needs to send some binary
data. However, we explicitly do not want to be sending huge files to be signed,
so base64-encoding small pieces of binary data should be acceptable.&lt;/p&gt;

&lt;h2 id=&quot;the-bridge-is-too-smart&quot;&gt;The bridge is too smart&lt;/h2&gt;

&lt;p&gt;The bridge includes features that are not used, and that complicate the
implementation.&lt;/p&gt;

&lt;h3 id=&quot;fedora-account-system-integration&quot;&gt;Fedora Account System integration&lt;/h3&gt;

&lt;p&gt;The bridge supports configuring a set of required Fedora Account System groups
the user needs to be in to make requests. It checks the user against the
account system when it connects by using the Common Name in the client
certificate as the username.&lt;/p&gt;

&lt;p&gt;However, this feature is &lt;a href=&quot;https://github.com/fedora-infra/ansible/blob/cf00289c066041ad32d0a8147114df54ec2bb616/roles/sigul/bridge/templates/bridge.conf.j2#L15&quot;&gt;not used by
Fedora&lt;/a&gt;,
and since Fedora is probably the only deployment of this service, we probably
don’t need this feature.&lt;/p&gt;

&lt;h3 id=&quot;the-bridge-alters-commands&quot;&gt;The bridge alters commands&lt;/h3&gt;

&lt;p&gt;For most commands, the bridge shovels bits between the client connection and
the server connection. However, before it shovels, it parses out the requests
and responses before forwarding them. There’s really only one good reason for
this. Two particular commands &lt;em&gt;may&lt;/em&gt; alter the client request before sending it
to the server. Those two commands are “sign-rpm” and “sign-rpms”.&lt;/p&gt;

&lt;p&gt;In the event that the client requests a signature for an RPM or multiple RPMs
and the request doesn’t include a payload, the bridge will download the RPM
from Koji. Now, the bridge doesn’t have the HMAC keys used in other commands to
sign the request, so the client &lt;em&gt;also&lt;/em&gt; contacts Koji and includes the RPM’s
checksum in the request headers.&lt;/p&gt;

&lt;p&gt;This particular design choice might have been done to save a hop when
transferring large RPMs, but these days you don’t need to send the whole RPM,
just the header to be signed.&lt;/p&gt;

&lt;p&gt;It’s confusing to have the bridge take an active role in client requests.
What’s more, if we push this responsibility to the client, there’s no reason
the bridge needs to see the requests and responses at all.&lt;/p&gt;

&lt;h3 id=&quot;usernames&quot;&gt;Usernames&lt;/h3&gt;

&lt;p&gt;As noted in the Fedora Account System integration section, the bridge uses the
client certificate’s Common Name to determine the username. However, all
requests &lt;em&gt;also&lt;/em&gt; include a “user” field.&lt;/p&gt;

&lt;p&gt;The server checks to ensure either the request username matches the client
certificate’s Common Name, or if the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lenient_username_check&lt;/code&gt; configuration
option is set which disables that check, or if the Common Name is in a
configuration option, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;proxy_usernames&lt;/code&gt;, listing that that Common Name can use
whatever username it wants.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://github.com/fedora-infra/ansible/blob/cf00289c066041ad32d0a8147114df54ec2bb616/roles/sigul/server/templates/server.conf.j2#L1&quot;&gt;Fedora
configuration&lt;/a&gt;
doesn’t define either of those configuration options, and it’s confusing to
have two places to define the username.&lt;/p&gt;

&lt;h3 id=&quot;passwords&quot;&gt;Passwords&lt;/h3&gt;

&lt;p&gt;Users have several types of passwords.&lt;/p&gt;

&lt;p&gt;Users are given access to signing keys by setting a user-specific passphrase
for each key. This passphrase is used to encrypt a copy of the “real” key
password, so each user can access the key without ever knowing what the key
password is.&lt;/p&gt;

&lt;p&gt;However, each user also has an “account password” which is only needed for
admin commands, and only works if the account is flagged as an admin. Given
that the client certificate can be password-protected it’s not clear to me that
this adds any value, but it is confusing.&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;To summarize, the major changes I’m considering in a new version of the Sigul protocol are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;em&gt;All&lt;/em&gt; client-server communication happens over the nested TLS session. The
client and server no longer need to manually HMAC-sign requests and responses
as a result.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;The bridge is a simple proxy that authenticates the client and server
connections via mutual TLS and then shovels bits between the two connections
without any knowledge of the content. Drop the Fedora Account System
integration, and push the responsibility of communicating with Koji to the
client.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Switch from the homegrown serialization format to JSON for requests and responses.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Rely exclusively on the client certificate’s Common Name for the username.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Remove the admin password from user accounts as they can password-protect
their client key and also use features like systemd-creds to encrypt the key
with the host’s TPM.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Wed, 09 Jul 2025 16:17:00 +0000</pubDate>
        <link>https://www.jcline.org/blog/fedora/signing/2025/07/09/fedora-artifact-signing-p2.html</link>
        <guid isPermaLink="true">https://www.jcline.org/blog/fedora/signing/2025/07/09/fedora-artifact-signing-p2.html</guid>
        
        
        <category>blog</category>
        
        <category>fedora</category>
        
        <category>signing</category>
        
      </item>
    
      <item>
        <title>How artifacts are signed in Fedora</title>
        <description>&lt;p&gt;For the last few months, one of the things I’ve been working on in Fedora is
adding support for SecureBoot on Arm64. The details of that work will be the
subject of a later post, but as part of this work I’ve become somewhat familiar
with the signing infrastructure in Fedora and how it works. This post
introduces the various pieces of the current infrastructure, and how they fit
together.&lt;/p&gt;

&lt;h2 id=&quot;signed&quot;&gt;Signed?&lt;/h2&gt;

&lt;p&gt;Pretty much anything Fedora produces and distributes is digitally signed so
users can verify it did, in fact, come from the Fedora project. Perhaps the
most obvious example of this is the RPM packages Fedora produces. However,
plenty of other artifacts are also signed, like OSTree commits.&lt;/p&gt;

&lt;p&gt;Signing works using &lt;a href=&quot;https://wikipedia.org/wiki/Public-key_cryptography&quot;&gt;public-key
cryptography&lt;/a&gt;. We have the
private key that we need to keep secret, and we distribute the public keys to
users so they can verify the artifact.&lt;/p&gt;

&lt;h2 id=&quot;robosignatory&quot;&gt;Robosignatory&lt;/h2&gt;

&lt;p&gt;Signing is (mostly) an automated process. A service, called
&lt;a href=&quot;https://pypi.org/project/robosignatory/&quot;&gt;robosignatory&lt;/a&gt;, connects to an AMQP
message broker and subscribes to several message topics. When an artifact is
created that needs signing, the creator of the artifact sends a message to the
AMQP broker using one of these topics.&lt;/p&gt;

&lt;p&gt;Robosignatory does not sign artifacts itself. It collects requests from various
other services and submits them to the signing server on behalf of those
systems. The signing server is the system that contains and protects the
private keys.&lt;/p&gt;

&lt;h2 id=&quot;sigul&quot;&gt;Sigul&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://pagure.io/sigul&quot;&gt;Sigul&lt;/a&gt; is the signing server that holds the private
keys and performs signing operations. It is composed of three parts: the
client, the bridge, and the server.&lt;/p&gt;

&lt;h3 id=&quot;the-server&quot;&gt;The Server&lt;/h3&gt;

&lt;p&gt;The Sigul server is designed to control access to signing keys and to provide a
minimal attack surface. It does not behave like a traditional server in that it
does not accept incoming network connections. Instead, it connects to the Sigul
bridge, and the bridge forwards requests to it via that connection.&lt;/p&gt;

&lt;p&gt;This allows the firewall to be configured to allow outgoing traffic to a single
host, and to block all incoming connections. This does mean that the host
cannot be managed via normal SSH operations. Instead this is done in Fedora via
the host’s out-of-band management console.&lt;/p&gt;

&lt;p&gt;Private keys are encrypted on disk. When users are granted access to keys, the
key is encrypted for that particular user.&lt;/p&gt;

&lt;h3 id=&quot;the-bridge&quot;&gt;The Bridge&lt;/h3&gt;

&lt;p&gt;The Sigul bridge acts as a specialized proxy. Both the client and server connect
to the bridge, and it forwards requests and responses between them.&lt;/p&gt;

&lt;p&gt;Unlike the typical proxy, the Sigul bridge does a few interesting things.
Firstly, it requires the client to authenticate via TLS certificates. The
client then sends the bridge the request it wishes to make. The bridge forwards
that request to the server. The bridge then expects the client and server to
send an arbitrary amount of data to each other. This arbitrary data is framed
as chunks and it forwards that data back and forth until an end of stream
signal is sent by the server and client.&lt;/p&gt;

&lt;p&gt;Finally, it forwards the server’s response to the client.&lt;/p&gt;

&lt;h3 id=&quot;the-client&quot;&gt;The Client&lt;/h3&gt;

&lt;p&gt;The client is a command-line interface that sends requests to the server via
the bridge. This client can be used to create users and keys, grant and revoke
access to keys, and request signatures in various formats. Robosignatory
invokes the CLI to sign artifacts.&lt;/p&gt;

&lt;p&gt;The client connects to the bridge as described above. After authenticating with
the bridge and sending the request, it begins a &lt;em&gt;second&lt;/em&gt; TLS connection
configured with the Sigul server’s hostname, and sends and receives data on
this connection over the TLS connection it has with the Sigul bridge. It relies
on this inner TLS connection to send a set of session keys to the server
without the bridge being able to intercept them. These session keys are used to
sign the Sigul server’s responses so the client can be sure the bridge did not
tamper with them.&lt;/p&gt;

&lt;p&gt;After it has established a set of secrets with the server, the remainder of the
interaction occurs over the TLS connection with the bridge, but since the
responses are signed both the client and server can be confident the bridge has
not tampered with the conversation.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;It’s an interesting setup, and has served Fedora for many years. There is
plenty of code in Sigul which dates back to 2009, not long after Python 2.6 was
released with exciting new features like the standard library json module. It
was likely developed for RHEL 5 which means Python 2.4.&lt;/p&gt;

&lt;p&gt;Unfortunately, at least one of the libraries (python-nss) it depends on to work
are not maintained and not in Fedora 42, so something will have to be done in
the near future. Still, it’s not ready to sign off just yet.&lt;/p&gt;
</description>
        <pubDate>Fri, 28 Mar 2025 19:39:00 +0000</pubDate>
        <link>https://www.jcline.org/blog/fedora/signing/2025/03/28/fedora-artifact-signing-p1.html</link>
        <guid isPermaLink="true">https://www.jcline.org/blog/fedora/signing/2025/03/28/fedora-artifact-signing-p1.html</guid>
        
        
        <category>blog</category>
        
        <category>fedora</category>
        
        <category>signing</category>
        
      </item>
    
      <item>
        <title>Publishing messages in Fedora's infrastructure</title>
        <description>&lt;p&gt;While at Flock to Fedora 2024, someone noted, to paraphrase a conversation from
a month ago, that working with Fedora’s messaging infrastructure seemed more
intimidating now that it’s transitioned from ZeroMQ to AMQP. I promised to
write up a blog post walking through the process of adding support to an
application, so here it is (better late than never?).&lt;/p&gt;

&lt;p&gt;There are two ways to interact with Fedora’s messaging system as a developer.
Your application can send (publish) messages, receive (consume) them, or both.&lt;/p&gt;

&lt;p&gt;This blog post will focus on adding support to your application for publishing
in Fedora’s infrastructure, and how to effectively unit test them. For anything
not covered in this post, I recommend the &lt;a href=&quot;https://fedora-messaging.readthedocs.io/en/stable/index.html&quot;&gt;official
documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;getting-started&quot;&gt;Getting Started&lt;/h2&gt;

&lt;p&gt;This post assumes you’ll be using Python and the
&lt;a href=&quot;https://github.com/fedora-infra/fedora-messaging/&quot;&gt;fedora-messaging&lt;/a&gt; library
which uses &lt;a href=&quot;https://pypi.org/project/pika/&quot;&gt;Pika&lt;/a&gt; to communicate with the
message broker. If you’re working in a different language this post isn’t for
you, but you can use any AMQP-0.9 client to send messages in the &lt;a href=&quot;https://fedora-messaging.readthedocs.io/en/stable/api/wire-format.html&quot;&gt;documented
message
format&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Use your Python package management tool of choice to &lt;a href=&quot;https://fedora-messaging.readthedocs.io/en/stable/user-guide/installation.html&quot;&gt;install
fedora-messaging&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;publishing&quot;&gt;Publishing&lt;/h2&gt;

&lt;p&gt;A minimal implementation looks like this:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-Python3&quot;&gt;from fedora_messaging import api

msg = api.Message(topic=&quot;my.topic&quot;, body={&quot;some_key&quot;: [&quot;some&quot;, &quot;value&quot;]})
api.publish(msg)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This example, while pleasingly simple, glosses over some important details which
you will need to address.&lt;/p&gt;

&lt;h3 id=&quot;errors&quot;&gt;Errors&lt;/h3&gt;

&lt;p&gt;The first adjustment to make is to add error handling. See the &lt;a href=&quot;https://fedora-messaging.readthedocs.io/en/stable/api/api.html#publish&quot;&gt;API
documentation&lt;/a&gt;
for details on each exception, but here are the ones you generally want to
handle:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-Python3&quot;&gt;from fedora_messaging import api, exceptions

msg = api.Message(topic=&quot;my.topic&quot;, body={&quot;some_key&quot;: [&quot;some&quot;, &quot;value&quot;]})
try:
    api.publish(msg)
except exceptions.ConnectionException as err:
    print(f&quot;Connection to the message broker was lost: {err}&quot;)
except (exceptions.PublishTimeout, exceptions.PublishReturned) as err:
    print(f&quot;Failed to publish the message: {err}&quot;)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;What you do when an error occurs is up to you. You could retry in a loop until
it succeeds, or to skip publishing a message if it’s not critical that a
message be sent for each event. Finally, you could do a mixture of the two by
adding it to a queue of messages to publish next time.&lt;/p&gt;

&lt;h3 id=&quot;schema&quot;&gt;Schema&lt;/h3&gt;

&lt;p&gt;The next adjustment is to define a schema for your message. While this isn’t a
strict requirement, it ensures you don’t accidentally change the format of the
message and break users, and it is required if you want notifications generated
from your messages via the Fedora Notification service to look good. There’s a
&lt;a href=&quot;https://fedora-messaging.readthedocs.io/en/stable/tutorial/schemas.html&quot;&gt;tutorial on providing
schema&lt;/a&gt;
which I won’t reproduce here, but this is what a message with a schema looks
like:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-Python3&quot;&gt;from fedora_messaging import api

class MyMessage(api.Message):
    &quot;&quot;&quot;A schema and common properties to enable Fedora Notification support&quot;&quot;&quot;
    topic = &quot;my.topic&quot;
    body_schema = {
        &quot;id&quot;: &quot;https://fedoraproject.org/message-schema/v1/my-app.message&quot;,
        &quot;$schema&quot;: &quot;https://json-schema.org/draft/2019-09/schema&quot;,
        &quot;description&quot;: (
            &quot;This message is sent by my-app when something interesting happens&quot;
        ),
        &quot;type&quot;: &quot;object&quot;,
        &quot;properties&quot;: {
            &quot;some_key&quot;: {
                &quot;type&quot;: &quot;array&quot;,
                &quot;description&quot;: &quot;This is some key, isn't it?&quot;,
                &quot;items&quot;: {&quot;type&quot;: &quot;string&quot;},
            },
        },
        &quot;required&quot;: [
            &quot;some_key&quot;,
        ],
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I highly recommend following the tutorial and using CookieCutter to produce
your Python package containing your message schema.&lt;/p&gt;

&lt;p&gt;After you create your Python package containing your message schema, add it as
a dependency to your application and publish it on PyPi for consumers to use.&lt;/p&gt;

&lt;h3 id=&quot;testing&quot;&gt;Testing&lt;/h3&gt;

&lt;p&gt;Now that we know how to publish messages in our applications, we probably want
to add tests to ensure messages are published (or not) as we expect.&lt;/p&gt;

&lt;p&gt;Here’s what a test looks like:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-Python3&quot;&gt;from fedora_messaging import api, exceptions, testing

# This is our publish code from above inside a function
def function_that_publishes():
    msg = api.Message(topic=&quot;my.topic&quot;, body={&quot;some_key&quot;: [&quot;some&quot;, &quot;value&quot;]})
    try:
        api.publish(msg)
    except exceptions.ConnectionException as err:
        print(f&quot;Connection to the message broker was lost: {err}&quot;)
    except (exceptions.PublishTimeout, exceptions.PublishReturned) as err:
        print(f&quot;Failed to publish the message: {err}&quot;)


# This will assert a single message is published of type &quot;message.Message&quot;
with testing.mock_sends(api.Message):
    function_that_publishes()

# This will assert a single message is published of type &quot;message.Message&quot;
# and that the message topic and bodies match
with testing.mock_sends(api.Message(topic=&quot;my.topic&quot;, body={&quot;some_key&quot;: [&quot;some&quot;, &quot;value&quot;]})):
    function_that_publishes()
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And that’s it!&lt;/p&gt;

&lt;h3 id=&quot;configuration&quot;&gt;Configuration&lt;/h3&gt;

&lt;p&gt;One last thing will need to be done when deploying your application:
configuring it to connect to Fedora’s broker.&lt;/p&gt;

&lt;p&gt;The API relies on configuration being provided by a TOML file located at, by
default, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/fedora-messaging/config.toml&lt;/code&gt;, but can be changed by setting the
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FEDORA_MESSAGING_CONF&lt;/code&gt; environment variable to the alternate location.&lt;/p&gt;

&lt;p&gt;The default configuration values for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fedora-messaging&lt;/code&gt; work well for RabbitMQ
running on the same host as your application, but we need to adjust some
values to work with Fedora’s message broker:&lt;/p&gt;

&lt;div class=&quot;language-toml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# This defaults to &quot;false&quot;, which means your application will attempt to create&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# the AMQP objects which works well for development. In Fedora, your&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# application will get a &quot;Permission Denied&quot; on startup since your account will&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# only have permission to create a few specific resources.&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;passive_declares&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Both these values rely on variables set in Fedora's Ansible repository, but&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# in this example I've expanded them to what they are in production.&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# &amp;lt;your-app-user&amp;gt; will be provided by Fedora's infrastructure team.&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;amqp_url&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;amqps://&amp;lt;your-app-user&amp;gt;:@rabbitmq.fedoraproject.org/%2Fpubsub&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;topic_prefix&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;org.fedoraproject.prod&quot;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# The TLS settings include a CA certificate used to verify the identity of the&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# Fedora broker, along with a client certificate and key to authenticate your&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# application to the broker.&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# You must file a ticket with Fedora's infrastructure team to have an account&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# created for your application along with a client certificate for authentication.&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# The key and certificate is kept in Ansible. &lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tls&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ca_cert&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/etc/pki/rabbitmq/ca/rabbitmq.ca&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;keyfile&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/etc/pki/rabbitmq/key/user.key&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;certfile&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/etc/pki/rabbitmq/cert/user.crt&quot;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# This identifies your application to RabbitMQ to aid in debugging.&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;client_properties&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;My Application&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;These are the only settings you should need to adjust for publishing messages.&lt;/p&gt;
</description>
        <pubDate>Thu, 05 Sep 2024 18:16:00 +0000</pubDate>
        <link>https://www.jcline.org/blog/fedora/amqp/2024/09/05/messaging-in-fedora.html</link>
        <guid isPermaLink="true">https://www.jcline.org/blog/fedora/amqp/2024/09/05/messaging-in-fedora.html</guid>
        
        
        <category>blog</category>
        
        <category>fedora</category>
        
        <category>amqp</category>
        
      </item>
    
      <item>
        <title>HDR in Linux: Part 2</title>
        <description>&lt;p&gt;In the &lt;a href=&quot;https://www.jcline.org/blog/fedora/graphics/hdr/2021/05/07/hdr-in-linux-p1.html&quot;&gt;previous
post&lt;/a&gt;,
we learned what HDR is: a larger luminance range that requires more bits per
component, new transfer functions to encode that luminance, and potentially
some metadata. We can examine the work required to use it in a “standard” Linux
desktop. By “standard”, I of course meant &lt;em&gt;my&lt;/em&gt; desktop environment, which is
GNOME on Fedora.&lt;/p&gt;

&lt;p&gt;To do this, we’ll consider a single use-case and examine each portion of the
stack, starting at the top and working our way down. The use-case is watching
an HDR movie in GNOME’s video application, Totem. In this scenario, the
application isn’t likely to tone-map its content as it has been created with
HDR metadata the display itself can use to tone-map when necessary, but I will
note where this &lt;em&gt;could&lt;/em&gt; happen.&lt;/p&gt;

&lt;p&gt;Let’s review the high-level requirements:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;The Wayland display server (compositor) must determine the display
capabilities (color primaries and white point, luminance, transfer function,
bits per component, etc) in order to know what outputs are possible and/or
optimal.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Client applications may want to know what content encoding the Wayland
display server supports and maybe even what the optimal content encoding is
for the display it is on (the native primaries, transfer function, bit depth,
luminance capabilities, etc)&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Client applications need to express the transfer function, bit depth, color
space, and any HDR metadata for its content (the “content encoding”) to the
compositor. The compositor, in turn, needs to ensure the content from all the
client applications are blended properly and output in a format the display
can handle.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You might be familiar with text encoding. Adding HDR support is very similar to
adding support for various text encoding formats when previously everything
assumed content was encoded with ASCII. Instead of ASCII, the graphics stack
largely assumes everything is sRGB.&lt;/p&gt;

&lt;h2 id=&quot;totem&quot;&gt;Totem&lt;/h2&gt;

&lt;p&gt;At the top of the stack we have &lt;a href=&quot;https://gitlab.gnome.org/GNOME/totem&quot;&gt;Totem&lt;/a&gt;,
which provides the user interface and playback management features. It relies
on GStreamer to deal with the video itself. It &lt;a href=&quot;https://gitlab.gnome.org/GNOME/totem/-/blob/V_3_38_0/src/backend/bacon-video-widget.c#L6155&quot;&gt;appears to
use&lt;/a&gt;
the
&lt;a href=&quot;https://gstreamer.freedesktop.org/documentation/playback/playbin.html&quot;&gt;playbin&lt;/a&gt;
plugin, which GStreamer describes as an “everything-in-one” abstraction for a
audio and video player.&lt;/p&gt;

&lt;p&gt;Since Totem is all about wiring up a user interface to the GStreamer playbin
plugin, GStreamer needs to be HDR-aware. Totem uses GTK and Clutter-GTK to
create the user interface, so those libraries are also something we should
examine.&lt;/p&gt;

&lt;h2 id=&quot;gstreamer&quot;&gt;GStreamer&lt;/h2&gt;

&lt;p&gt;Based on the GStreamer documentation and &lt;a href=&quot;https://gstconf.ubicast.tv/videos/hdr-seeing-the-world-as-it-is/&quot;&gt;this talk by Edward
Hervey&lt;/a&gt; all
the necessary features appear to already be implemented for HDR video playback.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://gstreamer.freedesktop.org/documentation/video/index.html&quot;&gt;GStreamer video
library&lt;/a&gt;
contains an &lt;a href=&quot;https://gstreamer.freedesktop.org/documentation/video/video-hdr.html&quot;&gt;HDR
section&lt;/a&gt;.
The APIs, available since 1.20, include interfaces for working with SMPTE ST
2086 (static metadata) and SMPTE 2094-40 (dynamic metadata). This metadata can
be attached to a
&lt;a href=&quot;https://gstreamer.freedesktop.org/documentation/video/video-hdr.html#gst_buffer_add_video_hdr_meta&quot;&gt;GstBuffer&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The
&lt;a href=&quot;https://gstreamer.freedesktop.org/documentation/video/video-info.html&quot;&gt;GstVideoInfo&lt;/a&gt;
structure includes
&lt;a href=&quot;https://gstreamer.freedesktop.org/documentation/video/video-color.html#GstVideoColorimetry&quot;&gt;colorimetry&lt;/a&gt;
including the color primaries and transfer function. GstVideoInfo also includes
the
&lt;a href=&quot;https://gstreamer.freedesktop.org/documentation/video/video-format.html#GstVideoFormatInfo&quot;&gt;GstVideoFormatInfo&lt;/a&gt;
structure which describes the pixel format, including bit depth. These contain
all the information required to decode and convert the content. The
&lt;a href=&quot;https://gstreamer.freedesktop.org/documentation/video/video-frame.html&quot;&gt;GstVideoFrame&lt;/a&gt;
ties the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GstBuffer&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GstVideoInfo&lt;/code&gt; together.&lt;/p&gt;

&lt;p&gt;So GStreamer has all the content metadata we need. Next, we need to make sure
it gets from the application, Totem, to the Wayland display server. The
client-to-server interaction occurs in the
&lt;a href=&quot;https://en.wikipedia.org/wiki/EGL_(API)&quot;&gt;EGL&lt;/a&gt; or &lt;a href=&quot;https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/vkspec.html#wsi&quot;&gt;Vulkan Window System
Integration
(WSI)&lt;/a&gt;
APIs. These APIs are where OpenGL and Vulkan APIs are integrated into the
particular operating system. This is where buffers are allocated for use within
the graphics libraries and they include ways to provide the content encoding.
As long as the metadata GStreamer extracts is passed to the relevant EGL or
Vulkan API, we are all set. Mesa includes implementations of EGL and Vulkan
WSI, so we will examine those after GTK.&lt;/p&gt;

&lt;h2 id=&quot;clutter-gtk&quot;&gt;Clutter-GTK&lt;/h2&gt;

&lt;p&gt;In the section on Totem, we noted that it uses
&lt;a href=&quot;https://gitlab.gnome.org/GNOME/clutter-gtk&quot;&gt;Clutter-GTK&lt;/a&gt;. The documentation is
sparse and appears to assume the reader has an understanding of Clutter and why
one would want to use it.
&lt;a href=&quot;https://developer.gnome.org/clutter/stable/&quot;&gt;Clutter&lt;/a&gt; appears to be a library
for arranging a collection of 2-dimensional surfaces (“actors” in Clutter’s
terms) in 3-dimensional space.&lt;/p&gt;

&lt;p&gt;If we look at &lt;a href=&quot;https://gitlab.gnome.org/GNOME/clutter&quot;&gt;Clutter’s repository&lt;/a&gt;
the README notes it is in “deep maintenance mode” and is intended to be
replaced with GTK 4. Based on the commit log this is true, so we will look no
further and focus on GTK 4.0.&lt;/p&gt;

&lt;h2 id=&quot;gtk&quot;&gt;GTK&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://docs.gtk.org/&quot;&gt;GTK&lt;/a&gt; is a “widget toolkit”. It provides, either through
a dependency or on its own, an application abstraction, event loop, rendering
interfaces, methods to declare various user interface elements (buttons, text
fields, etc), and other useful utilities for building a graphical user
interface.&lt;/p&gt;

&lt;p&gt;As of GTK 4, rendering is usually performed by OpenGL. As we’ll examine later,
OpenGL APIs offer the necessary interfaces to handle HDR content, but GTK’s
rendering model introduces a few issues. To understand the difficulties we need
to understand how GTK builds the application window.&lt;/p&gt;

&lt;h3 id=&quot;a-window-into-gtk&quot;&gt;A Window into GTK&lt;/h3&gt;

&lt;p&gt;GTK documents its &lt;a href=&quot;https://docs.gtk.org/gtk4/drawing-model.html&quot;&gt;drawing model&lt;/a&gt;
in some detail and it would best to read through it before proceeding.&lt;/p&gt;

&lt;p&gt;One of the most relevant parts of the documentation is that&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Each GTK toplevel window or dialog is associated with a windowing system
surface. Child widgets such as buttons or entries don’t have their own
surface; they use the surface of their toplevel”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;GTK has each widget render itself, and then combines them all into a single
surface which it shares with the Wayland display server to be combined with all
the other client surfaces. We’ll look at the Wayland protocol in detail later,
but what’s important to know right now is that the surface is where the content
encoding should be stored.&lt;/p&gt;

&lt;p&gt;This single surface that GTK produces, therefore, needs to be composed of
widgets that all use the same content encoding. Unfortunately, GTK does not
offer a way specify what content encoding to use for widgets it renders, nor
does it provide a way for users to convey the content encoding &lt;em&gt;they&lt;/em&gt; are using
for their custom widgets. All content GTK produces on the application’s behalf,
like buttons, menus, and the window decorations are rendered in an undefined
color space with an undefined transfer function. In practice, this seems to be
sRGB.  All textures GTK creates are hard-coded to use 8 bits per component,
including when it combines all the widgets into a single image.&lt;/p&gt;

&lt;p&gt;This means, unfortunately, that at the moment there is no way to use GTK for an
application interested in producing HDR content (video players, image viewers,
content creation tools, etc).&lt;/p&gt;

&lt;h3 id=&quot;ways-forward&quot;&gt;Ways Forward&lt;/h3&gt;

&lt;p&gt;There are a few ways for GTK to work in a world where sRGB isn’t the only
content. I can’t really say which approach is best, or even if this is an
exhaustive list of options since my knowledge of GTK is only a few days old.
Regardless, a good bit of work is necessary to make GTK HDR-ready.&lt;/p&gt;

&lt;h4 id=&quot;widgets-with-content-encoding&quot;&gt;Widgets with Content Encoding&lt;/h4&gt;

&lt;p&gt;One solution, if GTK wishes to remain in the business of blending images
together, is to ensure each widget includes a way to express how the content it
produces is encoded. GTK can then use this to ensure they are combined
correctly. If, for example, sRGB content and PQ-encoded content were combined
into a destination image without converting the sRGB content, the end result
would be that the sRGB portions of the image would be extremely dark. This, of
course, introduces a reasonable amount of complexity to GTK as it needs to
convert between a potentially long list of formats in addition to introducing
interfaces for specifying the content encoding everywhere it matters.&lt;/p&gt;

&lt;h4 id=&quot;sub-surfaces&quot;&gt;Sub-surfaces&lt;/h4&gt;

&lt;p&gt;Wayland offers a way to create surfaces with a parent-child relationship called
&lt;a href=&quot;https://wayland.freedesktop.org/docs/html/apa.html#protocol-spec-wl_subsurface&quot;&gt;subsurfaces&lt;/a&gt;.
Since the surface is where the content encoding is stored, GTK could use
sub-surfaces to defer the blending of different content encoding to the Wayland
display server. This would allow GTK to remain in its undefined, sRGB-ish world
while allowing applications to handle modern content with other content
encoding. However, I am told these are problematic as they cannot be clipped or
transformed by GTK.&lt;/p&gt;

&lt;h4 id=&quot;require-a-single-hdr-format&quot;&gt;Require a single HDR Format&lt;/h4&gt;

&lt;p&gt;Rather than handling converting all the widgets to the same content encoding,
GTK could specify &lt;em&gt;one&lt;/em&gt; HDR-capable format it supports and require all widgets
to use that if HDR has been requested for the application. A format like scRGB
with half-precision 16-bit floating point numbers for each color component.
This, however, has the downside that all HDR content needs to be converted from
its native format to scRGB, blended with the other GTK widgets, and then
converted back to some on-the-wire HDR format in the Wayland display server.&lt;/p&gt;

&lt;p&gt;Even with this approach, GTK cannot blend the SDR content into HDR content
without considering what luminance range to map SDR content to, which needs to
correspond to the display brightness level the user has set.&lt;/p&gt;

&lt;h4 id=&quot;use-sub-surfaces-outside-gtk&quot;&gt;Use Sub-surfaces Outside GTK&lt;/h4&gt;

&lt;p&gt;Applications can work around GTK not dealing with content encoding by not using
GTK for anything other than the menus, buttons, and so on. In fact, this is how
Firefox handles things; GTK is used for the “chrome” and the content is
rendered to a Wayland sub-surface which Firefox manages itself and overlays on
the GTK surface. This sub-surface can be properly configured for HDR and the
compositor can handle blending it with GTK’s surface.&lt;/p&gt;

&lt;p&gt;This, of course, isn’t ideal as the applications have to work around the
toolkit rather than having the toolkit help them. However, it is a path
forward worth mentioning.&lt;/p&gt;

&lt;h2 id=&quot;mesa&quot;&gt;Mesa&lt;/h2&gt;

&lt;p&gt;Mesa provides the OpenGL implementation that clients like GTK use to render
their content. To allocate storage for the output of that rendering, clients
use the EGL or Vulkan WSI interfaces for which Mesa also provides
implementations.&lt;/p&gt;

&lt;h3 id=&quot;egl&quot;&gt;EGL&lt;/h3&gt;

&lt;p&gt;The full &lt;a href=&quot;https://www.khronos.org/egl&quot;&gt;EGL standard&lt;/a&gt; is available online. Of
particular interest to us are the interfaces for creating surfaces.
&lt;a href=&quot;https://www.khronos.org/registry/EGL/sdk/docs/man/html/eglCreatePlatformWindowSurface.xhtml&quot;&gt;eglCreatePlatformWindowSurface&lt;/a&gt;
includes a list of attributes, including the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EGL_GL_COLORSPACE&lt;/code&gt; attribute.
As of EGL 1.5, only &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EGL_GL_COLORSPACE_SRGB&lt;/code&gt; (which defines both color
primaries and a transfer function) and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EGL_GL_COLORSPACE_LINEAR&lt;/code&gt; (presumably
the sRGB primaries, linear light) are defined. However there are a number of
extensions to EGL for additional color space attributes including:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_gl_colorspace_bt2020_linear.txt&quot;&gt;BT2020 with linear or PQ-encoded
luminance&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_gl_colorspace_display_p3.txt&quot;&gt;Display-P3 with linear and “sRGB-like” encoded
luminance&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Additionally, there is an
&lt;a href=&quot;https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_image_gl_colorspace.txt&quot;&gt;extension&lt;/a&gt;
to allow the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EGL_GL_COLORSPACE&lt;/code&gt; attribute to be applied to
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eglCreateImage&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;These extensions make it possible to convey the color primaries and transfer
function to the Wayland display server.&lt;/p&gt;

&lt;p&gt;However, this is not currently done because the Wayland protocol has not yet
accepted a way to send the content encoding. The
&lt;a href=&quot;https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/14&quot;&gt;proposed&lt;/a&gt;
protocol has been under discussion (200+ comments) for a year. The details will
be covered in the Wayland section below, but for now what’s important to know
is that the protocol needs to define a message for the color space and transfer
function that is sent from the client via Mesa when a surface is created in
&lt;a href=&quot;https://gitlab.freedesktop.org/mesa/mesa/-/blob/21.1/src/egl/drivers/dri2/platform_wayland.c#L303&quot;&gt;EGL&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Finally, there is an EGL extension for specifying the HDR metadata for a
surface.  By calling
&lt;a href=&quot;https://www.khronos.org/registry/EGL/sdk/docs/man/html/eglSurfaceAttrib.xhtml&quot;&gt;eglSurfaceAttrib&lt;/a&gt;
and providing the attributes defined in
&lt;a href=&quot;https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_surface_SMPTE2086_metadata.txt&quot;&gt;EXT_surface_SMPTE2086_metadata&lt;/a&gt;
a client can set the HDR metadata for the content. This is actually already
&lt;a href=&quot;https://gitlab.freedesktop.org/mesa/mesa/-/commit/799b3d16d4bb0caa16dc35de66e11eca8517cd02&quot;&gt;partially wired up in
Mesa&lt;/a&gt;.
All that remains is for the Wayland-specific EGL code to send the HDR metadata
to the Wayland display server. Again, there is no protocol for this at the
moment.&lt;/p&gt;

&lt;p&gt;While it is a dizzying array of extensions, EGL already includes all the
metadata and APIs to send the content encoding from the client (Totem, by way
of GStreamer) to the Wayland compositor.&lt;/p&gt;

&lt;h3 id=&quot;vulkan-wsi&quot;&gt;Vulkan WSI&lt;/h3&gt;

&lt;p&gt;The Vulkan Window System Interface requires much less bouncing between
extensions to determine how it all works.&lt;/p&gt;

&lt;p&gt;For WSI, the content encoding is set when creating a
&lt;a href=&quot;https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/vkspec.html#_wsi_swapchain&quot;&gt;swapchain&lt;/a&gt;
by providing a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VkSwapchainCreateInfoKHR&lt;/code&gt; structure. This includes the pixel
format as well as the &lt;a href=&quot;https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/vkspec.html#VkColorSpaceKHR&quot;&gt;color
space&lt;/a&gt;.
Like EGL, the color space enumeration defines both the color primaries and the
transfer function together. There is support for sRGB, Display-P3, BT2020, and
more, all with linear and one or more transfer function options.&lt;/p&gt;

&lt;p&gt;The HDR metadata can be provided by the client using
&lt;a href=&quot;https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/vkspec.html#_hdr_metadata&quot;&gt;vkSetHdrMetadataEXT&lt;/a&gt;,
which applies to the swapchain. With those two calls, a client can describe the
content encoding.&lt;/p&gt;

&lt;p&gt;Like EGL, the WSI implementation in Mesa does not forward this content encoding
to the Wayland compositor because there’s no protocol.&lt;/p&gt;

&lt;p&gt;Ville Syrjälä created a proof-of-concept branch of Mesa back in 2017 to have
Mesa use the Wayland protocol proposal (of the time), and I &lt;a href=&quot;https://gitlab.freedesktop.org/jcline/mesa/-/tree/hdr_poc&quot;&gt;rebased
it&lt;/a&gt; recently to
“work” with the latest Wayland protocol proposal. It doesn’t &lt;em&gt;actually&lt;/em&gt; work
yet, but it is a good approximation of the work required, which is implementing
the standard’s functions to call libwayland functions with the content encoding
details.&lt;/p&gt;

&lt;h2 id=&quot;the-wayland-protocol&quot;&gt;The Wayland Protocol&lt;/h2&gt;

&lt;p&gt;As we touched on in the Mesa section, the Wayland protocol is still being
discussed. The discussion is extensive, and I cannot do it justice in this
post, but in this section I’ll outline the basics of what the protocol &lt;em&gt;must&lt;/em&gt;
include and approximately what that would look like.&lt;/p&gt;

&lt;p&gt;Wayland is a client-server
&lt;a href=&quot;https://wayland.freedesktop.org/architecture.html&quot;&gt;architecture&lt;/a&gt;. The clients
are applications like the video player Totem. A Wayland display server handles
client requests and notifies them of relevant events.&lt;/p&gt;

&lt;p&gt;You can, at this point, probably guess what information the protocol needs to
include. The client needs to inform the server of:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;The color primaries (red, green, blue, and white point coordinates)&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;The transfer function used to encode the luminance.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;HDR metadata when it is available - in the case of Totem playing an HDR
movie, HDR metadata will be available. This metadata may remain the same for
many frames (perhaps the whole movie), or it may change frame-by-frame.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without this information, the Wayland display server cannot properly tone-map
or blend the content, nor could it offload those tasks to dedicated GPU
hardware or the display since they both need the same information.&lt;/p&gt;

&lt;p&gt;The protocol includes an object that represents a rectangular area that can be
displayed, the
&lt;a href=&quot;https://wayland.freedesktop.org/docs/html/apa.html#protocol-spec-wl_surface&quot;&gt;wl_surface&lt;/a&gt;.
The surface is backed by a
&lt;a href=&quot;https://wayland.freedesktop.org/docs/html/apa.html#protocol-spec-wl_buffer&quot;&gt;wl_buffer&lt;/a&gt;.
The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wl_buffer&lt;/code&gt; holds the content and the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wl_surface&lt;/code&gt; describes the role
of the content and how the server should interpret the contents of the
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wl_buffer&lt;/code&gt;, such as how it should be scaled or rotated. Since the
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wl_surface&lt;/code&gt; includes metadata about the content, it is probably the right
place to include other details about how the server should interpret the
buffer, namely the color primaries, transfer function, and HDR metadata. While
the color primaries and transfer function aren’t likely to change over the
lifetime of a surface, the HDR metadata can change - HDR10+ and Dolby Vision
allow for frame-by-frame HDR metadata - so the protocol should account for
that.&lt;/p&gt;

&lt;p&gt;Additionally, the Wayland display server should be able to inform the clients
of:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;The native color primaries of the display. This will &lt;em&gt;not&lt;/em&gt; be a standard
color space such as sRGB.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Standard color spaces the display will accept (and internally convert to its
native color space).&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Transfer functions the display can decode.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Minimum luminance, maximum sustainable luminance, and peak (burst) luminance.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;HDR metadata formats the display will accept.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the case of Totem playing an HDR movie, it would not need to concern itself
with tone-mapping and would not need to know about what displays it might be
targeting. However, something like a video that generates content would benefit
from targeting the display’s capabilities.&lt;/p&gt;

&lt;h2 id=&quot;mutter&quot;&gt;Mutter&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://gitlab.gnome.org/GNOME/mutter&quot;&gt;Mutter&lt;/a&gt; is, among other things, a
Wayland display server. Once a protocol is in place, Mutter needs to implement
it. To support HDR, Mutter needs to be able to:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Inform clients of display capabilities.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Act upon the content encoding provided by the clients to correctly convert
all content to the same encoding and blend them all together into the desktop
we know and love.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To inform clients of the display capabilities, Mutter can use the Extended
Display Identification Data (EDID) from the kernel, which contains the display
color primaries, supported HDR formats, and so on. It will be covered in detail
in the kernel section below. The EDID is, in fact, already being used in Mutter
so this will be a small addition to that. There is some trickiness around
multi-monitor setups as clients don’t generally know which monitor they are
being displayed on, but displays with differing pixel density have a similar
challenge and Wayland handles that by providing events to the application when
it enters a new display and the display content encoding could work the same
way.&lt;/p&gt;

&lt;p&gt;To act upon the client-provided content encoding, however, requires a bit more
work.&lt;/p&gt;

&lt;h3 id=&quot;consolidating-existing-color-management&quot;&gt;Consolidating Existing Color Management&lt;/h3&gt;

&lt;p&gt;GNOME already has some color management capabilities. At the moment, they are
split up in gnome-settings-daemon, colord, and Mutter.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/hughsie/colord/&quot;&gt;colord&lt;/a&gt; is a system service that maintains
a database of devices, color profiles, and the association between the two
(including things like the default profile). At the moment, however,
gnome-settings-daemon is responsible for querying colord and sets up the
profile by calling Mutter D-Bus APIs.&lt;/p&gt;

&lt;p&gt;Mutter needs to be aware of each display’s color profile in order transform the
client content if necessary. The existing API for setting color profiles is
also difficult to use because it acts on CRTC objects rather than displays.
Some displays are composed of multiple panels tiled together and are therefore
backed by several CRTCs. It makes more sense for Mutter to query colord itself
rather than having gnome-settings-daemon do it.  gnome-settings-daemon also
handles the Night Light feature, which adjusts the color profile to shift the
white point towards red to remove the blue light.  Mutter needs to either
handle the Night Light feature or, preferably, provide a “temperature” API and
allow Mutter users to control when and how much to adjust the color
temperature.&lt;/p&gt;

&lt;h3 id=&quot;support-converting-buffer-formats&quot;&gt;Support Converting Buffer Formats&lt;/h3&gt;

&lt;p&gt;There are a number of ways to store an image, but they generally fall into two
categories. The first is to represent the red, green, and blue components, and
that’s the way we’ve been discussing images in this blog post series.&lt;/p&gt;

&lt;p&gt;Another way is to represent the luminance of a pixel, and then two color
components for that same pixel. The third color component can be calculated
using the lumanince and two known color components. This approach is commonly
referred to as &lt;a href=&quot;https://en.wikipedia.org/wiki/YUV&quot;&gt;YUV&lt;/a&gt; although there are
several flavors. This is a valuable approach because humans are more spatially
sensitive to luminance than they are color which means it’s possible to produce
a reasonably good-looking image even if you keep track of, say, every other
pixel’s color. This is called &lt;a href=&quot;https://en.wikipedia.org/wiki/Chroma_subsampling&quot;&gt;chroma
subsampling&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A lot of content, like films, are encoded using this approach, but at the
moment Mutter does not support YUV formats. It would be convenient, since we’re
adding a way for clients to communicate content encoding, to handle such
formats. There has been some work in this area by &lt;a href=&quot;https://gitlab.gnome.org/GNOME/mutter/-/commits/wip/nielsdg/meta-multi-texture-wip&quot;&gt;Niels De
Graef&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;compositing-the-content&quot;&gt;Compositing the Content&lt;/h3&gt;

&lt;p&gt;Once Mutter has knowledge of the content encoding of every surface, it can
blend them together and convert them to the target encoding of the display.
This involves:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Convert YUV to RGB if necessary.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Perform a color space transformation so that all surfaces are using the same
color primaries and whitepoint.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Removing the transfer function on surfaces so that Mutter is dealing with
the optical (linear) values rather than the non-linear on-the-wire encoding.
Adding two non-linear values together without accounting for the
non-linearity results in incorrect luminance levels. You can try this with
the following Python snippet:&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;def gamma(u):
    &quot;&quot;&quot;Apply the sRGB gamma definition to linear light levels&quot;&quot;&quot;
    return 1.055 * u**(1/24) - 0.055
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;Consider adding linear light level 50 to linear light level 50 and then
applying the transfer function versus adding the non-linear encoding of 50
to itself:&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &amp;gt;&amp;gt;&amp;gt; gamma(100)
 1.2231616798531606
 &amp;gt;&amp;gt;&amp;gt; gamma(50) + gamma(50)
 2.3735497958717904
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Blend the surfaces together. This becomes tricky with HDR since SDR does not
have a clearly defined luminance range, it’s just how bright your display is
set, but PQ-encoded content does have a well-defined luminance range. Mutter
will need define the SDR luminance range and map that content into that
well-defined range to ensure SDR content isn’t too dim or too bright.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;tone-mapping&quot;&gt;Tone-mapping&lt;/h3&gt;

&lt;p&gt;With HDR, clients may provide surfaces that encode luminances beyond the
capabilities of the target display. Displays account for this by tone-mapping
out-of-range content into the display range. This is described in &lt;a href=&quot;https://www.itu.int/dms_pub/itu-r/opb/rep/R-REP-BT.2390-8-2020-PDF-E.pdf&quot;&gt;Section
5.4.1 of
BT.2390&lt;/a&gt;.
The important thing to know is that providing the display with out-of-range
luminance levels will result in something that looks fine to most end users,
but there may be a desire to carefully control what happens when this occurs.&lt;/p&gt;

&lt;p&gt;Mutter would be responsible for implementing alternate tone-mapping if the
display’s default behavior is unacceptable for the user. This would most likely
be a concern for content creators.&lt;/p&gt;

&lt;h2 id=&quot;the-kernel&quot;&gt;The Kernel&lt;/h2&gt;

&lt;p&gt;We have arrived at the kernel, the final stop in this journey down the stack.
The good news is that the basic requirements for HDR have already been met.
We’ll touch on those and then look at additional features necessary for us to
use certain hardware features.&lt;/p&gt;

&lt;h3 id=&quot;the-bare-minimum&quot;&gt;The Bare Minimum&lt;/h3&gt;

&lt;p&gt;These are the bare minimum features for HDR and are already present in the
kernel.&lt;/p&gt;

&lt;h4 id=&quot;display-capabilities&quot;&gt;Display Capabilities&lt;/h4&gt;

&lt;p&gt;As we noted in the Mutter section, the
&lt;a href=&quot;https://en.wikipedia.org/wiki/Extended_Display_Identification_Data&quot;&gt;EDID&lt;/a&gt;
structure provided by a display is already available to userspace via the &lt;a href=&quot;https://www.kernel.org/doc/html/latest/gpu/drm-kms.html#standard-connector-properties&quot;&gt;EDID
connector
property&lt;/a&gt;.
Userspace can parse it for the chromaticity coordinates of the display
primaries, the luminance, supported color spaces, and supported transfer
functions. At the moment, many different projects implement parsing the EDID
structure, but there have been some &lt;a href=&quot;https://lists.x.org/archives/xorg-devel/2021-April/058690.html&quot;&gt;requests for a
library&lt;/a&gt; to do
so.&lt;/p&gt;

&lt;p&gt;One thing worth noting is that EDID is quite old and has a replacement
standard, DisplayID. Both standards are available from
&lt;a href=&quot;https://vesa.org/vesa-standards/&quot;&gt;VESA&lt;/a&gt; for free, although you do need to
provide your name and email address to access them. DisplayID has a &lt;a href=&quot;https://en.wikipedia.org/wiki/DisplayID#0x21_Display_Parameters&quot;&gt;Display
Parameters&lt;/a&gt;
block that includes luminance and chromaticity values, and the &lt;a href=&quot;https://en.wikipedia.org/wiki/DisplayID#0x26_Display_interface_features&quot;&gt;Display
Interface
Features&lt;/a&gt;
block contains supported color space and transfer function combinations as well
as bit depths. EDID included much of this information, but only as an extension
documented in CTA-861 (currently at revision G). Recently CTA made this
&lt;a href=&quot;https://shop.cta.tech/products/a-dtv-profile-for-uncompressed-high-speed-digital-interfaces-cta-861-g&quot;&gt;available for
free&lt;/a&gt;,
although you must provide an email and billing address to download it.&lt;/p&gt;

&lt;p&gt;Displays can provide an EDID, DisplayID, or both. For the curious, the VESA
E-DCC standard documents how these structures are accessed over
&lt;a href=&quot;https://en.wikipedia.org/wiki/I%C2%B2C&quot;&gt;I²C&lt;/a&gt;. How I²C is sent over, for
example, DisplayPort is documented within the DisplayPort specification, which
is only available to VESA members so the very curious will have to resort to
reading the &lt;a href=&quot;https://elixir.bootlin.com/linux/v5.12/source/drivers/gpu/drm/drm_dp_helper.c#L226&quot;&gt;Linux kernel’s
implementation&lt;/a&gt;
if they aren’t members.&lt;/p&gt;

&lt;p&gt;DisplayID can be embedded in the EDID structure as an extension so it may be
available to userspace via the existing EDID connector property, but at this
time there is not a dedicated kernel interface to retrieve a standalone
DisplayID structure that I am aware of. The work to add one would be minimal,
however, since it’s extremely similar to the EDID.&lt;/p&gt;

&lt;h4 id=&quot;hdr-metadata&quot;&gt;HDR Metadata&lt;/h4&gt;

&lt;p&gt;The kernel exposes a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HDR_OUTPUT_METADATA&lt;/code&gt; &lt;a href=&quot;https://www.kernel.org/doc/html/v5.12/gpu/drm-kms.html#standard-connector-properties&quot;&gt;connector
property&lt;/a&gt;
that userspace can use to send HDR metadata to the kernel and thus to the display.&lt;/p&gt;

&lt;p&gt;At this time there is only one &lt;a href=&quot;https://www.kernel.org/doc/html/v5.12/gpu/drm-uapi.html#c.hdr_output_metadata&quot;&gt;supported metadata
type&lt;/a&gt;,
which corresponds to the HDR metadata defined in CTA-861 in section 6.9
“Dynamic Range and Mastering InfoFrame”. This &lt;em&gt;should&lt;/em&gt; be all that is required
for HDR10 and HDR10+ content.&lt;/p&gt;

&lt;h4 id=&quot;bits-per-component&quot;&gt;Bits Per Component&lt;/h4&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;max bpc&lt;/code&gt; standard connector property is a &lt;a href=&quot;https://www.kernel.org/doc/html/latest/gpu/drm-kms.html#property-types-and-blob-property-support&quot;&gt;range
property&lt;/a&gt;
that allows the hardware driver to indicate valid bits per color component.
Userspace can set this property so long as it is within the driver-provided
range to control the output bit depth.&lt;/p&gt;

&lt;h3 id=&quot;nice-to-have-features&quot;&gt;Nice-to-have Features&lt;/h3&gt;

&lt;p&gt;The bare minimum feature set is present for HDR content and allows us to query
display capabilities, pass HDR metadata on to the display, and configure the
bits-per-component sent to the display.&lt;/p&gt;

&lt;p&gt;This leaves userspace to handle color space transformations, applying the
transfer function’s inverse to get linear light values, blending, and
re-encoding the results with a (perhaps different) transfer function. All that
is possible, of course, but there are a number of hardware features for all
those operations and it would be great to use them when they are there rather
than performing the operations using expensive CPU or general purpose graphics
shaders cycles.&lt;/p&gt;

&lt;h4 id=&quot;planes&quot;&gt;Planes&lt;/h4&gt;

&lt;p&gt;Before we cover the encoding, decoding, color space transformations, and so on,
it’s important to become familiar with
&lt;a href=&quot;https://www.kernel.org/doc/html/v5.12/gpu/drm-kms.html#plane-abstraction&quot;&gt;planes&lt;/a&gt;.
Planes hold images and descriptions of how those images should be blended into
the final output image. These are analogous to a Wayland surface, which
contains a buffer with the image data and metadata for the image.&lt;/p&gt;

&lt;p&gt;Planes describe how they should be cropped, scaled, rotated, and blended with
other planes to compose the final image sent to the display. These planes are
backed by dedicated hardware that can efficiently do those operations. For the
curious, the &lt;a href=&quot;https://01.org/sites/default/files/documentation/intel-gfx-prm-osrc-dg1-vol12-displayengine.pdf&quot;&gt;display
engine&lt;/a&gt;
section of Intel’s documentation describes some of the hardware plane’s
capabilities. Of particular interest is the diagram around page 258 which
illustrates the plane pipeline.&lt;/p&gt;

&lt;p&gt;One or more planes are used to compose the image the
&lt;a href=&quot;https://www.kernel.org/doc/html/v5.12/gpu/drm-kms.html#crtc-abstraction&quot;&gt;CRTC&lt;/a&gt;
scans out to the display.&lt;/p&gt;

&lt;h4 id=&quot;decoding-color-space-transformations-blending-and-encoding&quot;&gt;Decoding, Color Space Transformations, Blending, and Encoding&lt;/h4&gt;

&lt;p&gt;Graphics hardware typically provides hardware dedicated to efficiently decode,
transform, and re-encode images. Encoding and decoding are done with look-up
tables that approximate the smooth curve of the desired transfer function by
mapping encoded values to optical values and optical values to encoded values.
For historical reasons, these are referred to as the gamma and de-gamma LUTs
respectively.&lt;/p&gt;

&lt;p&gt;The hardware-backed look-up tables for encoding and decoding content with an
arbitrary transfer function are exposed to userspace as properties attached to
CRTC objects and are also documented in the &lt;a href=&quot;https://www.kernel.org/doc/html/v5.12/gpu/drm-kms.html#color-management-properties&quot;&gt;color
management&lt;/a&gt;
section of the KMS interface. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GAMMA_LUT&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DEGAMMA_LUT&lt;/code&gt; properties and
their respective lengths allow us to set arbitrary transfer function
approximations. The number of look-up table entries is hardware-dependent so it
may not be appropriate to use them in all cases.&lt;/p&gt;

&lt;p&gt;Color space transformations are applied by using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CTM&lt;/code&gt; (colorspace
transformation matrix) property defined in the &lt;a href=&quot;https://www.kernel.org/doc/html/v5.12/gpu/drm-kms.html#color-management-properties&quot;&gt;color
management&lt;/a&gt;
interface. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CTM&lt;/code&gt; property lets userspace define a color transformation
matrix to describe how to map from the source color space to the destination
color space. When combined with a de-gamma LUT and gamma LUT, it can be used to
efficiently decode, transform, and re-encode content for a particular color
space.&lt;/p&gt;

&lt;p&gt;Finally, the aforementioned planes include &lt;a href=&quot;https://www.kernel.org/doc/html/v5.12/gpu/drm-kms.html#plane-composition-properties&quot;&gt;composition
properties&lt;/a&gt;
that allow userspace to control how planes are blended together.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;However&lt;/em&gt;, these APIs have a few problems. The color management properties are
attached to the CRTC, which is intended to abstract the display pipeline and is
made up of planes. If the planes that make up the CRTC do not all have the same
transfer function applied to them or use different color spaces, there is no
way to express that each plane needs its own look-up table or color
transformation matrix.&lt;/p&gt;

&lt;p&gt;While it used to be reasonably safe to assume all the planes were the same
(e.g. sRGB), with HDR this is no longer the case. There are likely going to be
HDR planes and SDR planes that need to be blended together, so there needs to
be a way to express the color management properties - the content encoding - on
a plane-by-plane basis. In addition to the content encoding of each plane, we
need to be able to express &lt;em&gt;how&lt;/em&gt; HDR and SDR content should be blended. In the
case of PQ-encoded HDR content, it is expressed in absolute luminance up to
10,000 nits. Userspace needs to express how the SDR luminance range maps into
the HDR luminance range so the SDR encoding can be converted into HDR encoding.&lt;/p&gt;

&lt;p&gt;Harry Wentland from AMD recently started a &lt;a href=&quot;https://lore.kernel.org/dri-devel/20210426173852.484368-1-harry.wentland@amd.com/&quot;&gt;discussion on API changes for
planes&lt;/a&gt;
which looks to address these shortcomings.&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;All told, a good portion of the work necessary for basic HDR support is done or
in progress. Some of the larger challenges are in the compositors as they need
to line up client capabilities with hardware capabilities and make up any
differences between the two.&lt;/p&gt;

&lt;p&gt;Applications that use OpenGL or Vulkan directly don’t need to concern
themselves with GTK’s lack of HDR support. However, there are a non-trivial
handful of applications that would benefit from GTK supporting HDR content,
like Totem, Eye of GNOME (the image viewer), and content creation tools.&lt;/p&gt;

&lt;p&gt;Finally, there’s plenty of work on the kernel side to ensure userspace can make
use of all the hardware available to it.&lt;/p&gt;
</description>
        <pubDate>Mon, 28 Jun 2021 18:36:30 +0000</pubDate>
        <link>https://www.jcline.org/blog/fedora/graphics/hdr/2021/06/28/hdr-in-linux-p2.html</link>
        <guid isPermaLink="true">https://www.jcline.org/blog/fedora/graphics/hdr/2021/06/28/hdr-in-linux-p2.html</guid>
        
        
        <category>blog</category>
        
        <category>fedora</category>
        
        <category>graphics</category>
        
        <category>hdr</category>
        
      </item>
    
      <item>
        <title>HDR in Linux: Part 1</title>
        <description>&lt;p&gt;In recent months I have been investigating high dynamic range (HDR) support for
the Linux desktop, and what needs to be done so that a user could, for example,
watch a high dynamic range video.&lt;/p&gt;

&lt;p&gt;The problem with HDR is not so much that there is no material out there
covering it, but that there’s a huge amount of material can be rather
confusing. I thought it best to add more material that is probably also
confusing, but may help me think through HDR better. The following content is
likely wrong as I have no background in colorimetry, the human visual system,
or graphics generally. I’d love to hear what makes no sense.&lt;/p&gt;

&lt;p&gt;In this post I’ll cover what HDR is and why we care about it. In the next post,
I’ll cover the work in specific projects that has been done to support it and
what is left to be done.&lt;/p&gt;

&lt;h1 id=&quot;background&quot;&gt;Background&lt;/h1&gt;

&lt;p&gt;Before attempting to understand dynamic range, I found it helpful to understand
the basics of electromagnetic radiation (light) and how the human visual system
(eyes) interact with light so that we can produce graphics for human
consumption. With an understanding of how to do that, we can complete our
ultimate goal of tricking the human visual system into seeing extremely
realistic images of cats even if there are no cats around.&lt;/p&gt;

&lt;h2 id=&quot;light&quot;&gt;Light&lt;/h2&gt;

&lt;p&gt;Light can be described as a wave with an amplitude and frequency/wavelength or
a particle (photon) with a frequency/wavelength. Human eyes detect a very small
frequency range of electromagnetic radiation. More particles is equivalent to a
higher amplitude wave. The range of wavelengths eyes typically see is
approximately 380 nanometers to 750 nanometers.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/visible_spectrum.png&quot; alt=&quot;The visible light spectrum&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Humans detect wavelength of light as colors, with blue in the 450-490nm range,
green in 525-560nm range, and red in the 630-700nm range.&lt;/p&gt;

&lt;p&gt;More photons of a given wavelength are perceived by humans as a brighter light
of the color associated with the wavelength.&lt;/p&gt;

&lt;h2 id=&quot;luminance&quot;&gt;Luminance&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Luminance&quot;&gt;Luminance&lt;/a&gt; is the measure of the
amount of light in the &lt;em&gt;visibile spectrum&lt;/em&gt; that passes through an area.  The SI
unit is candela per square meter (cd/m²). This is often also referred to as a
“nit”, presumably because cd/m² is somewhat laborious to type out.&lt;/p&gt;

&lt;p&gt;The human eye can detect a luminance range from about 0.000001 cd/m² to
around 100,000,000 cd/m². We can readily go out into the world and &lt;a href=&quot;https://en.wikipedia.org/wiki/Orders_of_magnitude_(luminance)&quot;&gt;experience
this entire range,
too&lt;/a&gt;. For
example, the luminance of the sun’s disk at noon is around 1,600,000,000 cd/m².
This is much higher than the human eye can safely experience; do not go stare
at the sun.&lt;/p&gt;

&lt;p&gt;One important detail is that the human perception of luminance is &lt;a href=&quot;https://en.wikipedia.org/wiki/Weber%E2%80%93Fechner_law&quot;&gt;roughly
logarithmic&lt;/a&gt;. You
might already be familiar with this as
&lt;a href=&quot;https://en.wikipedia.org/wiki/Decibel&quot;&gt;decibels&lt;/a&gt; are used to describe sound
levels. The fact that human sensitivity to luminance is non-linear becomes very
important later when we need to compress data.&lt;/p&gt;

&lt;h2 id=&quot;human-visual-system&quot;&gt;Human Visual System&lt;/h2&gt;

&lt;p&gt;The eye is composed of two general types of cells. The rod cells which are very
sensitive and can detect very low amplitude (brightness) light waves, but don’t
differentiate between wavelengths (color) well.&lt;/p&gt;

&lt;p&gt;The cone cells, by contrast, come in several flavors where each flavor is
sensitive to a different wavelength. Many humans have three flavors,
imaginatively referred to as S, M, and L. S detects “short” wavelengths (blue),
M detects “medium” wavelengths (green), and L detects “long” wavelengths (red).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://upload.wikimedia.org/wikipedia/commons/0/04/Cone-fundamentals-with-srgb-spectrum.svg&quot; alt=&quot;A diagram of the sensitivity of typical cone cells&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Different colors are produced by stimulating these cone cells in with different
ratios of wavelengths.&lt;/p&gt;

&lt;p&gt;One thing to keep in mind, as it adds a minor complication to calculating
luminance, is that not all the cone cells are equally sensitive. If you were to
take a blue light, a green light, and a red light that all emit the same number
of photons, the green light would appear the brightest by far. The luminance,
therefore, depends on the wavelength of the light.&lt;/p&gt;

&lt;p&gt;While the human visual system is capable of detecting a very large range of
luminance levels, it cannot do so all at once. If you’re outside on a sunny day
and walk into a dark room, it takes some time for your eyes to adjust to the
new luminance levels.&lt;/p&gt;

&lt;h2 id=&quot;color-spaces&quot;&gt;Color Spaces&lt;/h2&gt;

&lt;p&gt;When discussing displays and their capabilities you will hear about color
spaces and perhaps see a diagram like this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://upload.wikimedia.org/wikipedia/commons/5/5f/CIE-1931_diagram_in_LAB_space.svg&quot; alt=&quot;Chromaticity diagram by Paulschou at en.wikipedia, CC BY-SA 3.0 , via
Wikimedia
Commons&quot; /&gt;&lt;/p&gt;

&lt;p&gt;There’s a lot going on in this diagram and it is rather confusing (to me,
anyway), so it’s worth covering the basics. There are &lt;a href=&quot;https://medium.com/hipster-color-science/a-beginners-guide-to-colorimetry-401f1830b65a&quot;&gt;some
good&lt;/a&gt;
&lt;a href=&quot;https://agraphicsguy.wordpress.com/2018/11/29/basic-color-science-for-graphics-engineers/&quot;&gt;blog
posts&lt;/a&gt;
with much more detail.&lt;/p&gt;

&lt;p&gt;You’ll notice the outside curve of this color blob has numbers marked along
them. These are light wavelengths, and the outer curve is the color we see for
that pure wavelength. On the bottom edge there are no wavelengths. This is
because those colors are what we see when the long cones and the short cones
both sense light. All the colors not on the edge of the curve are created by
blending together multiple wavelengths of light.&lt;/p&gt;

&lt;p&gt;Often, you’ll hear people talk about the “temperature” of light. This is in
reference to how as objects heat up they begin emitting light in the visible
spectrum. The curve starting on the red side, passing through the center, and
continuing toward blue shows the mapping of temperature to colors.&lt;/p&gt;

&lt;p&gt;If we pick three points on this plane and form a triangle, we can create any
color enclosed by the triangle by adding various ratios of those three colors
together. These points have coordinates in this two-dimensional space, and we
can describe the triangle by providing those coordinates. Using this approach,
we can describe the colors a display can create by specifying three pairs of
coordinates. This is the “color space”.&lt;/p&gt;

&lt;p&gt;If you’re using GNOME, you can see a diagram of your display’s color gamut by
going to the “Color” settings section, selecting the color profile, and
clicking view details:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/gnome-settings-cie-diagram.png&quot; alt=&quot;GNOME Settings displaying a CIE 1931 chromaticity diagram&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Note that these diagrams do not include the luminance. Doing so creates a third
dimension and can be used to visualize a “color volume”.&lt;/p&gt;

&lt;h2 id=&quot;displays&quot;&gt;Displays&lt;/h2&gt;

&lt;p&gt;There are several ways to display images for human consumption. One way is to
reflect light off a surface that absorbs some frequencies of light and reflect
the rest into the eye (paintings, books, e-ink, etc). The other way is to
produce light to shine into the eye, such as the liquid crystal or organic LED
screen you’re probably looking at right now.&lt;/p&gt;

&lt;p&gt;There’s a few challenges with producing the light directly. The first is that
we need to produce a range of light frequencies to cover the visible spectrum
of human vision. As humans usually have three types of frequency-sensing cells,
we don’t necessarily need to produce every single frequency in the visible
spectrum. Instead, we can pick three frequencies that align with where the cone
cells are sensitive and by adjusting the ratio of these three produce light
that contains all information the human eye uses and none of the information it
cannot detect.&lt;/p&gt;

&lt;p&gt;This is why a display pixel is typically made up of blueish, greenish, and
reddish components. Since this isn’t a perfect world, displays don’t tend to be
capable of producing a single frequency. The frequency range of these
components impact how the three overlapping cone sensitivities can be
stimulated and therefore determine the range of colors the display is capable
of producing. This range is referred to as the display’s “color gamut”. The
color gamut can vary quite a bit from display to display, so we need to account
for this when we encode images. We also need to handle cases where the display
cannot produce the color we wish to show. We could not, for example, accurately
display an image of a laser emitting pure 525nm light unless the green
sub-pixel happened to also emit pure 525nm light. This is called “gamut
mapping” as we map one color gamut to another color gamut.&lt;/p&gt;

&lt;p&gt;The second issue is that if we want to perfectly replicate the real world, we
would need to be able to emit light in the range of 0.000001-1,600,000,000
cd/m². However, as we’ve already established, the sun is not safe to look at so
it would be best to not faithfully reproduce it on a display. Even if we
restrict ourselves to the 0.000001-100,000,000 cd/m² range, the power
requirements and heat produced would be astronomical. We have to attempt to
represent the world using a smaller luminance range than we actually
experience. In other words, we have to compress the luminance range of our
world to fit the luminance range of the display. This process is often referred
to as “tone-mapping”.&lt;/p&gt;

&lt;h2 id=&quot;dynamic-range&quot;&gt;Dynamic Range&lt;/h2&gt;

&lt;p&gt;Dynamic range is the ratio between the smallest value and largest value. When
used in reference to displays, the value is luminance.&lt;/p&gt;

&lt;p&gt;The lowest luminance level is determined by how much ambient light is
reflecting off the surface of the display (e.g. are you sitting in a dark room,
outside on a sunny day, etc), how much of the backlight (if there is one)
leaks through the display panel, and so on.&lt;/p&gt;

&lt;p&gt;The highest luminance level is determined by the light source of the display.
This depends on the type of display, power usage requirements, user
preferences, on the ambient temperature (emitting visible light also emits
heat, and excessive heat can damage the display components), and perhaps other
factors I am not aware of.&lt;/p&gt;

&lt;p&gt;As both these values depend on environmental factors the dynamic range of a
display can change from moment to moment, which is something we may wish to
account for when compressing the luminance range of the world to fit the
particular display.&lt;/p&gt;

&lt;p&gt;So what is a high dynamic range display? In short, a display that is
simultaneously capable of lower luminance levels and higher luminance levels
than previous “standard dynamic range” displays. What these levels are exactly
can be hand-wavy, but &lt;a href=&quot;https://displayhdr.org/performance-criteria/&quot;&gt;VESA provides some clear
specifications&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This higher dynamic range ultimately means the way in which we compress the
luminance range (tone-map) needs to change.&lt;/p&gt;

&lt;h1 id=&quot;light-from-scene-to-display&quot;&gt;Light from Scene to Display&lt;/h1&gt;

&lt;p&gt;Now that we know the pieces of the puzzle, we can examine how an image ends up
on our displays.  In this section, we’ll cover the journey of an image from its
creation to its display. Perhaps the most self-contained example is a modern
video game. This allows us to dodge the added complication of camera sensors,
but the process for real-world image capture is similar.&lt;/p&gt;

&lt;h2 id=&quot;scene-referred-lighting&quot;&gt;Scene-referred Lighting&lt;/h2&gt;

&lt;p&gt;Video games typically model a world complete with realistic lighting.  When
they do this, they need to work with “real world” luminance levels. For
example, the sun that lights the scene may be modeled to be seen as 1.6 billion
nits. Working with real world luminance that cannot be directly displayed is
often called “scene-referred” luminance. We have to transform the
scene-referred luminance levels to something we can display (“display-referred”
luminance) by compressing or shifting the luminance values to a displayable
range.&lt;/p&gt;

&lt;p&gt;You might think it’s best to leave content in scene-referred lighting levels
until the last possible moment (when the display is setting the value for each
pixel), and that would indeed make some things simpler. However, it makes some
things more difficult, like attempting to blend the scene-referred image with
an image that is already display-referred. When doing operations like that, we
need every part to be in the same frame of reference, either scene-referred or
display-referred.&lt;/p&gt;

&lt;p&gt;There’s a more difficult problem with keeping all content scene-referred,
though. Representing numbers accurately in the 0.000001-1,600,000,000 cd/m²
range requires a lot of bits, and we’re about to have a serious bit budget
imposed on us. We have to transport the image data from the host graphics
processing unit to the display. While both HDMI and DisplayPort have rather
enormous bandwidth, the image resolution is also quite large. If we used a 32
bit floating point number for each red, green, and blue (RGB) component of a
pixel on a display with 3840 x 2160 (4K) pixels, it would require 32 x 3 x 3840
x 2160 bits per frame, or 760 Mbits per frame. At a modest 60 frames per
second, that’s 44.5 Gbits. DisplayPort 1.4 provides 25.9 Gbits and HDMI 2.0
give us 14.4 Gbits.&lt;/p&gt;

&lt;p&gt;Fortunately for us, as long as we ensure the way we encode luminance levels
matches up with the way the human eye detects luminance levels, we can save a
&lt;em&gt;lot&lt;/em&gt; of bits. In fact, for the “standard” dynamic range, we can manage with
only 8 bits (256 luminance levels) per color (24 bits total for RGB).&lt;/p&gt;

&lt;h2 id=&quot;tone-mapping&quot;&gt;Tone-mapping&lt;/h2&gt;

&lt;p&gt;In order to convert scene-referred, real world luminance to a range for our
target display, we need to:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Have an algorithm for mapping one luminance value to another. Input values
have a range [0,∞) and the output values need to cover the range of our
target display.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Know the capability of our target display.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For now we’ll focus on #1, although #2 is important and, unfortunately,
somewhat tricky.&lt;/p&gt;

&lt;p&gt;There are many different approaches to tone-mapping and results can be
subjective. This is not a post about tone-mapping (there are many multi-hundred
page publications on the subject), we’ll just go over a couple examples to get
the idea.&lt;/p&gt;

&lt;p&gt;An extremely silly, but technically valid tone-mapping algorithm would be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;f(x)
= 1&lt;/code&gt;, where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt; is the input luminance. This maps any input luminance level to
1.&lt;/p&gt;

&lt;p&gt;A more useful approach might be the function &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;f(x) = xⁿ/(xⁿ + sⁿ)&lt;/code&gt; where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt;
is the input luminance, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;n&lt;/code&gt; is a parameter changes the slope of the mapping
curve, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;s&lt;/code&gt; shifts the curve on the horizontal axis. As a somewhat random
example, with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;n = 3&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;s = 10&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/example-tone-map.png&quot; alt=&quot;Example tone mapping curve that maps all input luminances between 0 and 1&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This &lt;a href=&quot;https://en.wikipedia.org/wiki/Sigmoid_function&quot;&gt;sigmoid function&lt;/a&gt; gently
approaches the minimum and maximum values of 0 and 1. We can then map the [0,
1] range to the display luminance range.&lt;/p&gt;

&lt;p&gt;There are many articles and papers out there dedicated to tone mapping. There’s
a chapter on the topic in &lt;a href=&quot;https://books.google.com/books?id=LCtbDwAAQBAJ&quot;&gt;“The high dynamic range imaging pipeline” by Gabriel
Eilertsen&lt;/a&gt;, for example, which
include a number of sample images and a much more thorough examination of the
process than this blog post.&lt;/p&gt;

&lt;p&gt;This process is occasionally referred to as an “opti-optical transfer function”
or “OOTF” as it maps optical (luminance) values to different optical values.
Note that it is also possible to tone-map luminance values that are encoded for
storage or transportation, so-called “electronic” values, which you might see
referred to as electro-electronic transfer functions or “EETF”&lt;/p&gt;

&lt;h2 id=&quot;encoding&quot;&gt;Encoding&lt;/h2&gt;

&lt;p&gt;Once we’ve tone-mapped our content to use the display’s luminance range, we
need to send the content off to the display so that we can see it. As the
section on scene-referred light mentioned, we have a limited amount of
bandwidth, and any increase or decrease in bits used per pixel impacts how many
bits are required for a frame, and thus how many frames we can send to the
display every second. The key is to use as few bits as we can get away with.&lt;/p&gt;

&lt;p&gt;The method used to encode luminance levels is called the “&lt;a href=&quot;https://en.wikipedia.org/wiki/Transfer_functions_in_imaging&quot;&gt;transfer
function&lt;/a&gt;”,
“opto-electronic transfer function”, or “OETF”. These are functions that
approximate the human’s sensitivity to luminance levels.&lt;/p&gt;

&lt;p&gt;Since the display needs to decode the encoded data we send it back to linear
luminance values, we need functions that can be easily “undone” with an inverse
function. The property we’re looking for in our encoding and decoding functions
is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;g(f(x)) = x&lt;/code&gt;. This is called the “inverse transfer function”,
“electro-optical transfer function”, or “EOTF” since it converts the
“on-the-wire” values back to optical values.&lt;/p&gt;

&lt;p&gt;Note that the terminology and definitions of these transfer functions can be
confusing and vague. Some people appear to use OETF to mean both a tone-mapping
operation and a transfer to electronic values.&lt;/p&gt;

&lt;h3 id=&quot;encoding-sdr&quot;&gt;Encoding SDR&lt;/h3&gt;

&lt;p&gt;The “standard dynamic range” is not particularly standard, but usually tops out
around 300 nits. Typically, 8 bits per RGB component is used. 8 bits allow us
to express 256 luminance levels per component.&lt;/p&gt;

&lt;h4 id=&quot;the-naïve-approach&quot;&gt;The Naïve Approach&lt;/h4&gt;

&lt;p&gt;One method to encoding would be to evenly spread each level across the
luminance range. What would this look like? In our thought experiment, we’ll
assume the monitor’s minimum luminance is 0.5 nits, and its maximum luminance
is 200 nits.  This gives us a range of 199.5 nits. Equally distributed, each
step increases the luminance by about 0.78 nits.&lt;/p&gt;

&lt;p&gt;Sending [0, 0, 0] for red, green, and blue results in the minimum luminance of
0.5 nits. Adding a step [1, 1, 1] would result in 1.28 nits.&lt;/p&gt;

&lt;p&gt;Sending [254, 254, 254] results in 198.44 nits, and sending [255, 255, 255]
results in 200 nits.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/linear-encoded-luminance.png&quot; alt=&quot;Graph of linear display-referred luminance mapped to code points&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The problem with this approach is that with lower luminance values, each step
is above what the human eye differentiate, its &lt;a href=&quot;https://en.wikipedia.org/wiki/Just-noticeable_difference&quot;&gt;just-noticeable
difference&lt;/a&gt;. At
higher luminance levels, each step is so far below the just-noticeable
difference that it would require many steps for a human to notice any
difference at all. It results in clearly visible bands in the lower luminance
areas of the image we display and completely undetectable detail in the higher
luminance areas of the image. We could resolve this by adding more bits (and
therefore more levels), but we can’t afford to do that without giving up
resolution and framerate.&lt;/p&gt;

&lt;p&gt;Instead, we want to take these wasted levels at high luminances and use them in
lower luminances.&lt;/p&gt;

&lt;h4 id=&quot;gamma&quot;&gt;Gamma&lt;/h4&gt;

&lt;p&gt;The “gamma” transfer function has a &lt;a href=&quot;https://en.wikipedia.org/wiki/Cathode-ray_tube#Gamma&quot;&gt;long and interesting
history&lt;/a&gt; and is part of
the &lt;a href=&quot;https://en.wikipedia.org/wiki/SRGB&quot;&gt;sRGB standard&lt;/a&gt;. The sRGB version is
actually a piecewise function which uses the gamma function for all but the
very lowest luminance levels, but we can ignore that complication here.&lt;/p&gt;

&lt;p&gt;The basic function is in the form &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;f(x) = Axᵞ&lt;/code&gt;. In sRGB, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;A = 1.055&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;𝛾 =
1/2.4&lt;/code&gt;. The inverse function used to decode the luminance is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;A = 1/1.055&lt;/code&gt; and
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;𝛾 = 2.4&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The encoding curve looks like this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/gamma-encoded-luminance.png&quot; alt=&quot;Graph of the gamma function with gamma = 1/2.4&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In this graph, we map the linear, display-referred luminance values into the
range [0, 1]. These input luminances are mapped to an encoded value also in the
range [0, 1]. We can see from the graph above that the lower third of the
linear luminance levels get mapped on the lower two thirds of our encoding
range.&lt;/p&gt;

&lt;p&gt;We can convert the output to integer values between 0 and 255 by multiplying
the encoded luminance value by 255 and rounding:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/gamma-encoded-mapped-255.png&quot; alt=&quot;Graph of the gamma function with gamma = 1/2.4 mapped to 0-255&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Now, instead of wasting many of our precious bits on luminance steps on the
high end that no one can see, we’re spending most of them on low luminance
levels.&lt;/p&gt;

&lt;p&gt;This is a pretty good approach when we have 256 steps (8 bits) per component
and luminance range of a few hundred nits, but the further we stretch the range
of nits, the bigger our steps have to be, and eventually we’ll cross a point
where some steps are about the just-noticeable difference line.&lt;/p&gt;

&lt;p&gt;We can fix this by increasing the number of bits used. What happens if we move
to 10 or even 12 bits? At 12 bits per component, we have 4096 steps.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/gamma-encoded-mapped-4096.png&quot; alt=&quot;Graph of the gamma function with gamma = 1/2.4 mapped to 0-4096&quot; /&gt;&lt;/p&gt;

&lt;p&gt;At first glance this seems fine, but what if we adjust the graph to display
actual nit values for the linear light? Here’s the same graph with linear light
using a range of 0-1000 nits.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/gamma-encoded-mapped-4096-1knits.png&quot; alt=&quot;Graph of the gamma function with gamma = 1/2.4 mapped to 0-4096&quot; /&gt;&lt;/p&gt;

&lt;p&gt;We can see that fewer than half our steps, or approximately 2000, land in the
luminance ranges we use today for standard dynamic range displays. Once again,
we’re allocating too many steps to the very high luminance levels where the
human eye becomes less and less sensitive. This is even worse as the luminance
range increases:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/gamma-encoded-mapped-4096-10knits.png&quot; alt=&quot;Graph of the gamma function with gamma = 1/2.4 mapped to 0-4096&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Thus, for displays with a higher dynamic range, we may want to adjust the
transfer function to get the most value out of each bit we spend.&lt;/p&gt;

&lt;h3 id=&quot;encoding-higher-dynamic-ranges&quot;&gt;Encoding Higher Dynamic Ranges&lt;/h3&gt;

&lt;p&gt;As we saw when examining the gamma transfer function, it works reasonably
well for low luminance ranges, but as the range increases it starts to be
sub-optimal.&lt;/p&gt;

&lt;p&gt;There are two major approaches for higher dynamic range encoding. Both have
advantages and disadvantages, and displays can support either or both. If
you’re curious if your displays are capable of using either of these
approaches, you can check by installing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;edid-decode&lt;/code&gt; and running:&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;find /sys/devices -name edid -exec edid-decode {} \;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Look for a “HDR Static Metadata Data Block” and see what is included in the
electro-optical transfer function list. This section will likely not be present
if your display doesn’t support HDR.&lt;/p&gt;

&lt;h4 id=&quot;the-perceptual-quantizer&quot;&gt;The Perceptual Quantizer&lt;/h4&gt;

&lt;p&gt;The &lt;a href=&quot;https://en.wikipedia.org/wiki/Perceptual_Quantizer&quot;&gt;Perceptual Quantizer&lt;/a&gt;
(PQ) transfer function, sometimes referred to by the much less cool name “SMPTE
ST 2084”, defines a new curve to use for encoding. It is unlike the gamma
transfer function in that it is defined to cover a well-defined luminance
range, rather than just being fit to whatever the display happens to be capable
of. The luminance range the curve is defined to cover is 0.0001-10,000 cd/m².&lt;/p&gt;

&lt;p&gt;This is a useful concept when encoding the content as we can now express
physical quantities in addition to “more/less luminance”. It gives the decoding
side a frame of reference if it needs to further tone-map the content.&lt;/p&gt;

&lt;p&gt;The curve looks like:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/pq-encoded-luminance.png&quot; alt=&quot;Part of the Perceptual Quantizer curve&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This graph is only part of the curve, and you can see the slope is so extreme
it doesn’t render very well. It packs a vast majority of the code points into
the lower luminance range where the eye is most discerning. Currently, it’s
common to use this curve with 10 bits per component.&lt;/p&gt;

&lt;p&gt;The function that describes this curve is more complicated than the gamma curve
so I won’t attempt to render it poorly here. The curious can consult the
Wikipedia page linked above.&lt;/p&gt;

&lt;p&gt;The perceptual quantizer transfer function can be paired with metadata
describing the image or images it is used to encode. This metadata is usually
the primary colors of the display used to create the content as well as
luminance statistics like the image’s minimum, maximum, and average luminance.
This metadata allows consumers of the content to perform better tone-mapping
and gamut-mapping since it describes the exact color volume of the content.&lt;/p&gt;

&lt;p&gt;The PQ curve supports encoding luminance up to 10,000 nits, but there are no
consumers displays capable of that. Even professional displays built for
movie-making are well short of that, currently around 4,000 nits. This is still
much higher than current consumer displays, so the metadata can be used to
tone-map and gamut-map the content from the capabilities of fancy display the
film studio used to the capabilities of the individual display. In the future,
when consumer monitors become more capable, the same films will look better as
they require less tone-mapping and the original intent can be more accurately
rendered.&lt;/p&gt;

&lt;h4 id=&quot;the-hybrid-log-gamma&quot;&gt;The Hybrid Log-Gamma&lt;/h4&gt;

&lt;p&gt;As the name suggests, the second approach for higher dynamic range encoding is
to use a hybrid of the gamma function and a log function. The &lt;a href=&quot;https://en.wikipedia.org/wiki/Hybrid_Log-Gamma&quot;&gt;hybrid
log-gamma&lt;/a&gt; (HLG) curve is
designed with backwards-compatibility in mind.&lt;/p&gt;

&lt;p&gt;HLG uses a piecewise function defined as follows when the input luminance has
been normalized to a range of 0-1:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;When the input luminance &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;L&lt;/code&gt; is between 0 and 1/12: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sqrt(3) * L^0.5&lt;/code&gt; (this
is our old friend the gamma function &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;f(x) = Axᵞ&lt;/code&gt;, with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;A = 1.732&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;𝛾 =
1/2&lt;/code&gt;)&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;When input luminance &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;L&lt;/code&gt; is between 1/12 and 1: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a * ln(12L - b) + c&lt;/code&gt; where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a =
0.17883277&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;b = 1 - 4a&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;c = 0.55991073&lt;/code&gt;.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/assets/hlg-encoded-luminance.png&quot; alt=&quot;The Gamma and Log curves in the HLG transfer function&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In the above graph, the red curve is the gamma curve, the green curve is the
log curve, and the vertical line is the point where the gamma curve stops being
used in favor of the log curve when encoding.&lt;/p&gt;

&lt;p&gt;As this encoding scheme is defined in terms of relative luminance like the
gamma transfer function, there is no metadata for tone-mapping.&lt;/p&gt;

&lt;h2 id=&quot;transmission-and-display&quot;&gt;Transmission and Display&lt;/h2&gt;

&lt;p&gt;Once we’ve encoded the image with a transfer function the display supports, the
bits are sent to the display via HDMI or DisplayPort. At this point, what
happens next is up to whatever is on the other end of the cable. I have not
pulled apart a display and learned what secrets it holds, but we can make some
reasonable guesses without destroying anything:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;If metadata is provided (PQ-encoded images only), the display examines it and
  determines of any tone-mapping is required due to the content exceeding its
  capabilities. If it’s not provided, it likely assumes the content spans the
  entire range defined by PQ.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;If the display includes light sensors to detect ambient light levels, it
&lt;em&gt;might&lt;/em&gt; decide to tone-map the content, even if it’s capable of displaying
all luminance levels encoded.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Displays tend to include configuration to alter the color and brightness. It
will take these into account when deciding if/how to tone-map or gamut-map
the content we gave it.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Some gamut-mapping and tone-mapping will occur in the display depending on
all the above variables, at which point it will emit some light.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now, that’s a very high-level overview of the process, but we can map these
high-level steps to portions of the Linux desktop and discuss what needs to
change in order for us to support HDR.&lt;/p&gt;

&lt;h1 id=&quot;summary&quot;&gt;Summary&lt;/h1&gt;

&lt;p&gt;Now that we understand (in theory) HDR, it’s worth asking what all this is good
for.&lt;/p&gt;

&lt;p&gt;If, as part of your movie-watching experience, you want an eye-wateringly
bright sunset where you can still make out the blades of grass in the shadow of
a rock this is an important feature. The more cinematic video games could look
even better. These are the most obvious applications, and where many users will
encounter HDR, but it’s not necessarily limited to entertainment. HDR is all
about transmitting and displaying more information, so any process that
involves visualization could benefit from more luminance levels.&lt;/p&gt;

&lt;p&gt;As we’ll see in the next post, however, there is a good bit of work left to be
done before you can, for example, enjoy an HDR film in GNOME’s Videos
application.&lt;/p&gt;
</description>
        <pubDate>Fri, 07 May 2021 15:55:08 +0000</pubDate>
        <link>https://www.jcline.org/blog/fedora/graphics/hdr/2021/05/07/hdr-in-linux-p1.html</link>
        <guid isPermaLink="true">https://www.jcline.org/blog/fedora/graphics/hdr/2021/05/07/hdr-in-linux-p1.html</guid>
        
        
        <category>blog</category>
        
        <category>fedora</category>
        
        <category>graphics</category>
        
        <category>hdr</category>
        
      </item>
    
  </channel>
</rss>
