My step by step guides for SDL3 to build for Android and WebAssembly

A new version: Deploying SDL3 app on Android using SDL3-x.y.z.aar

New version for SDL 3.3: Setting Up SDL 3.3 for Android Development

Result of testing: Discord

Testing of the GitHub - madebr/sdlcross: Little SDL application buildable on Desktop + Android example with SDL3 Renderer API that shows squares when we touch the screen:
sdlcross-SDL3-Aug-14-2025

Example in pure OpenGL ES 2.0 that draws a simple green triangle:

I used the https://github.com/Genymobile/scrcpy app to create a screenshot on PC using USB cable from the phone.

Added the following info to the guide: Setting Up SDL 3.3 for Android Development

  • Important! The project name in CMakeLists.txt:
# Declare the project
project(my-project)
  • Must be the same as the project name in the .java file located at app\src\main\java\com\example\sdlcross, inside the getLibraries() method:
package com.example.sdlcross;

import org.libsdl.app.SDLActivity;

public class MyActivity extends SDLActivity {
    protected String[] getLibraries() {
        return new String[] { "my-project" };
    }
}

There is my mistake above. The executable name should be the same with the return new String[] { "sdlcross" };:

  • Important! The executable name in CMakeLists.txt:
add_executable(sdlcross src/main.c)
  • Must be the same as the project name in the .java file located at app\src\main\java\com\example\sdlcross, inside the getLibraries() method:
package com.example.sdlcross;

import org.libsdl.app.SDLActivity;

public class MyActivity extends SDLActivity {
    protected String[] getLibraries() {
        return new String[] { "sdlcross" };
    }
}

Note. The SDL 3.3 is not release yet. I call the currect master branch like 3.3.

Drawing a triangle using SDL3 (OpenGL, WebAssembly)

Guide: Setting up SDL3 with OpenGL ES 3.0 for WebAssembly

Source code: C, C++

Note. You can disable multisampling if you delete this code:

    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); // Enable MULTISAMPLE
    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 2); // can be 2, 4, 8 or 16

Note 2. There is no difference between 2 and 4 (value of multisampling) at least in this triangle.


Drawing a rotated rectangle using cglm (OpenGL, WebAssembly)

Guide: Setting up cglm with SDL3 and OpenGL ES 3.0 for WebAssembly

Source code: C, C++

Size of the Release build - 942 KB:

Drawing a texture using SDL3_image (OpenGL, WebAssembly)

Guide: Setting up SDL3_image with OpenGL ES 3.0 (Wasm)

Source code: C, C++

Drawing a texture using stb_image (OpenGL, WebAssembly)

Guide: Setting up stb_image with OpenGL ES 3.0 (Wasm)

Source code: C, C++

Rendering a text using SDL3_ttf (OpenGL, WebAssembly)

Guide: Setting up SDL3_ttf (OpenGL, WebAssembly)

Source code: C, C++

Size of the Release build - 1.81 MB:

Asset: LiberationSans-Regular.ttf file - 401 KB (link)

[Workaround] Background music looping in Wasm (SDL3_mixer)

I’ve been running into an issue where MIX_SetTrackLoops(..., -1) doesn’t loop background music infinitely in my Wasm/Emscripten build:

The Issue: The track stops after it completes its first run, and the loop command is ignored:

SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event)
{
    App *myApp = (App *)appstate;

    // Handle initial music trigger on any input
    bool isInput = (event->type == SDL_EVENT_KEY_DOWN ||
                    event->type == SDL_EVENT_MOUSE_BUTTON_DOWN);

    if (!myApp->musicStarted && isInput)
    {
        MIX_SetTrackLoops(myApp->musicTrack, -1); // -1 loops infinitely, 0 disables looping
        MIX_PlayTrack(myApp->musicTrack, 0);
        myApp->musicStarted = true;
    }

The Workaround: You must create an explicit options packet using SDL_CreateProperties(), assign your infinite loop instruction to MIX_PROP_PLAY_LOOPS_NUMBER, and feed that packet into MIX_PlayTrack:

SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event)
{
    App *myApp = (App *)appstate;

    // Handle initial music trigger on any input
    bool isInput = (event->type == SDL_EVENT_KEY_DOWN ||
                    event->type == SDL_EVENT_MOUSE_BUTTON_DOWN);

    if (!myApp->musicStarted && isInput)
    {
        // Create an options configuration packet
        SDL_PropertiesID props = SDL_CreateProperties();
        
        // Pack the "infinite loop" instruction (-1) into the container
        SDL_SetNumberProperty(props, MIX_PROP_PLAY_LOOPS_NUMBER, -1);
        
        // Hand the configuration properties over to SDL3_mixer
        MIX_PlayTrack(myApp->musicTrack, props);
        
        // Clean up the handle container (SDL copies properties internally)
        SDL_DestroyProperties(props);
        
        myApp->musicStarted = true;
    }

    // Handle rest of events...
    return SDL_APP_CONTINUE;
}

