Cross-Platform Mobile C++ Development with CMake: A Unified Build System for iOS, Android, Windows, and macOS

tqjxlmtqjxlm
6 min read

Introduction

Have you ever dreamed of writing C++ code once and deploying it seamlessly across iOS, Android, Windows, and macOS? I recently embarked on this journey while developing a mobile hardware ray tracing renderer. The goal was ambitious: create a framework that rivals the convenience of modern web development tools, but for C++ applications targeting all major platforms.

The good news? In 2025, this is not only possible but surprisingly achievable. I've open-sourced the result as Sparkle: A Cross-Platform Hardware Ray Tracing Renderer.

Why Now?

The C++ ecosystem has evolved significantly:

  1. Modern C++ - The standard library is richer than ever, and tools like vcpkg have revolutionized package management
  2. Universal Clang Support - All platforms now support Clang, with Android and macOS using it as their default compiler
  3. Mature CMake - While still quirky, CMake has become the de facto standard for cross-platform C++ builds
  4. Platform Evolution - Each platform has developed mature toolchains that still embrace C++
  5. Graphics API Convergence - Vulkan's cross-platform design and the convergence of graphics APIs make unified rendering possible
  6. Python Everywhere - Python's ubiquity provides a "free" cross-platform scripting solution for complex build automation
  7. AI Assistance - Modern AI tools can help navigate documentation, automate tedious tasks, and debug complex build issues

The Magic: One Command, Any Platform

Here's what the final build system looks like:

# Build for Windows (on Windows)
python3 build.py --framework glfw --config Release

# Build and run on macOS
python3 build.py --framework glfw --run

# Generate Visual Studio project (Windows)
python3 build.py --framework glfw --generate_only

# Build Android APK and deploy to connected device
python3 build.py --framework android --run

# Build for iOS with hardware ray tracing
python3 build.py --framework ios --config Release --run --pipeline gpu

The result? Identical rendering output across all platforms:

Cross-platform rendering result

Design Philosophy

Core Principles

  1. Platform-Agnostic by Default - Use standard library and cross-platform libraries wherever possible
  2. Isolate Platform-Specific Code - Keep platform dependencies contained and minimal
  3. Minimize Macros - Reserve them only for compiler/platform-specific needs
  4. Source Dependencies Over Binaries - Increases build time but dramatically reduces maintenance overhead
  5. Python Over CMake/C++ - Use Python for complex build logic instead of CMake scripting
  6. Modern Language Features - Embrace the latest C++ standards and compiler features
  7. Compiler as Guardian - Enable all warnings, treat warnings as errors, enforce code style
  8. Unified Toolchain - Standardize on Clang across platforms while maintaining native toolchain compatibility
  9. Configuration Over Convention - Make features toggleable for easier debugging and experimentation

Strategic Trade-offs

  1. Latest Everything - Support only the newest toolchains, SDKs, and hardware to leverage cutting-edge features
  2. Focused Feature Set - Implement representative, cross-platform solutions rather than comprehensive feature coverage
  3. Delayed Optimization - Prioritize stability and maintainability over premature optimization

Architecture Overview

Module Structure

sparkle/
├── libraries/          # Platform-independent modules
│   ├── core/          # Logging, math, threading, profiling
│   ├── io/            # Resource loading and file systems
│   ├── scene/         # Scene graph and nodes
│   ├── renderer/      # Rendering pipeline
│   ├── rhi/           # Graphics API abstraction
│   └── application/   # Main loop and initialization
├── frameworks/        # Platform-specific implementations
│   ├── glfw/         # Desktop (Windows/macOS)
│   ├── android/      # Android-specific code
│   ├── apple/        # Shared Apple platform code
│   ├── macos/        # macOS native support
│   └── ios/          # iOS native support
├── shaders/          # Cross-platform shaders
├── resources/        # Assets and configurations
├── build_system/     # Build scripts and platform resources
└── thirdparty/       # Dependencies

CMake Architecture

The build system uses three strategically designed CMake files:

  1. Main CMake - Manages global parameters, creates core libraries, handles platform-specific configurations
  2. Dependencies CMake - Consolidates all third-party dependencies into a single static library
  3. Shader CMake - Manages shader compilation (likely to be replaced with Python/C++ in the future)

Key Dependencies

  • GLFW - Mature cross-platform windowing for desktop platforms
  • Eigen - Powerful math library (overkill for graphics, but incredibly robust)
  • Dear ImGui - Lightweight, flexible immediate-mode GUI
  • spdlog - Fast, feature-rich logging
  • cpptrace - Cross-platform stack traces for debugging

Platform Implementation Details

Windows: The Reference Implementation

Windows development is straightforward thanks to GLFW and Microsoft's legendary backward compatibility. The interesting part is Clang on Windows support:

  • Install LLVM toolchain via Visual Studio Installer (clang-cl)
  • Develop without touching Visual Studio IDE
  • Unified compiler behavior across all platforms
  • Note: VS ships Clang 16 while official Clang is at 18+

macOS: Native Metal Support

Sparkle provides two macOS implementations:

  1. GLFW - Works out of the box with MoltenVK for Vulkan translation
  2. Native - Required for Metal ray tracing support

The native implementation bridges C++ and Apple's frameworks:

// Key components connected via Storyboard
- AppDelegate    → Application lifecycle
- MetalView      → Rendering context and swapchain
- ViewController → Window management and events
- Storyboard     → Wires everything together

iOS: Mobile Challenges

iOS support required several adaptations:

  1. Build System - Using ios-cmake for proper toolchain configuration
  2. Code Signing - Automatic signing for local development (no paid developer account required)
  3. Touch Events - Custom UIKit integration for touch handling
  4. ImGui Backend - Custom implementation for iOS touch and rendering
  5. File System - Sandbox-aware file handling using NSBundle and app directories

Android: GameActivity Integration

Android's approach uses GameActivity for C++ integration:

  1. Create a Kotlin/Java class extending GameActivity
  2. Load the C++ library from this class
  3. Implement GameActivity hooks in C++
  4. Configure AndroidManifest.xml to use your GameActivity
  5. Link everything through gradle's externalNativeBuild

The Build System Magic

The Python-based build system provides consistent commands across all platforms:

Features

  • Platform Selection - Target any supported platform
  • Build Configurations - Debug/Release modes
  • Instant Run - Build and deploy in one command
  • Mobile Deployment - Direct USB deployment to devices
  • IDE Integration - Generate native project files
  • LSP Support - Export compile_commands.json for clangd
  • Advanced Options - Shader debugging, ASAN, profiling

Automatic Dependency Management

The build system handles most dependencies automatically:

  1. Git Submodules - Checked and updated on each build
  2. CMake FetchContent - Downloaded during configuration
  3. Vulkan SDK - Silent installation to project directory
  4. Binary Dependencies - Via system package manager or vcpkg fallback
  5. macOS Tools - LLVM and CMake via Homebrew

Manual installation is still required for:

  • Visual Studio
  • Xcode
  • Android Studio
  • Platform-specific SDKs

Tested Platforms

PlatformOSCPUGPU
WindowsWindows 11Ryzen 5975WXGeForce RTX 4080
AndroidAndroid 13Snapdragon 8 Gen2Adreno 740
macOSmacOS 15.5Apple M3 ProApple M3 Pro
iOSiOS 18.5Apple A18Apple A18

Conclusion

Building a truly cross-platform C++ application framework is challenging but achievable. The key is embracing modern tools, making strategic trade-offs, and maintaining a clear separation between platform-agnostic and platform-specific code.

Sparkle demonstrates that with careful design, you can achieve the "write once, run everywhere" dream for C++ applications, even with advanced features like hardware ray tracing.

The project is actively developed and welcomes contributions. Whether you're building a game engine, a rendering system, or any cross-platform C++ application, I hope Sparkle's approach provides valuable insights for your journey.

Check out the full source code and documentation at github.com/tqjxlm/Sparkle.


Have you tackled cross-platform C++ development? What challenges did you face? Share your experiences in the comments below!

0
Subscribe to my newsletter

Read articles from tqjxlm directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

tqjxlm
tqjxlm