Skip to content

Commit b4dac5a

Browse files
committed
Fully implement multiple windows on Emscripten
1 parent 22362a1 commit b4dac5a

File tree

6 files changed

+199
-13
lines changed

6 files changed

+199
-13
lines changed

src/test/SDL_test_common.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1395,13 +1395,21 @@ bool SDLTest_CommonInit(SDLTest_CommonState *state)
13951395
}
13961396
}
13971397

1398+
props = SDL_CreateProperties();
13981399
if (state->num_windows > 1) {
13991400
(void)SDL_snprintf(title, SDL_arraysize(title), "%s %d",
14001401
state->window_title, i + 1);
1402+
#if SDL_PLATFORM_EMSCRIPTEN
1403+
if (i != 0) {
1404+
char id[64];
1405+
SDL_snprintf(id, sizeof(id), "#canvas%d", i + 1);
1406+
SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_CANVAS_ID, id);
1407+
SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_KEYBOARD_ELEMENT, id);
1408+
}
1409+
#endif
14011410
} else {
14021411
SDL_strlcpy(title, state->window_title, SDL_arraysize(title));
14031412
}
1404-
props = SDL_CreateProperties();
14051413
SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, title);
14061414
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, r.x);
14071415
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, r.y);

src/video/SDL_video.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2646,9 +2646,13 @@ bool SDL_RecreateWindow(SDL_Window *window, SDL_WindowFlags flags)
26462646
SDL_Vulkan_UnloadLibrary();
26472647
}
26482648

2649+
// Don't actually recreate the window in Emscripten because the SDL_WindowData's
2650+
// canvas_id and keyboard_element would be lost if destroyed
2651+
#if !SDL_PLATFORM_EMSCRIPTEN
26492652
if (_this->DestroyWindow && !(flags & SDL_WINDOW_EXTERNAL)) {
26502653
_this->DestroyWindow(_this, window);
26512654
}
2655+
#endif
26522656

26532657
if (need_gl_load) {
26542658
if (!SDL_GL_LoadLibrary(NULL)) {
@@ -2676,6 +2680,11 @@ bool SDL_RecreateWindow(SDL_Window *window, SDL_WindowFlags flags)
26762680
window->w = window->windowed.w = window->floating.w;
26772681
window->h = window->windowed.h = window->floating.h;
26782682

2683+
#if SDL_PLATFORM_EMSCRIPTEN
2684+
// Prevent -Wunused-but-set-variable
2685+
(void)loaded_opengl;
2686+
(void)loaded_vulkan;
2687+
#else
26792688
if (!_this->CreateSDLWindow(_this, window, 0)) {
26802689
if (loaded_opengl) {
26812690
SDL_GL_UnloadLibrary();
@@ -2687,6 +2696,7 @@ bool SDL_RecreateWindow(SDL_Window *window, SDL_WindowFlags flags)
26872696
}
26882697
return false;
26892698
}
2699+
#endif
26902700
}
26912701

26922702
if (flags & SDL_WINDOW_EXTERNAL) {
@@ -2717,7 +2727,9 @@ bool SDL_RecreateWindow(SDL_Window *window, SDL_WindowFlags flags)
27172727
_this->SetWindowHitTest(window, true);
27182728
}
27192729

2730+
#if !SDL_PLATFORM_EMSCRIPTEN
27202731
SDL_FinishWindowCreation(window, flags);
2732+
#endif
27212733

27222734
return true;
27232735
}

src/video/emscripten/SDL_emscriptenframebuffer.c

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ bool Emscripten_CreateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *wind
3535

3636
// Free the old framebuffer surface
3737
SDL_WindowData *data = window->internal;
38+
if (!data) {
39+
// No framebuffer needed for this window
40+
return true;
41+
}
3842
surface = data->surface;
3943
SDL_DestroySurface(surface);
4044

@@ -166,9 +170,10 @@ bool Emscripten_UpdateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *wind
166170
void Emscripten_DestroyWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window)
167171
{
168172
SDL_WindowData *data = window->internal;
169-
170-
SDL_DestroySurface(data->surface);
171-
data->surface = NULL;
173+
if (data) {
174+
SDL_DestroySurface(data->surface);
175+
data->surface = NULL;
176+
}
172177
}
173178

174179
#endif // SDL_VIDEO_DRIVER_EMSCRIPTEN

