
Cross-platform SDL3 bindings offering a curated common API, embedding static SDL3 per target, automatic JNI extraction, Emscripten wasm assets, and graphics/audio examples.
Kotlin Multiplatform bindings for SDL3, with a curated common API backed by three implementations:
SDL submodule into a JNI shared library (libsdl_jni) that is built by CMake (jni/) and shipped as per-OS/arch sdl-kmp-jni-jvm-* artifacts. NativeLoader extracts the matching binary at runtime, so consumers need nothing beyond the normal dependencies (no LWJGL, no system SDL).SDL submodule is compiled per target with CMake and embedded into the published klib, so consumers get a fully self-contained binary (no dynamic SDL3 dependency). This includes the Android native targets (androidNative*), cross-compiled with the Android NDK.sdl-kmp-android-jvm) packages SDL's own android-project Java layer (org.libsdl.app.SDLActivity and friends) together with the per-ABI libsdl_jni.so (SDL3 + JNI bridge, built with the NDK from the same jni/ sources). Consumers just extend SDLActivity — no SDL Java code to copy.All three implementations build SDL3 from the pinned SDL submodule; the fixes that must not live in the submodule history are kept under patches/ and applied idempotently at build time (applySubmodulePatches) before any task that compiles the SDL sources.
| Platform | Targets | Implementation |
|---|---|---|
| JVM |
jvm (Linux/macOS/Windows) |
JNI shared library (libsdl_jni), SDL3 compiled from source |
| macOS |
macosArm64, macosX64
|
cinterop + embedded static SDL3 |
| Linux |
linuxX64, linuxArm64
|
cinterop + embedded static SDL3 |
| Windows | mingwX64 |
cinterop + embedded static SDL3 |
| iOS |
iosArm64, iosX64, iosSimulatorArm64
|
cinterop + embedded static SDL3 |
| tvOS |
tvosArm64, tvosSimulatorArm64
|
cinterop + embedded static SDL3 |
| Android (native) |
androidNativeArm64, androidNativeArm32, androidNativeX64, androidNativeX86
|
cinterop + embedded static SDL3 (built with the NDK) |
| Android (JVM) |
sdl-kmp-android-jvm AAR (arm64-v8a, armeabi-v7a, x86_64, x86) |
SDL android-project Java layer + per-ABI libsdl_jni.so
|
| Web (browser) | wasmJs |
SDL3 compiled to a standalone Emscripten module, driven through a JS bridge |
Not supported: watchOS (SDL3 has no watchOS support) and visionOS (Kotlin/Native has no visionOS targets yet).
build.gradle.kts:
kotlin {
sourceSets {
commonMain.dependencies {
implementation("cn.enaium.sdl:sdl-kmp:1.0.10")
}
}
}import cn.enaium.sdl.*
fun main() {
SDL.setMainReady()
if (!SDL.init(SDLInitFlags.VIDEO)) {
error("SDL_Init failed: ${SDL.error()}")
}
SDL.createWindow("hello sdl-kmp", 800, 600).use { window ->
SDL.createRenderer(window).use { renderer ->
var running = true
while (running) {
while (true) {
val event = SDL.pollEvent() ?: break
when (event) {
is SDLEvent.Quit -> running = false
is SDLEvent.Key ->
if (event.down && event.keycode == SDLKeycode.ESCAPE) running = false
else -> Unit
}
}
renderer.drawColor = SDLColor(18, 18, 24)
renderer.clear()
renderer.drawColor = SDLColor(255, 0, 128)
renderer.fillRect(SDLRect(100, 100, 200, 200))
renderer.present()
SDL.delay(16)
}
}
}
SDL.quit()
}SDL.setMainReady() before SDL.init on the main thread. It is only
required on Apple platforms (macOS/iOS/tvOS); on Linux/Windows it is a harmless
no-op that records the calling thread as the main thread and never blocks.linuxArm64 target is built on Linux aarch64 hosts or cross-compiled from x86_64 with the aarch64-linux-gnu toolchain (gcc-aarch64-linux-gnu g++-aarch64-linux-gnu); SDL3's dlopen-based drivers only need the arch-agnostic headers, so no multiarch sysroot is required.DISPLAY, SDL.init(SDL_INIT_VIDEO)
can block while XOpenDisplay tries to connect. Set SDL_VIDEO_DRIVER=dummy (hint or
environment variable) before init, or export DISPLAY correctly.IrLinkageError
("No function found for symbol ...") at the first SDL call. Keep the consumer's
Kotlin version in sync.SDL_VIDEO_DRIVER=dummy hint (environment variable or SDL.setHint) makes SDL run headless — useful for CI and servers.-XstartOnFirstThread JVM argument (so AppKit/Cocoa can initialise). The example runJvm tasks already set this.sdl-kmp-jni-jvm-{os}-{arch} artifact is a transitive runtime dependency of sdl-kmp; NativeLoader extracts the bundled libsdl_jni from the classpath and System.load()s it, so no java.library.path setup is needed.dlopen), so the published klib has no link-time dependency on X11.androidNative* target requires an installed Android NDK (found under $ANDROID_HOME/ndk); the SDL3 static library is cross-compiled with its CMake toolchain. At runtime the app must be launched through org.libsdl.app.SDLActivity (or a subclass), which loads the shared library and calls its exported SDL_main (see the examples/sdl_renderer/android module).sdl-kmp-android-jvm instead of copying SDLActivity from the SDL android-project — the AAR bundles the org.libsdl.app classes (matching the statically linked SDL3 version) and libsdl_jni.so for all four NDK ABIs. Write an Activity extending org.libsdl.app.SDLActivity and return "sdl_jni" from getLibraries(); the Kotlin cn.enaium.sdl.SDL binding is not part of the AAR (it can be added via the sdl-kmp JVM artifact).The SDL3 static library is embedded in each target's published klib (built per target by the sdl-kmp/native/CMakeLists.txt wrapper). The required frameworks/system libraries are recorded in the cinterop klib as linkerOpts (see sdl.def) and are applied automatically when the consumer's final binary is linked.
All examples live under examples/ as standalone KMP modules; each provides
commonMain logic and thin platform entry points (main() / SDL_main).
examples/sdl_renderer — "bouncing box" demo using SDL_Renderer
(renderer, textures, audio, input). Runs on JVM, macOS, Linux, Windows
(MinGW) and Android (with its android submodule APK).examples/sdl_vulkan — minimal Vulkan triangle (gradient shaders) on
JVM, macOS, Linux and Windows. On the JVM the renderer uses the LWJGL
Vulkan bindings (the example's own dependency - the sdl-kmp library itself
does not use LWJGL), wired to SDL's SDL_Vulkan_GetVkGetInstanceProcAddr;
on native targets a small C helper builds the pipeline.examples/sdl_opengl — minimal OpenGL 3.3 core / GLES 3 triangle on
JVM, macOS, Linux and Windows.examples/sdl_opengl_es — minimal OpenGL ES 3.0 gradient triangle
(the browser-capable GL profile: WebGL2 on wasm). Runs on JVM (GL calls go
through the LWJGL OpenGL bindings, an example-only dependency), macOS,
Linux, Windows and Android, with browser (wasmJs) and android
submodules.examples/sdl_gpu — triangle rendered through the SDL3 GPU API
(cross-backend: Metal on macOS, Vulkan on Android) entirely from
commonMain. Runs on JVM, macOS, Linux, Windows and Android (with its
android submodule APK).examples/sdl_renderer/browser — browser (wasmJs) runner for the
sdl_renderer demo. SDL3 is compiled to a standalone Emscripten module
(:sdl-kmp:linkWasmSdl) and loaded before the Kotlin/Wasm module runs.Kotlin/Wasm cannot embed C libraries (and does not merge library resources
into the web output), so for the wasmJs target SDL3 is compiled with
Emscripten into a standalone module (sdl_wasm.js + sdl_wasm.wasm)
exposing the whole sdl-kmp API as flat functions. A JS glue layer
(sdl-kmp/wasm/sdl_kmp_glue.js) instantiates that module and bridges it
to the Kotlin wasmJs actuals. Building it requires the Emscripten SDK (see
gradle.properties/the wasm.emsdk property; the CI installs it).
The module is published separately as cn.enaium.sdl:sdl-kmp-wasm-assets
(a jar with sdl_wasm.js, sdl_wasm.wasm and sdl_kmp_glue.js at its root,
built by :sdl-kmp:wasm:jar from :sdl-kmp:linkWasmSdl). A wasmJs
consumer unpacks that jar into the web root next to the Kotlin/Wasm output
and loads the glue before running the Kotlin module:
A wasmJs consumer must load the SDL module before running the Kotlin module:
<script type="module">
import { initSdlKmp } from './sdl_kmp_glue.js';
await initSdlKmp(); // instantiate SDL3
await import('./index.mjs'); // Kotlin module; main() auto-runs
</script>See examples/sdl_renderer/browser for a complete runnable page (its
browser-node-test.mjs runs the demo headlessly in Node with the dummy
drivers).
# Publish the library to the local Maven repository first (macOS builds all
# Apple targets + JVM + the darwin JNI artifacts; Linux builds the
# linuxX64/mingwX64 klibs and the linux-x86_64 JNI artifact; Windows (or
# Linux with the MinGW x86_64-w64-mingw32 toolchain) builds the
# windows-x86_64 JNI artifact).
./gradlew :sdl-kmp:publishToMavenLocal
./gradlew :jni-jvm-darwin-aarch64:publishToMavenLocal :jni-jvm-darwin-x86_64:publishToMavenLocal # macOS
./gradlew :jni-jvm-linux-x86_64:publishToMavenLocal # Linux
./gradlew :jni-jvm-linux-aarch64:publishToMavenLocal # Linux (aarch64 host or cross)
./gradlew :jni-jvm-windows-x86_64:publishToMavenLocal # Windows (MinGW host)
./gradlew :android-jvm:publishToMavenLocal # Android JVM AAR (SDK + NDK)
# JVM (pass SDL_VIDEO_DRIVER=dummy for headless mode)
./gradlew :examples:sdl_renderer:jvmRun
SDL_VIDEO_DRIVER=dummy ./gradlew :examples:sdl_renderer:jvmRun
# Native
./gradlew :examples:sdl_renderer:runDebugExecutableMacosArm64
SDL_VIDEO_DRIVER=dummy ./gradlew :examples:sdl_renderer:runDebugExecutableLinuxX64
# GPU examples (macOS: needs a display; Vulkan needs a Vulkan driver)
./gradlew :examples:sdl_vulkan:jvmRun
./gradlew :examples:sdl_opengl:jvmRun
./gradlew :examples:sdl_opengl_es:jvmRun
./gradlew :examples:sdl_gpu:jvmRun
./gradlew :examples:sdl_vulkan:runDebugExecutableLinuxX64
./gradlew :examples:sdl_opengl:runDebugExecutableLinuxX64
./gradlew :examples:sdl_opengl_es:runDebugExecutableLinuxX64
./gradlew :examples:sdl_gpu:runDebugExecutableMacosArm64The sdl_renderer and sdl_gpu examples each have an android submodule:
an Android application (AGP) that runs the same demo. The KMP module builds
libmain.so for every androidNative ABI (exporting SDL_main from
androidMain); the Android app copies those into its jniLibs and its
MainActivity extends org.libsdl.app.SDLActivity (loaded from the SDL
submodule so it matches the statically linked SDL3 version), which loads
libmain.so and calls SDL_main.
# Build the APKs (requires an Android NDK; install the app on a device/emulator
# with adb).
./gradlew :examples:sdl_renderer:android:assembleDebug
./gradlew :examples:sdl_gpu:android:assembleDebug
./gradlew :examples:sdl_opengl_es:android:assembleDebug
adb install -r examples/sdl_renderer/android/build/outputs/apk/debug/android-debug.apkThe SDL submodule stays pinned to an upstream commit; fixes live under patches/SDL.patch and are applied by the root applySubmodulePatches task before any task that configures or compiles the SDL sources (native static libs, per-OS JNI, Android JNI, wasm). The apply is idempotent (git apply --reverse --check skips when already applied), so both CI and local builds work from a clean checkout. To update a fix, edit the SDL working tree and regenerate the patch:
git -C SDL diff > patches/SDL.patch
git -C SDL checkout -- .Current fixes: X11 remote-injected clicks (drop the keyboard-focus requirement for slave pointer buttons) and Cocoa GCMouse/NSEvent duplicate handling (always deliver NSEvent button events; GCMouse is used for raw motion only, so synthetic clicks from remote-control / accessibility tools are no longer dropped).
# Unit + integration tests on the host platform
./gradlew :sdl-kmp:jvmTest :sdl-kmp:macosArm64Test # macOS
./gradlew :sdl-kmp:jvmTest :sdl-kmp:linuxX64Test # Linux
# Build the Android JVM library (requires the Android SDK + NDK)
./gradlew :android-jvm:assembleDebug
./gradlew :android-jvm:publishToMavenLocalBuilding the Linux native SDL3 library (any linuxX64 task) requires the
Wayland, X11 and audio development packages: on Debian/Ubuntu that is
libwayland-dev libwayland-bin libxkbcommon-dev libegl-dev libdecor-0-dev
plus the X11 libx* dev packages and
libpipewire-0.3-dev libpulse-dev libasound2-dev (these also gate the
PipeWire/Pulse/ALSA audio drivers at build time — without them the published
klib falls back to X11 and has no audio drivers). The GitHub Actions workflows
install these automatically.
.github/workflows/test.yml — manual trigger: macOS builds all Apple klibs and the darwin JNI artifacts and runs JVM + native tests; Linux runs linuxX64Test, cross-compiles linuxArm64/mingwX64, builds the linux-* JNI artifacts, and runs the renderer example headless; Windows builds the windows-x86_64 JNI artifact natively (MinGW) and runs JVM tests; Android installs the NDK, builds the four androidNative klibs and assembles the sdl_renderer/sdl_gpu APKs; Web installs the Emscripten SDK, builds the wasmJs klib and the browser example..github/workflows/publish.yml — manual workflow that publishes the metadata + JVM + Apple klibs and the sdl-kmp-jni-jvm-darwin-* artifacts from macos-14, the linuxX64/linuxArm64/mingwX64 klibs and the sdl-kmp-jni-jvm-linux-* artifacts from ubuntu-latest, sdl-kmp-jni-jvm-windows-x86_64 from windows-latest (native MinGW build), and the four androidNative klibs from ubuntu-latest (with the NDK) to Maven Central.Required secrets: MAVEN_CENTRAL_USERNAME, MAVEN_CENTRAL_PASSWORD, SIGNING_KEY (base64 GPG keyring), SIGNING_KEY_ID, SIGNING_PASSWORD.
MIT. The bundled SDL3 submodule is licensed under the zlib license.
Kotlin Multiplatform bindings for SDL3, with a curated common API backed by three implementations:
SDL submodule into a JNI shared library (libsdl_jni) that is built by CMake (jni/) and shipped as per-OS/arch sdl-kmp-jni-jvm-* artifacts. NativeLoader extracts the matching binary at runtime, so consumers need nothing beyond the normal dependencies (no LWJGL, no system SDL).SDL submodule is compiled per target with CMake and embedded into the published klib, so consumers get a fully self-contained binary (no dynamic SDL3 dependency). This includes the Android native targets (androidNative*), cross-compiled with the Android NDK.sdl-kmp-android-jvm) packages SDL's own android-project Java layer (org.libsdl.app.SDLActivity and friends) together with the per-ABI libsdl_jni.so (SDL3 + JNI bridge, built with the NDK from the same jni/ sources). Consumers just extend SDLActivity — no SDL Java code to copy.All three implementations build SDL3 from the pinned SDL submodule; the fixes that must not live in the submodule history are kept under patches/ and applied idempotently at build time (applySubmodulePatches) before any task that compiles the SDL sources.
| Platform | Targets | Implementation |
|---|---|---|
| JVM |
jvm (Linux/macOS/Windows) |
JNI shared library (libsdl_jni), SDL3 compiled from source |
| macOS |
macosArm64, macosX64
|
cinterop + embedded static SDL3 |
| Linux |
linuxX64, linuxArm64
|
cinterop + embedded static SDL3 |
| Windows | mingwX64 |
cinterop + embedded static SDL3 |
| iOS |
iosArm64, iosX64, iosSimulatorArm64
|
cinterop + embedded static SDL3 |
| tvOS |
tvosArm64, tvosSimulatorArm64
|
cinterop + embedded static SDL3 |
| Android (native) |
androidNativeArm64, androidNativeArm32, androidNativeX64, androidNativeX86
|
cinterop + embedded static SDL3 (built with the NDK) |
| Android (JVM) |
sdl-kmp-android-jvm AAR (arm64-v8a, armeabi-v7a, x86_64, x86) |
SDL android-project Java layer + per-ABI libsdl_jni.so
|
| Web (browser) | wasmJs |
SDL3 compiled to a standalone Emscripten module, driven through a JS bridge |
Not supported: watchOS (SDL3 has no watchOS support) and visionOS (Kotlin/Native has no visionOS targets yet).
build.gradle.kts:
kotlin {
sourceSets {
commonMain.dependencies {
implementation("cn.enaium.sdl:sdl-kmp:1.0.10")
}
}
}import cn.enaium.sdl.*
fun main() {
SDL.setMainReady()
if (!SDL.init(SDLInitFlags.VIDEO)) {
error("SDL_Init failed: ${SDL.error()}")
}
SDL.createWindow("hello sdl-kmp", 800, 600).use { window ->
SDL.createRenderer(window).use { renderer ->
var running = true
while (running) {
while (true) {
val event = SDL.pollEvent() ?: break
when (event) {
is SDLEvent.Quit -> running = false
is SDLEvent.Key ->
if (event.down && event.keycode == SDLKeycode.ESCAPE) running = false
else -> Unit
}
}
renderer.drawColor = SDLColor(18, 18, 24)
renderer.clear()
renderer.drawColor = SDLColor(255, 0, 128)
renderer.fillRect(SDLRect(100, 100, 200, 200))
renderer.present()
SDL.delay(16)
}
}
}
SDL.quit()
}SDL.setMainReady() before SDL.init on the main thread. It is only
required on Apple platforms (macOS/iOS/tvOS); on Linux/Windows it is a harmless
no-op that records the calling thread as the main thread and never blocks.linuxArm64 target is built on Linux aarch64 hosts or cross-compiled from x86_64 with the aarch64-linux-gnu toolchain (gcc-aarch64-linux-gnu g++-aarch64-linux-gnu); SDL3's dlopen-based drivers only need the arch-agnostic headers, so no multiarch sysroot is required.DISPLAY, SDL.init(SDL_INIT_VIDEO)
can block while XOpenDisplay tries to connect. Set SDL_VIDEO_DRIVER=dummy (hint or
environment variable) before init, or export DISPLAY correctly.IrLinkageError
("No function found for symbol ...") at the first SDL call. Keep the consumer's
Kotlin version in sync.SDL_VIDEO_DRIVER=dummy hint (environment variable or SDL.setHint) makes SDL run headless — useful for CI and servers.-XstartOnFirstThread JVM argument (so AppKit/Cocoa can initialise). The example runJvm tasks already set this.sdl-kmp-jni-jvm-{os}-{arch} artifact is a transitive runtime dependency of sdl-kmp; NativeLoader extracts the bundled libsdl_jni from the classpath and System.load()s it, so no java.library.path setup is needed.dlopen), so the published klib has no link-time dependency on X11.androidNative* target requires an installed Android NDK (found under $ANDROID_HOME/ndk); the SDL3 static library is cross-compiled with its CMake toolchain. At runtime the app must be launched through org.libsdl.app.SDLActivity (or a subclass), which loads the shared library and calls its exported SDL_main (see the examples/sdl_renderer/android module).sdl-kmp-android-jvm instead of copying SDLActivity from the SDL android-project — the AAR bundles the org.libsdl.app classes (matching the statically linked SDL3 version) and libsdl_jni.so for all four NDK ABIs. Write an Activity extending org.libsdl.app.SDLActivity and return "sdl_jni" from getLibraries(); the Kotlin cn.enaium.sdl.SDL binding is not part of the AAR (it can be added via the sdl-kmp JVM artifact).The SDL3 static library is embedded in each target's published klib (built per target by the sdl-kmp/native/CMakeLists.txt wrapper). The required frameworks/system libraries are recorded in the cinterop klib as linkerOpts (see sdl.def) and are applied automatically when the consumer's final binary is linked.
All examples live under examples/ as standalone KMP modules; each provides
commonMain logic and thin platform entry points (main() / SDL_main).
examples/sdl_renderer — "bouncing box" demo using SDL_Renderer
(renderer, textures, audio, input). Runs on JVM, macOS, Linux, Windows
(MinGW) and Android (with its android submodule APK).examples/sdl_vulkan — minimal Vulkan triangle (gradient shaders) on
JVM, macOS, Linux and Windows. On the JVM the renderer uses the LWJGL
Vulkan bindings (the example's own dependency - the sdl-kmp library itself
does not use LWJGL), wired to SDL's SDL_Vulkan_GetVkGetInstanceProcAddr;
on native targets a small C helper builds the pipeline.examples/sdl_opengl — minimal OpenGL 3.3 core / GLES 3 triangle on
JVM, macOS, Linux and Windows.examples/sdl_opengl_es — minimal OpenGL ES 3.0 gradient triangle
(the browser-capable GL profile: WebGL2 on wasm). Runs on JVM (GL calls go
through the LWJGL OpenGL bindings, an example-only dependency), macOS,
Linux, Windows and Android, with browser (wasmJs) and android
submodules.examples/sdl_gpu — triangle rendered through the SDL3 GPU API
(cross-backend: Metal on macOS, Vulkan on Android) entirely from
commonMain. Runs on JVM, macOS, Linux, Windows and Android (with its
android submodule APK).examples/sdl_renderer/browser — browser (wasmJs) runner for the
sdl_renderer demo. SDL3 is compiled to a standalone Emscripten module
(:sdl-kmp:linkWasmSdl) and loaded before the Kotlin/Wasm module runs.Kotlin/Wasm cannot embed C libraries (and does not merge library resources
into the web output), so for the wasmJs target SDL3 is compiled with
Emscripten into a standalone module (sdl_wasm.js + sdl_wasm.wasm)
exposing the whole sdl-kmp API as flat functions. A JS glue layer
(sdl-kmp/wasm/sdl_kmp_glue.js) instantiates that module and bridges it
to the Kotlin wasmJs actuals. Building it requires the Emscripten SDK (see
gradle.properties/the wasm.emsdk property; the CI installs it).
The module is published separately as cn.enaium.sdl:sdl-kmp-wasm-assets
(a jar with sdl_wasm.js, sdl_wasm.wasm and sdl_kmp_glue.js at its root,
built by :sdl-kmp:wasm:jar from :sdl-kmp:linkWasmSdl). A wasmJs
consumer unpacks that jar into the web root next to the Kotlin/Wasm output
and loads the glue before running the Kotlin module:
A wasmJs consumer must load the SDL module before running the Kotlin module:
<script type="module">
import { initSdlKmp } from './sdl_kmp_glue.js';
await initSdlKmp(); // instantiate SDL3
await import('./index.mjs'); // Kotlin module; main() auto-runs
</script>See examples/sdl_renderer/browser for a complete runnable page (its
browser-node-test.mjs runs the demo headlessly in Node with the dummy
drivers).
# Publish the library to the local Maven repository first (macOS builds all
# Apple targets + JVM + the darwin JNI artifacts; Linux builds the
# linuxX64/mingwX64 klibs and the linux-x86_64 JNI artifact; Windows (or
# Linux with the MinGW x86_64-w64-mingw32 toolchain) builds the
# windows-x86_64 JNI artifact).
./gradlew :sdl-kmp:publishToMavenLocal
./gradlew :jni-jvm-darwin-aarch64:publishToMavenLocal :jni-jvm-darwin-x86_64:publishToMavenLocal # macOS
./gradlew :jni-jvm-linux-x86_64:publishToMavenLocal # Linux
./gradlew :jni-jvm-linux-aarch64:publishToMavenLocal # Linux (aarch64 host or cross)
./gradlew :jni-jvm-windows-x86_64:publishToMavenLocal # Windows (MinGW host)
./gradlew :android-jvm:publishToMavenLocal # Android JVM AAR (SDK + NDK)
# JVM (pass SDL_VIDEO_DRIVER=dummy for headless mode)
./gradlew :examples:sdl_renderer:jvmRun
SDL_VIDEO_DRIVER=dummy ./gradlew :examples:sdl_renderer:jvmRun
# Native
./gradlew :examples:sdl_renderer:runDebugExecutableMacosArm64
SDL_VIDEO_DRIVER=dummy ./gradlew :examples:sdl_renderer:runDebugExecutableLinuxX64
# GPU examples (macOS: needs a display; Vulkan needs a Vulkan driver)
./gradlew :examples:sdl_vulkan:jvmRun
./gradlew :examples:sdl_opengl:jvmRun
./gradlew :examples:sdl_opengl_es:jvmRun
./gradlew :examples:sdl_gpu:jvmRun
./gradlew :examples:sdl_vulkan:runDebugExecutableLinuxX64
./gradlew :examples:sdl_opengl:runDebugExecutableLinuxX64
./gradlew :examples:sdl_opengl_es:runDebugExecutableLinuxX64
./gradlew :examples:sdl_gpu:runDebugExecutableMacosArm64The sdl_renderer and sdl_gpu examples each have an android submodule:
an Android application (AGP) that runs the same demo. The KMP module builds
libmain.so for every androidNative ABI (exporting SDL_main from
androidMain); the Android app copies those into its jniLibs and its
MainActivity extends org.libsdl.app.SDLActivity (loaded from the SDL
submodule so it matches the statically linked SDL3 version), which loads
libmain.so and calls SDL_main.
# Build the APKs (requires an Android NDK; install the app on a device/emulator
# with adb).
./gradlew :examples:sdl_renderer:android:assembleDebug
./gradlew :examples:sdl_gpu:android:assembleDebug
./gradlew :examples:sdl_opengl_es:android:assembleDebug
adb install -r examples/sdl_renderer/android/build/outputs/apk/debug/android-debug.apkThe SDL submodule stays pinned to an upstream commit; fixes live under patches/SDL.patch and are applied by the root applySubmodulePatches task before any task that configures or compiles the SDL sources (native static libs, per-OS JNI, Android JNI, wasm). The apply is idempotent (git apply --reverse --check skips when already applied), so both CI and local builds work from a clean checkout. To update a fix, edit the SDL working tree and regenerate the patch:
git -C SDL diff > patches/SDL.patch
git -C SDL checkout -- .Current fixes: X11 remote-injected clicks (drop the keyboard-focus requirement for slave pointer buttons) and Cocoa GCMouse/NSEvent duplicate handling (always deliver NSEvent button events; GCMouse is used for raw motion only, so synthetic clicks from remote-control / accessibility tools are no longer dropped).
# Unit + integration tests on the host platform
./gradlew :sdl-kmp:jvmTest :sdl-kmp:macosArm64Test # macOS
./gradlew :sdl-kmp:jvmTest :sdl-kmp:linuxX64Test # Linux
# Build the Android JVM library (requires the Android SDK + NDK)
./gradlew :android-jvm:assembleDebug
./gradlew :android-jvm:publishToMavenLocalBuilding the Linux native SDL3 library (any linuxX64 task) requires the
Wayland, X11 and audio development packages: on Debian/Ubuntu that is
libwayland-dev libwayland-bin libxkbcommon-dev libegl-dev libdecor-0-dev
plus the X11 libx* dev packages and
libpipewire-0.3-dev libpulse-dev libasound2-dev (these also gate the
PipeWire/Pulse/ALSA audio drivers at build time — without them the published
klib falls back to X11 and has no audio drivers). The GitHub Actions workflows
install these automatically.
.github/workflows/test.yml — manual trigger: macOS builds all Apple klibs and the darwin JNI artifacts and runs JVM + native tests; Linux runs linuxX64Test, cross-compiles linuxArm64/mingwX64, builds the linux-* JNI artifacts, and runs the renderer example headless; Windows builds the windows-x86_64 JNI artifact natively (MinGW) and runs JVM tests; Android installs the NDK, builds the four androidNative klibs and assembles the sdl_renderer/sdl_gpu APKs; Web installs the Emscripten SDK, builds the wasmJs klib and the browser example..github/workflows/publish.yml — manual workflow that publishes the metadata + JVM + Apple klibs and the sdl-kmp-jni-jvm-darwin-* artifacts from macos-14, the linuxX64/linuxArm64/mingwX64 klibs and the sdl-kmp-jni-jvm-linux-* artifacts from ubuntu-latest, sdl-kmp-jni-jvm-windows-x86_64 from windows-latest (native MinGW build), and the four androidNative klibs from ubuntu-latest (with the NDK) to Maven Central.Required secrets: MAVEN_CENTRAL_USERNAME, MAVEN_CENTRAL_PASSWORD, SIGNING_KEY (base64 GPG keyring), SIGNING_KEY_ID, SIGNING_PASSWORD.
MIT. The bundled SDL3 submodule is licensed under the zlib license.