1 /*
2   Simple DirectMedia Layer
3   Copyright (C) 1997-2021 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 void
Emscripten_DeleteDevice(SDL_VideoDevice * device)58 Emscripten_DeleteDevice(SDL_VideoDevice * device)
59 {
60     SDL_free(device);
61 }
62 
63 static SDL_VideoDevice *
Emscripten_CreateDevice(int devindex)64 Emscripten_CreateDevice(int devindex)
65 {
66     SDL_VideoDevice *device;
67 
68     /* Initialize all variables that we clean on shutdown */
69     device = (SDL_VideoDevice *) SDL_calloc(1, sizeof(SDL_VideoDevice));
70     if (!device) {
71         SDL_OutOfMemory();
72         return (0);
73     }
74 
75     /* Firefox sends blur event which would otherwise prevent full screen
76      * when the user clicks to allow full screen.
77      * See https://bugzilla.mozilla.org/show_bug.cgi?id=1144964
78     */
79     SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0");
80 
81     /* Set the function pointers */
82     device->VideoInit = Emscripten_VideoInit;
83     device->VideoQuit = Emscripten_VideoQuit;
84     device->GetDisplayUsableBounds = Emscripten_GetDisplayUsableBounds;
85     device->SetDisplayMode = Emscripten_SetDisplayMode;
86 
87 
88     device->PumpEvents = Emscripten_PumpEvents;
89 
90     device->CreateSDLWindow = Emscripten_CreateWindow;
91     device->SetWindowTitle = Emscripten_SetWindowTitle;
92     /*device->SetWindowIcon = Emscripten_SetWindowIcon;
93     device->SetWindowPosition = Emscripten_SetWindowPosition;*/
94     device->SetWindowSize = Emscripten_SetWindowSize;
95     /*device->ShowWindow = Emscripten_ShowWindow;
96     device->HideWindow = Emscripten_HideWindow;
97     device->RaiseWindow = Emscripten_RaiseWindow;
98     device->MaximizeWindow = Emscripten_MaximizeWindow;
99     device->MinimizeWindow = Emscripten_MinimizeWindow;
100     device->RestoreWindow = Emscripten_RestoreWindow;
101     device->SetWindowMouseGrab = Emscripten_SetWindowMouseGrab;*/
102     device->DestroyWindow = Emscripten_DestroyWindow;
103     device->SetWindowFullscreen = Emscripten_SetWindowFullscreen;
104 
105     device->CreateWindowFramebuffer = Emscripten_CreateWindowFramebuffer;
106     device->UpdateWindowFramebuffer = Emscripten_UpdateWindowFramebuffer;
107     device->DestroyWindowFramebuffer = Emscripten_DestroyWindowFramebuffer;
108 
109 #if SDL_VIDEO_OPENGL_EGL
110     device->GL_LoadLibrary = Emscripten_GLES_LoadLibrary;
111     device->GL_GetProcAddress = Emscripten_GLES_GetProcAddress;
112     device->GL_UnloadLibrary = Emscripten_GLES_UnloadLibrary;
113     device->GL_CreateContext = Emscripten_GLES_CreateContext;
114     device->GL_MakeCurrent = Emscripten_GLES_MakeCurrent;
115     device->GL_SetSwapInterval = Emscripten_GLES_SetSwapInterval;
116     device->GL_GetSwapInterval = Emscripten_GLES_GetSwapInterval;
117     device->GL_SwapWindow = Emscripten_GLES_SwapWindow;
118     device->GL_DeleteContext = Emscripten_GLES_DeleteContext;
119     device->GL_GetDrawableSize = Emscripten_GLES_GetDrawableSize;
120 #endif
121 
122     device->free = Emscripten_DeleteDevice;
123 
124     return device;
125 }
126 
127 VideoBootStrap Emscripten_bootstrap = {
128     EMSCRIPTENVID_DRIVER_NAME, "SDL emscripten video driver",
129     Emscripten_CreateDevice
130 };
131 
132 
133 int
Emscripten_VideoInit(_THIS)134 Emscripten_VideoInit(_THIS)
135 {
136     SDL_DisplayMode mode;
137 
138     /* Use a fake 32-bpp desktop mode */
139     mode.format = SDL_PIXELFORMAT_RGB888;
140     emscripten_get_screen_size(&mode.w, &mode.h);
141 
142     mode.refresh_rate = 0;
143     mode.driverdata = NULL;
144     if (SDL_AddBasicVideoDisplay(&mode) < 0) {
145         return -1;
146     }
147 
148     SDL_AddDisplayMode(&_this->displays[0], &mode);
149 
150     Emscripten_InitMouse();
151 
152     /* We're done! */
153     return 0;
154 }
155 
156 static int
Emscripten_SetDisplayMode(_THIS,SDL_VideoDisplay * display,SDL_DisplayMode * mode)157 Emscripten_SetDisplayMode(_THIS, SDL_VideoDisplay * display, SDL_DisplayMode * mode)
158 {
159     /* can't do this */
160     return 0;
161 }
162 
163 static void
Emscripten_VideoQuit(_THIS)164 Emscripten_VideoQuit(_THIS)
165 {
166     Emscripten_FiniMouse();
167 }
168 
169 static int
Emscripten_GetDisplayUsableBounds(_THIS,SDL_VideoDisplay * display,SDL_Rect * rect)170 Emscripten_GetDisplayUsableBounds(_THIS, SDL_VideoDisplay * display, SDL_Rect * rect)
171 {
172     if (rect) {
173         rect->x = 0;
174         rect->y = 0;
175         rect->w = EM_ASM_INT_V({
176             return window.innerWidth;
177         });
178         rect->h = EM_ASM_INT_V({
179             return window.innerHeight;
180         });
181     }
182     return 0;
183 }
184 
185 static void
Emscripten_PumpEvents(_THIS)186 Emscripten_PumpEvents(_THIS)
187 {
188     /* do nothing. */
189 }
190 
191 static int
Emscripten_CreateWindow(_THIS,SDL_Window * window)192 Emscripten_CreateWindow(_THIS, SDL_Window * window)
193 {
194     SDL_WindowData *wdata;
195     double scaled_w, scaled_h;
196     double css_w, css_h;
197 
198     /* Allocate window internal data */
199     wdata = (SDL_WindowData *) SDL_calloc(1, sizeof(SDL_WindowData));
200     if (wdata == NULL) {
201         return SDL_OutOfMemory();
202     }
203 
204     wdata->canvas_id = SDL_strdup("#canvas");
205 
206     if (window->flags & SDL_WINDOW_ALLOW_HIGHDPI) {
207         wdata->pixel_ratio = emscripten_get_device_pixel_ratio();
208     } else {
209         wdata->pixel_ratio = 1.0f;
210     }
211 
212     scaled_w = SDL_floor(window->w * wdata->pixel_ratio);
213     scaled_h = SDL_floor(window->h * wdata->pixel_ratio);
214 
215     /* set a fake size to check if there is any CSS sizing the canvas */
216     emscripten_set_canvas_element_size(wdata->canvas_id, 1, 1);
217     emscripten_get_element_css_size(wdata->canvas_id, &css_w, &css_h);
218 
219     wdata->external_size = SDL_floor(css_w) != 1 || SDL_floor(css_h) != 1;
220 
221     if ((window->flags & SDL_WINDOW_RESIZABLE) && wdata->external_size) {
222         /* external css has resized us */
223         scaled_w = css_w * wdata->pixel_ratio;
224         scaled_h = css_h * wdata->pixel_ratio;
225 
226         SDL_SendWindowEvent(window, SDL_WINDOWEVENT_RESIZED, css_w, css_h);
227     }
228     emscripten_set_canvas_element_size(wdata->canvas_id, scaled_w, scaled_h);
229 
230     /* if the size is not being controlled by css, we need to scale down for hidpi */
231     if (!wdata->external_size) {
232         if (wdata->pixel_ratio != 1.0f) {
233             /*scale canvas down*/
234             emscripten_set_element_css_size(wdata->canvas_id, window->w, window->h);
235         }
236     }
237 
238 #if SDL_VIDEO_OPENGL_EGL
239     if (window->flags & SDL_WINDOW_OPENGL) {
240         if (!_this->egl_data) {
241             if (SDL_GL_LoadLibrary(NULL) < 0) {
242                 return -1;
243             }
244         }
245         wdata->egl_surface = SDL_EGL_CreateSurface(_this, 0);
246 
247         if (wdata->egl_surface == EGL_NO_SURFACE) {
248             return SDL_SetError("Could not create GLES window surface");
249         }
250     }
251 #endif
252 
253     wdata->window = window;
254 
255     /* Setup driver data for this window */
256     window->driverdata = wdata;
257 
258     /* One window, it always has focus */
259     SDL_SetMouseFocus(window);
260     SDL_SetKeyboardFocus(window);
261 
262     Emscripten_RegisterEventHandlers(wdata);
263 
264     /* Window has been successfully created */
265     return 0;
266 }
267 
Emscripten_SetWindowSize(_THIS,SDL_Window * window)268 static void Emscripten_SetWindowSize(_THIS, SDL_Window * window)
269 {
270     SDL_WindowData *data;
271 
272     if (window->driverdata) {
273         data = (SDL_WindowData *) window->driverdata;
274         /* update pixel ratio */
275         if (window->flags & SDL_WINDOW_ALLOW_HIGHDPI) {
276             data->pixel_ratio = emscripten_get_device_pixel_ratio();
277         }
278         emscripten_set_canvas_element_size(data->canvas_id, window->w * data->pixel_ratio, window->h * data->pixel_ratio);
279 
280         /*scale canvas down*/
281         if (!data->external_size && data->pixel_ratio != 1.0f) {
282             emscripten_set_element_css_size(data->canvas_id, window->w, window->h);
283         }
284     }
285 }
286 
287 static void
Emscripten_DestroyWindow(_THIS,SDL_Window * window)288 Emscripten_DestroyWindow(_THIS, SDL_Window * window)
289 {
290     SDL_WindowData *data;
291 
292     if(window->driverdata) {
293         data = (SDL_WindowData *) window->driverdata;
294 
295         Emscripten_UnregisterEventHandlers(data);
296 #if SDL_VIDEO_OPENGL_EGL
297         if (data->egl_surface != EGL_NO_SURFACE) {
298             SDL_EGL_DestroySurface(_this, data->egl_surface);
299             data->egl_surface = EGL_NO_SURFACE;
300         }
301 #endif
302 
303         /* We can't destroy the canvas, so resize it to zero instead */
304         emscripten_set_canvas_element_size(data->canvas_id, 0, 0);
305         SDL_free(data->canvas_id);
306 
307         SDL_free(window->driverdata);
308         window->driverdata = NULL;
309     }
310 }
311 
312 static void
Emscripten_SetWindowFullscreen(_THIS,SDL_Window * window,SDL_VideoDisplay * display,SDL_bool fullscreen)313 Emscripten_SetWindowFullscreen(_THIS, SDL_Window * window, SDL_VideoDisplay * display, SDL_bool fullscreen)
314 {
315     SDL_WindowData *data;
316     if(window->driverdata) {
317         data = (SDL_WindowData *) window->driverdata;
318 
319         if(fullscreen) {
320             EmscriptenFullscreenStrategy strategy;
321             SDL_bool is_desktop_fullscreen = (window->flags & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN_DESKTOP;
322             int res;
323 
324             strategy.scaleMode = is_desktop_fullscreen ? EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH : EMSCRIPTEN_FULLSCREEN_SCALE_ASPECT;
325 
326             if(!is_desktop_fullscreen) {
327                 strategy.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_NONE;
328             } else if(window->flags & SDL_WINDOW_ALLOW_HIGHDPI) {
329                 strategy.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_HIDEF;
330             } else {
331                 strategy.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF;
332             }
333 
334             strategy.filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT;
335 
336             strategy.canvasResizedCallback = Emscripten_HandleCanvasResize;
337             strategy.canvasResizedCallbackUserData = data;
338 
339             data->requested_fullscreen_mode = window->flags & (SDL_WINDOW_FULLSCREEN_DESKTOP | SDL_WINDOW_FULLSCREEN);
340             data->fullscreen_resize = is_desktop_fullscreen;
341 
342             res = emscripten_request_fullscreen_strategy(data->canvas_id, 1, &strategy);
343             if(res != EMSCRIPTEN_RESULT_SUCCESS && res != EMSCRIPTEN_RESULT_DEFERRED) {
344                 /* unset flags, fullscreen failed */
345                 window->flags &= ~(SDL_WINDOW_FULLSCREEN_DESKTOP | SDL_WINDOW_FULLSCREEN);
346             }
347         }
348         else
349             emscripten_exit_fullscreen();
350     }
351 }
352 
353 static void
Emscripten_SetWindowTitle(_THIS,SDL_Window * window)354 Emscripten_SetWindowTitle(_THIS, SDL_Window * window) {
355     emscripten_set_window_title(window->title);
356 }
357 
358 #endif /* SDL_VIDEO_DRIVER_EMSCRIPTEN */
359 
360 /* vi: set ts=4 sw=4 expandtab: */
361