src/video/emscripten/SDL_emscriptenopengles.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,30 @@ bool Emscripten_GLES_MakeCurrent(SDL_VideoDevice *_this, SDL_Window *window, SDL
151151
if (context != window_data->gl_context) {
152152
return SDL_SetError("Cannot make context current to another window");
153153
}
154+
155+
// Emscripten only applies WebGL rendering to the canvas: Module['canvas']
156+
// So, the canvas in the Module object will be updated to the one associated to this window
157+
const int canvas_set = MAIN_THREAD_EM_ASM_INT({
158+
try
159+
{
160+
var id = UTF8ToString($0);
161+
var canvas = document.querySelector(id);
162+
if (!canvas) {
163+
return false;
164+
}
165+
Module['canvas'] = canvas;
166+
return true;
167+
}
168+
catch(e)
169+
{
170+
// querySelector throws if id isn't a valid selector
171+
}
172+
return false;
173+
}, window_data->canvas_id);
174+
if (!canvas_set) {
175+
SDL_SetError("Canvas '%s' is not present in DOM", window_data->canvas_id);
176+
return false;
177+
}
154178
}
155179

156180
if (emscripten_webgl_make_context_current((EMSCRIPTEN_WEBGL_CONTEXT_HANDLE)context) != EMSCRIPTEN_RESULT_SUCCESS) {

src/video/emscripten/SDL_emscriptenvideo.c

Lines changed: 143 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ static void Emscripten_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window)
4747
static SDL_FullscreenResult Emscripten_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_FullscreenOp fullscreen);
4848
static void Emscripten_PumpEvents(SDL_VideoDevice *_this);
4949
static void Emscripten_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window);
50+
static bool Emscripten_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window);
51+
static void Emscripten_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window);
52+
static void Emscripten_HideWindow(SDL_VideoDevice *_this, SDL_Window *window);
5053

5154
static bool pumpevents_has_run = false;
5255
static int pending_swap_interval = -1;
@@ -166,12 +169,12 @@ static SDL_VideoDevice *Emscripten_CreateDevice(void)
166169

