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

Did I understand that correctly with WebAssembly you can run SDLx programs in a browser?

Yes, Emscripten will compile your C/C++ style code to run in any browser that supports using HTML5, javascript, and WebAssembly. Emscripten supports SDL 1 and 2 and the common sub-libraries (TTF, IMG, NET, MIXER).
Looks like support is being added or has been added for SDL3, but it may take a bit for the SDL3 sub-libraries to also be brought in line (I don’t know this for certain, perhaps they already are?).

… But there are some pretty severe limitations.
It is very difficult to get save files to work with Emscripten. I have found several tutorials that show different ways to create local storage, but Emscripten keeps getting updated in ways that then break those tutorials. They have a major focus on running in a sandboxed environment. To date I have not found a correct way to save game data while running up-to-date-Emscripten compiled code.

Click it: Emscripten-Generated Code to run SDL3 application in your broswer.

It is the source code of the applications above that was compiled to WebAssembly:

#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <cmath>

struct AppContext {
    SDL_Window* window;
    SDL_Renderer* renderer;
    SDL_bool app_quit = SDL_FALSE;
};

int SDL_Fail(){
    SDL_LogError(SDL_LOG_CATEGORY_CUSTOM, "Error %s", SDL_GetError());
    return -1;
}

int SDL_AppInit(void** appstate, int argc, char* argv[]) {
    // init the library, here we make a window so we only need the Video capabilities.
    if (SDL_Init(SDL_INIT_VIDEO)){
        return SDL_Fail();
    }
    
    // create a window
    SDL_Window* window = SDL_CreateWindow("Window", 352, 430, SDL_WINDOW_RESIZABLE);
    if (!window){
        return SDL_Fail();
    }
    
    SDL_Renderer* renderer = SDL_CreateRenderer(window, NULL, 0);
    if (!renderer){
        return SDL_Fail();
    }
    
    // print some information about the window
    SDL_ShowWindow(window);
    {
        int width, height, bbwidth, bbheight;
        SDL_GetWindowSize(window, &width, &height);
        SDL_GetWindowSizeInPixels(window, &bbwidth, &bbheight);
        SDL_Log("Window size: %ix%i", width, height);
        SDL_Log("Backbuffer size: %ix%i", bbwidth, bbheight);
        if (width != bbwidth){
            SDL_Log("This is a highdpi environment.");
        }
    }

    // set up the application data
    *appstate = new AppContext{
       window,
       renderer,
    };
    
    SDL_Log("Application started successfully!");

    return 0;
}

int SDL_AppEvent(void *appstate, const SDL_Event* event) {
    auto* app = (AppContext*)appstate;
    
    if (event->type == SDL_EVENT_QUIT) {
        app->app_quit = SDL_TRUE;
    }

    return 0;
}

int SDL_AppIterate(void *appstate) {
    auto* app = (AppContext*)appstate;

    // draw a color
    auto time = SDL_GetTicks() / 1000.f;
    auto red = (std::sin(time) + 1) / 2.0 * 255;
    auto green = (std::sin(time / 2) + 1) / 2.0 * 255;
    auto blue = (std::sin(time) * 2 + 1) / 2.0 * 255;
    
    SDL_SetRenderDrawColor(app->renderer, red, green, blue, SDL_ALPHA_OPAQUE);
    SDL_RenderClear(app->renderer);
    SDL_RenderPresent(app->renderer);

    return app->app_quit;
}

void SDL_AppQuit(void* appstate) {
    auto* app = (AppContext*)appstate;
    if (app) {
        SDL_DestroyRenderer(app->renderer);
        SDL_DestroyWindow(app->window);
        delete app;
    }

    SDL_Quit();
    SDL_Log("Application quit successfully!");
}

You can even write applications with 3D graphics in C++ using OpenGL ES and compile them to WebAssembly. You can use WebSockets to connect with Node.js server to create multiplayer games. Use the free Glitch hosting to practice in creating of game servers:

This program in pure OpenGL ES that I have ran in the browser:

#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <SDL3/SDL_opengles2.h>
#include <cmath>

struct AppContext
{
    SDL_Window *window;
    SDL_GLContext glcontext;
    SDL_bool app_quit = SDL_FALSE;
};

int SDL_Fail()
{
    SDL_LogError(SDL_LOG_CATEGORY_CUSTOM, "Error %s", SDL_GetError());
    return -1;
}

