Zig SHOWTIME
Windows dominates desktop computing solely because of video games, but Linux fragmentation can be bypassed if upstream maintainers and Valve adopt hybrid ELF binaries that link graphics drivers dynamically at runtime.
AI-assisted summary of 2021: Year of the Linux Gaming Desktop - Andrew Kelley — original source. Check the original for full context.
Source published:
Key ideas
- Andrew Kelley argues that desktop operating system market share is driven almost exclusively by video games, trapping Linux in a chicken-and-egg cycle where developers follow Windows market share and players stay where the games are. (1:36)
- A proof-of-concept hybrid ELF executable ('static-window-9') successfully displays a Vulkan triangle across diverse Linux distributions—including glibc and musl environments like Ubuntu, NixOS, Void Linux, and Arch—without recompilation or containers. (3:45)
- The universal binary bypasses hardcoded dynamic linker paths by starting as an uninterpreted static binary, discovering the host dynamic linker at runtime, and reinvoking itself via execve with LD_PRELOAD. (8:54)
- Patching the official Vulkan loader to use RTLD_GLOBAL instead of RTLD_LOCAL enables process-wide symbol sharing, eliminating the need to vendor or re-implement graphics driver stacks. (25:13)
Detailed notes
## Background This presentation was delivered by Andrew Kelley on the Zig SHOWTIME channel on YouTube, published on February 19, 2021, as part of episode #21. The broadcast features a technical talk followed by an interview and community Q&A exploring whether Linux can overcome its runtime hurdles to become a viable gaming desktop.
## TL;DR Kelley argues that video games remain the primary reason Windows dominates the consumer desktop market, trapped in a self-reinforcing feedback loop that starves Linux of commercial support. To tackle the platform's notoriously brittle shared library and dynamic linker fragmentation, he demonstrates a proof-of-concept hybrid ELF executable capable of running a Vulkan window out of the box across radically different distributions like NixOS, Void Linux, and Ubuntu. The solution relies on a statically bootstrapped binary with a stripped dynamic section that inspects the host distribution at runtime, invokes execve on the system dynamic linker, and links dynamically into native host libc and GPU drivers. Ultimately, Kelley urges the Linux and graphics communities—as well as companies like Valve—to rethink dynamic linking conventions and officially support universal binaries for gamers.
## Atmosphere & style The presentation carries the pragmatic, hacker-spirited energy characteristic of the Zig community, balancing self-deprecating humor with deep systems-level tenacity. Kelley is candid about his 'clickbait' title and the sheer absurdity of the linker workarounds he had to assemble, yet his delivery remains grounded in genuine technical optimism. The atmosphere shifts naturally from a focused screen-share deep dive into assembly-level headers to a relaxed, banter-filled livestream Q&A with host Loris Cro, leaving viewers with the impression of an ambitious proof-of-concept meant to challenge entrenched industry norms.
## Bulletin - [01:36](https://www.youtube.com/watch?v=pq1XqP4-qOo&t=96s) Andrew Kelley argues that desktop operating system market share is driven almost exclusively by video games, trapping Linux in a chicken-and-egg cycle where developers follow Windows market share and players stay where the games are. - [03:45](https://www.youtube.com/watch?v=pq1XqP4-qOo&t=225s) A proof-of-concept hybrid ELF executable ('static-window-9') successfully displays a Vulkan triangle across diverse Linux distributions—including glibc and musl environments like Ubuntu, NixOS, Void Linux, and Arch—without recompilation or containers. - [08:54](https://www.youtube.com/watch?v=pq1XqP4-qOo&t=534s) The universal binary bypasses hardcoded dynamic linker paths by starting as an uninterpreted static binary, discovering the host dynamic linker at runtime, and reinvoking itself via execve with LD_PRELOAD. - [25:13](https://www.youtube.com/watch?v=pq1XqP4-qOo&t=1513s) Patching the official Vulkan loader to use RTLD_GLOBAL instead of RTLD_LOCAL enables process-wide symbol sharing, eliminating the need to vendor or re-implement graphics driver stacks.
## Key takeaways - [01:36](https://www.youtube.com/watch?v=pq1XqP4-qOo&t=96s) Windows maintains consumer desktop dominance primarily because it commands the overwhelming majority of video games, creating a self-reinforcing loop that keeps developers and gamers off Linux. - [06:01](https://www.youtube.com/watch?v=pq1XqP4-qOo&t=361s) Standard Linux binary portability fails across distributions due to conflicting C standard libraries (glibc vs. musl) and hardcoded interpreter paths inside the ELF .interp header. - [06:01](https://www.youtube.com/watch?v=pq1XqP4-qOo&t=361s) If a conventional Linux executable encounters a missing or mismatched interpreter path, the kernel terminates process creation before reaching main. - [12:38](https://www.youtube.com/watch?v=pq1XqP4-qOo&t=758s) By dynamically linking against a dummy library and then stripping the DT_NEEDED dependency with patchelf, an ELF binary can retain a valid .dynamic section while omitting the .interp header. - [14:12](https://www.youtube.com/watch?v=pq1XqP4-qOo&t=852s) Declaring dlopen as a weak external symbol enables a two-pass entry point: if dlopen is NULL, the binary executes its static bootstrapping phase; if populated, it proceeds with dynamic execution. - [14:12](https://www.youtube.com/watch?v=pq1XqP4-qOo&t=852s) During static bootstrapping, the binary dynamically inspects host distribution metadata to locate the system dynamic linker, sets LD_PRELOAD for libdl and libpthread, and replaces its process image via execve. - [19:10](https://www.youtube.com/watch?v=pq1XqP4-qOo&t=1150s) glibc and musl require distinct initialization flows, with musl requiring explicit invocations of __libc_start_main and manual symbol resolution via dlopen. - [20:55](https://www.youtube.com/watch?v=pq1XqP4-qOo&t=1255s) Statically embedding the upstream Vulkan-Loader requires stubbing all standard C library functions as weak symbols to prevent immediate segmentation faults during the initial static bootstrap pass. - [25:13](https://www.youtube.com/watch?v=pq1XqP4-qOo&t=1513s) Upstream dynamic loading using RTLD_LOCAL isolates symbols within the loader scope; patching it to RTLD_GLOBAL allows the host graphics driver and application to share cached runtime dependencies. - [32:16](https://www.youtube.com/watch?v=pq1XqP4-qOo&t=1936s) Re-implementing the X11 network protocol purely in application code provides little practical benefit because host graphics drivers already load dynamic X11 shared libraries into memory. - [36:55](https://www.youtube.com/watch?v=pq1XqP4-qOo&t=2215s) Native Wayland support can be incorporated cleanly through fallback detection, attempting to load libwayland-client.so first before falling back to XCB and XWayland. - [29:12](https://www.youtube.com/watch?v=pq1XqP4-qOo&t=1752s) Toolchains like LLD and compiler toolchains often strip the dynamic section when an executable lacks direct dynamic dependencies, unintentionally breaking hybrid static-dynamic binary architectures. - [41:11](https://www.youtube.com/watch?v=pq1XqP4-qOo&t=2471s) Upstream collaboration across Vulkan-Loader maintainers, distribution packagers, and platform holders like Valve is required to standardize hybrid dynamic loading rather than relying on brittle build hacks.
## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=pq1XqP4-qOo&t=0s) Introduction: A Statically Linked Dream
The session opens with an intro highlighting the ultimate goal of the talk: Andrew Kelley has a dream—an "almost statically linked dream"—where video games can be built and distributed for Linux as distribution-agnostic executables. The host jokes to Gabe Newell, urging him to put down *Half-Life: Alyx Episode 2* because he will want to watch this presentation, officially titled "2021: Year of the Linux Gaming Desktop."
[01:36](https://www.youtube.com/watch?v=pq1XqP4-qOo&t=96s) The Gaming Feedback Loop on Desktop Operating Systems
Andrew takes the stage and addresses the perennial question in the Linux community: will this year finally be the "Year of the Linux Desktop"? His blunt answer is no. Andrew argues that Windows and macOS dominate the consumer desktop market exclusively because of video games. “It's just because of games that's my premise,” he states, identifying this as the single defining advantage those platforms maintain over Linux.
Game developers target Windows and macOS because they want to run viable businesses, following the overwhelming market share visible in Steam hardware and software user surveys. If an independent or AAA developer ignores the massive blue Windows slice of the pie chart in favor of the tiny Linux slice, they simply will not make money. Gamers, in turn, choose Windows because that is where the vast catalog of games lives. This creates a self-reinforcing feedback loop:
```mermaid flowchart LR accTitle: Desktop Gaming Feedback Loop accDescr: Diagram illustrating the self-reinforcing cycle between game developers and gamers on Windows. A["Gamers choose Windows<br>(Where the games are)"] --> B["Game developers target Windows<br>(Where the money is)"] B --> A ```
Andrew concedes that he has no solution to dismantle this entrenched feedback loop, openly calling the title of his talk a "bait and switch" clickbait hook. However, he believes it is entirely possible to make the Linux gaming experience seamless for end users, which could gradually expand Linux's slice of the pie.
[03:45](https://www.youtube.com/watch?v=pq1XqP4-qOo&t=225s) The Proof of Concept: A Universal Linux Executable
Andrew moves to a live demonstration of his proof of concept: an ELF executable named `static-window-9`, hosted at a temporary URL that he jokes he has not cleaned in years and will leave up indefinitely. The number nine in the filename, he quips, simply marks how many times he failed before getting it right.
The binary is a minimal graphical application that opens a window and renders a basic Vulkan triangle. It does not even handle window resizing or graceful exits; the user has to terminate the process from the terminal. Under the hood, it talks directly to the X11 display server protocol (or XWayland if running on Wayland) and binds into the host system's native Vulkan graphics drivers. Andrew demonstrates that this exact same ELF binary runs out-of-the-box across a wide spectrum of Linux distributions without recompilation or containers:
- Ubuntu - NixOS - Void Linux (musl libc variant) - Void Linux (glibc variant) - Clear Linux - Arch Linux - Debian Sid
He runs the binary on his local machine to verify that the triangle appears on screen as advertised, proving the concept is functional.
[06:01](https://www.youtube.com/watch?v=pq1XqP4-qOo&t=361s) Why Linux Binary Portability Is Hard: The Dynamic Linker and Libc
To explain why running a single graphical binary everywhere is difficult, Andrew compares his executable against a standard Vulkan demo binary like `vkcube` from `vulkan-tools`. On Windows, distributing an executable that talks to graphics drivers is straightforward, making it a much friendlier platform for game developers. On standard Linux distributions, a regular binary introduces distribution-specific barriers:
1. Dynamic Dependencies: Running `ldd` against `vkcube` reveals direct shared library dependencies on `libX11`, `libc`, and the system Vulkan loader (`libvulkan`). 2. Conflicting C Standard Libraries: Host graphics drivers dynamically link against the host system's `libc`. Different distributions deploy fundamentally different C standard libraries, most notably GNU C Library (`glibc`) versus `musl`, which are not ABI-interchangeable. 3. Hardcoded Interpreter Path: Inspecting the ELF headers of a conventional binary via `readelf` reveals the `.interp` section. This section stores a hardcoded absolute file path to the system dynamic linker (such as `/lib64/ld-linux-x86-64.so.2` on glibc systems, a musl-specific path on musl distros, or a `/nix/store/...` hash path on NixOS).
If a user copies a binary compiled on one distribution to another whose interpreter path differs, the kernel fails before the program's code can execute: “If I tried to run this binary vk cube on a different Linux it would just say like error it would never execute it never even gets main because this path wouldn't exist on your computer.” This brittle coupling explains why platforms like Valve's Steam target only an Ubuntu-compatible base runtime to avoid dealing with varying system setups.
[08:54](https://www.youtube.com/watch?v=pq1XqP4-qOo&t=534s) Evolving the Self-Execution Architecture
Because the kernel invokes the dynamic interpreter specified in `.interp` before reaching `main`, application code normally cannot intervene to resolve missing paths. Andrew credits Jonathan Marler for sparking the solution. Marler had explored an ELF binary concept that executes statically first and then invokes `execve` on itself, passing its own binary as the argument to the dynamic linker. Because dynamic linkers (such as `ld.so`) are themselves executable binaries, passing an ELF file to the loader command-line instructs it to dynamically link and run that target executable.
Marler’s initial prototype had two drawbacks: - It required linking against a dummy shared library to force the linker to emit an ELF `.dynamic` section. - It still relied on a hardcoded path to invoke the dynamic loader.
Andrew designed a more comprehensive runtime strategy to bypass these limitations:
```mermaid flowchart TD accTitle: Universal Linux Binary Execution Flow accDescr: Flowchart showing how the hybrid ELF binary bootstraps statically, discovers the dynamic linker, reinvokes itself, loads system libc and Vulkan, and draws graphics. subgraph Phase1["Static Bootstrapping"] A["Start as static binary<br>(No.interp section)"] --> B{"Running under dynamic linker?"} B -- "No" --> C["Detect system dynamic linker path at runtime"] C --> D["execve() self using discovered linker"] end subgraph Phase2["Dynamic Integration"] D --> B B -- "Yes" --> E["Detect host system libc (glibc or musl)"] E --> F["dlopen() host libc & load Vulkan driver"] F --> G["Render Vulkan graphics triangle"] end ```
[11:33](https://www.youtube.com/watch?v=pq1XqP4-qOo&t=693s) Crafting the Hybrid ELF Binary
The foundational hurdle in this design is constructing a hybrid ELF executable that the Linux kernel will happily run statically, but which also contains the necessary ELF metadata to be processed by an external dynamic loader.
Andrew inspects his `static-window-9` binary with standard command-line tools to show its internal layout. Running `ldd` reports that the binary is "statically linked." Examining its ELF program headers with `readelf` confirms that there is no `.interp` section present, meaning the kernel directly maps the binary into memory and enters its entry point without looking for a pre-configured loader. Crucially, however, the binary still contains an ELF `.dynamic` section. This dynamic section provides the entry tables needed for dynamic linkers to bind symbols once reinvoked, bridging the gap between a standalone static binary and the host's dynamic driver ecosystem.
[12:38](https://www.youtube.com/watch?v=pq1XqP4-qOo&t=758s) Crafting a Binary with a Dynamic Section but No Dynamic Dependencies
Andrew Kelley explains how to construct an executable that can function both statically and dynamically without being rejected or mishandled by the Linux kernel. If an executable has an `INTERP` program header, the operating system kernel immediately invokes the specified dynamic linker path; conversely, without a `.dynamic` section, dynamic linking cannot take place later. To balance both requirements, Kelley creates a dummy shared library during the build and dynamically links the executable against it. This forces the linker to emit a valid `.dynamic` section.
Immediately following compilation, Kelley uses the `patchelf` utility with the `--remove-needed` flag targeted at `libdummy`. This strips the `DT_NEEDED libdummy` tag out of the `.dynamic` section entirely. The result is a unique binary: it retains a valid `.dynamic` section structure, but it lists zero dynamic library dependencies and contains no hardcoded `INTERP` entry. Because there is no interpreter header, Linux launches the binary directly into its static entry point without attempting to resolve dynamic libraries at startup. As Kelley summarizes: “We have a executable that supports being run both dynamically and statically, so it has a dynamic section but no dynamic dependencies, and the OS or Linux will not try to run it with a dynamic linker because there's no interp entry.”
```mermaid flowchart TD accTitle: Binary Stripping and Re-execution Architecture accDescr: Diagram illustrating how the binary starts statically, detects the dynamic linker, and re-execs itself with LD_PRELOAD.
subgraph Build["Build Stage"] A["Compile with dummy shared library"] --> B["Binary has.dynamic section"] B --> C["Run patchelf --remove-needed libdummy"] C --> D["Executable has.dynamic section but NO dependencies & NO INTERP"] end
subgraph Execution["Runtime Execution"] D --> E["Kernel runs binary statically: enters main()"] E --> F{"dlopen symbol present?"} F -->|"No (First Pass)"| G["Detect native target info & dynamic linker path"] G --> H["Prepare argv: insert dynamic linker as argv[0]"] H --> I["Set LD_PRELOAD=libdl:libpthread"] I --> J["Call execve() to reload self via dynamic linker"] J --> F F -->|"Yes (Second Pass)"| K["dlopen!= null: run libc-agnostic setup & enter main2()"] end ```
[14:12](https://www.youtube.com/watch?v=pq1XqP4-qOo&t=852s) The Two-Pass Entry Point and Self-Re-execution via execve
Reaching the `main` function introduces an immediate architectural puzzle: the binary can enter `main` under two completely different execution contexts. On the initial run, the OS kernel executed the binary directly as a static program without a dynamic linker. On the subsequent pass, the binary is invoked through the dynamic linker. Because both paths lead to the very same `main`, the startup code must detect which environment it is currently running in.
To achieve this, the program defines a weak external symbol reference for `dlopen` (`weak extern dlopen`). The `dlopen` symbol is ordinarily provided by `libc`/`libdl`. If the binary is running statically on the first pass, `dlopen` resolves to `NULL`. When `dlopen` is `NULL`, the program executes its bootstrap path: it must find the host system's dynamic linker and call `execve` on itself so it can re-enter `main` under dynamic execution. Kelley emphasizes: “Making it a weak extern symbol means that we support if it's not available, in which case um this code will run because we don't have dlopen because we're being run statically; however, if the symbol is available, then we go down here and we know that we're actually running inside the dynamic linker.”
Because different Linux distributions store their dynamic linkers at different paths, hardcoding is impossible. Kelley relies on logic already developed for the Zig standard library: - Zig's `detectNativeTargetInfo` function inspects the host distribution at runtime to extract the native target triple and the exact absolute path to the dynamic linker. - The binary constructs a new `argv` array where `argv[0]` is the detected dynamic linker path, followed by the path to the executable itself and the original command-line arguments. - Because stripping the `DT_NEEDED` tags during build removed all automatic library dependencies, the program modifies the environment variables before calling `execve`, explicitly setting `LD_PRELOAD` to pull in `libdl` and `libpthread` (necessary for dynamic loading and threading primitives like mutexes). - When `execve` executes, it wipes out the current process image and reloads the binary via the specified dynamic linker, satisfying the `LD_PRELOAD` libraries. - Upon re-entering `main`, the weak `dlopen` symbol is no longer `NULL`, allowing the binary to advance into normal dynamic execution.
[19:10](https://www.youtube.com/watch?v=pq1XqP4-qOo&t=1150s) Handling libc Diversity: glibc vs. musl
Solving the dynamic linker problem brings the program to its next hurdle: Linux distributions do not share a single C standard library implementation. While standard systems like Ubuntu use GNU C Library (glibc), lightweight distributions like Alpine Linux rely on musl. Once re-executed under the dynamic linker, the binary still has to bridge structural behavioral differences between these libraries.
On glibc-based systems, loading the dynamic linker smoothly brings in the required standard runtime environment, allowing the program to proceed straight to application logic. Musl, by contrast, is more bare-bones and requires manual intervention: - On musl, the code explicitly invokes musl's `__libc_start_main`, providing pointers to handle process initialization. - It must manually set up exit handlers (calling `exit` explicitly) and use `dlopen`/`dlsym` to resolve required standard `libc` functions. - The binary once again calls `detectNativeTargetInfo` from the Zig standard library to detect the native runtime triple (distinguishing whether the host is glibc or musl). - After distribution-specific setup finishes, execution routes into a unified second stage, `main2`, where the application can safely access the Vulkan and X11 APIs.
[20:55](https://www.youtube.com/watch?v=pq1XqP4-qOo&t=1255s) Hacking the Vulkan Loader and Weak libc Function Symbols
Even with runtime `libc` selection functional, running graphics APIs required overhauling the official Vulkan loader (`Vulkan-Loader`), a Khronos Group project that locates hardware drivers and populates function pointers for the Vulkan API. The loader smooths out system variations, but it is fundamentally designed to be built as a dynamic library linked against the host system's C library. Kelley wanted to embed and build the loader statically into the binary without tying it to a specific `libc`.
To build the loader statically, Kelley extracted only the necessary C source files from the upstream Khronos repository into the Zig project. Taking advantage of Zig's built-in C compilation pipeline (powered by `libclang`), he configured the `build.zig` build script to define the requisite preprocessor macros, compile the selected source files, and strictly enforce Position Independent Code (`-fPIC`) so the generated machine code remains valid whether run statically or dynamically.
Embedding the loader introduced a major issue: the loader code makes dozens of calls to standard C library routines—such as `memcpy`, `memset`, `putchar`, and `fclose`. During the static first pass of `main`, before the dynamic linker has run and before `libc` is loaded, calling any of these loader routines would instantly crash:
| Problem | Root Cause | Implemented Solution | |:--- |:--- |:--- | | **Premature libc calls crash** | In the initial static bootstrap pass, no `libc` is loaded in memory. Any standard C library function address is completely unresolved. | Kelley authored a custom `libc.h` header declaring every required standard function (`memcpy`, `memset`, `fclose`, etc.) with `__attribute__((weak))`. Unresolved symbols default safely to `NULL`. | | **Vulkan driver symbols hidden** | Upstream dynamic loading often uses `RTLD_LOCAL`, restricting symbol visibility to the loaded library itself. | Kelley patched the loader's calls from `RTLD_LOCAL` to `RTLD_GLOBAL`, ensuring that when the X11 and Vulkan libraries are loaded via `dlopen`, their symbols are exported globally for the underlying graphics drivers. |
As Kelley puts it regarding the missing `libc` functions on the initial pass: “If we tried to call inside here if we tried to call any of the Vulkan loader stuff it would crash because every single libc function like `memcpy`, `memset`, um you know `putchar`, `fclose`, all these, they would all just segmentation fault because they're all zero, they're all null, because they're weak.” By combining the weak-symbol header override with patched `RTLD_GLOBAL` dynamic loading flags, the executable defers all loader activity until `main2`, where the correct native `libc` and driver dependencies have finally been loaded into the global symbol space.
[25:13](https://www.youtube.com/watch?v=pq1XqP4-qOo&t=1513s) Patching the Vulkan Loader and the Static Linking Debate
Kelley explains that the overall goal was to rely on the dynamic loader's caching mechanism so that when the application requests a library, it simply reuses the exact same one the Vulkan driver already loaded. To make that guarantee hold up, Kelley had to patch the Vulkan loader directly, forcing it to perform a global dynamic load (`RTLD_GLOBAL`) rather than a local load (`RTLD_LOCAL`). By making the load global, the loaded symbols become visible across the entire process instead of remaining confined behind isolated namespaces.
```mermaid flowchart TD accTitle: Vulkan Loader Patching Flow accDescr: Diagram illustrating how patching the Vulkan loader from RTLD_LOCAL to RTLD_GLOBAL allows the application to share driver dependencies.
subgraph StandardBehavior["Unpatched Vulkan Loader"] V1["Vulkan Loader"] -->|"dlopen(..., RTLD_LOCAL)"| D1["Driver & Shared Objects"] A1["Application dlopen()"] -.->|"Cannot see symbols"| D1 end
subgraph PatchedBehavior["Patched Vulkan Loader"] V2["Vulkan Loader"] -->|"dlopen(..., RTLD_GLOBAL)"| D2["Driver & Shared Objects"] A2["Application dlopen()"] -->|"Finds cached symbols"| D2 end ```
He then touches on the politics of the library, adding a "caveat emptor" from an encounter on Discord or the Khronos group where maintainers expressed strong disapproval of static linking:
> “I was talking to one of the maintainers of this library and they were saying that they don't like it when people static link Vulkan loader because, I don't remember, there's some reason that they don't want you to static link it; but it does make the game work so, I don't know, maybe they should consider this a use case.”
Kelley argues that Linux distribution maintainers and Vulkan loader developers ought to collaborate to support static linking officially, because “this is what players want: they want to run the game.” Relying on an out-of-tree patch highlights why this demo is strictly a proof of concept. The upstream Vulkan loader repository moves fast, and maintaining a personal fork with vendor copy-pasted code introduces an ongoing maintenance burden to pull down and reconcile upstream commits.
---
[27:07](https://www.youtube.com/watch?v=pq1XqP4-qOo&t=1627s) Cross-Compiling X11 Headers and the Basic Vulkan Triangle
Once dynamic symbol caching is resolved, what remains inside the application code is invoking the X11 APIs to initialize a window and establish a Vulkan context. Kelley points out that while X11 calls ultimately just send network messages over a socket to the X server, using the exact same dynamic C runtime shared objects that the graphics driver links against remains the most dependable approach to ensure consistency across the entire address space.
Cross-compiling introduced its own friction. Because the Vulkan loader depends on X11 C header files, Kelley could not cleanly pull headers from his host operating system. He had to dig into X11's complex build system to determine how those header files were generated, extract them, and manually adjust preprocessor definitions (`#define`s) to make them cross-compilation safe. Because the underlying X11 protocol is an established network specification with a strictly frozen ABI, those constants will not break or change over time. From there, the rest of the application is minimal: Kelley notes he simply followed the basic triangle demo from `vulkan-tutorial.com`.
---
[29:12](https://www.youtube.com/watch?v=pq1XqP4-qOo&t=1752s) The Core Lesson: Raising Standards for Linux Gaming
Addressing game developers in the audience who might be eager to implement this setup in C or C++, Kelley gives a candid assessment: as things stand, it is not realistic for production use due to the sheer volume of obstacles. However, the experiment proves that bypassing the typical Linux shared library fragility is technically achievable.
> “A lot of these hoops are arbitrary and unnecessary, and I'm demonstrating that it's possible to get around them; but really I think that what we just need is an acknowledgement that this is the user experience that users, that players deserve, and we just need more people to demand this as how it works on Linux.”
Kelley wants the demo to inspire the community to raise the bar for platform standards. He mentions that the repository is public but unpolished, and in fact had recently regressed and stopped building. That failure occurred because the Zig compiler had introduced genuine improvements to how it links code and passes arguments to LLVM’s LLD linker.
```mermaid flowchart LR accTitle: Linker Assumption Conflict accDescr: Chart showing how standard linker reasoning conflicts with hybrid static and dynamic game binaries.
subgraph StandardLinker["Default Linker Logic"] L1["Inspect Dependencies"] -->|"No dynamic dependencies"| L2["Omit dynamic section entirely"] end
subgraph HybridBinary["Hybrid Portable Game"] H1["Static Game Engine"] -->|"Needs dlopen runtime calls"| H2["Requires dynamic section & loader"] end
L2 -.->|"Breaks execution"| H2 ```
In standard linker implementations, if an executable has no direct dynamic dependencies, the linker logically strips out the dynamic section entirely. For a hybrid binary—one that statically embeds its runtime yet expects to dynamic-load host driver libraries—that omission completely breaks the executable. Kelley emphasizes that tooling must explicitly recognize and formalize this hybrid approach, or else minor tooling improvements will inevitably trample the delicate list of hacks required to keep it functioning.
---
[32:16](https://www.youtube.com/watch?v=pq1XqP4-qOo&t=1936s) Q&A: Community Questions and Native X11 Protocols
Loris Cro opens the question-and-answer session, monitoring questions across Twitch, Discord, and the `#zig` IRC channel on Freenode. He reminds viewers to keep questions relevant to the talk, saving broader roadmap inquiries for later. Loris admits that despite disliking Apple, developer experience and ecosystem friction are precisely why his daily driver remains a Mac rather than a desktop Linux machine.
The first technical question comes from chat participant `gw1`, who asks whether implementing the X11 protocol purely in Zig (referencing project `juicebox`) could eliminate the need to dynamically depend on XCB or other X11 shared libraries. Kelley breaks down the runtime reality via explicit premises: 1. The game must communicate with graphics drivers like Vulkan or OpenGL. 2. The graphics drivers themselves carry hard dynamic dependencies on the X11 shared objects. 3. Therefore, those shared libraries will be loaded into the process memory space regardless of whether the main binary implements the network protocol directly. 4. Since the process already pays that memory and loading cost, the application might as well use those loaded shared objects directly rather than reinventing the client protocol stack.
---
[35:19](https://www.youtube.com/watch?v=pq1XqP4-qOo&t=2119s) Q&A: Pitching Valve and Debugging `dlopen` Scope
Viewer `zakus` jokingly asks when Valve will sponsor Zig. Kelley embraces the banter:
> “Hey Valve, you want to sponsor Zig, you should definitely do it: everyone's going to be writing games in Zig in five years.”
Loris jokes back that he will call Gabe Newell directly, remarking that he already calls him up to complain every time he misses his shots with a Tec-9 in *Counter-Strike: Global Offensive*.
A participant then asks for clarification regarding why weak symbols and `dlopen` failed before the loader patch, wondering why standard C symbols did not resolve if Vulkan was called only after loading libc. Kelley begins stepping through the exact debugging sequence on his screen (after a brief technical hiccup where Loris checks screen sharing). He clarifies that when he first attempted to `dlopen` the shared object already brought into virtual memory by the driver, the dynamic linker refused to find it. Because the unpatched Vulkan loader had originally opened the library with `RTLD_LOCAL`, its symbols remained restricted to its private loader scope, hiding them from the application until Kelley forced the loader to open them globally via `RTLD_GLOBAL`.
[36:55](https://www.youtube.com/watch?v=pq1XqP4-qOo&t=2215s) RTLD_LOCAL vs. RTLD_GLOBAL and Wayland Compatibility
Wrapping up a previous technical question from chat participant Murilo, Andrew Kelley explains the behavior of dynamic loading flags. When the Vulkan loader originally loaded its dependency using `RTLD_LOCAL`, subsequent attempts to reference or acquire that same handle failed or returned an invalid object because symbols were confined to their own local scope. Once switched to `RTLD_GLOBAL`, the dynamic linker makes those symbols globally available, ensuring that the call consistently returns a valid, shared object handle across the binary.
The host then fields an audience question asking what modifications would be required to make this dynamic linking and driver initialization setup work under Wayland. Andrew delivers a deadpan quip first: “Nothing, because Wayland, it supports the X11 protocol.” After laughing, he clarifies that while XWayland is a completely valid answer, a native implementation is also trivial to add. Explicit Wayland support would simply involve attempting to dynamically open a Wayland library object (`libwayland-client.so`) first. Because a graphics driver running in a native Wayland session depends on Wayland rather than X11, the application would check if the Wayland handle succeeds; if so, it uses it, and if not, it falls back to the X11/XCB implementation. Andrew highlights his stance on display server compatibility: “I am of the strong opinion that if Wayland is doing it right, this code should all just work fine on Wayland with almost no penalties.”
```mermaid flowchart TD accTitle: Wayland and X11 Dynamic Initialization accDescr: Application attempts to load native Wayland graphics libraries first, falling back to X11 if unavailable.
subgraph Detection ["Display Server Fallback Logic"] A["Try dlopen('libwayland-client.so')"] -->|Success| B["Use Native Wayland Pipeline"] A -->|Failure| C["Fall back to XCB / X11 Pipeline"] C --> D["Runs natively or via XWayland"] end ```
[38:33](https://www.youtube.com/watch?v=pq1XqP4-qOo&t=2313s) Absolute Paths, DT_RUNPATH, and Patching the Vulkan Loader
The Q&A turns to dynamic linking metadata, addressing an audience question about whether the ELF `DT_NEEDED` entry can simply specify the library name (e.g., `libname.so`) without an absolute path. Inspecting the standard control binary `vkcube`, Andrew demonstrates that `DT_NEEDED` already specifies library names rather than hardcoded absolute paths, listing entries like XCB, libc, and the Vulkan driver. The real friction arises from `DT_RUNPATH`:
``` Dynamic section at offset 0x... contains entries: NEEDED Shared library: [libxcb.so.0] NEEDED Shared library: [libc.so.6] RUNPATH Library runpath: [/usr/lib/x86_64-linux-gnu:...] ```
Andrew points out that hardcoding search directories inside the binary breaks portability: “And the problem is that you don't even—even with this way you just don't know what the, you know, what the run paths are until runtime, that's the problem. Like this run path would be wrong for other distros.”
Following this, viewer Johnny Martin asks whether patching Vulkan to load drivers globally would solve the duplicate shared object issue. Andrew clarifies the precise layer that would need modification. The initialization flow calls functions inside `vulkan-loader`, which in turn executes `dlopen` on the actual graphics driver. The goal is simply to have the application and loader reference that exact same loaded driver instance. Rather than altering graphics drivers directly, sending an upstream patch to `vulkan-loader` to adopt `RTLD_GLOBAL` would make this portable paradigm clean: - The driver initialization logic inside `vulkan-loader` performs the underlying `dlopen` call on the vendor driver. - Modifying `vulkan-loader` upstream avoids any requirement for GPU vendors (Nvidia, AMD, Intel) to patch their individual proprietary or open-source drivers. - While upstream maintainers might raise theoretical arguments against polluting global namespace symbols, Andrew maintains that “in this case it's the right call.”
[41:11](https://www.youtube.com/watch?v=pq1XqP4-qOo&t=2471s) The Linux Desktop Vision and Valve's Role
In closing out the session, Andrew steps back from low-level linker mechanics to assess the broader trajectory of Linux gaming. He commends Valve for the massive progress already made with Steam and Proton, observing that several modern games already perform better under Linux than they do on native Windows. He views distribution packaging conventions and rigid runtime library expectations as the final hurdles preventing seamless cross-distribution distribution for indie and commercial game developers alike.
Andrew expresses optimism that the industry can standardize around this flexible dynamic loading model: “I sincerely hope that people at Valve watch this short talk and get inspired, and use their influence and power to help the Linux distribution ecosystem support this use case better. I think that the users want it, the players want it, the game developers want it, and ultimately the Linux maintainers, the package maintainers are going to do what people want, so we just have to make people want this more.” The host agrees with this call to action, thanking Andrew for the presentation and announcing a short intermission before returning for the second half of the stream covering the upcoming Zig language roadmap.
## Notable quotes > [35:19](https://www.youtube.com/watch?v=pq1XqP4-qOo&t=2119s) Hey Valve, you want to sponsor Zig, you should definitely do it: everyone's going to be writing games in Zig in five years. — Andrew Kelley
> [25:13](https://www.youtube.com/watch?v=pq1XqP4-qOo&t=1513s) this is what players want: they want to run the game. — Andrew Kelley
## Mentioned Andrew Kelley, Loris Cro, Gabe Newell, Jonathan Marler, Zig, Linux, Windows, macOS, Ubuntu, NixOS, Void Linux, Clear Linux, Arch Linux, Debian Sid, Alpine Linux, Valve, Steam, Proton, Vulkan, Khronos Group, Vulkan-Loader, X11, XWayland, Wayland, XCB, glibc, musl, LLVM, LLD, patchelf, Half-Life: Alyx Episode 2, Counter-Strike: Global Offensive
## Video description From Zig SHOWTIME #21 Subscribe to the Zig SHOWTIME Newsletter https://zig.show
0:00 Title 1:35 Talk 32:15 Interview
[Source](https://www.youtube.com/watch?v=pq1XqP4-qOo)