Proper way to handle SDL2 Resizing in Emscripten
Asked Answered
W

3

7

Looking for the proper way to resize an SDL2 window/canvas that is coming from Emscripten. Currently I'm adding an event listener on the JS 'resize' event and sending the Canvas parent's client width + height to Emscripten, then updating a width and a height variable which are called on each render.

This is producing weird results - the scale is always off, the actual usable SDL2 area isn't changed, and the pointer events no longer line up with SDL.

My window size variables are:

int canvasWidth = 800;
int canvasHeight = 600;

This is my init code:

void Init()
{
    int i;
    for (i = 0; i < 256; i++)
        gPressed[i] = gWasPressed[i] = 0;
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE) < 0)
    {
        fprintf(stderr, "Video initialization failed: %s\n", SDL_GetError());
        SDL_Quit();
        exit(0);
    }


    SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
    SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
    SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
    SDL_GL_SetAttribute( SDL_GL_ALPHA_SIZE, 5);
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);

    int flags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI;


    gSDLWindow = SDL_CreateWindow(
        "",
        SDL_WINDOWPOS_CENTERED,
        SDL_WINDOWPOS_CENTERED,
        800,
        600,
        flags);
    SDL_SetRenderDrawColor(SDL_GetRenderer(gSDLWindow), 0, 0, 0, 0);

    SDL_GLContext glcontext = SDL_GL_CreateContext(gSDLWindow);

    SDL_GL_SetSwapInterval(1);


    glViewport(0, 0, canvasWidth, canvasHeight);
    glewInit();


    InitImGui();
    framework_init_flat();
    framework_init_tex();

    atexit(SDL_Quit);

}

And this is my per-frame update code:

void Update()
{
    ImGuiIO& io = ImGui::GetIO();

    // Setup resolution (every frame to accommodate for window resizing)
    io.DisplaySize = ImVec2((float)canvasWidth, (float)canvasHeight);

    SDL_SetWindowSize(gSDLWindow, canvasWidth, canvasHeight);

    // Setup time step
    static double time = 0.0f;
    const double current_time = SDL_GetTicks() / 1000.0;
    if (current_time == time)
        return;
    io.DeltaTime = (float)(current_time - time);
    time = current_time;

    io.MousePos = ImVec2((float)gUIState.mousex, (float)gUIState.mousey);
    io.MouseDown[0] = gUIState.mousedown != 0;
    io.MouseDown[1] = 0;

    if (gUIState.scroll)
    {
        io.MouseWheel += (float)gUIState.scroll * 0.5f;
        gUIState.scroll = 0;
    }

    if (gUIState.textinput[0])
    {
        io.AddInputCharactersUTF8(gUIState.textinput);
        gUIState.textinput[0] = 0;
    }

    for (int n = 0; n < 256; n++)
        io.KeysDown[n] = gPressed[n] != 0;
    io.KeyShift = ((SDL_GetModState() & KMOD_SHIFT) != 0);
    io.KeyCtrl = ((SDL_GetModState() & KMOD_CTRL) != 0);
    io.KeyAlt = ((SDL_GetModState() & KMOD_ALT) != 0);
}
Wilbourn answered 21/9, 2020 at 6:40 Comment(0)
S
6

Why not use the SDL window event system? This works for me:

void update_screen_size(int w, int h) 
{
   glViewport(0, 0, w, h);
}

void somewhere_in_your_main_loop() 
{
    SDL_Event event;
    while (SDL_PollEvent(&event)) 
    {
        switch (event.type) 
        {
        case SDL_QUIT:
            emscripten_cancel_main_loop();
            break;
        case SDL_WINDOWEVENT:
            if (event.window.event == SDL_WINDOWEVENT_RESIZED) 
            {
                update_screen_size(event.window.data1, event.window.data2);
            }
            break;
        default:
            break;
        }
    }
}

Another way is to query the html canvas each frame:

EM_JS(int, get_canvas_width, (), { return Module.canvas.width; });
EM_JS(int, get_canvas_height, (), { return Module.canvas.height; });

void somewhere_in_your_main_loop()
{
    update_screen_size(get_canvas_width(), get_canvas_height());
}
Scorpaenid answered 22/9, 2020 at 21:4 Comment(1)
You are having an extra "s" in the update_screen_size(get_canvass ... SO needs 6 characters change to accept my editsRolan
S
2

Here's how I handle it:

// During startup:
emscripten_set_resize_callback(
  EMSCRIPTEN_EVENT_TARGET_WINDOW,
  0, 0, on_web_display_size_changed
);

// The callback:
static EM_BOOL on_web_display_size_changed( int event_type, 
  const EmscriptenUiEvent *event, void *user_data )
{
  display_size_changed = 1;  // custom global flag
  return 0;
}

// In the emscripten_set_main_loop() callback:
if (display_size_changed)
{
  double w, h;
  emscripten_get_element_css_size( "#canvas", &w, &h );
  SDL_SetWindowSize( sdl_window, (int)w, (int) h );

  display_size_changed = 0;
}
Schoolman answered 6/9, 2023 at 17:33 Comment(0)
S
1

Handling resizing of the canvas

  1. SDL3 - recommended. It works for Web, Desktop, Android and so on:
int SDL_AppEvent(void *appstate, const SDL_Event *event)
{
    auto *app = (AppContext *)appstate;

    switch (event->type)
    {
        case SDL_EVENT_WINDOW_RESIZED:
        {
            int w = SDL_GetWindowSurface(app->window)->w;
            int h = SDL_GetWindowSurface(app->window)->h;
            std::cout << w << " " << h << std::endl;
            break;
        }
        case SDL_EVENT_QUIT:
        {
            app->app_quit = SDL_TRUE;
            break;
        }
        default:
        {
            break;
        }
    }

    return 0;
}
  1. Emscripten - not recommended because it works in the browser only:
#include <emscripten.h>
#include <emscripten/html5.h>

int SDL_AppInit(void **appstate, int argc, char *argv[])
{
    // ...

    emscripten_set_resize_callback(
        EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, 0,
        +[](int eventType, const EmscriptenUiEvent *e, void *userData) -> EM_BOOL
        {
            int w = e->windowInnerWidth;
            int h = e->windowInnerHeight;
            std::cout << w << " " << h << std::endl;
            return EM_FALSE;
        });

    // ...
}

If you need a size of the canvas at the first resize:

  1. SDL3 - recommended:
bool isFirstResize = true;

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

    if (isFirstResize)
    {
        int w = SDL_GetWindowSurface(app->window)->w;
        int h = SDL_GetWindowSurface(app->window)->h;
        std::cout << w << " " << h << std::endl;
        isFirstResize = false;
    }

    // ...
}
  1. Emscripten:
#include <emscripten.h>
#include <emscripten/html5.h>

bool isFirstResize = true;

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

    if (isFirstResize)
    {
        int w, h;
        emscripten_get_canvas_element_size("#canvas", &w, &h);
        std::cout << w << " " << h << std::endl;
        isFirstResize = false;
    }

    // ...
}
Sclerous answered 5/7 at 13:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.