summaryrefslogtreecommitdiff
path: root/platform
diff options
context:
space:
mode:
Diffstat (limited to 'platform')
-rw-r--r--platform/javascript/.eslintrc.js8
-rw-r--r--platform/javascript/audio_driver_javascript.cpp141
-rw-r--r--platform/javascript/audio_driver_javascript.h157
-rw-r--r--platform/javascript/display_server_javascript.cpp7
-rw-r--r--platform/javascript/display_server_javascript.h2
-rw-r--r--platform/javascript/godot_audio.h7
-rw-r--r--platform/javascript/js/libs/audio.worklet.js49
-rw-r--r--platform/javascript/js/libs/library_godot_audio.js120
-rw-r--r--platform/javascript/os_javascript.cpp19
-rw-r--r--platform/javascript/os_javascript.h2
-rw-r--r--platform/osx/export/export_plugin.cpp53
-rw-r--r--platform/uwp/SCsub2
-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.cpp23
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 {