1 /*
2   Simple DirectMedia Layer
3   Copyright (C) 1997-2020 Sam Lantinga <slouken@libsdl.org>
4 
5   This software is provided 'as-is', without any express or implied
6   warranty.  In no event will the authors be held liable for any damages
7   arising from the use of this software.
8 
9   Permission is granted to anyone to use this software for any purpose,
10   including commercial applications, and to alter it and redistribute it
11   freely, subject to the following restrictions:
12 
13   1. The origin of this software must not be misrepresented; you must not
14      claim that you wrote the original software. If you use this software
15      in a product, an acknowledgment in the product documentation would be
16      appreciated but is not required.
17   2. Altered source versions must be plainly marked as such, and must not be
18      misrepresented as being the original software.
19   3. This notice may not be removed or altered from any source distribution.
20 */
21 #include "../../SDL_internal.h"
22 
23 #if SDL_VIDEO_DRIVER_EMSCRIPTEN
24 
25 #include "SDL_video.h"
26 #include "SDL_mouse.h"
27 #include "SDL_hints.h"
28 #include "../SDL_sysvideo.h"
29 #include "../SDL_pixels_c.h"
30 #include "../SDL_egl_c.h"
31 #include "../../events/SDL_events_c.h"
32 
33 #include "SDL_emscriptenvideo.h"
34 #include "SDL_emscriptenopengles.h"
35 #include "SDL_emscriptenframebuffer.h"
36 #include "SDL_emscriptenevents.h"
37 #include "SDL_emscriptenmouse.h"
38 
39 #define EMSCRIPTENVID_DRIVER_NAME "emscripten"
40 
41 /* Initialization/Query functions */
42 static int Emscripten_VideoInit(_THIS);
43 static int Emscripten_SetDisplayMode(_THIS, SDL_VideoDisplay * display, SDL_DisplayMode * mode);
44 static void Emscripten_VideoQuit(_THIS);
45 static int Emscripten_GetDisplayUsableBounds(_THIS, SDL_VideoDisplay * display, SDL_Rect * rect);
46 
47 static int Emscripten_CreateWindow(_THIS, SDL_Window * window);
48 static void Emscripten_SetWindowSize(_THIS, SDL_Window * window);
49 static void Emscripten_DestroyWindow(_THIS, SDL_Window * window);
50 static void Emscripten_SetWindowFullscreen(_THIS, SDL_Window * window, SDL_VideoDisplay * display, SDL_bool fullscreen);
51 static void Emscripten_PumpEvents(_THIS);
52 static void Emscripten_SetWindowTitle(_THIS, SDL_Window * window);
53 
54 
55 /* Emscripten driver bootstrap functions */
56 
57 static int
Emscripten_Available(void)58 Emscripten_Available(void)
59 {
60     return (1);
61 }
62 
63 static void
Emscripten_DeleteDevice(SDL_VideoDevice * device)64 Emscripten_DeleteDevice(SDL_VideoDevice * device)
65 {
66     SDL_free(device);
67 }
68 
69 static SDL_VideoDevice *
Emscripten_CreateDevice(int devindex)70 Emscripten_CreateDevice(int devindex)
71 {
72     SDL_VideoDevice *device;
73 
74     /* Initialize all variables that we clean on shutdown */
75     device = (SDL_VideoDevice *) SDL_calloc(1, sizeof(SDL_VideoDevice));
76     if (!device) {
77         SDL_OutOfMemory();
78         return (0);
79     }
80 
81     /* Firefox sends blur event which would otherwise prevent full screen
82      * when the user clicks to allow full screen.
83      * See https://bugzilla.mozilla.org/show_bug.cgi?id=1144964
84     */
85     SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0");
86 
87     /* Set the function pointers */
88     device->VideoInit = Emscripten_VideoInit;
89     device->VideoQuit = Emscripten_VideoQuit;
90     device->GetDisplayUsableBounds = Emscripten_GetDisplayUsableBounds;
91     device->SetDisplayMode = Emscripten_SetDisplayMode;
92 
93 
94     device->PumpEvents = Emscripten_PumpEvents;
95 
96     device->CreateSDLWindow = Emscripten_CreateWindow;
97     device->SetWindowTitle = Emscripten_SetWindowTitle;
98     /*device->SetWindowIcon = Emscripten_SetWindowIcon;
99     device->SetWindowPosition = Emscripten_SetWindowPosition;*/
100     device->SetWindowSize = Emscripten_SetWindowSize;
101     /*device->ShowWindow = Emscripten_ShowWindow;
102     device->HideWindow = Emscripten_HideWindow;
103     device->RaiseWindow = Emscripten_RaiseWindow;
104     device->MaximizeWindow = Emscripten_MaximizeWindow;
105     device->MinimizeWindow = Emscripten_MinimizeWindow;
106     device->RestoreWindow = Emscripten_RestoreWindow;
107     device->SetWindowGrab = Emscripten_SetWindowGrab;*/
108     device->DestroyWindow = Emscripten_DestroyWindow;
109     device->SetWindowFullscreen = Emscripten_SetWindowFullscreen;
110 
111     device->CreateWindowFramebuffer = Emscripten_CreateWindowFramebuffer;
112     device->UpdateWindowFramebuffer = Emscripten_UpdateWindowFramebuffer;
113     device->DestroyWindowFramebuffer = Emscripten_DestroyWindowFramebuffer;
114 
115 #if SDL_VIDEO_OPENGL_EGL
116     device->GL_LoadLibrary = Emscripten_GLES_LoadLibrary;
117     device->GL_GetProcAddress = Emscripten_GLES_GetProcAddress;
118     device->GL_UnloadLibrary = Emscripten_GLES_UnloadLibrary;
119     device->GL_CreateContext = Emscripten_GLES_CreateContext;
120     device->GL_MakeCurrent = Emscripten_GLES_MakeCurrent;
121     device->GL_SetSwapInterval = Emscripten_GLES_SetSwapInterval;
122     device->GL_GetSwapInterval = Emscripten_GLES_GetSwapInterval;
123     device->GL_SwapWindow = Emscripten_GLES_SwapWindow;
124     device->GL_DeleteContext = Emscripten_GLES_DeleteContext;
125     device->GL_GetDrawableSize = Emscripten_GLES_GetDrawableSize;
126 #endif
127 
128     device->free = Emscripten_DeleteDevice;
129 
130     return device;
131 }
132 
133 VideoBootStrap Emscripten_bootstrap = {
134     EMSCRIPTENVID_DRIVER_NAME, "SDL emscripten video driver",
135     Emscripten_Available, Emscripten_CreateDevice
136 };
137 
138 
139 int
Emscripten_VideoInit(_THIS)140 Emscripten_VideoInit(_THIS)
141 {
142     SDL_DisplayMode mode;
143 
144     /* Use a fake 32-bpp desktop mode */
145     mode.format = SDL_PIXELFORMAT_RGB888;
146 
147     mode.w = EM_ASM_INT_V({
148         return screen.width;
149     });
150 
151     mode.h = EM_ASM_INT_V({
152         return screen.height;
153     });
154 
155     mode.refresh_rate = 0;
156     mode.driverdata = NULL;
157     if (SDL_AddBasicVideoDisplay(&mode) < 0) {
158         return -1;
159     }
160 
161     SDL_AddDisplayMode(&_this->displays[0], &mode);
162 
163     Emscripten_InitMouse();
164 
165     /* We're done! */
166     return 0;
167 }
168 
169 static int
Emscripten_SetDisplayMode(_THIS,SDL_VideoDisplay * display,SDL_DisplayMode * mode)170 Emscripten_SetDisplayMode(_THIS, SDL_VideoDisplay * display, SDL_DisplayMode * mode)
171 {
172     /* can't do this */
173     return 0;
174 }
175 
176 static void
Emscripten_VideoQuit(_THIS)177 Emscripten_VideoQuit(_THIS)
178 {
179     Emscripten_FiniMouse();
180 }
181 
182 static int
Emscripten_GetDisplayUsableBounds(_THIS,SDL_VideoDisplay * display,SDL_Rect * rect)183 Emscripten_GetDisplayUsableBounds(_THIS, SDL_VideoDisplay * display, SDL_Rect * rect)
184 {
185     if (rect) {
186         rect->x = 0;
187         rect->y = 0;
188         rect->w = EM_ASM_INT_V({
189             return window.innerWidth;
190         });
191         rect->h = EM_ASM_INT_V({
192             return window.innerHeight;
193         });
194     }
195     return 0;
196 }
197 
198 static void
Emscripten_PumpEvents(_THIS)199 Emscripten_PumpEvents(_THIS)
200 {
201     /* do nothing. */
202 }
203 
204 static int
Emscripten_CreateWindow(_THIS,SDL_Window * window)205 Emscripten_CreateWindow(_THIS, SDL_Window * window)
206 {
207     SDL_WindowData *wdata;
208     double scaled_w, scaled_h;
209     double css_w, css_h;
210 
211     /* Allocate window internal data */
212     wdata = (SDL_WindowData *) SDL_calloc(1, sizeof(SDL_WindowData));
213     if (wdata == NULL) {
214         return SDL_OutOfMemory();
215     }
216 
217     wdata->canvas_id = SDL_strdup("#canvas");
218 
219     if (window->flags & SDL_WINDOW_ALLOW_HIGHDPI) {
220         wdata->pixel_ratio = emscripten_get_device_pixel_ratio();
221     } else {
222         wdata->pixel_ratio = 1.0f;
223     }
224 
225     scaled_w = SDL_floor(window->w * wdata->pixel_ratio);
226     scaled_h = SDL_floor(window->h * wdata->pixel_ratio);
227 
228     /* set a fake size to check if there is any CSS sizing the canvas */
229     emscripten_set_canvas_element_size(wdata->canvas_id, 1, 1);
230     emscripten_get_element_css_size(wdata->canvas_id, &css_w, &css_h);
231 
232     wdata->external_size = SDL_floor(css_w) != 1 || SDL_floor(css_h) != 1;
233 
234     if ((window->flags & SDL_WINDOW_RESIZABLE) && wdata->external_size) {
235         /* external css has resized us */
236         scaled_w = css_w * wdata->pixel_ratio;
237         scaled_h = css_h * wdata->pixel_ratio;
238 
239         SDL_SendWindowEvent(window, SDL_WINDOWEVENT_RESIZED, css_w, css_h);
240     }
241     emscripten_set_canvas_element_size(wdata->canvas_id, scaled_w, scaled_h);
242 
243     /* if the size is not being controlled by css, we need to scale down for hidpi */
244     if (!wdata->external_size) {
245         if (wdata->pixel_ratio != 1.0f) {
246             /*scale canvas down*/
247             emscripten_set_element_css_size(wdata->canvas_id, window->w, window->h);
248         }
249     }
250 
251 #if SDL_VIDEO_OPENGL_EGL
252     if (window->flags & SDL_WINDOW_OPENGL) {
253         if (!_this->egl_data) {
254             if (SDL_GL_LoadLibrary(NULL) < 0) {
255                 return -1;
256             }
257         }
258         wdata->egl_surface = SDL_EGL_CreateSurface(_this, 0);
259 
260         if (wdata->egl_surface == EGL_NO_SURFACE) {
261             return SDL_SetError("Could not create GLES window surface");
262         }
263     }
264 #endif
265 
266     wdata->window = window;
267 
268     /* Setup driver data for this window */
269     window->driverdata = wdata;
270 
271     /* One window, it always has focus */
272     SDL_SetMouseFocus(window);
273     SDL_SetKeyboardFocus(window);
274 
275     Emscripten_RegisterEventHandlers(wdata);
276 
277     /* Window has been successfully created */
278     return 0;
279 }
280 
Emscripten_SetWindowSize(_THIS,SDL_Window * window)281 static void Emscripten_SetWindowSize(_THIS, SDL_Window * window)
282 {
283     SDL_WindowData *data;
284 
285     if (window->driverdata) {
286         data = (SDL_WindowData *) window->driverdata;
287         /* update pixel ratio */
288         if (window->flags & SDL_WINDOW_ALLOW_HIGHDPI) {
289             data->pixel_ratio = emscripten_get_device_pixel_ratio();
290         }
291         emscripten_set_canvas_element_size(data->canvas_id, window->w * data->pixel_ratio, window->h * data->pixel_ratio);
292 
293         /*scale canvas down*/
294         if (!data->external_size && data->pixel_ratio != 1.0f) {
295             emscripten_set_element_css_size(data->canvas_id, window->w, window->h);
296         }
297     }
298 }
299 
300 static void
Emscripten_DestroyWindow(_THIS,SDL_Window * window)301 Emscripten_DestroyWindow(_THIS, SDL_Window * window)
302 {
303     SDL_WindowData *data;
304 
305     if(window->driverdata) {
306         data = (SDL_WindowData *) window->driverdata;
307 
308         Emscripten_UnregisterEventHandlers(data);
309 #if SDL_VIDEO_OPENGL_EGL
310         if (data->egl_surface != EGL_NO_SURFACE) {
311             SDL_EGL_DestroySurface(_this, data->egl_surface);
312             data->egl_surface = EGL_NO_SURFACE;
313         }
314 #endif
315 
316         /* We can't destroy the canvas, so resize it to zero instead */
317         emscripten_set_canvas_element_size(data->canvas_id, 0, 0);
318         SDL_free(data->canvas_id);
319 
320         SDL_free(window->driverdata);
321         window->driverdata = NULL;
322     }
323 }
324 
325 static void
Emscripten_SetWindowFullscreen(_THIS,SDL_Window * window,SDL_VideoDisplay * display,SDL_bool fullscreen)326 Emscripten_SetWindowFullscreen(_THIS, SDL_Window * window, SDL_VideoDisplay * display, SDL_bool fullscreen)
327 {
328     SDL_WindowData *data;
329     if(window->driverdata) {
330         data = (SDL_WindowData *) window->driverdata;
331 
332         if(fullscreen) {
333             EmscriptenFullscreenStrategy strategy;
334             SDL_bool is_desktop_fullscreen = (window->flags & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN_DESKTOP;
335             int res;
336 
337             strategy.scaleMode = is_desktop_fullscreen ? EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH : EMSCRIPTEN_FULLSCREEN_SCALE_ASPECT;
338 
339             if(!is_desktop_fullscreen) {
340                 strategy.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_NONE;
341             } else if(window->flags & SDL_WINDOW_ALLOW_HIGHDPI) {
342                 strategy.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_HIDEF;
343             } else {
344                 strategy.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF;
345             }
346 
347             strategy.filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT;
348 
349             strategy.canvasResizedCallback = Emscripten_HandleCanvasResize;
350             strategy.canvasResizedCallbackUserData = data;
351 
352             data->requested_fullscreen_mode = window->flags & (SDL_WINDOW_FULLSCREEN_DESKTOP | SDL_WINDOW_FULLSCREEN);
353             data->fullscreen_resize = is_desktop_fullscreen;
354 
355             res = emscripten_request_fullscreen_strategy(data->canvas_id, 1, &strategy);
356             if(res != EMSCRIPTEN_RESULT_SUCCESS && res != EMSCRIPTEN_RESULT_DEFERRED) {
357                 /* unset flags, fullscreen failed */
358                 window->flags &= ~(SDL_WINDOW_FULLSCREEN_DESKTOP | SDL_WINDOW_FULLSCREEN);
359             }
360         }
361         else
362             emscripten_exit_fullscreen();
363     }
364 }
365 
366 static void
Emscripten_SetWindowTitle(_THIS,SDL_Window * window)367 Emscripten_SetWindowTitle(_THIS, SDL_Window * window) {
368     EM_ASM_INT({
369       if (typeof setWindowTitle !== 'undefined') {
370         setWindowTitle(UTF8ToString($0));
371       }
372       return 0;
373     }, window->title);
374 }
375 
376 #endif /* SDL_VIDEO_DRIVER_EMSCRIPTEN */
377 
378 /* vi: set ts=4 sw=4 expandtab: */
379