Playing a background music and sound effect using SDL3_mixer (OpenGL, WebAssembly)

Guide: Setting up SDL3_mixer (OpenGL, WebAssembly)

Source code: C, C++

Release build size:

C and C++ Release builds has the same sizes - 4.18 MB:

Asset sizes:

Playing a background music and sound effect using OpenAL (OpenGL, WebAssembly)

Guide: Setting up OpenAL (OpenGL, WebAssembly)

Source code: GitHub Repository

Release build size - 3.84 MB:

Asset sizes:

Read comments:

if (EMSCRIPTEN)
    target_link_options(app PRIVATE 
        "-Oz"                   # Optimize for smallest code size (aggressively strips metadata and optimizes for binary footprint).
        "-sMALLOC=emmalloc"     # Use 'emmalloc', a smaller, simpler memory allocator suitable for memory-constrained web apps.
        "-sUSE_SDL=0"           # Disable the legacy Emscripten-provided SDL1/SDL2 port (allows using custom or system-integrated SDL3).
        "-sMAX_WEBGL_VERSION=2" # Limit the maximum WebGL context version to 2.0.
        "-sMIN_WEBGL_VERSION=2" # Set the minimum required WebGL version to 2.0 (disallows fallback to 1.0).
        "-sFULL_ES3=1"          # Enable full WebGL 2.0 / OpenGL ES 3.0 feature set support.
    )
endif()

Short comments:

if (EMSCRIPTEN)
    target_link_options(app PRIVATE 
        "-Oz"                   # Maximize code size optimization
        "-sMALLOC=emmalloc"     # Use minimal memory allocator
        "-sUSE_SDL=0"           # Disable built-in SDL
        "-sMAX_WEBGL_VERSION=2" # Set WebGL 2.0 max
        "-sMIN_WEBGL_VERSION=2" # Require WebGL 2.0 min
        "-sFULL_ES3=1"          # Enable full GLES3/WebGL 2
    )
endif()

Failed to enable VSync on Intel(R) HD Graphics 630 (i7-7700)

I cannot enable it on Desktop:

    int status = SDL_GL_SetSwapInterval(1);
    if (status != 0)
    {
        SDL_Log("VSync failed: %s", SDL_GetError());
    }

Solution is to limit FPS:

SDL_AppResult SDL_AppIterate(void *appState)
{
    App *app = (App *)appState;

    // High-precision timing variables
    static Uint64 lastFrameTime = 0;
    const Uint64 targetFrameTimeNS = 16666666; // ~16.6ms for 60 FPS
    
    Uint64 now = SDL_GetTicksNS();
    
    // If we haven't hit the target time, yield the CPU
    if ((now - lastFrameTime) < targetFrameTimeNS)
    {
        // Sleep to yield control back to the Windows scheduler
        // This drops CPU usage from 15% to <1%
        SDL_Delay(1); 
        return SDL_APP_CONTINUE;
    }
    
    // Update our last frame time
    lastFrameTime = now;

    glClear(GL_COLOR_BUFFER_BIT);

Before:

After:

P.S. I use the latest Intel drivers:

The thread is about building SDL3 ‘for Android and WebAssembly’ so when you say “on desktop” do you mean specifically when running in a browser, or more generally?

1 Like

I have moved this problem to the topic: Failed to enable VSync on Intel(R) HD Graphics 630 (i7-7700)