Tutorial 135: A Native X11 Build

CNA Tutorials  ·  CNA snapshot 009d40f5

ℹ

What you’ll learn: how to configure CNA with CNA_PLATFORM=X11 and CNA_ENABLE_SDL=OFF, build the demos, prove the binary contains no SDL, run them under a real or virtual X server, and point your own game at the same configuration.

ℹ

Before you start — Tutorial 02: Setting Up Your Dev Environment for the workspace layout (CNA and sharp-runtime on their next branches, plus easy-gl and meta-gl) and Tutorial 03: Your First CNA Window for the small game project used in step 7. You need a Linux machine with an X server: Xorg, or Xwayland on a Wayland desktop. Without a display you can still follow along with xvfb-run.

Every CNA tutorial so far used the default platform, SDL3. In this snapshot CNA also has a native X11 backend written directly against Xlib, and a configuration switch that removes SDL from the build entirely. This tutorial builds CNA's own 2D and 3D demos that way, proves the result contains no SDL, runs them, and then points your own game at the same configuration. The commands mirror the ones CNA's continuous integration is configured to run (workflow job x11-sdl-free-gpu), so each flag has a reason.

What you are building

AxisValueWhy
PlatformCNA_PLATFORM=X11Windows, events, input and clipboard come from Xlib, XKB and XInput2 instead of SDL3.
SDL switchCNA_ENABLE_SDL=OFFSDL is not fetched, built, found or linked. Anything that needs SDL is refused at configure time.
AudioCNA_AUDIO_PLATFORM=NULLSilent. The default audio (SDL3) needs SDL, so you must pick another value; Tutorial 137 swaps in real ALSA sound.
RendererCNA_GRAPHICS_RENDERER=OPENGL33, plus VULKAN;SOFTWARE;HEADLESS selectable at run timeOpenGL 3.3 through GLX. It must be a renderer that does not link SDL (not SDL_RENDERER, SDL_GPU, FNA3D or FREEDIRECT).

1. Install the packages

On Debian or Ubuntu 24.04 (CI's image; it uses GCC 14):

sudo apt-get install -y g++-14 cmake ninja-build pkg-config git \
  libx11-dev libxext-dev libxrandr-dev libxi-dev libxcursor-dev libxfixes-dev libxss-dev \
  libgl1-mesa-dev libglx-dev libgl1-mesa-dri libvulkan-dev mesa-vulkan-drivers \
  libdbus-1-dev xvfb x11-utils openbox

export CC=gcc-14 CXX=g++-14

Only libx11-dev and libxext-dev are mandatory (plus the XKBlib.h header that ships with libX11). Every other package is optional and switches off exactly one capability when absent:

PackageWhat you lose without it
libxi-devRelative mouse, input-device enumeration, touch and pen.
libxrandr-devMultiple-display enumeration; exclusive fullscreen degrades to borderless.
libxcursor-dev, libxfixes-dev, libxss-devCustom cursor images; pointer hiding; screen-saver suspension.
libgl-dev / libglx-devThe GLX context, so no OpenGL renderer can open a window.
libvulkan-devThe Vulkan surface, so no VULKAN renderer.
libdbus-devFile dialogs and OpenUrl through the desktop portal.
xvfb, x11-utils, openbox, Mesa driversOnly needed to run without a real X session (step 6).

No SDL packages are needed — and the SDL submodules are never used in this configuration.

2. Clone the workspace

mkdir cna-workspace && cd cna-workspace
git clone -b next https://github.com/libcna/cna.git
git clone -b next https://github.com/libcna/sharp-runtime.git
git clone https://github.com/libcna/easy-gl.git      # OPENGL33 is an EasyGL profile
git clone https://github.com/libcna/meta-gl.git
cd cna
git submodule update --init

Both cna and sharp-runtime must be their next branch (the default branches are alpha.1-era). easy-gl and meta-gl are needed because OPENGL33 is one of the five EasyGL renderer identities. The non-recursive submodule update fetches five submodules; with SDL off, only third_party/draco matters (and vendor/googletest if you turn tests on). If you would rather skip Draco, add -DCNA_ENABLE_DRACO=OFF below.

3. Configure

cmake -S . -B build -G Ninja \
  -DCMAKE_BUILD_TYPE=Release \
  -DCNA_ENABLE_SDL=OFF \
  -DCNA_PLATFORM=X11 \
  -DCNA_GRAPHICS_RENDERER=OPENGL33 \
  -DCNA_GRAPHICS_RENDERERS="OPENGL33;VULKAN;SOFTWARE;HEADLESS" \
  -DCNA_AUDIO_PLATFORM=NULL \
  -DCNA_BUILD_TESTS=OFF \
  -DCNA_ENABLE_NET=OFF

These are CI's flags with tests switched off to keep the build small (CI builds with tests on, and uses ALSA audio; networking is off there too and is not needed for this tutorial). Because the plural option CNA_GRAPHICS_RENDERERS is set, the build contains all four renderers and the singular one is the default; you can pick another at run time in step 6. The configure log should contain lines like these (the list of optional pieces depends on the packages you installed):

-- CNA: Using X11 platform implementation
-- CNA: X11 platform dependencies -- X11 + Xext; optional present: Xi, Xrandr, Xcursor, Xfixes, Xau, Xss, XShm, GLX, Vulkan headers, D-Bus headers
-- CNA: Using NULL audio platform implementation
-- CNA: SDL is NOT configured (CNA_ENABLE_SDL=OFF). No SDL source is fetched, built, found or linked by this configuration.

Read the second line: it is the honest list of what your X11 build can do. GLX must be there for the OpenGL renderer; Vulkan headers for VULKAN.

4. Build

cmake --build build --target cna_demo_2d cna_house3d_demo --parallel

cna_demo_2d is the 2D sprite demo (it works with every renderer) and cna_house3d_demo is the walkable 3D house. The first is built for any renderer; the second exists because the default renderer, OPENGL33, supports 3D. Do not build the target CNA: it is an umbrella interface library, not a buildable target.

5. Prove there is no SDL

ldd build/cna_demo_2d | grep -i sdl                        # nothing
readelf -d build/cna_demo_2d | grep -i 'NEEDED.*sdl'      # nothing
nm -D --undefined-only build/cna_demo_2d | grep ' SDL_'   # nothing
find build -iname '*SDL2*' -o -iname '*SDL3*'             # no SDL artifact in the build tree

CI runs the equivalent checks and additionally asserts that libasound is not a NEEDED entry (it is loaded at run time if you choose ALSA audio), so a machine without it still starts. If any command above prints something, an SDL dependency has crept into your selection — but configuration would already have refused that.

6. Run it

The demo finds its Content/ folder next to the executable, so run from the build directory.

cd build

# On your own X session (or Xwayland): a real window
./cna_demo_2d
./cna_house3d_demo          # W/S/A/D or arrows to move, Space to jump, Tab toggles fly mode, Esc quits

# Pick another renderer at run time (the build contains all four)
CNA_GRAPHICS_RENDERER=VULKAN ./cna_demo_2d

# No display? Give it a private virtual X server and stop after six frames
xvfb-run -a ./cna_demo_2d --smoke 6
CNA_GRAPHICS_RENDERER=SOFTWARE xvfb-run -a ./cna_demo_2d --smoke 6

The CNA_GRAPHICS_RENDERER environment variable is read once, before the first device is created; it must name a renderer compiled into the binary or the game fails at start-up with a message saying so. The 2D demo also stops when you press Esc.

⚠

No window? Check the renderer. SOFTWARE, HEADLESS and STUB (and PORTABLEGL) are CPU or no-output renderers: on X11, Wayland, Win32 and SDL3 they render off-screen and open no window at all. The SOFTWARE command above runs and exits cleanly precisely because it draws nothing on screen. Use OPENGL33 or VULKAN to see a window.

If your desktop is Wayland, the window appears through Xwayland: the compositor owns the screen, so the global pointer is exact only over your own windows, minimising and focus follow the compositor's rules, and scaling is done by the compositor (X11 reports a display scale of 1 by design). For a native Wayland client, see Tutorial 136.

7. Build your own game the same way

The platform is a build-time choice; your game code does not change. Take the project from Tutorial 03 and configure it with the same selections (the flags on the command line win over the defaults inside its CMakeLists.txt):

cd my-first-game
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release \
  -DCNA_ENABLE_SDL=OFF -DCNA_PLATFORM=X11 -DCNA_AUDIO_PLATFORM=NULL \
  -DCNA_GRAPHICS_RENDERER=OPENGL33
cmake --build build
./build/MyFirstGame

Use the same source files; this is the same configuration CNA's own demos build with, but note that we validated the demos, not a separate consumer project. To confirm at run time which platform your binary was built for, print the default platform name before Run():

#include <iostream>

#include "CNA/Platform/PlatformFactory.hpp"

int main() {
    std::cout << "platform: " << CNA::Platform::PlatformFactory::GetDefaultName() << '\n';
    // ... MyGame game; game.Run();
    return 0;
}

It prints X11 for this build (SDL3 for the default one). There is no CNA_PLATFORM environment variable: the platform is fixed at configure time.

8. Ask the platform what it can do

Every platform reports 32 capability flags, and a false one means the call refuses with PlatformNotSupportedException. This snippet lists the implementations compiled into your binary and a few flags of the active one (it compiles against this snapshot's headers):

#include <iostream>

#include "CNA/Platform/IPlatform.hpp"
#include "CNA/Platform/PlatformCapabilities.hpp"
#include "CNA/Platform/PlatformFactory.hpp"

int main() {
    using CNA::Platform::PlatformFactory;

    // Every platform implementation compiled into this binary, and the default one.
    for (const std::string& name : PlatformFactory::GetAvailable()) {
        std::cout << "available: " << name << '\n';
    }
    std::cout << "default:   " << PlatformFactory::GetDefaultName() << '\n';

    auto platform = PlatformFactory::Create();
    const auto caps = platform->GetCapabilities();
    std::cout << std::boolalpha
              << "openGlContext=" << caps.openGlContext
              << " vulkanSurface=" << caps.vulkanSurface
              << " highDpi=" << caps.highDpi
              << " gamepad=" << caps.gamepad
              << " ime=" << caps.ime
              << " globalPointer=" << caps.globalPointer << '\n';
    return 0;
}

On X11 the list is X11, Headless and Terminal (the last two are always compiled on POSIX); highDpi is false by design; openGlContext is true only if GLX was available at build and the server provides GLX 1.3; gamepad comes from the kernel's evdev nodes and does not need a display. With no reachable X server, the platform still constructs, every display capability reads false, and starting a window reports the original connection error. The full matrix is on Platform Support.

Troubleshooting

SymptomCause and fix
Configure fails: “CNA_PLATFORM=X11 was requested but this machine cannot build it”The message names the missing package (libx11-dev, libxext-dev, or XKBlib.h). CNA never falls back to SDL3 for X11.
Configure fails: “CNA_ENABLE_SDL=OFF, but this configuration genuinely requires SDL”Something in your selection needs SDL. The default platform and audio are both SDL3, so pass CNA_PLATFORM and CNA_AUDIO_PLATFORM explicitly, and avoid SDL_RENDERER, SDL_GPU, FNA3D, FREEDIRECT.
Configure fails: missing sibling easy-gl or meta-glClone both next to cna, or choose VULKAN (needs only the Vulkan headers/loader) or OPENGL4 for the renderer.
Configure fails: pinned Draco source not foundRun git submodule update --init third_party/draco, or add -DCNA_ENABLE_DRACO=OFF.
The program cannot connect to the X serverDISPLAY is not set or points nowhere. Run under a session, or use xvfb-run -a.
OpenGL context creation failsNo GLX at build time (the configure summary lacks GLX) or no GL driver at run time. Install libgl-dev/libglx-dev and Mesa (libgl1-mesa-dri), or try LIBGL_ALWAYS_SOFTWARE=1.
No window appears and no errorYou selected a CPU renderer; see the warning in step 6.
Wayland desktop: odd focus or pointer behaviourYou are running through Xwayland. Use SDL3, or try the native Wayland backend in Tutorial 136.

What CI covers — and what it does not

The CI job x11-sdl-free-gpu is configured to build exactly this selection (with ALSA audio and tests on), build both demos, checks that no SDL or libasound is linked, runs the 3D demo's smoke and exclusive-fullscreen tests under every compiled-in renderer, and runs cna_demo_2d --smoke 6 once per renderer. All of it happens on a virtual X server (Xvfb) with Mesa's software GL and Vulkan drivers. It does not prove behaviour on a real GPU, a real compositor, multiple monitors or hot-plug, or real input devices. CNA's own notes describe manual validation on real desktops; that is not a gate. See Native Platforms — CI evidence for the exact job list.

Where to go next