Merge pull request #40052 from Faless/js/more_improvements_3.2

[3.2]  HTML5 fixes, audio fallback, fixed FPS.
This commit is contained in:
Rémi Verschelde 2020-07-02 20:24:44 +02:00 committed by GitHub
commit 2759fe85dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 87 additions and 35 deletions

View File

@ -36,6 +36,15 @@
AudioDriverJavaScript *AudioDriverJavaScript::singleton = NULL; AudioDriverJavaScript *AudioDriverJavaScript::singleton = NULL;
bool AudioDriverJavaScript::is_available() {
return EM_ASM_INT({
if (!(window.AudioContext || window.webkitAudioContext)) {
return 0;
}
return 1;
}) != 0;
}
const char *AudioDriverJavaScript::get_name() const { const char *AudioDriverJavaScript::get_name() const {
return "JavaScript"; return "JavaScript";
} }
@ -207,12 +216,14 @@ void AudioDriverJavaScript::finish_async() {
/* clang-format off */ /* clang-format off */
EM_ASM({ 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) { Module.async_finish.push(new Promise(function(accept, reject) {
if (!ref) { if (!ref) {
console.log("Ref not found!", $0, Module.IDHandler); console.log("Ref not found!", id, Module.IDHandler);
setTimeout(accept, 0); setTimeout(accept, 0);
} else { } else {
Module.IDHandler.remove(id);
const context = ref['context']; const context = ref['context'];
// Disconnect script and input. // Disconnect script and input.
ref['script'].disconnect(); ref['script'].disconnect();
@ -226,7 +237,6 @@ void AudioDriverJavaScript::finish_async() {
}); });
} }
})); }));
Module.IDHandler.remove($0);
}, id); }, id);
/* clang-format on */ /* clang-format on */
} }

View File

@ -41,6 +41,7 @@ class AudioDriverJavaScript : public AudioDriver {
int buffer_length; int buffer_length;
public: public:
static bool is_available();
void mix_to_js(); void mix_to_js();
void process_capture(float sample); void process_capture(float sample);

View File

@ -30,11 +30,12 @@
#include "core/io/resource_loader.h" #include "core/io/resource_loader.h"
#include "main/main.h" #include "main/main.h"
#include "os_javascript.h" #include "platform/javascript/os_javascript.h"
#include <emscripten/emscripten.h> #include <emscripten/emscripten.h>
static OS_JavaScript *os = NULL; static OS_JavaScript *os = NULL;
static uint64_t target_ticks = 0;
// Files drop (implemented in JS for now). // Files drop (implemented in JS for now).
extern "C" EMSCRIPTEN_KEEPALIVE void _drop_files_callback(char *p_filev[], int p_filec) { extern "C" EMSCRIPTEN_KEEPALIVE void _drop_files_callback(char *p_filev[], int p_filec) {
@ -58,13 +59,32 @@ void exit_callback() {
} }
void main_loop_callback() { void main_loop_callback() {
uint64_t current_ticks = os->get_ticks_usec();
bool force_draw = os->check_size_force_redraw();
if (force_draw) {
Main::force_redraw();
} else if (current_ticks < target_ticks && !force_draw) {
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()) { if (os->main_loop_iterate()) {
emscripten_cancel_main_loop(); // Cancel current loop and wait for finalize_async. emscripten_cancel_main_loop(); // Cancel current loop and wait for finalize_async.
/* clang-format off */
EM_ASM({ EM_ASM({
// This will contain the list of operations that need to complete before cleanup. // 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->get_main_loop()->finish();
os->finalize_async(); // Will add all the async finish functions. os->finalize_async(); // Will add all the async finish functions.
EM_ASM({ EM_ASM({
@ -81,9 +101,6 @@ extern "C" EMSCRIPTEN_KEEPALIVE void cleanup_after_sync() {
} }
extern "C" EMSCRIPTEN_KEEPALIVE void main_after_fs_sync(char *p_idbfs_err) { extern "C" EMSCRIPTEN_KEEPALIVE void main_after_fs_sync(char *p_idbfs_err) {
OS_JavaScript *os = OS_JavaScript::get_singleton();
// Set IDBFS status // Set IDBFS status
String idbfs_err = String::utf8(p_idbfs_err); String idbfs_err = String::utf8(p_idbfs_err);
if (!idbfs_err.empty()) { if (!idbfs_err.empty()) {
@ -106,7 +123,6 @@ extern "C" EMSCRIPTEN_KEEPALIVE void main_after_fs_sync(char *p_idbfs_err) {
EM_ASM({ EM_ASM({
stringToUTF8(Module['locale'], $0, 16); stringToUTF8(Module['locale'], $0, 16);
}, locale_ptr); }, locale_ptr);
/* clang-format on */ /* clang-format on */
setenv("LANG", locale_ptr, true); setenv("LANG", locale_ptr, true);
@ -115,14 +131,19 @@ extern "C" EMSCRIPTEN_KEEPALIVE void main_after_fs_sync(char *p_idbfs_err) {
ResourceLoader::set_abort_on_missing_resources(false); ResourceLoader::set_abort_on_missing_resources(false);
Main::start(); Main::start();
os->get_main_loop()->init(); os->get_main_loop()->init();
// Immediately run the first iteration.
// We are inside an animation frame, we want to immediately draw on the newly setup canvas.
main_loop_callback(); main_loop_callback();
emscripten_resume_main_loop(); emscripten_resume_main_loop();
} }
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
// Create and mount userfs immediately.
EM_ASM({
FS.mkdir('/userfs');
FS.mount(IDBFS, {}, '/userfs');
});
os = new OS_JavaScript(argc, argv); os = new OS_JavaScript(argc, argv);
// TODO: Check error return value.
Main::setup(argv[0], argc - 1, &argv[1], false); Main::setup(argv[0], argc - 1, &argv[1], false);
emscripten_set_main_loop(main_loop_callback, -1, false); emscripten_set_main_loop(main_loop_callback, -1, false);
emscripten_pause_main_loop(); // Will need to wait for FS sync. emscripten_pause_main_loop(); // Will need to wait for FS sync.
@ -131,8 +152,6 @@ int main(int argc, char *argv[]) {
// run the 'main_after_fs_sync' function. // run the 'main_after_fs_sync' function.
/* clang-format off */ /* clang-format off */
EM_ASM({ EM_ASM({
FS.mkdir('/userfs');
FS.mount(IDBFS, {}, '/userfs');
FS.syncfs(true, function(err) { FS.syncfs(true, function(err) {
requestAnimationFrame(function() { requestAnimationFrame(function() {
ccall('main_after_fs_sync', null, ['string'], [err ? err.message : ""]); ccall('main_after_fs_sync', null, ['string'], [err ? err.message : ""]);

View File

@ -94,18 +94,22 @@ static Point2 compute_position_in_canvas(int x, int y) {
(int)(canvas_height / element_height * (y - canvas_y))); (int)(canvas_height / element_height * (y - canvas_y)));
} }
static bool cursor_inside_canvas = true; bool OS_JavaScript::check_size_force_redraw() {
extern "C" EMSCRIPTEN_KEEPALIVE void _canvas_resize_callback() {
OS_JavaScript *os = OS_JavaScript::get_singleton();
int canvas_width; int canvas_width;
int canvas_height; int canvas_height;
// Update the framebuffer size. emscripten_get_canvas_element_size(canvas_id.utf8().get_data(), &canvas_width, &canvas_height);
emscripten_get_canvas_element_size(os->canvas_id.utf8().get_data(), &canvas_width, &canvas_height); if (last_width != canvas_width || last_height != canvas_height) {
emscripten_set_canvas_element_size(os->canvas_id.utf8().get_data(), canvas_width, canvas_height); last_width = canvas_width;
Main::force_redraw(); last_height = canvas_height;
// Update the framebuffer size and for redraw.
emscripten_set_canvas_element_size(canvas_id.utf8().get_data(), canvas_width, canvas_height);
return true;
}
return false;
} }
static bool cursor_inside_canvas = true;
EM_BOOL OS_JavaScript::fullscreen_change_callback(int p_event_type, const EmscriptenFullscreenChangeEvent *p_event, void *p_user_data) { EM_BOOL OS_JavaScript::fullscreen_change_callback(int p_event_type, const EmscriptenFullscreenChangeEvent *p_event, void *p_user_data) {
OS_JavaScript *os = get_singleton(); OS_JavaScript *os = get_singleton();
@ -293,7 +297,7 @@ EM_BOOL OS_JavaScript::keydown_callback(int p_event_type, const EmscriptenKeyboa
} }
os->input->parse_input_event(ev); os->input->parse_input_event(ev);
// Resume audio context after input in case autoplay was denied. // Resume audio context after input in case autoplay was denied.
os->audio_driver_javascript.resume(); os->resume_audio();
return true; return true;
} }
@ -386,7 +390,7 @@ EM_BOOL OS_JavaScript::mouse_button_callback(int p_event_type, const EmscriptenM
os->input->parse_input_event(ev); os->input->parse_input_event(ev);
// Resume audio context after input in case autoplay was denied. // Resume audio context after input in case autoplay was denied.
os->audio_driver_javascript.resume(); os->resume_audio();
// Prevent multi-click text selection and wheel-click scrolling anchor. // Prevent multi-click text selection and wheel-click scrolling anchor.
// Context menu is prevented through contextmenu event. // Context menu is prevented through contextmenu event.
return true; return true;
@ -738,7 +742,7 @@ EM_BOOL OS_JavaScript::touch_press_callback(int p_event_type, const EmscriptenTo
os->input->parse_input_event(ev); os->input->parse_input_event(ev);
} }
// Resume audio context after input in case autoplay was denied. // Resume audio context after input in case autoplay was denied.
os->audio_driver_javascript.resume(); os->resume_audio();
return true; return true;
} }
@ -1062,12 +1066,6 @@ Error OS_JavaScript::initialize(const VideoMode &p_desired, int p_video_driver,
Module.listeners['drop'] = Module.drop_handler; // Defined in native/utils.js Module.listeners['drop'] = Module.drop_handler; // Defined in native/utils.js
canvas.addEventListener('dragover', Module.listeners['dragover'], false); canvas.addEventListener('dragover', Module.listeners['dragover'], false);
canvas.addEventListener('drop', Module.listeners['drop'], false); canvas.addEventListener('drop', Module.listeners['drop'], false);
// Resize
const resize_callback = cwrap('_canvas_resize_callback', null, []);
Module.resize_observer = new window['ResizeObserver'](function(elements) {
resize_callback();
});
Module.resize_observer.observe(canvas);
// Quit request // Quit request
Module['request_quit'] = function() { Module['request_quit'] = function() {
send_notification(notifications[notifications.length - 1]); send_notification(notifications[notifications.length - 1]);
@ -1101,6 +1099,12 @@ MainLoop *OS_JavaScript::get_main_loop() const {
return main_loop; return main_loop;
} }
void OS_JavaScript::resume_audio() {
if (audio_driver_javascript) {
audio_driver_javascript->resume();
}
}
bool OS_JavaScript::main_loop_iterate() { bool OS_JavaScript::main_loop_iterate() {
if (is_userfs_persistent() && sync_wait_time >= 0) { if (is_userfs_persistent() && sync_wait_time >= 0) {
@ -1167,10 +1171,10 @@ void OS_JavaScript::finalize_async() {
} }
}); });
Module.listeners = {}; Module.listeners = {};
Module.resize_observer.unobserve(canvas);
delete Module.resize_observer;
}); });
audio_driver_javascript.finish_async(); if (audio_driver_javascript) {
audio_driver_javascript->finish_async();
}
} }
void OS_JavaScript::finalize() { void OS_JavaScript::finalize() {
@ -1180,6 +1184,9 @@ void OS_JavaScript::finalize() {
emscripten_webgl_commit_frame(); emscripten_webgl_commit_frame();
memdelete(visual_server); memdelete(visual_server);
emscripten_webgl_destroy_context(webgl_ctx); emscripten_webgl_destroy_context(webgl_ctx);
if (audio_driver_javascript) {
memdelete(audio_driver_javascript);
}
} }
// Miscellaneous // Miscellaneous
@ -1409,6 +1416,9 @@ OS_JavaScript::OS_JavaScript(int p_argc, char *p_argv[]) {
last_click_ms = 0; last_click_ms = 0;
last_click_pos = Point2(-100, -100); last_click_pos = Point2(-100, -100);
last_width = 0;
last_height = 0;
window_maximized = false; window_maximized = false;
entering_fullscreen = false; entering_fullscreen = false;
just_exited_fullscreen = false; just_exited_fullscreen = false;
@ -1416,11 +1426,15 @@ OS_JavaScript::OS_JavaScript(int p_argc, char *p_argv[]) {
main_loop = NULL; main_loop = NULL;
visual_server = NULL; visual_server = NULL;
audio_driver_javascript = NULL;
idb_available = false; idb_available = false;
sync_wait_time = -1; sync_wait_time = -1;
AudioDriverManager::add_driver(&audio_driver_javascript); if (AudioDriverJavaScript::is_available()) {
audio_driver_javascript = memnew(AudioDriverJavaScript);
AudioDriverManager::add_driver(audio_driver_javascript);
}
Vector<Logger *> loggers; Vector<Logger *> loggers;
loggers.push_back(memnew(StdLogger)); loggers.push_back(memnew(StdLogger));

View File

@ -61,9 +61,12 @@ class OS_JavaScript : public OS_Unix {
double last_click_ms; double last_click_ms;
int last_click_button_index; int last_click_button_index;
int last_width;
int last_height;
MainLoop *main_loop; MainLoop *main_loop;
int video_driver_index; int video_driver_index;
AudioDriverJavaScript audio_driver_javascript; AudioDriverJavaScript *audio_driver_javascript;
VisualServer *visual_server; VisualServer *visual_server;
bool idb_available; bool idb_available;
@ -90,6 +93,8 @@ class OS_JavaScript : public OS_Unix {
static void file_access_close_callback(const String &p_file, int p_flags); static void file_access_close_callback(const String &p_file, int p_flags);
protected: protected:
void resume_audio();
virtual int get_current_video_driver() const; virtual int get_current_video_driver() const;
virtual void initialize_core(); virtual void initialize_core();
@ -105,6 +110,7 @@ protected:
public: public:
String canvas_id; String canvas_id;
void finalize_async(); void finalize_async();
bool check_size_force_redraw();
// Override return type to make writing static callbacks less tedious. // Override return type to make writing static callbacks less tedious.
static OS_JavaScript *get_singleton(); static OS_JavaScript *get_singleton();
@ -159,6 +165,7 @@ public:
String get_executable_path() const; String get_executable_path() const;
virtual Error shell_open(String p_uri); virtual Error shell_open(String p_uri);
virtual String get_name() const; virtual String get_name() const;
virtual void add_frame_delay(bool p_can_draw) {}
virtual bool can_draw() const; virtual bool can_draw() const;
virtual String get_cache_path() const; virtual String get_cache_path() const;

View File

@ -184,6 +184,7 @@ void AudioDriverManager::initialize(int p_driver) {
GLOBAL_DEF_RST("audio/enable_audio_input", false); GLOBAL_DEF_RST("audio/enable_audio_input", false);
GLOBAL_DEF_RST("audio/mix_rate", DEFAULT_MIX_RATE); GLOBAL_DEF_RST("audio/mix_rate", DEFAULT_MIX_RATE);
GLOBAL_DEF_RST("audio/output_latency", DEFAULT_OUTPUT_LATENCY); 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; int failed_driver = -1;