SDL2 Setup with Vcpkg as Package Manager

Use Vcpkg to easily handle your cross-platform dependencies

ยท 3 min read

While developing Offlands, I realized that adding and maintaining a growing collection of librairies can be a challenge, especially when the project targets multiple operating systems.

Initially, countless weekends were spent to make SDL, Discord and Steam SDK compile on both Linux, Windows and MacOS. At that time, the integration each dependency was achieved by adding the git repository as a submodule. This automatically provides a way to handle the versioning of each dependency. However, this was only managable when these dependencies were providing a CMake integration out of the box.

Therefore, while looking at this growing pain, actively working with NPM in JavaScript and being aware of both crate.io for Rust and pkg.go.dev for Go, I wondered if similar solutions were available in C++.

During the evaluation phase, I ran into both Conan and Vcpkg. While testing Vcpkg, I was quickly able to integrate most of dependencies thanks to the existing ports such as sdl2, sdl2-image, sdl2-mixer, enet and flatbuffers.

You can find a simple example of a setup using Vcpkg with SDL2 in @njakob/vcpkg-sdl2-example which is close representation of the structure I currently use for Offlands.

The first step is to install Vcpkg. While you can have your local copy anywhere, I usually checkouk Vcpkg as a submodule. This enables me to know exactly which version of Vcpkg my project is currently built with.

git submodule add https://github.com/Microsoft/vcpkg.git
./vcpkg/bootstrap-vcpkg.sh

With Vcpkg available, it is recommanded to handle dependencies through a manifest.

{
  "name": "vcpkg-sdl2-example",
  "version-semver": "0.1.0",
  "dependencies": [
    {
      "name": "sdl2"
    }
  ]
}

This will enable you to keep an overview of your dependencies and let Vcpkg take care of providing what is necessary for their integration.

cmake_minimum_required(VERSION 3.22)

project(example LANGUAGES CXX)

find_package(SDL2 CONFIG REQUIRED)

add_executable(example src/main.cpp)

target_compile_features(example PRIVATE cxx_std_20)
target_link_libraries(example PRIVATE SDL2::SDL2)
target_link_libraries(example PRIVATE SDL2::SDL2main)
#include <cstdlib>
#include <iostream>
#include <SDL2/SDL.h>
#include <SDL2/SDL_main.h>

static auto shouldQuit = false;

auto ProcessInputs() -> void {
  SDL_Event event;
  while (SDL_PollEvent(&event) > 0) {
    switch (event.type) {
      case SDL_QUIT: {
        shouldQuit = true;
        return;
      }
    }
  }
}

auto main(int argc, char* argv[]) -> int {
  if (SDL_Init(SDL_INIT_EVERYTHING) != 0) {
    std::cout << "Unable to initialize SDL: " << SDL_GetError() << std::endl;
    return EXIT_FAILURE;
  }

  auto window = SDL_CreateWindow(
    "Example", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600, 0
  );

  while (!shouldQuit) {
    ProcessInputs();
  }

  return EXIT_SUCCESS;
}

Considering both the CMakeLists.txt file and this simple code snippet representing the main.cpp file, you will simply need the following commands to build your project.

cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=./vcpkg/scripts/buildsystems/vcpkg.cmake
cmake --build build

You can also use a CMakePresets.json file to handle the toolchain and have a seamless integration with your IDE when CMake is supported.

{
  "version": 3,
  "cmakeMinimumRequired": {
    "major": 3,
    "minor": 19
  },
  "configurePresets": [
    {
      "name": "development",
      "binaryDir": "${sourceDir}/build/${presetName}",
      "generator": "Ninja",
      "cacheVariables": {
        "CMAKE_TOOLCHAIN_FILE": {
          "type": "FILEPATH",
          "value": "${sourceDir}/vcpkg/scripts/buildsystems/vcpkg.cmake"
        }
      }
    }
  ]
}