Tutorial 80: Cross-Platform Build Guide
Platform matrix
| Platform | Backend | Status | Notes |
|---|---|---|---|
| Linux | EasyGL / Vulkan / SDL_Renderer | Fully supported | Primary development platform |
| Windows | EasyGL / Vulkan / SDL_Renderer | Supported | MSVC 2022 or MinGW-w64 |
| macOS | EasyGL / SDL_Renderer | Partial | OpenGL deprecated; Metal backend planned |
| Android | EasyGL (OpenGL ES 3.0) | Experimental | Requires NDK + SDL3 Android template |
| WebAssembly | EasyGL (WebGL 2) | Supported | Via Emscripten; see Tutorial 81 |
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_BACKEND=EASYGL (or VULKAN
/ SDL_RENDERER) to choose the rendering backend.
# Linux native (default)
cmake -S . -B build -DCNA_GRAPHICS_BACKEND=EASYGL
# Windows cross from Linux using MinGW
cmake -S . -B build-win \
-DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/mingw-w64.cmake \
-DCNA_GRAPHICS_BACKEND=EASYGL
# WebAssembly via Emscripten
cmake -S . -B build-wasm \
-DCMAKE_TOOLCHAIN_FILE=$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake \
-DCNA_GRAPHICS_BACKEND=EASYGL
# 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_BACKEND=EASYGL
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: use Emscripten virtual filesystem
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 EASYGL backend 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 ...
SDL3 platform layer
SDL3 abstracts window creation, event handling, and OpenGL context management across all platforms. CNA delegates all platform I/O to SDL3, which means most cross-platform issues are handled automatically. Platform-specific code in CNA itself is limited to path resolution and a few storage APIs. If you encounter a platform-specific problem, check the SDL3 issue tracker before looking at CNA.
CMakeLists.txt with platform detection
cmake_minimum_required(VERSION 3.20)
project(MyGame CXX)
set(CMAKE_CXX_STANDARD 23)
# Find CNA (assumed built as a sibling project)
find_package(CNA REQUIRED)
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;
}