Platform Support

Per-platform status, stated at the level it has actually been verified  ·  CNA snapshot c1c316b9

ℹ

What changed since alpha.1. This page describes the current development snapshot (commit c1c316b9, branch apple/m4-stabilization). This snapshot offers three platform implementations — SDL3, the one windowing implementation, plus the windowless Headless and POSIX Terminal hosts — three audio implementations (one native and SDL-free), a build switch that removes SDL for windowless builds, a 32-flag capability contract (29 in alpha.1), and a curated set of 14 renderer identities. Everything below is stated at the level the code and CI actually prove. Statements about CI describe what the workflow files at this snapshot configure, build, run and assert; we read those files and did not inspect run results (see the caveat under What CI actually covers).

Four separate axes

In this snapshot, “backend” is still too ambiguous to be useful. CNA makes four different decisions, plus one build switch that decides whether SDL is present at all:

AxisQuestion it answersSelection
Target operating systemWhich toolchain and OS ABI is this binary built for?CMake toolchain / target; reported by CNA::TargetPlatform
Platform implementationWho owns windows, events, input, timing and host services?CNA_PLATFORM — 3 implementations
Graphics rendererWho turns CNA draw calls into pixels?CNA_GRAPHICS_RENDERER and optional CNA_GRAPHICS_RENDERERS — 14 identities
Audio implementationWho opens playback and recording devices, and (for two values) mixes?CNA_AUDIO_PLATFORM — 3 implementations
(switch) SDL availabilityIs SDL configured, built and linked at all?CNA_ENABLE_SDL = AUTO (default), ON or OFF

The platform implementation implements CNA::Platform::IPlatform and supplies narrow services to a renderer. It does not decide the renderer, audio implementation or target OS. This is also why the old public CNA::Platform enum became CNA::TargetPlatform (Desktop, Android, iOS, Web): CNA::Platform now names the host-integration namespace. The axes are independent with hard exclusions, listed under combination rules.

Platform and audio implementations

OptionImplemented valuesDefaultBoundary
CNA_PLATFORMSDL3, HEADLESS, TERMINALSDL3 on every OSSDL3 is the one windowing implementation: Windows, X11, Wayland, macOS, iOS, Android and the browser are reached through SDL’s own video drivers. TERMINAL is offered only off Windows (POSIX termios). Any other value fails configuration by name — there is no fallback to SDL3. Values are case-sensitive.
CNA_AUDIO_PLATFORMSDL3, NULL, ALSASDL3 on every OSAll three select low-level device code. SDL3 and ALSA also define SOUND_ENABLED and provide a mixer (SDL3_mixer, or CNA's own mixer for ALSA). NULL is not a feature-equivalent game-audio backend. ALSA is Linux-only. OPENAL and WASAPI are reserved and rejected.
CNA_ENABLE_SDLAUTO, ON, OFFAUTOAUTO and ON configure SDL3 as before. OFF fetches, builds and links no SDL, and refuses any selection that genuinely needs it (platform or audio SDL3; renderers SDL_RENDERER, SDL_GPU, FNA3D).
# A display-free simulation that still uses SDL3 audio
cmake -S . -B build-headless \
  -DCNA_PLATFORM=HEADLESS \
  -DCNA_AUDIO_PLATFORM=SDL3 \
  -DCNA_GRAPHICS_RENDERER=HEADLESS


# No SDL anywhere: a windowless build with ALSA audio and CNA's own mixer (Linux)
cmake -S . -B build-sdl-free -G Ninja \
  -DCNA_ENABLE_SDL=OFF \
  -DCNA_PLATFORM=HEADLESS \
  -DCNA_AUDIO_PLATFORM=ALSA \
  -DCNA_GRAPHICS_RENDERER=HEADLESS

# POSIX terminal host: CPU renderers only
cmake -S . -B build-terminal \
  -DCNA_PLATFORM=TERMINAL \
  -DCNA_AUDIO_PLATFORM=NULL \
  -DCNA_GRAPHICS_RENDERER=SOFTWARE
⚠

Audio selection is ahead of audio parity. In modules/CMakeLists.txt, SOUND_ENABLED is defined for CNA_AUDIO_PLATFORM=SDL3 and CNA_AUDIO_PLATFORM=ALSA only. NULL builds omit the mixer, decoder and mixer-dependent tests; the XNA audio facade remains present but does not gain production playback from the Null device class (SoundEffect::Play() returns false). Null is therefore useful for deterministic configuration and device-contract work, not proof that a silent mixer consumes normal game audio. Audio System has the full implementation matrix.

The three platform implementations

ImplementationWhat it isThird-party inputsAutomatic CI evidence
SDL3 DefaultThe one windowing implementation, on every target: Windows, X11 and Wayland sessions, macOS, iOS, Android and the browser through SDL’s own video drivers. A renderer that needs a native handle (HWND; X11 Display* and window; Wayland wl_display* and wl_surface*; a Cocoa or UIKit window) gets it from the SDL3 window through NativeWindowHandle.Vendored SDL3 (shared on Linux, macOS, Windows and Android; static on iOS and Emscripten)Many workflows: platform matrix (OPENGLES3, VULKAN, SOFTWARE cells, with the SDL3 window suite also run on SDL’s x11 driver under Xvfb and its wayland driver under a headless Weston), input, general tests, Apple, Emscripten; the native-MSVC Windows job is manual
HEADLESSOne window object, no optional services (only the always-present file-system and system-information services), every capability false. Always compiled in.NonePlatform matrix (including an SDL-free Headless + ALSA job), multi-renderer CI
TERMINALPOSIX-only terminal host (termios/poll, Kitty keyboard probe); presents finished CPU frames. One window.NonePlatform matrix cell plus a pseudo-TTY demo integration test

The three audio implementations at a glance

ValueMixer (SOUND_ENABLED)OSCaptureNotes
SDL3 (default)Yes — SDL3_mixerEvery targetYesNeeds SDL, so it cannot be combined with CNA_ENABLE_SDL=OFF.
NULLNoAllNoDeterministic silent transport; the only SDL-free choice off Linux.
ALSAYes — CNA's own mixerLinux onlyYesNo SDL. libasound is loaded at run time; CNA_AUDIO_DEVICE and CNA_AUDIO_RECORDING_DEVICE pick devices.

Combination rules and hard exclusions

  • TERMINAL accepts only CPU renderers: SOFTWARE, HEADLESS, STUB.
  • CNA_ENABLE_SDL=OFF refuses platform SDL3, audio SDL3 and the three SDL-linked renderers, with a message naming the reason.
  • Platform gates: TERMINAL only off Windows; ALSA only on Linux. A windowed build on any operating system uses SDL3.
  • Renderer combinations in CNA_GRAPHICS_RENDERERS obey one rule: identities gated to different operating systems cannot be mixed. See Runtime Renderer Selection.
  • A name outside the 14 public renderer identities is a configure-time error.

Window systems through SDL3, and SDL-free builds

SDL3 is CNA’s one windowing implementation, and the window system is SDL’s choice of video driver rather than a separate CNA platform: Windows uses SDL’s windows driver, a Linux desktop its x11 or wayland driver, and macOS, iOS, Android and the browser SDL’s own drivers for them. The native handles a renderer needs still reach it unchanged — an HWND, an X11 Display* and window, a Wayland wl_display* and wl_surface* — through NativeWindowHandle and its TryGetWin32/TryGetX11/TryGetWayland accessors (and CNA_NATIVE_WINDOW_SYSTEM_* in the C API). The dedicated page, Windows, X11 and Wayland, has the details and the evidence.

🖥

X11 through SDL3

In CI

SDL’s x11 video driver on an Xorg session (or Xwayland). CnaPlatformSdl3X11Tests runs the SDL3 window suite on a private Xvfb and checks that the Display* and window a renderer receives match the driver; the platform workflow runs it on every push. The recorded local validation ran seven renderers’ 2D and 3D demos on it.

🌊

Wayland through SDL3

In CI

SDL’s wayland video driver on a Wayland session, provided the Wayland development packages were present when the vendored SDL3 was first configured. CnaPlatformSdl3WaylandTests runs the same window suite on a private headless Weston and checks the wl_display* and wl_surface* a renderer receives.

🪟

Windows through SDL3

Manual CI

SDL’s windows video driver, with the HWND passed to the Direct3D and Vulkan renderers. The sdl3-windows job builds CNA natively with MSVC and runs the platform module suite on GitHub’s windows-latest image; it runs on manual dispatch, and its recorded run passed. A mingw-w64 cross-build of the window suite also passed under Wine.

🔧

SDL-free builds

In CI

-DCNA_ENABLE_SDL=OFF builds a binary with no SDL in it, for servers, test runners and terminal games. It has no window: the platform is HEADLESS or TERMINAL, the renderer HEADLESS, SOFTWARE or STUB, and audio NULL or (on Linux) ALSA. A platform job builds a Headless + ALSA tree this way and checks that nothing links SDL.

Two other tutorials cover the remaining pieces: Tutorial 137 (ALSA audio with CNA's own mixer) and Tutorial 139 (the POSIX terminal platform).

Capability matrix (32 flags)

Every platform reports a fixed set of 32 boolean capabilities through IPlatform::GetCapabilities(). A capability is true only when the service behind it is really wired up; a false one means the call refuses deterministically with PlatformNotSupportedException (never a silent no-op). “If…” entries depend on the machine and are decided once, when the platform is created. Three flags — dragAndDrop, primarySelection, clipboardData — were appended since alpha.1's 29.

Capability flagSDL3TerminalHeadless
multipleWindowsYesNo (one)No (one)
highDpiYesNoNo
multipleDisplaysYesNoNo
borderlessFullscreenYesNoNo
nativeWindowHandleYesNoNo
surfacePresentationYesIf stdout is a TTYNo
openGlContextYesNoNo
vulkanSurfaceIf Vulkan is availableNoNo
clipboardYesNoNo
clipboardDataYesNoNo
dragAndDropYesNoNo
primarySelectionYes (Unix desktops)NoNo
textInputYesNoNo
imeYesNoNo
exactKeyboardStateYesIf the Kitty keyboard protocol answersNo
pixelAccurateMouseYesNo (cells)No
relativeMouseYesNoNo
cursorShapesYesNoNo
globalPointerYesNoNo
inputDeviceEnumerationYesNoNo
gamepadYesNoNo
joystickYesNoNo
gamepadRumbleYesNoNo
gamepadSensorsYesNoNo
hapticsYesNoNo
sensorsYesNoNo
powerInfoYesNoNo
messageBoxYesNoNo
nativeFileDialogYesNoNo
trayIf supportedNoNo
cameraIf supportedNoNo
managedEntrypointYes (Android and iOS main)NoNo

Read from Sdl3Platform.cpp, TerminalPlatform.cpp and HeadlessPlatform.cpp at snapshot c1c316b9. The SDL3 column is the SDL3 platform’s own report; whether SDL’s video driver on a given system can deliver a service (a global pointer on Wayland, for example) is decided by SDL and the window system at run time. Touch and pen events (TouchEvent) are delivered by SDL3.

What "supported" means here

"Supported" is doing a lot of work in most framework documentation. This page avoids the word and names the level instead, because the difference between "there is code for it" and "someone ran it" is exactly the difference that costs you a weekend.

LevelWhat it means
Code path existsSources and CMake wiring are present for the target.
ConfiguresCMake accepts the target and the chosen renderer, and produces a build system.
Cross-compilesA full build completes from a different host operating system.
Runs under Wine or an emulatorThe binaries execute and render, but not on the operating system they target.
Runs on real hardwareSomeone ran it on the actual platform.
Covered by CIA workflow builds or tests it automatically on push and pull request — the only level that keeps working without anyone remembering to check.

These levels do not imply each other. The repository contains MinGW/Wine paths, but no automatic workflow uses them; the native-Windows lanes still require manual dispatch. macOS and iOS have build workflows that trigger automatically, and a recorded campaign on a physical Mac (see macOS). Android has source/NDK wiring and no workflow.

Platform matrix

PlatformRenderersHighest level reachedAutomatic CI
Linux Every renderer that is not Windows-only (2), Apple-only (1) or Emscripten-only (1) — 10 of the 14 identities. Default: OPENGLES3. Runs on real hardware Yes — multiple focused workflows on ubuntu-* runners (Xvfb and Mesa software drivers)
Windows The 2 Windows-only renderers (DIRECTX9, DIRECTX11), plus the portable ones. Default: SDL_RENDERER. Native MSVC builds run on GitHub’s Windows image by manual dispatch; Direct3D 11 validated by hand on one physical Windows 11 machine No automatic Windows job — the MSVC Direct3D 11, SDL3 platform and content workflows are manual dispatch only
Web (Emscripten) WEBGL2, plus WEBGPU. Default: WEBGL2. Build workflow configured; 90 C++ sample builds played in Chrome An Emscripten multi-renderer build/link lane (WEBGL2 + WEBGPU); runtime evidence is the published sample gallery, each build run in Chrome when it was published, and CNA.NET’s headless-Chromium runs
macOS METAL (Apple-only), OPENGL33, WEBGPU, SDL_GPU, FNA3D, SOFTWARE and the other portable renderers. Default: SDL_RENDERER. Runs on real hardware — full test trees on a physical Mac mini M4 (see macOS) Yes — Apple and Metal workflows on GitHub’s macos-26 runners (a paravirtual GPU); no green hosted run after the campaign’s last CI change is recorded
Android Portable renderers via the NDK toolchain. Default: SDL_RENDERER. Code paths and NDK sensor implementations exist No CI of any kind
iOS / tvOS SDL_RENDERER and METAL (the iOS allow-list); tvOS has no validated path. iOS Simulator execution with a pixel probe; device build final-linked, never run iOS device and simulator builds for both renderers; the simulator legs launch a smoke app and (for METAL) the pixel probe. No physical-device claim.
⚠

A single renderer remains the default build. The opt-in CNA_GRAPHICS_RENDERERS mode can link compatible sets, but it does not erase per-renderer capability or platform boundaries.

Status by operating system and platform implementation

Only what CI or code proves. CI = an automatic workflow (push or pull request to next, develop, main) builds or tests it. Manual CI = a workflow exists but runs only on workflow_dispatch. Code = sources and CMake wiring, no workflow. Local = only in-tree documents or scripts claim validation.

TargetPlatform implementations availableReal / testedExperimentalNot proven
Linux, X11 session (Xorg) SDL3 (default; SDL’s x11 driver), HEADLESS, TERMINAL CI SDL3 × OPENGLES3/VULKAN/SOFTWARE/SDL_RENDERER, and the SDL3 window suite on the x11 driver; an SDL-free Headless + ALSA build. All on Xvfb. Local seven renderers’ 2D and 3D demos on SDL’s x11 driver (2026-10-06). — Real desktops and physical GPUs are not a CI gate; the GPU pixel/oracle matrix is not gated.
Linux, Wayland session SDL3 (SDL’s wayland driver, which exists only if the Wayland development packages were present when SDL was first built; otherwise X11 through Xwayland) CI the SDL3 window suite on the wayland driver under a private headless Weston. Local seven renderers’ 2D and 3D demos on SDL’s wayland driver (2026-10-06). — Real compositors (GNOME, KDE) are not a CI gate.
Windows SDL3 (default; SDL’s windows driver), HEADLESS Manual CI MSVC build of DIRECTX11 (300 CTest entries passed on windows-latest, 2026-10-06); the SDL3 platform suite on MSVC; a Headless content-pipeline build. — FFmpeg video (never built for Windows); WASAPI audio (reserved); XInput.
macOS SDL3 (default; SDL’s Cocoa driver). CMake also offers HEADLESS and TERMINAL, which no macOS lane exercises. Real hardware A physical Mac mini M4 (Apple M4, 16 GB, macOS 27.0.1, Xcode 27, Apple clang 21) ran seven full test trees with no failure: METAL 11,015, SOFTWARE 11,084, OPENGL33 with compiled effects 11,916, SDL_GPU 10,962, WEBGPU 10,968, FNA3D 10,875, SDL_RENDERER 10,940 (each with its named skips). CI, configured macos-26 builds of SDL_RENDERER with portable suites and an .app launch, and of METAL with its ^Metal tests. Deployment floor 13.3. — On-screen presentation (the M4 console was locked), Intel Macs, VULKAN/MoltenVK, and any green hosted run after the campaign.
iOS SDL3 (default; SDL’s UIKit driver). HEADLESS and TERMINAL are not exercised on iOS. Simulator iOS Simulator (iPhone 17 profile) on the M4: cna_ios_smoke runs, and cna_ios_pixel_probe on METAL reads back exact pixels. Build only device apps for SDL_RENDERER and METAL final-link. Deployment floor 16.3; networking must be off (CNA_ENABLE_NET=OFF). The whole target is experimental Any physical iPhone or iPad: device pixels, touch, audio, storage, background and resume, performance; tvOS.
Android SDL3 (default; the only one CI configures). CMake also offers HEADLESS and TERMINAL; none is exercised on Android. Code; emulator runs outside CI NDK sensors, the SDL entry point and a Gradle demo project (minSdk 24, arm64-v8a); lifecycle, Back-button, portrait and APK-content fixes in this snapshot were found by running samples and games on the x86_64 Android emulator. No workflow, no preset. — Physical devices, GL context loss, and everything at run time beyond those emulator runs.
WebAssembly (Emscripten) SDL3 (default; the only one CI configures). CMake also offers HEADLESS and TERMINAL; none is exercised on the web. CI workflows, configured one bundle with WEBGL2;WEBGPU built and its renderer archives, registry entries and JS selection surface asserted; emsdk 6.0.3; Draco off. Threads option CNA_ENABLE_EMSCRIPTEN_THREADS; CNA_EMSCRIPTEN_USE_WASMFS=OFF for persistent saves in a threaded build Video (no FFmpeg build), sanitizers, any CNA_PLATFORM other than SDL3.
POSIX terminal TERMINAL CI TerminalPlatform unit tests plus a pseudo-TTY run of the 2D demo with SOFTWARE. — The Windows console (not offered).
Headless (any host) HEADLESS CI platform matrix and multi-renderer jobs. — —

Renderer availability by operating system

Which identities CMake will accept for each target. “Gated” means a hard FATAL_ERROR on any other target; “not gated” means CMake has no operating-system rule (the renderer may still need its own dependency: a Vulkan loader, a fetched library, a sibling checkout). This is configure-time acceptance, not a statement that every accepted pair has been run.

Renderer groupLinuxWindowsmacOSiOSAndroidWeb
DIRECTX9, DIRECTX11 (2, Windows-only)RefusedGated to here (native or mingw-w64)RefusedRefusedRefusedRefused
WEBGL2 (1, Emscripten-only)RefusedRefusedRefusedRefusedRefusedGated to here. Default WEBGL2
METAL (1, Apple-only)RefusedRefusedAcceptedAccepted (allow-listed)RefusedRefused
OPENGLES3, OPENGL33 (EasyGL; need ../easy-gl and ../meta-gl)Default is OPENGLES3Accepted (CMake warns OPENGLES3 is “primarily tested on Linux”)Accepted (same warning)Refused (allow-list)AcceptedRefused (use WEBGL2)
SDL_RENDERERAcceptedDefaultDefaultDefault (allow-listed)DefaultNot gated
VULKAN, SDL_GPU, WEBGPU, FNA3D, SOFTWARE, HEADLESS, STUBNot gatedNot gatedNot gatedRefused (allow-list)Not gatedNot gated by CMake; only WEBGL2 and WEBGPU are built by CI

That is 2 + 1 + 1 platform-gated identities and 10 that are not gated by operating system, for the 14 in total. The iOS allow-list can be overridden with CNA_APPLE_ALLOW_UNVALIDATED_RENDERER=ON for experiments only; that is not a support claim. TERMINAL further restricts the choice to SOFTWARE, HEADLESS and STUB. Which native window services a renderer needs is on Windows, X11 and Wayland.

Default selection per operating system

TargetCNA_PLATFORMCNA_AUDIO_PLATFORMCNA_GRAPHICS_RENDERERCNA_ENABLE_VIDEO (AUTO resolves to)
LinuxSDL3SDL3OPENGLES3 (needs the easy-gl and meta-gl siblings)On if the four FFmpeg pkg-config modules are found
Windows (MSVC or MinGW)SDL3SDL3SDL_RENDEREROff (unsupported target)
macOSSDL3SDL3SDL_RENDEREROn if FFmpeg is found (for example from Homebrew)
iOSSDL3SDL3SDL_RENDERER (METAL is the other allowed)Off
AndroidSDL3SDL3SDL_RENDEREROff
EmscriptenSDL3SDL3WEBGL2Off

There is no per-OS default for the platform or audio axes — both are SDL3 everywhere — and no CNA_PLATFORM environment variable. Only the renderer has run-time selection (CNA_GRAPHICS_RENDERER in the environment, in a multi-renderer build).

Linux

Linux is CNA's primary development platform and carries most of its CI. It is where the largest number of renderers can be selected, where all three platform implementations are offered, and where the project is developed on real hardware — the default renderer here is OPENGLES3, one of the three EasyGL profiles.

SDL3 (the default) works on X11 and Wayland sessions alike, through SDL’s x11 and wayland video drivers; SDL_VIDEO_DRIVER chooses between them when both are available. HEADLESS and TERMINAL need no display server. See Windows, X11 and Wayland.

The Windows-gated renderers are also driven from Linux: they are cross-compiled with MinGW-w64 and executed under Wine, so a Linux workstation is in practice the machine where the Direct3D ladder gets exercised too. See Windows for what that does and does not prove.

Practical requirements: the sharp-runtime sibling checkout (on the branch matching CNA’s) for every build, easy-gl plus meta-gl for the default renderer, a C++23 compiler (CI uses GCC 14), and, for a windowed build, the X11/GL/Vulkan/ALSA/D-Bus development packages so the vendored SDL3 gets real video drivers. FFmpeg development packages are optional now. Building has the exact commands and package lists.

Windows

Windows has the largest platform-exclusive renderer set in the project: 2 renderers refuse to configure anywhere else — DIRECTX9 and DIRECTX11. The default renderer on Windows is SDL_RENDERER. Windows windows come from SDL3’s windows video driver, which hands the Direct3D and Vulkan renderers the HWND they present into; HEADLESS is the windowless alternative. An SDL-free Windows build is therefore windowless, and must also use CNA_AUDIO_PLATFORM=NULL.

⚠

The repository provides MinGW cross-build and Wine/DXVK execution paths, but none is an automatic workflow in this snapshot. The native MSVC workflows — DIRECTX11, the SDL3 platform suite and a Headless content-pipeline build — are all manual dispatch; their recorded dispatch on GitHub’s windows-latest image (2026-10-06) passed. Treat runtime claims as results from the exact manual configuration, not as continuous native-Windows coverage.

Two further Windows facts worth knowing before you plan a release:

  • FFmpeg video is never built for Windows. CNA_ENABLE_VIDEO=AUTO silently falls back to the no-video backend on Windows and MinGW, and ON is a configure error. Video and VideoPlayer still compile and link; without a decoder they throw System::NotSupportedException at run time.
  • The MinGW cross-build is not covered by CI. Apart from a MinGW header-compatibility check for the experimental C API, building the engine for Windows is exercised by hand (MinGW + Wine/DXVK) or by the manual MSVC workflows.

Web (Emscripten)

The web target is real and has a browser build workflow. WEBGL2 is Emscripten-only and the default; WEBGPU also has a browser route. The strongest evidence is the 90 C++ sample builds playable at samples.libcna.com; saves persist in the browser’s IndexedDB, and games that start threads use a threaded build. The multi-renderer Emscripten job configures and links a WEBGL2 + WEBGPU bundle, and the workflow pins emsdk 6.0.3. The platform is SDL3 here by default and in every browser workflow (CMake’s platform selection would also list HEADLESS and TERMINAL, neither of which is exercised on the web); CNA_PLATFORM=EMSCRIPTEN is reserved and refused.

⚠

Saves persist on the web. The storage module mounts the browser’s IndexedDB (IDBFS) for StorageDevice (/cna-storage) and isolated storage (/save) and restores it before main(). Threaded builds keep WasmFS by default and need -DCNA_EMSCRIPTEN_USE_WASMFS=OFF for persistence; if the mount fails, StorageDevice reports the device as not connected. See Storage.

ℹ

Your Game object no longer must be heap-allocated on the web. Earlier builds unwound the caller's stack, so a stack-allocated Game was silently corrupted. In this snapshot Game::Run() blocks on the caller's own stack through Asyncify, so an ordinary local MyGame game; game.Run(); is supported. CNA-owned application executables link Asyncify automatically; an external final executable that uses a blocking Game::Run() must link CNA::EmscriptenAsyncify.

Video links but cannot decode here: the Video/VideoPlayer types are part of every build, no FFmpeg backend exists for Emscripten, and playback throws System::NotSupportedException at run time. Networking discovery does not work on Emscripten either.

Serve the generated .html, .js and .wasm from a local web server; browsers block direct file:// access. Configure with emcmake: emcmake cmake --preset web — the web preset carries no toolchain file, so cmake --preset web alone would configure natively.

macOS

macOS has a recorded full-suite run on real hardware. CNA’s Apple stabilization campaign ran on a physical Mac mini M4 (Apple M4, arm64, 16 GB, macOS 27.0.1, Xcode 27, Apple clang 21), on SDL3 — Apple is reached through SDL’s Cocoa video driver, not through a CNA backend of its own. Seven renderer trees ran their complete CTest suites there with no failing test; every skip declines by name:

Renderer tree on the Mac mini M4Tests passedSkipped by namecna-samples matrix
METAL11,015 / 11,01511491 / 91
SOFTWARE11,084 / 11,084133—
OPENGL33 (compiled effects on)11,916 / 11,91612891 / 91
SDL_GPU (SDL’s Metal driver)10,962 / 10,96229590 / 91 (LensFlare needs occlusion queries)
WEBGPU (wgpu-native on Metal)10,968 / 10,96827991 / 91
FNA3D (FNA3D’s Metal driver)10,875 / 10,87543390 / 91 (LensFlare)
SDL_RENDERER10,940 / 10,940966 (2D-only refusals)—

The runs were made with the console locked, so macOS reported every window occluded: the readback-based pixel tests ran, but on-screen presentation and vsync pacing were never observed. OPENGLES3 does not apply (macOS has no native OpenGL ES; OPENGL33 uses Apple’s 4.1 core context), VULKAN through MoltenVK was out of scope, no Intel Mac was run, and WEBGL2 is a browser renderer. CNA’s own verdict on METAL is Supported but not primary-production: compiled XNA effects need -DCNA_METAL_COMPILED_EFFECTS=ON, custom MSL effects are SpriteBatch-scoped, PresentInterval.Two presents as One, the sampler LOD bias needs the macOS 26 SDK and OS, and no soak run has bounded its caches. See Graphics Renderers and Tutorial 109.

CI is a separate, narrower layer: apple-ci.yml builds CnaTests on SDL_RENDERER on GitHub’s macos-26 runners, runs the portable suites and launches a self-contained .app, and metal-macos-ci.yml builds METAL and runs its ^Metal tests on the runners’ paravirtual GPU, which cannot run Metal shader validation. CNA records no green hosted run after the campaign’s last workflow change.

Practical notes. The macOS deployment floor is 13.3 (a lower CNA_MACOS_DEPLOYMENT_TARGET is a configure error, because CNA needs Apple’s floating-point std::to_chars); Xcode 15.4 is too old, CI uses Xcode 26. FFmpeg is optional (CNA_ENABLE_VIDEO=AUTO uses it when found), but Homebrew’s FFmpeg is built for the host macOS, so a binary linked against it does not start on an older macOS whatever the deployment target says; use CNA_ENABLE_VIDEO=OFF or an FFmpeg built for the floor. Online Gamer Services sessions need a libcurl with WebSocket support (Homebrew’s curl): Apple’s system libcurl lacks it, and the relay then refuses by name. SystemLink discovery between processes works on macOS. SDL3 is the platform; CMake also offers HEADLESS and TERMINAL, which no macOS lane exercises.

Android

Android is genuinely wired rather than aspirational: the NDK toolchain path is in the build system, the code paths exist, and the NDK sensor implementations are real — Compass and Motion are in fact Android-only and report NotSupported elsewhere. See Sensors.

⚠

Android has no CI at all — zero jobs, zero presets. Every Android build is one somebody ran by hand, and nothing catches an Android regression automatically. The only Android build project in the repository is the Devices demo (Gradle: minSdk 24, target/compile SDK 35, NDK 30.0.14904198, arm64-v8a only). CNA's own Android status note, last updated before alpha.1, records an NDK cross-build failing inside two sibling sharp-runtime files (an unused private field under -Werror in FileStream, and std::chrono::clock_cast in FileSystemInfo.cpp). Reading sharp-runtime next @ 41b918c9 shows both causes gone (the field no longer exists and clock_cast is confined to Microsoft's STL), but we did not re-run an NDK build against it, so treat Android builds as unverified. Video is unavailable here too.

iOS and tvOS

iOS support is experimental. The snapshot ships an iOS toolchain file (cmake/toolchains/ios.cmake, with CNA_IOS_SIMULATOR=ON for the simulator SDK) and admits two renderers: SDL_RENDERER and the native METAL renderer, which attaches its CAMetalLayer view to SDL’s UIWindow. A configure needs -DCNA_ENABLE_NET=OFF (the iOS SDK ships no libcurl), so iOS builds have no networking, and FFmpeg video is off. The deployment floor is iOS 16.3.

What has been demonstrated, at its real level:

  • iOS Simulator execution with pixel evidence. In the iOS Simulator (iPhone 17 profile) on the Mac mini M4, cna_ios_smoke runs and cna_ios_pixel_probe reads back the METAL frame. The first campaign run was exact on 29 of 31 launches, the two all-zero results being the first two after the initial install; the cause was later found in SDL’s UIKit view-frame handling and patched in CNA’s vendored SDL, after which the stricter probe (frame 1 and the ten frames after it) was exact in 8 of 8 launches.
  • A final-linked device build. Device apps for SDL_RENDERER and METAL compile and link (arm64, iOS 16.3, system frameworks only). They were never run: there was no physical device or signing identity.
  • CI, as configured. apple-ci.yml builds device and simulator apps for both renderers and, on the simulator, launches the smoke app and the METAL pixel probe.

No physical iPhone or iPad has run CNA, so nothing here establishes device pixels, real touch input, audio, storage, background and resume, or performance. CNA_APPLE_ALLOW_UNVALIDATED_RENDERER=ON merely permits experiments outside the allow-list; it is not a support claim. tvOS has no validated path.

What CI actually covers

This snapshot contains 18 GitHub Actions workflow files (24 jobs; 16 files run automatically on pushes and pull requests to next, develop and main, and two are manual or branch-specific). They cover general tests, platform conformance, multi-renderer Linux and Emscripten builds, Apple/Metal, declared C API compatibility/release gates, and focused renderer/subsystem checks. Trigger and evidence still matter more than the raw file count. The general job configures OPENGLES3, runs a full default build and an unfiltered ctest, and tolerates a single named known failure.

⚠

Configured is not the same as passing. Statements on this page describe what the workflow files at this snapshot configure, build, run and assert; we read those files and did not inspect run results, except where a run recorded in CNA’s own plans is cited. Every workflow now takes sharp-runtime (and, where needed, easy-gl and meta-gl) from the branch matching CNA’s own — the pushed branch, then next, then develop — through scripts/ci/sibling_branch.sh, so no lane builds against a stale sibling pin.

WorkflowTriggerRunnerWhat it does
General, input, devices and focused renderer checksMostly push / PRLinuxUnit, build, browser and renderer-specific contracts; exact filters vary by workflow.
multi-renderer-ci.yml and emscripten-multi-renderer-ci.ymlPush / PRLinux / EmscriptenRuntime-selection sets, combination rules and the Emscripten WEBGL2 + WEBGPU build/link check.
platform-ci.ymlPush / PR (one job manual)Linux; Windows for the manual jobPlatform abstraction and SDL3/Headless/Terminal combinations; the SDL3 window suite on SDL’s x11 driver (private Xvfb) and wayland driver (private headless Weston); an SDL-free Headless + ALSA build; the CNA_ENABLE_SDL matrix; and, on manual dispatch, the SDL3 platform suite built natively with MSVC.
apple-ci.yml and metal-macos-ci.ymlPush / PRmacOS (macos-26)macOS SDL_RENDERER portable suites and an .app launch; METAL build and ^Metal tests; iOS device final-link and simulator launch for SDL_RENDERER and METAL.
Five c-api-*.yml workflowsPush / PRLinuxABI baseline, compatibility, coverage, limitations and release-gate checks. No workflow builds the C API library.
d3d-windows-ci.yml and content-pipeline-windows-ci.ymlManual onlyWindowsNative MSVC DIRECTX11 and Headless content-pipeline suites (the last also runs on pushes to one named branch).

How to read the matrix

The snapshot has much broader automated coverage than alpha.1, but a workflow is not universal proof:

  • The Windows Direct3D, SDL3-on-Windows and content-pipeline lanes remain manual, so their existence is not an automatic merge gate.
  • The iOS device lane final-links; the simulator lane launches a smoke app. Neither establishes physical-device or full-game correctness.
  • The Emscripten multi-renderer lane covers configuration and linking; the runtime evidence for the web is the published sample gallery and CNA.NET’s headless-Chromium runs.
  • Android has source and NDK wiring but no automatic workflow.
  • Every Linux job runs on a virtual X server (or a headless Weston) with Mesa’s software drivers, and the macOS jobs on GitHub’s virtual Macs. None provides a physical-GPU or real-desktop run; the physical-hardware evidence comes from recorded campaigns.

Not exercised by any workflow: Android, tvOS, CNA_BUILD_C_API=ON builds, Clang on Linux (except C header compatibility), CNA_ENABLE_VIDEO=OFF on Linux, the full engine + DirectX under Wine/MinGW, and the GPU pixel/oracle corpus (run locally, by hand). Verification & Known Issues covers the wider testing picture, and Graphics Renderers the per-renderer side of it.

Where video is available

⚠

Video and VideoPlayer exist in every build and link everywhere, but decoding is available on Linux and macOS only, and only where the optional FFmpeg backend was compiled in: CNA_ENABLE_VIDEO is ON, or AUTO found libavcodec, libavformat, libavutil and libswresample. On Windows, Emscripten, Android and iOS FFmpeg is never built (AUTO falls back silently, ON is a configure error). Without a backend the calls throw System::NotSupportedException at run time — a run-time exception, not a link error. Audio and MediaPlayer song playback do not depend on FFmpeg. See Video Playback.