Tutorial 80: Cross-Platform Build Guide
What you’ll learn
- Which targets CNA supports, at what level, and what each needs from the build.
- CMake toolchain files, and guarding platform code with
#ifdef. - Path separators and case-sensitive file systems — the two portability bugs that bite first.
- Running OpenGL ES on the desktop through Mesa.
Before you start — Tutorial 20: Building and Running Your Game — this generalises that single-platform build to the rest of the matrix.
Platform matrix
These are support levels as they actually stand, not aspirations. The renderer column names one sensible choice per platform — CNA has 50 renderer identities in total, and the platform gates that decide which are even configurable are covered in Tutorial 72.
| Platform | Typical renderer | Level | Notes |
|---|---|---|---|
| Linux | OPENGLES3 (default), VULKAN, SDL_RENDERER |
Primary | Development and CI platform, on real hardware. Most renderers are Linux-configurable. |
| Windows | 14 Windows-only renderers, plus the portable ones | Supported, thinly tested | MSVC workflows and the repository's Wine/DXVK or vkd3d-proton routes are manual in alpha.1; there is no continuous native-Windows or Wine gate. |
| macOS | METAL |
Supported, with real CI | METAL is macOS-gated and has an automatically triggered macos-14 CI job — the only renderer that does. |
| Android | OPENGLES3 |
Code paths exist, unverified | NDK sensor backends and build wiring are present. No CI at all. See Tutorial 82. |
| Web (Emscripten) | WEBGL2 (default), WEBGL1, CANVAS, HTML_DOM, SVG_DOM |
Supported, with caveats | HTML_DOM has real browser CI. No save persistence, no video, and Game must be heap-allocated — see Tutorial 81. |
| iOS / tvOS | — | Not supported | No toolchain file exists. The METAL gate states plainly that iOS and tvOS remain unvalidated. |
Prerequisites that catch people out
- Sibling checkouts, not submodules. Every build needs
../sharp-runtimenext tocna/. Because the Linux default renderer isOPENGLES3, a default Linux build also needs../easy-gland../meta-gl.FREEDIRECTadditionally needs../free-direct(which uses../free-api). - FFmpeg is a hard requirement on Linux and macOS. No option disables it — configure fails without
libavcodec-dev,libavformat-dev,libavutil-devandlibswresample-dev. - Video is absent entirely on Windows, Web and Android. The video translation units are excluded from those builds. The headers still exist, so code calling
Video/VideoPlayercompiles and then fails to link. - Submodules:
git submodule update --init. Non-recursive is correct, and much faster. - The C++ framework has no general install/export package. C++ consumers
add_subdirectorythe CNA tree. The experimental C layer declares a separate package, but its alpha.1 implementation is compile-blocked by the incomplete C renderer map.
CMake toolchains
CNA uses a standard CMake build with optional toolchain files for cross-compilation. Select the appropriate
toolchain file for your target and pass -DCNA_GRAPHICS_RENDERER=OPENGLES3 (or VULKAN
/ SDL_RENDERER) to choose the renderer. The equivalent single-option form
-DCNA_RENDERER_OPENGLES3=ON also works — use one or the other, never both.
# Linux native (default)
cmake -S . -B build -DCNA_GRAPHICS_RENDERER=OPENGLES3
# Windows cross from Linux using MinGW
cmake -S . -B build-win \
-DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/mingw-w64.cmake \
-DCNA_GRAPHICS_RENDERER=DIRECTX11
# WebAssembly via Emscripten
cmake -S . -B build-wasm \
-DCMAKE_TOOLCHAIN_FILE=$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake \
-DCNA_GRAPHICS_RENDERER=WEBGL2
# Android (requires NDK)
cmake -S . -B build-android \
-DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \
-DANDROID_ABI=arm64-v8a \
-DANDROID_PLATFORM=android-24 \
-DCNA_GRAPHICS_RENDERER=OPENGLES3
# macOS native
cmake -S . -B build-macos -DCNA_GRAPHICS_RENDERER=METAL
# Build the whole directory, or a real target such as CnaTests —
# "--target CNA" no longer works: CNA is an INTERFACE library with no sources.
cmake --build build
Conditional platform code with #ifdef
#include "Microsoft/Xna/Framework/Game.hpp"
void MyGame::LoadContent() override {
#if defined(_WIN32)
// Windows-specific: load from AppData
auto savePath = std::string(getenv("APPDATA")) + "\\MyGame\\";
#elif defined(__EMSCRIPTEN__)
// WebAssembly: an in-memory virtual filesystem. NOTHING written here
// survives a page reload — CNA mounts no IDBFS and never calls FS.syncfs.
// See Tutorial 81 before designing a save system for the web.
auto savePath = std::string("/saves/");
#elif defined(__ANDROID__)
// Android: use SDL storage path
auto savePath = std::string(SDL_GetPrefPath("com.example", "MyGame"));
#else
// Linux / macOS
auto savePath = std::string(getenv("HOME")) + "/.local/share/MyGame/";
#endif
// ... use savePath ...
}
Path separators
CNA's ContentManager accepts both / and \ on all platforms. Internally
paths are normalized. Prefer / in your code and avoid hardcoding \. Use
SDL_GetBasePath() to find the executable directory at runtime in a platform-neutral way.
File system case sensitivity
Linux file systems are case-sensitive; Windows NTFS is case-insensitive by default. Always use consistent
casing for asset filenames (prefer all lowercase). A common portability bug is naming a file
Assets/Textures/Logo.png on Windows but referencing it as assets/textures/logo.png
on Linux — the Windows build loads it fine while the Linux build silently fails.
OpenGL ES on desktop (Mesa)
CNA's OPENGLES3 renderer targets OpenGL ES 3.0, which runs on desktop Linux via Mesa's GLES implementation.
No special configuration is needed; Mesa exposes GLES through the same EGL/GLX path used for desktop OpenGL.
# Verify GLES support:
glxinfo | grep "OpenGL ES"
# Expected: OpenGL ES profile version string: OpenGL ES 3.2 Mesa ...
Platform and audio layers
Alpha.1 separates the target operating system, graphics renderer, host platform and audio I/O. SDL3 remains
the default host and audio implementation, but CNA_PLATFORM can select SDL2,
HEADLESS or POSIX-only TERMINAL, while CNA_AUDIO_PLATFORM can select
SDL2 or NULL. These axes have explicit compatibility rules, so do not infer host
behaviour from the renderer name alone. At alpha.1 only the SDL3 audio choice defines
SOUND_ENABLED and links the SDL3_mixer-backed XNA playback engine; SDL2 and Null expose
lower-level device selections, not equivalent high-level playback.
CMakeLists.txt with platform detection
cmake_minimum_required(VERSION 3.20)
project(MyGame CXX)
set(CMAKE_CXX_STANDARD 23)
# Add the CNA source checkout (the C++ framework has no general installed package)
add_subdirectory(../cna cna-build)
add_executable(MyGame main.cpp)
target_link_libraries(MyGame PRIVATE CNA::CNA)
# Platform-specific link libraries
if(WIN32)
target_link_libraries(MyGame PRIVATE winmm)
elseif(ANDROID)
target_link_libraries(MyGame PRIVATE android log)
elseif(EMSCRIPTEN)
set_target_properties(MyGame PROPERTIES
SUFFIX ".html"
LINK_FLAGS "-s USE_SDL=3 -s FULL_ES3=1 --shell-file shell.html"
)
endif()
# Detect 64-bit build
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
message(STATUS "Building 64-bit")
else()
message(STATUS "Building 32-bit")
endif()
Cross-platform file path helper
#include <string>
#include <algorithm>
// Normalize path separators to forward slash
std::string NormalizePath(std::string path) {
std::replace(path.begin(), path.end(), '\\', '/');
return path;
}
// Join two path segments safely
std::string JoinPath(const std::string& base, const std::string& rel) {
if (base.empty()) return rel;
std::string result = NormalizePath(base);
if (result.back() != '/') result += '/';
result += NormalizePath(rel);
return result;
}