int SDL_AppInit(void **appstate, int argc, char *argv[])
{
    // init the library, here we make a window so we only need the Video capabilities.
    if (SDL_Init(SDL_INIT_VIDEO))
    {
        return SDL_Fail();
    }

    // create a window
    SDL_Window *window = SDL_CreateWindow("Window", 352, 430,
        SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
    if (!window)
    {
        return SDL_Fail();
    }

    SDL_GLContext glcontext = SDL_GL_CreateContext(window);
    if (!glcontext)
    {
        return SDL_Fail();
    }

    // print some information about the window
    SDL_ShowWindow(window);
    {
        int width, height, bbwidth, bbheight;
        SDL_GetWindowSize(window, &width, &height);
        SDL_GetWindowSizeInPixels(window, &bbwidth, &bbheight);
        SDL_Log("Window size: %ix%i", width, height);
        SDL_Log("Backbuffer size: %ix%i", bbwidth, bbheight);
        if (width != bbwidth)
        {
            SDL_Log("This is a highdpi environment.");
        }
    }

    // set up the application data
    *appstate = new AppContext {
        window,
        glcontext,
    };

    SDL_Log("Application started successfully!");

    return 0;
}

int SDL_AppEvent(void *appstate, const SDL_Event *event)
{
    auto *app = (AppContext *)appstate;

    if (event->type == SDL_EVENT_QUIT)
    {
        app->app_quit = SDL_TRUE;
    }

    return 0;
}

int SDL_AppIterate(void *appstate)
{
    auto *app = (AppContext *)appstate;

    // draw a color
    auto time = SDL_GetTicks() / 1000.f;
    auto red = (std::sin(time) + 1) / 2.0;
    auto green = (std::sin(time / 2) + 1) / 2.0;
    auto blue = (std::sin(time) * 2 + 1) / 2.0;

    glClearColor(red, green, blue, 1.f);
    glClear(GL_COLOR_BUFFER_BIT);
    // Draw your stuff here
    SDL_GL_SwapWindow(app->window);

    return app->app_quit;
}

void SDL_AppQuit(void *appstate)
{
    auto *app = (AppContext *)appstate;
    if (app)
    {
        SDL_GL_DeleteContext(app->glcontext);
        SDL_DestroyWindow(app->window);
        delete app;
    }

    SDL_Quit();
    SDL_Log("Application quit successfully!");
}

Drawing a simple triangle in OpenGL ES 2.0 that I have tested in the browser and real Android phone:

#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <SDL3/SDL_opengles2.h>
#include <cmath>
#include <vector>

struct AppContext
{
    SDL_Window *window;
    SDL_GLContext glcontext;
    SDL_bool app_quit = SDL_FALSE;
};

const char *vertexShaderSource =
    "attribute vec2 aPosition;\n"
    "void main()"
    "{\n"
    "    gl_Position = vec4(aPosition, 0.0, 1.0);\n"
    "}\n";

const char *fragmentShaderSource =
    "void main()"
    "{\n"
    "    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
    "}\n";

GLuint createShader(const char *shaderSource, int shaderType)
{
    GLuint shader = glCreateShader(shaderType);
    glShaderSource(shader, 1, &shaderSource, NULL);
    glCompileShader(shader);
    GLint status;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
    if (status == GL_FALSE)
    {
        GLint maxLength = 0;
        glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength);
        std::vector<GLchar> errorLog(maxLength);
        glGetShaderInfoLog(shader, maxLength, &maxLength, &errorLog[0]);
        glDeleteShader(shader); // Don't leak the shader
        // std::printf("%s\n", &(errorLog[0]));
    }
    return shader;
}

GLuint createShaderProgram()
{
    GLuint program = glCreateProgram();
    GLuint vShader = createShader(vertexShaderSource, GL_VERTEX_SHADER);
    GLuint fShader = createShader(fragmentShaderSource, GL_FRAGMENT_SHADER);

    glAttachShader(program, vShader);
    glAttachShader(program, fShader);
    glLinkProgram(program);
    glUseProgram(program);

    return program;
}

