Skip to content

Hidden SDL_Window* used as shared GL context fails on Emscripten #12678

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
vittorioromeo opened this issue Mar 30, 2025 · 1 comment
Open
Milestone

Comments

@vittorioromeo
Copy link
Contributor

I have ran into another issue while implementing an SDL3 backend for my fork of SFML.

The design of the library involves the creation of a shared GL context at the beginning of main that allows resources such as textures and shaders to be reused by multiple GL contexts.

With a bespoke per-platform implementation of OpenGL contexts, this approach works well on every platform. Notably, I create a dummy context in this way for Emscripten + OpenGL ES:

EGLNativeWindowType dummyWindow{};
m_surface = eglCreateWindowSurface(m_display, m_config, *static_cast<EGLNativeWindowType*>(windowPtr), nullptr);
constexpr EGLint contextAttribs[]{EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE, EGL_NONE};
eglCreateContext(m_display, m_config, toShared, contextAttribs);

I'm in the process of removing all of these bespoke per-platform implementations with a single portable SDL3-based GL context implementation. Because every SDL3 OpenGL context requires to be associated with a window, I am using a hidden window to create my shared context:

sharedCtxWindow = SDL_CreateWindow("", 1, 1, SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN);

This works great on every platform, but fails catastrophically on Emscripten. The hidden window is created successfully, but upon attempting to create the second window, an error like this one is produced in the browser:

Uncaught ErrnoError: File exists
    ErrnoError http://localhost:6931/x.js:2007
    mknod http://localhost:6931/x.js:2554
    mkdir http://localhost:6931/x.js:2600
    165439 http://localhost:6931/x.js:860
    runMainThreadEmAsm http://localhost:6931/x.js:4098
    _emscripten_asm_const_int_sync_on_main_thread http://localhost:6931/x.js:4100
    createExportWrapper http://localhost:6931/x.js:677
    callMain http://localhost:6931/x.js:9436
    doRun http://localhost:6931/x.js:9489
    run http://localhost:6931/x.js:9498
[x.js:2007:11](http://localhost:6931/x.js)

Unfortunately, it would be very difficult for me to change the design of the library, which revolves around the idea of a shared context installed at the beginning of main. At this point I am considering not using SDL3 to create OpenGL contexts and keeping the bespoke implementations, which is a bit of a pain...

I have created a MVCE here:

#include <SDL3/SDL.h>
#include <SDL3/SDL_timer.h>

#include <iostream>

int main()
{
    if (!SDL_Init(SDL_INIT_VIDEO))
    {
        std::cerr << "SDL_Init Error: " << SDL_GetError() << '\n';
        return 1;
    }

    auto sharedCtxWindow = SDL_CreateWindow("", 1, 1, SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN);
    if (sharedCtxWindow == nullptr)
    {
        std::cerr << "Failed to create shared context hidden window:" << SDL_GetError() << '\n';
        return 1;
    }

    auto sharedCtx = SDL_GL_CreateContext(sharedCtxWindow);
    if (sharedCtx == nullptr)
    {
        std::cerr << "Failed to create shared GL context:" << SDL_GetError() << '\n';
        return 1;
    }

    auto window = SDL_CreateWindow("Example", 640, 480, SDL_WINDOW_OPENGL);
    if (window == nullptr)
    {
        std::cerr << "Failed to create window:" << SDL_GetError() << '\n';
        return 1;
    }

    auto windowCtx = SDL_GL_CreateContext(window);
    if (windowCtx == nullptr)
    {
        std::cerr << "Failed to create window GL context:" << SDL_GetError() << '\n';
        return 1;
    }

    if (!SDL_GL_MakeCurrent(sharedCtxWindow, sharedCtx))
    {
        std::cerr << "Failed to activate shared GL context: " << SDL_GetError() << '\n';
        return 1;
    }

    SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1);

    if (!SDL_GL_MakeCurrent(window, windowCtx))
    {
        std::cerr << "Failed to activate window GL context: " << SDL_GetError() << '\n';
        return 1;
    }

    SDL_Event event;

    while (true)
    {
        while (SDL_PollEvent(&event))
        {
            if (event.type == SDL_EVENT_QUIT)
            {
                return 0;
            }
        }

        SDL_GL_SwapWindow(window);
        SDL_Delay(10);
    }
}

To compile and run:

em++ sdlbug.cpp --use-port=sdl3 -o x.html
emrun ./x.html
@Temdog007
Copy link
Contributor

Currently, SDL will use the same canvas ID for all windows created with SDL_CreateWindow unless the SDL_HINT_EMSCRIPTEN_CANVAS_SELECTOR hint is set before creating the window. The file exists error is the result of the Emscripten_set_drag_event_callbacks trying to create a directory that already exists (due to creating the previous window) for handling dropped files.

Both of these issues are fixed in #12575. However, the first issue is resolved provided you set the SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_CANVAS_ID property and use SDL_CreateWindowWithProperties.

I've never worked with shared contexts in OpenGL. So, I don't know what is expected result is supposed to be. Below is the MVCE modified to work with the changes in #12575.

#define SDL_MAIN_USE_CALLBACKS 1
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <stdio.h>

SDL_Window *sharedCtxWindow = NULL;
SDL_GLContext sharedCtx = NULL;

SDL_Window *window = NULL;
SDL_GLContext windowCtx = NULL;

SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
{
    if (!SDL_Init(SDL_INIT_VIDEO))
    {
        fprintf(stderr, "Failed to intiialize SDL: %s\n", SDL_GetError());
        return SDL_APP_FAILURE;
    }

    window = SDL_CreateWindow("Example", 640, 480, SDL_WINDOW_OPENGL);
    if (window == NULL)
    {
        fprintf(stderr, "Failed to create window: %s\n", SDL_GetError());
        return SDL_APP_FAILURE;
    }

    windowCtx = SDL_GL_CreateContext(window);
    if (windowCtx == NULL)
    {
        fprintf(stderr, "Failed to create context: %s\n", SDL_GetError());
        return SDL_APP_FAILURE;
    }

    SDL_PropertiesID props = SDL_CreateProperties();
    SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, 1);
    SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, 1);
    SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_FLAGS_NUMBER, SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN);
    SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_CANVAS_ID, "#shared");
    sharedCtxWindow = SDL_CreateWindowWithProperties(props);
    SDL_DestroyProperties(props);
    if (sharedCtxWindow == NULL)
    {
        fprintf(stderr, "Failed to create window: %s\n", SDL_GetError());
        return SDL_APP_FAILURE;
    }

    sharedCtx = SDL_GL_CreateContext(sharedCtxWindow);
    if (sharedCtx == NULL)
    {
        fprintf(stderr, "Failed to create context: %s\n", SDL_GetError());
        return SDL_APP_FAILURE;
    }

    if (!SDL_GL_MakeCurrent(sharedCtxWindow, sharedCtx))
    {
        fprintf(stderr, "Failed to change current context: %s\n", SDL_GetError());
        return SDL_APP_FAILURE;
    }

    SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1);

    if (!SDL_GL_MakeCurrent(window, windowCtx))
    {
        fprintf(stderr, "Failed to change current context: %s\n", SDL_GetError());
        return SDL_APP_FAILURE;
    }
    return SDL_APP_CONTINUE;
}

void SDL_AppQuit(void *appstate, SDL_AppResult result)
{
    SDL_GL_DestroyContext(sharedCtx);
    SDL_DestroyWindow(sharedCtxWindow);

    SDL_GL_DestroyContext(windowCtx);
    SDL_DestroyWindow(window);
}

SDL_AppResult SDL_AppIterate(void *appstate)
{
    SDL_GL_SwapWindow(window);
    return SDL_APP_CONTINUE;
}

SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event)
{
    if (event->type == SDL_EVENT_QUIT)
    {
        return SDL_APP_SUCCESS;
    }
    return SDL_APP_CONTINUE;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants