Merge pull request #40052 from Faless/js/more_improvements_3.2
[3.2] HTML5 fixes, audio fallback, fixed FPS.
This commit is contained in:
commit
2759fe85dc
|
@ -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 */
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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 : ""]);
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue