diff options
Diffstat (limited to 'platform')
79 files changed, 1238 insertions, 700 deletions
diff --git a/platform/android/SCsub b/platform/android/SCsub index ec42bc42b5..d8013b0baf 100644 --- a/platform/android/SCsub +++ b/platform/android/SCsub @@ -6,7 +6,6 @@ android_files = [ "os_android.cpp", "file_access_android.cpp", "audio_driver_opensl.cpp", - "file_access_jandroid.cpp", "dir_access_jandroid.cpp", "thread_jandroid.cpp", "net_socket_android.cpp", @@ -14,6 +13,7 @@ android_files = [ "java_godot_lib_jni.cpp", "java_class_wrapper.cpp", "java_godot_wrapper.cpp", + "java_godot_view_wrapper.cpp", "java_godot_io_wrapper.cpp", "jni_utils.cpp", "android_keys_utils.cpp", diff --git a/platform/android/android_keys_utils.h b/platform/android/android_keys_utils.h index a10afa1df8..4a34e77324 100644 --- a/platform/android/android_keys_utils.h +++ b/platform/android/android_keys_utils.h @@ -35,8 +35,8 @@ #include <core/os/keyboard.h> struct _WinTranslatePair { - unsigned int keysym; - unsigned int keycode; + unsigned int keysym = 0; + unsigned int keycode = 0; }; static _WinTranslatePair _ak_to_keycode[] = { diff --git a/platform/android/api/java_class_wrapper.h b/platform/android/api/java_class_wrapper.h index 64da049407..63d71f5cf1 100644 --- a/platform/android/api/java_class_wrapper.h +++ b/platform/android/api/java_class_wrapper.h @@ -66,10 +66,10 @@ class JavaClass : public Reference { Map<StringName, Variant> constant_map; struct MethodInfo { - bool _static; + bool _static = false; Vector<uint32_t> param_types; Vector<StringName> param_sigs; - uint32_t return_type; + uint32_t return_type = 0; jmethodID method; }; diff --git a/platform/android/audio_driver_opensl.cpp b/platform/android/audio_driver_opensl.cpp index 740e9a3132..e96e80e967 100644 --- a/platform/android/audio_driver_opensl.cpp +++ b/platform/android/audio_driver_opensl.cpp @@ -339,6 +339,4 @@ void AudioDriverOpenSL::set_pause(bool p_pause) { AudioDriverOpenSL::AudioDriverOpenSL() { s_ad = this; - pause = false; - active = false; } diff --git a/platform/android/audio_driver_opensl.h b/platform/android/audio_driver_opensl.h index b30711705b..999cbe4657 100644 --- a/platform/android/audio_driver_opensl.h +++ b/platform/android/audio_driver_opensl.h @@ -38,19 +38,19 @@ #include <SLES/OpenSLES_Android.h> class AudioDriverOpenSL : public AudioDriver { - bool active; + bool active = false; Mutex mutex; enum { BUFFER_COUNT = 2 }; - bool pause; + bool pause = false; - uint32_t buffer_size; - int16_t *buffers[BUFFER_COUNT]; - int32_t *mixdown_buffer; - int last_free; + uint32_t buffer_size = 0; + int16_t *buffers[BUFFER_COUNT] = {}; + int32_t *mixdown_buffer = nullptr; + int last_free = 0; Vector<int16_t> rec_buffer; diff --git a/platform/android/detect.py b/platform/android/detect.py index 0accacb679..650606ff8b 100644 --- a/platform/android/detect.py +++ b/platform/android/detect.py @@ -215,7 +215,7 @@ def configure(env): env.Append(CPPFLAGS=["-isystem", env["ANDROID_NDK_ROOT"] + "/sources/cxx-stl/llvm-libc++abi/include"]) # Disable exceptions and rtti on non-tools (template) builds - if env["tools"]: + if env["tools"] or env["builtin_icu"]: env.Append(CXXFLAGS=["-frtti"]) else: env.Append(CXXFLAGS=["-fno-rtti", "-fno-exceptions"]) @@ -334,6 +334,6 @@ def get_ndk_version(path): key_value = list(map(lambda x: x.strip(), line.split("="))) if key_value[0] == "Pkg.Revision": return key_value[1] - except: + except Exception: print("Could not read source prop file '%s'" % prop_file_path) return None diff --git a/platform/android/dir_access_jandroid.cpp b/platform/android/dir_access_jandroid.cpp index ba75a4b10c..ac619973d2 100644 --- a/platform/android/dir_access_jandroid.cpp +++ b/platform/android/dir_access_jandroid.cpp @@ -30,7 +30,7 @@ #include "dir_access_jandroid.h" #include "core/string/print_string.h" -#include "file_access_jandroid.h" +#include "file_access_android.h" #include "string_android.h" #include "thread_jandroid.h" @@ -146,7 +146,7 @@ bool DirAccessJAndroid::file_exists(String p_file) { else sd = current_dir.plus_file(p_file); - FileAccessJAndroid *f = memnew(FileAccessJAndroid); + FileAccessAndroid *f = memnew(FileAccessAndroid); bool exists = f->file_exists(sd); memdelete(f); diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp index e82a12ece5..8711a4333c 100644 --- a/platform/android/display_server_android.cpp +++ b/platform/android/display_server_android.cpp @@ -41,7 +41,7 @@ #if defined(VULKAN_ENABLED) #include "drivers/vulkan/rendering_device_vulkan.h" #include "platform/android/vulkan/vulkan_context_android.h" -#include "servers/rendering/rasterizer_rd/rasterizer_rd.h" +#include "servers/rendering/renderer_rd/renderer_compositor_rd.h" #endif DisplayServerAndroid *DisplayServerAndroid::get_singleton() { @@ -447,7 +447,7 @@ DisplayServerAndroid::DisplayServerAndroid(const String &p_rendering_driver, Dis rendering_device_vulkan = memnew(RenderingDeviceVulkan); rendering_device_vulkan->initialize(context_vulkan); - RasterizerRD::make_current(); + RendererCompositorRD::make_current(); } #endif @@ -498,9 +498,28 @@ void DisplayServerAndroid::_set_key_modifier_state(Ref<InputEventWithModifiers> } void DisplayServerAndroid::process_key_event(int p_keycode, int p_scancode, int p_unicode_char, bool p_pressed) { + static char32_t prev_wc = 0; + char32_t unicode = p_unicode_char; + if ((p_unicode_char & 0xfffffc00) == 0xd800) { + if (prev_wc != 0) { + ERR_PRINT("invalid utf16 surrogate input"); + } + prev_wc = unicode; + return; // Skip surrogate. + } else if ((unicode & 0xfffffc00) == 0xdc00) { + if (prev_wc == 0) { + ERR_PRINT("invalid utf16 surrogate input"); + return; // Skip invalid surrogate. + } + unicode = (prev_wc << 10UL) + unicode - ((0xd800 << 10UL) + 0xdc00 - 0x10000); + prev_wc = 0; + } else { + prev_wc = 0; + } + Ref<InputEventKey> ev; ev.instance(); - int val = p_unicode_char; + int val = unicode; int keycode = android_get_keysym(p_keycode); int phy_keycode = android_get_keysym(p_scancode); @@ -667,7 +686,7 @@ void DisplayServerAndroid::process_hover(int p_type, Point2 p_pos) { } } -void DisplayServerAndroid::process_mouse_event(int event_action, int event_android_buttons_mask, Point2 event_pos, float event_vertical_factor, float event_horizontal_factor) { +void DisplayServerAndroid::process_mouse_event(int input_device, int event_action, int event_android_buttons_mask, Point2 event_pos, float event_vertical_factor, float event_horizontal_factor) { int event_buttons_mask = _android_button_mask_to_godot_button_mask(event_android_buttons_mask); switch (event_action) { case AMOTION_EVENT_ACTION_BUTTON_PRESS: @@ -675,8 +694,13 @@ void DisplayServerAndroid::process_mouse_event(int event_action, int event_andro Ref<InputEventMouseButton> ev; ev.instance(); _set_key_modifier_state(ev); - ev->set_position(event_pos); - ev->set_global_position(event_pos); + if ((input_device & AINPUT_SOURCE_MOUSE) == AINPUT_SOURCE_MOUSE) { + ev->set_position(event_pos); + ev->set_global_position(event_pos); + } else { + ev->set_position(hover_prev_pos); + ev->set_global_position(hover_prev_pos); + } ev->set_pressed(event_action == AMOTION_EVENT_ACTION_BUTTON_PRESS); int changed_button_mask = buttons_state ^ event_buttons_mask; @@ -691,18 +715,29 @@ void DisplayServerAndroid::process_mouse_event(int event_action, int event_andro Ref<InputEventMouseMotion> ev; ev.instance(); _set_key_modifier_state(ev); - ev->set_position(event_pos); - ev->set_global_position(event_pos); - ev->set_relative(event_pos - hover_prev_pos); + if ((input_device & AINPUT_SOURCE_MOUSE) == AINPUT_SOURCE_MOUSE) { + ev->set_position(event_pos); + ev->set_global_position(event_pos); + ev->set_relative(event_pos - hover_prev_pos); + hover_prev_pos = event_pos; + } else { + ev->set_position(hover_prev_pos); + ev->set_global_position(hover_prev_pos); + ev->set_relative(event_pos); + } ev->set_button_mask(event_buttons_mask); Input::get_singleton()->accumulate_input_event(ev); - hover_prev_pos = event_pos; } break; case AMOTION_EVENT_ACTION_SCROLL: { Ref<InputEventMouseButton> ev; ev.instance(); - ev->set_position(event_pos); - ev->set_global_position(event_pos); + if ((input_device & AINPUT_SOURCE_MOUSE) == AINPUT_SOURCE_MOUSE) { + ev->set_position(event_pos); + ev->set_global_position(event_pos); + } else { + ev->set_position(hover_prev_pos); + ev->set_global_position(hover_prev_pos); + } ev->set_pressed(true); buttons_state = event_buttons_mask; if (event_vertical_factor > 0) { @@ -790,6 +825,24 @@ void DisplayServerAndroid::process_gyroscope(const Vector3 &p_gyroscope) { Input::get_singleton()->set_gyroscope(p_gyroscope); } +void DisplayServerAndroid::mouse_set_mode(MouseMode p_mode) { + if (mouse_mode == p_mode) { + return; + } + + if (p_mode == MouseMode::MOUSE_MODE_CAPTURED) { + OS_Android::get_singleton()->get_godot_java()->get_godot_view()->request_pointer_capture(); + } else { + OS_Android::get_singleton()->get_godot_java()->get_godot_view()->release_pointer_capture(); + } + + mouse_mode = p_mode; +} + +DisplayServer::MouseMode DisplayServerAndroid::mouse_get_mode() const { + return mouse_mode; +} + Point2i DisplayServerAndroid::mouse_get_position() const { return hover_prev_pos; } diff --git a/platform/android/display_server_android.h b/platform/android/display_server_android.h index aa5a2c1185..f1f1a6a278 100644 --- a/platform/android/display_server_android.h +++ b/platform/android/display_server_android.h @@ -41,7 +41,7 @@ class RenderingDeviceVulkan; class DisplayServerAndroid : public DisplayServer { public: struct TouchPos { - int id; + int id = 0; Point2 pos; }; @@ -52,12 +52,12 @@ public: }; struct JoypadEvent { - int device; - int type; - int index; - bool pressed; - float value; - int hat; + int device = 0; + int type = 0; + int index = 0; + bool pressed = false; + float value = 0; + int hat = 0; }; private: @@ -70,6 +70,8 @@ private: int buttons_state; + MouseMode mouse_mode; + bool keep_screen_on; Vector<TouchPos> touch; @@ -172,12 +174,15 @@ public: void process_gyroscope(const Vector3 &p_gyroscope); void process_touch(int p_event, int p_pointer, const Vector<TouchPos> &p_points); void process_hover(int p_type, Point2 p_pos); - void process_mouse_event(int event_action, int event_android_buttons_mask, Point2 event_pos, float event_vertical_factor = 0, float event_horizontal_factor = 0); + void process_mouse_event(int input_device, int event_action, int event_android_buttons_mask, Point2 event_pos, float event_vertical_factor = 0, float event_horizontal_factor = 0); void process_double_tap(int event_android_button_mask, Point2 p_pos); void process_scroll(Point2 p_pos); void process_joy_event(JoypadEvent p_event); void process_key_event(int p_keycode, int p_scancode, int p_unicode_char, bool p_pressed); + void mouse_set_mode(MouseMode p_mode); + MouseMode mouse_get_mode() const; + static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error); static Vector<String> get_rendering_drivers_func(); static void register_android_driver(); diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp index d24c96f87a..eed3b226c8 100644 --- a/platform/android/export/export.cpp +++ b/platform/android/export/export.cpp @@ -205,7 +205,7 @@ static const char *SPLASH_BG_COLOR_PATH = "res/drawable/splash_bg_color.png"; struct LauncherIcon { const char *export_path; - int dimensions; + int dimensions = 0; }; static const int icon_densities_count = 6; @@ -250,12 +250,12 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { String id; String name; String description; - int api_level; + int api_level = 0; }; struct APKExportData { zipFile apk; - EditorProgress *ep; + EditorProgress *ep = nullptr; }; Vector<PluginConfig> plugins; diff --git a/platform/android/file_access_android.cpp b/platform/android/file_access_android.cpp index 2446ca2829..0d933fb858 100644 --- a/platform/android/file_access_android.cpp +++ b/platform/android/file_access_android.cpp @@ -157,11 +157,6 @@ bool FileAccessAndroid::file_exists(const String &p_path) { return true; } -FileAccessAndroid::FileAccessAndroid() { - a = nullptr; - eof = false; -} - FileAccessAndroid::~FileAccessAndroid() { close(); } diff --git a/platform/android/file_access_android.h b/platform/android/file_access_android.h index a347c63ffb..7fc7d8c83d 100644 --- a/platform/android/file_access_android.h +++ b/platform/android/file_access_android.h @@ -39,10 +39,10 @@ class FileAccessAndroid : public FileAccess { static FileAccess *create_android(); - mutable AAsset *a; - mutable size_t len; - mutable size_t pos; - mutable bool eof; + mutable AAsset *a = nullptr; + mutable size_t len = 0; + mutable size_t pos = 0; + mutable bool eof = false; public: static AAssetManager *asset_manager; @@ -74,7 +74,6 @@ public: //static void make_default(); - FileAccessAndroid(); ~FileAccessAndroid(); }; diff --git a/platform/android/file_access_jandroid.cpp b/platform/android/file_access_jandroid.cpp deleted file mode 100644 index df8b57fd3a..0000000000 --- a/platform/android/file_access_jandroid.cpp +++ /dev/null @@ -1,197 +0,0 @@ -/*************************************************************************/ -/* file_access_jandroid.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "file_access_jandroid.h" -#include "core/os/os.h" -#include "thread_jandroid.h" -#include <unistd.h> - -jobject FileAccessJAndroid::io = nullptr; -jclass FileAccessJAndroid::cls; -jmethodID FileAccessJAndroid::_file_open = 0; -jmethodID FileAccessJAndroid::_file_get_size = 0; -jmethodID FileAccessJAndroid::_file_seek = 0; -jmethodID FileAccessJAndroid::_file_read = 0; -jmethodID FileAccessJAndroid::_file_tell = 0; -jmethodID FileAccessJAndroid::_file_eof = 0; -jmethodID FileAccessJAndroid::_file_close = 0; - -FileAccess *FileAccessJAndroid::create_jandroid() { - return memnew(FileAccessJAndroid); -} - -Error FileAccessJAndroid::_open(const String &p_path, int p_mode_flags) { - if (is_open()) - close(); - - String path = fix_path(p_path).simplify_path(); - if (path.begins_with("/")) - path = path.substr(1, path.length()); - else if (path.begins_with("res://")) - path = path.substr(6, path.length()); - - JNIEnv *env = ThreadAndroid::get_env(); - - jstring js = env->NewStringUTF(path.utf8().get_data()); - int res = env->CallIntMethod(io, _file_open, js, (p_mode_flags & WRITE) ? true : false); - env->DeleteLocalRef(js); - - OS::get_singleton()->print("fopen: '%s' ret %i\n", path.utf8().get_data(), res); - - if (res <= 0) - return ERR_FILE_CANT_OPEN; - id = res; - - return OK; -} - -void FileAccessJAndroid::close() { - if (!is_open()) - return; - - JNIEnv *env = ThreadAndroid::get_env(); - - env->CallVoidMethod(io, _file_close, id); - id = 0; -} - -bool FileAccessJAndroid::is_open() const { - return id != 0; -} - -void FileAccessJAndroid::seek(size_t p_position) { - JNIEnv *env = ThreadAndroid::get_env(); - - ERR_FAIL_COND_MSG(!is_open(), "File must be opened before use."); - env->CallVoidMethod(io, _file_seek, id, p_position); -} - -void FileAccessJAndroid::seek_end(int64_t p_position) { - ERR_FAIL_COND_MSG(!is_open(), "File must be opened before use."); - - seek(get_len()); -} - -size_t FileAccessJAndroid::get_position() const { - JNIEnv *env = ThreadAndroid::get_env(); - ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use."); - return env->CallIntMethod(io, _file_tell, id); -} - -size_t FileAccessJAndroid::get_len() const { - JNIEnv *env = ThreadAndroid::get_env(); - ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use."); - return env->CallIntMethod(io, _file_get_size, id); -} - -bool FileAccessJAndroid::eof_reached() const { - JNIEnv *env = ThreadAndroid::get_env(); - ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use."); - return env->CallIntMethod(io, _file_eof, id); -} - -uint8_t FileAccessJAndroid::get_8() const { - ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use."); - uint8_t byte; - get_buffer(&byte, 1); - return byte; -} - -int FileAccessJAndroid::get_buffer(uint8_t *p_dst, int p_length) const { - ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use."); - if (p_length == 0) - return 0; - JNIEnv *env = ThreadAndroid::get_env(); - - jbyteArray jca = (jbyteArray)env->CallObjectMethod(io, _file_read, id, p_length); - - int len = env->GetArrayLength(jca); - env->GetByteArrayRegion(jca, 0, len, (jbyte *)p_dst); - env->DeleteLocalRef((jobject)jca); - - return len; -} - -Error FileAccessJAndroid::get_error() const { - if (eof_reached()) - return ERR_FILE_EOF; - return OK; -} - -void FileAccessJAndroid::flush() { -} - -void FileAccessJAndroid::store_8(uint8_t p_dest) { -} - -bool FileAccessJAndroid::file_exists(const String &p_path) { - JNIEnv *env = ThreadAndroid::get_env(); - - String path = fix_path(p_path).simplify_path(); - if (path.begins_with("/")) - path = path.substr(1, path.length()); - else if (path.begins_with("res://")) - path = path.substr(6, path.length()); - - jstring js = env->NewStringUTF(path.utf8().get_data()); - int res = env->CallIntMethod(io, _file_open, js, false); - if (res <= 0) { - env->DeleteLocalRef(js); - return false; - } - env->CallVoidMethod(io, _file_close, res); - env->DeleteLocalRef(js); - return true; -} - -void FileAccessJAndroid::setup(jobject p_io) { - io = p_io; - JNIEnv *env = ThreadAndroid::get_env(); - - jclass c = env->GetObjectClass(io); - cls = (jclass)env->NewGlobalRef(c); - - _file_open = env->GetMethodID(cls, "file_open", "(Ljava/lang/String;Z)I"); - _file_get_size = env->GetMethodID(cls, "file_get_size", "(I)I"); - _file_tell = env->GetMethodID(cls, "file_tell", "(I)I"); - _file_eof = env->GetMethodID(cls, "file_eof", "(I)Z"); - _file_seek = env->GetMethodID(cls, "file_seek", "(II)V"); - _file_read = env->GetMethodID(cls, "file_read", "(II)[B"); - _file_close = env->GetMethodID(cls, "file_close", "(I)V"); -} - -FileAccessJAndroid::FileAccessJAndroid() { - id = 0; -} - -FileAccessJAndroid::~FileAccessJAndroid() { - if (is_open()) - close(); -} diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.java b/platform/android/java/lib/src/org/godotengine/godot/Godot.java index 3bbe35091c..ad1dc53bc0 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.java +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.java @@ -988,4 +988,9 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC public void initInputDevices() { mRenderView.initInputDevices(); } + + @Keep + private GodotRenderView getRenderView() { // used by native side to get renderView + return mRenderView; + } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java index d731e080c4..2cd67933ee 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java @@ -144,6 +144,11 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView return inputHandler.onGenericMotionEvent(event) || super.onGenericMotionEvent(event); } + @Override + public boolean onCapturedPointerEvent(MotionEvent event) { + return inputHandler.onGenericMotionEvent(event); + } + private void init(XRMode xrMode, boolean translucent, int depth, int stencil) { setPreserveEGLContextOnPause(true); setFocusableInTouchMode(true); diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java index 6cd5ca7b4e..d5e0345a9c 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java @@ -120,6 +120,11 @@ public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderV } @Override + public boolean onCapturedPointerEvent(MotionEvent event) { + return mInputHandler.onGenericMotionEvent(event); + } + + @Override public void onResume() { super.onResume(); diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java index f3e985f944..b052cd9d92 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java +++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java @@ -181,6 +181,7 @@ public class GodotInputHandler implements InputDeviceListener { arr[i * 3 + 2] = event.getY(i); } final int action = event.getActionMasked(); + final int pointer_idx = event.getPointerId(event.getActionIndex()); mRenderView.queueOnRenderThread(new Runnable() { @Override @@ -189,12 +190,9 @@ public class GodotInputHandler implements InputDeviceListener { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_MOVE: { - GodotLib.touch(event.getSource(), action, 0, evcount, arr); - } break; + case MotionEvent.ACTION_MOVE: case MotionEvent.ACTION_POINTER_UP: case MotionEvent.ACTION_POINTER_DOWN: { - int pointer_idx = event.getPointerId(event.getActionIndex()); GodotLib.touch(event.getSource(), action, pointer_idx, evcount, arr); } break; } @@ -247,7 +245,7 @@ public class GodotInputHandler implements InputDeviceListener { } }); return true; - } else if ((event.getSource() & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) { + } else if (event.isFromSource(InputDevice.SOURCE_MOUSE) || event.isFromSource(InputDevice.SOURCE_MOUSE_RELATIVE)) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { return handleMouseEvent(event); } @@ -464,6 +462,11 @@ public class GodotInputHandler implements InputDeviceListener { } }); } + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_UP: { + // we can safely ignore these cases because they are always come beside ACTION_BUTTON_PRESS and ACTION_BUTTON_RELEASE + return true; + } } return false; } diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp index 5dc773fae2..48b8171222 100644 --- a/platform/android/java_godot_lib_jni.cpp +++ b/platform/android/java_godot_lib_jni.cpp @@ -43,7 +43,6 @@ #include "dir_access_jandroid.h" #include "display_server_android.h" #include "file_access_android.h" -#include "file_access_jandroid.h" #include "jni_utils.h" #include "main/main.h" #include "net_socket_android.h" @@ -89,14 +88,10 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *en godot_io_java = new GodotIOJavaWrapper(env, godot_java->get_member_object("io", "Lorg/godotengine/godot/GodotIO;", env)); ThreadAndroid::make_default(jvm); -#ifdef USE_JAVA_FILE_ACCESS - FileAccessJAndroid::setup(godot_io_java->get_instance()); -#else jobject amgr = env->NewGlobalRef(p_asset_manager); FileAccessAndroid::asset_manager = AAssetManager_fromJava(env, amgr); -#endif DirAccessJAndroid::setup(godot_io_java->get_instance()); AudioDriverAndroid::setup(godot_io_java->get_instance()); @@ -251,9 +246,8 @@ void touch_preprocessing(JNIEnv *env, jclass clazz, jint input_device, jint ev, tp.id = (int)p[0]; points.push_back(tp); } - - if ((input_device & AINPUT_SOURCE_MOUSE) == AINPUT_SOURCE_MOUSE) { - DisplayServerAndroid::get_singleton()->process_mouse_event(ev, buttons_mask, points[0].pos, vertical_factor, horizontal_factor); + if ((input_device & AINPUT_SOURCE_MOUSE) == AINPUT_SOURCE_MOUSE || (input_device & AINPUT_SOURCE_MOUSE_RELATIVE) == AINPUT_SOURCE_MOUSE_RELATIVE) { + DisplayServerAndroid::get_singleton()->process_mouse_event(input_device, ev, buttons_mask, points[0].pos, vertical_factor, horizontal_factor); } else { DisplayServerAndroid::get_singleton()->process_touch(ev, pointer, points); } diff --git a/platform/android/java_godot_view_wrapper.cpp b/platform/android/java_godot_view_wrapper.cpp new file mode 100644 index 0000000000..6655dd9895 --- /dev/null +++ b/platform/android/java_godot_view_wrapper.cpp @@ -0,0 +1,66 @@ +/*************************************************************************/ +/* java_godot_view_wrapper.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "java_godot_view_wrapper.h" + +#include "thread_jandroid.h" + +GodotJavaViewWrapper::GodotJavaViewWrapper(jobject godot_view) { + JNIEnv *env = ThreadAndroid::get_env(); + + _godot_view = env->NewGlobalRef(godot_view); + + _cls = (jclass)env->NewGlobalRef(env->GetObjectClass(godot_view)); + + if (android_get_device_api_level() >= __ANDROID_API_O__) { + _request_pointer_capture = env->GetMethodID(_cls, "requestPointerCapture", "()V"); + _release_pointer_capture = env->GetMethodID(_cls, "releasePointerCapture", "()V"); + } +} + +void GodotJavaViewWrapper::request_pointer_capture() { + if (_request_pointer_capture != 0) { + JNIEnv *env = ThreadAndroid::get_env(); + env->CallVoidMethod(_godot_view, _request_pointer_capture); + } +} + +void GodotJavaViewWrapper::release_pointer_capture() { + if (_request_pointer_capture != 0) { + JNIEnv *env = ThreadAndroid::get_env(); + env->CallVoidMethod(_godot_view, _release_pointer_capture); + } +} + +GodotJavaViewWrapper::~GodotJavaViewWrapper() { + JNIEnv *env = ThreadAndroid::get_env(); + env->DeleteGlobalRef(_godot_view); + env->DeleteGlobalRef(_cls); +} diff --git a/platform/android/file_access_jandroid.h b/platform/android/java_godot_view_wrapper.h index e252a4d3ac..4c8f6edad0 100644 --- a/platform/android/file_access_jandroid.h +++ b/platform/android/java_godot_view_wrapper.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* file_access_jandroid.h */ +/* java_godot_view_wrapper.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,56 +28,29 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef FILE_ACCESS_JANDROID_H -#define FILE_ACCESS_JANDROID_H +#ifndef GODOT_JAVA_GODOT_VIEW_WRAPPER_H +#define GODOT_JAVA_GODOT_VIEW_WRAPPER_H -#include "core/os/file_access.h" -#include "java_godot_lib_jni.h" -class FileAccessJAndroid : public FileAccess { - static jobject io; - static jclass cls; +#include <android/log.h> +#include <jni.h> - static jmethodID _file_open; - static jmethodID _file_get_size; - static jmethodID _file_seek; - static jmethodID _file_tell; - static jmethodID _file_eof; - static jmethodID _file_read; - static jmethodID _file_close; +// Class that makes functions in java/src/org/godotengine/godot/GodotView.java callable from C++ +class GodotJavaViewWrapper { +private: + jclass _cls; - int id; - static FileAccess *create_jandroid(); + jobject _godot_view; -public: - virtual Error _open(const String &p_path, int p_mode_flags); ///< open a file - virtual void close(); ///< close a file - virtual bool is_open() const; ///< true when file is open - - virtual void seek(size_t p_position); ///< seek to a given position - virtual void seek_end(int64_t p_position = 0); ///< seek from the end of file - virtual size_t get_position() const; ///< get position in the file - virtual size_t get_len() const; ///< get size of the file - - virtual bool eof_reached() const; ///< reading passed EOF - - virtual uint8_t get_8() const; ///< get a byte - virtual int get_buffer(uint8_t *p_dst, int p_length) const; + jmethodID _request_pointer_capture = 0; + jmethodID _release_pointer_capture = 0; - virtual Error get_error() const; ///< get last error - - virtual void flush(); - virtual void store_8(uint8_t p_dest); ///< store a byte - - virtual bool file_exists(const String &p_path); ///< return true if a file exists - - static void setup(jobject p_io); +public: + GodotJavaViewWrapper(jobject godot_view); - virtual uint64_t _get_modified_time(const String &p_file) { return 0; } - virtual uint32_t _get_unix_permissions(const String &p_file) { return 0; } - virtual Error _set_unix_permissions(const String &p_file, uint32_t p_permissions) { return FAILED; } + void request_pointer_capture(); + void release_pointer_capture(); - FileAccessJAndroid(); - ~FileAccessJAndroid(); + ~GodotJavaViewWrapper(); }; -#endif // FILE_ACCESS_JANDROID_H +#endif //GODOT_JAVA_GODOT_VIEW_WRAPPER_H diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp index cff591d903..7919e47b5c 100644 --- a/platform/android/java_godot_wrapper.cpp +++ b/platform/android/java_godot_wrapper.cpp @@ -109,6 +109,16 @@ jobject GodotJavaWrapper::get_class_loader() { } } +GodotJavaViewWrapper *GodotJavaWrapper::get_godot_view() { + if (_godot_view != nullptr) { + return _godot_view; + } + JNIEnv *env = ThreadAndroid::get_env(); + jmethodID godot_view_getter = env->GetMethodID(godot_class, "getRenderView", "()Lorg/godotengine/godot/GodotRenderView;"); + _godot_view = new GodotJavaViewWrapper(env->CallObjectMethod(godot_instance, godot_view_getter)); + return _godot_view; +} + void GodotJavaWrapper::on_video_init(JNIEnv *p_env) { if (_on_video_init) if (p_env == nullptr) diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h index e0c3809a64..c212e107cb 100644 --- a/platform/android/java_godot_wrapper.h +++ b/platform/android/java_godot_wrapper.h @@ -37,6 +37,7 @@ #include <android/log.h> #include <jni.h> +#include "java_godot_view_wrapper.h" #include "string_android.h" // Class that makes functions in java/src/org/godotengine/godot/Godot.java callable from C++ @@ -47,6 +48,8 @@ private: jclass godot_class; jclass activity_class; + GodotJavaViewWrapper *_godot_view = nullptr; + jmethodID _on_video_init = 0; jmethodID _restart = 0; jmethodID _finish = 0; @@ -74,6 +77,7 @@ public: jobject get_member_object(const char *p_name, const char *p_class, JNIEnv *p_env = nullptr); jobject get_class_loader(); + GodotJavaViewWrapper *get_godot_view(); void on_video_init(JNIEnv *p_env = nullptr); void on_godot_main_loop_started(JNIEnv *p_env = nullptr); diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp index 00733f6dbb..b90fb3ce6e 100644 --- a/platform/android/os_android.cpp +++ b/platform/android/os_android.cpp @@ -31,15 +31,13 @@ #include "os_android.h" #include "core/config/project_settings.h" -#include "core/io/file_access_buffered_fa.h" #include "drivers/unix/dir_access_unix.h" #include "drivers/unix/file_access_unix.h" -#include "file_access_android.h" #include "main/main.h" #include "platform/android/display_server_android.h" #include "dir_access_jandroid.h" -#include "file_access_jandroid.h" +#include "file_access_android.h" #include "net_socket_android.h" #include <dlfcn.h> @@ -62,16 +60,10 @@ void OS_Android::initialize_core() { if (use_apk_expansion) FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_RESOURCES); else { -#ifdef USE_JAVA_FILE_ACCESS - FileAccess::make_default<FileAccessBufferedFA<FileAccessJAndroid>>(FileAccess::ACCESS_RESOURCES); -#else - //FileAccess::make_default<FileAccessBufferedFA<FileAccessAndroid> >(FileAccess::ACCESS_RESOURCES); FileAccess::make_default<FileAccessAndroid>(FileAccess::ACCESS_RESOURCES); -#endif } FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_USERDATA); FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_FILESYSTEM); - //FileAccessBufferedFA<FileAccessUnix>::make_default(); if (use_apk_expansion) DirAccess::make_default<DirAccessUnix>(DirAccess::ACCESS_RESOURCES); else @@ -314,6 +306,7 @@ OS_Android::OS_Android(GodotJavaWrapper *p_godot_java, GodotIOJavaWrapper *p_god #if defined(OPENGL_ENABLED) gl_extensions = nullptr; use_gl2 = false; + use_16bits_fbo = false; #endif #if defined(VULKAN_ENABLED) diff --git a/platform/iphone/display_server_iphone.h b/platform/iphone/display_server_iphone.h index 229b1e80db..2d77e9adff 100644 --- a/platform/iphone/display_server_iphone.h +++ b/platform/iphone/display_server_iphone.h @@ -36,7 +36,7 @@ #if defined(VULKAN_ENABLED) #include "drivers/vulkan/rendering_device_vulkan.h" -#include "servers/rendering/rasterizer_rd/rasterizer_rd.h" +#include "servers/rendering/renderer_rd/renderer_compositor_rd.h" #include "vulkan_context_iphone.h" diff --git a/platform/iphone/display_server_iphone.mm b/platform/iphone/display_server_iphone.mm index d47d131719..e8abf1e3fa 100644 --- a/platform/iphone/display_server_iphone.mm +++ b/platform/iphone/display_server_iphone.mm @@ -73,7 +73,7 @@ DisplayServerIPhone::DisplayServerIPhone(const String &p_rendering_driver, Displ // return ERR_UNAVAILABLE; } - // rendering_server = memnew(RenderingServerRaster); + // rendering_server = memnew(RenderingServerDefault); // // FIXME: Reimplement threaded rendering // if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) { // rendering_server = memnew(RenderingServerWrapMT(rendering_server, @@ -118,7 +118,7 @@ DisplayServerIPhone::DisplayServerIPhone(const String &p_rendering_driver, Displ rendering_device_vulkan = memnew(RenderingDeviceVulkan); rendering_device_vulkan->initialize(context_vulkan); - RasterizerRD::make_current(); + RendererCompositorRD::make_current(); } #endif diff --git a/platform/iphone/export/export.cpp b/platform/iphone/export/export.cpp index fff82c9467..3406c75c35 100644 --- a/platform/iphone/export/export.cpp +++ b/platform/iphone/export/export.cpp @@ -94,8 +94,8 @@ class EditorExportPlatformIOS : public EditorExportPlatform { struct IOSExportAsset { String exported_path; - bool is_framework; // framework is anything linked to the binary, otherwise it's a resource - bool should_embed; + bool is_framework = false; // framework is anything linked to the binary, otherwise it's a resource + bool should_embed = false; }; String _get_additional_plist_content(); @@ -314,9 +314,9 @@ Vector<EditorExportPlatformIOS::ExportArchitecture> EditorExportPlatformIOS::_ge struct LoadingScreenInfo { const char *preset_key; const char *export_name; - int width; - int height; - bool rotate; + int width = 0; + int height = 0; + bool rotate = false; }; static const LoadingScreenInfo loading_screen_infos[] = { @@ -667,7 +667,7 @@ struct IconInfo { const char *actual_size_side; const char *scale; const char *unscaled_size; - bool is_required; + bool is_required = false; }; static const IconInfo icon_infos[] = { @@ -962,7 +962,7 @@ Error EditorExportPlatformIOS::_walk_dir_recursive(DirAccess *p_da, FileHandler struct CodesignData { const Ref<EditorExportPreset> &preset; - bool debug; + bool debug = false; CodesignData(const Ref<EditorExportPreset> &p_preset, bool p_debug) : preset(p_preset), diff --git a/platform/iphone/os_iphone.h b/platform/iphone/os_iphone.h index 04a0a478d5..e98f870ba9 100644 --- a/platform/iphone/os_iphone.h +++ b/platform/iphone/os_iphone.h @@ -38,7 +38,7 @@ #include "ios.h" #include "joypad_iphone.h" #include "servers/audio_server.h" -#include "servers/rendering/rasterizer.h" +#include "servers/rendering/renderer_compositor.h" #if defined(VULKAN_ENABLED) #include "drivers/vulkan/rendering_device_vulkan.h" diff --git a/platform/iphone/os_iphone.mm b/platform/iphone/os_iphone.mm index b87e6f37a0..dac61f2d9d 100644 --- a/platform/iphone/os_iphone.mm +++ b/platform/iphone/os_iphone.mm @@ -47,7 +47,7 @@ #import <dlfcn.h> #if defined(VULKAN_ENABLED) -#include "servers/rendering/rasterizer_rd/rasterizer_rd.h" +#include "servers/rendering/renderer_rd/renderer_compositor_rd.h" #import <QuartzCore/CAMetalLayer.h> #include <vulkan/vulkan_metal.h> #endif diff --git a/platform/javascript/SCsub b/platform/javascript/SCsub index 627ae778b1..7a8005fe30 100644 --- a/platform/javascript/SCsub +++ b/platform/javascript/SCsub @@ -12,13 +12,8 @@ javascript_files = [ "api/javascript_tools_editor_plugin.cpp", ] -build_targets = ["#bin/godot${PROGSUFFIX}.js", "#bin/godot${PROGSUFFIX}.wasm"] -if env["threads_enabled"]: - build_targets.append("#bin/godot${PROGSUFFIX}.worker.js") - -build = env.add_program(build_targets, javascript_files) - -env.AddJSLibraries( +sys_env = env.Clone() +sys_env.AddJSLibraries( [ "js/libs/library_godot_audio.js", "js/libs/library_godot_display.js", @@ -29,12 +24,48 @@ env.AddJSLibraries( ) if env["tools"]: - env.AddJSLibraries(["js/libs/library_godot_editor_tools.js"]) + sys_env.AddJSLibraries(["js/libs/library_godot_editor_tools.js"]) if env["javascript_eval"]: - env.AddJSLibraries(["js/libs/library_godot_eval.js"]) -for lib in env["JS_LIBS"]: - env.Append(LINKFLAGS=["--js-library", lib]) -env.Depends(build, env["JS_LIBS"]) + sys_env.AddJSLibraries(["js/libs/library_godot_eval.js"]) +for lib in sys_env["JS_LIBS"]: + sys_env.Append(LINKFLAGS=["--js-library", lib]) + +build = [] +if env["gdnative_enabled"]: + build_targets = ["#bin/godot${PROGSUFFIX}.js", "#bin/godot${PROGSUFFIX}.wasm"] + # Reset libraries. The main runtime will only link emscripten libraries, not godot ones. + sys_env["LIBS"] = [] + # We use IDBFS. Since Emscripten 1.39.1 it needs to be linked explicitly. + sys_env.Append(LIBS=["idbfs.js"]) + # JS prepended to the module code loading the side library. + sys_env.Append(LINKFLAGS=["--pre-js", sys_env.File("js/dynlink.pre.js")]) + # Configure it as a main module (dynamic linking support). + sys_env.Append(CCFLAGS=["-s", "MAIN_MODULE=1"]) + sys_env.Append(LINKFLAGS=["-s", "MAIN_MODULE=1"]) + sys_env.Append(CCFLAGS=["-s", "EXPORT_ALL=1"]) + sys_env.Append(LINKFLAGS=["-s", "EXPORT_ALL=1"]) + # Force exporting the standard library (printf, malloc, etc.) + sys_env["ENV"]["EMCC_FORCE_STDLIBS"] = "libc,libc++,libc++abi" + # The main emscripten runtime, with exported standard libraries. + sys = sys_env.Program(build_targets, ["javascript_runtime.cpp"]) + sys_env.Depends(sys, "js/dynlink.pre.js") + + # The side library, containing all Godot code. + wasm_env = env.Clone() + wasm_env.Append(CPPDEFINES=["WASM_GDNATIVE"]) # So that OS knows it can run GDNative libraries. + wasm_env.Append(CCFLAGS=["-s", "SIDE_MODULE=2"]) + wasm_env.Append(LINKFLAGS=["-s", "SIDE_MODULE=2"]) + wasm = wasm_env.add_program("#bin/godot.side${PROGSUFFIX}.wasm", javascript_files) + build = [sys[0], sys[1], wasm[0]] +else: + build_targets = ["#bin/godot${PROGSUFFIX}.js", "#bin/godot${PROGSUFFIX}.wasm"] + if env["threads_enabled"]: + build_targets.append("#bin/godot${PROGSUFFIX}.worker.js") + # We use IDBFS. Since Emscripten 1.39.1 it needs to be linked explicitly. + sys_env.Append(LIBS=["idbfs.js"]) + build = sys_env.Program(build_targets, javascript_files + ["javascript_runtime.cpp"]) + +sys_env.Depends(build[0], sys_env["JS_LIBS"]) engine = [ "js/engine/preloader.js", @@ -61,10 +92,19 @@ out_files = [ ] html_file = "#misc/dist/html/editor.html" if env["tools"] else "#misc/dist/html/full-size.html" in_files = [js_wrapped, build[1], html_file, "#platform/javascript/js/libs/audio.worklet.js"] -if env["threads_enabled"]: - in_files.append(build[2]) +if env["gdnative_enabled"]: + in_files.append(build[2]) # Runtime + out_files.append(zip_dir.File(binary_name + ".side.wasm")) +elif env["threads_enabled"]: + in_files.append(build[2]) # Worker out_files.append(zip_dir.File(binary_name + ".worker.js")) +if env["tools"]: + in_files.append("#misc/dist/html/logo.svg") + out_files.append(zip_dir.File("logo.svg")) + in_files.append("#icon.png") + out_files.append(zip_dir.File("favicon.png")) + zip_files = env.InstallAs(out_files, in_files) env.Zip( "#bin/godot", diff --git a/platform/javascript/audio_driver_javascript.cpp b/platform/javascript/audio_driver_javascript.cpp index dd982bc3a8..78fbed6d0f 100644 --- a/platform/javascript/audio_driver_javascript.cpp +++ b/platform/javascript/audio_driver_javascript.cpp @@ -189,7 +189,9 @@ Error AudioDriverJavaScript::capture_start() { lock(); input_buffer_init(buffer_length); unlock(); - godot_audio_capture_start(); + if (godot_audio_capture_start()) { + return FAILED; + } return OK; } diff --git a/platform/javascript/detect.py b/platform/javascript/detect.py index 71189cf697..d53c774e77 100644 --- a/platform/javascript/detect.py +++ b/platform/javascript/detect.py @@ -1,6 +1,8 @@ import os +import sys from emscripten_helpers import run_closure_compiler, create_engine_file, add_js_libraries +from methods import get_compiler_version from SCons.Util import WhereIs @@ -20,9 +22,17 @@ def get_opts(): from SCons.Variables import BoolVariable return [ + ("initial_memory", "Initial WASM memory (in MiB)", 16), + BoolVariable("use_assertions", "Use Emscripten runtime assertions", False), + BoolVariable("use_thinlto", "Use ThinLTO", False), + BoolVariable("use_ubsan", "Use Emscripten undefined behavior sanitizer (UBSAN)", False), + BoolVariable("use_asan", "Use Emscripten address sanitizer (ASAN)", False), + BoolVariable("use_lsan", "Use Emscripten leak sanitizer (LSAN)", False), + BoolVariable("use_safe_heap", "Use Emscripten SAFE_HEAP sanitizer", False), # eval() can be a security concern, so it can be disabled. BoolVariable("javascript_eval", "Enable JavaScript eval interface", True), BoolVariable("threads_enabled", "Enable WebAssembly Threads support (limited browser support)", False), + BoolVariable("gdnative_enabled", "Enable WebAssembly GDNative support (produces bigger binaries)", False), BoolVariable("use_closure_compiler", "Use closure compiler to minimize JavaScript code", False), ] @@ -40,6 +50,9 @@ def get_flags(): def configure(env): + if not isinstance(env["initial_memory"], int): + print("Initial memory must be a valid integer") + sys.exit(255) ## Build type @@ -62,15 +75,20 @@ def configure(env): env.Append(CPPDEFINES=["DEBUG_ENABLED"]) env.Append(CCFLAGS=["-O1", "-g"]) env.Append(LINKFLAGS=["-O1", "-g"]) + env["use_assertions"] = True + + if env["use_assertions"]: env.Append(LINKFLAGS=["-s", "ASSERTIONS=1"]) if env["tools"]: if not env["threads_enabled"]: - raise RuntimeError( - "Threads must be enabled to build the editor. Please add the 'threads_enabled=yes' option" - ) - # Tools need more memory. Initial stack memory in bytes. See `src/settings.js` in emscripten repository (will be renamed to INITIAL_MEMORY). - env.Append(LINKFLAGS=["-s", "TOTAL_MEMORY=33554432"]) + print("Threads must be enabled to build the editor. Please add the 'threads_enabled=yes' option") + sys.exit(255) + if env["initial_memory"] < 32: + print("Editor build requires at least 32MiB of initial memory. Forcing it.") + env["initial_memory"] = 32 + elif env["builtin_icu"]: + env.Append(CCFLAGS=["-frtti"]) else: # Disable exceptions and rtti on non-tools (template) builds # These flags help keep the file size down. @@ -78,15 +96,32 @@ def configure(env): # Don't use dynamic_cast, necessary with no-rtti. env.Append(CPPDEFINES=["NO_SAFE_CAST"]) + env.Append(LINKFLAGS=["-s", "INITIAL_MEMORY=%sMB" % env["initial_memory"]]) + ## Copy env variables. env["ENV"] = os.environ # LTO - if env["use_lto"]: - env.Append(CCFLAGS=["-s", "WASM_OBJECT_FILES=0"]) - env.Append(LINKFLAGS=["-s", "WASM_OBJECT_FILES=0"]) - env.Append(CCFLAGS=["-flto"]) - env.Append(LINKFLAGS=["-flto"]) + if env["use_thinlto"]: + env.Append(CCFLAGS=["-flto=thin"]) + env.Append(LINKFLAGS=["-flto=thin"]) + elif env["use_lto"]: + env.Append(CCFLAGS=["-flto=full"]) + env.Append(LINKFLAGS=["-flto=full"]) + + # Sanitizers + if env["use_ubsan"]: + env.Append(CCFLAGS=["-fsanitize=undefined"]) + env.Append(LINKFLAGS=["-fsanitize=undefined"]) + if env["use_asan"]: + env.Append(CCFLAGS=["-fsanitize=address"]) + env.Append(LINKFLAGS=["-fsanitize=address"]) + if env["use_lsan"]: + env.Append(CCFLAGS=["-fsanitize=leak"]) + env.Append(LINKFLAGS=["-fsanitize=leak"]) + if env["use_safe_heap"]: + env.Append(CCFLAGS=["-s", "SAFE_HEAP=1"]) + env.Append(LINKFLAGS=["-s", "SAFE_HEAP=1"]) # Closure compiler if env["use_closure_compiler"]: @@ -133,6 +168,10 @@ def configure(env): if env["javascript_eval"]: env.Append(CPPDEFINES=["JAVASCRIPT_EVAL_ENABLED"]) + if env["threads_enabled"] and env["gdnative_enabled"]: + print("Threads and GDNative support can't be both enabled due to WebAssembly limitations") + sys.exit(255) + # Thread support (via SharedArrayBuffer). if env["threads_enabled"]: env.Append(CPPDEFINES=["PTHREAD_NO_RENAME"]) @@ -144,14 +183,19 @@ def configure(env): else: env.Append(CPPDEFINES=["NO_THREADS"]) + if env["gdnative_enabled"]: + major, minor, patch = get_compiler_version(env) + if major < 2 or (major == 2 and minor == 0 and patch < 10): + print("GDNative support requires emscripten >= 2.0.10, detected: %s.%s.%s" % (major, minor, patch)) + sys.exit(255) + env.Append(CCFLAGS=["-s", "RELOCATABLE=1"]) + env.Append(LINKFLAGS=["-s", "RELOCATABLE=1"]) + env.extra_suffix = ".gdnative" + env.extra_suffix + # Reduce code size by generating less support code (e.g. skip NodeJS support). env.Append(LINKFLAGS=["-s", "ENVIRONMENT=web,worker"]) - # We use IDBFS in javascript_main.cpp. Since Emscripten 1.39.1 it needs to - # be linked explicitly. - env.Append(LIBS=["idbfs.js"]) - - env.Append(LINKFLAGS=["-s", "BINARYEN=1"]) + # Wrap the JavaScript support code around a closure named Godot. env.Append(LINKFLAGS=["-s", "MODULARIZE=1", "-s", "EXPORT_NAME='Godot'"]) # Allow increasing memory buffer size during runtime. This is efficient @@ -162,12 +206,14 @@ def configure(env): # This setting just makes WebGL 2 APIs available, it does NOT disable WebGL 1. env.Append(LINKFLAGS=["-s", "USE_WEBGL2=1"]) + # Do not call main immediately when the support code is ready. env.Append(LINKFLAGS=["-s", "INVOKE_RUN=0"]) # Allow use to take control of swapping WebGL buffers. env.Append(LINKFLAGS=["-s", "OFFSCREEN_FRAMEBUFFER=1"]) - # callMain for manual start, FS for preloading, PATH and ERRNO_CODES for BrowserFS. + # callMain for manual start. env.Append(LINKFLAGS=["-s", "EXTRA_EXPORTED_RUNTIME_METHODS=['callMain']"]) + # Add code that allow exiting runtime. env.Append(LINKFLAGS=["-s", "EXIT_RUNTIME=1"]) diff --git a/platform/javascript/display_server_javascript.cpp b/platform/javascript/display_server_javascript.cpp index af8800d565..92e13553fc 100644 --- a/platform/javascript/display_server_javascript.cpp +++ b/platform/javascript/display_server_javascript.cpp @@ -948,8 +948,8 @@ void DisplayServerJavaScript::window_set_size(const Size2i p_size, WindowID p_wi last_width = p_size.x; last_height = p_size.y; double scale = godot_js_display_pixel_ratio_get(); - emscripten_set_canvas_element_size(canvas_id, p_size.x * scale, p_size.y * scale); - emscripten_set_element_css_size(canvas_id, p_size.x, p_size.y); + emscripten_set_canvas_element_size(canvas_id, p_size.x, p_size.y); + emscripten_set_element_css_size(canvas_id, p_size.x / scale, p_size.y / scale); } Size2i DisplayServerJavaScript::window_get_size(WindowID p_window) const { diff --git a/platform/javascript/export/export.cpp b/platform/javascript/export/export.cpp index c3b7e0304e..37681b2484 100644 --- a/platform/javascript/export/export.cpp +++ b/platform/javascript/export/export.cpp @@ -37,16 +37,13 @@ #include "platform/javascript/logo.gen.h" #include "platform/javascript/run_icon.gen.h" -#define EXPORT_TEMPLATE_WEBASSEMBLY_RELEASE "webassembly_release.zip" -#define EXPORT_TEMPLATE_WEBASSEMBLY_DEBUG "webassembly_debug.zip" - class EditorHTTPServer : public Reference { private: Ref<TCP_Server> server; Ref<StreamPeerTCP> connection; - uint64_t time; + uint64_t time = 0; uint8_t req_buf[4096]; - int req_pos; + int req_pos = 0; void _clear_client() { connection = Ref<StreamPeerTCP>(); @@ -85,36 +82,44 @@ public: // Wrong protocol ERR_FAIL_COND_MSG(req[0] != "GET" || req[2] != "HTTP/1.1", "Invalid method or HTTP version."); - String filepath = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmp_js_export"); + const String cache_path = EditorSettings::get_singleton()->get_cache_dir(); const String basereq = "/tmp_js_export"; - String ctype = ""; + String filepath; + String ctype; if (req[1] == basereq + ".html") { - filepath += ".html"; + filepath = cache_path.plus_file(req[1].get_file()); ctype = "text/html"; } else if (req[1] == basereq + ".js") { - filepath += ".js"; + filepath = cache_path.plus_file(req[1].get_file()); ctype = "application/javascript"; } else if (req[1] == basereq + ".audio.worklet.js") { - filepath += ".audio.worklet.js"; + filepath = cache_path.plus_file(req[1].get_file()); ctype = "application/javascript"; } else if (req[1] == basereq + ".worker.js") { - filepath += ".worker.js"; + filepath = cache_path.plus_file(req[1].get_file()); ctype = "application/javascript"; } else if (req[1] == basereq + ".pck") { - filepath += ".pck"; + filepath = cache_path.plus_file(req[1].get_file()); ctype = "application/octet-stream"; } else if (req[1] == basereq + ".png" || req[1] == "/favicon.png") { // Also allow serving the generated favicon for a smoother loading experience. if (req[1] == "/favicon.png") { filepath = EditorSettings::get_singleton()->get_cache_dir().plus_file("favicon.png"); } else { - filepath += ".png"; + filepath = basereq + ".png"; } ctype = "image/png"; + } else if (req[1] == basereq + ".side.wasm") { + filepath = cache_path.plus_file(req[1].get_file()); + ctype = "application/wasm"; } else if (req[1] == basereq + ".wasm") { - filepath += ".wasm"; + filepath = cache_path.plus_file(req[1].get_file()); ctype = "application/wasm"; - } else { + } else if (req[1].ends_with(".wasm")) { + filepath = cache_path.plus_file(req[1].get_file()); // TODO dangerous? + ctype = "application/wasm"; + } + if (filepath.empty() || !FileAccess::exists(filepath)) { String s = "HTTP/1.1 404 Not Found\r\n"; s += "Connection: Close\r\n"; s += "\r\n"; @@ -203,15 +208,40 @@ class EditorExportPlatformJavaScript : public EditorExportPlatform { Ref<ImageTexture> logo; Ref<ImageTexture> run_icon; Ref<ImageTexture> stop_icon; - int menu_options; - - void _fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags); + int menu_options = 0; -private: Ref<EditorHTTPServer> server; - bool server_quit; + bool server_quit = false; Mutex server_lock; - Thread *server_thread; + Thread *server_thread = nullptr; + + enum ExportMode { + EXPORT_MODE_NORMAL = 0, + EXPORT_MODE_THREADS = 1, + EXPORT_MODE_GDNATIVE = 2, + }; + + String _get_template_name(ExportMode p_mode, bool p_debug) const { + String name = "webassembly"; + switch (p_mode) { + case EXPORT_MODE_THREADS: + name += "_threads"; + break; + case EXPORT_MODE_GDNATIVE: + name += "_gdnative"; + break; + default: + break; + } + if (p_debug) { + name += "_debug.zip"; + } else { + name += "_release.zip"; + } + return name; + } + + void _fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags, const Vector<SharedObject> p_shared_objects); static void _server_thread_poll(void *data); @@ -250,7 +280,7 @@ public: ~EditorExportPlatformJavaScript(); }; -void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags) { +void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags, const Vector<SharedObject> p_shared_objects) { String str_template = String::utf8(reinterpret_cast<const char *>(p_html.ptr()), p_html.size()); String str_export; Vector<String> lines = str_template.split("\n"); @@ -258,6 +288,10 @@ void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Re String flags_json; gen_export_flags(flags, p_flags); flags_json = JSON::print(flags); + String libs; + for (int i = 0; i < p_shared_objects.size(); i++) { + libs += "\"" + p_shared_objects[i].path.get_file() + "\","; + } for (int i = 0; i < lines.size(); i++) { String current_line = lines[i]; @@ -265,6 +299,7 @@ void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Re current_line = current_line.replace("$GODOT_PROJECT_NAME", ProjectSettings::get_singleton()->get_setting("application/config/name")); current_line = current_line.replace("$GODOT_HEAD_INCLUDE", p_preset->get("html/head_include")); current_line = current_line.replace("$GODOT_FULL_WINDOW", p_preset->get("html/full_window_size") ? "true" : "false"); + current_line = current_line.replace("$GODOT_GDNATIVE_LIBS", libs); current_line = current_line.replace("$GODOT_DEBUG_ENABLED", p_debug ? "true" : "false"); current_line = current_line.replace("$GODOT_ARGS", flags_json); str_export += current_line + "\n"; @@ -291,12 +326,19 @@ void EditorExportPlatformJavaScript::get_preset_features(const Ref<EditorExportP r_features->push_back("etc2"); } } + ExportMode mode = (ExportMode)(int)p_preset->get("variant/export_type"); + if (mode == EXPORT_MODE_THREADS) { + r_features->push_back("threads"); + } else if (mode == EXPORT_MODE_GDNATIVE) { + r_features->push_back("wasm32"); + } } void EditorExportPlatformJavaScript::get_export_options(List<ExportOption> *r_options) { r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "variant/export_type", PROPERTY_HINT_ENUM, "Regular,Threads,GDNative"), 0)); // Export type. r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "vram_texture_compression/for_desktop"), true)); // S3TC r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "vram_texture_compression/for_mobile"), false)); // ETC or ETC2, depending on renderer @@ -320,11 +362,11 @@ Ref<Texture2D> EditorExportPlatformJavaScript::get_logo() const { bool EditorExportPlatformJavaScript::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const { String err; bool valid = false; + ExportMode mode = (ExportMode)(int)p_preset->get("variant/export_type"); // Look for export templates (first official, and if defined custom templates). - - bool dvalid = exists_export_template(EXPORT_TEMPLATE_WEBASSEMBLY_DEBUG, &err); - bool rvalid = exists_export_template(EXPORT_TEMPLATE_WEBASSEMBLY_RELEASE, &err); + bool dvalid = exists_export_template(_get_template_name(mode, true), &err); + bool rvalid = exists_export_template(_get_template_name(mode, false), &err); if (p_preset->get("custom_template/debug") != "") { dvalid = FileAccess::exists(p_preset->get("custom_template/debug")); @@ -377,11 +419,8 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese template_path = template_path.strip_edges(); if (template_path == String()) { - if (p_debug) { - template_path = find_export_template(EXPORT_TEMPLATE_WEBASSEMBLY_DEBUG); - } else { - template_path = find_export_template(EXPORT_TEMPLATE_WEBASSEMBLY_RELEASE); - } + ExportMode mode = (ExportMode)(int)p_preset->get("variant/export_type"); + template_path = find_export_template(_get_template_name(mode, p_debug)); } if (!DirAccess::exists(p_path.get_base_dir())) { @@ -393,12 +432,24 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese return ERR_FILE_NOT_FOUND; } + Vector<SharedObject> shared_objects; String pck_path = p_path.get_basename() + ".pck"; - Error error = save_pack(p_preset, pck_path); + Error error = save_pack(p_preset, pck_path, &shared_objects); if (error != OK) { EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + pck_path); return error; } + DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + for (int i = 0; i < shared_objects.size(); i++) { + String dst = p_path.get_base_dir().plus_file(shared_objects[i].path.get_file()); + error = da->copy(shared_objects[i].path, dst); + if (error != OK) { + EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + shared_objects[i].path.get_file()); + memdelete(da); + return error; + } + } + memdelete(da); FileAccess *src_f = nullptr; zlib_filefunc_def io = zipio_create_io_from_file(&src_f); @@ -437,14 +488,18 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese if (!custom_html.empty()) { continue; } - _fix_html(data, p_preset, p_path.get_file().get_basename(), p_debug, p_flags); + _fix_html(data, p_preset, p_path.get_file().get_basename(), p_debug, p_flags, shared_objects); file = p_path.get_file(); } else if (file == "godot.js") { file = p_path.get_file().get_basename() + ".js"; + } else if (file == "godot.worker.js") { file = p_path.get_file().get_basename() + ".worker.js"; + } else if (file == "godot.side.wasm") { + file = p_path.get_file().get_basename() + ".side.wasm"; + } else if (file == "godot.audio.worklet.js") { file = p_path.get_file().get_basename() + ".audio.worklet.js"; @@ -475,7 +530,7 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese buf.resize(f->get_len()); f->get_buffer(buf.ptrw(), buf.size()); memdelete(f); - _fix_html(buf, p_preset, p_path.get_file().get_basename(), p_debug, p_flags); + _fix_html(buf, p_preset, p_path.get_file().get_basename(), p_debug, p_flags, shared_objects); f = FileAccess::open(p_path, FileAccess::WRITE); if (!f) { @@ -577,6 +632,7 @@ Error EditorExportPlatformJavaScript::run(const Ref<EditorExportPreset> &p_prese DirAccess::remove_file_or_error(basepath + ".audio.worklet.js"); DirAccess::remove_file_or_error(basepath + ".pck"); DirAccess::remove_file_or_error(basepath + ".png"); + DirAccess::remove_file_or_error(basepath + ".side.wasm"); DirAccess::remove_file_or_error(basepath + ".wasm"); DirAccess::remove_file_or_error(EditorSettings::get_singleton()->get_cache_dir().plus_file("favicon.png")); return err; @@ -625,7 +681,6 @@ void EditorExportPlatformJavaScript::_server_thread_poll(void *data) { EditorExportPlatformJavaScript::EditorExportPlatformJavaScript() { server.instance(); - server_quit = false; server_thread = Thread::create(_server_thread_poll, this); Ref<Image> img = memnew(Image(_javascript_logo)); @@ -642,8 +697,6 @@ EditorExportPlatformJavaScript::EditorExportPlatformJavaScript() { } else { stop_icon.instance(); } - - menu_options = 0; } EditorExportPlatformJavaScript::~EditorExportPlatformJavaScript() { diff --git a/platform/javascript/godot_audio.h b/platform/javascript/godot_audio.h index 7ebda3ad39..aeb234269e 100644 --- a/platform/javascript/godot_audio.h +++ b/platform/javascript/godot_audio.h @@ -41,14 +41,14 @@ extern int godot_audio_is_available(); 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(); -extern void godot_audio_capture_start(); +extern int godot_audio_capture_start(); extern void godot_audio_capture_stop(); // Worklet typedef int32_t GodotAudioState[4]; extern void 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_state_add(GodotAudioState p_state, int p_idx, int p_value); +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); diff --git a/platform/javascript/http_request.h b/platform/javascript/http_request.h index 54e98c1927..41e4749216 100644 --- a/platform/javascript/http_request.h +++ b/platform/javascript/http_request.h @@ -47,7 +47,7 @@ typedef enum { extern int godot_xhr_new(); extern void godot_xhr_reset(int p_xhr_id); -extern bool godot_xhr_free(int p_xhr_id); +extern void godot_xhr_free(int p_xhr_id); extern int godot_xhr_open(int p_xhr_id, const char *p_method, const char *p_url, const char *p_user = nullptr, const char *p_password = nullptr); diff --git a/platform/javascript/javascript_main.cpp b/platform/javascript/javascript_main.cpp index 2d28a63566..b4985a4f36 100644 --- a/platform/javascript/javascript_main.cpp +++ b/platform/javascript/javascript_main.cpp @@ -75,7 +75,7 @@ void main_loop_callback() { } /// When calling main, it is assumed FS is setup and synced. -int main(int argc, char *argv[]) { +extern EMSCRIPTEN_KEEPALIVE int godot_js_main(int argc, char *argv[]) { os = new OS_JavaScript(); // We must override main when testing is enabled diff --git a/platform/javascript/javascript_runtime.cpp b/platform/javascript/javascript_runtime.cpp new file mode 100644 index 0000000000..bfe9fbd1bc --- /dev/null +++ b/platform/javascript/javascript_runtime.cpp @@ -0,0 +1,35 @@ +/*************************************************************************/ +/* javascript_runtime.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +extern int godot_js_main(int argc, char *argv[]); + +int main(int argc, char *argv[]) { + return godot_js_main(argc, argv); +} diff --git a/platform/javascript/js/dynlink.pre.js b/platform/javascript/js/dynlink.pre.js new file mode 100644 index 0000000000..34bc371ea9 --- /dev/null +++ b/platform/javascript/js/dynlink.pre.js @@ -0,0 +1 @@ +Module['dynamicLibraries'] = [Module['thisProgram'] + '.side.wasm'].concat(Module['dynamicLibraries'] ? Module['dynamicLibraries'] : []); diff --git a/platform/javascript/js/engine/engine.js b/platform/javascript/js/engine/engine.js index 74153b672a..4b8a7dde69 100644 --- a/platform/javascript/js/engine/engine.js +++ b/platform/javascript/js/engine/engine.js @@ -34,6 +34,7 @@ const Engine = (function () { this.onExecute = null; this.onExit = null; this.persistentPaths = ['/userfs']; + this.gdnativeLibs = []; } Engine.prototype.init = /** @param {string=} basePath */ function (basePath) { @@ -58,6 +59,10 @@ const Engine = (function () { initPromise = new Promise(function (resolve, reject) { config['locateFile'] = Utils.createLocateRewrite(loadPath); config['instantiateWasm'] = Utils.createInstantiatePromise(loadPromise); + // Emscripten configuration. + config['thisProgram'] = me.executableName; + config['noExitRuntime'] = true; + config['dynamicLibraries'] = me.gdnativeLibs; Godot(config).then(function (module) { module['initFS'](me.persistentPaths).then(function (fs_err) { me.rtenv = module; @@ -119,9 +124,6 @@ const Engine = (function () { locale = navigator.languages ? navigator.languages[0] : navigator.language; locale = locale.split('.')[0]; } - // Emscripten configuration. - me.rtenv['thisProgram'] = me.executableName; - me.rtenv['noExitRuntime'] = true; // Godot configuration. me.rtenv['initConfig']({ 'resizeCanvasOnStart': me.resizeCanvasOnStart, @@ -249,6 +251,10 @@ const Engine = (function () { this.persistentPaths = persistentPaths; }; + Engine.prototype.setGDNativeLibraries = function (gdnativeLibs) { + this.gdnativeLibs = gdnativeLibs; + }; + Engine.prototype.requestQuit = function () { if (this.rtenv) { this.rtenv['request_quit'](); @@ -277,6 +283,7 @@ const Engine = (function () { Engine.prototype['setOnExit'] = Engine.prototype.setOnExit; Engine.prototype['copyToFS'] = Engine.prototype.copyToFS; Engine.prototype['setPersistentPaths'] = Engine.prototype.setPersistentPaths; + Engine.prototype['setGDNativeLibraries'] = Engine.prototype.setGDNativeLibraries; Engine.prototype['requestQuit'] = Engine.prototype.requestQuit; return Engine; }()); diff --git a/platform/javascript/js/engine/utils.js b/platform/javascript/js/engine/utils.js index d0fca4e1cb..9273bbad42 100644 --- a/platform/javascript/js/engine/utils.js +++ b/platform/javascript/js/engine/utils.js @@ -8,6 +8,8 @@ const Utils = { // eslint-disable-line no-unused-vars return `${execName}.audio.worklet.js`; } else if (path.endsWith('.js')) { return `${execName}.js`; + } else if (path.endsWith('.side.wasm')) { + return `${execName}.side.wasm`; } else if (path.endsWith('.wasm')) { return `${execName}.wasm`; } diff --git a/platform/javascript/js/libs/library_godot_audio.js b/platform/javascript/js/libs/library_godot_audio.js index 0c1f477f34..d01b8d887b 100644 --- a/platform/javascript/js/libs/library_godot_audio.js +++ b/platform/javascript/js/libs/library_godot_audio.js @@ -77,28 +77,37 @@ const GodotAudio = { create_input: function (callback) { if (GodotAudio.input) { - return; // Already started. + return 0; // Already started. } function gotMediaInput(stream) { - GodotAudio.input = GodotAudio.ctx.createMediaStreamSource(stream); - callback(GodotAudio.input); + try { + GodotAudio.input = GodotAudio.ctx.createMediaStreamSource(stream); + callback(GodotAudio.input); + } catch (e) { + GodotRuntime.error('Failed creaating input.', e); + } } - if (navigator.mediaDevices.getUserMedia) { + if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { navigator.mediaDevices.getUserMedia({ 'audio': true, }).then(gotMediaInput, function (e) { - GodotRuntime.print(e); + GodotRuntime.error('Error getting user media.', e); }); } else { if (!navigator.getUserMedia) { navigator.getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia; } + if (!navigator.getUserMedia) { + GodotRuntime.error('getUserMedia not available.'); + return 1; + } navigator.getUserMedia({ 'audio': true, }, gotMediaInput, function (e) { GodotRuntime.print(e); }); } + return 0; }, close_async: function (resolve, reject) { @@ -137,6 +146,7 @@ const GodotAudio = { }, }, + godot_audio_is_available__sig: 'i', godot_audio_is_available__proxy: 'sync', godot_audio_is_available: function () { if (!(window.AudioContext || window.webkitAudioContext)) { @@ -145,12 +155,14 @@ const GodotAudio = { return 1; }, + 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); const latencyupdate = GodotRuntime.get_func(p_latency_update); return GodotAudio.init(p_mix_rate, p_latency, statechange, latencyupdate); }, + godot_audio_resume__sig: 'v', godot_audio_resume: function () { if (GodotAudio.ctx && GodotAudio.ctx.state !== 'running') { GodotAudio.ctx.resume(); @@ -158,16 +170,15 @@ const GodotAudio = { }, godot_audio_capture_start__proxy: 'sync', + godot_audio_capture_start__sig: 'i', godot_audio_capture_start: function () { - if (GodotAudio.input) { - return; // Already started. - } - GodotAudio.create_input(function (input) { + return GodotAudio.create_input(function (input) { input.connect(GodotAudio.driver.get_node()); }); }, godot_audio_capture_stop__proxy: 'sync', + godot_audio_capture_stop__sig: 'v', godot_audio_capture_stop: function () { if (GodotAudio.input) { const tracks = GodotAudio.input['mediaStream']['getTracks'](); @@ -241,10 +252,12 @@ const GodotAudioWorklet = { }, }, + godot_audio_worklet_create__sig: 'vi', godot_audio_worklet_create: function (channels) { GodotAudioWorklet.create(channels); }, + godot_audio_worklet_start__sig: 'viiiii', godot_audio_worklet_start: function (p_in_buf, p_in_size, p_out_buf, p_out_size, p_state) { const out_buffer = GodotRuntime.heapSub(HEAPF32, p_out_buf, p_out_size); const in_buffer = GodotRuntime.heapSub(HEAPF32, p_in_buf, p_in_size); @@ -252,15 +265,18 @@ const GodotAudioWorklet = { GodotAudioWorklet.start(in_buffer, out_buffer, state); }, + 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); return Atomics.load(HEAP32, (p_state >> 2) + p_idx); }, + godot_audio_worklet_state_add__sig: 'iiii', godot_audio_worklet_state_add: function (p_state, p_idx, p_value) { return Atomics.add(HEAP32, (p_state >> 2) + p_idx, p_value); }, + godot_audio_worklet_state_get__sig: 'iii', godot_audio_worklet_state_get: function (p_state, p_idx) { return Atomics.load(HEAP32, (p_state >> 2) + p_idx); }, @@ -330,10 +346,12 @@ const GodotAudioScript = { }, }, + godot_audio_script_create__sig: 'iii', godot_audio_script_create: function (buffer_length, channel_count) { return GodotAudioScript.create(buffer_length, channel_count); }, + godot_audio_script_start__sig: 'viiiii', godot_audio_script_start: function (p_in_buf, p_in_size, p_out_buf, p_out_size, p_cb) { const onprocess = GodotRuntime.get_func(p_cb); GodotAudioScript.start(p_in_buf, p_in_size, p_out_buf, p_out_size, onprocess); diff --git a/platform/javascript/js/libs/library_godot_display.js b/platform/javascript/js/libs/library_godot_display.js index 9651b48952..800d6f414f 100644 --- a/platform/javascript/js/libs/library_godot_display.js +++ b/platform/javascript/js/libs/library_godot_display.js @@ -280,6 +280,7 @@ const GodotDisplay = { window_icon: '', }, + godot_js_display_is_swap_ok_cancel__sig: 'i', godot_js_display_is_swap_ok_cancel: function () { const win = (['Windows', 'Win64', 'Win32', 'WinCE']); const plat = navigator.platform || ''; @@ -289,10 +290,12 @@ const GodotDisplay = { return 0; }, + godot_js_display_alert__sig: 'vi', godot_js_display_alert: function (p_text) { window.alert(GodotRuntime.parseString(p_text)); // eslint-disable-line no-alert }, + godot_js_display_pixel_ratio_get__sig: 'f', godot_js_display_pixel_ratio_get: function () { return window.devicePixelRatio || 1; }, @@ -300,14 +303,17 @@ const GodotDisplay = { /* * Canvas */ + godot_js_display_canvas_focus__sig: 'v', godot_js_display_canvas_focus: function () { GodotConfig.canvas.focus(); }, + godot_js_display_canvas_is_focused__sig: 'i', godot_js_display_canvas_is_focused: function () { return document.activeElement === GodotConfig.canvas; }, + godot_js_display_canvas_bounding_rect_position_get__sig: 'vii', godot_js_display_canvas_bounding_rect_position_get: function (r_x, r_y) { const brect = GodotConfig.canvas.getBoundingClientRect(); GodotRuntime.setHeapValue(r_x, brect.x, 'i32'); @@ -317,6 +323,7 @@ const GodotDisplay = { /* * Touchscreen */ + godot_js_display_touchscreen_is_available__sig: 'i', godot_js_display_touchscreen_is_available: function () { return 'ontouchstart' in window; }, @@ -324,6 +331,7 @@ const GodotDisplay = { /* * Clipboard */ + godot_js_display_clipboard_set__sig: 'ii', godot_js_display_clipboard_set: function (p_text) { const text = GodotRuntime.parseString(p_text); if (!navigator.clipboard || !navigator.clipboard.writeText) { @@ -336,6 +344,7 @@ const GodotDisplay = { return 0; }, + godot_js_display_clipboard_get__sig: 'ii', godot_js_display_clipboard_get: function (callback) { const func = GodotRuntime.get_func(callback); try { @@ -354,6 +363,7 @@ const GodotDisplay = { /* * Window */ + godot_js_display_window_request_fullscreen__sig: 'v', godot_js_display_window_request_fullscreen: function () { const canvas = GodotConfig.canvas; (canvas.requestFullscreen || canvas.msRequestFullscreen @@ -362,10 +372,12 @@ const GodotDisplay = { ).call(canvas); }, + godot_js_display_window_title_set__sig: 'vi', godot_js_display_window_title_set: function (p_data) { document.title = GodotRuntime.parseString(p_data); }, + godot_js_display_window_icon_set__sig: 'vii', godot_js_display_window_icon_set: function (p_ptr, p_len) { let link = document.getElementById('-gd-engine-icon'); if (link === null) { @@ -386,6 +398,7 @@ const GodotDisplay = { /* * Cursor */ + godot_js_display_cursor_set_visible__sig: 'vi', godot_js_display_cursor_set_visible: function (p_visible) { const visible = p_visible !== 0; if (visible === GodotDisplayCursor.visible) { @@ -399,14 +412,17 @@ const GodotDisplay = { } }, + godot_js_display_cursor_is_hidden__sig: 'i', godot_js_display_cursor_is_hidden: function () { return !GodotDisplayCursor.visible; }, + godot_js_display_cursor_set_shape__sig: 'vi', godot_js_display_cursor_set_shape: function (p_string) { GodotDisplayCursor.set_shape(GodotRuntime.parseString(p_string)); }, + godot_js_display_cursor_set_custom_shape__sig: 'viiiii', godot_js_display_cursor_set_custom_shape: function (p_shape, p_ptr, p_len, p_hotspot_x, p_hotspot_y) { const shape = GodotRuntime.parseString(p_shape); const old_shape = GodotDisplayCursor.cursors[shape]; @@ -432,6 +448,7 @@ const GodotDisplay = { /* * Listeners */ + godot_js_display_notification_cb__sig: 'viiiii', godot_js_display_notification_cb: function (callback, p_enter, p_exit, p_in, p_out) { const canvas = GodotConfig.canvas; const func = GodotRuntime.get_func(callback); @@ -443,6 +460,7 @@ const GodotDisplay = { }); }, + godot_js_display_paste_cb__sig: 'vi', godot_js_display_paste_cb: function (callback) { const func = GodotRuntime.get_func(callback); GodotDisplayListeners.add(window, 'paste', function (evt) { @@ -453,6 +471,7 @@ const GodotDisplay = { }, false); }, + godot_js_display_drop_files_cb__sig: 'vi', godot_js_display_drop_files_cb: function (callback) { const func = GodotRuntime.get_func(callback); const dropFiles = function (files) { diff --git a/platform/javascript/js/libs/library_godot_editor_tools.js b/platform/javascript/js/libs/library_godot_editor_tools.js index f39fed04a8..12edc8e733 100644 --- a/platform/javascript/js/libs/library_godot_editor_tools.js +++ b/platform/javascript/js/libs/library_godot_editor_tools.js @@ -30,6 +30,7 @@ const GodotEditorTools = { godot_js_editor_download_file__deps: ['$FS'], + godot_js_editor_download_file__sig: 'viii', godot_js_editor_download_file: function (p_path, p_name, p_mime) { const path = GodotRuntime.parseString(p_path); const name = GodotRuntime.parseString(p_name); diff --git a/platform/javascript/js/libs/library_godot_eval.js b/platform/javascript/js/libs/library_godot_eval.js index 33ff231726..1a2440dd24 100644 --- a/platform/javascript/js/libs/library_godot_eval.js +++ b/platform/javascript/js/libs/library_godot_eval.js @@ -30,6 +30,7 @@ const GodotEval = { godot_js_eval__deps: ['$GodotRuntime'], + godot_js_eval__sig: 'iiiiiii', godot_js_eval: function (p_js, p_use_global_ctx, p_union_ptr, p_byte_arr, p_byte_arr_write, p_callback) { const js_code = GodotRuntime.parseString(p_js); let eval_ret = null; diff --git a/platform/javascript/js/libs/library_godot_http_request.js b/platform/javascript/js/libs/library_godot_http_request.js index 2b9aa88208..d4468bd5aa 100644 --- a/platform/javascript/js/libs/library_godot_http_request.js +++ b/platform/javascript/js/libs/library_godot_http_request.js @@ -50,6 +50,7 @@ const GodotHTTPRequest = { }, }, + godot_xhr_new__sig: 'i', godot_xhr_new: function () { const newId = GodotHTTPRequest.getUnusedRequestId(); GodotHTTPRequest.requests[newId] = new XMLHttpRequest(); @@ -57,30 +58,36 @@ const GodotHTTPRequest = { return newId; }, + godot_xhr_reset__sig: 'vi', godot_xhr_reset: function (xhrId) { GodotHTTPRequest.requests[xhrId] = new XMLHttpRequest(); GodotHTTPRequest.setupRequest(GodotHTTPRequest.requests[xhrId]); }, + godot_xhr_free__sig: 'vi', godot_xhr_free: function (xhrId) { GodotHTTPRequest.requests[xhrId].abort(); GodotHTTPRequest.requests[xhrId] = null; }, + godot_xhr_open__sig: 'viiiii', godot_xhr_open: function (xhrId, method, url, p_user, p_password) { const user = p_user > 0 ? GodotRuntime.parseString(p_user) : null; const password = p_password > 0 ? GodotRuntime.parseString(p_password) : null; GodotHTTPRequest.requests[xhrId].open(GodotRuntime.parseString(method), GodotRuntime.parseString(url), true, user, password); }, + godot_xhr_set_request_header__sig: 'viii', godot_xhr_set_request_header: function (xhrId, header, value) { GodotHTTPRequest.requests[xhrId].setRequestHeader(GodotRuntime.parseString(header), GodotRuntime.parseString(value)); }, + godot_xhr_send_null__sig: 'vi', godot_xhr_send_null: function (xhrId) { GodotHTTPRequest.requests[xhrId].send(); }, + godot_xhr_send_string__sig: 'vii', godot_xhr_send_string: function (xhrId, strPtr) { if (!strPtr) { GodotRuntime.error('Failed to send string per XHR: null pointer'); @@ -89,6 +96,7 @@ const GodotHTTPRequest = { GodotHTTPRequest.requests[xhrId].send(GodotRuntime.parseString(strPtr)); }, + godot_xhr_send_data__sig: 'viii', godot_xhr_send_data: function (xhrId, ptr, len) { if (!ptr) { GodotRuntime.error('Failed to send data per XHR: null pointer'); @@ -101,23 +109,28 @@ const GodotHTTPRequest = { GodotHTTPRequest.requests[xhrId].send(HEAPU8.subarray(ptr, ptr + len)); }, + godot_xhr_abort__sig: 'vi', godot_xhr_abort: function (xhrId) { GodotHTTPRequest.requests[xhrId].abort(); }, + godot_xhr_get_status__sig: 'ii', godot_xhr_get_status: function (xhrId) { return GodotHTTPRequest.requests[xhrId].status; }, + godot_xhr_get_ready_state__sig: 'ii', godot_xhr_get_ready_state: function (xhrId) { return GodotHTTPRequest.requests[xhrId].readyState; }, + godot_xhr_get_response_headers_length__sig: 'ii', godot_xhr_get_response_headers_length: function (xhrId) { const headers = GodotHTTPRequest.requests[xhrId].getAllResponseHeaders(); return headers === null ? 0 : GodotRuntime.strlen(headers); }, + godot_xhr_get_response_headers__sig: 'viii', godot_xhr_get_response_headers: function (xhrId, dst, len) { const str = GodotHTTPRequest.requests[xhrId].getAllResponseHeaders(); if (str === null) { @@ -126,11 +139,13 @@ const GodotHTTPRequest = { GodotRuntime.stringToHeap(str, dst, len); }, + godot_xhr_get_response_length__sig: 'ii', godot_xhr_get_response_length: function (xhrId) { const body = GodotHTTPRequest.requests[xhrId].response; return body === null ? 0 : body.byteLength; }, + godot_xhr_get_response__sig: 'viii', godot_xhr_get_response: function (xhrId, dst, len) { let buf = GodotHTTPRequest.requests[xhrId].response; if (buf === null) { diff --git a/platform/javascript/js/libs/library_godot_os.js b/platform/javascript/js/libs/library_godot_os.js index 488753d704..260cfbf97f 100644 --- a/platform/javascript/js/libs/library_godot_os.js +++ b/platform/javascript/js/libs/library_godot_os.js @@ -75,14 +75,17 @@ const GodotConfig = { }, }, + godot_js_config_canvas_id_get__sig: 'vii', godot_js_config_canvas_id_get: function (p_ptr, p_ptr_max) { GodotRuntime.stringToHeap(`#${GodotConfig.canvas.id}`, p_ptr, p_ptr_max); }, + godot_js_config_locale_get__sig: 'vii', godot_js_config_locale_get: function (p_ptr, p_ptr_max) { GodotRuntime.stringToHeap(GodotConfig.locale, p_ptr, p_ptr_max); }, + godot_js_config_is_resize_on_start__sig: 'i', godot_js_config_is_resize_on_start: function () { return GodotConfig.resize_on_start ? 1 : 0; }, @@ -200,7 +203,7 @@ const GodotFS = { } FS.mkdirTree(dir); } - FS.writeFile(path, new Uint8Array(buffer), { 'flags': 'wx+' }); + FS.writeFile(path, new Uint8Array(buffer)); }, }, }; @@ -239,19 +242,23 @@ const GodotOS = { }, }, + godot_js_os_finish_async__sig: 'vi', godot_js_os_finish_async: function (p_callback) { const func = GodotRuntime.get_func(p_callback); GodotOS.finish_async(func); }, + godot_js_os_request_quit_cb__sig: 'vi', godot_js_os_request_quit_cb: function (p_callback) { GodotOS.request_quit = GodotRuntime.get_func(p_callback); }, + godot_js_os_fs_is_persistent__sig: 'i', godot_js_os_fs_is_persistent: function () { return GodotFS.is_persistent(); }, + godot_js_os_fs_sync__sig: 'vi', godot_js_os_fs_sync: function (callback) { const func = GodotRuntime.get_func(callback); GodotOS._fs_sync_promise = GodotFS.sync(); @@ -260,6 +267,7 @@ const GodotOS = { }); }, + godot_js_os_execute__sig: 'ii', godot_js_os_execute: function (p_json) { const json_args = GodotRuntime.parseString(p_json); const args = JSON.parse(json_args); @@ -270,6 +278,7 @@ const GodotOS = { return 1; }, + godot_js_os_shell_open__sig: 'vi', godot_js_os_shell_open: function (p_uri) { window.open(GodotRuntime.parseString(p_uri), '_blank'); }, diff --git a/platform/javascript/os_javascript.cpp b/platform/javascript/os_javascript.cpp index 80723d54fc..8c976da58e 100644 --- a/platform/javascript/os_javascript.cpp +++ b/platform/javascript/os_javascript.cpp @@ -31,7 +31,6 @@ #include "os_javascript.h" #include "core/debugger/engine_debugger.h" -#include "core/io/file_access_buffered_fa.h" #include "core/io/json.h" #include "drivers/unix/dir_access_unix.h" #include "drivers/unix/file_access_unix.h" @@ -43,6 +42,7 @@ #include "modules/websocket/remote_debugger_peer_websocket.h" #endif +#include <dlfcn.h> #include <emscripten.h> #include <stdlib.h> @@ -51,7 +51,6 @@ // Lifecycle void OS_JavaScript::initialize() { OS_Unix::initialize_core(); - FileAccess::make_default<FileAccessBufferedFA<FileAccessUnix>>(FileAccess::ACCESS_RESOURCES); DisplayServerJavaScript::register_javascript_driver(); #ifdef MODULE_WEBSOCKET_ENABLED @@ -127,12 +126,24 @@ int OS_JavaScript::get_process_id() const { } bool OS_JavaScript::_check_internal_feature_support(const String &p_feature) { - if (p_feature == "HTML5" || p_feature == "web") + if (p_feature == "HTML5" || p_feature == "web") { return true; + } #ifdef JAVASCRIPT_EVAL_ENABLED - if (p_feature == "JavaScript") + if (p_feature == "JavaScript") { + return true; + } +#endif +#ifndef NO_THREADS + if (p_feature == "threads") { return true; + } +#endif +#if WASM_GDNATIVE + if (p_feature == "wasm32") { + return true; + } #endif return false; @@ -187,6 +198,13 @@ bool OS_JavaScript::is_userfs_persistent() const { return idb_available; } +Error OS_JavaScript::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) { + String path = p_path.get_file(); + p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW); + ERR_FAIL_COND_V_MSG(!p_library_handle, ERR_CANT_OPEN, "Can't open dynamic library: " + p_path + ". Error: " + dlerror()); + return OK; +} + OS_JavaScript *OS_JavaScript::get_singleton() { return static_cast<OS_JavaScript *>(OS::get_singleton()); } diff --git a/platform/javascript/os_javascript.h b/platform/javascript/os_javascript.h index 03a3053367..6682f8fd85 100644 --- a/platform/javascript/os_javascript.h +++ b/platform/javascript/os_javascript.h @@ -87,6 +87,7 @@ public: String get_user_data_dir() const override; bool is_userfs_persistent() const override; + Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) override; void resume_audio(); diff --git a/platform/linuxbsd/detect.py b/platform/linuxbsd/detect.py index a1bc5867c5..277aafc107 100644 --- a/platform/linuxbsd/detect.py +++ b/platform/linuxbsd/detect.py @@ -129,7 +129,6 @@ def configure(env): if "clang++" not in os.path.basename(env["CXX"]): env["CC"] = "clang" env["CXX"] = "clang++" - env.Append(CPPDEFINES=["TYPED_METHOD_BIND"]) env.extra_suffix = ".llvm" + env.extra_suffix if env["use_lld"]: @@ -206,14 +205,31 @@ def configure(env): # freetype depends on libpng and zlib, so bundling one of them while keeping others # as shared libraries leads to weird issues - if env["builtin_freetype"] or env["builtin_libpng"] or env["builtin_zlib"]: + if ( + env["builtin_freetype"] + or env["builtin_libpng"] + or env["builtin_zlib"] + or env["builtin_graphite"] + or env["builtin_harfbuzz"] + ): env["builtin_freetype"] = True env["builtin_libpng"] = True env["builtin_zlib"] = True + env["builtin_graphite"] = True + env["builtin_harfbuzz"] = True if not env["builtin_freetype"]: env.ParseConfig("pkg-config freetype2 --cflags --libs") + if not env["builtin_graphite"]: + env.ParseConfig("pkg-config graphite2 --cflags --libs") + + if not env["builtin_icu"]: + env.ParseConfig("pkg-config icu-uc --cflags --libs") + + if not env["builtin_harfbuzz"]: + env.ParseConfig("pkg-config harfbuzz harfbuzz-icu --cflags --libs") + if not env["builtin_libpng"]: env.ParseConfig("pkg-config libpng16 --cflags --libs") diff --git a/platform/linuxbsd/detect_prime_x11.cpp b/platform/linuxbsd/detect_prime_x11.cpp index e5a9bb4737..709523e836 100644 --- a/platform/linuxbsd/detect_prime_x11.cpp +++ b/platform/linuxbsd/detect_prime_x11.cpp @@ -56,7 +56,7 @@ typedef GLXContext (*GLXCREATECONTEXTATTRIBSARBPROC)(Display *, GLXFBConfig, GLX struct vendor { const char *glxvendor; - int priority; + int priority = 0; }; vendor vendormap[] = { diff --git a/platform/linuxbsd/display_server_x11.cpp b/platform/linuxbsd/display_server_x11.cpp index 5fa737af8e..136bee68e3 100644 --- a/platform/linuxbsd/display_server_x11.cpp +++ b/platform/linuxbsd/display_server_x11.cpp @@ -40,9 +40,10 @@ #include "scene/resources/texture.h" #if defined(VULKAN_ENABLED) -#include "servers/rendering/rasterizer_rd/rasterizer_rd.h" +#include "servers/rendering/renderer_rd/renderer_compositor_rd.h" #endif +#include <limits.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -465,59 +466,138 @@ Bool DisplayServerX11::_predicate_clipboard_selection(Display *display, XEvent * } } +Bool DisplayServerX11::_predicate_clipboard_incr(Display *display, XEvent *event, XPointer arg) { + if (event->type == PropertyNotify && event->xproperty.state == PropertyNewValue) { + return True; + } else { + return False; + } +} + String DisplayServerX11::_clipboard_get_impl(Atom p_source, Window x11_window, Atom target) const { String ret; - Atom type; - Atom selection = XA_PRIMARY; - int format, result; - unsigned long len, bytes_left, dummy; - unsigned char *data; Window selection_owner = XGetSelectionOwner(x11_display, p_source); - if (selection_owner == x11_window) { return internal_clipboard; } if (selection_owner != None) { - { - // Block events polling while processing selection events. - MutexLock mutex_lock(events_mutex); + // Block events polling while processing selection events. + MutexLock mutex_lock(events_mutex); - XConvertSelection(x11_display, p_source, target, selection, - x11_window, CurrentTime); + Atom selection = XA_PRIMARY; + XConvertSelection(x11_display, p_source, target, selection, + x11_window, CurrentTime); - XFlush(x11_display); + XFlush(x11_display); - // Blocking wait for predicate to be True - // and remove the event from the queue. - XEvent event; - XIfEvent(x11_display, &event, _predicate_clipboard_selection, (XPointer)&x11_window); - } + // Blocking wait for predicate to be True and remove the event from the queue. + XEvent event; + XIfEvent(x11_display, &event, _predicate_clipboard_selection, (XPointer)&x11_window); - // - // Do not get any data, see how much data is there - // + // Do not get any data, see how much data is there. + Atom type; + int format, result; + unsigned long len, bytes_left, dummy; + unsigned char *data; XGetWindowProperty(x11_display, x11_window, selection, // Tricky.. 0, 0, // offset - len 0, // Delete 0==FALSE - AnyPropertyType, //flag + AnyPropertyType, // flag &type, // return type &format, // return format - &len, &bytes_left, //that + &len, &bytes_left, // data length &data); - // DATA is There - if (bytes_left > 0) { + + if (data) { + XFree(data); + } + + if (type == XInternAtom(x11_display, "INCR", 0)) { + // Data is going to be received incrementally. + DEBUG_LOG_X11("INCR selection started.\n"); + + LocalVector<uint8_t> incr_data; + uint32_t data_size = 0; + bool success = false; + + // Delete INCR property to notify the owner. + XDeleteProperty(x11_display, x11_window, type); + + // Process events from the queue. + bool done = false; + while (!done) { + if (!_wait_for_events()) { + // Error or timeout, abort. + break; + } + + // Non-blocking wait for next event and remove it from the queue. + XEvent ev; + while (XCheckIfEvent(x11_display, &ev, _predicate_clipboard_incr, nullptr)) { + result = XGetWindowProperty(x11_display, x11_window, + selection, // selection type + 0, LONG_MAX, // offset - len + True, // delete property to notify the owner + AnyPropertyType, // flag + &type, // return type + &format, // return format + &len, &bytes_left, // data length + &data); + + DEBUG_LOG_X11("PropertyNotify: len=%lu, format=%i\n", len, format); + + if (result == Success) { + if (data && (len > 0)) { + uint32_t prev_size = incr_data.size(); + if (prev_size == 0) { + // First property contains initial data size. + unsigned long initial_size = *(unsigned long *)data; + incr_data.resize(initial_size); + } else { + // New chunk, resize to be safe and append data. + incr_data.resize(MAX(data_size + len, prev_size)); + memcpy(incr_data.ptr() + data_size, data, len); + data_size += len; + } + } else { + // Last chunk, process finished. + done = true; + success = true; + } + } else { + printf("Failed to get selection data chunk.\n"); + done = true; + } + + if (data) { + XFree(data); + } + + if (done) { + break; + } + } + } + + if (success && (data_size > 0)) { + ret.parse_utf8((const char *)incr_data.ptr(), data_size); + } + } else if (bytes_left > 0) { + // Data is ready and can be processed all at once. result = XGetWindowProperty(x11_display, x11_window, selection, 0, bytes_left, 0, AnyPropertyType, &type, &format, &len, &dummy, &data); + if (result == Success) { ret.parse_utf8((const char *)data); } else { - printf("FAIL\n"); + printf("Failed to get selection data.\n"); } + if (data) { XFree(data); } @@ -552,6 +632,60 @@ String DisplayServerX11::clipboard_get() const { return ret; } +Bool DisplayServerX11::_predicate_clipboard_save_targets(Display *display, XEvent *event, XPointer arg) { + if (event->xany.window == *(Window *)arg) { + return (event->type == SelectionRequest) || + (event->type == SelectionNotify); + } else { + return False; + } +} + +void DisplayServerX11::_clipboard_transfer_ownership(Atom p_source, Window x11_window) const { + _THREAD_SAFE_METHOD_ + + Window selection_owner = XGetSelectionOwner(x11_display, p_source); + + if (selection_owner != x11_window) { + return; + } + + // Block events polling while processing selection events. + MutexLock mutex_lock(events_mutex); + + Atom clipboard_manager = XInternAtom(x11_display, "CLIPBOARD_MANAGER", False); + Atom save_targets = XInternAtom(x11_display, "SAVE_TARGETS", False); + XConvertSelection(x11_display, clipboard_manager, save_targets, None, + x11_window, CurrentTime); + + // Process events from the queue. + while (true) { + if (!_wait_for_events()) { + // Error or timeout, abort. + break; + } + + // Non-blocking wait for next event and remove it from the queue. + XEvent ev; + while (XCheckIfEvent(x11_display, &ev, _predicate_clipboard_save_targets, (XPointer)&x11_window)) { + switch (ev.type) { + case SelectionRequest: + _handle_selection_request_event(&(ev.xselectionrequest)); + break; + + case SelectionNotify: { + if (ev.xselection.target == save_targets) { + // Once SelectionNotify is received, we're done whether it succeeded or not. + return; + } + + break; + } + } + } + } +} + int DisplayServerX11::get_screen_count() const { _THREAD_SAFE_METHOD_ @@ -2291,53 +2425,105 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, Input::get_singleton()->accumulate_input_event(k); } -void DisplayServerX11::_handle_selection_request_event(XSelectionRequestEvent *p_event) { - XEvent respond; - if (p_event->target == XInternAtom(x11_display, "UTF8_STRING", 0) || - p_event->target == XInternAtom(x11_display, "COMPOUND_TEXT", 0) || - p_event->target == XInternAtom(x11_display, "TEXT", 0) || - p_event->target == XA_STRING || - p_event->target == XInternAtom(x11_display, "text/plain;charset=utf-8", 0) || - p_event->target == XInternAtom(x11_display, "text/plain", 0)) { - // Directly using internal clipboard because we know our window - // is the owner during a selection request. - CharString clip = internal_clipboard.utf8(); - XChangeProperty(x11_display, - p_event->requestor, - p_event->property, - p_event->target, - 8, - PropModeReplace, - (unsigned char *)clip.get_data(), - clip.length()); - respond.xselection.property = p_event->property; - } else if (p_event->target == XInternAtom(x11_display, "TARGETS", 0)) { - Atom data[7]; +Atom DisplayServerX11::_process_selection_request_target(Atom p_target, Window p_requestor, Atom p_property) const { + if (p_target == XInternAtom(x11_display, "TARGETS", 0)) { + // Request to list all supported targets. + Atom data[9]; data[0] = XInternAtom(x11_display, "TARGETS", 0); - data[1] = XInternAtom(x11_display, "UTF8_STRING", 0); - data[2] = XInternAtom(x11_display, "COMPOUND_TEXT", 0); - data[3] = XInternAtom(x11_display, "TEXT", 0); - data[4] = XA_STRING; - data[5] = XInternAtom(x11_display, "text/plain;charset=utf-8", 0); - data[6] = XInternAtom(x11_display, "text/plain", 0); + data[1] = XInternAtom(x11_display, "SAVE_TARGETS", 0); + data[2] = XInternAtom(x11_display, "MULTIPLE", 0); + data[3] = XInternAtom(x11_display, "UTF8_STRING", 0); + data[4] = XInternAtom(x11_display, "COMPOUND_TEXT", 0); + data[5] = XInternAtom(x11_display, "TEXT", 0); + data[6] = XA_STRING; + data[7] = XInternAtom(x11_display, "text/plain;charset=utf-8", 0); + data[8] = XInternAtom(x11_display, "text/plain", 0); XChangeProperty(x11_display, - p_event->requestor, - p_event->property, + p_requestor, + p_property, XA_ATOM, 32, PropModeReplace, (unsigned char *)&data, sizeof(data) / sizeof(data[0])); - respond.xselection.property = p_event->property; - + return p_property; + } else if (p_target == XInternAtom(x11_display, "SAVE_TARGETS", 0)) { + // Request to check if SAVE_TARGETS is supported, nothing special to do. + XChangeProperty(x11_display, + p_requestor, + p_property, + XInternAtom(x11_display, "NULL", False), + 32, + PropModeReplace, + nullptr, + 0); + return p_property; + } else if (p_target == XInternAtom(x11_display, "UTF8_STRING", 0) || + p_target == XInternAtom(x11_display, "COMPOUND_TEXT", 0) || + p_target == XInternAtom(x11_display, "TEXT", 0) || + p_target == XA_STRING || + p_target == XInternAtom(x11_display, "text/plain;charset=utf-8", 0) || + p_target == XInternAtom(x11_display, "text/plain", 0)) { + // Directly using internal clipboard because we know our window + // is the owner during a selection request. + CharString clip = internal_clipboard.utf8(); + XChangeProperty(x11_display, + p_requestor, + p_property, + p_target, + 8, + PropModeReplace, + (unsigned char *)clip.get_data(), + clip.length()); + return p_property; } else { - char *targetname = XGetAtomName(x11_display, p_event->target); - printf("No Target '%s'\n", targetname); - if (targetname) { - XFree(targetname); + char *target_name = XGetAtomName(x11_display, p_target); + printf("Target '%s' not supported.\n", target_name); + if (target_name) { + XFree(target_name); } + return None; + } +} + +void DisplayServerX11::_handle_selection_request_event(XSelectionRequestEvent *p_event) const { + XEvent respond; + if (p_event->target == XInternAtom(x11_display, "MULTIPLE", 0)) { + // Request for multiple target conversions at once. + Atom atom_pair = XInternAtom(x11_display, "ATOM_PAIR", False); respond.xselection.property = None; + + Atom type; + int format; + unsigned long len; + unsigned long remaining; + unsigned char *data = nullptr; + if (XGetWindowProperty(x11_display, p_event->requestor, p_event->property, 0, LONG_MAX, False, atom_pair, &type, &format, &len, &remaining, &data) == Success) { + if ((len >= 2) && data) { + Atom *targets = (Atom *)data; + for (uint64_t i = 0; i < len; i += 2) { + Atom target = targets[i]; + Atom &property = targets[i + 1]; + property = _process_selection_request_target(target, p_event->requestor, property); + } + + XChangeProperty(x11_display, + p_event->requestor, + p_event->property, + atom_pair, + 32, + PropModeReplace, + (unsigned char *)targets, + len); + + respond.xselection.property = p_event->property; + } + XFree(data); + } + } else { + // Request for target conversion. + respond.xselection.property = _process_selection_request_target(p_event->target, p_event->requestor, p_event->property); } respond.xselection.type = SelectionNotify; @@ -2473,32 +2659,43 @@ Bool DisplayServerX11::_predicate_all_events(Display *display, XEvent *event, XP return True; } -void DisplayServerX11::_poll_events() { +bool DisplayServerX11::_wait_for_events() const { int x11_fd = ConnectionNumber(x11_display); fd_set in_fds; - while (!events_thread_done) { - XFlush(x11_display); + XFlush(x11_display); - FD_ZERO(&in_fds); - FD_SET(x11_fd, &in_fds); + FD_ZERO(&in_fds); + FD_SET(x11_fd, &in_fds); - struct timeval tv; - tv.tv_usec = 0; - tv.tv_sec = 1; + struct timeval tv; + tv.tv_usec = 0; + tv.tv_sec = 1; - // Wait for next event or timeout. - int num_ready_fds = select(x11_fd + 1, &in_fds, NULL, NULL, &tv); + // Wait for next event or timeout. + int num_ready_fds = select(x11_fd + 1, &in_fds, NULL, NULL, &tv); + + if (num_ready_fds > 0) { + // Event received. + return true; + } else { + // Error or timeout. if (num_ready_fds < 0) { - ERR_PRINT("_poll_events: select error: " + itos(errno)); + ERR_PRINT("_wait_for_events: select error: " + itos(errno)); } + return false; + } +} + +void DisplayServerX11::_poll_events() { + while (!events_thread_done) { + _wait_for_events(); // Process events from the queue. { MutexLock mutex_lock(events_mutex); - // Non-blocking wait for next event - // and remove it from the queue. + // Non-blocking wait for next event and remove it from the queue. XEvent ev; while (XCheckIfEvent(x11_display, &ev, _predicate_all_events, nullptr)) { // Check if the input manager wants to process the event. @@ -3891,12 +4088,12 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode rendering_device_vulkan = memnew(RenderingDeviceVulkan); rendering_device_vulkan->initialize(context_vulkan); - RasterizerRD::make_current(); + RendererCompositorRD::make_current(); } #endif /* - rendering_server = memnew(RenderingServerRaster); + rendering_server = memnew(RenderingServerDefault); if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) { rendering_server = memnew(RenderingServerWrapMT(rendering_server, get_render_thread_mode() == RENDER_SEPARATE_THREAD)); } @@ -4068,6 +4265,11 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode } DisplayServerX11::~DisplayServerX11() { + // Send owned clipboard data to clipboard manager before exit. + Window x11_main_window = windows[MAIN_WINDOW_ID].x11_window; + _clipboard_transfer_ownership(XA_PRIMARY, x11_main_window); + _clipboard_transfer_ownership(XInternAtom(x11_display, "CLIPBOARD", 0), x11_main_window); + events_thread_done = true; Thread::wait_to_finish(events_thread); memdelete(events_thread); diff --git a/platform/linuxbsd/display_server_x11.h b/platform/linuxbsd/display_server_x11.h index 9ff28b9ed2..0507ef3fff 100644 --- a/platform/linuxbsd/display_server_x11.h +++ b/platform/linuxbsd/display_server_x11.h @@ -43,7 +43,7 @@ #include "drivers/unix/os_unix.h" #include "joypad_linux.h" #include "servers/audio_server.h" -#include "servers/rendering/rasterizer.h" +#include "servers/rendering/renderer_compositor.h" #include "servers/rendering_server.h" #if defined(OPENGL_ENABLED) @@ -63,25 +63,25 @@ // Hints for X11 fullscreen typedef struct { - unsigned long flags; - unsigned long functions; - unsigned long decorations; - long inputMode; - unsigned long status; + unsigned long flags = 0; + unsigned long functions = 0; + unsigned long decorations = 0; + long inputMode = 0; + unsigned long status = 0; } Hints; typedef struct _xrr_monitor_info { Atom name; - Bool primary; - Bool automatic; - int noutput; - int x; - int y; - int width; - int height; - int mwidth; - int mheight; - RROutput *outputs; + Bool primary = false; + Bool automatic = false; + int noutput = 0; + int x = 0; + int y = 0; + int width = 0; + int height = 0; + int mwidth = 0; + int mheight = 0; + RROutput *outputs = nullptr; } xrr_monitor_info; #undef CursorShape @@ -204,10 +204,13 @@ class DisplayServerX11 : public DisplayServer { Point2i center; void _handle_key_event(WindowID p_window, XKeyEvent *p_event, LocalVector<XEvent> &p_events, uint32_t &p_event_index, bool p_echo = false); - void _handle_selection_request_event(XSelectionRequestEvent *p_event); + + Atom _process_selection_request_target(Atom p_target, Window p_requestor, Atom p_property) const; + void _handle_selection_request_event(XSelectionRequestEvent *p_event) const; String _clipboard_get_impl(Atom p_source, Window x11_window, Atom target) const; String _clipboard_get(Atom p_source, Window x11_window) const; + void _clipboard_transfer_ownership(Atom p_source, Window x11_window) const; //bool minimized; //bool window_has_focus; @@ -262,10 +265,13 @@ class DisplayServerX11 : public DisplayServer { bool events_thread_done = false; LocalVector<XEvent> polled_events; static void _poll_events_thread(void *ud); + bool _wait_for_events() const; void _poll_events(); static Bool _predicate_all_events(Display *display, XEvent *event, XPointer arg); static Bool _predicate_clipboard_selection(Display *display, XEvent *event, XPointer arg); + static Bool _predicate_clipboard_incr(Display *display, XEvent *event, XPointer arg); + static Bool _predicate_clipboard_save_targets(Display *display, XEvent *event, XPointer arg); protected: void _window_changed(XEvent *event); diff --git a/platform/linuxbsd/joypad_linux.cpp b/platform/linuxbsd/joypad_linux.cpp index 4a9d0a8181..0676587f7a 100644 --- a/platform/linuxbsd/joypad_linux.cpp +++ b/platform/linuxbsd/joypad_linux.cpp @@ -49,15 +49,6 @@ static const char *ignore_str = "/dev/input/js"; #endif -JoypadLinux::Joypad::Joypad() { - fd = -1; - dpad = 0; - devpath = ""; - for (int i = 0; i < MAX_ABS; i++) { - abs_info[i] = nullptr; - } -} - JoypadLinux::Joypad::~Joypad() { for (int i = 0; i < MAX_ABS; i++) { if (abs_info[i]) { diff --git a/platform/linuxbsd/joypad_linux.h b/platform/linuxbsd/joypad_linux.h index 0d175193a5..71eab78f99 100644 --- a/platform/linuxbsd/joypad_linux.h +++ b/platform/linuxbsd/joypad_linux.h @@ -56,17 +56,16 @@ private: Input::JoyAxis curr_axis[MAX_ABS]; int key_map[MAX_KEY]; int abs_map[MAX_ABS]; - int dpad; - int fd; + int dpad = 0; + int fd = -1; String devpath; - input_absinfo *abs_info[MAX_ABS]; + input_absinfo *abs_info[MAX_ABS] = {}; - bool force_feedback; - int ff_effect_id; - uint64_t ff_effect_timestamp; + bool force_feedback = false; + int ff_effect_id = 0; + uint64_t ff_effect_timestamp = 0; - Joypad(); ~Joypad(); void reset(); }; diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp index e569aa03d7..ac88d457a7 100644 --- a/platform/linuxbsd/os_linuxbsd.cpp +++ b/platform/linuxbsd/os_linuxbsd.cpp @@ -303,65 +303,147 @@ static String get_mountpoint(const String &p_path) { } Error OS_LinuxBSD::move_to_trash(const String &p_path) { - String trash_can = ""; + int err_code; + List<String> args; + args.push_back(p_path); + args.push_front("trash"); // The command is `gio trash <file_name>` so we need to add it to args. + Error result = execute("gio", args, true, nullptr, nullptr, &err_code); // For GNOME based machines. + if (result == OK && !err_code) { + return OK; + } else if (err_code == 2) { + return ERR_FILE_NOT_FOUND; + } + + args.pop_front(); + args.push_front("move"); + args.push_back("trash:/"); // The command is `kioclient5 move <file_name> trash:/`. + result = execute("kioclient5", args, true, nullptr, nullptr, &err_code); // For KDE based machines. + if (result == OK && !err_code) { + return OK; + } else if (err_code == 2) { + return ERR_FILE_NOT_FOUND; + } + + args.pop_front(); + args.pop_back(); + result = execute("gvfs-trash", args, true, nullptr, nullptr, &err_code); // For older Linux machines. + if (result == OK && !err_code) { + return OK; + } else if (err_code == 2) { + return ERR_FILE_NOT_FOUND; + } + + // If the commands `kioclient5`, `gio` or `gvfs-trash` don't exist on the system we do it manually. + String trash_path = ""; String mnt = get_mountpoint(p_path); - // If there is a directory "[Mountpoint]/.Trash-[UID]/files", use it as the trash can. + // If there is a directory "[Mountpoint]/.Trash-[UID], use it as the trash can. if (mnt != "") { - String path(mnt + "/.Trash-" + itos(getuid()) + "/files"); + String path(mnt + "/.Trash-" + itos(getuid())); struct stat s; if (!stat(path.utf8().get_data(), &s)) { - trash_can = path; + trash_path = path; } } - // Otherwise, if ${XDG_DATA_HOME} is defined, use "${XDG_DATA_HOME}/Trash/files" as the trash can. - if (trash_can == "") { + // Otherwise, if ${XDG_DATA_HOME} is defined, use "${XDG_DATA_HOME}/Trash" as the trash can. + if (trash_path == "") { char *dhome = getenv("XDG_DATA_HOME"); if (dhome) { - trash_can = String(dhome) + "/Trash/files"; + trash_path = String(dhome) + "/Trash"; } } - // Otherwise, if ${HOME} is defined, use "${HOME}/.local/share/Trash/files" as the trash can. - if (trash_can == "") { + // Otherwise, if ${HOME} is defined, use "${HOME}/.local/share/Trash" as the trash can. + if (trash_path == "") { char *home = getenv("HOME"); if (home) { - trash_can = String(home) + "/.local/share/Trash/files"; + trash_path = String(home) + "/.local/share/Trash"; } } // Issue an error if none of the previous locations is appropriate for the trash can. - if (trash_can == "") { - ERR_PRINT("move_to_trash: Could not determine the trash can location"); - return FAILED; - } + ERR_FAIL_COND_V_MSG(trash_path == "", FAILED, "Could not determine the trash can location"); // Create needed directories for decided trash can location. - DirAccess *dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - Error err = dir_access->make_dir_recursive(trash_can); - memdelete(dir_access); + { + DirAccess *dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + Error err = dir_access->make_dir_recursive(trash_path); + + // Issue an error if trash can is not created proprely. + ERR_FAIL_COND_V_MSG(err != OK, err, "Could not create the trash path \"" + trash_path + "\""); + err = dir_access->make_dir_recursive(trash_path + "/files"); + ERR_FAIL_COND_V_MSG(err != OK, err, "Could not create the trash path \"" + trash_path + "\"/files"); + err = dir_access->make_dir_recursive(trash_path + "/info"); + ERR_FAIL_COND_V_MSG(err != OK, err, "Could not create the trash path \"" + trash_path + "\"/info"); + memdelete(dir_access); + } - // Issue an error if trash can is not created proprely. - if (err != OK) { - ERR_PRINT("move_to_trash: Could not create the trash can \"" + trash_can + "\""); - return err; + // The trash can is successfully created, now we check that we don't exceed our file name length limit. + // If the file name is too long trim it so we can add the identifying number and ".trashinfo". + // Assumes that the file name length limit is 255 characters. + String file_name = basename(p_path.utf8().get_data()); + if (file_name.length() > 240) { + file_name = file_name.substr(0, file_name.length() - 15); + } + + String dest_path = trash_path + "/files/" + file_name; + struct stat buff; + int id_number = 0; + String fn = file_name; + + // Checks if a resource with the same name already exist in the trash can, + // if there is, add an identifying number to our resource's name. + while (stat(dest_path.utf8().get_data(), &buff) == 0) { + id_number++; + + // Added a limit to check for identically named files already on the trash can + // if there are too many it could make the editor unresponsive. + ERR_FAIL_COND_V_MSG(id_number > 99, FAILED, "Too many identically named resources already in the trash can."); + fn = file_name + "." + itos(id_number); + dest_path = trash_path + "/files/" + fn; + } + file_name = fn; + + // Generates the .trashinfo file + OS::Date date = OS::get_singleton()->get_date(false); + OS::Time time = OS::get_singleton()->get_time(false); + String timestamp = vformat("%04d-%02d-%02dT%02d:%02d:", date.year, date.month, date.day, time.hour, time.min); + timestamp = vformat("%s%02d", timestamp, time.sec); // vformat only supports up to 6 arguments. + String trash_info = "[Trash Info]\nPath=" + p_path.http_escape() + "\nDeletionDate=" + timestamp + "\n"; + { + Error err; + FileAccess *file = FileAccess::open(trash_path + "/info/" + file_name + ".trashinfo", FileAccess::WRITE, &err); + ERR_FAIL_COND_V_MSG(err != OK, err, "Can't create trashinfo file:" + trash_path + "/info/" + file_name + ".trashinfo"); + file->store_string(trash_info); + file->close(); + + // Rename our resource before moving it to the trash can. + DirAccess *dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + err = dir_access->rename(p_path, p_path.get_base_dir() + "/" + file_name); + ERR_FAIL_COND_V_MSG(err != OK, err, "Can't rename file \"" + p_path + "\""); + memdelete(dir_access); } - // The trash can is successfully created, now move the given resource to it. + // Move the given resource to the trash can. // Do not use DirAccess:rename() because it can't move files across multiple mountpoints. List<String> mv_args; - mv_args.push_back(p_path); - mv_args.push_back(trash_can); - int retval; - err = execute("mv", mv_args, true, nullptr, nullptr, &retval); - - // Issue an error if "mv" failed to move the given resource to the trash can. - if (err != OK || retval != 0) { - ERR_PRINT("move_to_trash: Could not move the resource \"" + p_path + "\" to the trash can \"" + trash_can + "\""); - return FAILED; + mv_args.push_back(p_path.get_base_dir() + "/" + file_name); + mv_args.push_back(trash_path + "/files"); + { + int retval; + Error err = execute("mv", mv_args, true, nullptr, nullptr, &retval); + + // Issue an error if "mv" failed to move the given resource to the trash can. + if (err != OK || retval != 0) { + ERR_PRINT("move_to_trash: Could not move the resource \"" + p_path + "\" to the trash can \"" + trash_path + "/files\""); + DirAccess *dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + err = dir_access->rename(p_path.get_base_dir() + "/" + file_name, p_path); + memdelete(dir_access); + ERR_FAIL_COND_V_MSG(err != OK, err, "Could not rename " + p_path.get_base_dir() + "/" + file_name + " back to its original name:" + p_path); + return FAILED; + } } - return OK; } diff --git a/platform/linuxbsd/os_linuxbsd.h b/platform/linuxbsd/os_linuxbsd.h index cd4fbd9db5..6e93bf6ef2 100644 --- a/platform/linuxbsd/os_linuxbsd.h +++ b/platform/linuxbsd/os_linuxbsd.h @@ -39,7 +39,7 @@ #include "drivers/unix/os_unix.h" #include "joypad_linux.h" #include "servers/audio_server.h" -#include "servers/rendering/rasterizer.h" +#include "servers/rendering/renderer_compositor.h" #include "servers/rendering_server.h" class OS_LinuxBSD : public OS_Unix { diff --git a/platform/osx/display_server_osx.h b/platform/osx/display_server_osx.h index 073d35008b..ea55a3ff28 100644 --- a/platform/osx/display_server_osx.h +++ b/platform/osx/display_server_osx.h @@ -77,13 +77,13 @@ public: struct KeyEvent { WindowID window_id; - unsigned int osx_state; - bool pressed; - bool echo; - bool raw; - uint32_t keycode; - uint32_t physical_keycode; - uint32_t unicode; + unsigned int osx_state = false; + bool pressed = false; + bool echo = false; + bool raw = false; + uint32_t keycode = 0; + uint32_t physical_keycode = 0; + uint32_t unicode = 0; }; struct WarpEvent { diff --git a/platform/osx/display_server_osx.mm b/platform/osx/display_server_osx.mm index 1ad7117b39..c4c2426f1f 100644 --- a/platform/osx/display_server_osx.mm +++ b/platform/osx/display_server_osx.mm @@ -52,7 +52,7 @@ #endif #if defined(VULKAN_ENABLED) -#include "servers/rendering/rasterizer_rd/rasterizer_rd.h" +#include "servers/rendering/renderer_rd/renderer_compositor_rd.h" #include <QuartzCore/CAMetalLayer.h> #endif @@ -701,8 +701,6 @@ static const NSRange kEmptyRange = { NSNotFound, 0 }; characters = (NSString *)aString; } - NSUInteger i, length = [characters length]; - NSCharacterSet *ctrlChars = [NSCharacterSet controlCharacterSet]; NSCharacterSet *wsnlChars = [NSCharacterSet whitespaceAndNewlineCharacterSet]; if ([characters rangeOfCharacterFromSet:ctrlChars].length && [characters rangeOfCharacterFromSet:wsnlChars].length == 0) { @@ -712,8 +710,15 @@ static const NSRange kEmptyRange = { NSNotFound, 0 }; return; } - for (i = 0; i < length; i++) { - const unichar codepoint = [characters characterAtIndex:i]; + Char16String text; + text.resize([characters length] + 1); + [characters getCharacters:(unichar *)text.ptrw() range:NSMakeRange(0, [characters length])]; + + String u32text; + u32text.parse_utf16(text.ptr(), text.length()); + + for (int i = 0; i < u32text.length(); i++) { + const char32_t codepoint = u32text[i]; if ((codepoint & 0xFF00) == 0xF700) { continue; } @@ -1315,7 +1320,16 @@ static int remapKey(unsigned int key, unsigned int state) { if (!wd.im_active && length > 0 && keycode_has_unicode(remapKey([event keyCode], [event modifierFlags]))) { // Fallback unicode character handler used if IME is not active - for (NSUInteger i = 0; i < length; i++) { + Char16String text; + text.resize([characters length] + 1); + [characters getCharacters:(unichar *)text.ptrw() range:NSMakeRange(0, [characters length])]; + + String u32text; + u32text.parse_utf16(text.ptr(), text.length()); + + for (int i = 0; i < u32text.length(); i++) { + const char32_t codepoint = u32text[i]; + DisplayServerOSX::KeyEvent ke; ke.window_id = window_id; @@ -1325,7 +1339,7 @@ static int remapKey(unsigned int key, unsigned int state) { ke.keycode = remapKey([event keyCode], [event modifierFlags]); ke.physical_keycode = translateKey([event keyCode]); ke.raw = true; - ke.unicode = [characters characterAtIndex:i]; + ke.unicode = codepoint; _push_to_key_event_buffer(ke); } @@ -1417,7 +1431,15 @@ static int remapKey(unsigned int key, unsigned int state) { // Fallback unicode character handler used if IME is not active if (!wd.im_active && length > 0 && keycode_has_unicode(remapKey([event keyCode], [event modifierFlags]))) { - for (NSUInteger i = 0; i < length; i++) { + Char16String text; + text.resize([characters length] + 1); + [characters getCharacters:(unichar *)text.ptrw() range:NSMakeRange(0, [characters length])]; + + String u32text; + u32text.parse_utf16(text.ptr(), text.length()); + + for (int i = 0; i < u32text.length(); i++) { + const char32_t codepoint = u32text[i]; DisplayServerOSX::KeyEvent ke; ke.window_id = window_id; @@ -1427,7 +1449,7 @@ static int remapKey(unsigned int key, unsigned int state) { ke.keycode = remapKey([event keyCode], [event modifierFlags]); ke.physical_keycode = translateKey([event keyCode]); ke.raw = true; - ke.unicode = [characters characterAtIndex:i]; + ke.unicode = codepoint; _push_to_key_event_buffer(ke); } @@ -3848,7 +3870,7 @@ DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode rendering_device_vulkan = memnew(RenderingDeviceVulkan); rendering_device_vulkan->initialize(context_vulkan); - RasterizerRD::make_current(); + RendererCompositorRD::make_current(); } #endif diff --git a/platform/osx/export/export.cpp b/platform/osx/export/export.cpp index e988e51e72..eecd2ed641 100644 --- a/platform/osx/export/export.cpp +++ b/platform/osx/export/export.cpp @@ -42,13 +42,13 @@ #include "editor/editor_node.h" #include "editor/editor_settings.h" #include "platform/osx/logo.gen.h" -#include "string.h" + #include <sys/stat.h> class EditorExportPlatformOSX : public EditorExportPlatform { GDCLASS(EditorExportPlatformOSX, EditorExportPlatform); - int version_code; + int version_code = 0; Ref<ImageTexture> logo; @@ -572,36 +572,36 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p pkg_name = "Unnamed"; } - String pkg_name_safe = OS::get_singleton()->get_safe_dir_name(pkg_name); - - Error err = OK; - String tmp_app_path_name = ""; + pkg_name = OS::get_singleton()->get_safe_dir_name(pkg_name); - DirAccess *tmp_app_path = nullptr; String export_format = use_dmg() && p_path.ends_with("dmg") ? "dmg" : "zip"; // Create our application bundle. - tmp_app_path_name = EditorSettings::get_singleton()->get_cache_dir().plus_file(pkg_name + ".app"); + String tmp_app_dir_name = pkg_name + ".app"; + String tmp_app_path_name = EditorSettings::get_singleton()->get_cache_dir().plus_file(tmp_app_dir_name); print_line("Exporting to " + tmp_app_path_name); - tmp_app_path = DirAccess::create_for_path(tmp_app_path_name); - if (!tmp_app_path) { + + Error err = OK; + + DirAccessRef tmp_app_dir = DirAccess::create_for_path(tmp_app_path_name); + if (!tmp_app_dir) { err = ERR_CANT_CREATE; } // Create our folder structure. if (err == OK) { print_line("Creating " + tmp_app_path_name + "/Contents/MacOS"); - err = tmp_app_path->make_dir_recursive(tmp_app_path_name + "/Contents/MacOS"); + err = tmp_app_dir->make_dir_recursive(tmp_app_path_name + "/Contents/MacOS"); } if (err == OK) { print_line("Creating " + tmp_app_path_name + "/Contents/Frameworks"); - err = tmp_app_path->make_dir_recursive(tmp_app_path_name + "/Contents/Frameworks"); + err = tmp_app_dir->make_dir_recursive(tmp_app_path_name + "/Contents/Frameworks"); } if (err == OK) { print_line("Creating " + tmp_app_path_name + "/Contents/Resources"); - err = tmp_app_path->make_dir_recursive(tmp_app_path_name + "/Contents/Resources"); + err = tmp_app_dir->make_dir_recursive(tmp_app_path_name + "/Contents/Resources"); } // Now process our template. @@ -678,14 +678,14 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p ret = unzGoToNextFile(src_pkg_zip); continue; // skip } - file = file.replace("/data.mono.osx.64.release_debug/", "/data_" + pkg_name_safe + "/"); + file = file.replace("/data.mono.osx.64.release_debug/", "/data_" + pkg_name + "/"); } if (file.find("/data.mono.osx.64.release/") != -1) { if (p_debug) { ret = unzGoToNextFile(src_pkg_zip); continue; // skip } - file = file.replace("/data.mono.osx.64.release/", "/data_" + pkg_name_safe + "/"); + file = file.replace("/data.mono.osx.64.release/", "/data_" + pkg_name + "/"); } print_line("ADDING: " + file + " size: " + itos(data.size())); @@ -694,7 +694,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p // Write it into our application bundle. file = tmp_app_path_name.plus_file(file); if (err == OK) { - err = tmp_app_path->make_dir_recursive(file.get_base_dir()); + err = tmp_app_dir->make_dir_recursive(file.get_base_dir()); } if (err == OK) { FileAccess *f = FileAccess::open(file, FileAccess::WRITE); @@ -797,7 +797,10 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p } // Clean up temporary .app dir. - OS::get_singleton()->move_to_trash(tmp_app_path_name); + tmp_app_dir->change_dir(tmp_app_path_name); + tmp_app_dir->erase_contents_recursive(); + tmp_app_dir->change_dir(".."); + tmp_app_dir->remove(tmp_app_dir_name); } return err; diff --git a/platform/osx/joypad_osx.cpp b/platform/osx/joypad_osx.cpp index cfc371710b..b2871b261e 100644 --- a/platform/osx/joypad_osx.cpp +++ b/platform/osx/joypad_osx.cpp @@ -37,14 +37,6 @@ static JoypadOSX *self = nullptr; joypad::joypad() { - device_ref = nullptr; - ff_device = nullptr; - ff_axes = nullptr; - ff_directions = nullptr; - ffservice = 0; - ff_timestamp = 0; - id = 0; - ff_constant_force.lMagnitude = 10000; ff_effect.dwDuration = 0; ff_effect.dwSamplePeriod = 0; diff --git a/platform/osx/joypad_osx.h b/platform/osx/joypad_osx.h index dc238e68e4..6c2a1ea70b 100644 --- a/platform/osx/joypad_osx.h +++ b/platform/osx/joypad_osx.h @@ -46,10 +46,10 @@ struct rec_element { IOHIDElementRef ref; IOHIDElementCookie cookie; - uint32_t usage; + uint32_t usage = 0; - int min; - int max; + int min = 0; + int max = 0; struct Comparator { bool operator()(const rec_element p_a, const rec_element p_b) const { return p_a.usage < p_b.usage; } @@ -57,22 +57,22 @@ struct rec_element { }; struct joypad { - IOHIDDeviceRef device_ref; + IOHIDDeviceRef device_ref = nullptr; Vector<rec_element> axis_elements; Vector<rec_element> button_elements; Vector<rec_element> hat_elements; - int id; + int id = 0; - io_service_t ffservice; /* Interface for force feedback, 0 = no ff */ + io_service_t ffservice = 0; /* Interface for force feedback, 0 = no ff */ FFCONSTANTFORCE ff_constant_force; FFDeviceObjectReference ff_device; FFEffectObjectReference ff_object; - uint64_t ff_timestamp; - LONG *ff_directions; + uint64_t ff_timestamp = 0; + LONG *ff_directions = nullptr; FFEFFECT ff_effect; - DWORD *ff_axes; + DWORD *ff_axes = nullptr; void add_hid_elements(CFArrayRef p_array); void add_hid_element(IOHIDElementRef p_element); diff --git a/platform/osx/os_osx.mm b/platform/osx/os_osx.mm index 399a29cbe0..ed03e953a5 100644 --- a/platform/osx/os_osx.mm +++ b/platform/osx/os_osx.mm @@ -286,7 +286,7 @@ Error OS_OSX::shell_open(String p_uri) { String OS_OSX::get_locale() const { NSString *locale_code = [[NSLocale preferredLanguages] objectAtIndex:0]; - return [locale_code UTF8String]; + return String([locale_code UTF8String]).replace("-", "_"); } String OS_OSX::get_executable_path() const { diff --git a/platform/server/detect.py b/platform/server/detect.py index a3f60b0710..bf4744a234 100644 --- a/platform/server/detect.py +++ b/platform/server/detect.py @@ -94,7 +94,6 @@ def configure(env): if "clang++" not in os.path.basename(env["CXX"]): env["CC"] = "clang" env["CXX"] = "clang++" - env.Append(CPPDEFINES=["TYPED_METHOD_BIND"]) env.extra_suffix = ".llvm" + env.extra_suffix if env["use_coverage"]: @@ -139,14 +138,31 @@ def configure(env): # freetype depends on libpng and zlib, so bundling one of them while keeping others # as shared libraries leads to weird issues - if env["builtin_freetype"] or env["builtin_libpng"] or env["builtin_zlib"]: + if ( + env["builtin_freetype"] + or env["builtin_libpng"] + or env["builtin_zlib"] + or env["builtin_graphite"] + or env["builtin_harfbuzz"] + ): env["builtin_freetype"] = True env["builtin_libpng"] = True env["builtin_zlib"] = True + env["builtin_graphite"] = True + env["builtin_harfbuzz"] = True if not env["builtin_freetype"]: env.ParseConfig("pkg-config freetype2 --cflags --libs") + if not env["builtin_graphite"]: + env.ParseConfig("pkg-config graphite2 --cflags --libs") + + if not env["builtin_icu"]: + env.ParseConfig("pkg-config icu-uc --cflags --libs") + + if not env["builtin_harfbuzz"]: + env.ParseConfig("pkg-config harfbuzz harfbuzz-icu --cflags --libs") + if not env["builtin_libpng"]: env.ParseConfig("pkg-config libpng16 --cflags --libs") @@ -234,7 +250,17 @@ def configure(env): env.Append(CPPDEFINES=["SERVER_ENABLED", "UNIX_ENABLED"]) if platform.system() == "Darwin": - env.Append(LINKFLAGS=["-framework", "Cocoa", "-framework", "Carbon", "-lz", "-framework", "IOKit"]) + env.Append( + LINKFLAGS=[ + "-framework", + "Cocoa", + "-framework", + "Carbon", + "-lz", + "-framework", + "IOKit", + ] + ) env.Append(LIBS=["pthread"]) diff --git a/platform/server/os_server.cpp b/platform/server/os_server.cpp index 9937ae5b62..f8b420b1c8 100644 --- a/platform/server/os_server.cpp +++ b/platform/server/os_server.cpp @@ -33,7 +33,7 @@ #include "core/string/print_string.h" #include "drivers/dummy/rasterizer_dummy.h" #include "drivers/dummy/texture_loader_dummy.h" -#include "servers/rendering/rendering_server_raster.h" +#include "servers/rendering/rendering_server_default.h" #include "main/main.h" @@ -49,14 +49,6 @@ const char *OS_Server::get_video_driver_name(int p_driver) const { return "Dummy"; } -int OS_Server::get_audio_driver_count() const { - return 1; -} - -const char *OS_Server::get_audio_driver_name(int p_driver) const { - return "Dummy"; -} - int OS_Server::get_current_video_driver() const { return video_driver_index; } @@ -76,7 +68,7 @@ Error OS_Server::initialize(const VideoMode &p_desired, int p_video_driver, int video_driver_index = p_video_driver; // unused in server platform, but should still be initialized - rendering_server = memnew(RenderingServerRaster); + rendering_server = memnew(RenderingServerDefault); rendering_server->init(); AudioDriverManager::initialize(p_audio_driver); diff --git a/platform/server/os_server.h b/platform/server/os_server.h index 06ea483fd4..95b6b5143b 100644 --- a/platform/server/os_server.h +++ b/platform/server/os_server.h @@ -41,28 +41,28 @@ #include "platform/x11/crash_handler_linuxbsd.h" #endif #include "servers/audio_server.h" -#include "servers/rendering/rasterizer.h" +#include "servers/rendering/renderer_compositor.h" #include "servers/rendering_server.h" #undef CursorShape class OS_Server : public OS_Unix { - RenderingServer *rendering_server; + RenderingServer *rendering_server = nullptr; VideoMode current_videomode; List<String> args; - MainLoop *main_loop; + MainLoop *main_loop = nullptr; - bool grab; + bool grab = false; virtual void delete_main_loop(); - bool force_quit; + bool force_quit = false; - InputDefault *input; + InputDefault *input = nullptr; CrashHandler crash_handler; - int video_driver_index; + int video_driver_index = 0; Ref<ResourceFormatDummyTexture> resource_loader_dummy; @@ -70,8 +70,6 @@ protected: virtual int get_video_driver_count() const; virtual const char *get_video_driver_name(int p_driver) const; virtual int get_current_video_driver() const; - virtual int get_audio_driver_count() const; - virtual const char *get_audio_driver_name(int p_driver) const; virtual void initialize_core(); virtual Error initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver); diff --git a/platform/uwp/context_egl_uwp.cpp b/platform/uwp/context_egl_uwp.cpp index 2da6c5897a..a6607ed42c 100644 --- a/platform/uwp/context_egl_uwp.cpp +++ b/platform/uwp/context_egl_uwp.cpp @@ -204,7 +204,8 @@ ContextEGL_UWP::ContextEGL_UWP(CoreWindow ^ p_window, Driver p_driver) : mEglContext(EGL_NO_CONTEXT), mEglSurface(EGL_NO_SURFACE), driver(p_driver), - window(p_window) {} + window(p_window), + vsync(false) {} ContextEGL_UWP::~ContextEGL_UWP() { cleanup(); diff --git a/platform/uwp/export/export.cpp b/platform/uwp/export/export.cpp index 30568241a9..00b57c48f3 100644 --- a/platform/uwp/export/export.cpp +++ b/platform/uwp/export/export.cpp @@ -108,7 +108,7 @@ class AppxPackager { struct BlockHash { String base64_hash; - size_t compressed_size; + size_t compressed_size = 0; }; struct FileMeta { @@ -120,12 +120,10 @@ class AppxPackager { Vector<BlockHash> hashes; uLong file_crc32 = 0; ZPOS64_T zip_offset = 0; - - FileMeta() {} }; String progress_task; - FileAccess *package; + FileAccess *package = nullptr; Set<String> mime_types; diff --git a/platform/uwp/joypad_uwp.h b/platform/uwp/joypad_uwp.h index 13f246a438..2df87d6fd5 100644 --- a/platform/uwp/joypad_uwp.h +++ b/platform/uwp/joypad_uwp.h @@ -58,21 +58,12 @@ private: struct ControllerDevice { Windows::Gaming::Input::IGameController ^ controller_reference; - int id; - bool connected; - ControllerType type; - float ff_timestamp; - float ff_end_timestamp; - bool vibrating; - - ControllerDevice() { - id = -1; - connected = false; - type = ControllerType::GAMEPAD_CONTROLLER; - ff_timestamp = 0.0f; - ff_end_timestamp = 0.0f; - vibrating = false; - } + int id = -1; + bool connected = false; + ControllerType type = ControllerType::GAMEPAD_CONTROLLER; + float ff_timestamp = 0; + float ff_end_timestamp = 0; + bool vibrating = false; }; ControllerDevice controllers[MAX_CONTROLLERS]; diff --git a/platform/uwp/os_uwp.cpp b/platform/uwp/os_uwp.cpp index 79508055e5..780eba8407 100644 --- a/platform/uwp/os_uwp.cpp +++ b/platform/uwp/os_uwp.cpp @@ -44,7 +44,7 @@ #include "main/main.h" #include "platform/windows/windows_terminal_logger.h" #include "servers/audio_server.h" -#include "servers/rendering/rendering_server_raster.h" +#include "servers/rendering/rendering_server_default.h" #include "servers/rendering/rendering_server_wrap_mt.h" #include "thread_uwp.h" @@ -240,7 +240,7 @@ Error OS_UWP::initialize(const VideoMode &p_desired, int p_video_driver, int p_a set_video_mode(vm); - rendering_server = memnew(RenderingServerRaster); + rendering_server = memnew(RenderingServerDefault); // FIXME: Reimplement threaded rendering if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) { rendering_server = memnew(RenderingServerWrapMT(rendering_server, false)); diff --git a/platform/uwp/os_uwp.h b/platform/uwp/os_uwp.h index 88961bf143..fd25cefe2b 100644 --- a/platform/uwp/os_uwp.h +++ b/platform/uwp/os_uwp.h @@ -39,7 +39,7 @@ #include "drivers/xaudio2/audio_driver_xaudio2.h" #include "joypad_uwp.h" #include "servers/audio_server.h" -#include "servers/rendering/rasterizer.h" +#include "servers/rendering/renderer_compositor.h" #include "servers/rendering_server.h" #include <fcntl.h> @@ -55,13 +55,13 @@ public: CHAR_EVENT_MESSAGE }; - bool alt, shift, control; - MessageType type; - bool pressed; - unsigned int keycode; - unsigned int physical_keycode; - unsigned int unicode; - bool echo; + bool alt = false, shift = false, control = false; + MessageType type = KEY_EVENT_MESSAGE; + bool pressed = false; + unsigned int keycode = 0; + unsigned int physical_keycode = 0; + unsigned int unicode = 0; + bool echo = false; CorePhysicalKeyStatus status; }; diff --git a/platform/windows/context_gl_windows.cpp b/platform/windows/context_gl_windows.cpp index 1c32639a38..54251fc66c 100644 --- a/platform/windows/context_gl_windows.cpp +++ b/platform/windows/context_gl_windows.cpp @@ -211,6 +211,7 @@ ContextGL_Windows::ContextGL_Windows(HWND hwnd, bool p_opengl_3_context) { hWnd = hwnd; use_vsync = false; vsync_via_compositor = false; + pixel_format = 0; } ContextGL_Windows::~ContextGL_Windows() { diff --git a/platform/windows/crash_handler_windows.cpp b/platform/windows/crash_handler_windows.cpp index 7abf451062..0f2f49c5ce 100644 --- a/platform/windows/crash_handler_windows.cpp +++ b/platform/windows/crash_handler_windows.cpp @@ -57,7 +57,7 @@ struct module_data { std::string image_name; std::string module_name; - void *base_address; + void *base_address = nullptr; DWORD load_size; }; diff --git a/platform/windows/detect.py b/platform/windows/detect.py index e0b2a52014..859051ede9 100644 --- a/platform/windows/detect.py +++ b/platform/windows/detect.py @@ -71,6 +71,7 @@ def get_opts(): BoolVariable("use_mingw", "Use the Mingw compiler, even if MSVC is installed. Only used on Windows.", False), BoolVariable("use_llvm", "Use the LLVM compiler", False), BoolVariable("use_thinlto", "Use ThinLTO", False), + BoolVariable("use_static_cpp", "Link MinGW/MSVC C++ runtime libraries statically", True), ] @@ -94,7 +95,7 @@ def build_res_file(target, source, env): out = subprocess.Popen(cmd, shell=True, stderr=subprocess.PIPE).communicate() if len(out[1]): return 1 - except: + except Exception: return 1 return 0 @@ -221,7 +222,11 @@ def configure_msvc(env, manual_msvc_config): ## Compile/link flags - env.AppendUnique(CCFLAGS=["/MT", "/Gd", "/GR", "/nologo"]) + if env["use_static_cpp"]: + env.AppendUnique(CCFLAGS=["/MT"]) + else: + env.AppendUnique(CCFLAGS=["/MD"]) + env.AppendUnique(CCFLAGS=["/Gd", "/GR", "/nologo"]) # Force to use Unicode encoding env.AppendUnique(CCFLAGS=["/utf-8"]) env.AppendUnique(CXXFLAGS=["/TP"]) # assume all sources are C++ @@ -373,12 +378,14 @@ def configure_mingw(env): mingw_prefix = "" if env["bits"] == "32": - env.Append(LINKFLAGS=["-static"]) - env.Append(LINKFLAGS=["-static-libgcc"]) - env.Append(LINKFLAGS=["-static-libstdc++"]) + if env["use_static_cpp"]: + env.Append(LINKFLAGS=["-static"]) + env.Append(LINKFLAGS=["-static-libgcc"]) + env.Append(LINKFLAGS=["-static-libstdc++"]) mingw_prefix = env["mingw_prefix_32"] else: - env.Append(LINKFLAGS=["-static"]) + if env["use_static_cpp"]: + env.Append(LINKFLAGS=["-static"]) mingw_prefix = env["mingw_prefix_64"] if env["use_llvm"]: diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index dfbb734ee4..62fcd20f61 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -2792,6 +2792,24 @@ void DisplayServerWindows::_process_key_events() { case WM_CHAR: { // extended keys should only be processed as WM_KEYDOWN message. if (!KeyMappingWindows::is_extended_key(ke.wParam) && ((i == 0 && ke.uMsg == WM_CHAR) || (i > 0 && key_event_buffer[i - 1].uMsg == WM_CHAR))) { + static char32_t prev_wc = 0; + char32_t unicode = ke.wParam; + if ((unicode & 0xfffffc00) == 0xd800) { + if (prev_wc != 0) { + ERR_PRINT("invalid utf16 surrogate input"); + } + prev_wc = unicode; + break; // Skip surrogate. + } else if ((unicode & 0xfffffc00) == 0xdc00) { + if (prev_wc == 0) { + ERR_PRINT("invalid utf16 surrogate input"); + break; // Skip invalid surrogate. + } + unicode = (prev_wc << 10UL) + unicode - ((0xd800 << 10UL) + 0xdc00 - 0x10000); + prev_wc = 0; + } else { + prev_wc = 0; + } Ref<InputEventKey> k; k.instance(); @@ -2803,7 +2821,7 @@ void DisplayServerWindows::_process_key_events() { k->set_pressed(true); k->set_keycode(KeyMappingWindows::get_keysym(ke.wParam)); k->set_physical_keycode(KeyMappingWindows::get_scansym((ke.lParam >> 16) & 0xFF, ke.lParam & (1 << 24))); - k->set_unicode(ke.wParam); + k->set_unicode(unicode); if (k->get_unicode() && gr_mem) { k->set_alt(false); k->set_control(false); @@ -2840,7 +2858,25 @@ void DisplayServerWindows::_process_key_events() { k->set_physical_keycode(KeyMappingWindows::get_scansym((ke.lParam >> 16) & 0xFF, ke.lParam & (1 << 24))); if (i + 1 < key_event_pos && key_event_buffer[i + 1].uMsg == WM_CHAR) { - k->set_unicode(key_event_buffer[i + 1].wParam); + char32_t unicode = key_event_buffer[i + 1].wParam; + static char32_t prev_wck = 0; + if ((unicode & 0xfffffc00) == 0xd800) { + if (prev_wck != 0) { + ERR_PRINT("invalid utf16 surrogate input"); + } + prev_wck = unicode; + break; // Skip surrogate. + } else if ((unicode & 0xfffffc00) == 0xdc00) { + if (prev_wck == 0) { + ERR_PRINT("invalid utf16 surrogate input"); + break; // Skip invalid surrogate. + } + unicode = (prev_wck << 10UL) + unicode - ((0xd800 << 10UL) + 0xdc00 - 0x10000); + prev_wck = 0; + } else { + prev_wck = 0; + } + k->set_unicode(unicode); } if (k->get_unicode() && gr_mem) { k->set_alt(false); @@ -3169,7 +3205,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win rendering_device_vulkan = memnew(RenderingDeviceVulkan); rendering_device_vulkan->initialize(context_vulkan); - RasterizerRD::make_current(); + RendererCompositorRD::make_current(); } #endif diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h index c64a1b3b09..684746919a 100644 --- a/platform/windows/display_server_windows.h +++ b/platform/windows/display_server_windows.h @@ -43,8 +43,8 @@ #include "joypad_windows.h" #include "key_mapping_windows.h" #include "servers/audio_server.h" -#include "servers/rendering/rasterizer.h" -#include "servers/rendering/rasterizer_rd/rasterizer_rd.h" +#include "servers/rendering/renderer_compositor.h" +#include "servers/rendering/renderer_rd/renderer_compositor_rd.h" #include "servers/rendering_server.h" #ifdef XAUDIO2_ENABLED diff --git a/platform/windows/joypad_windows.cpp b/platform/windows/joypad_windows.cpp index 2a5c8a7763..ae5e846dac 100644 --- a/platform/windows/joypad_windows.cpp +++ b/platform/windows/joypad_windows.cpp @@ -151,7 +151,7 @@ bool JoypadWindows::setup_dinput_joypad(const DIDEVICEINSTANCE *instance) { const DWORD devtype = (instance->dwDevType & 0xFF); - if ((devtype != DI8DEVTYPE_JOYSTICK) && (devtype != DI8DEVTYPE_GAMEPAD) && (devtype != DI8DEVTYPE_1STPERSON)) { + if ((devtype != DI8DEVTYPE_JOYSTICK) && (devtype != DI8DEVTYPE_GAMEPAD) && (devtype != DI8DEVTYPE_1STPERSON) && (devtype != DI8DEVTYPE_DRIVING)) { return false; } diff --git a/platform/windows/joypad_windows.h b/platform/windows/joypad_windows.h index 223b44fcd6..08adc6b663 100644 --- a/platform/windows/joypad_windows.h +++ b/platform/windows/joypad_windows.h @@ -92,21 +92,13 @@ private: }; struct xinput_gamepad { - int id; - bool attached; - bool vibrating; - DWORD last_packet; + int id = 0; + bool attached = false; + bool vibrating = false; + DWORD last_packet = 0; XINPUT_STATE state; - uint64_t ff_timestamp; - uint64_t ff_end_timestamp; - - xinput_gamepad() { - attached = false; - vibrating = false; - ff_timestamp = 0; - ff_end_timestamp = 0; - last_packet = 0; - } + uint64_t ff_timestamp = 0; + uint64_t ff_end_timestamp = 0; }; typedef DWORD(WINAPI *XInputGetState_t)(DWORD dwUserIndex, XINPUT_STATE *pState); diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index 646bc3aa4c..451f3bf18c 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -46,7 +46,7 @@ #include "main/main.h" #include "platform/windows/display_server_windows.h" #include "servers/audio_server.h" -#include "servers/rendering/rendering_server_raster.h" +#include "servers/rendering/rendering_server_default.h" #include "servers/rendering/rendering_server_wrap_mt.h" #include "windows_terminal_logger.h" @@ -183,7 +183,6 @@ void OS_Windows::initialize() { FileAccess::make_default<FileAccessWindows>(FileAccess::ACCESS_RESOURCES); FileAccess::make_default<FileAccessWindows>(FileAccess::ACCESS_USERDATA); FileAccess::make_default<FileAccessWindows>(FileAccess::ACCESS_FILESYSTEM); - //FileAccessBufferedFA<FileAccessWindows>::make_default(); DirAccess::make_default<DirAccessWindows>(DirAccess::ACCESS_RESOURCES); DirAccess::make_default<DirAccessWindows>(DirAccess::ACCESS_USERDATA); DirAccess::make_default<DirAccessWindows>(DirAccess::ACCESS_FILESYSTEM); @@ -562,21 +561,21 @@ String OS_Windows::get_locale() const { LANGID langid = GetUserDefaultUILanguage(); String neutral; - int lang = langid & ((1 << 9) - 1); - int sublang = langid & ~((1 << 9) - 1); + int lang = PRIMARYLANGID(langid); + int sublang = SUBLANGID(langid); while (wl->locale) { if (wl->main_lang == lang && wl->sublang == SUBLANG_NEUTRAL) neutral = wl->locale; if (lang == wl->main_lang && sublang == wl->sublang) - return wl->locale; + return String(wl->locale).replace("-", "_"); wl++; } if (neutral != "") - return neutral; + return String(neutral).replace("-", "_"); return "en"; } @@ -802,6 +801,11 @@ void OS_Windows::set_current_tablet_driver(const String &p_driver) { } OS_Windows::OS_Windows(HINSTANCE _hInstance) { + ticks_per_second = 0; + ticks_start = 0; + main_loop = nullptr; + process_map = nullptr; + //Note: Wacom WinTab driver API for pen input, for devices incompatible with Windows Ink. HMODULE wintab_lib = LoadLibraryW(L"wintab32.dll"); if (wintab_lib) { diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h index a3dbb23182..5451e15d36 100644 --- a/platform/windows/os_windows.h +++ b/platform/windows/os_windows.h @@ -40,7 +40,7 @@ #include "drivers/winmidi/midi_driver_winmidi.h" #include "key_mapping_windows.h" #include "servers/audio_server.h" -#include "servers/rendering/rasterizer.h" +#include "servers/rendering/renderer_compositor.h" #include "servers/rendering_server.h" #ifdef XAUDIO2_ENABLED #include "drivers/xaudio2/audio_driver_xaudio2.h" |