Technical Documentation: libsdl-org/SDL
ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference
Overview (`README.md`)
Simple DirectMedia Layer (SDL for short) is a cross-platform library
designed to make it easy to write multi-media software, such as games
and emulators.
You can find the latest release and additional information at:
https://www.libsdl.org/
Installation instructions and a quick introduction is available in
INSTALL.md
This library is distributed under the terms of the zlib license,
available in LICENSE.txt.
Enjoy!
Sam Lantinga ([email protected])
Overview (`docs/README.md`)
Simple DirectMedia Layer
Simple DirectMedia Layer is a cross-platform development library designed
to provide low level access to audio, keyboard, mouse, joystick, and graphics
hardware. It is used by video playback software, emulators, and popular games
including Valve's award winning catalog and many Humble Bundle games.
SDL officially supports Windows, macOS, Linux, iOS, Android, Xbox, PlayStation 4/5, Nintendo Switch, and many other platforms.
SDL is written in C, works natively with C++, and there are bindings
available for several other languages, including C# and Python.
This library is distributed under the zlib license, which can be found
in the file "LICENSE.txt".
Information on building SDL with CMake is available in README-cmake.md
The best way to learn how to use SDL is to check out the header files in
the "include" subdirectory and the programs in the "examples" subdirectory.
The header files and test programs are well commented and always up to date.
Information on reporting bugs and contributing is available in README-contributing.md
More documentation and FAQs are available online at the wiki
If you need help with the library, or just want to discuss SDL related
issues, you can join the SDL Discourse,
which can be used as a web forum or a mailing list, at your preference.
If you want to report bugs or contribute patches, please submit them to
our bug tracker
Enjoy!
Sam Lantinga mailto:[email protected]
Overview (`examples/README.md`)
Examples
What is this?
In here are a collection of standalone SDL application examples. Unless
otherwise stated, they should work on all supported platforms out of the box.
If they don't please file a bug to let us know.
What is this SDL_AppIterate thing?
SDL can optionally build apps as a collection of callbacks instead of the
usual program structure that starts and ends in a function called main.
The examples use this format for two reasons.
First, it allows the examples to work when built as web applications without
a pile of ugly #ifdefs, and all of these examples are published on the web
at examples.libsdl.org, so you can easily see
them in action.
Second, it's example code! The callbacks let us cleanly break the program up
into the four logical pieces most apps care about:
- Program startup
- Event handling
- What the program actually does in a single frame
- Program shutdown
A detailed technical explanation of these callbacks is in
docs/README-main-functions.md (or view that page on the web on
the wiki).
I would like to build and run these examples myself.
When you build SDL with CMake, you can add -DSDL_EXAMPLES=On to the
CMake command line. When you build SDL, these examples will be built with it.
But most of these can just be built as a single .c file, as long as you point
your compiler at SDL3's headers and link against SDL.
What is the license on the example code? Can I paste this into my project?
All code in the examples directory is considered public domain! You can do
anything you like with it, including copy/paste it into your closed-source
project, sell it, and pretend you wrote it yourself. We do not require you to
give us credit for this code (but we always appreciate if you do!).
This is only true for the examples directory. The rest of SDL falls under the
zlib license.
What is template.html and highlight-plugin.lua in this directory?
This is what examples.libsdl.org uses when
generating the web versions of these example programs. You can ignore this,
unless you are improving it, in which case we definitely would love to hear
from you!
What is template.c in this directory?
If writing new examples, this is the skeleton code we start from, to keep
everything consistent. You can ignore it.
How are the thumbnails/onmouseover media created?
(Since I have to figure this out every time.)
This is how Ryan is doing it currently.
rm -f frame*.pngTemporarily add
#include "../../save-rendering-to-bitmaps.h"after any SDL
includes in the example program.Launch the example app, interact with it, let it run for a few seconds, quit.
This will dump a "frameX.png" file for each frame rendered.
Make a video in webp format from the bitmaps (this assumes the bitmaps were
stored at 60fps, you might have to tweak).bashffmpeg -framerate 60 -pattern_type glob -i 'frame*.png' -loop 0 -quality 40 -r 10 -frames:v 40 onmouseover.webpYou might need to start in the middle of the video, or mess with quality or
number of frames to generate, ymmv.Pick a frame for the thumbnail, make it a .png, and run that png through
pngquant for massive file size reduction without any obvious loss in quality:bashconvert frame00000.png cvt.png ; pngquant cvt.png --output thumbnail.png ; rm -f cvt.png
Overview (`src/hidapi/README.md`)
HIDAPI library for Windows, Linux, FreeBSD and macOS
| CI instance | Status |
|---|---|
Linux/macOS/Windows (master) |
|
Windows (master) |
|
BSD, last build (branch/PR) |
|
Coverity Scan (last) |
HIDAPI is a multi-platform library which allows an application to interface
with USB and Bluetooth HID-Class devices on Windows, Linux, FreeBSD, and macOS.
HIDAPI can be either built as a shared library (.so, .dll or .dylib) or
can be embedded directly into a target application by adding a single source
file (per platform) and a single header.
See remarks on embedding directly into your build system.
HIDAPI library was originally developed by Alan Ott (signal11).
It was moved to libusb/hidapi on June 4th, 2019, in order to merge important bugfixes and continue development of the library.
Table of Contents
About
HIDAPI has four back-ends:
- Windows (using
hid.dll) - Linux/hidraw (using the Kernel's hidraw driver)
- libusb (using libusb-1.0 - Linux/BSD/other UNIX-like systems)
- macOS (using IOHidManager)
On Linux, either the hidraw or the libusb back-end can be used. There are
tradeoffs, and the functionality supported is slightly different. Both are
built by default. It is up to the application linking to hidapi to choose
the backend at link time by linking to either libhidapi-libusb orlibhidapi-hidraw.
Note that you will need to install an udev rule file with your application
for unprivileged users to be able to access HID devices with hidapi. Refer
to the 69-hid.rules file in the udev directory
for an example.
__Linux/hidraw__ (`linux/hid.c`):
This back-end uses the hidraw interface in the Linux kernel, and supports
both USB and Bluetooth HID devices. It requires kernel version at least 2.6.39
to build. In addition, it will only communicate with devices which have hidraw
nodes associated with them.
Keyboards, mice, and some other devices which are blacklisted from having
hidraw nodes will not work. Fortunately, for nearly all the uses of hidraw,
this is not a problem.
__Linux/FreeBSD/libusb__ (`libusb/hid.c`):
This back-end uses libusb-1.0 to communicate directly to a USB device. This
back-end will of course not work with Bluetooth devices.
Test GUI
HIDAPI also comes with a Test GUI. The Test GUI is cross-platform and uses
Fox Toolkit http://www.fox-toolkit.org. It will build on every platform
which HIDAPI supports. Since it relies on a 3rd party library, building it
is optional but it is useful when debugging hardware.
NOTE: Test GUI based on Fox Toolkit is not actively developed nor supported
by HIDAPI team. It is kept as a historical artifact. It may even work sometime
or on some platforms, but it is not going to get any new features or bugfixes.
Instructions for installing Fox-Toolkit on each platform is not provided.
Make sure to use Fox-Toolkit v1.6 if you choose to use it.
Console Test App
If you want to play around with your HID device before starting
any development with HIDAPI and using a GUI app is not an option for you, you may try hidapitester.
This app has a console interface for most of the features supported
by HIDAPI library.
What Does the API Look Like?
The API provides the most commonly used HID functions including sending
and receiving of input, output, and feature reports. The sample program,
which communicates with a heavily hacked up version of the Microchip USB
Generic HID sample looks like this (with error checking removed for
simplicity):
Warning: Only run the code you understand, and only when it conforms to the
device spec. Writing data (hid_write) at random to your HID devices can break them.
/* Detailed source-code truncated for AI context efficiency. */You can also use hidtest/test.c
as a starting point for your applications.
License
HIDAPI may be used by one of three licenses as outlined in LICENSE.txt.
Installing HIDAPI
If you want to build your own application that uses HID devices with HIDAPI,
you need to get HIDAPI development package.
Depending on what your development environment is, HIDAPI likely to be provided
by your package manager.
For instance on Ubuntu, HIDAPI is available via APT:
sudo apt install libhidapi-devHIDAPI package name for other systems/package managers may differ.
Check the documentation/package list of your package manager.
Build from Source
Check BUILD.md for details.
Overview (`src/hidapi/netbsd/README.md`)
Implementation Notes
NetBSD maps every uhidev device to one or more uhid
devices. Each uhid device only supports one report ID.
The parent device uhidev creates one uhid device per
report ID found in the hardware's report descriptor.
In the event there are no report ID(s) found within the
report descriptor, only one uhid device with a report ID
of 0 is created.
In order to remain compatible with existing hidapi APIs,
all the uhid devices created by the parent uhidev device
must be opened under the same hid_device instance to ensure
that we can route reports to their appropriate uhid device.
Internally the uhid driver will insert the report ID as
needed so we must also omit the report ID in any situation
where the hidapi API expects it to be included in the
report data stream.
Given the design of uhid, it must be augmented with extra
platform specific APIs to ensure that the exact relationship
between uhidev devices and uhid devices can be determined.
The NetBSD implementation does this via the drvctl kernel
driver. At present there is no known way to do this on OpenBSD
for a uhid implementation to be at the same level as the
NetBSD one.
Overview (`src/hidapi/subprojects/README.md`)
This folder is used only to support meson.build subproject command
which would only look for a subproject in a "subprojects" directory.
Overview (`src/video/yuv2rgb/README.md`)
From: https://github.com/descampsa/yuv2rgb
yuv2rgb
C library for fast image conversion between yuv420p and rgb24.
This is a simple library for optimized image conversion between YUV420p and rgb24.
It was done mainly as an exercise to learn to use sse intrinsics, so there may still be room for optimization.
For each conversion, a standard c optimized function and two sse function (with aligned and unaligned memory) are implemented.
The sse version requires only SSE2, which is available on any reasonably recent CPU.
The library also supports the three different YUV (YCrCb to be correct) color spaces that exist (see comments in code), and others can be added simply.
There is a simple test program, that convert a raw YUV file to rgb ppm format, and measure computation time.
Optionally, it also compares the result and computation time with the ffmpeg implementation (that uses MMX), and with the IPP functions.
To compile, simply do :
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release ..
makeThe test program only support raw YUV files for the YUV420 format, and ppm for the RGB24 format.
To generate a raw yuv file, you can use avconv:
avconv -i example.jpg -c:v rawvideo -pix_fmt yuv420p example.yuvTo generate the rgb file, you can use the ImageMagick convert program:
convert example.jpg example.ppmThen, for YUV420 to RGB24 conversion, use the test program like that:
./test_yuv_rgb yuv2rgb image.yuv 4096 2160 imageThe second and third parameters are image width and height (that are needed because not available in the raw YUV file), and fourth parameter is the output filename template (several output files will be generated, named for example output_sse.ppm, output_av.ppm, etc.)
Similarly, for RGB24 to YUV420 conversion:
./test_yuv_rgb yuv2rgb image.ppm imageOn my computer, the test program on a 4K image give the following for yuv2rgb:
Time will be measured in each configuration for 100 iterations...
Processing time (std) : 2.630193 sec
Processing time (sse2_unaligned) : 0.704394 sec
Processing time (ffmpeg_unaligned) : 1.221432 sec
Processing time (ipp_unaligned) : 0.636274 sec
Processing time (sse2_aligned) : 0.606648 sec
Processing time (ffmpeg_aligned) : 1.227100 sec
Processing time (ipp_aligned) : 0.636951 secAnd for rgb2yuv:
Time will be measured in each configuration for 100 iterations...
Processing time (std) : 2.588675 sec
Processing time (sse2_unaligned) : 0.676625 sec
Processing time (ffmpeg_unaligned) : 3.385816 sec
Processing time (ipp_unaligned) : 0.593890 sec
Processing time (sse2_aligned) : 0.640630 sec
Processing time (ffmpeg_aligned) : 3.397952 sec
Processing time (ipp_aligned) : 0.579043 secconfiguration : gcc 4.9.2, swscale 3.0.0, IPP 9.0.1, intel i7-5500U
Overview (`src/hidapi/windows/pp_data_dump/README.md`)
pp_data_dump.exe for Windows
pp_data_dump.exe is a small command line tool for Windows, which dumps the content of the Preparsed Data structure, provided by the Windows HID subsystem, into a file.
The generated file is in a text format, which is readable for human, as by the hid_report_reconstructor_test.exe unit test executable of the HIDAPI project. This unit test allows it to test the HIDAPIs report descriptor reconstructor - offline, without the hardware device connected.
pp_data_dump.exe has no arguments, just connect you HID device and execute pp_data_dump.exe. It will generate one file with the name
<vendor_id>_<product_id>_<usage>_<usage_table>.pp_datafor each top-level collection, of each connected HID device.
File content
The content of such a .pp_data file looks like the struct, which HIDAPI uses internally to represent the Preparsed Data.
NOTE:
Windows parses HID report descriptors into opaque _HIDP_PREPARSED_DATA objects.
The internal structure of _HIDP_PREPARSED_DATA is reserved for internal system use.
Microsoft doesn't document this structure. hid_preparsed_data.cc is taken as a reference for its parsing.
/* Detailed source-code truncated for AI context efficiency. */INTRO Androidstudio (`docs/INTRO-androidstudio.md`)
Introduction to SDL with Android Studio
We'll start by creating a simple project to build and run hello.c
- Use our handy script to create a template project:
./build-scripts/create-android-project.py org.libsdl.hello docs/hello.c- Run Android Studio and open the newly created build/org.libsdl.hello directory
- Build and run!
A more complete example is available at:
https://github.com/Ravbug/sdl3-sample
Additional information and troubleshooting is available in README-android.md
INTRO Cmake (`docs/INTRO-cmake.md`)
Introduction to SDL with CMake
The easiest way to use SDL is to include it as a subproject in your project.
We'll start by creating a simple project to build and run hello.c
Get a copy of the SDL source:
git clone https://github.com/libsdl-org/SDL.git vendored/SDLCreate the file CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(hello)
# set the output directory for built objects.
# This makes sure that the dynamic library goes into the build directory automatically.
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/$<CONFIGURATION>")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/$<CONFIGURATION>")
# This assumes the SDL source is available in vendored/SDL
add_subdirectory(vendored/SDL EXCLUDE_FROM_ALL)
# Create your game executable target as usual
add_executable(hello WIN32 hello.c)
# Link to the actual SDL3 library.
target_link_libraries(hello PRIVATE SDL3::SDL3)Configure and Build:
cmake -S . -B build
cmake --build buildRun:
The executable should be in the build directory:
cd build
./helloIf there wasn't an executable there despite the above Build section running successfully, it's likely because you're following this guide using the Visual Studio toolchain, it should instead be in the build/Debug directory:
cd build/Debug
./helloA more complete example is available at:
https://github.com/Ravbug/sdl3-sample
Additional information and troubleshooting is available in README-cmake.md
INTRO Emscripten (`docs/INTRO-emscripten.md`)
Introduction to SDL with Emscripten
The easiest way to use SDL is to include it as a subproject in your project.
We'll start by creating a simple project to build and run hello.c
First, you should have the Emscripten SDK installed from:
https://emscripten.org/docs/getting_started/downloads.html
Create the file CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(hello)
# set the output directory for built objects.
# This makes sure that the dynamic library goes into the build directory automatically.
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/$<CONFIGURATION>")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/$<CONFIGURATION>")
# This assumes the SDL source is available in vendored/SDL
add_subdirectory(vendored/SDL EXCLUDE_FROM_ALL)
# on Web targets, we need CMake to generate a HTML webpage.
if(EMSCRIPTEN)
set(CMAKE_EXECUTABLE_SUFFIX ".html" CACHE INTERNAL "")
endif()
# Create your game executable target as usual
add_executable(hello WIN32 hello.c)
# Link to the actual SDL3 library.
target_link_libraries(hello PRIVATE SDL3::SDL3)Build:
emcmake cmake -S . -B build
cd build
emmake makeYou can now run your app by pointing a webserver at your build directory and connecting a web browser to it, opening hello.html
A more complete example is available at:
https://github.com/Ravbug/sdl3-sample
Additional information and troubleshooting is available in README-emscripten.md
INTRO Mingw (`docs/INTRO-mingw.md`)
Introduction to SDL with MinGW
Without getting deep into the history, MinGW is a long running project that aims to bring gcc to Windows. That said, there's many distributions, versions, and forks floating around. We recommend installing MSYS2, as it's the easiest way to get a modern toolchain with a package manager to help with dependency management. This would allow you to follow the MSYS2 section below.
Otherwise you'll want to follow the "Other Distributions" section below.
We'll start by creating a simple project to build and run hello.c.
MSYS2
Open the MSYS2 UCRT64 prompt and then ensure you've installed the following packages. This will get you working toolchain, CMake, Ninja, and of course SDL3.
pacman -S mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-ninja mingw-w64-ucrt-x86_64-cmake mingw-w64-ucrt-x86_64-sdl3Create the file CMakeLists.txt
cmake_minimum_required(VERSION 3.26)
project(hello C CXX)
find_package(SDL3 REQUIRED)
add_executable(hello)
target_sources(hello
PRIVATE
hello.c
)
target_link_libraries(hello SDL3::SDL3)Configure and Build:
cmake -S . -B build
cmake --build buildRun:
The executable is in the build directory:
cd build
./helloOther Distributions
Things can get quite complicated with other distributions of MinGW. If you can't follow the cmake intro, perhaps due to issues getting cmake to understand your toolchain, this section should work.
Acquire SDL
Download the SDL3-devel-<version>-mingw.zip asset from the latest release. Then extract it inside your project folder such that the output of ls SDL3-<version> looks like INSTALL.md LICENSE.txt Makefile README.md cmake i686-w64-mingw32 x86_64-w64-mingw32.
Know your Target Architecture
It is not uncommon for folks to not realize their distribution is targeting 32bit Windows despite things like the name of the toolchain, or the fact that they're running on a 64bit system. We'll ensure we know up front what we need:
Create a file named arch.c with the following contents:
#include <stddef.h>
#include <stdio.h>
int main() {
#if defined(__x86_64__) || defined(_M_X64) || defined(i386) || defined(__i386__) || defined(__i386) || defined(_M_IX86)
size_t ptr_size = sizeof(int*);
if (4 == ptr_size) puts("i686-w64-mingw32");
else if (8 == ptr_size) puts("x86_64-w64-mingw32");
else puts("Unknown Architecture");
#else
puts("Unknown Architecture");
#endif
return 0;
}Then run
gcc arch.c
./a.exeThis should print out which library directory we'll need to use when compiling, keep this value in mind, you'll need to use it when compiling in the next section as <arch>. If you get "Unknown Architecture" please report a bug.
Build and Run
Now we should have everything needed to compile and run our program. You'll need to ensure to replace <version> with the version of the release of SDL3 you downloaded, as well as use the <arch> we learned in the previous section.
gcc hello.c -o hello.exe -I SDL3-<version>/<arch>/include -L SDL3-<version>/<arch>/lib -lSDL3 -mwindows
cp SDL3-<version>/<arch>/bin/SDL3.dll SDL3.dll
./hello.exeINTRO Visualstudio (`docs/INTRO-visualstudio.md`)
Introduction to SDL with Visual Studio
The easiest way to use SDL is to include it as a subproject in your project.
We'll start by creating a simple project to build and run hello.c
- Get a copy of the SDL source, you can clone the repo, or download the "Source Code" asset from the latest release.
- If you've downloaded a release, make sure to extract the contents somewhere you can find it.
- Create a new project in Visual Studio, using the C++ Empty Project template
- Add hello.c to the Source Files
- Right click the solution, select add an existing project, navigate to
VisualC/SDLfrom within the source you cloned or downloaded above and add SDL.vcxproj - Select your main project and go to Project -> Add -> Reference and select SDL3
- Select your main project and go to Project -> Properties, set the filter at the top to "All Configurations" and "All Platforms", select C/C++ -> General and add the SDL include directory to "Additional Include Directories"
- Build and run!
INTRO Xcode (`docs/INTRO-xcode.md`)
Introduction to SDL with Xcode
The easiest way to use SDL is to include it as a subproject in your project.
We'll start by creating a simple project to build and run hello.c
- Create a new project in Xcode, using the App template and selecting Objective C as the language
- Remove the .h and .m files that were automatically added to the project
- Remove the main storyboard that was automatically added to the project
- On iOS projects, select the project, select the main target, select the Info tab, look for "Custom iOS Target Properties", and remove "Main storyboard base file name" and "Application Scene Manifest"
- Right click the project and select "Add Files to [project]", navigate to the SDL docs directory and add the file hello.c
- Right click the project and select "Add Files to [project]", navigate to the SDL Xcode/SDL directory and add SDL.xcodeproj
- Select the project, select the main target, select the General tab, look for "Frameworks, Libraries, and Embedded Content", and add SDL3.framework
- Build and run!
README Android (`docs/README-android.md`)
Android
Matt Styles wrote a tutorial on building SDL for Android with Visual Studio:
http://trederia.blogspot.de/2017/03/building-sdl2-for-android-with-visual.html
The rest of this README covers the Android gradle style build process.
Requirements
Android SDK (version 35 or later)
https://developer.android.com/sdk/index.html
Android NDK r28c or later
https://developer.android.com/tools/sdk/ndk/index.html
Minimum API level supported by SDL: 21 (Android 5.0)
How the port works
- Android applications are Java-based, optionally with parts written in C
- As SDL apps are C-based, we use a small Java shim that uses JNI to talk to
the SDL library - This means that your application C code must be placed inside an Android
Java project, along with some C support code that communicates with Java - This eventually produces a standard Android .apk package
The Android Java code implements an "Activity" and can be found in:
android-project/app/src/main/java/org/libsdl/app/SDLActivity.java
The Java code loads your game code, the SDL shared library, and
dispatches to native functions implemented in the SDL library:
src/core/android/SDL_android.c
Building a simple app
For simple projects you can use the script located at build-scripts/create-android-project.py
There's two ways of using it:
./create-android-project.py com.yourcompany.yourapp < sources.list
./create-android-project.py com.yourcompany.yourapp source1.c source2.c ...sourceN.csources.list should be a text file with a source file name in each line
Filenames should be specified relative to the current directory, for example if
you are in the build-scripts directory and want to create the testspriteminimal.c test, you'll
run:
./create-android-project.py org.libsdl.testspriteminimal ../test/testspriteminimal.c ../test/icon.hOne limitation of this script is that all sources provided will be aggregated into
a single directory, thus all your source files should have a unique name.
Once the project is complete the script will tell you how to build the project.
If you want to create a signed release APK, you can use the project created by this
utility to generate it.
If you see link errors about missing SDLTest_* symbols,
you need to add ../src/test/*.c as an extra argument to create-android-project.py.
Running the script with --help will list all available options, and their purposes.
Finally, a word of caution: re running create-android-project.py wipes any changes you may have
done in the build directory for the app!
Building a more complex app
For more complex projects, follow these instructions:
Get the source code for SDL and copy the 'android-project' directory located at SDL/android-project to a suitable location in your project.
The 'android-project' directory can basically be seen as a sort of starting point for the android-port of your project. It contains the glue code between the Android Java 'frontend' and the SDL code 'backend'. It also contains some standard behaviour, like how events should be handled, which you will be able to change.
If you are not already building SDL as a part of your project (e.g. via CMake add_subdirectory() or FetchContent) move or symlink the SDL directory into the 'android-project/app/jni' directory. Alternatively you can use the SDL3 Android Archive (.aar), see below for more details.
This is needed as SDL has to be compiled by the Android compiler.
Edit 'android-project/app/build.gradle' to include any assets that your app needs by adding 'assets.srcDirs' in 'sourceSets.main'.
For example:
assets.srcDirs = ['../../assets', '../../shaders']
If using CMake:
Edit 'android-project/app/build.gradle' to set 'buildWithCMake' to true and set 'externalNativeBuild' cmake path to your top level CMakeLists.txt.
For example:
path '../../CMakeLists.txt'Change the target containing your main function to be built as a shared library called "main" when compiling for Android. (e.g. add_executable(MyGame main.c) should become add_library(main SHARED main.c) on Android)
If using Android Makefiles:
- Edit 'android-project/app/jni/src/Android.mk' to include your source files. They should be separated by spaces after the 'LOCAL_SRC_FILES := ' declaration.
To build your app, run ./gradlew installDebug or ./gradlew installRelease in the project directory. It will build and install your .apk on any connected Android device. If you want to use Android Studio, simply open your 'android-project' directory and start building.
Additionally the SDL_helloworld project contains a small example program with a functional Android port that you can use as a reference.
Here's an explanation of the files in the Android project, so you can customize them:
android-project/app
build.gradle - build info including the application version and SDK
src/main/AndroidManifest.xml - package manifest. Among others, it contains the class name of the main Activity and the package name of the application.
jni/ - directory holding native code
jni/Application.mk - Application JNI settings, including target platform and STL library
jni/Android.mk - Android makefile that can call recursively the Android.mk files in all subdirectories
jni/CMakeLists.txt - Top-level CMake project that adds SDL as a subproject
jni/SDL/ - (symlink to) directory holding the SDL library files
jni/SDL/Android.mk - Android makefile for creating the SDL shared library
jni/src/ - directory holding your C/C++ source
jni/src/Android.mk - Android makefile that you should customize to include your source code and any library references
jni/src/CMakeLists.txt - CMake file that you may customize to include your source code and any library references
src/main/assets/ - directory holding asset files for your application
src/main/res/ - directory holding resources for your application
src/main/res/mipmap-* - directories holding icons for different phone hardware
src/main/res/values/strings.xml - strings used in your application, including the application name
src/main/java/org/libsdl/app/SDLActivity.java - the Java class handling the initialization and binding to SDL. Be very careful changing this, as the SDL library relies on this implementation. You should instead subclass this for your application.Using the SDL3 Android Archive (.aar)
The Android archive allows use of SDL3 in your Android project, without needing to copy any SDL C or JAVA source into your project.
For integration with CMake/ndk-build, it uses prefab.
Copy the archive to a app/libs directory in your project and add the following to app/gradle.build:
android {
/* ... */
buildFeatures {
prefab true
}
}
dependencies {
implementation files('libs/SDL3-X.Y.Z.aar') /* Replace with the filename of the actual SDL3-x.y.z.aar file you downloaded */
/* ... */
}If you use CMake, add the following to your CMakeLists.txt:
find_package(SDL3 REQUIRED CONFIG)
target_link_libraries(yourgame PRIVATE SDL3::SDL3)If you use ndk-build, add the following before include $(BUILD_SHARED_LIBRARY) to your Android.mk:
LOCAL_SHARED_LIBRARIES := SDL3 SDL3-HeadersAnd add the following at the bottom:
# https://google.github.io/prefab/build-systems.html
# Add the prefab modules to the import path.
$(call import-add-path,/out)
# Import @PROJECT_NAME@ so we can depend on it.
$(call import-module,prefab/@PROJECT_NAME@)The build-scripts/create-android-project.py script can create a project using Android aar-chives from scratch:
build-scripts/create-android-project.py --variant aar com.yourcompany.yourapp < sources.listCustomizing your application name
To customize your application name, edit build.gradle to replace
"org.libsdl.app" with an identifier for your product package.
Then create a Java class extending SDLActivity and place it in a directory
under src matching your package, e.g.
app/src/main/java/com/gamemaker/game/MyGame.javaHere's an example of a minimal class file:
--- MyGame.java --------------------------
package com.gamemaker.game;
import org.libsdl.app.SDLActivity;
/**
* A sample wrapper class that just calls SDLActivity
*/
public class MyGame extends SDLActivity { }
------------------------------------------Then replace "SDLActivity" in AndroidManifest.xml with the name of your
class, .e.g. "MyGame"
Then edit app/src/main/res/values/strings.xml and change the name there.
Customizing your application icon
Conceptually changing your icon is just replacing the "ic_launcher.png" files in
the drawable directories under the res directory. There are several directories
for different screen sizes.
Loading assets
Any files you put in the "app/src/main/assets" directory of your project
directory will get bundled into the application package and you can load
them using the standard functions in SDL_iostream.h.
As of SDL 3.6.0, SDL APIs, such as SDL_EnumerateDirectory() and
SDL_IOFromFile(), understand paths that are prefixed with "assets://" and will
look for paths exclusively inside the APK's "assets" directory. Since this is
where app-specific data files are meant to be located, SDL_GetBasePath() on
Android now returns "assets://" to make this work as expected across platforms.
Note that SDL 3.2.28 to 3.6.0 returned "./" on Android, and before that,
SDL_GetBasePath() always returned NULL on this platform.
Obviously, paths prefixed with "assets://" are only useful to SDL; other APIs,
like fopen(), will not understand them at all.
As an alternate approach: SDL APIs on Android treat relative paths in a
special way. It will look for files under the path returned by
SDL_GetAndroidInternalStoragePath() first, and failing that, will attempt to
look for them as if they were prefixed by "assets://", with the relative path
starting in the base of the assets tree. Absolute paths never check against
internal storage or assets.
There are also a few Android specific functions that allow you to get other
useful paths for saving and loading data:
- SDL_GetAndroidInternalStoragePath()
- SDL_GetAndroidExternalStorageState()
- SDL_GetAndroidExternalStoragePath()
- SDL_GetAndroidCachePath()
See SDL_system.h for more details on these functions.
The asset packaging system will, by default, compress certain file extensions.
SDL includes two asset file access mechanisms, the preferred one is the so
called "File Descriptor" method, which is faster and doesn't involve the Dalvik
GC, but given this method does not work on compressed assets, there is also the
"Input Stream" method, which is automatically used as a fall back by SDL. You
may want to keep this fact in mind when building your APK, specially when large
files are involved.
For more information on which extensions get compressed by default and how to
disable this behaviour, see for example:
http://ponystyle.com/blog/2010/03/26/dealing-with-asset-compression-in-android-apps/
Activity lifecycle
On Android the application goes through a fixed life cycle and you will get
notifications of state changes via application events. When these events
are delivered you must handle them in an event callback because the OS may
not give you any processing time after the events are delivered.
e.g.
bool HandleAppEvents(void *userdata, SDL_Event *event)
{
switch (event->type)
{
case SDL_EVENT_TERMINATING:
/* Terminate the app.
Shut everything down before returning from this function.
*/
return false;
case SDL_EVENT_LOW_MEMORY:
/* You will get this when your app is paused and Android wants more memory.
Release as much memory as possible.
*/
return false;
case SDL_EVENT_WILL_ENTER_BACKGROUND:
/* Prepare your app to go into the background. Stop loops, etc.
This gets called when the user hits the home button, or gets a call.
You should not make any OpenGL graphics calls or use the rendering API,
in addition, you should set the render target to NULL, if you're using
it, e.g. call SDL_SetRenderTarget(renderer, NULL).
*/
return false;
case SDL_EVENT_DID_ENTER_BACKGROUND:
/* Your app is NOT active at this point. */
return false;
case SDL_EVENT_WILL_ENTER_FOREGROUND:
/* This call happens when your app is coming back to the foreground.
Restore all your state here.
*/
return false;
case SDL_EVENT_DID_ENTER_FOREGROUND:
/* Restart your loops here.
Your app is interactive and getting CPU again.
You have access to the OpenGL context or rendering API at this point.
However, there's a chance (on older hardware, or on systems under heavy load),
where the graphics context cannot be restored. You should listen for the
event SDL_EVENT_RENDER_DEVICE_RESET and recreate your OpenGL context and
restore your textures when you get it, or quit the app.
*/
return false;
default:
/* No special processing, add it to the event queue */
return true;
}
}
int main(int argc, char *argv[])
{
SDL_SetEventFilter(HandleAppEvents, NULL);
... run your main loop
return 0;
}Note that if you are using main callbacks instead of a standard C main() function,
your SDL_AppEvent() callback will run as these events arrive and you do not need to
use SDL_SetEventFilter.
If SDL_HINT_ANDROID_BLOCK_ON_PAUSE hint is set (the default),
the event loop will block itself when the app is paused (ie, when the user
returns to the main Android dashboard). Blocking is better in terms of battery
use, and it allows your app to spring back to life instantaneously after resume
(versus polling for a resume message).
You can control activity re-creation (eg. onCreate()) behaviour. This allows you
to choose whether to keep or re-initialize java and native static datas, see
SDL_HINT_ANDROID_ALLOW_RECREATE_ACTIVITY in SDL_hints.h.
Insets and Safe Areas
As of Android 15, SDL windows cover the entire screen, extending under notches
and system bars. The OS expects you to take those into account when displaying
content and SDL provides the function SDL_GetWindowSafeArea() so you know what
area is available for interaction. Outside of the safe area can be potentially
covered by system bars or used by OS gestures.
Mouse / Touch events
In some case, SDL generates synthetic mouse (resp. touch) events for touch
(resp. mouse) devices.
To enable/disable this behavior, see SDL_hints.h:
- SDL_HINT_TOUCH_MOUSE_EVENTS
- SDL_HINT_MOUSE_TOUCH_EVENTS
Misc
For some devices, it appears to work better setting explicitly GL attributes
before creating a window:
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
Threads and the Java VM
For a quick tour on how Linux native threads interoperate with the Java VM, take
a look here: https://developer.android.com/guide/practices/jni.html
If you want to use threads in your SDL app, it's strongly recommended that you
do so by creating them using SDL functions. This way, the required attach/detach
handling is managed by SDL automagically. If you have threads created by other
means and they make calls to SDL functions, make sure that you call
Android_JNI_SetupThread() before doing anything else otherwise SDL will attach
your thread automatically anyway (when you make an SDL call), but it'll never
detach it.
If you ever want to use JNI in a native thread (created by "SDL_CreateThread()"),
it won't be able to find your java class and method because of the java class loader
which is different for native threads, than for java threads (eg your "main()").
the work-around is to find class/method, in you "main()" thread, and to use them
in your native thread.
see:
https://developer.android.com/training/articles/perf-jni#faq:-why-didnt-findclass-find-my-class
Using STL
You can use STL in your project by creating an Application.mk file in the jni
folder and adding the following line:
APP_STL := c++_sharedFor more information go here:
https://developer.android.com/ndk/guides/cpp-support
Using the emulator
There are some good tips and tricks for getting the most out of the
emulator here: https://developer.android.com/tools/devices/emulator.html
Especially useful is the info on setting up OpenGL ES 2.0 emulation.
Notice that this software emulator is incredibly slow and needs a lot of disk space.
Using a real device works better.
Troubleshooting
You can see if adb can see any devices with the following command:
adb devicesYou can see the output of log messages on the default device with:
adb logcatYou can push files to the device with:
adb push local_file remote_path_and_fileYou can push files to the SD Card at /sdcard, for example:
adb push moose.dat /sdcard/moose.datYou can see the files on the SD card with a shell command:
adb shell ls /sdcard/You can start a command shell on the default device with:
adb shellYou can remove the library files of your project (and not the SDL lib files) with:
ndk-build cleanYou can do a build with the following command:
ndk-buildYou can see the complete command line that ndk-build is using by passing V=1 on the command line:
ndk-build V=1If your application crashes in native code, you can use ndk-stack to get a symbolic stack trace:
https://developer.android.com/ndk/guides/ndk-stack
If you want to go through the process manually, you can use addr2line to convert the
addresses in the stack trace to lines in your code.
For example, if your crash looks like this:
I/DEBUG ( 31): signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 400085d0
I/DEBUG ( 31): r0 00000000 r1 00001000 r2 00000003 r3 400085d4
I/DEBUG ( 31): r4 400085d0 r5 40008000 r6 afd41504 r7 436c6a7c
I/DEBUG ( 31): r8 436c6b30 r9 435c6fb0 10 435c6f9c fp 4168d82c
I/DEBUG ( 31): ip 8346aff0 sp 436c6a60 lr afd1c8ff pc afd1c902 cpsr 60000030
I/DEBUG ( 31): #00 pc 0001c902 /system/lib/libc.so
I/DEBUG ( 31): #01 pc 0001ccf6 /system/lib/libc.so
I/DEBUG ( 31): #02 pc 000014bc /data/data/org.libsdl.app/lib/libmain.so
I/DEBUG ( 31): #03 pc 00001506 /data/data/org.libsdl.app/lib/libmain.soYou can see that there's a crash in the C library being called from the main code.
I run addr2line with the debug version of my code:
arm-eabi-addr2line -C -f -e obj/local/armeabi/libmain.soand then paste in the number after "pc" in the call stack, from the line that I care about:
000014bc
I get output from addr2line showing that it's in the quit function, in testspriteminimal.c, on line 23.
You can add logging to your code to help show what's happening:
#include <android/log.h>
__android_log_print(ANDROID_LOG_INFO, "foo", "Something happened! x = %d", x);If you need to build without optimization turned on, you can create a file called
"Application.mk" in the jni directory, with the following line in it:
APP_OPTIM := debugMemory debugging
The best (and slowest) way to debug memory issues on Android is valgrind.
Valgrind has support for Android out of the box, just grab code using:
git clone https://sourceware.org/git/valgrind.git... and follow the instructions in the file README.android to build it.
One thing I needed to do on macOS was change the path to the toolchain,
and add ranlib to the environment variables:
export RANLIB=$NDKROOT/toolchains/arm-linux-androideabi-4.4.3/prebuilt/darwin-x86/bin/arm-linux-androideabi-ranlib
Once valgrind is built, you can create a wrapper script to launch your
application with it, changing org.libsdl.app to your package identifier:
--- start_valgrind_app -------------------
#!/system/bin/sh
export TMPDIR=/data/data/org.libsdl.app
exec /data/local/Inst/bin/valgrind --log-file=/sdcard/valgrind.log --error-limit=no $*
------------------------------------------Then push it to the device:
adb push start_valgrind_app /data/localand make it executable:
adb shell chmod 755 /data/local/start_valgrind_appand tell Android to use the script to launch your application:
adb shell setprop wrap.org.libsdl.app "logwrapper /data/local/start_valgrind_app"If the setprop command says "could not set property", it's likely that
your package name is too long and you should make it shorter by changing
AndroidManifest.xml and the path to your class file in android-project/src
You can then launch your application normally and waaaaaaaiiittt for it.
You can monitor the startup process with the logcat command above, and
when it's done (or even while it's running) you can grab the valgrind
output file:
adb pull /sdcard/valgrind.logWhen you're done instrumenting with valgrind, you can disable the wrapper:
adb shell setprop wrap.org.libsdl.app ""Graphics debugging
If you are developing on a compatible Tegra-based tablet, NVidia provides
Tegra Graphics Debugger at their website. Because SDL3 dynamically loads EGL
and GLES libraries, you must follow their instructions for installing the
interposer library on a rooted device. The non-rooted instructions are not
compatible with applications that use SDL3 for video.
The Tegra Graphics Debugger is available from NVidia here:
https://developer.nvidia.com/tegra-graphics-debugger
A note regarding the use of the "dirty rectangles" rendering technique
If your app uses a variation of the "dirty rectangles" rendering technique,
where you only update a portion of the screen on each frame, you may notice a
variety of visual glitches on Android, that are not present on other platforms.
This is caused by SDL's use of EGL as the support system to handle OpenGL ES/ES2
contexts, in particular the use of the eglSwapBuffers function. As stated in the
documentation for the function "The contents of ancillary buffers are always
undefined after calling eglSwapBuffers".
Ending your application
Two legitimate ways:
return from your main() function. Java side will automatically terminate the
Activity by calling Activity.finish().Android OS can decide to terminate your application by calling onDestroy()
(see Activity life cycle). Your application will receive an SDL_EVENT_QUIT you
can handle to save things and quit.
Don't call exit() as it stops the activity badly.
NB: "Back button" can be handled as a SDL_EVENT_KEY_DOWN/UP events, with Keycode
SDLK_AC_BACK, for any purpose.
Known issues
- The number of buttons reported for each joystick is hardcoded to be 36, which
is the current maximum number of buttons Android can report.
Building the SDL tests
SDL's CMake build system can create APK's for the tests.
It can build all tests with a single command without a dependency on gradle or Android Studio.
The APK's are signed with a debug certificate.
The only caveat is that the APK's support a single architecture.
Requirements
- SDL source tree
- CMake
- ninja or make
- Android Platform SDK
- Android NDK
- Android Build tools
- Java JDK (version should be compatible with Android)
- keytool (usually provided with the Java JDK), used for generating a debug certificate
- zip
CMake configuration
When configuring the CMake project, you need to use the Android NDK CMake toolchain, and pass the Android home path through SDL_ANDROID_HOME.
cmake .. -DCMAKE_TOOLCHAIN_FILE=<path/to/android.toolchain.cmake> -DANDROID_ABI=<android-abi> -DSDL_ANDROID_HOME=<path-to-android-sdk-home> -DANDROID_PLATFORM=21 -DSDL_TESTS=ONRemarks:
android.toolchain.cmakecan usually be found at$ANDROID_HOME/ndk/x.y.z/build/cmake/android.toolchain.cmakeANDROID_ABIshould be one ofarm64-v8a,armeabi-v7a,x86orx86_64.- When CMake is unable to find required paths, use
cmake-guito override requiredSDL_ANDROID_CMake cache variables.
Building the APK's
For the testsprite executable, the testsprite-apk target will build the associated APK:
cmake --build . --target testsprite-apkAPK's of all tests can be built with the sdl-test-apks target:
cmake --build . --target sdl-test-apksInstallation/removal of the tests
testsprite.apk APK can be installed on your Android machine using the install-testsprite target:
cmake --build . --target install-testspriteAPK's of all tests can be installed with the install-sdl-test-apks target:
cmake --build . --target install-sdl-test-apksAll SDL tests can be uninstalled with the uninstall-sdl-test-apks target:
cmake --build . --target uninstall-sdl-test-apksStarting the tests
After installation, the tests can be started using the Android Launcher GUI.
Alternatively, they can also be started using CMake targets.
This command will start the testsprite executable:
cmake --build . --target start-testspriteThere is also a convenience target which will build, install and start a test:
cmake --build . --target build-install-start-testspriteNot all tests provide a GUI. For those, you can use adb logcat to read the output.
README Bsd (`docs/README-bsd.md`)
FreeBSD / OpenBSD / NetBSD
SDL is fully supported on BSD platforms, and is built using CMake.
If you want to run on the console, you can take a look at KMSDRM support on BSD
SDL is not designed to be used in setuid or setgid executables.
README Cmake (`docs/README-cmake.md`)
CMake
The CMake build system is supported with the following environments:
- Android
- Emscripten
- FreeBSD
- Haiku
- Linux
- macOS, iOS, tvOS, and visionOS with support for XCode
- Microsoft Visual Studio
- MinGW and Msys
- NetBSD
- Nintendo 3DS
- PlayStation 2
- PlayStation Portable
- PlayStation Vita
- RISC OS
Building SDL on Windows
Assuming you're in the SDL source directory, building and installing to C:/SDL can be done with:
cmake -S . -B build
cmake --build build --config RelWithDebInfo
cmake --install build --config RelWithDebInfo --prefix C:/SDLBuilding SDL on UNIX
SDL will build with very few dependencies, but for full functionality you should install the packages detailed in README-linux.md.
Assuming you're in the SDL source directory, building and installing to /usr/local can be done with:
cmake -S . -B build
cmake --build build
sudo cmake --install build --prefix /usr/localBuilding SDL on macOS
Assuming you're in the SDL source directory, building and installing to ~/SDL can be done with:
cmake -S . -B build -DSDL_FRAMEWORK=ON -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64"
cmake --build build
cmake --install build --prefix ~/SDLBuilding SDL tests
You can build the SDL test programs by adding -DSDL_TESTS=ON to the first cmake command above:
cmake -S . -B build -DSDL_TESTS=ONand then building normally. The test programs will be built and can be run from build/test/.
Building SDL examples
You can build the SDL example programs by adding -DSDL_EXAMPLES=ON to the first cmake command above:
cmake -S . -B build -DSDL_EXAMPLES=ONand then building normally. The example programs will be built and can be run from build/examples/.
Including SDL in your project
SDL can be included in your project in 2 major ways:
- using a system SDL library, provided by your (UNIX) distribution or a package manager
- using a vendored SDL library: this is SDL copied or symlinked in a subfolder.
The following CMake script supports both, depending on the value of MYGAME_VENDORED.
cmake_minimum_required(VERSION 3.5)
project(mygame)
# Create an option to switch between a system sdl library and a vendored SDL library
option(MYGAME_VENDORED "Use vendored libraries" OFF)
if(MYGAME_VENDORED)
# This assumes you have added SDL as a submodule in vendored/SDL
add_subdirectory(vendored/SDL EXCLUDE_FROM_ALL)
else()
# 1. Look for a SDL3 package,
# 2. look for the SDL3-shared component, and
# 3. fail if the shared component cannot be found.
find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3-shared)
endif()
# Create your game executable target as usual
add_executable(mygame WIN32 mygame.c)
# Link to the actual SDL3 library.
target_link_libraries(mygame PRIVATE SDL3::SDL3)A system SDL library
For CMake to find SDL, it must be installed in a default location CMake is looking for.
The following components are available, to be used as an argument of find_package.
| Component name | Description |
|---|---|
| SDL3-shared | The SDL3 shared library, available through the SDL3::SDL3-shared target |
| SDL3-static | The SDL3 static library, available through the SDL3::SDL3-static target |
| SDL3_test | The SDL3_test static library, available through the SDL3::SDL3_test target |
| SDL3 | The SDL3 library, available through the SDL3::SDL3 target. This is an alias of SDL3::SDL3-shared or SDL3::SDL3-static. This component is always available. |
| Headers | The SDL3 headers, available through the SDL3::Headers target. This component is always available. |
SDL's CMake support guarantees a SDL3::SDL3 target.
Neither SDL3::SDL3-shared nor SDL3::SDL3-static are guaranteed to exist.
Using a vendored SDL
This only requires a copy of SDL in a subdirectory + add_subdirectory.
Alternatively, use FetchContent.
Depending on the configuration, the same targets as a system SDL package are available.
CMake configuration options
Build optimized library
By default, CMake provides 4 build types: Debug, Release, RelWithDebInfo and MinSizeRel.
The main difference(s) between these are the optimization options and the generation of debug info.
To configure SDL as an optimized Release library, configure SDL with:
cmake ~/SDL -DCMAKE_BUILD_TYPE=ReleaseTo build it, run:
cmake --build . --config ReleaseShared or static
By default, only a dynamic (=shared) SDL library is built and installed.
The options -DSDL_SHARED= and -DSDL_STATIC= accept boolean values to change this.
Exceptions exist:
- some platforms don't support dynamic libraries, so only
-DSDL_STATIC=ONmakes sense. - a static Apple framework is not supported
Man pages
Configuring with -DSDL_INSTALL_DOCS=TRUE installs man pages.
We recommend package managers of unix distributions to install SDL3's man pages.
This adds an extra build-time dependency on Perl.
Pass custom compile options to the compiler
- Use
CMAKE_<LANG>_FLAGSto pass extra
flags to the compiler. - Use
CMAKE_EXE_LINKER_FLAGSto pass extra option to the linker for executables. - Use
CMAKE_SHARED_LINKER_FLAGSto pass extra options to the linker for shared libraries.
Compile Options Examples
build a SDL library optimized for (more) modern x64 microprocessor architectures.
With gcc or clang:
shcmake ~/sdl -DCMAKE_C_FLAGS="-march=x86-64-v3" -DCMAKE_CXX_FLAGS="-march=x86-64-v3"With Visual C:
shcmake .. -DCMAKE_C_FLAGS="/ARCH:AVX2" -DCMAKE_CXX_FLAGS="/ARCH:AVX2"
Apple
CMake documentation for cross building for Apple:
link
iOS/tvOS/visionOS
CMake 3.14+ natively includes support for iOS, tvOS and watchOS. visionOS requires CMake 3.28+.
SDL binaries may be built using Xcode or Make, possibly among other build-systems.
When using a compatible version of CMake, it should be possible to:
- build SDL dylibs, both static and dynamic dylibs
- build SDL frameworks, only shared
- build SDL test apps
Frameworks
Configure with -DSDL_FRAMEWORK=ON to build a SDL framework instead of a dylib shared library.
Only shared frameworks are supported, no static ones.
Platforms
Use -DCMAKE_SYSTEM_NAME=<value> to configure the platform. CMake can target only one platform at a time.
| Apple platform | CMAKE_SYSTEM_NAME value |
|---|---|
| macOS (MacOS X) | Darwin |
| iOS | iOS |
| tvOS | tvOS |
| visionOS | visionOS |
| watchOS | watchOS |
Universal binaries
A universal binaries, can be built by configuring CMake with-DCMAKE_OSX_ARCHITECTURES=<semicolon-separated list of CPU architectures>.
For example -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" will build binaries that run on both Intel cpus and Apple silicon.
SDL supports following Apple architectures:
| Platform | CMAKE_OSX_ARCHITECTURES value |
|---|---|
| 64-bit ARM (Apple Silicon) | arm64 |
| x86_64 | x86_64 |
| 32-bit ARM | armv7s |
CMake documentation: link
Simulators and/or non-default macOS platform SDK
Use -DCMAKE_OSX_SYSROOT=<value> to configure a different platform SDK.
The value can be either the name of the SDK, or a full path to the sdk (e.g. /full/path/to/iPhoneOS.sdk).
| SDK | CMAKE_OSX_SYSROOT value |
|---|---|
| iphone | iphoneos |
| iphonesimulator | iphonesimulator |
| appleTV | appletvos |
| appleTV simulator | appletvsimulator |
| visionOS | xr |
| visionOS simulator | xrsimulator |
| watchOS | watchos |
| watchOS simulator | watchsimulator |
Append with a version number to target a specific SDK revision: e.g. iphoneos12.4, appletvos12.4.
CMake documentation: link
Apple Examples
for macOS, building a dylib and/or static library for x86_64 and arm64:
bashcmake ~/sdl -DCMAKE_SYSTEM_NAME=Darwin -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -DCMAKE_OSX_DEPLOYMENT_TARGET=10.11for macOS, building an universal framework for x86_64 and arm64:
bashcmake ~/sdl -DSDL_FRAMEWORK=ON -DCMAKE_SYSTEM_NAME=Darwin -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -DCMAKE_OSX_DEPLOYMENT_TARGET=10.11for iOS-Simulator, using the latest, installed SDK:
bashcmake ~/sdl -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_SYSROOT=iphonesimulator -DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_OSX_DEPLOYMENT_TARGET=9.0for iOS-Device, using the latest, installed SDK, 64-bit only
bashcmake ~/sdl -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_SYSROOT=iphoneos -DCMAKE_OSX_ARCHITECTURES=arm64 -DCMAKE_OSX_DEPLOYMENT_TARGET=9.0for iOS-Device, using the latest, installed SDK, mixed 32/64 bit
cmakecmake ~/sdl -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_SYSROOT=iphoneos -DCMAKE_OSX_ARCHITECTURES="arm64;armv7s" -DCMAKE_OSX_DEPLOYMENT_TARGET=9.0for iOS-Device, using a specific SDK revision (iOS 12.4, in this example):
cmakecmake ~/sdl -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_SYSROOT=iphoneos12.4 -DCMAKE_OSX_ARCHITECTURES=arm64for iOS-Simulator, using the latest, installed SDK, and building SDL test apps (as .app bundles):
cmakecmake ~/sdl -DSDL_TESTS=1 -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_SYSROOT=iphonesimulator -DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_OSX_DEPLOYMENT_TARGET=9.0for tvOS-Simulator, using the latest, installed SDK:
cmakecmake ~/sdl -DCMAKE_SYSTEM_NAME=tvOS -DCMAKE_OSX_SYSROOT=appletvsimulator -DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_OSX_DEPLOYMENT_TARGET=9.0for tvOS-Device, using the latest, installed SDK:
cmakecmake ~/sdl -DCMAKE_SYSTEM_NAME=tvOS -DCMAKE_OSX_SYSROOT=appletvos -DCMAKE_OSX_ARCHITECTURES=arm64` -DCMAKE_OSX_DEPLOYMENT_TARGET=9.0for QNX/aarch64, using the latest, installed SDK:
cmakecmake ~/sdl -DCMAKE_TOOLCHAIN_FILE=~/sdl/build-scripts/cmake-toolchain-qnx-aarch64le.cmake -DSDL_X11=0
SDL-specific CMake options
SDL can be customized through (platform-specific) CMake options.
The following table shows generic options that are available for most platforms.
At the end of SDL CMake configuration, a table shows all CMake options along with its detected value.
| CMake option | Valid values | Description |
|---|---|---|
-DSDL_SHARED= |
ON/OFF |
Build SDL shared library (not all platforms support this) (libSDL3.so/libSDL3.dylib/SDL3.dll) |
-DSDL_STATIC= |
ON/OFF |
Build SDL static library (libSDL3.a/SDL3-static.lib) |
-DSDL_TEST_LIBRARY= |
ON/OFF |
Build SDL test library (libSDL3_test.a/SDL3_test.lib) |
-DSDL_TESTS= |
ON/OFF |
Build SDL test programs (requires -DSDL_TEST_LIBRARY=ON) |
-DSDL_DISABLE_INSTALL= |
ON/OFF |
Don't create a SDL install target |
-DSDL_DISABLE_INSTALL_DOCS= |
ON/OFF |
Don't install the SDL documentation |
-DSDL_INSTALL_TESTS= |
ON/OFF |
Install the SDL test programs |
Incompatibilities
`SDL_LIBC=OFF` and sanitizers
Building with -DSDL_LIBC=OFF will make it impossible to use the sanitizer, such as the address sanitizer.
Configure your project with -DSDL_LIBC=ON to make use of sanitizers.
CMake FAQ
CMake fails to build without X11 or Wayland support
Install the required system packages prior to running CMake.
See README-linux.md for the list of dependencies on Linux.
Other unix operating systems should provide similar packages.
If you really don't need to show windows, add -DSDL_UNIX_CONSOLE_BUILD=ON to the CMake configure command.
How do I copy a SDL3 dynamic library to another location?
Use CMake generator expressions.
Generator expressions support multiple configurations, and are evaluated during build system generation time.
On Windows, the following example copies SDL3.dll to the directory where mygame.exe is built.
if(WIN32)
add_custom_command(
TARGET mygame POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy $<TARGET_FILE:SDL3::SDL3-shared> $<TARGET_FILE_DIR:mygame>
VERBATIM
)
endif()On Unix systems, $<TARGET_FILE:...> will refer to the dynamic library (or framework),
and you might need to use $<TARGET_SONAME_FILE:tgt> instead.
Most often, you can avoid copying libraries by configuring your project with absolute CMAKE_LIBRARY_OUTPUT_DIRECTORY
and CMAKE_RUNTIME_OUTPUT_DIRECTORY paths.
When using a multi-config generator (such as Visual Studio or Ninja Multi-Config), eventually add /$<CONFIG> to both paths.
Linking against a static SDL library fails due to relocation errors
On unix platforms, all code that ends up in shared libraries needs to be built as relocatable (=position independent) code.
However, by default CMake builds static libraries as non-relocatable.
Configuring SDL with -DCMAKE_POSITION_INDEPENDENT_CODE=ON will result in a static libSDL3.a library
which you can link against to create a shared library.
README Contributing (`docs/README-contributing.md`)
Contributing to SDL
We appreciate your interest in contributing to SDL, this document will describe how to report bugs, contribute code or ideas or edit documentation.
Table Of Contents
Filing a GitHub issue
Reporting a bug
If you think you have found a bug and would like to report it, here are the steps you should take:
- Before opening a new issue, ensure your bug has not already been reported on the GitHub Issues page.
- On the issue tracker, click on New Issue.
- Include details about your environment, such as your Operating System and SDL version.
- If possible, provide a small example that reproduces your bug.
Suggesting enhancements
If you want to suggest changes for the project, here are the steps you should take:
- Check if the suggestion has already been made on:
- the issue tracker;
- the discourse forum;
- or if a pull request already exists.
- On the issue tracker, click on New Issue.
- Describe what change you would like to happen.
Contributing code
This section will cover how the process of forking the project, making a change and opening a pull request.
Forking the project
The first step consists in making a fork of the project, this is only necessary for the first contribution.
Head over to https://github.com/libsdl-org/SDL and click on the Fork button in the top right corner of your screen, you may leave the fields unchanged and click Create Fork.
You will be redirected to your fork of the repository, click the green Code button and copy the git clone link.
If you had already forked the repository, you may update it from the web page using the Fetch upstream button.
Following the style guide
Code formatting is done using a custom .clang-format file, you can learn more about how to run it here.
Some legacy code may not be formatted, so please avoid formatting the whole file at once and only format around your changes.
For your commit message to be properly displayed on GitHub, it should contain:
- A short description of the commit of 50 characters or less on the first line.
- If necessary, add a blank line followed by a long description, each line should be 72 characters or less.
For example:
Fix crash in SDL_FooBar.
This addresses the issue #123456 by making sure Foo was successful
before calling Bar.Running the tests
Tests allow you to verify if your changes did not break any behaviour, here are the steps to follow:
- Before pushing, run the
testautomationsuite on your machine, there should be no more failing tests after your change than before. - After pushing to your fork, Continuous Integration (GitHub Actions) will ensure compilation and tests still pass on other systems.
Opening a pull request
- Head over to your fork's GitHub page.
- Click on the
Contributebutton andOpen Pull Request. - Fill out the pull request template.
- If any changes are requested, you can add new commits to your fork and they will be automatically added to the pull request.
Continuous integration
For each push and/or pull request, GitHub Actions will try to build SDL and the test suite on most supported platforms.
Its behaviour can be influenced slightly by including SDL-specific tags in your commit message:
[sdl-ci-filter GLOB]limits the platforms for which to run ci.[sdl-ci-artifacts]forces SDL artifacts, which can then be downloaded from the summary page.[sdl-ci-trackmem-symbol-names]makes sure the final report generated by--trackmemcontains symbol names.[sdl-ci-ctest-args]allows overriding the ctest arguments. Adding--repeat-until-fail Nand-R NAMEis useful.
Contributing to the documentation
Editing a function documentation
The wiki documentation for API functions is synchronised from the headers' doxygen comments. As such, all modifications to syntax; function parameters; return value; version; related functions should be done in the header directly.
Editing the wiki
Other changes to the wiki should done directly from https://wiki.libsdl.org/ ... Just click the "edit" link at the bottom of any page!
README Documentation Rules (`docs/README-documentation-rules.md`)
Rules for documentation
These are the rules for the care and feeding of wikiheaders.pl.
No style guide
When adding or editing documentation, we don't (currently) have a style guide
for what it should read like, so try to make it consistent with the rest of
the existing text. It generally should read more like technical reference
manuals and not sound conversational in tone.
Most of these rules are about how to make sure the documentation works on
a technical level, as scripts need to parse it, and there are a few simple
rules we need to obey to cooperate with those scripts.
The wiki and headers share the same text.
There is a massive Perl script (build-scripts/wikiheaders.pl, hereafter
referred to as "wikiheaders") that can read both the wiki and the public
headers, and move changes in one across to the other.
If you prefer to use the wiki, go ahead and edit there. If you prefer to use
your own text editor, or command line tools to batch-process text, etc, you
can clone the wiki as a git repo and
work locally.
Don't taunt wikiheaders.
The script isn't magic; it's a massive pile of Regular Expressions and not
a full C or markdown parser. While it isn't fragile, if you try to do clever
things, you might confuse it. This is to the benefit of documentation, though,
where we would rather you not do surprising things.
UTF-8 only!
All text must be UTF-8 encoded. The wiki will refuse to update files that are
malformed.
We _sort of_ write in Doxygen format.
To document a symbol, we use something that looks like Doxygen (and Javadoc)
standard comment format:
/**
* This is a function that does something.
*
* It can be used for frozzling bobbles. Be aware that the Frozulator module
* _must_ be initialized before calling this.
*
* \param frozzlevel The amount of frozzling to perform.
* \param color What color bobble to frozzle. 0 is red, 1 is green.
* \returns the number of bobbles that were actually frozzled, -1 on error.
*
* \threadsafety Do not call this from two threads at once, or the bobbles
* won't all frozzle correctly!
*
* \since This function is available since SDL 7.3.1.
*
* \sa SDL_DoSomethingElse
*/
extern SDL_DECLSPEC int SDLCALL SDL_DoSomething(int frozzlevel, int color);Note the /** at the start of the comment. That's a "Doxygen-style" comment,
and wikiheaders will treat this differently than a comment with one *, as
this signifies that this is not just a comment, but documentation.
These comments must start in the first column of the line, or wikiheaders
will ignore them, even with the "/**" start (we should improve the script
someday to handle this, but currently this is a requirement).
We do not parse every magic Doxygen tag, and we don't parse them in @param
format. The goal here was to mostly coexist with people that might want
to run Doxygen on the SDL headers, not to build Doxygen from scratch. That
being said, compatibility with Doxygen is not a hard requirement here.
wikiheaders uses these specific tags to turn this comment into a (hopefully)
well-formatted wiki page, and also can generate manpages and books in LaTeX
format from it!
Text markup in the headers is always done in Markdown format! But less is
more: try not to markup text more than necessary.
Doxygen tags we support:
\brief one-line description(Not required, and wikiheaders will remove tag).\param varname description(One for each function/macro parameter)\returns description(One for each function, don't use onvoidreturns).\sa(each of these get tucked into a "See Also" section on the wiki)\since This function is available since SDL 3.0.0.(one per Doxygen comment)\threadsafety description(one per function/macro).\deprecated description(one per symbol, if symbol is deprecated!)
Other Doxygen things might exist in the headers, but they aren't understood
by wikiheaders.
Use Markdown.
The wiki also supports MediaWiki format, but we are transitioning away from it.
The headers always use Markdown. If you're editing the wiki from a git clone,
just make .md files and the wiki will know what to do with them.
Most things in the headers can be documented.
wikiheaders understands functions, typedefs, structs/unions/enums, #defines
... basically most of what makes up a C header. Just slap a Doxygen-style
comment in front of most things and it'll work.
Defines right below typedefs and functions bind.
Any #define directly below a function or non-struct/union/enum typedef is
considered part of that declaration. This happens to work well with how our
headers work, as these defines tend to be bitflags and such that are related
to that symbol.
wikiheaders will include those defines in the syntax section of the wiki
page, and generate stub pages for each define that simply says "please refer
to (The Actual Symbol You Care About)" with a link. It will also pull in
any blank lines and most preprocessor directives for the syntax text, too.
Sometimes an unrelated define, by itself, just happens to be right below one
of these symbols in the header. The easiest way to deal with this is either
to document that define with a Doxygen-style comment, if it makes sense to do
so, or just add a normal C comment right above it if not, so wikiheaders
doesn't bind it to the previous symbol.
Don't document the `SDL_test*.h` headers.
These are in the public headers but they aren't really considered public APIs.
They live in a separate library that doesn't, or at least probably shouldn't,
ship to end users. As such, we don't want it documented on the wiki.
For now, we do this by not having any Doxygen-style comments in these files.
Please keep it that way! If you want to document these headers, just don't
use the magic two-* comment.
The first line is the summary.
The first line of a piece of documentation is meant to be a succinct
description. This is what Doxygen would call the \brief tag. wikiheaders
will split this text out until the first period (end of sentence!), and when
word wrapping, shuffle the overflow into a new paragraph below it.
Split paragraphs with a blank line.
And don't indent them at all (indenting in Markdown is treated as preformatted
text).
wikiheaders will wordwrap header comments so they fit in 80 columns, so if you
don't leave a blank line between paragraphs, they will smush into a single
block of text when wordwrapping.
Lists must be the start of a new paragraph.
If you write this:
Here is some text without a blank line
before an unordered list!
- item a
- item b
- item c...then wikiheaders will word wrap this as a single paragraph, mangling the list.
Put a blank line before the list, and everything will format and wrap correctly.
This is a limitation of wikiheaders. Don't get bit by it!
Don't worry about word wrapping.
If you don't word-wrap your header edits perfectly (and you won't, I promise),
wikiheaders will send your change to the wiki, and then to make things match,
send it right back to the headers with correct word wrapping. Since this
happens right after you push your changes, you might as well just write
however you like and assume the system will clean it up for you.
Things that start with `SDL_` will automatically become wiki links.
wikiheaders knows to turn these into links to other pages, so if you reference
an SDL symbol in the header documentation, you don't need to link to it.
You can optionally wrap the symbol in backticks, and wikiheaders will know to
link the backticked thing. It will not generate links in three-backtick
code/preformatted blocks.
URLs will automatically become links.
You can use Markdown's [link markup format](https://example.com/), but
sometimes it's clearer to list bare URLs; the URL will be visible on the
wiki page, but also clickable to follow the link. This is up to your judgment
on a case-by-case basis.
Hide stuff from wikiheaders.
If all else fails, you can block off pieces of the header with this
magic line (whitespace is ignored):
#ifndef SDL_WIKI_DOCUMENTATION_SECTIONEverything between this line and the next #endif will just be skipped by
wikiheaders. Note that wikiheaders is not a C preprocessor! Don't try to
nest conditionals or use !defined.
Just block off sections if you need to. And: you almost never need to.
Hide stuff from the compiler.
If you need to put something that's only of interest to wikiheaders, the
convention is to put it in a block like this:
#ifdef SDL_WIKI_DOCUMENTATION_SECTIONGenerally this is used when there's a collection of preprocessor conditionals
to define the same symbol differently in different circumstances. You put
that symbol in this block with some reasonable generic version and the
Doxygen-style comment. Because wikiheaders doesn't care about this
preprocessor magic, and the C compiler can be as fancy as it wants, this is
strictly a useful convention.
Struct/union/enum typedefs must have the name on the first line.
This is because wikiheaders is not a full C parser. Don't write this:
typedef struct
{
int a;
int b;
} SDL_MyStruct;...make sure the name is at the start, too:
typedef struct SDL_MyStruct
{
int a;
int b;
} SDL_MyStruct;wikiheaders will complain loudly if you don't do this, and exit with an
error message.
Don't repeat type names in `\param` and `\returns` sections.
Wikiheaders will explicitly mention the datatype for each parameter and the
return value, linking to the datatype's wikipage. Users reading the headers
can see the types in the function signature right below the documentation
comment. So don't mention the type a second time in the documentation if
possible. It looks cluttered and repetitive to do so.
Keep `\param` and `\returns` sections short.
These strings end up in a table that we don't want to be bulky.
Try to keep these to one sentence/phrase where possible. If you need more
detail--even extremely common details, like "you need to free the returned
pointer"--put that information in the general Remarks section, where you
can be as verbose as you like.
(One exception for SDL: the return value almost always notes that on error,
you should call SDL_GetError() to get more information. The documentation
is so saturated with this that it's just the standard now.)
Convention at the moment is that pointer params that are permitted to
be NULL, which is somewhat uncommon, end with terse "May be NULL." sentence
at the end, and pointers that must be non-NULL (most of them) say nothing.
This is fine.
Code examples go in the wiki.
We don't want the headers cluttered up with code examples. These live on the
wiki pages, and wikiheaders knows to not bridge them back to the headers.
Put them in a ## Code Examples section, and make sure to wrap them in a
three-backtick-c section for formatting purposes. Only write code in C,
please.
Do you _need_ a code example?
Most code examples aren't actually useful. If your code example is justSDL_CreateWindow("Hello SDL", 640, 480, 0); then just delete it; if all
you're showing is how to call a function in C, it's not a useful code example.
Not all functions need an example. One with complex setup or usage details
might, though!
Code examples are compiled by GitHub Actions.
On each change to the wiki, there is a script that pulls out all the code
examples into discrete C files and attempts to compile them, and complains
if they don't work.
Unrecognized sections are left alone in the wiki.
A wiki section that starts with ## Section Name (or == Section Name == in
MediaWiki format) that isn't one of the recognized names will be left alone
by wikiheaders. Recognized sections might get overwritten with new content
from the headers, but the wiki file will not have other sections cleaned out
(this is how Code Examples remain wiki only, for example). You can use this
to add Wiki-specific text, or stuff that doesn't make sense in a header, or
would merely clutter it up.
A possibly-incomplete list of sections that will be overwritten by changes
to the headers:
- The page title line, and the "brief" one-sentence description section.
- "Deprecated"
- "Header File"
- "Syntax"
- "Function Parameters"
- "Macro Parameters"
- "Fields"
- "Values"
- "Return Value"
- "Remarks"
- "Thread Safety"
- "Version"
- "See Also"
Unrecognized sections are removed from the headers!
If you add Doxygen with a ## (###, etc) section header, it'll
migrate to the wiki and be removed from the headers. Generally
the correct thing to do is never use section headers in the Doxygen.
wikiheaders will reorder standard sections.
The standard sections are always kept in a consistent order by
wikiheaders, both in the headers and the wiki. If they're placed in
a non-standard order, wikiheaders will reorder them.
For sections that aren't standard, wikiheaders will place them at
the end of the wiki page, in the order they were seen when it loaded
the page for processing.
It's okay to repeat yourself.
Each individual piece of documentation becomes a separate page on the wiki, so
small repeated details can just exist in different pieces of documentation. If
it's complicated, it's not unreasonable to say "Please refer to
SDL_SomeOtherFunction for more details" ... wiki users can click right
through, header users can search for the function name.
The docs directory is bridged to the wiki, too.
You might be reading this document on the wiki! Any README-*.md files in
the docs directory are bridged to the wiki, so docs/README-linux.md lands
at https://wiki.libsdl.org/SDL3/README-linux ...these are just copied directly
without any further processing by wikiheaders, and changes go in both
directions.
The wiki can have its own pages, too.
If a page name isn't a symbol that wikiheaders sees in the headers, or a
README in the source's docs directory, or a few other exceptions, it'll
assume it's an unrelated wiki page and leave it alone. So feel free to
write any wiki-only pages that make sense and not worry about it junking
up the headers!
Wiki categories are (mostly) managed automatically.
The wiki will see this pattern as the last thing on a page and treat it as a
list of categories that page belongs to:
----
[CategoryStuff](CategoryStuff), [CategoryWhatever](CategoryWhatever)You can use this to simply tag a page as part of a category, and the user can
click directly to see other pages in that category. The wiki will
automatically manage a Category* pages that list any tagged pages.
You should not add tags to the public headers. They don't mean anything
there. wikiheaders will add a few tags that make sense when generating wiki
content from the header files, and it will preserve other tags already present
on the page, so if you want to add extra categories to something, tag it on
the wiki itself.
The wiki uses some magic HTML comment tags to decide how to list items on
Category pages and let other content live on the page as well. You can
see an example of this in action at:
https://raw.githubusercontent.com/libsdl-org/sdlwiki/main/SDL3/CategoryEvents.md
Categorizing the headers.
To put a symbol in a specific category, we use three approaches in SDL:
- Things in the
SDL_test*.hheaders aren't categorized at all (and you
shouldn't document them!) - Most files are categorized by header name: we strip off the leading
SDL_
and capitalize the first letter of what's left. So everything in SDL_audio.h
is in the "Audio" category, everything in SDL_video.h is in the "Video"
category, etc. - If wikiheaders sees a comment like this on a line by itself......then all symbols below that will land in the "Blah" category. We use thisc
/* WIKI CATEGORY: Blah */
at the top of a few headers where the simple
chop-off-SDL_-and-captialize-the-first-letter trick doesn't work well, but
one could theoretically use this for headers that have some overlap in
category.
Category documentation lives in headers.
To document a category (text that lives before the item lists on a wiki
category page), you have to follow a simple rule:
The first Doxygen-style comment in a header must start with:
/**
* # CategoryABCIf these conditions aren't met, wikiheaders will assume that documentation
belongs to whatever is below it instead of the Category.
The text of this comment will be added to the appropriate wiki Category page,
at the top, replacing everything in the file until it sees a line that starts
with an HTML comment (<!--), or a line that starts with ----. Everything
after that in the wiki file will be preserved.
Likewise, when bridging back to the headers, if wikiheaders sees one of
these comments, it'll copy the top section of the Category page back into the
comment.
Beyond stripping the initial * portion off each line, these comments are
treated as pure Markdown. They don't support any Doxygen tags like \sa or\since.
Enum/struct versioning
If you have an enum or struct, it'll list its \since field as the first SDL
release it was available in. However, we might later add new values to an enum
or fields to a struct. These lines, arriving in a newer version, should have a
note about that, like this one on SDL_SCALEMODE_PIXELART:
typedef enum SDL_ScaleMode
{
SDL_SCALEMODE_INVALID = -1,
SDL_SCALEMODE_NEAREST, /**< nearest pixel sampling */
SDL_SCALEMODE_LINEAR, /**< linear filtering */
SDL_SCALEMODE_PIXELART /**< nearest pixel sampling with improved scaling for pixel art (since SDL 3.3.0) */
} SDL_ScaleMode;README Dos (`docs/README-dos.md`)
DOS
SDL port for MS-DOS using the DJGPP GCC cross-compiler.
Building
To build for DOS, make sure you have a DJGPP cross-compiler in your PATH and run:
cmake -S. -Bbuild -DCMAKE_TOOLCHAIN_FILE=build-scripts/i586-pc-msdosdjgpp.cmake -DCMAKE_BUILD_TYPE=Release
cmake --build buildThe toolchain file looks for i586-pc-msdosdjgpp-gcc or i386-pc-msdosdjgpp-gcc in PATH.
Running
DOS executables require a DPMI host. Place CWSDPMI.EXE next to your executable.
To run in DOSBox:
dosbox myapp.exeSystem Requirements
| Component | Minimum |
|---|---|
| CPU | i386 or higher |
| RAM | 4 MB |
| Video | VGA (256-color mode 13h) |
| Audio | Sound Blaster |
| DPMI | CWSDPMI.exe or compatible host |
Higher resolutions (640×480 and above) require a VESA VBE 1.2+ compatible video card.
Notes
Memory Model
The DOS port produces 32-bit protected-mode DPMI executables. It uses the "fat DS" nearptr trick (__djgpp_nearptr_enable()) to convert physical addresses to usable C pointers. This allows direct access to the VESA linear framebuffer and DMA buffers from C code without segment descriptor manipulation.
SDL on DOS requires the fat DS trick. SDL_RunApp() enables it automatically via SDL_main.h. If you define SDL_MAIN_HANDLED, you must call __djgpp_nearptr_enable() yourself before initializing SDL, or video initialization will fail with a clear error message.
Threading
DOS has no OS-level threads. SDL on DOS implements cooperative threading via a mini-scheduler that uses setjmp/longjmp for context switching. Threads are never preempted mid-instruction. Context switches occur only at explicit yield points such as SDL_Delay and the event pump, so make sure your main loop calls SDL_PumpEvents or SDL_Delay regularly. SDL_Delay(0) yields to other threads without sleeping and is safe to call in tight loops. In some cases, like a load screen, a longer delay (e.g. SDL_Delay(16)) may be needed to give background threads enough CPU time.
Video
The video driver supports VGA mode 13h (320×200×256) on any VGA card, and higher resolutions via VESA BIOS Extensions (VBE 1.2+). Both linear framebuffer (VBE 2.0+) and banked framebuffer modes are supported. Hardware page-flipping is used for tear-free rendering when available.
Only software rendering is supported. There is no GPU renderer.
All video modes are effectively fullscreen. When creating a window, the driver selects the closest available video mode to the requested size.
8-bit indexed color (INDEX8) modes with programmable VGA DAC palettes are supported but will only be used when explicitly requested via the SDL_PIXELFORMAT window creation property.
EGA and CGA cards are not supported. The driver detects VGA hardware at initialization and will fail with a clear error message if VGA is not present.
Direct Framebuffer Hint
Setting SDL_HINT_DOS_ALLOW_DIRECT_FRAMEBUFFER to "1" before calling SDL_GetWindowSurface() enables a fast path that skips the normal surface copy. SDL_UpdateWindowSurface() copies the system-RAM surface directly to VRAM via dosmemput and only programs the VGA DAC palette when it changes. Page flipping is done without waiting for vblank. No software cursor compositing is performed.
This mode is designed for applications like Quake that manage their own rendering and want maximum frame throughput. The trade-offs are:
- No vsync. Tearing is expected.
- No software cursor. The application must draw its own cursor if needed.
- Reading back the surface may be slow on real hardware (uncached VRAM).
The hint must be set before the first call to SDL_GetWindowSurface(). Changing it after that has no effect.
Audio
Sound Blaster support is available for SB16 (16-bit stereo), SB Pro (8-bit stereo), and SB 2.0/1.x (8-bit mono). Configured automatically from the BLASTER environment variable.
A ring buffer sits between the SDL audio pipeline and the DMA hardware. The audio thread fills the ring buffer cooperatively, and the Sound Blaster IRQ handler copies data from the ring buffer to the DMA buffer directly. This gives roughly 45 ms of cushion before audio would stutter. If your game runs at 22 fps or above, audio will be glitch-free with no extra effort. Below 20 fps, adding a SDL_Delay(0) in the middle of your game loop should be enough to keep the ring buffer fed.
Audio recording is not implemented.
Input
- Keyboard: IRQ1-driven with full extended scancode (0xE0 prefix) support.
- Mouse: INT 33h mouse driver with relative motion via mickeys.
- Joystick: gameport joystick via direct port 0x201 reads with software calibration. The joystick must be connected at init; there is no hot-plug detection. Note that disconnecting the joystick can degrade performance.
Limitations
- No shared library / dynamic loading support (no
SDL_LoadObject). DXE support may be added in the future.
README Dynapi (`docs/README-dynapi.md`)
Dynamic API
Originally posted on Ryan's Google+ account.
Background:
- The Steam Runtime has (at least in theory) a really kick-ass build of SDL,
but developers are shipping their own SDL with individual Steam games.
These games might stop getting updates, but a newer SDL might be needed later.
Certainly we'll always be fixing bugs in SDL, even if a new video target isn't
ever needed, and these fixes won't make it to a game shipping its own SDL. - Even if we replace the SDL in those games with a compatible one, that is to
say, edit a developer's Steam depot (yuck!), there are developers that are
statically linking SDL that we can't do this for. We can't even force the
dynamic loader to ignore their SDL in this case, of course. - If you don't ship an SDL with the game in some form, people that disabled the
Steam Runtime, or just tried to run the game from the command line instead of
Steam might find themselves unable to run the game, due to a missing dependency. - If you want to ship on non-Steam platforms like GOG or Humble Bundle, or target
generic Linux boxes that may or may not have SDL installed, you have to ship
the library or risk a total failure to launch. So now, you might have to have
a non-Steam build plus a Steam build (that is, one with and one without SDL
included), which is inconvenient if you could have had one universal build
that works everywhere. - We like the zlib license, but the biggest complaint from the open source
community about the license change is the static linking. The LGPL forced this
as a legal, not technical issue, but zlib doesn't care. Even those that aren't
concerned about the GNU freedoms found themselves solving the same problems:
swapping in a newer SDL to an older game often times can save the day.
Static linking stops this dead.
So here's what we did:
SDL now has, internally, a table of function pointers. So, this is what SDL_Init
now looks like:
bool SDL_Init(SDL_InitFlags flags)
{
return jump_table.SDL_Init(flags);
}Except that is all done with a bunch of macro magic so we don't have to maintain
every one of these.
What is jump_table.SDL_init()? Eventually, that's a function pointer of the real
SDL_Init() that you've been calling all this time. But at startup, it looks more
like this:
bool SDL_Init_DEFAULT(SDL_InitFlags flags)
{
SDL_InitDynamicAPI();
return jump_table.SDL_Init(flags);
}SDL_InitDynamicAPI() fills in jump_table with all the actual SDL function
pointers, which means that this _DEFAULT function never gets called again.
First call to any SDL function sets the whole thing up.
So you might be asking, what was the value in that? Isn't this what the operating
system's dynamic loader was supposed to do for us? Yes, but now we've got this
level of indirection, we can do things like this:
export SDL3_DYNAMIC_API=/my/actual/libSDL3.so.0
./MyGameThatIsStaticallyLinkedToSDLAnd now, this game that is statically linked to SDL, can still be overridden
with a newer, or better, SDL. The statically linked one will only be used as
far as calling into the jump table in this case. But in cases where no override
is desired, the statically linked version will provide its own jump table,
and everyone is happy.
So now:
- Developers can statically link SDL, and users can still replace it.
(We'd still rather you ship a shared library, though!) - Developers can ship an SDL with their game, Valve can override it for, say,
new features on SteamOS, or distros can override it for their own needs,
but it'll also just work in the default case. - Developers can ship the same package to everyone (Humble Bundle, GOG, etc),
and it'll do the right thing. - End users (and Valve) can update a game's SDL in almost any case,
to keep abandoned games running on newer platforms. - Everyone develops with SDL exactly as they have been doing all along.
Same headers, same ABI. Just get the latest version to enable this magic.
A little more about SDL_InitDynamicAPI():
Internally, InitAPI does some locking to make sure everything waits until a
single thread initializes everything (although even SDL_CreateThread() goes
through here before spinning a thread, too), and then decides if it should use
an external SDL library. If not, it sets up the jump table using the current
SDL's function pointers (which might be statically linked into a program, or in
a shared library of its own). If so, it loads that library and looks for and
calls a single function:
Sint32 SDL_DYNAPI_entry(Uint32 version, void *table, Uint32 tablesize);That function takes a version number (more on that in a moment), the address of
the jump table, and the size, in bytes, of the table.
Now, we've got policy here: this table's layout never changes; new stuff gets
added to the end. Therefore SDL_DYNAPI_entry() knows that it can provide all
the needed functions if tablesize <= sizeof its own jump table. If tablesize is
bigger (say, SDL 3.0.4 is trying to load SDL 3.0.3), then we know to abort, but
if it's smaller, we know we can provide the entire API that the caller needs.
The version variable is a failsafe switch.
Right now it's always 1. This number changes when there are major API changes
(so we know if the tablesize might be smaller, or entries in it have changed).
Right now SDL_DYNAPI_entry gives up if the version doesn't match, but it's not
inconceivable to have a small dispatch library that only supplies this one
function and loads different, otherwise-incompatible SDL libraries and has the
right one initialize the jump table based on the version. For something that
must generically catch lots of different versions of SDL over time, like the
Steam Client, this isn't a bad option.
Finally, I'm sure some people are reading this and thinking,
"I don't want that overhead in my project!"
To which I would point out that the extra function call through the jump table
probably wouldn't even show up in a profile, but lucky you: this can all be
disabled. You can build SDL without this if you absolutely must, but we would
encourage you not to do that. However, on heavily locked down platforms like
iOS, or maybe when debugging, it makes sense to disable it. The way this is
designed in SDL, you just have to change one #define, and the entire system
vaporizes out, and SDL functions exactly like it always did. Most of it is
macro magic, so the system is contained to one C file and a few headers.
However, this is on by default and you have to edit a header file to turn it
off. Our hopes is that if we make it easy to disable, but not too easy,
everyone will ultimately be able to get what they want, but we've gently
nudged everyone towards what we think is the best solution.
README Emscripten (`docs/README-emscripten.md`)
Emscripten
The state of things
(As of October 2024, but things move quickly and we don't update this
document often.)
In modern times, all the browsers you probably care about (Chrome, Firefox,
Edge, and Safari, on Windows, macOS, Linux, iOS and Android), support some
reasonable base configurations:
- WebAssembly (don't bother with asm.js any more)
- WebGL (which will look like OpenGL ES 2 or 3 to your app).
- Threads (see caveats, though!)
- Game controllers
- Autoupdating (so you can assume they have a recent version of the browser)
All this to say we're at the point where you don't have to make a lot of
concessions to get even a fairly complex SDL-based game up and running.
RTFM
This document is a quick rundown of some high-level details. The
documentation at emscripten.org is vast
and extremely detailed for a wide variety of topics, and you should at
least skim through it at some point.
Porting your app to Emscripten
Many many things just need some simple adjustments and they'll compile
like any other C/C++ code, as long as SDL was handling the platform-specific
work for your program.
First: assembly language code has to go. Replace it with C. You can even use
x86 SIMD intrinsic functions in Emscripten!
Second: Middleware has to go. If you have a third-party library you link
against, you either need an Emscripten port of it, or the source code to it
to compile yourself, or you need to remove it.
Third: If your program starts in a function called main(), you need to get
out of it and into a function that gets called repeatedly, and returns quickly,
called a mainloop.
Somewhere in your program, you probably have something that looks like a more
complicated version of this:
void main(void)
{
initialize_the_game();
while (game_is_still_running) {
check_for_new_input();
think_about_stuff();
draw_the_next_frame();
}
deinitialize_the_game();
}This will not work on Emscripten, because the main thread needs to be free
to do stuff and can't sit in this loop forever. So Emscripten lets you set up
a mainloop.
static void mainloop(void) /* this will run often, possibly at the monitor's refresh rate */
{
if (!game_is_still_running) {
deinitialize_the_game();
#ifdef __EMSCRIPTEN__
emscripten_cancel_main_loop(); /* this should "kill" the app. */
#else
exit(0);
#endif
}
check_for_new_input();
think_about_stuff();
draw_the_next_frame();
}
void main(void)
{
initialize_the_game();
#ifdef __EMSCRIPTEN__
emscripten_set_main_loop(mainloop, 0, 1);
#else
while (1) { mainloop(); }
#endif
}Basically, emscripten_set_main_loop(mainloop, 0, 1); says "runmainloop over and over until I end the program." The function will
run, and return, freeing the main thread for other tasks, and then
run again when it's time. The 1 parameter does some magic to make
your main() function end immediately; this is useful because you
don't want any shutdown code that might be sitting below this code
to actually run if main() were to continue on, since we're just
getting started.
Another option is to use SDL's main callbacks, which handle this for you
without platform-specific code in your app. Please refer to
the wiki
or docs/README-main-functions.md in the SDL source code.
There's a lot of little details that are beyond the scope of this
document, but that's the biggest initial set of hurdles to porting
your app to the web.
Do you need threads?
If you plan to use threads, they work on all major browsers now. HOWEVER,
they bring with them a lot of careful considerations. Rendering must
be done on the main thread. This is a general guideline for many
platforms, but a hard requirement on the web.
Many other things also must happen on the main thread; often times SDL
and Emscripten make efforts to "proxy" work to the main thread that
must be there, but you have to be careful (and read more detailed
documentation than this for the finer points).
Even when using threads, your main thread needs to set an Emscripten
mainloop (or use SDL's main callbacks) that runs quickly and returns, or
things will fail to work correctly.
You should definitely read Emscripten's pthreads docs
for all the finer points. Mostly SDL's thread API will work as expected,
but is built on pthreads, so it shares the same little incompatibilities
that are documented there, such as where you can use a mutex, and when
a thread will start running, etc.
IMPORTANT: You have to decide to either build something that uses
threads or something that doesn't; you can't have one build
that works everywhere. This is an Emscripten (or maybe WebAssembly?
Or just web browsers in general?) limitation. If you aren't using
threads, it's easier to not enable them at all, at build time.
If you use threads, you have to run from a web server that has
COOP/COEP headers set correctly
or your program will fail to start at all.
If building with threads, __EMSCRIPTEN_PTHREADS__ will be defined
for checking with the C preprocessor, so you can build something
different depending on what sort of build you're compiling.
Audio
Audio works as expected at the API level, but not exactly like other
platforms.
You'll only see a single default audio device. Audio recording also works;
if the browser pops up a prompt to ask for permission to access the
microphone, the SDL_OpenAudioDevice call will succeed and start producing
silence at a regular interval. Once the user approves the request, real
audio data will flow. If the user denies it, the app is not informed and
will just continue to receive silence.
Modern web browsers will not permit web pages to produce sound before the
user has interacted with them (clicked or tapped on them, usually); this is
for several reasons, not the least of which being that no one likes when a
random browser tab suddenly starts making noise and the user has to scramble
to figure out which and silence it.
SDL will allow you to open the audio device for playback in this circumstance,
and your audio streams will consume data, but SDL will throw the audio data
away until the user interacts with the page. This helps apps that depend on
the audio callback to make progress, and also keeps audio playback in sync
once the app is finally allowed to make noise.
There are two reasonable ways to deal with the silence at the app level:
if you are writing some sort of media player thing, where the user expects
there to be a volume control when you mouseover the canvas, just default
that control to a muted state; if the user clicks on the control to unmute
it, on this first click, adjust your app's volume appropriately, and SDL will
also start actually feeding the data to the browser. This allows the media to
play at start, and the user can reasonably opt-in to listening.
Many games do not have this sort of UI. For these, your best bet might be to
write a little Javascript that puts up a "Click here to play!" UI, and upon
the user clicking, remove that UI and then call the Emscripten app's main()
function. As far as the application knows, audio was able to play as soon as
the program started, and since this magic happens in a little Javascript, you
don't have to change your C/C++ code at all to make it happen.
Please see the discussion at https://github.com/libsdl-org/SDL/issues/6385
for some Javascript code to steal for this approach.
But if a game can just do without audio until the user clicks on the page,
it will still operate correctly, as if the page was merely muted before then.
Rendering
If you use SDL's 2D render API, it will use GLES2 internally, which
Emscripten will turn into WebGL calls. You can also use OpenGL ES 2
directly by creating a GL context and drawing into it.
If the browser (and hardware) support WebGL 2, you can create an OpenGL ES 3
context.
Calling SDL_RenderPresent (or SDL_GL_SwapWindow) will not actually
present anything on the screen until your return from your mainloop
function.
Note that SDL attempts to default to vsync off on all platforms. You almost
certainly do not want this in Emscripten, however, as it will affect the
efficiency of the mainloop. If using OpenGL directly, you should call
SDL_GL_SetSwapInterval(1) sometime near startup; if using the 2D render API,
either create the renderer with the property
SDL_PROP_RENDERER_CREATE_PRESENT_VSYNC_NUMBER set to 1, or call
SDL_SetRenderVSync(renderer, 1). If you don't explicitly set vsync, you'll get
a higher (but perhaps unstable) framerate, and use more power, but it will
still work. Choosing a vsync of 1 will use requestAnimationFrame if possible.
If you're using the SDL main callbacks, the mainloop defaults to using
requestAnimationFrame (effectively vsync), because it calls
emscripten_set_main_loop() with a zero fps. This is almost certainly what you
want to do! Do this even if you aren't using the main callbacks!
SDL will attempt to accommodate the app if it messes with vsync settings, or
doesn't use requestAnimationFrame, but modern thinking is that this is the
most efficient, consistent, and correct way to run a game in a web browser.
Building SDL/emscripten
Use the latest stable Emscripten release!
It's possible to build SDL with older Emscripten releases, such as 3.x, but
several things will be silently broken, as bugs got fixed and web standards
solidified over time. At the time of this writing, Emscripten 4.0.x is the
current stable release. You're encouraged to install the latest stable release
(emsdk install latest ; emsdk activate latest if using Emscripten's setup
script), and make sure you're reasonably up to date as time goes on.
Build:
This works on Linux/Unix and macOS. Please send comments about Windows.
Make sure you've installed emsdk
first, and run source emsdk_env.sh at the command line so it finds the
tools.
(These cmake options might be overkill, but this has worked for me.)
mkdir build
cd build
emcmake cmake ..
# you can also try `emcmake cmake -G Ninja ..` and then use `ninja` instead of this command.
emmake make -j4If you want to build with thread support, something like this works:
mkdir build
cd build
emcmake cmake -DSDL_PTHREADS=ON ..
# you can also do `emcmake cmake -G Ninja ..` and then use `ninja` instead of this command.
emmake make -j4To build the tests, add -DSDL_TESTS=ON to the emcmake cmake command line.
To build the examples, add -DSDL_EXAMPLES=ON to the emcmake cmake command line.
Building your app
You need to compile with emcc instead of gcc or clang or whatever, but
mostly it uses the same command line arguments as Clang.
Link against the libSDL3.a file you generated by building SDL.
Usually you would produce a binary like this:
gcc -o mygame mygame.c # or whateverBut for Emscripten, you want to output something else:
emcc -o index.html mygame.cThis will produce several files...support Javascript and WebAssembly (.wasm)
files. The -o index.html will produce a simple HTML page that loads and
runs your app. You will (probably) eventually want to replace or customize
that file and do -o index.js instead to just build the code pieces.
If you're working on a program of any serious size, you'll likely need to
link with -s ALLOW_MEMORY_GROWTH=1 -s MAXIMUM_MEMORY=1gb to get access
to more memory. If using pthreads, you'll need the -s MAXIMUM_MEMORY=1gb
or the app will fail to start on iOS browsers, but this might be a bug that
goes away in the future.
Data files
Your game probably has data files. Here's how to access them.
Filesystem access works like a Unix filesystem; you have a single directory
tree, possibly interpolated from several mounted locations, no drive letters,
'/' for a path separator. You can access them with standard file APIs like
open() or fopen() or SDL_IOStream. You can read or write from the filesystem.
By default, you probably have a "MEMFS" filesystem (all files are stored in
memory, but access to them is immediate and doesn't need to block). There are
other options, like "IDBFS" (files are stored in a local database, so they
don't need to be in RAM all the time and they can persist between runs of the
program, but access is not synchronous). You can mix and match these file
systems, mounting a MEMFS filesystem at one place and idbfs elsewhere, etc,
but that's beyond the scope of this document. Please refer to Emscripten's
page on the topic
for more info.
The easiest (but not the best) way to get at your data files is to embed
them in the app itself. Emscripten's linker has support for automating this.
emcc -o index.html loopwave.c --embed-file ../test/sample.wav@/sounds/sample.wavThis will pack ../test/sample.wav in your app, and make it available at
"/sounds/sample.wav" at runtime. Emscripten makes sure this data is available
before your main() function runs, and since it's in MEMFS, you can just
read it like you do on other platforms. --embed-file can also accept a
directory to pack an entire tree, and you can specify the argument multiple
times to pack unrelated things into the final installation.
Note that this is absolutely the best approach if you have a few small
files to include and shouldn't worry about the issue further. However, if you
have hundreds of megabytes and/or thousands of files, this is not so great,
since the user will download it all every time they load your page, and it
all has to live in memory at runtime.
Emscripten's documentation on the matter
gives other options and details, and is worth a read.
Please also read the next section on persistent storage, for a little help
from SDL.
Automount persistent storage
The file tree in Emscripten is provided by MEMFS by default, which stores all
files in RAM. This is often what you want, because it's fast and can be
accessed with the usual synchronous i/o functions like fopen or SDL_IOFromFile.
You can also write files to MEMFS, but when the browser tab goes away, so do
the files. But we want things like high scores, save games, etc, to still
exist if we reload the game later.
For this, Emscripten offers IDBFS, which backs files with the browser's
IndexedDB functionality.
To use this, the app has to mount the IDBFS filesystem somewhere in the
virtual file tree, and then wait for it to sync up. This needs to be done in
Javascript code. The sync will not complete until at least one (but possibly
several) iterations of the mainloop have passed, which means you can not
access any saved files during main() or SDL_AppInit() by default.
SDL can solve this problem for you: it can be built to automatically mount the
persistent files from IDBFS to a specific place in the file tree and wait
until the sync has completed before calling main() or SDL_AppInit(), so to
your C code, it looks like the files were always available.
To use this functionality, set the CMake variableSDL_EMSCRIPTEN_PERSISTENT_PATH to a path in the filetree where persistent
storage should be mounted:
mkdir build
cd build
emcmake cmake -DSDL_EMSCRIPTEN_PERSISTENT_PATH=/storage ..You should also link your app with -lidbfs.js. If your project links to SDL
using CMake's find_package(SDL3), or uses pkg-config sdl3 --libs, this will
be handled for you when used with an SDL built with-DSDL_EMSCRIPTEN_PERSISTENT_PATH.
Now /storage will be prepared when your program runs, and SDL_GetPrefPath()
will return a directory under that path. The storage is mounted with theautoPersist: true option, so when you write to that tree, whether with
SDL APIs or other functions like fopen(), Emscripten will know it needs to
sync that data back to the persistent database, and will do so automatically
within the next few iterations of the mainloop.
It's best to assume the sync will take a few frames to complete, and the
data is not safe until it does.
To summarize how to automate this:
- Build with
emcmake cmake -DSDL_EMSCRIPTEN_PERSISTENT_PATH=/storage - Link your app with
-lidbfs.jsif not handled automatically. - Write under
/storage, or use SDL_GetPrefPath()
Customizing index.html
You don't have to use the HTML that Emscripten produces; the above examples
use emcc -o index.html, but you can -o index.js instead to just output
code without an HTML page, and then provide your own. This is desirable for
shipping products, even though the Emscripten-provided HTML is fine for
prototyping. Certain things must be in the HTML file or your program will
not function correctly (or function at all). The specifics are beyond the
scope of this document, but it's likely best to start with the Emscripten HTML
and customize it, instead of starting from scratch.
The <canvas> element in the HTML must not have a border or padding, or
things will break in unexpected ways. This can be surprising when customizing
the page's look. Plan accordingly.
Debugging
Debugging web apps is a mixed bag. You should compile and link with-gsource-map, which embeds a ton of source-level debugging information into
the build, and make sure the app source code is available on the web server,
which is often a scary proposition for various reasons.
When you debug from the browser's tools and hit a breakpoint, you can step
through the actual C/C++ source code, though, which can be nice.
If you try debugging in Firefox and it doesn't work well for no apparent
reason, try Chrome, and vice-versa. These tools are still relatively new,
and improving all the time.
SDL_Log() (or printf) will write to the Javascript console,
so printf-style debugging can be easier than setting up a build
for proper debugging. Use whatever tools work best for you.
Questions?
Please give us feedback on this document at the SDL bug tracker.
If something is wrong or unclear, we want to know!
README Gdk (`docs/README-gdk.md`)
GDK
This port allows SDL applications to run via Microsoft's Game Development Kit (GDK).
Windows (GDK) and Xbox One/Xbox Series (GDKX) are both supported and all the required code is included in this public SDL release. However, only licensed Xbox developers have access to the GDKX libraries which will allow you to build the Xbox targets.
Requirements
- Microsoft Visual Studio 2022 (in theory, it should also work in 2019 or 2026, but this has not been tested)
- Microsoft GDK April 2026 Update 3 or newer (public release here)
- For Xbox, you will need the corresponding GDKX version (licensed developers only)
- To publish a package or successfully authenticate a user, you will need to create an app id/configure services in Partner Center. However, for local testing purposes (without authenticating on Xbox Live), the test identifiers used by the GDK test programs in the included solution work.
Windows GDK Status
The Windows GDK port supports the full set of Win32 APIs, renderers, controllers, input devices, etc., as the normal Windows x64 build of SDL.
Additionally, the GDK port adds the following:
Compile-time platform detection for SDL programs. The
SDL_PLATFORM_GDKis#defined on every GDK platform, and theSDL_PLATFORM_WINGDKis#defined on Windows GDK, specifically. (This distinction exists because other GDK platforms support a smaller subset of functionality. This allows you to mark code for "any" GDK separate from Windows GDK.)GDK-specific setup:
- Initializing/uninitializing the game runtime, and initializing Xbox Live services
- Creating a global task queue and setting it as the default for the process. When running any async operations, passing in
NULLas the task queue will make the task get added to the global task queue.
An implementation on
WinMainthat performs the above GDK setup that you can use by #include'ing SDL_main.h in the source file that includes your standard main() function. If you are unable to do this, you can instead manually callSDL_RunAppfrom your entry point, passing in yourSDL_mainfunction andNULLas the parameters. To useSDL_RunApp,#define SDL_MAIN_HANDLEDbefore#include <SDL3/SDL_main.h>.Global task queue callbacks are dispatched during
SDL_PumpEvents(which is also called internally if usingSDL_PollEvent).You can get the handle of the global task queue through
SDL_GetGDKTaskQueue, if needed. When done with the queue, be sure to useXTaskQueueCloseHandleto decrement the reference count (otherwise it will cause a resource leak).
Single-player games have some additional features available:
- Call
SDL_GetGDKDefaultUserto get the default XUserHandle pointer. SDL_GetPrefPathandSDL_Storagestill work, but only for single-player titles.- To enable support for these APIs, use
SDL_SetHintto setSDL_GDK_SERVICE_CONFIGURATION_IDto your SCID string at startup (it should be formatted like"00000000-0000-0000-0000-000000000000")
- To enable support for these APIs, use
These functions mostly wrap around async APIs, and thus should be treated as synchronous alternatives. Also note that the single-player functions return on any OS errors, so be sure to validate the return values!
- Call
What doesn't work:
- Compilation with anything other than through the included Visual C++ solution file
VisualC-GDK Solution
The included VisualC-GDK/SDL.sln solution includes the following targets for the Gaming.Desktop.x64 configuration:
- SDL3 (DLL) - This is the typical SDL3.dll, but for Gaming.Desktop.x64.
- tests/testgamecontroller - Standard SDL test program demonstrating controller functionality.
- tests/testgdk - GDK-specific test program that demonstrates using the global task queue to login a user into Xbox Live.
NOTE: As of the June 2022 GDK, you cannot test user logins without a valid Title ID and MSAAppId. You will need to manually change the identifiers in theMicrosoftGame.configto your valid IDs from Partner Center if you wish to test this. - tests/testsprite - Standard SDL test program demonstrating sprite drawing functionality.
If you set one of the test programs as a startup project, you can run it directly from Visual Studio.
Windows GDK Setup, Detailed Steps
These steps assume you already have a game using SDL that runs on Windows x64 along with a corresponding Visual Studio solution file for the x64 version. If you don't have this, it's easiest to use one of the test program vcxproj files in the VisualC-GDK directory as a starting point, though you will still need to do most of the steps below.
1. Add a Gaming.Desktop.x64 Configuration
In your game's existing Visual Studio Solution, go to Build > Configuration Manager. From the "Active solution platform" drop-down select "New...". From the drop-down list, select Gaming.Desktop.x64 and copy the settings from the x64 configuration.
2. Build SDL3 for GDK
Open VisualC-GDK/SDL.sln in Visual Studio, you need to build the SDL3 target for the Gaming.Desktop.x64 platform (Release is recommended). You will need to copy/keep track of the SDL3.dll, XCurl.dll (which is output by Gaming.Desktop.x64), and SDL3.lib output files for your game project.
Alternatively, you could setup your solution file to instead reference the SDL3 project file targets from the SDL source, and add those projects as a dependency. This would mean that SDL3 would be built when your game is built.
3. Configuring Project Settings
While the Gaming.Desktop.x64 configuration sets most of the required settings, there are some additional items to configure for your game project under the Gaming.Desktop.x64 Configuration:
- Under C/C++ > General > Additional Include Directories, make sure the
SDL/includepath is referenced - Under Linker > General > Additional Library Directories, make sure to reference the path where the newly-built SDL3.lib are
- Under Linker > Input > Additional Dependencies, you need the following:
SDL3.libxgameruntime.libMicrosoft.Xbox.Services.C.Thunks.lib
- Note that in general, the GDK libraries depend on the MSVC C/C++ runtime, so there is no way to remove this dependency from a GDK program that links against GDK.
4. Setting up SDL_main
Rather than using your own implementation of WinMain, it's recommended that you instead #include <SDL3/SDL_main.h> and declare a standard main function. If you are unable to do this, you can instead manually call SDL_RunApp from your entry point, passing in your SDL_main function and NULL as the parameters; in that case #define SDL_MAIN_HANDLED before including SDL_main.h
5. Required DLLs
The game will not launch in the debugger unless required DLLs are included in the directory that contains the game's .exe file. You need to make sure that the following files are copied into the directory:
- Your SDL3.dll
- Microsoft.Xbox.Services.C.Thunks.dll
- XCurl.dll
- libHttpClient.dll
6. Setting up MicrosoftGame.config
You can copy VisualC-GDK/tests/testgdk/MicrosoftGame.config and use that as a starting point in your project. Minimally, you will want to change the Executable Name attribute, the DefaultDisplayName, and the Description.
This file must be copied into the same directory as the game's .exe file. As with the DLLs, you can either use a post-build step or the "Copy file" item type.
For basic testing, you do not need to change anything else in MicrosoftGame.config. However, if you want to test any Xbox Live services (such as logging in users) or publish a package, you will need to setup a Game app on Partner Center.
Then, you need to set the following values to the values from Partner Center:
- Identity tag - Name and Publisher attributes
- TitleId
- MSAAppId
7. Adding Required Logos
Several logo PNG files are required to be able to launch the game, even from the debugger. You can use the sample logos provided in VisualC-GDK/logos. As with the other files, they must be copied into the same directory as the game's .exe file.
8. Copying any Data Files
When debugging GDK games, there is no way to specify a working directory. Therefore, any required game data must also be copied into the output directory, likely in a post-build step.
9. Build and Run from Visual Studio
At this point, you should be able to build and run your game from the Visual Studio Debugger. If you get any linker errors, make sure you double-check that you referenced all the required libs.
If you are testing Xbox Live functionality, it's likely you will need to change to the Sandbox for your title. To do this:
- Run "Desktop VS 2022 Gaming Command Prompt" from the Start Menu
- Switch the sandbox name with:
XblPCSandbox SANDBOX.# - (To switch back to the retail sandbox):
XblPCSandbox RETAIL
10. Packaging and Installing Locally
You can use one of the test program's PackageLayout.xml as a starting point. Minimally, you will need to change the exe to the correct name and also reference any required game data. As with the other data files, it's easiest if you have this copy to the output directory, although it's not a requirement as you can specify relative paths to files.
To create the package:
- Run "Desktop VS 2022 Gaming Command Prompt" from the Start Menu
cdto the directory containing thePackageLayout.xmlwith the correct paths (if you use the local path as in the sample package layout, this would be from your .exe output directory)mkdir Packageto create an output directory- To package the file into the
Packagedirectory, use:
makepkg pack /f PackageLayout.xml /lt /d . /nogameos /pc /pd Package - To install the package, use:
wdapp install PACKAGENAME.msixvc - Once the package is installed, you can run it from the start menu.
- As with when running from Visual Studio, if you need to test any Xbox Live functionality you must switch to the correct sandbox.
Xbox GDKX Setup
In general, the same process in the Windows GDK instructions work. There are just a few additional notes:
- For Xbox One consoles, use the Gaming.Xbox.XboxOne.x64 target
- For Xbox Series consoles, use the Gaming.Xbox.Scarlett.x64 target
- The Xbox One target sets the
SDL_PLATFORM_XBOXONEdefine and the Xbox Series target sets theSDL_PLATFORM_XBOXSERIESdefine - You don't need to link against the Xbox.Services Thunks lib nor include that dll in your package (it doesn't exist for Xbox)
- The shader blobs for Xbox are created in a pre-build step for the Xbox targets, rather than included in the source (due to NDA and version compatibility reasons)
- To create a package, use:
makepkg pack /f PackageLayout.xml /lt /d . /pd Package - To install the package, use:
xbapp install [PACKAGE].xvc - For some reason, if you make changes that require SDL3.dll to build, and you are running through the debugger (instead of a package), you have to rebuild your .exe target for the debugger to recognize the dll has changed and needs to be transferred to the console again
- While there are successful releases of Xbox titles using this port, it is not as extensively tested as other targets
Xbox Suspend/Resume Handling
Xbox certification requires that applications handle Suspend and Resume lifecycle events.
Please read the official GDK documentation for all the details, but here's what you absolutely need to know:
- When the operating system begins suspending an application, the app must complete all pending GPU operations and then signal to the OS that it is ready for suspension.
- When the OS resumes the application, the app must explicitly resume GPU operations.
- If any GPU API call happens in between suspension and resuming, the application will crash or hang.
Unfortunately it's impossible for SDL to know for certain when your application is ready for suspension, so it cannot handle these events internally.
Instead, SDL provides a few APIs you must use to handle these events yourself.
On startup, your application should call SDL_AddEventWatch to register a callback function which will listen for the SDL_EVENT_DID_ENTER_BACKGROUND and SDL_EVENT_WILL_ENTER_FOREGROUND events.
This callback function will be called from a background OS thread, so when one of these events occurs, you should notify your render thread about it rather than responding directly in the callback.
A simple strategy for this is to make your callback function post custom SDL_EVENT_USER events that your main loop's event processor can respond to.
When your render thread receives your notification of the SDL_EVENT_DID_ENTER_BACKGROUND event, it should do the following:
- Set a flag to suppress the entire render loop until the application resumes.
- Call
SDL_GDKSuspendRendererif you are using the Render API, or callSDL_GDKSuspendGPUif you are using the GPU API. - Finally, regardless of what graphics API you are using, call
SDL_GDKSuspendComplete.
When your render thread receives your notification of an SDL_EVENT_WILL_ENTER_FOREGROUND event, it should do the following:
- Call
SDL_GDKResumeRendererif you are using the Render API, or callSDL_GDKResumeGPUif you are using the GPU API. - Unset the flag suppressing your render loop to allow drawing again.
An example of this flow can be found in the testgdk project.
Troubleshooting
Xbox Live Login does not work
As of June 2022 GDK, you must have a valid Title Id and MSAAppId in order to test Xbox Live functionality such as user login. Make sure these are set correctly in the MicrosoftGame.config. This means that even testgdk will not let you login without setting these properties to valid values.
Furthermore, confirm that your PC is set to the correct sandbox.
"The current user has already installed an unpackaged version of this app. A packaged version cannot replace this." error when installing
Prior to June 2022 GDK, running from the Visual Studio debugger would still locally register the app (and it would appear on the start menu). To fix this, you have to uninstall it (it's simplest to right click on it from the start menu to uninstall it).
Missing headers / linker errors after updating to April 2026 GDK or newer
Existing projects using the "old layout" of GDK may need to add the following to their vcxproj in order for GDK system headers and libraries to be found:
<PropertyGroup>
<LibraryPath>$(Console_SdkLibPath);$(LibraryPath)</LibraryPath>
<IncludePath>$(Console_SdkIncludeRoot);$(IncludePath)</IncludePath>
</PropertyGroup>See SDL.vcxproj for an example of this.
README Haiku (`docs/README-haiku.md`)
Haiku OS
SDL is fully supported on Haiku OS, and is built using CMake.
README Harmonyos (`docs/README-harmonyos.md`)
HarmonyOS
(And also OpenHarmony.)
What is HarmonyOS?
(This is my current understanding, please correct these docs if incorrect.)
HarmonyOS is an operating system for tablets and phones. It is developed by
Huawei. It is competitive with platforms like
Android and iOS, capable of powering high-end phones. Unlike Google and Apple,
Huawei is a Chinese company, and HarmonyOS is largely serving the Chinese
market at this time.
The system uses some custom pieces, but also relies on open source software
from outside of the project, such as NodeJS, musl, etc.
Much of the platform is open source, under the name "OpenHarmony," and Huawei's
proprietary spin of OpenHarmony is called HarmonyOS. Huawei is the primary
contributor to OpenHarmony.
So it is probably best said: HarmonyOS is to Android what OpenHarmony is to
AOSP. But, to be clear, OpenHarmony is not derived from Android.
This document will generally refer to HarmonyOS, but from a development
viewpoint, you are actually targeting OpenHarmony, even if you're building
with intent to only target HarmonyOS on Huawei-produced phones.
Supported versions
All testing to date has been done on a real phone, a Huawei Mate 80. SDL
currently supports HarmonyOS 5.0.4 (API level 16) and later, although low-end
testing hasn't been done so there might be little changes needed for extreme
compatibility. Report bugs or send patches if this becomes a problem.
Some basic target platform truths
More or less, an app on HarmonyOS is a NodeJS process, in the same way that
an app on Android is a Java virtual machine process. While one generally
writes apps in Java or Kotlin for Android, HarmonyOS uses a TypeScript
variant called ArkTS. However, like SDL on Android, we will be focusing on
native code in C or C++, so SDL makes moves similar to its Android strategy,
migrating from ArkTS to native code as quickly as possible and handing control
over to a (presumably C or C++) native code app, and making it so the app can
entirely opt out of interacting with the ArkTS layer directly.
The equivalent of the Android "JNI" layer is NodeJS's NAPI interfaces. A large
portion of the system APIs are accessible directly from OS-provided native
libraries, so there is a lot less reason to dip back down into the ArkTS layer
for most tasks, whereas lots of things on Android need to talk to Java-only
interfaces to accomplish common and important tasks, even in an
otherwise-entirely native program.
The C/C++ compiler is based on Clang (you might end up using a compiler called
"BiSheng", which is Clang with some custom optimization techniques developed
by Huawei, but the binary is still called "clang").
The platform triplet is "aarch64-linux-ohos" (other CPU architectures are
possible, but this is likely the most common. The
devtools mention 32-bit ARM and x86-64, too). The CMake toolchain file sets
the variable OHOS (OpenHarmony OS) to "OHOS", so if(OHOS) works. Further,CMAKE_SYSTEM_NAME is set to "OHOS". The C preprocessor defines __OHOS__.
Getting hardware to run HarmonyOS
(This information is accurate as of September 2026, but may be out of date at
any moment, so please check other sources before assuming this is still
correct.)
HarmonyOS phones work in America (at least on WiFi; presumably one could get a
working (e)SIM card for phone calls and cell network access, but I haven't
tried). It may be difficult to obtain a phone: I couldn't find one for
purchase on Amazon or eBay from the United States. You might have an easier
time in other countries. I assume it is super-easy in China. :)
One can run OpenHarmony (not HarmonyOS) on a Raspberry Pi 4b, compliments of
the Oniro project, which provides both source code and prebuilt images for
download:
https://docs.oniroproject.org/device-development/developer-boards/raspberry-pi-4model-b/
This lets you run the open source variant of the OS on real hardware, but
obviously a Raspberry Pi isn't a tablet or a phone, so the experience is
somewhat tangential to the the most common configuration one might expect in
the field, but it does work.
DevEco Studio offers phone, tablet, and smartwatch emulators for Huawei
hardware, and these apparently run on Windows, macOS and Linux, but will not
let you download them outside of mainland China (except for smartwatch
emulators, for some reason). Comments on Huawei's forums suggest relaxing this
restriction is planned, but no timeline has been announced.
Get HarmonyOS development tools
You will need to download Huawei's developer tools. They offer a full IDE,
called "DevEco Studio," but this is not strictly necessary for compiling SDL
itself.
There are also command line tools available for Windows, macOS, and Linux, and
these are sufficient to compile SDL. For building full applications, you
likely need DevEco Studio to at least set up your project and manage signing
keys, etc. Some things (but not all) are doable from the command line tools,
and a handful of things are (currently) not.
DevEco Studio, the GUI, is only available for Windows and macOS. It appears to
be a fork of IntelliJ IDEA. It does not appear to be open source, afaict.
To download the developer tools, you'll need to register an account at
the Huawei developer site. Then visit
the tool download page
For Linux, scroll down to "Command Line Tools." Download for your platform,
which should be a simple .zip file, and unpack it. If you're on Windows or
macOS, download and install DevEco Studio itself while you're here, too.
Don't fight against DevEco Studio
I'm a Linux user, and I avoid programming IDEs in general, so my instinct is
to just get everything done with my text editor and the command line tools,
and this feels possible, but I haven't quite figured it out. The system is
friendly to things like CMake, clang, etc, and the documentation, even in
English, is pretty thorough, but I still couldn't get there without using
DevEco Studio a little.
The system appears designed to at least hand off a working project from
DevEco Studio to a Linux machine for building and deploying from the command
line tools, for Continuous Integration if nothing else. Figuring out how to
eliminate the Windows/Mac GUI dependency entirely is an ongoing and unresolved
concern. I definitely needed it to manage signing keys. Once I got that far, I
was able to copy the appropriate directories to a Linux system and use thehvigorw command line tool to build and sign an app, and hdc to install it
on a real phone. Please see "The Helper Script" section, below, to automate
a lot of the Linux command line pieces, too.
Install command line tools.
(If you're entirely focused on DevEco Studio, you can probably skip this.)
I unzipped the downloaded command line tools so they're sitting in$HOME/HarmonyOS-SDK/command-line-tools, but anywhere you want to put it is
fine.
When this document refers to this directory, it'll call it $CLT for short.
Add some things to your PATH, so you can find hdc and hvigorw, etc.
export PATH=$PATH:$CLT/sdk/default/openharmony/toolchains:$CLT/binPutting a HarmonyOS phone in developer mode.
If you want to test and debug your SDL apps natively on a real phone directly,
you have to put the device in Developer Mode. As the name implies, this is not
necessary to install published apps, but only apps under development locally.
Any retail phone running HarmonyOS should be able to do this. This is almost
identical to how one would do this for local Android development.
On the phone, go to Settings, Device Name (it's "Mate 80" on mine, in a box by
itself near the top of the main settings page). This will bring up an "About"
page. Scroll down to "Software version" and tap the version number seven times
quickly. The phone will pop up a dialog asking if you want to enable developer
mode and reboot. Say yes. If you've already done this, it will tell you so.
After reboot, go into Settings, System. There will be a new "Developer options"
section at the bottom. Turn on USB debugging. Plug in a USB cable to your
computer, and choose to trust the connection (and choose "use USB to transfer
files" if given the option, otherwise it will only allow charging over USB).
At this point, you should be able to use "hdc" somewhat like you would use
"adc" on an Android device:
See if the phone is visible/accessible:
hdc list targets -vYou can also connect the phone over Wifi on the same LAN and not use USB. If
you want to try, turn on "Wireless debugging" in Developer Options, note the
IP address and port, and run:
hdc tconn 192.168.1.104:40317Where the IP address and port match the phone.
(DevEco Studio: add the device by IP address and port by going to "Tools" ->
"IP Connection" on the menu bar. This appears to be IPv4 only at the moment,
but it successfully connected to the phone. This was a good option, because
for whatever reason the phone's USB connection would continually drop and
reconnect on my VirtualBox Windows 10 VM. But YMMV!)
HarmonyOS App Development with SDL
Create a new project
Copy the "openharmony-project" directory out of the root of SDL's source tree
to where you want your project to live, and name it appropriately
("mygame-openharmony" or whatever). Then, in that new directory:
- in AppScope/app.json5, change at least "bundleName" and "vendor" to match
your project. - in AppScope/resources/base/element/string.json, change "app_name"'s value to
the name of your app. - in build-profile.json5, you need to add a signingConfig (this is probably
doable without the GUI, but I have not entirely solved how to do this outside
of DevEco Studio yet).
Do not create a new project in DevEco Studio, since there are pieces of ArkTS
scripting that need to be set up correctly for SDL, and that code is already
present in openharmony-project. You should be able to open the project in
DevEco Studio once you've set it up.
Add source code to your new project.
Native code building uses CMake, whether you use DevEco Studio or not. This
makes integrating SDL into your project easy!
Edit the text file entry/src/main/cpp/CMakeLists.txt inside your new project
directory and change this line:
add_library(main SHARED myapp.c)"myapp.c" should be replaced with any source files that comprise your app.
This is a standard CMake project file, so you can get as fancy as you want
or just have a list of files separated by spaces.
These files do not have to be in this directory; they can live outside of
the project directory entirely, as long as you specify the correct directory
to reach them.
You must either copy the SDL source tree into entry/src/main/cpp, or make a
symlink to it, or edit the add_subdirectory("SDL") line to point to it.
This line makes SDL's source compile with the rest of the project. The
directory you point to must be the root of an SDL clone, the one that holds
CMakeLists.txt, src, and include.
Your app must compile to a shared library named "libmain.so" ... the
"add_library" line does this. Do not build an executable! It must be a
shared library, due to how HarmonyOS deals with apps. When you're done
building, you should have a libmain.so and a libSDL3.so file.
Customize your project
If you need some specific permissions (microphone/camera access, internet
access, etc), you must list them in
openharmony-project/entry/src/main/module.json5 ...some of these are
automatically granted on installation, some will require the user to
approve them on first use at runtime, but all of them must be listed. See
the "requestPermissions" section of this file for examples. There are more
permissions available than are listed there!
You must set up some basic strings, before publishing your app, or you will
either have an app icon with name "label" or fail certification because you
didn't explain clearly why you need various permissions.
These are in a JSON file in:
openharmony-project/entry/src/main/resources/base/element/string.json
You can do localizations of these strings by making a second copy of the
file, where "base" is replaced with the proper localization ("en_GB" or
whatever). See the documentation at:
https://developer.huawei.com/consumer/en/doc/harmonyos-guides/resource-categories-and-access
Static data files that ship with your app go in the directory:
openharmony-project/entry/src/main/resources/rawfile/
The file tree under this directory will be packaged up with the app and made
available through SDL APIs (see "Filesystem" and "IOStream" sections, below).
Get a HarmonyOS Debug Certificate
You need to sign apps to run them on a real device, even for local debugging
and testing and not just deployment and release.
For now, just open your project in DevEco Studio, then in the menu bar, go
to "File", "Project Structure", "Signing Configs", "Automatically generate
signature". Log in to your Huawei developer account if needed. Let it do the
work. Later, I copied the project's directory and ".ohos" dir in my home
directory to a Linux system, adjusted a few Windows absolute paths in
build-profile.json5, and that was good enough.
Build your app from the command line
Building your project from DevEco Studio is just a single click, but you
can also build from the command line on macOS, Windows, or Linux.
If you have everything (signing certs, the entire project structure, etc) in
place, you can build an app from the command line. The HarmonyOS equivalent of
Android's gradlew command is hvigorw. Go to the root folder of the project
and run:
hvigorw assembleHapIf everything went well (AND IT OFTEN DOES NOT, READ THE OUTPUT!), you'll have
a signed HarmonyOS app bundle (a ".hap" file) you can install to a device.
There is a Bash shell script to automate this as much as possible: see the
"The Helper Script" section, below.
Install on real hardware from the command line.
Make sure the phone is connected (run hdc list targets -v to verify).
# (or whatever the directory and .hap file are named.)
hdc install ./entry/build/default/outputs/default/entry-default-signed.hapThere is a Bash shell script to automate this as much as possible: see the
"The Helper Script" section, below.
Debugging
If you just want to do "printf debugging," then SDL_Log() will write to the
system's "hilog" output, and you can use hdc hilog |grep -F "APPBUNDLENAME"
to find your app's output in the absolute firehose of text that is produced
through that interface.
One can use a real GUI debugger through DevEco Studio without a lot of drama,
and one can also use lldb from the command line with "remote debugging." The
latter option takes a lot of manual setup, and additionally you have to be
familiar with LLDB's text interface to find this useful, but it can be done
from Linux, where DevEco Studio isn't available (and also Windows and macOS,
if all you have are the command line tools).
There is a Bash shell script to automate this as much as possible: see the
"The Helper Script" section, below.
The instructions on setup from scratch, without the shell script, are here:
https://developer.huawei.com/consumer/en/doc/harmonyos-guides/debug-lldb#remote-debugging
The only thing that document fails to mention is that the PID number you need
for LLDB's attach command can be obtained by running hdc shell ps and
looking for your app's bundle ID.
The Helper Script
There is a Bash shell script to simplify several tasks, if you're using the
command line tools. This is not necessary from DevEco Studio. This has only
been tested on Linux.
Make sure your PATH is set up (see "Install Command Line Tools," above), and
run this command:
SDL/build-scripts/harmonyos-tool.sh openharmony-project --build --debug"openharmony-project" is literally the path to your project file. It can be a
relative path, including "." if you are sitting in the directory.
All the other command line arguments are optional, and can be used in any
combination (for example, you can --launch without --build'ing, or --install
and --debug in one run, etc.)
If you specify --log, the script will fire up hdc hilog in the background
and filter out everything but your app bundle's log calls, so you can watch
the log while other tasks complete. It will start this once passed the
--install stage (even if you didn't install), so you are watching it while
launching/debugging begins. After all other tasks are done, harmonyos-tool.sh
won't terminate until either the hdc process terminates or the user hits
CTRL-C (rather, until SIGINT is delivered, which CTRL-C does). The script will
kill the hdc process in that case before terminating itself.
If you specify --clean, the script will clean up any build artifacts from
previous compiles, etc. This will run before --build, so you can specify--clean --build for a complete rebuild.
If you specify --build, the script will compile C/C++ code and assemble the
.hap file to be run on a device or emulator.
If you specify --install, the script will (re)install the .hap on a device
(which will kill the process if it's currently running).
If you specify --uninstall, the script will remove the existing app on a
device (which will kill the process if it's currently running).
If you specify --launch, the script will launch the app on a device, if it
isn't already running.
If you specify --debug, the script will prepare remote debugging via lldb,
and attach to the running app.
If you specify --kill, the script will force-stop any existing process.
(--debug implies --launch, "--build --launch" and "--build --debug" imply
--install.)
Most of these tasks obviously need a phone that hdc can talk to.
Note that debugging always attaches to a process, so if you need to debug
something at startup, plan to add a sleep to the start of the program, as it
can take several seconds for the attachment to complete. It isn't clear to me
how to launch an app from the debugger so you can break at startup, but there
is probably a way.
Also note that this script is fragile in general; be gentle, report bugs, send
patches.
SDL/HarmonyOS subsystem details
Specifics of actually using SDL on HarmonyOS follow.
SDL platform defines
SDL will define SDL_PLATFORM_OPENHARMONY and SDL_PLATFORM_UNIX. Do be cautioned
that while HarmonyOS has some Linux kernel compatibility interfaces, it is
not comparable to a "normal" Linux system in almost any way, so
SDL_PLATFORM_LINUX is not defined (SDL for Android, which literally uses the
Linux kernel, follows this same convention inside SDL). HarmonyOS devices
don't use the actual Linux kernel (they use a microkernel named "HongMeng"),
but OpenHarmony devices that aren't specifically HarmonyOS might use a Linux
kernel. Don't make assumptions.
Dynamic API
Presumably users cannot override the SDL build in their phone's apps, so the
Dynamic API is disabled on this platform.
Log
SDL_Log() and friends will push through hilog, so you can view the output
from the hdc hilog command. Note that logs are preformatted strings by the
time they hit hilog, so things like "%s" are already processed, and thus
there is no {public} and {private} format modifiers available like there
would be if calling into hilog's APIs directly.
Output from hdc hilog for SDL_Log("Hello world!"); will look something like:
06-29 22:22:02.953 25275 25275 I A00000/org.libsdl.loopwave/SDL/APP: Hello world!This is kind of chatty, but not unlike Android's adc logcat output.
These logs are viewable to end-users, even in release builds, if they have a
phone in Developer Mode that can talk to hdc, so be careful what you log!
Also plan to use "grep" to find relevant logs; hdc hilog is an endless
waterfall of unrelated data that you will get lost in immediately.
Main
HarmonyOS has a fairly complicated startup sequence (described later in this
document). Apps for this platform should use the "main callbacks" (with#define SDL_USE_MAIN_CALLBACKS) instead of an ANSI C style "main" entry
point, as that fits the app design paradigm of OpenHarmony.
However, if you build an app with a standard "main" function, SDL will notice
this, spin a thread, and attempt to call that function from the new background
thread, and call exit() when it returns. This happens to work, at least for
simple test cases that do rendering and touch input, but one uses this path at
their own risk, as several important things might result in unexpected race
conditions and unexpected behavior, possibly in a later version of the OS. It
is strongly recommended that you migrate to the Main Callbacks!
Filesystem
HarmonyOS uses '/' as a path separator, like Unix-style (etc) systems.
SDL_GetBasePath() returns "assets://" unconditionally (see IOStream section,
below). SDL_GetPrefPath() returns a path under the string returned from
OH_AbilityRuntime_ApplicationContextGetFilesDir(), which in practice looks
like "/data/storage/el2/base/files/APPNAME" ... this looks like an absolute
path, but HarmonyOS maps this to an app-and-user-specific path behind the
scenes. The prefpath is readable/writable with both SDL_IOStream and "normal"
APIs like fopen().
There is also SDL_GetOpenHarmonyInternalStoragePath(), which returns the base
directory used for SDL_GetPrefPath() without extra subdirs appended to it or
mkdir() calls issued, but generally SDL_GetPrefPath() is preferred as the
more-portable option.
IOStream
This works like Android: some files are accessible like normal filesystem
things, and can be accessed with fopen(), open(), etc. Other things are
installed with the application (the "HAP"), and they need special APIs to
access, that sort of map to Android's AAssetManager, but a little more
powerful (these are called "RawFile" APIs on HarmonyOS).
Both types of files can be accessed as SDL_IOStreams through SDL_IOFromFile().
SDL uses the same conventions as Android here: if the file path is a relative
path or prefixed with "assets://", it'll use the RawFile APIs. Absolute paths
will use "normal" filesystem APIs. SDL_GetBasePath() returns "assets://", so
one can access files installed with the app with SDL_IOFromFile().
"assets://" is entirely an SDL3 construct; it will not work with fopen(), and
is not understood by the system itself.
Async I/O
SDL uses the "generic" asyncio backend here (a thread pool that uses
SDL_IOStreams). Not only is there no sort of io_uring-style interface (afaik),
but this also means you can async-load stuff from "assets://" paths without
problems.
Video
One SDL_Window is allowed at a time and it takes the entire available display
(like Android). Currently we only report a single display (the phone/tablet's
screen).
Setting an SDL_Window to be fullscreen means it will turn off the system
status and navigation bars, allowing the window to use those extra pixels. A
non-fullscreen window leaves those bars in place, letting users see the
current time, battery/network/etc status, and other icon things.
Touch events work, and multitouch is supported. Mouse events (from an external,
physical mouse) are supported. Physical keyboard input works, and enabling
text input will pop up a screen keyboard.
Clipboard is supported for text. SDL_SetClipboardText() works as expected
and other apps will see the text. However, an app needs theohos.permission.READ_PASTEBOARD permission to obtain data from the system
clipboard, and this is a restricted permission: you must list it in
modules.json5, prompt the user to approve it beforehand by calling
SDL_RequestOpenHarmonyPermission(), and also the app developer must
explicitly apply for permission from Huawei through AppGallery Connect!
Consider if you actually need to read clipboard data into your app that badly.
SDL_GetSystemTheme() and SDL_EVENT_SYSTEM_THEME_CHANGED both work.
Render/GPU
OpenGL ES 3 is supported, as is Vulkan. Desktop OpenGL is supported by
the platform, too, but for now support is disabled within SDL itself as likely
superfluous (until it is not, open an issue if you need it). EGL is used to
manage GL contexts internally, and a HarmonyOS-specific Vulkan extension is
provided to get a surface.
Like other platforms, we favor OpenGL ES 2 for the 2D renderer; Vulkan and "GPU"
also works, but SDL's GLES is assumed to be more trusted at the current moment,
and historically GLES has been more solid on mobile devices than Vulkan, but
HarmonyOS-based phones might have significantly better Vulkan support than
other comparable smartphones; this is yet to be determined.
Since Vulkan works, the SDL3 GPU API is available and operational, in addition
to the hardware-accelerated 2D Renderer API.
Audio
HarmonyOS support OpenSL ES, but like on Android, it is deprecated. As our
OpenSL ES backend is heavy with Android-specific code, it is not used on
HarmonyOS. There is a new backend using HarmonyOS's OHAudio API. This is
almost exactly like Android's AAudio API.
Recording works, but you must request ohos.permission.MICROPHONE permission
before it will work. You must list it in modules.json5, and prompt the user to
approve it beforehand by calling SDL_RequestOpenHarmonyPermission(). Once
permission is granted, recording audio devices can be opened as usual, and
this permission remains for future runs, unless the user revokes in in the
system preferences later. On HarmonyOS, if a user denies permission, they will
never get the popup request dialog again, and must go and grant permission
through the system preferences. Plan accordingly if you want your UI to
explain this to users.
Note that SDL for Android currently requests permission from the user on
behalf of the app when opening a recording device, and blocks until the user
approves or denies the request, but on HarmonyOS this is not done. This may
change in the future, but for now the app should handle requesting permission
before opening the device. If a future revision of SDL tries to manage the
permission, it won't hurt anything if the app has already taken care of it.
Power
Battery percent, and whether the device is plugged in, are reported.
Thread
OpenHarmony uses pthreads, and are fully supported.
LoadSO
HarmonyOS uses ELF shared libraries and has a POSIX-style dlopen() mechanism,
so SDL_LoadObject() works as expected.
Tray
The Tray API uses the "dummy" backend as this functionality doesn't make sense
on a phone.
Time
The Time and Timer subsystems use the usual Unix implementations and work fine.
Locale
SDL_GetPreferredLocales() returns a single locale, as specified in System
Preferences -> "System" -> "Language & Region". In my case, I have it set to
English/United States, so SDL reports "en_US".
SDL will send SDL_EVENT_LOCALE_CHANGED events if the user changes their locale
in the system preferences while the app is running.
Camera
Camera access works. You must request the ohos.permission.CAMERA permission
in modules.json5. SDL will handle the UI to prompt the user for permission
during SDL_OpenCamera() if necessary, so the app doesn't have to manage this
further (unlike using a microphone in the audio API, SDL's camera API was
built on the idea that the user's permission or refusal might be coming at a
later time).
Misc
SDL_OpenURL() works, and will launch the appropriate app ("ability") for
whatever protocol is specified: "https://", etc, URLs will bring up a web
browser, custom URL schemes can launch specific apps, etc.
STILL TODO
- Message boxes
- Assert
- Dialog
- Haptic
- HIDAPI
- Joystick
- Process
- Sensor
- Storage
- Pen (tablets work over USB-C on the phone, but this isn't wired up in SDL at all)
- SDL_EVENT_DISPLAY_ORIENTATION (window resizes when phone orientation changes, this event doesn't fire yet, though)
- Can external displays be used over USB-C => HDMI? (yes, at least for mirroring)
- SDL_HINT_ORIENTATION (for runtime control; but you can explicitly set the "orientation" setting in module.json5 for now if you need a specific global, unchanging setting.)
Building SDL for HarmonyOS
(Skip this if you are developing an SDL-based app and not SDL itself. There's
a different section of this document for you, later.)
SDL for HarmonyOS is built with CMake, like other platforms. Please note that
you must use the CMake that comes with HarmonyOS's development tools, or
building will fail, as standard CMake does not recognize this platform at all
currently.
Generate a project file for building SDL. We use Ninja, because it's a fast
command line tool that will maximize multi-CPU compiling, but it's probably
possible to generate a real Visual Studio, Xcode, etc, project.
cd SDL
mkdir buildbot-ohos # or whatever you want to call the build directory.
cd buildbot-ohos
$CLT/sdk/default/openharmony/native/build-tools/cmake/bin/cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=$CLT/sdk/default/openharmony/native/build/cmake/ohos.toolchain.cmake -DCMAKE_BUILD_TYPE=Release -DOHOS_COMPATIBLE_SDK_VERSION=16.0.0 ..(One can also add -DOHOS_ARCH=cputype, where "cputype" is "arm64-v8a" for ARM64, "armeabi-v7a" for ARM32, and "x86_64" Intel 64-bit. It defaults to ARM64 if unspecified.)
If all went well, you should be able to build SDL for HarmonyOS now. In our
case, build with Ninja:
ninjaOr use whatever instructs Xcode/Visual Studio/etc to build things.
Startup sequence
(You can probably skip this section if you just want to target HarmonyOS with
an SDL3-based app and not delve into the low-level details.)
Starting an SDL app on HarmonyOS works differently than any other platform,
but efforts have been made to hide most of this from the C programmer, so
this should mostly feel like any other SDL app they have built in C before.
This is how startup works:
(Exact paths might vary, and maybe this will simplify later.)
- The actual application entry point is in ArkTS, in the source file
$PROJECT/entry/src/main/ets/entryability/EntryAbility.ets. - This file defines a class, EntryAbility, that extends UIAbility, and
also imports the native SDL library. - By now, libSDL3.so has been loaded. In a shared library constructor named
SDL_RegisterNativeInterfaces, it uses NAPI to register a native module. Once
this module initializes, we'll be able to call into SDL's C code from ArkTS
through interfaces we defined in SDL_Init_Native_Interfaces(). This is in
SDL/src/core/openharmony/SDL_openharmony.c. This will also hook into
XComponent's API, to register some event callbacks. - EntryAbility has an overridden method named onCreate. When this
runs, the app ("Ability" in HarmonyOS terms) is starting up. This function
calls into the sdl module we registered to hand it a few objects (including
the EntryAbility instance, and some small things that are trivial to create
in ArkTS. - SDL, now in native code, will save off those objects as appropriate, and
also monkey-patch EntryAbility, so that it now has several overridden
methods that only exist in native code. This lets us deal with ArkTS-only
interfaces on EntryAbility that the system intends to interact with, without
providing any ArkTS scripting for them. - EntryAbility now has an overridden method named onWindowStageCreate. When this
runs, the system is ready for the app to display to the screen. This
method, in C, callswindowStage.loadContent('pages/Index', ...). - This causes the app to load
$PROJECT/entry/src/main/ets/pages/Index.ets. - Index.ets specifies a layout for the window/view/whatever. We simply have a
single row and column containing a single "XComponent" which is more or
less a UI widget backed by native code. - When the XComponent's OnSurfaceCreated callback fires (landing in
SDL_XComponent_OnSurfaceCreatedCallback()), we are then ready to hand control
to the actual C application. This starts in
SDL_OpenHarmonyMainSurfaceCreated(), which eventually lands in
RunAppOpenHarmonyMain(). - Here we load libmain.so, which is where the app's actual C code lives.
If we see if libmain contains a symbol named "SDL_main", we spin a thread
and call into that symbol from the new thread as a standard ANSI C "main"
entry point, and exit() the process when it returns (which looks like a
crash on HarmonyOS, so please don't return.) - If there was no symbol called "SDL_main", we look for the Main Callbacks
symbols in libmain.so: SDL_AppInit, SDL_AppIterate, etc. If found, we will
call libmain.so's SDL_AppInit() and respond appropriately. If not found, we
panic and terminate the process, since we're out of options and something
was obviously built incorrectly. - Later, whenever the XComponent fires its OnFrame callback, we will call
SDL_OpenHarmonyOnFrameCallback(), which calls libmain.so's SDL_AppIterate(),
if we ended up using the Main Callbacks.
Incomplete notes
Everything below here is incomplete, in-progress, and maybe incomprehensible.
You can stop reading now, if you like.
A lot of how to get started with native code and rendering was gleaned from
https://gitee.com/harmonyos_samples/ndk-opengl and then trying to find
various symbols used in there in the HarmonyOS documentation.https://github.com/openharmony/app_samples/ETSUI/XComponent, seems to do
a lot of the native startup without the windowStage nonsense. Not clear if
this still works, or is safe to do, but it would simplify things a little.
Android mappings
These are not direct mappings (the command lines are not identical, etc).
- "adc" command line tool: "hdc"
- "adc logcat": "hdc hilog"
- "gradlew": "hvigorw"
Get a HarmonyOS Debug Certificate without DevEco Studio
This is a giant dump of notes for doing this manually, which I didn't quite
manage to get working. Huawei has docs on this, which I've sort of half limped
through.
For now, just create your project in DevEco Studio,
then in the menu bar, got to "File" -> "Project Structure" ->
"Signing Configs" -> "Automatically generate signature". Log in to your Huawei
developer account if needed. Let it do the work. Later, I copied the project's
directory and ".ohos" dir in my home directory to a Linux system, adjusted a
few Windows absolute paths in build-profile.json5, and that was good enough.
(Ignore these notes if you aren't improving them.)
If you aren't using DevEco Studio to automatically generate signatures:
You need a signing certificate before you can install apps on a real HarmonyOS
device.
You get a certificate by generating a Certificate Signing Request (a "CSR")
and submitting it to Huawei.
Right now this needs DevEco Studio to generate the CSR file, as a one-time
step. Go into the IDE, click the hamburger menu icon on the top left of the
window, choose the "Build" menu item, and then click "Generate Key and CSR".
In the dialog that pops up, you'll first make a signing key in ".p12" format.
Enter a filename and save path, choose a password for the key. Fill in the
"Alias" which is any short, unique, possibly-ASCII, human readable string.
Ignore the Advanced section.
Click "Next". Now you'll generate the CSR file. The same .p12 file, password
and alias you just generated will be prefilled in the new dialog. Just tell it
where to save the new .csr file. Click "Finish" and you're done with DevEco
Studio for this task.
The Common Name ("CN=" field) on the Certificate Request is what DevEco Studio
refers as an "alias." I put this in as "icculus.org" for mine, but it might
just need to be unique. It's required, all the other fields are not (indeed,
DevEco Studio does not ask for them by default and leaves them blank).
TODO IN THE FUTURE: the CSR files are in a standard format, so OpenSSL's
command line utility can probably generate them without having to go through
DevEco Studio, but anything OpenSSL is complicated and needs research.
Dumping the .csr file that DevEco Studio generated (using the command line:openssl req -in MyCSRFile.csr -noout -text) reports the following, so there
are probably OpenSSL incantations to generate your own .p12 and .csr file
directly.
Certificate Request:
Data:
Version: 1 (0x0)
Subject: C=, ST=, L=, O=, OU=, CN=[the "Alias" that I entered is here]
Subject Public Key Info:
Public Key Algorithm: id-ecPublicKey
Public-Key: (256 bit)
pub:
[hex values for public key are here]
ASN1 OID: prime256v1
NIST CURVE: P-256
Attributes:
Requested Extensions:
X509v3 Subject Key Identifier:
[hex values for key identifier are here]
Signature Algorithm: ecdsa-with-SHA256
Signature Value:
[hex values for signature are here]Once you have a .csr file, go to Huawei's AppGallery Connect portal, and find
the "Certificates, app IDs, and profiles" tab at the top. Click the "New
certificate" button, and upload the .csr file you generated. Give it a name,
set the "Certificate type" to "Debug certificate" and point to your .csr file.
Click "Submit".
If everything went okay, you'll have a certificate listed for download. This
should be an immediate thing, it doesn't need approval on Huawei's side.
Download the .cer file and save it for later.
README Highdpi (`docs/README-highdpi.md`)
SDL 3.0 has new support for high DPI displays. Interfaces provided by SDL uses the platform's native coordinates unless otherwise specified.
Explanation
Displays now have a content display scale, which is the expected scale for content based on the DPI settings of the display. For example, a 4K display might have a 2.0 (200%) display scale, which means that the user expects UI elements to be twice as big on this display, to aid in readability. You can query the display content scale using SDL_GetDisplayContentScale(), and when this changes you get an SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED event.
The window size is now distinct from the window pixel size, and the ratio between the two is the window pixel density. If the window is created with the SDL_WINDOW_HIGH_PIXEL_DENSITY flag, SDL will try to match the native pixel density for the display, otherwise it will try to have the pixel size match the window size. You can query the window pixel density using SDL_GetWindowPixelDensity(). You can query the window pixel size using SDL_GetWindowSizeInPixels(), and when this changes you get an SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED event. You are guaranteed to get a SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED event when a window is created and resized, and you can use this event to create and resize your graphics context for the window.
The window has a display scale, which is the scale from the pixel resolution to the desired content size, e.g. the combination of the pixel density and the content scale. For example, a 3840x2160 window displayed at 200% on Windows, and a 1920x1080 window with the high density flag on a 2x display on macOS will both have a pixel size of 3840x2160 and a display scale of 2.0. You can query the window display scale using SDL_GetWindowDisplayScale(), and when this changes you get an SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED event.
Numeric example
Given a window spanning a 3840x2160 monitor set to 2x display or 200% scaling, the following tabulates the effect of creating a window with or without SDL_WINDOW_HIGH_PIXEL_DENSITY on macOS and Windows:
| Value | macOS (Default) | macOS (HD) | Windows (Default & HD) |
|---|---|---|---|
SDL_GetWindowSize() |
1920x1080 | 1920x1080 | 3840x2160 |
SDL_GetWindowSizeInPixels() |
1920x1080 | 3840x2160 | 3840x2160 |
SDL_GetDisplayContentScale() |
1.0 | 1.0 | 2.0 |
SDL_GetWindowDisplayScale() |
1.0 | 2.0 | 2.0 |
SDL_GetWindowPixelDensity() |
1.0 | 2.0 | 1.0 |
Observe the difference between the approaches taken by macOS and Windows:
- The Windows and Android coordinate system always deals in physical device pixels, high DPI support is achieved by providing a content scale that tells the developer to draw objects larger. Ignoring this scale factor results in graphics appearing tiny.
- The macOS and iOS coordinate system always deals in window coordinates, high DPI support is achieved by providing an optional flag for the developer to request more pixels. Omitting this flag results in graphics having low detail.
- On Linux, X11 uses a similar approach to Windows and Wayland uses a similar approach to macOS.
Solution
Proper high DPI support takes into account both the content scale and the pixel density.
First, you'd create your window with the SDL_WINDOW_HIGH_PIXEL_DENSITY flag, assuming you want the highest detail possible. Then you'd get the window display scale to see how much your UI elements should be enlarged to be readable.
If you're using the SDL 2D renderer, SDL provides the function SDL_ConvertEventToRenderCoordinates() to convert mouse coordinates between window coordinates and rendering coordinates, and the more general functions SDL_RenderCoordinatesFromWindow() and SDL_RenderCoordinatesToWindow() to do other conversion between them.
If you're not using the 2D renderer, you can implement this yourself using SDL_GetWindowPixelDensity() as scale factor to convert from window coordinates to pixels.
Finally you'll want to test on both Windows and macOS if possible to make sure your high DPI support works in all environments.
README Ios (`docs/README-ios.md`)
iOS
Building the Simple DirectMedia Layer
SDL supports building with Xcode 12.2 and the iOS 14.2 SDK or newer, and deploying to iOS 11.0, tvOS 11.0, visionOS 1.3, and newer.
Instructions:
- Open SDL.xcodeproj (located in Xcode/SDL) in Xcode.
- Select your desired target, and hit build.
Using the Simple DirectMedia Layer for iOS with the SDL3 xcFramework
The recommended way to use SDL for iOS is by including the SDL3.xcframework which is now a build target of the SDL.xcodeproj file. An xcframework is a new (Xcode 11) uber-framework which can handle any combination of processor type and target OS platform.
You can either build the SDL3.xcframework yourself or download the latest release disk image asset (*.dmg).
In the past, iOS devices were always an ARM variant processor, and the simulator was always i386 or x86_64, and thus libraries could be combined into a single framework for both simulator and device. With the introduction of the Apple Silicon ARM-based machines, regular frameworks would collide as CPU type was no longer sufficient to differentiate the platform. So Apple created the new xcframework library package.
The xcframework target builds into a Products directory alongside the SDL.xcodeproj file, as SDL3.xcframework. This can be brought in to any iOS project and will function properly for both simulator and device, no matter their CPUs. Note that Intel Macs cannot cross-compile for Apple Silicon Macs. If you need AS compatibility, perform this build on an Apple Silicon Mac.
This target requires Xcode 11 or later. The target will simply fail to build if attempted on older Xcodes.
In addition, on Apple platforms, main() cannot be in a dynamically loaded library.
However, unlike in SDL2, in SDL3 SDL_main is implemented inline in SDL_main.h, so you don't need to link against a static libSDL3main.lib, and you don't need to copy a .c file from the SDL3 source either.
This means that iOS apps which used the statically-linked libSDL3.lib and now link with the xcframwork can just #include <SDL3/SDL_main.h> in the source file that contains their standard int main(int argc, char *argv[]) function to get a header-only SDL_main implementation that calls the SDL_RunApp() with your standard main function.
To use the SDL3.xcframework follow these steps:
- Run Xcode and create a new project using the iOS Game template, selecting the Objective C language and Metal game technology.
- In the main view, delete all files except for Assets and LaunchScreen
- Select the project in the main view, go to the "General" tab, scroll down to "Frameworks, Libraries, and Embedded Content", and drag and drop the SDL3.xcframework
- Still in "Frameworks, Libraries, and Embedded Content", select "Embed & Sign" for the SDL3.xcframework.
- Add the source files that you would normally have for an SDL program, making sure to have #include <SDL3/SDL_main.h> at the top of the file containing your main() function.
- Add any assets that your application needs.
- Enjoy!
Using an xcFramework is similar to using a regular framework. However, issues have been seen with the build system not seeing the headers in the xcFramework. To remedy this, add the path to the xcFramework in your app's target ==> Build Settings ==> Framework Search Paths and mark it recursive (this is critical). Also critical is to remove "*.framework" from Build Settings ==> Sub-Directories to Exclude in Recursive Searches. Clean the build folder, and on your next build the build system should be able to see any of these in your code, as expected:
#include "SDL3/SDL_main.h"
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
Using the Simple DirectMedia Layer for iOS by adding the SDL3 Xcode project
To maintain compatibility with older Xcode versions than version 11 you can add the SDL3 Xcode project file to your project:
- Run Xcode and create a new project using the iOS Game template, selecting the Objective C language and Metal game technology.
- In the main view, delete all files except for Assets and LaunchScreen
- Right click the project in the main view, select "Add Files...", and add the SDL project, Xcode/SDL/SDL.xcodeproj
- Select the project in the main view, go to the "Info" tab and under "Custom iOS Target Properties" remove the line "Main storyboard file base name"
- Select the project in the main view, go to the "Build Settings" tab, select "All", and edit "Header Search Path" and drag over the SDL "Public Headers" folder from the left
- Select the project in the main view, go to the "Build Phases" tab, select "Link Binary With Libraries", and add SDL3.framework from "Framework-iOS"
- Select the project in the main view, go to the "General" tab, scroll down to "Frameworks, Libraries, and Embedded Content", and select "Embed & Sign" for the SDL library.
- Add the source files that you would normally have for an SDL program, making sure to have #include <SDL3/SDL_main.h> at the top of the file containing your main() function.
- Add any assets that your application needs.
- Enjoy!
Notes for distributing your iOS app on the AppStore when using the embedded SDL3 Xcode project:
Embedding the SDL3 Xcode project makes SDL3.framework a target of your app, so it will be included in the archive created during the "Archive" step required for App Store submission. As this prevents successful distribution, remove the framework via a script in the Build Phase after the "Embed & Sign" step.
- Select the project in the main view, go to the "Build Phases" tab, click on the big '+' in this tab and click "New Run Script Phase".
- Scroll down to "Run Script" (after the "Embed SDL3 Framework") and enter the following script:
if [ -d "$INSTALL_ROOT/Library" ]; then
echo "Removing SDL3.framework from INSTALL_ROOT for archiving"
rm -rf "$INSTALL_ROOT/Library"
fi- Below the script entry uncheck the "Run Script:" options "For install builds only" and "Based on dependency analysis".
- Edit the Build Settings and set "User Script Sandboxing" to "No".
App Store Requirements and Asset Management
When preparing your SDL3 application for App Store submission, you must configure standard Apple platform assets directly within your wrapper Xcode project:
- App Icons & Assets: Create an Asset Catalog (
Assets.xcassetsorMedia.xcassets) via Xcode (File > New > File... > Asset Catalog). Add an "App Icon" set and populate all mandatory target resolutions for iPhone, iPad, or App Store presentation. Ensure the asset catalog is assigned to your app target's Build Phases > Copy Bundle Resources. - Target General Settings: In your project settings under the General tab, explicitly point the App Icons and Launch Images configuration to your newly created asset catalog icon set.
- Privacy Manifest (
PrivacyInfo.xcprivacy): If your app or its dependencies utilize any of Apple's declared "Required Reason APIs" (such as specific file timestamp or system boot time ticks), you must include a standard Apple Privacy Manifest file bundled into your target root directory to prevent automated App Store submission rejection.
Notes -- Retina / High-DPI and window sizes
Window and display mode sizes in SDL are in points rather than in pixels.
On iOS this means that a window created on an iPhone 6 will have a size in
points of 375 x 667, rather than a size in pixels of 750 x 1334. All iOS apps
are expected to size their content based on points rather than pixels,
as this allows different iOS devices to have different pixel densities
(Retina versus non-Retina screens, etc.) without apps caring too much.
SDL_GetWindowSize() and mouse coordinates are in points rather than pixels,
but the window will have a much greater pixel density when the device supports
it, and the SDL_GetWindowSizeInPixels() can be called to determine the size
in pixels of the drawable screen framebuffer.
The SDL 2D rendering API will automatically handle this for you, by default
providing a rendering area in points, and you can call SDL_SetRenderLogicalPresentation()
to gain access to the higher density resolution.
Some OpenGL ES functions such as glViewport expect sizes in pixels rather than
sizes in points. When doing 2D rendering with OpenGL ES, an orthographic projection
matrix using the size in points (SDL_GetWindowSize()) can be used in order to
display content at the same scale no matter whether a Retina device is used or not.
Notes -- Getting full screen resolution
Make sure that you have a Launch Screen key in your Info.plist, e.g.
<key>UILaunchScreen</key>
<dict/>If you don't specify a launch screen, then the OS will assume that your
application needs an older compatibility mode and will get a limited
resolution screen.
Notes -- Application events
On iOS the application goes through a fixed life cycle and you will get
notifications of state changes via application events. When these events
are delivered you must handle them in an event callback because the OS may
not give you any processing time after the events are delivered.
e.g.
bool HandleAppEvents(void *userdata, SDL_Event *event)
{
switch (event->type)
{
case SDL_EVENT_TERMINATING:
/* Terminate the app.
Shut everything down before returning from this function.
*/
return false;
case SDL_EVENT_LOW_MEMORY:
/* You will get this when your app is paused and iOS wants more memory.
Release as much memory as possible.
*/
return false;
case SDL_EVENT_WILL_ENTER_BACKGROUND:
/* Prepare your app to go into the background. Stop loops, etc.
This gets called when the user hits the home button, or gets a call.
*/
return false;
case SDL_EVENT_DID_ENTER_BACKGROUND:
/* This will get called if the user accepted whatever sent your app to the background.
If the user got a phone call and canceled it, you'll instead get an SDL_EVENT_DID_ENTER_FOREGROUND event and restart your loops.
When you get this, you have 5 seconds to save all your state or the app will be terminated.
Your app is NOT active at this point.
*/
return false;
case SDL_EVENT_WILL_ENTER_FOREGROUND:
/* This call happens when your app is coming back to the foreground.
Restore all your state here.
*/
return false;
case SDL_EVENT_DID_ENTER_FOREGROUND:
/* Restart your loops here.
Your app is interactive and getting CPU again.
*/
return false;
default:
/* No special processing, add it to the event queue */
return true;
}
}
int main(int argc, char *argv[])
{
SDL_SetEventFilter(HandleAppEvents, NULL);
... run your main loop
return 0;
}Note that if you are using main callbacks instead of a standard C main() function, your SDL_AppEvent() callback will run as these events arrive and you do not need to use SDL_SetEventFilter.
Notes -- Keyboard
The SDL keyboard API has been extended to support on-screen keyboards:
void SDL_StartTextInput()
-- enables text events and reveals the onscreen keyboard.
void SDL_StopTextInput()
-- disables text events and hides the onscreen keyboard.
bool SDL_TextInputActive()
-- returns whether or not text events are enabled (and the onscreen keyboard is visible)
Notes -- Mouse
iOS now supports Bluetooth mice on iPad, but by default will provide the mouse input as touch. In order for SDL to see the real mouse events, you should set the key UIApplicationSupportsIndirectInputEvents to true in your Info.plist
From iOS 17 onward, the key now defaults to true.
Notes -- Reading and Writing files
Each application installed on iPhone resides in a sandbox which includes its own application home directory. Your application may not access files outside this directory.
When your SDL based iPhone application starts up, it sets the working directory to the main bundle, where your application resources are stored. You cannot write to this directory. Instead, you should write document files to the directory returned by SDL_GetUserFolder(SDL_FOLDER_DOCUMENTS) and preferences to the directory returned by SDL_GetPrefPath().
More information on this subject is available here:
http://developer.apple.com/library/ios/#documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/Introduction/Introduction.html
Notes -- iPhone SDL limitations
Windows:
Full-size, single window applications only. You cannot create multi-window SDL applications for iPhone OS. The application window will fill the display, though you have the option of turning on or off the menu-bar (pass SDL_CreateWindow() the flag SDL_WINDOW_BORDERLESS).
Textures:
The optimal texture formats on iOS are SDL_PIXELFORMAT_ABGR8888, SDL_PIXELFORMAT_ABGR8888, SDL_PIXELFORMAT_XBGR8888, and SDL_PIXELFORMAT_RGB24 pixel formats.
Notes -- CoreBluetooth.framework
SDL_JOYSTICK_HIDAPI is disabled by default. It can give you access to a lot
more game controller devices, but it requires permission from the user before
your app will be able to talk to the Bluetooth hardware. "Made For iOS"
branded controllers do not need this as we don't have to speak to them
directly with raw bluetooth, so many apps can live without this.
You'll need to link with CoreBluetooth.framework and add something like this
to your Info.plist:
Game Center
Game Center integration might require that you break up your main loop in order to yield control back to the system. In other words, instead of running an endless main loop, you run each frame in a callback function, using:
bool SDL_SetiOSAnimationCallback(SDL_Window * window, int interval, SDL_iOSAnimationCallback callback, void *callbackParam);This will set up the given function to be called back on the animation callback, and then you have to return from main() to let the Cocoa event loop run.
e.g.
extern "C"
void ShowFrame(void*)
{
... do event handling, frame logic and rendering ...
}
int main(int argc, char *argv[])
{
... initialize game ...
#ifdef SDL_PLATFORM_IOS
// Initialize the Game Center for scoring and matchmaking
InitGameCenter();
// Set up the game to run in the window animation callback on iOS
// so that Game Center and so forth works correctly.
SDL_SetiOSAnimationCallback(window, 1, ShowFrame, NULL);
#else
while ( running ) {
ShowFrame(0);
DelayFrame();
}
#endif
return 0;
}Note that if you are using main callbacks instead of a standard C main() function, your SDL_AppIterate() callback is already doing this and you don't need to use SDL_SetiOSAnimationCallback.
Deploying to older versions of iOS
SDL supports deploying to older versions of iOS than are supported by the latest version of Xcode, all the way back to iOS 11.0
In order to do that you need to download an older version of Xcode:
https://developer.apple.com/download/more/?name=Xcode
Open the package contents of the older Xcode and your newer version of Xcode and copy over the folders in Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport
Then open the file Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/SDKSettings.plist and add the versions of iOS you want to deploy to the key Root/DefaultProperties/DEPLOYMENT_TARGET_SUGGESTED_VALUES
Open your project and set your deployment target to the desired version of iOS
Finally, remove GameController from the list of frameworks linked by your application and edit the build settings for "Other Linker Flags" and add -weak_framework GameController
README Kmsbsd (`docs/README-kmsbsd.md`)
KMSDRM on *BSD
KMSDRM is supported on FreeBSD and OpenBSD. DragonFlyBSD works but requires being a root user. NetBSD isn't supported yet because the application will crash when creating the KMSDRM screen.
WSCONS support has been brought back, but only as an input backend. It will not be brought back as a video backend to ease maintenance.
OpenBSD note: Note that the video backend assumes that the user has read/write permissions to the /dev/drm* devices.
SDL WSCONS input backend features
- It is keymap-aware; it will work properly with different keymaps.
- It has mouse support.
- Accent input is supported.
- Compose keys are supported.
- AltGr and Meta Shift keys work as intended.
Partially working or no input on OpenBSD/NetBSD.
The WSCONS input backend needs read/write access to the /dev/wskbd* devices, without which it will not work properly. /dev/wsmouse must also be read/write accessible, otherwise mouse input will not work.
Partially working or no input on FreeBSD.
The evdev devices are only accessible to the root user by default. Edit devfs rules to allow access to such devices. The /dev/kbd* devices are also only accessible to the root user by default. Edit devfs rules to allow access to such devices.
README Linux (`docs/README-linux.md`)
Linux
By default SDL will only link against glibc, the rest of the features will be
enabled dynamically at runtime depending on the available features on the target
system. So, for example if you built SDL with XRandR support and the target
system does not have the XRandR libraries installed, it will be disabled
at runtime, and you won't get a missing library error, at least with the
default configuration parameters.
SDL is not designed to be used in setuid or setgid executables.
Build Dependencies
Ubuntu 18.04, all available features enabled:
sudo apt-get install build-essential git make \
pkg-config cmake ninja-build gnome-desktop-testing libasound2-dev libpulse-dev \
libaudio-dev libfribidi-dev libjack-dev libsndio-dev libx11-dev libxext-dev \
libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev libxtst-dev \
libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \
libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev libthai-dev libusb-1.0-0-devUbuntu 22.04+ can also add libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev to that command line.
Fedora 35, all available features enabled:
sudo dnf install gcc git-core make cmake \
alsa-lib-devel fribidi-devel pulseaudio-libs-devel pipewire-devel \
libX11-devel libXext-devel libXrandr-devel libXcursor-devel libXfixes-devel \
libXi-devel libXScrnSaver-devel libXtst-devel dbus-devel ibus-devel \
systemd-devel mesa-libGL-devel libxkbcommon-devel mesa-libGLES-devel \
mesa-libEGL-devel vulkan-devel wayland-devel wayland-protocols-devel \
libdrm-devel mesa-libgbm-devel libusb1-devel libdecor-devel \
pipewire-jack-audio-connection-kit-devel libthai-develFedora 39+ can also add liburing-devel to that command line.
Fedora 40+ needs zlib-ng-compat-static to be added to that command line.
NOTES:
- The sndio audio target is unavailable on Fedora (but probably not what you
should want to use anyhow).
openSUSE Tumbleweed:
sudo zypper in libunwind-devel libusb-1_0-devel Mesa-libGL-devel libxkbcommon-devel libdrm-devel \
libgbm-devel pipewire-devel libpulse-devel sndio-devel Mesa-libEGL-devel alsa-devel xwayland-devel \
wayland-devel wayland-protocols-devel libthai-devel fribidi-develArch:
sudo pacman -S alsa-lib cmake hidapi ibus jack libdecor libthai fribidi libgl libpulse libusb libx11 libxcursor libxext libxfixes libxi libxinerama libxkbcommon libxrandr libxrender libxss libxtst mesa ninja pipewire sndio vulkan-driver vulkan-headers wayland wayland-protocolsJoystick does not work
If you compiled or are using a version of SDL with udev support (and you should!)
there's a few issues that may cause SDL to fail to detect your joystick. To
debug this, start by installing the evtest utility. On Ubuntu/Debian:
sudo apt-get install evtestThen run:
sudo evtestYou'll hopefully see your joystick listed along with a name like "/dev/input/eventXX"
Now run:
cat /dev/input/event/XXIf you get a permission error, you need to set a udev rule to change the mode of
your device (see below)
Also, try:
sudo udevadm info --query=all --name=input/eventXXIf you see a line stating ID_INPUT_JOYSTICK=1, great, if you don't see it,
you need to set up an udev rule to force this variable.
A combined rule for the Saitek Pro Flight Rudder Pedals to fix both issues looks
like:
SUBSYSTEM=="input", ATTRS{idProduct}=="0763", ATTRS{idVendor}=="06a3", MODE="0666", ENV{ID_INPUT_JOYSTICK}="1"
SUBSYSTEM=="input", ATTRS{idProduct}=="0764", ATTRS{idVendor}=="06a3", MODE="0666", ENV{ID_INPUT_JOYSTICK}="1"You can set up similar rules for your device by changing the values listed in
idProduct and idVendor. To obtain these values, try:
sudo udevadm info -a --name=input/eventXX | grep idVendor
sudo udevadm info -a --name=input/eventXX | grep idProductIf multiple values come up for each of these, the one you want is the first one of each.
On other systems which ship with an older udev (such as CentOS), you may need
to set up a rule such as:
SUBSYSTEM=="input", ENV{ID_CLASS}=="joystick", ENV{ID_INPUT_JOYSTICK}="1"README Macos (`docs/README-macos.md`)
macOS
These instructions are for people using Apple's macOS.
From the developer's point of view, macOS is a sort of hybrid Mac and
Unix system, and you have the option of using either traditional
command line tools or Apple's IDE Xcode.
SDL supports building with Xcode 12.2 and the macOS 11.0 SDK or newer, and deploying to macOS 10.13 and newer.
Command Line Build
To build SDL using the command line, use the CMake build script:
mkdir build
cd build
cmake .. -DCMAKE_OSX_DEPLOYMENT_TARGET=10.13
cmake --build .
sudo cmake --install .You can also build SDL as a Universal library (a single binary for both
64-bit Intel and ARM architectures):
mkdir build
cd build
cmake .. "-DCMAKE_OSX_ARCHITECTURES=arm64;x86_64" -DCMAKE_OSX_DEPLOYMENT_TARGET=10.13
cmake --build .
sudo cmake --install .If you are getting errors building SDL_mfijoystick.m with Xcode 12.2, find your SDKs
directory and move MacOSX10.15.sdk out of the way so it isn't accidentally being used
by the build environment.
To use the library once it's built, you essential have two possibilities:
use the traditional autoconf/automake/make method, or use Xcode.
Caveats for using SDL with macOS
If you register your own NSApplicationDelegate (using [NSApp setDelegate:]),
SDL will not register its own. This means that SDL will not terminate using
SDL_Quit if it receives a termination request, it will terminate like a
normal app, and it will not send a SDL_EVENT_DROP_FILE when you request to open a
file with the app. To solve these issues, put the following code in your
NSApplicationDelegate implementation:
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
{
if (SDL_EventEnabled(SDL_EVENT_QUIT)) {
SDL_Event event;
SDL_zero(event);
event.type = SDL_EVENT_QUIT;
SDL_PushEvent(&event);
}
return NSTerminateCancel;
}
- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename
{
if (SDL_EventEnabled(SDL_EVENT_DROP_FILE)) {
SDL_Event event;
SDL_zero(event);
event.type = SDL_EVENT_DROP_FILE;
event.drop.file = SDL_strdup([filename UTF8String]);
return SDL_PushEvent(&event);
}
return NO;
}SDL is not designed to be used in setuid or setgid executables.
Using the Simple DirectMedia Layer with a traditional Makefile
An existing build system for your SDL app has good chances to work almost
unchanged on macOS, as long as you link with the SDL framework. However,
to produce a "real" Mac binary that you can distribute to users, you need
to put the generated binary into a so called "bundle", which is basically
a fancy folder with a name like "MyCoolGame.app".
To get this build automatically, add something like the following rule to
your Makefile.am:
bundle_contents = APP_NAME.app/Contents
APP_NAME_bundle: EXE_NAME
mkdir -p $(bundle_contents)/MacOS
mkdir -p $(bundle_contents)/Resources
echo "APPL????" > $(bundle_contents)/PkgInfo
$(INSTALL_PROGRAM) $< $(bundle_contents)/MacOS/You should replace EXE_NAME with the name of the executable. APP_NAME is
what will be visible to the user in the Finder. Usually it will be the same
as EXE_NAME but capitalized. E.g. if EXE_NAME is "testgame" then APP_NAME
usually is "TestGame". You might also want to use @PACKAGE@ to use the
package name as specified in your configure.ac file.
If your project builds more than one application, you will have to do a bit
more. For each of your target applications, you need a separate rule.
If you want the created bundles to be installed, you may want to add this
rule to your Makefile.am:
install-exec-hook: APP_NAME_bundle
rm -rf $(DESTDIR)$(prefix)/Applications/APP_NAME.app
mkdir -p $(DESTDIR)$(prefix)/Applications/
cp -r $< /$(DESTDIR)$(prefix)Applications/This rule takes the Bundle created by the rule from step 3 and installs them
into "$(DESTDIR)$(prefix)/Applications/".
Again, if you want to install multiple applications, you will have to augment
the make rule accordingly.
But beware! That is only part of the story! With the above, you end up with
a barebones .app bundle, which is double-clickable from the Finder. But
there are some more things you should do before shipping your product...
You'll need to copy the SDL framework into the Contents/Frameworks
folder in your bundle, so it is included along with your application.Add an 'Info.plist' to your application. That is a special XML file which
contains some meta-information about your application (like some copyright
information, the version of your app, the name of an optional icon file,
and other things). Part of that information is displayed by the Finder
when you click on the .app, or if you look at the "Get Info" window.
More information about Info.plist files can be found on Apple's homepage.
As a final remark, let me add that I use some of the techniques (and some
variations of them) in Exult and
ScummVM; both are available in source on
the net, so feel free to take a peek at them for inspiration!
Using the Simple DirectMedia Layer with Xcode
These instructions are for using Apple's Xcode IDE to build SDL applications.
First steps
The first thing to do is to unpack the Xcode.tar.gz archive in the
top level SDL directory (where the Xcode.tar.gz archive resides).
Because Stuffit Expander will unpack the archive into a subdirectory,
you should unpack the archive manually from the command line:
cd [path_to_SDL_source]
tar zxf Xcode.tar.gzThis will create a new folder called Xcode, which you can browse
normally from the Finder.
Building the Framework
The SDL Library is packaged as a framework bundle, an organized
relocatable folder hierarchy of executable code, interface headers,
and additional resources. For practical purposes, you can think of a
framework as a more user and system-friendly shared library, whose library
file behaves more or less like a standard UNIX shared library.
To build the framework, simply open the framework project and build it.
By default, the framework bundle "SDL.framework" is installed in
/Library/Frameworks. Therefore, the testers and project stationary expect
it to be located there. However, it will function the same in any of the
following locations:
- ~/Library/Frameworks
- /Local/Library/Frameworks
- /System/Library/Frameworks
Build Options
There are two "Build Styles" (See the "Targets" tab) for SDL.
"Deployment" should be used if you aren't tweaking the SDL library.
"Development" should be used to debug SDL apps or the library itself.
Building the Testers
Open the SDLTest project and build away!
Using the Project Stationary
Copy the stationary to the indicated folders to access it from
the "New Project" and "Add target" menus. What could be easier?
Setting up a new project by hand
Some of you won't want to use the Stationary so I'll give some tips:
(this is accurate as of Xcode 12.5.)
- Click "File" -> "New" -> "Project...
- Choose "macOS" and then "App" from the "Application" section.
- Fill out the options in the next window. User interface is "XIB" and
Language is "Objective-C". - Remove "main.m" from your project
- Remove "MainMenu.xib" from your project
- Remove "AppDelegates.*" from your project
- Add "$(HOME)/Library/Frameworks/SDL.framework/Headers" to include path
- Add "$(HOME)/Library/Frameworks" to the frameworks search path
- Add "-framework SDL -framework Foundation -framework AppKit" to "OTHER_LDFLAGS"
- Add your files
- Clean and build
Building from command line
Use xcode-build in the same directory as your .pbxproj file
Running your app
You can send command line args to your app by either invoking it from
the command line (in *.app/Contents/MacOS) or by entering them in the
Executables" panel of the target settings.
Implementation Notes
Some things that may be of interest about how it all works...
Working directory
In SDL 1.2, the working directory of your SDL app is by default set to its
parent, but this is no longer the case in SDL 2.0 and later. SDL2 does not
change the working directory, which means it'll be whatever the command line
prompt that launched the program was using, or if launched by double-clicking
in the Finder, it will be "/", the root of the filesystem. Plan accordingly!
You can use SDL_GetBasePath() to find where the program is running from and
chdir() there directly.
You have a Cocoa App!
Your SDL app is essentially a Cocoa application. When your app
starts up and the libraries finish loading, a Cocoa procedure is called,
which sets up the working directory and calls your main() method.
You are free to modify your Cocoa app with generally no consequence
to SDL. You cannot, however, easily change the SDL window itself.
Functionality may be added in the future to help this.
Raw Mouse Input
SDL uses the Game Controller framework's GCMouse API to provide raw,
unaccelerated mouse input in relative mode. This is ideal for games
and applications requiring precise 1:1 mouse movement.
On versions older than macOS 11.0 (Big Sur), SDL would fall back to
NSEvent-based mouse input, which includes system mouse acceleration.
To use accelerated (system-scaled) mouse movement on macOS 14.0+, set the hint:
SDL_SetHint(SDL_HINT_MOUSE_RELATIVE_SYSTEM_SCALE, "1");Bug reports
Bugs are tracked at the GitHub issue tracker.
Please feel free to report bugs there!
README Main Functions (`docs/README-main-functions.md`)
Where an SDL program starts running.
History
SDL has a long, complicated history with starting a program.
In most of the civilized world, an application starts in a C-callable
function named "main". You probably learned it a long time ago:
int main(int argc, char **argv)
{
printf("Hello world!\n");
return 0;
}But not all platforms work like this. Windows apps might want a different
function named "WinMain", for example, so SDL set out to paper over this
difference.
Generally how this would work is: your app would always use the "standard"main(argc, argv) function as its entry point, and #include the proper
SDL header before that, which did some macro magic. On platforms that used
a standard main, it would do nothing and what you saw was what you got.
But those other platforms! If they needed something that wasn't main,
SDL's macro magic would quietly rename your function to SDL_main, and
provide its own entry point that called it. Your app was none the wiser and
your code worked everywhere without changes.
The main entry point in SDL3
Previous versions of SDL had a static library, SDLmain, that you would link
your app against. SDL3 still has the same macro tricks, but the static library
is gone. Now it's supplied by a "single-header library," which means you#include <SDL3/SDL_main.h> and that header will insert a small amount of
code into the source file that included it, so you no longer have to worry
about linking against an extra library that you might need on some platforms.
You just build your app and it works.
You should only include SDL_main.h from one file (the umbrella header,
SDL.h, does not include it), and know that it will #define main to
something else, so if you use this symbol elsewhere as a variable name, etc,
it can cause you unexpected problems.
SDL_main.h will also include platform-specific code (WinMain or whatnot) that
calls your actual main function. This is compiled directly into your
program.
If for some reason you need to include SDL_main.h in a file but also don't
want it to generate this platform-specific code, you should define a special
macro before including the header:
#define SDL_MAIN_NOIMPLIf you are moving from SDL2, remove any references to the SDLmain static
library from your build system, and you should be done. Things should work as
they always have.
If you have never controlled your process's entry point (you are using SDL
as a module from a general-purpose scripting language interpreter, or you're
using SDL in a plugin for some otherwise-unrelated app), then there is nothing
required of you here; there is no startup code in SDL's entry point code that
is required, so using SDL_main.h is completely optional. Just start using
the SDL API when you are ready.
Main callbacks in SDL3
There is a second option in SDL3 for how to structure your program. This is
completely optional and you can ignore it if you're happy using a standard
"main" function.
Some platforms would rather your program operate in chunks. Most of the time,
games tend to look like this at the highest level:
int main(int argc, char **argv)
{
initialize();
while (keep_running()) {
handle_new_events();
do_one_frame_of_stuff();
}
deinitialize();
}There are platforms that would rather be in charge of that while loop:
iOS would rather you return from main() immediately and then it will let you
know that it's time to update and draw the next frame of video. Emscripten
(programs that run on a web page) absolutely requires this to function at all.
Video targets like Wayland can notify the app when to draw a new frame, to
save battery life and cooperate with the compositor more closely.
In most cases, you can add special-case code to your program to deal with this
on different platforms, but SDL3 offers a system to handle this transparently on
the app's behalf.
To use this, you have to redesign the highest level of your app a little. Once
you do, it'll work on all supported SDL platforms without problems and#ifdefs in your code.
Instead of providing a "main" function, under this system, you would provide
several functions that SDL will call as appropriate.
Using the callback entry points works on every platform, because on platforms
that don't require them, we can fake them with a simple loop in an internal
implementation of the usual SDL_main.
The primary way we expect people to write SDL apps is still with SDL_main, and
this is not intended to replace it. If the app chooses to use this, it just
removes some platform-specific details they might have to otherwise manage,
and maybe removes a barrier to entry on some future platform. And you might
find you enjoy structuring your program like this more!
How to use main callbacks in SDL3
To enable the callback entry points, you include SDL_main.h with an extra define,
from a single source file in your project:
#define SDL_MAIN_USE_CALLBACKS
#include <SDL3/SDL_main.h>Once you do this, you do not write a "main" function at all (and if you do,
the app will likely fail to link). Instead, you provide the following
functions:
First:
SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv);This will be called once before anything else. argc/argv work like they
always do. If this returns SDL_APP_CONTINUE, the app runs. If it returns
SDL_APP_FAILURE, the app calls SDL_AppQuit and terminates with an exit
code that reports an error to the platform. If it returns SDL_APP_SUCCESS,
the app calls SDL_AppQuit and terminates with an exit code that reports
success to the platform. This function should not go into an infinite
mainloop; it should do any one-time startup it requires and then return.
If you want to, you can assign a pointer to *appstate, and this pointer
will be made available to you in later functions calls in their appstate
parameter. This allows you to avoid global variables, but is totally
optional. If you don't set this, the pointer will be NULL in later function
calls.
Then:
SDL_AppResult SDL_AppIterate(void *appstate);This is called over and over, possibly at the refresh rate of the display or
some other metric that the platform dictates. This is where the heart of your
app runs. It should return as quickly as reasonably possible, but it's not a
"run one memcpy and that's all the time you have" sort of thing. The app
should do any game updates, and render a frame of video. If it returns
SDL_APP_FAILURE, SDL will call SDL_AppQuit and terminate the process with an
exit code that reports an error to the platform. If it returns
SDL_APP_SUCCESS, the app calls SDL_AppQuit and terminates with an exit code
that reports success to the platform. If it returns SDL_APP_CONTINUE, then
SDL_AppIterate will be called again at some regular frequency. The platform
may choose to run this more or less (perhaps less in the background, etc),
or it might just call this function in a loop as fast as possible. You do
not check the event queue in this function (SDL_AppEvent exists for that).
Next:
SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event);This will be called whenever an SDL event arrives. Your app should not call
SDL_PollEvent, SDL_PumpEvent, etc, as SDL will manage all this for you. Return
values are the same as from SDL_AppIterate(), so you can terminate in response
to SDL_EVENT_QUIT, etc.
Finally:
void SDL_AppQuit(void *appstate, SDL_AppResult result);This is called once before terminating the app--assuming the app isn't being
forcibly killed or crashed--as a last chance to clean up. After this returns,
SDL will call SDL_Quit so the app doesn't have to (but it's safe for the app
to call it, too). Process termination proceeds as if the app returned normally
from main(), so atexit handles will run, if your platform supports that.
If you set *appstate during SDL_AppInit, this is where you should free that
data, as this pointer will not be provided to your app again.
The SDL_AppResult value that terminated the app is provided here, in case
it's useful to know if this was a successful or failing run of the app.
Using main functions from other languages
If you're not using C/C++, using SDL's entry points is still possible but is
more complex. Please refer to https://wiki.libsdl.org/SDL3/NonstandardStartup
for the technical details.
Summary and Best Practices
Always Include SDL_main.h in One Source File: When working with SDL,
remember that SDL_main.h must only be included in one source file in your
project. Including it in multiple files will lead to conflicts and undefined
behavior.Avoid Redefining main: If you're using SDL's entry point system (which
renamesmaintoSDL_main), do not definemainyourself. SDL takes care
of this for you, and redefining it can cause issues, especially when linking
with SDL libraries.Using SDL's Callback System: If you're working with more complex
scenarios, such as requiring more control over your application's flow
(e.g., with games or apps that need extensive event handling), consider
using SDL's callback system. Define the necessary callbacks and SDL will
handle initialization, event processing, and cleanup automatically.Platform-Specific Considerations: On platforms like Windows, SDL handles
the platform-specific entry point (likeWinMain) automatically. This means
you don't need to worry about writing platform-specific entry code when
using SDL.When to Skip SDL_main.h: If you do not require SDL's custom entry point
(for example, if you're integrating SDL into an existing application or a
scripting environment), you can omit SDL_main.h. However, this will limit
SDL's ability to abstract away platform-specific entry point details.
README Migration (`docs/README-migration.md`)
Migrating to SDL 3.0
This guide provides useful information for migrating applications from SDL 2.0 to SDL 3.0.
Details on API changes are organized by SDL 2.0 header below.
The file with your main() function should include <SDL3/SDL_main.h>, as that is no longer included in SDL.h.
Functions that previously returned a negative error code now return bool.
Code that used to look like this:
if (SDL_Function() < 0 || SDL_Function() == -1) {
/* Failure... */
}or
if (SDL_Function() == 0) {
/* Success... */
}or
if (!SDL_Function()) {
/* Success... */
}should be changed to:
if (SDL_Function()) {
/* Success... */
} else {
/* Failure... */
}This only applies to camel case functions, e.g. SDL_[A-Z]*. Lower case functions like SDL_strcmp() and SDL_memcmp() are unchanged, matching their C runtime counterpart.
Many functions and symbols have been renamed. We have provided a handy Python script rename_symbols.py to rename SDL2 functions to their SDL3 counterparts:
rename_symbols.py --all-symbols source_code_pathIt's also possible to apply a semantic patch to migrate more easily to SDL3: SDL_migration.cocci
SDL headers should now be included as #include <SDL3/SDL.h>. Typically that's the only SDL header you'll need in your application unless you are using OpenGL or Vulkan functionality. SDL_image, SDL_mixer, SDL_net, SDL_ttf and SDL_rtf have also their preferred include path changed: for SDL_image, it becomes #include <SDL3_image/SDL_image.h>. We have provided a handy Python script rename_headers.py to rename SDL2 headers to their SDL3 counterparts:
rename_headers.py source_code_pathSome macros are renamed and/or removed in SDL3. We have provided a handy Python script rename_macros.py to replace these, and also add fixme comments on how to further improve the code:
rename_macros.py source_code_pathCMake users should use this snippet to include SDL support in their project:
find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3)
target_link_libraries(mygame PRIVATE SDL3::SDL3)Autotools users should use this snippet to include SDL support in their project:
PKG_CHECK_MODULES([SDL3], [sdl3])and then add $SDL3_CFLAGS to their project CFLAGS and $SDL3_LIBS to their project LDFLAGS.
Makefile users can use this snippet to include SDL support in their project:
CFLAGS += $(shell pkg-config sdl3 --cflags)
LDFLAGS += $(shell pkg-config sdl3 --libs)The SDL3test library has been renamed SDL3_test.
The SDLmain library has been removed, it's been entirely replaced by SDL_main.h.
The vi format comments have been removed from source code. Vim users can use the editorconfig plugin to automatically set tab spacing for the SDL coding style.
Installed SDL CMake configuration files no longer define SDL3_PREFIX, SDL3_EXEC_PREFIX, SDL3_INCLUDE_DIR, SDL3_INCLUDE_DIRS, SDL3_BINDIR or SDL3_LIBDIR. Users are expected to use CMake generator expressions with SDL3::SDL3, SDL3::SDL3-shared, SDL3::SDL3-static or SDL3::Headers. By no longer defining these CMake variables, using a system SDL3 or using a vendoring SDL3 behave in the same way.
SDL_atomic.h
The following structures have been renamed:
- SDL_atomic_t => SDL_AtomicInt
The following functions have been renamed:
- SDL_AtomicAdd() => SDL_AddAtomicInt()
- SDL_AtomicCAS() => SDL_CompareAndSwapAtomicInt()
- SDL_AtomicCASPtr() => SDL_CompareAndSwapAtomicPointer()
- SDL_AtomicGet() => SDL_GetAtomicInt()
- SDL_AtomicGetPtr() => SDL_GetAtomicPointer()
- SDL_AtomicLock() => SDL_LockSpinlock()
- SDL_AtomicSet() => SDL_SetAtomicInt()
- SDL_AtomicSetPtr() => SDL_SetAtomicPointer()
- SDL_AtomicTryLock() => SDL_TryLockSpinlock()
- SDL_AtomicUnlock() => SDL_UnlockSpinlock()
SDL_audio.h
The audio subsystem in SDL3 is dramatically different than SDL2. The primary way to play audio is no longer an audio callback; instead you bind SDL_AudioStreams to devices; however, there is still a callback method available if needed.
The SDL 1.2 audio compatibility API has also been removed, as it was a simplified version of the audio callback interface.
SDL3 will not implicitly initialize the audio subsystem on your behalf if you open a device without doing so. Please explicitly call SDL_Init(SDL_INIT_AUDIO) at some point.
SDL2 referred to audio devices that record sound as "capture" devices, and ones that play sound to speakers as "output" devices. In SDL3, we've changed this terminology to be "recording" devices and "playback" devices, which we hope is more clear.
SDL3's audio subsystem offers an enormous amount of power over SDL2, but if you just want a simple migration of your existing code, you can ignore most of it. The simplest migration path from SDL2 looks something like this:
In SDL2, you might have done something like this to play audio...
void SDLCALL MyAudioCallback(void *userdata, Uint8 * stream, int len)
{
/* Calculate a little more audio here, maybe using `userdata`, write it to `stream` */
}
/* ...somewhere near startup... */
SDL_AudioSpec my_desired_audio_format;
SDL_zero(my_desired_audio_format);
my_desired_audio_format.format = AUDIO_S16;
my_desired_audio_format.channels = 2;
my_desired_audio_format.freq = 44100;
my_desired_audio_format.samples = 1024;
my_desired_audio_format.callback = MyAudioCallback;
my_desired_audio_format.userdata = &my_audio_callback_user_data;
SDL_AudioDeviceID my_audio_device = SDL_OpenAudioDevice(NULL, 0, &my_desired_audio_format, NULL, 0);
SDL_PauseAudioDevice(my_audio_device, 0);...in SDL3, you can do this...
void SDLCALL MyNewAudioCallback(void *userdata, SDL_AudioStream *stream, int additional_amount, int total_amount)
{
/* Calculate a little more audio here, maybe using `userdata`, write it to `stream`
*
* If you want to use the original callback, you could do something like this:
*/
if (additional_amount > 0) {
Uint8 *data = SDL_stack_alloc(Uint8, additional_amount);
if (data) {
MyAudioCallback(userdata, data, additional_amount);
SDL_PutAudioStreamData(stream, data, additional_amount);
SDL_stack_free(data);
}
}
}
/* ...somewhere near startup... */
const SDL_AudioSpec spec = { SDL_AUDIO_S16, 2, 44100 };
SDL_AudioStream *stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, MyNewAudioCallback, &my_audio_callback_user_data);
SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(stream));If you used SDL_QueueAudio instead of a callback in SDL2, this is also straightforward.
/* ...somewhere near startup... */
const SDL_AudioSpec spec = { SDL_AUDIO_S16, 2, 44100 };
SDL_AudioStream *stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, NULL, NULL);
SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(stream));
/* ...in your main loop... */
/* calculate a little more audio into `buf`, add it to `stream` */
SDL_PutAudioStreamData(stream, buf, buflen);...these same migration examples apply to audio recording, just using SDL_GetAudioStreamData instead of SDL_PutAudioStreamData.
SDL_AudioInit() and SDL_AudioQuit() have been removed. Instead you can call SDL_InitSubSystem() and SDL_QuitSubSystem() with SDL_INIT_AUDIO, which will properly refcount the subsystems. You can choose a specific audio driver using SDL_AUDIO_DRIVER hint.
The SDL_AUDIO_ALLOW_* symbols have been removed; now one may request the format they desire from the audio device, but ultimately SDL_AudioStream will manage the difference. One can use SDL_GetAudioDeviceFormat() to see what the final format is, if any "allowed" changes should be accommodated by the app.
SDL_AudioDeviceID now represents both an open audio device's handle (a "logical" device) and the instance ID that the hardware owns as long as it exists on the system (a "physical" device). The separation between device instances and device indexes is gone, and logical and physical devices are almost entirely interchangeable at the API level.
Devices are opened by physical device instance ID, and a new logical instance ID is generated by the open operation; This allows any device to be opened multiple times, possibly by unrelated pieces of code. SDL will manage the logical devices to provide a single stream of audio to the physical device behind the scenes.
Devices are not opened by an arbitrary string name anymore, but by device instance ID (or magic numbers to request a reasonable default, like a NULL string would do in SDL2). In SDL2, the string was used to open both a standard list of system devices, but also allowed for arbitrary devices, such as hostnames of network sound servers. In SDL3, many of the backends that supported arbitrary device names are obsolete and have been removed; of those that remain, arbitrary devices will be opened with a default device ID and an SDL_hint, so specific end-users can set an environment variable to fit their needs and apps don't have to concern themselves with it.
Many functions that would accept a device index and an iscapture parameter now just take an SDL_AudioDeviceID, as they are unique across all devices, instead of separate indices into playback and recording device lists.
Rather than iterating over audio devices using a device index, there are new functions, SDL_GetAudioPlaybackDevices() and SDL_GetAudioRecordingDevices(), to get the current list of devices, and new functions to get information about devices from their instance ID:
{
if (SDL_InitSubSystem(SDL_INIT_AUDIO)) {
int i, num_devices;
SDL_AudioDeviceID *devices = SDL_GetAudioPlaybackDevices(&num_devices);
if (devices) {
for (i = 0; i < num_devices; ++i) {
SDL_AudioDeviceID instance_id = devices[i];
SDL_Log("AudioDevice %" SDL_PRIu32 ": %s", instance_id, SDL_GetAudioDeviceName(instance_id));
}
SDL_free(devices);
}
SDL_QuitSubSystem(SDL_INIT_AUDIO);
}
}SDL_LockAudioDevice() and SDL_UnlockAudioDevice() have been removed, since there is no callback in another thread to protect. SDL's audio subsystem and SDL_AudioStream maintain their own locks internally, so audio streams are safe to use from any thread. If the app assigns a callback to a specific stream, it can use the stream's lock through SDL_LockAudioStream() if necessary.
SDL_PauseAudioDevice() no longer takes a second argument; it always pauses the device. To unpause, use SDL_ResumeAudioDevice().
Audio devices, opened by SDL_OpenAudioDevice(), no longer start in a paused state, as they don't begin processing audio until a stream is bound.
SDL_GetAudioDeviceStatus() has been removed; there is now SDL_AudioDevicePaused().
SDL_QueueAudio(), SDL_DequeueAudio, and SDL_ClearQueuedAudio and SDL_GetQueuedAudioSize() have been removed; an SDL_AudioStream bound to a device provides the exact same functionality.
APIs that use channel counts used to use a Uint8 for the channel; now they use int.
SDL_AudioSpec has been reduced; now it only holds format, channel, and sample rate. SDL_GetSilenceValueForFormat() can provide the information from the SDL_AudioSpec's removed silence field. SDL3 now manages the removed samples field; apps that want more control over device latency and throughput can force a newly-opened device's sample count with the SDL_HINT_AUDIO_DEVICE_SAMPLE_FRAMES hint, but most apps should not risk messing with the defaults. The other SDL2 SDL_AudioSpec fields aren't relevant anymore.
SDL_GetAudioDeviceSpec() is removed; use SDL_GetAudioDeviceFormat() instead.
SDL_GetDefaultAudioInfo() is removed; SDL_GetAudioDeviceFormat() with SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK or SDL_AUDIO_DEVICE_DEFAULT_RECORDING. There is no replacement for querying the default device name; the string is no longer used to open devices, and SDL3 will migrate between physical devices on the fly if the system default changes, so if you must show this to the user, a generic name like "System default" is recommended.
SDL_MixAudioFormat() and SDL_MIX_MAXVOLUME have been removed in favour of SDL_MixAudio(), which now takes the audio format, and a float volume between 0.0 and 1.0.
SDL_FreeWAV has been removed and calls can be replaced with SDL_free.
SDL_LoadWAV() is a proper function now and no longer a macro (but offers the same functionality otherwise).
SDL_LoadWAV_IO() and SDL_LoadWAV() return an bool now, like most of SDL. They no longer return a pointer to an SDL_AudioSpec.
SDL_AudioCVT interface has been removed, the SDL_AudioStream interface (for audio supplied in pieces) or the new SDL_ConvertAudioSamples() function (for converting a complete audio buffer in one call) can be used instead.
Code that used to look like this:
SDL_AudioCVT cvt;
SDL_BuildAudioCVT(&cvt, src_format, src_channels, src_rate, dst_format, dst_channels, dst_rate);
cvt.len = src_len;
cvt.buf = (Uint8 *) SDL_malloc(src_len * cvt.len_mult);
SDL_memcpy(cvt.buf, src_data, src_len);
SDL_ConvertAudio(&cvt);
do_something(cvt.buf, cvt.len_cvt);should be changed to:
Uint8 *dst_data = NULL;
int dst_len = 0;
const SDL_AudioSpec src_spec = { src_format, src_channels, src_rate };
const SDL_AudioSpec dst_spec = { dst_format, dst_channels, dst_rate };
if (!SDL_ConvertAudioSamples(&src_spec, src_data, src_len, &dst_spec, &dst_data, &dst_len)) {
/* error */
}
do_something(dst_data, dst_len);
SDL_free(dst_data);AUDIO_U16, AUDIO_U16LSB, AUDIO_U16MSB, and AUDIO_U16SYS have been removed. They were not heavily used, and one could not memset a buffer in this format to silence with a single byte value. Use a different audio format.
If you need to convert U16 audio data to a still-supported format at runtime, the fastest, lossless conversion is to SDL_AUDIO_S16:
/* this converts the buffer in-place. The buffer size does not change. */
Sint16 *audio_ui16_to_si16(Uint16 *buffer, const size_t num_samples)
{
size_t i;
const Uint16 *src = buffer;
Sint16 *dst = (Sint16 *) buffer;
for (i = 0; i < num_samples; i++) {
dst[i] = (Sint16) (src[i] ^ 0x8000);
}
return dst;
}All remaining AUDIO_* symbols have been renamed to SDL_AUDIO_* for API consistency, but otherwise are identical in value and usage.
In SDL2, SDL_AudioStream would convert/resample audio data during input (via SDL_AudioStreamPut). In SDL3, it does this work when requesting audio (via SDL_GetAudioStreamData, which would have been SDL_AudioStreamGet in SDL2). The way you use an AudioStream is roughly the same, just be aware that the workload moved to a different phase.
In SDL2, SDL_AudioStreamAvailable() returns 0 if passed a NULL stream. In SDL3, the equivalent SDL_GetAudioStreamAvailable() call returns -1 and sets an error string, which matches other audiostream APIs' behavior.
In SDL2, SDL_AUDIODEVICEREMOVED events would fire for open devices with the which field set to the SDL_AudioDeviceID of the lost device, and in later SDL2 releases, would also fire this event with a which field of zero for unopened devices, to signify that the app might want to refresh the available device list. In SDL3, this event works the same, except it won't ever fire with a zero; in this case it'll return the physical device's SDL_AudioDeviceID. Any still-open SDL_AudioDeviceIDs generated from this device with SDL_OpenAudioDevice() will also fire a separate event.
The following functions have been renamed:
- SDL_AudioStreamAvailable() => SDL_GetAudioStreamAvailable()
- SDL_AudioStreamClear() => SDL_ClearAudioStream(), returns bool
- SDL_AudioStreamFlush() => SDL_FlushAudioStream(), returns bool
- SDL_AudioStreamGet() => SDL_GetAudioStreamData()
- SDL_AudioStreamPut() => SDL_PutAudioStreamData(), returns bool
- SDL_FreeAudioStream() => SDL_DestroyAudioStream()
- SDL_LoadWAV_RW() => SDL_LoadWAV_IO(), returns bool
- SDL_MixAudioFormat() => SDL_MixAudio(), returns bool
- SDL_NewAudioStream() => SDL_CreateAudioStream()
The following functions have been removed:
- SDL_GetNumAudioDevices()
- SDL_GetAudioDeviceSpec()
- SDL_ConvertAudio()
- SDL_BuildAudioCVT()
- SDL_OpenAudio()
- SDL_CloseAudio()
- SDL_PauseAudio()
- SDL_GetAudioStatus()
- SDL_GetAudioDeviceStatus()
- SDL_GetDefaultAudioInfo()
- SDL_LockAudio()
- SDL_LockAudioDevice()
- SDL_UnlockAudio()
- SDL_UnlockAudioDevice()
- SDL_MixAudio()
- SDL_QueueAudio()
- SDL_DequeueAudio()
- SDL_ClearAudioQueue()
- SDL_GetQueuedAudioSize()
The following symbols have been renamed:
- AUDIO_F32 => SDL_AUDIO_F32LE
- AUDIO_F32LSB => SDL_AUDIO_F32LE
- AUDIO_F32MSB => SDL_AUDIO_F32BE
- AUDIO_F32SYS => SDL_AUDIO_F32
- AUDIO_S16 => SDL_AUDIO_S16LE
- AUDIO_S16LSB => SDL_AUDIO_S16LE
- AUDIO_S16MSB => SDL_AUDIO_S16BE
- AUDIO_S16SYS => SDL_AUDIO_S16
- AUDIO_S32 => SDL_AUDIO_S32LE
- AUDIO_S32LSB => SDL_AUDIO_S32LE
- AUDIO_S32MSB => SDL_AUDIO_S32BE
- AUDIO_S32SYS => SDL_AUDIO_S32
- AUDIO_S8 => SDL_AUDIO_S8
- AUDIO_U8 => SDL_AUDIO_U8
The following symbols have been removed:
- SDL_MIX_MAXVOLUME - mixer volume is now a float between 0.0 and 1.0
SDL_cpuinfo.h
The intrinsics headers (mmintrin.h, etc.) have been moved to <SDL3/SDL_intrin.h> and are no longer automatically included in SDL.h.
SDL_Has3DNow() has been removed; there is no replacement.
SDL_HasRDTSC() has been removed; there is no replacement. Don't use the RDTSC opcode in modern times, use SDL_GetPerformanceCounter and SDL_GetPerformanceFrequency instead.
SDL_SIMDAlloc(), SDL_SIMDRealloc(), and SDL_SIMDFree() have been removed. You can use SDL_aligned_alloc() and SDL_aligned_free() with SDL_GetSIMDAlignment() to get the same functionality.
The following functions have been renamed:
- SDL_GetCPUCount() => SDL_GetNumLogicalCPUCores()
- SDL_SIMDGetAlignment() => SDL_GetSIMDAlignment()
SDL_endian.h
The following functions have been renamed:
- SDL_SwapBE16() => SDL_Swap16BE()
- SDL_SwapBE32() => SDL_Swap32BE()
- SDL_SwapBE64() => SDL_Swap64BE()
- SDL_SwapLE16() => SDL_Swap16LE()
- SDL_SwapLE32() => SDL_Swap32LE()
- SDL_SwapLE64() => SDL_Swap64LE()
SDL_error.h
The following functions have been removed:
- SDL_GetErrorMsg() - Can be implemented as
SDL_strlcpy(errstr, SDL_GetError(), maxlen);
SDL_events.h
SDL_PRESSED and SDL_RELEASED have been removed. For the most part you can replace uses of these with true and false respectively. Events which had a field state to represent these values have had those fields changed to bool down, e.g. event.key.state is now event.key.down.
The timestamp member of the SDL_Event structure now represents nanoseconds, and is populated with SDL_GetTicksNS()
The timestamp_us member of the sensor events has been renamed sensor_timestamp and now represents nanoseconds. This value is filled in from the hardware, if available, and may not be synchronized with values returned from SDL_GetTicksNS().
You should set the event.common.timestamp field before passing an event to SDL_PushEvent(). If the timestamp is 0 it will be filled in with SDL_GetTicksNS().
Event memory is now managed by SDL, so you should not free the data in SDL_EVENT_DROP_FILE, and if you want to hold onto the text in SDL_EVENT_TEXT_EDITING and SDL_EVENT_TEXT_INPUT events, you should make a copy of it. SDL_TEXTINPUTEVENT_TEXT_SIZE is no longer necessary and has been removed.
Mouse events use floating point values for mouse coordinates and relative motion values. You can get sub-pixel motion depending on the platform and display scaling.
The SDL_DISPLAYEVENT_* events have been moved to top level events, and SDL_DISPLAYEVENT has been removed. In general, handling this change just means checking for the individual events instead of first checking for SDL_DISPLAYEVENT and then checking for display events. You can compare the event >= SDL_EVENT_DISPLAY_FIRST and <= SDL_EVENT_DISPLAY_LAST if you need to see whether it's a display event.
The SDL_WINDOWEVENT_* events have been moved to top level events, and SDL_WINDOWEVENT has been removed. In general, handling this change just means checking for the individual events instead of first checking for SDL_WINDOWEVENT and then checking for window events. You can compare the event >= SDL_EVENT_WINDOW_FIRST and <= SDL_EVENT_WINDOW_LAST if you need to see whether it's a window event.
The SDL_EVENT_WINDOW_RESIZED event is always sent, even in response to SDL_SetWindowSize().
The SDL_EVENT_WINDOW_SIZE_CHANGED event has been removed, and you can use SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED to detect window backbuffer size changes.
The keysym field of key events has been removed to remove one level of indirection, and sym has been renamed key.
Code that used to look like this:
SDL_Event event;
SDL_Keycode key = event.key.keysym.sym;
SDL_Keymod mod = event.key.keysym.mod;should be changed to:
SDL_Event event;
SDL_Keycode key = event.key.key;
SDL_Keymod mod = event.key.mod;The gamepad event structures caxis, cbutton, cdevice, ctouchpad, and csensor have been renamed gaxis, gbutton, gdevice, gtouchpad, and gsensor.
The mouseX and mouseY fields of SDL_MouseWheelEvent have been renamed mouse_x and mouse_y.
The touchId and fingerId fields of SDL_TouchFingerEvent have been renamed touchID and fingerID.
The level field of SDL_JoyBatteryEvent has been split into state and percent.
The iscapture field of SDL_AudioDeviceEvent has been renamed recording.
SDL_QUERY, SDL_IGNORE, SDL_ENABLE, and SDL_DISABLE have been removed. You can use the functions SDL_SetEventEnabled() and SDL_EventEnabled() to set and query event processing state.
SDL_AddEventWatch() now returns false if it fails because it ran out of memory and couldn't add the event watch callback.
SDL_RegisterEvents() now returns 0 if it couldn't allocate any user events.
SDL_EventFilter functions now return bool.
The following symbols have been renamed:
- SDL_APP_DIDENTERBACKGROUND => SDL_EVENT_DID_ENTER_BACKGROUND
- SDL_APP_DIDENTERFOREGROUND => SDL_EVENT_DID_ENTER_FOREGROUND
- SDL_APP_LOWMEMORY => SDL_EVENT_LOW_MEMORY
- SDL_APP_TERMINATING => SDL_EVENT_TERMINATING
- SDL_APP_WILLENTERBACKGROUND => SDL_EVENT_WILL_ENTER_BACKGROUND
- SDL_APP_WILLENTERFOREGROUND => SDL_EVENT_WILL_ENTER_FOREGROUND
- SDL_AUDIODEVICEADDED => SDL_EVENT_AUDIO_DEVICE_ADDED
- SDL_AUDIODEVICEREMOVED => SDL_EVENT_AUDIO_DEVICE_REMOVED
- SDL_CLIPBOARDUPDATE => SDL_EVENT_CLIPBOARD_UPDATE
- SDL_CONTROLLERAXISMOTION => SDL_EVENT_GAMEPAD_AXIS_MOTION
- SDL_CONTROLLERBUTTONDOWN => SDL_EVENT_GAMEPAD_BUTTON_DOWN
- SDL_CONTROLLERBUTTONUP => SDL_EVENT_GAMEPAD_BUTTON_UP
- SDL_CONTROLLERDEVICEADDED => SDL_EVENT_GAMEPAD_ADDED
- SDL_CONTROLLERDEVICEREMAPPED => SDL_EVENT_GAMEPAD_REMAPPED
- SDL_CONTROLLERDEVICEREMOVED => SDL_EVENT_GAMEPAD_REMOVED
- SDL_CONTROLLERSENSORUPDATE => SDL_EVENT_GAMEPAD_SENSOR_UPDATE
- SDL_CONTROLLERSTEAMHANDLEUPDATED => SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED
- SDL_CONTROLLERTOUCHPADDOWN => SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN
- SDL_CONTROLLERTOUCHPADMOTION => SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION
- SDL_CONTROLLERTOUCHPADUP => SDL_EVENT_GAMEPAD_TOUCHPAD_UP
- SDL_DROPBEGIN => SDL_EVENT_DROP_BEGIN
- SDL_DROPCOMPLETE => SDL_EVENT_DROP_COMPLETE
- SDL_DROPFILE => SDL_EVENT_DROP_FILE
- SDL_DROPTEXT => SDL_EVENT_DROP_TEXT
- SDL_FINGERDOWN => SDL_EVENT_FINGER_DOWN
- SDL_FINGERMOTION => SDL_EVENT_FINGER_MOTION
- SDL_FINGERUP => SDL_EVENT_FINGER_UP
- SDL_FIRSTEVENT => SDL_EVENT_FIRST
- SDL_JOYAXISMOTION => SDL_EVENT_JOYSTICK_AXIS_MOTION
- SDL_JOYBALLMOTION => SDL_EVENT_JOYSTICK_BALL_MOTION
- SDL_JOYBATTERYUPDATED => SDL_EVENT_JOYSTICK_BATTERY_UPDATED
- SDL_JOYBUTTONDOWN => SDL_EVENT_JOYSTICK_BUTTON_DOWN
- SDL_JOYBUTTONUP => SDL_EVENT_JOYSTICK_BUTTON_UP
- SDL_JOYDEVICEADDED => SDL_EVENT_JOYSTICK_ADDED
- SDL_JOYDEVICEREMOVED => SDL_EVENT_JOYSTICK_REMOVED
- SDL_JOYHATMOTION => SDL_EVENT_JOYSTICK_HAT_MOTION
- SDL_KEYDOWN => SDL_EVENT_KEY_DOWN
- SDL_KEYMAPCHANGED => SDL_EVENT_KEYMAP_CHANGED
- SDL_KEYUP => SDL_EVENT_KEY_UP
- SDL_LASTEVENT => SDL_EVENT_LAST
- SDL_LOCALECHANGED => SDL_EVENT_LOCALE_CHANGED
- SDL_MOUSEBUTTONDOWN => SDL_EVENT_MOUSE_BUTTON_DOWN
- SDL_MOUSEBUTTONUP => SDL_EVENT_MOUSE_BUTTON_UP
- SDL_MOUSEMOTION => SDL_EVENT_MOUSE_MOTION
- SDL_MOUSEWHEEL => SDL_EVENT_MOUSE_WHEEL
- SDL_POLLSENTINEL => SDL_EVENT_POLL_SENTINEL
- SDL_QUIT => SDL_EVENT_QUIT
- SDL_RENDER_DEVICE_RESET => SDL_EVENT_RENDER_DEVICE_RESET
- SDL_RENDER_TARGETS_RESET => SDL_EVENT_RENDER_TARGETS_RESET
- SDL_SENSORUPDATE => SDL_EVENT_SENSOR_UPDATE
- SDL_TEXTEDITING => SDL_EVENT_TEXT_EDITING
- SDL_TEXTEDITING_EXT => SDL_EVENT_TEXT_EDITING_EXT
- SDL_TEXTINPUT => SDL_EVENT_TEXT_INPUT
- SDL_USEREVENT => SDL_EVENT_USER
The following symbols have been removed:
- SDL_DROPEVENT_DATA_SIZE - drop event data is dynamically allocated
- SDL_SYSWMEVENT - you can use SDL_SetWindowsMessageHook() and SDL_SetX11EventHook() to watch and modify system events before SDL sees them.
- SDL_TEXTEDITINGEVENT_TEXT_SIZE - text editing event data is dynamically allocated
The following structures have been renamed:
- SDL_ControllerAxisEvent => SDL_GamepadAxisEvent
- SDL_ControllerButtonEvent => SDL_GamepadButtonEvent
- SDL_ControllerDeviceEvent => SDL_GamepadDeviceEvent
- SDL_ControllerSensorEvent => SDL_GamepadSensorEvent
- SDL_ControllerTouchpadEvent => SDL_GamepadTouchpadEvent
The following functions have been removed:
- SDL_EventState() - replaced with SDL_SetEventEnabled()
- SDL_GetEventState() - replaced with SDL_EventEnabled()
The following enums have been renamed:
- SDL_eventaction => SDL_EventAction
The following functions have been renamed:
- SDL_DelEventWatch() => SDL_RemoveEventWatch()
SDL_gamecontroller.h
SDL_gamecontroller.h has been renamed SDL_gamepad.h, and all APIs have been renamed to match.
The SDL_EVENT_GAMEPAD_ADDED event now provides the joystick instance ID in the which member of the cdevice event structure.
The functions SDL_GetGamepads(), SDL_GetGamepadNameForID(), SDL_GetGamepadPathForID(), SDL_GetGamepadPlayerIndexForID(), SDL_GetGamepadGUIDForID(), SDL_GetGamepadVendorForID(), SDL_GetGamepadProductForID(), SDL_GetGamepadProductVersionForID(), and SDL_GetGamepadTypeForID() have been added to directly query the list of available gamepads.
The gamepad face buttons have been renamed from A/B/X/Y to North/South/East/West to indicate that they are positional rather than hardware-specific. You can use SDL_GetGamepadButtonLabel() to get the labels for the face buttons, e.g. A/B/X/Y or Cross/Circle/Square/Triangle. The hint SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS is ignored, and mappings that use this hint are translated correctly into positional buttons. Applications should provide a way for users to swap between South/East as their accept/cancel buttons, as this varies based on region and muscle memory. You can use an approach similar to the following to handle this:
/* Detailed source-code truncated for AI context efficiency. */SDL_GameControllerGetSensorDataWithTimestamp() has been removed. If you want timestamps for the sensor data, you should use the sensor_timestamp member of SDL_EVENT_GAMEPAD_SENSOR_UPDATE events.
SDL_CONTROLLER_TYPE_VIRTUAL has been removed, so virtual controllers can emulate other gamepad types. If you need to know whether a controller is virtual, you can use SDL_IsJoystickVirtual().
SDL_CONTROLLER_TYPE_AMAZON_LUNA has been removed, and can be replaced with this code:
bool SDL_IsJoystickAmazonLunaController(Uint16 vendor_id, Uint16 product_id)
{
return ((vendor_id == 0x1949 && product_id == 0x0419) ||
(vendor_id == 0x0171 && product_id == 0x0419));
}SDL_CONTROLLER_TYPE_GOOGLE_STADIA has been removed, and can be replaced with this code:
bool SDL_IsJoystickGoogleStadiaController(Uint16 vendor_id, Uint16 product_id)
{
return (vendor_id == 0x18d1 && product_id == 0x9400);
}SDL_CONTROLLER_TYPE_NVIDIA_SHIELD has been removed, and can be replaced with this code:
bool SDL_IsJoystickNVIDIASHIELDController(Uint16 vendor_id, Uint16 product_id)
{
return (vendor_id == 0x0955 && (product_id == 0x7210 || product_id == 0x7214));
}The inputType and outputType fields of SDL_GamepadBinding have been renamed input_type and output_type.
SDL_GetGamepadTouchpadFinger() takes a pointer to bool for the finger state instead of a pointer to Uint8.
The following enums have been renamed:
- SDL_GameControllerAxis => SDL_GamepadAxis
- SDL_GameControllerBindType => SDL_GamepadBindingType
- SDL_GameControllerButton => SDL_GamepadButton
- SDL_GameControllerType => SDL_GamepadType
The following structures have been renamed:
- SDL_GameController => SDL_Gamepad
The following structures have been removed:
- SDL_GameControllerButtonBind - replaced with SDL_GamepadBinding
The following functions have been renamed:
- SDL_GameControllerAddMapping() => SDL_AddGamepadMapping()
- SDL_GameControllerAddMappingsFromFile() => SDL_AddGamepadMappingsFromFile()
- SDL_GameControllerAddMappingsFromRW() => SDL_AddGamepadMappingsFromIO()
- SDL_GameControllerClose() => SDL_CloseGamepad()
- SDL_GameControllerFromInstanceID() => SDL_GetGamepadFromID()
- SDL_GameControllerFromPlayerIndex() => SDL_GetGamepadFromPlayerIndex()
- SDL_GameControllerGetAppleSFSymbolsNameForAxis() => SDL_GetGamepadAppleSFSymbolsNameForAxis()
- SDL_GameControllerGetAppleSFSymbolsNameForButton() => SDL_GetGamepadAppleSFSymbolsNameForButton()
- SDL_GameControllerGetAttached() => SDL_GamepadConnected()
- SDL_GameControllerGetAxis() => SDL_GetGamepadAxis()
- SDL_GameControllerGetAxisFromString() => SDL_GetGamepadAxisFromString()
- SDL_GameControllerGetButton() => SDL_GetGamepadButton()
- SDL_GameControllerGetButtonFromString() => SDL_GetGamepadButtonFromString()
- SDL_GameControllerGetFirmwareVersion() => SDL_GetGamepadFirmwareVersion()
- SDL_GameControllerGetJoystick() => SDL_GetGamepadJoystick()
- SDL_GameControllerGetNumTouchpadFingers() => SDL_GetNumGamepadTouchpadFingers()
- SDL_GameControllerGetNumTouchpads() => SDL_GetNumGamepadTouchpads()
- SDL_GameControllerGetPlayerIndex() => SDL_GetGamepadPlayerIndex()
- SDL_GameControllerGetProduct() => SDL_GetGamepadProduct()
- SDL_GameControllerGetProductVersion() => SDL_GetGamepadProductVersion()
- SDL_GameControllerGetSensorData() => SDL_GetGamepadSensorData(), returns bool
- SDL_GameControllerGetSensorDataRate() => SDL_GetGamepadSensorDataRate()
- SDL_GameControllerGetSerial() => SDL_GetGamepadSerial()
- SDL_GameControllerGetSteamHandle() => SDL_GetGamepadSteamHandle()
- SDL_GameControllerGetStringForAxis() => SDL_GetGamepadStringForAxis()
- SDL_GameControllerGetStringForButton() => SDL_GetGamepadStringForButton()
- SDL_GameControllerGetTouchpadFinger() => SDL_GetGamepadTouchpadFinger(), returns bool
- SDL_GameControllerGetType() => SDL_GetGamepadType()
- SDL_GameControllerGetVendor() => SDL_GetGamepadVendor()
- SDL_GameControllerHasAxis() => SDL_GamepadHasAxis()
- SDL_GameControllerHasButton() => SDL_GamepadHasButton()
- SDL_GameControllerHasSensor() => SDL_GamepadHasSensor()
- SDL_GameControllerIsSensorEnabled() => SDL_GamepadSensorEnabled()
- SDL_GameControllerMapping() => SDL_GetGamepadMapping()
- SDL_GameControllerMappingForGUID() => SDL_GetGamepadMappingForGUID()
- SDL_GameControllerName() => SDL_GetGamepadName()
- SDL_GameControllerOpen() => SDL_OpenGamepad()
- SDL_GameControllerPath() => SDL_GetGamepadPath()
- SDL_GameControllerRumble() => SDL_RumbleGamepad(), returns bool
- SDL_GameControllerRumbleTriggers() => SDL_RumbleGamepadTriggers(), returns bool
- SDL_GameControllerSendEffect() => SDL_SendGamepadEffect(), returns bool
- SDL_GameControllerSetLED() => SDL_SetGamepadLED(), returns bool
- SDL_GameControllerSetPlayerIndex() => SDL_SetGamepadPlayerIndex(), returns bool
- SDL_GameControllerSetSensorEnabled() => SDL_SetGamepadSensorEnabled(), returns bool
- SDL_GameControllerUpdate() => SDL_UpdateGamepads()
- SDL_IsGameController() => SDL_IsGamepad()
The following functions have been removed:
- SDL_GameControllerEventState() - replaced with SDL_SetGamepadEventsEnabled() and SDL_GamepadEventsEnabled()
- SDL_GameControllerGetBindForAxis() - replaced with SDL_GetGamepadBindings()
- SDL_GameControllerGetBindForButton() - replaced with SDL_GetGamepadBindings()
- SDL_GameControllerHasLED() - replaced with SDL_PROP_GAMEPAD_CAP_RGB_LED_BOOLEAN
- SDL_GameControllerHasRumble() - replaced with SDL_PROP_GAMEPAD_CAP_RUMBLE_BOOLEAN
- SDL_GameControllerHasRumbleTriggers() - replaced with SDL_PROP_GAMEPAD_CAP_TRIGGER_RUMBLE_BOOLEAN
- SDL_GameControllerMappingForDeviceIndex() - replaced with SDL_GetGamepadMappingForID()
- SDL_GameControllerMappingForIndex() - replaced with SDL_GetGamepadMappings()
- SDL_GameControllerNameForIndex() - replaced with SDL_GetGamepadNameForID()
- SDL_GameControllerNumMappings() - replaced with SDL_GetGamepadMappings()
- SDL_GameControllerPathForIndex() - replaced with SDL_GetGamepadPathForID()
- SDL_GameControllerTypeForIndex() - replaced with SDL_GetGamepadTypeForID()
The following symbols have been renamed:
- SDL_CONTROLLER_AXIS_INVALID => SDL_GAMEPAD_AXIS_INVALID
- SDL_CONTROLLER_AXIS_LEFTX => SDL_GAMEPAD_AXIS_LEFTX
- SDL_CONTROLLER_AXIS_LEFTY => SDL_GAMEPAD_AXIS_LEFTY
- SDL_CONTROLLER_AXIS_MAX => SDL_GAMEPAD_AXIS_COUNT
- SDL_CONTROLLER_AXIS_RIGHTX => SDL_GAMEPAD_AXIS_RIGHTX
- SDL_CONTROLLER_AXIS_RIGHTY => SDL_GAMEPAD_AXIS_RIGHTY
- SDL_CONTROLLER_AXIS_TRIGGERLEFT => SDL_GAMEPAD_AXIS_LEFT_TRIGGER
- SDL_CONTROLLER_AXIS_TRIGGERRIGHT => SDL_GAMEPAD_AXIS_RIGHT_TRIGGER
- SDL_CONTROLLER_BINDTYPE_AXIS => SDL_GAMEPAD_BINDTYPE_AXIS
- SDL_CONTROLLER_BINDTYPE_BUTTON => SDL_GAMEPAD_BINDTYPE_BUTTON
- SDL_CONTROLLER_BINDTYPE_HAT => SDL_GAMEPAD_BINDTYPE_HAT
- SDL_CONTROLLER_BINDTYPE_NONE => SDL_GAMEPAD_BINDTYPE_NONE
- SDL_CONTROLLER_BUTTON_A => SDL_GAMEPAD_BUTTON_SOUTH
- SDL_CONTROLLER_BUTTON_B => SDL_GAMEPAD_BUTTON_EAST
- SDL_CONTROLLER_BUTTON_BACK => SDL_GAMEPAD_BUTTON_BACK
- SDL_CONTROLLER_BUTTON_DPAD_DOWN => SDL_GAMEPAD_BUTTON_DPAD_DOWN
- SDL_CONTROLLER_BUTTON_DPAD_LEFT => SDL_GAMEPAD_BUTTON_DPAD_LEFT
- SDL_CONTROLLER_BUTTON_DPAD_RIGHT => SDL_GAMEPAD_BUTTON_DPAD_RIGHT
- SDL_CONTROLLER_BUTTON_DPAD_UP => SDL_GAMEPAD_BUTTON_DPAD_UP
- SDL_CONTROLLER_BUTTON_GUIDE => SDL_GAMEPAD_BUTTON_GUIDE
- SDL_CONTROLLER_BUTTON_INVALID => SDL_GAMEPAD_BUTTON_INVALID
- SDL_CONTROLLER_BUTTON_LEFTSHOULDER => SDL_GAMEPAD_BUTTON_LEFT_SHOULDER
- SDL_CONTROLLER_BUTTON_LEFTSTICK => SDL_GAMEPAD_BUTTON_LEFT_STICK
- SDL_CONTROLLER_BUTTON_MAX => SDL_GAMEPAD_BUTTON_COUNT
- SDL_CONTROLLER_BUTTON_MISC1 => SDL_GAMEPAD_BUTTON_MISC1
- SDL_CONTROLLER_BUTTON_PADDLE1 => SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1
- SDL_CONTROLLER_BUTTON_PADDLE2 => SDL_GAMEPAD_BUTTON_LEFT_PADDLE1
- SDL_CONTROLLER_BUTTON_PADDLE3 => SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2
- SDL_CONTROLLER_BUTTON_PADDLE4 => SDL_GAMEPAD_BUTTON_LEFT_PADDLE2
- SDL_CONTROLLER_BUTTON_RIGHTSHOULDER => SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER
- SDL_CONTROLLER_BUTTON_RIGHTSTICK => SDL_GAMEPAD_BUTTON_RIGHT_STICK
- SDL_CONTROLLER_BUTTON_START => SDL_GAMEPAD_BUTTON_START
- SDL_CONTROLLER_BUTTON_TOUCHPAD => SDL_GAMEPAD_BUTTON_TOUCHPAD
- SDL_CONTROLLER_BUTTON_X => SDL_GAMEPAD_BUTTON_WEST
- SDL_CONTROLLER_BUTTON_Y => SDL_GAMEPAD_BUTTON_NORTH
- SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_LEFT => SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT
- SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_PAIR => SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR
- SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT => SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT
- SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO => SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO
- SDL_CONTROLLER_TYPE_PS3 => SDL_GAMEPAD_TYPE_PS3
- SDL_CONTROLLER_TYPE_PS4 => SDL_GAMEPAD_TYPE_PS4
- SDL_CONTROLLER_TYPE_PS5 => SDL_GAMEPAD_TYPE_PS5
- SDL_CONTROLLER_TYPE_UNKNOWN => SDL_GAMEPAD_TYPE_STANDARD
- SDL_CONTROLLER_TYPE_VIRTUAL => SDL_GAMEPAD_TYPE_VIRTUAL
- SDL_CONTROLLER_TYPE_XBOX360 => SDL_GAMEPAD_TYPE_XBOX360
- SDL_CONTROLLER_TYPE_XBOXONE => SDL_GAMEPAD_TYPE_XBOXONE
SDL_gesture.h
The gesture API has been removed. There is no replacement planned in SDL3.
However, the SDL2 code has been moved to a single-header library that can
be dropped into an SDL3 or SDL2 program, to continue to provide this
functionality to your app and aid migration. That is located in the
SDL_gesture GitHub repository.
SDL_guid.h
SDL_GUIDToString() returns a const pointer to the string representation of a GUID.
The following functions have been renamed:
- SDL_GUIDFromString() => SDL_StringToGUID()
SDL_haptic.h
Gamepads with simple rumble capability no longer show up in the SDL haptics interface, instead you should use SDL_RumbleGamepad().
Rather than iterating over haptic devices using device index, there is a new function SDL_GetHaptics() to get the current list of haptic devices, and new functions to get information about haptic devices from their instance ID:
{
if (SDL_InitSubSystem(SDL_INIT_HAPTIC)) {
int i, num_haptics;
SDL_HapticID *haptics = SDL_GetHaptics(&num_haptics);
if (haptics) {
for (i = 0; i < num_haptics; ++i) {
SDL_HapticID instance_id = haptics[i];
SDL_Log("Haptic %" SDL_PRIu32 ": %s", instance_id, SDL_GetHapticNameForID(instance_id));
}
SDL_free(haptics);
}
SDL_QuitSubSystem(SDL_INIT_HAPTIC);
}
}SDL_GetHapticEffectStatus() now returns bool instead of an int result. You should call SDL_GetHapticFeatures() to make sure effect status is supported before calling this function.
The following functions have been renamed:
- SDL_HapticClose() => SDL_CloseHaptic()
- SDL_HapticDestroyEffect() => SDL_DestroyHapticEffect()
- SDL_HapticGetEffectStatus() => SDL_GetHapticEffectStatus(), returns bool
- SDL_HapticNewEffect() => SDL_CreateHapticEffect()
- SDL_HapticNumAxes() => SDL_GetNumHapticAxes()
- SDL_HapticNumEffects() => SDL_GetMaxHapticEffects()
- SDL_HapticNumEffectsPlaying() => SDL_GetMaxHapticEffectsPlaying()
- SDL_HapticOpen() => SDL_OpenHaptic()
- SDL_HapticOpenFromJoystick() => SDL_OpenHapticFromJoystick()
- SDL_HapticOpenFromMouse() => SDL_OpenHapticFromMouse()
- SDL_HapticPause() => SDL_PauseHaptic(), returns bool
- SDL_HapticQuery() => SDL_GetHapticFeatures()
- SDL_HapticRumbleInit() => SDL_InitHapticRumble(), returns bool
- SDL_HapticRumblePlay() => SDL_PlayHapticRumble(), returns bool
- SDL_HapticRumbleStop() => SDL_StopHapticRumble(), returns bool
- SDL_HapticRunEffect() => SDL_RunHapticEffect(), returns bool
- SDL_HapticSetAutocenter() => SDL_SetHapticAutocenter(), returns bool
- SDL_HapticSetGain() => SDL_SetHapticGain(), returns bool
- SDL_HapticStopAll() => SDL_StopHapticEffects(), returns bool
- SDL_HapticStopEffect() => SDL_StopHapticEffect(), returns bool
- SDL_HapticUnpause() => SDL_ResumeHaptic(), returns bool
- SDL_HapticUpdateEffect() => SDL_UpdateHapticEffect(), returns bool
- SDL_JoystickIsHaptic() => SDL_IsJoystickHaptic()
- SDL_MouseIsHaptic() => SDL_IsMouseHaptic()
The following functions have been removed:
- SDL_HapticIndex() - replaced with SDL_GetHapticID()
- SDL_HapticName() - replaced with SDL_GetHapticNameForID()
- SDL_HapticOpened() - replaced with SDL_GetHapticFromID()
- SDL_NumHaptics() - replaced with SDL_GetHaptics()
SDL_hints.h
Calling SDL_GetHint() with the name of the hint being changed from within a hint callback will now return the new value rather than the old value. The old value is still passed as a parameter to the hint callback.
The environment variables SDL_VIDEODRIVER and SDL_AUDIODRIVER have been renamed to SDL_VIDEO_DRIVER and SDL_AUDIO_DRIVER, but the old names are still supported as a fallback.
The environment variables SDL_VIDEO_X11_WMCLASS and SDL_VIDEO_WAYLAND_WMCLASS have been removed and replaced by either using the appindentifier param to SDL_SetAppMetadata() or setting SDL_PROP_APP_METADATA_IDENTIFIER_STRING with SDL_SetAppMetadataProperty()
The environment variable AUDIODEV is used exclusively to specify the audio device for the OSS and NetBSD audio drivers. Its use in the ALSA driver has been replaced with the hint SDL_HINT_AUDIO_ALSA_DEFAULT_DEVICE and in the sndio driver with the environment variable AUDIODEVICE.
The following hints have been renamed:
- SDL_HINT_ALLOW_TOPMOST => SDL_HINT_WINDOW_ALLOW_TOPMOST
- SDL_HINT_AUDIODRIVER => SDL_HINT_AUDIO_DRIVER
- SDL_HINT_DIRECTINPUT_ENABLED => SDL_HINT_JOYSTICK_DIRECTINPUT
- SDL_HINT_GDK_TEXTINPUT_DEFAULT => SDL_HINT_GDK_TEXTINPUT_DEFAULT_TEXT
- SDL_HINT_JOYSTICK_GAMECUBE_RUMBLE_BRAKE => SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE_RUMBLE_BRAKE
- SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE => SDL_HINT_JOYSTICK_ENHANCED_REPORTS
- SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE => SDL_HINT_JOYSTICK_ENHANCED_REPORTS
- SDL_HINT_LINUX_DIGITAL_HATS => SDL_HINT_JOYSTICK_LINUX_DIGITAL_HATS
- SDL_HINT_LINUX_HAT_DEADZONES => SDL_HINT_JOYSTICK_LINUX_HAT_DEADZONES
- SDL_HINT_LINUX_JOYSTICK_CLASSIC => SDL_HINT_JOYSTICK_LINUX_CLASSIC
- SDL_HINT_LINUX_JOYSTICK_DEADZONES => SDL_HINT_JOYSTICK_LINUX_DEADZONES
- SDL_HINT_VIDEODRIVER => SDL_HINT_VIDEO_DRIVER
- SDL_HINT_VIDEO_WAYLAND_EMULATE_MOUSE_WARP => SDL_HINT_MOUSE_EMULATE_WARP_WITH_RELATIVE
The following hints have been removed:
- SDL_HINT_ACCELEROMETER_AS_JOYSTICK
- SDL_HINT_ANDROID_BLOCK_ON_PAUSE_PAUSEAUDIO - the audio will be paused when the application is paused, and SDL_HINT_ANDROID_BLOCK_ON_PAUSE can be used to control that
- SDL_HINT_AUDIO_DEVICE_APP_NAME - replaced by either using the appname param to SDL_SetAppMetadata() or setting SDL_PROP_APP_METADATA_NAME_STRING with SDL_SetAppMetadataProperty()
- SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS - gamepad buttons are always positional
- SDL_HINT_GRAB_KEYBOARD - use SDL_SetWindowKeyboardGrab() instead
- SDL_HINT_IDLE_TIMER_DISABLED - use SDL_DisableScreenSaver() instead
- SDL_HINT_IME_INTERNAL_EDITING - replaced with SDL_HINT_IME_IMPLEMENTED_UI
- SDL_HINT_IME_SHOW_UI - replaced with SDL_HINT_IME_IMPLEMENTED_UI
- SDL_HINT_IME_SUPPORT_EXTENDED_TEXT - the normal text editing event has extended text
- SDL_HINT_MOUSE_RELATIVE_MODE_WARP - relative mode is always implemented at the hardware level or reported as unavailable
- SDL_HINT_MOUSE_RELATIVE_SCALING - mouse coordinates are no longer automatically scaled by the SDL renderer
- SDL_HINT_PS2_DYNAMIC_VSYNC - use SDL_SetRenderVSync(renderer, -1) instead
- SDL_HINT_RENDER_BATCHING - Render batching is always enabled, apps should call SDL_FlushRenderer() before calling into a lower-level graphics API.
- SDL_HINT_RENDER_LOGICAL_SIZE_MODE - the logical size mode is explicitly set with SDL_SetRenderLogicalPresentation()
- SDL_HINT_RENDER_OPENGL_SHADERS - shaders are always used if they are available
- SDL_HINT_RENDER_SCALE_QUALITY - textures now default to linear filtering, use SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_NEAREST) if you want nearest pixel mode instead
- SDL_HINT_THREAD_STACK_SIZE - the stack size can be specified using SDL_CreateThreadWithProperties()
- SDL_HINT_VIDEO_EXTERNAL_CONTEXT - replaced with SDL_PROP_WINDOW_CREATE_EXTERNAL_GRAPHICS_CONTEXT_BOOLEAN in SDL_CreateWindowWithProperties()
- SDL_HINT_VIDEO_FOREIGN_WINDOW_OPENGL - replaced with SDL_PROP_WINDOW_CREATE_OPENGL_BOOLEAN in SDL_CreateWindowWithProperties()
- SDL_HINT_VIDEO_FOREIGN_WINDOW_VULKAN - replaced with SDL_PROP_WINDOW_CREATE_VULKAN_BOOLEAN in SDL_CreateWindowWithProperties()
- SDL_HINT_VIDEO_HIGHDPI_DISABLED - high DPI support is always enabled
- SDL_HINT_VIDEO_WINDOW_SHARE_PIXEL_FORMAT - replaced with SDL_PROP_WINDOW_CREATE_WIN32_PIXEL_FORMAT_HWND_POINTER in SDL_CreateWindowWithProperties()
- SDL_HINT_VIDEO_X11_FORCE_EGL - use SDL_HINT_VIDEO_FORCE_EGL instead
- SDL_HINT_VIDEO_X11_XINERAMA - Xinerama no longer supported by the X11 backend
- SDL_HINT_VIDEO_X11_XVIDMODE - Xvidmode no longer supported by the X11 backend
- SDL_HINT_WINDOWS_DISABLE_THREAD_NAMING - SDL now properly handles the 0x406D1388 Exception if no debugger intercepts it, preventing its propagation.
- SDL_HINT_WINDOWS_FORCE_MUTEX_CRITICAL_SECTIONS - Slim Reader/Writer Locks are always used if available
- SDL_HINT_WINDOWS_NO_CLOSE_ON_ALT_F4 - replaced with SDL_HINT_WINDOWS_CLOSE_ON_ALT_F4, defaulting to true
- SDL_HINT_WINRT_HANDLE_BACK_BUTTON - WinRT support was removed in SDL3.
- SDL_HINT_WINRT_PRIVACY_POLICY_LABEL - WinRT support was removed in SDL3.
- SDL_HINT_WINRT_PRIVACY_POLICY_URL - WinRT support was removed in SDL3.
- SDL_HINT_XINPUT_USE_OLD_JOYSTICK_MAPPING
The following environment variables have been renamed:
- SDL_AUDIODRIVER => SDL_AUDIO_DRIVER
- SDL_PATH_DSP => AUDIODEV
- SDL_VIDEODRIVER => SDL_VIDEO_DRIVER
The following environment variables have been removed:
- SDL_AUDIO_ALSA_DEBUG - replaced by setting the hint SDL_HINT_LOGGING to "audio=debug"
- SDL_DISKAUDIODELAY - replaced with the hint SDL_HINT_AUDIO_DISK_TIMESCALE which allows scaling the audio time rather than specifying an absolute delay.
- SDL_DISKAUDIOFILE - replaced with the hint SDL_HINT_AUDIO_DISK_OUTPUT_FILE
- SDL_DISKAUDIOFILEIN - replaced with the hint SDL_HINT_AUDIO_DISK_INPUT_FILE
- SDL_DUMMYAUDIODELAY - replaced with the hint SDL_HINT_AUDIO_DUMMY_TIMESCALE which allows scaling the audio time rather than specifying an absolute delay.
- SDL_HAPTIC_GAIN_MAX
- SDL_HIDAPI_DISABLE_LIBUSB - replaced with the hint SDL_HINT_HIDAPI_LIBUSB
- SDL_HIDAPI_JOYSTICK_DISABLE_UDEV - replaced with the hint SDL_HINT_HIDAPI_UDEV
- SDL_INPUT_FREEBSD_KEEP_KBD - replaced with the hint SDL_HINT_MUTE_CONSOLE_KEYBOARD
- SDL_INPUT_LINUX_KEEP_KBD - replaced with the hint SDL_HINT_MUTE_CONSOLE_KEYBOARD
- VITA_DISABLE_TOUCH_BACK - replaced with the hint SDL_HINT_VITA_ENABLE_BACK_TOUCH
- VITA_DISABLE_TOUCH_FRONT - replaced with the hint SDL_HINT_VITA_ENABLE_FRONT_TOUCH
- VITA_MODULE_PATH - replaced with the hint SDL_HINT_VITA_MODULE_PATH
- VITA_PVR_OGL - replaced with the hint SDL_HINT_VITA_PVR_OPENGL
- VITA_PVR_SKIP_INIT - replaced with the hint SDL_HINT_VITA_PVR_INIT
- VITA_RESOLUTION - replaced with the hint SDL_HINT_VITA_RESOLUTION
The following functions have been removed:
- SDL_ClearHints() - replaced with SDL_ResetHints()
The following functions have been renamed:
- SDL_DelHintCallback() => SDL_RemoveHintCallback()
SDL_init.h
On Haiku OS, SDL no longer sets the current working directory to the executable's path during SDL_Init(). If you need this functionality, the fastest solution is to add this code directly after the call to SDL_Init:
{
const char *path = SDL_GetBasePath();
if (path) {
chdir(path);
}
}The following symbols have been renamed:
- SDL_INIT_GAMECONTROLLER => SDL_INIT_GAMEPAD
The following symbols have been removed:
- SDL_INIT_NOPARACHUTE
- SDL_INIT_EVERYTHING - you should only initialize the subsystems you are using
- SDL_INIT_TIMER - no longer needed before calling SDL_AddTimer()
SDL_joystick.h
SDL_JoystickID has changed from Sint32 to Uint32, with an invalid ID being 0.
Rather than iterating over joysticks using device index, there is a new function SDL_GetJoysticks() to get the current list of joysticks, and new functions to get information about joysticks from their instance ID:
{
if (SDL_InitSubSystem(SDL_INIT_JOYSTICK)) {
int i, num_joysticks;
SDL_JoystickID *joysticks = SDL_GetJoysticks(&num_joysticks);
if (joysticks) {
for (i = 0; i < num_joysticks; ++i) {
SDL_JoystickID instance_id = joysticks[i];
const char *name = SDL_GetJoystickNameForID(instance_id);
const char *path = SDL_GetJoystickPathForID(instance_id);
SDL_Log("Joystick %" SDL_PRIu32 ": %s%s%s VID 0x%.4x, PID 0x%.4x",
instance_id, name ? name : "Unknown", path ? ", " : "", path ? path : "", SDL_GetJoystickVendorForID(instance_id), SDL_GetJoystickProductForID(instance_id));
}
SDL_free(joysticks);
}
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
}
}The SDL_EVENT_JOYSTICK_ADDED event now provides the joystick instance ID in the which member of the jdevice event structure.
The functions SDL_GetJoysticks(), SDL_GetJoystickNameForID(), SDL_GetJoystickPathForID(), SDL_GetJoystickPlayerIndexForID(), SDL_GetJoystickGUIDForID(), SDL_GetJoystickVendorForID(), SDL_GetJoystickProductForID(), SDL_GetJoystickProductVersionForID(), and SDL_GetJoystickTypeForID() have been added to directly query the list of available joysticks.
SDL_AttachVirtualJoystick() now returns the joystick instance ID instead of a device index, and returns 0 if there was an error.
SDL_VirtualJoystickDesc version should not be set to SDL_VIRTUAL_JOYSTICK_DESC_VERSION, instead the structure should be initialized using SDL_INIT_INTERFACE().
The following functions have been renamed:
- SDL_JoystickAttachVirtualEx() => SDL_AttachVirtualJoystick()
- SDL_JoystickClose() => SDL_CloseJoystick()
- SDL_JoystickDetachVirtual() => SDL_DetachVirtualJoystick(), returns bool
- SDL_JoystickFromInstanceID() => SDL_GetJoystickFromID()
- SDL_JoystickFromPlayerIndex() => SDL_GetJoystickFromPlayerIndex()
- SDL_JoystickGetAttached() => SDL_JoystickConnected()
- SDL_JoystickGetAxis() => SDL_GetJoystickAxis()
- SDL_JoystickGetAxisInitialState() => SDL_GetJoystickAxisInitialState()
- SDL_JoystickGetBall() => SDL_GetJoystickBall(), returns bool
- SDL_JoystickGetButton() => SDL_GetJoystickButton()
- SDL_JoystickGetFirmwareVersion() => SDL_GetJoystickFirmwareVersion()
- SDL_JoystickGetGUID() => SDL_GetJoystickGUID()
- SDL_JoystickGetGUIDFromString() => SDL_StringToGUID()
- SDL_JoystickGetHat() => SDL_GetJoystickHat()
- SDL_JoystickGetPlayerIndex() => SDL_GetJoystickPlayerIndex()
- SDL_JoystickGetProduct() => SDL_GetJoystickProduct()
- SDL_JoystickGetProductVersion() => SDL_GetJoystickProductVersion()
- SDL_JoystickGetSerial() => SDL_GetJoystickSerial()
- SDL_JoystickGetType() => SDL_GetJoystickType()
- SDL_JoystickGetVendor() => SDL_GetJoystickVendor()
- SDL_JoystickInstanceID() => SDL_GetJoystickID()
- SDL_JoystickIsVirtual() => SDL_IsJoystickVirtual()
- SDL_JoystickName() => SDL_GetJoystickName()
- SDL_JoystickNumAxes() => SDL_GetNumJoystickAxes()
- SDL_JoystickNumBalls() => SDL_GetNumJoystickBalls()
- SDL_JoystickNumButtons() => SDL_GetNumJoystickButtons()
- SDL_JoystickNumHats() => SDL_GetNumJoystickHats()
- SDL_JoystickOpen() => SDL_OpenJoystick()
- SDL_JoystickPath() => SDL_GetJoystickPath()
- SDL_JoystickRumble() => SDL_RumbleJoystick(), returns bool
- SDL_JoystickRumbleTriggers() => SDL_RumbleJoystickTriggers(), returns bool
- SDL_JoystickSendEffect() => SDL_SendJoystickEffect(), returns bool
- SDL_JoystickSetLED() => SDL_SetJoystickLED(), returns bool
- SDL_JoystickSetPlayerIndex() => SDL_SetJoystickPlayerIndex(), returns bool
- SDL_JoystickSetVirtualAxis() => SDL_SetJoystickVirtualAxis(), returns bool
- SDL_JoystickSetVirtualButton() => SDL_SetJoystickVirtualButton(), returns bool
- SDL_JoystickSetVirtualHat() => SDL_SetJoystickVirtualHat(), returns bool
- SDL_JoystickUpdate() => SDL_UpdateJoysticks()
The following symbols have been renamed:
- SDL_JOYSTICK_TYPE_GAMECONTROLLER => SDL_JOYSTICK_TYPE_GAMEPAD
The following functions have been removed:
- SDL_JoystickAttachVirtual() - replaced with SDL_AttachVirtualJoystick()
- SDL_JoystickCurrentPowerLevel() - replaced with SDL_GetJoystickConnectionState() and SDL_GetJoystickPowerInfo()
- SDL_JoystickEventState() - replaced with SDL_SetJoystickEventsEnabled() and SDL_JoystickEventsEnabled()
- SDL_JoystickGetDeviceGUID() - replaced with SDL_GetJoystickGUIDForID()
- SDL_JoystickGetDeviceInstanceID()
- SDL_JoystickGetDevicePlayerIndex() - replaced with SDL_GetJoystickPlayerIndexForID()
- SDL_JoystickGetDeviceProduct() - replaced with SDL_GetJoystickProductForID()
- SDL_JoystickGetDeviceProductVersion() - replaced with SDL_GetJoystickProductVersionForID()
- SDL_JoystickGetDeviceType() - replaced with SDL_GetJoystickTypeForID()
- SDL_JoystickGetDeviceVendor() - replaced with SDL_GetJoystickVendorForID()
- SDL_JoystickGetGUIDString() - replaced with SDL_GUIDToString()
- SDL_JoystickHasLED() - replaced with SDL_PROP_JOYSTICK_CAP_RGB_LED_BOOLEAN
- SDL_JoystickHasRumble() - replaced with SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN
- SDL_JoystickHasRumbleTriggers() - replaced with SDL_PROP_JOYSTICK_CAP_TRIGGER_RUMBLE_BOOLEAN
- SDL_JoystickNameForIndex() - replaced with SDL_GetJoystickNameForID()
- SDL_JoystickPathForIndex() - replaced with SDL_GetJoystickPathForID()
- SDL_NumJoysticks() - replaced with SDL_GetJoysticks()
- SDL_VIRTUAL_JOYSTICK_DESC_VERSION - no longer needed
The following symbols have been removed:
- SDL_IPHONE_MAX_GFORCE
- SDL_JOYBALLMOTION
The following structures have been renamed:
- SDL_JoystickGUID => SDL_GUID
SDL_keyboard.h
Text input is no longer automatically enabled when initializing video, you should call SDL_StartTextInput() when you want to receive text input and call SDL_StopTextInput() when you are done. Starting text input may shown an input method editor (IME) and cause key up/down events to be skipped, so should only be enabled when the application wants text input.
The text input state hase been changed to be window-specific. SDL_StartTextInput(), SDL_StopTextInput(), SDL_TextInputActive(), and SDL_ClearComposition() all now take a window parameter.
SDL_GetDefaultKeyFromScancode(), SDL_GetKeyFromScancode(), and SDL_GetScancodeFromKey() take an SDL_Keymod parameter and use that to provide the correct result based on keyboard modifier state.
SDL_GetKeyboardState() returns a pointer to bool instead of Uint8.
The following functions have been renamed:
- SDL_IsScreenKeyboardShown() => SDL_ScreenKeyboardShown()
- SDL_IsTextInputActive() => SDL_TextInputActive()
The following functions have been removed:
- SDL_IsTextInputShown()
- SDL_SetTextInputRect() - replaced with SDL_SetTextInputArea()
The following structures have been removed:
- SDL_Keysym
SDL_keycode.h
SDL_Keycode is now Uint32 and the SDLK_* constants are now defines instead of an enum, to more clearly reflect that they are a subset of the possible values of an SDL_Keycode.
In addition to the SDLK_SCANCODE_MASK bit found on key codes that directly map to scancodes, there is now theSDLK_EXTENDED_MASK bit used to denote key codes that don't have a corresponding scancode, and aren't a unicode value.
The following symbols have been removed:
- KMOD_RESERVED - No replacement. A bit named "RESERVED" probably shouldn't be used in an app, but if you need it, this was equivalent to KMOD_SCROLL (0x8000) in SDL2.
- SDLK_WWW
- SDLK_MAIL
- SDLK_CALCULATOR
- SDLK_COMPUTER
- SDLK_BRIGHTNESSDOWN
- SDLK_BRIGHTNESSUP
- SDLK_DISPLAYSWITCH
- SDLK_KBDILLUMTOGGLE
- SDLK_KBDILLUMDOWN
- SDLK_KBDILLUMUP
- SDLK_APP1
- SDLK_APP2
The following symbols have been renamed:
- KMOD_ALT => SDL_KMOD_ALT
- KMOD_CAPS => SDL_KMOD_CAPS
- KMOD_CTRL => SDL_KMOD_CTRL
- KMOD_GUI => SDL_KMOD_GUI
- KMOD_LALT => SDL_KMOD_LALT
- KMOD_LCTRL => SDL_KMOD_LCTRL
- KMOD_LGUI => SDL_KMOD_LGUI
- KMOD_LSHIFT => SDL_KMOD_LSHIFT
- KMOD_MODE => SDL_KMOD_MODE
- KMOD_NONE => SDL_KMOD_NONE
- KMOD_NUM => SDL_KMOD_NUM
- KMOD_RALT => SDL_KMOD_RALT
- KMOD_RCTRL => SDL_KMOD_RCTRL
- KMOD_RGUI => SDL_KMOD_RGUI
- KMOD_RSHIFT => SDL_KMOD_RSHIFT
- KMOD_SCROLL => SDL_KMOD_SCROLL
- KMOD_SHIFT => SDL_KMOD_SHIFT
- SDLK_AUDIOFASTFORWARD => SDLK_MEDIA_FAST_FORWARD
- SDLK_AUDIOMUTE => SDLK_MUTE
- SDLK_AUDIONEXT => SDLK_MEDIA_NEXT_TRACK
- SDLK_AUDIOPLAY => SDLK_MEDIA_PLAY
- SDLK_AUDIOPREV => SDLK_MEDIA_PREVIOUS_TRACK
- SDLK_AUDIOREWIND => SDLK_MEDIA_REWIND
- SDLK_AUDIOSTOP => SDLK_MEDIA_STOP
- SDLK_BACKQUOTE => SDLK_GRAVE
- SDLK_EJECT => SDLK_MEDIA_EJECT
- SDLK_MEDIASELECT => SDLK_MEDIA_SELECT
- SDLK_QUOTE => SDLK_APOSTROPHE
- SDLK_QUOTEDBL => SDLK_DBLAPOSTROPHE
- SDLK_a => SDLK_A
- SDLK_b => SDLK_B
- SDLK_c => SDLK_C
- SDLK_d => SDLK_D
- SDLK_e => SDLK_E
- SDLK_f => SDLK_F
- SDLK_g => SDLK_G
- SDLK_h => SDLK_H
- SDLK_i => SDLK_I
- SDLK_j => SDLK_J
- SDLK_k => SDLK_K
- SDLK_l => SDLK_L
- SDLK_m => SDLK_M
- SDLK_n => SDLK_N
- SDLK_o => SDLK_O
- SDLK_p => SDLK_P
- SDLK_q => SDLK_Q
- SDLK_r => SDLK_R
- SDLK_s => SDLK_S
- SDLK_t => SDLK_T
- SDLK_u => SDLK_U
- SDLK_v => SDLK_V
- SDLK_w => SDLK_W
- SDLK_x => SDLK_X
- SDLK_y => SDLK_Y
- SDLK_z => SDLK_Z
SDL_loadso.h
Shared object handles are now SDL_SharedObject *, an opaque type, instead of void *. This is just for type-safety and there is no functional difference.
SDL_LoadFunction() now returns SDL_FunctionPointer instead of void *, and should be cast to the appropriate function type. You can define SDL_FUNCTION_POINTER_IS_VOID_POINTER in your project to restore the previous behavior.
SDL_locale.h
SDL_GetPreferredLocales() now returns a NULL-terminated array of pointers, instead of an array of structs where the last struct has its fields set to NULL, and also can optionally report the count of the array. The returned pointer is still disposed of by the caller with a single SDL_free() call, that will take care of all the strings and structs in one block.
SDL_log.h
SDL_Log() no longer prints a log prefix by default for SDL_LOG_PRIORITY_INFO and below. The log prefixes can be customized with SDL_SetLogPriorityPrefix().
The following macros have been removed:
- SDL_MAX_LOG_MESSAGE - there's no message length limit anymore. If you need an artificial limit, this used to be 4096 in SDL versions before 2.0.24.
The following functions have been renamed:
- SDL_LogGetOutputFunction() => SDL_GetLogOutputFunction()
- SDL_LogGetPriority() => SDL_GetLogPriority()
- SDL_LogResetPriorities() => SDL_ResetLogPriorities()
- SDL_LogSetAllPriority() => SDL_SetLogPriorities()
- SDL_LogSetOutputFunction() => SDL_SetLogOutputFunction()
- SDL_LogSetPriority() => SDL_SetLogPriority()
The following symbols have been renamed:
- SDL_NUM_LOG_PRIORITIES => SDL_LOG_PRIORITY_COUNT
SDL_main.h
SDL3 doesn't have a static libSDLmain to link against anymore.
Instead SDL_main.h is now a header-only library and not included by SDL.h anymore.
Using it is really simple: Just #include <SDL3/SDL_main.h> in the source file with your standardint main(int argc, char* argv[]) function. See docs/README-main-functions.md for details.
Several platform-specific entry point functions have been removed as unnecessary. If for some reason you explicitly need them, here are easy replacements:
#define SDL_UIKitRunApp(ARGC, ARGV, MAIN_FUNC) SDL_RunApp(ARGC, ARGV, MAIN_FUNC, NULL)
#define SDL_GDKRunApp(MAIN_FUNC, RESERVED) SDL_RunApp(0, NULL, MAIN_FUNC, RESERVED)The following functions have been removed:
- SDL_WinRTRunApp() - WinRT support was removed in SDL3.
SDL_messagebox.h
The buttonid field of SDL_MessageBoxButtonData has been renamed buttonID.
The following symbols have been renamed:
- SDL_MESSAGEBOX_COLOR_MAX => SDL_MESSAGEBOX_COLOR_COUNT
SDL_metal.h
SDL_Metal_GetDrawableSize() has been removed. SDL_GetWindowSizeInPixels() can be used in its place.
SDL_mouse.h
SDL_ShowCursor() has been split into three functions: SDL_ShowCursor(), SDL_HideCursor(), and SDL_CursorVisible()
SDL_GetMouseState(), SDL_GetGlobalMouseState(), SDL_GetRelativeMouseState(), SDL_WarpMouseInWindow(), and SDL_WarpMouseGlobal() all use floating point mouse positions, to provide sub-pixel precision on platforms that support it.
SDL_SystemCursor's items from SDL2 have been renamed to match CSS cursor names.
The following functions have been renamed:
- SDL_FreeCursor() => SDL_DestroyCursor()
The following functions have been removed:
- SDL_SetRelativeMouseMode() - replaced with SDL_SetWindowRelativeMouseMode()
- SDL_GetRelativeMouseMode() - replaced with SDL_GetWindowRelativeMouseMode()
The following symbols have been renamed:
- SDL_BUTTON => SDL_BUTTON_MASK
- SDL_NUM_SYSTEM_CURSORS => SDL_SYSTEM_CURSOR_COUNT
- SDL_SYSTEM_CURSOR_ARROW => SDL_SYSTEM_CURSOR_DEFAULT
- SDL_SYSTEM_CURSOR_HAND => SDL_SYSTEM_CURSOR_POINTER
- SDL_SYSTEM_CURSOR_IBEAM => SDL_SYSTEM_CURSOR_TEXT
- SDL_SYSTEM_CURSOR_NO => SDL_SYSTEM_CURSOR_NOT_ALLOWED
- SDL_SYSTEM_CURSOR_SIZEALL => SDL_SYSTEM_CURSOR_MOVE
- SDL_SYSTEM_CURSOR_SIZENESW => SDL_SYSTEM_CURSOR_NESW_RESIZE
- SDL_SYSTEM_CURSOR_SIZENS => SDL_SYSTEM_CURSOR_NS_RESIZE
- SDL_SYSTEM_CURSOR_SIZENWSE => SDL_SYSTEM_CURSOR_NWSE_RESIZE
- SDL_SYSTEM_CURSOR_SIZEWE => SDL_SYSTEM_CURSOR_EW_RESIZE
- SDL_SYSTEM_CURSOR_WAITARROW => SDL_SYSTEM_CURSOR_PROGRESS
SDL_mutex.h
SDL_MUTEX_MAXWAIT has been removed; it suggested there was a maximum timeout one could outlive, instead of an infinite wait. Instead, pass a -1 to functions that accepted this symbol.
SDL_MUTEX_TIMEDOUT has been removed, the wait functions return true if the operation succeeded or false if they timed out.
SDL_LockMutex(), SDL_UnlockMutex(), SDL_WaitSemaphore(), SDL_SignalSemaphore(), SDL_WaitCondition(), SDL_SignalCondition(), and SDL_BroadcastCondition() now return void; if the object is valid (including being a NULL pointer, which returns immediately), these functions never fail. If the object is invalid or the caller does something illegal, like unlock another thread's mutex, this is considered undefined behavior.
SDL_TryWaitSemaphore(), SDL_WaitSemaphoreTimeout(), and SDL_WaitConditionTimeout() now return true if the operation succeeded or false if they timed out.
The following functions have been renamed:
- SDL_CondBroadcast() => SDL_BroadcastCondition()
- SDL_CondSignal() => SDL_SignalCondition()
- SDL_CondWait() => SDL_WaitCondition()
- SDL_CondWaitTimeout() => SDL_WaitConditionTimeout(), returns bool
- SDL_CreateCond() => SDL_CreateCondition()
- SDL_DestroyCond() => SDL_DestroyCondition()
- SDL_SemPost() => SDL_SignalSemaphore()
- SDL_SemTryWait() => SDL_TryWaitSemaphore(), returns bool
- SDL_SemValue() => SDL_GetSemaphoreValue()
- SDL_SemWait() => SDL_WaitSemaphore()
- SDL_SemWaitTimeout() => SDL_WaitSemaphoreTimeout(), returns bool
The following symbols have been renamed:
- SDL_cond => SDL_Condition
- SDL_mutex => SDL_Mutex
- SDL_sem => SDL_Semaphore
SDL_pixels.h
SDL_PixelFormat has been renamed SDL_PixelFormatDetails and just describes the pixel format, it does not include a palette for indexed pixel types.
SDL_PixelFormatEnum has been renamed SDL_PixelFormat and is used instead of Uint32 for API functions that refer to pixel format by enumerated value.
SDL_MapRGB(), SDL_MapRGBA(), SDL_GetRGB(), and SDL_GetRGBA() take an optional palette parameter for indexed color lookups.
Code that used to look like this:
SDL_GetRGBA(pixel, surface->format, &r, &g, &b, &a);should be changed to:
SDL_GetRGBA(pixel, SDL_GetPixelFormatDetails(surface->format), SDL_GetSurfacePalette(surface), &r, &g, &b, &a);Code that used to look like this:
pixel = SDL_MapRGBA(surface->format, r, g, b, a);should be changed to:
pixel = SDL_MapSurfaceRGBA(surface, r, g, b, a);SDL_CalculateGammaRamp has been removed, because SDL_SetWindowGammaRamp has been removed as well due to poor support in modern operating systems (see SDL_video.h).
The following functions have been renamed:
- SDL_AllocFormat() => SDL_GetPixelFormatDetails()
- SDL_AllocPalette() => SDL_CreatePalette()
- SDL_FreePalette() => SDL_DestroyPalette()
- SDL_MasksToPixelFormatEnum() => SDL_GetPixelFormatForMasks()
- SDL_PixelFormatEnumToMasks() => SDL_GetMasksForPixelFormat(), returns bool
The following symbols have been renamed:
- SDL_PIXELFORMAT_BGR444 => SDL_PIXELFORMAT_XBGR4444
- SDL_PIXELFORMAT_BGR555 => SDL_PIXELFORMAT_XBGR1555
- SDL_PIXELFORMAT_BGR888 => SDL_PIXELFORMAT_XBGR8888
- SDL_PIXELFORMAT_RGB444 => SDL_PIXELFORMAT_XRGB4444
- SDL_PIXELFORMAT_RGB555 => SDL_PIXELFORMAT_XRGB1555
- SDL_PIXELFORMAT_RGB888 => SDL_PIXELFORMAT_XRGB8888
The following functions have been removed:
- SDL_FreeFormat()
- SDL_SetPixelFormatPalette()
- SDL_CalculateGammaRamp()
The following macros have been removed:
- SDL_Colour - use SDL_Color instead
The following structures have been renamed:
- SDL_PixelFormat => SDL_PixelFormatDetails
SDL_platform.h
The following platform preprocessor macros have been renamed:
| SDL2 | SDL3 |
|---|---|
__3DS__ |
SDL_PLATFORM_3DS |
__AIX__ |
SDL_PLATFORM_AIX |
__ANDROID__ |
SDL_PLATFORM_ANDROID |
__APPLE__ |
SDL_PLATFORM_APPLE |
__BSDI__ |
SDL_PLATFORM_BSDI |
__CYGWIN_ |
SDL_PLATFORM_CYGWIN |
__EMSCRIPTEN__ |
SDL_PLATFORM_EMSCRIPTEN |
__FREEBSD__ |
SDL_PLATFORM_FREEBSD |
__GDK__ |
SDL_PLATFORM_GDK |
__HAIKU__ |
SDL_PLATFORM_HAIKU |
__HPUX__ |
SDL_PLATFORM_HPUX |
__IPHONEOS__ |
SDL_PLATFORM_IOS |
__IRIX__ |
SDL_PLATFORM_IRIX |
__LINUX__ |
SDL_PLATFORM_LINUX |
__MACOSX__ |
SDL_PLATFORM_MACOS |
__NETBSD__ |
SDL_PLATFORM_NETBSD |
__OPENBSD__ |
SDL_PLATFORM_OPENBSD |
__OS2__ |
SDL_PLATFORM_OS2 |
__OSF__ |
SDL_PLATFORM_OSF |
__PS2__ |
SDL_PLATFORM_PS2 |
__PSP__ |
SDL_PLATFORM_PSP |
__QNXNTO__ |
SDL_PLATFORM_QNXNTO |
__RISCOS__ |
SDL_PLATFORM_RISCOS |
__SOLARIS__ |
SDL_PLATFORM_SOLARIS |
__TVOS__ |
SDL_PLATFORM_TVOS |
__unix__ |
SDL_PLATFORM_UNIX |
__VITA__ |
SDL_PLATFORM_VITA |
__WIN32__ |
SDL_PLATFORM_WIN32 |
__WINGDK__ |
SDL_PLATFORM_WINGDK |
__XBOXONE__ |
SDL_PLATFORM_XBOXONE |
__XBOXSERIES__ |
SDL_PLATFORM_XBOXSERIES |
You can use the Python script rename_macros.py to automatically rename these in your source code.
A new macro SDL_PLATFORM_WINDOWS has been added that is true for all Windows platforms, including Xbox, GDK, etc.
The following platform preprocessor macros have been removed:
__DREAMCAST____NACL____PNACL____WINDOWS____WINRT__
SDL_quit.h
SDL_quit.h has been completely removed. It only had one symbol in it--SDL_QuitRequested--and if you want it, you can just add this to your app...
#define SDL_QuitRequested() (SDL_PumpEvents(), (SDL_PeepEvents(NULL,0,SDL_PEEKEVENT,SDL_EVENT_QUIT,SDL_EVENT_QUIT) > 0))...but this macro is sort of messy, calling two functions in sequence in an expression.
The following macros have been removed:
- SDL_QuitRequested - call SDL_PumpEvents() then SDL_PeepEvents() directly, instead.
SDL_rect.h
The following functions have been renamed:
- SDL_EncloseFPoints() => SDL_GetRectEnclosingPointsFloat()
- SDL_EnclosePoints() => SDL_GetRectEnclosingPoints()
- SDL_FRectEmpty() => SDL_RectEmptyFloat()
- SDL_FRectEquals() => SDL_RectsEqualFloat()
- SDL_FRectEqualsEpsilon() => SDL_RectsEqualEpsilon()
- SDL_HasIntersection() => SDL_HasRectIntersection()
- SDL_HasIntersectionF() => SDL_HasRectIntersectionFloat()
- SDL_IntersectFRect() => SDL_GetRectIntersectionFloat()
- SDL_IntersectFRectAndLine() => SDL_GetRectAndLineIntersectionFloat()
- SDL_IntersectRect() => SDL_GetRectIntersection()
- SDL_IntersectRectAndLine() => SDL_GetRectAndLineIntersection()
- SDL_PointInFRect() => SDL_PointInRectFloat()
- SDL_RectEquals() => SDL_RectsEqual()
- SDL_UnionFRect() => SDL_GetRectUnionFloat(), returns bool
- SDL_UnionRect() => SDL_GetRectUnion(), returns bool
SDL_render.h
The 2D renderer API always uses batching in SDL3. There is no magic to turn
it on and off; it doesn't matter if you select a specific renderer or try to
use any hint. This means that all apps that use SDL3's 2D renderer and also
want to call directly into the platform's lower-layer graphics API must call
SDL_FlushRenderer() before doing so. This will make sure any pending rendering
work from SDL is done before the app starts directly drawing.
SDL_GetRenderDriverInfo() has been removed, since most of the information it reported were
estimates and could not be accurate before creating a renderer. Often times this function
was used to figure out the index of a driver, so one would call it in a for-loop, looking
for the driver named "opengl" or whatnot. SDL_GetRenderDriver() has been added for this
functionality, which returns only the name of the driver.
SDL_CreateRenderer()'s second argument is no longer an integer index, but aconst char * representing a renderer's name; if you were just using a for-loop to find
which index is the "opengl" or whatnot driver, you can just pass that string directly
here, now. Passing NULL is the same as passing -1 here in SDL2, to signify you want SDL
to decide for you.
SDL_CreateRenderer()'s flags parameter has been removed. See specific flags below for how to achieve the same functionality in SDL 3.0.
SDL_CreateWindowAndRenderer() now takes the window title as the first parameter.
SDL_GetRendererInfo() has been removed. The name of a renderer can be retrieved using SDL_GetRendererName(), and the other information is available as properties on the renderer.
Textures are created with SDL_SCALEMODE_LINEAR by default, and use SDL_BLENDMODE_BLEND by default if they are created with a format that has an alpha channel.
SDL_QueryTexture() has been removed. The properties of the texture can be queried using SDL_PROP_TEXTURE_FORMAT_NUMBER, SDL_PROP_TEXTURE_ACCESS_NUMBER, SDL_PROP_TEXTURE_WIDTH_NUMBER, and SDL_PROP_TEXTURE_HEIGHT_NUMBER. A function SDL_GetTextureSize() has been added to get the size of the texture as floating point values.
Mouse and touch events are no longer filtered to change their coordinates, instead you
can call SDL_ConvertEventToRenderCoordinates() to explicitly map event coordinates into
the rendering viewport.
SDL_RenderWindowToLogical() and SDL_RenderLogicalToWindow() have been renamed SDL_RenderCoordinatesFromWindow() and SDL_RenderCoordinatesToWindow() and take floating point coordinates in both directions.
The viewport, clipping state, and scale for render targets are now persistent and will remain set whenever they are active.
SDL_Vertex has been changed to use floating point colors, in the range of [0..1] for SDR content.
SDL_RenderReadPixels() returns a surface instead of filling in preallocated memory.
SDL_RenderSetLogicalSize() (now called SDL_SetRenderLogicalPresentation()) in SDL2 would modify the scaling and viewport state. In SDL3, logical presentation maintains its state separately, so the app can use its own viewport and scaling while also setting a logical size.
The following functions have been renamed:
- SDL_GetRendererOutputSize() => SDL_GetCurrentRenderOutputSize(), returns bool
- SDL_RenderCopy() => SDL_RenderTexture(), returns bool
- SDL_RenderCopyEx() => SDL_RenderTextureRotated(), returns bool
- SDL_RenderCopyExF() => SDL_RenderTextureRotated(), returns bool
- SDL_RenderCopyF() => SDL_RenderTexture(), returns bool
- SDL_RenderDrawLine() => SDL_RenderLine(), returns bool
- SDL_RenderDrawLineF() => SDL_RenderLine(), returns bool
- SDL_RenderDrawLines() => SDL_RenderLines(), returns bool
- SDL_RenderDrawLinesF() => SDL_RenderLines(), returns bool
- SDL_RenderDrawPoint() => SDL_RenderPoint(), returns bool
- SDL_RenderDrawPointF() => SDL_RenderPoint(), returns bool
- SDL_RenderDrawPoints() => SDL_RenderPoints(), returns bool
- SDL_RenderDrawPointsF() => SDL_RenderPoints(), returns bool
- SDL_RenderDrawRect() => SDL_RenderRect(), returns bool
- SDL_RenderDrawRectF() => SDL_RenderRect(), returns bool
- SDL_RenderDrawRects() => SDL_RenderRects(), returns bool
- SDL_RenderDrawRectsF() => SDL_RenderRects(), returns bool
- SDL_RenderFillRectF() => SDL_RenderFillRect(), returns bool
- SDL_RenderFillRectsF() => SDL_RenderFillRects(), returns bool
- SDL_RenderFlush() => SDL_FlushRenderer(), returns bool
- SDL_RenderGetClipRect() => SDL_GetRenderClipRect(), returns bool
- SDL_RenderGetIntegerScale() => SDL_GetRenderIntegerScale()
- SDL_RenderGetLogicalSize() => SDL_GetRenderLogicalPresentation(), returns bool
- SDL_RenderGetMetalCommandEncoder() => SDL_GetRenderMetalCommandEncoder()
- SDL_RenderGetMetalLayer() => SDL_GetRenderMetalLayer()
- SDL_RenderGetScale() => SDL_GetRenderScale(), returns bool
- SDL_RenderGetViewport() => SDL_GetRenderViewport(), returns bool
- SDL_RenderGetWindow() => SDL_GetRenderWindow()
- SDL_RenderIsClipEnabled() => SDL_RenderClipEnabled()
- SDL_RenderLogicalToWindow() => SDL_RenderCoordinatesToWindow(), returns bool
- SDL_RenderSetClipRect() => SDL_SetRenderClipRect(), returns bool
- SDL_RenderSetLogicalSize() => SDL_SetRenderLogicalPresentation(), returns bool
- SDL_RenderSetScale() => SDL_SetRenderScale(), returns bool
- SDL_RenderSetVSync() => SDL_SetRenderVSync(), returns bool
- SDL_RenderSetViewport() => SDL_SetRenderViewport(), returns bool
- SDL_RenderWindowToLogical() => SDL_RenderCoordinatesFromWindow(), returns bool
The following functions have been removed:
- SDL_GL_BindTexture() - use SDL_GetTextureProperties() to get the OpenGL texture ID and bind the texture directly
- SDL_GL_UnbindTexture() - use SDL_GetTextureProperties() to get the OpenGL texture ID and unbind the texture directly
- SDL_GetTextureUserData() - use SDL_GetTextureProperties() instead
- SDL_RenderGetIntegerScale()
- SDL_RenderSetIntegerScale() - this is now explicit with SDL_LOGICAL_PRESENTATION_INTEGER_SCALE
- SDL_RenderTargetSupported() - render targets are usually supported; just create a texture with SDL_TEXTUREACCESS_TARGET and see if it fails.
- SDL_SetTextureUserData() - use SDL_GetTextureProperties() instead
The following enums have been renamed:
- SDL_RendererFlip => SDL_FlipMode - moved to SDL_surface.h
The following symbols have been renamed:
- SDL_ScaleModeLinear => SDL_SCALEMODE_LINEAR
- SDL_ScaleModeNearest => SDL_SCALEMODE_NEAREST
The following symbols have been removed:
- SDL_RENDERER_ACCELERATED - all renderers except
SDL_SOFTWARE_RENDERERare accelerated - SDL_RENDERER_PRESENTVSYNC - replaced with SDL_PROP_RENDERER_CREATE_PRESENT_VSYNC_NUMBER during renderer creation and SDL_PROP_RENDERER_VSYNC_NUMBER after renderer creation
- SDL_RENDERER_SOFTWARE - you can check whether the name of the renderer is
SDL_SOFTWARE_RENDERER - SDL_RENDERER_TARGETTEXTURE - most renderers support target texture functionality; just create a texture with SDL_TEXTUREACCESS_TARGET and see if it fails.
- SDL_ScaleModeBest - use SDL_SCALEMODE_LINEAR instead
SDL_rwops.h
The following symbols have been renamed:
- RW_SEEK_CUR => SDL_IO_SEEK_CUR
- RW_SEEK_END => SDL_IO_SEEK_END
- RW_SEEK_SET => SDL_IO_SEEK_SET
SDL_rwops.h is now named SDL_iostream.h
SDL_RWops is now an opaque structure, and has been renamed to SDL_IOStream. The SDL3 APIs to create an SDL_IOStream (SDL_IOFromFile, etc) are renamed but otherwise still function as they did in SDL2. However, to make a custom SDL_IOStream with app-provided function pointers, call SDL_OpenIO and provide the function pointers through there. To call into an SDL_IOStream's functionality, use the standard APIs (SDL_ReadIO, etc), as the function pointers are internal.
SDL_IOStream is not to be confused with the unrelated standard C++ iostream class!
The RWops function pointers are now in a separate structure called SDL_IOStreamInterface, which is provided to SDL_OpenIO when creating a custom SDL_IOStream implementation. All the functions now take a void * userdata argument for their first parameter instead of an SDL_IOStream, since that's now an opaque structure.
SDL_RWread and SDL_RWwrite (and the read and write function pointers) have a different function signature in SDL3, in addition to being renamed.
Previously they looked more like stdio:
size_t SDL_RWread(SDL_RWops *context, void *ptr, size_t size, size_t maxnum);
size_t SDL_RWwrite(SDL_RWops *context, const void *ptr, size_t size, size_t maxnum);But now they look more like POSIX:
size_t SDL_ReadIO(SDL_IOStream *context, void *ptr, size_t size);
size_t SDL_WriteIO(SDL_IOStream *context, const void *ptr, size_t size);Code that used to look like this:
size_t custom_read(void *ptr, size_t size, size_t nitems, SDL_RWops *stream)
{
return SDL_RWread(stream, ptr, size, nitems);
}should be changed to:
size_t custom_read(void *ptr, size_t size, size_t nitems, SDL_IOStream *stream)
{
if (size > 0 && nitems > 0) {
return SDL_ReadIO(stream, ptr, size * nitems) / size;
}
return 0;
}SDL_RWops::type was removed; it wasn't meaningful for app-provided implementations at all, and wasn't much use for SDL's internal implementations, either. If you have to identify the type, you can examine the SDL_IOStream's properties to detect built-in implementations.
SDL_IOStreamInterface::close implementations should clean up their own userdata, but not call SDL_CloseIO on themselves; now the contract is always that SDL_CloseIO is called, which calls ->close before freeing the opaque object.
SDL_AllocRW(), SDL_FreeRW(), SDL_RWclose() and direct access to the ->close function pointer have been removed from the API, so there's only one path to manage RWops lifetimes now: SDL_OpenIO() and SDL_CloseIO().
SDL_RWFromFP has been removed from the API, due to issues when the SDL library uses a different C runtime from the application.
You can implement this in your own code easily:
/* Detailed source-code truncated for AI context efficiency. */The internal FILE * is available through a standard SDL_IOStream property, for streams made through SDL_IOFromFile() that use stdio behind the scenes; apps use this pointer at their own risk and should make sure that SDL and the app are using the same C runtime.
On Apple platforms, SDL_RWFromFile (now called SDL_IOFromFile) no longer tries to read from inside the app bundle's resource directory, instead now using the specified path unchanged. One can use SDL_GetBasePath() to find the resource directory on these platforms.
The functions SDL_ReadU8(), SDL_ReadU16LE(), SDL_ReadU16BE(), SDL_ReadU32LE(), SDL_ReadU32BE(), SDL_ReadU64LE(), and SDL_ReadU64BE() now return true if the read succeeded and false if it didn't, and store the data in a pointer passed in as a parameter.
The following functions have been renamed:
- SDL_RWFromConstMem() => SDL_IOFromConstMem()
- SDL_RWFromFile() => SDL_IOFromFile()
- SDL_RWFromMem() => SDL_IOFromMem()
- SDL_RWclose() => SDL_CloseIO(), returns bool
- SDL_RWread() => SDL_ReadIO()
- SDL_RWseek() => SDL_SeekIO()
- SDL_RWsize() => SDL_GetIOSize()
- SDL_RWtell() => SDL_TellIO()
- SDL_RWwrite() => SDL_WriteIO()
- SDL_ReadBE16() => SDL_ReadU16BE()
- SDL_ReadBE32() => SDL_ReadU32BE()
- SDL_ReadBE64() => SDL_ReadU64BE()
- SDL_ReadLE16() => SDL_ReadU16LE()
- SDL_ReadLE32() => SDL_ReadU32LE()
- SDL_ReadLE64() => SDL_ReadU64LE()
- SDL_WriteBE16() => SDL_WriteU16BE()
- SDL_WriteBE32() => SDL_WriteU32BE()
- SDL_WriteBE64() => SDL_WriteU64BE()
- SDL_WriteLE16() => SDL_WriteU16LE()
- SDL_WriteLE32() => SDL_WriteU32LE()
- SDL_WriteLE64() => SDL_WriteU64LE()
The following structures have been renamed:
- SDL_RWops => SDL_IOStream
SDL_scancode.h
The following symbols have been removed:
- SDL_SCANCODE_WWW
- SDL_SCANCODE_MAIL
- SDL_SCANCODE_CALCULATOR
- SDL_SCANCODE_COMPUTER
- SDL_SCANCODE_BRIGHTNESSDOWN
- SDL_SCANCODE_BRIGHTNESSUP
- SDL_SCANCODE_DISPLAYSWITCH
- SDL_SCANCODE_KBDILLUMTOGGLE
- SDL_SCANCODE_KBDILLUMDOWN
- SDL_SCANCODE_KBDILLUMUP
- SDL_SCANCODE_APP1
- SDL_SCANCODE_APP2
The following symbols have been renamed:
- SDL_NUM_SCANCODES => SDL_SCANCODE_COUNT
- SDL_SCANCODE_AUDIOFASTFORWARD => SDL_SCANCODE_MEDIA_FAST_FORWARD
- SDL_SCANCODE_AUDIOMUTE => SDL_SCANCODE_MUTE
- SDL_SCANCODE_AUDIONEXT => SDL_SCANCODE_MEDIA_NEXT_TRACK
- SDL_SCANCODE_AUDIOPLAY => SDL_SCANCODE_MEDIA_PLAY
- SDL_SCANCODE_AUDIOPREV => SDL_SCANCODE_MEDIA_PREVIOUS_TRACK
- SDL_SCANCODE_AUDIOREWIND => SDL_SCANCODE_MEDIA_REWIND
- SDL_SCANCODE_AUDIOSTOP => SDL_SCANCODE_MEDIA_STOP
- SDL_SCANCODE_EJECT => SDL_SCANCODE_MEDIA_EJECT
- SDL_SCANCODE_MEDIASELECT => SDL_SCANCODE_MEDIA_SELECT
SDL_sensor.h
SDL_SensorID has changed from Sint32 to Uint32, with an invalid ID being 0.
Rather than iterating over sensors using device index, there is a new function SDL_GetSensors() to get the current list of sensors, and new functions to get information about sensors from their instance ID:
{
if (SDL_InitSubSystem(SDL_INIT_SENSOR)) {
int i, num_sensors;
SDL_SensorID *sensors = SDL_GetSensors(&num_sensors);
if (sensors) {
for (i = 0; i < num_sensors; ++i) {
SDL_Log("Sensor %" SDL_PRIu32 ": %s, type %d, platform type %d",
sensors[i],
SDL_GetSensorNameForID(sensors[i]),
SDL_GetSensorTypeForID(sensors[i]),
SDL_GetSensorNonPortableTypeForID(sensors[i]));
}
SDL_free(sensors);
}
SDL_QuitSubSystem(SDL_INIT_SENSOR);
}
}Removed SDL_SensorGetDataWithTimestamp(), if you want timestamps for the sensor data, you should use the sensor_timestamp member of SDL_EVENT_SENSOR_UPDATE events.
The following functions have been renamed:
- SDL_SensorClose() => SDL_CloseSensor()
- SDL_SensorFromInstanceID() => SDL_GetSensorFromID()
- SDL_SensorGetData() => SDL_GetSensorData(), returns bool
- SDL_SensorGetInstanceID() => SDL_GetSensorID()
- SDL_SensorGetName() => SDL_GetSensorName()
- SDL_SensorGetNonPortableType() => SDL_GetSensorNonPortableType()
- SDL_SensorGetType() => SDL_GetSensorType()
- SDL_SensorOpen() => SDL_OpenSensor()
- SDL_SensorUpdate() => SDL_UpdateSensors()
The following functions have been removed:
- SDL_LockSensors()
- SDL_NumSensors() - replaced with SDL_GetSensors()
- SDL_SensorGetDeviceInstanceID()
- SDL_SensorGetDeviceName() - replaced with SDL_GetSensorNameForID()
- SDL_SensorGetDeviceNonPortableType() - replaced with SDL_GetSensorNonPortableTypeForID()
- SDL_SensorGetDeviceType() - replaced with SDL_GetSensorTypeForID()
- SDL_UnlockSensors()
SDL_shape.h
This header has been removed and a simplified version of this API has been added as SDL_SetWindowShape() in SDL_video.h. See test/testshape.c for an example.
SDL_stdinc.h
The standard C headers like stdio.h and stdlib.h are no longer included, you should include them directly in your project if you use non-SDL C runtime functions.
M_PI is no longer defined in SDL_stdinc.h, you can use the new symbols SDL_PI_D (double) and SDL_PI_F (float) instead.
bool is now defined as bool, and is 1 byte instead of the size of an int.
SDL3 attempts to apply consistency to case-insensitive string functions. In SDL2, things like SDL_strcasecmp() would usually only work on English letters, and depending on the user's locale, possibly not even those. In SDL3, consistency is applied:
- Many things that don't care about case-insensitivity, like SDL_strcmp(), continue to work with any null-terminated string of bytes, even if it happens to be malformed UTF-8.
- SDL_strcasecmp() expects valid UTF-8 strings, and will attempt to support most Unicode characters with a technique known as "case-folding," which is to say it can match 'A' and 'a', and also 'Η' and 'η', but ALSO 'ß' and "ss". This is probably how most apps assumed it worked in SDL2 and won't need any changes.
- SDL_strncasecmp() works the same, but the third parameter takes bytes, as before, so SDL_strlen() can continue to be used with it. If a string hits the limit in the middle of a codepoint, the half-processed bytes of the codepoint will be treated as a collection of U+0xFFFD (REPLACEMENT CHARACTER) codepoints, which you probably don't want.
- SDL_wcscasecmp() and SDL_wcsncasecmp() work the same way but operate on UTF-16 or UTF-32 encoded strings, depending on what the platform considers "wchar_t" to be. SDL_wcsncasecmp's third parameter is number of wchar_t values, not bytes, but UTF-16 has the same concerns as UTF-8 for variable-length codepoints.
- SDL_strcasestr() expects valid UTF-8 strings, and will compare codepoints using case-folding.
- SDL_tolower() and SDL_toupper() continue to only work on single bytes (even though the parameter is an
int) and only converts low-ASCII English A through Z. - SDL_strlwr() and SDL_strupr() operates on individual bytes (not UTF-8 codepoints) and only change low-ASCII English 'A' through 'Z'. These functions do not check the string for valid UTF-8 encoding.
- The ctype.h replacement SDL_is*() functions (SDL_isalpha, SDL_isdigit, etc) only work on low-ASCII characters and ignore user locale, assuming English. This makes these functions consistent in SDL3, but applications need to be careful to understand their limits.
Please note that the case-folding technique used by SDL3 will not produce correct results for the "Turkish 'I'"; this one letter is a surprisingly hard problem in the Unicode world, and since these functions do not specify the human language in use, we have chosen to ignore this problem.
SDL_strtoll(), SDL_strtoull(), SDL_lltoa(), and SDL_ulltoa() use long long values instead of 64-bit values, to match their C runtime counterparts.
SDL_setenv() is not thread-safe and has been renamed SDL_setenv_unsafe().
The following macros have been removed:
- SDL_TABLESIZE() - use SDL_arraysize() instead
The following functions have been renamed:
- SDL_size_add_overflow() => SDL_size_add_check_overflow(), returns bool
- SDL_size_mul_overflow() => SDL_size_mul_check_overflow(), returns bool
- SDL_strtokr() => SDL_strtok_r()
The following functions have been removed:
- SDL_memcpy4()
The following symbols have been renamed:
- SDL_FALSE => false
- SDL_TRUE => true
- SDL_bool => bool
SDL_surface.h
SDL_Surface has been simplified and internal details are no longer in the public structure.
The format member of SDL_Surface is now an enumerated pixel format value. You can get the full details of the pixel format by calling SDL_GetPixelFormatDetails(surface->format). You can get the palette associated with the surface by calling SDL_GetSurfacePalette(). You can get the clip rectangle by calling SDL_GetSurfaceClipRect().
The userdata member of SDL_Surface has been replaced with a more general properties interface, which can be queried with SDL_GetSurfaceProperties()
Indexed format surfaces no longer have a palette by default. Surfaces without a palette will copy the pixels untranslated between surfaces.
Code that used to look like this:
SDL_Surface *surface = SDL_CreateRGBSurfaceWithFormat(0, 32, 32, 8, SDL_PIXELFORMAT_INDEX8);
SDL_Palette *palette = surface->format->palette;
...should be changed to:
SDL_Surface *surface = SDL_CreateSurface(32, 32, SDL_PIXELFORMAT_INDEX8);
SDL_Palette *palette = SDL_CreateSurfacePalette(surface);
...Removed the unused 'flags' parameter from SDL_ConvertSurface.
SDL_CreateRGBSurface() and SDL_CreateRGBSurfaceWithFormat() have been combined into a new function SDL_CreateSurface().
SDL_CreateRGBSurfaceFrom() and SDL_CreateRGBSurfaceWithFormatFrom() have been combined into a new function SDL_CreateSurfaceFrom(), and the parameter order has changed for consistency with SDL_CreateSurface().
You can implement the old functions in your own code easily:
SDL_Surface *SDL_CreateRGBSurface(Uint32 flags, int width, int height, int depth, Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask)
{
return SDL_CreateSurface(width, height,
SDL_GetPixelFormatForMasks(depth, Rmask, Gmask, Bmask, Amask));
}
SDL_Surface *SDL_CreateRGBSurfaceWithFormat(Uint32 flags, int width, int height, int depth, Uint32 format)
{
return SDL_CreateSurface(width, height, format);
}
SDL_Surface *SDL_CreateRGBSurfaceFrom(void *pixels, int width, int height, int depth, int pitch, Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask)
{
return SDL_CreateSurfaceFrom(width, height,
SDL_GetPixelFormatForMasks(depth, Rmask, Gmask, Bmask, Amask),
pixels, pitch);
}
SDL_Surface *SDL_CreateRGBSurfaceWithFormatFrom(void *pixels, int width, int height, int depth, int pitch, Uint32 format)
{
return SDL_CreateSurfaceFrom(width, height, format, pixels, pitch);
}But if you're migrating your code which uses masks, you probably have a format in mind, possibly one of these:
// Various mask (R, G, B, A) and their corresponding format:
0xFF000000 0x00FF0000 0x0000FF00 0x000000FF => SDL_PIXELFORMAT_RGBA8888
0x00FF0000 0x0000FF00 0x000000FF 0xFF000000 => SDL_PIXELFORMAT_ARGB8888
0x0000FF00 0x00FF0000 0xFF000000 0x000000FF => SDL_PIXELFORMAT_BGRA8888
0x000000FF 0x0000FF00 0x00FF0000 0xFF000000 => SDL_PIXELFORMAT_ABGR8888
0x0000F800 0x000007E0 0x0000001F 0x00000000 => SDL_PIXELFORMAT_RGB565SDL_BlitSurface() and SDL_BlitSurfaceScaled() now have a const dstrect parameter and do not fill it in with the final destination rectangle.
SDL_BlitSurfaceScaled() and SDL_BlitSurfaceUncheckedScaled() now take a scale parameter.
SDL_PixelFormat is used instead of Uint32 for API functions that refer to pixel format by enumerated value.
SDL_SetSurfaceColorKey() takes an bool to enable and disable colorkey. RLE acceleration isn't controlled by the parameter, you should use SDL_SetSurfaceRLE() to change that separately.
SDL_SetSurfaceRLE() takes an bool to enable and disable RLE acceleration.
SDL_PremultiplyAlpha() takes an extra bool parameter ("linear"). For SDL2 compatibility, pass false for this parameter.
The following functions have been renamed:
- SDL_BlitScaled() => SDL_BlitSurfaceScaled(), returns bool
- SDL_ConvertSurfaceFormat() => SDL_ConvertSurface()
- SDL_FillRect() => SDL_FillSurfaceRect(), returns bool
- SDL_FillRects() => SDL_FillSurfaceRects(), returns bool
- SDL_FreeSurface() => SDL_DestroySurface()
- SDL_GetClipRect() => SDL_GetSurfaceClipRect(), returns bool
- SDL_GetColorKey() => SDL_GetSurfaceColorKey(), returns bool
- SDL_HasColorKey() => SDL_SurfaceHasColorKey()
- SDL_HasSurfaceRLE() => SDL_SurfaceHasRLE()
- SDL_LoadBMP_RW() => SDL_LoadBMP_IO()
- SDL_LowerBlit() => SDL_BlitSurfaceUnchecked(), returns bool
- SDL_LowerBlitScaled() => SDL_BlitSurfaceUncheckedScaled(), returns bool
- SDL_SaveBMP_RW() => SDL_SaveBMP_IO(), returns bool
- SDL_SetClipRect() => SDL_SetSurfaceClipRect()
- SDL_SetColorKey() => SDL_SetSurfaceColorKey(), returns bool
- SDL_UpperBlit() => SDL_BlitSurface(), returns bool
- SDL_UpperBlitScaled() => SDL_BlitSurfaceScaled(), returns bool
The following symbols have been removed:
- SDL_SWSURFACE
The following functions have been removed:
- SDL_FreeFormat()
- SDL_GetYUVConversionMode()
- SDL_GetYUVConversionModeForResolution()
- SDL_SetYUVConversionMode() - use SDL_SetSurfaceColorspace() to set the surface colorspace and SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER with SDL_CreateTextureWithProperties() to set the texture colorspace. The default colorspace for YUV pixel formats is SDL_COLORSPACE_JPEG.
- SDL_SoftStretch() - use SDL_StretchSurface() with SDL_SCALEMODE_NEAREST
- SDL_SoftStretchLinear() - use SDL_StretchSurface() with SDL_SCALEMODE_LINEAR
The following symbols have been renamed:
- SDL_PREALLOC => SDL_SURFACE_PREALLOCATED
- SDL_SIMD_ALIGNED => SDL_SURFACE_SIMD_ALIGNED
SDL_system.h
SDL_WindowsMessageHook has changed signatures so the message may be modified and it can block further message processing.
SDL_RequestAndroidPermission is no longer a blocking call; the caller now provides a callback function that fires when a response is available.
SDL_iPhoneSetAnimationCallback() and SDL_iPhoneSetEventPump() have been renamed to SDL_SetiOSAnimationCallback() and SDL_SetiOSEventPump(), respectively. SDL2 has had macros to provide this new name with the old symbol since the introduction of the iPad, but now the correctly-named symbol is the only option.
SDL_IsAndroidTV() has been renamed SDL_IsTV() and is no longer Android-specific; an app running on an Apple TV device will also return true, for example.
The following functions have been removed:
- SDL_GetWinRTFSPathUNICODE() - WinRT support was removed in SDL3.
- SDL_GetWinRTFSPathUTF8() - WinRT support was removed in SDL3.
- SDL_RenderGetD3D11Device() - replaced with the "SDL.renderer.d3d11.device" property
- SDL_RenderGetD3D12Device() - replaced with the "SDL.renderer.d3d12.device" property
- SDL_RenderGetD3D9Device() - replaced with the "SDL.renderer.d3d9.device" property
- SDL_WinRTGetDeviceFamily() - WinRT support was removed in SDL3.
The following functions have been renamed:
- SDL_AndroidBackButton() => SDL_SendAndroidBackButton()
- SDL_AndroidGetActivity() => SDL_GetAndroidActivity()
- SDL_AndroidGetExternalStoragePath() => SDL_GetAndroidExternalStoragePath()
- SDL_AndroidGetExternalStorageState() => SDL_GetAndroidExternalStorageState()
- SDL_AndroidGetInternalStoragePath() => SDL_GetAndroidInternalStoragePath()
- SDL_AndroidGetJNIEnv() => SDL_GetAndroidJNIEnv()
- SDL_AndroidRequestPermission() => SDL_RequestAndroidPermission(), returns bool
- SDL_AndroidRequestPermissionCallback() => SDL_RequestAndroidPermissionCallback()
- SDL_AndroidSendMessage() => SDL_SendAndroidMessage(), returns bool
- SDL_AndroidShowToast() => SDL_ShowAndroidToast(), returns bool
- SDL_DXGIGetOutputInfo() => SDL_GetDXGIOutputInfo(), returns bool
- SDL_Direct3D9GetAdapterIndex() => SDL_GetDirect3D9AdapterIndex()
- SDL_GDKGetDefaultUser() => SDL_GetGDKDefaultUser(), returns bool
- SDL_GDKGetTaskQueue() => SDL_GetGDKTaskQueue(), returns bool
- SDL_IsAndroidTV() => SDL_IsTV()
- SDL_LinuxSetThreadPriority() => SDL_SetLinuxThreadPriority(), returns bool
- SDL_LinuxSetThreadPriorityAndPolicy() => SDL_SetLinuxThreadPriorityAndPolicy(), returns bool
- SDL_OnApplicationDidBecomeActive() => SDL_OnApplicationDidEnterForeground()
- SDL_OnApplicationWillResignActive() => SDL_OnApplicationWillEnterBackground()
- SDL_iOSSetAnimationCallback() => SDL_SetiOSAnimationCallback(), returns bool
- SDL_iOSSetEventPump() => SDL_SetiOSEventPump()
- SDL_iPhoneSetAnimationCallback() => SDL_SetiOSAnimationCallback(), returns bool
- SDL_iPhoneSetEventPump() => SDL_SetiOSEventPump()
SDL_syswm.h
This header has been removed.
The Windows and X11 events are now available via callbacks which you can set with SDL_SetWindowsMessageHook() and SDL_SetX11EventHook().
The information previously available in SDL_GetWindowWMInfo() is now available as window properties, e.g.
/* Detailed source-code truncated for AI context efficiency. */becomes:
/* Detailed source-code truncated for AI context efficiency. */SDL_thread.h
SDL_CreateThreadWithStackSize has been replaced with SDL_CreateThreadWithProperties.
SDL_CreateThread and SDL_CreateThreadWithProperties now take beginthread/endthread function pointers on all platforms (ignoring them on most), and have been replaced with macros that hide this detail on all platforms. This works the same as before at the source code level, but the actual function signature that is called in SDL has changed. The library's exported symbol is SDL_CreateThreadRuntime, and looking for "SDL_CreateThread" in the DLL/Shared Library/Dylib will fail. You should not call this directly, but instead always use the macro!
SDL_GetTLS() and SDL_SetTLS() take a pointer to a TLS ID, and will automatically initialize it in a thread-safe way as needed.
The following functions have been renamed:
- SDL_SetThreadPriority() => SDL_SetCurrentThreadPriority()
- SDL_TLSCleanup() => SDL_CleanupTLS()
- SDL_TLSGet() => SDL_GetTLS()
- SDL_TLSSet() => SDL_SetTLS(), returns bool
- SDL_ThreadID() => SDL_GetCurrentThreadID()
The following functions have been removed:
- SDL_TLSCreate() - TLS IDs are automatically allocated as needed.
The following symbols have been renamed:
- SDL_threadID => SDL_ThreadID
SDL_timer.h
SDL_GetTicks() now returns a 64-bit value. Instead of using the SDL_TICKS_PASSED macro, you can directly compare tick values, e.g.
Uint32 deadline = SDL_GetTicks() + 1000;
...
if (SDL_TICKS_PASSED(SDL_GetTicks(), deadline)) {
...
}becomes:
Uint64 deadline = SDL_GetTicks() + 1000
...
if (SDL_GetTicks() >= deadline) {
...
}If you were using this macro for other things besides SDL ticks values, you can define it in your own code as:
#define SDL_TICKS_PASSED(A, B) ((Sint32)((B) - (A)) <= 0)The callback passed to SDL_AddTimer() has changed parameters to:
Uint32 SDLCALL TimerCallback(void *userdata, SDL_TimerID timerID, Uint32 interval);SDL_touch.h
SDL_GetTouchName is replaced with SDL_GetTouchDeviceName(), which takes an SDL_TouchID instead of an index.
SDL_TouchID and SDL_FingerID are now Uint64 with 0 being an invalid value.
Rather than iterating over touch devices using an index, there is a new function SDL_GetTouchDevices() to get the available devices.
Rather than iterating over touch fingers using an index, there is a new function SDL_GetTouchFingers() to get the current set of active fingers.
The following functions have been removed:
- SDL_GetNumTouchDevices() - replaced with SDL_GetTouchDevices()
- SDL_GetNumTouchFingers() - replaced with SDL_GetTouchFingers()
- SDL_GetTouchDevice() - replaced with SDL_GetTouchDevices()
- SDL_GetTouchFinger() - replaced with SDL_GetTouchFingers()
SDL_version.h
SDL_GetRevisionNumber() has been removed from the API, it always returned 0 in SDL 2.0.
SDL_GetVersion() returns the version number, which can be directly compared with another version wrapped with SDL_VERSIONNUM().
The following structures have been removed:
- SDL_version
The following symbols have been renamed:
- SDL_COMPILEDVERSION => SDL_VERSION
- SDL_PATCHLEVEL => SDL_MICRO_VERSION
SDL_video.h
Several video backends have had their names lower-cased ("kmsdrm", "rpi", "android", "psp", "ps2", "vita"). SDL already does a case-insensitive compare for SDL_HINT_VIDEO_DRIVER tests, but if your app is calling SDL_GetVideoDriver() or SDL_GetCurrentVideoDriver() and doing case-sensitive compares on those strings, please update your code.
SDL_VideoInit() and SDL_VideoQuit() have been removed. Instead you can call SDL_InitSubSystem() and SDL_QuitSubSystem() with SDL_INIT_VIDEO, which will properly refcount the subsystems. You can choose a specific video driver using SDL_HINT_VIDEO_DRIVER.
Rather than iterating over displays using display index, there is a new function SDL_GetDisplays() to get the current list of displays, and functions which used to take a display index now take SDL_DisplayID, with an invalid ID being 0.
{
if (SDL_InitSubSystem(SDL_INIT_VIDEO)) {
int i, num_displays = 0;
SDL_DisplayID *displays = SDL_GetDisplays(&num_displays);
if (displays) {
for (i = 0; i < num_displays; ++i) {
SDL_DisplayID instance_id = displays[i];
const char *name = SDL_GetDisplayName(instance_id);
SDL_Log("Display %" SDL_PRIu32 ": %s", instance_id, name ? name : "Unknown");
}
SDL_free(displays);
}
SDL_QuitSubSystem(SDL_INIT_VIDEO);
}
}SDL_CreateWindow() has been simplified and no longer takes a window position. You can use SDL_CreateWindowWithProperties() if you need to set the window position when creating it, e.g.
SDL_PropertiesID props = SDL_CreateProperties();
SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, title);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, x);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, y);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, width);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, height);
// For window flags you should use separate window creation properties,
// but for easier migration from SDL2 you can use the following:
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_FLAGS_NUMBER, flags);
pWindow = SDL_CreateWindowWithProperties(props);
SDL_DestroyProperties(props);
if (window) {
...
}The SDL_WINDOWPOS_UNDEFINED_DISPLAY() and SDL_WINDOWPOS_CENTERED_DISPLAY() macros take a display ID instead of display index. The display ID 0 has a special meaning in this case, and is used to indicate the primary display.
The SDL_WINDOW_SHOWN flag has been removed. Windows are shown by default and can be created hidden by using the SDL_WINDOW_HIDDEN flag.
The SDL_WINDOW_SKIP_TASKBAR flag has been replaced by the SDL_WINDOW_UTILITY flag, which has the same functionality.
SDL_DisplayMode now includes the pixel density which can be greater than 1.0 for display modes that have a higher pixel size than the mode size. You should use SDL_GetWindowSizeInPixels() to get the actual pixel size of the window back buffer.
The refresh rate in SDL_DisplayMode is now a float, as well as being represented as a precise fraction with numerator and denominator.
Rather than iterating over display modes using an index, there is a new function SDL_GetFullscreenDisplayModes() to get the list of available fullscreen modes on a display.
{
SDL_DisplayID display = SDL_GetPrimaryDisplay();
int num_modes = 0;
SDL_DisplayMode **modes = SDL_GetFullscreenDisplayModes(display, &num_modes);
if (modes) {
for (i = 0; i < num_modes; ++i) {
SDL_DisplayMode *mode = modes[i];
SDL_Log("Display %" SDL_PRIu32 " mode %d: %dx%d@%gx %gHz",
display, i, mode->w, mode->h, mode->pixel_density, mode->refresh_rate);
}
SDL_free(modes);
}
}SDL_GetDesktopDisplayMode() and SDL_GetCurrentDisplayMode() return pointers to display modes rather than filling in application memory.
Windows now have an explicit fullscreen mode that is set, using SDL_SetWindowFullscreenMode(). The fullscreen mode for a window can be queried with SDL_GetWindowFullscreenMode(), which returns a pointer to the mode, or NULL if the window will be fullscreen desktop. SDL_SetWindowFullscreen() just takes a boolean value, setting the correct fullscreen state based on the selected mode.
SDL_WINDOW_FULLSCREEN_DESKTOP has been removed, and you can call SDL_GetWindowFullscreenMode() to see whether an exclusive fullscreen mode will be used or the borderless fullscreen desktop mode will be used when the window is fullscreen.
SDL_SetWindowBrightness(), SDL_GetWindowBrightness, SDL_SetWindowGammaRamp(), and SDL_GetWindowGammaRamp have been removed from the API, because they interact poorly with modern operating systems and aren't able to limit their effects to the SDL window.
Programs which have access to shaders can implement more robust versions of those functions using custom shader code rendered as a post-process effect.
Removed SDL_GL_CONTEXT_EGL from OpenGL configuration attributes. You can instead use SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
SDL_GL_GetProcAddress() and SDL_EGL_GetProcAddress() now return SDL_FunctionPointer instead of void *, and should be cast to the appropriate function type. You can define SDL_FUNCTION_POINTER_IS_VOID_POINTER in your project to restore the previous behavior.
SDL_GL_DeleteContext() has been renamed to SDL_GL_DestroyContext to match SDL naming conventions (and glX/EGL!).
SDL_GL_GetSwapInterval() takes the interval as an output parameter and returns true if the function succeeds or false if there was an error.
SDL_GL_GetDrawableSize() has been removed. SDL_GetWindowSizeInPixels() can be used in its place.
The SDL_WINDOW_TOOLTIP and SDL_WINDOW_POPUP_MENU window flags are now supported on Windows, Mac (Cocoa), X11, and Wayland. Creating windows with these flags must happen via the SDL_CreatePopupWindow() function. This function requires passing in the handle to a valid parent window for the popup, and the popup window is positioned relative to the parent.
SDL_WindowFlags is used instead of Uint32 for API functions that refer to window flags, and has been extended to 64 bits.
SDL_GetWindowOpacity() directly returns the opacity instead of using an out parameter.
The following functions have been renamed:
- SDL_GL_DeleteContext() => SDL_GL_DestroyContext(), returns bool
- SDL_GetClosestDisplayMode() => SDL_GetClosestFullscreenDisplayMode(), returns bool
- SDL_GetDisplayOrientation() => SDL_GetCurrentDisplayOrientation()
- SDL_GetPointDisplayIndex() => SDL_GetDisplayForPoint()
- SDL_GetRectDisplayIndex() => SDL_GetDisplayForRect()
- SDL_GetWindowDisplayIndex() => SDL_GetDisplayForWindow()
- SDL_GetWindowDisplayMode() => SDL_GetWindowFullscreenMode()
- SDL_HasWindowSurface() => SDL_WindowHasSurface()
- SDL_IsScreenSaverEnabled() => SDL_ScreenSaverEnabled()
- SDL_SetWindowDisplayMode() => SDL_SetWindowFullscreenMode(), returns bool
The following functions have been removed:
- SDL_GetDisplayDPI() - not reliable across platforms, approximately replaced by multiplying SDL_GetWindowDisplayScale() times 160 on iPhone and Android, and 96 on other platforms.
- SDL_GetDisplayMode()
- SDL_GetNumDisplayModes() - replaced with SDL_GetFullscreenDisplayModes()
- SDL_GetNumVideoDisplays() - replaced with SDL_GetDisplays()
- SDL_SetWindowGrab() - use SDL_SetWindowMouseGrab() instead, along with SDL_SetWindowKeyboardGrab() if you also set SDL_HINT_GRAB_KEYBOARD.
- SDL_GetWindowGrab() - use SDL_GetWindowMouseGrab() instead, along with SDL_GetWindowKeyboardGrab() if you also set SDL_HINT_GRAB_KEYBOARD.
- SDL_GetWindowData() - use SDL_GetPointerProperty() instead, along with SDL_GetWindowProperties()
- SDL_SetWindowData() - use SDL_SetPointerProperty() instead, along with SDL_GetWindowProperties()
- SDL_CreateWindowFrom() - use SDL_CreateWindowWithProperties() with the properties that allow you to wrap an existing window
- SDL_SetWindowInputFocus() - use SDL_RaiseWindow() instead
- SDL_SetWindowModalFor() - use SDL_SetWindowParent() with SDL_SetWindowModal() instead
- SDL_SetWindowBrightness() - use a shader or other in-game effect.
- SDL_GetWindowBrightness() - use a shader or other in-game effect.
- SDL_SetWindowGammaRamp() - use a shader or other in-game effect.
- SDL_GetWindowGammaRamp() - use a shader or other in-game effect.
The SDL_Window id type is named SDL_WindowID
The following environment variables have been removed:
- SDL_VIDEO_GL_DRIVER - replaced with the hint SDL_HINT_OPENGL_LIBRARY
- SDL_VIDEO_EGL_DRIVER - replaced with the hint SDL_HINT_EGL_LIBRARY
The following symbols have been renamed:
- SDL_DISPLAYEVENT_DISCONNECTED => SDL_EVENT_DISPLAY_REMOVED
- SDL_DISPLAYEVENT_MOVED => SDL_EVENT_DISPLAY_MOVED
- SDL_DISPLAYEVENT_ORIENTATION => SDL_EVENT_DISPLAY_ORIENTATION
- SDL_GLattr => SDL_GLAttr
- SDL_GLcontextFlag => SDL_GLContextFlag
- SDL_GLcontextReleaseFlag => SDL_GLContextReleaseFlag
- SDL_GLprofile => SDL_GLProfile
- SDL_WINDOWEVENT_CLOSE => SDL_EVENT_WINDOW_CLOSE_REQUESTED
- SDL_WINDOWEVENT_DISPLAY_CHANGED => SDL_EVENT_WINDOW_DISPLAY_CHANGED
- SDL_WINDOWEVENT_ENTER => SDL_EVENT_WINDOW_MOUSE_ENTER
- SDL_WINDOWEVENT_EXPOSED => SDL_EVENT_WINDOW_EXPOSED
- SDL_WINDOWEVENT_FOCUS_GAINED => SDL_EVENT_WINDOW_FOCUS_GAINED
- SDL_WINDOWEVENT_FOCUS_LOST => SDL_EVENT_WINDOW_FOCUS_LOST
- SDL_WINDOWEVENT_HIDDEN => SDL_EVENT_WINDOW_HIDDEN
- SDL_WINDOWEVENT_HIT_TEST => SDL_EVENT_WINDOW_HIT_TEST
- SDL_WINDOWEVENT_ICCPROF_CHANGED => SDL_EVENT_WINDOW_ICCPROF_CHANGED
- SDL_WINDOWEVENT_LEAVE => SDL_EVENT_WINDOW_MOUSE_LEAVE
- SDL_WINDOWEVENT_MAXIMIZED => SDL_EVENT_WINDOW_MAXIMIZED
- SDL_WINDOWEVENT_MINIMIZED => SDL_EVENT_WINDOW_MINIMIZED
- SDL_WINDOWEVENT_MOVED => SDL_EVENT_WINDOW_MOVED
- SDL_WINDOWEVENT_RESIZED => SDL_EVENT_WINDOW_RESIZED
- SDL_WINDOWEVENT_RESTORED => SDL_EVENT_WINDOW_RESTORED
- SDL_WINDOWEVENT_SHOWN => SDL_EVENT_WINDOW_SHOWN
- SDL_WINDOW_ALLOW_HIGHDPI => SDL_WINDOW_HIGH_PIXEL_DENSITY
- SDL_WINDOW_INPUT_GRABBED => SDL_WINDOW_MOUSE_GRABBED
The following symbols have been removed:
- SDL_WINDOWEVENT_SIZE_CHANGED - handle the SDL_EVENT_WINDOW_RESIZED and SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED events instead
- SDL_WINDOWEVENT_TAKE_FOCUS
The following window operations are now considered to be asynchronous requests and should not be assumed to succeed unless
a corresponding event has been received:
- SDL_SetWindowSize() (SDL_EVENT_WINDOW_RESIZED)
- SDL_SetWindowPosition() (SDL_EVENT_WINDOW_MOVED)
- SDL_MinimizeWindow() (SDL_EVENT_WINDOW_MINIMIZED)
- SDL_MaximizeWindow() (SDL_EVENT_WINDOW_MAXIMIZED)
- SDL_RestoreWindow() (SDL_EVENT_WINDOW_RESTORED)
- SDL_SetWindowFullscreen() (SDL_EVENT_WINDOW_ENTER_FULLSCREEN / SDL_EVENT_WINDOW_LEAVE_FULLSCREEN)
If it is required that operations be applied immediately after one of the preceding calls, the SDL_SyncWindow() function
will attempt to wait until all pending window operations have completed. The SDL_HINT_VIDEO_SYNC_WINDOW_OPERATIONS hint
can also be set to automatically synchronize after all calls to an asynchronous window operation, mimicking the behavior
of SDL 2. Be aware that synchronizing can potentially block for long periods of time, as it may have to wait for window
animations to complete. Also note that windowing systems can deny or not precisely obey these requests (e.g. windows may
not be allowed to be larger than the usable desktop space or placed offscreen), so a corresponding event may never arrive
or not contain the expected values.
SDL_vulkan.h
SDL_Vulkan_GetInstanceExtensions() no longer takes a window parameter, and no longer makes the app allocate query/allocate space for the result, instead returning a static const internal string.
SDL_Vulkan_GetVkGetInstanceProcAddr() now returns SDL_FunctionPointer instead of void *, and should be cast to PFN_vkGetInstanceProcAddr.
SDL_Vulkan_CreateSurface() now takes a VkAllocationCallbacks pointer as its third parameter. If you don't have an allocator to supply, pass a NULL here to use the system default allocator (SDL2 always used the system default allocator here).
SDL_Vulkan_GetDrawableSize() has been removed. SDL_GetWindowSizeInPixels() can be used in its place.
SDL_vulkanInstance and SDL_vulkanSurface have been removed. They were for compatibility with Tizen, who had built their own Vulkan interface into SDL2, but these apps will need changes for the SDL3 API if they are upgraded anyhow.
README N3ds (`docs/README-n3ds.md`)
Nintendo 3DS
SDL port for the Nintendo 3DS Homebrew toolchain contributed by:
Credits to:
- The awesome people who ported SDL to other homebrew platforms.
- The Devkitpro team for making all the tools necessary to achieve this.
Building
To build for the Nintendo 3DS, make sure you have devkitARM and cmake installed and run:
cmake -S. -Bbuild -DCMAKE_TOOLCHAIN_FILE="$DEVKITPRO/cmake/3DS.cmake" -DCMAKE_BUILD_TYPE=Release
cmake --build build
cmake --install buildNotes
- Currently only software rendering is supported.
- SDL3_main should be used to ensure ROMFS is enabled - this is done with
#include <SDL3/SDL_main.h>in the source file that contains your main function. - By default, the extra L2 cache and higher clock speeds of the New 2/3DS lineup are enabled. If you wish to turn it off, use
osSetSpeedupEnable(false)in your main function. SDL_GetBasePathreturns the romfs root instead of the executable's directory.- The Nintendo 3DS uses a cooperative threading model on a single core, meaning a thread will never yield unless done manually through the
SDL_Delayfunctions, or blocking waits (SDL_LockMutex,SDL_WaitSemaphore,SDL_WaitCondition,SDL_WaitThread). To avoid starving other threads,SDL_TryWaitSemaphoreandSDL_WaitSemaphoreTimeoutwill yield if they fail to acquire the semaphore, see https://github.com/libsdl-org/SDL/pull/6776 for more information.
README Ngage (`docs/README-ngage.md`)
Nokia N-Gage
SDL port for the Nokia N-Gage
Homebrew toolchain
contributed by:
Many thanks to:
icculus and slouken for always making room for us, even when we show up in 2025
still waving the N-Gage flag.The Nokia N-Gage Discord community
who keeps the platform alive.The staff and supporters of the
Suomen pelimuseo, and
to Heikki Jungmann, for their ongoing love and dedication for the Nokia N-Gage --
you guys are awesome!
History
When SDL support was discontinued due to the lack of C99 support at the time,
this version was rebuilt from the ground up after resolving the compiler issues.
In contrast to the earlier SDL2 port, this version features a dedicated rendering
backend and a functional, albeit limited, audio interface. Support for the
software renderer has been removed.
The outcome is a significantly leaner and more efficient SDL port, which we hope
will breathe new life into this beloved yet obscure platform.
Existing Issues and Limitations
For now, the new
SDL3 main callbacks
are not optional and must be used. This is important as the callbacks
are optional on other platforms.If the application is put in the background while sound is playing,
some of the audio is looped until the app is back in focus.It is recommended initialising SDLs audio sub-system even when it
is not required. The backend is started at a higher level. Initialising
SDLs audio sub-system ensures that the backend is properly deinitialised.Because the audio sample rate can change during phone calls, the sample
rate is currently fixed at 8kHz to ensure stable behavior. Although
dynamically adjusting the sample rate is theoretically possible, the
current implementation doesn't support it yet. This limitation is
expected to be resolved in a future update.Dependency tracking is currently non-functional.
The compiler doesn't support aggregate initialization for structs, so
each field must be assigned explicitly.
README Platforms (`docs/README-platforms.md`)
Platforms
Supported Platforms
SDL3 has been known to work on the following platforms at some point:
- Android
- DOS
- Emscripten (Web browsers)
- FreeBSD
- Haiku OS
- HarmonyOS/OpenHarmony
- iOS
- Linux
- macOS (10.14 and later)
- NetBSD
- Nintendo Switch and Switch 2 (Separate NDA-only fork)
- Nintendo 3DS (Homebrew)
- Nokia N-Gage
- OpenBSD
- PlayStation 2 (Homebrew)
- PlayStation 4 (Separate NDA-only fork)
- PlayStation 5 (Separate NDA-only fork)
- PlayStation Portable (Homebrew)
- PlayStation Vita (Homebrew)
- QNX
- RISC OS
- SteamOS
- tvOS
- visionOS
- Windows (XP and later)
- Windows GDK
- Xbox
Note that the SDL maintainers do not test on all these platforms; if a less-common system breaks, please let us know and send patches if you can.
If you'd like to port SDL to a new platform, feel free to get in touch! A guide to porting SDL2 was written a while ago, and most of it still applies to SDL3.
Unsupported Platforms
If your favorite system is listed below, we aren't working on it. However, if you send reasonable patches and are willing to support the port in the long term, we are happy to take a look!
All of these still work with SDL2, which is an incompatible API, but an option if you need to support these platforms still.
- Google Stadia
- NaCL
- OS/2
- WinPhone
- WinRT/UWP
- Pandora
General notes for Unix platforms
Some aspects of SDL functionality are common to all Unix-based platforms.
Privileged processes (setuid, setgid, setcap)
SDL is not designed to be used in programs with elevated privileges,
such as setuid (chmod u+s) or setgid (chmod g+s) executables,
or executables with file-based capabilities
(setcap cap_sys_nice+ep or similar).
It does not make any attempt to avoid trusting environment variables
or other aspects of the inherited execution environment.
Programs running with elevated privileges in an attacker-controlled
execution environment should not call SDL functions.
README Porting (`docs/README-porting.md`)
Porting
Porting To A New Platform
The first thing you have to do when porting to a new platform, is look at
include/SDL_platform.h and create an entry there for your operating system.
The standard format is "SDL_PLATFORM_X", where X is the name of the OS.
Ideally SDL_platform_defines.h will be able to auto-detect the system it's building
on based on C preprocessor symbols.
There are two basic ways of building SDL at the moment:
CMake: cmake -S . -B build && cmake --build build && cmake --install install
If you have a system that supports CMake, then you might try this. Edit CMakeLists.txt,
take a look at the large section labelled:
"Platform-specific options and settings!"
Add a section for your platform, and then re-run 'cmake -S . -B build' and build!
Using an IDE:
If you're using an IDE or other non-configure build system, you'll probably want to create a custom
SDL_build_config.hfor your platform. Editinclude/build_config/SDL_build_config.h, add a section for your platform, and create a customSDL_build_config_{platform}.h, based onSDL_build_config_minimal.handSDL_build_config.h.cmakeAdd the top level include directory to the header search path, and then add
the following sources to the project:src/.c
src/atomic/.c
src/audio/.c
src/cpuinfo/.c
src/events/.c
src/file/.c
src/haptic/.c
src/joystick/.c
src/power/.c
src/render/.c
src/render/software/.c
src/stdlib/.c
src/thread/.c
src/timer/.c
src/video/.c
src/audio/disk/.c
src/audio/dummy/.c
src/filesystem/dummy/.c
src/video/dummy/.c
src/haptic/dummy/.c
src/joystick/dummy/.c
src/thread/generic/.c
src/timer/dummy/.c
src/loadso/dummy/.c
Once you have a working library without any drivers, you can go back to each
of the major subsystems and start implementing drivers for your platform.
If you have any questions, don't hesitate to ask on the SDL mailing list:
http://www.libsdl.org/mailing-list.php
Enjoy!
Sam Lantinga ([email protected])
README Ps2 (`docs/README-ps2.md`)
PS2
SDL port for the Sony Playstation 2 contributed by:
- Francisco Javier Trujillo Mata
Credit to
- The guys that ported SDL to PSP & Vita because I'm taking them as reference.
- David G. F. for helping me with several issues and tests.
Building
To build SDL library for the PS2, make sure you have the latest PS2Dev status and run:
export PS2DEV=/usr/local/ps2dev # or wherever your ps2dev installation is
export PS2SDK=$PS2DEV/ps2sdk
export PATH=$PATH:$PS2DEV/bin:$PS2DEV/ee/bin:$PS2DEV/iop/bin:$PS2DEV/dvp/bin:$PS2SDK/bin
cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=$PS2DEV/share/ps2dev.cmake
cmake --build build
cmake --install buildHints
SDL_HINT_PS2_GS_WIDTH: Width of the framebuffer. Defaults to 640.SDL_HINT_PS2_GS_HEIGHT: Height of the framebuffer. Defaults to 448.SDL_HINT_PS2_GS_COLOR_DEPTH: Color depth of the framebuffer. Can be "32", "24" or "16". Defaults to 24.SDL_HINT_PS2_GS_PROGRESSIVE: Whether to use progressive, instead of interlaced. Defaults to 0.SDL_HINT_PS2_GS_MODE: Regional standard of the signal. "NTSC" (60hz), "PAL" (50hz) or "" (the console's region, default).
Notes
If you trying to debug a SDL app through ps2client you need to avoid the IOP reset, otherwise you will lose the connection with your computer.
So to avoid the reset of the IOP CPU, you need to call to the macro SDL_PS2_SKIP_IOP_RESET();.
It could be something similar as:
.....
SDL_PS2_SKIP_IOP_RESET();
int main(int argc, char *argv[])
{
.....For a release binary is recommendable to reset the IOP always.
Remember to do a clean compilation every time you enable or disable the SDL_PS2_SKIP_IOP_RESET otherwise the change won't be reflected.
Getting PS2 Dev
Running on Emulators
To Do
- PS2 Screen Keyboard
- Dialogs
- Others
README Ps4 (`docs/README-ps4.md`)
Sony PlayStation 4
SDL3 runs on the PS4! There are commercial games shipping with this port. This port is kept in a separate repository, but is available for free, under the zlib license, to anyone that is under NDA for PlayStation development with Sony. Please contact Ryan (icculus at icculus dot org) for details.
README Ps5 (`docs/README-ps5.md`)
Sony PlayStation 5
SDL3 runs on the PS5! There are commercial games shipping with this port. This port is kept in a separate repository, but is available for free, under the zlib license, to anyone that is under NDA for PlayStation development with Sony. Please contact Ryan (icculus at icculus dot org) for details.
README Psp (`docs/README-psp.md`)
PSP
SDL port for the Sony PSP contributed by:
- Captain Lex
- Francisco Javier Trujillo Mata
- Wouter Wijsman
Credit to
Marcus R.Brown, Jim Paris, Matthew H for the original SDL 1.2 for PSP
Geecko for his PSP GU lib "Glib2d"
Building
To build SDL library for the PSP, make sure you have the latest PSPDev status and run:
cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=$PSPDEV/psp/share/pspdev.cmake
cmake --build build
cmake --install buildGetting PSP Dev
Running on PPSSPP Emulator
Compiling a HelloWorld
To Do
- PSP Screen Keyboard
- Dialogs
README Qnx (`docs/README-qnx.md`)
QNX
SDL port for QNX, providing both screen and Wayland video backends.
This was originally contributed by Elad Lahav for QNX 7.0.
The port was later improved and adapted for QNX 8.0 by:
- Ethan Leir
- Roberto Speranza
- Darcy Phipps
- Jai Moraes
- Pierce McKinnon
Further changes to enable Wayland with the EGL backend were made by Felix Xing
and Aaron Bassett.
Building
Building SDL3 for QNX requires Wayland to be built and installed. The commands
to build it are,
# Note, if you're cross-compiling, you will need to source qnxsdp-env.sh and
# provide the path to a cmake toolchain file with -DCMAKE_TOOLCHAIN_FILE=$TOOLCHAIN_DIR/qnx.nto.toolchain.cmake
cmake -B build -DCMAKE_BUILD_TYPE=Release -DSDL_X11=0
cmake --build build
cmake --install buildQNX self-hosted
QNX provides a self-hosted environment, available with the free license.
This is the easiest way to get your hands on SDL.
QNX build-files
You can find the cross-compiled build tools at https://github.com/qnx-ports/build-files
Notes - screen
- Currently, only software and OpenGLES2 rendering is supported.
- Unless your application is managed by a window manager capable of closing the application, you will need to quit it yourself.
- Restraining the mouse to a window or warping the mouse cursor will not work.
Notes - alsa
- For sound to work on the rpi, you may need to run,bash
export SDL_AUDIO_DEVICE_SAMPLE_FRAMES=6912 # Or any other value retrieved from speaker-test
README Riscos (`docs/README-riscos.md`)
RISC OS
Requirements:
- RISC OS 3.5 or later.
- SharedUnixLibrary.
- DigitalRenderer, for audio support.
- Iconv, for
SDL_iconvand related functions.
Compiling:
Currently, SDL for RISC OS only supports compiling with GCCSDK under Linux.
The following commands can be used to build SDL for RISC OS using CMake:
cmake -Bbuild-riscos -DCMAKE_TOOLCHAIN_FILE=$GCCSDK_INSTALL_ENV/toolchain-riscos.cmake -DRISCOS=ON -DCMAKE_INSTALL_PREFIX=$GCCSDK_INSTALL_ENV -DCMAKE_BUILD_TYPE=Release
cmake --build build-riscos
cmake --install build-riscosWhen using GCCSDK 4.7.4 release 6 or earlier versions, the builtin atomic functions are broken, meaning it's currently necessary to compile with -DSDL_GCC_ATOMICS=OFF using CMake. Newer versions of GCCSDK don't have this problem.
Current level of implementation
The video driver currently provides full screen video support with keyboard and mouse input. Windowed mode is not yet supported, but is planned in the future. Only software rendering is supported.
The filesystem APIs return either Unix-style paths or RISC OS-style paths based on the value of the __riscosify_control symbol, as is standard for UnixLib functions.
The audio, loadso, thread and timer APIs are currently provided by UnixLib.
The joystick, locale and power APIs are not yet implemented.
README Steamos (`docs/README-steamos.md`)
SteamOS
SteamOS is literally a Linux system, and uses the same binaries you distribute to generic Linux Steam users, so generally speaking, all the other Linux advice applies.
If you are shipping a Linux game on Steam, or explicitly targeting SteamOS, the system is guaranteed to provide SDL. The Steam Client will set up the dynamic loader path so that a known-good copy of SDL is available to any program that needs it before launching a game. Steam provides all major versions of SDL to date, in this manner, for both x86 and amd64, in addition to several add-on libraries like SDL_image and SDL_mixer. When shipping a Linux game on Steam, do not ship a build of SDL with your game. Link against SDL as normal, and expect it to be available on the player's system. This allows Valve to make fixes and improvements to their SDL and those fixes to flow on to your game.
We are obsessive about SDL3 having a backwards-compatible ABI. Whether you build your game using the Steam Runtime SDK or just about any other copy of SDL, it should work with the one that ships with Steam.
In fact, it's not a bad idea to just copy the SDL build out of the Steam Runtime if you plan to ship a Linux game for non-Steam platforms, too, since you know it's definitely well-built.
README Strings (`docs/README-strings.md`)
String policies in SDL3.
Encoding.
Unless otherwise specified, all strings in SDL, across all platforms, are
UTF-8 encoded and can represent the full range of Unicode.
README Switch (`docs/README-switch.md`)
Nintendo Switch
SDL3 runs on the Nintendo Switch! Both Switch and Switch 2 are supported. There are commercial games shipping with this port. This port is kept in a separate repository, but is available for free, under the zlib license, to anyone that is under the appropriate NDAs with Nintendo. Please contact Ryan (icculus at icculus dot org) for details.
README Touch (`docs/README-touch.md`)
Touch
System Specific Notes
Linux:
The linux touch system is currently based off event streams, and proc/bus/devices. The active user must be given permissions to read /dev/input/TOUCHDEVICE, where TOUCHDEVICE is the event stream for your device. Currently only Wacom tablets are supported. If you have an unsupported tablet contact me at [email protected] and I will help you get support for it.
Mac:
The Mac and iPhone APIs are pretty. If your touch device supports them then you'll be fine. If it doesn't, then there isn't much we can do.
iPhone:
Works out of box.
Windows:
Unfortunately there is no windows support as of yet. Support for Windows 7 is planned, but we currently have no way to test. If you have a Windows 7 WM_TOUCH supported device, and are willing to help test please contact me at [email protected]
Events
SDL_EVENT_FINGER_DOWN:
Sent when a finger (or stylus) is placed on a touch device.
Fields:
- event.tfinger.touchId - the Id of the touch device.
- event.tfinger.fingerId - the Id of the finger which just went down.
- event.tfinger.x - the x coordinate of the touch (0..1)
- event.tfinger.y - the y coordinate of the touch (0..1)
- event.tfinger.pressure - the pressure of the touch (0..1)
SDL_EVENT_FINGER_MOTION:
Sent when a finger (or stylus) is moved on the touch device.
Fields:
Same as SDL_EVENT_FINGER_DOWN but with additional:
- event.tfinger.dx - change in x coordinate during this motion event.
- event.tfinger.dy - change in y coordinate during this motion event.
SDL_EVENT_FINGER_UP:
Sent when a finger (or stylus) is lifted from the touch device.
Fields:
Same as SDL_EVENT_FINGER_DOWN.
Functions
SDL provides the ability to access the underlying SDL_Finger structures.
These structures should never be modified.
The following functions are included from SDL_touch.h
Devices are tracked by instance ID, of type SDL_TouchID.
To get a list of available device SDL_TouchID values, call SDL_GetTouchDevices().
This returns an array of device IDs, terminated by a zero ID. Optionally, you can
get a count of IDs by passing a non-NULL int* to SDL_GetTouchDevices() if you'd
rather not iterate the whole array to get this number.
A SDL_TouchID may be used to get pointers to SDL_Finger.
SDL_GetNumTouchFingers(touchID) may be used to get the number of fingers currently down on the device.
The most common reason to access SDL_Finger is to query the fingers outside the event. In most cases accessing the fingers is using the event. This would be accomplished by code like the following:
float x = event.tfinger.x;
float y = event.tfinger.y;To get a SDL_Finger, call SDL_GetTouchFinger(SDL_TouchID touchID, int index), where touchID is a SDL_TouchID, and index is the requested finger.
This returns a SDL_Finger *, or NULL if the finger does not exist, or has been removed.
A SDL_Finger is guaranteed to be persistent for the duration of a touch, but it will be de-allocated as soon as the finger is removed. This occurs when the SDL_EVENT_FINGER_UP event is added to the event queue, and thus before the SDL_EVENT_FINGER_UP event is polled.
As a result, be very careful to check for NULL return values.
A SDL_Finger has the following fields:
- x, y:
The current coordinates of the touch. - pressure:
The pressure of the touch.
Please direct questions/comments to:
[email protected]
(original author, API was changed since)
README Ubuntu Touch (`docs/README-ubuntu-touch.md`)
Ubuntu Touch / Lomiri
Ubuntu Touch being similar to Ubuntu desktop, most features should be supported
out-of-the-box with SDL.
Developing apps
Ubuntu Touch apps are developed using Clickable.
Clickable provides an SDL template. It is highly recommended to use the template
as a starting point for both new and existing apps.
Considerations
Ubuntu Touch is similar to the desktop version of Ubuntu, but presents some
differences in behavior. Developers should be wary of the following:
SDL_GetPrefPath
The only allowed writable folder is ~/.local/share/<appname>/.SDL_GetPrefPath ignores its arguments and will always return that path on
Ubuntu Touch. No changes are needed in apps.
Video driver
Currently, a bug forces SDL to
use the Wayland driver on Ubuntu Touch. No changes are needed in apps.
Extra functions
SDL provides SDL_IsUbuntuTouch() to differentiate between Ubuntu Touch and
regular Unix, which can help if certain platform-specific tweaks are needed.
README Versions (`docs/README-versions.md`)
Versioning
Since 3.2.0
SDL follows an "odd/even" versioning policy, similar to GLib, GTK, Flatpak
and older versions of the Linux kernel:
If the minor version (second part) and the patch version (third part) is
divisible by 2 (for example 3.2.6, 3.4.0), this indicates a version of
SDL that is believed to be stable and suitable for production use.In stable releases, the patchlevel or micro version (third part)
indicates bugfix releases. Bugfix releases may add small changes
to the ABI, so newer patch versions are backwards-compatible but
not fully forwards-compatible. For example, programs built against
SDL 3.2.0 should work fine with SDL 3.2.8, but programs built against
SDL 3.2.8 may not work with 3.2.0.The minor version increases when significant changes are made that
require longer development or testing time, e.g. major new functionality,
or revamping support for a platform. Newer minor versions are
backwards-compatible, but not fully forwards-compatible. For example,
programs built against SDL 3.2.x should work fine with SDL 3.4.x,
but programs built against SDL 3.4.x may not work with 3.2.x.
If the minor version (second part) or patch version (third part) is not
divisible by 2 (for example 3.2.9, 3.3.x), this indicates a development
prerelease of SDL that is not suitable for stable software distributions.
Use with caution.The patchlevel or micro version (third part) increases with each prerelease.
Prereleases are backwards-compatible with older stable branches.
For example, programs built against SDL 3.2.x should work fine with
SDL 3.3.x, but programs built against SDL 3.3.x may not work with 3.2.x.Prereleases are not guaranteed to be backwards-compatible with each other.
For example, new API or ABI added in 3.3.0 might be removed or changed in
3.3.1. If this would be a problem for you, please do not use prereleases.Only use a prerelease if you can guarantee that you will promptly upgrade
to the stable release that follows it. For example, do not use 3.3.x
unless you will be able to upgrade to 3.4.0 when it becomes available.Software distributions that have a freeze policy (in particular Linux
distributions with a release cycle, such as Debian and Fedora)
should only package stable releases, and not prereleases.
README Vita (`docs/README-vita.md`)
PS Vita
SDL port for the Sony Playstation Vita and Sony Playstation TV
Credit to
- xerpi, cpasjuste and rsn8887 for initial (vita2d) port
- vitasdk/dolcesdk devs
- CBPS discord (Namely Graphene and SonicMastr)
Building
To build for the PSVita, make sure you have vitasdk and cmake installed and run:
cmake -S. -Bbuild -DCMAKE_TOOLCHAIN_FILE=${VITASDK}/share/vita.toolchain.cmake -DCMAKE_BUILD_TYPE=Release
cmake --build build
cmake --install buildNotes
- gles1/gles2 support and renderers are disabled by default and can be enabled by configuring with
-DVIDEO_VITA_PVR=ON
These renderers support 720p and 1080i resolutions. These can be specified with:SDL_SetHint(SDL_HINT_VITA_RESOLUTION, "720");andSDL_SetHint(SDL_HINT_VITA_RESOLUTION, "1080"); - Desktop GL 1.X and 2.X support and renderers are also disabled by default and also can be enabled with
-DVIDEO_VITA_PVR=ONas long as gl4es4vita is present in your SDK.
They support the same resolutions as the gles1/gles2 backends and require specifyingSDL_SetHint(SDL_HINT_VITA_PVR_OPENGL, "1");
anytime before video subsystem initialization. - gles2 support via PIB is disabled by default and can be enabled by configuring with
-DVIDEO_VITA_PIB=ON - By default SDL emits mouse events for touch events on every touchscreen.
Vita has two touchscreens, so it's recommended to useSDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");and handle touch events instead.
Individual touchscreens can be disabled with:SDL_SetHint(SDL_HINT_VITA_ENABLE_FRONT_TOUCH, "0");andSDL_SetHint(SDL_HINT_VITA_ENABLE_BACK_TOUCH, "0"); - Support for L2/R2/R3/R3 buttons, haptic feedback and gamepad led only available on PSTV, or when using external ds4 gamepad on vita.
README Wayland (`docs/README-wayland.md`)
Wayland
Wayland is a replacement for the X11 window system protocol and architecture and is favored over X11 by default in SDL3
for communicating with desktop compositors. It works well for the majority of applications, however, applications may
encounter limitations or behavior that is different from other windowing systems.
Common issues:
Legacy, DPI-unaware applications are blurry
- Wayland handles high-DPI displays by scaling the desktop, which causes applications that are not designed to be
DPI-aware to be automatically scaled by the window manager, which results in them being blurry. SDL can attempt to
scale these applications such that they will be output with a 1:1 pixel aspect, however this may be buggy, especially
with odd-sized windows and/or scale factors that aren't quarter-increments (125%, 150%, etc...). To enable this, set
the environment variableSDL_VIDEO_WAYLAND_SCALE_TO_DISPLAY=1
Window decorations are missing, or the decorations look strange
- On some desktops (i.e. GNOME), Wayland applications use a library
called libdecor to provide window decorations. If this library is
not installed, the decorations will be missing. This library uses plugins to generate different decoration styles, and
if a plugin to generate native-looking decorations is not installed (i.e. the GTK plugin), the decorations will not
appear to be 'native'.
Border insets for client-side decorations
- Applications drawing client-side decorations (shadows or invisible resize borders) can declare their insets via the
SDL_PROP_WINDOW_WAYLAND_BORDER_INSET_*window properties when the window is created with theSDL_PROP_WINDOW_CREATE_WAYLAND_ENABLE_INSETS_BOOLEANproperty set totrue. SDL then sets the xdg window geometry
to the surface minus the insets (in points, regardless of pixel density) and translates configure sizes and size
limits accordingly. Maximized and fullscreen windows are always sized exactly as configured, while tiled windows keep
their insets. Changes take effect on the next configure. Only xdg-toplevel windows are affected; popup and custom-role
surfaces ignore the properties, as does libdecor, which manages the window geometry itself (seeSDL_HINT_VIDEO_WAYLAND_ALLOW_LIBDECOR). Note that in certain cases, compositors may ignore the min and max size
hints, as well as the non-resizable state on windows. When this occurs and insets are enabled, SDL can't mask around
the main surface or use scaled viewports to compensate, so clients are responsible for handling this behavior
themselves.
Windows do not appear immediately after creation
- Wayland requires that the application initially present a buffer before the window becomes visible. Additionally,
applications must have an event loop and processes messages on a regular basis, or the application can appear
unresponsive to both the user and desktop compositor.
The display reported as the primary by ```SDL_GetPrimaryDisplay()``` is incorrect
- Wayland doesn't natively have the concept of a primary display, so SDL attempts to determine it by querying various
system settings, and falling back to a selection algorithm if this fails. If it is incorrect, it can be manually
overridden by setting theSDL_VIDEO_DISPLAY_PRIORITYhint.
```SDL_SetWindowPosition()``` doesn't work on non-popup windows
- Wayland does not allow toplevel windows to position themselves programmatically.
How do I save and restore window layout and state between runs?
- To preserve the state of toplevel windows across runs, the
xdg-session-management-v1protocol is required:- Create a session by setting the
SDL_PROP_GLOBAL_VIDEO_WAYLAND_SESSION_ID_STRINGproperty to a non-null value
before creating a window. - Assign the windows you want managed by the session a unique, stable ID string with the
SDL_PROP_WINDOW_CREATE_WAYLAND_WINDOW_ID_STRINGproperty at creation time. This string should be human-readable,
but not translated. - Serialize the session ID property
SDL_PROP_GLOBAL_VIDEO_WAYLAND_SESSION_ID_STRINGbefore shutting down. - On subsequent runs, restore the session ID string property to the previously serialized value before window
creation. This property may change at some arbitrary point during runtime, so serialization should be deferred as
late as possible.
- Create a session by setting the
Retrieving the global mouse cursor position when the cursor is outside a window doesn't work
- Wayland only provides applications with the cursor position within the borders of the application windows. Querying
the global position when an application window does not have mouse focus returns 0,0 as the actual cursor position is
unknown. In most cases, applications don't actually need the global cursor position and should use the window-relative
coordinates as provided by the mouse movement event or fromSDL_GetMouseState()instead.
Warping the mouse cursor to or from a point outside the window doesn't work
- Warping the cursor on Wayland requires that either the
wp_pointer_warp_v1orzwp_pointer_confinement_v1protocol
is supported by the compositor. Compositors typically restrict pointer warps to be within the window that currently
has mouse focus.
Minimize/Restored window events are not sent, and the ```SDL_WINDOW_MINIMIZED``` flag is not set.
- Wayland windows do not currently report the minimized state, aside from when it is activated programmatically via
SDL_MinimizeWindow(). Minimizing a window from the window controls or a desktop shortcut will not send a
minimized event or flag the window as being minimized.
The application icon can't be set via ```SDL_SetWindowIcon()```
- Wayland requires compositor support for the
xdg-toplevel-icon-v1protocol to set window icons programmatically.
Otherwise, the launcher icon from the associated desktop entry file, aka a.desktopfile, will typically be used.
Please see the Desktop Entry Specification for
more information on the format of this file. Note that if your application manually sets the application ID via theSDL_APP_IDhint string, the desktop entry file name should match the application ID. For example, if your
application ID is set toorg.my_org.sdl_app, the desktop entry file should be namedorg.my_org.sdl_app.desktop.
The application progress bar can't be set via ```SDL_SetWindowProgressState()``` or ```SDL_SetWindowProgressValue()```
- Only some Desktop Environments support the underlying API. Known compatible DEs: Unity, KDE
- The underlying API requires a desktop entry file, aka a
.desktopfile.
Please see the Desktop Entry Specification for
more information on the format of this file. Note that if your application manually sets the application ID via theSDL_APP_IDhint string, the desktop entry file name should match the application ID. For example, if your
application ID is set toorg.my_org.sdl_app, the desktop entry file should be namedorg.my_org.sdl_app.desktop.
Keyboard grabs don't work when running under XWayland
- On GNOME based desktops, the dconf setting
org/gnome/mutter/wayland/xwayland-allow-grabsmust be enabled.
Using custom Wayland windowing protocols with SDL windows
Under normal operation, an SDL_Window corresponds to an XDG toplevel window, which provides a standard desktop window.
If an application wishes to use a different windowing protocol with an SDL window (e.g. wlr_layer_shell) while still
having SDL handle input and rendering, it needs to create a custom, roleless surface and attach that surface to its own
toplevel window.
This is done by using SDL_CreateWindowWithProperties() and setting theSDL_PROP_WINDOW_CREATE_WAYLAND_SURFACE_ROLE_CUSTOM_BOOLEAN property to true. Once the window has been
successfully created, the wl_display and wl_surface objects can then be retrieved from theSDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER and SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER properties respectively.
Surfaces don't receive any size change notifications, so if an application changes the window size, it must inform SDL
that the surface size has changed by calling SDL_SetWindowSize() with the new dimensions.
Custom surfaces will automatically handle scaling internally if the window was created with theSDL_PROP_WINDOW_CREATE_HIGH_PIXEL_DENSITY_BOOLEAN property set to true. In this case, applications should
not manually attach viewports or change the surface scale value, as SDL will handle this internally. Calls
to SDL_SetWindowSize() should use the logical size of the window, and SDL_GetWindowSizeInPixels() should be used to
query the size of the backbuffer surface in pixels. If this property is not set or is false, applications can
attach their own viewports or change the surface scale manually, and the SDL backend will not interfere or change any
values internally. In this case, calls to SDL_SetWindowSize() should pass the requested surface size in pixels, not
the logical window size, as no scaling calculations will be done internally.
All window functions that control window state aside from SDL_SetWindowSize() are no-ops with custom surfaces.
Please see the minimal example in tests/testwaylandcustom.c for an example of how to use a custom, roleless surface
and attach it to an application-managed toplevel window.
Importing external surfaces into SDL windows
Wayland windows and surfaces are more intrinsically tied to the client library than other windowing systems, therefore,
when importing surfaces, it is necessary for both SDL and the application or toolkit to use the same wl_display
object. This can be set/queried via the global SDL_PROP_GLOBAL_VIDEO_WAYLAND_WL_DISPLAY_POINTER property. To
import an external wl_display, set this property before initializing the SDL video subsystem, and read the value to
export the internal wl_display after the video subsystem has been initialized. Setting this property after the video
subsystem has been initialized has no effect, and reading it when the video subsystem is uninitialized will either
return the user provided value, if one was set while in the uninitialized state, or NULL.
Once this is done, and the application has created or obtained the wl_surface to be wrapped in an SDL_Window, the
window is created with SDL_CreateWindowWithProperties() with theSDL_PROP_WINDOW_CREATE_WAYLAND_WL_SURFACE_POINTER property to set to the wl_surface object that is to be
imported by SDL.
SDL receives no notification regarding size changes on external surfaces or toplevel windows, so if the external surface
needs to be resized, SDL must be informed by calling SDL_SetWindowSize() with the new dimensions.
If desired, SDL can automatically handle the scaling for the surface by setting theSDL_PROP_WINDOW_CREATE_HIGH_PIXEL_DENSITY_BOOLEAN property to true, however, if the surface being imported
already has, or will have, a viewport/fractional scale manager attached to it by the application or an external toolkit,
a protocol violation will result. Avoid setting this property if importing surfaces from toolkits such as Qt or GTK.
If the window is flagged as high pixel density, calls to SDL_SetWindowSize() should pass the logical size of the
window and SDL_GetWindowSizeInPixels() should be used to retrieve the backbuffer size in pixels. Otherwise, calls toSDL_SetWindowSize() should pass the requested surface size in pixels, not the logical window size, as no scaling
calculations will be done internally.
All window functions that control window state aside from SDL_SetWindowSize() are no-ops with external surfaces.
An example of how to use external surfaces with a wl_display owned by SDL can be seen in tests/testnativewayland.c,
and the following is a minimal example of interoperation with Qt 6, with Qt owning the wl_display:
/* Detailed source-code truncated for AI context efficiency. */README Windows (`docs/README-windows.md`)
Windows
Old systems
WinRT, Windows Phone, and UWP are no longer supported.
All desktop Windows versions, back to Windows XP, are still supported.
LLVM and Intel C++ compiler support
SDL will build with the Visual Studio project files with LLVM-based compilers, such as the Intel oneAPI C++
compiler, but you'll have to manually add the "-msse3" command line option
to at least the SDL_audiocvt.c source file, and possibly others. This may
not be necessary if you build SDL with CMake instead of the included Visual
Studio solution.
Details are here: https://github.com/libsdl-org/SDL/issues/5186
MinGW-w64 compiler support
SDL can be built with MinGW-w64 and CMake. Minimum tested MinGW-w64 version is 8.0.3.
On a Windows host, you first need to install and set up the MSYS2 environment, which provides the MinGW-w64 toolchain. Install MSYS2, typically to C:\msys64, and follow the instructions on the MSYS2 wiki to use the MinGW-w64 shell to update all components in the MSYS2 environment. This generally amounts to running pacman -Syuu from the mingw64 shell, but refer to MSYS2's documentation for more details. Once the MSYS2 environment has been updated, install the x86_64 MinGW toolchain from the mingw64 shell with the command pacman -S mingw-w64-x86_64-toolchain. (You can additionally install mingw-w64-i686-toolchain if you intend to build 32-bit binaries as well. The remainder of this section assumes you only want to build 64-bit binaries.)
To build and install SDL, you can use PowerShell or any CMake-compatible IDE. First, install CMake, Ninja, and Git. These tools can be installed using any number of tools, such as the MSYS2's pacman, winget, Chocolatey, or by manually downloading and running the installers. Clone SDL to an appropriate location with git and run the following commands from the root of the cloned repository:
mkdir build
cmake -S . -B build -G Ninja -DCMAKE_TOOLCHAIN_FILE=build-scripts/cmake-toolchain-mingw64-x86_64.cmake
cmake --build build --parallel
cmake --install build --prefix C:/LibrariesThis installs SDL to C:\Libraries. You can specify another directory of your choice as desired. Ensure that your CMAKE_PREFIX_PATH includes C:\Libraries when you want to build against this copy of SDL. The simplest way to do this is to pass it to CMake as an option at configuration time:
cmake .. -G Ninja -DCMAKE_PREFIX_PATH=C:/LibrariesYou will also need to configure CMake to use the MinGW-w64 toolchain to build your own project. Here is a minimal toolchain file that you could use for this purpose:
set(CMAKE_SYSTEM_NAME Windows)
set(CMAKE_SYSTEM_PROCESSOR x86_64)
find_program(CMAKE_C_COMPILER NAMES x86_64-w64-mingw32-gcc REQUIRED)
find_program(CMAKE_CXX_COMPILER NAMES x86_64-w64-mingw32-g++ REQUIRED)
find_program(CMAKE_RC_COMPILER NAMES x86_64-w64-mingw32-windres windres REQUIRED)Save this in your project and refer to it at configuration time with the option -DCMAKE_TOOLCHAIN_FILE.
On Windows, you also need to copy SDL3.dll to an appropriate directory so that the game can find it at runtime. For guidance, see README-cmake.md.
Below is a minimal CMakeLists.txt file to build your game linked against a system SDL that was built with the MinGW-w64 toolchain. See README-cmake.md for more details on including SDL in your CMake project.
cmake_minimum_required(VERSION 3.15)
project(mygame)
find_package(SDL3 REQUIRED CONFIG COMPONENTS SDL3-shared)
add_executable(mygame WIN32 mygame.c)
target_link_libraries(mygame PRIVATE SDL3::SDL3)
# On Windows, copy SDL3.dll to the build directory
if(WIN32)
add_custom_command(
TARGET mygame POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy $<TARGET_FILE:SDL3::SDL3-shared> $<TARGET_FILE_DIR:mygame>
VERBATIM
)
endif()OpenGL ES 2.x support
SDL has support for OpenGL ES 2.x under Windows via two alternative
implementations.
The most straightforward method consists in running your app in a system with
a graphic card paired with a relatively recent (as of November of 2013) driver
which supports the WGL_EXT_create_context_es2_profile extension. Vendors known
to ship said extension on Windows currently include nVidia and Intel.
The other method involves using the
ANGLE library. If an OpenGL ES 2.x
context is requested and no WGL_EXT_create_context_es2_profile extension is
found, SDL will try to load the libEGL.dll library provided by ANGLE.
To obtain the ANGLE binaries, you can either compile from source from
https://chromium.googlesource.com/angle/angle or copy the relevant binaries
from a recent Chrome/Chromium install for Windows. The files you need are:
- libEGL.dll
- libGLESv2.dll
- d3dcompiler_46.dll (supports Windows Vista or later, better shader
compiler) or d3dcompiler_43.dll (supports Windows XP or later)
If you compile ANGLE from source, you can configure it so it does not need the
d3dcompiler_* DLL at all (for details on this, see their documentation).
However, by default SDL will try to preload the d3dcompiler_46.dll to
comply with ANGLE's requirements. If you wish SDL to preload
d3dcompiler_43.dll (to support Windows XP) or to skip this step at all, you
can use the SDL_HINT_VIDEO_WIN_D3DCOMPILER hint (see SDL_hints.h for more
details).
Known Bugs:
- SDL_GL_SetSwapInterval is currently a no op when using ANGLE. It appears
that there's a bug in the library which prevents the window contents from
refreshing if this is set to anything other than the default value.
Vulkan Surface Support
Support for creating Vulkan surfaces is configured on by default. To disable
it change the value of SDL_VIDEO_VULKAN to 0 in SDL_config_windows.h. You
must install the Vulkan SDK in order to
use Vulkan graphics in your application.
Transparent Window Support
SDL uses the Desktop Window Manager (DWM) to create transparent windows. DWM is
always enabled from Windows 8 and above. Windows 7 only enables DWM with Aero Glass
theme.
However, it cannot be guaranteed to work on all hardware configurations (an example
is hybrid GPU systems, such as NVIDIA Optimus laptops).
README Xr (`docs/README-xr.md`)
OpenXR / VR Development with SDL
This document covers how to build OpenXR (VR/AR) applications using SDL's GPU API with OpenXR integration.
Overview
SDL3 provides OpenXR integration through the GPU API, allowing you to render to VR/AR headsets using a unified interface across multiple graphics backends (Vulkan, D3D12, Metal).
Key features:
- Automatic OpenXR instance and session management
- Swapchain creation and image acquisition
- Support for multi-pass stereo rendering
- Works with desktop VR runtimes (SteamVR, Oculus, Windows Mixed Reality) and standalone headsets (Meta Quest, Pico)
Desktop Development
Requirements
OpenXR Loader (
openxr_loader.dll/libopenxr_loader.so)- On Windows: Usually installed with VR runtime software (Oculus, SteamVR)
- On Linux: Install via package manager (e.g.,
libopenxr-loader1on Ubuntu) - Can also use
SDL_HINT_OPENXR_LIBRARYto specify a custom loader path
OpenXR Runtime
- At least one OpenXR runtime must be installed and active
- Examples: SteamVR, Oculus Desktop, Monado (Linux)
VR Headset
- Connected and recognized by the runtime
Basic Usage
#include <openxr/openxr.h>
#include <SDL3/SDL.h>
#include <SDL3/SDL_openxr.h>
// These will be populated by SDL
XrInstance xr_instance = XR_NULL_HANDLE;
XrSystemId xr_system_id = 0;
// Create GPU device with XR enabled
SDL_PropertiesID props = SDL_CreateProperties();
SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_XR_ENABLE_BOOLEAN, true);
SDL_SetPointerProperty(props, SDL_PROP_GPU_DEVICE_CREATE_XR_INSTANCE_POINTER, &xr_instance);
SDL_SetPointerProperty(props, SDL_PROP_GPU_DEVICE_CREATE_XR_SYSTEM_ID_POINTER, &xr_system_id);
// Optional: Override app name/version (defaults to SDL_SetAppMetadata values if not set)
SDL_SetStringProperty(props, SDL_PROP_GPU_DEVICE_CREATE_XR_APPLICATION_NAME_STRING, "My VR App");
SDL_SetNumberProperty(props, SDL_PROP_GPU_DEVICE_CREATE_XR_APPLICATION_VERSION_NUMBER, 1);
SDL_GPUDevice *device = SDL_CreateGPUDeviceWithProperties(props);
SDL_DestroyProperties(props);
// xr_instance and xr_system_id are now populated by SDLSee test/testgpu_spinning_cube_xr.c for a complete example.
Android Development
Building OpenXR applications for Android standalone headsets (Meta Quest, Pico, etc.) requires additional manifest configuration beyond standard Android apps.
Android Manifest Requirements
The manifest requirements fall into three categories:
- OpenXR Standard (Khronos) - Required for all OpenXR apps
- Platform-Specific - Required for specific headset platforms
- Optional Features - Enable additional capabilities
OpenXR Standard Requirements (All Platforms)
These are required by the Khronos OpenXR specification for Android:
Permissions
<uses-permission android:name="org.khronos.openxr.permission.OPENXR" />
<uses-permission android:name="org.khronos.openxr.permission.OPENXR_SYSTEM" />Queries (Android 11+)
Required for the app to discover OpenXR runtimes:
<queries>
<provider android:authorities="org.khronos.openxr.runtime_broker;org.khronos.openxr.system_runtime_broker" />
<intent>
<action android:name="org.khronos.openxr.OpenXRRuntimeService" />
</intent>
<intent>
<action android:name="org.khronos.openxr.OpenXRApiLayerService" />
</intent>
</queries>Hardware Features
<uses-feature android:name="android.hardware.vr.headtracking"
android:required="true"
android:version="1" />
<uses-feature android:name="android.hardware.touchscreen"
android:required="false" />
<uses-feature android:glEsVersion="0x00030002" android:required="true" />
<uses-feature android:name="android.hardware.vulkan.level"
android:required="true"
android:version="1" />
<uses-feature android:name="android.hardware.vulkan.version"
android:required="true"
android:version="0x00401000" />Intent Category
<activity ...>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="org.khronos.openxr.intent.category.IMMERSIVE_HMD" />
</intent-filter>
</activity>Meta Quest Requirements
These are required for apps to run properly on Meta Quest devices. Without these, your app may launch in "pancake" 2D mode instead of VR.
VR Intent Category (Critical!)
<activity ...>
<intent-filter>
...
<category android:name="com.oculus.intent.category.VR" />
</intent-filter>
</activity>Supported Devices
<application ...>
<meta-data android:name="com.oculus.supportedDevices"
android:value="quest|quest2|questpro|quest3|quest3s" />
</application>Focus Handling (Recommended)
<application ...>
<meta-data android:name="com.oculus.vr.focusaware"
android:value="true" />
</application>Hand Tracking (Optional)
<uses-feature android:name="oculus.software.handtracking"
android:required="false" />
<application ...>
<meta-data android:name="com.oculus.handtracking.version"
android:value="V2.0" />
<meta-data android:name="com.oculus.handtracking.frequency"
android:value="HIGH" />
</application>VR Splash Screen (Optional)
<application ...>
<meta-data android:name="com.oculus.ossplash"
android:value="true" />
<meta-data android:name="com.oculus.ossplash.colorspace"
android:value="QUEST_SRGB_NONGAMMA" />
<meta-data android:name="com.oculus.ossplash.background"
android:resource="@drawable/vr_splash" />
</application>Pico Requirements
For Pico Neo, Pico 4, and other Pico headsets:
VR Intent Category
<activity ...>
<intent-filter>
...
<category android:name="com.picovr.intent.category.VR" />
</intent-filter>
</activity>Supported Devices (Optional)
<application ...>
<meta-data android:name="pvr.app.type"
android:value="vr" />
</application>HTC Vive Focus / VIVE XR Elite
<activity ...>
<intent-filter>
...
<category android:name="com.htc.intent.category.VRAPP" />
</intent-filter>
</activity>Quick Reference Table
| Declaration | Purpose | Scope |
|---|---|---|
org.khronos.openxr.permission.OPENXR |
Runtime communication | All OpenXR |
android.hardware.vr.headtracking |
Marks app as VR | All OpenXR |
org.khronos.openxr.intent.category.IMMERSIVE_HMD |
Khronos standard VR category | All OpenXR |
com.oculus.intent.category.VR |
Launch in VR mode | Meta Quest |
com.oculus.supportedDevices |
Device compatibility | Meta Quest |
com.oculus.vr.focusaware |
System menu handling | Meta Quest |
com.picovr.intent.category.VR |
Launch in VR mode | Pico |
com.htc.intent.category.VRAPP |
Launch in VR mode | HTC Vive |
Example Manifest
SDL provides an example XR manifest template at:test/android/cmake/AndroidManifest.xr.xml.cmake
This template includes:
- All Khronos OpenXR requirements
- Meta Quest support (configurable via
SDL_ANDROID_XR_META_SUPPORTCMake option) - Proper intent filters for VR launching
Common Issues
App launches in 2D "pancake" mode
Cause: Missing platform-specific VR intent category.
Solution: Add the appropriate category for your target platform:
- Meta Quest:
com.oculus.intent.category.VR - Pico:
com.picovr.intent.category.VR - HTC:
com.htc.intent.category.VRAPP
"No OpenXR runtime found" error
Cause: The OpenXR loader can't find a runtime.
Solutions:
- Desktop: Ensure VR software (SteamVR, Oculus) is installed and running
- Android: Ensure your manifest has the correct
<queries>block for runtime discovery - Linux: Install
libopenxr-loader1and configure the active runtime
OpenXR loader not found
Cause: openxr_loader.dll / libopenxr_loader.so is not in the library path.
Solutions:
- Install the Khronos OpenXR SDK
- On Windows, VR runtimes typically install this, but may not add it to PATH
- Use
SDL_HINT_OPENXR_LIBRARYto specify the loader path explicitly
Vulkan validation errors on shutdown
Cause: GPU resources destroyed while still in use.
Solution: Call SDL_WaitForGPUIdle(device) before releasing any GPU resources or destroying the device.
Additional Resources
- Khronos OpenXR Specification
- Meta Quest Developer Documentation
- Pico Developer Documentation
- SDL GPU API Documentation
- Example code:
test/testgpu_spinning_cube_xr.c
Release Checklist (`docs/release_checklist.md`)
Release checklist
Run
build-scripts/create-release.py -R libsdl-org/SDL --refto do
a dry run creating the release assets. Verify that the archives are correct.Tag the release, e.g.
git tag release-3.8.0; git push --tagsRun
build-scripts/create-release.py -R libsdl-org/SDL --ref <release-tag>
to have GitHub Actions create release assets. This makes sure the revision
string baked into the archives is correct.Verify that the source archive REVISION.txt has the correct release tag.
Sign the source archives and upload everything to libsdl.org
Create a GitHub release and attach the archives you just generated.
If this is a feature release, also tag the sdlwiki with the same tag.
New feature release
Update
WhatsNew.txtBump version number to 3.EVEN.0:
./build-scripts/update-version.sh 3 EVEN 0
Do the release
Immediately create a branch for patch releases, e.g.
git branch release-3.EVEN.xBump version number from 3.EVEN.0 to 3.(EVEN+1).0
./build-scripts/update-version.sh 3 EVEN+1 0
Update the website file include/header.inc.php to reflect the new version
New bugfix release
Bump version number from 3.Y.Z to 3.Y.(Z+1) (Y is even)
./build-scripts/update-version.sh 3 Y Z+1
Do the release
Update the website file include/header.inc.php to reflect the new version
New development prerelease
Bump version number from 3.Y.Z to 3.Y.(Z+1) (Y is odd)
./build-scripts/update-version.sh 3 Y Z+1
Do the release
--- METRICS ---
- Files Extracted: 53
- Estimated Token Budget: ~98073 tokens
- Canonical Reference: https://codewiki.google/github.com/libsdl-org/SDL