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