void initVertexBuffers(GLuint program)
{
    float vertPositions[] = {
        -0.5f, -0.5f,
        0.5f, -0.5f,
        0.f, 0.5f
    };
    GLuint vertPosBuffer;
    glGenBuffers(1, &vertPosBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, vertPosBuffer);
    int amount = sizeof(vertPositions) / sizeof(vertPositions[0]);
    glBufferData(GL_ARRAY_BUFFER, amount * sizeof(GLfloat),
        vertPositions, GL_STATIC_DRAW);
    GLint aPositionLocation = glGetAttribLocation(program, "aPosition");
    glVertexAttribPointer(aPositionLocation, 2, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(aPositionLocation);
}

int SDL_Fail()
{
    SDL_LogError(SDL_LOG_CATEGORY_CUSTOM, "Error %s", SDL_GetError());
    return -1;
}

int SDL_AppInit(void **appstate, int argc, char *argv[])
{
    // Init the library, here we make a window so we only need the Video capabilities
    if (SDL_Init(SDL_INIT_VIDEO))
    {
        return SDL_Fail();
    }

    // create a window
    SDL_Window *window = SDL_CreateWindow("OpenGL Window", 352, 430,
        SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
    if (!window)
    {
        return SDL_Fail();
    }

    SDL_GLContext glcontext = SDL_GL_CreateContext(window);

    GLuint program = createShaderProgram();
    initVertexBuffers(program);

    SDL_ShowWindow(window);

    // Set up the application data
    *appstate = new AppContext {
        window,
        glcontext,
    };

    SDL_Log("Application started successfully!");

    return 0;
}

int SDL_AppEvent(void *appstate, const SDL_Event *event)
{
    auto *app = (AppContext *)appstate;

    if (event->type == SDL_EVENT_QUIT)
    {
        app->app_quit = SDL_TRUE;
    }

    return 0;
}

int SDL_AppIterate(void *appstate)
{
    auto *app = (AppContext *)appstate;

    glClearColor(0.1, 0.3, 0.2, 1.f);
    glClear(GL_COLOR_BUFFER_BIT);
    glDrawArrays(GL_TRIANGLES, 0, 3);
    SDL_GL_SwapWindow(app->window);

    return app->app_quit;
}

void SDL_AppQuit(void *appstate)
{
    auto *app = (AppContext *)appstate;
    if (app)
    {
        SDL_GL_DeleteContext(app->glcontext);
        SDL_DestroyWindow(app->window);
        delete app;
    }

    SDL_Quit();
    SDL_Log("Application quit successfully!");
}

I have made the next demo for WebAssembly, Android, and Desktop using: Qt C++, OpenGL ES 2.0, OpenAL-Soft (this is a library for music and sounds), Box2D (for jumps, collision detections, and ray casting), Hiero (this is an application to create a font with distance field from TTF), Free Texture Packer (to pack images to one texture atlas), and Tiled map editor (to position sprites and Box2D static colliders).

All resources (sprites, music and sounds) have been replaced with free ones. You can see a list of free resources here. For example, I took the sprites here: More-Bit 8-Bit Mario by webfussel

I have made a custom joystick for Android in pure OpenGL ES 2.0 (this is an animation from the real phone that I made using scrcpy):

mario-2d-jumps-webfussel-opengles2-qt6-cpp-android

@ 8Observer8

I tried to compile the above examples under normal Linux, but there were lots of errors.
There are obvious errors here, variables declared twice.

    auto time = SDL_GetTicks() / 1000.f;
    auto red = (std::sin(time) + 1) / 2.0 * 255;
    auto green = (std::sin(time / 2) + 1) / 2.0 * 255;
    auto blue = (std::sin(time) * 2 + 1) / 2.0 * 255;
    auto red = 150 / 255.f;
    auto green = 30 / 255.f;
    auto blue = 50 / 255.f;

I tried to compile the above examples under normal Linux, but there were lots of errors.

There are obvious errors here, variables declared twice.

WebAssembly is certainly an exciting project. I assume you can certainly run programs without SDL.
When I have the time and the desire, I’ll take a closer look at it.
I briefly tried implementing your instructions on Linux, but unfortunately I didn’t get very far.

There is a similar project in the FPC/Lazarus world. Pascal programming, which is converted into JavaScript.
Here is an example from with. If you click on project1.html, it will start.

http://mathias1000.bplaced.net/WebGL/WebGL_Matrix/
http://mathias1000.bplaced.net/WebGL/WebGL_Matrix/project1.html

Thanks and sorry! I have fixed it. I just had some experiments and had not fixed it. I have the tutorial: Convert rendering using the SDL3 API to rendering using OpenGL ES 2.0

I have added a link above to the GitHub repository in the post with Super Mario demo clone above. All resources (sprites, music and sounds) have been replaced with free ones. You can see a list of free resources here. For example, I took the sprites here: More-Bit 8-Bit Mario by webfussel

Unfortunately it still doesn’t want to.
When I look at your source the main() is missing.
As with many examples and …/SDL3/SDL/test

g++ main.c -lSDL3
/usr/bin/ld: /usr/lib/gcc/x86_64-linux-gnu/11/../../../x86_64-linux-gnu/Scrt1.o: in function `_start':
(.text+0x1b): undefined reference to `main'
collect2: error: ld returned 1 exit status

I used this template for SDL3: sdl3-sample It doesn’t have the main() function in the main.cpp file. Try to build it with cmake (read README.md in the repository). Follow this:

# You need to clone with submodules, otherwise SDL will not download.
git clone https://github.com/Ravbug/sdl3-sample --depth=1 --recurse-submodules
cd sdl3-sample
mkdir build
cd build
cmake ..