Native Platforms (X11, Wayland, Win32)
What is new in this snapshot. v0.1.0-alpha.1 had one real windowing platform, SDL3 (plus a narrow SDL2 edge, a headless platform and a POSIX terminal). The next branch adds three native platform backends written directly against the host window system — X11 (Xlib), WAYLAND (the Wayland protocols) and WIN32 (user32/gdi32) — and a CNA_ENABLE_SDL switch that lets a build contain no SDL at all. SDL3 is still the default, and the only platform CI configures, on macOS, iOS, Android and the web; nothing here removes or degrades it. The other axes (target OS, renderer, audio) stay independent.
What “native” means
A platform implementation owns windows, events, input, timing and host services (clipboard, dialogs, displays, controllers). CNA selects exactly one at configure time with CNA_PLATFORM; all of them implement the same CNA::Platform::IPlatform contract and report what they can do through a set of 32 capability flags. A native backend is one that talks to the operating system's own window-system API and contains no SDL code: source scans and link checks in CNA's test suite keep it that way. Three exist, and each is only offered where it can actually be built.
| Backend | Runs on | Talks to | Offered by CMake when | Automatic CI workflow (as configured) |
|---|---|---|---|---|
X11 | Linux and other Unix-like hosts with an X server (native Xorg, or Xwayland on a Wayland desktop) | Xlib, XKB, XInput2, XRandR, Xcursor, MIT-SHM, GLX; the freedesktop D-Bus portal; kernel evdev for controllers | libX11, libXext and X11/XKBlib.h are found; the target is not Windows, Emscripten, Android or iOS | Yes two jobs in platform-ci.yml, on a virtual X server |
WAYLAND | Linux (a Wayland session, natively — no Xwayland) | libwayland-client, xdg-shell, xkbcommon, wl_shm, EGL and Vulkan surfaces, text-input-v3, fractional scale and about a dozen more protocols; portal; evdev | wayland-client ≥ 1.18, xkbcommon ≥ 0.5, wayland-scanner and wayland-protocols with stable/xdg-shell are found; not Windows, Emscripten, Android or Apple | No no workflow selects it; suites run locally |
WIN32 | Windows, including a mingw-w64 cross-build from Linux | user32, gdi32, opengl32 (WGL), ole32, shell32, uuid; Vulkan loaded at run time | the target system is Windows | Partly the platform module only: mingw cross-build under Wine on every push; native MSVC is manual |
Each backend is a source directory of its own under modules/platform/src/: X11 has 43 source files, Wayland 34 and Win32 31, against 28 for the SDL3 backend. X11 and Wayland share a few helpers that are not specific to either — XKB key tables, the freedesktop portal client (libdbus, loaded at run time), POSIX timing and Linux evdev controller support (kernel <linux/input.h>, no libudev).
Requested means required. If you set CNA_PLATFORM=X11 or WAYLAND on a machine that cannot build it, configuration fails with the missing package named; it never falls back to SDL3 (or to X11 through Xwayland). Likewise WIN32 off Windows and TERMINAL on Windows are refused as “reserved identifier that is NOT implemented”, and SDL12 and EMSCRIPTEN are reserved everywhere. Values are case-sensitive.
Choosing between SDL3 and a native backend
| If you want… | Use | Why |
|---|---|---|
| The most complete backend, one code path for Linux, Windows, macOS, iOS, Android and the web | SDL3 (default) | It is the default (and the only one CI configures) on macOS, iOS, Android and Emscripten, and has the full capability set (gamepads and sensors everywhere, IME, drag-and-drop, tray, camera). |
| A Linux binary with no SDL in it, on X11 or Xwayland | X11 with CNA_ENABLE_SDL=OFF | A CI workflow is configured to build, link and run two real games this way (see What CI is configured to check). |
| A Linux binary that talks to a Wayland compositor directly | WAYLAND | Native Wayland client with fractional scaling and text-input-v3. Treat it as implemented and locally tested, not CI-verified. |
| A Windows binary that calls only OS libraries | WIN32 (with NULL audio for a fully SDL-free build) | Links only user32, gdi32, opengl32, ole32, shell32, uuid. It has no gamepads, no IME and no drag-and-drop yet. |
| Gamepads on Windows | SDL3 | The Win32 backend does not wire up XInput. |
| No window at all (servers, tests, batch rendering) | HEADLESS or TERMINAL | See Platform Support. |
Native X11
CNA_PLATFORM=X11 is written directly against Xlib and answers the question “can CNA run on a Linux desktop without SDL?” with a build configuration that a CI job builds and runs on every push. Only libX11, libXext and the XKBlib.h header are mandatory; every other extension is optional and switches off exactly one capability when absent, so the backend stays available on stripped-down systems.
Dependencies and what each one gates
| Piece | Required? | Without it |
|---|---|---|
libX11, libXext, X11/XKBlib.h | Mandatory | The backend is not offered; configure names the package (libx11-dev, libxext-dev). |
| libXi (XInput2) | Optional | No relativeMouse, no input-device enumeration; touch and pen contacts need a server that speaks XInput 2.2. |
| libXrandr (≥ 1.2) | Optional | No multipleDisplays; exclusive fullscreen has no mode to switch to and becomes borderless. |
| libXcursor | Optional | No custom ARGB cursors; the standard shapes still work. |
| libXfixes, libXau, libXss | Optional | Pointer hiding without an owned pixmap, the exclusive-fullscreen mode guardian's authorisation cookie, and a screen saver suspended for this client alone. |
MIT-SHM (XShm.h) | Optional | The software presenter falls back to plain XPutImage. |
| GLX headers (libglvnd or Mesa) | Optional; the entry points are resolved at run time | No openGlContext, so no GL-family renderer can run. |
| Vulkan headers | Optional; headers only, the loader is the renderer's | vulkanSurface is unaffected: CMake defines CNA_X11_HAVE_VULKAN_HEADERS, but no X11 source reads it, and the loader and vkCreateXlibSurfaceKHR are resolved at run time. A machine without a Vulkan driver still builds and runs everything else. |
D-Bus headers (libdbus-1-dev) | Optional; libdbus-1.so.3 is loaded at run time | No file dialogs, OpenUrl or desktop screen-saver inhibition through the portal. |
Kernel headers (linux/input.h) | Optional | No gamepads, joysticks, rumble, haptics or power information (evdev and sysfs). |
The configure log ends its X11 line with a summary such as “X11 + Xext; optional present: Xi, Xrandr, …; absent: …”, which is the quickest way to see which capabilities your build will have.
Behaviour worth knowing
- No HiDPI, by design. X11 has one coordinate space, so
highDpiis false and a window's display scale is 1. The session's scale is exposed per display as its content scale. - Keyboard. Layout-independent scancodes are derived from XKB key names; key releases are real events (nothing is synthesised). Text goes through XIM (
Xutf8LookupString), and IME composition is available when the application asks to draw it. - Clipboard and drag-and-drop. ICCCM selection ownership including
INCR, any MIME type, the middle-click PRIMARY selection, and XDND 5 as a drop target. - Fullscreen. Borderless fullscreen uses the EWMH hint when the window manager advertises it; exclusive fullscreen changes the display mode through XRandR and restores it on exit, and after
SIGKILLthrough a guardian process. - Message boxes are drawn with Xlib in a window of their own; file dialogs come from the desktop portal when the session bus offers one. Nothing shells out to
zenityorkdialog. - Under Xwayland (X11 on a Wayland desktop) the backend works, but the compositor owns the screen: the global pointer is exact only over your own windows, minimised windows can stay mapped, focus is the compositor's decision, and scaling happens in the compositor. CNA's notes record validation on GNOME 48 with real hardware; that is the project's own record, not a CI result.
- Controllers need no display. Gamepads, joysticks, rumble, motion sensors and force-feedback devices come from the kernel's evdev nodes (Linux only), so they work even for a process that could not reach an X server.
Native Wayland
CNA_PLATFORM=WAYLAND is a genuine Wayland client: libwayland-client, xdg-shell, xkbcommon and the compositor's own protocols, with no SDL, no Xlib and no Xwayland. Window state follows an explicit configure state machine (CreateWindow returns only after the compositor's first configure has been acknowledged), and a compositor that goes away ends the connection cleanly instead of crashing the game.
Dependencies
| Piece | Required? | Without it |
|---|---|---|
wayland-client ≥ 1.18, xkbcommon ≥ 0.5 | Mandatory, linked | The backend is not offered. |
wayland-scanner and wayland-protocols with stable/xdg-shell | Mandatory, build time | The backend is not offered. Protocol bindings are generated into the build tree; nothing generated is committed. |
| Optional protocol XML files: xdg-output, viewporter, fractional-scale-v1, relative-pointer, pointer-constraints, text-input-v3, primary-selection, xdg-decoration, xdg-activation, idle-inhibit, xdg-foreign-v2, tablet-v2, cursor-shape-v1 | Optional, build time | Exactly that protocol's capability (for example no IME without text-input-v3, no fractional scale without fractional-scale-v1 plus viewporter). |
EGL and wayland-egl headers | Optional; libEGL.so.1 and libwayland-egl.so.1 load at run time | No openGlContext. |
wayland-cursor headers | Optional; loaded at run time | No cursor-theme fallback (cursor-shape still works). |
Vulkan headers, D-Bus headers, linux/input.h | Optional | No vulkanSurface without the Vulkan headers (unlike X11, where the surface does not depend on them), no desktop portal without the D-Bus headers, no evdev controllers without linux/input.h. |
wayland-server | Test builds only | The in-process test compositor is unavailable; it is never linked into the platform library. |
Behaviour worth knowing
- Scaling. Fractional scale through
wp_fractional_scale_v1andwp_viewporterwhere offered, integer scale otherwise, sohighDpiis true (unlike X11). - No global pointer, by design. Wayland gives a client neither the pointer position outside its surfaces nor the power to move it, so
globalPointeris false and pointer capture, global position and warping refuse withPlatformNotSupportedException. - Window position belongs to the compositor.
WindowDescriptionx/y/centre are ignored and no “moved” event exists. Exclusive fullscreen is requested but reported as borderless fullscreen: an ordinary Wayland client cannot own a display mode. - No message box and no tray. Wayland has no dialog protocol, and CNA declined both to embed a font and to launch
zenity; draw the dialog with your ownSpriteFont. File dialogs are unaffected (the desktop portal draws them). - Only unprivileged protocols. Nothing from
wlr-*, no layer shell, no virtual input, no screen capture — a client that needed those would be a desktop component, not a game. - The capability set is fixed at start-up. If a compositor later withdraws a global, the corresponding call throws
PlatformNotSupportedExceptionnaming it rather than silently pretending. - Software presentation. The
wl_shmpresenter service exists, but a CPU renderer (SOFTWARE) opens no window on any windowing platform; see Renderers on native platforms.
No CI for Wayland. No GitHub workflow selects CNA_PLATFORM=WAYLAND. The backend has its own registered suites (an in-process test compositor, headless Weston, GNOME's mutter with a real input method), but they are launched locally through tools/platform/wayland_test_server.sh; the claim that they pass on Weston and mutter comes from CNA's own documentation, and we did not run them. Treat native Wayland as implemented and locally tested, not continuously verified.
Native Win32
CNA_PLATFORM=WIN32 implements the platform contract directly on the Windows API: one HWND per window, WGL for OpenGL, StretchDIBits for CPU frames, Raw Input for relative mouse, IFileOpenDialog/IFileSaveDialog for file dialogs, EnumDisplayMonitors for displays. It links only operating-system import libraries — user32, gdi32, opengl32, ole32, shell32, uuid — and resolves vulkan-1.dll at run time, so a machine without a Vulkan driver simply reports vulkanSurface as false. It declares Windows 8.1 (0x0603) as the level its headers target; a source comment says every entry point above a Windows 7 floor is resolved at run time, so the binary itself is not meant to require more (read from source, not executed on Windows 7). It builds with MSVC and with mingw-w64.
What it does not have yet
| Missing capability | Why / what is used instead |
|---|---|
gamepad, joystick, rumble, sensors, haptics | XInput is not wired up. Use the SDL3 platform where Windows gamepads matter. |
ime | Composition and candidate lists are not delivered. Delivering composition without candidates would be a half-working IME a game cannot detect, so the flag stays false. |
dragAndDrop, clipboardData, primarySelection | No WM_DROPFILES/OLE drop target, no registered clipboard formats (the clipboard is text only), and Windows has no primary selection. |
tray, camera, sensors, managedEntrypoint | Not wired up or out of scope. |
Each missing capability is reported false, its service accessor returns nullptr, and a gated call raises PlatformNotSupportedException naming the capability — none is a stub that silently succeeds.
Host-process etiquette
- The backend never sets DPI awareness, never raises the timer resolution, never changes the current directory and never takes over the thread's COM apartment. Declare DPI awareness in your manifest; the backend reports values that are coherent with whatever you chose.
- Closing a window is a request.
WM_CLOSEbecomesCloseRequestedand does not destroy the window; closing one of several windows does not quit the process at the platform level. AGame, however, callsExit()on anyCloseRequestedwithout checking which window sent it. - Window sizes are client sizes (converted through
AdjustWindowRectExForDpi). - If you include
<windows.h>together with CNA platform headers, four macros collide with CNA identifiers (CreateWindow,CreateDirectory,MessageBox,GetClassName):#undefthem or include the CNA headers first.
Audio on an SDL-free Windows build
Audio is a separate axis, and Windows has no SDL-free audio implementation: ALSA is Linux-only and WASAPI is reserved and refused. A fully SDL-free Win32 build therefore selects CNA_AUDIO_PLATFORM=NULL (silent). If you need sound on Windows, keep SDL3 audio (which needs SDL, so CNA_ENABLE_SDL cannot be OFF). See Audio System.
Capability differences
The platform contract exposes 32 boolean capabilities. Each backend reports what it can really do, and a false capability means the call refuses deterministically with PlatformNotSupportedException. The full seven-platform matrix is on Platform Support; here are the columns that matter for choosing a native backend, with SDL3 for comparison. “If…” means the answer depends on the running system and is decided once, when the platform object is created.
| Capability flag | SDL3 | X11 | Wayland | Win32 |
|---|---|---|---|---|
multipleWindows | Yes | Yes | Yes | Yes |
highDpi | Yes | No (by design) | Yes (fractional or integer) | Yes |
multipleDisplays | Yes | If RandR | Yes, whenever the compositor connection succeeded (outputs are enumerated from wl_output) | Yes |
borderlessFullscreen | Yes | If the window manager advertises EWMH fullscreen | Yes | Yes |
nativeWindowHandle | Yes | Yes (Display* + XID) | Yes (wl_display* + wl_surface*) | Yes (HWND) |
surfacePresentation | Yes | Yes (XPutImage, MIT-SHM optional) | If wl_shm offers XRGB8888 | Yes |
openGlContext | Yes | If GLX 1.3 (loaded at run time) | If EGL loads and initialises | Yes (WGL) |
vulkanSurface | If Vulkan is available | Yes, with an X connection (the loader and the xlib-surface entry point are resolved at run time, not at build time) | If Vulkan headers were present at build | If vulkan-1.dll loads |
clipboard | Yes | Yes (ICCCM, INCR) | If a seat and data device exist | Yes (text) |
clipboardData | Yes | Yes (any MIME type) | If a data device exists | No |
dragAndDrop | Yes | Yes (XDND 5, target side) | If a data device exists | No |
primarySelection | Yes (Unix desktops) | Yes | If the primary-selection protocol exists | No |
textInput | Yes | Yes | Yes | Yes |
ime | Yes | If the app draws composition and the input method agrees | If text-input-v3 exists | No |
exactKeyboardState | Yes | Yes | Yes | Yes |
pixelAccurateMouse | Yes | Yes | Yes | Yes |
relativeMouse | Yes | If XInput2 raw motion | If relative-pointer and pointer-constraints | Yes |
cursorShapes | Yes | Yes | If cursor-shape or a cursor theme / wl_shm | Yes |
globalPointer | Yes | Yes | No (by design) | Yes |
inputDeviceEnumeration | Yes | If XInput2 (and evdev controllers) | Yes | Yes |
gamepad | Yes | Yes on Linux (kernel evdev) | Yes on Linux (kernel evdev) | No (no XInput) |
joystick | Yes | Yes on Linux (kernel evdev) | Yes on Linux (kernel evdev) | No |
gamepadRumble | Yes | Yes on Linux (evdev) | Yes on Linux (evdev) | No |
gamepadSensors | Yes | Yes on Linux (evdev) | Yes on Linux (evdev) | No |
haptics | Yes | Yes on Linux (force feedback) | Yes on Linux (force feedback) | No |
sensors | Yes | No | No | No |
powerInfo | Yes | Yes on Linux (sysfs) | Yes on Linux (sysfs) | Yes |
messageBox | Yes | Yes (drawn with Xlib) | No (by design) | Yes |
nativeFileDialog | Yes | If the desktop portal is on the session bus | If the desktop portal is on the session bus | Yes |
tray | If supported | If a system tray is running | No | No |
camera | If supported | No | No | No |
managedEntrypoint | Yes (Android and iOS main) | No | No | No |
Building without SDL
CNA_ENABLE_SDL decides whether the vendored SDL3 sub-build is configured at all.
| Value | Effect |
|---|---|
AUTO (default) | SDL3 is configured exactly as before — identical to a build that never heard of the option. |
ON | The same, stated explicitly. |
OFF | No SDL source is fetched, built, found or linked (find_package for SDL and its satellites is blocked for every project in the tree). Any selection that genuinely requires SDL is refused at configure time, naming the reason; nothing is substituted. |
With OFF, these selections are refused with a message that begins “CNA_ENABLE_SDL=OFF, but this configuration genuinely requires SDL”: platform SDL3 or SDL2; audio SDL3 or SDL2; and the renderers SDL_RENDERER, SDL_GPU, FNA3D and FREEDIRECT (also when they appear only in CNA_GRAPHICS_RENDERERS). Note that the default audio is SDL3, so an SDL-free configure must always choose NULL or ALSA explicitly.
Recipes
The first is the CI-proven, sound-free X11 cell; the second adds real renderers and ALSA audio. Both were run by CI at this snapshot; the exact flags are copied from the workflow.
# SDL-free X11, headless renderer, silent audio (workflow job "x11-sdl-free")
cmake -S . -B build -G Ninja \
-DCMAKE_BUILD_TYPE=Debug \
-DCNA_ENABLE_SDL=OFF \
-DCNA_PLATFORM=X11 \
-DCNA_GRAPHICS_RENDERER=HEADLESS \
-DCNA_AUDIO_PLATFORM=NULL \
-DCNA_ENABLE_NET=OFF
# SDL-free X11 with OpenGL 3.3, Vulkan, software and headless renderers
# selectable at run time, and ALSA audio (job "x11-sdl-free-gpu")
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=ALSA \
-DCNA_ENABLE_NET=OFF
A native-Wayland recipe and a mingw-w64 Windows recipe are in Build recipes. Step-by-step versions with run commands are tutorials 135, 136 and 138.
Proving that no SDL is in the binary
ldd build/cna_demo_2d | grep -i sdl # prints nothing
readelf -d build/cna_demo_2d | grep -i 'NEEDED.*sdl' # prints nothing
nm -D --undefined-only build/cna_demo_2d | grep ' SDL_' # prints nothing
find build -iname '*SDL2*' -o -iname '*SDL3*' # no SDL artifact in the build tree
CI runs equivalent checks, plus one that asserts libasound is not a NEEDED entry: ALSA is dlopened at run time so that a machine without it still starts.
Renderers on native platforms
The platform and renderer axes are independent, but a renderer needs services the platform must offer (a native window handle, a GL context, a Vulkan surface). What that means for the native backends:
| Renderer family | X11 | Wayland | Win32 |
|---|---|---|---|
GL family: OPENGLES2, OPENGLES3, OPENGL33 (needs ../easy-gl and ../meta-gl) | GLX context | EGL context | WGL context |
OPENGL4 (desktop GL 4.1+ core) | GLX | EGL | WGL |
VULKAN | needs Vulkan headers at build | needs Vulkan headers at build | needs vulkan-1.dll at run time |
SOFTWARE, PORTABLEGL, HEADLESS, STUB | Off-screen on every windowing platform: they create no window (readback only). Only SOFTWARE on TERMINAL presents. | ||
DIRECTX9, DIRECTX11, DIRECTX12, DIRECT2D, GDI | not buildable (Windows-only) | not buildable (Windows-only) | use the HWND |
SDL_RENDERER, SDL_GPU, FNA3D, FREEDIRECT | Link SDL3 by identity: refused when CNA_ENABLE_SDL=OFF. They can still be selected with a native platform if SDL is enabled. | ||
WEBGPU | Not exercised by any native-platform CI job; treat combinations with native platforms as unverified. | ||
METAL, WEBGL1, WEBGL2, CANVAS, HTML_DOM, SVG_DOM | Platform-gated to macOS or Emscripten; not offered on these targets. | ||
No window? Check the renderer first. If a native-platform build starts, runs and shows nothing, you probably selected SOFTWARE, PORTABLEGL, HEADLESS or STUB. Those are deliberately window-less on X11, Wayland, Win32 and SDL3. Pick OPENGL33, OPENGLES3, OPENGL4 or VULKAN for a visible window, or set CNA_GRAPHICS_RENDERER in the environment at run time when the build contains several (see Runtime Renderer Selection).
Build recipes
These assume the workspace layout from Building: cna on the next branch, sharp-runtime on its next branch, and — for the GL-family renderers — easy-gl and meta-gl next to them. Package names are Debian/Ubuntu (CI uses Ubuntu 24.04 and GCC 14).
X11
sudo apt-get install -y g++-14 cmake ninja-build pkg-config \
libx11-dev libxext-dev libxrandr-dev libxi-dev libxcursor-dev libxfixes-dev libxss-dev \
libgl-dev libglx-dev libvulkan-dev libdbus-1-dev libasound2-dev
cd cna
git submodule update --init third_party/draco # or add -DCNA_ENABLE_DRACO=OFF
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release \
-DCNA_ENABLE_SDL=OFF -DCNA_PLATFORM=X11 \
-DCNA_GRAPHICS_RENDERER=OPENGL33 -DCNA_AUDIO_PLATFORM=NULL \
-DCNA_BUILD_TESTS=OFF -DCNA_ENABLE_NET=OFF
cmake --build build --target cna_demo_2d
(cd build && ./cna_demo_2d)
Only libx11-dev and libxext-dev are mandatory. Add libasound2-dev only for CNA_AUDIO_PLATFORM=ALSA. Tutorial 135 walks through this end to end.
Wayland
sudo apt-get install -y g++-14 cmake ninja-build pkg-config \
libwayland-dev libwayland-bin wayland-protocols libxkbcommon-dev \
libegl-dev libvulkan-dev libdbus-1-dev
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release \
-DCNA_ENABLE_SDL=OFF -DCNA_PLATFORM=WAYLAND \
-DCNA_GRAPHICS_RENDERER=OPENGL33 -DCNA_AUDIO_PLATFORM=NULL \
-DCNA_BUILD_TESTS=OFF -DCNA_ENABLE_NET=OFF
cmake --build build --target cna_demo_2d
This is the documented recipe; it is not exercised by CI. Tutorial 136 covers it honestly, including what to check first.
Win32 (from Linux with mingw-w64, or natively)
# From Linux, targeting Windows
sudo apt-get install -y g++-mingw-w64-x86-64 cmake ninja-build
cmake -S . -B build-windows -G Ninja \
-DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/mingw-w64.cmake \
-DCNA_ENABLE_SDL=OFF -DCNA_PLATFORM=WIN32 \
-DCNA_AUDIO_PLATFORM=NULL -DCNA_GRAPHICS_RENDERER=DIRECTX11 \
-DCNA_BUILD_TESTS=OFF -DCNA_BUILD_EXAMPLES=OFF
# Natively on Windows (MSVC)
cmake -S . -B build -DCNA_PLATFORM=WIN32 -DCNA_GRAPHICS_RENDERER=DIRECTX11
The full CNA + DirectX build for Windows is manual CI only; the automatic Windows evidence is the platform harness described in the next section.
Validation harnesses
CNA ships its own harnesses for the native backends. They are ordinary CTest entries or standalone programs in the repository, so you can re-run them.
| Harness | Backend | What it does | Needs |
|---|---|---|---|
ctest -R CnaX11 (seven entries: mapping, input method, evdev, integration, window manager, touchscreen, exclusive fullscreen) | X11 | Key tables and event mapping, GLX contexts, clipboard interop, XDND, message boxes, portal dialogs, EWMH fullscreen, controllers through a virtual uinput device, touch and pen, exclusive fullscreen including SIGKILL recovery | Xvfb (started privately by tools/platform/x11_test_server.sh); some suites need openbox, dbus-daemon, ibus, /dev/uinput and skip otherwise |
cna_x11_desktop_validation <scenario> | X11 | Drives the backend through the public contract and checks the result over a second X connection. Scenarios include info, lifecycle, wm, displays, glx, vulkan, presenter, lifetime, stress, clipboard, keyboard, mouse, relative, soak. Prints one PASS/FAIL/SKIP line per check; reports which server it ran on (Xorg, Xwayland or Xvfb). | Built when CNA_PLATFORM=X11 and tests are on. Some scenarios inject input or overwrite the clipboard — read the table before running on your desktop. |
ctest -R CnaWayland (mapping, protocol, portal, Weston, Weston GPU, Weston scaled, mutter, mutter Czech layout, ibus, link closure) | Wayland | 78 protocol cases against an in-process compositor that raises the errors a strict compositor would; live suites on headless Weston and on GNOME's mutter with real input; a real Korean input method; a binary link-closure check (no SDL, X11, xcb or GLX) | wayland-server for tests; Weston, gnome-shell, ibus as available — missing ones skip (exit 77). Launched through tools/platform/wayland_test_server.sh |
cna_wayland_desktop_validation <scenario> | Wayland | info, lifecycle, scale, presenter, gl, vulkan, lifetime, stress, soak, clipboard, interactive. Never injects input. | A running Wayland compositor (normally your real desktop) |
tools/platform/standalone_tests (cna_platform_tests, cna_win32_directx_probe) | Win32 (and any other) | Builds the platform module and its whole suite on their own — no sharp-runtime, no SDL, no renderer — for any CNA_PLATFORM. Includes a probe that creates real Direct3D 11 and 12 devices and swap chains on a Win32 window. | mingw-w64 + Wine on Linux, or MSVC on Windows |
tools/platform/validate_win32_native.ps1 and friends | Win32 | Scripts for a real Windows machine (PowerShell, plus a shell script for the DPI matrix): interactive runs, clipboard interop, Unicode paths, soak, sanitizer builds | A Windows desktop |
A minimal way to see which platforms your own binary contains and what the active one can do (this compiles against the 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;
}
Exactly one implementation is compiled as the default (CNA_PLATFORM_<NAME>); HEADLESS is always compiled in, and TERMINAL is compiled on every non-Windows build, so GetAvailable() can list more than one name. There is no CNA_PLATFORM environment variable: the platform is a build-time choice.
What CI is configured to check
All of it lives in .github/workflows/platform-ci.yml and runs on push and pull request to next, develop and main unless noted. This section describes what those jobs are configured to build, run and assert, read from the workflow file; we did not inspect run results. These jobs fetch sharp-runtime through scripts/ci/clone_siblings.sh, which picks its next branch on a push to next — unlike several other workflows that pin an older revision (see Platform Support). Every Linux job runs under a virtual X server (Xvfb) and Mesa's software GL and Vulkan drivers; none is a real desktop or a physical GPU.
| Job | Configuration | What it checks |
|---|---|---|
x11-sdl-free | CNA_ENABLE_SDL=OFF, X11, HEADLESS, NULL audio; Ubuntu 24.04, GCC 14, Xvfb + openbox | Builds the platform, audio, content and content-pipeline suites plus cna_content; no SDL artifact exists in the build tree and no test binary links SDL; the X11 suites pass; when the runner offers /dev/uinput, the uinput, D-Bus and private-server suites must run, not skip; the SDL-containment gates pass. |
x11-sdl-free-gpu | CNA_ENABLE_SDL=OFF, X11, OPENGL33;VULKAN;SOFTWARE;HEADLESS selectable at run time, ALSA audio, Release | Builds cna_demo_2d, cna_house3d_demo and CnaAudioTests; neither SDL nor libasound is a NEEDED entry; both games run under every renderer (house demo smoke tests, exclusive fullscreen, cna_demo_2d --smoke 6 per renderer); the ALSA device, mixer, microphone and XACT suites pass on ALSA's silent null device. |
win32-cross | mingw-w64 cross-build of the standalone platform harness for WIN32, executed under Wine | The backend still compiles for Windows, the full platform contract suite passes against it alongside Headless, its sources contain no SDL reference, and a Direct3D probe runs where Wine has a device. Wine is not Windows. |
win32-native | The same harness on windows-latest with MSVC | Manual dispatch only adds a second compiler, a real per-monitor-DPI desktop, real Direct3D 11/12 and real shell dialogs. |
sdl-enable-matrix | CNA_ENABLE_SDL = AUTO, ON, OFF against platform choices (configure only) | AUTO and ON configure; OFF + X11 configures; OFF + SDL3 must fail with “genuinely requires SDL”. |
| (no job) | CNA_PLATFORM=WAYLAND | Not covered Wayland development packages are installed in some jobs but every test there runs under Xvfb. |
The Win32 jobs build tools/platform/standalone_tests, not the full CNA engine: the automatic Windows evidence is about the platform module, not about a DirectX game. The native-MSVC, Direct3D and GDI lanes of the full engine are manual workflows.
Honest limits
- Wayland has no CI and its local validation is recorded only in CNA's own documents.
- X11 evidence comes from Xvfb. A physical GPU's GLX driver, a real compositor's fullscreen behaviour, multi-monitor layouts and hot-plug, real input devices and language-specific input-method engines are outside what CI can show; CNA's notes record manual validation on real desktops, but that is not a gate.
- Win32 has no gamepads, no IME, no drag-and-drop and text-only clipboard; the full-engine Windows lanes are manual.
- No SDL-free audio on Windows or macOS.
ALSAis Linux-only;OPENALandWASAPIare reserved and refused. - SDL-free means no SDL-based renderers (
SDL_RENDERER,SDL_GPU,FNA3D,FREEDIRECT). - X11 on other Unixes (FreeBSD, OpenBSD, NetBSD) is expected to build but is untested; native Wayland is not offered on macOS, iOS, Android or the web, and native X11 is not offered on iOS, Android or the web (on macOS it is offered whenever libX11, libXext and
XKBlib.hare found; not exercised). - The native backends are new in this development snapshot on
next; they are not part of thev0.1.0-alpha.1tag.
Further reading
- Platform Support — the axes, the seven-platform capability matrix, per-OS status and renderer availability.
- Building CNA — branches, siblings, submodules, presets and every CMake option.
- Audio System — ALSA, CNA's own mixer and
CNA_AUDIO_DEVICE. - Tutorials: 135 Native X11, 136 Native Wayland, 137 ALSA audio, 138 SDL-free build, 139 Terminal platform.
- CNA's own manuals at this snapshot: platform-x11.md, platform-wayland.md, platform-win32.md, testing-x11-desktop.md.
Deep dives on this topic
Long-form pages that explain the exact semantics, invariants and evidence behind this subject.
- The cross-platform contract: axes, composition and evidence per route — How target OS, platform implementation, renderer set, audio implementation and the XNA surface compose in CNA, what IPlatform owns, and why each platform claim is an evidence vector.
Known issues in this area
Current defects, gaps and limitations at this snapshot that touch this subject.
- CNA-BUG-179: Win32 EnterExclusiveFullscreen fails silently yet GetFullscreenMode reports ExclusiveFullscreen — EnterExclusiveFullscreen returns silently when a monitor-info, EnumDisplaySettingsW or ChangeDisplaySettingsExW step fails, though its comment says a refusal is reported; SetFullscreenMode still records ExclusiveFullscre
- CNA-BUG-180: Win32GlContext ignores multisample and per-channel format requests and reports the requested description as if it were granted — DescribeFormat ignores GlContextDescription's per-channel and multisample fields, and GetContextAttributes returns the requested description verbatim, so a legacy fallback context is reported as the requested version.
- CNA-BUG-196: CNA_X11_HAVE_VULKAN_HEADERS is defined by the X11 CMake but read by no source, so the dependency summary implies a Vulkan-header gate that does not exist — PlatformX11.cmake defines CNA_X11_HAVE_VULKAN_HEADERS and lists Vulkan headers as an optional dependency, but no X11 source reads the macro; the X11 Vulkan surface is created unconditionally and resolves its entry point
- CNA-BUG-233: CLAUDE.md's Platform Boundary lists WIN32 as a reserved identifier that fails configuration, but cmake/PlatformSelection.cmake implements CNA_PLATFORM=WIN32 on Windows — CNA's contributor instructions name SDL3, SDL2, X11, WAYLAND, HEADLESS and TERMINAL as the platforms and SDL12/WIN32/EMSCRIPTEN as reserved; the build offers WIN32 on Windows and reserves TERMINAL there.
- CNA-BUG-236: docs/platform-abstraction.md's Implementations table lists five backends and omits X11 and Wayland — CNA's platform-abstraction document lists SDL3, SDL2, WIN32, HEADLESS and TERMINAL in its Implementations table, but PlatformSelection.cmake offers seven implementations, adding the native X11 and WAYLAND backends.
- CNA-BUG-238: Win32Window's CnaPlatformWindow.Owned property is written and removed but never read, and its comment misdescribes AdoptWindowHandle — The kOwnedWindowProperty is set on every CNA-created HWND and removed on destroy but read nowhere; its comment says it lets AdoptWindowHandle refuse an owned window, whereas adoption returns a borrowed wrapper sharing th
- CNA-BUG-239: Two stale comments in the Win32 backend: a nonexistent spike path and a wrong Windows version — Win32DirectXIntegrationTests.cpp says real device and swapchain creation on the platform's handle is proved by spikes/win32-directx-spike/, a directory that has never existed (spikes/win32-spike covers window, message pu
- CNA-BUG-240: X11 header comments misdescribe the connection lifetime, the service accessors and the Xlib locking — X11Connection's comment says the connection opens on the first Video acquisition, but the platform opens it in its constructor; the accessor comments call services null before Video and AcquireSubsystem a thrower of Plat
- CNA-GAP-056: A foreign HWND adopted by handle is not subclassed and is absent from windows_, so the Win32 GL, Vulkan, relative-mouse and text-input services refuse it — AdoptWindowHandle wraps a foreign HWND without subclassing it or adding it to windows_, so it raises no CNA events and FindWindow cannot resolve its id; the WGL, Vulkan, relative-mouse and text-input services then reject
- CNA-VGAP-012: No CI workflow selects CNA_PLATFORM=WAYLAND, so the native Wayland backend's suites run only on a developer's machine — The Wayland ctest suites are registered only under CNA_PLATFORM=WAYLAND and launched through tools/platform/wayland_test_server.sh, but no GitHub workflow configures that platform, so the native Wayland backend has no co