Tutorial 80: Cross-Platform Build Guide

CNA — C++ XNA 4.0 reimplementation

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;
}