167170
device->CreateSDLWindow = Emscripten_CreateWindow;
168171
device->SetWindowTitle = Emscripten_SetWindowTitle;
169-
/*device->SetWindowIcon = Emscripten_SetWindowIcon;
170-
device->SetWindowPosition = Emscripten_SetWindowPosition;*/
172+
/*device->SetWindowIcon = Emscripten_SetWindowIcon;*/
173+
device->SetWindowPosition = Emscripten_SetWindowPosition;
171174
device->SetWindowSize = Emscripten_SetWindowSize;
172-
/*device->ShowWindow = Emscripten_ShowWindow;
175+
device->ShowWindow = Emscripten_ShowWindow;
173176
device->HideWindow = Emscripten_HideWindow;
174-
device->RaiseWindow = Emscripten_RaiseWindow;
177+
/*device->RaiseWindow = Emscripten_RaiseWindow;
175178
device->MaximizeWindow = Emscripten_MaximizeWindow;
176179
device->MinimizeWindow = Emscripten_MinimizeWindow;
177180
device->RestoreWindow = Emscripten_RestoreWindow;
@@ -480,8 +483,7 @@ static bool Emscripten_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window,
480483
selector = SDL_GetStringProperty(props, SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_CANVAS_ID, "#canvas");
481484
}
482485

483-
// Since SDL3 doesn't create the canvas element in the DOM, it should be verified
484-
// that the canvas ID refers to an HTML5CanvasElement.
486+
// If an element with this id already exists, it should be verified that it is to an HTML5CanvasElement
485487
canvas_exists = MAIN_THREAD_EM_ASM_INT({
486488
var id = UTF8ToString($0);
487489
try
@@ -498,8 +500,75 @@ static bool Emscripten_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window,
498500
return false;
499501
}, selector);
500502

503+
// Create the canvas if it doesn't exist. Assign a class to the element to signal that it was created by SDL3
501504
if (!canvas_exists) {
502-
SDL_SetError("Element '%s' is either not a HTMLCanvasElement or doesn't exist", selector);
505+
int x = SDL_GetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, SDL_WINDOWPOS_UNDEFINED);
506+
if (SDL_WINDOWPOS_ISUNDEFINED(x)) {
507+
x = 0;
508+
}
509+
int y = SDL_GetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, SDL_WINDOWPOS_UNDEFINED);
510+
if (SDL_WINDOWPOS_ISUNDEFINED(y)) {
511+
y = 0;
512+
}
513+
const int w = SDL_GetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, 0);
514+
const int h = SDL_GetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, 0);
515+
SDL_Window *parent = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_CREATE_PARENT_POINTER, NULL);
516+
const char *parent_id;
517+
if (parent) {
518+
SDL_WindowData *parent_window_data = parent->internal;
519+
parent_id = parent_window_data->canvas_id;
520+
} else {
521+
parent_id = "body";
522+
}
523+
canvas_exists = MAIN_THREAD_EM_ASM_INT({
524+
try
525+
{
526+
var id = UTF8ToString($0);
527+
var x = $1;
528+
var y = $2;
529+
var w = $3;
530+
var h = $4;
531+
var parent_id = UTF8ToString($5);
532+
var x_centered = $6;
533+
var y_centered = $7;
534+
if (x_centered) {
535+
x = window.innerWidth / 2 - w / 2;
536+
}
537+
if (y_centered) {
538+
y = window.innerHeight / 2 - h / 2;
539+
}
540+
var canvas = document.querySelector(id);
541+
if (canvas) {
542+
// Element with this id is not a canvas
543+
return false;
544+
}
545+
canvas = document.createElement("canvas");
546+
canvas.classList.add("SDL3_canvas");
547+
canvas.id = id.replace("#", "");
548+
canvas.oncontextmenu = (e) => { e.preventDefault(); };
549+
canvas.style.position = "absolute";
550+
canvas.style.left = `${x}px`;
551+
canvas.style.top = `${y}px`;
552+
canvas.width = w;
553+
canvas.height = h;
554+
var parent = document.querySelector(parent_id);
555+
if (parent_id !== "body" && parent) {
556+
parent.parentNode.append(canvas);
557+
} else {
558+
document.body.append(canvas);
559+
}
560+
return true;
561+
}
562+
catch(e)
563+
{
564+
// querySelector throws if id isn't a valid selector
565+
}
566+
return false;
567+
}, selector, x, y, w, h, parent_id, SDL_WINDOWPOS_CENTERED_DISPLAY(x), SDL_WINDOWPOS_CENTERED_DISPLAY(y));
568+
}
569+
570+
if (!canvas_exists) {
571+
SDL_SetError("'%s' is not a valid selector or doesn't refer to an HTMLCanvasElement", selector);
503572
return false;
504573
}
505574

@@ -617,8 +686,20 @@ static void Emscripten_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window)
617686

618687
Emscripten_UnregisterEventHandlers(data);
619688

620-
// We can't destroy the canvas, so resize it to zero instead
621-
emscripten_set_canvas_element_size(data->canvas_id, 0, 0);
689+
MAIN_THREAD_EM_ASM({
690+
var id = UTF8ToString($0);
691+
var canvas = document.querySelector(id);
692+
if (canvas) {
693+
if (canvas.classList.contains("SDL3_canvas")) {
694+
canvas.remove();
695+
} else {
696+
// Since the canvas wasn't created by SDL3, just resize it to zero instead.
697+
// TODO: Is this necessary?
698+
canvas.width = 0;
699+
canvas.height = 0;
700+
}
701+
}
702+
}, data->canvas_id);
622703

623704
vdata = _this->internal;
624705
SDL_SetBooleanProperty(vdata->window_map, data->canvas_id, false);
@@ -685,4 +766,57 @@ static void Emscripten_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window
685766
emscripten_set_window_title(window->title);
686767
}
687768

769+
static bool Emscripten_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window)
770+
{
771+
SDL_WindowData *window_data = window->internal;
772+
return MAIN_THREAD_EM_ASM_INT({
773+
try
774+
{
775+
var id = UTF8ToString($0);
776+
var x = $1;
777+
var y = $2;
778+
var canvas = document.querySelector(id);
779+
if (canvas && canvas.style.position && canvas.style.position !== 'static') {
780+
canvas.style.left = `${x}px`;
781+
canvas.style.top = `${y}px`;
782+
return true;
783+
}
784+
}
785+
catch(e)
786+
{
787+
// querySelector throws if id is not a valid selector
788+
}
789+
return false;
790+
}, window_data->canvas_id, window->pending.x, window->pending.y);
791+
}
792+
793+
static void Emscripten_SetWindowDisplay(SDL_Window *window, const char *display)
794+
{
795+
SDL_WindowData *window_data = window->internal;
796+
MAIN_THREAD_EM_ASM({
797+
var id = UTF8ToString($0);
798+
var display = UTF8ToString($1);
799+
try
800+
{
801+
let canvas = document.querySelector(id);
802+
if (canvas) {
803+
canvas.style.display = display;
804+
}
805+
}
806+
catch(e)
807+
{
808+
// querySelector throws if id is not a valid selector
809+
}
810+
}, window_data->canvas_id, display);
811+
}
812+
813+
static void Emscripten_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window)
814+
{
815+
Emscripten_SetWindowDisplay(window, "");
816+
}
817+
818+
static void Emscripten_HideWindow(SDL_VideoDevice *_this, SDL_Window *window)
819+
{
820+
Emscripten_SetWindowDisplay(window, "none");
821+
}
688822
#endif // SDL_VIDEO_DRIVER_EMSCRIPTEN

test/testmouse.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,9 @@ int main(int argc, char *argv[])
326326
return 0;
327327
}
328328

329+
/* Window won't show on Emscripten without this */
330+
SDL_ShowWindow(window);
331+
329332
/* Main render loop */
330333
#ifdef SDL_PLATFORM_EMSCRIPTEN
331334
emscripten_set_main_loop_arg(loop, &loop_data, 0, 1);

0 commit comments

Comments
 (0)