diff options
-rw-r--r-- | platform/javascript/audio_driver_javascript.cpp | 20 | ||||
-rw-r--r-- | platform/javascript/audio_driver_javascript.h | 7 | ||||
-rw-r--r-- | platform/javascript/display_server_javascript.cpp | 101 | ||||
-rw-r--r-- | platform/javascript/display_server_javascript.h | 20 | ||||
-rw-r--r-- | platform/javascript/javascript_main.cpp | 52 | ||||
-rw-r--r-- | platform/javascript/os_javascript.cpp | 25 | ||||
-rw-r--r-- | platform/javascript/os_javascript.h | 5 | ||||
-rw-r--r-- | servers/audio_server.cpp | 1 |
8 files changed, 149 insertions, 82 deletions
diff --git a/platform/javascript/audio_driver_javascript.cpp b/platform/javascript/audio_driver_javascript.cpp index b8914414e6..9604914b2c 100644 --- a/platform/javascript/audio_driver_javascript.cpp +++ b/platform/javascript/audio_driver_javascript.cpp @@ -36,6 +36,15 @@ AudioDriverJavaScript *AudioDriverJavaScript::singleton = nullptr; +bool AudioDriverJavaScript::is_available() { + return EM_ASM_INT({ + if (!(window.AudioContext || window.webkitAudioContext)) { + return 0; + } + return 1; + }) != 0; +} + const char *AudioDriverJavaScript::get_name() const { return "JavaScript"; } @@ -207,12 +216,14 @@ void AudioDriverJavaScript::finish_async() { /* clang-format off */ EM_ASM({ - var ref = Module.IDHandler.get($0); + const id = $0; + var ref = Module.IDHandler.get(id); Module.async_finish.push(new Promise(function(accept, reject) { if (!ref) { - console.log("Ref not found!", $0, Module.IDHandler); + console.log("Ref not found!", id, Module.IDHandler); setTimeout(accept, 0); } else { + Module.IDHandler.remove(id); const context = ref['context']; // Disconnect script and input. ref['script'].disconnect(); @@ -226,7 +237,6 @@ void AudioDriverJavaScript::finish_async() { }); } })); - Module.IDHandler.remove($0); }, id); /* clang-format on */ } @@ -293,9 +303,5 @@ Error AudioDriverJavaScript::capture_stop() { } AudioDriverJavaScript::AudioDriverJavaScript() { - _driver_id = 0; - internal_buffer = nullptr; - buffer_length = 0; - singleton = this; } diff --git a/platform/javascript/audio_driver_javascript.h b/platform/javascript/audio_driver_javascript.h index 9b26be001e..f029a91db0 100644 --- a/platform/javascript/audio_driver_javascript.h +++ b/platform/javascript/audio_driver_javascript.h @@ -34,12 +34,13 @@ #include "servers/audio_server.h" class AudioDriverJavaScript : public AudioDriver { - float *internal_buffer; + float *internal_buffer = nullptr; - int _driver_id; - int buffer_length; + int _driver_id = 0; + int buffer_length = 0; public: + static bool is_available(); void mix_to_js(); void process_capture(float sample); diff --git a/platform/javascript/display_server_javascript.cpp b/platform/javascript/display_server_javascript.cpp index 0312efb377..2f0a2faa83 100644 --- a/platform/javascript/display_server_javascript.cpp +++ b/platform/javascript/display_server_javascript.cpp @@ -44,18 +44,15 @@ #define DOM_BUTTON_XBUTTON1 3 #define DOM_BUTTON_XBUTTON2 4 +char DisplayServerJavaScript::canvas_id[256] = { 0 }; +static bool cursor_inside_canvas = true; + DisplayServerJavaScript *DisplayServerJavaScript::get_singleton() { return static_cast<DisplayServerJavaScript *>(DisplayServer::get_singleton()); } // Window (canvas) -extern "C" EMSCRIPTEN_KEEPALIVE void _set_canvas_id(uint8_t *p_data, int p_data_size) { - DisplayServerJavaScript *display = DisplayServerJavaScript::get_singleton(); - display->canvas_id.parse_utf8((const char *)p_data, p_data_size); - display->canvas_id = "#" + display->canvas_id; -} - -static void focus_canvas() { +void DisplayServerJavaScript::focus_canvas() { /* clang-format off */ EM_ASM( Module['canvas'].focus(); @@ -63,7 +60,7 @@ static void focus_canvas() { /* clang-format on */ } -static bool is_canvas_focused() { +bool DisplayServerJavaScript::is_canvas_focused() { /* clang-format off */ return EM_ASM_INT_V( return document.activeElement == Module['canvas']; @@ -71,8 +68,21 @@ static bool is_canvas_focused() { /* clang-format on */ } -static Point2 compute_position_in_canvas(int x, int y) { - DisplayServerJavaScript *display = DisplayServerJavaScript::get_singleton(); +bool DisplayServerJavaScript::check_size_force_redraw() { + int canvas_width; + int canvas_height; + emscripten_get_canvas_element_size(DisplayServerJavaScript::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 and for redraw. + emscripten_set_canvas_element_size(DisplayServerJavaScript::canvas_id, canvas_width, canvas_height); + return true; + } + return false; +} + +Point2 DisplayServerJavaScript::compute_position_in_canvas(int p_x, int p_y) { int canvas_x = EM_ASM_INT({ return Module['canvas'].getBoundingClientRect().x; }); @@ -81,23 +91,22 @@ static Point2 compute_position_in_canvas(int x, int y) { }); int canvas_width; int canvas_height; - emscripten_get_canvas_element_size(display->canvas_id.utf8().get_data(), &canvas_width, &canvas_height); + emscripten_get_canvas_element_size(canvas_id, &canvas_width, &canvas_height); double element_width; double element_height; - emscripten_get_element_css_size(display->canvas_id.utf8().get_data(), &element_width, &element_height); + emscripten_get_element_css_size(canvas_id, &element_width, &element_height); - return Point2((int)(canvas_width / element_width * (x - canvas_x)), - (int)(canvas_height / element_height * (y - canvas_y))); + return Point2((int)(canvas_width / element_width * (p_x - canvas_x)), + (int)(canvas_height / element_height * (p_y - canvas_y))); } -static bool cursor_inside_canvas = true; - EM_BOOL DisplayServerJavaScript::fullscreen_change_callback(int p_event_type, const EmscriptenFullscreenChangeEvent *p_event, void *p_user_data) { DisplayServerJavaScript *display = get_singleton(); // Empty ID is canvas. String target_id = String::utf8(p_event->id); - if (target_id.empty() || "#" + target_id == display->canvas_id) { + String canvas_str_id = String::utf8(canvas_id); + if (target_id.empty() || target_id == canvas_str_id) { // This event property is the only reliable data on // browser fullscreen state. if (p_event->isFullscreen) { @@ -131,14 +140,14 @@ extern "C" EMSCRIPTEN_KEEPALIVE void _drop_files_callback(char *p_filev[], int p // Keys template <typename T> -static void dom2godot_mod(T *emscripten_event_ptr, Ref<InputEventWithModifiers> godot_event) { +void DisplayServerJavaScript::dom2godot_mod(T *emscripten_event_ptr, Ref<InputEventWithModifiers> godot_event) { godot_event->set_shift(emscripten_event_ptr->shiftKey); godot_event->set_alt(emscripten_event_ptr->altKey); godot_event->set_control(emscripten_event_ptr->ctrlKey); godot_event->set_metakey(emscripten_event_ptr->metaKey); } -static Ref<InputEventKey> setup_key_event(const EmscriptenKeyboardEvent *emscripten_event) { +Ref<InputEventKey> DisplayServerJavaScript::setup_key_event(const EmscriptenKeyboardEvent *emscripten_event) { Ref<InputEventKey> ev; ev.instance(); ev->set_echo(emscripten_event->repeat); @@ -289,7 +298,7 @@ EM_BOOL DisplayServerJavaScript::mousemove_callback(int p_event_type, const Emsc } // Cursor -static const char *godot2dom_cursor(DisplayServer::CursorShape p_shape) { +const char *DisplayServerJavaScript::godot2dom_cursor(DisplayServer::CursorShape p_shape) { switch (p_shape) { case DisplayServer::CURSOR_ARROW: return "auto"; @@ -330,7 +339,7 @@ static const char *godot2dom_cursor(DisplayServer::CursorShape p_shape) { } } -static void set_css_cursor(const char *p_cursor) { +void DisplayServerJavaScript::set_css_cursor(const char *p_cursor) { /* clang-format off */ EM_ASM_({ Module['canvas'].style.cursor = UTF8ToString($0); @@ -338,7 +347,7 @@ static void set_css_cursor(const char *p_cursor) { /* clang-format on */ } -static bool is_css_cursor_hidden() { +bool DisplayServerJavaScript::is_css_cursor_hidden() const { /* clang-format off */ return EM_ASM_INT({ return Module['canvas'].style.cursor === 'none'; @@ -820,23 +829,6 @@ DisplayServer *DisplayServerJavaScript::create_func(const String &p_rendering_dr } DisplayServerJavaScript::DisplayServerJavaScript(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { - /* clang-format off */ - EM_ASM({ - const canvas = Module['canvas']; - var enc = new TextEncoder("utf-8"); - var buffer = new Uint8Array(enc.encode(canvas.id)); - var len = buffer.byteLength; - var out = _malloc(len); - HEAPU8.set(buffer, out); - ccall("_set_canvas_id", - "void", - ["number", "number"], - [out, len] - ); - _free(out); - }); - /* clang-format on */ - RasterizerDummy::make_current(); // TODO GLES2 in Godot 4.0... or webgpu? #if 0 EmscriptenWebGLContextAttributes attributes; @@ -859,7 +851,7 @@ DisplayServerJavaScript::DisplayServerJavaScript(const String &p_rendering_drive gl_initialization_error = true; } - EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_create_context(canvas_id.utf8().get_data(), &attributes); + EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_create_context(canvas_id, &attributes); if (emscripten_webgl_make_context_current(ctx) != EMSCRIPTEN_RESULT_SUCCESS) { gl_initialization_error = true; } @@ -881,7 +873,6 @@ DisplayServerJavaScript::DisplayServerJavaScript(const String &p_rendering_drive } EMSCRIPTEN_RESULT result; - CharString id = canvas_id.utf8(); #define EM_CHECK(ev) \ if (result != EMSCRIPTEN_RESULT_SUCCESS) \ ERR_PRINT("Error while setting " #ev " callback: Code " + itos(result)); @@ -895,16 +886,16 @@ DisplayServerJavaScript::DisplayServerJavaScript(const String &p_rendering_drive // JavaScript APIs. For APIs that are not (sufficiently) exposed, EM_ASM // is used below. SET_EM_CALLBACK(EMSCRIPTEN_EVENT_TARGET_WINDOW, mousemove, mousemove_callback) - SET_EM_CALLBACK(id.get_data(), mousedown, mouse_button_callback) + SET_EM_CALLBACK(canvas_id, mousedown, mouse_button_callback) SET_EM_CALLBACK(EMSCRIPTEN_EVENT_TARGET_WINDOW, mouseup, mouse_button_callback) - SET_EM_CALLBACK(id.get_data(), wheel, wheel_callback) - SET_EM_CALLBACK(id.get_data(), touchstart, touch_press_callback) - SET_EM_CALLBACK(id.get_data(), touchmove, touchmove_callback) - SET_EM_CALLBACK(id.get_data(), touchend, touch_press_callback) - SET_EM_CALLBACK(id.get_data(), touchcancel, touch_press_callback) - SET_EM_CALLBACK(id.get_data(), keydown, keydown_callback) - SET_EM_CALLBACK(id.get_data(), keypress, keypress_callback) - SET_EM_CALLBACK(id.get_data(), keyup, keyup_callback) + SET_EM_CALLBACK(canvas_id, wheel, wheel_callback) + SET_EM_CALLBACK(canvas_id, touchstart, touch_press_callback) + SET_EM_CALLBACK(canvas_id, touchmove, touchmove_callback) + SET_EM_CALLBACK(canvas_id, touchend, touch_press_callback) + SET_EM_CALLBACK(canvas_id, touchcancel, touch_press_callback) + SET_EM_CALLBACK(canvas_id, keydown, keydown_callback) + SET_EM_CALLBACK(canvas_id, keypress, keypress_callback) + SET_EM_CALLBACK(canvas_id, keyup, keyup_callback) SET_EM_CALLBACK(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, fullscreenchange, fullscreen_change_callback) SET_EM_CALLBACK_NOTARGET(gamepadconnected, gamepad_change_callback) SET_EM_CALLBACK_NOTARGET(gamepaddisconnected, gamepad_change_callback) @@ -1012,7 +1003,7 @@ Size2i DisplayServerJavaScript::screen_get_size(int p_screen) const { Rect2i DisplayServerJavaScript::screen_get_usable_rect(int p_screen) const { int canvas[2]; - emscripten_get_canvas_element_size(canvas_id.utf8().get_data(), canvas, canvas + 1); + emscripten_get_canvas_element_size(canvas_id, canvas, canvas + 1); return Rect2i(0, 0, canvas[0], canvas[1]); } @@ -1103,12 +1094,14 @@ Size2i DisplayServerJavaScript::window_get_min_size(WindowID p_window) const { } void DisplayServerJavaScript::window_set_size(const Size2i p_size, WindowID p_window) { - emscripten_set_canvas_element_size(canvas_id.utf8().get_data(), p_size.x, p_size.y); + last_width = p_size.x; + last_height = p_size.y; + emscripten_set_canvas_element_size(canvas_id, p_size.x, p_size.y); } Size2i DisplayServerJavaScript::window_get_size(WindowID p_window) const { int canvas[2]; - emscripten_get_canvas_element_size(canvas_id.utf8().get_data(), canvas, canvas + 1); + emscripten_get_canvas_element_size(canvas_id, canvas, canvas + 1); return Size2(canvas[0], canvas[1]); } @@ -1134,7 +1127,7 @@ void DisplayServerJavaScript::window_set_mode(WindowMode p_mode, WindowID p_wind strategy.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF; strategy.filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT; strategy.canvasResizedCallback = nullptr; - EMSCRIPTEN_RESULT result = emscripten_request_fullscreen_strategy(canvas_id.utf8().get_data(), false, &strategy); + 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."); } break; diff --git a/platform/javascript/display_server_javascript.h b/platform/javascript/display_server_javascript.h index 9860ecdf98..b149665d67 100644 --- a/platform/javascript/display_server_javascript.h +++ b/platform/javascript/display_server_javascript.h @@ -53,6 +53,21 @@ class DisplayServerJavaScript : public DisplayServer { double last_click_ms = 0; int last_click_button_index = -1; + int last_width = 0; + int last_height = 0; + + // utilities + static Point2 compute_position_in_canvas(int p_x, int p_y); + static void focus_canvas(); + static bool is_canvas_focused(); + template <typename T> + static void dom2godot_mod(T *emscripten_event_ptr, Ref<InputEventWithModifiers> godot_event); + static Ref<InputEventKey> setup_key_event(const EmscriptenKeyboardEvent *emscripten_event); + static const char *godot2dom_cursor(DisplayServer::CursorShape p_shape); + static void set_css_cursor(const char *p_cursor); + bool is_css_cursor_hidden() const; + + // events static EM_BOOL fullscreen_change_callback(int p_event_type, const EmscriptenFullscreenChangeEvent *p_event, void *p_user_data); static EM_BOOL keydown_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data); @@ -81,17 +96,20 @@ protected: public: // Override return type to make writing static callbacks less tedious. static DisplayServerJavaScript *get_singleton(); + static char canvas_id[256]; WindowMode window_mode = WINDOW_MODE_WINDOWED; String clipboard; - String canvas_id; Callable window_event_callback; Callable input_event_callback; Callable input_text_callback; Callable drop_files_callback; + // utilities + bool check_size_force_redraw(); + // from DisplayServer virtual void alert(const String &p_alert, const String &p_title = "ALERT!"); virtual bool has_feature(Feature p_feature) const; diff --git a/platform/javascript/javascript_main.cpp b/platform/javascript/javascript_main.cpp index fd61c46e63..99672745e7 100644 --- a/platform/javascript/javascript_main.cpp +++ b/platform/javascript/javascript_main.cpp @@ -30,11 +30,13 @@ #include "core/io/resource_loader.h" #include "main/main.h" -#include "os_javascript.h" +#include "platform/javascript/display_server_javascript.h" +#include "platform/javascript/os_javascript.h" #include <emscripten/emscripten.h> static OS_JavaScript *os = nullptr; +static uint64_t target_ticks = 0; void exit_callback() { emscripten_cancel_main_loop(); // After this, we can exit! @@ -46,12 +48,32 @@ void exit_callback() { } void main_loop_callback() { + uint64_t current_ticks = os->get_ticks_usec(); + + bool force_draw = DisplayServerJavaScript::get_singleton()->check_size_force_redraw(); + if (force_draw) { + Main::force_redraw(); + } else if (current_ticks < target_ticks) { + return; // Skip frame. + } + + int target_fps = Engine::get_singleton()->get_target_fps(); + if (target_fps > 0) { + target_ticks += (uint64_t)(1000000 / target_fps); + } if (os->main_loop_iterate()) { emscripten_cancel_main_loop(); // Cancel current loop and wait for finalize_async. + /* clang-format off */ EM_ASM({ // This will contain the list of operations that need to complete before cleanup. - Module.async_finish = []; + Module.async_finish = [ + // Always contains at least one async promise, to avoid firing immediately if nothing is added. + new Promise(function(accept, reject) { + setTimeout(accept, 0); + }) + ]; }); + /* clang-format on */ os->get_main_loop()->finish(); os->finalize_async(); // Will add all the async finish functions. EM_ASM({ @@ -79,13 +101,35 @@ extern "C" EMSCRIPTEN_KEEPALIVE void main_after_fs_sync(char *p_idbfs_err) { ResourceLoader::set_abort_on_missing_resources(false); Main::start(); os->get_main_loop()->init(); - emscripten_resume_main_loop(); // Immediately run the first iteration. // We are inside an animation frame, we want to immediately draw on the newly setup canvas. main_loop_callback(); + emscripten_resume_main_loop(); } int main(int argc, char *argv[]) { + // Create and mount userfs immediately. + EM_ASM({ + FS.mkdir('/userfs'); + FS.mount(IDBFS, {}, '/userfs'); + }); + + // Configure locale. + char locale_ptr[16]; + /* clang-format off */ + EM_ASM({ + stringToUTF8(Module['locale'], $0, 16); + }, locale_ptr); + /* clang-format on */ + setenv("LANG", locale_ptr, true); + + // Ensure the canvas ID. + /* clang-format off */ + EM_ASM({ + stringToUTF8("#" + Module['canvas'].id, $0, 255); + }, DisplayServerJavaScript::canvas_id); + /* clang-format on */ + os = new OS_JavaScript(); Main::setup(argv[0], argc - 1, &argv[1], false); emscripten_set_main_loop(main_loop_callback, -1, false); @@ -95,8 +139,6 @@ int main(int argc, char *argv[]) { // run the 'main_after_fs_sync' function. /* clang-format off */ EM_ASM({ - FS.mkdir('/userfs'); - FS.mount(IDBFS, {}, '/userfs'); FS.syncfs(true, function(err) { requestAnimationFrame(function() { ccall('main_after_fs_sync', null, ['string'], [err ? err.message : ""]); diff --git a/platform/javascript/os_javascript.cpp b/platform/javascript/os_javascript.cpp index ad4b5a5afa..1ff4304bcf 100644 --- a/platform/javascript/os_javascript.cpp +++ b/platform/javascript/os_javascript.cpp @@ -74,18 +74,12 @@ void OS_JavaScript::initialize() { EngineDebugger::register_uri_handler("ws://", RemoteDebuggerPeerWebSocket::create); EngineDebugger::register_uri_handler("wss://", RemoteDebuggerPeerWebSocket::create); #endif - - char locale_ptr[16]; - /* clang-format off */ - EM_ASM({ - stringToUTF8(Module['locale'], $0, 16); - }, locale_ptr); - /* clang-format on */ - setenv("LANG", locale_ptr, true); } void OS_JavaScript::resume_audio() { - audio_driver_javascript.resume(); + if (audio_driver_javascript) { + audio_driver_javascript->resume(); + } } void OS_JavaScript::set_main_loop(MainLoop *p_main_loop) { @@ -133,11 +127,17 @@ void OS_JavaScript::delete_main_loop() { void OS_JavaScript::finalize_async() { finalizing = true; - audio_driver_javascript.finish_async(); + if (audio_driver_javascript) { + audio_driver_javascript->finish_async(); + } } void OS_JavaScript::finalize() { delete_main_loop(); + if (audio_driver_javascript) { + memdelete(audio_driver_javascript); + audio_driver_javascript = nullptr; + } } // Miscellaneous @@ -246,7 +246,10 @@ void OS_JavaScript::initialize_joypads() { } OS_JavaScript::OS_JavaScript() { - AudioDriverManager::add_driver(&audio_driver_javascript); + if (AudioDriverJavaScript::is_available()) { + audio_driver_javascript = memnew(AudioDriverJavaScript); + AudioDriverManager::add_driver(audio_driver_javascript); + } Vector<Logger *> loggers; loggers.push_back(memnew(StdLogger)); diff --git a/platform/javascript/os_javascript.h b/platform/javascript/os_javascript.h index f0f18b44f8..22234f9355 100644 --- a/platform/javascript/os_javascript.h +++ b/platform/javascript/os_javascript.h @@ -40,7 +40,7 @@ class OS_JavaScript : public OS_Unix { MainLoop *main_loop = nullptr; - AudioDriverJavaScript audio_driver_javascript; + AudioDriverJavaScript *audio_driver_javascript = nullptr; bool finalizing = false; bool idb_available = false; @@ -83,6 +83,9 @@ public: String get_executable_path() const; virtual Error shell_open(String p_uri); virtual String get_name() const; + // Override default OS implementation which would block the main thread with delay_usec. + // Implemented in javascript_main.cpp loop callback instead. + virtual void add_frame_delay(bool p_can_draw) {} virtual bool can_draw() const; virtual String get_cache_path() const; diff --git a/servers/audio_server.cpp b/servers/audio_server.cpp index 09d2914e05..6b70f41e57 100644 --- a/servers/audio_server.cpp +++ b/servers/audio_server.cpp @@ -184,6 +184,7 @@ void AudioDriverManager::initialize(int p_driver) { GLOBAL_DEF_RST("audio/enable_audio_input", false); GLOBAL_DEF_RST("audio/mix_rate", DEFAULT_MIX_RATE); GLOBAL_DEF_RST("audio/output_latency", DEFAULT_OUTPUT_LATENCY); + GLOBAL_DEF_RST("audio/output_latency.web", 50); // Safer default output_latency for web. int failed_driver = -1; |