From 45a67fab35fa53b5dbc7de30df2b5ee9ff1aedfe Mon Sep 17 00:00:00 2001 From: Fabio Alessandrelli Date: Sat, 30 Jan 2021 11:35:03 +0100 Subject: [PATCH] [HTML5] Better fullscreen, canvas resizing. Three canvas resize policies: - `None`: Godot window settings are ignored. - `Project`: Godot handles the canvas like a native app (resizing it when setting the window size). - `Adaptive`: Canvas size will always adapt to browser window size. Use `None` if you want to control the canvas size with custom JavaScript code. --- misc/dist/html/full-size.html | 17 -- platform/javascript/export/export.cpp | 4 +- platform/javascript/godot_js.h | 12 +- .../js/libs/library_godot_display.js | 186 ++++++++++++++++-- .../javascript/js/libs/library_godot_os.js | 5 - platform/javascript/os_javascript.cpp | 99 ++-------- platform/javascript/os_javascript.h | 1 - 7 files changed, 198 insertions(+), 126 deletions(-) diff --git a/misc/dist/html/full-size.html b/misc/dist/html/full-size.html index c7e657e53da..08912ba8608 100644 --- a/misc/dist/html/full-size.html +++ b/misc/dist/html/full-size.html @@ -161,23 +161,6 @@ $GODOT_HEAD_INCLUDE } requestAnimationFrame(animate); - function adjustCanvasDimensions() { - const scale = window.devicePixelRatio || 1; - if (lastWidth != window.innerWidth || lastHeight != window.innerHeight || lastScale != scale) { - lastScale = scale; - lastWidth = window.innerWidth; - lastHeight = window.innerHeight; - canvas.width = Math.floor(lastWidth * scale); - canvas.height = Math.floor(lastHeight * scale); - canvas.style.width = lastWidth + "px"; - canvas.style.height = lastHeight + "px"; - } - } - if (GODOT_CONFIG['canvasResizePolicy'] == 2) { - animationCallbacks.push(adjustCanvasDimensions); - adjustCanvasDimensions(); - } - function setStatusMode(mode) { if (statusMode === mode || !initializing) diff --git a/platform/javascript/export/export.cpp b/platform/javascript/export/export.cpp index 209805788e5..05e3c6f51cb 100644 --- a/platform/javascript/export/export.cpp +++ b/platform/javascript/export/export.cpp @@ -296,7 +296,7 @@ void EditorExportPlatformJavaScript::_fix_html(Vector &p_html, const Re args.push_back(flags[i]); } Dictionary config; - config["canvasResizePolicy"] = p_preset->get("html/full_window_size") ? 2 : 1; + config["canvasResizePolicy"] = p_preset->get("html/canvas_resize_policy"); config["gdnativeLibs"] = libs; config["executable"] = p_name; config["args"] = args; @@ -353,7 +353,7 @@ void EditorExportPlatformJavaScript::get_export_options(List *r_op r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "vram_texture_compression/for_mobile"), false)); // ETC or ETC2, depending on renderer r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "html/custom_html_shell", PROPERTY_HINT_FILE, "*.html"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "html/head_include", PROPERTY_HINT_MULTILINE_TEXT), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "html/full_window_size"), true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "html/canvas_resize_policy", PROPERTY_HINT_ENUM, "None,Project,Adaptive"), 2)); } String EditorExportPlatformJavaScript::get_name() const { diff --git a/platform/javascript/godot_js.h b/platform/javascript/godot_js.h index 81443de7a2b..5aa8677a548 100644 --- a/platform/javascript/godot_js.h +++ b/platform/javascript/godot_js.h @@ -40,7 +40,6 @@ extern "C" { // Config extern void godot_js_config_locale_get(char *p_ptr, int p_ptr_max); extern void godot_js_config_canvas_id_get(char *p_ptr, int p_ptr_max); -extern int godot_js_config_canvas_resize_policy_get(); // OS extern void godot_js_os_finish_async(void (*p_callback)()); @@ -61,10 +60,15 @@ extern int godot_js_display_is_swap_ok_cancel(); // Display canvas extern void godot_js_display_canvas_focus(); extern int godot_js_display_canvas_is_focused(); -extern void godot_js_display_canvas_bounding_rect_position_get(int32_t *p_x, int32_t *p_y); // Display window -extern void godot_js_display_window_request_fullscreen(); +extern void godot_js_display_desired_size_set(int p_width, int p_height); +extern int godot_js_display_size_update(); +extern void godot_js_display_window_size_get(int32_t *p_x, int32_t *p_y); +extern void godot_js_display_screen_size_get(int32_t *p_x, int32_t *p_y); +extern int godot_js_display_fullscreen_request(); +extern int godot_js_display_fullscreen_exit(); +extern void godot_js_display_compute_position(int p_x, int p_y, int32_t *r_x, int32_t *r_y); extern void godot_js_display_window_title_set(const char *p_text); extern void godot_js_display_window_icon_set(const uint8_t *p_ptr, int p_len); @@ -88,7 +92,7 @@ extern int godot_js_display_gamepad_sample_get(int p_idx, float r_btns[16], int3 extern void godot_js_display_notification_cb(void (*p_callback)(int p_notification), int p_enter, int p_exit, int p_in, int p_out); extern void godot_js_display_paste_cb(void (*p_callback)(const char *p_text)); extern void godot_js_display_drop_files_cb(void (*p_callback)(char **p_filev, int p_filec)); -extern void godot_js_display_setup_canvas(); +extern void godot_js_display_setup_canvas(int p_width, int p_height, int p_fullscreen); #ifdef __cplusplus } #endif diff --git a/platform/javascript/js/libs/library_godot_display.js b/platform/javascript/js/libs/library_godot_display.js index 9ec295b39f4..fdb5cc0ec26 100644 --- a/platform/javascript/js/libs/library_godot_display.js +++ b/platform/javascript/js/libs/library_godot_display.js @@ -396,13 +396,118 @@ const GodotDisplayGamepads = { }; mergeInto(LibraryManager.library, GodotDisplayGamepads); +const GodotDisplayScreen = { + $GodotDisplayScreen__deps: ['$GodotConfig', '$GodotOS', '$GL', 'emscripten_webgl_get_current_context'], + $GodotDisplayScreen: { + desired_size: [0, 0], + isFullscreen: function () { + const elem = document.fullscreenElement || document.mozFullscreenElement + || document.webkitFullscreenElement || document.msFullscreenElement; + if (elem) { + return elem === GodotConfig.canvas; + } + // But maybe knowing the element is not supported. + return document.fullscreen || document.mozFullScreen + || document.webkitIsFullscreen; + }, + hasFullscreen: function () { + return document.fullscreenEnabled || document.mozFullScreenEnabled + || document.webkitFullscreenEnabled; + }, + requestFullscreen: function () { + if (!GodotDisplayScreen.hasFullscreen()) { + return 1; + } + const canvas = GodotConfig.canvas; + try { + const promise = (canvas.requestFullscreen || canvas.msRequestFullscreen + || canvas.mozRequestFullScreen || canvas.mozRequestFullscreen + || canvas.webkitRequestFullscreen + ).call(canvas); + // Some browsers (Safari) return undefined. + // For the standard ones, we need to catch it. + if (promise) { + promise.catch(function () { + // nothing to do. + }); + } + } catch (e) { + return 1; + } + return 0; + }, + exitFullscreen: function () { + if (!GodotDisplayScreen.isFullscreen()) { + return 0; + } + try { + const promise = document.exitFullscreen(); + if (promise) { + promise.catch(function () { + // nothing to do. + }); + } + } catch (e) { + return 1; + } + return 0; + }, + _updateGL: function () { + const gl_context_handle = _emscripten_webgl_get_current_context(); // eslint-disable-line no-undef + const gl = GL.getContext(gl_context_handle); + if (gl) { + GL.resizeOffscreenFramebuffer(gl); + } + }, + updateSize: function () { + const isFullscreen = GodotDisplayScreen.isFullscreen(); + const wantsFullWindow = GodotConfig.canvas_resize_policy === 2; + const noResize = GodotConfig.canvas_resize_policy === 0; + const wwidth = GodotDisplayScreen.desired_size[0]; + const wheight = GodotDisplayScreen.desired_size[1]; + const canvas = GodotConfig.canvas; + let width = wwidth; + let height = wheight; + if (noResize) { + // Don't resize canvas, just update GL if needed. + if (canvas.width !== width || canvas.height !== height) { + GodotDisplayScreen.desired_size = [canvas.width, canvas.height]; + GodotDisplayScreen._updateGL(); + return 1; + } + return 0; + } + const scale = window.devicePixelRatio || 1; + if (isFullscreen || wantsFullWindow) { + // We need to match screen size. + width = window.innerWidth * scale; + height = window.innerHeight * scale; + } + const csw = `${width / scale}px`; + const csh = `${height / scale}px`; + if (canvas.style.width !== csw || canvas.style.height !== csh || canvas.width !== width || canvas.height !== height) { + // Size doesn't match. + // Resize canvas, set correct CSS pixel size, update GL. + canvas.width = width; + canvas.height = height; + canvas.style.width = csw; + canvas.style.height = csh; + GodotDisplayScreen._updateGL(); + return 1; + } + return 0; + }, + }, +}; +mergeInto(LibraryManager.library, GodotDisplayScreen); + /** * Display server interface. * * Exposes all the functions needed by DisplayServer implementation. */ const GodotDisplay = { - $GodotDisplay__deps: ['$GodotConfig', '$GodotRuntime', '$GodotDisplayCursor', '$GodotDisplayListeners', '$GodotDisplayDragDrop', '$GodotDisplayGamepads'], + $GodotDisplay__deps: ['$GodotConfig', '$GodotRuntime', '$GodotDisplayCursor', '$GodotDisplayListeners', '$GodotDisplayDragDrop', '$GodotDisplayGamepads', '$GodotDisplayScreen'], $GodotDisplay: { window_icon: '', findDPI: function () { @@ -453,6 +558,48 @@ const GodotDisplay = { return window.devicePixelRatio || 1; }, + godot_js_display_fullscreen_request__sig: 'i', + godot_js_display_fullscreen_request: function () { + return GodotDisplayScreen.requestFullscreen(); + }, + + godot_js_display_fullscreen_exit__sig: 'i', + godot_js_display_fullscreen_exit: function () { + return GodotDisplayScreen.exitFullscreen(); + }, + + godot_js_display_desired_size_set__sig: 'v', + godot_js_display_desired_size_set: function (width, height) { + GodotDisplayScreen.desired_size = [width, height]; + GodotDisplayScreen.updateSize(); + }, + + godot_js_display_size_update__sig: 'i', + godot_js_display_size_update: function () { + return GodotDisplayScreen.updateSize(); + }, + + godot_js_display_screen_size_get__sig: 'vii', + godot_js_display_screen_size_get: function (width, height) { + const scale = window.devicePixelRatio || 1; + GodotRuntime.setHeapValue(width, window.screen.width * scale, 'i32'); + GodotRuntime.setHeapValue(height, window.screen.height * scale, 'i32'); + }, + + godot_js_display_window_size_get: function (p_width, p_height) { + GodotRuntime.setHeapValue(p_width, GodotConfig.canvas.width, 'i32'); + GodotRuntime.setHeapValue(p_height, GodotConfig.canvas.height, 'i32'); + }, + + godot_js_display_compute_position: function (x, y, r_x, r_y) { + const canvas = GodotConfig.canvas; + const rect = canvas.getBoundingClientRect(); + const rw = canvas.width / rect.width; + const rh = canvas.height / rect.height; + GodotRuntime.setHeapValue(r_x, (x - rect.x) * rw, 'i32'); + GodotRuntime.setHeapValue(r_y, (y - rect.y) * rh, 'i32'); + }, + /* * Canvas */ @@ -466,13 +613,6 @@ const GodotDisplay = { return document.activeElement === GodotConfig.canvas; }, - godot_js_display_canvas_bounding_rect_position_get__sig: 'vii', - godot_js_display_canvas_bounding_rect_position_get: function (r_x, r_y) { - const brect = GodotConfig.canvas.getBoundingClientRect(); - GodotRuntime.setHeapValue(r_x, brect.x, 'i32'); - GodotRuntime.setHeapValue(r_y, brect.y, 'i32'); - }, - /* * Touchscreen */ @@ -516,15 +656,6 @@ const GodotDisplay = { /* * Window */ - godot_js_display_window_request_fullscreen__sig: 'v', - godot_js_display_window_request_fullscreen: function () { - const canvas = GodotConfig.canvas; - (canvas.requestFullscreen || canvas.msRequestFullscreen - || canvas.mozRequestFullScreen || canvas.mozRequestFullscreen - || canvas.webkitRequestFullscreen - ).call(canvas); - }, - godot_js_display_window_title_set__sig: 'vi', godot_js_display_window_title_set: function (p_data) { document.title = GodotRuntime.parseString(p_data); @@ -645,8 +776,8 @@ const GodotDisplay = { GodotDisplayListeners.add(canvas, 'drop', GodotDisplayDragDrop.handler(dropFiles)); }, - godot_js_display_setup_canvas__sig: 'v', - godot_js_display_setup_canvas: function () { + godot_js_display_setup_canvas__sig: 'viii', + godot_js_display_setup_canvas: function (p_width, p_height, p_fullscreen) { const canvas = GodotConfig.canvas; GodotDisplayListeners.add(canvas, 'contextmenu', function (ev) { ev.preventDefault(); @@ -655,6 +786,23 @@ const GodotDisplay = { alert('WebGL context lost, please reload the page'); // eslint-disable-line no-alert ev.preventDefault(); }, false); + switch (GodotConfig.canvas_resize_policy) { + case 0: // None + GodotDisplayScreen.desired_size = [canvas.width, canvas.height]; + break; + case 1: // Project + GodotDisplayScreen.desired_size = [p_width, p_height]; + break; + default: // Full window + // Ensure we display in the right place, the size will be handled by updateSize + canvas.style.position = 'absolute'; + canvas.style.top = 0; + canvas.style.left = 0; + break; + } + if (p_fullscreen) { + GodotDisplayScreen.requestFullscreen(); + } }, /* diff --git a/platform/javascript/js/libs/library_godot_os.js b/platform/javascript/js/libs/library_godot_os.js index ef6df17e97c..0f189b013c7 100644 --- a/platform/javascript/js/libs/library_godot_os.js +++ b/platform/javascript/js/libs/library_godot_os.js @@ -91,11 +91,6 @@ const GodotConfig = { godot_js_config_locale_get: function (p_ptr, p_ptr_max) { GodotRuntime.stringToHeap(GodotConfig.locale, p_ptr, p_ptr_max); }, - - godot_js_config_canvas_resize_policy_get__sig: 'i', - godot_js_config_canvas_resize_policy_get: function () { - return GodotConfig.canvas_resize_policy; - }, }; autoAddDeps(GodotConfig, '$GodotConfig'); diff --git a/platform/javascript/os_javascript.cpp b/platform/javascript/os_javascript.cpp index b0bc90a414a..d283c4486bc 100644 --- a/platform/javascript/os_javascript.cpp +++ b/platform/javascript/os_javascript.cpp @@ -93,35 +93,14 @@ void OS_JavaScript::send_notification_callback(int p_notification) { // Window (canvas) -Point2 OS_JavaScript::compute_position_in_canvas(int x, int y) { - OS_JavaScript *os = get_singleton(); - int canvas_x; - int canvas_y; - godot_js_display_canvas_bounding_rect_position_get(&canvas_x, &canvas_y); - int canvas_width; - int canvas_height; - emscripten_get_canvas_element_size(os->canvas_id, &canvas_width, &canvas_height); - - double element_width; - double element_height; - emscripten_get_element_css_size(os->canvas_id, &element_width, &element_height); - - return Point2((int)(canvas_width / element_width * (x - canvas_x)), - (int)(canvas_height / element_height * (y - canvas_y))); +Point2 OS_JavaScript::compute_position_in_canvas(int p_x, int p_y) { + int point[2]; + godot_js_display_compute_position(p_x, p_y, point, point + 1); + return Point2(point[0], point[1]); } bool OS_JavaScript::check_size_force_redraw() { - int canvas_width; - int canvas_height; - emscripten_get_canvas_element_size(canvas_id, &canvas_width, &canvas_height); - if (last_width != canvas_width || last_height != canvas_height) { - last_width = canvas_width; - last_height = canvas_height; - // Update the framebuffer size for redraw. - emscripten_set_canvas_element_size(canvas_id, canvas_width, canvas_height); - return true; - } - return false; + return godot_js_display_size_update() != 0; } EM_BOOL OS_JavaScript::fullscreen_change_callback(int p_event_type, const EmscriptenFullscreenChangeEvent *p_event, void *p_user_data) { @@ -155,17 +134,13 @@ OS::VideoMode OS_JavaScript::get_video_mode(int p_screen) const { } Size2 OS_JavaScript::get_screen_size(int p_screen) const { - - EmscriptenFullscreenChangeEvent ev; - EMSCRIPTEN_RESULT result = emscripten_get_fullscreen_status(&ev); - ERR_FAIL_COND_V(result != EMSCRIPTEN_RESULT_SUCCESS, Size2()); - double scale = godot_js_display_pixel_ratio_get(); - return Size2(ev.screenWidth * scale, ev.screenHeight * scale); + int size[2]; + godot_js_display_screen_size_get(size, size + 1); + return Size2(size[0], size[1]); } void OS_JavaScript::set_window_size(const Size2 p_size) { - windowed_size = p_size; if (video_mode.fullscreen) { window_maximized = false; set_window_fullscreen(false); @@ -174,17 +149,14 @@ void OS_JavaScript::set_window_size(const Size2 p_size) { emscripten_exit_soft_fullscreen(); window_maximized = false; } - double scale = godot_js_display_pixel_ratio_get(); - emscripten_set_canvas_element_size(canvas_id, p_size.x, p_size.y); - emscripten_set_element_css_size(canvas_id, p_size.x / scale, p_size.y / scale); + godot_js_display_desired_size_set(p_size.x, p_size.y); } } Size2 OS_JavaScript::get_window_size() const { - - int canvas[2]; - emscripten_get_canvas_element_size(canvas_id, canvas, canvas + 1); - return Size2(canvas[0], canvas[1]); + int size[2]; + godot_js_display_window_size_get(size, size + 1); + return Size2(size[0], size[1]); } void OS_JavaScript::set_window_maximized(bool p_enabled) { @@ -229,20 +201,12 @@ void OS_JavaScript::set_window_fullscreen(bool p_enabled) { // This must be called before requesting full screen. emscripten_exit_soft_fullscreen(); } - EmscriptenFullscreenStrategy strategy; - strategy.scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH; - strategy.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF; - strategy.filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT; - strategy.canvasResizedCallback = NULL; - EMSCRIPTEN_RESULT result = emscripten_request_fullscreen_strategy(canvas_id, false, &strategy); - ERR_FAIL_COND_MSG(result == EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED, "Enabling fullscreen is only possible from an input callback for the HTML5 platform."); - ERR_FAIL_COND_MSG(result != EMSCRIPTEN_RESULT_SUCCESS, "Enabling fullscreen is only possible from an input callback for the HTML5 platform."); - // Not fullscreen yet, so prevent "windowed" canvas dimensions from - // being overwritten. + int result = godot_js_display_fullscreen_request(); + ERR_FAIL_COND_MSG(result, "The request was denied. Remember that enabling fullscreen is only possible from an input callback for the HTML5 platform."); entering_fullscreen = true; } else { // No logic allowed here, since exiting w/ ESC key won't use this function. - ERR_FAIL_COND(emscripten_exit_fullscreen() != EMSCRIPTEN_RESULT_SUCCESS); + ERR_FAIL_COND(godot_js_display_fullscreen_exit()); } } @@ -815,7 +779,11 @@ void OS_JavaScript::initialize_core() { Error OS_JavaScript::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) { - godot_js_display_setup_canvas(); // Handle contextmenu, webglcontextlost + video_mode = p_desired; + // fullscreen_change_callback will correct this if the request is successful. + video_mode.fullscreen = false; + godot_js_display_setup_canvas(video_mode.width, video_mode.height, video_mode.fullscreen); // Handle contextmenu, webglcontextlost + swap_ok_cancel = godot_js_display_is_swap_ok_cancel() == 1; EmscriptenWebGLContextAttributes attributes; @@ -884,21 +852,6 @@ Error OS_JavaScript::initialize(const VideoMode &p_desired, int p_video_driver, video_driver_index = p_video_driver; - video_mode = p_desired; - // fullscreen_change_callback will correct this if the request is successful. - video_mode.fullscreen = false; - // Emscripten only attempts fullscreen requests if the user input callback - // was registered through one its own functions, so request manually for - // start-up fullscreen. - if (p_desired.fullscreen) { - godot_js_display_window_request_fullscreen(); - } - if (godot_js_config_canvas_resize_policy_get() == 1) { - set_window_size(Size2(video_mode.width, video_mode.height)); - } else { - set_window_size(get_window_size()); - } - AudioDriverManager::initialize(p_audio_driver); visual_server = memnew(VisualServerRaster()); #ifndef NO_THREADS @@ -998,20 +951,10 @@ bool OS_JavaScript::main_loop_iterate() { strategy.canvasResizedCallback = NULL; emscripten_enter_soft_fullscreen(canvas_id, &strategy); } else { - set_window_size(Size2(windowed_size.width, windowed_size.height)); + godot_js_display_size_update(); } just_exited_fullscreen = false; } - - int canvas[2]; - emscripten_get_canvas_element_size(canvas_id, canvas, canvas + 1); - video_mode.width = canvas[0]; - video_mode.height = canvas[1]; - if (!window_maximized && !video_mode.fullscreen && !just_exited_fullscreen && !entering_fullscreen) { - windowed_size.width = canvas[0]; - windowed_size.height = canvas[1]; - } - return Main::iteration(); } diff --git a/platform/javascript/os_javascript.h b/platform/javascript/os_javascript.h index 8f5b51712ab..31e7c2c7e32 100644 --- a/platform/javascript/os_javascript.h +++ b/platform/javascript/os_javascript.h @@ -42,7 +42,6 @@ class OS_JavaScript : public OS_Unix { private: VideoMode video_mode; - Vector2 windowed_size; bool window_maximized; bool entering_fullscreen; bool just_exited_fullscreen;