diff options
Diffstat (limited to 'platform')
-rw-r--r-- | platform/javascript/.eslintrc.js | 8 | ||||
-rw-r--r-- | platform/javascript/audio_driver_javascript.cpp | 141 | ||||
-rw-r--r-- | platform/javascript/audio_driver_javascript.h | 157 | ||||
-rw-r--r-- | platform/javascript/display_server_javascript.cpp | 7 | ||||
-rw-r--r-- | platform/javascript/display_server_javascript.h | 2 | ||||
-rw-r--r-- | platform/javascript/godot_audio.h | 7 | ||||
-rw-r--r-- | platform/javascript/js/libs/audio.worklet.js | 49 | ||||
-rw-r--r-- | platform/javascript/js/libs/library_godot_audio.js | 120 | ||||
-rw-r--r-- | platform/javascript/os_javascript.cpp | 19 | ||||
-rw-r--r-- | platform/javascript/os_javascript.h | 2 | ||||
-rw-r--r-- | platform/osx/export/export_plugin.cpp | 53 | ||||
-rw-r--r-- | platform/uwp/SCsub | 2 | ||||
-rw-r--r-- | platform/uwp/app_uwp.cpp (renamed from platform/uwp/app.cpp) | 4 | ||||
-rw-r--r-- | platform/uwp/app_uwp.h (renamed from platform/uwp/app.h) | 6 | ||||
-rw-r--r-- | platform/windows/os_windows.cpp | 23 |
15 files changed, 433 insertions, 167 deletions
diff --git a/platform/javascript/.eslintrc.js b/platform/javascript/.eslintrc.js index 0ff9d67d26..2c81f1f02d 100644 --- a/platform/javascript/.eslintrc.js +++ b/platform/javascript/.eslintrc.js @@ -39,5 +39,13 @@ module.exports = { // Closure compiler (exported properties) "quote-props": ["error", "consistent"], "dot-notation": "off", + // No comma dangle for functions (it's madness, and ES2017) + "comma-dangle": ["error", { + "arrays": "always-multiline", + "objects": "always-multiline", + "imports": "always-multiline", + "exports": "always-multiline", + "functions": "never" + }], } }; diff --git a/platform/javascript/audio_driver_javascript.cpp b/platform/javascript/audio_driver_javascript.cpp index 420cb2f2f7..cfe6c69072 100644 --- a/platform/javascript/audio_driver_javascript.cpp +++ b/platform/javascript/audio_driver_javascript.cpp @@ -34,22 +34,18 @@ #include <emscripten.h> -AudioDriverJavaScript *AudioDriverJavaScript::singleton = nullptr; +AudioDriverJavaScript::AudioContext AudioDriverJavaScript::audio_context; bool AudioDriverJavaScript::is_available() { return godot_audio_is_available() != 0; } -const char *AudioDriverJavaScript::get_name() const { - return "JavaScript"; -} - void AudioDriverJavaScript::_state_change_callback(int p_state) { - singleton->state = p_state; + AudioDriverJavaScript::audio_context.state = p_state; } void AudioDriverJavaScript::_latency_update_callback(float p_latency) { - singleton->output_latency = p_latency; + AudioDriverJavaScript::audio_context.output_latency = p_latency; } void AudioDriverJavaScript::_audio_driver_process(int p_from, int p_samples) { @@ -105,17 +101,19 @@ void AudioDriverJavaScript::_audio_driver_capture(int p_from, int p_samples) { } Error AudioDriverJavaScript::init() { - mix_rate = GLOBAL_GET("audio/driver/mix_rate"); int latency = GLOBAL_GET("audio/driver/output_latency"); - - channel_count = godot_audio_init(&mix_rate, latency, &_state_change_callback, &_latency_update_callback); + if (!audio_context.inited) { + audio_context.mix_rate = GLOBAL_GET("audio/driver/mix_rate"); + audio_context.channel_count = godot_audio_init(&audio_context.mix_rate, latency, &_state_change_callback, &_latency_update_callback); + audio_context.inited = true; + } + mix_rate = audio_context.mix_rate; + channel_count = audio_context.channel_count; buffer_length = closest_power_of_2((latency * mix_rate / 1000)); -#ifndef NO_THREADS - node = memnew(WorkletNode); -#else - node = memnew(ScriptProcessorNode); -#endif - buffer_length = node->create(buffer_length, channel_count); + Error err = create(buffer_length, channel_count); + if (err != OK) { + return err; + } if (output_rb) { memdelete_arr(output_rb); } @@ -134,19 +132,17 @@ Error AudioDriverJavaScript::init() { } void AudioDriverJavaScript::start() { - if (node) { - node->start(output_rb, memarr_len(output_rb), input_rb, memarr_len(input_rb)); - } + start(output_rb, memarr_len(output_rb), input_rb, memarr_len(input_rb)); } void AudioDriverJavaScript::resume() { - if (state == 0) { // 'suspended' + if (audio_context.state == 0) { // 'suspended' godot_audio_resume(); } } float AudioDriverJavaScript::get_latency() { - return output_latency + (float(buffer_length) / mix_rate); + return audio_context.output_latency + (float(buffer_length) / mix_rate); } int AudioDriverJavaScript::get_mix_rate() const { @@ -157,24 +153,8 @@ AudioDriver::SpeakerMode AudioDriverJavaScript::get_speaker_mode() const { return get_speaker_mode_by_total_channels(channel_count); } -void AudioDriverJavaScript::lock() { - if (node) { - node->unlock(); - } -} - -void AudioDriverJavaScript::unlock() { - if (node) { - node->unlock(); - } -} - void AudioDriverJavaScript::finish() { - if (node) { - node->finish(); - memdelete(node); - node = nullptr; - } + finish_driver(); if (output_rb) { memdelete_arr(output_rb); output_rb = nullptr; @@ -203,41 +183,66 @@ Error AudioDriverJavaScript::capture_stop() { return OK; } -AudioDriverJavaScript::AudioDriverJavaScript() { - singleton = this; -} - #ifdef NO_THREADS /// ScriptProcessorNode implementation -void AudioDriverJavaScript::ScriptProcessorNode::_process_callback() { - AudioDriverJavaScript::singleton->_audio_driver_capture(); - AudioDriverJavaScript::singleton->_audio_driver_process(); +AudioDriverScriptProcessor *AudioDriverScriptProcessor::singleton = nullptr; + +void AudioDriverScriptProcessor::_process_callback() { + AudioDriverScriptProcessor::singleton->_audio_driver_capture(); + AudioDriverScriptProcessor::singleton->_audio_driver_process(); } -int AudioDriverJavaScript::ScriptProcessorNode::create(int p_buffer_samples, int p_channels) { - return godot_audio_script_create(p_buffer_samples, p_channels); +Error AudioDriverScriptProcessor::create(int &p_buffer_samples, int p_channels) { + if (!godot_audio_has_script_processor()) { + return ERR_UNAVAILABLE; + } + return (Error)godot_audio_script_create(&p_buffer_samples, p_channels); } -void AudioDriverJavaScript::ScriptProcessorNode::start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) { +void AudioDriverScriptProcessor::start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) { godot_audio_script_start(p_in_buf, p_in_buf_size, p_out_buf, p_out_buf_size, &_process_callback); } + +/// AudioWorkletNode implementation (no threads) +AudioDriverWorklet *AudioDriverWorklet::singleton = nullptr; + +Error AudioDriverWorklet::create(int &p_buffer_size, int p_channels) { + if (!godot_audio_has_worklet()) { + return ERR_UNAVAILABLE; + } + return (Error)godot_audio_worklet_create(p_channels); +} + +void AudioDriverWorklet::start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) { + _audio_driver_process(); + godot_audio_worklet_start_no_threads(p_out_buf, p_out_buf_size, &_process_callback, p_in_buf, p_in_buf_size, &_capture_callback); +} + +void AudioDriverWorklet::_process_callback(int p_pos, int p_samples) { + AudioDriverWorklet *driver = AudioDriverWorklet::singleton; + driver->_audio_driver_process(p_pos, p_samples); +} + +void AudioDriverWorklet::_capture_callback(int p_pos, int p_samples) { + AudioDriverWorklet *driver = AudioDriverWorklet::singleton; + driver->_audio_driver_capture(p_pos, p_samples); +} #else -/// AudioWorkletNode implementation -void AudioDriverJavaScript::WorkletNode::_audio_thread_func(void *p_data) { - AudioDriverJavaScript::WorkletNode *obj = static_cast<AudioDriverJavaScript::WorkletNode *>(p_data); - AudioDriverJavaScript *driver = AudioDriverJavaScript::singleton; - const int out_samples = memarr_len(driver->output_rb); - const int in_samples = memarr_len(driver->input_rb); +/// AudioWorkletNode implementation (threads) +void AudioDriverWorklet::_audio_thread_func(void *p_data) { + AudioDriverWorklet *driver = static_cast<AudioDriverWorklet *>(p_data); + const int out_samples = memarr_len(driver->get_output_rb()); + const int in_samples = memarr_len(driver->get_input_rb()); int wpos = 0; int to_write = out_samples; int rpos = 0; int to_read = 0; int32_t step = 0; - while (!obj->quit) { + while (!driver->quit) { if (to_read) { driver->lock(); driver->_audio_driver_capture(rpos, to_read); - godot_audio_worklet_state_add(obj->state, STATE_SAMPLES_IN, -to_read); + godot_audio_worklet_state_add(driver->state, STATE_SAMPLES_IN, -to_read); driver->unlock(); rpos += to_read; if (rpos >= in_samples) { @@ -247,38 +252,40 @@ void AudioDriverJavaScript::WorkletNode::_audio_thread_func(void *p_data) { if (to_write) { driver->lock(); driver->_audio_driver_process(wpos, to_write); - godot_audio_worklet_state_add(obj->state, STATE_SAMPLES_OUT, to_write); + godot_audio_worklet_state_add(driver->state, STATE_SAMPLES_OUT, to_write); driver->unlock(); wpos += to_write; if (wpos >= out_samples) { wpos -= out_samples; } } - step = godot_audio_worklet_state_wait(obj->state, STATE_PROCESS, step, 1); - to_write = out_samples - godot_audio_worklet_state_get(obj->state, STATE_SAMPLES_OUT); - to_read = godot_audio_worklet_state_get(obj->state, STATE_SAMPLES_IN); + step = godot_audio_worklet_state_wait(driver->state, STATE_PROCESS, step, 1); + to_write = out_samples - godot_audio_worklet_state_get(driver->state, STATE_SAMPLES_OUT); + to_read = godot_audio_worklet_state_get(driver->state, STATE_SAMPLES_IN); } } -int AudioDriverJavaScript::WorkletNode::create(int p_buffer_size, int p_channels) { - godot_audio_worklet_create(p_channels); - return p_buffer_size; +Error AudioDriverWorklet::create(int &p_buffer_size, int p_channels) { + if (!godot_audio_has_worklet()) { + return ERR_UNAVAILABLE; + } + return (Error)godot_audio_worklet_create(p_channels); } -void AudioDriverJavaScript::WorkletNode::start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) { +void AudioDriverWorklet::start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) { godot_audio_worklet_start(p_in_buf, p_in_buf_size, p_out_buf, p_out_buf_size, state); thread.start(_audio_thread_func, this); } -void AudioDriverJavaScript::WorkletNode::lock() { +void AudioDriverWorklet::lock() { mutex.lock(); } -void AudioDriverJavaScript::WorkletNode::unlock() { +void AudioDriverWorklet::unlock() { mutex.unlock(); } -void AudioDriverJavaScript::WorkletNode::finish() { +void AudioDriverWorklet::finish_driver() { quit = true; // Ask thread to quit. thread.wait_to_finish(); } diff --git a/platform/javascript/audio_driver_javascript.h b/platform/javascript/audio_driver_javascript.h index 393693640f..6a0b4bcb0e 100644 --- a/platform/javascript/audio_driver_javascript.h +++ b/platform/javascript/audio_driver_javascript.h @@ -38,52 +38,15 @@ #include "godot_audio.h" class AudioDriverJavaScript : public AudioDriver { -public: - class AudioNode { - public: - virtual int create(int p_buffer_size, int p_output_channels) = 0; - virtual void start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) = 0; - virtual void finish() {} - virtual void lock() {} - virtual void unlock() {} - virtual ~AudioNode() {} - }; - - class WorkletNode : public AudioNode { - private: - enum { - STATE_LOCK, - STATE_PROCESS, - STATE_SAMPLES_IN, - STATE_SAMPLES_OUT, - STATE_MAX, - }; - Mutex mutex; - Thread thread; - bool quit = false; - int32_t state[STATE_MAX] = { 0 }; - - static void _audio_thread_func(void *p_data); - - public: - int create(int p_buffer_size, int p_output_channels) override; - void start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) override; - void finish() override; - void lock() override; - void unlock() override; - }; - - class ScriptProcessorNode : public AudioNode { - private: - static void _process_callback(); - - public: - int create(int p_buffer_samples, int p_channels) override; - void start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) override; - }; - private: - AudioNode *node = nullptr; + struct AudioContext { + bool inited = false; + float output_latency = 0.0; + int state = -1; + int channel_count = 0; + int mix_rate = 0; + }; + static AudioContext audio_context; float *output_rb = nullptr; float *input_rb = nullptr; @@ -91,36 +54,108 @@ private: int buffer_length = 0; int mix_rate = 0; int channel_count = 0; - int state = 0; - float output_latency = 0.0; static void _state_change_callback(int p_state); static void _latency_update_callback(float p_latency); + static AudioDriverJavaScript *singleton; + protected: void _audio_driver_process(int p_from = 0, int p_samples = 0); void _audio_driver_capture(int p_from = 0, int p_samples = 0); + float *get_output_rb() const { return output_rb; } + float *get_input_rb() const { return input_rb; } + + virtual Error create(int &p_buffer_samples, int p_channels) = 0; + virtual void start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) = 0; + virtual void finish_driver() {} public: static bool is_available(); - static AudioDriverJavaScript *singleton; + virtual Error init() final; + virtual void start() final; + virtual void finish() final; + + virtual float get_latency() override; + virtual int get_mix_rate() const override; + virtual SpeakerMode get_speaker_mode() const override; + + virtual Error capture_start() override; + virtual Error capture_stop() override; + + static void resume(); + + AudioDriverJavaScript() {} +}; + +#ifdef NO_THREADS +class AudioDriverScriptProcessor : public AudioDriverJavaScript { +private: + static void _process_callback(); + + static AudioDriverScriptProcessor *singleton; + +protected: + Error create(int &p_buffer_samples, int p_channels) override; + void start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) override; + +public: + virtual const char *get_name() const override { return "ScriptProcessor"; } + + virtual void lock() override {} + virtual void unlock() override {} + + AudioDriverScriptProcessor() { singleton = this; } +}; + +class AudioDriverWorklet : public AudioDriverJavaScript { +private: + static void _process_callback(int p_pos, int p_samples); + static void _capture_callback(int p_pos, int p_samples); + + static AudioDriverWorklet *singleton; + +protected: + virtual Error create(int &p_buffer_size, int p_output_channels) override; + virtual void start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) override; - virtual const char *get_name() const; +public: + virtual const char *get_name() const override { return "AudioWorklet"; } + + virtual void lock() override {} + virtual void unlock() override {} - virtual Error init(); - virtual void start(); - void resume(); - virtual float get_latency(); - virtual int get_mix_rate() const; - virtual SpeakerMode get_speaker_mode() const; - virtual void lock(); - virtual void unlock(); - virtual void finish(); + AudioDriverWorklet() { singleton = this; } +}; +#else +class AudioDriverWorklet : public AudioDriverJavaScript { +private: + enum { + STATE_LOCK, + STATE_PROCESS, + STATE_SAMPLES_IN, + STATE_SAMPLES_OUT, + STATE_MAX, + }; + Mutex mutex; + Thread thread; + bool quit = false; + int32_t state[STATE_MAX] = { 0 }; - virtual Error capture_start(); - virtual Error capture_stop(); + static void _audio_thread_func(void *p_data); - AudioDriverJavaScript(); +protected: + virtual Error create(int &p_buffer_size, int p_output_channels) override; + virtual void start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) override; + virtual void finish_driver() override; + +public: + virtual const char *get_name() const override { return "AudioWorklet"; } + + void lock() override; + void unlock() override; }; #endif + +#endif diff --git a/platform/javascript/display_server_javascript.cpp b/platform/javascript/display_server_javascript.cpp index be4d2cba20..124b4ee1c8 100644 --- a/platform/javascript/display_server_javascript.cpp +++ b/platform/javascript/display_server_javascript.cpp @@ -595,6 +595,12 @@ void DisplayServerJavaScript::virtual_keyboard_hide() { godot_js_display_vk_hide(); } +// Window blur callback +EM_BOOL DisplayServerJavaScript::blur_callback(int p_event_type, const EmscriptenFocusEvent *p_event, void *p_user_data) { + Input::get_singleton()->release_pressed_events(); + return false; +} + // Gamepad void DisplayServerJavaScript::gamepad_callback(int p_index, int p_connected, const char *p_id, const char *p_guid) { Input *input = Input::get_singleton(); @@ -797,6 +803,7 @@ DisplayServerJavaScript::DisplayServerJavaScript(const String &p_rendering_drive SET_EM_CALLBACK(canvas_id, mousedown, mouse_button_callback) SET_EM_WINDOW_CALLBACK(mousemove, mousemove_callback) SET_EM_WINDOW_CALLBACK(mouseup, mouse_button_callback) + SET_EM_WINDOW_CALLBACK(blur, blur_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) diff --git a/platform/javascript/display_server_javascript.h b/platform/javascript/display_server_javascript.h index bf5e229c9a..1863ddefeb 100644 --- a/platform/javascript/display_server_javascript.h +++ b/platform/javascript/display_server_javascript.h @@ -85,6 +85,8 @@ private: static EM_BOOL touch_press_callback(int p_event_type, const EmscriptenTouchEvent *p_event, void *p_user_data); static EM_BOOL touchmove_callback(int p_event_type, const EmscriptenTouchEvent *p_event, void *p_user_data); + static EM_BOOL blur_callback(int p_event_type, const EmscriptenFocusEvent *p_event, void *p_user_data); + static void gamepad_callback(int p_index, int p_connected, const char *p_id, const char *p_guid); void process_joypads(); diff --git a/platform/javascript/godot_audio.h b/platform/javascript/godot_audio.h index de8f046bbd..eba025ab63 100644 --- a/platform/javascript/godot_audio.h +++ b/platform/javascript/godot_audio.h @@ -38,6 +38,8 @@ extern "C" { #include "stddef.h" extern int godot_audio_is_available(); +extern int godot_audio_has_worklet(); +extern int godot_audio_has_script_processor(); extern int godot_audio_init(int *p_mix_rate, int p_latency, void (*_state_cb)(int), void (*_latency_cb)(float)); extern void godot_audio_resume(); @@ -46,14 +48,15 @@ extern void godot_audio_capture_stop(); // Worklet typedef int32_t GodotAudioState[4]; -extern void godot_audio_worklet_create(int p_channels); +extern int godot_audio_worklet_create(int p_channels); extern void godot_audio_worklet_start(float *p_in_buf, int p_in_size, float *p_out_buf, int p_out_size, GodotAudioState p_state); +extern void godot_audio_worklet_start_no_threads(float *p_out_buf, int p_out_size, void (*p_out_cb)(int p_pos, int p_frames), float *p_in_buf, int p_in_size, void (*p_in_cb)(int p_pos, int p_frames)); extern int godot_audio_worklet_state_add(GodotAudioState p_state, int p_idx, int p_value); extern int godot_audio_worklet_state_get(GodotAudioState p_state, int p_idx); extern int godot_audio_worklet_state_wait(int32_t *p_state, int p_idx, int32_t p_expected, int p_timeout); // Script -extern int godot_audio_script_create(int p_buffer_size, int p_channels); +extern int godot_audio_script_create(int *p_buffer_size, int p_channels); extern void godot_audio_script_start(float *p_in_buf, int p_in_size, float *p_out_buf, int p_out_size, void (*p_cb)()); #ifdef __cplusplus diff --git a/platform/javascript/js/libs/audio.worklet.js b/platform/javascript/js/libs/audio.worklet.js index df475ba52d..52b3aedf8c 100644 --- a/platform/javascript/js/libs/audio.worklet.js +++ b/platform/javascript/js/libs/audio.worklet.js @@ -29,15 +29,16 @@ /*************************************************************************/ class RingBuffer { - constructor(p_buffer, p_state) { + constructor(p_buffer, p_state, p_threads) { this.buffer = p_buffer; this.avail = p_state; + this.threads = p_threads; this.rpos = 0; this.wpos = 0; } data_left() { - return Atomics.load(this.avail, 0); + return this.threads ? Atomics.load(this.avail, 0) : this.avail; } space_left() { @@ -55,10 +56,16 @@ class RingBuffer { to_write -= high; this.rpos = 0; } - output.set(this.buffer.subarray(this.rpos, this.rpos + to_write), from); + if (to_write) { + output.set(this.buffer.subarray(this.rpos, this.rpos + to_write), from); + } this.rpos += to_write; - Atomics.add(this.avail, 0, -output.length); - Atomics.notify(this.avail, 0); + if (this.threads) { + Atomics.add(this.avail, 0, -output.length); + Atomics.notify(this.avail, 0); + } else { + this.avail -= output.length; + } } write(p_buffer) { @@ -77,14 +84,19 @@ class RingBuffer { this.buffer.set(low); this.wpos = low.length; } - Atomics.add(this.avail, 0, to_write); - Atomics.notify(this.avail, 0); + if (this.threads) { + Atomics.add(this.avail, 0, to_write); + Atomics.notify(this.avail, 0); + } else { + this.avail += to_write; + } } } class GodotProcessor extends AudioWorkletProcessor { constructor() { super(); + this.threads = false; this.running = true; this.lock = null; this.notifier = null; @@ -100,24 +112,31 @@ class GodotProcessor extends AudioWorkletProcessor { } process_notify() { - Atomics.add(this.notifier, 0, 1); - Atomics.notify(this.notifier, 0); + if (this.notifier) { + Atomics.add(this.notifier, 0, 1); + Atomics.notify(this.notifier, 0); + } } parse_message(p_cmd, p_data) { if (p_cmd === 'start' && p_data) { const state = p_data[0]; let idx = 0; + this.threads = true; this.lock = state.subarray(idx, ++idx); this.notifier = state.subarray(idx, ++idx); const avail_in = state.subarray(idx, ++idx); const avail_out = state.subarray(idx, ++idx); - this.input = new RingBuffer(p_data[1], avail_in); - this.output = new RingBuffer(p_data[2], avail_out); + this.input = new RingBuffer(p_data[1], avail_in, true); + this.output = new RingBuffer(p_data[2], avail_out, true); } else if (p_cmd === 'stop') { this.running = false; this.output = null; this.input = null; + } else if (p_cmd === 'start_nothreads') { + this.output = new RingBuffer(p_data[0], p_data[0].length, false); + } else if (p_cmd === 'chunk') { + this.output.write(p_data); } } @@ -139,7 +158,10 @@ class GodotProcessor extends AudioWorkletProcessor { if (this.input_buffer.length !== chunk) { this.input_buffer = new Float32Array(chunk); } - if (this.input.space_left() >= chunk) { + if (!this.threads) { + GodotProcessor.write_input(this.input_buffer, input); + this.port.postMessage({ 'cmd': 'input', 'data': this.input_buffer }); + } else if (this.input.space_left() >= chunk) { GodotProcessor.write_input(this.input_buffer, input); this.input.write(this.input_buffer); } else { @@ -156,6 +178,9 @@ class GodotProcessor extends AudioWorkletProcessor { if (this.output.data_left() >= chunk) { this.output.read(this.output_buffer); GodotProcessor.write_output(output, this.output_buffer); + if (!this.threads) { + this.port.postMessage({ 'cmd': 'read', 'data': chunk }); + } } else { this.port.postMessage('Output buffer has not enough frames! Skipping output frame.'); } diff --git a/platform/javascript/js/libs/library_godot_audio.js b/platform/javascript/js/libs/library_godot_audio.js index c9dae1a7af..6cbb0567f4 100644 --- a/platform/javascript/js/libs/library_godot_audio.js +++ b/platform/javascript/js/libs/library_godot_audio.js @@ -159,6 +159,16 @@ const GodotAudio = { return 1; }, + godot_audio_has_worklet__sig: 'i', + godot_audio_has_worklet: function () { + return (GodotAudio.ctx && GodotAudio.ctx.audioWorklet) ? 1 : 0; + }, + + godot_audio_has_script_processor__sig: 'i', + godot_audio_has_script_processor: function () { + return (GodotAudio.ctx && GodotAudio.ctx.createScriptProcessor) ? 1 : 0; + }, + godot_audio_init__sig: 'iiiii', godot_audio_init: function (p_mix_rate, p_latency, p_state_change, p_latency_update) { const statechange = GodotRuntime.get_func(p_state_change); @@ -209,6 +219,7 @@ const GodotAudioWorklet = { $GodotAudioWorklet: { promise: null, worklet: null, + ring_buffer: null, create: function (channels) { const path = GodotConfig.locate_file('godot.audio.worklet.js'); @@ -218,7 +229,7 @@ const GodotAudioWorklet = { 'godot-processor', { 'outputChannelCount': [channels], - }, + } ); return Promise.resolve(); }); @@ -239,6 +250,86 @@ const GodotAudioWorklet = { }); }, + start_no_threads: function (p_out_buf, p_out_size, out_callback, p_in_buf, p_in_size, in_callback) { + function RingBuffer() { + let wpos = 0; + let rpos = 0; + let pending_samples = 0; + const wbuf = new Float32Array(p_out_size); + + function send(port) { + if (pending_samples === 0) { + return; + } + const buffer = GodotRuntime.heapSub(HEAPF32, p_out_buf, p_out_size); + const size = buffer.length; + const tot_sent = pending_samples; + out_callback(wpos, pending_samples); + if (wpos + pending_samples >= size) { + const high = size - wpos; + wbuf.set(buffer.subarray(wpos, size)); + pending_samples -= high; + wpos = 0; + } + if (pending_samples > 0) { + wbuf.set(buffer.subarray(wpos, wpos + pending_samples), tot_sent - pending_samples); + } + port.postMessage({ 'cmd': 'chunk', 'data': wbuf.subarray(0, tot_sent) }); + wpos += pending_samples; + pending_samples = 0; + } + this.receive = function (recv_buf) { + const buffer = GodotRuntime.heapSub(HEAPF32, p_in_buf, p_in_size); + const from = rpos; + let to_write = recv_buf.length; + let high = 0; + if (rpos + to_write >= p_in_size) { + high = p_in_size - rpos; + buffer.set(recv_buf.subarray(0, high), rpos); + to_write -= high; + rpos = 0; + } + if (to_write) { + buffer.set(recv_buf.subarray(high, to_write), rpos); + } + in_callback(from, recv_buf.length); + rpos += to_write; + }; + this.consumed = function (size, port) { + pending_samples += size; + send(port); + }; + } + GodotAudioWorklet.ring_buffer = new RingBuffer(); + GodotAudioWorklet.promise.then(function () { + const node = GodotAudioWorklet.worklet; + const buffer = GodotRuntime.heapSlice(HEAPF32, p_out_buf, p_out_size); + node.connect(GodotAudio.ctx.destination); + node.port.postMessage({ + 'cmd': 'start_nothreads', + 'data': [buffer, p_in_size], + }); + node.port.onmessage = function (event) { + if (!GodotAudioWorklet.worklet) { + return; + } + if (event.data['cmd'] === 'read') { + const read = event.data['data']; + GodotAudioWorklet.ring_buffer.consumed(read, GodotAudioWorklet.worklet.port); + } else if (event.data['cmd'] === 'input') { + const buf = event.data['data']; + if (buf.length > p_in_size) { + GodotRuntime.error('Input chunk is too big'); + return; + } + GodotAudioWorklet.ring_buffer.receive(buf); + } else { + GodotRuntime.error(event.data); + } + }; + }); + }, + get_node: function () { return GodotAudioWorklet.worklet; }, @@ -262,9 +353,15 @@ const GodotAudioWorklet = { }, }, - godot_audio_worklet_create__sig: 'vi', + godot_audio_worklet_create__sig: 'ii', godot_audio_worklet_create: function (channels) { - GodotAudioWorklet.create(channels); + try { + GodotAudioWorklet.create(channels); + } catch (e) { + GodotRuntime.error('Error starting AudioDriverWorklet', e); + return 1; + } + return 0; }, godot_audio_worklet_start__sig: 'viiiii', @@ -275,6 +372,13 @@ const GodotAudioWorklet = { GodotAudioWorklet.start(in_buffer, out_buffer, state); }, + godot_audio_worklet_start_no_threads__sig: 'viiiiii', + godot_audio_worklet_start_no_threads: function (p_out_buf, p_out_size, p_out_callback, p_in_buf, p_in_size, p_in_callback) { + const out_callback = GodotRuntime.get_func(p_out_callback); + const in_callback = GodotRuntime.get_func(p_in_callback); + GodotAudioWorklet.start_no_threads(p_out_buf, p_out_size, out_callback, p_in_buf, p_in_size, in_callback); + }, + godot_audio_worklet_state_wait__sig: 'iiii', godot_audio_worklet_state_wait: function (p_state, p_idx, p_expected, p_timeout) { Atomics.wait(HEAP32, (p_state >> 2) + p_idx, p_expected, p_timeout); @@ -358,7 +462,15 @@ const GodotAudioScript = { godot_audio_script_create__sig: 'iii', godot_audio_script_create: function (buffer_length, channel_count) { - return GodotAudioScript.create(buffer_length, channel_count); + const buf_len = GodotRuntime.getHeapValue(buffer_length, 'i32'); + try { + const out_len = GodotAudioScript.create(buf_len, channel_count); + GodotRuntime.setHeapValue(buffer_length, out_len, 'i32'); + } catch (e) { + GodotRuntime.error('Error starting AudioDriverScriptProcessor', e); + return 1; + } + return 0; }, godot_audio_script_start__sig: 'viiiii', diff --git a/platform/javascript/os_javascript.cpp b/platform/javascript/os_javascript.cpp index 95c5909d50..4431bd5f1b 100644 --- a/platform/javascript/os_javascript.cpp +++ b/platform/javascript/os_javascript.cpp @@ -63,9 +63,7 @@ void OS_JavaScript::initialize() { } void OS_JavaScript::resume_audio() { - if (audio_driver_javascript) { - audio_driver_javascript->resume(); - } + AudioDriverJavaScript::resume(); } void OS_JavaScript::set_main_loop(MainLoop *p_main_loop) { @@ -101,10 +99,10 @@ void OS_JavaScript::delete_main_loop() { void OS_JavaScript::finalize() { delete_main_loop(); - if (audio_driver_javascript) { - memdelete(audio_driver_javascript); - audio_driver_javascript = nullptr; + for (AudioDriverJavaScript *driver : audio_drivers) { + memdelete(driver); } + audio_drivers.clear(); } // Miscellaneous @@ -229,8 +227,13 @@ OS_JavaScript::OS_JavaScript() { setenv("LANG", locale_ptr, true); if (AudioDriverJavaScript::is_available()) { - audio_driver_javascript = memnew(AudioDriverJavaScript); - AudioDriverManager::add_driver(audio_driver_javascript); +#ifdef NO_THREADS + audio_drivers.push_back(memnew(AudioDriverScriptProcessor)); +#endif + audio_drivers.push_back(memnew(AudioDriverWorklet)); + } + for (int i = 0; i < audio_drivers.size(); i++) { + AudioDriverManager::add_driver(audio_drivers[i]); } idb_available = godot_js_os_fs_is_persistent(); diff --git a/platform/javascript/os_javascript.h b/platform/javascript/os_javascript.h index efac2dbca7..d053082d92 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 = nullptr; + List<AudioDriverJavaScript *> audio_drivers; bool idb_is_syncing = false; bool idb_available = false; diff --git a/platform/osx/export/export_plugin.cpp b/platform/osx/export/export_plugin.cpp index 54a3104482..2404c20153 100644 --- a/platform/osx/export/export_plugin.cpp +++ b/platform/osx/export/export_plugin.cpp @@ -95,6 +95,7 @@ void EditorExportPlatformOSX::get_export_options(List<ExportOption> *r_options) r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/entitlements/app_sandbox/files_pictures", PROPERTY_HINT_ENUM, "No,Read-only,Read-write"), 0)); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/entitlements/app_sandbox/files_music", PROPERTY_HINT_ENUM, "No,Read-only,Read-write"), 0)); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/entitlements/app_sandbox/files_movies", PROPERTY_HINT_ENUM, "No,Read-only,Read-write"), 0)); + r_options->push_back(ExportOption(PropertyInfo(Variant::ARRAY, "codesign/entitlements/app_sandbox/helper_executables", PROPERTY_HINT_ARRAY_TYPE, itos(Variant::STRING) + "/" + itos(PROPERTY_HINT_GLOBAL_FILE) + ":"), Array())); r_options->push_back(ExportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "codesign/custom_options"), PackedStringArray())); @@ -535,6 +536,8 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p err = ERR_CANT_CREATE; } + Array helpers = p_preset->get("codesign/entitlements/app_sandbox/helper_executables"); + // Create our folder structure. if (err == OK) { print_line("Creating " + tmp_app_path_name + "/Contents/MacOS"); @@ -546,6 +549,11 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p err = tmp_app_dir->make_dir_recursive(tmp_app_path_name + "/Contents/Frameworks"); } + if ((err == OK) && helpers.size() > 0) { + print_line("Creating " + tmp_app_path_name + "/Contents/Helpers"); + err = tmp_app_dir->make_dir_recursive(tmp_app_path_name + "/Contents/Helpers"); + } + if (err == OK) { print_line("Creating " + tmp_app_path_name + "/Contents/Resources"); err = tmp_app_dir->make_dir_recursive(tmp_app_path_name + "/Contents/Resources"); @@ -688,6 +696,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p bool sign_enabled = p_preset->get("codesign/enable"); String ent_path = p_preset->get("codesign/entitlements/custom_file"); + String hlp_ent_path = EditorPaths::get_singleton()->get_cache_dir().plus_file(pkg_name + "_helper.entitlements"); if (sign_enabled && (ent_path == "")) { ent_path = EditorPaths::get_singleton()->get_cache_dir().plus_file(pkg_name + ".entitlements"); @@ -819,10 +828,43 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p } else { err = ERR_CANT_CREATE; } + + if ((err == OK) && helpers.size() > 0) { + ent_f = FileAccess::open(hlp_ent_path, FileAccess::WRITE); + if (ent_f) { + ent_f->store_line("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); + ent_f->store_line("<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">"); + ent_f->store_line("<plist version=\"1.0\">"); + ent_f->store_line("<dict>"); + ent_f->store_line("<key>com.apple.security.app-sandbox</key>"); + ent_f->store_line("<true/>"); + ent_f->store_line("<key>com.apple.security.inherit</key>"); + ent_f->store_line("<true/>"); + ent_f->store_line("</dict>"); + ent_f->store_line("</plist>"); + + ent_f->close(); + memdelete(ent_f); + } else { + err = ERR_CANT_CREATE; + } + } + } + + if ((err == OK) && helpers.size() > 0) { + DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + for (int i = 0; i < helpers.size(); i++) { + String hlp_path = helpers[i]; + err = da->copy(hlp_path, tmp_app_path_name + "/Contents/Helpers/" + hlp_path.get_file()); + if (err == OK && sign_enabled) { + err = _code_sign(p_preset, tmp_app_path_name + "/Contents/Helpers/" + hlp_path.get_file(), hlp_ent_path); + } + FileAccess::set_unix_permissions(tmp_app_path_name + "/Contents/Helpers/" + hlp_path.get_file(), 0755); + } } if (err == OK) { - DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); for (int i = 0; i < shared_objects.size(); i++) { String src_path = ProjectSettings::get_singleton()->globalize_path(shared_objects[i].path); if (da->dir_exists(src_path)) { @@ -842,7 +884,6 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p err = _code_sign(p_preset, tmp_app_path_name + "/Contents/Frameworks/" + src_path.get_file(), ent_path); } } - memdelete(da); } if (sign_enabled) { @@ -903,6 +944,9 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p err = _notarize(p_preset, p_path); } + // Clean up temporary entitlements files. + DirAccess::remove_file_or_error(hlp_ent_path); + // Clean up temporary .app dir. tmp_app_dir->change_dir(tmp_app_path_name); tmp_app_dir->erase_contents_recursive(); @@ -916,7 +960,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p void EditorExportPlatformOSX::_zip_folder_recursive(zipFile &p_zip, const String &p_root_path, const String &p_folder, const String &p_pkg_name) { String dir = p_root_path.plus_file(p_folder); - DirAccess *da = DirAccess::open(dir); + DirAccessRef da = DirAccess::open(dir); da->list_dir_begin(); String f; while ((f = da->get_next()) != "") { @@ -967,7 +1011,7 @@ void EditorExportPlatformOSX::_zip_folder_recursive(zipFile &p_zip, const String } else if (da->current_is_dir()) { _zip_folder_recursive(p_zip, p_root_path, p_folder.plus_file(f), p_pkg_name); } else { - bool is_executable = (p_folder.ends_with("MacOS") && (f == p_pkg_name)); + bool is_executable = (p_folder.ends_with("MacOS") && (f == p_pkg_name)) || p_folder.ends_with("Helpers"); OS::Time time = OS::get_singleton()->get_time(); OS::Date date = OS::get_singleton()->get_date(); @@ -1012,7 +1056,6 @@ void EditorExportPlatformOSX::_zip_folder_recursive(zipFile &p_zip, const String } } da->list_dir_end(); - memdelete(da); } bool EditorExportPlatformOSX::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const { diff --git a/platform/uwp/SCsub b/platform/uwp/SCsub index 71c402358f..8726d32d61 100644 --- a/platform/uwp/SCsub +++ b/platform/uwp/SCsub @@ -7,7 +7,7 @@ files = [ "#platform/windows/windows_terminal_logger.cpp", "joypad_uwp.cpp", "context_egl_uwp.cpp", - "app.cpp", + "app_uwp.cpp", "os_uwp.cpp", ] diff --git a/platform/uwp/app.cpp b/platform/uwp/app_uwp.cpp index 1da17ffc5d..50e33e6c49 100644 --- a/platform/uwp/app.cpp +++ b/platform/uwp/app_uwp.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* app.cpp */ +/* app_uwp.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -32,7 +32,7 @@ // This file demonstrates how to initialize EGL in a Windows Store app, using ICoreWindow. // -#include "app.h" +#include "app_uwp.h" #include "core/io/dir_access.h" #include "core/io/file_access.h" diff --git a/platform/uwp/app.h b/platform/uwp/app_uwp.h index 0b02527dae..8d4a0b90c3 100644 --- a/platform/uwp/app.h +++ b/platform/uwp/app_uwp.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* app.h */ +/* app_uwp.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,7 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#pragma once +#ifndef APP_UWP_H +#define APP_UWP_H #include <string> @@ -111,3 +112,4 @@ namespace GodotUWP } /* clang-format on */ +#endif // APP_UWP_H diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index 2a0a509d43..78b7be8a30 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -557,8 +557,27 @@ String OS_Windows::get_stdin_string(bool p_block) { } Error OS_Windows::shell_open(String p_uri) { - ShellExecuteW(nullptr, nullptr, (LPCWSTR)(p_uri.utf16().get_data()), nullptr, nullptr, SW_SHOWNORMAL); - return OK; + INT_PTR ret = (INT_PTR)ShellExecuteW(nullptr, nullptr, (LPCWSTR)(p_uri.utf16().get_data()), nullptr, nullptr, SW_SHOWNORMAL); + if (ret > 32) { + return OK; + } else { + switch (ret) { + case ERROR_FILE_NOT_FOUND: + case SE_ERR_DLLNOTFOUND: + return ERR_FILE_NOT_FOUND; + case ERROR_PATH_NOT_FOUND: + return ERR_FILE_BAD_PATH; + case ERROR_BAD_FORMAT: + return ERR_FILE_CORRUPT; + case SE_ERR_ACCESSDENIED: + return ERR_UNAUTHORIZED; + case 0: + case SE_ERR_OOM: + return ERR_OUT_OF_MEMORY; + default: + return FAILED; + } + } } String OS_Windows::get_locale() const { |