diff options
273 files changed, 6864 insertions, 1530 deletions
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b8082fb136..a1d406ff92 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -36,6 +36,7 @@ doc_classes/* @godotengine/documentation /drivers/xaudio2/ @godotengine/audio ## Rendering +/drivers/d3d12/ @godotengine/rendering /drivers/dummy/ @godotengine/rendering /drivers/gles3/ @godotengine/rendering /drivers/spirv-reflect/ @godotengine/rendering diff --git a/.github/workflows/web_builds.yml b/.github/workflows/web_builds.yml index 9524b5260b..a1b7f95cec 100644 --- a/.github/workflows/web_builds.yml +++ b/.github/workflows/web_builds.yml @@ -40,10 +40,11 @@ jobs: - uses: actions/checkout@v4 - name: Set up Emscripten latest - uses: mymindstorm/setup-emsdk@v12 + uses: mymindstorm/setup-emsdk@v14 with: version: ${{env.EM_VERSION}} actions-cache-folder: ${{env.EM_CACHE_FOLDER}} + cache-key: emsdk-${{ matrix.cache-name }}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}} - name: Verify Emscripten setup run: | diff --git a/.github/workflows/windows_builds.yml b/.github/workflows/windows_builds.yml index 35ed1a773d..2794c83e22 100644 --- a/.github/workflows/windows_builds.yml +++ b/.github/workflows/windows_builds.yml @@ -7,7 +7,7 @@ on: env: # Used for the cache key. Add version suffix to force clean build. GODOT_BASE_BRANCH: master - SCONSFLAGS: verbose=yes warnings=extra werror=yes module_text_server_fb_enabled=yes d3d12=yes "dxc_path=${{github.workspace}}/dxc" "mesa_libs=${{github.workspace}}/godot-nir-static" + SCONSFLAGS: verbose=yes warnings=extra werror=yes module_text_server_fb_enabled=yes d3d12=yes SCONS_CACHE_MSVC_CONFIG: true concurrency: @@ -49,27 +49,8 @@ jobs: - name: Setup python and scons uses: ./.github/actions/godot-deps - - name: Download pre-built DirectX Shader Compiler - uses: dsaltares/fetch-gh-release-asset@1.0.0 - with: - repo: Microsoft/DirectXShaderCompiler - version: tags/v1.7.2308 - file: dxc_2023_08_14.zip - target: dxc.zip - - - name: Extract pre-built DirectX Shader Compiler - run: 7z x dxc.zip -o${{github.workspace}}/dxc - - - name: Download pre-built Mesa-NIR - uses: dsaltares/fetch-gh-release-asset@1.0.0 - with: - repo: godotengine/godot-nir-static - version: tags/23.1.0-devel - file: godot-nir-23.1.0-1-devel.zip - target: godot-nir-static.zip - - - name: Extract pre-built Mesa-NIR - run: 7z x godot-nir-static.zip -o${{github.workspace}}/godot-nir-static + - name: Download Direct3D 12 SDK components + run: python ./misc/scripts/install_d3d12_sdk_windows.py - name: Setup MSVC problem matcher uses: ammaraskar/msvc-problem-matcher@master diff --git a/.lgtm.yml b/.lgtm.yml deleted file mode 100644 index e4841eaff4..0000000000 --- a/.lgtm.yml +++ /dev/null @@ -1,7 +0,0 @@ -extraction: - cpp: - after_prepare: - - pip3 install scons - - PATH="/opt/work/.local/bin:$PATH" - index: - build_command: scons -j2 diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt index 625aac8c13..125c5618c7 100644 --- a/COPYRIGHT.txt +++ b/COPYRIGHT.txt @@ -529,7 +529,7 @@ License: Expat Files: ./thirdparty/zlib/ Comment: zlib -Copyright: 1995-2023, Jean-loup Gailly and Mark Adler +Copyright: 1995-2024, Jean-loup Gailly and Mark Adler License: Zlib Files: ./thirdparty/zstd/ diff --git a/core/config/engine.cpp b/core/config/engine.cpp index 24080c056a..203f8c3882 100644 --- a/core/config/engine.cpp +++ b/core/config/engine.cpp @@ -110,7 +110,6 @@ Dictionary Engine::get_version_info() const { dict["hex"] = VERSION_HEX; dict["status"] = VERSION_STATUS; dict["build"] = VERSION_BUILD; - dict["year"] = VERSION_YEAR; String hash = String(VERSION_HASH); dict["hash"] = hash.is_empty() ? String("unknown") : hash; diff --git a/core/core_constants.cpp b/core/core_constants.cpp index 3b96fc20c6..aaabbabfd9 100644 --- a/core/core_constants.cpp +++ b/core/core_constants.cpp @@ -507,6 +507,10 @@ void register_global_constants() { BIND_CORE_BITFIELD_CLASS_FLAG(KeyModifierMask, KEY_MASK, KPAD); BIND_CORE_BITFIELD_CLASS_FLAG(KeyModifierMask, KEY_MASK, GROUP_SWITCH); + BIND_CORE_ENUM_CLASS_CONSTANT(KeyLocation, KEY_LOCATION, UNSPECIFIED); + BIND_CORE_ENUM_CLASS_CONSTANT(KeyLocation, KEY_LOCATION, LEFT); + BIND_CORE_ENUM_CLASS_CONSTANT(KeyLocation, KEY_LOCATION, RIGHT); + BIND_CORE_ENUM_CLASS_CONSTANT(MouseButton, MOUSE_BUTTON, NONE); BIND_CORE_ENUM_CLASS_CONSTANT(MouseButton, MOUSE_BUTTON, LEFT); BIND_CORE_ENUM_CLASS_CONSTANT(MouseButton, MOUSE_BUTTON, RIGHT); diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp index ce01531b5c..2904e54b22 100644 --- a/core/extension/gdextension.cpp +++ b/core/extension/gdextension.cpp @@ -653,6 +653,8 @@ void GDExtension::_unregister_extension_class(GDExtensionClassLibraryPtr p_libra if (!ext->is_reloading) { self->extension_classes.erase(class_name); } + + GDExtensionEditorHelp::remove_class(class_name); #else self->extension_classes.erase(class_name); #endif @@ -1196,4 +1198,17 @@ void GDExtensionEditorPlugins::remove_extension_class(const StringName &p_class_ extension_classes.erase(p_class_name); } } + +GDExtensionEditorHelp::EditorHelpLoadXmlBufferFunc GDExtensionEditorHelp::editor_help_load_xml_buffer = nullptr; +GDExtensionEditorHelp::EditorHelpRemoveClassFunc GDExtensionEditorHelp::editor_help_remove_class = nullptr; + +void GDExtensionEditorHelp::load_xml_buffer(const uint8_t *p_buffer, int p_size) { + ERR_FAIL_NULL(editor_help_load_xml_buffer); + editor_help_load_xml_buffer(p_buffer, p_size); +} + +void GDExtensionEditorHelp::remove_class(const String &p_class) { + ERR_FAIL_NULL(editor_help_remove_class); + editor_help_remove_class(p_class); +} #endif // TOOLS_ENABLED diff --git a/core/extension/gdextension.h b/core/extension/gdextension.h index 0b39581751..588efed017 100644 --- a/core/extension/gdextension.h +++ b/core/extension/gdextension.h @@ -197,6 +197,26 @@ public: return extension_classes; } }; + +class GDExtensionEditorHelp { +protected: + friend class EditorHelp; + + // Similarly to EditorNode above, we need to be able to ask EditorHelp to parse + // new documentation data. Note though that, differently from EditorHelp, this + // is initialized even _before_ it gets instantiated, as we need to rely on + // this method while initializing the engine. + typedef void (*EditorHelpLoadXmlBufferFunc)(const uint8_t *p_buffer, int p_size); + static EditorHelpLoadXmlBufferFunc editor_help_load_xml_buffer; + + typedef void (*EditorHelpRemoveClassFunc)(const String &p_class); + static EditorHelpRemoveClassFunc editor_help_remove_class; + +public: + static void load_xml_buffer(const uint8_t *p_buffer, int p_size); + static void remove_class(const String &p_class); +}; + #endif // TOOLS_ENABLED #endif // GDEXTENSION_H diff --git a/core/extension/gdextension_interface.cpp b/core/extension/gdextension_interface.cpp index e02e7aa701..88572f24f0 100644 --- a/core/extension/gdextension_interface.cpp +++ b/core/extension/gdextension_interface.cpp @@ -42,6 +42,8 @@ #include "core/variant/variant.h" #include "core/version.h" +#include <string.h> + class CallableCustomExtension : public CallableCustom { void *userdata; void *token; @@ -1373,6 +1375,19 @@ static void gdextension_editor_remove_plugin(GDExtensionConstStringNamePtr p_cla #endif } +static void gdextension_editor_help_load_xml_from_utf8_chars_and_len(const char *p_data, GDExtensionInt p_size) { +#ifdef TOOLS_ENABLED + GDExtensionEditorHelp::load_xml_buffer((const uint8_t *)p_data, p_size); +#endif +} + +static void gdextension_editor_help_load_xml_from_utf8_chars(const char *p_data) { +#ifdef TOOLS_ENABLED + size_t len = strlen(p_data); + gdextension_editor_help_load_xml_from_utf8_chars_and_len(p_data, len); +#endif +} + #define REGISTER_INTERFACE_FUNC(m_name) GDExtension::register_interface_function(#m_name, (GDExtensionInterfaceFunctionPtr)&gdextension_##m_name) void gdextension_setup_interface() { @@ -1516,6 +1531,8 @@ void gdextension_setup_interface() { REGISTER_INTERFACE_FUNC(classdb_get_class_tag); REGISTER_INTERFACE_FUNC(editor_add_plugin); REGISTER_INTERFACE_FUNC(editor_remove_plugin); + REGISTER_INTERFACE_FUNC(editor_help_load_xml_from_utf8_chars); + REGISTER_INTERFACE_FUNC(editor_help_load_xml_from_utf8_chars_and_len); } #undef REGISTER_INTERFACE_FUNCTION diff --git a/core/extension/gdextension_interface.h b/core/extension/gdextension_interface.h index d58f0226d8..8fda11c651 100644 --- a/core/extension/gdextension_interface.h +++ b/core/extension/gdextension_interface.h @@ -2617,6 +2617,31 @@ typedef void (*GDExtensionInterfaceEditorAddPlugin)(GDExtensionConstStringNamePt */ typedef void (*GDExtensionInterfaceEditorRemovePlugin)(GDExtensionConstStringNamePtr p_class_name); +/** + * @name editor_help_load_xml_from_utf8_chars + * @since 4.3 + * + * Loads new XML-formatted documentation data in the editor. + * + * The provided pointer can be immediately freed once the function returns. + * + * @param p_data A pointer to an UTF-8 encoded C string (null terminated). + */ +typedef void (*GDExtensionsInterfaceEditorHelpLoadXmlFromUtf8Chars)(const char *p_data); + +/** + * @name editor_help_load_xml_from_utf8_chars_and_len + * @since 4.3 + * + * Loads new XML-formatted documentation data in the editor. + * + * The provided pointer can be immediately freed once the function returns. + * + * @param p_data A pointer to an UTF-8 encoded C string. + * @param p_size The number of bytes (not code units). + */ +typedef void (*GDExtensionsInterfaceEditorHelpLoadXmlFromUtf8CharsAndLen)(const char *p_data, GDExtensionInt p_size); + #ifdef __cplusplus } #endif diff --git a/core/input/input_event.cpp b/core/input/input_event.cpp index e99dd04599..89ffcecf50 100644 --- a/core/input/input_event.cpp +++ b/core/input/input_event.cpp @@ -364,6 +364,15 @@ char32_t InputEventKey::get_unicode() const { return unicode; } +void InputEventKey::set_location(KeyLocation p_key_location) { + location = p_key_location; + emit_changed(); +} + +KeyLocation InputEventKey::get_location() const { + return location; +} + void InputEventKey::set_echo(bool p_enable) { echo = p_enable; emit_changed(); @@ -436,6 +445,23 @@ String InputEventKey::as_text_key_label() const { return mods_text.is_empty() ? kc : mods_text + "+" + kc; } +String InputEventKey::as_text_location() const { + String loc; + + switch (location) { + case KeyLocation::LEFT: + loc = "left"; + break; + case KeyLocation::RIGHT: + loc = "right"; + break; + default: + break; + } + + return loc; +} + String InputEventKey::as_text() const { String kc; @@ -464,6 +490,11 @@ String InputEventKey::to_string() { String kc = ""; String physical = "false"; + String loc = as_text_location(); + if (loc.is_empty()) { + loc = "unspecified"; + } + if (keycode == Key::NONE && physical_keycode == Key::NONE && unicode != 0) { kc = "U+" + String::num_uint64(unicode, 16) + " (" + String::chr(unicode) + ")"; } else if (keycode != Key::NONE) { @@ -478,7 +509,7 @@ String InputEventKey::to_string() { String mods = InputEventWithModifiers::as_text(); mods = mods.is_empty() ? "none" : mods; - return vformat("InputEventKey: keycode=%s, mods=%s, physical=%s, pressed=%s, echo=%s", kc, mods, physical, p, e); + return vformat("InputEventKey: keycode=%s, mods=%s, physical=%s, location=%s, pressed=%s, echo=%s", kc, mods, physical, loc, p, e); } Ref<InputEventKey> InputEventKey::create_reference(Key p_keycode, bool p_physical) { @@ -531,6 +562,9 @@ bool InputEventKey::action_match(const Ref<InputEvent> &p_event, bool p_exact_ma match = keycode == key->keycode; } else if (physical_keycode != Key::NONE) { match = physical_keycode == key->physical_keycode; + if (location != KeyLocation::UNSPECIFIED) { + match &= location == key->location; + } } else { match = false; } @@ -572,6 +606,9 @@ bool InputEventKey::is_match(const Ref<InputEvent> &p_event, bool p_exact_match) return (keycode == key->keycode) && (!p_exact_match || get_modifiers_mask() == key->get_modifiers_mask()); } else if (physical_keycode != Key::NONE) { + if (location != KeyLocation::UNSPECIFIED && location != key->location) { + return false; + } return (physical_keycode == key->physical_keycode) && (!p_exact_match || get_modifiers_mask() == key->get_modifiers_mask()); } else { @@ -594,6 +631,9 @@ void InputEventKey::_bind_methods() { ClassDB::bind_method(D_METHOD("set_unicode", "unicode"), &InputEventKey::set_unicode); ClassDB::bind_method(D_METHOD("get_unicode"), &InputEventKey::get_unicode); + ClassDB::bind_method(D_METHOD("set_location", "location"), &InputEventKey::set_location); + ClassDB::bind_method(D_METHOD("get_location"), &InputEventKey::get_location); + ClassDB::bind_method(D_METHOD("set_echo", "echo"), &InputEventKey::set_echo); ClassDB::bind_method(D_METHOD("get_keycode_with_modifiers"), &InputEventKey::get_keycode_with_modifiers); @@ -603,12 +643,14 @@ void InputEventKey::_bind_methods() { ClassDB::bind_method(D_METHOD("as_text_keycode"), &InputEventKey::as_text_keycode); ClassDB::bind_method(D_METHOD("as_text_physical_keycode"), &InputEventKey::as_text_physical_keycode); ClassDB::bind_method(D_METHOD("as_text_key_label"), &InputEventKey::as_text_key_label); + ClassDB::bind_method(D_METHOD("as_text_location"), &InputEventKey::as_text_location); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "pressed"), "set_pressed", "is_pressed"); ADD_PROPERTY(PropertyInfo(Variant::INT, "keycode"), "set_keycode", "get_keycode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "physical_keycode"), "set_physical_keycode", "get_physical_keycode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "key_label"), "set_key_label", "get_key_label"); ADD_PROPERTY(PropertyInfo(Variant::INT, "unicode"), "set_unicode", "get_unicode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "location", PROPERTY_HINT_ENUM, "Unspecified,Left,Right"), "set_location", "get_location"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "echo"), "set_echo", "is_echo"); } diff --git a/core/input/input_event.h b/core/input/input_event.h index ed7ccf0a9f..61a53116e9 100644 --- a/core/input/input_event.h +++ b/core/input/input_event.h @@ -157,6 +157,7 @@ class InputEventKey : public InputEventWithModifiers { Key physical_keycode = Key::NONE; Key key_label = Key::NONE; uint32_t unicode = 0; ///unicode + KeyLocation location = KeyLocation::UNSPECIFIED; bool echo = false; /// true if this is an echo key @@ -178,6 +179,9 @@ public: void set_unicode(char32_t p_unicode); char32_t get_unicode() const; + void set_location(KeyLocation p_key_location); + KeyLocation get_location() const; + void set_echo(bool p_enable); virtual bool is_echo() const override; @@ -193,6 +197,7 @@ public: virtual String as_text_physical_keycode() const; virtual String as_text_keycode() const; virtual String as_text_key_label() const; + virtual String as_text_location() const; virtual String as_text() const override; virtual String to_string() override; diff --git a/core/io/file_access_memory.cpp b/core/io/file_access_memory.cpp index 0983920b94..da02c883e8 100644 --- a/core/io/file_access_memory.cpp +++ b/core/io/file_access_memory.cpp @@ -119,7 +119,7 @@ uint64_t FileAccessMemory::get_length() const { } bool FileAccessMemory::eof_reached() const { - return pos > length; + return pos >= length; } uint8_t FileAccessMemory::get_8() const { diff --git a/core/math/basis.cpp b/core/math/basis.cpp index cd8c87b158..1ff6cdd588 100644 --- a/core/math/basis.cpp +++ b/core/math/basis.cpp @@ -722,7 +722,7 @@ Basis::operator String() const { Quaternion Basis::get_quaternion() const { #ifdef MATH_CHECKS - ERR_FAIL_COND_V_MSG(!is_rotation(), Quaternion(), "Basis must be normalized in order to be casted to a Quaternion. Use get_rotation_quaternion() or call orthonormalized() if the Basis contains linearly independent vectors."); + ERR_FAIL_COND_V_MSG(!is_rotation(), Quaternion(), "Basis " + operator String() + " must be normalized in order to be casted to a Quaternion. Use get_rotation_quaternion() or call orthonormalized() if the Basis contains linearly independent vectors."); #endif /* Allow getting a quaternion from an unnormalized transform */ Basis m = *this; @@ -849,7 +849,7 @@ void Basis::set_quaternion(const Quaternion &p_quaternion) { void Basis::set_axis_angle(const Vector3 &p_axis, real_t p_angle) { // Rotation matrix from axis and angle, see https://en.wikipedia.org/wiki/Rotation_matrix#Rotation_matrix_from_axis_angle #ifdef MATH_CHECKS - ERR_FAIL_COND_MSG(!p_axis.is_normalized(), "The axis Vector3 must be normalized."); + ERR_FAIL_COND_MSG(!p_axis.is_normalized(), "The axis Vector3 " + p_axis.operator String() + " must be normalized."); #endif Vector3 axis_sq(p_axis.x * p_axis.x, p_axis.y * p_axis.y, p_axis.z * p_axis.z); real_t cosine = Math::cos(p_angle); diff --git a/core/math/quaternion.cpp b/core/math/quaternion.cpp index e4ad17c8ef..cbaaa1371a 100644 --- a/core/math/quaternion.cpp +++ b/core/math/quaternion.cpp @@ -41,7 +41,7 @@ real_t Quaternion::angle_to(const Quaternion &p_to) const { Vector3 Quaternion::get_euler(EulerOrder p_order) const { #ifdef MATH_CHECKS - ERR_FAIL_COND_V_MSG(!is_normalized(), Vector3(0, 0, 0), "The quaternion must be normalized."); + ERR_FAIL_COND_V_MSG(!is_normalized(), Vector3(0, 0, 0), "The quaternion " + operator String() + " must be normalized."); #endif return Basis(*this).get_euler(p_order); } @@ -88,7 +88,7 @@ bool Quaternion::is_normalized() const { Quaternion Quaternion::inverse() const { #ifdef MATH_CHECKS - ERR_FAIL_COND_V_MSG(!is_normalized(), Quaternion(), "The quaternion must be normalized."); + ERR_FAIL_COND_V_MSG(!is_normalized(), Quaternion(), "The quaternion " + operator String() + " must be normalized."); #endif return Quaternion(-x, -y, -z, w); } @@ -112,8 +112,8 @@ Quaternion Quaternion::exp() const { Quaternion Quaternion::slerp(const Quaternion &p_to, const real_t &p_weight) const { #ifdef MATH_CHECKS - ERR_FAIL_COND_V_MSG(!is_normalized(), Quaternion(), "The start quaternion must be normalized."); - ERR_FAIL_COND_V_MSG(!p_to.is_normalized(), Quaternion(), "The end quaternion must be normalized."); + ERR_FAIL_COND_V_MSG(!is_normalized(), Quaternion(), "The start quaternion " + operator String() + " must be normalized."); + ERR_FAIL_COND_V_MSG(!p_to.is_normalized(), Quaternion(), "The end quaternion " + p_to.operator String() + " must be normalized."); #endif Quaternion to1; real_t omega, cosom, sinom, scale0, scale1; @@ -153,8 +153,8 @@ Quaternion Quaternion::slerp(const Quaternion &p_to, const real_t &p_weight) con Quaternion Quaternion::slerpni(const Quaternion &p_to, const real_t &p_weight) const { #ifdef MATH_CHECKS - ERR_FAIL_COND_V_MSG(!is_normalized(), Quaternion(), "The start quaternion must be normalized."); - ERR_FAIL_COND_V_MSG(!p_to.is_normalized(), Quaternion(), "The end quaternion must be normalized."); + ERR_FAIL_COND_V_MSG(!is_normalized(), Quaternion(), "The start quaternion " + operator String() + " must be normalized."); + ERR_FAIL_COND_V_MSG(!p_to.is_normalized(), Quaternion(), "The end quaternion " + p_to.operator String() + " must be normalized."); #endif const Quaternion &from = *this; @@ -177,8 +177,8 @@ Quaternion Quaternion::slerpni(const Quaternion &p_to, const real_t &p_weight) c Quaternion Quaternion::spherical_cubic_interpolate(const Quaternion &p_b, const Quaternion &p_pre_a, const Quaternion &p_post_b, const real_t &p_weight) const { #ifdef MATH_CHECKS - ERR_FAIL_COND_V_MSG(!is_normalized(), Quaternion(), "The start quaternion must be normalized."); - ERR_FAIL_COND_V_MSG(!p_b.is_normalized(), Quaternion(), "The end quaternion must be normalized."); + ERR_FAIL_COND_V_MSG(!is_normalized(), Quaternion(), "The start quaternion " + operator String() + " must be normalized."); + ERR_FAIL_COND_V_MSG(!p_b.is_normalized(), Quaternion(), "The end quaternion " + p_b.operator String() + " must be normalized."); #endif Quaternion from_q = *this; Quaternion pre_q = p_pre_a; @@ -228,8 +228,8 @@ Quaternion Quaternion::spherical_cubic_interpolate(const Quaternion &p_b, const Quaternion Quaternion::spherical_cubic_interpolate_in_time(const Quaternion &p_b, const Quaternion &p_pre_a, const Quaternion &p_post_b, const real_t &p_weight, const real_t &p_b_t, const real_t &p_pre_a_t, const real_t &p_post_b_t) const { #ifdef MATH_CHECKS - ERR_FAIL_COND_V_MSG(!is_normalized(), Quaternion(), "The start quaternion must be normalized."); - ERR_FAIL_COND_V_MSG(!p_b.is_normalized(), Quaternion(), "The end quaternion must be normalized."); + ERR_FAIL_COND_V_MSG(!is_normalized(), Quaternion(), "The start quaternion " + operator String() + " must be normalized."); + ERR_FAIL_COND_V_MSG(!p_b.is_normalized(), Quaternion(), "The end quaternion " + p_b.operator String() + " must be normalized."); #endif Quaternion from_q = *this; Quaternion pre_q = p_pre_a; @@ -294,7 +294,7 @@ real_t Quaternion::get_angle() const { Quaternion::Quaternion(const Vector3 &p_axis, real_t p_angle) { #ifdef MATH_CHECKS - ERR_FAIL_COND_MSG(!p_axis.is_normalized(), "The axis Vector3 must be normalized."); + ERR_FAIL_COND_MSG(!p_axis.is_normalized(), "The axis Vector3 " + p_axis.operator String() + " must be normalized."); #endif real_t d = p_axis.length(); if (d == 0) { diff --git a/core/math/quaternion.h b/core/math/quaternion.h index ea952304a5..f8133df559 100644 --- a/core/math/quaternion.h +++ b/core/math/quaternion.h @@ -33,8 +33,7 @@ #include "core/math/math_funcs.h" #include "core/math/vector3.h" - -class String; +#include "core/string/ustring.h" struct _NO_DISCARD_ Quaternion { union { @@ -90,7 +89,7 @@ struct _NO_DISCARD_ Quaternion { _FORCE_INLINE_ Vector3 xform(const Vector3 &v) const { #ifdef MATH_CHECKS - ERR_FAIL_COND_V_MSG(!is_normalized(), v, "The quaternion must be normalized."); + ERR_FAIL_COND_V_MSG(!is_normalized(), v, "The quaternion " + operator String() + " must be normalized."); #endif Vector3 u(x, y, z); Vector3 uv = u.cross(v); diff --git a/core/math/vector2.cpp b/core/math/vector2.cpp index df8c804243..9982415b97 100644 --- a/core/math/vector2.cpp +++ b/core/math/vector2.cpp @@ -162,7 +162,7 @@ Vector2 Vector2::move_toward(const Vector2 &p_to, const real_t p_delta) const { // slide returns the component of the vector along the given plane, specified by its normal vector. Vector2 Vector2::slide(const Vector2 &p_normal) const { #ifdef MATH_CHECKS - ERR_FAIL_COND_V_MSG(!p_normal.is_normalized(), Vector2(), "The normal Vector2 must be normalized."); + ERR_FAIL_COND_V_MSG(!p_normal.is_normalized(), Vector2(), "The normal Vector2 " + p_normal.operator String() + "must be normalized."); #endif return *this - p_normal * this->dot(p_normal); } @@ -173,7 +173,7 @@ Vector2 Vector2::bounce(const Vector2 &p_normal) const { Vector2 Vector2::reflect(const Vector2 &p_normal) const { #ifdef MATH_CHECKS - ERR_FAIL_COND_V_MSG(!p_normal.is_normalized(), Vector2(), "The normal Vector2 must be normalized."); + ERR_FAIL_COND_V_MSG(!p_normal.is_normalized(), Vector2(), "The normal Vector2 " + p_normal.operator String() + "must be normalized."); #endif return 2.0f * p_normal * this->dot(p_normal) - *this; } diff --git a/core/math/vector3.h b/core/math/vector3.h index 18943a820f..7551b21493 100644 --- a/core/math/vector3.h +++ b/core/math/vector3.h @@ -33,8 +33,8 @@ #include "core/error/error_macros.h" #include "core/math/math_funcs.h" +#include "core/string/ustring.h" -class String; struct Basis; struct Vector2; struct Vector3i; @@ -512,7 +512,7 @@ void Vector3::zero() { // slide returns the component of the vector along the given plane, specified by its normal vector. Vector3 Vector3::slide(const Vector3 &p_normal) const { #ifdef MATH_CHECKS - ERR_FAIL_COND_V_MSG(!p_normal.is_normalized(), Vector3(), "The normal Vector3 must be normalized."); + ERR_FAIL_COND_V_MSG(!p_normal.is_normalized(), Vector3(), "The normal Vector3 " + p_normal.operator String() + " must be normalized."); #endif return *this - p_normal * this->dot(p_normal); } @@ -523,7 +523,7 @@ Vector3 Vector3::bounce(const Vector3 &p_normal) const { Vector3 Vector3::reflect(const Vector3 &p_normal) const { #ifdef MATH_CHECKS - ERR_FAIL_COND_V_MSG(!p_normal.is_normalized(), Vector3(), "The normal Vector3 must be normalized."); + ERR_FAIL_COND_V_MSG(!p_normal.is_normalized(), Vector3(), "The normal Vector3 " + p_normal.operator String() + " must be normalized."); #endif return 2.0f * p_normal * this->dot(p_normal) - *this; } diff --git a/core/object/undo_redo.cpp b/core/object/undo_redo.cpp index 3c1d4ef95e..569e6ae19f 100644 --- a/core/object/undo_redo.cpp +++ b/core/object/undo_redo.cpp @@ -316,6 +316,14 @@ void UndoRedo::commit_action(bool p_execute) { _redo(p_execute); // perform action committing--; + if (max_steps > 0) { + // Clear early steps. + + while (actions.size() > max_steps) { + _pop_history_tail(); + } + } + if (add_message && callback && actions.size() > 0) { callback(callback_ud, actions[actions.size() - 1].name); } @@ -473,6 +481,14 @@ uint64_t UndoRedo::get_version() const { return version; } +void UndoRedo::set_max_steps(int p_max_steps) { + max_steps = p_max_steps; +} + +int UndoRedo::get_max_steps() const { + return max_steps; +} + void UndoRedo::set_commit_notify_callback(CommitNotifyCallback p_callback, void *p_ud) { callback = p_callback; callback_ud = p_ud; @@ -517,9 +533,13 @@ void UndoRedo::_bind_methods() { ClassDB::bind_method(D_METHOD("has_undo"), &UndoRedo::has_undo); ClassDB::bind_method(D_METHOD("has_redo"), &UndoRedo::has_redo); ClassDB::bind_method(D_METHOD("get_version"), &UndoRedo::get_version); + ClassDB::bind_method(D_METHOD("set_max_steps", "max_steps"), &UndoRedo::set_max_steps); + ClassDB::bind_method(D_METHOD("get_max_steps"), &UndoRedo::get_max_steps); ClassDB::bind_method(D_METHOD("redo"), &UndoRedo::redo); ClassDB::bind_method(D_METHOD("undo"), &UndoRedo::undo); + ADD_PROPERTY(PropertyInfo(Variant::INT, "max_steps", PROPERTY_HINT_RANGE, "0,50,1,or_greater"), "set_max_steps", "get_max_steps"); + ADD_SIGNAL(MethodInfo("version_changed")); BIND_ENUM_CONSTANT(MERGE_DISABLE); diff --git a/core/object/undo_redo.h b/core/object/undo_redo.h index b3a3322e4b..62aebff877 100644 --- a/core/object/undo_redo.h +++ b/core/object/undo_redo.h @@ -80,6 +80,7 @@ private: int current_action = -1; bool force_keep_in_merge_ends = false; int action_level = 0; + int max_steps = 0; MergeMode merge_mode = MERGE_DISABLE; bool merging = false; uint64_t version = 1; @@ -135,6 +136,9 @@ public: uint64_t get_version() const; + void set_max_steps(int p_max_steps); + int get_max_steps() const; + void set_commit_notify_callback(CommitNotifyCallback p_callback, void *p_ud); void set_method_notify_callback(MethodNotifyCallback p_method_callback, void *p_ud); diff --git a/core/os/keyboard.h b/core/os/keyboard.h index 785972d31d..2051973336 100644 --- a/core/os/keyboard.h +++ b/core/os/keyboard.h @@ -260,6 +260,12 @@ enum class KeyModifierMask { GROUP_SWITCH = (1 << 30) }; +enum class KeyLocation { + UNSPECIFIED, + LEFT, + RIGHT +}; + // To avoid having unnecessary operators, only define the ones that are needed. constexpr Key operator-(uint32_t a, Key b) { diff --git a/core/os/spin_lock.h b/core/os/spin_lock.h index 93ea782b60..d386cd5890 100644 --- a/core/os/spin_lock.h +++ b/core/os/spin_lock.h @@ -33,6 +33,25 @@ #include "core/typedefs.h" +#if defined(__APPLE__) + +#include <os/lock.h> + +class SpinLock { + mutable os_unfair_lock _lock = OS_UNFAIR_LOCK_INIT; + +public: + _ALWAYS_INLINE_ void lock() const { + os_unfair_lock_lock(&_lock); + } + + _ALWAYS_INLINE_ void unlock() const { + os_unfair_lock_unlock(&_lock); + } +}; + +#else + #include <atomic> class SpinLock { @@ -49,4 +68,6 @@ public: } }; +#endif // __APPLE__ + #endif // SPIN_LOCK_H diff --git a/core/variant/binder_common.h b/core/variant/binder_common.h index 34b54f1d00..c9f5ae7fc6 100644 --- a/core/variant/binder_common.h +++ b/core/variant/binder_common.h @@ -176,6 +176,7 @@ VARIANT_ENUM_CAST(Variant::Operator); VARIANT_ENUM_CAST(Key); VARIANT_BITFIELD_CAST(KeyModifierMask); +VARIANT_ENUM_CAST(KeyLocation); static inline Key &operator|=(Key &a, BitField<KeyModifierMask> b) { a = static_cast<Key>(static_cast<int>(a) | static_cast<int>(b.operator int64_t())); diff --git a/core/variant/variant.h b/core/variant/variant.h index 602d287f22..429b346896 100644 --- a/core/variant/variant.h +++ b/core/variant/variant.h @@ -502,6 +502,7 @@ public: VARIANT_ENUM_CLASS_CONSTRUCTOR(JoyAxis) VARIANT_ENUM_CLASS_CONSTRUCTOR(JoyButton) VARIANT_ENUM_CLASS_CONSTRUCTOR(Key) + VARIANT_ENUM_CLASS_CONSTRUCTOR(KeyLocation) VARIANT_ENUM_CLASS_CONSTRUCTOR(MIDIMessage) VARIANT_ENUM_CLASS_CONSTRUCTOR(MouseButton) diff --git a/core/version.h b/core/version.h index 5ddb09284e..abb81312ac 100644 --- a/core/version.h +++ b/core/version.h @@ -33,6 +33,12 @@ #include "core/version_generated.gen.h" +// Copied from typedefs.h to stay lean. +#ifndef _STR +#define _STR(m_x) #m_x +#define _MKSTR(m_x) _STR(m_x) +#endif + // Godot versions are of the form <major>.<minor> for the initial release, // and then <major>.<minor>.<patch> for subsequent bugfix releases where <patch> != 0 // That's arbitrary, but we find it pretty and it's the current policy. diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index d72fc61394..f8d1c99393 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -1429,6 +1429,7 @@ <description> Encodes a [Variant] value to a byte array, without encoding objects. Deserialization can be done with [method bytes_to_var]. [b]Note:[/b] If you need object serialization, see [method var_to_bytes_with_objects]. + [b]Note:[/b] Encoding [Callable] is not supported and will result in an empty value, regardless of the data. </description> </method> <method name="var_to_bytes_with_objects"> @@ -1436,6 +1437,7 @@ <param index="0" name="variable" type="Variant" /> <description> Encodes a [Variant] value to a byte array. Encoding objects is allowed (and can potentially include executable code). Deserialization can be done with [method bytes_to_var_with_objects]. + [b]Note:[/b] Encoding [Callable] is not supported and will result in an empty value, regardless of the data. </description> </method> <method name="var_to_str"> @@ -2372,6 +2374,16 @@ <constant name="KEY_MASK_GROUP_SWITCH" value="1073741824" enum="KeyModifierMask" is_bitfield="true"> Group Switch key mask. </constant> + <constant name="KEY_LOCATION_UNSPECIFIED" value="0" enum="KeyLocation"> + Used for keys which only appear once, or when a comparison doesn't need to differentiate the [code]LEFT[/code] and [code]RIGHT[/code] versions. + For example, when using [method InputEvent.is_match], an event which has [constant KEY_LOCATION_UNSPECIFIED] will match any [enum KeyLocation] on the passed event. + </constant> + <constant name="KEY_LOCATION_LEFT" value="1" enum="KeyLocation"> + A key which is to the left of its twin. + </constant> + <constant name="KEY_LOCATION_RIGHT" value="2" enum="KeyLocation"> + A key which is to the right of its twin. + </constant> <constant name="MOUSE_BUTTON_NONE" value="0" enum="MouseButton"> Enum value which doesn't correspond to any mouse button. This is used to initialize [enum MouseButton] properties with a generic state. </constant> @@ -2520,61 +2532,66 @@ The maximum number of game controller axes: OpenVR supports up to 5 Joysticks making a total of 10 axes. </constant> <constant name="MIDI_MESSAGE_NONE" value="0" enum="MIDIMessage"> - Enum value which doesn't correspond to any MIDI message. This is used to initialize [enum MIDIMessage] properties with a generic state. + Does not correspond to any MIDI message. This is the default value of [member InputEventMIDI.message]. </constant> <constant name="MIDI_MESSAGE_NOTE_OFF" value="8" enum="MIDIMessage"> - MIDI note OFF message. Not all MIDI devices send this event; some send [constant MIDI_MESSAGE_NOTE_ON] with zero velocity instead. See the documentation of [InputEventMIDI] for information of how to use MIDI inputs. + MIDI message sent when a note is released. + [b]Note:[/b] Not all MIDI devices send this message; some may send [constant MIDI_MESSAGE_NOTE_ON] with [member InputEventMIDI.velocity] set to [code]0[/code]. </constant> <constant name="MIDI_MESSAGE_NOTE_ON" value="9" enum="MIDIMessage"> - MIDI note ON message. Some MIDI devices send this event with velocity zero instead of [constant MIDI_MESSAGE_NOTE_OFF], but implementations vary. See the documentation of [InputEventMIDI] for information of how to use MIDI inputs. + MIDI message sent when a note is pressed. </constant> <constant name="MIDI_MESSAGE_AFTERTOUCH" value="10" enum="MIDIMessage"> - MIDI aftertouch message. This message is most often sent by pressing down on the key after it "bottoms out". + MIDI message sent to indicate a change in pressure while a note is being pressed down, also called aftertouch. </constant> <constant name="MIDI_MESSAGE_CONTROL_CHANGE" value="11" enum="MIDIMessage"> - MIDI control change message. This message is sent when a controller value changes. Controllers include devices such as pedals and levers. + MIDI message sent when a controller value changes. In a MIDI device, a controller is any input that doesn't play notes. These may include sliders for volume, balance, and panning, as well as switches and pedals. See the [url=https://en.wikipedia.org/wiki/General_MIDI#Controller_events]General MIDI specification[/url] for a small list. </constant> <constant name="MIDI_MESSAGE_PROGRAM_CHANGE" value="12" enum="MIDIMessage"> - MIDI program change message. This message sent when the program patch number changes. + MIDI message sent when the MIDI device changes its current instrument (also called [i]program[/i] or [i]preset[/i]). </constant> <constant name="MIDI_MESSAGE_CHANNEL_PRESSURE" value="13" enum="MIDIMessage"> - MIDI channel pressure message. This message is most often sent by pressing down on the key after it "bottoms out". This message is different from polyphonic after-touch as it indicates the highest pressure across all keys. + MIDI message sent to indicate a change in pressure for the whole channel. Some MIDI devices may send this instead of [constant MIDI_MESSAGE_AFTERTOUCH]. </constant> <constant name="MIDI_MESSAGE_PITCH_BEND" value="14" enum="MIDIMessage"> - MIDI pitch bend message. This message is sent to indicate a change in the pitch bender (wheel or lever, typically). + MIDI message sent when the value of the pitch bender changes, usually a wheel on the MIDI device. </constant> <constant name="MIDI_MESSAGE_SYSTEM_EXCLUSIVE" value="240" enum="MIDIMessage"> - MIDI system exclusive message. This has behavior exclusive to the device you're receiving input from. Getting this data is not implemented in Godot. + MIDI system exclusive (SysEx) message. This type of message is not standardized and it's highly dependent on the MIDI device sending it. + [b]Note:[/b] Getting this message's data from [InputEventMIDI] is not implemented. </constant> <constant name="MIDI_MESSAGE_QUARTER_FRAME" value="241" enum="MIDIMessage"> - MIDI quarter frame message. Contains timing information that is used to synchronize MIDI devices. Getting this data is not implemented in Godot. + MIDI message sent every quarter frame to keep connected MIDI devices synchronized. Related to [constant MIDI_MESSAGE_TIMING_CLOCK]. + [b]Note:[/b] Getting this message's data from [InputEventMIDI] is not implemented. </constant> <constant name="MIDI_MESSAGE_SONG_POSITION_POINTER" value="242" enum="MIDIMessage"> - MIDI song position pointer message. Gives the number of 16th notes since the start of the song. Getting this data is not implemented in Godot. + MIDI message sent to jump onto a new position in the current sequence or song. + [b]Note:[/b] Getting this message's data from [InputEventMIDI] is not implemented. </constant> <constant name="MIDI_MESSAGE_SONG_SELECT" value="243" enum="MIDIMessage"> - MIDI song select message. Specifies which sequence or song is to be played. Getting this data is not implemented in Godot. + MIDI message sent to select a sequence or song to play. + [b]Note:[/b] Getting this message's data from [InputEventMIDI] is not implemented. </constant> <constant name="MIDI_MESSAGE_TUNE_REQUEST" value="246" enum="MIDIMessage"> - MIDI tune request message. Upon receiving a tune request, all analog synthesizers should tune their oscillators. + MIDI message sent to request a tuning calibration. Used on analog synthesizers. Most modern MIDI devices do not need this message. </constant> <constant name="MIDI_MESSAGE_TIMING_CLOCK" value="248" enum="MIDIMessage"> - MIDI timing clock message. Sent 24 times per quarter note when synchronization is required. + MIDI message sent 24 times after [constant MIDI_MESSAGE_QUARTER_FRAME], to keep connected MIDI devices synchronized. </constant> <constant name="MIDI_MESSAGE_START" value="250" enum="MIDIMessage"> - MIDI start message. Start the current sequence playing. This message will be followed with Timing Clocks. + MIDI message sent to start the current sequence or song from the beginning. </constant> <constant name="MIDI_MESSAGE_CONTINUE" value="251" enum="MIDIMessage"> - MIDI continue message. Continue at the point the sequence was stopped. + MIDI message sent to resume from the point the current sequence or song was paused. </constant> <constant name="MIDI_MESSAGE_STOP" value="252" enum="MIDIMessage"> - MIDI stop message. Stop the current sequence. + MIDI message sent to pause the current sequence or song. </constant> <constant name="MIDI_MESSAGE_ACTIVE_SENSING" value="254" enum="MIDIMessage"> - MIDI active sensing message. This message is intended to be sent repeatedly to tell the receiver that a connection is alive. + MIDI message sent repeatedly while the MIDI device is idle, to tell the receiver that the connection is alive. Most MIDI devices do not send this message. </constant> <constant name="MIDI_MESSAGE_SYSTEM_RESET" value="255" enum="MIDIMessage"> - MIDI system reset message. Reset all receivers in the system to power-up status. It should not be sent on power-up itself. + MIDI message sent to reset a MIDI device to its default state, as if it was just turned on. It should not be sent when the MIDI device is being turned on. </constant> <constant name="OK" value="0" enum="Error"> Methods that return [enum Error] return [constant OK] when no error occurred. diff --git a/doc/classes/AnimatedSprite2D.xml b/doc/classes/AnimatedSprite2D.xml index d4f840cfa7..6eab269905 100644 --- a/doc/classes/AnimatedSprite2D.xml +++ b/doc/classes/AnimatedSprite2D.xml @@ -117,6 +117,7 @@ <signal name="animation_finished"> <description> Emitted when the animation reaches the end, or the start if it is played in reverse. When the animation finishes, it pauses the playback. + [b]Note:[/b] This signal is not emitted if an animation is looping. </description> </signal> <signal name="animation_looped"> diff --git a/doc/classes/AnimatedSprite3D.xml b/doc/classes/AnimatedSprite3D.xml index ff4bf97122..5f0a6c3e8d 100644 --- a/doc/classes/AnimatedSprite3D.xml +++ b/doc/classes/AnimatedSprite3D.xml @@ -104,6 +104,7 @@ <signal name="animation_finished"> <description> Emitted when the animation reaches the end, or the start if it is played in reverse. When the animation finishes, it pauses the playback. + [b]Note:[/b] This signal is not emitted if an animation is looping. </description> </signal> <signal name="animation_looped"> diff --git a/doc/classes/AudioEffect.xml b/doc/classes/AudioEffect.xml index f0e8b4f19e..4fe47ac011 100644 --- a/doc/classes/AudioEffect.xml +++ b/doc/classes/AudioEffect.xml @@ -1,18 +1,33 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="AudioEffect" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - Audio effect for audio. + Base class for audio effect resources. </brief_description> <description> - Base resource for audio bus. Applies an audio effect on the bus that the resource is applied on. + The base [Resource] for every audio effect. In the editor, an audio effect can be added to the current bus layout through the Audio panel. At run-time, it is also possible to manipulate audio effects through [method AudioServer.add_bus_effect], [method AudioServer.remove_bus_effect], and [method AudioServer.get_bus_effect]. + When applied on a bus, an audio effect creates a corresponding [AudioEffectInstance]. The instance is directly responsible for manipulating the sound, based on the original audio effect's properties. </description> <tutorials> + <link title="Audio buses">$DOCS_URL/tutorials/audio/audio_buses.html</link> <link title="Audio Mic Record Demo">https://godotengine.org/asset-library/asset/527</link> </tutorials> <methods> <method name="_instantiate" qualifiers="virtual"> <return type="AudioEffectInstance" /> <description> + Override this method to customize the [AudioEffectInstance] created when this effect is applied on a bus in the editor's Audio panel, or through [method AudioServer.add_bus_effect]. + [codeblock] + extends AudioEffect + + @export var strength = 4.0 + + func _instantiate(): + var effect = CustomAudioEffectInstance.new() + effect.base = self + + return effect + [/codeblock] + [b]Note:[/b] It is recommended to keep a reference to the original [AudioEffect] in the new instance. Depending on the implementation this allows the effect instance to listen for changes at run-time and be modified accordingly. </description> </method> </methods> diff --git a/doc/classes/AudioEffectInstance.xml b/doc/classes/AudioEffectInstance.xml index 3e868ae9da..0f3ae11793 100644 --- a/doc/classes/AudioEffectInstance.xml +++ b/doc/classes/AudioEffectInstance.xml @@ -1,10 +1,13 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="AudioEffectInstance" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> + Manipulates the audio it receives for a given effect. </brief_description> <description> + An audio effect instance manipulates the audio it receives for a given effect. This instance is automatically created by an [AudioEffect] when it is added to a bus, and should usually not be created directly. If necessary, it can be fetched at run-time with [method AudioServer.get_bus_effect_instance]. </description> <tutorials> + <link title="Audio buses">$DOCS_URL/tutorials/audio/audio_buses.html</link> </tutorials> <methods> <method name="_process" qualifiers="virtual"> @@ -13,11 +16,15 @@ <param index="1" name="dst_buffer" type="AudioFrame*" /> <param index="2" name="frame_count" type="int" /> <description> + Called by the [AudioServer] to process this effect. When [method _process_silence] is not overridden or it returns [code]false[/code], this method is called only when the bus is active. + [b]Note:[/b] It is not useful to override this method in GDScript or C#. Only GDExtension can take advantage of it. </description> </method> <method name="_process_silence" qualifiers="virtual const"> <return type="bool" /> <description> + Override this method to customize the processing behavior of this effect instance. + Should return [code]true[/code] to force the [AudioServer] to always call [method _process], even if the bus has been muted or cannot otherwise be heard. </description> </method> </methods> diff --git a/doc/classes/Bone2D.xml b/doc/classes/Bone2D.xml index a62c160080..da7f08e53d 100644 --- a/doc/classes/Bone2D.xml +++ b/doc/classes/Bone2D.xml @@ -15,7 +15,7 @@ <method name="apply_rest"> <return type="void" /> <description> - Stores the node's current transforms in [member rest]. + Resets the bone to the rest pose. This is equivalent to setting [member Node2D.transform] to [member rest]. </description> </method> <method name="get_autocalculate_length_and_angle" qualifiers="const"> diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml index 2d4c2c9682..eb8ed56a02 100644 --- a/doc/classes/DisplayServer.xml +++ b/doc/classes/DisplayServer.xml @@ -119,9 +119,33 @@ <param index="6" name="callback" type="Callable" /> <description> Displays OS native dialog for selecting files or directories in the file system. - Callbacks have the following arguments: [code]bool status, PackedStringArray selected_paths, int selected_filter_index[/code]. - [b]Note:[/b] This method is implemented if the display server has the [constant FEATURE_NATIVE_DIALOG] feature. - [b]Note:[/b] This method is implemented on Linux, Windows and macOS. + Callbacks have the following arguments: [code]status: bool, selected_paths: PackedStringArray, selected_filter_index: int[/code]. + [b]Note:[/b] This method is implemented if the display server has the [constant FEATURE_NATIVE_DIALOG] feature, i.e. Linux, Windows, and macOS. + [b]Note:[/b] [param current_directory] might be ignored. + [b]Note:[/b] On Linux, [param show_hidden] is ignored. + [b]Note:[/b] On macOS, native file dialogs have no title. + [b]Note:[/b] On macOS, sandboxed apps will save security-scoped bookmarks to retain access to the opened folders across multiple sessions. Use [method OS.get_granted_permissions] to get a list of saved bookmarks. + </description> + </method> + <method name="file_dialog_with_options_show"> + <return type="int" enum="Error" /> + <param index="0" name="title" type="String" /> + <param index="1" name="current_directory" type="String" /> + <param index="2" name="root" type="String" /> + <param index="3" name="filename" type="String" /> + <param index="4" name="show_hidden" type="bool" /> + <param index="5" name="mode" type="int" enum="DisplayServer.FileDialogMode" /> + <param index="6" name="filters" type="PackedStringArray" /> + <param index="7" name="options" type="Dictionary[]" /> + <param index="8" name="callback" type="Callable" /> + <description> + Displays OS native dialog for selecting files or directories in the file system with additional user selectable options. + [param options] is array of [Dictionary]s with the following keys: + - [code]"name"[/code] - option's name [String]. + - [code]"values"[/code] - [PackedStringArray] of values. If empty, boolean option (check box) is used. + - [code]"default"[/code] - default selected option index ([int]) or default boolean value ([bool]). + Callbacks have the following arguments: [code]status: bool, selected_paths: PackedStringArray, selected_filter_index: int, selected_option: Dictionary[/code]. + [b]Note:[/b] This method is implemented if the display server has the [constant FEATURE_NATIVE_DIALOG] feature, i.e. Linux, Windows, and macOS. [b]Note:[/b] [param current_directory] might be ignored. [b]Note:[/b] On Linux, [param show_hidden] is ignored. [b]Note:[/b] On macOS, native file dialogs have no title. diff --git a/doc/classes/EditorInterface.xml b/doc/classes/EditorInterface.xml index d0de09e451..94bb91d805 100644 --- a/doc/classes/EditorInterface.xml +++ b/doc/classes/EditorInterface.xml @@ -87,6 +87,7 @@ <return type="VBoxContainer" /> <description> Returns the editor control responsible for main screen plugins and tools. Use it with plugins that implement [method EditorPlugin._has_main_screen]. + [b]Note:[/b] This node is a [VBoxContainer], which means that if you add a [Control] child to it, you need to set the child's [member Control.size_flags_vertical] to [constant Control.SIZE_EXPAND_FILL] to make it use the full available space. [b]Warning:[/b] Removing and freeing this node will render a part of the editor useless and may cause a crash. </description> </method> @@ -195,6 +196,15 @@ Shows the given property on the given [param object] in the editor's Inspector dock. If [param inspector_only] is [code]true[/code], plugins will not attempt to edit [param object]. </description> </method> + <method name="is_multi_window_enabled" qualifiers="const"> + <return type="bool" /> + <description> + Returns [code]true[/code] if multiple window support is enabled in the editor. Multiple window support is enabled if [i]all[/i] of these statements are true: + - [member EditorSettings.interface/multi_window/enable] is [code]true[/code]. + - [member EditorSettings.interface/editor/single_window_mode] is [code]false[/code]. + - [member Viewport.gui_embed_subwindows] is [code]false[/code]. This is forced to [code]true[/code] on platforms that don't support multiple windows such as Web, or when the [code]--single-window[/code] [url=$DOCS_URL/tutorials/editor/command_line_tutorial.html]command line argument[/url] is used. + </description> + </method> <method name="is_playing_scene" qualifiers="const"> <return type="bool" /> <description> diff --git a/doc/classes/EditorPlugin.xml b/doc/classes/EditorPlugin.xml index 407d64e9a2..956157e8cb 100644 --- a/doc/classes/EditorPlugin.xml +++ b/doc/classes/EditorPlugin.xml @@ -334,7 +334,7 @@ <return type="bool" /> <description> Returns [code]true[/code] if this is a main screen editor plugin (it goes in the workspace selector together with [b]2D[/b], [b]3D[/b], [b]Script[/b] and [b]AssetLib[/b]). - When the plugin's workspace is selected, other main screen plugins will be hidden, but your plugin will not appear automatically. It needs to be added as a child of [method EditorInterface.get_base_control] and made visible inside [method _make_visible]. + When the plugin's workspace is selected, other main screen plugins will be hidden, but your plugin will not appear automatically. It needs to be added as a child of [method EditorInterface.get_editor_main_screen] and made visible inside [method _make_visible]. Use [method _get_plugin_name] and [method _get_plugin_icon] to customize the plugin button's appearance. [codeblock] var plugin_control diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml index b7f3ec9963..2f24acd684 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -495,7 +495,7 @@ <member name="filesystem/file_dialog/thumbnail_size" type="int" setter="" getter=""> The thumbnail size to use in the editor's file dialogs (in pixels). See also [member docks/filesystem/thumbnail_size]. </member> - <member name="filesystem/import/blender/blender3_path" type="String" setter="" getter=""> + <member name="filesystem/import/blender/blender_path" type="String" setter="" getter=""> The path to the directory containing the Blender executable used for converting the Blender 3D scene files [code].blend[/code] to glTF 2.0 format during import. Blender 3.0 or later is required. To enable this feature for your specific project, use [member ProjectSettings.filesystem/import/blender/enabled]. </member> @@ -616,6 +616,7 @@ </member> <member name="interface/editor/single_window_mode" type="bool" setter="" getter=""> If [code]true[/code], embed modal windows such as docks inside the main editor window. When single-window mode is enabled, tooltips will also be embedded inside the main editor window, which means they can't be displayed outside of the editor window. + [b]Note:[/b] To query whether the editor can use multiple windows in an editor plugin, use [method EditorInterface.is_multi_window_enabled] instead of querying the value of this editor setting. </member> <member name="interface/editor/ui_layout_direction" type="int" setter="" getter=""> Editor UI default layout direction. @@ -637,8 +638,9 @@ If [code]true[/code], display OpenType features marked as [code]hidden[/code] by the font file in the [Font] editor. </member> <member name="interface/multi_window/enable" type="bool" setter="" getter=""> - If [code]true[/code], the multi window support in editor is enabled. The following panels can become dedicated windows (made floating): Docks, Script editor, and Shader editor. + If [code]true[/code], multiple window support in editor is enabled. The following panels can become dedicated windows (i.e. made floating): Docks, Script editor, and Shader editor. [b]Note:[/b] When [member interface/editor/single_window_mode] is [code]true[/code], the multi window support is always disabled. + [b]Note:[/b] To query whether the editor can use multiple windows in an editor plugin, use [method EditorInterface.is_multi_window_enabled] instead of querying the value of this editor setting. </member> <member name="interface/multi_window/maximize_window" type="bool" setter="" getter=""> If [code]true[/code], when panels are made floating they will be maximized. diff --git a/doc/classes/Engine.xml b/doc/classes/Engine.xml index 653dc4fd88..580756c12d 100644 --- a/doc/classes/Engine.xml +++ b/doc/classes/Engine.xml @@ -179,7 +179,6 @@ [code]status[/code] - Holds the status (e.g. "beta", "rc1", "rc2", ... "stable") as a String [code]build[/code] - Holds the build name (e.g. "custom_build") as a String [code]hash[/code] - Holds the full Git commit hash as a String - [code]year[/code] - Holds the year the version was released in as an int [code]string[/code] - [code]major[/code] + [code]minor[/code] + [code]patch[/code] + [code]status[/code] + [code]build[/code] in a single String The [code]hex[/code] value is encoded as follows, from left to right: one byte for the major, one byte for the minor, one byte for the patch version. For example, "3.1.12" would be [code]0x03010C[/code]. [b]Note:[/b] It's still an int internally, and printing it will give you its decimal representation, which is not particularly meaningful. Use hexadecimal literals for easy version comparisons from code: [codeblocks] diff --git a/doc/classes/FileDialog.xml b/doc/classes/FileDialog.xml index 75856e91d8..47a1801f58 100644 --- a/doc/classes/FileDialog.xml +++ b/doc/classes/FileDialog.xml @@ -19,6 +19,15 @@ For example, a [param filter] of [code]"*.png, *.jpg"[/code] and a [param description] of [code]"Images"[/code] results in filter text "Images (*.png, *.jpg)". </description> </method> + <method name="add_option"> + <return type="void" /> + <param index="0" name="name" type="String" /> + <param index="1" name="values" type="PackedStringArray" /> + <param index="2" name="index" type="int" /> + <description> + Adds an additional [OptionButton] to the file dialog. If [param values] is empty, a [CheckBox] is added instead. + </description> + </method> <method name="clear_filters"> <return type="void" /> <description> @@ -38,6 +47,33 @@ [b]Warning:[/b] This is a required internal node, removing and freeing it may cause a crash. If you wish to hide it or any of its children, use their [member CanvasItem.visible] property. </description> </method> + <method name="get_option_default" qualifiers="const"> + <return type="int" /> + <param index="0" name="option" type="int" /> + <description> + Returns the default value index of the [OptionButton] or [CheckBox] with index [param option]. + </description> + </method> + <method name="get_option_name" qualifiers="const"> + <return type="String" /> + <param index="0" name="option" type="int" /> + <description> + Returns the name of the [OptionButton] or [CheckBox] with index [param option]. + </description> + </method> + <method name="get_option_values" qualifiers="const"> + <return type="PackedStringArray" /> + <param index="0" name="option" type="int" /> + <description> + Returns an array of values of the [OptionButton] with index [param option]. + </description> + </method> + <method name="get_selected_options" qualifiers="const"> + <return type="Dictionary" /> + <description> + Returns a [Dictionary] with the selected values of the additional [OptionButton]s and/or [CheckBox]es. [Dictionary] keys are names and values are selected value indices. + </description> + </method> <method name="get_vbox"> <return type="VBoxContainer" /> <description> @@ -51,6 +87,30 @@ Invalidate and update the current dialog content list. </description> </method> + <method name="set_option_default"> + <return type="void" /> + <param index="0" name="option" type="int" /> + <param index="1" name="index" type="int" /> + <description> + Sets the default value index of the [OptionButton] or [CheckBox] with index [param option]. + </description> + </method> + <method name="set_option_name"> + <return type="void" /> + <param index="0" name="option" type="int" /> + <param index="1" name="name" type="String" /> + <description> + Sets the name of the [OptionButton] or [CheckBox] with index [param option]. + </description> + </method> + <method name="set_option_values"> + <return type="void" /> + <param index="0" name="option" type="int" /> + <param index="1" name="values" type="PackedStringArray" /> + <description> + Sets the option values of the [OptionButton] with index [param option]. + </description> + </method> </methods> <members> <member name="access" type="int" setter="set_access" getter="get_access" enum="FileDialog.Access" default="0"> @@ -76,6 +136,9 @@ <member name="mode_overrides_title" type="bool" setter="set_mode_overrides_title" getter="is_mode_overriding_title" default="true"> If [code]true[/code], changing the [member file_mode] property will set the window title accordingly (e.g. setting [member file_mode] to [constant FILE_MODE_OPEN_FILE] will change the window title to "Open a File"). </member> + <member name="option_count" type="int" setter="set_option_count" getter="get_option_count" default="0"> + The number of additional [OptionButton]s and [CheckBox]es in the dialog. + </member> <member name="root_subfolder" type="String" setter="set_root_subfolder" getter="get_root_subfolder" default=""""> If non-empty, the given sub-folder will be "root" of this [FileDialog], i.e. user won't be able to go to its parent directory. </member> diff --git a/doc/classes/GraphEdit.xml b/doc/classes/GraphEdit.xml index e5952d9f71..709d26668c 100644 --- a/doc/classes/GraphEdit.xml +++ b/doc/classes/GraphEdit.xml @@ -323,7 +323,7 @@ <signals> <signal name="begin_node_move"> <description> - Emitted at the beginning of a GraphNode movement. + Emitted at the beginning of a [GraphElement]'s movement. </description> </signal> <signal name="connection_drag_ended"> @@ -366,13 +366,14 @@ </signal> <signal name="copy_nodes_request"> <description> - Emitted when the user presses [kbd]Ctrl + C[/kbd]. + Emitted when this [GraphEdit] captures a [code]ui_copy[/code] action ([kbd]Ctrl + C[/kbd] by default). In general, this signal indicates that the selected [GraphElement]s should be copied. </description> </signal> <signal name="delete_nodes_request"> <param index="0" name="nodes" type="StringName[]" /> <description> - Emitted when attempting to remove a GraphNode from the GraphEdit. Provides a list of node names to be removed (all selected nodes, excluding nodes without closing button). + Emitted when this [GraphEdit] captures a [code]ui_graph_delete[/code] action ([kbd]Delete[/kbd] by default). + [param nodes] is an array of node names that should be removed. These usually include all selected nodes. </description> </signal> <signal name="disconnection_request"> @@ -386,28 +387,29 @@ </signal> <signal name="duplicate_nodes_request"> <description> - Emitted when a GraphNode is attempted to be duplicated in the GraphEdit. + Emitted when this [GraphEdit] captures a [code]ui_graph_duplicate[/code] action ([kbd]Ctrl + D[/kbd] by default). In general, this signal indicates that the selected [GraphElement]s should be duplicated. </description> </signal> <signal name="end_node_move"> <description> - Emitted at the end of a GraphNode movement. + Emitted at the end of a [GraphElement]'s movement. </description> </signal> <signal name="node_deselected"> <param index="0" name="node" type="Node" /> <description> + Emitted when the given [GraphElement] node is deselected. </description> </signal> <signal name="node_selected"> <param index="0" name="node" type="Node" /> <description> - Emitted when a GraphNode is selected. + Emitted when the given [GraphElement] node is selected. </description> </signal> <signal name="paste_nodes_request"> <description> - Emitted when the user presses [kbd]Ctrl + V[/kbd]. + Emitted when this [GraphEdit] captures a [code]ui_paste[/code] action ([kbd]Ctrl + V[/kbd] by default). In general, this signal indicates that previously copied [GraphElement]s should be pasted. </description> </signal> <signal name="popup_request"> diff --git a/doc/classes/InputEventKey.xml b/doc/classes/InputEventKey.xml index 48a6804290..7b6fc3f216 100644 --- a/doc/classes/InputEventKey.xml +++ b/doc/classes/InputEventKey.xml @@ -24,6 +24,12 @@ Returns a [String] representation of the event's [member keycode] and modifiers. </description> </method> + <method name="as_text_location" qualifiers="const"> + <return type="String" /> + <description> + Returns a [String] representation of the event's [member location]. This will be a blank string if the event is not specific to a location. + </description> + </method> <method name="as_text_physical_keycode" qualifiers="const"> <return type="String" /> <description> @@ -77,6 +83,9 @@ +-----+ +-----+ [/codeblock] </member> + <member name="location" type="int" setter="set_location" getter="get_location" enum="KeyLocation" default="0"> + Represents the location of a key which has both left and right versions, such as [kbd]Shift[/kbd] or [kbd]Alt[/kbd]. + </member> <member name="physical_keycode" type="int" setter="set_physical_keycode" getter="get_physical_keycode" enum="Key" default="0"> Represents the physical location of a key on the 101/102-key US QWERTY keyboard, which corresponds to one of the [enum Key] constants. To get a human-readable representation of the [InputEventKey], use [method OS.get_keycode_string] in combination with [method DisplayServer.keyboard_get_keycode_from_physical]: diff --git a/doc/classes/InputEventMIDI.xml b/doc/classes/InputEventMIDI.xml index 8e2ad52801..0f0b756651 100644 --- a/doc/classes/InputEventMIDI.xml +++ b/doc/classes/InputEventMIDI.xml @@ -1,12 +1,12 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="InputEventMIDI" inherits="InputEvent" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - Represents an input event from a MIDI device, such as a piano. + Represents a MIDI message from a MIDI device, such as a musical keyboard. </brief_description> <description> - InputEventMIDI allows receiving input events from MIDI (Musical Instrument Digital Interface) devices such as a piano. - MIDI signals can be sent over a 5-pin MIDI connector or over USB, if your device supports both be sure to check the settings in the device to see which output it's using. - To receive input events from MIDI devices, you need to call [method OS.open_midi_inputs]. You can check which devices are detected using [method OS.get_connected_midi_inputs]. + InputEventMIDI stores information about messages from [url=https://en.wikipedia.org/wiki/MIDI]MIDI[/url] (Musical Instrument Digital Interface) devices. These may include musical keyboards, synthesizers, and drum machines. + MIDI messages can be received over a 5-pin MIDI connector or over USB. If your device supports both be sure to check the settings in the device to see which output it is using. + By default, Godot does not detect MIDI devices. You need to call [method OS.open_midi_inputs], first. You can check which devices are detected with [method OS.get_connected_midi_inputs], and close the connection with [method OS.close_midi_inputs]. [codeblocks] [gdscript] func _ready(): @@ -17,16 +17,16 @@ if input_event is InputEventMIDI: _print_midi_info(input_event) - func _print_midi_info(midi_event: InputEventMIDI): + func _print_midi_info(midi_event): print(midi_event) - print("Channel " + str(midi_event.channel)) - print("Message " + str(midi_event.message)) - print("Pitch " + str(midi_event.pitch)) - print("Velocity " + str(midi_event.velocity)) - print("Instrument " + str(midi_event.instrument)) - print("Pressure " + str(midi_event.pressure)) - print("Controller number: " + str(midi_event.controller_number)) - print("Controller value: " + str(midi_event.controller_value)) + print("Channel ", midi_event.channel) + print("Message ", midi_event.message) + print("Pitch ", midi_event.pitch) + print("Velocity ", midi_event.velocity) + print("Instrument ", midi_event.instrument) + print("Pressure ", midi_event.pressure) + print("Controller number: ", midi_event.controller_number) + print("Controller value: ", midi_event.controller_value) [/gdscript] [csharp] public override void _Ready() @@ -35,9 +35,9 @@ GD.Print(OS.GetConnectedMidiInputs()); } - public override void _Input(InputEvent @event) + public override void _Input(InputEvent inputEvent) { - if (@event is InputEventMIDI midiEvent) + if (inputEvent is InputEventMIDI midiEvent) { PrintMIDIInfo(midiEvent); } @@ -57,7 +57,7 @@ } [/csharp] [/codeblocks] - Note that Godot does not currently support MIDI output, so there is no way to emit MIDI signals from Godot. Only MIDI input works. + [b]Note:[/b] Godot does not support MIDI output, so there is no way to emit MIDI messages from Godot. Only MIDI input is supported. </description> <tutorials> <link title="MIDI Message Status Byte List">https://www.midi.org/specifications-old/item/table-2-expanded-messages-list-status-bytes</link> @@ -66,33 +66,39 @@ </tutorials> <members> <member name="channel" type="int" setter="set_channel" getter="get_channel" default="0"> - The MIDI channel of this input event. There are 16 channels, so this value ranges from 0 to 15. MIDI channel 9 is reserved for the use with percussion instruments, the rest of the channels are for non-percussion instruments. + The MIDI channel of this message, ranging from [code]0[/code] to [code]15[/code]. MIDI channel [code]9[/code] is reserved for percussion instruments. </member> <member name="controller_number" type="int" setter="set_controller_number" getter="get_controller_number" default="0"> - If the message is [constant MIDI_MESSAGE_CONTROL_CHANGE], this indicates the controller number, otherwise this is zero. Controllers include devices such as pedals and levers. + The unique number of the controller, if [member message] is [constant MIDI_MESSAGE_CONTROL_CHANGE], otherwise this is [code]0[/code]. This value can be used to identify sliders for volume, balance, and panning, as well as switches and pedals on the MIDI device. See the [url=https://en.wikipedia.org/wiki/General_MIDI#Controller_events]General MIDI specification[/url] for a small list. </member> <member name="controller_value" type="int" setter="set_controller_value" getter="get_controller_value" default="0"> - If the message is [constant MIDI_MESSAGE_CONTROL_CHANGE], this indicates the controller value, otherwise this is zero. Controllers include devices such as pedals and levers. + The value applied to the controller. If [member message] is [constant MIDI_MESSAGE_CONTROL_CHANGE], this value ranges from [code]0[/code] to [code]127[/code], otherwise it is [code]0[/code]. See also [member controller_value]. </member> <member name="instrument" type="int" setter="set_instrument" getter="get_instrument" default="0"> - The instrument of this input event. This value ranges from 0 to 127. Refer to the instrument list for [url=https://en.wikipedia.org/wiki/General_MIDI#Program_change_events]General MIDI[/url] to see a list of instruments, except that this value is 0-index, so subtract one from every number on that chart. A standard piano will have an instrument number of 0. + The instrument (also called [i]program[/i] or [i]preset[/i]) used on this MIDI message. This value ranges from [code]0[/code] to [code]127[/code]. + To see what each value means, refer to the [url=https://en.wikipedia.org/wiki/General_MIDI#Program_change_events]General MIDI's instrument list[/url]. Keep in mind that the list is off by 1 because it does not begin from 0. A value of [code]0[/code] corresponds to the acoustic grand piano. </member> <member name="message" type="int" setter="set_message" getter="get_message" enum="MIDIMessage" default="0"> - Returns a value indicating the type of message for this MIDI signal. This is a member of the [enum MIDIMessage] enum. - For MIDI messages between 0x80 and 0xEF, only the left half of the bits are returned as this value, as the other part is the channel (ex: 0x94 becomes 0x9). For MIDI messages from 0xF0 to 0xFF, the value is returned as-is. - Notes will return [constant MIDI_MESSAGE_NOTE_ON] when activated, but they might not always return [constant MIDI_MESSAGE_NOTE_OFF] when deactivated, therefore your code should treat the input as stopped if some period of time has passed. - Some MIDI devices may send [constant MIDI_MESSAGE_NOTE_ON] with zero velocity instead of [constant MIDI_MESSAGE_NOTE_OFF]. - For more information, see the note in [member velocity] and the MIDI message status byte list chart linked above. + Represents the type of MIDI message (see the [enum MIDIMessage] enum). + For more information, see the [url=https://www.midi.org/specifications-old/item/table-2-expanded-messages-list-status-bytes]MIDI message status byte list chart[/url]. </member> <member name="pitch" type="int" setter="set_pitch" getter="get_pitch" default="0"> - The pitch index number of this MIDI signal. This value ranges from 0 to 127. On a piano, middle C is 60, and A440 is 69, see the "MIDI note" column of the piano key frequency chart on Wikipedia for more information. + The pitch index number of this MIDI message. This value ranges from [code]0[/code] to [code]127[/code]. + On a piano, the [b]middle C[/b] is [code]60[/code], followed by a [b]C-sharp[/b] ([code]61[/code]), then a [b]D[/b] ([code]62[/code]), and so on. Each octave is split in offsets of 12. See the "MIDI note number" column of the [url=https://en.wikipedia.org/wiki/Piano_key_frequencies]piano key frequency chart[/url] a full list. </member> <member name="pressure" type="int" setter="set_pressure" getter="get_pressure" default="0"> - The pressure of the MIDI signal. This value ranges from 0 to 127. For many devices, this value is always zero. + The strength of the key being pressed. This value ranges from [code]0[/code] to [code]127[/code]. + [b]Note:[/b] For many devices, this value is always [code]0[/code]. Other devices such as musical keyboards may simulate pressure by changing the [member velocity], instead. </member> <member name="velocity" type="int" setter="set_velocity" getter="get_velocity" default="0"> - The velocity of the MIDI signal. This value ranges from 0 to 127. For a piano, this corresponds to how quickly the key was pressed, and is rarely above about 110 in practice. - [b]Note:[/b] Some MIDI devices may send a [constant MIDI_MESSAGE_NOTE_ON] message with zero velocity and expect this to be treated the same as a [constant MIDI_MESSAGE_NOTE_OFF] message, but device implementations vary so Godot reports event data exactly as received. Depending on the hardware and the needs of the game/app, this MIDI quirk can be handled robustly with a couple lines of script (check for [constant MIDI_MESSAGE_NOTE_ON] with velocity zero). + The velocity of the MIDI message. This value ranges from [code]0[/code] to [code]127[/code]. For a musical keyboard, this corresponds to how quickly the key was pressed, and is rarely above [code]110[/code] in practice. + [b]Note:[/b] Some MIDI devices may send a [constant MIDI_MESSAGE_NOTE_ON] message with [code]0[/code] velocity and expect it to be treated the same as a [constant MIDI_MESSAGE_NOTE_OFF] message. If necessary, this can be handled with a few lines of code: + [codeblock] + func _input(event): + if event is InputEventMIDI: + if event.message == MIDI_MESSAGE_NOTE_ON and event.velocity > 0: + print("Note pressed!") + [/codeblock] </member> </members> </class> diff --git a/doc/classes/LightmapGI.xml b/doc/classes/LightmapGI.xml index fa3bfe513f..13d48d8650 100644 --- a/doc/classes/LightmapGI.xml +++ b/doc/classes/LightmapGI.xml @@ -9,7 +9,7 @@ [b]Performance:[/b] [LightmapGI] provides the best possible run-time performance for global illumination. It is suitable for low-end hardware including integrated graphics and mobile devices. [b]Note:[/b] Due to how lightmaps work, most properties only have a visible effect once lightmaps are baked again. [b]Note:[/b] Lightmap baking on [CSGShape3D]s and [PrimitiveMesh]es is not supported, as these cannot store UV2 data required for baking. - [b]Note:[/b] If no custom lightmappers are installed, [LightmapGI] can only be baked when using the Vulkan backend (Forward+ or Mobile), not OpenGL. + [b]Note:[/b] If no custom lightmappers are installed, [LightmapGI] can only be baked from devices that support the Forward+ or Mobile rendering backends. </description> <tutorials> <link title="Using Lightmap global illumination">$DOCS_URL/tutorials/3d/global_illumination/using_lightmap_gi.html</link> diff --git a/doc/classes/MissingNode.xml b/doc/classes/MissingNode.xml index 6e5f839778..4e528206cc 100644 --- a/doc/classes/MissingNode.xml +++ b/doc/classes/MissingNode.xml @@ -4,15 +4,17 @@ An internal editor class intended for keeping the data of unrecognized nodes. </brief_description> <description> - This is an internal editor class intended for keeping data of nodes of unknown type (most likely this type was supplied by an extension that is no longer loaded). It can't be manually instantiated or placed in the scene. Ignore it if you don't know what it is. + This is an internal editor class intended for keeping data of nodes of unknown type (most likely this type was supplied by an extension that is no longer loaded). It can't be manually instantiated or placed in a scene. + [b]Warning:[/b] Ignore missing nodes unless you know what you are doing. Existing properties on a missing node can be freely modified in code, regardless of the type they are intended to be. </description> <tutorials> </tutorials> <members> <member name="original_class" type="String" setter="set_original_class" getter="get_original_class"> - Returns the name of the type this node was originally. + The name of the class this node was supposed to be (see [method Object.get_class]). </member> <member name="recording_properties" type="bool" setter="set_recording_properties" getter="is_recording_properties"> + If set to [code]true[/code], allows new properties to be added on top of the existing ones with [method Object.set]. </member> </members> </class> diff --git a/doc/classes/MissingResource.xml b/doc/classes/MissingResource.xml index 84a12290b3..442b0abd5e 100644 --- a/doc/classes/MissingResource.xml +++ b/doc/classes/MissingResource.xml @@ -4,15 +4,17 @@ An internal editor class intended for keeping the data of unrecognized resources. </brief_description> <description> - This is an internal editor class intended for keeping data of resources of unknown type (most likely this type was supplied by an extension that is no longer loaded). It can't be manually instantiated or placed in the scene. Ignore it if you don't know what it is. + This is an internal editor class intended for keeping data of resources of unknown type (most likely this type was supplied by an extension that is no longer loaded). It can't be manually instantiated or placed in a scene. + [b]Warning:[/b] Ignore missing resources unless you know what you are doing. Existing properties on a missing resource can be freely modified in code, regardless of the type they are intended to be. </description> <tutorials> </tutorials> <members> <member name="original_class" type="String" setter="set_original_class" getter="get_original_class"> - Returns the name of the class this resource was originally. + The name of the class this resource was supposed to be (see [method Object.get_class]). </member> <member name="recording_properties" type="bool" setter="set_recording_properties" getter="is_recording_properties"> + If set to [code]true[/code], allows new properties to be added on top of the existing ones with [method Object.set]. </member> </members> </class> diff --git a/doc/classes/Object.xml b/doc/classes/Object.xml index e4f4d7682b..5fa43f868e 100644 --- a/doc/classes/Object.xml +++ b/doc/classes/Object.xml @@ -76,83 +76,112 @@ <method name="_get_property_list" qualifiers="virtual"> <return type="Dictionary[]" /> <description> - Override this method to customize how script properties should be handled by the engine. + Override this method to provide a custom list of additional properties to handle by the engine. Should return a property list, as an [Array] of dictionaries. The result is added to the array of [method get_property_list], and should be formatted in the same way. Each [Dictionary] must at least contain the [code]name[/code] and [code]type[/code] entries. - The example below displays [code]hammer_type[/code] in the Inspector dock, only if [code]holding_hammer[/code] is [code]true[/code]: + You can use [method _property_can_revert] and [method _property_get_revert] to customize the default values of the properties added by this method. + The example below displays a list of numbers shown as words going from [code]ZERO[/code] to [code]FIVE[/code], with [code]number_count[/code] controlling the size of the list: [codeblocks] [gdscript] @tool - extends Node2D + extends Node - @export var holding_hammer = false: - set(value): - holding_hammer = value + @export var number_count = 3: + set(nc): + number_count = nc + numbers.resize(number_count) notify_property_list_changed() - var hammer_type = 0 - - func _get_property_list(): - # By default, `hammer_type` is not visible in the editor. - var property_usage = PROPERTY_USAGE_NO_EDITOR - if holding_hammer: - property_usage = PROPERTY_USAGE_DEFAULT + var numbers = PackedInt32Array([0, 0, 0]) + func _get_property_list(): var properties = [] - properties.append({ - "name": "hammer_type", - "type": TYPE_INT, - "usage": property_usage, # See above assignment. - "hint": PROPERTY_HINT_ENUM, - "hint_string": "Wooden,Iron,Golden,Enchanted" - }) + + for i in range(number_count): + properties.append({ + "name": "number_%d" % i, + "type": TYPE_INT, + "hint": PROPERTY_HINT_ENUM, + "hint_string": "ZERO,ONE,TWO,THREE,FOUR,FIVE", + }) return properties + + func _get(property): + if property.begins_with("number_"): + var index = property.get_slice("_", 1).to_int() + return numbers[index] + + func _set(property, value): + if property.begins_with("number_"): + var index = property.get_slice("_", 1).to_int() + numbers[index] = value + return true + return false [/gdscript] [csharp] [Tool] - public partial class MyNode2D : Node2D + public partial class MyNode : Node { - private bool _holdingHammer; + private int _numberCount; [Export] - public bool HoldingHammer + public int NumberCount { - get => _holdingHammer; + get => _numberCount; set { - _holdingHammer = value; + _numberCount = value; + _numbers.Resize(_numberCount); NotifyPropertyListChanged(); } } - public int HammerType { get; set; } + private List<int> _numbers = new(); public override Godot.Collections.Array<Godot.Collections.Dictionary> _GetPropertyList() { - // By default, `HammerType` is not visible in the editor. - var propertyUsage = PropertyUsageFlags.NoEditor; + var properties = new Godot.Collections.Array<Godot.Collections.Dictionary>(); - if (HoldingHammer) + for (int i = 0; i < _numberCount; i++) { - propertyUsage = PropertyUsageFlags.Default; + properties.Add(new Godot.Collections.Dictionary() + { + { "name", $"number_{i}" }, + { "type", (int)Variant.Type.Int }, + { "hint", (int)PropertyHint.Enum }, + { "hint_string", "Zero,One,Two,Three,Four,Five" }, + }); } - var properties = new Godot.Collections.Array<Godot.Collections.Dictionary>(); - properties.Add(new Godot.Collections.Dictionary() + return properties; + } + + public override Variant _Get(StringName property) + { + string propertyName = property.ToString(); + if (propertyName.StartsWith("number_")) { - { "name", "HammerType" }, - { "type", (int)Variant.Type.Int }, - { "usage", (int)propertyUsage }, // See above assignment. - { "hint", (int)PropertyHint.Enum }, - { "hint_string", "Wooden,Iron,Golden,Enchanted" } - }); + int index = int.Parse(propertyName.Substring("number_".Length)); + return _numbers[index]; + } + return default; + } - return properties; + public override bool _Set(StringName property, Variant value) + { + string propertyName = property.ToString(); + if (propertyName.StartsWith("number_")) + { + int index = int.Parse(propertyName.Substring("number_".Length)); + numbers[index] = value.As<int>(); + return true; + } + return false; } } [/csharp] [/codeblocks] - [b]Note:[/b] This method is intended for advanced purposes. For most common use cases, the scripting languages offer easier ways to handle properties. See [annotation @GDScript.@export], [annotation @GDScript.@export_enum], [annotation @GDScript.@export_group], etc. + [b]Note:[/b] This method is intended for advanced purposes. For most common use cases, the scripting languages offer easier ways to handle properties. See [annotation @GDScript.@export], [annotation @GDScript.@export_enum], [annotation @GDScript.@export_group], etc. If you want to customize exported properties, use [method _validate_property]. [b]Note:[/b] If the object's script is not [annotation @GDScript.@tool], this method will not be called in the editor. </description> </method> @@ -274,7 +303,7 @@ <return type="void" /> <param index="0" name="property" type="Dictionary" /> <description> - Override this method to customize existing properties. Every property info goes through this method. The dictionary contents is the same as in [method _get_property_list]. + Override this method to customize existing properties. Every property info goes through this method, except properties added with [method _get_property_list]. The dictionary contents is the same as in [method _get_property_list]. [codeblocks] [gdscript] @tool diff --git a/doc/classes/PackedByteArray.xml b/doc/classes/PackedByteArray.xml index a934d32a6d..c5f522ba10 100644 --- a/doc/classes/PackedByteArray.xml +++ b/doc/classes/PackedByteArray.xml @@ -174,6 +174,7 @@ <param index="1" name="compression_mode" type="int" default="0" /> <description> Returns a new [PackedByteArray] with the data decompressed. Set [param buffer_size] to the size of the uncompressed data. Set the compression mode using one of [enum FileAccess.CompressionMode]'s constants. + [b]Note:[/b] Decompression is not guaranteed to work with data not compressed by Godot, for example if data compressed with the deflate compression mode lacks a checksum or header. </description> </method> <method name="decompress_dynamic" qualifiers="const"> @@ -184,6 +185,7 @@ Returns a new [PackedByteArray] with the data decompressed. Set the compression mode using one of [enum FileAccess.CompressionMode]'s constants. [b]This method only accepts brotli, gzip, and deflate compression modes.[/b] This method is potentially slower than [method decompress], as it may have to re-allocate its output buffer multiple times while decompressing, whereas [method decompress] knows it's output buffer size from the beginning. GZIP has a maximal compression ratio of 1032:1, meaning it's very possible for a small compressed payload to decompress to a potentially very large output. To guard against this, you may provide a maximum size this function is allowed to allocate in bytes via [param max_output_size]. Passing -1 will allow for unbounded output. If any positive value is passed, and the decompression exceeds that amount in bytes, then an error will be returned. + [b]Note:[/b] Decompression is not guaranteed to work with data not compressed by Godot, for example if data compressed with the deflate compression mode lacks a checksum or header. </description> </method> <method name="duplicate"> diff --git a/doc/classes/PhysicsServer3D.xml b/doc/classes/PhysicsServer3D.xml index 32810b0daf..4735091f20 100644 --- a/doc/classes/PhysicsServer3D.xml +++ b/doc/classes/PhysicsServer3D.xml @@ -1014,10 +1014,262 @@ Gets a slider_joint parameter (see [enum SliderJointParam] constants). </description> </method> + <method name="soft_body_add_collision_exception"> + <return type="void" /> + <param index="0" name="body" type="RID" /> + <param index="1" name="body_b" type="RID" /> + <description> + Adds the given body to the list of bodies exempt from collisions. + </description> + </method> + <method name="soft_body_create"> + <return type="RID" /> + <description> + Creates a new soft body and returns its internal [RID]. + </description> + </method> <method name="soft_body_get_bounds" qualifiers="const"> <return type="AABB" /> <param index="0" name="body" type="RID" /> <description> + Returns the bounds of the given soft body in global coordinates. + </description> + </method> + <method name="soft_body_get_collision_layer" qualifiers="const"> + <return type="int" /> + <param index="0" name="body" type="RID" /> + <description> + Returns the physics layer or layers that the given soft body belongs to. + </description> + </method> + <method name="soft_body_get_collision_mask" qualifiers="const"> + <return type="int" /> + <param index="0" name="body" type="RID" /> + <description> + Returns the physics layer or layers that the given soft body can collide with. + </description> + </method> + <method name="soft_body_get_damping_coefficient" qualifiers="const"> + <return type="float" /> + <param index="0" name="body" type="RID" /> + <description> + Returns the damping coefficient of the given soft body. + </description> + </method> + <method name="soft_body_get_drag_coefficient" qualifiers="const"> + <return type="float" /> + <param index="0" name="body" type="RID" /> + <description> + Returns the drag coefficient of the given soft body. + </description> + </method> + <method name="soft_body_get_linear_stiffness" qualifiers="const"> + <return type="float" /> + <param index="0" name="body" type="RID" /> + <description> + Returns the linear stiffness of the given soft body. + </description> + </method> + <method name="soft_body_get_point_global_position" qualifiers="const"> + <return type="Vector3" /> + <param index="0" name="body" type="RID" /> + <param index="1" name="point_index" type="int" /> + <description> + Returns the current position of the given soft body point in global coordinates. + </description> + </method> + <method name="soft_body_get_pressure_coefficient" qualifiers="const"> + <return type="float" /> + <param index="0" name="body" type="RID" /> + <description> + Returns the pressure coefficient of the given soft body. + </description> + </method> + <method name="soft_body_get_simulation_precision" qualifiers="const"> + <return type="int" /> + <param index="0" name="body" type="RID" /> + <description> + Returns the simulation precision of the given soft body. + </description> + </method> + <method name="soft_body_get_space" qualifiers="const"> + <return type="RID" /> + <param index="0" name="body" type="RID" /> + <description> + Returns the [RID] of the space assigned to the given soft body. + </description> + </method> + <method name="soft_body_get_state" qualifiers="const"> + <return type="Variant" /> + <param index="0" name="body" type="RID" /> + <param index="1" name="state" type="int" enum="PhysicsServer3D.BodyState" /> + <description> + Returns the given soft body state (see [enum BodyState] constants). + [b]Note:[/b] Godot's default physics implementation does not support [constant BODY_STATE_LINEAR_VELOCITY], [constant BODY_STATE_ANGULAR_VELOCITY], [constant BODY_STATE_SLEEPING], or [constant BODY_STATE_CAN_SLEEP]. + </description> + </method> + <method name="soft_body_get_total_mass" qualifiers="const"> + <return type="float" /> + <param index="0" name="body" type="RID" /> + <description> + Returns the total mass assigned to the given soft body. + </description> + </method> + <method name="soft_body_is_point_pinned" qualifiers="const"> + <return type="bool" /> + <param index="0" name="body" type="RID" /> + <param index="1" name="point_index" type="int" /> + <description> + Returns whether the given soft body point is pinned. + </description> + </method> + <method name="soft_body_move_point"> + <return type="void" /> + <param index="0" name="body" type="RID" /> + <param index="1" name="point_index" type="int" /> + <param index="2" name="global_position" type="Vector3" /> + <description> + Moves the given soft body point to a position in global coordinates. + </description> + </method> + <method name="soft_body_pin_point"> + <return type="void" /> + <param index="0" name="body" type="RID" /> + <param index="1" name="point_index" type="int" /> + <param index="2" name="pin" type="bool" /> + <description> + Pins or unpins the given soft body point based on the value of [param pin]. + [b]Note:[/b] Pinning a point effectively makes it kinematic, preventing it from being affected by forces, but you can still move it using [method soft_body_move_point]. + </description> + </method> + <method name="soft_body_remove_all_pinned_points"> + <return type="void" /> + <param index="0" name="body" type="RID" /> + <description> + Unpins all points of the given soft body. + </description> + </method> + <method name="soft_body_remove_collision_exception"> + <return type="void" /> + <param index="0" name="body" type="RID" /> + <param index="1" name="body_b" type="RID" /> + <description> + Removes the given body from the list of bodies exempt from collisions. + </description> + </method> + <method name="soft_body_set_collision_layer"> + <return type="void" /> + <param index="0" name="body" type="RID" /> + <param index="1" name="layer" type="int" /> + <description> + Sets the physics layer or layers the given soft body belongs to. + </description> + </method> + <method name="soft_body_set_collision_mask"> + <return type="void" /> + <param index="0" name="body" type="RID" /> + <param index="1" name="mask" type="int" /> + <description> + Sets the physics layer or layers the given soft body can collide with. + </description> + </method> + <method name="soft_body_set_damping_coefficient"> + <return type="void" /> + <param index="0" name="body" type="RID" /> + <param index="1" name="damping_coefficient" type="float" /> + <description> + Sets the damping coefficient of the given soft body. Higher values will slow down the body more noticeably when forces are applied. + </description> + </method> + <method name="soft_body_set_drag_coefficient"> + <return type="void" /> + <param index="0" name="body" type="RID" /> + <param index="1" name="drag_coefficient" type="float" /> + <description> + Sets the drag coefficient of the given soft body. Higher values increase this body's air resistance. + [b]Note:[/b] This value is currently unused by Godot's default physics implementation. + </description> + </method> + <method name="soft_body_set_linear_stiffness"> + <return type="void" /> + <param index="0" name="body" type="RID" /> + <param index="1" name="stiffness" type="float" /> + <description> + Sets the linear stiffness of the given soft body. Higher values will result in a stiffer body, while lower values will increase the body's ability to bend. The value can be between [code]0.0[/code] and [code]1.0[/code] (inclusive). + </description> + </method> + <method name="soft_body_set_mesh"> + <return type="void" /> + <param index="0" name="body" type="RID" /> + <param index="1" name="mesh" type="RID" /> + <description> + Sets the mesh of the given soft body. + </description> + </method> + <method name="soft_body_set_pressure_coefficient"> + <return type="void" /> + <param index="0" name="body" type="RID" /> + <param index="1" name="pressure_coefficient" type="float" /> + <description> + Sets the pressure coefficient of the given soft body. Simulates pressure build-up from inside this body. Higher values increase the strength of this effect. + </description> + </method> + <method name="soft_body_set_ray_pickable"> + <return type="void" /> + <param index="0" name="body" type="RID" /> + <param index="1" name="enable" type="bool" /> + <description> + Sets whether the given soft body will be pickable when using object picking. + </description> + </method> + <method name="soft_body_set_simulation_precision"> + <return type="void" /> + <param index="0" name="body" type="RID" /> + <param index="1" name="simulation_precision" type="int" /> + <description> + Sets the simulation precision of the given soft body. Increasing this value will improve the resulting simulation, but can affect performance. Use with care. + </description> + </method> + <method name="soft_body_set_space"> + <return type="void" /> + <param index="0" name="body" type="RID" /> + <param index="1" name="space" type="RID" /> + <description> + Assigns a space to the given soft body (see [method space_create]). + </description> + </method> + <method name="soft_body_set_state"> + <return type="void" /> + <param index="0" name="body" type="RID" /> + <param index="1" name="state" type="int" enum="PhysicsServer3D.BodyState" /> + <param index="2" name="variant" type="Variant" /> + <description> + Sets the given body state for the given body (see [enum BodyState] constants). + [b]Note:[/b] Godot's default physics implementation does not support [constant BODY_STATE_LINEAR_VELOCITY], [constant BODY_STATE_ANGULAR_VELOCITY], [constant BODY_STATE_SLEEPING], or [constant BODY_STATE_CAN_SLEEP]. + </description> + </method> + <method name="soft_body_set_total_mass"> + <return type="void" /> + <param index="0" name="body" type="RID" /> + <param index="1" name="total_mass" type="float" /> + <description> + Sets the total mass for the given soft body. + </description> + </method> + <method name="soft_body_set_transform"> + <return type="void" /> + <param index="0" name="body" type="RID" /> + <param index="1" name="transform" type="Transform3D" /> + <description> + Sets the global transform of the given soft body. + </description> + </method> + <method name="soft_body_update_rendering_server"> + <return type="void" /> + <param index="0" name="body" type="RID" /> + <param index="1" name="rendering_server_handler" type="PhysicsServer3DRenderingServerHandler" /> + <description> + Requests that the physics server updates the rendering server with the latest positions of the given soft body's points through the [param rendering_server_handler] interface. </description> </method> <method name="space_create"> diff --git a/doc/classes/PopupMenu.xml b/doc/classes/PopupMenu.xml index 40d5a9f4a2..8f6507eabf 100644 --- a/doc/classes/PopupMenu.xml +++ b/doc/classes/PopupMenu.xml @@ -133,8 +133,25 @@ <param index="4" name="accel" type="int" enum="Key" default="0" /> <description> Adds a new multistate item with text [param label]. - Contrarily to normal binary items, multistate items can have more than two states, as defined by [param max_states]. Each press or activate of the item will increase the state by one. The default value is defined by [param default_state]. + Contrarily to normal binary items, multistate items can have more than two states, as defined by [param max_states]. The default value is defined by [param default_state]. An [param id] can optionally be provided, as well as an accelerator ([param accel]). If no [param id] is provided, one will be created from the index. If no [param accel] is provided, then the default value of 0 (corresponding to [constant @GlobalScope.KEY_NONE]) will be assigned to the item (which means it won't have any accelerator). See [method get_item_accelerator] for more info on accelerators. + [b]Note:[/b] Multistate items don't update their state automatically and must be done manually. See [method toggle_item_multistate], [method set_item_multistate] and [method get_item_multistate] for more info on how to control it. + Example usage: + [codeblock] + func _ready(): + add_multistate_item("Item", 3, 0) + + index_pressed.connect(func(index: int): + toggle_item_multistate(index) + match get_item_multistate(index): + 0: + print("First state") + 1: + print("Second state") + 2: + print("Third state") + ) + [/codeblock] </description> </method> <method name="add_radio_check_item"> @@ -266,6 +283,20 @@ Returns the metadata of the specified item, which might be of any type. You can set it with [method set_item_metadata], which provides a simple way of assigning context data to items. </description> </method> + <method name="get_item_multistate" qualifiers="const"> + <return type="int" /> + <param index="0" name="index" type="int" /> + <description> + Returns the state of the item at the given [param index]. + </description> + </method> + <method name="get_item_multistate_max" qualifiers="const"> + <return type="int" /> + <param index="0" name="index" type="int" /> + <description> + Returns the max states of the item at the given [param index]. + </description> + </method> <method name="get_item_shortcut" qualifiers="const"> <return type="Shortcut" /> <param index="0" name="index" type="int" /> @@ -489,6 +520,14 @@ Sets the state of a multistate item. See [method add_multistate_item] for details. </description> </method> + <method name="set_item_multistate_max"> + <return type="void" /> + <param index="0" name="index" type="int" /> + <param index="1" name="max_states" type="int" /> + <description> + Sets the max states of a multistate item. See [method add_multistate_item] for details. + </description> + </method> <method name="set_item_shortcut"> <return type="void" /> <param index="0" name="index" type="int" /> diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index cbd797273c..0876261a31 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -943,7 +943,7 @@ </member> <member name="filesystem/import/blender/enabled" type="bool" setter="" getter="" default="true"> If [code]true[/code], Blender 3D scene files with the [code].blend[/code] extension will be imported by converting them to glTF 2.0. - This requires configuring a path to a Blender executable in the editor settings at [code]filesystem/import/blender/blender3_path[/code]. Blender 3.0 or later is required. + This requires configuring a path to a Blender executable in the editor settings at [code]filesystem/import/blender/blender_path[/code]. Blender 3.0 or later is required. </member> <member name="filesystem/import/blender/enabled.android" type="bool" setter="" getter="" default="false"> Override for [member filesystem/import/blender/enabled] on Android where Blender can't easily be accessed from Godot. diff --git a/doc/classes/RDPipelineDepthStencilState.xml b/doc/classes/RDPipelineDepthStencilState.xml index fa2a4e17c9..b8245f97af 100644 --- a/doc/classes/RDPipelineDepthStencilState.xml +++ b/doc/classes/RDPipelineDepthStencilState.xml @@ -10,47 +10,67 @@ </tutorials> <members> <member name="back_op_compare" type="int" setter="set_back_op_compare" getter="get_back_op_compare" enum="RenderingDevice.CompareOperator" default="7"> + The method used for comparing the previous back stencil value and [member back_op_reference]. </member> <member name="back_op_compare_mask" type="int" setter="set_back_op_compare_mask" getter="get_back_op_compare_mask" default="0"> + Selects which bits from the back stencil value will be compared. </member> <member name="back_op_depth_fail" type="int" setter="set_back_op_depth_fail" getter="get_back_op_depth_fail" enum="RenderingDevice.StencilOperation" default="1"> + The operation to perform on the stencil buffer for back pixels that pass the stencil test but fail the depth test. </member> <member name="back_op_fail" type="int" setter="set_back_op_fail" getter="get_back_op_fail" enum="RenderingDevice.StencilOperation" default="1"> + The operation to perform on the stencil buffer for back pixels that fail the stencil test </member> <member name="back_op_pass" type="int" setter="set_back_op_pass" getter="get_back_op_pass" enum="RenderingDevice.StencilOperation" default="1"> + The operation to perform on the stencil buffer for back pixels that pass the stencil test. </member> <member name="back_op_reference" type="int" setter="set_back_op_reference" getter="get_back_op_reference" default="0"> + The value the previous back stencil value will be compared to. </member> <member name="back_op_write_mask" type="int" setter="set_back_op_write_mask" getter="get_back_op_write_mask" default="0"> + Selects which bits from the back stencil value will be changed. </member> <member name="depth_compare_operator" type="int" setter="set_depth_compare_operator" getter="get_depth_compare_operator" enum="RenderingDevice.CompareOperator" default="7"> + The method used for comparing the previous and current depth values. </member> <member name="depth_range_max" type="float" setter="set_depth_range_max" getter="get_depth_range_max" default="0.0"> + The maximum depth that returns true for [member enable_depth_range]. </member> <member name="depth_range_min" type="float" setter="set_depth_range_min" getter="get_depth_range_min" default="0.0"> + The minimum depth that returns true for [member enable_depth_range]. </member> <member name="enable_depth_range" type="bool" setter="set_enable_depth_range" getter="get_enable_depth_range" default="false"> + If [code]true[/code], each depth value will be tested to see if it is between [member depth_range_min] and [member depth_range_max]. If it is outside of these values, it is discarded. </member> <member name="enable_depth_test" type="bool" setter="set_enable_depth_test" getter="get_enable_depth_test" default="false"> If [code]true[/code], enables depth testing which allows objects to be automatically occluded by other objects based on their depth. This also allows objects to be partially occluded by other objects. If [code]false[/code], objects will appear in the order they were drawn (like in Godot's 2D renderer). </member> <member name="enable_depth_write" type="bool" setter="set_enable_depth_write" getter="get_enable_depth_write" default="false"> + If [code]true[/code], writes to the depth buffer whenever the depth test returns true. Only works when enable_depth_test is also true. </member> <member name="enable_stencil" type="bool" setter="set_enable_stencil" getter="get_enable_stencil" default="false"> + If [code]true[/code], enables stencil testing. There are separate stencil buffers for front-facing triangles and back-facing triangles. See properties that begin with "front_op" and properties with "back_op" for each. </member> <member name="front_op_compare" type="int" setter="set_front_op_compare" getter="get_front_op_compare" enum="RenderingDevice.CompareOperator" default="7"> + The method used for comparing the previous front stencil value and [member front_op_reference]. </member> <member name="front_op_compare_mask" type="int" setter="set_front_op_compare_mask" getter="get_front_op_compare_mask" default="0"> + Selects which bits from the front stencil value will be compared. </member> <member name="front_op_depth_fail" type="int" setter="set_front_op_depth_fail" getter="get_front_op_depth_fail" enum="RenderingDevice.StencilOperation" default="1"> + The operation to perform on the stencil buffer for front pixels that pass the stencil test but fail the depth test. </member> <member name="front_op_fail" type="int" setter="set_front_op_fail" getter="get_front_op_fail" enum="RenderingDevice.StencilOperation" default="1"> + The operation to perform on the stencil buffer for front pixels that fail the stencil test. </member> <member name="front_op_pass" type="int" setter="set_front_op_pass" getter="get_front_op_pass" enum="RenderingDevice.StencilOperation" default="1"> + The operation to perform on the stencil buffer for front pixels that pass the stencil test. </member> <member name="front_op_reference" type="int" setter="set_front_op_reference" getter="get_front_op_reference" default="0"> + The value the previous front stencil value will be compared to. </member> <member name="front_op_write_mask" type="int" setter="set_front_op_write_mask" getter="get_front_op_write_mask" default="0"> + Selects which bits from the front stencil value will be changed. </member> </members> </class> diff --git a/doc/classes/RDSamplerState.xml b/doc/classes/RDSamplerState.xml index 44f040dabd..f1ba630226 100644 --- a/doc/classes/RDSamplerState.xml +++ b/doc/classes/RDSamplerState.xml @@ -26,12 +26,13 @@ The mipmap LOD bias to use. Positive values will make the sampler blurrier at a given distance, while negative values will make the sampler sharper at a given distance (at the risk of looking grainy). Recommended values are between [code]-0.5[/code] and [code]0.0[/code]. Only effective if the sampler has mipmaps available. </member> <member name="mag_filter" type="int" setter="set_mag_filter" getter="get_mag_filter" enum="RenderingDevice.SamplerFilter" default="0"> - The sampler's magnification filter. + The sampler's magnification filter. It is the filtering method used when sampling texels that appear bigger than on-screen pixels. </member> <member name="max_lod" type="float" setter="set_max_lod" getter="get_max_lod" default="1e+20"> The maximum mipmap LOD bias to display (lowest resolution). Only effective if the sampler has mipmaps available. </member> <member name="min_filter" type="int" setter="set_min_filter" getter="get_min_filter" enum="RenderingDevice.SamplerFilter" default="0"> + The sampler's minification filter. It is the filtering method used when sampling texels that appear smaller than on-screen pixels. </member> <member name="min_lod" type="float" setter="set_min_lod" getter="get_min_lod" default="0.0"> The minimum mipmap LOD bias to display (highest resolution). Only effective if the sampler has mipmaps available. @@ -49,6 +50,7 @@ The repeat mode to use along the W axis of UV coordinates. This affects the returned values if sampling outside the UV bounds. Only effective for 3D samplers. </member> <member name="unnormalized_uvw" type="bool" setter="set_unnormalized_uvw" getter="get_unnormalized_uvw" default="false"> + If [code]true[/code], the texture will be sampled with coordinates ranging from 0 to the texture's resolution. Otherwise, the coordinates will be normalized and range from 0 to 1. </member> <member name="use_anisotropy" type="bool" setter="set_use_anisotropy" getter="get_use_anisotropy" default="false"> If [code]true[/code], perform anisotropic sampling. See [member anisotropy_max]. diff --git a/doc/classes/RayCast2D.xml b/doc/classes/RayCast2D.xml index 22e3ae07ba..a101fd24de 100644 --- a/doc/classes/RayCast2D.xml +++ b/doc/classes/RayCast2D.xml @@ -4,7 +4,7 @@ A ray in 2D space, used to find the first [CollisionObject2D] it intersects. </brief_description> <description> - A raycast represents a ray from its origin to its [member target_position] that finds the closest [CollisionObject2D] along its path, if it intersects any. This is useful for a lot of things, such as + A raycast represents a ray from its origin to its [member target_position] that finds the closest [CollisionObject2D] along its path, if it intersects any. [RayCast2D] can ignore some objects by adding them to an exception list, by making its detection reporting ignore [Area2D]s ([member collide_with_areas]) or [PhysicsBody2D]s ([member collide_with_bodies]), or by configuring physics layers. [RayCast2D] calculates intersection every physics frame, and it holds the result until the next physics frame. For an immediate raycast, or if you want to configure a [RayCast2D] multiple times within the same physics frame, use [method force_raycast_update]. To sweep over a region of 2D space, you can approximate the region with multiple [RayCast2D]s or use [ShapeCast2D]. diff --git a/doc/classes/RayCast3D.xml b/doc/classes/RayCast3D.xml index 406fed107f..641c5ec653 100644 --- a/doc/classes/RayCast3D.xml +++ b/doc/classes/RayCast3D.xml @@ -4,7 +4,7 @@ A ray in 3D space, used to find the first [CollisionObject3D] it intersects. </brief_description> <description> - A raycast represents a ray from its origin to its [member target_position] that finds the closest [CollisionObject3D] along its path, if it intersects any. This is useful for a lot of things, such as + A raycast represents a ray from its origin to its [member target_position] that finds the closest [CollisionObject3D] along its path, if it intersects any. [RayCast3D] can ignore some objects by adding them to an exception list, by making its detection reporting ignore [Area3D]s ([member collide_with_areas]) or [PhysicsBody3D]s ([member collide_with_bodies]), or by configuring physics layers. [RayCast3D] calculates intersection every physics frame, and it holds the result until the next physics frame. For an immediate raycast, or if you want to configure a [RayCast3D] multiple times within the same physics frame, use [method force_raycast_update]. To sweep over a region of 3D space, you can approximate the region with multiple [RayCast3D]s or use [ShapeCast3D]. diff --git a/doc/classes/Skeleton3D.xml b/doc/classes/Skeleton3D.xml index 6cb1c948ea..2a629abc0d 100644 --- a/doc/classes/Skeleton3D.xml +++ b/doc/classes/Skeleton3D.xml @@ -310,12 +310,14 @@ [b]Note:[/b] Unless this value is [code]1.0[/code], the key value in animation will not match the actual position value. </member> <member name="show_rest_only" type="bool" setter="set_show_rest_only" getter="is_show_rest_only" default="false"> + If [code]true[/code], forces the bones in their default rest pose, regardless of their values. In the editor, this also prevents the bones from being edited. </member> </members> <signals> <signal name="bone_enabled_changed"> <param index="0" name="bone_idx" type="int" /> <description> + Emitted when the bone at [param bone_idx] is toggled with [method set_bone_enabled]. Use [method is_bone_enabled] to check the new value. </description> </signal> <signal name="bone_pose_changed"> @@ -326,15 +328,19 @@ </signal> <signal name="pose_updated"> <description> + Emitted when the pose is updated, after [constant NOTIFICATION_UPDATE_SKELETON] is received. </description> </signal> <signal name="show_rest_only_changed"> <description> + Emitted when the value of [member show_rest_only] changes. </description> </signal> </signals> <constants> <constant name="NOTIFICATION_UPDATE_SKELETON" value="50"> + Notification received when this skeleton's pose needs to be updated. + This notification is received [i]before[/i] the related [signal pose_updated] signal. </constant> </constants> </class> diff --git a/doc/classes/Texture2DRD.xml b/doc/classes/Texture2DRD.xml index b935a7763b..fe3a8a3213 100644 --- a/doc/classes/Texture2DRD.xml +++ b/doc/classes/Texture2DRD.xml @@ -10,7 +10,7 @@ </tutorials> <members> <member name="resource_local_to_scene" type="bool" setter="set_local_to_scene" getter="is_local_to_scene" overrides="Resource" default="false" /> - <member name="texture_rd_rid" type="RID" setter="set_texture_rd_rid" getter="get_texture_rd_rid" default="RID()"> + <member name="texture_rd_rid" type="RID" setter="set_texture_rd_rid" getter="get_texture_rd_rid"> The RID of the texture object created on the [RenderingDevice]. </member> </members> diff --git a/doc/classes/Texture3DRD.xml b/doc/classes/Texture3DRD.xml index f9d72b7a0f..38cd454bec 100644 --- a/doc/classes/Texture3DRD.xml +++ b/doc/classes/Texture3DRD.xml @@ -9,7 +9,7 @@ <tutorials> </tutorials> <members> - <member name="texture_rd_rid" type="RID" setter="set_texture_rd_rid" getter="get_texture_rd_rid" default="RID()"> + <member name="texture_rd_rid" type="RID" setter="set_texture_rd_rid" getter="get_texture_rd_rid"> The RID of the texture object created on the [RenderingDevice]. </member> </members> diff --git a/doc/classes/TextureLayeredRD.xml b/doc/classes/TextureLayeredRD.xml index 65f2d87624..c3de7f6913 100644 --- a/doc/classes/TextureLayeredRD.xml +++ b/doc/classes/TextureLayeredRD.xml @@ -9,7 +9,7 @@ <tutorials> </tutorials> <members> - <member name="texture_rd_rid" type="RID" setter="set_texture_rd_rid" getter="get_texture_rd_rid" default="RID()"> + <member name="texture_rd_rid" type="RID" setter="set_texture_rd_rid" getter="get_texture_rd_rid"> The RID of the texture object created on the [RenderingDevice]. </member> </members> diff --git a/doc/classes/TreeItem.xml b/doc/classes/TreeItem.xml index 597a363514..3b8a4c8872 100644 --- a/doc/classes/TreeItem.xml +++ b/doc/classes/TreeItem.xml @@ -160,6 +160,13 @@ Returns the custom color of column [param column]. </description> </method> + <method name="get_custom_draw_callback" qualifiers="const"> + <return type="Callable" /> + <param index="0" name="column" type="int" /> + <description> + Returns the custom callback of column [param column]. + </description> + </method> <method name="get_custom_font" qualifiers="const"> <return type="Font" /> <param index="0" name="column" type="int" /> @@ -553,7 +560,7 @@ Sets the given column's custom color. </description> </method> - <method name="set_custom_draw"> + <method name="set_custom_draw" is_deprecated="true"> <return type="void" /> <param index="0" name="column" type="int" /> <param index="1" name="object" type="Object" /> @@ -561,6 +568,16 @@ <description> Sets the given column's custom draw callback to [param callback] method on [param object]. The [param callback] should accept two arguments: the [TreeItem] that is drawn and its position and size as a [Rect2]. + [i]Deprecated.[/i] Use [method TreeItem.set_custom_draw_callback] instead. + </description> + </method> + <method name="set_custom_draw_callback"> + <return type="void" /> + <param index="0" name="column" type="int" /> + <param index="1" name="callback" type="Callable" /> + <description> + Sets the given column's custom draw callback. Use an empty [Callable] ([code skip-lint]Callable()[/code]) to clear the custom callback. + The [param callback] should accept two arguments: the [TreeItem] that is drawn and its position and size as a [Rect2]. </description> </method> <method name="set_custom_font"> diff --git a/doc/classes/UndoRedo.xml b/doc/classes/UndoRedo.xml index 50414d2580..3d36eafb08 100644 --- a/doc/classes/UndoRedo.xml +++ b/doc/classes/UndoRedo.xml @@ -255,6 +255,11 @@ </description> </method> </methods> + <members> + <member name="max_steps" type="int" setter="set_max_steps" getter="get_max_steps" default="0"> + The maximum number of steps that can be stored in the undo/redo history. If the number of stored steps exceeds this limit, older steps are removed from history and can no longer be reached by calling [method undo]. A value of [code]0[/code] or lower means no limit. + </member> + </members> <signals> <signal name="version_changed"> <description> diff --git a/doc/classes/Vector4i.xml b/doc/classes/Vector4i.xml index 464c19c223..ded5d6f3ef 100644 --- a/doc/classes/Vector4i.xml +++ b/doc/classes/Vector4i.xml @@ -6,7 +6,7 @@ <description> A 4-element structure that can be used to represent 4D grid coordinates or any other quadruplet of integers. It uses integer coordinates and is therefore preferable to [Vector4] when exact precision is required. Note that the values are limited to 32 bits, and unlike [Vector4] this cannot be configured with an engine build option. Use [int] or [PackedInt64Array] if 64-bit values are needed. - [b]Note:[/b] In a boolean context, a Vector4i will evaluate to [code]false[/code] if it's equal to [code]Vector4i(0, 0, 0, 0)[/code]. Otherwise, a Vector3i will always evaluate to [code]true[/code]. + [b]Note:[/b] In a boolean context, a Vector4i will evaluate to [code]false[/code] if it's equal to [code]Vector4i(0, 0, 0, 0)[/code]. Otherwise, a Vector4i will always evaluate to [code]true[/code]. </description> <tutorials> </tutorials> diff --git a/doc/classes/XRInterface.xml b/doc/classes/XRInterface.xml index a7878378dd..29a5b75672 100644 --- a/doc/classes/XRInterface.xml +++ b/doc/classes/XRInterface.xml @@ -256,7 +256,7 @@ Player is free to move around, full positional tracking. </constant> <constant name="XR_PLAY_AREA_STAGE" value="4" enum="PlayAreaMode"> - Same as [constant XR_PLAY_AREA_ROOMSCALE] but origin point is fixed to the center of the physical space, [method XRServer.center_on_hmd] disabled. + Same as [constant XR_PLAY_AREA_ROOMSCALE] but origin point is fixed to the center of the physical space. In this mode, system-level recentering may be disabled, requiring the use of [method XRServer.center_on_hmd]. </constant> <constant name="XR_ENV_BLEND_MODE_OPAQUE" value="0" enum="EnvironmentBlendMode"> Opaque blend mode. This is typically used for VR devices. diff --git a/doc/classes/XROrigin3D.xml b/doc/classes/XROrigin3D.xml index b35d27ab12..041e8cd7aa 100644 --- a/doc/classes/XROrigin3D.xml +++ b/doc/classes/XROrigin3D.xml @@ -5,20 +5,19 @@ </brief_description> <description> This is a special node within the AR/VR system that maps the physical location of the center of our tracking space to the virtual location within our game world. - There should be only one of these nodes in your scene and you must have one. All the XRCamera3D, XRController3D and XRAnchor3D nodes should be direct children of this node for spatial tracking to work correctly. + Multiple origin points can be added to the scene tree, but only one can used at a time. All the [XRCamera3D], [XRController3D], and [XRAnchor3D] nodes should be direct children of this node for spatial tracking to work correctly. It is the position of this node that you update when your character needs to move through your game world while we're not moving in the real world. Movement in the real world is always in relation to this origin point. - For example, if your character is driving a car, the XROrigin3D node should be a child node of this car. Or, if you're implementing a teleport system to move your character, you should change the position of this node. + For example, if your character is driving a car, the [XROrigin3D] node should be a child node of this car. Or, if you're implementing a teleport system to move your character, you should change the position of this node. </description> <tutorials> <link title="XR documentation index">$DOCS_URL/tutorials/xr/index.html</link> </tutorials> <members> <member name="current" type="bool" setter="set_current" getter="is_current" default="false"> - Is this XROrigin3D node the current origin used by the [XRServer]? + If [code]true[/code], this origin node is currently being used by the [XRServer]. Only one origin point can be used at a time. </member> <member name="world_scale" type="float" setter="set_world_scale" getter="get_world_scale" default="1.0"> - Allows you to adjust the scale to your game's units. Most AR/VR platforms assume a scale of 1 game world unit = 1 real world meter. - [b]Note:[/b] This method is a passthrough to the [XRServer] itself. + The scale of the game world compared to the real world. This is the same as [member XRServer.world_scale]. By default, most AR/VR platforms assume that 1 game unit corresponds to 1 real world meter. </member> </members> </class> diff --git a/doc/classes/XRServer.xml b/doc/classes/XRServer.xml index 4ef5d3473b..e98b6f3780 100644 --- a/doc/classes/XRServer.xml +++ b/doc/classes/XRServer.xml @@ -113,7 +113,7 @@ [b]Note:[/b] This property is managed by the current [XROrigin3D] node. It is exposed for access from GDExtensions. </member> <member name="world_scale" type="float" setter="set_world_scale" getter="get_world_scale" default="1.0"> - Allows you to adjust the scale to your game's units. Most AR/VR platforms assume a scale of 1 game world unit = 1 real world meter. + The scale of the game world compared to the real world. By default, most AR/VR platforms assume that 1 game unit corresponds to 1 real world meter. </member> </members> <signals> diff --git a/drivers/d3d12/SCsub b/drivers/d3d12/SCsub index a49d55ebc0..6b23e1bad0 100644 --- a/drivers/d3d12/SCsub +++ b/drivers/d3d12/SCsub @@ -25,7 +25,7 @@ env_d3d12_rdd.Append(CPPPATH=["#thirdparty/d3d12ma"]) # Agility SDK. -if env["agility_sdk_path"] != "": +if env["agility_sdk_path"] != "" and os.path.exists(env["agility_sdk_path"]): env_d3d12_rdd.Append(CPPDEFINES=["AGILITY_SDK_ENABLED"]) if env["agility_sdk_multiarch"]: env_d3d12_rdd.Append(CPPDEFINES=["AGILITY_SDK_MULTIARCH_ENABLED"]) @@ -33,7 +33,7 @@ if env["agility_sdk_path"] != "": # PIX. -if env["pix_path"] != "": +if env["pix_path"] != "" and os.path.exists(env["pix_path"]): env_d3d12_rdd.Append(CPPDEFINES=["PIX_ENABLED"]) env_d3d12_rdd.Append(CPPPATH=[env["pix_path"] + "/Include"]) diff --git a/drivers/d3d12/d3d12_context.cpp b/drivers/d3d12/d3d12_context.cpp index 0ba3f59119..b2ef0e66d8 100644 --- a/drivers/d3d12/d3d12_context.cpp +++ b/drivers/d3d12/d3d12_context.cpp @@ -520,9 +520,8 @@ Error D3D12Context::_create_device(DeviceBasics &r_basics) { res = r_basics.device.As(&info_queue); ERR_FAIL_COND_V(!SUCCEEDED(res), ERR_CANT_CREATE); -#if 0 // This causes crashes. Needs investigation. ComPtr<ID3D12InfoQueue1> info_queue_1; - device.As(&info_queue_1); + r_basics.device.As(&info_queue_1); if (info_queue_1) { // Custom printing supported (added in Windows 10 Release Preview build 20236). @@ -530,9 +529,7 @@ Error D3D12Context::_create_device(DeviceBasics &r_basics) { res = info_queue_1->RegisterMessageCallback(&_debug_message_func, D3D12_MESSAGE_CALLBACK_IGNORE_FILTERS, nullptr, 0); ERR_FAIL_COND_V(!SUCCEEDED(res), ERR_CANT_CREATE); - } else -#endif - { + } else { // Rely on D3D12's own debug printing. if (Engine::get_singleton()->is_abort_on_gpu_errors_enabled()) { diff --git a/drivers/d3d12/d3d12_context.h b/drivers/d3d12/d3d12_context.h index ac90d38467..b6551d798d 100644 --- a/drivers/d3d12/d3d12_context.h +++ b/drivers/d3d12/d3d12_context.h @@ -174,7 +174,7 @@ private: LocalVector<ID3D12CommandList *> command_list_queue; uint32_t command_list_count = 1; - static void _debug_message_func( + static void STDMETHODCALLTYPE _debug_message_func( D3D12_MESSAGE_CATEGORY p_category, D3D12_MESSAGE_SEVERITY p_severity, D3D12_MESSAGE_ID p_id, diff --git a/drivers/d3d12/rendering_device_driver_d3d12.cpp b/drivers/d3d12/rendering_device_driver_d3d12.cpp index aed82b4ce8..65ee68b7fe 100644 --- a/drivers/d3d12/rendering_device_driver_d3d12.cpp +++ b/drivers/d3d12/rendering_device_driver_d3d12.cpp @@ -91,10 +91,8 @@ extern "C" { static const D3D12_RANGE VOID_RANGE = {}; -static const uint32_t ROOT_CONSTANT_SPACE = RDD::MAX_UNIFORM_SETS + 1; -static const uint32_t ROOT_CONSTANT_REGISTER = 0; -static const uint32_t RUNTIME_DATA_SPACE = RDD::MAX_UNIFORM_SETS + 2; -static const uint32_t RUNTIME_DATA_REGISTER = 0; +static const uint32_t ROOT_CONSTANT_REGISTER = GODOT_NIR_DESCRIPTOR_SET_MULTIPLIER * (RDD::MAX_UNIFORM_SETS + 1); +static const uint32_t RUNTIME_DATA_REGISTER = GODOT_NIR_DESCRIPTOR_SET_MULTIPLIER * (RDD::MAX_UNIFORM_SETS + 2); #ifdef DEV_ENABLED //#define DEBUG_COUNT_BARRIERS @@ -2304,9 +2302,7 @@ Vector<uint8_t> RenderingDeviceDriverD3D12::shader_compile_binary_from_spirv(Vec nir_options.lower_base_vertex = false; dxil_spirv_runtime_conf dxil_runtime_conf = {}; - dxil_runtime_conf.runtime_data_cbv.register_space = RUNTIME_DATA_SPACE; dxil_runtime_conf.runtime_data_cbv.base_shader_register = RUNTIME_DATA_REGISTER; - dxil_runtime_conf.push_constant_cbv.register_space = ROOT_CONSTANT_SPACE; dxil_runtime_conf.push_constant_cbv.base_shader_register = ROOT_CONSTANT_REGISTER; dxil_runtime_conf.zero_based_vertex_instance_id = true; dxil_runtime_conf.zero_based_compute_workgroup_id = true; @@ -2424,10 +2420,10 @@ Vector<uint8_t> RenderingDeviceDriverD3D12::shader_compile_binary_from_spirv(Vec DEV_ASSERT(p_dxil_type < ARRAY_SIZE(DXIL_TYPE_TO_CLASS)); ResourceClass res_class = DXIL_TYPE_TO_CLASS[p_dxil_type]; - if (p_register == ROOT_CONSTANT_REGISTER && p_space == ROOT_CONSTANT_SPACE) { + if (p_register == ROOT_CONSTANT_REGISTER && p_space == 0) { DEV_ASSERT(res_class == RES_CLASS_CBV); shader_data_in.binary_data.dxil_push_constant_stages |= (1 << shader_data_in.stage); - } else if (p_register == RUNTIME_DATA_REGISTER && p_space == RUNTIME_DATA_SPACE) { + } else if (p_register == RUNTIME_DATA_REGISTER && p_space == 0) { DEV_ASSERT(res_class == RES_CLASS_CBV); shader_data_in.binary_data.nir_runtime_data_root_param_idx = 1; // Temporary, to be determined later. } else { @@ -2586,7 +2582,7 @@ Vector<uint8_t> RenderingDeviceDriverD3D12::shader_compile_binary_from_spirv(Vec push_constant.InitAsConstants( binary_data.push_constant_size / sizeof(uint32_t), ROOT_CONSTANT_REGISTER, - ROOT_CONSTANT_SPACE, + 0, stages_to_d3d12_visibility(binary_data.dxil_push_constant_stages)); root_params.push_back(push_constant); } @@ -2599,7 +2595,7 @@ Vector<uint8_t> RenderingDeviceDriverD3D12::shader_compile_binary_from_spirv(Vec nir_runtime_data.InitAsConstants( sizeof(dxil_spirv_vertex_runtime_data) / sizeof(uint32_t), RUNTIME_DATA_REGISTER, - RUNTIME_DATA_SPACE, + 0, D3D12_SHADER_VISIBILITY_VERTEX); root_params.push_back(nir_runtime_data); } diff --git a/drivers/gles3/effects/copy_effects.cpp b/drivers/gles3/effects/copy_effects.cpp index c6fb6ca70b..7b350d8da6 100644 --- a/drivers/gles3/effects/copy_effects.cpp +++ b/drivers/gles3/effects/copy_effects.cpp @@ -152,7 +152,17 @@ void CopyEffects::copy_cube_to_rect(const Rect2 &p_rect) { return; } - copy.shader.version_set_uniform(CopyShaderGLES3::COPY_SECTION, p_rect.position.x, p_rect.position.y, p_rect.size.x, p_rect.size.y, copy.shader_version, CopyShaderGLES3::MODE_COPY_SECTION); + copy.shader.version_set_uniform(CopyShaderGLES3::COPY_SECTION, p_rect.position.x, p_rect.position.y, p_rect.size.x, p_rect.size.y, copy.shader_version, CopyShaderGLES3::MODE_CUBE_TO_OCTAHEDRAL); + draw_screen_quad(); +} + +void CopyEffects::copy_cube_to_panorama(float p_mip_level) { + bool success = copy.shader.version_bind_shader(copy.shader_version, CopyShaderGLES3::MODE_CUBE_TO_PANORAMA); + if (!success) { + return; + } + + copy.shader.version_set_uniform(CopyShaderGLES3::MIP_LEVEL, p_mip_level, copy.shader_version, CopyShaderGLES3::MODE_CUBE_TO_PANORAMA); draw_screen_quad(); } diff --git a/drivers/gles3/effects/copy_effects.h b/drivers/gles3/effects/copy_effects.h index 509d07b955..62707ebe99 100644 --- a/drivers/gles3/effects/copy_effects.h +++ b/drivers/gles3/effects/copy_effects.h @@ -65,6 +65,7 @@ public: void copy_to_and_from_rect(const Rect2 &p_rect); void copy_screen(); void copy_cube_to_rect(const Rect2 &p_rect); + void copy_cube_to_panorama(float p_mip_level); void bilinear_blur(GLuint p_source_texture, int p_mipmap_count, const Rect2i &p_region); void gaussian_blur(GLuint p_source_texture, int p_mipmap_count, const Rect2i &p_region, const Size2i &p_size); void set_color(const Color &p_color, const Rect2i &p_region); diff --git a/drivers/gles3/rasterizer_canvas_gles3.cpp b/drivers/gles3/rasterizer_canvas_gles3.cpp index fe16d8838b..2613b6762c 100644 --- a/drivers/gles3/rasterizer_canvas_gles3.cpp +++ b/drivers/gles3/rasterizer_canvas_gles3.cpp @@ -775,6 +775,7 @@ void RasterizerCanvasGLES3::_render_items(RID p_to_render_target, int p_item_cou _render_batch(p_lights, i, r_render_info); } + glDisable(GL_SCISSOR_TEST); state.current_batch_index = 0; state.canvas_instance_batches.clear(); state.last_item_index += index; diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp index a646b1ec7d..f0a6397041 100644 --- a/drivers/gles3/rasterizer_scene_gles3.cpp +++ b/drivers/gles3/rasterizer_scene_gles3.cpp @@ -280,7 +280,9 @@ void RasterizerSceneGLES3::_geometry_instance_add_surface_with_material(Geometry GLES3::Mesh::Surface *s = reinterpret_cast<GLES3::Mesh::Surface *>(sdcache->surface); if (p_material->shader_data->uses_tangent && !(s->format & RS::ARRAY_FORMAT_TANGENT)) { - WARN_PRINT_ED("Attempting to use a shader that requires tangents with a mesh that doesn't contain tangents. Ensure that meshes are imported with the 'ensure_tangents' option. If creating your own meshes, add an `ARRAY_TANGENT` array (when using ArrayMesh) or call `generate_tangents()` (when using SurfaceTool)."); + String shader_path = p_material->shader_data->path.is_empty() ? "" : "(" + p_material->shader_data->path + ")"; + String mesh_path = mesh_storage->mesh_get_path(p_mesh).is_empty() ? "" : "(" + mesh_storage->mesh_get_path(p_mesh) + ")"; + WARN_PRINT_ED(vformat("Attempting to use a shader %s that requires tangents with a mesh %s that doesn't contain tangents. Ensure that meshes are imported with the 'ensure_tangents' option. If creating your own meshes, add an `ARRAY_TANGENT` array (when using ArrayMesh) or call `generate_tangents()` (when using SurfaceTool).", shader_path, mesh_path)); } } @@ -541,77 +543,48 @@ void RasterizerSceneGLES3::_invalidate_sky(Sky *p_sky) { } } -void RasterizerSceneGLES3::_update_dirty_skys() { - Sky *sky = dirty_sky_list; - - while (sky) { - if (sky->radiance == 0) { - sky->mipmap_count = Image::get_image_required_mipmaps(sky->radiance_size, sky->radiance_size, Image::FORMAT_RGBA8) - 1; - // Left uninitialized, will attach a texture at render time - glGenFramebuffers(1, &sky->radiance_framebuffer); - - GLenum internal_format = GL_RGB10_A2; - - glGenTextures(1, &sky->radiance); - glBindTexture(GL_TEXTURE_CUBE_MAP, sky->radiance); +GLuint _init_radiance_texture(int p_size, int p_mipmaps, String p_name) { + GLuint radiance_id = 0; + glGenTextures(1, &radiance_id); + glBindTexture(GL_TEXTURE_CUBE_MAP, radiance_id); #ifdef GL_API_ENABLED - if (RasterizerGLES3::is_gles_over_gl()) { - GLenum format = GL_RGBA; - GLenum type = GL_UNSIGNED_INT_2_10_10_10_REV; - //TODO, on low-end compare this to allocating each face of each mip individually - // see: https://www.khronos.org/registry/OpenGL-Refpages/es3.0/html/glTexStorage2D.xhtml - for (int i = 0; i < 6; i++) { - glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, internal_format, sky->radiance_size, sky->radiance_size, 0, format, type, nullptr); - } + if (RasterizerGLES3::is_gles_over_gl()) { + //TODO, on low-end compare this to allocating each face of each mip individually + // see: https://www.khronos.org/registry/OpenGL-Refpages/es3.0/html/glTexStorage2D.xhtml + for (int i = 0; i < 6; i++) { + glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB10_A2, p_size, p_size, 0, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV, nullptr); + } - glGenerateMipmap(GL_TEXTURE_CUBE_MAP); - } + glGenerateMipmap(GL_TEXTURE_CUBE_MAP); + } #endif // GL_API_ENABLED #ifdef GLES_API_ENABLED - if (!RasterizerGLES3::is_gles_over_gl()) { - glTexStorage2D(GL_TEXTURE_CUBE_MAP, sky->mipmap_count, internal_format, sky->radiance_size, sky->radiance_size); - } + if (!RasterizerGLES3::is_gles_over_gl()) { + glTexStorage2D(GL_TEXTURE_CUBE_MAP, p_mipmaps, GL_RGB10_A2, p_size, p_size); + } #endif // GLES_API_ENABLED - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_BASE_LEVEL, 0); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_LEVEL, sky->mipmap_count - 1); - - GLES3::Utilities::get_singleton()->texture_allocated_data(sky->radiance, Image::get_image_data_size(sky->radiance_size, sky->radiance_size, Image::FORMAT_RGBA8, true), "Sky radiance map"); - - glGenTextures(1, &sky->raw_radiance); - glBindTexture(GL_TEXTURE_CUBE_MAP, sky->raw_radiance); - -#ifdef GL_API_ENABLED - if (RasterizerGLES3::is_gles_over_gl()) { - GLenum format = GL_RGBA; - GLenum type = GL_UNSIGNED_INT_2_10_10_10_REV; - //TODO, on low-end compare this to allocating each face of each mip individually - // see: https://www.khronos.org/registry/OpenGL-Refpages/es3.0/html/glTexStorage2D.xhtml - for (int i = 0; i < 6; i++) { - glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, internal_format, sky->radiance_size, sky->radiance_size, 0, format, type, nullptr); - } + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_BASE_LEVEL, 0); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_LEVEL, p_mipmaps - 1); + + GLES3::Utilities::get_singleton()->texture_allocated_data(radiance_id, Image::get_image_data_size(p_size, p_size, Image::FORMAT_RGBA8, true), p_name); + return radiance_id; +} - glGenerateMipmap(GL_TEXTURE_CUBE_MAP); - } -#endif // GL_API_ENABLED -#ifdef GLES_API_ENABLED - if (!RasterizerGLES3::is_gles_over_gl()) { - glTexStorage2D(GL_TEXTURE_CUBE_MAP, sky->mipmap_count, internal_format, sky->radiance_size, sky->radiance_size); - } -#endif // GLES_API_ENABLED - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_BASE_LEVEL, 0); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_LEVEL, sky->mipmap_count - 1); +void RasterizerSceneGLES3::_update_dirty_skys() { + Sky *sky = dirty_sky_list; - glBindTexture(GL_TEXTURE_CUBE_MAP, 0); - GLES3::Utilities::get_singleton()->texture_allocated_data(sky->raw_radiance, Image::get_image_data_size(sky->radiance_size, sky->radiance_size, Image::FORMAT_RGBA8, true), "Sky raw radiance map"); + while (sky) { + if (sky->radiance == 0) { + sky->mipmap_count = Image::get_image_required_mipmaps(sky->radiance_size, sky->radiance_size, Image::FORMAT_RGBA8) - 1; + // Left uninitialized, will attach a texture at render time + glGenFramebuffers(1, &sky->radiance_framebuffer); + sky->radiance = _init_radiance_texture(sky->radiance_size, sky->mipmap_count, "Sky radiance texture"); + sky->raw_radiance = _init_radiance_texture(sky->radiance_size, sky->mipmap_count, "Sky raw radiance texture"); } sky->reflection_dirty = true; @@ -1140,7 +1113,80 @@ void RasterizerSceneGLES3::_filter_sky_radiance(Sky *p_sky, int p_base_layer) { } Ref<Image> RasterizerSceneGLES3::sky_bake_panorama(RID p_sky, float p_energy, bool p_bake_irradiance, const Size2i &p_size) { - return Ref<Image>(); + Sky *sky = sky_owner.get_or_null(p_sky); + ERR_FAIL_NULL_V(sky, Ref<Image>()); + + _update_dirty_skys(); + + if (sky->radiance == 0) { + return Ref<Image>(); + } + + GLES3::CopyEffects *copy_effects = GLES3::CopyEffects::get_singleton(); + GLES3::Config *config = GLES3::Config::get_singleton(); + + GLuint rad_tex = 0; + glGenTextures(1, &rad_tex); + glBindTexture(GL_TEXTURE_2D, rad_tex); + if (config->float_texture_supported) { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, p_size.width, p_size.height, 0, GL_RGBA, GL_FLOAT, nullptr); + GLES3::Utilities::get_singleton()->texture_allocated_data(rad_tex, p_size.width * p_size.height * 16, "Temp sky panorama"); + } else { + // Fallback to RGBA8 on devices that don't support rendering to floating point textures. This will look bad, but we have no choice. + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, p_size.width, p_size.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + GLES3::Utilities::get_singleton()->texture_allocated_data(rad_tex, p_size.width * p_size.height * 4, "Temp sky panorama"); + } + + GLuint rad_fbo = 0; + glGenFramebuffers(1, &rad_fbo); + glBindFramebuffer(GL_FRAMEBUFFER, rad_fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, rad_tex, 0); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_CUBE_MAP, sky->radiance); + glViewport(0, 0, p_size.width, p_size.height); + + glClearColor(0.0, 0.0, 0.0, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + + copy_effects->copy_cube_to_panorama(p_bake_irradiance ? float(sky->mipmap_count) : 0.0); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glDeleteFramebuffers(1, &rad_fbo); + // Create a dummy texture so we can use texture_2d_get. + RID tex_rid = GLES3::TextureStorage::get_singleton()->texture_allocate(); + GLES3::Texture texture; + texture.width = p_size.width; + texture.height = p_size.height; + texture.alloc_width = p_size.width; + texture.alloc_height = p_size.height; + texture.format = Image::FORMAT_RGBAF; + texture.real_format = Image::FORMAT_RGBAF; + texture.gl_format_cache = GL_RGBA; + texture.gl_type_cache = GL_FLOAT; + texture.type = GLES3::Texture::TYPE_2D; + texture.target = GL_TEXTURE_2D; + texture.active = true; + texture.tex_id = rad_tex; + texture.is_render_target = true; + + GLES3::TextureStorage::get_singleton()->texture_2d_initialize_from_texture(tex_rid, texture); + Ref<Image> img = GLES3::TextureStorage::get_singleton()->texture_2d_get(tex_rid); + GLES3::Utilities::get_singleton()->texture_free_data(rad_tex); + + texture.is_render_target = false; + texture.tex_id = 0; + GLES3::TextureStorage::get_singleton()->texture_free(tex_rid); + + for (int i = 0; i < p_size.width; i++) { + for (int j = 0; j < p_size.height; j++) { + Color c = img->get_pixel(i, j); + c.r *= p_energy; + c.g *= p_energy; + c.b *= p_energy; + img->set_pixel(i, j, c); + } + } + return img; } /* ENVIRONMENT API */ @@ -1174,7 +1220,65 @@ void RasterizerSceneGLES3::environment_set_volumetric_fog_filter_active(bool p_e } Ref<Image> RasterizerSceneGLES3::environment_bake_panorama(RID p_env, bool p_bake_irradiance, const Size2i &p_size) { - return Ref<Image>(); + ERR_FAIL_COND_V(p_env.is_null(), Ref<Image>()); + + RS::EnvironmentBG environment_background = environment_get_background(p_env); + + if (environment_background == RS::ENV_BG_CAMERA_FEED || environment_background == RS::ENV_BG_CANVAS || environment_background == RS::ENV_BG_KEEP) { + return Ref<Image>(); // Nothing to bake. + } + + RS::EnvironmentAmbientSource ambient_source = environment_get_ambient_source(p_env); + + bool use_ambient_light = false; + bool use_cube_map = false; + if (ambient_source == RS::ENV_AMBIENT_SOURCE_BG && (environment_background == RS::ENV_BG_CLEAR_COLOR || environment_background == RS::ENV_BG_COLOR)) { + use_ambient_light = true; + } else { + use_cube_map = (ambient_source == RS::ENV_AMBIENT_SOURCE_BG && environment_background == RS::ENV_BG_SKY) || ambient_source == RS::ENV_AMBIENT_SOURCE_SKY; + use_ambient_light = use_cube_map || ambient_source == RS::ENV_AMBIENT_SOURCE_COLOR; + } + + use_cube_map = use_cube_map || (environment_background == RS::ENV_BG_SKY && environment_get_sky(p_env).is_valid()); + + Color ambient_color; + float ambient_color_sky_mix = 0.0; + if (use_ambient_light) { + ambient_color_sky_mix = environment_get_ambient_sky_contribution(p_env); + const float ambient_energy = environment_get_ambient_light_energy(p_env); + ambient_color = environment_get_ambient_light(p_env); + ambient_color = ambient_color.srgb_to_linear(); + ambient_color.r *= ambient_energy; + ambient_color.g *= ambient_energy; + ambient_color.b *= ambient_energy; + } + + if (use_cube_map) { + Ref<Image> panorama = sky_bake_panorama(environment_get_sky(p_env), environment_get_bg_energy_multiplier(p_env), p_bake_irradiance, p_size); + if (use_ambient_light) { + for (int x = 0; x < p_size.width; x++) { + for (int y = 0; y < p_size.height; y++) { + panorama->set_pixel(x, y, ambient_color.lerp(panorama->get_pixel(x, y), ambient_color_sky_mix)); + } + } + } + return panorama; + } else { + const float bg_energy_multiplier = environment_get_bg_energy_multiplier(p_env); + Color panorama_color = ((environment_background == RS::ENV_BG_CLEAR_COLOR) ? RSG::texture_storage->get_default_clear_color() : environment_get_bg_color(p_env)); + panorama_color = panorama_color.srgb_to_linear(); + panorama_color.r *= bg_energy_multiplier; + panorama_color.g *= bg_energy_multiplier; + panorama_color.b *= bg_energy_multiplier; + + if (use_ambient_light) { + panorama_color = ambient_color.lerp(panorama_color, ambient_color_sky_mix); + } + + Ref<Image> panorama = Image::create_empty(p_size.width, p_size.height, false, Image::FORMAT_RGBAF); + panorama->fill(panorama_color); + return panorama; + } } void RasterizerSceneGLES3::positional_soft_shadow_filter_set_quality(RS::ShadowQuality p_quality) { @@ -1485,7 +1589,15 @@ void RasterizerSceneGLES3::_setup_environment(const RenderDataGLES3 *p_render_da //time global variables scene_state.ubo.time = time; - if (is_environment(p_render_data->environment)) { + if (get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_UNSHADED) { + scene_state.ubo.use_ambient_light = true; + scene_state.ubo.ambient_light_color_energy[0] = 1; + scene_state.ubo.ambient_light_color_energy[1] = 1; + scene_state.ubo.ambient_light_color_energy[2] = 1; + scene_state.ubo.ambient_light_color_energy[3] = 1.0; + scene_state.ubo.use_ambient_cubemap = false; + scene_state.ubo.use_reflection_cubemap = false; + } else if (is_environment(p_render_data->environment)) { RS::EnvironmentBG env_bg = environment_get_background(p_render_data->environment); RS::EnvironmentAmbientSource ambient_src = environment_get_ambient_source(p_render_data->environment); @@ -2163,6 +2275,7 @@ void RasterizerSceneGLES3::_render_shadow_pass(RID p_light, RID p_shadow_atlas, scene_state.cull_mode = GLES3::SceneShaderData::CULL_BACK; glColorMask(0, 0, 0, 0); + glDrawBuffers(0, nullptr); RasterizerGLES3::clear_depth(1.0); if (needs_clear) { glClear(GL_DEPTH_BUFFER_BIT); @@ -2324,7 +2437,7 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_ bool keep_color = false; float sky_energy_multiplier = 1.0; - if (get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_OVERDRAW) { + if (unlikely(get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_OVERDRAW)) { clear_color = Color(0, 0, 0, 1); //in overdraw mode, BG should always be black } else if (render_data.environment.is_valid()) { RS::EnvironmentBG bg_mode = environment_get_background(render_data.environment); @@ -2421,8 +2534,9 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_ glColorMask(0, 0, 0, 0); RasterizerGLES3::clear_depth(1.0); - glClear(GL_DEPTH_BUFFER_BIT); + glDrawBuffers(0, nullptr); + uint64_t spec_constant = SceneShaderGLES3::DISABLE_FOG | SceneShaderGLES3::DISABLE_LIGHT_DIRECTIONAL | SceneShaderGLES3::DISABLE_LIGHTMAP | SceneShaderGLES3::DISABLE_LIGHT_OMNI | SceneShaderGLES3::DISABLE_LIGHT_SPOT; @@ -2455,6 +2569,11 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_ scene_state.current_depth_test = GLES3::SceneShaderData::DEPTH_TEST_ENABLED; scene_state.current_depth_draw = GLES3::SceneShaderData::DEPTH_DRAW_ALWAYS; + { + GLuint db = GL_COLOR_ATTACHMENT0; + glDrawBuffers(1, &db); + } + if (!fb_cleared) { RasterizerGLES3::clear_depth(1.0); glClear(GL_DEPTH_BUFFER_BIT); @@ -2693,6 +2812,7 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params, } glBindTexture(GL_TEXTURE_CUBE_MAP, texture_to_bind); } + } else if constexpr (p_pass_mode == PASS_MODE_DEPTH || p_pass_mode == PASS_MODE_SHADOW) { shader_variant = SceneShaderGLES3::MODE_DEPTH; } @@ -2730,8 +2850,16 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params, material_data = surf->material_shadow; mesh_surface = surf->surface_shadow; } else { - shader = surf->shader; - material_data = surf->material; + if (unlikely(get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_OVERDRAW)) { + material_data = overdraw_material_data_ptr; + shader = material_data->shader_data; + } else if (unlikely(get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_LIGHTING)) { + material_data = default_material_data_ptr; + shader = material_data->shader_data; + } else { + shader = surf->shader; + material_data = surf->material; + } mesh_surface = surf->surface; } @@ -2864,7 +2992,7 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params, // Find cull variant. GLES3::SceneShaderData::Cull cull_mode = shader->cull_mode; - if ((surf->flags & GeometryInstanceSurface::FLAG_USES_DOUBLE_SIDED_SHADOWS)) { + if (p_pass_mode == PASS_MODE_MATERIAL || (surf->flags & GeometryInstanceSurface::FLAG_USES_DOUBLE_SIDED_SHADOWS)) { cull_mode = GLES3::SceneShaderData::CULL_DISABLED; } else { bool mirror = inst->mirror; @@ -2901,7 +3029,7 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params, GLuint vertex_array_gl = 0; GLuint index_array_gl = 0; uint64_t vertex_input_mask = shader->vertex_input_mask; - if (inst->lightmap_instance.is_valid()) { + if (inst->lightmap_instance.is_valid() || p_pass_mode == PASS_MODE_MATERIAL) { vertex_input_mask |= 1 << RS::ARRAY_TEX_UV2; } @@ -3175,6 +3303,10 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params, material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::MODEL_FLAGS, inst->flags_cache, shader->version, instance_variant, spec_constants); + if (p_pass_mode == PASS_MODE_MATERIAL) { + material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::UV_OFFSET, p_params->uv_offset, shader->version, instance_variant, spec_constants); + } + // Can be index count or vertex count uint32_t count = 0; if (surf->lod_index > 0) { @@ -3345,6 +3477,8 @@ void RasterizerSceneGLES3::render_particle_collider_heightfield(RID p_collider, glEnable(GL_CULL_FACE); scene_state.cull_mode = GLES3::SceneShaderData::CULL_BACK; + glDrawBuffers(0, nullptr); + glColorMask(0, 0, 0, 0); RasterizerGLES3::clear_depth(1.0); @@ -3358,6 +3492,93 @@ void RasterizerSceneGLES3::render_particle_collider_heightfield(RID p_collider, glBindFramebuffer(GL_FRAMEBUFFER, 0); } +void RasterizerSceneGLES3::_render_uv2(const PagedArray<RenderGeometryInstance *> &p_instances, GLuint p_framebuffer, const Rect2i &p_region) { + RENDER_TIMESTAMP("Setup Rendering UV2"); + + RenderDataGLES3 render_data; + render_data.instances = &p_instances; + + scene_state.ubo.emissive_exposure_normalization = -1.0; // Use default exposure normalization. + + _setup_environment(&render_data, true, Vector2(1, 1), true, Color(), false); + + PassMode pass_mode = PASS_MODE_MATERIAL; + + _fill_render_list(RENDER_LIST_SECONDARY, &render_data, pass_mode); + render_list[RENDER_LIST_SECONDARY].sort_by_key(); + + RENDER_TIMESTAMP("Render 3D Material"); + + { + glBindFramebuffer(GL_FRAMEBUFFER, p_framebuffer); + glViewport(p_region.position.x, p_region.position.y, p_region.size.x, p_region.size.y); + + GLuint global_buffer = GLES3::MaterialStorage::get_singleton()->global_shader_parameters_get_uniform_buffer(); + + glBindBufferBase(GL_UNIFORM_BUFFER, SCENE_GLOBALS_UNIFORM_LOCATION, global_buffer); + glBindBuffer(GL_UNIFORM_BUFFER, 0); + + glDisable(GL_BLEND); + glDepthMask(GL_TRUE); + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LESS); + glDisable(GL_SCISSOR_TEST); + glCullFace(GL_BACK); + glEnable(GL_CULL_FACE); + scene_state.cull_mode = GLES3::SceneShaderData::CULL_BACK; + + TightLocalVector<GLenum> draw_buffers; + draw_buffers.push_back(GL_COLOR_ATTACHMENT0); + draw_buffers.push_back(GL_COLOR_ATTACHMENT1); + draw_buffers.push_back(GL_COLOR_ATTACHMENT2); + draw_buffers.push_back(GL_COLOR_ATTACHMENT3); + glDrawBuffers(draw_buffers.size(), draw_buffers.ptr()); + + glClearColor(0.0, 0.0, 0.0, 0.0); + RasterizerGLES3::clear_depth(1.0); + glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); + + uint64_t base_spec_constant = 0; + base_spec_constant |= SceneShaderGLES3::RENDER_MATERIAL; + base_spec_constant |= SceneShaderGLES3::DISABLE_FOG; + base_spec_constant |= SceneShaderGLES3::DISABLE_LIGHT_DIRECTIONAL; + base_spec_constant |= SceneShaderGLES3::DISABLE_LIGHT_OMNI; + base_spec_constant |= SceneShaderGLES3::DISABLE_LIGHT_SPOT; + base_spec_constant |= SceneShaderGLES3::DISABLE_LIGHTMAP; + + RenderListParameters render_list_params(render_list[RENDER_LIST_SECONDARY].elements.ptr(), render_list[RENDER_LIST_SECONDARY].elements.size(), false, base_spec_constant, true, Vector2(0, 0)); + + const int uv_offset_count = 9; + static const Vector2 uv_offsets[uv_offset_count] = { + Vector2(-1, 1), + Vector2(1, 1), + Vector2(1, -1), + Vector2(-1, -1), + Vector2(-1, 0), + Vector2(1, 0), + Vector2(0, -1), + Vector2(0, 1), + Vector2(0, 0), + }; + + for (int i = 0; i < uv_offset_count; i++) { + Vector2 ofs = uv_offsets[i]; + ofs.x /= p_region.size.width; + ofs.y /= p_region.size.height; + render_list_params.uv_offset = ofs; + _render_list_template<PASS_MODE_MATERIAL>(&render_list_params, &render_data, 0, render_list[RENDER_LIST_SECONDARY].elements.size()); + } + + render_list_params.uv_offset = Vector2(0, 0); + render_list_params.force_wireframe = false; + _render_list_template<PASS_MODE_MATERIAL>(&render_list_params, &render_data, 0, render_list[RENDER_LIST_SECONDARY].elements.size()); + + GLuint db = GL_COLOR_ATTACHMENT0; + glDrawBuffers(1, &db); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + } +} + void RasterizerSceneGLES3::set_time(double p_time, double p_step) { time = p_time; time_step = p_step; @@ -3505,7 +3726,155 @@ void RasterizerSceneGLES3::sub_surface_scattering_set_scale(float p_scale, float } TypedArray<Image> RasterizerSceneGLES3::bake_render_uv2(RID p_base, const TypedArray<RID> &p_material_overrides, const Size2i &p_image_size) { - return TypedArray<Image>(); + GLES3::Config *config = GLES3::Config::get_singleton(); + ERR_FAIL_COND_V_MSG(p_image_size.width <= 0, TypedArray<Image>(), "Image width must be greater than 0."); + ERR_FAIL_COND_V_MSG(p_image_size.height <= 0, TypedArray<Image>(), "Image height must be greater than 0."); + + GLuint albedo_alpha_tex = 0; + GLuint normal_tex = 0; + GLuint orm_tex = 0; + GLuint emission_tex = 0; + GLuint depth_tex = 0; + glGenTextures(1, &albedo_alpha_tex); + glGenTextures(1, &normal_tex); + glGenTextures(1, &orm_tex); + glGenTextures(1, &emission_tex); + glGenTextures(1, &depth_tex); + + glBindTexture(GL_TEXTURE_2D, albedo_alpha_tex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, p_image_size.width, p_image_size.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + GLES3::Utilities::get_singleton()->texture_allocated_data(albedo_alpha_tex, p_image_size.width * p_image_size.height * 4, "Lightmap albedo texture"); + + glBindTexture(GL_TEXTURE_2D, normal_tex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, p_image_size.width, p_image_size.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + GLES3::Utilities::get_singleton()->texture_allocated_data(normal_tex, p_image_size.width * p_image_size.height * 4, "Lightmap normal texture"); + + glBindTexture(GL_TEXTURE_2D, orm_tex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, p_image_size.width, p_image_size.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + GLES3::Utilities::get_singleton()->texture_allocated_data(orm_tex, p_image_size.width * p_image_size.height * 4, "Lightmap ORM texture"); + + // Consider rendering to RGBA8 encoded as RGBE, then manually convert to RGBAH on CPU. + glBindTexture(GL_TEXTURE_2D, emission_tex); + if (config->float_texture_supported) { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, p_image_size.width, p_image_size.height, 0, GL_RGBA, GL_FLOAT, nullptr); + GLES3::Utilities::get_singleton()->texture_allocated_data(emission_tex, p_image_size.width * p_image_size.height * 16, "Lightmap emission texture"); + } else { + // Fallback to RGBA8 on devices that don't support rendering to floating point textures. This will look bad, but we have no choice. + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, p_image_size.width, p_image_size.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + GLES3::Utilities::get_singleton()->texture_allocated_data(emission_tex, p_image_size.width * p_image_size.height * 4, "Lightmap emission texture"); + } + + glBindTexture(GL_TEXTURE_2D, depth_tex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, p_image_size.width, p_image_size.height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, nullptr); + GLES3::Utilities::get_singleton()->texture_allocated_data(depth_tex, p_image_size.width * p_image_size.height * 3, "Lightmap depth texture"); + + GLuint fbo = 0; + glGenFramebuffers(1, &fbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, albedo_alpha_tex, 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, normal_tex, 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D, orm_tex, 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT3, GL_TEXTURE_2D, emission_tex, 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depth_tex, 0); + + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) { + glDeleteFramebuffers(1, &fbo); + GLES3::Utilities::get_singleton()->texture_free_data(albedo_alpha_tex); + GLES3::Utilities::get_singleton()->texture_free_data(normal_tex); + GLES3::Utilities::get_singleton()->texture_free_data(orm_tex); + GLES3::Utilities::get_singleton()->texture_free_data(emission_tex); + GLES3::Utilities::get_singleton()->texture_free_data(depth_tex); + + WARN_PRINT("Could not create render target, status: " + GLES3::TextureStorage::get_singleton()->get_framebuffer_error(status)); + return TypedArray<Image>(); + } + + RenderGeometryInstance *gi_inst = geometry_instance_create(p_base); + ERR_FAIL_NULL_V(gi_inst, TypedArray<Image>()); + + uint32_t sc = RSG::mesh_storage->mesh_get_surface_count(p_base); + Vector<RID> materials; + materials.resize(sc); + + for (uint32_t i = 0; i < sc; i++) { + if (i < (uint32_t)p_material_overrides.size()) { + materials.write[i] = p_material_overrides[i]; + } + } + + gi_inst->set_surface_materials(materials); + + if (cull_argument.size() == 0) { + cull_argument.push_back(nullptr); + } + cull_argument[0] = gi_inst; + _render_uv2(cull_argument, fbo, Rect2i(0, 0, p_image_size.width, p_image_size.height)); + + geometry_instance_free(gi_inst); + + TypedArray<Image> ret; + + // Create a dummy texture so we can use texture_2d_get. + RID tex_rid = GLES3::TextureStorage::get_singleton()->texture_allocate(); + GLES3::Texture texture; + texture.width = p_image_size.width; + texture.height = p_image_size.height; + texture.alloc_width = p_image_size.width; + texture.alloc_height = p_image_size.height; + texture.format = Image::FORMAT_RGBA8; + texture.real_format = Image::FORMAT_RGBA8; + texture.gl_format_cache = GL_RGBA; + texture.gl_type_cache = GL_UNSIGNED_BYTE; + texture.type = GLES3::Texture::TYPE_2D; + texture.target = GL_TEXTURE_2D; + texture.active = true; + texture.is_render_target = true; // Enable this so the texture isn't cached in the editor. + + GLES3::TextureStorage::get_singleton()->texture_2d_initialize_from_texture(tex_rid, texture); + GLES3::Texture *tex = GLES3::TextureStorage::get_singleton()->get_texture(tex_rid); + + { + tex->tex_id = albedo_alpha_tex; + Ref<Image> img = GLES3::TextureStorage::get_singleton()->texture_2d_get(tex_rid); + GLES3::Utilities::get_singleton()->texture_free_data(albedo_alpha_tex); + ret.push_back(img); + } + + { + tex->tex_id = normal_tex; + Ref<Image> img = GLES3::TextureStorage::get_singleton()->texture_2d_get(tex_rid); + GLES3::Utilities::get_singleton()->texture_free_data(normal_tex); + ret.push_back(img); + } + + { + tex->tex_id = orm_tex; + Ref<Image> img = GLES3::TextureStorage::get_singleton()->texture_2d_get(tex_rid); + GLES3::Utilities::get_singleton()->texture_free_data(orm_tex); + ret.push_back(img); + } + + { + tex->tex_id = emission_tex; + if (config->float_texture_supported) { + tex->format = Image::FORMAT_RGBAF; + tex->real_format = Image::FORMAT_RGBAH; + tex->gl_type_cache = GL_FLOAT; + } + Ref<Image> img = GLES3::TextureStorage::get_singleton()->texture_2d_get(tex_rid); + GLES3::Utilities::get_singleton()->texture_free_data(emission_tex); + ret.push_back(img); + } + + tex->is_render_target = false; + tex->tex_id = 0; + GLES3::TextureStorage::get_singleton()->texture_free(tex_rid); + + GLES3::Utilities::get_singleton()->texture_free_data(depth_tex); + glDeleteFramebuffers(1, &fbo); + return ret; } bool RasterizerSceneGLES3::free(RID p_rid) { @@ -3546,6 +3915,8 @@ RasterizerSceneGLES3::RasterizerSceneGLES3() { GLES3::MaterialStorage *material_storage = GLES3::MaterialStorage::get_singleton(); GLES3::Config *config = GLES3::Config::get_singleton(); + cull_argument.set_page_pool(&cull_argument_pool); + // Quality settings. use_physical_light_units = GLOBAL_GET("rendering/lights_and_shadows/use_physical_light_units"); @@ -3638,6 +4009,29 @@ void fragment() { scene_globals.default_material = material_storage->material_allocate(); material_storage->material_initialize(scene_globals.default_material); material_storage->material_set_shader(scene_globals.default_material, scene_globals.default_shader); + default_material_data_ptr = static_cast<GLES3::SceneMaterialData *>(GLES3::MaterialStorage::get_singleton()->material_get_data(scene_globals.default_material, RS::SHADER_SPATIAL)); + } + + { + // Overdraw material and shader. + scene_globals.overdraw_shader = material_storage->shader_allocate(); + material_storage->shader_initialize(scene_globals.overdraw_shader); + material_storage->shader_set_code(scene_globals.overdraw_shader, R"( +// 3D editor Overdraw debug draw mode shader. + +shader_type spatial; + +render_mode blend_add, unshaded; + +void fragment() { + ALBEDO = vec3(0.4, 0.8, 0.8); + ALPHA = 0.2; +} +)"); + scene_globals.overdraw_material = material_storage->material_allocate(); + material_storage->material_initialize(scene_globals.overdraw_material); + material_storage->material_set_shader(scene_globals.overdraw_material, scene_globals.overdraw_shader); + overdraw_material_data_ptr = static_cast<GLES3::SceneMaterialData *>(GLES3::MaterialStorage::get_singleton()->material_get_data(scene_globals.overdraw_material, RS::SHADER_SPATIAL)); } { @@ -3752,6 +4146,10 @@ RasterizerSceneGLES3::~RasterizerSceneGLES3() { RSG::material_storage->material_free(scene_globals.default_material); RSG::material_storage->shader_free(scene_globals.default_shader); + // Overdraw Shader + RSG::material_storage->material_free(scene_globals.overdraw_material); + RSG::material_storage->shader_free(scene_globals.overdraw_shader); + // Sky Shader GLES3::MaterialStorage::get_singleton()->shaders.sky_shader.version_free(sky_globals.shader_default_version); RSG::material_storage->material_free(sky_globals.default_material); diff --git a/drivers/gles3/rasterizer_scene_gles3.h b/drivers/gles3/rasterizer_scene_gles3.h index 045511321a..efe614f692 100644 --- a/drivers/gles3/rasterizer_scene_gles3.h +++ b/drivers/gles3/rasterizer_scene_gles3.h @@ -61,6 +61,7 @@ enum PassMode { PASS_MODE_COLOR_TRANSPARENT, PASS_MODE_SHADOW, PASS_MODE_DEPTH, + PASS_MODE_MATERIAL, }; // These should share as much as possible with SkyUniform Location @@ -152,8 +153,13 @@ private: RID default_material; RID default_shader; RID cubemap_filter_shader_version; + RID overdraw_material; + RID overdraw_shader; } scene_globals; + GLES3::SceneMaterialData *default_material_data_ptr = nullptr; + GLES3::SceneMaterialData *overdraw_material_data_ptr = nullptr; + /* LIGHT INSTANCE */ struct LightData { @@ -370,7 +376,7 @@ private: float ambient_light_color_energy[4]; float ambient_color_sky_mix; - uint32_t material_uv2_mode; + uint32_t pad2; float emissive_exposure_normalization; uint32_t use_ambient_light = 0; @@ -460,13 +466,15 @@ private: bool reverse_cull = false; uint64_t spec_constant_base_flags = 0; bool force_wireframe = false; + Vector2 uv_offset = Vector2(0, 0); - RenderListParameters(GeometryInstanceSurface **p_elements, int p_element_count, bool p_reverse_cull, uint64_t p_spec_constant_base_flags, bool p_force_wireframe = false) { + RenderListParameters(GeometryInstanceSurface **p_elements, int p_element_count, bool p_reverse_cull, uint64_t p_spec_constant_base_flags, bool p_force_wireframe = false, Vector2 p_uv_offset = Vector2()) { elements = p_elements; element_count = p_element_count; reverse_cull = p_reverse_cull; spec_constant_base_flags = p_spec_constant_base_flags; force_wireframe = p_force_wireframe; + uv_offset = p_uv_offset; } }; @@ -642,6 +650,10 @@ protected: void _draw_sky(RID p_env, const Projection &p_projection, const Transform3D &p_transform, float p_luminance_multiplier, bool p_use_multiview, bool p_flip_y); void _free_sky_data(Sky *p_sky); + // Needed for a single argument calls (material and uv2). + PagedArrayPool<RenderGeometryInstance *> cull_argument_pool; + PagedArray<RenderGeometryInstance *> cull_argument; + public: static RasterizerSceneGLES3 *get_singleton() { return singleton; } @@ -742,6 +754,7 @@ public: void sub_surface_scattering_set_scale(float p_scale, float p_depth_scale) override; TypedArray<Image> bake_render_uv2(RID p_base, const TypedArray<RID> &p_material_overrides, const Size2i &p_image_size) override; + void _render_uv2(const PagedArray<RenderGeometryInstance *> &p_instances, GLuint p_framebuffer, const Rect2i &p_region); bool free(RID p_rid) override; void update() override; diff --git a/drivers/gles3/shaders/copy.glsl b/drivers/gles3/shaders/copy.glsl index f37968a4fd..b5ab15309c 100644 --- a/drivers/gles3/shaders/copy.glsl +++ b/drivers/gles3/shaders/copy.glsl @@ -8,6 +8,7 @@ mode_gaussian_blur = #define MODE_GAUSSIAN_BLUR mode_mipmap = #define MODE_MIPMAP mode_simple_color = #define MODE_SIMPLE_COLOR \n#define USE_COPY_SECTION mode_cube_to_octahedral = #define CUBE_TO_OCTAHEDRAL \n#define USE_COPY_SECTION +mode_cube_to_panorama = #define CUBE_TO_PANORAMA #[specializations] @@ -53,21 +54,34 @@ uniform highp vec2 pixel_size; #endif #ifdef CUBE_TO_OCTAHEDRAL -uniform samplerCube source_cube; // texunit:0 - vec3 oct_to_vec3(vec2 e) { vec3 v = vec3(e.xy, 1.0 - abs(e.x) - abs(e.y)); float t = max(-v.z, 0.0); v.xy += t * -sign(v.xy); return normalize(v); } -#else -uniform sampler2D source; // texunit:0 +#endif +#ifdef CUBE_TO_PANORAMA +uniform lowp float mip_level; #endif +#if defined(CUBE_TO_OCTAHEDRAL) || defined(CUBE_TO_PANORAMA) +uniform samplerCube source_cube; // texunit:0 + +#else // ~(defined(CUBE_TO_OCTAHEDRAL) || defined(CUBE_TO_PANORAMA)) +uniform sampler2D source; // texunit:0 + +#endif // !(defined(CUBE_TO_OCTAHEDRAL) || defined(CUBE_TO_PANORAMA)) + layout(location = 0) out vec4 frag_color; +// This expects 0-1 range input, outside that range it behaves poorly. +vec3 srgb_to_linear(vec3 color) { + // Approximation from http://chilliant.blogspot.com/2012/08/srgb-approximations-for-hlsl.html + return color * (color * (color * 0.305306011 + 0.682171111) + 0.012522878); +} + void main() { #ifdef MODE_SIMPLE_COPY vec4 color = texture(source, uv_interp); @@ -111,4 +125,21 @@ void main() { frag_color = texture(source_cube, dir); #endif + +#ifdef CUBE_TO_PANORAMA + + const float PI = 3.14159265359; + + float phi = uv_interp.x * 2.0 * PI; + float theta = uv_interp.y * PI; + + vec3 normal; + normal.x = sin(phi) * sin(theta) * -1.0; + normal.y = cos(theta); + normal.z = cos(phi) * sin(theta) * -1.0; + + vec3 color = srgb_to_linear(textureLod(source_cube, normal, mip_level).rgb); + frag_color = vec4(color, 1.0); + +#endif } diff --git a/drivers/gles3/shaders/scene.glsl b/drivers/gles3/shaders/scene.glsl index e95d684763..9e2c8a4452 100644 --- a/drivers/gles3/shaders/scene.glsl +++ b/drivers/gles3/shaders/scene.glsl @@ -31,6 +31,7 @@ USE_ADDITIVE_LIGHTING = false // these are false, we are doing a directional light pass. ADDITIVE_OMNI = false ADDITIVE_SPOT = false +RENDER_MATERIAL = false #[vertex] @@ -90,7 +91,7 @@ layout(location = 3) in vec4 color_attrib; layout(location = 4) in vec2 uv_attrib; #endif -#if defined(UV2_USED) || defined(USE_LIGHTMAP) +#if defined(UV2_USED) || defined(USE_LIGHTMAP) || defined(RENDER_MATERIAL) layout(location = 5) in vec2 uv2_attrib; #endif @@ -160,12 +161,12 @@ layout(std140) uniform SceneData { // ubo:2 mediump vec4 ambient_light_color_energy; mediump float ambient_color_sky_mix; - bool material_uv2_mode; + float pad2; float emissive_exposure_normalization; bool use_ambient_light; + bool use_ambient_cubemap; bool use_reflection_cubemap; - float fog_aerial_perspective; float time; @@ -249,6 +250,10 @@ uniform highp vec4 uv_scale; uniform highp uint model_flags; +#ifdef RENDER_MATERIAL +uniform mediump vec2 uv_offset; +#endif + /* Varyings */ out highp vec3 vertex_interp; @@ -511,6 +516,12 @@ void main() { #else gl_Position = projection_matrix * vec4(vertex_interp, 1.0); #endif + +#ifdef RENDER_MATERIAL + gl_Position.xy = (uv2_attrib.xy + uv_offset) * 2.0 - 1.0; + gl_Position.z = 0.00001; + gl_Position.w = 1.0; +#endif } /* clang-format off */ @@ -632,12 +643,12 @@ layout(std140) uniform SceneData { // ubo:2 mediump vec4 ambient_light_color_energy; mediump float ambient_color_sky_mix; - bool material_uv2_mode; + float pad2; float emissive_exposure_normalization; bool use_ambient_light; + bool use_ambient_cubemap; bool use_reflection_cubemap; - float fog_aerial_perspective; float time; @@ -867,19 +878,37 @@ uniform highp sampler2DArray color_buffer; // texunit:-5 vec3 multiview_uv(vec2 uv) { return vec3(uv, ViewIndex); } +ivec3 multiview_uv(ivec2 uv) { + return ivec3(uv, int(ViewIndex)); +} #else uniform highp sampler2D depth_buffer; // texunit:-6 uniform highp sampler2D color_buffer; // texunit:-5 vec2 multiview_uv(vec2 uv) { return uv; } +ivec2 multiview_uv(ivec2 uv) { + return uv; +} #endif uniform highp mat4 world_transform; uniform mediump float opaque_prepass_threshold; +#ifndef MODE_RENDER_DEPTH +#ifdef RENDER_MATERIAL +layout(location = 0) out vec4 albedo_output_buffer; +layout(location = 1) out vec4 normal_output_buffer; +layout(location = 2) out vec4 orm_output_buffer; +layout(location = 3) out vec4 emission_output_buffer; + +#else // !RENDER_MATERIAL +// Normal color rendering. layout(location = 0) out vec4 frag_color; +#endif // !RENDER_MATERIAL +#endif // !MODE_RENDER_DEPTH + vec3 F0(float metallic, float specular, vec3 albedo) { float dielectric = 0.16 * specular * specular; // use albedo * metallic as colored specular reflectance at 0 angle for metallic materials; @@ -936,7 +965,7 @@ void light_compute(vec3 N, vec3 L, vec3 V, float A, vec3 light_color, bool is_di #endif inout vec3 diffuse_light, inout vec3 specular_light) { -#if defined(USE_LIGHT_SHADER_CODE) +#if defined(LIGHT_CODE_USED) // light is written by the light shader highp mat4 model_matrix = world_transform; @@ -1072,7 +1101,7 @@ void light_compute(vec3 N, vec3 L, vec3 V, float A, vec3 light_color, bool is_di alpha = min(alpha, clamp(1.0 - attenuation, 0.0, 1.0)); #endif -#endif // USE_LIGHT_SHADER_CODE +#endif // LIGHT_CODE_USED } float get_omni_spot_attenuation(float distance, float inv_range, float decay) { @@ -1660,6 +1689,23 @@ void main() { // Nothing happens, so a tree-ssa optimizer will result in no fragment shader :) #else // !MODE_RENDER_DEPTH + +#ifdef RENDER_MATERIAL + + albedo_output_buffer.rgb = albedo; + albedo_output_buffer.a = alpha; + + normal_output_buffer.rgb = normal * 0.5 + 0.5; + normal_output_buffer.a = 0.0; + + orm_output_buffer.r = ao; + orm_output_buffer.g = roughness; + orm_output_buffer.b = metallic; + orm_output_buffer.a = 1.0; + + emission_output_buffer.rgb = emission; + emission_output_buffer.a = 0.0; +#else // !RENDER_MATERIAL #ifdef BASE_PASS #ifdef MODE_UNSHADED frag_color = vec4(albedo, alpha); @@ -1914,6 +1960,6 @@ void main() { frag_color.rgb += additive_light_color; #endif // USE_ADDITIVE_LIGHTING - +#endif // !RENDER_MATERIAL #endif //!MODE_RENDER_DEPTH } diff --git a/drivers/gles3/storage/material_storage.cpp b/drivers/gles3/storage/material_storage.cpp index 13ab05c0a0..852327cf30 100644 --- a/drivers/gles3/storage/material_storage.cpp +++ b/drivers/gles3/storage/material_storage.cpp @@ -1178,7 +1178,6 @@ MaterialStorage::MaterialStorage() { actions.usage_defines["SCREEN_PIXEL_SIZE"] = "@SCREEN_UV"; actions.usage_defines["NORMAL"] = "#define NORMAL_USED\n"; actions.usage_defines["NORMAL_MAP"] = "#define NORMAL_MAP_USED\n"; - actions.usage_defines["LIGHT"] = "#define LIGHT_SHADER_CODE_USED\n"; actions.usage_defines["SPECULAR_SHININESS"] = "#define SPECULAR_SHININESS_USED\n"; actions.render_mode_defines["skip_vertex_transform"] = "#define SKIP_TRANSFORM_USED\n"; @@ -1326,9 +1325,6 @@ MaterialStorage::MaterialStorage() { actions.usage_defines["BACKLIGHT"] = "#define LIGHT_BACKLIGHT_USED\n"; actions.usage_defines["SCREEN_UV"] = "#define SCREEN_UV_USED\n"; - actions.usage_defines["DIFFUSE_LIGHT"] = "#define USE_LIGHT_SHADER_CODE\n"; - actions.usage_defines["SPECULAR_LIGHT"] = "#define USE_LIGHT_SHADER_CODE\n"; - actions.usage_defines["FOG"] = "#define CUSTOM_FOG_USED\n"; actions.usage_defines["RADIANCE"] = "#define CUSTOM_RADIANCE_USED\n"; actions.usage_defines["IRRADIANCE"] = "#define CUSTOM_IRRADIANCE_USED\n"; diff --git a/drivers/gles3/storage/mesh_storage.cpp b/drivers/gles3/storage/mesh_storage.cpp index b4e266d976..475f3c33b8 100644 --- a/drivers/gles3/storage/mesh_storage.cpp +++ b/drivers/gles3/storage/mesh_storage.cpp @@ -727,6 +727,20 @@ AABB MeshStorage::mesh_get_aabb(RID p_mesh, RID p_skeleton) { return aabb; } +void MeshStorage::mesh_set_path(RID p_mesh, const String &p_path) { + Mesh *mesh = mesh_owner.get_or_null(p_mesh); + ERR_FAIL_NULL(mesh); + + mesh->path = p_path; +} + +String MeshStorage::mesh_get_path(RID p_mesh) const { + Mesh *mesh = mesh_owner.get_or_null(p_mesh); + ERR_FAIL_NULL_V(mesh, String()); + + return mesh->path; +} + void MeshStorage::mesh_set_shadow_mesh(RID p_mesh, RID p_shadow_mesh) { Mesh *mesh = mesh_owner.get_or_null(p_mesh); ERR_FAIL_NULL(mesh); diff --git a/drivers/gles3/storage/mesh_storage.h b/drivers/gles3/storage/mesh_storage.h index 217c4dabf0..cea81baa0b 100644 --- a/drivers/gles3/storage/mesh_storage.h +++ b/drivers/gles3/storage/mesh_storage.h @@ -142,6 +142,8 @@ struct Mesh { RID shadow_mesh; HashSet<Mesh *> shadow_owners; + String path; + Dependency dependency; }; @@ -304,8 +306,11 @@ public: virtual void mesh_set_custom_aabb(RID p_mesh, const AABB &p_aabb) override; virtual AABB mesh_get_custom_aabb(RID p_mesh) const override; - virtual AABB mesh_get_aabb(RID p_mesh, RID p_skeleton = RID()) override; + + virtual void mesh_set_path(RID p_mesh, const String &p_path) override; + virtual String mesh_get_path(RID p_mesh) const override; + virtual void mesh_set_shadow_mesh(RID p_mesh, RID p_shadow_mesh) override; virtual void mesh_clear(RID p_mesh) override; diff --git a/drivers/gles3/storage/texture_storage.h b/drivers/gles3/storage/texture_storage.h index a4e5eb260e..c0c8119dfe 100644 --- a/drivers/gles3/storage/texture_storage.h +++ b/drivers/gles3/storage/texture_storage.h @@ -494,6 +494,10 @@ public: }; bool owns_texture(RID p_rid) { return texture_owner.owns(p_rid); }; + void texture_2d_initialize_from_texture(RID p_texture, Texture &p_tex) { + texture_owner.initialize_rid(p_texture, p_tex); + } + virtual bool can_create_resources_async() const override; virtual RID texture_allocate() override; diff --git a/drivers/gles3/storage/utilities.h b/drivers/gles3/storage/utilities.h index ea7bf4a4c2..b9603b972e 100644 --- a/drivers/gles3/storage/utilities.h +++ b/drivers/gles3/storage/utilities.h @@ -111,6 +111,7 @@ public: } // Records that data was allocated for state tracking purposes. + // Size is measured in bytes. _FORCE_INLINE_ void texture_allocated_data(GLuint p_id, uint32_t p_size, String p_name = "") { texture_mem_cache += p_size; #ifdef DEV_ENABLED diff --git a/drivers/vulkan/rendering_device_driver_vulkan.cpp b/drivers/vulkan/rendering_device_driver_vulkan.cpp index 8378e26666..45137407a2 100644 --- a/drivers/vulkan/rendering_device_driver_vulkan.cpp +++ b/drivers/vulkan/rendering_device_driver_vulkan.cpp @@ -35,6 +35,8 @@ #include "thirdparty/misc/smolv.h" #include "vulkan_context.h" +#define PRINT_NATIVE_COMMANDS 0 + /*****************/ /**** GENERIC ****/ /*****************/ @@ -622,6 +624,10 @@ RDD::TextureID RenderingDeviceDriverVulkan::texture_create(const TextureFormat & tex_info->allocation.handle = allocation; vmaGetAllocationInfo(allocator, tex_info->allocation.handle, &tex_info->allocation.info); +#if PRINT_NATIVE_COMMANDS + print_line(vformat("vkCreateImageView: 0x%uX for 0x%uX", uint64_t(vk_image_view), uint64_t(vk_image))); +#endif + return TextureID(tex_info); } @@ -710,6 +716,10 @@ RDD::TextureID RenderingDeviceDriverVulkan::texture_create_shared(TextureID p_or tex_info->vk_view_create_info = image_view_create_info; tex_info->allocation = {}; +#if PRINT_NATIVE_COMMANDS + print_line(vformat("vkCreateImageView: 0x%uX for 0x%uX", uint64_t(new_vk_image_view), uint64_t(owner_tex_info->vk_view_create_info.image))); +#endif + return TextureID(tex_info); } @@ -759,6 +769,10 @@ RDD::TextureID RenderingDeviceDriverVulkan::texture_create_shared_from_slice(Tex tex_info->vk_view_create_info = image_view_create_info; tex_info->allocation = {}; +#if PRINT_NATIVE_COMMANDS + print_line(vformat("vkCreateImageView: 0x%uX for 0x%uX (%d %d %d %d)", uint64_t(new_vk_image_view), uint64_t(owner_tex_info->vk_view_create_info.image), p_mipmap, p_mipmaps, p_layer, p_layers)); +#endif + return TextureID(tex_info); } @@ -1071,6 +1085,23 @@ void RenderingDeviceDriverVulkan::command_pipeline_barrier( vk_image_barriers[i].subresourceRange.layerCount = p_texture_barriers[i].subresources.layer_count; } +#if PRINT_NATIVE_COMMANDS + print_line(vformat("vkCmdPipelineBarrier MEMORY %d BUFFER %d TEXTURE %d", p_memory_barriers.size(), p_buffer_barriers.size(), p_texture_barriers.size())); + for (uint32_t i = 0; i < p_memory_barriers.size(); i++) { + print_line(vformat(" VkMemoryBarrier #%d src 0x%uX dst 0x%uX", i, vk_memory_barriers[i].srcAccessMask, vk_memory_barriers[i].dstAccessMask)); + } + + for (uint32_t i = 0; i < p_buffer_barriers.size(); i++) { + print_line(vformat(" VkBufferMemoryBarrier #%d src 0x%uX dst 0x%uX buffer 0x%ux", i, vk_buffer_barriers[i].srcAccessMask, vk_buffer_barriers[i].dstAccessMask, uint64_t(vk_buffer_barriers[i].buffer))); + } + + for (uint32_t i = 0; i < p_texture_barriers.size(); i++) { + print_line(vformat(" VkImageMemoryBarrier #%d src 0x%uX dst 0x%uX image 0x%ux old %d new %d (%d %d %d %d)", i, vk_image_barriers[i].srcAccessMask, vk_image_barriers[i].dstAccessMask, + uint64_t(vk_image_barriers[i].image), vk_image_barriers[i].oldLayout, vk_image_barriers[i].newLayout, vk_image_barriers[i].subresourceRange.baseMipLevel, vk_image_barriers[i].subresourceRange.levelCount, + vk_image_barriers[i].subresourceRange.baseArrayLayer, vk_image_barriers[i].subresourceRange.layerCount)); + } +#endif + vkCmdPipelineBarrier( (VkCommandBuffer)p_cmd_buffer.id, (VkPipelineStageFlags)p_src_stages, @@ -1225,6 +1256,14 @@ RDD::FramebufferID RenderingDeviceDriverVulkan::framebuffer_create(RenderPassID VkResult err = vkCreateFramebuffer(vk_device, &framebuffer_create_info, nullptr, &vk_framebuffer); ERR_FAIL_COND_V_MSG(err, FramebufferID(), "vkCreateFramebuffer failed with error " + itos(err) + "."); +#if PRINT_NATIVE_COMMANDS + print_line(vformat("vkCreateFramebuffer 0x%uX with %d attachments", uint64_t(vk_framebuffer), p_attachments.size())); + for (uint32_t i = 0; i < p_attachments.size(); i++) { + const TextureInfo *attachment_info = (const TextureInfo *)p_attachments[i].id; + print_line(vformat(" Attachment #%d: IMAGE 0x%uX VIEW 0x%uX", i, uint64_t(attachment_info->vk_view_create_info.image), uint64_t(attachment_info->vk_view))); + } +#endif + return FramebufferID(vk_framebuffer); } @@ -2467,10 +2506,18 @@ void RenderingDeviceDriverVulkan::command_begin_render_pass(CommandBufferID p_cm VkSubpassContents vk_subpass_contents = p_cmd_buffer_type == COMMAND_BUFFER_TYPE_PRIMARY ? VK_SUBPASS_CONTENTS_INLINE : VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS; vkCmdBeginRenderPass((VkCommandBuffer)p_cmd_buffer.id, &render_pass_begin, vk_subpass_contents); + +#if PRINT_NATIVE_COMMANDS + print_line(vformat("vkCmdBeginRenderPass Pass 0x%uX Framebuffer 0x%uX", p_render_pass.id, p_framebuffer.id)); +#endif } void RenderingDeviceDriverVulkan::command_end_render_pass(CommandBufferID p_cmd_buffer) { vkCmdEndRenderPass((VkCommandBuffer)p_cmd_buffer.id); + +#if PRINT_NATIVE_COMMANDS + print_line("vkCmdEndRenderPass"); +#endif } void RenderingDeviceDriverVulkan::command_next_render_subpass(CommandBufferID p_cmd_buffer, CommandBufferType p_cmd_buffer_type) { diff --git a/editor/doc_tools.cpp b/editor/doc_tools.cpp index 44f6444a31..952c093eb6 100644 --- a/editor/doc_tools.cpp +++ b/editor/doc_tools.cpp @@ -1652,3 +1652,15 @@ Error DocTools::load_compressed(const uint8_t *p_data, int p_compressed_size, in return OK; } + +Error DocTools::load_xml(const uint8_t *p_data, int p_size) { + Ref<XMLParser> parser = memnew(XMLParser); + Error err = parser->_open_buffer(p_data, p_size); + if (err) { + return err; + } + + _load(parser); + + return OK; +} diff --git a/editor/doc_tools.h b/editor/doc_tools.h index 7f29cc238a..a6910baf28 100644 --- a/editor/doc_tools.h +++ b/editor/doc_tools.h @@ -56,6 +56,7 @@ public: Error _load(Ref<XMLParser> parser); Error load_compressed(const uint8_t *p_data, int p_compressed_size, int p_uncompressed_size); + Error load_xml(const uint8_t *p_data, int p_size); }; #endif // DOC_TOOLS_H diff --git a/editor/editor_dock_manager.cpp b/editor/editor_dock_manager.cpp index dfe9504706..54789bdef1 100644 --- a/editor/editor_dock_manager.cpp +++ b/editor/editor_dock_manager.cpp @@ -702,15 +702,18 @@ EditorDockManager::EditorDockManager() { dock_select->set_v_size_flags(Control::SIZE_EXPAND_FILL); dock_vb->add_child(dock_select); - if (!SceneTree::get_singleton()->get_root()->is_embedding_subwindows() && !EDITOR_GET("interface/editor/single_window_mode") && EDITOR_GET("interface/multi_window/enable")) { - dock_float = memnew(Button); - dock_float->set_text(TTR("Make Floating")); - dock_float->set_focus_mode(Control::FOCUS_NONE); - dock_float->set_h_size_flags(Control::SIZE_SHRINK_CENTER); - dock_float->connect("pressed", callable_mp(this, &EditorDockManager::_dock_make_selected_float)); - - dock_vb->add_child(dock_float); + dock_float = memnew(Button); + dock_float->set_text(TTR("Make Floating")); + if (!EditorNode::get_singleton()->is_multi_window_enabled()) { + dock_float->set_disabled(true); + dock_float->set_tooltip_text(EditorNode::get_singleton()->get_multiwindow_support_tooltip_text()); + } else { + dock_float->set_tooltip_text(TTR("Make this dock floating.")); } + dock_float->set_focus_mode(Control::FOCUS_NONE); + dock_float->set_h_size_flags(Control::SIZE_SHRINK_CENTER); + dock_float->connect("pressed", callable_mp(this, &EditorDockManager::_dock_make_selected_float)); + dock_vb->add_child(dock_float); dock_select_popup->reset_size(); } diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index f0f7f87711..dd0bbccbee 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -31,6 +31,7 @@ #include "editor_help.h" #include "core/core_constants.h" +#include "core/extension/gdextension.h" #include "core/input/input.h" #include "core/object/script_language.h" #include "core/os/keyboard.h" @@ -83,6 +84,7 @@ const Vector<String> classes_with_csharp_differences = { // TODO: this is sometimes used directly as doc->something, other times as EditorHelp::get_doc_data(), which is thread-safe. // Might this be a problem? DocTools *EditorHelp::doc = nullptr; +DocTools *EditorHelp::ext_doc = nullptr; static bool _attempt_doc_load(const String &p_class) { // Docgen always happens in the outer-most class: it also generates docs for inner classes. @@ -2369,6 +2371,28 @@ String EditorHelp::get_cache_full_path() { return EditorPaths::get_singleton()->get_cache_dir().path_join("editor_doc_cache.res"); } +void EditorHelp::load_xml_buffer(const uint8_t *p_buffer, int p_size) { + if (!ext_doc) { + ext_doc = memnew(DocTools); + } + + ext_doc->load_xml(p_buffer, p_size); + + if (doc) { + doc->load_xml(p_buffer, p_size); + } +} + +void EditorHelp::remove_class(const String &p_class) { + if (ext_doc && ext_doc->has_doc(p_class)) { + ext_doc->remove_doc(p_class); + } + + if (doc && doc->has_doc(p_class)) { + doc->remove_doc(p_class); + } +} + void EditorHelp::_load_doc_thread(void *p_udata) { Ref<Resource> cache_res = ResourceLoader::load(get_cache_full_path()); if (cache_res.is_valid() && cache_res->get_meta("version_hash", "") == doc_version_hash) { @@ -2416,6 +2440,11 @@ void EditorHelp::_gen_doc_thread(void *p_udata) { void EditorHelp::_gen_extensions_docs() { doc->generate((DocTools::GENERATE_FLAG_SKIP_BASIC_TYPES | DocTools::GENERATE_FLAG_EXTENSION_CLASSES_ONLY)); + + // Append extra doc data, as it gets overridden by the generation step. + if (ext_doc) { + doc->merge_from(*ext_doc); + } } void EditorHelp::generate_doc(bool p_use_cache) { @@ -2554,6 +2583,11 @@ void EditorHelp::_bind_methods() { ADD_SIGNAL(MethodInfo("go_to_help")); } +void EditorHelp::init_gdext_pointers() { + GDExtensionEditorHelp::editor_help_load_xml_buffer = &EditorHelp::load_xml_buffer; + GDExtensionEditorHelp::editor_help_remove_class = &EditorHelp::remove_class; +} + EditorHelp::EditorHelp() { set_custom_minimum_size(Size2(150 * EDSCALE, 0)); @@ -2862,9 +2896,17 @@ void EditorHelpTooltip::parse_tooltip(const String &p_text) { const String &property_name = slices[2]; const String &property_args = slices[3]; + String formatted_text; + + // Exclude internal properties, they are not documented. + if (type == "internal_property") { + formatted_text = "[i]" + TTR("This property can only be set in the Inspector.") + "[/i]"; + set_text(formatted_text); + return; + } + String title; String description; - String formatted_text; if (type == "class") { title = class_name; diff --git a/editor/editor_help.h b/editor/editor_help.h index ff440a679a..896f0adf43 100644 --- a/editor/editor_help.h +++ b/editor/editor_help.h @@ -113,6 +113,7 @@ class EditorHelp : public VBoxContainer { RichTextLabel *class_desc = nullptr; HSplitContainer *h_split = nullptr; static DocTools *doc; + static DocTools *ext_doc; ConfirmationDialog *search_dialog = nullptr; LineEdit *search = nullptr; @@ -209,6 +210,9 @@ public: static void cleanup_doc(); static String get_cache_full_path(); + static void load_xml_buffer(const uint8_t *p_buffer, int p_size); + static void remove_class(const String &p_class); + void go_to_help(const String &p_help); void go_to_class(const String &p_class); void update_doc(); @@ -228,6 +232,8 @@ public: void update_toggle_scripts_button(); + static void init_gdext_pointers(); + EditorHelp(); ~EditorHelp(); }; diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index a3530fc9ab..bfadc48b33 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -436,6 +436,10 @@ void EditorProperty::set_doc_path(const String &p_doc_path) { doc_path = p_doc_path; } +void EditorProperty::set_internal(bool p_internal) { + internal = p_internal; +} + void EditorProperty::update_property() { GDVIRTUAL_CALL(_update_property); } @@ -748,10 +752,10 @@ void EditorProperty::shortcut_input(const Ref<InputEvent> &p_event) { if (ED_IS_SHORTCUT("property_editor/copy_value", p_event)) { menu_option(MENU_COPY_VALUE); accept_event(); - } else if (ED_IS_SHORTCUT("property_editor/paste_value", p_event) && !is_read_only()) { + } else if (!is_read_only() && ED_IS_SHORTCUT("property_editor/paste_value", p_event)) { menu_option(MENU_PASTE_VALUE); accept_event(); - } else if (ED_IS_SHORTCUT("property_editor/copy_property_path", p_event)) { + } else if (!internal && ED_IS_SHORTCUT("property_editor/copy_property_path", p_event)) { menu_option(MENU_COPY_PROPERTY_PATH); accept_event(); } @@ -1036,6 +1040,8 @@ void EditorProperty::_update_popup() { menu->add_icon_shortcut(get_editor_theme_icon(SNAME("ActionPaste")), ED_GET_SHORTCUT("property_editor/paste_value"), MENU_PASTE_VALUE); menu->add_icon_shortcut(get_editor_theme_icon(SNAME("CopyNodePath")), ED_GET_SHORTCUT("property_editor/copy_property_path"), MENU_COPY_PROPERTY_PATH); menu->set_item_disabled(MENU_PASTE_VALUE, is_read_only()); + menu->set_item_disabled(MENU_COPY_PROPERTY_PATH, internal); + if (!pin_hidden) { menu->add_separator(); if (can_pin) { @@ -3329,7 +3335,11 @@ void EditorInspector::update_tree() { if (use_doc_hints) { // `|` separator used in `EditorHelpTooltip` for formatting. if (theme_item_name.is_empty()) { - ep->set_tooltip_text("property|" + classname + "|" + property_prefix + p.name + "|"); + if (p.usage & PROPERTY_USAGE_INTERNAL) { + ep->set_tooltip_text("internal_property|" + classname + "|" + property_prefix + p.name + "|"); + } else { + ep->set_tooltip_text("property|" + classname + "|" + property_prefix + p.name + "|"); + } } else { ep->set_tooltip_text("theme_item|" + classname + "|" + theme_item_name + "|"); } @@ -3337,6 +3347,8 @@ void EditorInspector::update_tree() { } ep->set_doc_path(doc_path); + ep->set_internal(p.usage & PROPERTY_USAGE_INTERNAL); + ep->update_property(); ep->_update_pin_flags(); ep->update_editor_property_status(); diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h index e36606c080..2dcad88840 100644 --- a/editor/editor_inspector.h +++ b/editor/editor_inspector.h @@ -74,6 +74,7 @@ private: StringName property; String property_path; String doc_path; + bool internal = false; bool has_doc_tooltip = false; int property_usage; @@ -156,6 +157,7 @@ public: EditorInspector *get_parent_inspector() const; void set_doc_path(const String &p_doc_path); + void set_internal(bool p_internal); virtual void update_property(); void update_editor_property_status(); diff --git a/editor/editor_interface.cpp b/editor/editor_interface.cpp index bad28ff43d..dcaf7fbd00 100644 --- a/editor/editor_interface.cpp +++ b/editor/editor_interface.cpp @@ -235,6 +235,10 @@ bool EditorInterface::is_distraction_free_mode_enabled() const { return EditorNode::get_singleton()->is_distraction_free_mode_enabled(); } +bool EditorInterface::is_multi_window_enabled() const { + return EditorNode::get_singleton()->is_multi_window_enabled(); +} + float EditorInterface::get_editor_scale() const { return EDSCALE; } @@ -445,6 +449,7 @@ void EditorInterface::_bind_methods() { ClassDB::bind_method(D_METHOD("set_main_screen_editor", "name"), &EditorInterface::set_main_screen_editor); ClassDB::bind_method(D_METHOD("set_distraction_free_mode", "enter"), &EditorInterface::set_distraction_free_mode); ClassDB::bind_method(D_METHOD("is_distraction_free_mode_enabled"), &EditorInterface::is_distraction_free_mode_enabled); + ClassDB::bind_method(D_METHOD("is_multi_window_enabled"), &EditorInterface::is_multi_window_enabled); ClassDB::bind_method(D_METHOD("get_editor_scale"), &EditorInterface::get_editor_scale); diff --git a/editor/editor_interface.h b/editor/editor_interface.h index 73e89ae2f2..9515a1226f 100644 --- a/editor/editor_interface.h +++ b/editor/editor_interface.h @@ -99,6 +99,7 @@ public: void set_main_screen_editor(const String &p_name); void set_distraction_free_mode(bool p_enter); bool is_distraction_free_mode_enabled() const; + bool is_multi_window_enabled() const; float get_editor_scale() const; diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 24bfba3844..23d5704048 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -870,7 +870,11 @@ void EditorNode::_resources_changed(const Vector<String> &p_resources) { } if (!res->editor_can_reload_from_file()) { - continue; + Ref<Script> scr = res; + // Scripts are reloaded via the script editor. + if (scr.is_null() || ScriptEditor::get_singleton()->get_open_scripts().has(scr)) { + continue; + } } if (!res->get_path().is_resource_file() && !res->get_path().is_absolute_path()) { continue; @@ -3711,6 +3715,10 @@ bool EditorNode::is_scene_open(const String &p_path) { return false; } +bool EditorNode::is_multi_window_enabled() const { + return !SceneTree::get_singleton()->get_root()->is_embedding_subwindows() && !EDITOR_GET("interface/editor/single_window_mode") && EDITOR_GET("interface/multi_window/enable"); +} + void EditorNode::fix_dependencies(const String &p_for_file) { dependency_fixer->edit(p_for_file); } @@ -4122,6 +4130,20 @@ void EditorNode::request_instantiate_scenes(const Vector<String> &p_files) { SceneTreeDock::get_singleton()->instantiate_scenes(p_files); } +String EditorNode::get_multiwindow_support_tooltip_text() const { + if (SceneTree::get_singleton()->get_root()->is_embedding_subwindows()) { + if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_SUBWINDOWS)) { + return TTR("Multi-window support is not available because the `--single-window` command line argument was used to start the editor."); + } else { + return TTR("Multi-window support is not available because the current platform doesn't support multiple windows."); + } + } else if (EDITOR_GET("interface/editor/single_window_mode")) { + return TTR("Multi-window support is not available because Interface > Editor > Single Window Mode is enabled in the editor settings."); + } + + return TTR("Multi-window support is not available because Interface > Multi Window > Enable is disabled in the editor settings."); +} + void EditorNode::_inherit_request(String p_file) { current_menu_option = FILE_NEW_INHERITED_SCENE; _dialog_action(p_file); diff --git a/editor/editor_node.h b/editor/editor_node.h index f1dea0c11e..014b72c580 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -758,6 +758,8 @@ public: bool is_resource_read_only(Ref<Resource> p_resource, bool p_foreign_resources_are_writable = false); + String get_multiwindow_support_tooltip_text() const; + bool is_changing_scene() const; VBoxContainer *get_main_screen_control(); @@ -807,6 +809,7 @@ public: List<AdditiveNodeEntry> &p_addition_list); bool is_scene_open(const String &p_path); + bool is_multi_window_enabled() const; void setup_color_picker(ColorPicker *p_picker); diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index 25510122f4..3b1a69459d 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -520,7 +520,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "filesystem/file_dialog/thumbnail_size", 64, "32,128,16") // Import (for glft module) - EDITOR_SETTING_USAGE(Variant::STRING, PROPERTY_HINT_GLOBAL_DIR, "filesystem/import/blender/blender3_path", "", "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) + EDITOR_SETTING_USAGE(Variant::STRING, PROPERTY_HINT_GLOBAL_FILE, "filesystem/import/blender/blender_path", "", "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) EDITOR_SETTING_USAGE(Variant::INT, PROPERTY_HINT_RANGE, "filesystem/import/blender/rpc_port", 6011, "0,65535,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) EDITOR_SETTING_USAGE(Variant::FLOAT, PROPERTY_HINT_RANGE, "filesystem/import/blender/rpc_server_uptime", 5, "0,300,1,or_greater,suffix:s", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) EDITOR_SETTING_USAGE(Variant::STRING, PROPERTY_HINT_GLOBAL_FILE, "filesystem/import/fbx/fbx2gltf_path", "", "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) diff --git a/editor/event_listener_line_edit.cpp b/editor/event_listener_line_edit.cpp index 2c46e1c20a..c29e83d624 100644 --- a/editor/event_listener_line_edit.cpp +++ b/editor/event_listener_line_edit.cpp @@ -79,7 +79,11 @@ String EventListenerLineEdit::get_event_text(const Ref<InputEvent> &p_event, boo if (!text.is_empty()) { text += " " + TTR("or") + " "; } - text += mods_text + keycode_get_string(key->get_physical_keycode()) + " (" + TTR("Physical") + ")"; + text += mods_text + keycode_get_string(key->get_physical_keycode()) + " (" + TTR("Physical"); + if (key->get_location() != KeyLocation::UNSPECIFIED) { + text += " " + key->as_text_location(); + } + text += ")"; } if (key->get_key_label() != Key::NONE) { if (!text.is_empty()) { diff --git a/editor/export/export_template_manager.cpp b/editor/export/export_template_manager.cpp index 69ad076f8a..1491adee52 100644 --- a/editor/export/export_template_manager.cpp +++ b/editor/export/export_template_manager.cpp @@ -466,6 +466,13 @@ bool ExportTemplateManager::_install_file_selected(const String &p_file, bool p_ break; } + if (String::utf8(fname).ends_with("/")) { + // File is a directory, ignore it. + // Directories will be created when extracting each file. + ret = unzGoToNextFile(pkg); + continue; + } + String file_path(String::utf8(fname).simplify_path()); String file = file_path.get_file(); diff --git a/editor/find_in_files.cpp b/editor/find_in_files.cpp index c708e77719..c6087f5b13 100644 --- a/editor/find_in_files.cpp +++ b/editor/find_in_files.cpp @@ -747,7 +747,7 @@ void FindInFilesPanel::_on_result_found(String fpath, int line_number, int begin String start = vformat("%3s: ", line_number); item->set_text(text_index, start + text); - item->set_custom_draw(text_index, this, "_draw_result_text"); + item->set_custom_draw_callback(text_index, callable_mp(this, &FindInFilesPanel::draw_result_text)); Result r; r.line_number = line_number; @@ -988,7 +988,6 @@ void FindInFilesPanel::set_progress_visible(bool p_visible) { void FindInFilesPanel::_bind_methods() { ClassDB::bind_method("_on_result_found", &FindInFilesPanel::_on_result_found); ClassDB::bind_method("_on_finished", &FindInFilesPanel::_on_finished); - ClassDB::bind_method("_draw_result_text", &FindInFilesPanel::draw_result_text); ADD_SIGNAL(MethodInfo(SIGNAL_RESULT_SELECTED, PropertyInfo(Variant::STRING, "path"), diff --git a/editor/gui/scene_tree_editor.cpp b/editor/gui/scene_tree_editor.cpp index 7a9df26fa7..1f35da322a 100644 --- a/editor/gui/scene_tree_editor.cpp +++ b/editor/gui/scene_tree_editor.cpp @@ -132,20 +132,32 @@ void SceneTreeEditor::_cell_button_pressed(Object *p_item, int p_column, int p_i } undo_redo->commit_action(); } else if (p_id == BUTTON_WARNING) { - String config_err = n->get_configuration_warnings_as_string(); - if (config_err.is_empty()) { + const PackedStringArray warnings = n->get_configuration_warnings(); + + if (warnings.is_empty()) { return; } - const PackedInt32Array boundaries = TS->string_get_word_breaks(config_err, "", 80); + // Improve looks on tooltip, extra spacing on non-bullet point newlines. + const String bullet_point = U"• "; + String all_warnings; + for (const String &w : warnings) { + all_warnings += "\n" + bullet_point + w; + } + + // Limit the line width while keeping some padding. + // It is not efficient, but it does not have to be. + const PackedInt32Array boundaries = TS->string_get_word_breaks(all_warnings, "", 80); PackedStringArray lines; for (int i = 0; i < boundaries.size(); i += 2) { const int start = boundaries[i]; const int end = boundaries[i + 1]; - lines.append(config_err.substr(start, end - start + 1)); + const String line = all_warnings.substr(start, end - start); + lines.append(line); } + all_warnings = String("\n").join(lines).indent(" ").replace(U" •", U"\n•").substr(2); // We don't want the first two newlines. - warning->set_text(String("\n").join(lines)); + warning->set_text(all_warnings); warning->popup_centered(); } else if (p_id == BUTTON_SIGNALS) { @@ -282,9 +294,9 @@ void SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent) { if (can_rename) { //should be can edit.. - String conf_warning = p_node->get_configuration_warnings_as_string(); - if (!conf_warning.is_empty()) { - const int num_warnings = p_node->get_configuration_warnings().size(); + const PackedStringArray warnings = p_node->get_configuration_warnings(); + const int num_warnings = warnings.size(); + if (num_warnings > 0) { String warning_icon; if (num_warnings == 1) { warning_icon = SNAME("NodeWarning"); @@ -296,17 +308,15 @@ void SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent) { // Improve looks on tooltip, extra spacing on non-bullet point newlines. const String bullet_point = U"• "; - int next_newline = 0; - while (next_newline != -1) { - next_newline = conf_warning.find("\n", next_newline + 2); - if (conf_warning.substr(next_newline + 1, bullet_point.length()) != bullet_point) { - conf_warning = conf_warning.insert(next_newline + 1, " "); - } + String all_warnings; + for (const String &w : warnings) { + all_warnings += "\n\n" + bullet_point + w.replace("\n", "\n "); + } + if (num_warnings == 1) { + all_warnings.remove_at(0); // With only one warning, two newlines do not look great. } - String newline = (num_warnings == 1 ? "\n" : "\n\n"); - - item->add_button(0, get_editor_theme_icon(warning_icon), BUTTON_WARNING, false, TTR("Node configuration warning:") + newline + conf_warning); + item->add_button(0, get_editor_theme_icon(warning_icon), BUTTON_WARNING, false, TTR("Node configuration warning:") + all_warnings); } if (p_node->is_unique_name_in_owner()) { @@ -1051,10 +1061,9 @@ void SceneTreeEditor::_rename_node(Node *p_node, const String &p_name) { } } + new_name = p_node->get_parent()->prevalidate_child_name(p_node, new_name); if (new_name == p_node->get_name()) { - if (item->get_text(0).is_empty()) { - item->set_text(0, new_name); - } + item->set_text(0, new_name); return; } @@ -1545,6 +1554,7 @@ SceneTreeEditor::SceneTreeEditor(bool p_label, bool p_can_rename, bool p_can_ope warning = memnew(AcceptDialog); add_child(warning); warning->set_title(TTR("Node Configuration Warning!")); + warning->set_flag(Window::FLAG_POPUP, true); last_hash = 0; blocked = 0; diff --git a/editor/input_event_configuration_dialog.cpp b/editor/input_event_configuration_dialog.cpp index 0f483fcaef..22673bec64 100644 --- a/editor/input_event_configuration_dialog.cpp +++ b/editor/input_event_configuration_dialog.cpp @@ -64,6 +64,7 @@ void InputEventConfigurationDialog::_set_event(const Ref<InputEvent> &p_event, c bool show_mods = false; bool show_device = false; bool show_key = false; + bool show_location = false; if (mod.is_valid()) { show_mods = true; @@ -77,12 +78,17 @@ void InputEventConfigurationDialog::_set_event(const Ref<InputEvent> &p_event, c if (k.is_valid()) { show_key = true; - if (k->get_keycode() == Key::NONE && k->get_physical_keycode() == Key::NONE && k->get_key_label() != Key::NONE) { + Key phys_key = k->get_physical_keycode(); + if (k->get_keycode() == Key::NONE && phys_key == Key::NONE && k->get_key_label() != Key::NONE) { key_mode->select(KEYMODE_UNICODE); } else if (k->get_keycode() != Key::NONE) { key_mode->select(KEYMODE_KEYCODE); - } else if (k->get_physical_keycode() != Key::NONE) { + } else if (phys_key != Key::NONE) { key_mode->select(KEYMODE_PHY_KEYCODE); + if (phys_key == Key::SHIFT || phys_key == Key::CTRL || phys_key == Key::ALT || phys_key == Key::META) { + key_location->select((int)k->get_location()); + show_location = true; + } } else { // Invalid key. event = Ref<InputEvent>(); @@ -103,6 +109,7 @@ void InputEventConfigurationDialog::_set_event(const Ref<InputEvent> &p_event, c mod_container->set_visible(show_mods); device_container->set_visible(show_device); key_mode->set_visible(show_key); + location_container->set_visible(show_location); additional_options_container->show(); // Update mode selector based on original key event. @@ -240,6 +247,9 @@ void InputEventConfigurationDialog::_on_listen_input_changed(const Ref<InputEven k->set_physical_keycode(Key::NONE); k->set_keycode(Key::NONE); } + if (key_location->get_selected_id() == (int)KeyLocation::UNSPECIFIED) { + k->set_location(KeyLocation::UNSPECIFIED); + } } Ref<InputEventWithModifiers> mod = received_event; @@ -433,6 +443,17 @@ void InputEventConfigurationDialog::_key_mode_selected(int p_mode) { _set_event(k, original_event); } +void InputEventConfigurationDialog::_key_location_selected(int p_location) { + Ref<InputEventKey> k = event; + if (k.is_null()) { + return; + } + + k->set_location((KeyLocation)p_location); + + _set_event(k, original_event); +} + void InputEventConfigurationDialog::_input_list_item_selected() { TreeItem *selected = input_list_tree->get_selected(); @@ -594,6 +615,8 @@ void InputEventConfigurationDialog::popup_and_configure(const Ref<InputEvent> &p // Select "All Devices" by default. device_id_option->select(0); + // Also "all locations". + key_location->select(0); } if (!p_current_action_name.is_empty()) { @@ -726,5 +749,24 @@ InputEventConfigurationDialog::InputEventConfigurationDialog() { key_mode->hide(); additional_options_container->add_child(key_mode); + // Key Location Selection + + location_container = memnew(HBoxContainer); + location_container->hide(); + + Label *location_label = memnew(Label); + location_label->set_text(TTR("Physical location")); + location_container->add_child(location_label); + + key_location = memnew(OptionButton); + key_location->set_h_size_flags(Control::SIZE_EXPAND_FILL); + key_location->add_item(TTR("Any"), (int)KeyLocation::UNSPECIFIED); + key_location->add_item(TTR("Left"), (int)KeyLocation::LEFT); + key_location->add_item(TTR("Right"), (int)KeyLocation::RIGHT); + key_location->connect("item_selected", callable_mp(this, &InputEventConfigurationDialog::_key_location_selected)); + + location_container->add_child(key_location); + additional_options_container->add_child(location_container); + main_vbox->add_child(additional_options_container); } diff --git a/editor/input_event_configuration_dialog.h b/editor/input_event_configuration_dialog.h index bde0b73ade..3ef089be8b 100644 --- a/editor/input_event_configuration_dialog.h +++ b/editor/input_event_configuration_dialog.h @@ -99,6 +99,9 @@ private: OptionButton *key_mode = nullptr; + HBoxContainer *location_container = nullptr; + OptionButton *key_location = nullptr; + void _set_event(const Ref<InputEvent> &p_event, const Ref<InputEvent> &p_original_event, bool p_update_input_list_selection = true); void _on_listen_input_changed(const Ref<InputEvent> &p_event); void _on_listen_focus_changed(); @@ -110,6 +113,7 @@ private: void _mod_toggled(bool p_checked, int p_index); void _autoremap_command_or_control_toggled(bool p_checked); void _key_mode_selected(int p_mode); + void _key_location_selected(int p_location); void _device_selection_changed(int p_option_button_index); void _set_current_device(int p_device); diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index 55191f44d4..c8e65e98a7 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -4074,18 +4074,19 @@ ScriptEditor::ScriptEditor(WindowWrapper *p_wrapper) { script_forward->set_disabled(true); script_forward->set_tooltip_text(TTR("Go to next edited document.")); - if (p_wrapper->is_window_available()) { - menu_hb->add_child(memnew(VSeparator)); + menu_hb->add_child(memnew(VSeparator)); - make_floating = memnew(ScreenSelect); - make_floating->set_flat(true); + make_floating = memnew(ScreenSelect); + make_floating->set_flat(true); + make_floating->connect("request_open_in_screen", callable_mp(window_wrapper, &WindowWrapper::enable_window_on_screen).bind(true)); + if (!make_floating->is_disabled()) { + // Override default ScreenSelect tooltip if multi-window support is available. make_floating->set_tooltip_text(TTR("Make the script editor floating.")); - make_floating->connect("request_open_in_screen", callable_mp(window_wrapper, &WindowWrapper::enable_window_on_screen).bind(true)); - - menu_hb->add_child(make_floating); - p_wrapper->connect("window_visibility_changed", callable_mp(this, &ScriptEditor::_window_changed)); } + menu_hb->add_child(make_floating); + p_wrapper->connect("window_visibility_changed", callable_mp(this, &ScriptEditor::_window_changed)); + tab_container->connect("tab_changed", callable_mp(this, &ScriptEditor::_tab_changed)); erase_tab_confirm = memnew(ConfirmationDialog); diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp index 5798ff9d99..a018ec095b 100644 --- a/editor/plugins/shader_editor_plugin.cpp +++ b/editor/plugins/shader_editor_plugin.cpp @@ -643,20 +643,21 @@ ShaderEditorPlugin::ShaderEditorPlugin() { file_menu->get_popup()->set_item_disabled(file_menu->get_popup()->get_item_index(i), true); } - if (window_wrapper->is_window_available()) { - Control *padding = memnew(Control); - padding->set_h_size_flags(Control::SIZE_EXPAND_FILL); - menu_hb->add_child(padding); - - make_floating = memnew(ScreenSelect); - make_floating->set_flat(true); + Control *padding = memnew(Control); + padding->set_h_size_flags(Control::SIZE_EXPAND_FILL); + menu_hb->add_child(padding); + + make_floating = memnew(ScreenSelect); + make_floating->set_flat(true); + make_floating->connect("request_open_in_screen", callable_mp(window_wrapper, &WindowWrapper::enable_window_on_screen).bind(true)); + if (!make_floating->is_disabled()) { + // Override default ScreenSelect tooltip if multi-window support is available. make_floating->set_tooltip_text(TTR("Make the shader editor floating.")); - make_floating->connect("request_open_in_screen", callable_mp(window_wrapper, &WindowWrapper::enable_window_on_screen).bind(true)); - - menu_hb->add_child(make_floating); - window_wrapper->connect("window_visibility_changed", callable_mp(this, &ShaderEditorPlugin::_window_changed)); } + menu_hb->add_child(make_floating); + window_wrapper->connect("window_visibility_changed", callable_mp(this, &ShaderEditorPlugin::_window_changed)); + shader_list = memnew(ItemList); shader_list->set_auto_translate(false); shader_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); diff --git a/editor/plugins/text_editor.cpp b/editor/plugins/text_editor.cpp index 475aba0eeb..3fdb0388f2 100644 --- a/editor/plugins/text_editor.cpp +++ b/editor/plugins/text_editor.cpp @@ -612,21 +612,6 @@ TextEditor::TextEditor() { edit_hb = memnew(HBoxContainer); - search_menu = memnew(MenuButton); - search_menu->set_shortcut_context(this); - edit_hb->add_child(search_menu); - search_menu->set_text(TTR("Search")); - search_menu->set_switch_on_hover(true); - search_menu->get_popup()->connect("id_pressed", callable_mp(this, &TextEditor::_edit_option)); - - search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find"), SEARCH_FIND); - search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_next"), SEARCH_FIND_NEXT); - search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_previous"), SEARCH_FIND_PREV); - search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace"), SEARCH_REPLACE); - search_menu->get_popup()->add_separator(); - search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_in_files"), SEARCH_IN_FILES); - search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace_in_files"), REPLACE_IN_FILES); - edit_menu = memnew(MenuButton); edit_menu->set_shortcut_context(this); edit_hb->add_child(edit_menu); @@ -685,6 +670,21 @@ TextEditor::TextEditor() { add_syntax_highlighter(highlighter); set_syntax_highlighter(plain_highlighter); + search_menu = memnew(MenuButton); + search_menu->set_shortcut_context(this); + edit_hb->add_child(search_menu); + search_menu->set_text(TTR("Search")); + search_menu->set_switch_on_hover(true); + search_menu->get_popup()->connect("id_pressed", callable_mp(this, &TextEditor::_edit_option)); + + search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find"), SEARCH_FIND); + search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_next"), SEARCH_FIND_NEXT); + search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_previous"), SEARCH_FIND_PREV); + search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace"), SEARCH_REPLACE); + search_menu->get_popup()->add_separator(); + search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_in_files"), SEARCH_IN_FILES); + search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace_in_files"), REPLACE_IN_FILES); + MenuButton *goto_menu = memnew(MenuButton); goto_menu->set_shortcut_context(this); edit_hb->add_child(goto_menu); diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index 89fff008ea..bc02232b35 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -1791,7 +1791,7 @@ void VisualShaderEditor::_update_options_menu() { } Ref<VisualShaderNodeParameterRef> parameter_ref = Object::cast_to<VisualShaderNodeParameterRef>(vsn.ptr()); - if (parameter_ref.is_valid()) { + if (parameter_ref.is_valid() && parameter_ref->is_shader_valid()) { check_result = -1; if (members_input_port_type != VisualShaderNode::PORT_TYPE_MAX) { diff --git a/editor/register_editor_types.cpp b/editor/register_editor_types.cpp index 04a06ff732..484fa7b85f 100644 --- a/editor/register_editor_types.cpp +++ b/editor/register_editor_types.cpp @@ -282,6 +282,10 @@ void register_editor_types() { ei_singleton.editor_only = true; Engine::get_singleton()->add_singleton(ei_singleton); + // Required as GDExtensions can register docs at init time way before this + // class is actually instantiated. + EditorHelp::init_gdext_pointers(); + OS::get_singleton()->benchmark_end_measure("Editor", "Register Types"); } diff --git a/editor/renames_map_3_to_4.cpp b/editor/renames_map_3_to_4.cpp index ba6095db47..694580911c 100644 --- a/editor/renames_map_3_to_4.cpp +++ b/editor/renames_map_3_to_4.cpp @@ -642,7 +642,6 @@ const char *RenamesMap3To4::csharp_function_renames[][2] = { { "_SetEditorDescription", "SetEditorDescription" }, // Node { "_SetPlaying", "SetPlaying" }, // AnimatedSprite3D { "_ToplevelRaiseSelf", "_TopLevelRaiseSelf" }, // CanvasItem - { "_UpdateWrapAt", "_UpdateWrapAtColumn" }, // TextEdit { "AddCancel", "AddCancelButton" }, // AcceptDialog { "AddCentralForce", "AddConstantCentralForce" }, //RigidBody2D { "AddChildBelowNode", "AddSibling" }, // Node diff --git a/editor/window_wrapper.cpp b/editor/window_wrapper.cpp index a085c2e44f..b2b237269a 100644 --- a/editor/window_wrapper.cpp +++ b/editor/window_wrapper.cpp @@ -315,7 +315,7 @@ void WindowWrapper::set_margins_enabled(bool p_enabled) { } WindowWrapper::WindowWrapper() { - if (SceneTree::get_singleton()->get_root()->is_embedding_subwindows() || EDITOR_GET("interface/editor/single_window_mode") || !EDITOR_GET("interface/multi_window/enable")) { + if (!EditorNode::get_singleton()->is_multi_window_enabled()) { return; } @@ -375,7 +375,9 @@ void ScreenSelect::_build_advanced_menu() { } void ScreenSelect::_emit_screen_signal(int p_screen_idx) { - emit_signal("request_open_in_screen", p_screen_idx); + if (!is_disabled()) { + emit_signal("request_open_in_screen", p_screen_idx); + } } void ScreenSelect::_bind_methods() { @@ -436,13 +438,19 @@ void ScreenSelect::pressed() { } ScreenSelect::ScreenSelect() { - set_tooltip_text(TTR("Make this panel floating.\nRight click to open the screen selector.")); set_button_mask(MouseButtonMask::RIGHT); set_flat(true); set_toggle_mode(true); set_focus_mode(FOCUS_NONE); set_action_mode(ACTION_MODE_BUTTON_PRESS); + if (!EditorNode::get_singleton()->is_multi_window_enabled()) { + set_disabled(true); + set_tooltip_text(EditorNode::get_singleton()->get_multiwindow_support_tooltip_text()); + } else { + set_tooltip_text(TTR("Make this panel floating.\nRight-click to open the screen selector.")); + } + // Create the popup. const Size2 borders = Size2(4, 4) * EDSCALE; diff --git a/main/main.cpp b/main/main.cpp index 1e0869e7f5..49cb1ca24d 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -2229,7 +2229,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "xr/openxr/default_action_map", PROPERTY_HINT_FILE, "*.tres"), "res://openxr_action_map.tres"); GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "xr/openxr/form_factor", PROPERTY_HINT_ENUM, "Head Mounted,Handheld"), "0"); GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "xr/openxr/view_configuration", PROPERTY_HINT_ENUM, "Mono,Stereo"), "1"); // "Mono,Stereo,Quad,Observer" - GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "xr/openxr/reference_space", PROPERTY_HINT_ENUM, "Local,Stage"), "1"); + GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "xr/openxr/reference_space", PROPERTY_HINT_ENUM, "Local,Stage,Local Floor"), "1"); GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "xr/openxr/environment_blend_mode", PROPERTY_HINT_ENUM, "Opaque,Additive,Alpha"), "0"); GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "xr/openxr/foveation_level", PROPERTY_HINT_ENUM, "Off,Low,Medium,High"), "0"); GLOBAL_DEF_BASIC("xr/openxr/foveation_dynamic", false); diff --git a/methods.py b/methods.py index a55c622ed0..f36591d211 100644 --- a/methods.py +++ b/methods.py @@ -163,7 +163,6 @@ def get_version_info(module_version_string="", silent=False): "status": str(version.status), "build": str(build_name), "module_config": str(version.module_config) + module_version_string, - "year": int(version.year), "website": str(version.website), "docs_branch": str(version.docs), } @@ -232,7 +231,6 @@ def generate_version_header(module_version_string=""): #define VERSION_STATUS "{status}" #define VERSION_BUILD "{build}" #define VERSION_MODULE_CONFIG "{module_config}" -#define VERSION_YEAR {year} #define VERSION_WEBSITE "{website}" #define VERSION_DOCS_BRANCH "{docs_branch}" #define VERSION_DOCS_URL "https://docs.godotengine.org/en/" VERSION_DOCS_BRANCH diff --git a/misc/scripts/install_d3d12_sdk_windows.py b/misc/scripts/install_d3d12_sdk_windows.py new file mode 100644 index 0000000000..8cfa1d399b --- /dev/null +++ b/misc/scripts/install_d3d12_sdk_windows.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python + +import os +import urllib.request +import shutil + +# Base Godot dependencies path +# If cross-compiling (no LOCALAPPDATA), we install in `bin` +deps_folder = os.getenv("LOCALAPPDATA") +if deps_folder: + deps_folder = os.path.join(deps_folder, "Godot", "build_deps") +else: + deps_folder = os.path.join("bin", "build_deps") + +# DirectX Shader Compiler +dxc_version = "v1.7.2308" +dxc_filename = "dxc_2023_08_14.zip" +dxc_archive = os.path.join(deps_folder, dxc_filename) +dxc_folder = os.path.join(deps_folder, "dxc") +# Mesa NIR +mesa_version = "23.1.0-devel" +mesa_filename = "godot-nir-23.1.0-1-devel.zip" +mesa_archive = os.path.join(deps_folder, mesa_filename) +mesa_folder = os.path.join(deps_folder, "mesa") +# WinPixEventRuntime +pix_version = "1.0.231030001" +pix_archive = os.path.join(deps_folder, f"WinPixEventRuntime_{pix_version}.nupkg") +pix_folder = os.path.join(deps_folder, "pix") +# DirectX 12 Agility SDK +agility_sdk_version = "1.610.4" +agility_sdk_archive = os.path.join(deps_folder, f"Agility_SDK_{agility_sdk_version}.nupkg") +agility_sdk_folder = os.path.join(deps_folder, "agility_sdk") + +# Create dependencies folder +if not os.path.exists(deps_folder): + os.makedirs(deps_folder) + +# DirectX Shader Compiler +print("[1/4] DirectX Shader Compiler") +if os.path.isfile(dxc_archive): + os.remove(dxc_archive) +print(f"Downloading DirectX Shader Compiler {dxc_filename} ...") +urllib.request.urlretrieve( + f"https://github.com/microsoft/DirectXShaderCompiler/releases/download/{dxc_version}/{dxc_filename}", + dxc_archive, +) +if os.path.exists(dxc_folder): + print(f"Removing existing local DirectX Shader Compiler installation in {dxc_folder} ...") + shutil.rmtree(dxc_folder) +print(f"Extracting DirectX Shader Compiler {dxc_filename} to {dxc_folder} ...") +shutil.unpack_archive(dxc_archive, dxc_folder) +os.remove(dxc_archive) +print(f"DirectX Shader Compiler {dxc_filename} installed successfully.\n") + +# Mesa NIR +print("[2/4] Mesa NIR") +if os.path.isfile(mesa_archive): + os.remove(mesa_archive) +print(f"Downloading Mesa NIR {mesa_filename} ...") +urllib.request.urlretrieve( + f"https://github.com/godotengine/godot-nir-static/releases/download/{mesa_version}/{mesa_filename}", + mesa_archive, +) +if os.path.exists(mesa_folder): + print(f"Removing existing local Mesa NIR installation in {mesa_folder} ...") + shutil.rmtree(mesa_folder) +print(f"Extracting Mesa NIR {mesa_filename} to {mesa_folder} ...") +shutil.unpack_archive(mesa_archive, mesa_folder) +os.remove(mesa_archive) +print(f"Mesa NIR {mesa_filename} installed successfully.\n") + +# WinPixEventRuntime +print("[3/4] WinPixEventRuntime") +if os.path.isfile(pix_archive): + os.remove(pix_archive) +print(f"Downloading WinPixEventRuntime {pix_version} ...") +urllib.request.urlretrieve(f"https://www.nuget.org/api/v2/package/WinPixEventRuntime/{pix_version}", pix_archive) +if os.path.exists(pix_folder): + print(f"Removing existing local WinPixEventRuntime installation in {pix_folder} ...") + shutil.rmtree(pix_folder) +print(f"Extracting WinPixEventRuntime {pix_version} to {pix_folder} ...") +shutil.unpack_archive(pix_archive, pix_folder, "zip") +os.remove(pix_archive) +print(f"WinPixEventRuntime {pix_version} installed successfully.\n") + +# DirectX 12 Agility SDK +print("[4/4] DirectX 12 Agility SDK") +if os.path.isfile(agility_sdk_archive): + os.remove(agility_sdk_archive) +print(f"Downloading DirectX 12 Agility SDK {agility_sdk_version} ...") +urllib.request.urlretrieve( + f"https://www.nuget.org/api/v2/package/Microsoft.Direct3D.D3D12/{agility_sdk_version}", agility_sdk_archive +) +if os.path.exists(agility_sdk_folder): + print(f"Removing existing local DirectX 12 Agility SDK installation in {agility_sdk_folder} ...") + shutil.rmtree(agility_sdk_folder) +print(f"Extracting DirectX 12 Agility SDK {agility_sdk_version} to {agility_sdk_folder} ...") +shutil.unpack_archive(agility_sdk_archive, agility_sdk_folder, "zip") +os.remove(agility_sdk_archive) +print(f"DirectX 12 Agility SDK {agility_sdk_version} installed successfully.\n") + +# Complete message +print(f"All Direct3D 12 SDK components were installed to {deps_folder} successfully!") +print('You can now build Godot with Direct3D 12 support enabled by running "scons d3d12=yes".') diff --git a/modules/etcpak/image_compress_etcpak.cpp b/modules/etcpak/image_compress_etcpak.cpp index e9ce92dd80..9c04f5b40e 100644 --- a/modules/etcpak/image_compress_etcpak.cpp +++ b/modules/etcpak/image_compress_etcpak.cpp @@ -44,9 +44,9 @@ EtcpakType _determine_etc_type(Image::UsedChannels p_channels) { case Image::USED_CHANNELS_LA: return EtcpakType::ETCPAK_TYPE_ETC2_ALPHA; case Image::USED_CHANNELS_R: - return EtcpakType::ETCPAK_TYPE_ETC2; + return EtcpakType::ETCPAK_TYPE_ETC2_R; case Image::USED_CHANNELS_RG: - return EtcpakType::ETCPAK_TYPE_ETC2_RA_AS_RG; + return EtcpakType::ETCPAK_TYPE_ETC2_RG; case Image::USED_CHANNELS_RGB: return EtcpakType::ETCPAK_TYPE_ETC2; case Image::USED_CHANNELS_RGBA: @@ -114,6 +114,12 @@ void _compress_etcpak(EtcpakType p_compresstype, Image *r_img) { } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2) { target_format = Image::FORMAT_ETC2_RGB8; r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC. + } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_R) { + target_format = Image::FORMAT_ETC2_R11; + r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC. + } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_RG) { + target_format = Image::FORMAT_ETC2_RG11; + r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC. } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_RA_AS_RG) { target_format = Image::FORMAT_ETC2_RA_AS_RG; r_img->convert_rg_to_ra_rgba8(); @@ -224,22 +230,49 @@ void _compress_etcpak(EtcpakType p_compresstype, Image *r_img) { // Override the src_mip_read pointer to our temporary Vector. src_mip_read = padded_src.ptr(); } - if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC1) { - CompressEtc1RgbDither(src_mip_read, dest_mip_write, blocks, mip_w); - } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2) { - CompressEtc2Rgb(src_mip_read, dest_mip_write, blocks, mip_w, true); - } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_ALPHA || p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_RA_AS_RG) { - CompressEtc2Rgba(src_mip_read, dest_mip_write, blocks, mip_w, true); - } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT1) { - CompressDxt1Dither(src_mip_read, dest_mip_write, blocks, mip_w); - } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT5 || p_compresstype == EtcpakType::ETCPAK_TYPE_DXT5_RA_AS_RG) { - CompressDxt5(src_mip_read, dest_mip_write, blocks, mip_w); - } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_RGTC_RG) { - CompressRgtcRG(src_mip_read, dest_mip_write, blocks, mip_w); - } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_RGTC_R) { - CompressRgtcR(src_mip_read, dest_mip_write, blocks, mip_w); - } else { - ERR_FAIL_MSG("etcpak: Invalid or unsupported compression format."); + + switch (p_compresstype) { + case EtcpakType::ETCPAK_TYPE_ETC1: + CompressEtc1RgbDither(src_mip_read, dest_mip_write, blocks, mip_w); + break; + + case EtcpakType::ETCPAK_TYPE_ETC2: + CompressEtc2Rgb(src_mip_read, dest_mip_write, blocks, mip_w, true); + break; + + case EtcpakType::ETCPAK_TYPE_ETC2_ALPHA: + case EtcpakType::ETCPAK_TYPE_ETC2_RA_AS_RG: + CompressEtc2Rgba(src_mip_read, dest_mip_write, blocks, mip_w, true); + break; + + case EtcpakType::ETCPAK_TYPE_ETC2_R: + CompressEtc2R8(src_mip_read, dest_mip_write, blocks, mip_w); + break; + + case EtcpakType::ETCPAK_TYPE_ETC2_RG: + CompressEtc2RG8(src_mip_read, dest_mip_write, blocks, mip_w); + break; + + case EtcpakType::ETCPAK_TYPE_DXT1: + CompressDxt1Dither(src_mip_read, dest_mip_write, blocks, mip_w); + break; + + case EtcpakType::ETCPAK_TYPE_DXT5: + case EtcpakType::ETCPAK_TYPE_DXT5_RA_AS_RG: + CompressDxt5(src_mip_read, dest_mip_write, blocks, mip_w); + break; + + case EtcpakType::ETCPAK_TYPE_RGTC_R: + CompressRgtcR(src_mip_read, dest_mip_write, blocks, mip_w); + break; + + case EtcpakType::ETCPAK_TYPE_RGTC_RG: + CompressRgtcRG(src_mip_read, dest_mip_write, blocks, mip_w); + break; + + default: + ERR_FAIL_MSG("etcpak: Invalid or unsupported compression format."); + break; } } diff --git a/modules/etcpak/image_compress_etcpak.h b/modules/etcpak/image_compress_etcpak.h index ff8bb635b4..9d5343740b 100644 --- a/modules/etcpak/image_compress_etcpak.h +++ b/modules/etcpak/image_compress_etcpak.h @@ -38,6 +38,8 @@ enum class EtcpakType { ETCPAK_TYPE_ETC2, ETCPAK_TYPE_ETC2_ALPHA, ETCPAK_TYPE_ETC2_RA_AS_RG, + ETCPAK_TYPE_ETC2_R, + ETCPAK_TYPE_ETC2_RG, ETCPAK_TYPE_DXT1, ETCPAK_TYPE_DXT5, ETCPAK_TYPE_DXT5_RA_AS_RG, diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 7026d131e3..dd9e49fb8d 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -5326,8 +5326,21 @@ GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator return result; } -// TODO: Add safe/unsafe return variable (for variant cases) bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion, const GDScriptParser::Node *p_source_node) { +#ifdef DEBUG_ENABLED + if (p_source_node) { + if (p_target.kind == GDScriptParser::DataType::ENUM) { + if (p_source.kind == GDScriptParser::DataType::BUILTIN && p_source.builtin_type == Variant::INT) { + parser->push_warning(p_source_node, GDScriptWarning::INT_AS_ENUM_WITHOUT_CAST); + } + } + } +#endif + return check_type_compatibility(p_target, p_source, p_allow_implicit_conversion, p_source_node); +} + +// TODO: Add safe/unsafe return variable (for variant cases) +bool GDScriptAnalyzer::check_type_compatibility(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion, const GDScriptParser::Node *p_source_node) { // These return "true" so it doesn't affect users negatively. ERR_FAIL_COND_V_MSG(!p_target.is_set(), true, "Parser bug (please report): Trying to check compatibility of unset target type"); ERR_FAIL_COND_V_MSG(!p_source.is_set(), true, "Parser bug (please report): Trying to check compatibility of unset value type"); @@ -5362,11 +5375,6 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ if (p_target.kind == GDScriptParser::DataType::ENUM) { if (p_source.kind == GDScriptParser::DataType::BUILTIN && p_source.builtin_type == Variant::INT) { -#ifdef DEBUG_ENABLED - if (p_source_node) { - parser->push_warning(p_source_node, GDScriptWarning::INT_AS_ENUM_WITHOUT_CAST); - } -#endif return true; } if (p_source.kind == GDScriptParser::DataType::ENUM) { diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index 4ed476a3df..e398ccfdbb 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -147,6 +147,7 @@ public: Variant make_variable_default_value(GDScriptParser::VariableNode *p_variable); const HashMap<String, Ref<GDScriptParserRef>> &get_depended_parsers(); + static bool check_type_compatibility(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false, const GDScriptParser::Node *p_source_node = nullptr); GDScriptAnalyzer(GDScriptParser *p_parser); }; diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 210e2c3898..44e104da05 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -552,6 +552,19 @@ static int _get_property_location(const StringName &p_class, const StringName &p return depth | ScriptLanguage::LOCATION_PARENT_MASK; } +static int _get_property_location(Ref<Script> p_script, const StringName &p_property) { + int depth = 0; + Ref<Script> scr = p_script; + while (scr.is_valid()) { + if (scr->get_member_line(p_property) != -1) { + return depth | ScriptLanguage::LOCATION_PARENT_MASK; + } + depth++; + scr = scr->get_base_script(); + } + return depth + _get_property_location(p_script->get_instance_base_type(), p_property); +} + static int _get_constant_location(const StringName &p_class, const StringName &p_constant) { if (!ClassDB::has_integer_constant(p_class, p_constant)) { return ScriptLanguage::LOCATION_OTHER; @@ -567,6 +580,19 @@ static int _get_constant_location(const StringName &p_class, const StringName &p return depth | ScriptLanguage::LOCATION_PARENT_MASK; } +static int _get_constant_location(Ref<Script> p_script, const StringName &p_constant) { + int depth = 0; + Ref<Script> scr = p_script; + while (scr.is_valid()) { + if (scr->get_member_line(p_constant) != -1) { + return depth | ScriptLanguage::LOCATION_PARENT_MASK; + } + depth++; + scr = scr->get_base_script(); + } + return depth + _get_constant_location(p_script->get_instance_base_type(), p_constant); +} + static int _get_signal_location(const StringName &p_class, const StringName &p_signal) { if (!ClassDB::has_signal(p_class, p_signal)) { return ScriptLanguage::LOCATION_OTHER; @@ -582,6 +608,19 @@ static int _get_signal_location(const StringName &p_class, const StringName &p_s return depth | ScriptLanguage::LOCATION_PARENT_MASK; } +static int _get_signal_location(Ref<Script> p_script, const StringName &p_signal) { + int depth = 0; + Ref<Script> scr = p_script; + while (scr.is_valid()) { + if (scr->get_member_line(p_signal) != -1) { + return depth | ScriptLanguage::LOCATION_PARENT_MASK; + } + depth++; + scr = scr->get_base_script(); + } + return depth + _get_signal_location(p_script->get_instance_base_type(), p_signal); +} + static int _get_method_location(const StringName &p_class, const StringName &p_method) { if (!ClassDB::has_method(p_class, p_method)) { return ScriptLanguage::LOCATION_OTHER; @@ -597,6 +636,19 @@ static int _get_method_location(const StringName &p_class, const StringName &p_m return depth | ScriptLanguage::LOCATION_PARENT_MASK; } +static int _get_method_location(Ref<Script> p_script, const StringName &p_method) { + int depth = 0; + Ref<Script> scr = p_script; + while (scr.is_valid()) { + if (scr->get_member_line(p_method) != -1) { + return depth | ScriptLanguage::LOCATION_PARENT_MASK; + } + depth++; + scr = scr->get_base_script(); + } + return depth + _get_method_location(p_script->get_instance_base_type(), p_method); +} + static int _get_enum_constant_location(const StringName &p_class, const StringName &p_enum_constant) { if (!ClassDB::get_integer_constant_enum(p_class, p_enum_constant)) { return ScriptLanguage::LOCATION_OTHER; @@ -1083,13 +1135,13 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base List<PropertyInfo> members; scr->get_script_property_list(&members); for (const PropertyInfo &E : members) { - if (E.usage & (PROPERTY_USAGE_CATEGORY | PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SUBGROUP)) { + if (E.usage & (PROPERTY_USAGE_CATEGORY | PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SUBGROUP | PROPERTY_USAGE_INTERNAL)) { continue; } if (E.name.contains("/")) { continue; } - int location = p_recursion_depth + _get_property_location(scr->get_class_name(), E.name); + int location = p_recursion_depth + _get_property_location(scr, E.name); ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, location); r_result.insert(option.display, option); } @@ -1097,7 +1149,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base List<MethodInfo> signals; scr->get_script_signal_list(&signals); for (const MethodInfo &E : signals) { - int location = p_recursion_depth + _get_signal_location(scr->get_class_name(), E.name); + int location = p_recursion_depth + _get_signal_location(scr, E.name); ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_SIGNAL, location); r_result.insert(option.display, option); } @@ -1105,7 +1157,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base HashMap<StringName, Variant> constants; scr->get_constants(&constants); for (const KeyValue<StringName, Variant> &E : constants) { - int location = p_recursion_depth + _get_constant_location(scr->get_class_name(), E.key); + int location = p_recursion_depth + _get_constant_location(scr, E.key); ScriptLanguage::CodeCompletionOption option(E.key.operator String(), ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT, location); r_result.insert(option.display, option); } @@ -1117,7 +1169,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base if (E.name.begins_with("@")) { continue; } - int location = p_recursion_depth + _get_method_location(scr->get_class_name(), E.name); + int location = p_recursion_depth + _get_method_location(scr, E.name); ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION, location); if (E.arguments.size()) { option.insert_text += "("; @@ -1158,7 +1210,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base List<PropertyInfo> pinfo; ClassDB::get_property_list(type, &pinfo); for (const PropertyInfo &E : pinfo) { - if (E.usage & (PROPERTY_USAGE_CATEGORY | PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SUBGROUP)) { + if (E.usage & (PROPERTY_USAGE_CATEGORY | PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SUBGROUP | PROPERTY_USAGE_INTERNAL)) { continue; } if (E.name.contains("/")) { @@ -1221,7 +1273,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base } for (const PropertyInfo &E : members) { - if (E.usage & (PROPERTY_USAGE_CATEGORY | PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SUBGROUP)) { + if (E.usage & (PROPERTY_USAGE_CATEGORY | PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SUBGROUP | PROPERTY_USAGE_INTERNAL)) { continue; } if (!String(E.name).contains("/")) { @@ -2034,6 +2086,21 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, default: break; } + } else { + if (p_context.current_class) { + GDScriptCompletionIdentifier base_identifier; + + GDScriptCompletionIdentifier base; + base.value = p_context.base; + base.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + base.type.kind = GDScriptParser::DataType::CLASS; + base.type.class_type = p_context.current_class; + base.type.is_meta_type = p_context.current_function && p_context.current_function->is_static; + + if (_guess_identifier_type_from_base(p_context, base, p_identifier->name, base_identifier)) { + id_type = base_identifier.type; + } + } } while (suite) { @@ -2087,8 +2154,15 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, if (last_assigned_expression && last_assign_line < p_context.current_line) { GDScriptParser::CompletionContext c = p_context; c.current_line = last_assign_line; - r_type.assigned_expression = last_assigned_expression; - if (_guess_expression_type(c, last_assigned_expression, r_type)) { + GDScriptCompletionIdentifier assigned_type; + if (_guess_expression_type(c, last_assigned_expression, assigned_type)) { + if (id_type.is_set() && assigned_type.type.is_set() && !GDScriptAnalyzer::check_type_compatibility(id_type, assigned_type.type)) { + // The assigned type is incompatible. The annotated type takes priority. + r_type.assigned_expression = last_assigned_expression; + r_type.type = id_type; + } else { + r_type = assigned_type; + } return true; } } @@ -2146,20 +2220,6 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, return true; } - // Check current class (including inheritance). - if (p_context.current_class) { - GDScriptCompletionIdentifier base; - base.value = p_context.base; - base.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; - base.type.kind = GDScriptParser::DataType::CLASS; - base.type.class_type = p_context.current_class; - base.type.is_meta_type = p_context.current_function && p_context.current_function->is_static; - - if (_guess_identifier_type_from_base(p_context, base, p_identifier->name, r_type)) { - return true; - } - } - // Check global scripts. if (ScriptServer::is_global_class(p_identifier->name)) { String script = ScriptServer::get_global_class_path(p_identifier->name); @@ -3454,6 +3514,12 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co } if (ClassDB::has_property(class_name, p_symbol, true)) { + PropertyInfo prop_info; + ClassDB::get_property_info(class_name, p_symbol, &prop_info, true); + if (prop_info.usage & PROPERTY_USAGE_INTERNAL) { + return ERR_CANT_RESOLVE; + } + r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_PROPERTY; r_result.class_name = base_type.native_type; r_result.class_member = p_symbol; diff --git a/modules/gltf/doc_classes/EditorSceneFormatImporterBlend.xml b/modules/gltf/doc_classes/EditorSceneFormatImporterBlend.xml index 24f6dbd887..e3433e6e29 100644 --- a/modules/gltf/doc_classes/EditorSceneFormatImporterBlend.xml +++ b/modules/gltf/doc_classes/EditorSceneFormatImporterBlend.xml @@ -5,7 +5,7 @@ </brief_description> <description> Imports Blender scenes in the [code].blend[/code] file format through the glTF 2.0 3D import pipeline. This importer requires Blender to be installed by the user, so that it can be used to export the scene as glTF 2.0. - The location of the Blender binary is set via the [code]filesystem/import/blender/blender3_path[/code] editor setting. + The location of the Blender binary is set via the [code]filesystem/import/blender/blender_path[/code] editor setting. This importer is only used if [member ProjectSettings.filesystem/import/blender/enabled] is enabled, otherwise [code].blend[/code] files present in the project folder are not imported. Blend import requires Blender 3.0. Internally, the EditorSceneFormatImporterBlend uses the Blender glTF "Use Original" mode to reference external textures. diff --git a/modules/gltf/editor/editor_import_blend_runner.cpp b/modules/gltf/editor/editor_import_blend_runner.cpp index 659a60e6a1..330310d92a 100644 --- a/modules/gltf/editor/editor_import_blend_runner.cpp +++ b/modules/gltf/editor/editor_import_blend_runner.cpp @@ -153,13 +153,7 @@ String dict_to_xmlrpc(const Dictionary &p_dict) { } Error EditorImportBlendRunner::start_blender(const String &p_python_script, bool p_blocking) { - String blender_path = EDITOR_GET("filesystem/import/blender/blender3_path"); - -#ifdef WINDOWS_ENABLED - blender_path = blender_path.path_join("blender.exe"); -#else - blender_path = blender_path.path_join("blender"); -#endif + String blender_path = EDITOR_GET("filesystem/import/blender/blender_path"); List<String> args; args.push_back("--background"); @@ -198,6 +192,40 @@ Error EditorImportBlendRunner::do_import(const Dictionary &p_options) { } } +HTTPClient::Status EditorImportBlendRunner::connect_blender_rpc(const Ref<HTTPClient> &p_client, int p_timeout_usecs) { + p_client->connect_to_host("127.0.0.1", rpc_port); + HTTPClient::Status status = p_client->get_status(); + + int attempts = 1; + int wait_usecs = 1000; + + bool done = false; + while (!done) { + OS::get_singleton()->delay_usec(wait_usecs); + status = p_client->get_status(); + switch (status) { + case HTTPClient::STATUS_RESOLVING: + case HTTPClient::STATUS_CONNECTING: { + p_client->poll(); + break; + } + case HTTPClient::STATUS_CONNECTED: { + done = true; + break; + } + default: { + if (attempts * wait_usecs < p_timeout_usecs) { + p_client->connect_to_host("127.0.0.1", rpc_port); + } else { + return status; + } + } + } + } + + return status; +} + Error EditorImportBlendRunner::do_import_rpc(const Dictionary &p_options) { kill_timer->stop(); @@ -217,25 +245,9 @@ Error EditorImportBlendRunner::do_import_rpc(const Dictionary &p_options) { // Connect to RPC server. Ref<HTTPClient> client = HTTPClient::create(); - client->connect_to_host("127.0.0.1", rpc_port); - - bool done = false; - while (!done) { - HTTPClient::Status status = client->get_status(); - switch (status) { - case HTTPClient::STATUS_RESOLVING: - case HTTPClient::STATUS_CONNECTING: { - client->poll(); - break; - } - case HTTPClient::STATUS_CONNECTED: { - done = true; - break; - } - default: { - ERR_FAIL_V_MSG(ERR_CONNECTION_ERROR, vformat("Unexpected status during RPC connection: %d", status)); - } - } + HTTPClient::Status status = connect_blender_rpc(client, 1000000); + if (status != HTTPClient::STATUS_CONNECTED) { + ERR_FAIL_V_MSG(ERR_CONNECTION_ERROR, vformat("Unexpected status during RPC connection: %d", status)); } // Send XML request. @@ -246,9 +258,9 @@ Error EditorImportBlendRunner::do_import_rpc(const Dictionary &p_options) { } // Wait for response. - done = false; + bool done = false; while (!done) { - HTTPClient::Status status = client->get_status(); + status = client->get_status(); switch (status) { case HTTPClient::STATUS_REQUESTING: { client->poll(); diff --git a/modules/gltf/editor/editor_import_blend_runner.h b/modules/gltf/editor/editor_import_blend_runner.h index b2b82394e1..626f3c9eba 100644 --- a/modules/gltf/editor/editor_import_blend_runner.h +++ b/modules/gltf/editor/editor_import_blend_runner.h @@ -33,6 +33,7 @@ #ifdef TOOLS_ENABLED +#include "core/io/http_client.h" #include "core/os/os.h" #include "scene/main/node.h" #include "scene/main/timer.h" @@ -60,6 +61,7 @@ public: bool is_running() { return blender_pid != 0 && OS::get_singleton()->is_process_running(blender_pid); } bool is_using_rpc() { return rpc_port != 0; } Error do_import(const Dictionary &p_options); + HTTPClient::Status connect_blender_rpc(const Ref<HTTPClient> &p_client, int p_timeout_usecs); EditorImportBlendRunner(); }; diff --git a/modules/gltf/editor/editor_scene_importer_blend.cpp b/modules/gltf/editor/editor_scene_importer_blend.cpp index 4636782063..a91856c4a1 100644 --- a/modules/gltf/editor/editor_scene_importer_blend.cpp +++ b/modules/gltf/editor/editor_scene_importer_blend.cpp @@ -55,20 +55,7 @@ #endif static bool _get_blender_version(const String &p_path, int &r_major, int &r_minor, String *r_err = nullptr) { - String path = p_path; -#ifdef WINDOWS_ENABLED - path = path.path_join("blender.exe"); -#else - path = path.path_join("blender"); -#endif - -#if defined(MACOS_ENABLED) - if (!FileAccess::exists(path)) { - path = p_path.path_join("Blender"); - } -#endif - - if (!FileAccess::exists(path)) { + if (!FileAccess::exists(p_path)) { if (r_err) { *r_err = TTR("Path does not contain a Blender installation."); } @@ -77,7 +64,7 @@ static bool _get_blender_version(const String &p_path, int &r_major, int &r_mino List<String> args; args.push_back("--version"); String pipe; - Error err = OS::get_singleton()->execute(path, args, &pipe); + Error err = OS::get_singleton()->execute(p_path, args, &pipe); if (err != OK) { if (r_err) { *r_err = TTR("Can't execute Blender binary."); @@ -87,7 +74,7 @@ static bool _get_blender_version(const String &p_path, int &r_major, int &r_mino int bl = pipe.find("Blender "); if (bl == -1) { if (r_err) { - *r_err = vformat(TTR("Unexpected --version output from Blender binary at: %s."), path); + *r_err = vformat(TTR("Unexpected --version output from Blender binary at: %s."), p_path); } return false; } @@ -126,7 +113,7 @@ void EditorSceneFormatImporterBlend::get_extensions(List<String> *r_extensions) Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_t p_flags, const HashMap<StringName, Variant> &p_options, List<String> *r_missing_deps, Error *r_err) { - String blender_path = EDITOR_GET("filesystem/import/blender/blender3_path"); + String blender_path = EDITOR_GET("filesystem/import/blender/blender_path"); if (blender_major_version == -1 || blender_minor_version == -1) { _get_blender_version(blender_path, blender_major_version, blender_minor_version, nullptr); @@ -369,7 +356,7 @@ static bool _test_blender_path(const String &p_path, String *r_err = nullptr) { bool EditorFileSystemImportFormatSupportQueryBlend::is_active() const { bool blend_enabled = GLOBAL_GET("filesystem/import/blender/enabled"); - if (blend_enabled && !_test_blender_path(EDITOR_GET("filesystem/import/blender/blender3_path").operator String())) { + if (blend_enabled && !_test_blender_path(EDITOR_GET("filesystem/import/blender/blender_path").operator String())) { // Intending to import Blender, but blend not configured. return true; } @@ -409,11 +396,59 @@ void EditorFileSystemImportFormatSupportQueryBlend::_validate_path(String p_path } } -bool EditorFileSystemImportFormatSupportQueryBlend::_autodetect_path(String p_path) { - if (_test_blender_path(p_path)) { - auto_detected_path = p_path; - return true; +bool EditorFileSystemImportFormatSupportQueryBlend::_autodetect_path() { + // Autodetect + auto_detected_path = ""; + +#if defined(MACOS_ENABLED) + Vector<String> find_paths = { + "/opt/homebrew/bin/blender", + "/opt/local/bin/blender", + "/usr/local/bin/blender", + "/usr/local/opt/blender", + "/Applications/Blender.app/Contents/MacOS/Blender", + }; + { + List<String> mdfind_args; + mdfind_args.push_back("kMDItemCFBundleIdentifier=org.blenderfoundation.blender"); + + String output; + Error err = OS::get_singleton()->execute("mdfind", mdfind_args, &output); + if (err == OK) { + for (const String &find_path : output.split("\n")) { + find_paths.push_back(find_path.path_join("Contents/MacOS/Blender")); + } + } + } +#elif defined(WINDOWS_ENABLED) + Vector<String> find_paths = { + "C:\\Program Files\\Blender Foundation\\blender.exe", + "C:\\Program Files (x86)\\Blender Foundation\\blender.exe", + }; + { + char blender_opener_path[MAX_PATH]; + DWORD path_len = MAX_PATH; + HRESULT res = AssocQueryString(0, ASSOCSTR_EXECUTABLE, ".blend", "open", blender_opener_path, &path_len); + if (res == S_OK) { + find_paths.push_back(String(blender_opener_path).get_base_dir().path_join("blender.exe")); + } } + +#elif defined(UNIX_ENABLED) + Vector<String> find_paths = { + "/usr/bin/blender", + "/usr/local/bin/blender", + "/opt/blender/bin/blender", + }; +#endif + + for (const String &find_path : find_paths) { + if (_test_blender_path(find_path)) { + auto_detected_path = find_path; + return true; + } + } + return false; } @@ -427,7 +462,7 @@ void EditorFileSystemImportFormatSupportQueryBlend::_select_install(String p_pat } void EditorFileSystemImportFormatSupportQueryBlend::_browse_install() { if (blender_path->get_text() != String()) { - browse_dialog->set_current_dir(blender_path->get_text()); + browse_dialog->set_current_file(blender_path->get_text()); } browse_dialog->popup_centered_ratio(); @@ -479,76 +514,10 @@ bool EditorFileSystemImportFormatSupportQueryBlend::query() { EditorNode::get_singleton()->get_gui_base()->add_child(browse_dialog); } - String path = EDITOR_GET("filesystem/import/blender/blender3_path"); + String path = EDITOR_GET("filesystem/import/blender/blender_path"); - if (path == "") { - // Autodetect - auto_detected_path = ""; - -#if defined(MACOS_ENABLED) - - { - Vector<String> mdfind_paths; - { - List<String> mdfind_args; - mdfind_args.push_back("kMDItemCFBundleIdentifier=org.blenderfoundation.blender"); - - String output; - Error err = OS::get_singleton()->execute("mdfind", mdfind_args, &output); - if (err == OK) { - mdfind_paths = output.split("\n"); - } - } - - bool found = false; - for (const String &found_path : mdfind_paths) { - found = _autodetect_path(found_path.path_join("Contents/MacOS")); - if (found) { - break; - } - } - if (!found) { - found = _autodetect_path("/opt/homebrew/bin"); - } - if (!found) { - found = _autodetect_path("/opt/local/bin"); - } - if (!found) { - found = _autodetect_path("/usr/local/bin"); - } - if (!found) { - found = _autodetect_path("/usr/local/opt"); - } - if (!found) { - found = _autodetect_path("/Applications/Blender.app/Contents/MacOS"); - } - } -#elif defined(WINDOWS_ENABLED) - { - char blender_opener_path[MAX_PATH]; - DWORD path_len = MAX_PATH; - HRESULT res = AssocQueryString(0, ASSOCSTR_EXECUTABLE, ".blend", "open", blender_opener_path, &path_len); - if (res == S_OK && _autodetect_path(String(blender_opener_path).get_base_dir())) { - // Good. - } else if (_autodetect_path("C:\\Program Files\\Blender Foundation")) { - // Good. - } else { - _autodetect_path("C:\\Program Files (x86)\\Blender Foundation"); - } - } - -#elif defined(UNIX_ENABLED) - if (_autodetect_path("/usr/bin")) { - // Good. - } else if (_autodetect_path("/usr/local/bin")) { - // Good - } else { - _autodetect_path("/opt/blender/bin"); - } -#endif - if (auto_detected_path != "") { - path = auto_detected_path; - } + if (path.is_empty() && _autodetect_path()) { + path = auto_detected_path; } blender_path->set_text(path); @@ -569,7 +538,7 @@ bool EditorFileSystemImportFormatSupportQueryBlend::query() { if (confirmed) { // Can only confirm a valid path. - EditorSettings::get_singleton()->set("filesystem/import/blender/blender3_path", blender_path->get_text()); + EditorSettings::get_singleton()->set("filesystem/import/blender/blender_path", blender_path->get_text()); EditorSettings::get_singleton()->save(); } else { // Disable Blender import diff --git a/modules/gltf/editor/editor_scene_importer_blend.h b/modules/gltf/editor/editor_scene_importer_blend.h index c1f4280170..ed1b19eaf3 100644 --- a/modules/gltf/editor/editor_scene_importer_blend.h +++ b/modules/gltf/editor/editor_scene_importer_blend.h @@ -95,7 +95,7 @@ class EditorFileSystemImportFormatSupportQueryBlend : public EditorFileSystemImp String auto_detected_path; void _validate_path(String p_path); - bool _autodetect_path(String p_path); + bool _autodetect_path(); void _path_confirmed(); diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index c840889f5e..2acca5b856 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -6079,22 +6079,22 @@ struct SceneFormatImporterGLTFInterpolate { template <> struct SceneFormatImporterGLTFInterpolate<Quaternion> { Quaternion lerp(const Quaternion &a, const Quaternion &b, const float c) const { - ERR_FAIL_COND_V_MSG(!a.is_normalized(), Quaternion(), "The quaternion \"a\" must be normalized."); - ERR_FAIL_COND_V_MSG(!b.is_normalized(), Quaternion(), "The quaternion \"b\" must be normalized."); + ERR_FAIL_COND_V_MSG(!a.is_normalized(), Quaternion(), vformat("The quaternion \"a\" %s must be normalized.", a)); + ERR_FAIL_COND_V_MSG(!b.is_normalized(), Quaternion(), vformat("The quaternion \"b\" %s must be normalized.", b)); return a.slerp(b, c).normalized(); } Quaternion catmull_rom(const Quaternion &p0, const Quaternion &p1, const Quaternion &p2, const Quaternion &p3, const float c) { - ERR_FAIL_COND_V_MSG(!p1.is_normalized(), Quaternion(), "The quaternion \"p1\" must be normalized."); - ERR_FAIL_COND_V_MSG(!p2.is_normalized(), Quaternion(), "The quaternion \"p2\" must be normalized."); + ERR_FAIL_COND_V_MSG(!p1.is_normalized(), Quaternion(), vformat("The quaternion \"p1\" (%s) must be normalized.", p1)); + ERR_FAIL_COND_V_MSG(!p2.is_normalized(), Quaternion(), vformat("The quaternion \"p2\" (%s) must be normalized.", p2)); return p1.slerp(p2, c).normalized(); } Quaternion bezier(const Quaternion start, const Quaternion control_1, const Quaternion control_2, const Quaternion end, const float t) { - ERR_FAIL_COND_V_MSG(!start.is_normalized(), Quaternion(), "The start quaternion must be normalized."); - ERR_FAIL_COND_V_MSG(!end.is_normalized(), Quaternion(), "The end quaternion must be normalized."); + ERR_FAIL_COND_V_MSG(!start.is_normalized(), Quaternion(), vformat("The start quaternion %s must be normalized.", start)); + ERR_FAIL_COND_V_MSG(!end.is_normalized(), Quaternion(), vformat("The end quaternion %s must be normalized.", end)); return start.slerp(end, t).normalized(); } diff --git a/modules/gltf/register_types.cpp b/modules/gltf/register_types.cpp index 94c9d66f78..216309abf1 100644 --- a/modules/gltf/register_types.cpp +++ b/modules/gltf/register_types.cpp @@ -55,23 +55,39 @@ static void _editor_init() { // Blend to glTF importer. + String blender_path = EDITOR_GET("filesystem/import/blender/blender_path"); + if (blender_path.is_empty() && EditorSettings::get_singleton()->has_setting("filesystem/import/blender/blender3_path")) { + blender_path = EditorSettings::get_singleton()->get("filesystem/import/blender/blender3_path"); + + if (!blender_path.is_empty()) { +#if defined(MACOS_ENABLED) + if (blender_path.contains(".app")) { + blender_path += "/Contents/MacOS/Blender"; + } else { + blender_path += "/blender"; + } +#elif defined(WINDOWS_ENABLED) + blender_path += "\\blender.exe"; +#elif defined(UNIX_ENABLED) + blender_path += "/blender"; +#endif + + EditorSettings::get_singleton()->set("filesystem/import/blender/blender_path", blender_path); + } + + EditorSettings::get_singleton()->erase("filesystem/import/blender/blender3_path"); + EditorSettings::get_singleton()->save(); + } + bool blend_enabled = GLOBAL_GET("filesystem/import/blender/enabled"); - String blender3_path = EDITOR_GET("filesystem/import/blender/blender3_path"); if (blend_enabled) { - Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - if (blender3_path.is_empty()) { - WARN_PRINT(TTR("Blend file import is enabled in the project settings, but no Blender path is configured in the editor settings. Blend files will not be imported.")); - } else if (!da->dir_exists(blender3_path)) { - WARN_PRINT(TTR("Blend file import is enabled, but the Blender path doesn't point to an accessible directory. Blend files will not be imported.")); - } else { - Ref<EditorSceneFormatImporterBlend> importer; - importer.instantiate(); - ResourceImporterScene::add_scene_importer(importer); - - Ref<EditorFileSystemImportFormatSupportQueryBlend> blend_import_query; - blend_import_query.instantiate(); - EditorFileSystem::get_singleton()->add_import_format_support_query(blend_import_query); - } + Ref<EditorSceneFormatImporterBlend> importer; + importer.instantiate(); + ResourceImporterScene::add_scene_importer(importer); + + Ref<EditorFileSystemImportFormatSupportQueryBlend> blend_import_query; + blend_import_query.instantiate(); + EditorFileSystem::get_singleton()->add_import_format_support_query(blend_import_query); } memnew(EditorImportBlendRunner); EditorNode::get_singleton()->add_child(EditorImportBlendRunner::get_singleton()); diff --git a/modules/lightmapper_rd/lightmapper_rd.cpp b/modules/lightmapper_rd/lightmapper_rd.cpp index d2fe8a7534..02b5aefc9f 100644 --- a/modules/lightmapper_rd/lightmapper_rd.cpp +++ b/modules/lightmapper_rd/lightmapper_rd.cpp @@ -769,7 +769,7 @@ LightmapperRD::BakeError LightmapperRD::_dilate(RenderingDevice *rd, Ref<RDShade #ifdef DEBUG_TEXTURES for (int i = 0; i < atlas_slices; i++) { - Vector<uint8_t> s = rd->texture_get_data(light_accum_tex, i); + Vector<uint8_t> s = rd->texture_get_data(source_light_tex, i); Ref<Image> img = Image::create_from_data(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAH, s); img->convert(Image::FORMAT_RGBA8); img->save_png("res://5_dilated_" + itos(i) + ".png"); diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index ef24dc35ca..33fef2d58c 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -697,19 +697,25 @@ struct CSharpScriptDepSort { // Shouldn't happen but just in case... return false; } - const Script *I = B->get_base_script().ptr(); + const CSharpScript *I = get_base_script(B.ptr()).ptr(); while (I) { if (I == A.ptr()) { // A is a base of B return true; } - I = I->get_base_script().ptr(); + I = get_base_script(I).ptr(); } // A isn't a base of B return false; } + + // Special fix for constructed generic types. + Ref<CSharpScript> get_base_script(const CSharpScript *p_script) const { + Ref<CSharpScript> base_script = p_script->base_script; + return base_script.is_valid() && !base_script->class_name.is_empty() ? base_script : nullptr; + } }; void CSharpLanguage::reload_all_scripts() { @@ -2897,8 +2903,11 @@ Ref<Resource> ResourceFormatLoaderCSharpScript::load(const String &p_path, const ERR_FAIL_COND_V_MSG(err != OK, Ref<Resource>(), "Cannot load C# script file '" + p_path + "'."); #endif - scr->set_path(p_original_path); + // Only one instance of a C# script is allowed to exist. + ERR_FAIL_COND_V_MSG(!scr->get_path().is_empty() && scr->get_path() != p_original_path, Ref<Resource>(), + "The C# script path is different from the path it was registered in the C# dictionary."); + scr->set_path(p_original_path, true); scr->reload(); if (r_error) { diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h index 310cb81929..918776a6ba 100644 --- a/modules/mono/csharp_script.h +++ b/modules/mono/csharp_script.h @@ -60,6 +60,7 @@ class CSharpScript : public Script { friend class CSharpInstance; friend class CSharpLanguage; + friend struct CSharpScriptDepSort; bool tool = false; bool global_class = false; diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/MustBeVariantSamples.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/MustBeVariantSamples.cs index 1e06091e80..f19da77be6 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/MustBeVariantSamples.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/MustBeVariantSamples.cs @@ -131,8 +131,253 @@ public class ClassWithGenericVariant<[MustBeVariant] T> public class MustBeVariantAnnotatedMethods { + [GenericTypeAttribute<bool>()] + public void MethodWithAttributeBool() + { + } + + [GenericTypeAttribute<char>()] + public void MethodWithAttributeChar() + { + } + + [GenericTypeAttribute<sbyte>()] + public void MethodWithAttributeSByte() + { + } + + [GenericTypeAttribute<byte>()] + public void MethodWithAttributeByte() + { + } + + [GenericTypeAttribute<short>()] + public void MethodWithAttributeInt16() + { + } + + [GenericTypeAttribute<ushort>()] + public void MethodWithAttributeUInt16() + { + } + + [GenericTypeAttribute<int>()] + public void MethodWithAttributeInt32() + { + } + + [GenericTypeAttribute<uint>()] + public void MethodWithAttributeUInt32() + { + } + + [GenericTypeAttribute<long>()] + public void MethodWithAttributeInt64() + { + } + + [GenericTypeAttribute<ulong>()] + public void MethodWithAttributeUInt64() + { + } + + [GenericTypeAttribute<float>()] + public void MethodWithAttributeSingle() + { + } + + [GenericTypeAttribute<double>()] + public void MethodWithAttributeDouble() + { + } + [GenericTypeAttribute<string>()] - public void MethodWithAttributeOk() + public void MethodWithAttributeString() + { + } + + [GenericTypeAttribute<Vector2>()] + public void MethodWithAttributeVector2() + { + } + + [GenericTypeAttribute<Vector2I>()] + public void MethodWithAttributeVector2I() + { + } + + [GenericTypeAttribute<Rect2>()] + public void MethodWithAttributeRect2() + { + } + + [GenericTypeAttribute<Rect2I>()] + public void MethodWithAttributeRect2I() + { + } + + [GenericTypeAttribute<Transform2D>()] + public void MethodWithAttributeTransform2D() + { + } + + [GenericTypeAttribute<Vector3>()] + public void MethodWithAttributeVector3() + { + } + + [GenericTypeAttribute<Vector3I>()] + public void MethodWithAttributeVector3I() + { + } + + [GenericTypeAttribute<Vector4>()] + public void MethodWithAttributeVector4() + { + } + + [GenericTypeAttribute<Vector4I>()] + public void MethodWithAttributeVector4I() + { + } + + [GenericTypeAttribute<Basis>()] + public void MethodWithAttributeBasis() + { + } + + [GenericTypeAttribute<Quaternion>()] + public void MethodWithAttributeQuaternion() + { + } + + [GenericTypeAttribute<Transform3D>()] + public void MethodWithAttributeTransform3D() + { + } + + [GenericTypeAttribute<Projection>()] + public void MethodWithAttributeProjection() + { + } + + [GenericTypeAttribute<Aabb>()] + public void MethodWithAttributeAabb() + { + } + + [GenericTypeAttribute<Color>()] + public void MethodWithAttributeColor() + { + } + + [GenericTypeAttribute<Plane>()] + public void MethodWithAttributePlane() + { + } + + [GenericTypeAttribute<Callable>()] + public void MethodWithAttributeCallable() + { + } + + [GenericTypeAttribute<Signal>()] + public void MethodWithAttributeSignal() + { + } + + [GenericTypeAttribute<GodotObject>()] + public void MethodWithAttributeGodotObject() + { + } + + [GenericTypeAttribute<StringName>()] + public void MethodWithAttributeStringName() + { + } + + [GenericTypeAttribute<NodePath>()] + public void MethodWithAttributeNodePath() + { + } + + [GenericTypeAttribute<Rid>()] + public void MethodWithAttributeRid() + { + } + + [GenericTypeAttribute<Dictionary>()] + public void MethodWithAttributeDictionary() + { + } + + [GenericTypeAttribute<Array>()] + public void MethodWithAttributeArray() + { + } + + [GenericTypeAttribute<byte[]>()] + public void MethodWithAttributeByteArray() + { + } + + [GenericTypeAttribute<int[]>()] + public void MethodWithAttributeInt32Array() + { + } + + [GenericTypeAttribute<long[]>()] + public void MethodWithAttributeInt64Array() + { + } + + [GenericTypeAttribute<float[]>()] + public void MethodWithAttributeSingleArray() + { + } + + [GenericTypeAttribute<double[]>()] + public void MethodWithAttributeDoubleArray() + { + } + + [GenericTypeAttribute<string[]>()] + public void MethodWithAttributeStringArray() + { + } + + [GenericTypeAttribute<Vector2[]>()] + public void MethodWithAttributeVector2Array() + { + } + + [GenericTypeAttribute<Vector3[]>()] + public void MethodWithAttributeVector3Array() + { + } + + [GenericTypeAttribute<Color[]>()] + public void MethodWithAttributeColorArray() + { + } + + [GenericTypeAttribute<GodotObject[]>()] + public void MethodWithAttributeGodotObjectArray() + { + } + + [GenericTypeAttribute<StringName[]>()] + public void MethodWithAttributeStringNameArray() + { + } + + [GenericTypeAttribute<NodePath[]>()] + public void MethodWithAttributeNodePathArray() + { + } + + [GenericTypeAttribute<Rid[]>()] + public void MethodWithAttributeRidArray() { } @@ -145,8 +390,253 @@ public class MustBeVariantAnnotatedMethods */ } +[GenericTypeAttribute<bool>()] +public class ClassVariantAnnotatedBool +{ +} + +[GenericTypeAttribute<char>()] +public class ClassVariantAnnotatedChar +{ +} + +[GenericTypeAttribute<sbyte>()] +public class ClassVariantAnnotatedSByte +{ +} + +[GenericTypeAttribute<byte>()] +public class ClassVariantAnnotatedByte +{ +} + +[GenericTypeAttribute<short>()] +public class ClassVariantAnnotatedInt16 +{ +} + +[GenericTypeAttribute<ushort>()] +public class ClassVariantAnnotatedUInt16 +{ +} + +[GenericTypeAttribute<int>()] +public class ClassVariantAnnotatedInt32 +{ +} + +[GenericTypeAttribute<uint>()] +public class ClassVariantAnnotatedUInt32 +{ +} + +[GenericTypeAttribute<long>()] +public class ClassVariantAnnotatedInt64 +{ +} + +[GenericTypeAttribute<ulong>()] +public class ClassVariantAnnotatedUInt64 +{ +} + +[GenericTypeAttribute<float>()] +public class ClassVariantAnnotatedSingle +{ +} + +[GenericTypeAttribute<double>()] +public class ClassVariantAnnotatedDouble +{ +} + [GenericTypeAttribute<string>()] -public class ClassVariantAnnotated +public class ClassVariantAnnotatedString +{ +} + +[GenericTypeAttribute<Vector2>()] +public class ClassVariantAnnotatedVector2 +{ +} + +[GenericTypeAttribute<Vector2I>()] +public class ClassVariantAnnotatedVector2I +{ +} + +[GenericTypeAttribute<Rect2>()] +public class ClassVariantAnnotatedRect2 +{ +} + +[GenericTypeAttribute<Rect2I>()] +public class ClassVariantAnnotatedRect2I +{ +} + +[GenericTypeAttribute<Transform2D>()] +public class ClassVariantAnnotatedTransform2D +{ +} + +[GenericTypeAttribute<Vector3>()] +public class ClassVariantAnnotatedVector3 +{ +} + +[GenericTypeAttribute<Vector3I>()] +public class ClassVariantAnnotatedVector3I +{ +} + +[GenericTypeAttribute<Vector4>()] +public class ClassVariantAnnotatedVector4 +{ +} + +[GenericTypeAttribute<Vector4I>()] +public class ClassVariantAnnotatedVector4I +{ +} + +[GenericTypeAttribute<Basis>()] +public class ClassVariantAnnotatedBasis +{ +} + +[GenericTypeAttribute<Quaternion>()] +public class ClassVariantAnnotatedQuaternion +{ +} + +[GenericTypeAttribute<Transform3D>()] +public class ClassVariantAnnotatedTransform3D +{ +} + +[GenericTypeAttribute<Projection>()] +public class ClassVariantAnnotatedProjection +{ +} + +[GenericTypeAttribute<Aabb>()] +public class ClassVariantAnnotatedAabb +{ +} + +[GenericTypeAttribute<Color>()] +public class ClassVariantAnnotatedColor +{ +} + +[GenericTypeAttribute<Plane>()] +public class ClassVariantAnnotatedPlane +{ +} + +[GenericTypeAttribute<Callable>()] +public class ClassVariantAnnotatedCallable +{ +} + +[GenericTypeAttribute<Signal>()] +public class ClassVariantAnnotatedSignal +{ +} + +[GenericTypeAttribute<GodotObject>()] +public class ClassVariantAnnotatedGodotObject +{ +} + +[GenericTypeAttribute<StringName>()] +public class ClassVariantAnnotatedStringName +{ +} + +[GenericTypeAttribute<NodePath>()] +public class ClassVariantAnnotatedNodePath +{ +} + +[GenericTypeAttribute<Rid>()] +public class ClassVariantAnnotatedRid +{ +} + +[GenericTypeAttribute<Dictionary>()] +public class ClassVariantAnnotatedDictionary +{ +} + +[GenericTypeAttribute<Array>()] +public class ClassVariantAnnotatedArray +{ +} + +[GenericTypeAttribute<byte[]>()] +public class ClassVariantAnnotatedByteArray +{ +} + +[GenericTypeAttribute<int[]>()] +public class ClassVariantAnnotatedInt32Array +{ +} + +[GenericTypeAttribute<long[]>()] +public class ClassVariantAnnotatedInt64Array +{ +} + +[GenericTypeAttribute<float[]>()] +public class ClassVariantAnnotatedSingleArray +{ +} + +[GenericTypeAttribute<double[]>()] +public class ClassVariantAnnotatedDoubleArray +{ +} + +[GenericTypeAttribute<string[]>()] +public class ClassVariantAnnotatedStringArray +{ +} + +[GenericTypeAttribute<Vector2[]>()] +public class ClassVariantAnnotatedVector2Array +{ +} + +[GenericTypeAttribute<Vector3[]>()] +public class ClassVariantAnnotatedVector3Array +{ +} + +[GenericTypeAttribute<Color[]>()] +public class ClassVariantAnnotatedColorArray +{ +} + +[GenericTypeAttribute<GodotObject[]>()] +public class ClassVariantAnnotatedGodotObjectArray +{ +} + +[GenericTypeAttribute<StringName[]>()] +public class ClassVariantAnnotatedStringNameArray +{ +} + +[GenericTypeAttribute<NodePath[]>()] +public class ClassVariantAnnotatedNodePathArray +{ +} + +[GenericTypeAttribute<Rid[]>()] +public class ClassVariantAnnotatedRidArray { } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/CSharpAnalyzerVerifier.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/CSharpAnalyzerVerifier.cs index e3e7373b2e..253889296b 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/CSharpAnalyzerVerifier.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/CSharpAnalyzerVerifier.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Testing; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Testing; @@ -12,8 +13,10 @@ using Microsoft.CodeAnalysis.Text; namespace Godot.SourceGenerators.Tests; public static class CSharpAnalyzerVerifier<TAnalyzer> -where TAnalyzer : DiagnosticAnalyzer, new() + where TAnalyzer : DiagnosticAnalyzer, new() { + public const LanguageVersion LangVersion = LanguageVersion.CSharp11; + public class Test : CSharpAnalyzerTest<TAnalyzer, XUnitVerifier> { public Test() @@ -24,7 +27,7 @@ where TAnalyzer : DiagnosticAnalyzer, new() { Project project = solution.GetProject(projectId)!.AddMetadataReference(Constants.GodotSharpAssembly - .CreateMetadataReference()); + .CreateMetadataReference()).WithParseOptions(new CSharpParseOptions(LangVersion)); return project.Solution; }); diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/Godot.SourceGenerators.Tests.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/Godot.SourceGenerators.Tests.csproj index 13e54a543f..32d7ca7486 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/Godot.SourceGenerators.Tests.csproj +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/Godot.SourceGenerators.Tests.csproj @@ -15,6 +15,7 @@ </PropertyGroup> <ItemGroup> + <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.8.0" /> <PackageReference Include="Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit" Version="1.1.1" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.1" /> <PackageReference Include="Microsoft.CodeAnalysis.Testing.Verifiers.XUnit" Version="1.1.1" /> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0301.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0301.cs index 031039cba1..462da31d66 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0301.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0301.cs @@ -10,6 +10,7 @@ public class MustBeVariantGD0301 // This raises a GD0301 diagnostic error: object is not Variant (and Method<T> requires a variant generic type). Method<{|GD0301:object|}>(); } + public void MethodCallsOk() { // All these calls are valid because they are Variant types. @@ -68,4 +69,585 @@ public class MustBeVariantGD0301 public void Method<[MustBeVariant] T>() { } + + public void MustBeVariantClasses() + { + new ClassWithGenericVariant<bool>(); + new ClassWithGenericVariant<char>(); + new ClassWithGenericVariant<sbyte>(); + new ClassWithGenericVariant<byte>(); + new ClassWithGenericVariant<short>(); + new ClassWithGenericVariant<ushort>(); + new ClassWithGenericVariant<int>(); + new ClassWithGenericVariant<uint>(); + new ClassWithGenericVariant<long>(); + new ClassWithGenericVariant<ulong>(); + new ClassWithGenericVariant<float>(); + new ClassWithGenericVariant<double>(); + new ClassWithGenericVariant<string>(); + new ClassWithGenericVariant<Vector2>(); + new ClassWithGenericVariant<Vector2I>(); + new ClassWithGenericVariant<Rect2>(); + new ClassWithGenericVariant<Rect2I>(); + new ClassWithGenericVariant<Transform2D>(); + new ClassWithGenericVariant<Vector3>(); + new ClassWithGenericVariant<Vector3I>(); + new ClassWithGenericVariant<Vector4>(); + new ClassWithGenericVariant<Vector4I>(); + new ClassWithGenericVariant<Basis>(); + new ClassWithGenericVariant<Quaternion>(); + new ClassWithGenericVariant<Transform3D>(); + new ClassWithGenericVariant<Projection>(); + new ClassWithGenericVariant<Aabb>(); + new ClassWithGenericVariant<Color>(); + new ClassWithGenericVariant<Plane>(); + new ClassWithGenericVariant<Callable>(); + new ClassWithGenericVariant<Signal>(); + new ClassWithGenericVariant<GodotObject>(); + new ClassWithGenericVariant<StringName>(); + new ClassWithGenericVariant<NodePath>(); + new ClassWithGenericVariant<Rid>(); + new ClassWithGenericVariant<Dictionary>(); + new ClassWithGenericVariant<Array>(); + new ClassWithGenericVariant<byte[]>(); + new ClassWithGenericVariant<int[]>(); + new ClassWithGenericVariant<long[]>(); + new ClassWithGenericVariant<float[]>(); + new ClassWithGenericVariant<double[]>(); + new ClassWithGenericVariant<string[]>(); + new ClassWithGenericVariant<Vector2[]>(); + new ClassWithGenericVariant<Vector3[]>(); + new ClassWithGenericVariant<Color[]>(); + new ClassWithGenericVariant<GodotObject[]>(); + new ClassWithGenericVariant<StringName[]>(); + new ClassWithGenericVariant<NodePath[]>(); + new ClassWithGenericVariant<Rid[]>(); + + // This class fails because generic type is not Variant-compatible. + new ClassWithGenericVariant<{|GD0301:object|}>(); + } +} + +public class ClassWithGenericVariant<[MustBeVariant] T> +{ +} + +public class MustBeVariantAnnotatedMethods +{ + [GenericTypeAttribute<bool>()] + public void MethodWithAttributeBool() + { + } + + [GenericTypeAttribute<char>()] + public void MethodWithAttributeChar() + { + } + + [GenericTypeAttribute<sbyte>()] + public void MethodWithAttributeSByte() + { + } + + [GenericTypeAttribute<byte>()] + public void MethodWithAttributeByte() + { + } + + [GenericTypeAttribute<short>()] + public void MethodWithAttributeInt16() + { + } + + [GenericTypeAttribute<ushort>()] + public void MethodWithAttributeUInt16() + { + } + + [GenericTypeAttribute<int>()] + public void MethodWithAttributeInt32() + { + } + + [GenericTypeAttribute<uint>()] + public void MethodWithAttributeUInt32() + { + } + + [GenericTypeAttribute<long>()] + public void MethodWithAttributeInt64() + { + } + + [GenericTypeAttribute<ulong>()] + public void MethodWithAttributeUInt64() + { + } + + [GenericTypeAttribute<float>()] + public void MethodWithAttributeSingle() + { + } + + [GenericTypeAttribute<double>()] + public void MethodWithAttributeDouble() + { + } + + [GenericTypeAttribute<string>()] + public void MethodWithAttributeString() + { + } + + [GenericTypeAttribute<Vector2>()] + public void MethodWithAttributeVector2() + { + } + + [GenericTypeAttribute<Vector2I>()] + public void MethodWithAttributeVector2I() + { + } + + [GenericTypeAttribute<Rect2>()] + public void MethodWithAttributeRect2() + { + } + + [GenericTypeAttribute<Rect2I>()] + public void MethodWithAttributeRect2I() + { + } + + [GenericTypeAttribute<Transform2D>()] + public void MethodWithAttributeTransform2D() + { + } + + [GenericTypeAttribute<Vector3>()] + public void MethodWithAttributeVector3() + { + } + + [GenericTypeAttribute<Vector3I>()] + public void MethodWithAttributeVector3I() + { + } + + [GenericTypeAttribute<Vector4>()] + public void MethodWithAttributeVector4() + { + } + + [GenericTypeAttribute<Vector4I>()] + public void MethodWithAttributeVector4I() + { + } + + [GenericTypeAttribute<Basis>()] + public void MethodWithAttributeBasis() + { + } + + [GenericTypeAttribute<Quaternion>()] + public void MethodWithAttributeQuaternion() + { + } + + [GenericTypeAttribute<Transform3D>()] + public void MethodWithAttributeTransform3D() + { + } + + [GenericTypeAttribute<Projection>()] + public void MethodWithAttributeProjection() + { + } + + [GenericTypeAttribute<Aabb>()] + public void MethodWithAttributeAabb() + { + } + + [GenericTypeAttribute<Color>()] + public void MethodWithAttributeColor() + { + } + + [GenericTypeAttribute<Plane>()] + public void MethodWithAttributePlane() + { + } + + [GenericTypeAttribute<Callable>()] + public void MethodWithAttributeCallable() + { + } + + [GenericTypeAttribute<Signal>()] + public void MethodWithAttributeSignal() + { + } + + [GenericTypeAttribute<GodotObject>()] + public void MethodWithAttributeGodotObject() + { + } + + [GenericTypeAttribute<StringName>()] + public void MethodWithAttributeStringName() + { + } + + [GenericTypeAttribute<NodePath>()] + public void MethodWithAttributeNodePath() + { + } + + [GenericTypeAttribute<Rid>()] + public void MethodWithAttributeRid() + { + } + + [GenericTypeAttribute<Dictionary>()] + public void MethodWithAttributeDictionary() + { + } + + [GenericTypeAttribute<Array>()] + public void MethodWithAttributeArray() + { + } + + [GenericTypeAttribute<byte[]>()] + public void MethodWithAttributeByteArray() + { + } + + [GenericTypeAttribute<int[]>()] + public void MethodWithAttributeInt32Array() + { + } + + [GenericTypeAttribute<long[]>()] + public void MethodWithAttributeInt64Array() + { + } + + [GenericTypeAttribute<float[]>()] + public void MethodWithAttributeSingleArray() + { + } + + [GenericTypeAttribute<double[]>()] + public void MethodWithAttributeDoubleArray() + { + } + + [GenericTypeAttribute<string[]>()] + public void MethodWithAttributeStringArray() + { + } + + [GenericTypeAttribute<Vector2[]>()] + public void MethodWithAttributeVector2Array() + { + } + + [GenericTypeAttribute<Vector3[]>()] + public void MethodWithAttributeVector3Array() + { + } + + [GenericTypeAttribute<Color[]>()] + public void MethodWithAttributeColorArray() + { + } + + [GenericTypeAttribute<GodotObject[]>()] + public void MethodWithAttributeGodotObjectArray() + { + } + + [GenericTypeAttribute<StringName[]>()] + public void MethodWithAttributeStringNameArray() + { + } + + [GenericTypeAttribute<NodePath[]>()] + public void MethodWithAttributeNodePathArray() + { + } + + [GenericTypeAttribute<Rid[]>()] + public void MethodWithAttributeRidArray() + { + } + + // This method definition fails because generic type is not Variant-compatible. + [GenericTypeAttribute<{|GD0301:object|}>()] + public void MethodWithWrongAttribute() + { + } +} + +[GenericTypeAttribute<bool>()] +public class ClassVariantAnnotatedBool +{ +} + +[GenericTypeAttribute<char>()] +public class ClassVariantAnnotatedChar +{ +} + +[GenericTypeAttribute<sbyte>()] +public class ClassVariantAnnotatedSByte +{ +} + +[GenericTypeAttribute<byte>()] +public class ClassVariantAnnotatedByte +{ +} + +[GenericTypeAttribute<short>()] +public class ClassVariantAnnotatedInt16 +{ +} + +[GenericTypeAttribute<ushort>()] +public class ClassVariantAnnotatedUInt16 +{ +} + +[GenericTypeAttribute<int>()] +public class ClassVariantAnnotatedInt32 +{ +} + +[GenericTypeAttribute<uint>()] +public class ClassVariantAnnotatedUInt32 +{ +} + +[GenericTypeAttribute<long>()] +public class ClassVariantAnnotatedInt64 +{ +} + +[GenericTypeAttribute<ulong>()] +public class ClassVariantAnnotatedUInt64 +{ +} + +[GenericTypeAttribute<float>()] +public class ClassVariantAnnotatedSingle +{ +} + +[GenericTypeAttribute<double>()] +public class ClassVariantAnnotatedDouble +{ +} + +[GenericTypeAttribute<string>()] +public class ClassVariantAnnotatedString +{ +} + +[GenericTypeAttribute<Vector2>()] +public class ClassVariantAnnotatedVector2 +{ +} + +[GenericTypeAttribute<Vector2I>()] +public class ClassVariantAnnotatedVector2I +{ +} + +[GenericTypeAttribute<Rect2>()] +public class ClassVariantAnnotatedRect2 +{ +} + +[GenericTypeAttribute<Rect2I>()] +public class ClassVariantAnnotatedRect2I +{ +} + +[GenericTypeAttribute<Transform2D>()] +public class ClassVariantAnnotatedTransform2D +{ +} + +[GenericTypeAttribute<Vector3>()] +public class ClassVariantAnnotatedVector3 +{ +} + +[GenericTypeAttribute<Vector3I>()] +public class ClassVariantAnnotatedVector3I +{ +} + +[GenericTypeAttribute<Vector4>()] +public class ClassVariantAnnotatedVector4 +{ +} + +[GenericTypeAttribute<Vector4I>()] +public class ClassVariantAnnotatedVector4I +{ +} + +[GenericTypeAttribute<Basis>()] +public class ClassVariantAnnotatedBasis +{ +} + +[GenericTypeAttribute<Quaternion>()] +public class ClassVariantAnnotatedQuaternion +{ +} + +[GenericTypeAttribute<Transform3D>()] +public class ClassVariantAnnotatedTransform3D +{ +} + +[GenericTypeAttribute<Projection>()] +public class ClassVariantAnnotatedProjection +{ +} + +[GenericTypeAttribute<Aabb>()] +public class ClassVariantAnnotatedAabb +{ +} + +[GenericTypeAttribute<Color>()] +public class ClassVariantAnnotatedColor +{ +} + +[GenericTypeAttribute<Plane>()] +public class ClassVariantAnnotatedPlane +{ +} + +[GenericTypeAttribute<Callable>()] +public class ClassVariantAnnotatedCallable +{ +} + +[GenericTypeAttribute<Signal>()] +public class ClassVariantAnnotatedSignal +{ +} + +[GenericTypeAttribute<GodotObject>()] +public class ClassVariantAnnotatedGodotObject +{ +} + +[GenericTypeAttribute<StringName>()] +public class ClassVariantAnnotatedStringName +{ +} + +[GenericTypeAttribute<NodePath>()] +public class ClassVariantAnnotatedNodePath +{ +} + +[GenericTypeAttribute<Rid>()] +public class ClassVariantAnnotatedRid +{ +} + +[GenericTypeAttribute<Dictionary>()] +public class ClassVariantAnnotatedDictionary +{ +} + +[GenericTypeAttribute<Array>()] +public class ClassVariantAnnotatedArray +{ +} + +[GenericTypeAttribute<byte[]>()] +public class ClassVariantAnnotatedByteArray +{ +} + +[GenericTypeAttribute<int[]>()] +public class ClassVariantAnnotatedInt32Array +{ +} + +[GenericTypeAttribute<long[]>()] +public class ClassVariantAnnotatedInt64Array +{ +} + +[GenericTypeAttribute<float[]>()] +public class ClassVariantAnnotatedSingleArray +{ +} + +[GenericTypeAttribute<double[]>()] +public class ClassVariantAnnotatedDoubleArray +{ +} + +[GenericTypeAttribute<string[]>()] +public class ClassVariantAnnotatedStringArray +{ +} + +[GenericTypeAttribute<Vector2[]>()] +public class ClassVariantAnnotatedVector2Array +{ +} + +[GenericTypeAttribute<Vector3[]>()] +public class ClassVariantAnnotatedVector3Array +{ +} + +[GenericTypeAttribute<Color[]>()] +public class ClassVariantAnnotatedColorArray +{ +} + +[GenericTypeAttribute<GodotObject[]>()] +public class ClassVariantAnnotatedGodotObjectArray +{ +} + +[GenericTypeAttribute<StringName[]>()] +public class ClassVariantAnnotatedStringNameArray +{ +} + +[GenericTypeAttribute<NodePath[]>()] +public class ClassVariantAnnotatedNodePathArray +{ +} + +[GenericTypeAttribute<Rid[]>()] +public class ClassVariantAnnotatedRidArray +{ +} + +// This class definition fails because generic type is not Variant-compatible. +[GenericTypeAttribute<{|GD0301:object|}>()] +public class ClassNonVariantAnnotated +{ +} + +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)] +public class GenericTypeAttribute<[MustBeVariant] T> : Attribute +{ } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/CodeAnalysisAttributes.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/CodeAnalysisAttributes.cs new file mode 100644 index 0000000000..fa591bc873 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/CodeAnalysisAttributes.cs @@ -0,0 +1,7 @@ +namespace System.Diagnostics.CodeAnalysis +{ + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue)] + public sealed class NotNullAttribute : Attribute + { + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj index 7d2395ba61..b1578fb5f4 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj @@ -1,7 +1,7 @@ <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>netstandard2.0</TargetFramework> - <LangVersion>9.0</LangVersion> + <LangVersion>10</LangVersion> <Nullable>enable</Nullable> </PropertyGroup> <PropertyGroup> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Helper.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Helper.cs new file mode 100644 index 0000000000..aecf127686 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Helper.cs @@ -0,0 +1,15 @@ +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; + +namespace Godot.SourceGenerators +{ + public static class Helper + { + [Conditional("DEBUG")] + public static void ThrowIfNull([NotNull] object? value) + { + _ = value ?? throw new ArgumentNullException(); + } + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MustBeVariantAnalyzer.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MustBeVariantAnalyzer.cs index 2a9758516c..b4f78fd218 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MustBeVariantAnalyzer.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MustBeVariantAnalyzer.cs @@ -1,5 +1,4 @@ using System.Collections.Immutable; -using System.Diagnostics; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -34,7 +33,7 @@ namespace Godot.SourceGenerators // Method invocation or variable declaration that contained the type arguments var parentSyntax = context.Node.Parent; - Debug.Assert(parentSyntax != null); + Helper.ThrowIfNull(parentSyntax); var sm = context.SemanticModel; @@ -49,9 +48,10 @@ namespace Godot.SourceGenerators continue; var typeSymbol = sm.GetSymbolInfo(typeSyntax).Symbol as ITypeSymbol; - Debug.Assert(typeSymbol != null); + Helper.ThrowIfNull(typeSymbol); var parentSymbol = sm.GetSymbolInfo(parentSyntax).Symbol; + Helper.ThrowIfNull(parentSymbol); if (!ShouldCheckTypeArgument(context, parentSyntax, parentSymbol, typeSyntax, typeSymbol, i)) { @@ -69,7 +69,7 @@ namespace Godot.SourceGenerators var marshalType = MarshalUtils.ConvertManagedTypeToMarshalType(typeSymbol, typeCache); - if (marshalType == null) + if (marshalType is null) { Common.ReportGenericTypeArgumentMustBeVariant(context, typeSyntax, typeSymbol); continue; @@ -109,11 +109,19 @@ namespace Godot.SourceGenerators /// <returns><see langword="true"/> if the type must be variant and must be analyzed.</returns> private bool ShouldCheckTypeArgument(SyntaxNodeAnalysisContext context, SyntaxNode parentSyntax, ISymbol parentSymbol, TypeSyntax typeArgumentSyntax, ITypeSymbol typeArgumentSymbol, int typeArgumentIndex) { - var typeParamSymbol = parentSymbol switch + ITypeParameterSymbol? typeParamSymbol = parentSymbol switch { - IMethodSymbol methodSymbol => methodSymbol.TypeParameters[typeArgumentIndex], - INamedTypeSymbol typeSymbol => typeSymbol.TypeParameters[typeArgumentIndex], - _ => null, + IMethodSymbol methodSymbol when parentSyntax.Parent is AttributeSyntax && + methodSymbol.ContainingType.TypeParameters.Length > 0 + => methodSymbol.ContainingType.TypeParameters[typeArgumentIndex], + + IMethodSymbol { TypeParameters.Length: > 0 } methodSymbol + => methodSymbol.TypeParameters[typeArgumentIndex], + + INamedTypeSymbol { TypeParameters.Length: > 0 } typeSymbol + => typeSymbol.TypeParameters[typeArgumentIndex], + _ + => null }; if (typeParamSymbol == null) diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs index 7232e4d7d7..2aa0519269 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs @@ -144,7 +144,7 @@ namespace Godot.SourceGenerators .Append(" /// </summary>\n"); source.Append( - $" public new class MethodName : {symbol.BaseType.FullQualifiedNameIncludeGlobal()}.MethodName {{\n"); + $" public new class MethodName : {symbol.BaseType!.FullQualifiedNameIncludeGlobal()}.MethodName {{\n"); // Generate cached StringNames for methods and properties, for fast lookup diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs index de44ada6de..6e034c6e72 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs @@ -133,7 +133,7 @@ namespace Godot.SourceGenerators .Append(" /// </summary>\n"); source.Append( - $" public new class PropertyName : {symbol.BaseType.FullQualifiedNameIncludeGlobal()}.PropertyName {{\n"); + $" public new class PropertyName : {symbol.BaseType!.FullQualifiedNameIncludeGlobal()}.PropertyName {{\n"); // Generate cached StringNames for methods and properties, for fast lookup diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs index 5409d1a961..5246cc5780 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs @@ -185,7 +185,7 @@ namespace Godot.SourceGenerators .Append(" /// </summary>\n"); source.Append( - $" public new class SignalName : {symbol.BaseType.FullQualifiedNameIncludeGlobal()}.SignalName {{\n"); + $" public new class SignalName : {symbol.BaseType!.FullQualifiedNameIncludeGlobal()}.SignalName {{\n"); // Generate cached StringNames for methods and properties, for fast lookup diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsView.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsView.cs index b23b3f42ef..93b5a6c7f8 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsView.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsView.cs @@ -516,7 +516,7 @@ namespace GodotTools.Build public override void _Ready() { - var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); + var editorSettings = EditorInterface.Singleton.GetEditorSettings(); _layout = editorSettings.GetSetting(GodotSharpEditor.Settings.ProblemsLayout).As<ProblemsLayout>(); Name = "Problems".TTR(); @@ -655,7 +655,7 @@ namespace GodotTools.Build switch ((long)what) { case EditorSettings.NotificationEditorSettingsChanged: - var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); + var editorSettings = EditorInterface.Singleton.GetEditorSettings(); _layout = editorSettings.GetSetting(GodotSharpEditor.Settings.ProblemsLayout).As<ProblemsLayout>(); _toggleLayoutButton.ButtonPressed = GetToggleLayoutPressedState(); UpdateProblemsView(); diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 25a5720bc4..675651ccf7 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -1621,9 +1621,13 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str << CLOSE_BLOCK_L2 CLOSE_BLOCK_L1; } else { // Hide the constructor - output.append(MEMBER_BEGIN "internal "); - output.append(itype.proxy_name); - output.append("() {}\n"); + output << MEMBER_BEGIN "internal " << itype.proxy_name << "() : this(" + << (itype.memory_own ? "true" : "false") << ")\n" OPEN_BLOCK_L1 + << INDENT2 "unsafe\n" INDENT2 OPEN_BLOCK + << INDENT3 "_ConstructAndInitialize(null, " + << BINDINGS_NATIVE_NAME_FIELD ", CachedType, refCounted: " + << (itype.is_ref_counted ? "true" : "false") << ");\n" + << CLOSE_BLOCK_L2 CLOSE_BLOCK_L1; } // Add.. em.. trick constructor. Sort of. @@ -2237,10 +2241,6 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf p_output.append(INDENT1 "/// </summary>"); } - - if (p_imethod.method_doc->is_deprecated) { - p_output.append(MEMBER_BEGIN "[Obsolete(\"This method is deprecated.\")]"); - } } if (default_args_doc.get_string_length()) { @@ -2255,6 +2255,8 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf p_output.append(MEMBER_BEGIN "[Obsolete(\""); p_output.append(p_imethod.deprecation_message); p_output.append("\")]"); + } else if (p_imethod.method_doc && p_imethod.method_doc->is_deprecated) { + p_output.append(MEMBER_BEGIN "[Obsolete(\"This method is deprecated.\")]"); } if (p_imethod.is_compat) { diff --git a/modules/mono/glue/GodotSharp/.editorconfig b/modules/mono/glue/GodotSharp/.editorconfig index df4a6c2d0d..987e6c543b 100644 --- a/modules/mono/glue/GodotSharp/.editorconfig +++ b/modules/mono/glue/GodotSharp/.editorconfig @@ -6,6 +6,9 @@ dotnet_diagnostic.CA1062.severity = error dotnet_diagnostic.CA1069.severity = none # CA1708: Identifiers should differ by more than case dotnet_diagnostic.CA1708.severity = none +# CA1716: Identifiers should not match keywords +# This is suppressed, because it will report `@event` as well as `event` +dotnet_diagnostic.CA1716.severity = none # CS1591: Missing XML comment for publicly visible type or member dotnet_diagnostic.CS1591.severity = none # CS1573: Parameter has no matching param tag in the XML comment diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs index c7420dcf7e..fa74d5e101 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs @@ -16,7 +16,9 @@ namespace Godot.Collections /// interfacing with the engine. Otherwise prefer .NET collections /// such as <see cref="System.Array"/> or <see cref="List{T}"/>. /// </summary> +#pragma warning disable CA1710 // Identifiers should have correct suffix public sealed class Array : +#pragma warning restore CA1710 IList<Variant>, IReadOnlyList<Variant>, ICollection, diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs index 8f8e884b8c..84b2a04276 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs @@ -38,6 +38,8 @@ namespace Godot { if (NativePtr == IntPtr.Zero) { + Debug.Assert(nativeCtor != null); + NativePtr = nativeCtor(); InteropUtils.TieManagedToUnmanaged(this, NativePtr, diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs index d53bb9f536..e89bbbd370 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.IO; using System.Security; using System.Security.Cryptography; using System.Text; @@ -220,7 +219,7 @@ namespace Godot { if (hasText) { - sb.Append(instance.Substring(indentStop, i - indentStop)); + sb.Append(instance.AsSpan(indentStop, i - indentStop)); } sb.Append('\n'); hasText = false; @@ -252,7 +251,7 @@ namespace Godot if (hasText) { - sb.Append(instance.Substring(indentStop, instance.Length - indentStop)); + sb.Append(instance.AsSpan(indentStop, instance.Length - indentStop)); } return sb.ToString(); @@ -323,7 +322,7 @@ namespace Godot string slice = aux.GetSliceCharacter(' ', i); if (slice.Length > 0) { - slice = char.ToUpper(slice[0]) + slice.Substring(1); + slice = char.ToUpperInvariant(slice[0]) + slice.Substring(1); if (i > 0) cap += " "; cap += slice; @@ -408,13 +407,13 @@ namespace Godot bool shouldSplit = condA || condB || condC || canBreakNumberLetter || canBreakLetterNumber; if (shouldSplit) { - newString += instance.Substring(startIndex, i - startIndex) + "_"; + newString += string.Concat(instance.AsSpan(startIndex, i - startIndex), "_"); startIndex = i; } } newString += instance.Substring(startIndex, instance.Length - startIndex); - return lowerCase ? newString.ToLower() : newString; + return lowerCase ? newString.ToLowerInvariant() : newString; } /// <summary> @@ -479,9 +478,9 @@ namespace Godot return -1; // If this is empty, and the other one is not, then we're less... I think? if (to[toIndex] == 0) return 1; // Otherwise the other one is smaller.. - if (char.ToUpper(instance[instanceIndex]) < char.ToUpper(to[toIndex])) // More than + if (char.ToUpperInvariant(instance[instanceIndex]) < char.ToUpperInvariant(to[toIndex])) // More than return -1; - if (char.ToUpper(instance[instanceIndex]) > char.ToUpper(to[toIndex])) // Less than + if (char.ToUpperInvariant(instance[instanceIndex]) > char.ToUpperInvariant(to[toIndex])) // Less than return 1; instanceIndex++; @@ -853,7 +852,7 @@ namespace Godot else { sb.Append(prefix); - sb.Append(instance.Substring(lineStart, i - lineStart + 1)); + sb.Append(instance.AsSpan(lineStart, i - lineStart + 1)); } lineStart = i + 1; } @@ -861,7 +860,7 @@ namespace Godot if (lineStart != instance.Length) { sb.Append(prefix); - sb.Append(instance.Substring(lineStart)); + sb.Append(instance.AsSpan(lineStart)); } return sb.ToString(); } @@ -925,8 +924,8 @@ namespace Godot if (!caseSensitive) { - char sourcec = char.ToLower(instance[source]); - char targetc = char.ToLower(text[target]); + char sourcec = char.ToLowerInvariant(instance[source]); + char targetc = char.ToLowerInvariant(text[target]); match = sourcec == targetc; } else @@ -1234,7 +1233,7 @@ namespace Godot return false; if (caseSensitive) return instance[0] == expr[0]; - return (char.ToUpper(instance[0]) == char.ToUpper(expr[0])) && + return (char.ToUpperInvariant(instance[0]) == char.ToUpperInvariant(expr[0])) && ExprMatch(instance.Substring(1), expr.Substring(1), caseSensitive); } } diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index 3eb746677d..7ce2232aa4 100644 --- a/modules/mono/mono_gd/gd_mono.cpp +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -395,6 +395,7 @@ void GDMono::initialize() { if (godot_plugins_initialize != nullptr) { is_native_aot = true; + runtime_initialized = true; } else { ERR_FAIL_MSG(".NET: Failed to load hostfxr"); } diff --git a/modules/openxr/SCsub b/modules/openxr/SCsub index 4cf810f905..285c58a96e 100644 --- a/modules/openxr/SCsub +++ b/modules/openxr/SCsub @@ -93,36 +93,13 @@ if env["builtin_openxr"]: module_obj = [] env_openxr.add_source_files(module_obj, "*.cpp") -env_openxr.add_source_files(module_obj, "action_map/*.cpp") -env_openxr.add_source_files(module_obj, "scene/*.cpp") +env.modules_sources += module_obj -# We're a little more targeted with our extensions -if env["platform"] == "android": - env_openxr.add_source_files(module_obj, "extensions/openxr_android_extension.cpp") -if env["vulkan"]: - env_openxr.add_source_files(module_obj, "extensions/openxr_vulkan_extension.cpp") -if env["opengl3"] and env["platform"] != "macos": - env_openxr.add_source_files(module_obj, "extensions/openxr_opengl_extension.cpp") - -env_openxr.add_source_files(module_obj, "extensions/openxr_palm_pose_extension.cpp") -env_openxr.add_source_files(module_obj, "extensions/openxr_composition_layer_depth_extension.cpp") -env_openxr.add_source_files(module_obj, "extensions/openxr_eye_gaze_interaction.cpp") -env_openxr.add_source_files(module_obj, "extensions/openxr_htc_controller_extension.cpp") -env_openxr.add_source_files(module_obj, "extensions/openxr_htc_vive_tracker_extension.cpp") -env_openxr.add_source_files(module_obj, "extensions/openxr_huawei_controller_extension.cpp") -env_openxr.add_source_files(module_obj, "extensions/openxr_hand_tracking_extension.cpp") -env_openxr.add_source_files(module_obj, "extensions/openxr_fb_foveation_extension.cpp") -env_openxr.add_source_files(module_obj, "extensions/openxr_fb_update_swapchain_extension.cpp") -env_openxr.add_source_files(module_obj, "extensions/openxr_fb_passthrough_extension_wrapper.cpp") -env_openxr.add_source_files(module_obj, "extensions/openxr_fb_display_refresh_rate_extension.cpp") -env_openxr.add_source_files(module_obj, "extensions/openxr_pico_controller_extension.cpp") -env_openxr.add_source_files(module_obj, "extensions/openxr_wmr_controller_extension.cpp") -env_openxr.add_source_files(module_obj, "extensions/openxr_ml2_controller_extension.cpp") -env_openxr.add_source_files(module_obj, "extensions/openxr_extension_wrapper_extension.cpp") -env_openxr.add_source_files(module_obj, "extensions/openxr_api_extension.cpp") -env_openxr.add_source_files(module_obj, "extensions/openxr_meta_controller_extension.cpp") +Export("env_openxr") -env.modules_sources += module_obj +SConscript("action_map/SCsub") +SConscript("extensions/SCsub") +SConscript("scene/SCsub") if env.editor_build: SConscript("editor/SCsub") diff --git a/modules/openxr/action_map/SCsub b/modules/openxr/action_map/SCsub new file mode 100644 index 0000000000..7a493011ec --- /dev/null +++ b/modules/openxr/action_map/SCsub @@ -0,0 +1,10 @@ +#!/usr/bin/env python + +Import("env") +Import("env_openxr") + +module_obj = [] + +env_openxr.add_source_files(module_obj, "*.cpp") + +env.modules_sources += module_obj diff --git a/modules/openxr/extensions/SCsub b/modules/openxr/extensions/SCsub new file mode 100644 index 0000000000..1bd9cfaa22 --- /dev/null +++ b/modules/openxr/extensions/SCsub @@ -0,0 +1,18 @@ +#!/usr/bin/env python + +Import("env") +Import("env_openxr") + +module_obj = [] + +env_openxr.add_source_files(module_obj, "*.cpp") + +# These are platform dependent +if env["platform"] == "android": + env_openxr.add_source_files(module_obj, "platform/openxr_android_extension.cpp") +if env["vulkan"]: + env_openxr.add_source_files(module_obj, "platform/openxr_vulkan_extension.cpp") +if env["opengl3"] and env["platform"] != "macos": + env_openxr.add_source_files(module_obj, "platform/openxr_opengl_extension.cpp") + +env.modules_sources += module_obj diff --git a/modules/openxr/extensions/openxr_local_floor_extension.cpp b/modules/openxr/extensions/openxr_local_floor_extension.cpp new file mode 100644 index 0000000000..8e06dd8ed5 --- /dev/null +++ b/modules/openxr/extensions/openxr_local_floor_extension.cpp @@ -0,0 +1,59 @@ +/**************************************************************************/ +/* openxr_local_floor_extension.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 "openxr_local_floor_extension.h" + +#include "core/string/print_string.h" + +OpenXRLocalFloorExtension *OpenXRLocalFloorExtension::singleton = nullptr; + +OpenXRLocalFloorExtension *OpenXRLocalFloorExtension::get_singleton() { + return singleton; +} + +OpenXRLocalFloorExtension::OpenXRLocalFloorExtension() { + singleton = this; +} + +OpenXRLocalFloorExtension::~OpenXRLocalFloorExtension() { + singleton = nullptr; +} + +HashMap<String, bool *> OpenXRLocalFloorExtension::get_requested_extensions() { + HashMap<String, bool *> request_extensions; + + request_extensions[XR_EXT_LOCAL_FLOOR_EXTENSION_NAME] = &available; + + return request_extensions; +} + +bool OpenXRLocalFloorExtension::is_available() { + return available; +} diff --git a/modules/openxr/extensions/openxr_local_floor_extension.h b/modules/openxr/extensions/openxr_local_floor_extension.h new file mode 100644 index 0000000000..dff97d9954 --- /dev/null +++ b/modules/openxr/extensions/openxr_local_floor_extension.h @@ -0,0 +1,53 @@ +/**************************************************************************/ +/* openxr_local_floor_extension.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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. */ +/**************************************************************************/ + +#ifndef OPENXR_LOCAL_FLOOR_EXTENSION_H +#define OPENXR_LOCAL_FLOOR_EXTENSION_H + +#include "openxr_extension_wrapper.h" + +class OpenXRLocalFloorExtension : public OpenXRExtensionWrapper { +public: + static OpenXRLocalFloorExtension *get_singleton(); + + OpenXRLocalFloorExtension(); + virtual ~OpenXRLocalFloorExtension() override; + + virtual HashMap<String, bool *> get_requested_extensions() override; + + bool is_available(); + +private: + static OpenXRLocalFloorExtension *singleton; + + bool available = false; +}; + +#endif // OPENXR_LOCAL_FLOOR_EXTENSION_H diff --git a/modules/openxr/extensions/openxr_android_extension.cpp b/modules/openxr/extensions/platform/openxr_android_extension.cpp index c6082ca404..de542828c3 100644 --- a/modules/openxr/extensions/openxr_android_extension.cpp +++ b/modules/openxr/extensions/platform/openxr_android_extension.cpp @@ -30,7 +30,7 @@ #include "openxr_android_extension.h" -#include "../openxr_api.h" +#include "../../openxr_api.h" #include "java_godot_wrapper.h" #include "os_android.h" diff --git a/modules/openxr/extensions/openxr_android_extension.h b/modules/openxr/extensions/platform/openxr_android_extension.h index 0e7c44d6d5..e51b5824e8 100644 --- a/modules/openxr/extensions/openxr_android_extension.h +++ b/modules/openxr/extensions/platform/openxr_android_extension.h @@ -31,8 +31,8 @@ #ifndef OPENXR_ANDROID_EXTENSION_H #define OPENXR_ANDROID_EXTENSION_H -#include "../util.h" -#include "openxr_extension_wrapper.h" +#include "../../util.h" +#include "../openxr_extension_wrapper.h" class OpenXRAndroidExtension : public OpenXRExtensionWrapper { public: diff --git a/modules/openxr/extensions/openxr_opengl_extension.cpp b/modules/openxr/extensions/platform/openxr_opengl_extension.cpp index 9038e9f458..a9d970bbb9 100644 --- a/modules/openxr/extensions/openxr_opengl_extension.cpp +++ b/modules/openxr/extensions/platform/openxr_opengl_extension.cpp @@ -32,7 +32,7 @@ #ifdef GLES3_ENABLED -#include "../openxr_util.h" +#include "../../openxr_util.h" #include "drivers/gles3/effects/copy_effects.h" #include "drivers/gles3/storage/texture_storage.h" diff --git a/modules/openxr/extensions/openxr_opengl_extension.h b/modules/openxr/extensions/platform/openxr_opengl_extension.h index 5f529829a7..a3052d3f53 100644 --- a/modules/openxr/extensions/openxr_opengl_extension.h +++ b/modules/openxr/extensions/platform/openxr_opengl_extension.h @@ -33,14 +33,14 @@ #ifdef GLES3_ENABLED -#include "../openxr_api.h" -#include "../util.h" -#include "openxr_extension_wrapper.h" +#include "../../openxr_api.h" +#include "../../util.h" +#include "../openxr_extension_wrapper.h" #include "core/templates/vector.h" // Always include this as late as possible. -#include "../openxr_platform_inc.h" +#include "../../openxr_platform_inc.h" class OpenXROpenGLExtension : public OpenXRGraphicsExtensionWrapper { public: diff --git a/modules/openxr/extensions/openxr_vulkan_extension.cpp b/modules/openxr/extensions/platform/openxr_vulkan_extension.cpp index 9429d9e082..a2f2577959 100644 --- a/modules/openxr/extensions/openxr_vulkan_extension.cpp +++ b/modules/openxr/extensions/platform/openxr_vulkan_extension.cpp @@ -30,7 +30,7 @@ #include "openxr_vulkan_extension.h" -#include "../openxr_util.h" +#include "../../openxr_util.h" #include "core/string/print_string.h" #include "servers/rendering/renderer_rd/effects/copy_effects.h" diff --git a/modules/openxr/extensions/openxr_vulkan_extension.h b/modules/openxr/extensions/platform/openxr_vulkan_extension.h index 86c0f327dd..2d0973bb6b 100644 --- a/modules/openxr/extensions/openxr_vulkan_extension.h +++ b/modules/openxr/extensions/platform/openxr_vulkan_extension.h @@ -31,14 +31,14 @@ #ifndef OPENXR_VULKAN_EXTENSION_H #define OPENXR_VULKAN_EXTENSION_H -#include "../openxr_api.h" -#include "../util.h" -#include "openxr_extension_wrapper.h" +#include "../../openxr_api.h" +#include "../../util.h" +#include "../openxr_extension_wrapper.h" #include "core/templates/vector.h" // Always include this as late as possible. -#include "../openxr_platform_inc.h" +#include "../../openxr_platform_inc.h" class OpenXRVulkanExtension : public OpenXRGraphicsExtensionWrapper, VulkanHooks { public: diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp index da6fd2e9b2..eafabe03e7 100644 --- a/modules/openxr/openxr_api.cpp +++ b/modules/openxr/openxr_api.cpp @@ -46,11 +46,11 @@ #include "openxr_platform_inc.h" #ifdef VULKAN_ENABLED -#include "extensions/openxr_vulkan_extension.h" +#include "extensions/platform/openxr_vulkan_extension.h" #endif #if defined(GLES3_ENABLED) && !defined(MACOS_ENABLED) -#include "extensions/openxr_opengl_extension.h" +#include "extensions/platform/openxr_opengl_extension.h" #endif #include "extensions/openxr_composition_layer_depth_extension.h" @@ -665,13 +665,6 @@ bool OpenXRAPI::load_supported_reference_spaces() { print_verbose(String("OpenXR: Found supported reference space ") + OpenXRUtil::get_reference_space_name(supported_reference_spaces[i])); } - // Check value we loaded at startup... - if (!is_reference_space_supported(reference_space)) { - print_verbose(String("OpenXR: ") + OpenXRUtil::get_reference_space_name(reference_space) + String(" isn't supported, defaulting to ") + OpenXRUtil::get_reference_space_name(supported_reference_spaces[0])); - - reference_space = supported_reference_spaces[0]; - } - return true; } @@ -699,16 +692,31 @@ bool OpenXRAPI::setup_spaces() { // create play space { - if (!is_reference_space_supported(reference_space)) { - print_line("OpenXR: reference space ", OpenXRUtil::get_reference_space_name(reference_space), " is not supported."); - return false; + emulating_local_floor = false; + + if (is_reference_space_supported(requested_reference_space)) { + reference_space = requested_reference_space; + } else if (requested_reference_space == XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT && is_reference_space_supported(XR_REFERENCE_SPACE_TYPE_STAGE)) { + print_verbose("OpenXR: LOCAL_FLOOR space isn't supported, emulating using STAGE and LOCAL spaces."); + + reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL; + emulating_local_floor = true; + + // We'll use the STAGE space to get the floor height, but we can't do that until + // after xrWaitFrame(), so just set this flag for now. + should_reset_emulated_floor_height = true; + + } else { + // Fallback on LOCAL, which all OpenXR runtimes are required to support. + print_verbose(String("OpenXR: ") + OpenXRUtil::get_reference_space_name(requested_reference_space) + String(" isn't supported, defaulting to LOCAL space.")); + reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL; } XrReferenceSpaceCreateInfo play_space_create_info = { XR_TYPE_REFERENCE_SPACE_CREATE_INFO, // type nullptr, // next reference_space, // referenceSpaceType - identityPose // poseInReferenceSpace + identityPose, // poseInReferenceSpace }; result = xrCreateReferenceSpace(session, &play_space_create_info, &play_space); @@ -742,6 +750,80 @@ bool OpenXRAPI::setup_spaces() { return true; } +bool OpenXRAPI::reset_emulated_floor_height() { + ERR_FAIL_COND_V(!emulating_local_floor, false); + + // This is based on the example code in the OpenXR spec which shows how to + // emulate LOCAL_FLOOR if it's not supported. + // See: https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#XR_EXT_local_floor + + XrResult result; + + XrPosef identityPose = { + { 0.0, 0.0, 0.0, 1.0 }, + { 0.0, 0.0, 0.0 } + }; + + XrSpace local_space = XR_NULL_HANDLE; + XrSpace stage_space = XR_NULL_HANDLE; + + XrReferenceSpaceCreateInfo create_info = { + XR_TYPE_REFERENCE_SPACE_CREATE_INFO, // type + nullptr, // next + XR_REFERENCE_SPACE_TYPE_LOCAL, // referenceSpaceType + identityPose, // poseInReferenceSpace + }; + + result = xrCreateReferenceSpace(session, &create_info, &local_space); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to create LOCAL space in order to emulate LOCAL_FLOOR [", get_error_string(result), "]"); + return false; + } + + create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE; + result = xrCreateReferenceSpace(session, &create_info, &stage_space); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to create STAGE space in order to emulate LOCAL_FLOOR [", get_error_string(result), "]"); + xrDestroySpace(local_space); + return false; + } + + XrSpaceLocation stage_location = { + XR_TYPE_SPACE_LOCATION, // type + nullptr, // next + 0, // locationFlags + identityPose, // pose + }; + + result = xrLocateSpace(stage_space, local_space, get_next_frame_time(), &stage_location); + + xrDestroySpace(local_space); + xrDestroySpace(stage_space); + + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to locate STAGE space in LOCAL space, in order to emulate LOCAL_FLOOR [", get_error_string(result), "]"); + return false; + } + + XrSpace new_play_space; + create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL; + create_info.poseInReferenceSpace.position.y = stage_location.pose.position.y; + result = xrCreateReferenceSpace(session, &create_info, &new_play_space); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to recreate emulated LOCAL_FLOOR play space with latest floor estimate [", get_error_string(result), "]"); + return false; + } + + xrDestroySpace(play_space); + play_space = new_play_space; + + // If we've made it this far, it means we can properly emulate LOCAL_FLOOR, so we'll + // report that as the reference space to the outside world. + reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT; + + return true; +} + bool OpenXRAPI::load_supported_swapchain_formats() { ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); @@ -1180,10 +1262,10 @@ void OpenXRAPI::set_view_configuration(XrViewConfigurationType p_view_configurat view_configuration = p_view_configuration; } -void OpenXRAPI::set_reference_space(XrReferenceSpaceType p_reference_space) { +void OpenXRAPI::set_requested_reference_space(XrReferenceSpaceType p_requested_reference_space) { ERR_FAIL_COND(is_initialized()); - reference_space = p_reference_space; + requested_reference_space = p_requested_reference_space; } void OpenXRAPI::set_submit_depth_buffer(bool p_submit_depth_buffer) { @@ -1628,6 +1710,9 @@ bool OpenXRAPI::poll_events() { XrEventDataReferenceSpaceChangePending *event = (XrEventDataReferenceSpaceChangePending *)&runtimeEvent; print_verbose(String("OpenXR EVENT: reference space type ") + OpenXRUtil::get_reference_space_name(event->referenceSpaceType) + " change pending!"); + if (emulating_local_floor) { + should_reset_emulated_floor_height = true; + } if (event->poseValid && xr_interface) { xr_interface->on_pose_recentered(); } @@ -1783,6 +1868,11 @@ void OpenXRAPI::pre_render() { frame_state.predictedDisplayPeriod = 0; } + if (unlikely(should_reset_emulated_floor_height)) { + reset_emulated_floor_height(); + should_reset_emulated_floor_height = false; + } + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { wrapper->on_pre_render(); } @@ -2136,10 +2226,13 @@ OpenXRAPI::OpenXRAPI() { int reference_space_setting = GLOBAL_GET("xr/openxr/reference_space"); switch (reference_space_setting) { case 0: { - reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL; + requested_reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL; } break; case 1: { - reference_space = XR_REFERENCE_SPACE_TYPE_STAGE; + requested_reference_space = XR_REFERENCE_SPACE_TYPE_STAGE; + } break; + case 2: { + requested_reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT; } break; default: break; diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h index efa32b7544..6e55020aef 100644 --- a/modules/openxr/openxr_api.h +++ b/modules/openxr/openxr_api.h @@ -98,7 +98,8 @@ private: // configuration XrFormFactor form_factor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY; XrViewConfigurationType view_configuration = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; - XrReferenceSpaceType reference_space = XR_REFERENCE_SPACE_TYPE_STAGE; + XrReferenceSpaceType requested_reference_space = XR_REFERENCE_SPACE_TYPE_STAGE; + XrReferenceSpaceType reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL; bool submit_depth_buffer = false; // if set to true we submit depth buffers to OpenXR if a suitable extension is enabled. // blend mode @@ -149,6 +150,10 @@ private: bool view_pose_valid = false; XRPose::TrackingConfidence head_pose_confidence = XRPose::XR_TRACKING_CONFIDENCE_NONE; + bool emulating_local_floor = false; + bool should_reset_emulated_floor_height = false; + bool reset_emulated_floor_height(); + bool load_layer_properties(); bool load_supported_extensions(); bool is_extension_supported(const String &p_extension) const; @@ -333,7 +338,8 @@ public: void set_view_configuration(XrViewConfigurationType p_view_configuration); XrViewConfigurationType get_view_configuration() const { return view_configuration; } - void set_reference_space(XrReferenceSpaceType p_reference_space); + void set_requested_reference_space(XrReferenceSpaceType p_requested_reference_space); + XrReferenceSpaceType get_requested_reference_space() const { return requested_reference_space; } XrReferenceSpaceType get_reference_space() const { return reference_space; } void set_submit_depth_buffer(bool p_submit_depth_buffer); diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp index ceeb1b0278..6b311b73a8 100644 --- a/modules/openxr/openxr_interface.cpp +++ b/modules/openxr/openxr_interface.cpp @@ -686,15 +686,48 @@ Dictionary OpenXRInterface::get_system_info() { } bool OpenXRInterface::supports_play_area_mode(XRInterface::PlayAreaMode p_mode) { - return false; + if (p_mode == XRInterface::XR_PLAY_AREA_3DOF) { + return false; + } + return true; } XRInterface::PlayAreaMode OpenXRInterface::get_play_area_mode() const { + if (!openxr_api || !initialized) { + return XRInterface::XR_PLAY_AREA_UNKNOWN; + } + + XrReferenceSpaceType reference_space = openxr_api->get_reference_space(); + + if (reference_space == XR_REFERENCE_SPACE_TYPE_LOCAL) { + return XRInterface::XR_PLAY_AREA_SITTING; + } else if (reference_space == XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT) { + return XRInterface::XR_PLAY_AREA_ROOMSCALE; + } else if (reference_space == XR_REFERENCE_SPACE_TYPE_STAGE) { + return XRInterface::XR_PLAY_AREA_STAGE; + } + return XRInterface::XR_PLAY_AREA_UNKNOWN; } bool OpenXRInterface::set_play_area_mode(XRInterface::PlayAreaMode p_mode) { - return false; + ERR_FAIL_COND_V_MSG(initialized, false, "Cannot change play area mode after OpenXR interface has been initialized"); + ERR_FAIL_NULL_V(openxr_api, false); + + XrReferenceSpaceType reference_space; + + if (p_mode == XRInterface::XR_PLAY_AREA_SITTING) { + reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL; + } else if (p_mode == XRInterface::XR_PLAY_AREA_ROOMSCALE) { + reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT; + } else if (p_mode == XRInterface::XR_PLAY_AREA_STAGE) { + reference_space = XR_REFERENCE_SPACE_TYPE_STAGE; + } else { + return false; + } + + openxr_api->set_requested_reference_space(reference_space); + return true; } PackedVector3Array OpenXRInterface::get_play_area() const { diff --git a/modules/openxr/register_types.cpp b/modules/openxr/register_types.cpp index 5cc793dca3..04411a0c57 100644 --- a/modules/openxr/register_types.cpp +++ b/modules/openxr/register_types.cpp @@ -49,6 +49,7 @@ #include "extensions/openxr_htc_controller_extension.h" #include "extensions/openxr_htc_vive_tracker_extension.h" #include "extensions/openxr_huawei_controller_extension.h" +#include "extensions/openxr_local_floor_extension.h" #include "extensions/openxr_meta_controller_extension.h" #include "extensions/openxr_ml2_controller_extension.h" #include "extensions/openxr_palm_pose_extension.h" @@ -60,7 +61,7 @@ #endif #ifdef ANDROID_ENABLED -#include "extensions/openxr_android_extension.h" +#include "extensions/platform/openxr_android_extension.h" #endif #include "core/config/project_settings.h" @@ -107,6 +108,7 @@ void initialize_openxr_module(ModuleInitializationLevel p_level) { // register our other extensions OpenXRAPI::register_extension_wrapper(memnew(OpenXRPalmPoseExtension)); + OpenXRAPI::register_extension_wrapper(memnew(OpenXRLocalFloorExtension)); OpenXRAPI::register_extension_wrapper(memnew(OpenXRPicoControllerExtension)); OpenXRAPI::register_extension_wrapper(memnew(OpenXRCompositionLayerDepthExtension)); OpenXRAPI::register_extension_wrapper(memnew(OpenXRHTCControllerExtension)); diff --git a/modules/openxr/scene/SCsub b/modules/openxr/scene/SCsub new file mode 100644 index 0000000000..7a493011ec --- /dev/null +++ b/modules/openxr/scene/SCsub @@ -0,0 +1,10 @@ +#!/usr/bin/env python + +Import("env") +Import("env_openxr") + +module_obj = [] + +env_openxr.add_source_files(module_obj, "*.cpp") + +env.modules_sources += module_obj diff --git a/platform/android/android_input_handler.cpp b/platform/android/android_input_handler.cpp index bd194478d9..8e7f355114 100644 --- a/platform/android/android_input_handler.cpp +++ b/platform/android/android_input_handler.cpp @@ -124,6 +124,7 @@ void AndroidInputHandler::process_key_event(int p_physical_keycode, int p_unicod ev->set_physical_keycode(physical_keycode); ev->set_key_label(fix_key_label(p_key_label, keycode)); ev->set_unicode(fix_unicode(unicode)); + ev->set_location(godot_location_from_android_code(p_physical_keycode)); ev->set_pressed(p_pressed); ev->set_echo(p_echo); diff --git a/platform/android/android_keys_utils.cpp b/platform/android/android_keys_utils.cpp index f50437e82a..83ee98e8bc 100644 --- a/platform/android/android_keys_utils.cpp +++ b/platform/android/android_keys_utils.cpp @@ -38,3 +38,12 @@ Key godot_code_from_android_code(unsigned int p_code) { } return Key::UNKNOWN; } + +KeyLocation godot_location_from_android_code(unsigned int p_code) { + for (int i = 0; android_godot_location_pairs[i].android_code != AKEYCODE_MAX; i++) { + if (android_godot_location_pairs[i].android_code == p_code) { + return android_godot_location_pairs[i].godot_code; + } + } + return KeyLocation::UNSPECIFIED; +} diff --git a/platform/android/android_keys_utils.h b/platform/android/android_keys_utils.h index 5cf5628a8b..77c0911f2b 100644 --- a/platform/android/android_keys_utils.h +++ b/platform/android/android_keys_utils.h @@ -177,4 +177,24 @@ static AndroidGodotCodePair android_godot_code_pairs[] = { Key godot_code_from_android_code(unsigned int p_code); +// Key location determination. +struct AndroidGodotLocationPair { + unsigned int android_code = 0; + KeyLocation godot_code = KeyLocation::UNSPECIFIED; +}; + +static AndroidGodotLocationPair android_godot_location_pairs[] = { + { AKEYCODE_ALT_LEFT, KeyLocation::LEFT }, + { AKEYCODE_ALT_RIGHT, KeyLocation::RIGHT }, + { AKEYCODE_SHIFT_LEFT, KeyLocation::LEFT }, + { AKEYCODE_SHIFT_RIGHT, KeyLocation::RIGHT }, + { AKEYCODE_CTRL_LEFT, KeyLocation::LEFT }, + { AKEYCODE_CTRL_RIGHT, KeyLocation::RIGHT }, + { AKEYCODE_META_LEFT, KeyLocation::LEFT }, + { AKEYCODE_META_RIGHT, KeyLocation::RIGHT }, + { AKEYCODE_MAX, KeyLocation::UNSPECIFIED } +}; + +KeyLocation godot_location_from_android_code(unsigned int p_code); + #endif // ANDROID_KEYS_UTILS_H diff --git a/platform/ios/display_server_ios.h b/platform/ios/display_server_ios.h index 3d19222fa8..3fdcc07f0b 100644 --- a/platform/ios/display_server_ios.h +++ b/platform/ios/display_server_ios.h @@ -119,7 +119,7 @@ public: // MARK: Keyboard - void key(Key p_key, char32_t p_char, Key p_unshifted, Key p_physical, NSInteger p_modifier, bool p_pressed); + void key(Key p_key, char32_t p_char, Key p_unshifted, Key p_physical, NSInteger p_modifier, bool p_pressed, KeyLocation p_location); bool is_keyboard_active() const; // MARK: Motion diff --git a/platform/ios/display_server_ios.mm b/platform/ios/display_server_ios.mm index c31f503605..c660dc5697 100644 --- a/platform/ios/display_server_ios.mm +++ b/platform/ios/display_server_ios.mm @@ -247,7 +247,7 @@ void DisplayServerIOS::touches_canceled(int p_idx) { // MARK: Keyboard -void DisplayServerIOS::key(Key p_key, char32_t p_char, Key p_unshifted, Key p_physical, NSInteger p_modifier, bool p_pressed) { +void DisplayServerIOS::key(Key p_key, char32_t p_char, Key p_unshifted, Key p_physical, NSInteger p_modifier, bool p_pressed, KeyLocation p_location) { Ref<InputEventKey> ev; ev.instantiate(); ev->set_echo(false); @@ -270,6 +270,7 @@ void DisplayServerIOS::key(Key p_key, char32_t p_char, Key p_unshifted, Key p_ph ev->set_key_label(p_unshifted); ev->set_physical_keycode(p_physical); ev->set_unicode(fix_unicode(p_char)); + ev->set_location(p_location); perform_event(ev); } diff --git a/platform/ios/key_mapping_ios.h b/platform/ios/key_mapping_ios.h index 6cc61175bb..8874da3024 100644 --- a/platform/ios/key_mapping_ios.h +++ b/platform/ios/key_mapping_ios.h @@ -41,6 +41,7 @@ class KeyMappingIOS { public: static void initialize(); static Key remap_key(CFIndex p_keycode); + static KeyLocation key_location(CFIndex p_keycode); }; #endif // KEY_MAPPING_IOS_H diff --git a/platform/ios/key_mapping_ios.mm b/platform/ios/key_mapping_ios.mm index d2c84884d1..61f28aa84b 100644 --- a/platform/ios/key_mapping_ios.mm +++ b/platform/ios/key_mapping_ios.mm @@ -38,6 +38,7 @@ struct HashMapHasherKeys { }; HashMap<CFIndex, Key, HashMapHasherKeys> keyusage_map; +HashMap<CFIndex, KeyLocation, HashMapHasherKeys> location_map; void KeyMappingIOS::initialize() { if (@available(iOS 13.4, *)) { @@ -172,6 +173,15 @@ void KeyMappingIOS::initialize() { keyusage_map[0x029D] = Key::GLOBE; // "Globe" key on smart connector / Mac keyboard. keyusage_map[UIKeyboardHIDUsageKeyboardLANG1] = Key::JIS_EISU; keyusage_map[UIKeyboardHIDUsageKeyboardLANG2] = Key::JIS_KANA; + + location_map[UIKeyboardHIDUsageKeyboardLeftAlt] = KeyLocation::LEFT; + location_map[UIKeyboardHIDUsageKeyboardRightAlt] = KeyLocation::RIGHT; + location_map[UIKeyboardHIDUsageKeyboardLeftControl] = KeyLocation::LEFT; + location_map[UIKeyboardHIDUsageKeyboardRightControl] = KeyLocation::RIGHT; + location_map[UIKeyboardHIDUsageKeyboardLeftShift] = KeyLocation::LEFT; + location_map[UIKeyboardHIDUsageKeyboardRightShift] = KeyLocation::RIGHT; + location_map[UIKeyboardHIDUsageKeyboardLeftGUI] = KeyLocation::LEFT; + location_map[UIKeyboardHIDUsageKeyboardRightGUI] = KeyLocation::RIGHT; } } @@ -184,3 +194,13 @@ Key KeyMappingIOS::remap_key(CFIndex p_keycode) { } return Key::NONE; } + +KeyLocation KeyMappingIOS::key_location(CFIndex p_keycode) { + if (@available(iOS 13.4, *)) { + const KeyLocation *location = location_map.getptr(p_keycode); + if (location) { + return *location; + } + } + return KeyLocation::UNSPECIFIED; +} diff --git a/platform/ios/keyboard_input_view.mm b/platform/ios/keyboard_input_view.mm index bc6eb63ed5..8b614662b7 100644 --- a/platform/ios/keyboard_input_view.mm +++ b/platform/ios/keyboard_input_view.mm @@ -116,8 +116,8 @@ - (void)deleteText:(NSInteger)charactersToDelete { for (int i = 0; i < charactersToDelete; i++) { - DisplayServerIOS::get_singleton()->key(Key::BACKSPACE, 0, Key::BACKSPACE, Key::NONE, 0, true); - DisplayServerIOS::get_singleton()->key(Key::BACKSPACE, 0, Key::BACKSPACE, Key::NONE, 0, false); + DisplayServerIOS::get_singleton()->key(Key::BACKSPACE, 0, Key::BACKSPACE, Key::NONE, 0, true, KeyLocation::UNSPECIFIED); + DisplayServerIOS::get_singleton()->key(Key::BACKSPACE, 0, Key::BACKSPACE, Key::NONE, 0, false, KeyLocation::UNSPECIFIED); } } @@ -137,8 +137,8 @@ key = Key::SPACE; } - DisplayServerIOS::get_singleton()->key(key, character, key, Key::NONE, 0, true); - DisplayServerIOS::get_singleton()->key(key, character, key, Key::NONE, 0, false); + DisplayServerIOS::get_singleton()->key(key, character, key, Key::NONE, 0, true, KeyLocation::UNSPECIFIED); + DisplayServerIOS::get_singleton()->key(key, character, key, Key::NONE, 0, false, KeyLocation::UNSPECIFIED); } } diff --git a/platform/ios/view_controller.mm b/platform/ios/view_controller.mm index 1f55670b68..6f6c04c2c8 100644 --- a/platform/ios/view_controller.mm +++ b/platform/ios/view_controller.mm @@ -78,13 +78,15 @@ us = u32lbl[0]; } + KeyLocation location = KeyMappingIOS::key_location(press.key.keyCode); + if (!u32text.is_empty() && !u32text.begins_with("UIKey")) { for (int i = 0; i < u32text.length(); i++) { const char32_t c = u32text[i]; - DisplayServerIOS::get_singleton()->key(fix_keycode(us, key), c, fix_key_label(us, key), key, press.key.modifierFlags, true); + DisplayServerIOS::get_singleton()->key(fix_keycode(us, key), c, fix_key_label(us, key), key, press.key.modifierFlags, true, location); } } else { - DisplayServerIOS::get_singleton()->key(fix_keycode(us, key), 0, fix_key_label(us, key), key, press.key.modifierFlags, true); + DisplayServerIOS::get_singleton()->key(fix_keycode(us, key), 0, fix_key_label(us, key), key, press.key.modifierFlags, true, location); } } } @@ -110,7 +112,9 @@ us = u32lbl[0]; } - DisplayServerIOS::get_singleton()->key(fix_keycode(us, key), 0, fix_key_label(us, key), key, press.key.modifierFlags, false); + KeyLocation location = KeyMappingIOS::key_location(press.key.keyCode); + + DisplayServerIOS::get_singleton()->key(fix_keycode(us, key), 0, fix_key_label(us, key), key, press.key.modifierFlags, false, location); } } } diff --git a/platform/linuxbsd/freedesktop_portal_desktop.cpp b/platform/linuxbsd/freedesktop_portal_desktop.cpp index 3641f20c70..a3633e72b7 100644 --- a/platform/linuxbsd/freedesktop_portal_desktop.cpp +++ b/platform/linuxbsd/freedesktop_portal_desktop.cpp @@ -142,6 +142,54 @@ void FreeDesktopPortalDesktop::append_dbus_string(DBusMessageIter *p_iter, const } } +void FreeDesktopPortalDesktop::append_dbus_dict_options(DBusMessageIter *p_iter, const TypedArray<Dictionary> &p_options) { + DBusMessageIter dict_iter; + DBusMessageIter var_iter; + DBusMessageIter arr_iter; + const char *choices_key = "choices"; + + dbus_message_iter_open_container(p_iter, DBUS_TYPE_DICT_ENTRY, nullptr, &dict_iter); + dbus_message_iter_append_basic(&dict_iter, DBUS_TYPE_STRING, &choices_key); + dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_VARIANT, "a(ssa(ss)s)", &var_iter); + dbus_message_iter_open_container(&var_iter, DBUS_TYPE_ARRAY, "(ss(ss)s)", &arr_iter); + + for (int i = 0; i < p_options.size(); i++) { + const Dictionary &item = p_options[i]; + if (!item.has("name") || !item.has("values") || !item.has("default")) { + continue; + } + const String &name = item["name"]; + const Vector<String> &options = item["values"]; + int default_idx = item["default"]; + + DBusMessageIter struct_iter; + DBusMessageIter array_iter; + DBusMessageIter array_struct_iter; + dbus_message_iter_open_container(&arr_iter, DBUS_TYPE_STRUCT, nullptr, &struct_iter); + append_dbus_string(&struct_iter, name); // ID. + append_dbus_string(&struct_iter, name); // User visible name. + + dbus_message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "(ss)", &array_iter); + for (int j = 0; j < options.size(); j++) { + dbus_message_iter_open_container(&array_iter, DBUS_TYPE_STRUCT, nullptr, &array_struct_iter); + append_dbus_string(&array_struct_iter, itos(j)); + append_dbus_string(&array_struct_iter, options[j]); + dbus_message_iter_close_container(&array_iter, &array_struct_iter); + } + dbus_message_iter_close_container(&struct_iter, &array_iter); + if (options.is_empty()) { + append_dbus_string(&struct_iter, (default_idx) ? "true" : "false"); // Default selection. + } else { + append_dbus_string(&struct_iter, itos(default_idx)); // Default selection. + } + + dbus_message_iter_close_container(&arr_iter, &struct_iter); + } + dbus_message_iter_close_container(&var_iter, &arr_iter); + dbus_message_iter_close_container(&dict_iter, &var_iter); + dbus_message_iter_close_container(p_iter, &dict_iter); +} + void FreeDesktopPortalDesktop::append_dbus_dict_filters(DBusMessageIter *p_iter, const Vector<String> &p_filter_names, const Vector<String> &p_filter_exts) { DBusMessageIter dict_iter; DBusMessageIter var_iter; @@ -223,7 +271,7 @@ void FreeDesktopPortalDesktop::append_dbus_dict_bool(DBusMessageIter *p_iter, co dbus_message_iter_close_container(p_iter, &dict_iter); } -bool FreeDesktopPortalDesktop::file_chooser_parse_response(DBusMessageIter *p_iter, const Vector<String> &p_names, bool &r_cancel, Vector<String> &r_urls, int &r_index) { +bool FreeDesktopPortalDesktop::file_chooser_parse_response(DBusMessageIter *p_iter, const Vector<String> &p_names, bool &r_cancel, Vector<String> &r_urls, int &r_index, Dictionary &r_options) { ERR_FAIL_COND_V(dbus_message_iter_get_arg_type(p_iter) != DBUS_TYPE_UINT32, false); dbus_uint32_t resp_code; @@ -262,6 +310,34 @@ bool FreeDesktopPortalDesktop::file_chooser_parse_response(DBusMessageIter *p_it } } } + } else if (strcmp(key, "choices") == 0) { // a(ss) { + if (dbus_message_iter_get_arg_type(&var_iter) == DBUS_TYPE_ARRAY) { + DBusMessageIter struct_iter; + dbus_message_iter_recurse(&var_iter, &struct_iter); + while (dbus_message_iter_get_arg_type(&struct_iter) == DBUS_TYPE_STRUCT) { + DBusMessageIter opt_iter; + dbus_message_iter_recurse(&struct_iter, &opt_iter); + const char *opt_key = nullptr; + dbus_message_iter_get_basic(&opt_iter, &opt_key); + String opt_skey = String::utf8(opt_key); + + dbus_message_iter_next(&opt_iter); + const char *opt_val = nullptr; + dbus_message_iter_get_basic(&opt_iter, &opt_val); + String opt_sval = String::utf8(opt_val); + if (opt_sval == "true") { + r_options[opt_skey] = true; + } else if (opt_sval == "false") { + r_options[opt_skey] = false; + } else { + r_options[opt_skey] = opt_sval.to_int(); + } + + if (!dbus_message_iter_next(&struct_iter)) { + break; + } + } + } } else if (strcmp(key, "uris") == 0) { // as if (dbus_message_iter_get_arg_type(&var_iter) == DBUS_TYPE_ARRAY) { DBusMessageIter uri_iter; @@ -285,7 +361,7 @@ bool FreeDesktopPortalDesktop::file_chooser_parse_response(DBusMessageIter *p_it return true; } -Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_window_id, const String &p_xid, const String &p_title, const String &p_current_directory, const String &p_filename, DisplayServer::FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) { +Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_window_id, const String &p_xid, const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, DisplayServer::FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback, bool p_options_in_cb) { if (unsupported) { return FAILED; } @@ -322,6 +398,7 @@ Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_windo fd.callback = p_callback; fd.prev_focus = p_window_id; fd.filter_names = filter_names; + fd.opt_in_cb = p_options_in_cb; CryptoCore::RandomGenerator rng; ERR_FAIL_COND_V_MSG(rng.init(), FAILED, "Failed to initialize random number generator."); @@ -373,6 +450,8 @@ Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_windo append_dbus_dict_bool(&arr_iter, "multiple", p_mode == DisplayServer::FILE_DIALOG_MODE_OPEN_FILES); append_dbus_dict_bool(&arr_iter, "directory", p_mode == DisplayServer::FILE_DIALOG_MODE_OPEN_DIR); append_dbus_dict_filters(&arr_iter, filter_names, filter_exts); + + append_dbus_dict_options(&arr_iter, p_options); append_dbus_dict_string(&arr_iter, "current_folder", p_current_directory, true); if (p_mode == DisplayServer::FILE_DIALOG_MODE_SAVE_FILE) { append_dbus_dict_string(&arr_iter, "current_name", p_filename); @@ -427,14 +506,25 @@ Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_windo return OK; } -void FreeDesktopPortalDesktop::_file_dialog_callback(const Callable &p_callable, const Variant &p_status, const Variant &p_list, const Variant &p_index) { - Variant ret; - Callable::CallError ce; - const Variant *args[3] = { &p_status, &p_list, &p_index }; +void FreeDesktopPortalDesktop::_file_dialog_callback(const Callable &p_callable, const Variant &p_status, const Variant &p_list, const Variant &p_index, const Variant &p_options, bool p_opt_in_cb) { + if (p_opt_in_cb) { + Variant ret; + Callable::CallError ce; + const Variant *args[4] = { &p_status, &p_list, &p_index, &p_options }; - p_callable.callp(args, 3, ret, ce); - if (ce.error != Callable::CallError::CALL_OK) { - ERR_PRINT(vformat("Failed to execute file dialogs callback: %s.", Variant::get_callable_error_text(p_callable, args, 3, ce))); + p_callable.callp(args, 4, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat("Failed to execute file dialogs callback: %s.", Variant::get_callable_error_text(p_callable, args, 4, ce))); + } + } else { + Variant ret; + Callable::CallError ce; + const Variant *args[3] = { &p_status, &p_list, &p_index }; + + p_callable.callp(args, 3, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat("Failed to execute file dialogs callback: %s.", Variant::get_callable_error_text(p_callable, args, 3, ce))); + } } } @@ -458,11 +548,12 @@ void FreeDesktopPortalDesktop::_thread_file_dialog_monitor(void *p_ud) { if (dbus_message_iter_init(msg, &iter)) { bool cancel = false; Vector<String> uris; + Dictionary options; int index = 0; - file_chooser_parse_response(&iter, fd.filter_names, cancel, uris, index); + file_chooser_parse_response(&iter, fd.filter_names, cancel, uris, index, options); if (fd.callback.is_valid()) { - callable_mp(portal, &FreeDesktopPortalDesktop::_file_dialog_callback).call_deferred(fd.callback, !cancel, uris, index); + callable_mp(portal, &FreeDesktopPortalDesktop::_file_dialog_callback).call_deferred(fd.callback, !cancel, uris, index, options, fd.opt_in_cb); } if (fd.prev_focus != DisplayServer::INVALID_WINDOW_ID) { callable_mp(DisplayServer::get_singleton(), &DisplayServer::window_move_to_foreground).call_deferred(fd.prev_focus); diff --git a/platform/linuxbsd/freedesktop_portal_desktop.h b/platform/linuxbsd/freedesktop_portal_desktop.h index 71e9812ea9..c9da387241 100644 --- a/platform/linuxbsd/freedesktop_portal_desktop.h +++ b/platform/linuxbsd/freedesktop_portal_desktop.h @@ -49,12 +49,13 @@ private: bool read_setting(const char *p_namespace, const char *p_key, int p_type, void *r_value); static void append_dbus_string(DBusMessageIter *p_iter, const String &p_string); + static void append_dbus_dict_options(DBusMessageIter *p_iter, const TypedArray<Dictionary> &p_options); static void append_dbus_dict_filters(DBusMessageIter *p_iter, const Vector<String> &p_filter_names, const Vector<String> &p_filter_exts); static void append_dbus_dict_string(DBusMessageIter *p_iter, const String &p_key, const String &p_value, bool p_as_byte_array = false); static void append_dbus_dict_bool(DBusMessageIter *p_iter, const String &p_key, bool p_value); - static bool file_chooser_parse_response(DBusMessageIter *p_iter, const Vector<String> &p_names, bool &r_cancel, Vector<String> &r_urls, int &r_index); + static bool file_chooser_parse_response(DBusMessageIter *p_iter, const Vector<String> &p_names, bool &r_cancel, Vector<String> &r_urls, int &r_index, Dictionary &r_options); - void _file_dialog_callback(const Callable &p_callable, const Variant &p_status, const Variant &p_list, const Variant &p_index); + void _file_dialog_callback(const Callable &p_callable, const Variant &p_status, const Variant &p_list, const Variant &p_index, const Variant &p_options, bool p_opt_in_cb); struct FileDialogData { Vector<String> filter_names; @@ -62,6 +63,7 @@ private: DisplayServer::WindowID prev_focus = DisplayServer::INVALID_WINDOW_ID; Callable callback; String path; + bool opt_in_cb = false; }; Mutex file_dialog_mutex; @@ -77,7 +79,7 @@ public: bool is_supported() { return !unsupported; } - Error file_dialog_show(DisplayServer::WindowID p_window_id, const String &p_xid, const String &p_title, const String &p_current_directory, const String &p_filename, DisplayServer::FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback); + Error file_dialog_show(DisplayServer::WindowID p_window_id, const String &p_xid, const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, DisplayServer::FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback, bool p_options_in_cb); // Retrieve the system's preferred color scheme. // 0: No preference or unknown. diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp index 13a0a9f877..20e2e897f2 100644 --- a/platform/linuxbsd/x11/display_server_x11.cpp +++ b/platform/linuxbsd/x11/display_server_x11.cpp @@ -372,7 +372,18 @@ Error DisplayServerX11::file_dialog_show(const String &p_title, const String &p_ } String xid = vformat("x11:%x", (uint64_t)windows[window_id].x11_window); - return portal_desktop->file_dialog_show(last_focused_window, xid, p_title, p_current_directory, p_filename, p_mode, p_filters, p_callback); + return portal_desktop->file_dialog_show(last_focused_window, xid, p_title, p_current_directory, String(), p_filename, p_mode, p_filters, TypedArray<Dictionary>(), p_callback, false); +} + +Error DisplayServerX11::file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback) { + WindowID window_id = last_focused_window; + + if (!windows.has(window_id)) { + window_id = MAIN_WINDOW_ID; + } + + String xid = vformat("x11:%x", (uint64_t)windows[window_id].x11_window); + return portal_desktop->file_dialog_show(last_focused_window, xid, p_title, p_current_directory, p_root, p_filename, p_mode, p_filters, p_options, p_callback, true); } #endif @@ -3501,6 +3512,7 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, bool keypress = xkeyevent->type == KeyPress; Key keycode = KeyMappingX11::get_keycode(keysym_keycode); Key physical_keycode = KeyMappingX11::get_scancode(xkeyevent->keycode); + KeyLocation key_location = KeyMappingX11::get_location(xkeyevent->keycode); if (keycode >= Key::A + 32 && keycode <= Key::Z + 32) { keycode -= 'a' - 'A'; @@ -3538,6 +3550,8 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, k->set_unicode(fix_unicode(tmp[i])); } + k->set_location(key_location); + k->set_echo(false); if (k->get_keycode() == Key::BACKTAB) { @@ -3563,6 +3577,8 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, Key keycode = KeyMappingX11::get_keycode(keysym_keycode); Key physical_keycode = KeyMappingX11::get_scancode(xkeyevent->keycode); + KeyLocation key_location = KeyMappingX11::get_location(xkeyevent->keycode); + /* Phase 3, obtain a unicode character from the keysym */ // KeyMappingX11 also translates keysym to unicode. @@ -3662,6 +3678,9 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, if (keypress) { k->set_unicode(fix_unicode(unicode)); } + + k->set_location(key_location); + k->set_echo(p_echo); if (k->get_keycode() == Key::BACKTAB) { diff --git a/platform/linuxbsd/x11/display_server_x11.h b/platform/linuxbsd/x11/display_server_x11.h index 27bf7951ff..da4085772a 100644 --- a/platform/linuxbsd/x11/display_server_x11.h +++ b/platform/linuxbsd/x11/display_server_x11.h @@ -402,6 +402,7 @@ public: virtual bool is_dark_mode() const override; virtual Error file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) override; + virtual Error file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback) override; #endif virtual void mouse_set_mode(MouseMode p_mode) override; diff --git a/platform/linuxbsd/x11/key_mapping_x11.cpp b/platform/linuxbsd/x11/key_mapping_x11.cpp index c0e6b91d57..b589a2a573 100644 --- a/platform/linuxbsd/x11/key_mapping_x11.cpp +++ b/platform/linuxbsd/x11/key_mapping_x11.cpp @@ -1113,6 +1113,20 @@ void KeyMappingX11::initialize() { xkeysym_unicode_map[0x13BD] = 0x0153; xkeysym_unicode_map[0x13BE] = 0x0178; xkeysym_unicode_map[0x20AC] = 0x20AC; + + // Scancode to physical location map. + // Ctrl. + location_map[0x25] = KeyLocation::LEFT; + location_map[0x69] = KeyLocation::RIGHT; + // Shift. + location_map[0x32] = KeyLocation::LEFT; + location_map[0x3E] = KeyLocation::RIGHT; + // Alt. + location_map[0x40] = KeyLocation::LEFT; + location_map[0x6C] = KeyLocation::RIGHT; + // Meta. + location_map[0x85] = KeyLocation::LEFT; + location_map[0x86] = KeyLocation::RIGHT; } Key KeyMappingX11::get_keycode(KeySym p_keysym) { @@ -1173,3 +1187,11 @@ char32_t KeyMappingX11::get_unicode_from_keysym(KeySym p_keysym) { } return 0; } + +KeyLocation KeyMappingX11::get_location(unsigned int p_code) { + const KeyLocation *location = location_map.getptr(p_code); + if (location) { + return *location; + } + return KeyLocation::UNSPECIFIED; +} diff --git a/platform/linuxbsd/x11/key_mapping_x11.h b/platform/linuxbsd/x11/key_mapping_x11.h index ae8fd67f27..a51ee5f48e 100644 --- a/platform/linuxbsd/x11/key_mapping_x11.h +++ b/platform/linuxbsd/x11/key_mapping_x11.h @@ -54,6 +54,7 @@ class KeyMappingX11 { static inline HashMap<unsigned int, Key, HashMapHasherKeys> scancode_map; static inline HashMap<Key, unsigned int, HashMapHasherKeys> scancode_map_inv; static inline HashMap<KeySym, char32_t, HashMapHasherKeys> xkeysym_unicode_map; + static inline HashMap<unsigned int, KeyLocation, HashMapHasherKeys> location_map; KeyMappingX11() {} @@ -64,6 +65,7 @@ public: static unsigned int get_xlibcode(Key p_keysym); static Key get_scancode(unsigned int p_code); static char32_t get_unicode_from_keysym(KeySym p_keysym); + static KeyLocation get_location(unsigned int p_code); }; #endif // KEY_MAPPING_X11_H diff --git a/platform/macos/SCsub b/platform/macos/SCsub index 30202e5de7..ad19f12c58 100644 --- a/platform/macos/SCsub +++ b/platform/macos/SCsub @@ -20,6 +20,7 @@ files = [ "godot_main_macos.mm", "godot_menu_delegate.mm", "godot_menu_item.mm", + "godot_open_save_delegate.mm", "dir_access_macos.mm", "tts_macos.mm", "joypad_macos.cpp", diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h index cc343a6d64..24d4a349e2 100644 --- a/platform/macos/display_server_macos.h +++ b/platform/macos/display_server_macos.h @@ -75,6 +75,7 @@ public: Key physical_keycode = Key::NONE; Key key_label = Key::NONE; uint32_t unicode = 0; + KeyLocation location = KeyLocation::UNSPECIFIED; }; struct WindowData { @@ -234,6 +235,8 @@ private: int _get_system_menu_count(const NSMenu *p_menu) const; NSMenuItem *_menu_add_item(const String &p_menu_root, const String &p_label, Key p_accel, int p_index, int *r_out); + Error _file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback, bool p_options_in_cb); + public: NSMenu *get_dock_menu() const; void menu_callback(id p_sender); @@ -345,6 +348,7 @@ public: virtual Error dialog_input_text(String p_title, String p_description, String p_partial, const Callable &p_callback) override; virtual Error file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) override; + virtual Error file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback) override; virtual void mouse_set_mode(MouseMode p_mode) override; virtual MouseMode mouse_get_mode() const override; diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm index 378688f78a..396b943251 100644 --- a/platform/macos/display_server_macos.mm +++ b/platform/macos/display_server_macos.mm @@ -34,6 +34,7 @@ #include "godot_content_view.h" #include "godot_menu_delegate.h" #include "godot_menu_item.h" +#include "godot_open_save_delegate.h" #include "godot_window.h" #include "godot_window_delegate.h" #include "key_mapping_macos.h" @@ -477,6 +478,7 @@ void DisplayServerMacOS::_process_key_events() { k->set_physical_keycode(ke.physical_keycode); k->set_key_label(ke.key_label); k->set_unicode(ke.unicode); + k->set_location(ke.location); _push_input(k); } else { @@ -505,6 +507,7 @@ void DisplayServerMacOS::_process_key_events() { k->set_keycode(ke.keycode); k->set_physical_keycode(ke.physical_keycode); k->set_key_label(ke.key_label); + k->set_location(ke.location); if (i + 1 < key_event_pos && key_event_buffer[i + 1].keycode == Key::NONE) { k->set_unicode(key_event_buffer[i + 1].unicode); @@ -2079,139 +2082,37 @@ Error DisplayServerMacOS::dialog_show(String p_title, String p_description, Vect return OK; } -@interface FileDialogDropdown : NSObject { - NSSavePanel *dialog; - NSMutableArray *allowed_types; - int cur_index; -} - -- (instancetype)initWithDialog:(NSSavePanel *)p_dialog fileTypes:(NSMutableArray *)p_allowed_types; -- (void)popupAction:(id)sender; -- (int)getIndex; - -@end - -@implementation FileDialogDropdown - -- (int)getIndex { - return cur_index; -} - -- (instancetype)initWithDialog:(NSSavePanel *)p_dialog fileTypes:(NSMutableArray *)p_allowed_types { - if ((self = [super init])) { - dialog = p_dialog; - allowed_types = p_allowed_types; - cur_index = 0; - } - return self; +Error DisplayServerMacOS::file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) { + return _file_dialog_with_options_show(p_title, p_current_directory, String(), p_filename, p_show_hidden, p_mode, p_filters, TypedArray<Dictionary>(), p_callback, false); } -- (void)popupAction:(id)sender { - NSUInteger index = [sender indexOfSelectedItem]; - if (index < [allowed_types count]) { - [dialog setAllowedFileTypes:[allowed_types objectAtIndex:index]]; - cur_index = index; - } else { - [dialog setAllowedFileTypes:@[]]; - cur_index = -1; - } +Error DisplayServerMacOS::file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback) { + return _file_dialog_with_options_show(p_title, p_current_directory, p_root, p_filename, p_show_hidden, p_mode, p_filters, p_options, p_callback, true); } -@end - -FileDialogDropdown *_make_accessory_view(NSSavePanel *p_panel, const Vector<String> &p_filters) { - NSView *group = [[NSView alloc] initWithFrame:NSZeroRect]; - group.translatesAutoresizingMaskIntoConstraints = NO; - - NSTextField *label = [NSTextField labelWithString:[NSString stringWithUTF8String:RTR("Format").utf8().get_data()]]; - label.translatesAutoresizingMaskIntoConstraints = NO; - if (@available(macOS 10.14, *)) { - label.textColor = NSColor.secondaryLabelColor; - } - if (@available(macOS 11.10, *)) { - label.font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]; - } - [group addSubview:label]; - - NSPopUpButton *popup = [[NSPopUpButton alloc] initWithFrame:NSZeroRect pullsDown:NO]; - popup.translatesAutoresizingMaskIntoConstraints = NO; - - NSMutableArray *allowed_types = [[NSMutableArray alloc] init]; - bool allow_other = false; - for (int i = 0; i < p_filters.size(); i++) { - Vector<String> tokens = p_filters[i].split(";"); - if (tokens.size() >= 1) { - String flt = tokens[0].strip_edges(); - int filter_slice_count = flt.get_slice_count(","); - - NSMutableArray *type_filters = [[NSMutableArray alloc] init]; - for (int j = 0; j < filter_slice_count; j++) { - String str = (flt.get_slice(",", j).strip_edges()); - if (str.strip_edges() == "*.*" || str.strip_edges() == "*") { - allow_other = true; - } else if (!str.is_empty()) { - [type_filters addObject:[NSString stringWithUTF8String:str.replace("*.", "").strip_edges().utf8().get_data()]]; - } - } - - if ([type_filters count] > 0) { - NSString *name_str = [NSString stringWithUTF8String:((tokens.size() == 1) ? tokens[0] : vformat("%s (%s)", tokens[1], tokens[0])).strip_edges().utf8().get_data()]; - [allowed_types addObject:type_filters]; - [popup addItemWithTitle:name_str]; - } - } - } - FileDialogDropdown *handler = [[FileDialogDropdown alloc] initWithDialog:p_panel fileTypes:allowed_types]; - popup.target = handler; - popup.action = @selector(popupAction:); - - [group addSubview:popup]; - - NSView *view = [[NSView alloc] initWithFrame:NSZeroRect]; - view.translatesAutoresizingMaskIntoConstraints = NO; - [view addSubview:group]; - - NSMutableArray *constraints = [NSMutableArray array]; - [constraints addObject:[popup.topAnchor constraintEqualToAnchor:group.topAnchor constant:10]]; - [constraints addObject:[label.leadingAnchor constraintEqualToAnchor:group.leadingAnchor constant:10]]; - [constraints addObject:[popup.leadingAnchor constraintEqualToAnchor:label.trailingAnchor constant:10]]; - [constraints addObject:[popup.firstBaselineAnchor constraintEqualToAnchor:label.firstBaselineAnchor]]; - [constraints addObject:[group.trailingAnchor constraintEqualToAnchor:popup.trailingAnchor constant:10]]; - [constraints addObject:[group.bottomAnchor constraintEqualToAnchor:popup.bottomAnchor constant:10]]; - [constraints addObject:[group.topAnchor constraintEqualToAnchor:view.topAnchor]]; - [constraints addObject:[group.centerXAnchor constraintEqualToAnchor:view.centerXAnchor]]; - [constraints addObject:[view.bottomAnchor constraintEqualToAnchor:group.bottomAnchor]]; - [NSLayoutConstraint activateConstraints:constraints]; - - [p_panel setAllowsOtherFileTypes:allow_other]; - if ([allowed_types count] > 0) { - [p_panel setAccessoryView:view]; - [p_panel setAllowedFileTypes:[allowed_types objectAtIndex:0]]; - } - - return handler; -} - -Error DisplayServerMacOS::file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) { +Error DisplayServerMacOS::_file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback, bool p_options_in_cb) { _THREAD_SAFE_METHOD_ ERR_FAIL_INDEX_V(int(p_mode), FILE_DIALOG_MODE_SAVE_MAX, FAILED); NSString *url = [NSString stringWithUTF8String:p_current_directory.utf8().get_data()]; - FileDialogDropdown *handler = nullptr; - WindowID prev_focus = last_focused_window; + GodotOpenSaveDelegate *panel_delegate = [[GodotOpenSaveDelegate alloc] init]; + if (p_root.length() > 0) { + [panel_delegate setRootPath:p_root]; + } Callable callback = p_callback; // Make a copy for async completion handler. if (p_mode == FILE_DIALOG_MODE_SAVE_FILE) { NSSavePanel *panel = [NSSavePanel savePanel]; [panel setDirectoryURL:[NSURL fileURLWithPath:url]]; - handler = _make_accessory_view(panel, p_filters); + [panel_delegate makeAccessoryView:panel filters:p_filters options:p_options]; [panel setExtensionHidden:YES]; [panel setCanSelectHiddenExtension:YES]; [panel setCanCreateDirectories:YES]; [panel setShowsHiddenFiles:p_show_hidden]; + [panel setDelegate:panel_delegate]; if (p_filename != "") { NSString *fileurl = [NSString stringWithUTF8String:p_filename.utf8().get_data()]; [panel setNameFieldStringValue:fileurl]; @@ -2248,30 +2149,60 @@ Error DisplayServerMacOS::file_dialog_show(const String &p_title, const String & url.parse_utf8([[[panel URL] path] UTF8String]); files.push_back(url); if (!callback.is_null()) { - Variant v_result = true; - Variant v_files = files; - Variant v_index = [handler getIndex]; - Variant ret; - Callable::CallError ce; - const Variant *args[3] = { &v_result, &v_files, &v_index }; - - callback.callp(args, 3, ret, ce); - if (ce.error != Callable::CallError::CALL_OK) { - ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 3, ce))); + if (p_options_in_cb) { + Variant v_result = true; + Variant v_files = files; + Variant v_index = [panel_delegate getIndex]; + Variant v_opt = [panel_delegate getSelection]; + Variant ret; + Callable::CallError ce; + const Variant *args[4] = { &v_result, &v_files, &v_index, &v_opt }; + + callback.callp(args, 4, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 4, ce))); + } + } else { + Variant v_result = true; + Variant v_files = files; + Variant v_index = [panel_delegate getIndex]; + Variant ret; + Callable::CallError ce; + const Variant *args[3] = { &v_result, &v_files, &v_index }; + + callback.callp(args, 3, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 3, ce))); + } } } } else { if (!callback.is_null()) { - Variant v_result = false; - Variant v_files = Vector<String>(); - Variant v_index = [handler getIndex]; - Variant ret; - Callable::CallError ce; - const Variant *args[3] = { &v_result, &v_files, &v_index }; - - callback.callp(args, 3, ret, ce); - if (ce.error != Callable::CallError::CALL_OK) { - ERR_PRINT(vformat("Failed to execute file dialogs callback: %s.", Variant::get_callable_error_text(callback, args, 3, ce))); + if (p_options_in_cb) { + Variant v_result = false; + Variant v_files = Vector<String>(); + Variant v_index = [panel_delegate getIndex]; + Variant v_opt = [panel_delegate getSelection]; + Variant ret; + Callable::CallError ce; + const Variant *args[4] = { &v_result, &v_files, &v_index, &v_opt }; + + callback.callp(args, 4, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 4, ce))); + } + } else { + Variant v_result = false; + Variant v_files = Vector<String>(); + Variant v_index = [panel_delegate getIndex]; + Variant ret; + Callable::CallError ce; + const Variant *args[3] = { &v_result, &v_files, &v_index }; + + callback.callp(args, 3, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 3, ce))); + } } } } @@ -2283,13 +2214,14 @@ Error DisplayServerMacOS::file_dialog_show(const String &p_title, const String & NSOpenPanel *panel = [NSOpenPanel openPanel]; [panel setDirectoryURL:[NSURL fileURLWithPath:url]]; - handler = _make_accessory_view(panel, p_filters); + [panel_delegate makeAccessoryView:panel filters:p_filters options:p_options]; [panel setExtensionHidden:YES]; [panel setCanSelectHiddenExtension:YES]; [panel setCanCreateDirectories:YES]; [panel setCanChooseFiles:(p_mode != FILE_DIALOG_MODE_OPEN_DIR)]; [panel setCanChooseDirectories:(p_mode == FILE_DIALOG_MODE_OPEN_DIR || p_mode == FILE_DIALOG_MODE_OPEN_ANY)]; [panel setShowsHiddenFiles:p_show_hidden]; + [panel setDelegate:panel_delegate]; if (p_filename != "") { NSString *fileurl = [NSString stringWithUTF8String:p_filename.utf8().get_data()]; [panel setNameFieldStringValue:fileurl]; @@ -2333,30 +2265,60 @@ Error DisplayServerMacOS::file_dialog_show(const String &p_title, const String & files.push_back(url); } if (!callback.is_null()) { - Variant v_result = true; - Variant v_files = files; - Variant v_index = [handler getIndex]; - Variant ret; - Callable::CallError ce; - const Variant *args[3] = { &v_result, &v_files, &v_index }; - - callback.callp(args, 3, ret, ce); - if (ce.error != Callable::CallError::CALL_OK) { - ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 3, ce))); + if (p_options_in_cb) { + Variant v_result = true; + Variant v_files = files; + Variant v_index = [panel_delegate getIndex]; + Variant v_opt = [panel_delegate getSelection]; + Variant ret; + Callable::CallError ce; + const Variant *args[4] = { &v_result, &v_files, &v_index, &v_opt }; + + callback.callp(args, 4, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 4, ce))); + } + } else { + Variant v_result = true; + Variant v_files = files; + Variant v_index = [panel_delegate getIndex]; + Variant ret; + Callable::CallError ce; + const Variant *args[3] = { &v_result, &v_files, &v_index }; + + callback.callp(args, 3, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 3, ce))); + } } } } else { if (!callback.is_null()) { - Variant v_result = false; - Variant v_files = Vector<String>(); - Variant v_index = [handler getIndex]; - Variant ret; - Callable::CallError ce; - const Variant *args[3] = { &v_result, &v_files, &v_index }; - - callback.callp(args, 3, ret, ce); - if (ce.error != Callable::CallError::CALL_OK) { - ERR_PRINT(vformat("Failed to execute file dialogs callback: %s.", Variant::get_callable_error_text(callback, args, 3, ce))); + if (p_options_in_cb) { + Variant v_result = false; + Variant v_files = Vector<String>(); + Variant v_index = [panel_delegate getIndex]; + Variant v_opt = [panel_delegate getSelection]; + Variant ret; + Callable::CallError ce; + const Variant *args[4] = { &v_result, &v_files, &v_index, &v_opt }; + + callback.callp(args, 4, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 4, ce))); + } + } else { + Variant v_result = false; + Variant v_files = Vector<String>(); + Variant v_index = [panel_delegate getIndex]; + Variant ret; + Callable::CallError ce; + const Variant *args[3] = { &v_result, &v_files, &v_index }; + + callback.callp(args, 3, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 3, ce))); + } } } } diff --git a/platform/macos/godot_content_view.mm b/platform/macos/godot_content_view.mm index 139411249c..4505becbc2 100644 --- a/platform/macos/godot_content_view.mm +++ b/platform/macos/godot_content_view.mm @@ -576,21 +576,23 @@ String u32text; u32text.parse_utf16(text.ptr(), text.length()); + DisplayServerMacOS::KeyEvent ke; + ke.window_id = window_id; + ke.macos_state = [event modifierFlags]; + ke.pressed = true; + ke.echo = [event isARepeat]; + ke.keycode = KeyMappingMacOS::remap_key([event keyCode], [event modifierFlags], false); + ke.physical_keycode = KeyMappingMacOS::translate_key([event keyCode]); + ke.key_label = KeyMappingMacOS::remap_key([event keyCode], [event modifierFlags], true); + ke.raw = true; + + if (u32text.is_empty()) { + ke.unicode = 0; + ds->push_to_key_event_buffer(ke); + } for (int i = 0; i < u32text.length(); i++) { const char32_t codepoint = u32text[i]; - - DisplayServerMacOS::KeyEvent ke; - - ke.window_id = window_id; - ke.macos_state = [event modifierFlags]; - ke.pressed = true; - ke.echo = [event isARepeat]; - ke.keycode = KeyMappingMacOS::remap_key([event keyCode], [event modifierFlags], false); - ke.physical_keycode = KeyMappingMacOS::translate_key([event keyCode]); - ke.key_label = KeyMappingMacOS::remap_key([event keyCode], [event modifierFlags], true); ke.unicode = fix_unicode(codepoint); - ke.raw = true; - ds->push_to_key_event_buffer(ke); } } else { @@ -604,6 +606,7 @@ ke.physical_keycode = KeyMappingMacOS::translate_key([event keyCode]); ke.key_label = KeyMappingMacOS::remap_key([event keyCode], [event modifierFlags], true); ke.unicode = 0; + ke.location = KeyMappingMacOS::translate_location([event keyCode]); ke.raw = false; ds->push_to_key_event_buffer(ke); @@ -669,6 +672,7 @@ ke.physical_keycode = KeyMappingMacOS::translate_key(key); ke.key_label = KeyMappingMacOS::remap_key(key, mod, true); ke.unicode = 0; + ke.location = KeyMappingMacOS::translate_location(key); ds->push_to_key_event_buffer(ke); } @@ -696,6 +700,7 @@ ke.physical_keycode = KeyMappingMacOS::translate_key([event keyCode]); ke.key_label = KeyMappingMacOS::remap_key([event keyCode], [event modifierFlags], true); ke.unicode = 0; + ke.location = KeyMappingMacOS::translate_location([event keyCode]); ke.raw = true; ds->push_to_key_event_buffer(ke); diff --git a/platform/macos/godot_open_save_delegate.h b/platform/macos/godot_open_save_delegate.h new file mode 100644 index 0000000000..8857ef1fa9 --- /dev/null +++ b/platform/macos/godot_open_save_delegate.h @@ -0,0 +1,66 @@ +/**************************************************************************/ +/* godot_open_save_delegate.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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. */ +/**************************************************************************/ + +#ifndef GODOT_OPEN_SAVE_DELEGATE_H +#define GODOT_OPEN_SAVE_DELEGATE_H + +#import <AppKit/AppKit.h> +#import <Foundation/Foundation.h> + +#include "core/templates/hash_map.h" +#include "core/variant/typed_array.h" +#include "core/variant/variant.h" + +@interface GodotOpenSaveDelegate : NSObject <NSOpenSavePanelDelegate> { + NSSavePanel *dialog; + NSMutableArray *allowed_types; + + HashMap<int, String> ctr_ids; + Dictionary options; + int cur_index; + int ctr_id; + + String root; +} + +- (void)makeAccessoryView:(NSSavePanel *)p_panel filters:(const Vector<String> &)p_filters options:(const TypedArray<Dictionary> &)p_options; +- (void)setFileTypes:(NSMutableArray *)p_allowed_types; +- (void)popupOptionAction:(id)p_sender; +- (void)popupCheckAction:(id)p_sender; +- (void)popupFileAction:(id)p_sender; +- (int)getIndex; +- (Dictionary)getSelection; +- (int)setDefaultInt:(const String &)p_name value:(int)p_value; +- (int)setDefaultBool:(const String &)p_name value:(bool)p_value; +- (void)setRootPath:(const String &)p_root_path; + +@end + +#endif // GODOT_OPEN_SAVE_DELEGATE_H diff --git a/platform/macos/godot_open_save_delegate.mm b/platform/macos/godot_open_save_delegate.mm new file mode 100644 index 0000000000..306015d644 --- /dev/null +++ b/platform/macos/godot_open_save_delegate.mm @@ -0,0 +1,250 @@ +/**************************************************************************/ +/* godot_open_save_delegate.mm */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 "godot_open_save_delegate.h" + +@implementation GodotOpenSaveDelegate + +- (instancetype)init { + self = [super init]; + if ((self = [super init])) { + dialog = nullptr; + cur_index = 0; + ctr_id = 1; + allowed_types = nullptr; + root = String(); + } + return self; +} + +- (void)makeAccessoryView:(NSSavePanel *)p_panel filters:(const Vector<String> &)p_filters options:(const TypedArray<Dictionary> &)p_options { + dialog = p_panel; + + NSMutableArray *constraints = [NSMutableArray array]; + + NSView *base_view = [[NSView alloc] initWithFrame:NSZeroRect]; + base_view.translatesAutoresizingMaskIntoConstraints = NO; + + NSGridView *view = [NSGridView gridViewWithNumberOfColumns:2 rows:0]; + view.translatesAutoresizingMaskIntoConstraints = NO; + view.columnSpacing = 10; + view.rowSpacing = 10; + view.rowAlignment = NSGridRowAlignmentLastBaseline; + + int option_count = 0; + + for (int i = 0; i < p_options.size(); i++) { + const Dictionary &item = p_options[i]; + if (!item.has("name") || !item.has("values") || !item.has("default")) { + continue; + } + const String &name = item["name"]; + const Vector<String> &values = item["values"]; + int default_idx = item["default"]; + + NSTextField *label = [NSTextField labelWithString:[NSString stringWithUTF8String:name.utf8().get_data()]]; + if (@available(macOS 10.14, *)) { + label.textColor = NSColor.secondaryLabelColor; + } + if (@available(macOS 11.10, *)) { + label.font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]; + } + + NSView *popup = nullptr; + if (values.is_empty()) { + NSButton *popup_check = [NSButton checkboxWithTitle:@"" target:self action:@selector(popupCheckAction:)]; + int tag = [self setDefaultBool:name value:(bool)default_idx]; + popup_check.state = (default_idx) ? NSControlStateValueOn : NSControlStateValueOff; + popup_check.tag = tag; + popup = popup_check; + } else { + NSPopUpButton *popup_list = [[NSPopUpButton alloc] initWithFrame:NSZeroRect pullsDown:NO]; + for (int i = 0; i < values.size(); i++) { + [popup_list addItemWithTitle:[NSString stringWithUTF8String:values[i].utf8().get_data()]]; + } + int tag = [self setDefaultInt:name value:default_idx]; + [popup_list selectItemAtIndex:default_idx]; + popup_list.tag = tag; + popup_list.target = self; + popup_list.action = @selector(popupOptionAction:); + popup = popup_list; + } + + [view addRowWithViews:[NSArray arrayWithObjects:label, popup, nil]]; + + option_count++; + } + + NSMutableArray *new_allowed_types = [[NSMutableArray alloc] init]; + bool allow_other = false; + { + NSTextField *label = [NSTextField labelWithString:[NSString stringWithUTF8String:RTR("Format").utf8().get_data()]]; + if (@available(macOS 10.14, *)) { + label.textColor = NSColor.secondaryLabelColor; + } + if (@available(macOS 11.10, *)) { + label.font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]; + } + + NSPopUpButton *popup = [[NSPopUpButton alloc] initWithFrame:NSZeroRect pullsDown:NO]; + if (p_filters.is_empty()) { + [popup addItemWithTitle:@"All Files"]; + } + + for (int i = 0; i < p_filters.size(); i++) { + Vector<String> tokens = p_filters[i].split(";"); + if (tokens.size() >= 1) { + String flt = tokens[0].strip_edges(); + int filter_slice_count = flt.get_slice_count(","); + + NSMutableArray *type_filters = [[NSMutableArray alloc] init]; + for (int j = 0; j < filter_slice_count; j++) { + String str = (flt.get_slice(",", j).strip_edges()); + if (str.strip_edges() == "*.*" || str.strip_edges() == "*") { + allow_other = true; + } else if (!str.is_empty()) { + [type_filters addObject:[NSString stringWithUTF8String:str.replace("*.", "").strip_edges().utf8().get_data()]]; + } + } + + if ([type_filters count] > 0) { + NSString *name_str = [NSString stringWithUTF8String:((tokens.size() == 1) ? tokens[0] : vformat("%s (%s)", tokens[1], tokens[0])).strip_edges().utf8().get_data()]; + [new_allowed_types addObject:type_filters]; + [popup addItemWithTitle:name_str]; + } + } + } + [self setFileTypes:new_allowed_types]; + popup.target = self; + popup.action = @selector(popupFileAction:); + + [view addRowWithViews:[NSArray arrayWithObjects:label, popup, nil]]; + } + + [base_view addSubview:view]; + [constraints addObject:[view.topAnchor constraintEqualToAnchor:base_view.topAnchor constant:10]]; + [constraints addObject:[base_view.bottomAnchor constraintEqualToAnchor:view.bottomAnchor constant:10]]; + [constraints addObject:[base_view.centerXAnchor constraintEqualToAnchor:view.centerXAnchor constant:10]]; + [NSLayoutConstraint activateConstraints:constraints]; + + [p_panel setAllowsOtherFileTypes:allow_other]; + if (option_count > 0 || [new_allowed_types count] > 0) { + [p_panel setAccessoryView:base_view]; + } + if ([new_allowed_types count] > 0) { + [p_panel setAllowedFileTypes:[new_allowed_types objectAtIndex:0]]; + } +} + +- (int)getIndex { + return cur_index; +} + +- (Dictionary)getSelection { + return options; +} + +- (int)setDefaultInt:(const String &)p_name value:(int)p_value { + int cid = ctr_id++; + options[p_name] = p_value; + ctr_ids[cid] = p_name; + + return cid; +} + +- (int)setDefaultBool:(const String &)p_name value:(bool)p_value { + int cid = ctr_id++; + options[p_name] = p_value; + ctr_ids[cid] = p_name; + + return cid; +} + +- (void)setFileTypes:(NSMutableArray *)p_allowed_types { + allowed_types = p_allowed_types; +} + +- (instancetype)initWithDialog:(NSSavePanel *)p_dialog { + if ((self = [super init])) { + dialog = p_dialog; + cur_index = 0; + ctr_id = 1; + allowed_types = nullptr; + } + return self; +} + +- (void)popupCheckAction:(id)p_sender { + NSButton *btn = p_sender; + if (btn && ctr_ids.has(btn.tag)) { + options[ctr_ids[btn.tag]] = ([btn state] == NSControlStateValueOn); + } +} + +- (void)popupOptionAction:(id)p_sender { + NSPopUpButton *btn = p_sender; + if (btn && ctr_ids.has(btn.tag)) { + options[ctr_ids[btn.tag]] = (int)[btn indexOfSelectedItem]; + } +} + +- (void)popupFileAction:(id)p_sender { + NSPopUpButton *btn = p_sender; + if (btn) { + NSUInteger index = [btn indexOfSelectedItem]; + if (allowed_types && index < [allowed_types count]) { + [dialog setAllowedFileTypes:[allowed_types objectAtIndex:index]]; + cur_index = index; + } else { + [dialog setAllowedFileTypes:@[]]; + cur_index = -1; + } + } +} + +- (void)setRootPath:(const String &)p_root_path { + root = p_root_path; +} + +- (BOOL)panel:(id)sender validateURL:(NSURL *)url error:(NSError *_Nullable *)outError { + if (root.is_empty()) { + return YES; + } + + NSString *ns_path = url.URLByStandardizingPath.URLByResolvingSymlinksInPath.path; + String path = String::utf8([ns_path UTF8String]).simplify_path(); + if (!path.begins_with(root.simplify_path())) { + return NO; + } + + return YES; +} + +@end diff --git a/platform/macos/key_mapping_macos.h b/platform/macos/key_mapping_macos.h index 1bda4eb406..f5b0ff8d02 100644 --- a/platform/macos/key_mapping_macos.h +++ b/platform/macos/key_mapping_macos.h @@ -45,6 +45,7 @@ public: static Key translate_key(unsigned int p_key); static unsigned int unmap_key(Key p_key); static Key remap_key(unsigned int p_key, unsigned int p_state, bool p_unicode); + static KeyLocation translate_location(unsigned int p_key); // Mapping for menu shortcuts. static String keycode_get_native_string(Key p_keycode); diff --git a/platform/macos/key_mapping_macos.mm b/platform/macos/key_mapping_macos.mm index db3fa4e02d..b5e72048e7 100644 --- a/platform/macos/key_mapping_macos.mm +++ b/platform/macos/key_mapping_macos.mm @@ -46,6 +46,7 @@ HashSet<unsigned int> numpad_keys; HashMap<unsigned int, Key, HashMapHasherKeys> keysym_map; HashMap<Key, unsigned int, HashMapHasherKeys> keysym_map_inv; HashMap<Key, char32_t, HashMapHasherKeys> keycode_map; +HashMap<unsigned int, KeyLocation, HashMapHasherKeys> location_map; void KeyMappingMacOS::initialize() { numpad_keys.insert(0x41); //kVK_ANSI_KeypadDecimal @@ -321,6 +322,20 @@ void KeyMappingMacOS::initialize() { keycode_map[Key::BAR] = '|'; keycode_map[Key::BRACERIGHT] = '}'; keycode_map[Key::ASCIITILDE] = '~'; + + // Keysym -> physical location. + // Ctrl. + location_map[0x3b] = KeyLocation::LEFT; + location_map[0x3e] = KeyLocation::RIGHT; + // Shift. + location_map[0x38] = KeyLocation::LEFT; + location_map[0x3c] = KeyLocation::RIGHT; + // Alt/Option. + location_map[0x3a] = KeyLocation::LEFT; + location_map[0x3d] = KeyLocation::RIGHT; + // Meta/Command (yes, right < left). + location_map[0x36] = KeyLocation::RIGHT; + location_map[0x37] = KeyLocation::LEFT; } bool KeyMappingMacOS::is_numpad_key(unsigned int p_key) { @@ -396,6 +411,15 @@ Key KeyMappingMacOS::remap_key(unsigned int p_key, unsigned int p_state, bool p_ } } +// Translates a macOS keycode to a Godot key location. +KeyLocation KeyMappingMacOS::translate_location(unsigned int p_key) { + const KeyLocation *location = location_map.getptr(p_key); + if (location) { + return *location; + } + return KeyLocation::UNSPECIFIED; +} + String KeyMappingMacOS::keycode_get_native_string(Key p_keycode) { const char32_t *key = keycode_map.getptr(p_keycode); if (key) { diff --git a/platform/web/display_server_web.cpp b/platform/web/display_server_web.cpp index b4a190d47e..aacbe4879f 100644 --- a/platform/web/display_server_web.cpp +++ b/platform/web/display_server_web.cpp @@ -187,6 +187,7 @@ void DisplayServerWeb::_key_callback(const String &p_key_event_code, const Strin Key keycode = dom_code2godot_scancode(p_key_event_code.utf8().get_data(), p_key_event_key.utf8().get_data(), false); Key scancode = dom_code2godot_scancode(p_key_event_code.utf8().get_data(), p_key_event_key.utf8().get_data(), true); + KeyLocation location = dom_code2godot_key_location(p_key_event_code.utf8().get_data()); DisplayServerWeb::KeyEvent ke; @@ -197,6 +198,7 @@ void DisplayServerWeb::_key_callback(const String &p_key_event_code, const Strin ke.physical_keycode = scancode; ke.key_label = fix_key_label(c, keycode); ke.unicode = fix_unicode(c); + ke.location = location; ke.mod = p_modifiers; if (ds->key_event_pos >= ds->key_event_buffer.size()) { @@ -1383,6 +1385,7 @@ void DisplayServerWeb::process_events() { ev->set_physical_keycode(ke.physical_keycode); ev->set_key_label(ke.key_label); ev->set_unicode(ke.unicode); + ev->set_location(ke.location); if (ke.raw) { dom2godot_mod(ev, ke.mod, ke.keycode); } diff --git a/platform/web/display_server_web.h b/platform/web/display_server_web.h index 140aef952b..682d10704f 100644 --- a/platform/web/display_server_web.h +++ b/platform/web/display_server_web.h @@ -95,6 +95,7 @@ private: Key physical_keycode = Key::NONE; Key key_label = Key::NONE; uint32_t unicode = 0; + KeyLocation location = KeyLocation::UNSPECIFIED; int mod = 0; }; diff --git a/platform/web/dom_keys.inc b/platform/web/dom_keys.inc index cd94b779c0..b20a3a46b9 100644 --- a/platform/web/dom_keys.inc +++ b/platform/web/dom_keys.inc @@ -223,3 +223,24 @@ Key dom_code2godot_scancode(EM_UTF8 const p_code[32], EM_UTF8 const p_key[32], b return Key::NONE; #undef DOM2GODOT } + +KeyLocation dom_code2godot_key_location(EM_UTF8 const p_code[32]) { +#define DOM2GODOT(m_str, m_godot_code) \ + if (memcmp((const void *)m_str, (void *)p_code, strlen(m_str) + 1) == 0) { \ + return KeyLocation::m_godot_code; \ + } + + DOM2GODOT("AltLeft", LEFT); + DOM2GODOT("AltRight", RIGHT); + DOM2GODOT("ControlLeft", LEFT); + DOM2GODOT("ControlRight", RIGHT); + DOM2GODOT("MetaLeft", LEFT); + DOM2GODOT("MetaRight", RIGHT); + DOM2GODOT("OSLeft", LEFT); + DOM2GODOT("OSRight", RIGHT); + DOM2GODOT("ShiftLeft", LEFT); + DOM2GODOT("ShiftRight", RIGHT); + + return KeyLocation::UNSPECIFIED; +#undef DOM2GODOT +} diff --git a/platform/windows/SCsub b/platform/windows/SCsub index 1cfbc33ef8..2df644136b 100644 --- a/platform/windows/SCsub +++ b/platform/windows/SCsub @@ -109,7 +109,7 @@ if env["d3d12"]: ) # Agility SDK - if env["agility_sdk_path"] != "": + if env["agility_sdk_path"] != "" and os.path.exists(env["agility_sdk_path"]): agility_dlls = ["D3D12Core.dll", "d3d12SDKLayers.dll"] # Whether these are loaded from arch-specific directory or not has to be known at build time. target_dir = arch_bin_dir if env["agility_sdk_multiarch"] else "#bin" @@ -121,7 +121,7 @@ if env["d3d12"]: ) # PIX - if env["pix_path"] != "": + if env["pix_path"] != "" and os.path.exists(env["pix_path"]): pix_dll = "WinPixEventRuntime.dll" env.Command( "#bin/" + pix_dll, diff --git a/platform/windows/detect.py b/platform/windows/detect.py index 79698f5bd7..b1dd66ca48 100644 --- a/platform/windows/detect.py +++ b/platform/windows/detect.py @@ -164,6 +164,22 @@ def get_opts(): mingw = os.getenv("MINGW_PREFIX", "") + # Direct3D 12 SDK dependencies folder. + d3d12_deps_folder = os.getenv("LOCALAPPDATA") + if d3d12_deps_folder: + d3d12_deps_folder = os.path.join(d3d12_deps_folder, "Godot", "build_deps") + else: + # Cross-compiling, the deps install script puts things in `bin`. + # Getting an absolute path to it is a bit hacky in Python. + try: + import inspect + + caller_frame = inspect.stack()[1] + caller_script_dir = os.path.dirname(os.path.abspath(caller_frame[1])) + d3d12_deps_folder = os.path.join(caller_script_dir, "bin", "build_deps") + except: # Give up. + d3d12_deps_folder = "" + return [ ("mingw_prefix", "MinGW prefix", mingw), # Targeted Windows version: 7 (and later), minimum supported version @@ -188,15 +204,31 @@ def get_opts(): BoolVariable("incremental_link", "Use MSVC incremental linking. May increase or decrease build times.", False), ("angle_libs", "Path to the ANGLE static libraries", ""), # Direct3D 12 support. - ("mesa_libs", "Path to the MESA/NIR static libraries (required for D3D12)", ""), - ("dxc_path", "Path to the DirectX Shader Compiler distribution (required for D3D12)", ""), - ("agility_sdk_path", "Path to the Agility SDK distribution (optional for D3D12)", ""), + ( + "mesa_libs", + "Path to the MESA/NIR static libraries (required for D3D12)", + os.path.join(d3d12_deps_folder, "mesa"), + ), + ( + "dxc_path", + "Path to the DirectX Shader Compiler distribution (required for D3D12)", + os.path.join(d3d12_deps_folder, "dxc"), + ), + ( + "agility_sdk_path", + "Path to the Agility SDK distribution (optional for D3D12)", + os.path.join(d3d12_deps_folder, "agility_sdk"), + ), BoolVariable( "agility_sdk_multiarch", "Whether the Agility SDK DLLs will be stored in arch-specific subdirectories", False, ), - ("pix_path", "Path to the PIX runtime distribution (optional for D3D12)", ""), + ( + "pix_path", + "Path to the PIX runtime distribution (optional for D3D12)", + os.path.join(d3d12_deps_folder, "pix"), + ), ] @@ -441,6 +473,16 @@ def configure_msvc(env, vcvars_msvc_config): LIBS += ["vulkan"] if env["d3d12"]: + # Check whether we have d3d12 dependencies installed. + if not os.path.exists(env["mesa_libs"]) or not os.path.exists(env["dxc_path"]): + print("The Direct3D 12 rendering driver requires dependencies to be installed.") + print("You can install them by running `python misc\scripts\install_d3d12_sdk_windows.py`.") + print("See the documentation for more information:") + print( + "https://docs.godotengine.org/en/latest/contributing/development/compiling/compiling_for_windows.html" + ) + sys.exit(255) + env.AppendUnique(CPPDEFINES=["D3D12_ENABLED", "RD_ENABLED"]) LIBS += ["d3d12", "dxgi", "dxguid"] LIBS += ["version"] # Mesa dependency. @@ -452,15 +494,10 @@ def configure_msvc(env, vcvars_msvc_config): arch_subdir = "arm64" if env["arch"] == "arm64" else "x64" # PIX - if env["pix_path"] != "": + if env["pix_path"] != "" and os.path.exists(env["pix_path"]): env.Append(LIBPATH=[env["pix_path"] + "/bin/" + arch_subdir]) LIBS += ["WinPixEventRuntime"] - # Mesa - if env["mesa_libs"] == "": - print("The Direct3D 12 rendering driver requires mesa_libs to be set.") - sys.exit(255) - env.Append(LIBPATH=[env["mesa_libs"] + "/bin"]) LIBS += ["libNIR.windows." + env["arch"]] @@ -663,16 +700,21 @@ def configure_mingw(env): arch_subdir = "arm64" if env["arch"] == "arm64" else "x64" + # Check whether we have d3d12 dependencies installed. + if not os.path.exists(env["mesa_libs"]) or not os.path.exists(env["dxc_path"]): + print("The Direct3D 12 rendering driver requires dependencies to be installed.") + print("You can install them by running `python misc\scripts\install_d3d12_sdk_windows.py`.") + print("See the documentation for more information:") + print( + "https://docs.godotengine.org/en/latest/contributing/development/compiling/compiling_for_windows.html" + ) + sys.exit(255) + # PIX - if env["pix_path"] != "": + if env["pix_path"] != "" and os.path.exists(env["pix_path"]): env.Append(LIBPATH=[env["pix_path"] + "/bin/" + arch_subdir]) env.Append(LIBS=["WinPixEventRuntime"]) - # Mesa - if env["mesa_libs"] == "": - print("The Direct3D 12 rendering driver requires mesa_libs to be set.") - sys.exit(255) - env.Append(LIBPATH=[env["mesa_libs"] + "/bin"]) env.Append(LIBS=["libNIR.windows." + env["arch"]]) env.Append(LIBS=["version"]) # Mesa dependency. diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index b56954ae81..657caa7939 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -52,6 +52,10 @@ #define DWMWA_USE_IMMERSIVE_DARK_MODE 20 #endif +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 +#define DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 19 +#endif + #if defined(__GNUC__) // Workaround GCC warning from -Wcast-function-type. #define GetProcAddress (void *)GetProcAddress @@ -165,20 +169,26 @@ DisplayServer::WindowID DisplayServerWindows::_get_focused_window_or_popup() con void DisplayServerWindows::_register_raw_input_devices(WindowID p_target_window) { use_raw_input = true; - RAWINPUTDEVICE rid[1] = {}; - rid[0].usUsagePage = 0x01; - rid[0].usUsage = 0x02; + RAWINPUTDEVICE rid[2] = {}; + rid[0].usUsagePage = 0x01; // HID_USAGE_PAGE_GENERIC + rid[0].usUsage = 0x02; // HID_USAGE_GENERIC_MOUSE rid[0].dwFlags = 0; + rid[1].usUsagePage = 0x01; // HID_USAGE_PAGE_GENERIC + rid[1].usUsage = 0x06; // HID_USAGE_GENERIC_KEYBOARD + rid[1].dwFlags = 0; + if (p_target_window != INVALID_WINDOW_ID && windows.has(p_target_window)) { // Follow the defined window rid[0].hwndTarget = windows[p_target_window].hWnd; + rid[1].hwndTarget = windows[p_target_window].hWnd; } else { // Follow the keyboard focus rid[0].hwndTarget = 0; + rid[1].hwndTarget = 0; } - if (RegisterRawInputDevices(rid, 1, sizeof(rid[0])) == FALSE) { + if (RegisterRawInputDevices(rid, 2, sizeof(rid[0])) == FALSE) { // Registration failed. use_raw_input = false; } @@ -219,7 +229,137 @@ void DisplayServerWindows::tts_stop() { tts->stop(); } +// Silence warning due to a COM API weirdness. +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#endif + +class FileDialogEventHandler : public IFileDialogEvents, public IFileDialogControlEvents { + LONG ref_count = 1; + int ctl_id = 1; + + HashMap<int, String> ctls; + Dictionary selected; + String root; + +public: + // IUnknown methods + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppv) { + static const QITAB qit[] = { + QITABENT(FileDialogEventHandler, IFileDialogEvents), + QITABENT(FileDialogEventHandler, IFileDialogControlEvents), + { 0, 0 }, + }; + return QISearch(this, qit, riid, ppv); + } + + ULONG STDMETHODCALLTYPE AddRef() { + return InterlockedIncrement(&ref_count); + } + + ULONG STDMETHODCALLTYPE Release() { + long ref = InterlockedDecrement(&ref_count); + if (!ref) { + delete this; + } + return ref; + } + + // IFileDialogEvents methods + HRESULT STDMETHODCALLTYPE OnFileOk(IFileDialog *) { return S_OK; }; + HRESULT STDMETHODCALLTYPE OnFolderChange(IFileDialog *) { return S_OK; }; + + HRESULT STDMETHODCALLTYPE OnFolderChanging(IFileDialog *p_pfd, IShellItem *p_item) { + if (root.is_empty()) { + return S_OK; + } + + LPWSTR lpw_path = nullptr; + p_item->GetDisplayName(SIGDN_FILESYSPATH, &lpw_path); + if (!lpw_path) { + return S_FALSE; + } + String path = String::utf16((const char16_t *)lpw_path).simplify_path(); + if (!path.begins_with(root.simplify_path())) { + return S_FALSE; + } + return S_OK; + } + + HRESULT STDMETHODCALLTYPE OnHelp(IFileDialog *) { return S_OK; }; + HRESULT STDMETHODCALLTYPE OnSelectionChange(IFileDialog *) { return S_OK; }; + HRESULT STDMETHODCALLTYPE OnShareViolation(IFileDialog *, IShellItem *, FDE_SHAREVIOLATION_RESPONSE *) { return S_OK; }; + HRESULT STDMETHODCALLTYPE OnTypeChange(IFileDialog *pfd) { return S_OK; }; + HRESULT STDMETHODCALLTYPE OnOverwrite(IFileDialog *, IShellItem *, FDE_OVERWRITE_RESPONSE *) { return S_OK; }; + + // IFileDialogControlEvents methods + HRESULT STDMETHODCALLTYPE OnItemSelected(IFileDialogCustomize *p_pfdc, DWORD p_ctl_id, DWORD p_item_idx) { + if (ctls.has(p_ctl_id)) { + selected[ctls[p_ctl_id]] = (int)p_item_idx; + } + return S_OK; + } + + HRESULT STDMETHODCALLTYPE OnButtonClicked(IFileDialogCustomize *, DWORD) { return S_OK; }; + HRESULT STDMETHODCALLTYPE OnCheckButtonToggled(IFileDialogCustomize *p_pfdc, DWORD p_ctl_id, BOOL p_checked) { + if (ctls.has(p_ctl_id)) { + selected[ctls[p_ctl_id]] = (bool)p_checked; + } + return S_OK; + } + HRESULT STDMETHODCALLTYPE OnControlActivating(IFileDialogCustomize *, DWORD) { return S_OK; }; + + Dictionary get_selected() { + return selected; + } + + void set_root(const String &p_root) { + root = p_root; + } + + void add_option(IFileDialogCustomize *p_pfdc, const String &p_name, const Vector<String> &p_options, int p_default) { + int gid = ctl_id++; + int cid = ctl_id++; + + if (p_options.size() == 0) { + // Add check box. + p_pfdc->StartVisualGroup(gid, L""); + p_pfdc->AddCheckButton(cid, (LPCWSTR)p_name.utf16().get_data(), p_default); + p_pfdc->SetControlState(cid, CDCS_VISIBLE | CDCS_ENABLED); + p_pfdc->EndVisualGroup(); + selected[p_name] = (bool)p_default; + } else { + // Add combo box. + p_pfdc->StartVisualGroup(gid, (LPCWSTR)p_name.utf16().get_data()); + p_pfdc->AddComboBox(cid); + p_pfdc->SetControlState(cid, CDCS_VISIBLE | CDCS_ENABLED); + for (int i = 0; i < p_options.size(); i++) { + p_pfdc->AddControlItem(cid, i, (LPCWSTR)p_options[i].utf16().get_data()); + } + p_pfdc->SetSelectedControlItem(cid, p_default); + p_pfdc->EndVisualGroup(); + selected[p_name] = p_default; + } + ctls[cid] = p_name; + } + + virtual ~FileDialogEventHandler(){}; +}; + +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif + Error DisplayServerWindows::file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) { + return _file_dialog_with_options_show(p_title, p_current_directory, String(), p_filename, p_show_hidden, p_mode, p_filters, TypedArray<Dictionary>(), p_callback, false); +} + +Error DisplayServerWindows::file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback) { + return _file_dialog_with_options_show(p_title, p_current_directory, p_root, p_filename, p_show_hidden, p_mode, p_filters, p_options, p_callback, true); +} + +Error DisplayServerWindows::_file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback, bool p_options_in_cb) { _THREAD_SAFE_METHOD_ ERR_FAIL_INDEX_V(int(p_mode), FILE_DIALOG_MODE_SAVE_MAX, FAILED); @@ -269,6 +409,31 @@ Error DisplayServerWindows::file_dialog_show(const String &p_title, const String hr = CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC_SERVER, IID_IFileOpenDialog, (void **)&pfd); } if (SUCCEEDED(hr)) { + IFileDialogEvents *pfde = nullptr; + FileDialogEventHandler *event_handler = new FileDialogEventHandler(); + hr = event_handler->QueryInterface(IID_PPV_ARGS(&pfde)); + + DWORD cookie = 0; + hr = pfd->Advise(pfde, &cookie); + + IFileDialogCustomize *pfdc = nullptr; + hr = pfd->QueryInterface(IID_PPV_ARGS(&pfdc)); + + for (int i = 0; i < p_options.size(); i++) { + const Dictionary &item = p_options[i]; + if (!item.has("name") || !item.has("values") || !item.has("default")) { + continue; + } + const String &name = item["name"]; + const Vector<String> &options = item["values"]; + int default_idx = item["default"]; + + event_handler->add_option(pfdc, name, options, default_idx); + } + event_handler->set_root(p_root); + + pfdc->Release(); + DWORD flags; pfd->GetOptions(&flags); if (p_mode == FILE_DIALOG_MODE_OPEN_FILES) { @@ -306,8 +471,18 @@ Error DisplayServerWindows::file_dialog_show(const String &p_title, const String } hr = pfd->Show(windows[window_id].hWnd); + pfd->Unadvise(cookie); + + Dictionary options = event_handler->get_selected(); + + pfde->Release(); + event_handler->Release(); + UINT index = 0; pfd->GetFileTypeIndex(&index); + if (index > 0) { + index = index - 1; + } if (SUCCEEDED(hr)) { Vector<String> file_names; @@ -346,30 +521,60 @@ Error DisplayServerWindows::file_dialog_show(const String &p_title, const String } } if (!p_callback.is_null()) { - Variant v_result = true; - Variant v_files = file_names; - Variant v_index = index; - Variant ret; - Callable::CallError ce; - const Variant *args[3] = { &v_result, &v_files, &v_index }; - - p_callback.callp(args, 3, ret, ce); - if (ce.error != Callable::CallError::CALL_OK) { - ERR_PRINT(vformat("Failed to execute file dialogs callback: %s.", Variant::get_callable_error_text(p_callback, args, 3, ce))); + if (p_options_in_cb) { + Variant v_result = true; + Variant v_files = file_names; + Variant v_index = index; + Variant v_opt = options; + Variant ret; + Callable::CallError ce; + const Variant *args[4] = { &v_result, &v_files, &v_index, &v_opt }; + + p_callback.callp(args, 4, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat("Failed to execute file dialogs callback: %s.", Variant::get_callable_error_text(p_callback, args, 4, ce))); + } + } else { + Variant v_result = true; + Variant v_files = file_names; + Variant v_index = index; + Variant ret; + Callable::CallError ce; + const Variant *args[3] = { &v_result, &v_files, &v_index }; + + p_callback.callp(args, 3, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat("Failed to execute file dialogs callback: %s.", Variant::get_callable_error_text(p_callback, args, 3, ce))); + } } } } else { if (!p_callback.is_null()) { - Variant v_result = false; - Variant v_files = Vector<String>(); - Variant v_index = index; - Variant ret; - Callable::CallError ce; - const Variant *args[3] = { &v_result, &v_files, &v_index }; - - p_callback.callp(args, 3, ret, ce); - if (ce.error != Callable::CallError::CALL_OK) { - ERR_PRINT(vformat("Failed to execute file dialogs callback: %s.", Variant::get_callable_error_text(p_callback, args, 3, ce))); + if (p_options_in_cb) { + Variant v_result = false; + Variant v_files = Vector<String>(); + Variant v_index = index; + Variant v_opt = options; + Variant ret; + Callable::CallError ce; + const Variant *args[4] = { &v_result, &v_files, &v_index, &v_opt }; + + p_callback.callp(args, 4, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat("Failed to execute file dialogs callback: %s.", Variant::get_callable_error_text(p_callback, args, 4, ce))); + } + } else { + Variant v_result = false; + Variant v_files = Vector<String>(); + Variant v_index = index; + Variant ret; + Callable::CallError ce; + const Variant *args[3] = { &v_result, &v_files, &v_index }; + + p_callback.callp(args, 3, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat("Failed to execute file dialogs callback: %s.", Variant::get_callable_error_text(p_callback, args, 3, ce))); + } } } } @@ -2963,6 +3168,14 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA // Process window messages. switch (uMsg) { + case WM_CREATE: { + if (is_dark_mode_supported() && dark_title_available) { + BOOL value = is_dark_mode(); + + ::DwmSetWindowAttribute(windows[window_id].hWnd, use_legacy_dark_mode_before_20H1 ? DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 : DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value)); + SendMessageW(windows[window_id].hWnd, WM_PAINT, 0, 0); + } + } break; case WM_NCPAINT: { if (RenderingServer::get_singleton() && (windows[window_id].borderless || (windows[window_id].fullscreen && windows[window_id].multiwindow_fs))) { Color color = RenderingServer::get_singleton()->get_default_clear_color(); @@ -3095,14 +3308,14 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA if (lParam && CompareStringOrdinal(reinterpret_cast<LPCWCH>(lParam), -1, L"ImmersiveColorSet", -1, true) == CSTR_EQUAL) { if (is_dark_mode_supported() && dark_title_available) { BOOL value = is_dark_mode(); - ::DwmSetWindowAttribute(windows[window_id].hWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value)); + ::DwmSetWindowAttribute(windows[window_id].hWnd, use_legacy_dark_mode_before_20H1 ? DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 : DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value)); } } } break; case WM_THEMECHANGED: { if (is_dark_mode_supported() && dark_title_available) { BOOL value = is_dark_mode(); - ::DwmSetWindowAttribute(windows[window_id].hWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value)); + ::DwmSetWindowAttribute(windows[window_id].hWnd, use_legacy_dark_mode_before_20H1 ? DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 : DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value)); } } break; case WM_SYSCOMMAND: // Intercept system commands. @@ -3141,7 +3354,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } break; case WM_INPUT: { - if (mouse_mode != MOUSE_MODE_CAPTURED || !use_raw_input) { + if (!use_raw_input) { break; } @@ -3159,7 +3372,32 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA RAWINPUT *raw = (RAWINPUT *)lpb; - if (raw->header.dwType == RIM_TYPEMOUSE) { + if (raw->header.dwType == RIM_TYPEKEYBOARD) { + if (raw->data.keyboard.VKey == VK_SHIFT) { + // If multiple Shifts are held down at the same time, + // Windows natively only sends a KEYUP for the last one to be released. + if (raw->data.keyboard.Flags & RI_KEY_BREAK) { + if (GetAsyncKeyState(VK_SHIFT) < 0) { + // A Shift is released, but another Shift is still held + ERR_BREAK(key_event_pos >= KEY_EVENT_BUFFER_SIZE); + + KeyEvent ke; + ke.shift = false; + ke.alt = alt_mem; + ke.control = control_mem; + ke.meta = meta_mem; + ke.uMsg = WM_KEYUP; + ke.window_id = window_id; + + ke.wParam = VK_SHIFT; + // data.keyboard.MakeCode -> 0x2A - left shift, 0x36 - right shift. + // Bit 30 -> key was previously down, bit 31 -> key is being released. + ke.lParam = raw->data.keyboard.MakeCode << 16 | 1 << 30 | 1 << 31; + key_event_buffer[key_event_pos++] = ke; + } + } + } + } else if (mouse_mode == MOUSE_MODE_CAPTURED && raw->header.dwType == RIM_TYPEMOUSE) { Ref<InputEventMouseMotion> mm; mm.instantiate(); @@ -4164,6 +4402,7 @@ void DisplayServerWindows::_process_key_events() { } Key key_label = keycode; Key physical_keycode = KeyMappingWindows::get_scansym((ke.lParam >> 16) & 0xFF, ke.lParam & (1 << 24)); + KeyLocation location = KeyMappingWindows::get_location((ke.lParam >> 16) & 0xFF, ke.lParam & (1 << 24)); static BYTE keyboard_state[256]; memset(keyboard_state, 0, 256); @@ -4190,6 +4429,7 @@ void DisplayServerWindows::_process_key_events() { } k->set_keycode(keycode); k->set_physical_keycode(physical_keycode); + k->set_location(location); k->set_key_label(key_label); if (i + 1 < key_event_pos && key_event_buffer[i + 1].uMsg == WM_CHAR) { @@ -4355,7 +4595,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, if (is_dark_mode_supported() && dark_title_available) { BOOL value = is_dark_mode(); - ::DwmSetWindowAttribute(wd.hWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value)); + ::DwmSetWindowAttribute(wd.hWnd, use_legacy_dark_mode_before_20H1 ? DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 : DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value)); } #ifdef RD_ENABLED @@ -4493,6 +4733,7 @@ WTEnablePtr DisplayServerWindows::wintab_WTEnable = nullptr; // UXTheme API. bool DisplayServerWindows::dark_title_available = false; +bool DisplayServerWindows::use_legacy_dark_mode_before_20H1 = false; bool DisplayServerWindows::ux_theme_available = false; ShouldAppsUseDarkModePtr DisplayServerWindows::ShouldAppsUseDarkMode = nullptr; GetImmersiveColorFromColorSetExPtr DisplayServerWindows::GetImmersiveColorFromColorSetEx = nullptr; @@ -4614,8 +4855,11 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win GetImmersiveUserColorSetPreference = (GetImmersiveUserColorSetPreferencePtr)GetProcAddress(ux_theme_lib, MAKEINTRESOURCEA(98)); ux_theme_available = ShouldAppsUseDarkMode && GetImmersiveColorFromColorSetEx && GetImmersiveColorTypeFromName && GetImmersiveUserColorSetPreference; - if (os_ver.dwBuildNumber >= 22000) { + if (os_ver.dwBuildNumber >= 18363) { dark_title_available = true; + if (os_ver.dwBuildNumber < 19041) { + use_legacy_dark_mode_before_20H1 = true; + } } } diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h index 2668e14540..4e1d2cf85c 100644 --- a/platform/windows/display_server_windows.h +++ b/platform/windows/display_server_windows.h @@ -294,6 +294,7 @@ class DisplayServerWindows : public DisplayServer { // UXTheme API static bool dark_title_available; + static bool use_legacy_dark_mode_before_20H1; static bool ux_theme_available; static ShouldAppsUseDarkModePtr ShouldAppsUseDarkMode; static GetImmersiveColorFromColorSetExPtr GetImmersiveColorFromColorSetEx; @@ -497,6 +498,8 @@ class DisplayServerWindows : public DisplayServer { LRESULT _handle_early_window_message(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); Point2i _get_screens_origin() const; + Error _file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback, bool p_options_in_cb); + public: LRESULT WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); LRESULT MouseProc(int code, WPARAM wParam, LPARAM lParam); @@ -521,6 +524,7 @@ public: virtual Color get_accent_color() const override; virtual Error file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) override; + virtual Error file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback) override; virtual void mouse_set_mode(MouseMode p_mode) override; virtual MouseMode mouse_get_mode() const override; diff --git a/platform/windows/godot_res.rc b/platform/windows/godot_res.rc index 0593c8b069..8187c0c936 100644 --- a/platform/windows/godot_res.rc +++ b/platform/windows/godot_res.rc @@ -1,16 +1,12 @@ #include "core/version.h" -#ifndef _STR -#define _STR(m_x) #m_x -#define _MKSTR(m_x) _STR(m_x) -#endif GODOT_ICON ICON platform/windows/godot.ico 1 VERSIONINFO -FILEVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,0 -PRODUCTVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,0 -FILEOS 4 -FILETYPE 1 +FILEVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,0 +PRODUCTVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,0 +FILEOS 4 +FILETYPE 1 BEGIN BLOCK "StringFileInfo" BEGIN @@ -21,7 +17,7 @@ BEGIN VALUE "FileVersion", VERSION_NUMBER VALUE "ProductName", VERSION_NAME VALUE "Licence", "MIT" - VALUE "LegalCopyright", "Copyright (c) 2007-" _MKSTR(VERSION_YEAR) " Juan Linietsky, Ariel Manzur and contributors" + VALUE "LegalCopyright", "(c) 2007-present Juan Linietsky, Ariel Manzur and Godot Engine contributors" VALUE "Info", "https://godotengine.org" VALUE "ProductVersion", VERSION_FULL_BUILD END diff --git a/platform/windows/godot_res_wrap.rc b/platform/windows/godot_res_wrap.rc index 9dd29afe51..27ad26cbc5 100644 --- a/platform/windows/godot_res_wrap.rc +++ b/platform/windows/godot_res_wrap.rc @@ -1,16 +1,12 @@ #include "core/version.h" -#ifndef _STR -#define _STR(m_x) #m_x -#define _MKSTR(m_x) _STR(m_x) -#endif GODOT_ICON ICON platform/windows/godot_console.ico 1 VERSIONINFO -FILEVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,0 -PRODUCTVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,0 -FILEOS 4 -FILETYPE 1 +FILEVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,0 +PRODUCTVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,0 +FILEOS 4 +FILETYPE 1 BEGIN BLOCK "StringFileInfo" BEGIN @@ -21,7 +17,7 @@ BEGIN VALUE "FileVersion", VERSION_NUMBER VALUE "ProductName", VERSION_NAME " (Console)" VALUE "Licence", "MIT" - VALUE "LegalCopyright", "Copyright (c) 2007-" _MKSTR(VERSION_YEAR) " Juan Linietsky, Ariel Manzur and contributors" + VALUE "LegalCopyright", "(c) 2007-present Juan Linietsky, Ariel Manzur and Godot Engine contributors" VALUE "Info", "https://godotengine.org" VALUE "ProductVersion", VERSION_FULL_BUILD END diff --git a/platform/windows/key_mapping_windows.cpp b/platform/windows/key_mapping_windows.cpp index b376854c0c..20905d0fe9 100644 --- a/platform/windows/key_mapping_windows.cpp +++ b/platform/windows/key_mapping_windows.cpp @@ -46,6 +46,7 @@ HashMap<unsigned int, Key, HashMapHasherKeys> vk_map; HashMap<unsigned int, Key, HashMapHasherKeys> scansym_map; HashMap<Key, unsigned int, HashMapHasherKeys> scansym_map_inv; HashMap<unsigned int, Key, HashMapHasherKeys> scansym_map_ext; +HashMap<unsigned int, KeyLocation, HashMapHasherKeys> location_map; void KeyMappingWindows::initialize() { // VK_LBUTTON (0x01) @@ -380,6 +381,15 @@ void KeyMappingWindows::initialize() { scansym_map_ext[0x6C] = Key::LAUNCHMAIL; scansym_map_ext[0x6D] = Key::LAUNCHMEDIA; scansym_map_ext[0x78] = Key::MEDIARECORD; + + // Scancode to physical location map. + // Shift. + location_map[0x2A] = KeyLocation::LEFT; + location_map[0x36] = KeyLocation::RIGHT; + // Meta. + location_map[0x5B] = KeyLocation::LEFT; + location_map[0x5C] = KeyLocation::RIGHT; + // Ctrl and Alt must be handled differently. } Key KeyMappingWindows::get_keysym(unsigned int p_code) { @@ -424,3 +434,16 @@ bool KeyMappingWindows::is_extended_key(unsigned int p_code) { p_code == VK_RIGHT || p_code == VK_DOWN; } + +KeyLocation KeyMappingWindows::get_location(unsigned int p_code, bool p_extended) { + // Right- ctrl and alt have the same scancode as left, but are in the extended keys. + const Key *key = scansym_map.getptr(p_code); + if (key && (*key == Key::CTRL || *key == Key::ALT)) { + return p_extended ? KeyLocation::RIGHT : KeyLocation::LEFT; + } + const KeyLocation *location = location_map.getptr(p_code); + if (location) { + return *location; + } + return KeyLocation::UNSPECIFIED; +} diff --git a/platform/windows/key_mapping_windows.h b/platform/windows/key_mapping_windows.h index a98aa7ed68..e6f184a2cc 100644 --- a/platform/windows/key_mapping_windows.h +++ b/platform/windows/key_mapping_windows.h @@ -47,6 +47,7 @@ public: static unsigned int get_scancode(Key p_keycode); static Key get_scansym(unsigned int p_code, bool p_extended); static bool is_extended_key(unsigned int p_code); + static KeyLocation get_location(unsigned int p_code, bool p_extended); }; #endif // KEY_MAPPING_WINDOWS_H diff --git a/scene/2d/collision_shape_2d.cpp b/scene/2d/collision_shape_2d.cpp index 70ec57aa7a..ee413c7bc2 100644 --- a/scene/2d/collision_shape_2d.cpp +++ b/scene/2d/collision_shape_2d.cpp @@ -178,7 +178,7 @@ PackedStringArray CollisionShape2D::get_configuration_warnings() const { CollisionObject2D *col_object = Object::cast_to<CollisionObject2D>(get_parent()); if (col_object == nullptr) { - warnings.push_back(RTR("CollisionShape2D only serves to provide a collision shape to a CollisionObject2D derived node. Please only use it as a child of Area2D, StaticBody2D, RigidBody2D, CharacterBody2D, etc. to give them a shape.")); + warnings.push_back(RTR("CollisionShape2D only serves to provide a collision shape to a CollisionObject2D derived node.\nPlease only use it as a child of Area2D, StaticBody2D, RigidBody2D, CharacterBody2D, etc. to give them a shape.")); } if (!shape.is_valid()) { warnings.push_back(RTR("A shape must be provided for CollisionShape2D to function. Please create a shape resource for it!")); diff --git a/scene/2d/physics_body_2d.h b/scene/2d/physics_body_2d.h index f5448ead40..208e72c40f 100644 --- a/scene/2d/physics_body_2d.h +++ b/scene/2d/physics_body_2d.h @@ -362,44 +362,6 @@ public: int get_slide_collision_count() const; PhysicsServer2D::MotionResult get_slide_collision(int p_bounce) const; - CharacterBody2D(); - ~CharacterBody2D(); - -private: - real_t margin = 0.08; - MotionMode motion_mode = MOTION_MODE_GROUNDED; - PlatformOnLeave platform_on_leave = PLATFORM_ON_LEAVE_ADD_VELOCITY; - - bool floor_constant_speed = false; - bool floor_stop_on_slope = true; - bool floor_block_on_wall = true; - bool slide_on_ceiling = true; - int max_slides = 4; - int platform_layer = 0; - real_t floor_max_angle = Math::deg_to_rad((real_t)45.0); - real_t floor_snap_length = 1; - real_t wall_min_slide_angle = Math::deg_to_rad((real_t)15.0); - Vector2 up_direction = Vector2(0.0, -1.0); - uint32_t platform_floor_layers = UINT32_MAX; - uint32_t platform_wall_layers = 0; - Vector2 velocity; - - Vector2 floor_normal; - Vector2 platform_velocity; - Vector2 wall_normal; - Vector2 last_motion; - Vector2 previous_position; - Vector2 real_velocity; - - RID platform_rid; - ObjectID platform_object_id; - bool on_floor = false; - bool on_ceiling = false; - bool on_wall = false; - - Vector<PhysicsServer2D::MotionResult> motion_results; - Vector<Ref<KinematicCollision2D>> slide_colliders; - void set_safe_margin(real_t p_margin); real_t get_safe_margin() const; @@ -439,6 +401,44 @@ private: void set_platform_on_leave(PlatformOnLeave p_on_leave_velocity); PlatformOnLeave get_platform_on_leave() const; + CharacterBody2D(); + ~CharacterBody2D(); + +private: + real_t margin = 0.08; + MotionMode motion_mode = MOTION_MODE_GROUNDED; + PlatformOnLeave platform_on_leave = PLATFORM_ON_LEAVE_ADD_VELOCITY; + + bool floor_constant_speed = false; + bool floor_stop_on_slope = true; + bool floor_block_on_wall = true; + bool slide_on_ceiling = true; + int max_slides = 4; + int platform_layer = 0; + real_t floor_max_angle = Math::deg_to_rad((real_t)45.0); + real_t floor_snap_length = 1; + real_t wall_min_slide_angle = Math::deg_to_rad((real_t)15.0); + Vector2 up_direction = Vector2(0.0, -1.0); + uint32_t platform_floor_layers = UINT32_MAX; + uint32_t platform_wall_layers = 0; + Vector2 velocity; + + Vector2 floor_normal; + Vector2 platform_velocity; + Vector2 wall_normal; + Vector2 last_motion; + Vector2 previous_position; + Vector2 real_velocity; + + RID platform_rid; + ObjectID platform_object_id; + bool on_floor = false; + bool on_ceiling = false; + bool on_wall = false; + + Vector<PhysicsServer2D::MotionResult> motion_results; + Vector<Ref<KinematicCollision2D>> slide_colliders; + void _move_and_slide_floating(double p_delta); void _move_and_slide_grounded(double p_delta, bool p_was_on_floor); diff --git a/scene/3d/lightmap_gi.cpp b/scene/3d/lightmap_gi.cpp index 92b783392d..86ff6d15dd 100644 --- a/scene/3d/lightmap_gi.cpp +++ b/scene/3d/lightmap_gi.cpp @@ -1532,7 +1532,7 @@ PackedStringArray LightmapGI::get_configuration_warnings() const { PackedStringArray warnings = Node::get_configuration_warnings(); if (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") { - warnings.push_back(RTR("Lightmap cannot be baked when using the GL Compatibility backend yet. Support will be added in a future release.")); + warnings.push_back(RTR("Lightmap can only be baked from a device that supports the RD backends. Lightmap baking may fail.")); return warnings; } diff --git a/scene/3d/physics_body_3d.h b/scene/3d/physics_body_3d.h index 9798fc4845..e8d5ef2103 100644 --- a/scene/3d/physics_body_3d.h +++ b/scene/3d/physics_body_3d.h @@ -381,6 +381,45 @@ public: int get_slide_collision_count() const; PhysicsServer3D::MotionResult get_slide_collision(int p_bounce) const; + void set_safe_margin(real_t p_margin); + real_t get_safe_margin() const; + + bool is_floor_stop_on_slope_enabled() const; + void set_floor_stop_on_slope_enabled(bool p_enabled); + + bool is_floor_constant_speed_enabled() const; + void set_floor_constant_speed_enabled(bool p_enabled); + + bool is_floor_block_on_wall_enabled() const; + void set_floor_block_on_wall_enabled(bool p_enabled); + + bool is_slide_on_ceiling_enabled() const; + void set_slide_on_ceiling_enabled(bool p_enabled); + + int get_max_slides() const; + void set_max_slides(int p_max_slides); + + real_t get_floor_max_angle() const; + void set_floor_max_angle(real_t p_radians); + + real_t get_floor_snap_length(); + void set_floor_snap_length(real_t p_floor_snap_length); + + real_t get_wall_min_slide_angle() const; + void set_wall_min_slide_angle(real_t p_radians); + + uint32_t get_platform_floor_layers() const; + void set_platform_floor_layers(const uint32_t p_exclude_layer); + + uint32_t get_platform_wall_layers() const; + void set_platform_wall_layers(const uint32_t p_exclude_layer); + + void set_motion_mode(MotionMode p_mode); + MotionMode get_motion_mode() const; + + void set_platform_on_leave(PlatformOnLeave p_on_leave_velocity); + PlatformOnLeave get_platform_on_leave() const; + CharacterBody3D(); ~CharacterBody3D(); @@ -435,45 +474,6 @@ private: Vector<PhysicsServer3D::MotionResult> motion_results; Vector<Ref<KinematicCollision3D>> slide_colliders; - void set_safe_margin(real_t p_margin); - real_t get_safe_margin() const; - - bool is_floor_stop_on_slope_enabled() const; - void set_floor_stop_on_slope_enabled(bool p_enabled); - - bool is_floor_constant_speed_enabled() const; - void set_floor_constant_speed_enabled(bool p_enabled); - - bool is_floor_block_on_wall_enabled() const; - void set_floor_block_on_wall_enabled(bool p_enabled); - - bool is_slide_on_ceiling_enabled() const; - void set_slide_on_ceiling_enabled(bool p_enabled); - - int get_max_slides() const; - void set_max_slides(int p_max_slides); - - real_t get_floor_max_angle() const; - void set_floor_max_angle(real_t p_radians); - - real_t get_floor_snap_length(); - void set_floor_snap_length(real_t p_floor_snap_length); - - real_t get_wall_min_slide_angle() const; - void set_wall_min_slide_angle(real_t p_radians); - - uint32_t get_platform_floor_layers() const; - void set_platform_floor_layers(const uint32_t p_exclude_layer); - - uint32_t get_platform_wall_layers() const; - void set_platform_wall_layers(const uint32_t p_exclude_layer); - - void set_motion_mode(MotionMode p_mode); - MotionMode get_motion_mode() const; - - void set_platform_on_leave(PlatformOnLeave p_on_leave_velocity); - PlatformOnLeave get_platform_on_leave() const; - void _move_and_slide_floating(double p_delta); void _move_and_slide_grounded(double p_delta, bool p_was_on_floor); diff --git a/scene/3d/soft_body_3d.cpp b/scene/3d/soft_body_3d.cpp index 3f1878f30f..0aaa806ee6 100644 --- a/scene/3d/soft_body_3d.cpp +++ b/scene/3d/soft_body_3d.cpp @@ -714,9 +714,6 @@ void SoftBody3D::_update_cache_pin_points_datas() { if (!w[i].spatial_attachment_path.is_empty()) { w[i].spatial_attachment = Object::cast_to<Node3D>(get_node(w[i].spatial_attachment_path)); } - if (!w[i].spatial_attachment) { - ERR_PRINT("Node3D node not defined in the pinned point, this is undefined behavior for SoftBody3D!"); - } } } diff --git a/scene/animation/tween.cpp b/scene/animation/tween.cpp index 8193bbf3f1..34fc83500f 100644 --- a/scene/animation/tween.cpp +++ b/scene/animation/tween.cpp @@ -505,11 +505,13 @@ Tween::Tween(bool p_valid) { Ref<PropertyTweener> PropertyTweener::from(const Variant &p_value) { ERR_FAIL_COND_V(tween.is_null(), nullptr); - if (!tween->_validate_type_match(p_value, final_val)) { + + Variant from_value = p_value; + if (!tween->_validate_type_match(final_val, from_value)) { return nullptr; } - initial_val = p_value; + initial_val = from_value; do_continue = false; return this; } diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index 349102fafb..a7be5c9af0 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -1419,13 +1419,15 @@ void Control::_set_global_position(const Point2 &p_point) { void Control::set_global_position(const Point2 &p_point, bool p_keep_offsets) { ERR_MAIN_THREAD_GUARD; - Transform2D inv; - if (data.parent_canvas_item) { - inv = data.parent_canvas_item->get_global_transform().affine_inverse(); + Transform2D global_transform_cache = get_global_transform(); + if (p_point == global_transform_cache.get_origin()) { + return; // Edge case, but avoids calculation. } - set_position(inv.xform(p_point), p_keep_offsets); + Point2 internal_position = global_transform_cache.affine_inverse().xform(p_point); + + set_position(internal_position + data.pos_cache, p_keep_offsets); } Point2 Control::get_global_position() const { diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index 5d770c2eb2..8f58c1e6f5 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -30,9 +30,13 @@ #include "file_dialog.h" +#include "core/config/project_settings.h" #include "core/os/keyboard.h" #include "core/string/print_string.h" +#include "scene/gui/check_box.h" +#include "scene/gui/grid_container.h" #include "scene/gui/label.h" +#include "scene/gui/option_button.h" #include "scene/theme/theme_db.h" FileDialog::GetIconFunc FileDialog::get_icon_func = nullptr; @@ -56,20 +60,30 @@ void FileDialog::_focus_file_text() { } void FileDialog::popup(const Rect2i &p_rect) { + _update_option_controls(); + #ifdef TOOLS_ENABLED if (is_part_of_edited_scene()) { ConfirmationDialog::popup(p_rect); } #endif - if (access == ACCESS_FILESYSTEM && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG) && (use_native_dialog || OS::get_singleton()->is_sandboxed())) { - DisplayServer::get_singleton()->file_dialog_show(get_title(), dir->get_text(), file->get_text().get_file(), show_hidden_files, DisplayServer::FileDialogMode(mode), filters, callable_mp(this, &FileDialog::_native_dialog_cb)); + if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG) && (use_native_dialog || OS::get_singleton()->is_sandboxed())) { + String root; + if (access == ACCESS_RESOURCES) { + root = ProjectSettings::get_singleton()->get_resource_path(); + } else if (access == ACCESS_USERDATA) { + root = OS::get_singleton()->get_user_data_dir(); + } + DisplayServer::get_singleton()->file_dialog_with_options_show(get_title(), ProjectSettings::get_singleton()->globalize_path(dir->get_text()), root, file->get_text().get_file(), show_hidden_files, DisplayServer::FileDialogMode(mode), filters, _get_options(), callable_mp(this, &FileDialog::_native_dialog_cb)); } else { ConfirmationDialog::popup(p_rect); } } void FileDialog::set_visible(bool p_visible) { + _update_option_controls(); + #ifdef TOOLS_ENABLED if (is_part_of_edited_scene()) { ConfirmationDialog::set_visible(p_visible); @@ -77,23 +91,52 @@ void FileDialog::set_visible(bool p_visible) { } #endif - if (access == ACCESS_FILESYSTEM && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG) && (use_native_dialog || OS::get_singleton()->is_sandboxed())) { + if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG) && (use_native_dialog || OS::get_singleton()->is_sandboxed())) { if (p_visible) { - DisplayServer::get_singleton()->file_dialog_show(get_title(), dir->get_text(), file->get_text().get_file(), show_hidden_files, DisplayServer::FileDialogMode(mode), filters, callable_mp(this, &FileDialog::_native_dialog_cb)); + String root; + if (access == ACCESS_RESOURCES) { + root = ProjectSettings::get_singleton()->get_resource_path(); + } else if (access == ACCESS_USERDATA) { + root = OS::get_singleton()->get_user_data_dir(); + } + DisplayServer::get_singleton()->file_dialog_with_options_show(get_title(), ProjectSettings::get_singleton()->globalize_path(dir->get_text()), root, file->get_text().get_file(), show_hidden_files, DisplayServer::FileDialogMode(mode), filters, _get_options(), callable_mp(this, &FileDialog::_native_dialog_cb)); } } else { ConfirmationDialog::set_visible(p_visible); } } -void FileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &p_files, int p_filter) { +void FileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &p_files, int p_filter, const Dictionary &p_selected_options) { if (p_ok) { if (p_files.size() > 0) { - const String &f = p_files[0]; + Vector<String> files = p_files; + if (access != ACCESS_FILESYSTEM) { + for (String &file_name : files) { + file_name = ProjectSettings::get_singleton()->localize_path(file_name); + } + } + String f = files[0]; if (mode == FILE_MODE_OPEN_FILES) { - emit_signal(SNAME("files_selected"), p_files); + emit_signal(SNAME("files_selected"), files); } else { if (mode == FILE_MODE_SAVE_FILE) { + if (p_filter >= 0 && p_filter < filters.size()) { + bool valid = false; + String flt = filters[p_filter].get_slice(";", 0); + int filter_slice_count = flt.get_slice_count(","); + for (int j = 0; j < filter_slice_count; j++) { + String str = (flt.get_slice(",", j).strip_edges()); + if (f.match(str)) { + valid = true; + break; + } + } + + if (!valid && filter_slice_count > 0) { + String str = (flt.get_slice(",", 0).strip_edges()); + f += str.substr(1, str.length() - 1); + } + } emit_signal(SNAME("file_selected"), f); } else if ((mode == FILE_MODE_OPEN_ANY || mode == FILE_MODE_OPEN_FILE) && dir_access->file_exists(f)) { emit_signal(SNAME("file_selected"), f); @@ -103,7 +146,8 @@ void FileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &p_files, int } file->set_text(f); dir->set_text(f.get_base_dir()); - _filter_selected(p_filter); + selected_options = p_selected_options; + filter->select(p_filter); } } else { file->set_text(""); @@ -1007,6 +1051,212 @@ void FileDialog::_update_drives(bool p_select) { bool FileDialog::default_show_hidden_files = false; +TypedArray<Dictionary> FileDialog::_get_options() const { + TypedArray<Dictionary> out; + for (const FileDialog::Option &opt : options) { + Dictionary dict; + dict["name"] = opt.name; + dict["values"] = opt.values; + dict["default"] = (int)selected_options.get(opt.name, opt.default_idx); + out.push_back(dict); + } + return out; +} + +void FileDialog::_option_changed_checkbox_toggled(bool p_pressed, const String &p_name) { + if (selected_options.has(p_name)) { + selected_options[p_name] = p_pressed; + } +} + +void FileDialog::_option_changed_item_selected(int p_idx, const String &p_name) { + if (selected_options.has(p_name)) { + selected_options[p_name] = p_idx; + } +} + +void FileDialog::_update_option_controls() { + if (!options_dirty) { + return; + } + options_dirty = false; + + while (grid_options->get_child_count(false) > 0) { + Node *child = grid_options->get_child(0); + grid_options->remove_child(child); + child->queue_free(); + } + selected_options.clear(); + + for (const FileDialog::Option &opt : options) { + Label *lbl = memnew(Label); + lbl->set_text(opt.name); + grid_options->add_child(lbl); + if (opt.values.is_empty()) { + CheckBox *cb = memnew(CheckBox); + cb->set_pressed(opt.default_idx); + grid_options->add_child(cb); + cb->connect("toggled", callable_mp(this, &FileDialog::_option_changed_checkbox_toggled).bind(opt.name)); + selected_options[opt.name] = (bool)opt.default_idx; + } else { + OptionButton *ob = memnew(OptionButton); + for (const String &val : opt.values) { + ob->add_item(val); + } + ob->select(opt.default_idx); + grid_options->add_child(ob); + ob->connect("item_selected", callable_mp(this, &FileDialog::_option_changed_item_selected).bind(opt.name)); + selected_options[opt.name] = opt.default_idx; + } + } +} + +Dictionary FileDialog::get_selected_options() const { + return selected_options; +} + +String FileDialog::get_option_name(int p_option) const { + ERR_FAIL_INDEX_V(p_option, options.size(), String()); + return options[p_option].name; +} + +Vector<String> FileDialog::get_option_values(int p_option) const { + ERR_FAIL_INDEX_V(p_option, options.size(), Vector<String>()); + return options[p_option].values; +} + +int FileDialog::get_option_default(int p_option) const { + ERR_FAIL_INDEX_V(p_option, options.size(), -1); + return options[p_option].default_idx; +} + +void FileDialog::set_option_name(int p_option, const String &p_name) { + if (p_option < 0) { + p_option += get_option_count(); + } + ERR_FAIL_INDEX(p_option, options.size()); + options.write[p_option].name = p_name; + options_dirty = true; + if (is_visible()) { + _update_option_controls(); + } +} + +void FileDialog::set_option_values(int p_option, const Vector<String> &p_values) { + if (p_option < 0) { + p_option += get_option_count(); + } + ERR_FAIL_INDEX(p_option, options.size()); + options.write[p_option].values = p_values; + if (p_values.is_empty()) { + options.write[p_option].default_idx = CLAMP(options[p_option].default_idx, 0, 1); + } else { + options.write[p_option].default_idx = CLAMP(options[p_option].default_idx, 0, options[p_option].values.size() - 1); + } + options_dirty = true; + if (is_visible()) { + _update_option_controls(); + } +} + +void FileDialog::set_option_default(int p_option, int p_index) { + if (p_option < 0) { + p_option += get_option_count(); + } + ERR_FAIL_INDEX(p_option, options.size()); + if (options[p_option].values.is_empty()) { + options.write[p_option].default_idx = CLAMP(p_index, 0, 1); + } else { + options.write[p_option].default_idx = CLAMP(p_index, 0, options[p_option].values.size() - 1); + } + options_dirty = true; + if (is_visible()) { + _update_option_controls(); + } +} + +void FileDialog::add_option(const String &p_name, const Vector<String> &p_values, int p_index) { + Option opt; + opt.name = p_name; + opt.values = p_values; + if (opt.values.is_empty()) { + opt.default_idx = CLAMP(p_index, 0, 1); + } else { + opt.default_idx = CLAMP(p_index, 0, opt.values.size() - 1); + } + options.push_back(opt); + options_dirty = true; + if (is_visible()) { + _update_option_controls(); + } +} + +void FileDialog::set_option_count(int p_count) { + ERR_FAIL_COND(p_count < 0); + int prev_size = options.size(); + + if (prev_size == p_count) { + return; + } + options.resize(p_count); + + options_dirty = true; + notify_property_list_changed(); + if (is_visible()) { + _update_option_controls(); + } +} + +int FileDialog::get_option_count() const { + return options.size(); +} + +bool FileDialog::_set(const StringName &p_name, const Variant &p_value) { + Vector<String> components = String(p_name).split("/", true, 2); + if (components.size() >= 2 && components[0].begins_with("option_") && components[0].trim_prefix("option_").is_valid_int()) { + int item_index = components[0].trim_prefix("option_").to_int(); + String property = components[1]; + if (property == "name") { + set_option_name(item_index, p_value); + return true; + } else if (property == "values") { + set_option_values(item_index, p_value); + return true; + } else if (property == "default") { + set_option_default(item_index, p_value); + return true; + } + } + return false; +} + +bool FileDialog::_get(const StringName &p_name, Variant &r_ret) const { + Vector<String> components = String(p_name).split("/", true, 2); + if (components.size() >= 2 && components[0].begins_with("option_") && components[0].trim_prefix("option_").is_valid_int()) { + int item_index = components[0].trim_prefix("option_").to_int(); + String property = components[1]; + if (property == "name") { + r_ret = get_option_name(item_index); + return true; + } else if (property == "values") { + r_ret = get_option_values(item_index); + return true; + } else if (property == "default") { + r_ret = get_option_default(item_index); + return true; + } + } + return false; +} + +void FileDialog::_get_property_list(List<PropertyInfo> *p_list) const { + for (int i = 0; i < options.size(); i++) { + p_list->push_back(PropertyInfo(Variant::STRING, vformat("option_%d/name", i))); + p_list->push_back(PropertyInfo(Variant::PACKED_STRING_ARRAY, vformat("option_%d/values", i))); + p_list->push_back(PropertyInfo(Variant::INT, vformat("option_%d/default", i))); + } +} + void FileDialog::_bind_methods() { ClassDB::bind_method(D_METHOD("_cancel_pressed"), &FileDialog::_cancel_pressed); @@ -1014,6 +1264,16 @@ void FileDialog::_bind_methods() { ClassDB::bind_method(D_METHOD("add_filter", "filter", "description"), &FileDialog::add_filter, DEFVAL("")); ClassDB::bind_method(D_METHOD("set_filters", "filters"), &FileDialog::set_filters); ClassDB::bind_method(D_METHOD("get_filters"), &FileDialog::get_filters); + ClassDB::bind_method(D_METHOD("get_option_name", "option"), &FileDialog::get_option_name); + ClassDB::bind_method(D_METHOD("get_option_values", "option"), &FileDialog::get_option_values); + ClassDB::bind_method(D_METHOD("get_option_default", "option"), &FileDialog::get_option_default); + ClassDB::bind_method(D_METHOD("set_option_name", "option", "name"), &FileDialog::set_option_name); + ClassDB::bind_method(D_METHOD("set_option_values", "option", "values"), &FileDialog::set_option_values); + ClassDB::bind_method(D_METHOD("set_option_default", "option", "index"), &FileDialog::set_option_default); + ClassDB::bind_method(D_METHOD("set_option_count", "count"), &FileDialog::set_option_count); + ClassDB::bind_method(D_METHOD("get_option_count"), &FileDialog::get_option_count); + ClassDB::bind_method(D_METHOD("add_option", "name", "values", "index"), &FileDialog::add_option); + ClassDB::bind_method(D_METHOD("get_selected_options"), &FileDialog::get_selected_options); ClassDB::bind_method(D_METHOD("get_current_dir"), &FileDialog::get_current_dir); ClassDB::bind_method(D_METHOD("get_current_file"), &FileDialog::get_current_file); ClassDB::bind_method(D_METHOD("get_current_path"), &FileDialog::get_current_path); @@ -1043,6 +1303,7 @@ void FileDialog::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "access", PROPERTY_HINT_ENUM, "Resources,User Data,File System"), "set_access", "get_access"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "root_subfolder"), "set_root_subfolder", "get_root_subfolder"); ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "filters"), "set_filters", "get_filters"); + ADD_ARRAY_COUNT("Options", "option_count", "set_option_count", "get_option_count", "option_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_hidden_files"), "set_show_hidden_files", "is_showing_hidden_files"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_native_dialog"), "set_use_native_dialog", "get_use_native_dialog"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "current_dir", PROPERTY_HINT_DIR, "", PROPERTY_USAGE_NONE), "set_current_dir", "get_current_dir"); @@ -1195,6 +1456,11 @@ FileDialog::FileDialog() { file_box->add_child(filter); vbox->add_child(file_box); + grid_options = memnew(GridContainer); + grid_options->set_h_size_flags(Control::SIZE_SHRINK_CENTER); + grid_options->set_columns(2); + vbox->add_child(grid_options); + dir_access = DirAccess::create(DirAccess::ACCESS_RESOURCES); _update_drives(); diff --git a/scene/gui/file_dialog.h b/scene/gui/file_dialog.h index 8ae84fc9dc..7356e1c9e3 100644 --- a/scene/gui/file_dialog.h +++ b/scene/gui/file_dialog.h @@ -38,6 +38,8 @@ #include "scene/gui/option_button.h" #include "scene/gui/tree.h" +class GridContainer; + class FileDialog : public ConfirmationDialog { GDCLASS(FileDialog, ConfirmationDialog); @@ -70,6 +72,7 @@ private: Button *makedir = nullptr; Access access = ACCESS_RESOURCES; VBoxContainer *vbox = nullptr; + GridContainer *grid_options = nullptr; FileMode mode; LineEdit *dir = nullptr; HBoxContainer *drives_container = nullptr; @@ -128,6 +131,15 @@ private: Color icon_pressed_color; } theme_cache; + struct Option { + String name; + Vector<String> values; + int default_idx = 0; + }; + Vector<Option> options; + Dictionary selected_options; + bool options_dirty = false; + void update_dir(); void update_file_name(); void update_file_list(); @@ -159,15 +171,23 @@ private: virtual void shortcut_input(const Ref<InputEvent> &p_event) override; - void _native_dialog_cb(bool p_ok, const Vector<String> &p_files, int p_filter); + void _native_dialog_cb(bool p_ok, const Vector<String> &p_files, int p_filter, const Dictionary &p_selected_options); bool _is_open_should_be_disabled(); + TypedArray<Dictionary> _get_options() const; + void _update_option_controls(); + void _option_changed_checkbox_toggled(bool p_pressed, const String &p_name); + void _option_changed_item_selected(int p_idx, const String &p_name); + virtual void _post_popup() override; protected: void _validate_property(PropertyInfo &p_property) const; void _notification(int p_what); + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List<PropertyInfo> *p_list) const; static void _bind_methods(); public: @@ -190,6 +210,20 @@ public: void set_current_file(const String &p_file); void set_current_path(const String &p_path); + String get_option_name(int p_option) const; + Vector<String> get_option_values(int p_option) const; + int get_option_default(int p_option) const; + void set_option_name(int p_option, const String &p_name); + void set_option_values(int p_option, const Vector<String> &p_values); + void set_option_default(int p_option, int p_index); + + void add_option(const String &p_name, const Vector<String> &p_values, int p_index); + + void set_option_count(int p_count); + int get_option_count() const; + + Dictionary get_selected_options() const; + void set_root_subfolder(const String &p_root); String get_root_subfolder() const; diff --git a/scene/gui/menu_bar.cpp b/scene/gui/menu_bar.cpp index 7fa2653ed9..a35c0ba2d7 100644 --- a/scene/gui/menu_bar.cpp +++ b/scene/gui/menu_bar.cpp @@ -181,24 +181,7 @@ void MenuBar::_popup_visibility_changed(bool p_visible) { } if (switch_on_hover) { - Window *wnd = Object::cast_to<Window>(get_viewport()); - if (wnd) { - mouse_pos_adjusted = wnd->get_position(); - - if (wnd->is_embedded()) { - Window *wnd_parent = Object::cast_to<Window>(wnd->get_parent()->get_viewport()); - while (wnd_parent) { - if (!wnd_parent->is_embedded()) { - mouse_pos_adjusted += wnd_parent->get_position(); - break; - } - - wnd_parent = Object::cast_to<Window>(wnd_parent->get_parent()->get_viewport()); - } - } - - set_process_internal(true); - } + set_process_internal(true); } } @@ -338,8 +321,7 @@ void MenuBar::_notification(int p_what) { // Handled by OS. return; } - - Vector2 pos = DisplayServer::get_singleton()->mouse_get_position() - mouse_pos_adjusted - get_global_position(); + Vector2 pos = get_local_mouse_position(); if (pos == old_mouse_pos) { return; } diff --git a/scene/gui/menu_bar.h b/scene/gui/menu_bar.h index ba4df5f229..8431ac958b 100644 --- a/scene/gui/menu_bar.h +++ b/scene/gui/menu_bar.h @@ -71,7 +71,6 @@ class MenuBar : public Control { int selected_menu = -1; int active_menu = -1; - Vector2i mouse_pos_adjusted; Vector2i old_mouse_pos; ObjectID shortcut_context; diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp index 868383b141..080b687337 100644 --- a/scene/gui/menu_button.cpp +++ b/scene/gui/menu_button.cpp @@ -57,24 +57,7 @@ void MenuButton::_popup_visibility_changed(bool p_visible) { } if (switch_on_hover) { - Window *wnd = Object::cast_to<Window>(get_viewport()); - if (wnd) { - mouse_pos_adjusted = wnd->get_position(); - - if (wnd->is_embedded()) { - Window *wnd_parent = Object::cast_to<Window>(wnd->get_parent()->get_viewport()); - while (wnd_parent) { - if (!wnd_parent->is_embedded()) { - mouse_pos_adjusted += wnd_parent->get_position(); - break; - } - - wnd_parent = Object::cast_to<Window>(wnd_parent->get_parent()->get_viewport()); - } - } - - set_process_internal(true); - } + set_process_internal(true); } } @@ -155,8 +138,7 @@ void MenuButton::_notification(int p_what) { } break; case NOTIFICATION_INTERNAL_PROCESS: { - Vector2i mouse_pos = DisplayServer::get_singleton()->mouse_get_position() - mouse_pos_adjusted; - MenuButton *menu_btn_other = Object::cast_to<MenuButton>(get_viewport()->gui_find_control(mouse_pos)); + MenuButton *menu_btn_other = Object::cast_to<MenuButton>(get_viewport()->gui_find_control(get_viewport()->get_mouse_position())); if (menu_btn_other && menu_btn_other != this && menu_btn_other->is_switch_on_hover() && !menu_btn_other->is_disabled() && (get_parent()->is_ancestor_of(menu_btn_other) || menu_btn_other->get_parent()->is_ancestor_of(popup))) { diff --git a/scene/gui/menu_button.h b/scene/gui/menu_button.h index 95748a29f1..eea6b8e877 100644 --- a/scene/gui/menu_button.h +++ b/scene/gui/menu_button.h @@ -42,8 +42,6 @@ class MenuButton : public Button { bool disable_shortcuts = false; PopupMenu *popup = nullptr; - Vector2i mouse_pos_adjusted; - void _popup_visibility_changed(bool p_visible); protected: diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index 2414278e52..dc586e86c9 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -2086,6 +2086,26 @@ void PopupMenu::set_item_indent(int p_idx, int p_indent) { _menu_changed(); } +void PopupMenu::set_item_max_states(int p_idx, int p_max_states) { + if (p_idx < 0) { + p_idx += get_item_count(); + } + ERR_FAIL_INDEX(p_idx, items.size()); + + if (items[p_idx].max_states == p_max_states) { + return; + } + + items.write[p_idx].max_states = p_max_states; + + if (!global_menu_name.is_empty()) { + DisplayServer::get_singleton()->global_menu_set_item_max_states(global_menu_name, p_idx, p_max_states); + } + + control->queue_redraw(); + _menu_changed(); +} + void PopupMenu::set_item_multistate(int p_idx, int p_state) { if (p_idx < 0) { p_idx += get_item_count(); @@ -2724,6 +2744,7 @@ void PopupMenu::_bind_methods() { ClassDB::bind_method(D_METHOD("set_item_shortcut", "index", "shortcut", "global"), &PopupMenu::set_item_shortcut, DEFVAL(false)); ClassDB::bind_method(D_METHOD("set_item_indent", "index", "indent"), &PopupMenu::set_item_indent); ClassDB::bind_method(D_METHOD("set_item_multistate", "index", "state"), &PopupMenu::set_item_multistate); + ClassDB::bind_method(D_METHOD("set_item_multistate_max", "index", "max_states"), &PopupMenu::set_item_max_states); ClassDB::bind_method(D_METHOD("set_item_shortcut_disabled", "index", "disabled"), &PopupMenu::set_item_shortcut_disabled); ClassDB::bind_method(D_METHOD("toggle_item_checked", "index"), &PopupMenu::toggle_item_checked); @@ -2750,6 +2771,9 @@ void PopupMenu::_bind_methods() { ClassDB::bind_method(D_METHOD("get_item_shortcut", "index"), &PopupMenu::get_item_shortcut); ClassDB::bind_method(D_METHOD("get_item_indent", "index"), &PopupMenu::get_item_indent); + ClassDB::bind_method(D_METHOD("get_item_multistate_max", "index"), &PopupMenu::get_item_max_states); + ClassDB::bind_method(D_METHOD("get_item_multistate", "index"), &PopupMenu::get_item_state); + ClassDB::bind_method(D_METHOD("set_focused_item", "index"), &PopupMenu::set_focused_item); ClassDB::bind_method(D_METHOD("get_focused_item"), &PopupMenu::get_focused_item); ClassDB::bind_method(D_METHOD("set_item_count", "count"), &PopupMenu::set_item_count); diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index 9783f9d57b..35ababd913 100644 --- a/scene/gui/popup_menu.h +++ b/scene/gui/popup_menu.h @@ -264,6 +264,7 @@ public: void set_item_tooltip(int p_idx, const String &p_tooltip); void set_item_shortcut(int p_idx, const Ref<Shortcut> &p_shortcut, bool p_global = false); void set_item_indent(int p_idx, int p_indent); + void set_item_max_states(int p_idx, int p_max_states); void set_item_multistate(int p_idx, int p_state); void toggle_item_multistate(int p_idx); void set_item_shortcut_disabled(int p_idx, bool p_disabled); diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index fd481e1067..b6dd953d50 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -239,7 +239,8 @@ void RichTextLabel::_update_line_font(ItemFrame *p_frame, int p_line, const Ref< RID t = l.text_buf->get_rid(); int spans = TS->shaped_get_span_count(t); for (int i = 0; i < spans; i++) { - ItemText *it = reinterpret_cast<ItemText *>((uint64_t)TS->shaped_get_span_meta(t, i)); + Item *it_span = items.get_or_null(TS->shaped_get_span_meta(t, i)); + ItemText *it = reinterpret_cast<ItemText *>(it_span); if (it) { Ref<Font> font = p_base_font; int font_size = p_base_font_size; @@ -307,7 +308,7 @@ float RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font Size2 img_size = img->size; if (img->size_in_percent) { img_size = _get_image_size(img->image, p_width * img->rq_size.width / 100.f, p_width * img->rq_size.height / 100.f, img->region); - l.text_buf->resize_object((uint64_t)it, img_size, img->inline_align); + l.text_buf->resize_object(it->rid, img_size, img->inline_align); } } break; case ITEM_TABLE: { @@ -455,9 +456,9 @@ float RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font } int row_idx = (table->align_to_row < 0) ? table->rows_baseline.size() - 1 : table->align_to_row; if (table->rows_baseline.size() != 0 && row_idx < (int)table->rows_baseline.size() - 1) { - l.text_buf->resize_object((uint64_t)it, Size2(table->total_width, table->total_height), table->inline_align, Math::round(table->rows_baseline[row_idx])); + l.text_buf->resize_object(it->rid, Size2(table->total_width, table->total_height), table->inline_align, Math::round(table->rows_baseline[row_idx])); } else { - l.text_buf->resize_object((uint64_t)it, Size2(table->total_width, table->total_height), table->inline_align); + l.text_buf->resize_object(it->rid, Size2(table->total_width, table->total_height), table->inline_align); } } break; default: @@ -578,7 +579,7 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> } remaining_characters -= tx.length(); - l.text_buf->add_string(tx, font, font_size, lang, (uint64_t)it); + l.text_buf->add_string(tx, font, font_size, lang, it->rid); txt += tx; l.char_count += tx.length(); } break; @@ -588,7 +589,7 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> if (img->size_in_percent) { img_size = _get_image_size(img->image, p_width * img->rq_size.width / 100.f, p_width * img->rq_size.height / 100.f, img->region); } - l.text_buf->add_object((uint64_t)it, img_size, img->inline_align, 1); + l.text_buf->add_object(it->rid, img_size, img->inline_align, 1); txt += String::chr(0xfffc); l.char_count++; remaining_characters--; @@ -753,9 +754,9 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> } int row_idx = (table->align_to_row < 0) ? table->rows_baseline.size() - 1 : table->align_to_row; if (table->rows_baseline.size() != 0 && row_idx < (int)table->rows_baseline.size() - 1) { - l.text_buf->add_object((uint64_t)it, Size2(table->total_width, table->total_height), table->inline_align, t_char_count, Math::round(table->rows_baseline[row_idx])); + l.text_buf->add_object(it->rid, Size2(table->total_width, table->total_height), table->inline_align, t_char_count, Math::round(table->rows_baseline[row_idx])); } else { - l.text_buf->add_object((uint64_t)it, Size2(table->total_width, table->total_height), table->inline_align, t_char_count); + l.text_buf->add_object(it->rid, Size2(table->total_width, table->total_height), table->inline_align, t_char_count); } txt += String::chr(0xfffc).repeat(t_char_count); } break; @@ -932,7 +933,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o // Draw inlined objects. Array objects = TS->shaped_text_get_objects(rid); for (int i = 0; i < objects.size(); i++) { - Item *it = reinterpret_cast<Item *>((uint64_t)objects[i]); + Item *it = items.get_or_null(objects[i]); if (it != nullptr) { Rect2 rect = TS->shaped_text_get_object_rect(rid, objects[i]); //draw_rect(rect, Color(1,0,0), false, 2); //DEBUG_RECTS @@ -1601,7 +1602,7 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V Array objects = TS->shaped_text_get_objects(rid); for (int i = 0; i < objects.size(); i++) { - Item *it = reinterpret_cast<Item *>((uint64_t)objects[i]); + Item *it = items.get_or_null(objects[i]); if (it != nullptr) { Rect2 rect = TS->shaped_text_get_object_rect(rid, objects[i]); rect.position += p_ofs + off; @@ -3054,6 +3055,19 @@ void RichTextLabel::_invalidate_current_line(ItemFrame *p_frame) { } } +void RichTextLabel::_texture_changed(RID p_item) { + Item *it = items.get_or_null(p_item); + if (it && it->type == ITEM_IMAGE) { + ItemImage *img = reinterpret_cast<ItemImage *>(it); + Size2 new_size = _get_image_size(img->image, img->rq_size.width, img->rq_size.height, img->region); + if (img->size != new_size) { + main->first_invalid_line.store(0); + img->size = new_size; + } + } + queue_redraw(); +} + void RichTextLabel::add_text(const String &p_text) { _stop_thread(); MutexLock data_lock(data_mutex); @@ -3090,6 +3104,8 @@ void RichTextLabel::add_text(const String &p_text) { } else { //append item condition ItemText *item = memnew(ItemText); + item->owner = get_instance_id(); + item->rid = items.make_rid(item); item->text = line; _add_item(item, false); } @@ -3097,6 +3113,8 @@ void RichTextLabel::add_text(const String &p_text) { if (eol) { ItemNewline *item = memnew(ItemNewline); + item->owner = get_instance_id(); + item->rid = items.make_rid(item); item->line = current_frame->lines.size(); _add_item(item, false); current_frame->lines.resize(current_frame->lines.size() + 1); @@ -3174,6 +3192,7 @@ void RichTextLabel::_remove_item(Item *p_item, const int p_line) { // Then remove the provided item itself. p_item->parent->subitems.erase(p_item); } + items.free(p_item->rid); memdelete(p_item); } @@ -3231,6 +3250,8 @@ void RichTextLabel::add_image(const Ref<Texture2D> &p_image, int p_width, int p_ ERR_FAIL_COND(p_height < 0); ItemImage *item = memnew(ItemImage); + item->owner = get_instance_id(); + item->rid = items.make_rid(item); if (p_region.has_area()) { Ref<AtlasTexture> atlas_tex = memnew(AtlasTexture); @@ -3250,6 +3271,8 @@ void RichTextLabel::add_image(const Ref<Texture2D> &p_image, int p_width, int p_ item->key = p_key; item->tooltip = p_tooltip; + item->image->connect_changed(callable_mp(this, &RichTextLabel::_texture_changed).bind(item->rid), CONNECT_REFERENCE_COUNTED); + _add_item(item, false); } @@ -3278,6 +3301,9 @@ void RichTextLabel::update_image(const Variant &p_key, BitField<ImageUpdateMask> item->region = p_region; } if (p_mask & UPDATE_TEXTURE) { + if (item->image.is_valid()) { + item->image->disconnect_changed(callable_mp(this, &RichTextLabel::_texture_changed)); + } if (item->region.has_area()) { Ref<AtlasTexture> atlas_tex = memnew(AtlasTexture); atlas_tex->set_atlas(p_image); @@ -3286,6 +3312,7 @@ void RichTextLabel::update_image(const Variant &p_key, BitField<ImageUpdateMask> } else { item->image = p_image; } + item->image->connect_changed(callable_mp(this, &RichTextLabel::_texture_changed).bind(item->rid), CONNECT_REFERENCE_COUNTED); } if (p_mask & UPDATE_COLOR) { item->color = p_color; @@ -3345,6 +3372,8 @@ void RichTextLabel::add_newline() { return; } ItemNewline *item = memnew(ItemNewline); + item->owner = get_instance_id(); + item->rid = items.make_rid(item); item->line = current_frame->lines.size(); _add_item(item, false); current_frame->lines.resize(current_frame->lines.size() + 1); @@ -3413,7 +3442,8 @@ void RichTextLabel::push_dropcap(const String &p_string, const Ref<Font> &p_font ERR_FAIL_COND(p_size <= 0); ItemDropcap *item = memnew(ItemDropcap); - + item->owner = get_instance_id(); + item->rid = items.make_rid(item); item->text = p_string; item->font = p_font; item->font_size = p_size; @@ -3430,7 +3460,8 @@ void RichTextLabel::_push_def_font_var(DefaultFont p_def_font, const Ref<Font> & ERR_FAIL_COND(current->type == ITEM_TABLE); ItemFont *item = memnew(ItemFont); - + item->owner = get_instance_id(); + item->rid = items.make_rid(item); item->def_font = p_def_font; item->variation = true; item->font = p_font; @@ -3445,7 +3476,8 @@ void RichTextLabel::_push_def_font(DefaultFont p_def_font) { ERR_FAIL_COND(current->type == ITEM_TABLE); ItemFont *item = memnew(ItemFont); - + item->owner = get_instance_id(); + item->rid = items.make_rid(item); item->def_font = p_def_font; item->def_size = true; _add_item(item, true); @@ -3458,7 +3490,8 @@ void RichTextLabel::push_font(const Ref<Font> &p_font, int p_size) { ERR_FAIL_COND(current->type == ITEM_TABLE); ERR_FAIL_COND(p_font.is_null()); ItemFont *item = memnew(ItemFont); - + item->owner = get_instance_id(); + item->rid = items.make_rid(item); item->font = p_font; item->font_size = p_size; _add_item(item, true); @@ -3502,7 +3535,8 @@ void RichTextLabel::push_font_size(int p_font_size) { ERR_FAIL_COND(current->type == ITEM_TABLE); ItemFontSize *item = memnew(ItemFontSize); - + item->owner = get_instance_id(); + item->rid = items.make_rid(item); item->font_size = p_font_size; _add_item(item, true); } @@ -3513,7 +3547,8 @@ void RichTextLabel::push_outline_size(int p_ol_size) { ERR_FAIL_COND(current->type == ITEM_TABLE); ItemOutlineSize *item = memnew(ItemOutlineSize); - + item->owner = get_instance_id(); + item->rid = items.make_rid(item); item->outline_size = p_ol_size; _add_item(item, true); } @@ -3524,7 +3559,8 @@ void RichTextLabel::push_color(const Color &p_color) { ERR_FAIL_COND(current->type == ITEM_TABLE); ItemColor *item = memnew(ItemColor); - + item->owner = get_instance_id(); + item->rid = items.make_rid(item); item->color = p_color; _add_item(item, true); } @@ -3535,7 +3571,8 @@ void RichTextLabel::push_outline_color(const Color &p_color) { ERR_FAIL_COND(current->type == ITEM_TABLE); ItemOutlineColor *item = memnew(ItemOutlineColor); - + item->owner = get_instance_id(); + item->rid = items.make_rid(item); item->color = p_color; _add_item(item, true); } @@ -3546,6 +3583,8 @@ void RichTextLabel::push_underline() { ERR_FAIL_COND(current->type == ITEM_TABLE); ItemUnderline *item = memnew(ItemUnderline); + item->owner = get_instance_id(); + item->rid = items.make_rid(item); _add_item(item, true); } @@ -3556,6 +3595,8 @@ void RichTextLabel::push_strikethrough() { ERR_FAIL_COND(current->type == ITEM_TABLE); ItemStrikethrough *item = memnew(ItemStrikethrough); + item->owner = get_instance_id(); + item->rid = items.make_rid(item); _add_item(item, true); } @@ -3567,6 +3608,8 @@ void RichTextLabel::push_paragraph(HorizontalAlignment p_alignment, Control::Tex ERR_FAIL_COND(current->type == ITEM_TABLE); ItemParagraph *item = memnew(ItemParagraph); + item->owner = get_instance_id(); + item->rid = items.make_rid(item); item->alignment = p_alignment; item->direction = p_direction; item->language = p_language; @@ -3584,6 +3627,8 @@ void RichTextLabel::push_indent(int p_level) { ERR_FAIL_COND(p_level < 0); ItemIndent *item = memnew(ItemIndent); + item->owner = get_instance_id(); + item->rid = items.make_rid(item); item->level = p_level; _add_item(item, true, true); } @@ -3596,7 +3641,8 @@ void RichTextLabel::push_list(int p_level, ListType p_list, bool p_capitalize, c ERR_FAIL_COND(p_level < 0); ItemList *item = memnew(ItemList); - + item->owner = get_instance_id(); + item->rid = items.make_rid(item); item->list_type = p_list; item->level = p_level; item->capitalize = p_capitalize; @@ -3610,7 +3656,8 @@ void RichTextLabel::push_meta(const Variant &p_meta) { ERR_FAIL_COND(current->type == ITEM_TABLE); ItemMeta *item = memnew(ItemMeta); - + item->owner = get_instance_id(); + item->rid = items.make_rid(item); item->meta = p_meta; _add_item(item, true); } @@ -3621,7 +3668,8 @@ void RichTextLabel::push_language(const String &p_language) { ERR_FAIL_COND(current->type == ITEM_TABLE); ItemLanguage *item = memnew(ItemLanguage); - + item->owner = get_instance_id(); + item->rid = items.make_rid(item); item->language = p_language; _add_item(item, true); } @@ -3632,7 +3680,8 @@ void RichTextLabel::push_hint(const String &p_string) { ERR_FAIL_COND(current->type == ITEM_TABLE); ItemHint *item = memnew(ItemHint); - + item->owner = get_instance_id(); + item->rid = items.make_rid(item); item->description = p_string; _add_item(item, true); } @@ -3644,7 +3693,8 @@ void RichTextLabel::push_table(int p_columns, InlineAlignment p_alignment, int p ERR_FAIL_COND(current->type == ITEM_TABLE); ERR_FAIL_COND(p_columns < 1); ItemTable *item = memnew(ItemTable); - + item->owner = get_instance_id(); + item->rid = items.make_rid(item); item->columns.resize(p_columns); item->total_width = 0; item->inline_align = p_alignment; @@ -3662,6 +3712,8 @@ void RichTextLabel::push_fade(int p_start_index, int p_length) { ERR_FAIL_COND(current->type == ITEM_TABLE); ItemFade *item = memnew(ItemFade); + item->owner = get_instance_id(); + item->rid = items.make_rid(item); item->starting_index = p_start_index; item->length = p_length; _add_item(item, true); @@ -3673,6 +3725,8 @@ void RichTextLabel::push_shake(int p_strength = 10, float p_rate = 24.0f, bool p ERR_FAIL_COND(current->type == ITEM_TABLE); ItemShake *item = memnew(ItemShake); + item->owner = get_instance_id(); + item->rid = items.make_rid(item); item->strength = p_strength; item->rate = p_rate; item->connected = p_connected; @@ -3685,6 +3739,8 @@ void RichTextLabel::push_wave(float p_frequency = 1.0f, float p_amplitude = 10.0 ERR_FAIL_COND(current->type == ITEM_TABLE); ItemWave *item = memnew(ItemWave); + item->owner = get_instance_id(); + item->rid = items.make_rid(item); item->frequency = p_frequency; item->amplitude = p_amplitude; item->connected = p_connected; @@ -3697,6 +3753,8 @@ void RichTextLabel::push_tornado(float p_frequency = 1.0f, float p_radius = 10.0 ERR_FAIL_COND(current->type == ITEM_TABLE); ItemTornado *item = memnew(ItemTornado); + item->owner = get_instance_id(); + item->rid = items.make_rid(item); item->frequency = p_frequency; item->radius = p_radius; item->connected = p_connected; @@ -3709,6 +3767,8 @@ void RichTextLabel::push_rainbow(float p_saturation, float p_value, float p_freq ERR_FAIL_COND(current->type == ITEM_TABLE); ItemRainbow *item = memnew(ItemRainbow); + item->owner = get_instance_id(); + item->rid = items.make_rid(item); item->frequency = p_frequency; item->saturation = p_saturation; item->value = p_value; @@ -3720,6 +3780,8 @@ void RichTextLabel::push_pulse(const Color &p_color, float p_frequency, float p_ MutexLock data_lock(data_mutex); ItemPulse *item = memnew(ItemPulse); + item->owner = get_instance_id(); + item->rid = items.make_rid(item); item->color = p_color; item->frequency = p_frequency; item->ease = p_ease; @@ -3732,7 +3794,8 @@ void RichTextLabel::push_bgcolor(const Color &p_color) { ERR_FAIL_COND(current->type == ITEM_TABLE); ItemBGColor *item = memnew(ItemBGColor); - + item->owner = get_instance_id(); + item->rid = items.make_rid(item); item->color = p_color; _add_item(item, true); } @@ -3743,7 +3806,8 @@ void RichTextLabel::push_fgcolor(const Color &p_color) { ERR_FAIL_COND(current->type == ITEM_TABLE); ItemFGColor *item = memnew(ItemFGColor); - + item->owner = get_instance_id(); + item->rid = items.make_rid(item); item->color = p_color; _add_item(item, true); } @@ -3754,6 +3818,8 @@ void RichTextLabel::push_customfx(Ref<RichTextEffect> p_custom_effect, Dictionar ERR_FAIL_COND(current->type == ITEM_TABLE); ItemCustomFX *item = memnew(ItemCustomFX); + item->owner = get_instance_id(); + item->rid = items.make_rid(item); item->custom_effect = p_custom_effect; item->char_fx_transform->environment = p_environment; _add_item(item, true); @@ -3767,6 +3833,8 @@ void RichTextLabel::push_context() { ERR_FAIL_COND(current->type == ITEM_TABLE); ItemContext *item = memnew(ItemContext); + item->owner = get_instance_id(); + item->rid = items.make_rid(item); _add_item(item, true); } @@ -3835,6 +3903,8 @@ void RichTextLabel::push_cell() { ERR_FAIL_COND(current->type != ITEM_TABLE); ItemFrame *item = memnew(ItemFrame); + item->owner = get_instance_id(); + item->rid = items.make_rid(item); item->parent_frame = current_frame; _add_item(item, true); current_frame = item; @@ -6388,6 +6458,8 @@ Dictionary RichTextLabel::parse_expressions_for_values(Vector<String> p_expressi RichTextLabel::RichTextLabel(const String &p_text) { main = memnew(ItemFrame); + main->owner = get_instance_id(); + main->rid = items.make_rid(main); main->index = 0; current = main; main->lines.resize(1); @@ -6418,5 +6490,6 @@ RichTextLabel::RichTextLabel(const String &p_text) { RichTextLabel::~RichTextLabel() { _stop_thread(); + items.free(main->rid); memdelete(main); } diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index 5c8585565d..0070027a7e 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -32,6 +32,7 @@ #define RICH_TEXT_LABEL_H #include "core/object/worker_thread_pool.h" +#include "core/templates/rid_owner.h" #include "scene/gui/popup_menu.h" #include "scene/gui/scroll_bar.h" #include "scene/resources/text_paragraph.h" @@ -149,11 +150,18 @@ private: ItemType type = ITEM_FRAME; List<Item *> subitems; List<Item *>::Element *E = nullptr; + ObjectID owner; int line = 0; + RID rid; void _clear_children() { + RichTextLabel *owner_rtl = Object::cast_to<RichTextLabel>(ObjectDB::get_instance(owner)); while (subitems.size()) { - memdelete(subitems.front()->get()); + Item *subitem = subitems.front()->get(); + if (subitem && subitem->rid.is_valid() && owner_rtl) { + owner_rtl->items.free(subitem->rid); + } + memdelete(subitem); subitems.pop_front(); } } @@ -214,6 +222,14 @@ private: Variant key; String tooltip; ItemImage() { type = ITEM_IMAGE; } + ~ItemImage() { + if (image.is_valid()) { + RichTextLabel *owner_rtl = Object::cast_to<RichTextLabel>(ObjectDB::get_instance(owner)); + if (owner_rtl) { + image->disconnect_changed(callable_mp(owner_rtl, &RichTextLabel::_texture_changed)); + } + } + } }; struct ItemFont : public Item { @@ -463,6 +479,10 @@ private: void _add_item(Item *p_item, bool p_enter = false, bool p_ensure_newline = false); void _remove_item(Item *p_item, const int p_line); + void _texture_changed(RID p_item); + + RID_PtrOwner<Item> items; + String language; TextDirection text_direction = TEXT_DIRECTION_AUTO; TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT; diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index 11d5a01908..4ecbc173b7 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -584,16 +584,32 @@ Variant TreeItem::get_metadata(int p_column) const { return cells[p_column].meta; } +#ifndef DISABLE_DEPRECATED void TreeItem::set_custom_draw(int p_column, Object *p_object, const StringName &p_callback) { + WARN_DEPRECATED_MSG(R"*(The "set_custom_draw()" method is deprecated, use "set_custom_draw_callback()" instead.)*"); ERR_FAIL_INDEX(p_column, cells.size()); ERR_FAIL_NULL(p_object); - cells.write[p_column].custom_draw_obj = p_object->get_instance_id(); + cells.write[p_column].custom_draw_callback = Callable(p_object, p_callback); + + _changed_notify(p_column); +} +#endif // DISABLE_DEPRECATED + +void TreeItem::set_custom_draw_callback(int p_column, const Callable &p_callback) { + ERR_FAIL_INDEX(p_column, cells.size()); + cells.write[p_column].custom_draw_callback = p_callback; _changed_notify(p_column); } +Callable TreeItem::get_custom_draw_callback(int p_column) const { + ERR_FAIL_INDEX_V(p_column, cells.size(), Callable()); + + return cells[p_column].custom_draw_callback; +} + void TreeItem::set_collapsed(bool p_collapsed) { if (collapsed == p_collapsed || !tree) { return; @@ -1594,7 +1610,11 @@ void TreeItem::_bind_methods() { ClassDB::bind_method(D_METHOD("set_metadata", "column", "meta"), &TreeItem::set_metadata); ClassDB::bind_method(D_METHOD("get_metadata", "column"), &TreeItem::get_metadata); +#ifndef DISABLE_DEPRECATED ClassDB::bind_method(D_METHOD("set_custom_draw", "column", "object", "callback"), &TreeItem::set_custom_draw); +#endif // DISABLE_DEPRECATED + ClassDB::bind_method(D_METHOD("set_custom_draw_callback", "column", "callback"), &TreeItem::set_custom_draw_callback); + ClassDB::bind_method(D_METHOD("get_custom_draw_callback", "column"), &TreeItem::get_custom_draw_callback); ClassDB::bind_method(D_METHOD("set_collapsed", "enable"), &TreeItem::set_collapsed); ClassDB::bind_method(D_METHOD("is_collapsed"), &TreeItem::is_collapsed); @@ -2317,10 +2337,15 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 } break; case TreeItem::CELL_MODE_CUSTOM: { - if (p_item->cells[i].custom_draw_obj.is_valid()) { - Object *cdo = ObjectDB::get_instance(p_item->cells[i].custom_draw_obj); - if (cdo) { - cdo->call(p_item->cells[i].custom_draw_callback, p_item, Rect2(item_rect)); + if (p_item->cells[i].custom_draw_callback.is_valid()) { + Variant args[] = { p_item, Rect2(item_rect) }; + const Variant *argptrs[] = { &args[0], &args[1] }; + + Callable::CallError ce; + Variant ret; + p_item->cells[i].custom_draw_callback.callp(argptrs, 2, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT("Error calling custom draw method: " + Variant::get_callable_error_text(p_item->cells[i].custom_draw_callback, argptrs, 2, ce) + "."); } } diff --git a/scene/gui/tree.h b/scene/gui/tree.h index 2dda408dd7..8ec003be9c 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -99,8 +99,7 @@ private: Variant meta; String tooltip; - ObjectID custom_draw_obj; - StringName custom_draw_callback; + Callable custom_draw_callback; struct Button { int id = 0; @@ -285,7 +284,11 @@ public: void set_metadata(int p_column, const Variant &p_meta); Variant get_metadata(int p_column) const; +#ifndef DISABLE_DEPRECATED void set_custom_draw(int p_column, Object *p_object, const StringName &p_callback); +#endif // DISABLE_DEPRECATED + void set_custom_draw_callback(int p_column, const Callable &p_callback); + Callable get_custom_draw_callback(int p_column) const; void set_collapsed(bool p_collapsed); bool is_collapsed(); diff --git a/scene/gui/video_stream_player.cpp b/scene/gui/video_stream_player.cpp index 41a210e180..687a9e46a0 100644 --- a/scene/gui/video_stream_player.cpp +++ b/scene/gui/video_stream_player.cpp @@ -31,7 +31,6 @@ #include "video_stream_player.h" #include "core/os/os.h" -#include "scene/resources/image_texture.h" #include "scene/scene_string_names.h" #include "servers/audio_server.h" diff --git a/scene/gui/video_stream_player.h b/scene/gui/video_stream_player.h index 0b83608f0b..c173c8bfa3 100644 --- a/scene/gui/video_stream_player.h +++ b/scene/gui/video_stream_player.h @@ -36,8 +36,6 @@ #include "servers/audio/audio_rb_resampler.h" #include "servers/audio_server.h" -class ImageTexture; - class VideoStreamPlayer : public Control { GDCLASS(VideoStreamPlayer, Control); @@ -54,7 +52,7 @@ class VideoStreamPlayer : public Control { RID stream_rid; - Ref<ImageTexture> texture; + Ref<Texture2D> texture; AudioRBResampler resampler; Vector<AudioFrame> mix_buffer; diff --git a/scene/main/node.cpp b/scene/main/node.cpp index f7d695bf31..f25e178d23 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -1214,6 +1214,11 @@ String Node::validate_child_name(Node *p_child) { _generate_serial_child_name(p_child, name); return name; } + +String Node::prevalidate_child_name(Node *p_child, StringName p_name) { + _generate_serial_child_name(p_child, p_name); + return p_name; +} #endif String Node::adjust_name_casing(const String &p_name) { @@ -3131,20 +3136,6 @@ PackedStringArray Node::get_configuration_warnings() const { return ret; } -String Node::get_configuration_warnings_as_string() const { - PackedStringArray warnings = get_configuration_warnings(); - String all_warnings; - for (int i = 0; i < warnings.size(); i++) { - if (i > 0) { - all_warnings += "\n\n"; - } - // Format as a bullet point list to make multiple warnings easier to distinguish - // from each other. - all_warnings += String::utf8("• ") + warnings[i]; - } - return all_warnings; -} - void Node::update_configuration_warnings() { ERR_THREAD_GUARD #ifdef TOOLS_ENABLED diff --git a/scene/main/node.h b/scene/main/node.h index 8130c61a34..48eae3bbfa 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -614,6 +614,7 @@ public: #ifdef TOOLS_ENABLED String validate_child_name(Node *p_child); + String prevalidate_child_name(Node *p_child, StringName p_name); #endif static String adjust_name_casing(const String &p_name); @@ -636,7 +637,6 @@ public: _FORCE_INLINE_ Viewport *get_viewport() const { return data.viewport; } virtual PackedStringArray get_configuration_warnings() const; - String get_configuration_warnings_as_string() const; void update_configuration_warnings(); diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index f92ab76753..5999b85988 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -738,6 +738,14 @@ void Viewport::_process_picking() { while (physics_picking_events.size()) { local_input_handled = false; + if (!handle_input_locally) { + Viewport *vp = this; + while (!Object::cast_to<Window>(vp) && vp->get_parent()) { + vp = vp->get_parent()->get_viewport(); + } + vp->local_input_handled = false; + } + Ref<InputEvent> ev = physics_picking_events.front()->get(); physics_picking_events.pop_front(); diff --git a/scene/resources/mesh.cpp b/scene/resources/mesh.cpp index 6f12539a6d..0abf878659 100644 --- a/scene/resources/mesh.cpp +++ b/scene/resources/mesh.cpp @@ -1558,6 +1558,7 @@ void ArrayMesh::_create_if_empty() const { mesh = RS::get_singleton()->mesh_create(); RS::get_singleton()->mesh_set_blend_shape_mode(mesh, (RS::BlendShapeMode)blend_shape_mode); RS::get_singleton()->mesh_set_blend_shape_count(mesh, blend_shapes.size()); + RS::get_singleton()->mesh_set_path(mesh, get_path()); } } @@ -1666,6 +1667,7 @@ void ArrayMesh::_set_surfaces(const Array &p_surfaces) { // we can create it with a single call, which is a lot more efficient and thread friendly mesh = RS::get_singleton()->mesh_create_from_surfaces(surface_data, blend_shapes.size()); RS::get_singleton()->mesh_set_blend_shape_mode(mesh, (RS::BlendShapeMode)blend_shape_mode); + RS::get_singleton()->mesh_set_path(mesh, get_path()); } surfaces.clear(); diff --git a/scene/resources/texture_rd.cpp b/scene/resources/texture_rd.cpp index 6f25af6863..49e13824a9 100644 --- a/scene/resources/texture_rd.cpp +++ b/scene/resources/texture_rd.cpp @@ -37,7 +37,7 @@ void Texture2DRD::_bind_methods() { ClassDB::bind_method(D_METHOD("set_texture_rd_rid", "texture_rd_rid"), &Texture2DRD::set_texture_rd_rid); ClassDB::bind_method(D_METHOD("get_texture_rd_rid"), &Texture2DRD::get_texture_rd_rid); - ADD_PROPERTY(PropertyInfo(Variant::RID, "texture_rd_rid"), "set_texture_rd_rid", "get_texture_rd_rid"); + ADD_PROPERTY(PropertyInfo(Variant::RID, "texture_rd_rid", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_texture_rd_rid", "get_texture_rd_rid"); } int Texture2DRD::get_width() const { @@ -128,7 +128,7 @@ void TextureLayeredRD::_bind_methods() { ClassDB::bind_method(D_METHOD("set_texture_rd_rid", "texture_rd_rid"), &TextureLayeredRD::set_texture_rd_rid); ClassDB::bind_method(D_METHOD("get_texture_rd_rid"), &TextureLayeredRD::get_texture_rd_rid); - ADD_PROPERTY(PropertyInfo(Variant::RID, "texture_rd_rid"), "set_texture_rd_rid", "get_texture_rd_rid"); + ADD_PROPERTY(PropertyInfo(Variant::RID, "texture_rd_rid", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_texture_rd_rid", "get_texture_rd_rid"); } TextureLayered::LayeredType TextureLayeredRD::get_layered_type() const { @@ -255,7 +255,7 @@ void Texture3DRD::_bind_methods() { ClassDB::bind_method(D_METHOD("set_texture_rd_rid", "texture_rd_rid"), &Texture3DRD::set_texture_rd_rid); ClassDB::bind_method(D_METHOD("get_texture_rd_rid"), &Texture3DRD::get_texture_rd_rid); - ADD_PROPERTY(PropertyInfo(Variant::RID, "texture_rd_rid"), "set_texture_rd_rid", "get_texture_rd_rid"); + ADD_PROPERTY(PropertyInfo(Variant::RID, "texture_rd_rid", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_texture_rd_rid", "get_texture_rd_rid"); } Image::Format Texture3DRD::get_format() const { diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp index 41660767ab..2ed859617a 100644 --- a/scene/resources/visual_shader.cpp +++ b/scene/resources/visual_shader.cpp @@ -3591,6 +3591,10 @@ String VisualShaderNodeParameterRef::get_output_port_name(int p_port) const { return ""; } +bool VisualShaderNodeParameterRef::is_shader_valid() const { + return shader_rid.is_valid(); +} + void VisualShaderNodeParameterRef::set_shader_rid(const RID &p_shader_rid) { shader_rid = p_shader_rid; } diff --git a/scene/resources/visual_shader.h b/scene/resources/visual_shader.h index 7faebb86ab..d4d77e7609 100644 --- a/scene/resources/visual_shader.h +++ b/scene/resources/visual_shader.h @@ -640,6 +640,7 @@ public: virtual PortType get_output_port_type(int p_port) const override; virtual String get_output_port_name(int p_port) const override; + bool is_shader_valid() const; void set_shader_rid(const RID &p_shader); void set_parameter_name(const String &p_name); diff --git a/scene/theme/theme_db.cpp b/scene/theme/theme_db.cpp index 8dc9d288e2..6841a9e1d4 100644 --- a/scene/theme/theme_db.cpp +++ b/scene/theme/theme_db.cpp @@ -49,8 +49,8 @@ void ThemeDB::initialize_theme() { // Allow creating the default theme at a different scale to suit higher/lower base resolutions. float default_theme_scale = GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "gui/theme/default_theme_scale", PROPERTY_HINT_RANGE, "0.5,8,0.01", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED), 1.0); - String project_theme_path = GLOBAL_DEF_RST(PropertyInfo(Variant::STRING, "gui/theme/custom", PROPERTY_HINT_FILE, "*.tres,*.res,*.theme", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED), ""); - String project_font_path = GLOBAL_DEF_RST(PropertyInfo(Variant::STRING, "gui/theme/custom_font", PROPERTY_HINT_FILE, "*.tres,*.res,*.otf,*.ttf,*.woff,*.woff2,*.fnt,*.font,*.pfb,*.pfm", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED), ""); + String project_theme_path = GLOBAL_DEF_RST_BASIC(PropertyInfo(Variant::STRING, "gui/theme/custom", PROPERTY_HINT_FILE, "*.tres,*.res,*.theme", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED), ""); + String project_font_path = GLOBAL_DEF_RST_BASIC(PropertyInfo(Variant::STRING, "gui/theme/custom_font", PROPERTY_HINT_FILE, "*.tres,*.res,*.otf,*.ttf,*.woff,*.woff2,*.fnt,*.font,*.pfb,*.pfm", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED), ""); TextServer::FontAntialiasing font_antialiasing = (TextServer::FontAntialiasing)(int)GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "gui/theme/default_font_antialiasing", PROPERTY_HINT_ENUM, "None,Grayscale,LCD Subpixel", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED), 1); TextServer::Hinting font_hinting = (TextServer::Hinting)(int)GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "gui/theme/default_font_hinting", PROPERTY_HINT_ENUM, "None,Light,Normal", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED), TextServer::HINTING_LIGHT); diff --git a/servers/audio_server.cpp b/servers/audio_server.cpp index 86bdd13c80..b46befd502 100644 --- a/servers/audio_server.cpp +++ b/servers/audio_server.cpp @@ -273,7 +273,14 @@ void AudioServer::_driver_process(int p_frames, int32_t *p_buffer) { //master master, send to output int cs = master->channels.size(); + + // Take away 1 from the stride, as we are manually incrementing by 1 for stereo. + uintptr_t stride_minus_one = (cs * 2) - 1; + for (int k = 0; k < cs; k++) { + // The destination start for data will be the same in all cases. + int32_t *dest = &p_buffer[from_buf * (cs * 2) + (k * 2)]; + if (master->channels[k].active) { const AudioFrame *buf = master->channels[k].buffer.ptr(); @@ -281,18 +288,25 @@ void AudioServer::_driver_process(int p_frames, int32_t *p_buffer) { float l = CLAMP(buf[from + j].l, -1.0, 1.0); int32_t vl = l * ((1 << 20) - 1); int32_t vl2 = (vl < 0 ? -1 : 1) * (ABS(vl) << 11); - p_buffer[(from_buf + j) * (cs * 2) + k * 2 + 0] = vl2; + *dest = vl2; + dest++; float r = CLAMP(buf[from + j].r, -1.0, 1.0); int32_t vr = r * ((1 << 20) - 1); int32_t vr2 = (vr < 0 ? -1 : 1) * (ABS(vr) << 11); - p_buffer[(from_buf + j) * (cs * 2) + k * 2 + 1] = vr2; + *dest = vr2; + dest += stride_minus_one; } } else { + // Bizarrely, profiling indicates that detecting the common case of cs == 1, + // k == 0, and using memset is SLOWER than setting them individually. + // Perhaps it gets optimized to a faster instruction than memset. for (int j = 0; j < to_copy; j++) { - p_buffer[(from_buf + j) * (cs * 2) + k * 2 + 0] = 0; - p_buffer[(from_buf + j) * (cs * 2) + k * 2 + 1] = 0; + *dest = 0; + dest++; + *dest = 0; + dest += stride_minus_one; } } } diff --git a/servers/display_server.cpp b/servers/display_server.cpp index a587f72d02..847be469db 100644 --- a/servers/display_server.cpp +++ b/servers/display_server.cpp @@ -532,6 +532,11 @@ Error DisplayServer::file_dialog_show(const String &p_title, const String &p_cur return OK; } +Error DisplayServer::file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback) { + WARN_PRINT("Native dialogs not supported by this display server."); + return OK; +} + int DisplayServer::keyboard_get_layout_count() const { return 0; } @@ -804,6 +809,7 @@ void DisplayServer::_bind_methods() { ClassDB::bind_method(D_METHOD("dialog_input_text", "title", "description", "existing_text", "callback"), &DisplayServer::dialog_input_text); ClassDB::bind_method(D_METHOD("file_dialog_show", "title", "current_directory", "filename", "show_hidden", "mode", "filters", "callback"), &DisplayServer::file_dialog_show); + ClassDB::bind_method(D_METHOD("file_dialog_with_options_show", "title", "current_directory", "root", "filename", "show_hidden", "mode", "filters", "options", "callback"), &DisplayServer::file_dialog_with_options_show); ClassDB::bind_method(D_METHOD("keyboard_get_layout_count"), &DisplayServer::keyboard_get_layout_count); ClassDB::bind_method(D_METHOD("keyboard_get_current_layout"), &DisplayServer::keyboard_get_current_layout); diff --git a/servers/display_server.h b/servers/display_server.h index 92a4a4a699..6ec0831500 100644 --- a/servers/display_server.h +++ b/servers/display_server.h @@ -514,6 +514,7 @@ public: FILE_DIALOG_MODE_SAVE_MAX }; virtual Error file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback); + virtual Error file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback); virtual int keyboard_get_layout_count() const; virtual int keyboard_get_current_layout() const; diff --git a/servers/physics_server_3d.cpp b/servers/physics_server_3d.cpp index addd1a5663..686388f237 100644 --- a/servers/physics_server_3d.cpp +++ b/servers/physics_server_3d.cpp @@ -835,8 +835,60 @@ void PhysicsServer3D::_bind_methods() { /* SOFT BODY API */ + ClassDB::bind_method(D_METHOD("soft_body_create"), &PhysicsServer3D::soft_body_create); + + ClassDB::bind_method(D_METHOD("soft_body_update_rendering_server", "body", "rendering_server_handler"), &PhysicsServer3D::soft_body_update_rendering_server); + + ClassDB::bind_method(D_METHOD("soft_body_set_space", "body", "space"), &PhysicsServer3D::soft_body_set_space); + ClassDB::bind_method(D_METHOD("soft_body_get_space", "body"), &PhysicsServer3D::soft_body_get_space); + + ClassDB::bind_method(D_METHOD("soft_body_set_mesh", "body", "mesh"), &PhysicsServer3D::soft_body_set_mesh); + ClassDB::bind_method(D_METHOD("soft_body_get_bounds", "body"), &PhysicsServer3D::soft_body_get_bounds); + ClassDB::bind_method(D_METHOD("soft_body_set_collision_layer", "body", "layer"), &PhysicsServer3D::soft_body_set_collision_layer); + ClassDB::bind_method(D_METHOD("soft_body_get_collision_layer", "body"), &PhysicsServer3D::soft_body_get_collision_layer); + + ClassDB::bind_method(D_METHOD("soft_body_set_collision_mask", "body", "mask"), &PhysicsServer3D::soft_body_set_collision_mask); + ClassDB::bind_method(D_METHOD("soft_body_get_collision_mask", "body"), &PhysicsServer3D::soft_body_get_collision_mask); + + ClassDB::bind_method(D_METHOD("soft_body_add_collision_exception", "body", "body_b"), &PhysicsServer3D::soft_body_add_collision_exception); + ClassDB::bind_method(D_METHOD("soft_body_remove_collision_exception", "body", "body_b"), &PhysicsServer3D::soft_body_remove_collision_exception); + + ClassDB::bind_method(D_METHOD("soft_body_set_state", "body", "state", "variant"), &PhysicsServer3D::soft_body_set_state); + ClassDB::bind_method(D_METHOD("soft_body_get_state", "body", "state"), &PhysicsServer3D::soft_body_get_state); + + ClassDB::bind_method(D_METHOD("soft_body_set_transform", "body", "transform"), &PhysicsServer3D::soft_body_set_transform); + + ClassDB::bind_method(D_METHOD("soft_body_set_ray_pickable", "body", "enable"), &PhysicsServer3D::soft_body_set_ray_pickable); + + ClassDB::bind_method(D_METHOD("soft_body_set_simulation_precision", "body", "simulation_precision"), &PhysicsServer3D::soft_body_set_simulation_precision); + ClassDB::bind_method(D_METHOD("soft_body_get_simulation_precision", "body"), &PhysicsServer3D::soft_body_get_simulation_precision); + + ClassDB::bind_method(D_METHOD("soft_body_set_total_mass", "body", "total_mass"), &PhysicsServer3D::soft_body_set_total_mass); + ClassDB::bind_method(D_METHOD("soft_body_get_total_mass", "body"), &PhysicsServer3D::soft_body_get_total_mass); + + ClassDB::bind_method(D_METHOD("soft_body_set_linear_stiffness", "body", "stiffness"), &PhysicsServer3D::soft_body_set_linear_stiffness); + ClassDB::bind_method(D_METHOD("soft_body_get_linear_stiffness", "body"), &PhysicsServer3D::soft_body_get_linear_stiffness); + + ClassDB::bind_method(D_METHOD("soft_body_set_pressure_coefficient", "body", "pressure_coefficient"), &PhysicsServer3D::soft_body_set_pressure_coefficient); + ClassDB::bind_method(D_METHOD("soft_body_get_pressure_coefficient", "body"), &PhysicsServer3D::soft_body_get_pressure_coefficient); + + ClassDB::bind_method(D_METHOD("soft_body_set_damping_coefficient", "body", "damping_coefficient"), &PhysicsServer3D::soft_body_set_damping_coefficient); + ClassDB::bind_method(D_METHOD("soft_body_get_damping_coefficient", "body"), &PhysicsServer3D::soft_body_get_damping_coefficient); + + ClassDB::bind_method(D_METHOD("soft_body_set_drag_coefficient", "body", "drag_coefficient"), &PhysicsServer3D::soft_body_set_drag_coefficient); + ClassDB::bind_method(D_METHOD("soft_body_get_drag_coefficient", "body"), &PhysicsServer3D::soft_body_get_drag_coefficient); + + ClassDB::bind_method(D_METHOD("soft_body_move_point", "body", "point_index", "global_position"), &PhysicsServer3D::soft_body_move_point); + ClassDB::bind_method(D_METHOD("soft_body_get_point_global_position", "body", "point_index"), &PhysicsServer3D::soft_body_get_point_global_position); + + ClassDB::bind_method(D_METHOD("soft_body_remove_all_pinned_points", "body"), &PhysicsServer3D::soft_body_remove_all_pinned_points); + + ClassDB::bind_method(D_METHOD("soft_body_pin_point", "body", "point_index", "pin"), &PhysicsServer3D::soft_body_pin_point); + + ClassDB::bind_method(D_METHOD("soft_body_is_point_pinned", "body", "point_index"), &PhysicsServer3D::soft_body_is_point_pinned); + /* JOINT API */ ClassDB::bind_method(D_METHOD("joint_create"), &PhysicsServer3D::joint_create); diff --git a/servers/rendering/dummy/storage/material_storage.cpp b/servers/rendering/dummy/storage/material_storage.cpp new file mode 100644 index 0000000000..5a2c135ff5 --- /dev/null +++ b/servers/rendering/dummy/storage/material_storage.cpp @@ -0,0 +1,136 @@ +/**************************************************************************/ +/* material_storage.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 "material_storage.h" + +using namespace RendererDummy; + +MaterialStorage *MaterialStorage::singleton = nullptr; + +MaterialStorage::MaterialStorage() { + singleton = this; + ShaderCompiler::DefaultIdentifierActions actions; + dummy_compiler.initialize(actions); +} + +MaterialStorage::~MaterialStorage() { + singleton = nullptr; +} + +RID MaterialStorage::shader_allocate() { + return shader_owner.allocate_rid(); +} + +void MaterialStorage::shader_initialize(RID p_rid) { + shader_owner.initialize_rid(p_rid, DummyShader()); +} + +void MaterialStorage::shader_free(RID p_rid) { + DummyShader *shader = shader_owner.get_or_null(p_rid); + ERR_FAIL_NULL(shader); + + shader_owner.free(p_rid); +} + +void MaterialStorage::shader_set_code(RID p_shader, const String &p_code) { + DummyShader *shader = shader_owner.get_or_null(p_shader); + ERR_FAIL_NULL(shader); + if (p_code.is_empty()) { + return; + } + + String mode_string = ShaderLanguage::get_shader_type(p_code); + + RS::ShaderMode new_mode; + if (mode_string == "canvas_item") { + new_mode = RS::SHADER_CANVAS_ITEM; + } else if (mode_string == "particles") { + new_mode = RS::SHADER_PARTICLES; + } else if (mode_string == "spatial") { + new_mode = RS::SHADER_SPATIAL; + } else if (mode_string == "sky") { + new_mode = RS::SHADER_SKY; + } else if (mode_string == "fog") { + new_mode = RS::SHADER_FOG; + } else { + new_mode = RS::SHADER_MAX; + } + ShaderCompiler::IdentifierActions actions; + actions.uniforms = &shader->uniforms; + ShaderCompiler::GeneratedCode gen_code; + + Error err = MaterialStorage::get_singleton()->dummy_compiler.compile(new_mode, p_code, &actions, "", gen_code); + ERR_FAIL_COND_MSG(err != OK, "Shader compilation failed."); +} + +void MaterialStorage::get_shader_parameter_list(RID p_shader, List<PropertyInfo> *p_param_list) const { + DummyShader *shader = shader_owner.get_or_null(p_shader); + ERR_FAIL_NULL(shader); + + SortArray<Pair<StringName, int>, ShaderLanguage::UniformOrderComparator> sorter; + LocalVector<Pair<StringName, int>> filtered_uniforms; + + for (const KeyValue<StringName, ShaderLanguage::ShaderNode::Uniform> &E : shader->uniforms) { + if (E.value.scope != ShaderLanguage::ShaderNode::Uniform::SCOPE_LOCAL) { + continue; + } + if (E.value.texture_order >= 0) { + filtered_uniforms.push_back(Pair<StringName, int>(E.key, E.value.texture_order + 100000)); + } else { + filtered_uniforms.push_back(Pair<StringName, int>(E.key, E.value.order)); + } + } + int uniform_count = filtered_uniforms.size(); + sorter.sort(filtered_uniforms.ptr(), uniform_count); + + String last_group; + for (int i = 0; i < uniform_count; i++) { + const StringName &uniform_name = filtered_uniforms[i].first; + const ShaderLanguage::ShaderNode::Uniform &uniform = shader->uniforms[uniform_name]; + + String group = uniform.group; + if (!uniform.subgroup.is_empty()) { + group += "::" + uniform.subgroup; + } + + if (group != last_group) { + PropertyInfo pi; + pi.usage = PROPERTY_USAGE_GROUP; + pi.name = group; + p_param_list->push_back(pi); + + last_group = group; + } + + PropertyInfo pi = ShaderLanguage::uniform_to_property_info(uniform); + pi.name = uniform_name; + p_param_list->push_back(pi); + } +} diff --git a/servers/rendering/dummy/storage/material_storage.h b/servers/rendering/dummy/storage/material_storage.h index e8766b4a21..4fd178ac88 100644 --- a/servers/rendering/dummy/storage/material_storage.h +++ b/servers/rendering/dummy/storage/material_storage.h @@ -31,13 +31,31 @@ #ifndef MATERIAL_STORAGE_DUMMY_H #define MATERIAL_STORAGE_DUMMY_H +#include "servers/rendering/shader_compiler.h" +#include "servers/rendering/shader_language.h" #include "servers/rendering/storage/material_storage.h" #include "servers/rendering/storage/utilities.h" namespace RendererDummy { class MaterialStorage : public RendererMaterialStorage { +private: + static MaterialStorage *singleton; + + struct DummyShader { + HashMap<StringName, ShaderLanguage::ShaderNode::Uniform> uniforms; + }; + + mutable RID_Owner<DummyShader> shader_owner; + + ShaderCompiler dummy_compiler; + public: + static MaterialStorage *get_singleton() { return singleton; } + + MaterialStorage(); + ~MaterialStorage(); + /* GLOBAL SHADER UNIFORM API */ virtual void global_shader_parameter_add(const StringName &p_name, RS::GlobalShaderParameterType p_type, const Variant &p_value) override {} @@ -58,15 +76,15 @@ public: /* SHADER API */ - virtual RID shader_allocate() override { return RID(); } - virtual void shader_initialize(RID p_rid) override {} - virtual void shader_free(RID p_rid) override{}; + virtual RID shader_allocate() override; + virtual void shader_initialize(RID p_rid) override; + virtual void shader_free(RID p_rid) override; - virtual void shader_set_code(RID p_shader, const String &p_code) override {} + virtual void shader_set_code(RID p_shader, const String &p_code) override; virtual void shader_set_path_hint(RID p_shader, const String &p_code) override {} virtual String shader_get_code(RID p_shader) const override { return ""; } - virtual void get_shader_parameter_list(RID p_shader, List<PropertyInfo> *p_param_list) const override {} + virtual void get_shader_parameter_list(RID p_shader, List<PropertyInfo> *p_param_list) const override; virtual void shader_set_default_texture_parameter(RID p_shader, const StringName &p_name, RID p_texture, int p_index) override {} virtual RID shader_get_default_texture_parameter(RID p_shader, const StringName &p_name, int p_index) const override { return RID(); } diff --git a/servers/rendering/dummy/storage/mesh_storage.cpp b/servers/rendering/dummy/storage/mesh_storage.cpp index ab27711d6e..0b7ade1762 100644 --- a/servers/rendering/dummy/storage/mesh_storage.cpp +++ b/servers/rendering/dummy/storage/mesh_storage.cpp @@ -63,3 +63,33 @@ void MeshStorage::mesh_clear(RID p_mesh) { m->surfaces.clear(); } + +RID MeshStorage::multimesh_allocate() { + return multimesh_owner.allocate_rid(); +} + +void MeshStorage::multimesh_initialize(RID p_rid) { + multimesh_owner.initialize_rid(p_rid, DummyMultiMesh()); +} + +void MeshStorage::multimesh_free(RID p_rid) { + DummyMultiMesh *multimesh = multimesh_owner.get_or_null(p_rid); + ERR_FAIL_NULL(multimesh); + + multimesh_owner.free(p_rid); +} + +void MeshStorage::multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_buffer) { + DummyMultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh); + ERR_FAIL_NULL(multimesh); + multimesh->buffer.resize(p_buffer.size()); + float *cache_data = multimesh->buffer.ptrw(); + memcpy(cache_data, p_buffer.ptr(), p_buffer.size() * sizeof(float)); +} + +Vector<float> MeshStorage::multimesh_get_buffer(RID p_multimesh) const { + DummyMultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh); + ERR_FAIL_NULL_V(multimesh, Vector<float>()); + + return multimesh->buffer; +} diff --git a/servers/rendering/dummy/storage/mesh_storage.h b/servers/rendering/dummy/storage/mesh_storage.h index 89cccec54b..a7351655bd 100644 --- a/servers/rendering/dummy/storage/mesh_storage.h +++ b/servers/rendering/dummy/storage/mesh_storage.h @@ -50,11 +50,15 @@ private: mutable RID_Owner<DummyMesh> mesh_owner; -public: - static MeshStorage *get_singleton() { - return singleton; + struct DummyMultiMesh { + PackedFloat32Array buffer; }; + mutable RID_Owner<DummyMultiMesh> multimesh_owner; + +public: + static MeshStorage *get_singleton() { return singleton; } + MeshStorage(); ~MeshStorage(); @@ -113,8 +117,11 @@ public: virtual void mesh_set_custom_aabb(RID p_mesh, const AABB &p_aabb) override {} virtual AABB mesh_get_custom_aabb(RID p_mesh) const override { return AABB(); } - virtual AABB mesh_get_aabb(RID p_mesh, RID p_skeleton = RID()) override { return AABB(); } + + virtual void mesh_set_path(RID p_mesh, const String &p_path) override {} + virtual String mesh_get_path(RID p_mesh) const override { return String(); } + virtual void mesh_set_shadow_mesh(RID p_mesh, RID p_shadow_mesh) override {} virtual void mesh_clear(RID p_mesh) override; @@ -131,9 +138,9 @@ public: /* MULTIMESH API */ - virtual RID multimesh_allocate() override { return RID(); } - virtual void multimesh_initialize(RID p_rid) override {} - virtual void multimesh_free(RID p_rid) override {} + virtual RID multimesh_allocate() override; + virtual void multimesh_initialize(RID p_rid) override; + virtual void multimesh_free(RID p_rid) override; virtual void multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors = false, bool p_use_custom_data = false) override {} virtual int multimesh_get_instance_count(RID p_multimesh) const override { return 0; } @@ -151,8 +158,8 @@ public: virtual Transform2D multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const override { return Transform2D(); } virtual Color multimesh_instance_get_color(RID p_multimesh, int p_index) const override { return Color(); } virtual Color multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const override { return Color(); } - virtual void multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_buffer) override {} - virtual Vector<float> multimesh_get_buffer(RID p_multimesh) const override { return Vector<float>(); } + virtual void multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_buffer) override; + virtual Vector<float> multimesh_get_buffer(RID p_multimesh) const override; virtual void multimesh_set_visible_instances(RID p_multimesh, int p_visible) override {} virtual int multimesh_get_visible_instances(RID p_multimesh) const override { return 0; } diff --git a/servers/rendering/dummy/storage/texture_storage.h b/servers/rendering/dummy/storage/texture_storage.h index b3a5323e66..1331ca72c2 100644 --- a/servers/rendering/dummy/storage/texture_storage.h +++ b/servers/rendering/dummy/storage/texture_storage.h @@ -46,9 +46,7 @@ private: mutable RID_PtrOwner<DummyTexture> texture_owner; public: - static TextureStorage *get_singleton() { - return singleton; - }; + static TextureStorage *get_singleton() { return singleton; } TextureStorage(); ~TextureStorage(); diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp index 0c0062662a..3bd0fb6e2b 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp @@ -3730,7 +3730,9 @@ void RenderForwardClustered::_geometry_instance_add_surface_with_material(Geomet uint64_t format = RendererRD::MeshStorage::get_singleton()->mesh_surface_get_format(sdcache->surface); if (p_material->shader_data->uses_tangent && !(format & RS::ARRAY_FORMAT_TANGENT)) { - WARN_PRINT_ED("Attempting to use a shader that requires tangents with a mesh that doesn't contain tangents. Ensure that meshes are imported with the 'ensure_tangents' option. If creating your own meshes, add an `ARRAY_TANGENT` array (when using ArrayMesh) or call `generate_tangents()` (when using SurfaceTool)."); + String shader_path = p_material->shader_data->path.is_empty() ? "" : "(" + p_material->shader_data->path + ")"; + String mesh_path = mesh_storage->mesh_get_path(p_mesh).is_empty() ? "" : "(" + mesh_storage->mesh_get_path(p_mesh) + ")"; + WARN_PRINT_ED(vformat("Attempting to use a shader %s that requires tangents with a mesh %s that doesn't contain tangents. Ensure that meshes are imported with the 'ensure_tangents' option. If creating your own meshes, add an `ARRAY_TANGENT` array (when using ArrayMesh) or call `generate_tangents()` (when using SurfaceTool).", shader_path, mesh_path)); } } diff --git a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp index 86852ce020..abb0359f0d 100644 --- a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp @@ -680,9 +680,6 @@ void SceneShaderForwardClustered::init(const String p_defines) { actions.usage_defines["BACKLIGHT"] = "#define LIGHT_BACKLIGHT_USED\n"; actions.usage_defines["SCREEN_UV"] = "#define SCREEN_UV_USED\n"; - actions.usage_defines["DIFFUSE_LIGHT"] = "#define USE_LIGHT_SHADER_CODE\n"; - actions.usage_defines["SPECULAR_LIGHT"] = "#define USE_LIGHT_SHADER_CODE\n"; - actions.usage_defines["FOG"] = "#define CUSTOM_FOG_USED\n"; actions.usage_defines["RADIANCE"] = "#define CUSTOM_RADIANCE_USED\n"; actions.usage_defines["IRRADIANCE"] = "#define CUSTOM_IRRADIANCE_USED\n"; diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp index da04e6f938..db8c1acdbe 100644 --- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp +++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp @@ -263,28 +263,11 @@ RID RenderForwardMobile::RenderBufferDataForwardMobile::get_color_fbs(Framebuffe RID RenderForwardMobile::reflection_probe_create_framebuffer(RID p_color, RID p_depth) { // Our attachments - Vector<RID> fb; - fb.push_back(p_color); // 0 - fb.push_back(p_depth); // 1 + Vector<RID> attachments; + attachments.push_back(p_color); // 0 + attachments.push_back(p_depth); // 1 - // Now define our subpasses - Vector<RD::FramebufferPass> passes; - RD::FramebufferPass pass; - - // re-using the same attachments - pass.color_attachments.push_back(0); - pass.depth_attachment = 1; - - // - opaque pass - passes.push_back(pass); - - // - sky pass - passes.push_back(pass); - - // - alpha pass - passes.push_back(pass); - - return RD::get_singleton()->framebuffer_create_multipass(fb, passes); + return RD::get_singleton()->framebuffer_create(attachments); } void RenderForwardMobile::setup_render_buffer_data(Ref<RenderSceneBuffersRD> p_render_buffers) { @@ -1049,10 +1032,6 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_ALPHA, p_render_data, radiance_texture, samplers, true); - if (rb_data.is_valid()) { - framebuffer = rb_data->get_color_fbs(RenderBufferDataForwardMobile::FB_CONFIG_RENDER_PASS); - } - // this may be needed if we re-introduced steps that change info, not sure which do so in the previous implementation //_setup_environment(p_render_data, is_reflection_probe, screen_size, !is_reflection_probe, p_default_bg_color, false); @@ -2437,7 +2416,9 @@ void RenderForwardMobile::_geometry_instance_add_surface_with_material(GeometryI uint64_t format = RendererRD::MeshStorage::get_singleton()->mesh_surface_get_format(sdcache->surface); if (p_material->shader_data->uses_tangent && !(format & RS::ARRAY_FORMAT_TANGENT)) { - WARN_PRINT_ED("Attempting to use a shader that requires tangents with a mesh that doesn't contain tangents. Ensure that meshes are imported with the 'ensure_tangents' option. If creating your own meshes, add an `ARRAY_TANGENT` array (when using ArrayMesh) or call `generate_tangents()` (when using SurfaceTool)."); + String shader_path = p_material->shader_data->path.is_empty() ? "" : "(" + p_material->shader_data->path + ")"; + String mesh_path = mesh_storage->mesh_get_path(p_mesh).is_empty() ? "" : "(" + mesh_storage->mesh_get_path(p_mesh) + ")"; + WARN_PRINT_ED(vformat("Attempting to use a shader %s that requires tangents with a mesh %s that doesn't contain tangents. Ensure that meshes are imported with the 'ensure_tangents' option. If creating your own meshes, add an `ARRAY_TANGENT` array (when using ArrayMesh) or call `generate_tangents()` (when using SurfaceTool).", shader_path, mesh_path)); } } diff --git a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp index 043cdbc8e5..96cd5052c8 100644 --- a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp +++ b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp @@ -584,9 +584,6 @@ void SceneShaderForwardMobile::init(const String p_defines) { actions.usage_defines["BACKLIGHT"] = "#define LIGHT_BACKLIGHT_USED\n"; actions.usage_defines["SCREEN_UV"] = "#define SCREEN_UV_USED\n"; - actions.usage_defines["DIFFUSE_LIGHT"] = "#define USE_LIGHT_SHADER_CODE\n"; - actions.usage_defines["SPECULAR_LIGHT"] = "#define USE_LIGHT_SHADER_CODE\n"; - actions.usage_defines["FOG"] = "#define CUSTOM_FOG_USED\n"; actions.usage_defines["RADIANCE"] = "#define CUSTOM_RADIANCE_USED\n"; actions.usage_defines["IRRADIANCE"] = "#define CUSTOM_IRRADIANCE_USED\n"; diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp index 283b3ee09a..fa68565cc1 100644 --- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp @@ -2579,7 +2579,6 @@ RendererCanvasRenderRD::RendererCanvasRenderRD() { actions.usage_defines["SCREEN_PIXEL_SIZE"] = "@SCREEN_UV"; actions.usage_defines["NORMAL"] = "#define NORMAL_USED\n"; actions.usage_defines["NORMAL_MAP"] = "#define NORMAL_MAP_USED\n"; - actions.usage_defines["LIGHT"] = "#define LIGHT_SHADER_CODE_USED\n"; actions.usage_defines["SPECULAR_SHININESS"] = "#define SPECULAR_SHININESS_USED\n"; actions.usage_defines["POINT_SIZE"] = "#define USE_POINT_SIZE\n"; diff --git a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl index 930d981494..cb95621219 100644 --- a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl +++ b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl @@ -140,6 +140,9 @@ layout(location = 10) out flat uint instance_index_interp; vec3 multiview_uv(vec2 uv) { return vec3(uv, ViewIndex); } +ivec3 multiview_uv(ivec2 uv) { + return ivec3(uv, int(ViewIndex)); +} layout(location = 11) out vec4 combined_projected; #else // USE_MULTIVIEW // Set to zero, not supported in non stereo @@ -147,6 +150,9 @@ layout(location = 11) out vec4 combined_projected; vec2 multiview_uv(vec2 uv) { return uv; } +ivec2 multiview_uv(ivec2 uv) { + return uv; +} #endif //USE_MULTIVIEW invariant gl_Position; @@ -704,6 +710,9 @@ layout(location = 10) in flat uint instance_index_interp; vec3 multiview_uv(vec2 uv) { return vec3(uv, ViewIndex); } +ivec3 multiview_uv(ivec2 uv) { + return ivec3(uv, int(ViewIndex)); +} layout(location = 11) in vec4 combined_projected; #else // USE_MULTIVIEW // Set to zero, not supported in non stereo @@ -711,6 +720,9 @@ layout(location = 11) in vec4 combined_projected; vec2 multiview_uv(vec2 uv) { return uv; } +ivec2 multiview_uv(ivec2 uv) { + return uv; +} #endif //USE_MULTIVIEW //defines to keep compatibility with vertex diff --git a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl index 03737e087c..e9c69058f2 100644 --- a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl +++ b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl @@ -125,12 +125,18 @@ layout(location = 9) out highp float dp_clip; vec3 multiview_uv(vec2 uv) { return vec3(uv, ViewIndex); } +ivec3 multiview_uv(ivec2 uv) { + return ivec3(uv, int(ViewIndex)); +} #else // Set to zero, not supported in non stereo #define ViewIndex 0 vec2 multiview_uv(vec2 uv) { return uv; } +ivec2 multiview_uv(ivec2 uv) { + return uv; +} #endif //USE_MULTIVIEW invariant gl_Position; @@ -568,12 +574,18 @@ layout(location = 9) highp in float dp_clip; vec3 multiview_uv(vec2 uv) { return vec3(uv, ViewIndex); } +ivec3 multiview_uv(ivec2 uv) { + return ivec3(uv, int(ViewIndex)); +} #else // Set to zero, not supported in non stereo #define ViewIndex 0 vec2 multiview_uv(vec2 uv) { return uv; } +ivec2 multiview_uv(ivec2 uv) { + return uv; +} #endif //USE_MULTIVIEW //defines to keep compatibility with vertex diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp index b97ce2d006..245dac94ac 100644 --- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp @@ -768,6 +768,20 @@ AABB MeshStorage::mesh_get_aabb(RID p_mesh, RID p_skeleton) { return aabb; } +void MeshStorage::mesh_set_path(RID p_mesh, const String &p_path) { + Mesh *mesh = mesh_owner.get_or_null(p_mesh); + ERR_FAIL_NULL(mesh); + + mesh->path = p_path; +} + +String MeshStorage::mesh_get_path(RID p_mesh) const { + Mesh *mesh = mesh_owner.get_or_null(p_mesh); + ERR_FAIL_NULL_V(mesh, String()); + + return mesh->path; +} + void MeshStorage::mesh_set_shadow_mesh(RID p_mesh, RID p_shadow_mesh) { Mesh *mesh = mesh_owner.get_or_null(p_mesh); ERR_FAIL_NULL(mesh); diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.h b/servers/rendering/renderer_rd/storage_rd/mesh_storage.h index a1e2ffcf7e..771ac6d380 100644 --- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.h +++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.h @@ -162,6 +162,8 @@ private: RID shadow_mesh; HashSet<Mesh *> shadow_owners; + String path; + Dependency dependency; }; @@ -378,6 +380,9 @@ public: virtual AABB mesh_get_aabb(RID p_mesh, RID p_skeleton = RID()) override; virtual void mesh_set_shadow_mesh(RID p_mesh, RID p_shadow_mesh) override; + virtual void mesh_set_path(RID p_mesh, const String &p_path) override; + virtual String mesh_get_path(RID p_mesh) const override; + virtual void mesh_clear(RID p_mesh) override; virtual bool mesh_needs_instance(RID p_mesh, bool p_has_skeleton) override; diff --git a/servers/rendering/renderer_scene_cull.cpp b/servers/rendering/renderer_scene_cull.cpp index 73aacf311f..2f7e4fef06 100644 --- a/servers/rendering/renderer_scene_cull.cpp +++ b/servers/rendering/renderer_scene_cull.cpp @@ -4071,7 +4071,7 @@ bool RendererSceneCull::free(RID p_rid) { scenario_owner.free(p_rid); RendererSceneOcclusionCull::get_singleton()->remove_scenario(p_rid); - } else if (RendererSceneOcclusionCull::get_singleton()->is_occluder(p_rid)) { + } else if (RendererSceneOcclusionCull::get_singleton() && RendererSceneOcclusionCull::get_singleton()->is_occluder(p_rid)) { RendererSceneOcclusionCull::get_singleton()->free_occluder(p_rid); } else if (instance_owner.owns(p_rid)) { // delete the instance diff --git a/servers/rendering/rendering_device_graph.cpp b/servers/rendering/rendering_device_graph.cpp index 28526b8f58..904b439e65 100644 --- a/servers/rendering/rendering_device_graph.cpp +++ b/servers/rendering/rendering_device_graph.cpp @@ -33,6 +33,7 @@ #define PRINT_RENDER_GRAPH 0 #define FORCE_FULL_ACCESS_BITS 0 #define PRINT_RESOURCE_TRACKER_TOTAL 0 +#define PRINT_COMMAND_RECORDING 0 RenderingDeviceGraph::RenderingDeviceGraph() { // Default initialization. @@ -163,20 +164,35 @@ void RenderingDeviceGraph::_add_adjacent_command(int32_t p_previous_command_inde const uint32_t previous_command_data_offset = command_data_offsets[p_previous_command_index]; RecordedCommand &previous_command = *reinterpret_cast<RecordedCommand *>(&command_data[previous_command_data_offset]); previous_command.adjacent_command_list_index = _add_to_command_list(p_command_index, previous_command.adjacent_command_list_index); - r_command->src_stages = r_command->src_stages | previous_command.dst_stages; + previous_command.next_stages = previous_command.next_stages | r_command->self_stages; + r_command->previous_stages = r_command->previous_stages | previous_command.self_stages; } -int32_t RenderingDeviceGraph::_add_to_write_list(int32_t p_command_index, Rect2i suberesources, int32_t p_list_index) { +int32_t RenderingDeviceGraph::_add_to_slice_read_list(int32_t p_command_index, Rect2i p_subresources, int32_t p_list_index) { DEV_ASSERT(p_command_index < int32_t(command_count)); - DEV_ASSERT(p_list_index < int32_t(write_list_nodes.size())); + DEV_ASSERT(p_list_index < int32_t(read_slice_list_nodes.size())); - int32_t next_index = int32_t(write_list_nodes.size()); - write_list_nodes.resize(next_index + 1); + int32_t next_index = int32_t(read_slice_list_nodes.size()); + read_slice_list_nodes.resize(next_index + 1); - RecordedWriteListNode &new_node = write_list_nodes[next_index]; + RecordedSliceListNode &new_node = read_slice_list_nodes[next_index]; new_node.command_index = p_command_index; new_node.next_list_index = p_list_index; - new_node.subresources = suberesources; + new_node.subresources = p_subresources; + return next_index; +} + +int32_t RenderingDeviceGraph::_add_to_write_list(int32_t p_command_index, Rect2i p_subresources, int32_t p_list_index) { + DEV_ASSERT(p_command_index < int32_t(command_count)); + DEV_ASSERT(p_list_index < int32_t(write_slice_list_nodes.size())); + + int32_t next_index = int32_t(write_slice_list_nodes.size()); + write_slice_list_nodes.resize(next_index + 1); + + RecordedSliceListNode &new_node = write_slice_list_nodes[next_index]; + new_node.command_index = p_command_index; + new_node.next_list_index = p_list_index; + new_node.subresources = p_subresources; return next_index; } @@ -203,6 +219,9 @@ RenderingDeviceGraph::ComputeListInstruction *RenderingDeviceGraph::_allocate_co } void RenderingDeviceGraph::_add_command_to_graph(ResourceTracker **p_resource_trackers, ResourceUsage *p_resource_usages, uint32_t p_resource_count, int32_t p_command_index, RecordedCommand *r_command) { + // Assign the next stages derived from the stages the command requires first. + r_command->next_stages = r_command->self_stages; + if (command_label_index >= 0) { // If a label is active, tag the command with the label. r_command->label_index = command_label_index; @@ -242,6 +261,10 @@ void RenderingDeviceGraph::_add_command_to_graph(ResourceTracker **p_resource_tr resource_tracker->reset_if_outdated(tracking_frame); + const RDD::TextureSubresourceRange &subresources = resource_tracker->texture_subresources; + const Rect2i resource_tracker_rect(subresources.base_mipmap, subresources.base_layer, subresources.mipmap_count, subresources.layer_count); + Rect2i search_tracker_rect = resource_tracker_rect; + ResourceUsage new_resource_usage = p_resource_usages[i]; bool write_usage = _is_write_usage(new_resource_usage); BitField<RDD::BarrierAccessBits> new_usage_access = _usage_to_access_bits(new_resource_usage); @@ -264,9 +287,14 @@ void RenderingDeviceGraph::_add_command_to_graph(ResourceTracker **p_resource_tr // If the parent hasn't been used yet, we assign the usage of the slice to the entire resource. resource_tracker->parent->usage = new_resource_usage; - // Also assign the usage to the slice and consider it a write operation. + // Also assign the usage to the slice and consider it a write operation. Consider the parent's current usage access as its own. resource_tracker->usage = new_resource_usage; + resource_tracker->usage_access = resource_tracker->parent->usage_access; write_usage = true; + + // Indicate the area that should be tracked is the entire resource. + const RDD::TextureSubresourceRange &parent_subresources = resource_tracker->parent->texture_subresources; + search_tracker_rect = Rect2i(parent_subresources.base_mipmap, parent_subresources.base_layer, parent_subresources.mipmap_count, parent_subresources.layer_count); } else if (resource_tracker->in_parent_dirty_list) { if (resource_tracker->parent->usage == new_resource_usage) { // The slice will be transitioned to the resource of the parent and can be deleted from the dirty list. @@ -274,6 +302,8 @@ void RenderingDeviceGraph::_add_command_to_graph(ResourceTracker **p_resource_tr ResourceTracker *current_tracker = resource_tracker->parent->dirty_shared_list; bool initialized_dirty_rect = false; while (current_tracker != nullptr) { + current_tracker->reset_if_outdated(tracking_frame); + if (current_tracker == resource_tracker) { current_tracker->in_parent_dirty_list = false; @@ -305,6 +335,8 @@ void RenderingDeviceGraph::_add_command_to_graph(ResourceTracker **p_resource_tr ResourceTracker *current_tracker = resource_tracker->parent->dirty_shared_list; bool initialized_dirty_rect = false; while (current_tracker != nullptr) { + current_tracker->reset_if_outdated(tracking_frame); + if (current_tracker->texture_slice_or_dirty_rect.intersects(resource_tracker->texture_slice_or_dirty_rect)) { if (current_tracker->command_frame == tracking_frame && current_tracker->texture_slice_command_index == p_command_index) { ERR_FAIL_MSG("Texture slices that overlap can't be used in the same command."); @@ -312,6 +344,10 @@ void RenderingDeviceGraph::_add_command_to_graph(ResourceTracker **p_resource_tr // Delete the slice from the dirty list and revert it to the usage of the parent. if (current_tracker->texture_driver_id != 0) { _add_texture_barrier_to_command(current_tracker->texture_driver_id, current_tracker->usage_access, new_usage_access, current_tracker->usage, resource_tracker->parent->usage, current_tracker->texture_subresources, command_normalization_barriers, r_command->normalization_barrier_index, r_command->normalization_barrier_count); + + // Merge the area of the slice with the current tracking area of the command and indicate it's a write usage as well. + search_tracker_rect = search_tracker_rect.merge(current_tracker->texture_slice_or_dirty_rect); + write_usage = true; } current_tracker->in_parent_dirty_list = false; @@ -339,8 +375,9 @@ void RenderingDeviceGraph::_add_command_to_graph(ResourceTracker **p_resource_tr } } - // If it wasn't in the list, assume the usage is the same as the parent. + // If it wasn't in the list, assume the usage is the same as the parent. Consider the parent's current usage access as its own. resource_tracker->usage = resource_tracker->parent->usage; + resource_tracker->usage_access = resource_tracker->parent->usage_access; if (resource_tracker->usage != new_resource_usage) { // Insert to the dirty list if the requested usage is different. @@ -355,27 +392,30 @@ void RenderingDeviceGraph::_add_command_to_graph(ResourceTracker **p_resource_tr } } } else { - if (resource_tracker->dirty_shared_list != nullptr) { + ResourceTracker *current_tracker = resource_tracker->dirty_shared_list; + if (current_tracker != nullptr) { // Consider the usage as write if we must transition any of the slices. write_usage = true; } - while (resource_tracker->dirty_shared_list != nullptr) { - if (resource_tracker->dirty_shared_list->texture_driver_id != 0) { + while (current_tracker != nullptr) { + current_tracker->reset_if_outdated(tracking_frame); + + if (current_tracker->texture_driver_id != 0) { // Transition all slices to the layout of the parent resource. - _add_texture_barrier_to_command(resource_tracker->dirty_shared_list->texture_driver_id, resource_tracker->dirty_shared_list->usage_access, new_usage_access, resource_tracker->dirty_shared_list->usage, resource_tracker->usage, resource_tracker->dirty_shared_list->texture_subresources, command_normalization_barriers, r_command->normalization_barrier_index, r_command->normalization_barrier_count); + _add_texture_barrier_to_command(current_tracker->texture_driver_id, current_tracker->usage_access, new_usage_access, current_tracker->usage, resource_tracker->usage, current_tracker->texture_subresources, command_normalization_barriers, r_command->normalization_barrier_index, r_command->normalization_barrier_count); } - resource_tracker->dirty_shared_list->in_parent_dirty_list = false; - resource_tracker->dirty_shared_list = resource_tracker->dirty_shared_list->next_shared; + current_tracker->in_parent_dirty_list = false; + current_tracker = current_tracker->next_shared; } + + resource_tracker->dirty_shared_list = nullptr; } // Use the resource's parent tracker directly for all search operations. bool resource_has_parent = resource_tracker->parent != nullptr; ResourceTracker *search_tracker = resource_has_parent ? resource_tracker->parent : resource_tracker; - const RDD::TextureSubresourceRange &subresources = resource_tracker->texture_subresources; - Rect2i resource_tracker_rect(subresources.base_mipmap, subresources.base_layer, subresources.mipmap_count, subresources.layer_count); bool different_usage = resource_tracker->usage != new_resource_usage; bool write_usage_after_write = (write_usage && search_tracker->write_command_or_list_index >= 0); if (different_usage || write_usage_after_write) { @@ -418,18 +458,18 @@ void RenderingDeviceGraph::_add_command_to_graph(ResourceTracker **p_resource_tr int32_t previous_write_list_index = -1; int32_t write_list_index = search_tracker->write_command_or_list_index; while (write_list_index >= 0) { - const RecordedWriteListNode &write_list_node = write_list_nodes[write_list_index]; - if (!resource_has_parent || resource_tracker_rect.intersects(write_list_node.subresources)) { + const RecordedSliceListNode &write_list_node = write_slice_list_nodes[write_list_index]; + if (!resource_has_parent || search_tracker_rect.intersects(write_list_node.subresources)) { if (write_list_node.command_index == p_command_index) { ERR_FAIL_COND_MSG(!resource_has_parent, "Command can't have itself as a dependency."); } else { // Command is dependent on this command. Add this command to the adjacency list of the write command. _add_adjacent_command(write_list_node.command_index, p_command_index, r_command); - if (resource_has_parent && write_usage && resource_tracker_rect.encloses(write_list_node.subresources)) { + if (resource_has_parent && write_usage && search_tracker_rect.encloses(write_list_node.subresources)) { // Eliminate redundant writes from the list. if (previous_write_list_index >= 0) { - RecordedWriteListNode &previous_list_node = write_list_nodes[previous_write_list_index]; + RecordedSliceListNode &previous_list_node = write_slice_list_nodes[previous_write_list_index]; previous_list_node.next_list_index = write_list_node.next_list_index; } else { search_tracker->write_command_or_list_index = write_list_node.next_list_index; @@ -463,47 +503,69 @@ void RenderingDeviceGraph::_add_command_to_graph(ResourceTracker **p_resource_tr search_tracker->write_command_or_list_index = _add_to_write_list(search_tracker->write_command_or_list_index, tracker_rect, -1); } - search_tracker->write_command_or_list_index = _add_to_write_list(p_command_index, resource_tracker_rect, search_tracker->write_command_or_list_index); + search_tracker->write_command_or_list_index = _add_to_write_list(p_command_index, search_tracker_rect, search_tracker->write_command_or_list_index); search_tracker->write_command_list_enabled = true; } else { search_tracker->write_command_or_list_index = p_command_index; search_tracker->write_command_list_enabled = false; } - // We add this command to the adjacency list of all commands that were reading from this resource. We clear the list in the process. - int32_t previous_command_list_index = -1; - int32_t read_command_list_index = search_tracker->read_command_list_index; - while (read_command_list_index >= 0) { - const RecordedCommandListNode &command_list_node = command_list_nodes[read_command_list_index]; + // We add this command to the adjacency list of all commands that were reading from the entire resource. + int32_t read_full_command_list_index = search_tracker->read_full_command_list_index; + while (read_full_command_list_index >= 0) { + const RecordedCommandListNode &command_list_node = command_list_nodes[read_full_command_list_index]; if (command_list_node.command_index == p_command_index) { if (!resource_has_parent) { - // Slices are allowed to be in different usages in the same command as they are guaranteed to have no overlap in the same command. + // Only slices are allowed to be in different usages in the same command as they are guaranteed to have no overlap in the same command. ERR_FAIL_MSG("Command can't have itself as a dependency."); - } else { - // Advance to the next element. - read_command_list_index = command_list_node.next_list_index; - previous_command_list_index = read_command_list_index; } } else { - if (previous_command_list_index >= 0) { + // Add this command to the adjacency list of each command that was reading this resource. + _add_adjacent_command(command_list_node.command_index, p_command_index, r_command); + } + + read_full_command_list_index = command_list_node.next_list_index; + } + + if (!resource_has_parent) { + // Clear the full list if this resource is not a slice. + search_tracker->read_full_command_list_index = -1; + } + + // We add this command to the adjacency list of all commands that were reading from resource slices. + int32_t previous_slice_command_list_index = -1; + int32_t read_slice_command_list_index = search_tracker->read_slice_command_list_index; + while (read_slice_command_list_index >= 0) { + const RecordedSliceListNode &read_list_node = read_slice_list_nodes[read_slice_command_list_index]; + if (!resource_has_parent || search_tracker_rect.encloses(read_list_node.subresources)) { + if (previous_slice_command_list_index >= 0) { // Erase this element and connect the previous one to the next element. - command_list_nodes[previous_command_list_index].next_list_index = command_list_node.next_list_index; - read_command_list_index = command_list_node.next_list_index; - previous_command_list_index = read_command_list_index; + read_slice_list_nodes[previous_slice_command_list_index].next_list_index = read_list_node.next_list_index; } else { // Erase this element from the head of the list. - DEV_ASSERT(search_tracker->read_command_list_index == read_command_list_index); - read_command_list_index = command_list_node.next_list_index; - search_tracker->read_command_list_index = read_command_list_index; + DEV_ASSERT(search_tracker->read_slice_command_list_index == read_slice_command_list_index); + search_tracker->read_slice_command_list_index = read_list_node.next_list_index; } + // Advance to the next element. + read_slice_command_list_index = read_list_node.next_list_index; + } else { + previous_slice_command_list_index = read_slice_command_list_index; + read_slice_command_list_index = read_list_node.next_list_index; + } + + if (!resource_has_parent || search_tracker_rect.intersects(read_list_node.subresources)) { // Add this command to the adjacency list of each command that was reading this resource. - _add_adjacent_command(command_list_node.command_index, p_command_index, r_command); + // We only add the dependency if there's an intersection between slices or this resource isn't a slice. + _add_adjacent_command(read_list_node.command_index, p_command_index, r_command); } } + } else if (resource_has_parent) { + // We add a read dependency to the tracker to indicate this command reads from the resource slice. + search_tracker->read_slice_command_list_index = _add_to_slice_read_list(p_command_index, resource_tracker_rect, search_tracker->read_slice_command_list_index); } else { - // We add a read dependency to the tracker to indicate this command reads from the resource. - search_tracker->read_command_list_index = _add_to_command_list(p_command_index, search_tracker->read_command_list_index); + // We add a read dependency to the tracker to indicate this command reads from the entire resource. + search_tracker->read_full_command_list_index = _add_to_command_list(p_command_index, search_tracker->read_full_command_list_index); } } } @@ -913,9 +975,13 @@ void RenderingDeviceGraph::_group_barriers_for_render_commands(RDD::CommandBuffe const uint32_t command_data_offset = command_data_offsets[command_index]; const RecordedCommand *command = reinterpret_cast<RecordedCommand *>(&command_data[command_data_offset]); +#if PRINT_COMMAND_RECORDING + print_line(vformat("Grouping barriers for #%d", command_index)); +#endif + // Merge command's stage bits with the barrier group. - barrier_group.src_stages = barrier_group.src_stages | command->src_stages; - barrier_group.dst_stages = barrier_group.dst_stages | command->dst_stages; + barrier_group.src_stages = barrier_group.src_stages | command->previous_stages; + barrier_group.dst_stages = barrier_group.dst_stages | command->next_stages; // Merge command's memory barrier bits with the barrier group. barrier_group.memory_barrier.src_access = barrier_group.memory_barrier.src_access | command->memory_barrier.src_access; @@ -925,11 +991,17 @@ void RenderingDeviceGraph::_group_barriers_for_render_commands(RDD::CommandBuffe for (int32_t j = 0; j < command->normalization_barrier_count; j++) { const RDD::TextureBarrier &recorded_barrier = command_normalization_barriers[command->normalization_barrier_index + j]; barrier_group.normalization_barriers.push_back(recorded_barrier); +#if PRINT_COMMAND_RECORDING + print_line(vformat("Normalization Barrier #%d", barrier_group.normalization_barriers.size() - 1)); +#endif } for (int32_t j = 0; j < command->transition_barrier_count; j++) { const RDD::TextureBarrier &recorded_barrier = command_transition_barriers[command->transition_barrier_index + j]; barrier_group.transition_barriers.push_back(recorded_barrier); +#if PRINT_COMMAND_RECORDING + print_line(vformat("Transition Barrier #%d", barrier_group.transition_barriers.size() - 1)); +#endif } #if USE_BUFFER_BARRIERS @@ -1202,7 +1274,8 @@ void RenderingDeviceGraph::begin() { command_label_colors.clear(); command_label_offsets.clear(); command_list_nodes.clear(); - write_list_nodes.clear(); + read_slice_list_nodes.clear(); + write_slice_list_nodes.clear(); command_count = 0; command_label_count = 0; command_timestamp_index = -1; @@ -1225,7 +1298,7 @@ void RenderingDeviceGraph::add_buffer_clear(RDD::BufferID p_dst, ResourceTracker int32_t command_index; RecordedBufferClearCommand *command = static_cast<RecordedBufferClearCommand *>(_allocate_command(sizeof(RecordedBufferClearCommand), command_index)); command->type = RecordedCommand::TYPE_BUFFER_CLEAR; - command->dst_stages = RDD::PIPELINE_STAGE_TRANSFER_BIT; + command->self_stages = RDD::PIPELINE_STAGE_TRANSFER_BIT; command->buffer = p_dst; command->offset = p_offset; command->size = p_size; @@ -1241,7 +1314,7 @@ void RenderingDeviceGraph::add_buffer_copy(RDD::BufferID p_src, ResourceTracker int32_t command_index; RecordedBufferCopyCommand *command = static_cast<RecordedBufferCopyCommand *>(_allocate_command(sizeof(RecordedBufferCopyCommand), command_index)); command->type = RecordedCommand::TYPE_BUFFER_COPY; - command->dst_stages = RDD::PIPELINE_STAGE_TRANSFER_BIT; + command->self_stages = RDD::PIPELINE_STAGE_TRANSFER_BIT; command->source = p_src; command->destination = p_dst; command->region = p_region; @@ -1256,7 +1329,7 @@ void RenderingDeviceGraph::add_buffer_get_data(RDD::BufferID p_src, ResourceTrac int32_t command_index; RecordedBufferGetDataCommand *command = static_cast<RecordedBufferGetDataCommand *>(_allocate_command(sizeof(RecordedBufferGetDataCommand), command_index)); command->type = RecordedCommand::TYPE_BUFFER_GET_DATA; - command->dst_stages = RDD::PIPELINE_STAGE_TRANSFER_BIT; + command->self_stages = RDD::PIPELINE_STAGE_TRANSFER_BIT; command->source = p_src; command->destination = p_dst; command->region = p_region; @@ -1277,7 +1350,7 @@ void RenderingDeviceGraph::add_buffer_update(RDD::BufferID p_dst, ResourceTracke int32_t command_index; RecordedBufferUpdateCommand *command = static_cast<RecordedBufferUpdateCommand *>(_allocate_command(command_size, command_index)); command->type = RecordedCommand::TYPE_BUFFER_UPDATE; - command->dst_stages = RDD::PIPELINE_STAGE_TRANSFER_BIT; + command->self_stages = RDD::PIPELINE_STAGE_TRANSFER_BIT; command->destination = p_dst; command->buffer_copies_count = p_buffer_copies.size(); @@ -1369,7 +1442,7 @@ void RenderingDeviceGraph::add_compute_list_end() { uint32_t command_size = sizeof(RecordedComputeListCommand) + instruction_data_size; RecordedComputeListCommand *command = static_cast<RecordedComputeListCommand *>(_allocate_command(command_size, command_index)); command->type = RecordedCommand::TYPE_COMPUTE_LIST; - command->dst_stages = compute_instruction_list.stages; + command->self_stages = compute_instruction_list.stages; command->instruction_data_size = instruction_data_size; memcpy(command->instruction_data(), compute_instruction_list.data.ptr(), instruction_data_size); _add_command_to_graph(compute_instruction_list.command_trackers.ptr(), compute_instruction_list.command_tracker_usages.ptr(), compute_instruction_list.command_trackers.size(), command_index, command); @@ -1579,7 +1652,7 @@ void RenderingDeviceGraph::add_draw_list_end() { uint32_t command_size = sizeof(RecordedDrawListCommand) + clear_values_size + instruction_data_size; RecordedDrawListCommand *command = static_cast<RecordedDrawListCommand *>(_allocate_command(command_size, command_index)); command->type = RecordedCommand::TYPE_DRAW_LIST; - command->dst_stages = draw_instruction_list.stages; + command->self_stages = draw_instruction_list.stages; command->instruction_data_size = instruction_data_size; command->render_pass = draw_instruction_list.render_pass; command->framebuffer = draw_instruction_list.framebuffer; @@ -1602,7 +1675,7 @@ void RenderingDeviceGraph::add_texture_clear(RDD::TextureID p_dst, ResourceTrack int32_t command_index; RecordedTextureClearCommand *command = static_cast<RecordedTextureClearCommand *>(_allocate_command(sizeof(RecordedTextureClearCommand), command_index)); command->type = RecordedCommand::TYPE_TEXTURE_CLEAR; - command->dst_stages = RDD::PIPELINE_STAGE_TRANSFER_BIT; + command->self_stages = RDD::PIPELINE_STAGE_TRANSFER_BIT; command->texture = p_dst; command->color = p_color; command->range = p_range; @@ -1618,7 +1691,7 @@ void RenderingDeviceGraph::add_texture_copy(RDD::TextureID p_src, ResourceTracke int32_t command_index; RecordedTextureCopyCommand *command = static_cast<RecordedTextureCopyCommand *>(_allocate_command(sizeof(RecordedTextureCopyCommand), command_index)); command->type = RecordedCommand::TYPE_TEXTURE_COPY; - command->dst_stages = RDD::PIPELINE_STAGE_TRANSFER_BIT; + command->self_stages = RDD::PIPELINE_STAGE_TRANSFER_BIT; command->from_texture = p_src; command->to_texture = p_dst; command->region = p_region; @@ -1635,7 +1708,7 @@ void RenderingDeviceGraph::add_texture_get_data(RDD::TextureID p_src, ResourceTr uint64_t command_size = sizeof(RecordedTextureGetDataCommand) + p_buffer_texture_copy_regions.size() * sizeof(RDD::BufferTextureCopyRegion); RecordedTextureGetDataCommand *command = static_cast<RecordedTextureGetDataCommand *>(_allocate_command(command_size, command_index)); command->type = RecordedCommand::TYPE_TEXTURE_GET_DATA; - command->dst_stages = RDD::PIPELINE_STAGE_TRANSFER_BIT; + command->self_stages = RDD::PIPELINE_STAGE_TRANSFER_BIT; command->from_texture = p_src; command->to_buffer = p_dst; command->buffer_texture_copy_regions_count = p_buffer_texture_copy_regions.size(); @@ -1656,7 +1729,7 @@ void RenderingDeviceGraph::add_texture_resolve(RDD::TextureID p_src, ResourceTra int32_t command_index; RecordedTextureResolveCommand *command = static_cast<RecordedTextureResolveCommand *>(_allocate_command(sizeof(RecordedTextureResolveCommand), command_index)); command->type = RecordedCommand::TYPE_TEXTURE_RESOLVE; - command->dst_stages = RDD::PIPELINE_STAGE_TRANSFER_BIT; + command->self_stages = RDD::PIPELINE_STAGE_TRANSFER_BIT; command->from_texture = p_src; command->to_texture = p_dst; command->src_layer = p_src_layer; @@ -1676,7 +1749,7 @@ void RenderingDeviceGraph::add_texture_update(RDD::TextureID p_dst, ResourceTrac uint64_t command_size = sizeof(RecordedTextureUpdateCommand) + p_buffer_copies.size() * sizeof(RecordedBufferToTextureCopy); RecordedTextureUpdateCommand *command = static_cast<RecordedTextureUpdateCommand *>(_allocate_command(command_size, command_index)); command->type = RecordedCommand::TYPE_TEXTURE_UPDATE; - command->dst_stages = RDD::PIPELINE_STAGE_TRANSFER_BIT; + command->self_stages = RDD::PIPELINE_STAGE_TRANSFER_BIT; command->to_texture = p_dst; command->buffer_to_texture_copies_count = p_buffer_copies.size(); @@ -1693,7 +1766,7 @@ void RenderingDeviceGraph::add_capture_timestamp(RDD::QueryPoolID p_query_pool, int32_t command_index; RecordedCaptureTimestampCommand *command = static_cast<RecordedCaptureTimestampCommand *>(_allocate_command(sizeof(RecordedCaptureTimestampCommand), command_index)); command->type = RecordedCommand::TYPE_CAPTURE_TIMESTAMP; - command->dst_stages = 0; + command->self_stages = 0; command->pool = p_query_pool; command->index = p_index; _add_command_to_graph(nullptr, nullptr, 0, command_index, command); @@ -1852,6 +1925,10 @@ void RenderingDeviceGraph::end(RDD::CommandBufferID p_command_buffer, bool p_reo _print_render_commands(commands_sorted.ptr(), command_count); #endif +#if PRINT_COMMAND_RECORDING + print_line(vformat("Recording %d commands", command_count)); +#endif + uint32_t boosted_priority = 0; uint32_t current_level = commands_sorted[0].level; uint32_t current_level_start = 0; @@ -1884,6 +1961,10 @@ void RenderingDeviceGraph::end(RDD::CommandBufferID p_command_buffer, bool p_reo } _run_label_command_change(p_command_buffer, -1, -1, true, false, nullptr, 0, current_label_index, current_label_level); + +#if PRINT_COMMAND_RECORDING + print_line(vformat("Recorded %d commands", command_count)); +#endif } // Advance the frame counter. It's not necessary to do this if no commands are recorded because that means no secondary command buffers were used. diff --git a/servers/rendering/rendering_device_graph.h b/servers/rendering/rendering_device_graph.h index 84fbe02095..0d48f0491b 100644 --- a/servers/rendering/rendering_device_graph.h +++ b/servers/rendering/rendering_device_graph.h @@ -112,8 +112,9 @@ public: int32_t buffer_barrier_count = 0; #endif int32_t label_index = -1; - BitField<RDD::PipelineStageBits> src_stages; - BitField<RDD::PipelineStageBits> dst_stages; + BitField<RDD::PipelineStageBits> previous_stages; + BitField<RDD::PipelineStageBits> next_stages; + BitField<RDD::PipelineStageBits> self_stages; }; struct RecordedBufferCopy { @@ -150,7 +151,8 @@ public: struct ResourceTracker { uint32_t reference_count = 0; int64_t command_frame = -1; - int32_t read_command_list_index = -1; + int32_t read_full_command_list_index = -1; + int32_t read_slice_command_list_index = -1; int32_t write_command_or_list_index = -1; int32_t draw_list_index = -1; int32_t compute_list_index = -1; @@ -171,7 +173,8 @@ public: if (new_command_frame != command_frame) { usage_access.clear(); command_frame = new_command_frame; - read_command_list_index = -1; + read_full_command_list_index = -1; + read_slice_command_list_index = -1; write_command_or_list_index = -1; draw_list_index = -1; compute_list_index = -1; @@ -237,7 +240,7 @@ private: int32_t next_list_index = -1; }; - struct RecordedWriteListNode { + struct RecordedSliceListNode { int32_t command_index = -1; int32_t next_list_index = -1; Rect2i subresources; @@ -572,7 +575,8 @@ private: uint32_t command_count = 0; uint32_t command_label_count = 0; LocalVector<RecordedCommandListNode> command_list_nodes; - LocalVector<RecordedWriteListNode> write_list_nodes; + LocalVector<RecordedSliceListNode> read_slice_list_nodes; + LocalVector<RecordedSliceListNode> write_slice_list_nodes; int32_t command_timestamp_index = -1; int32_t command_synchronization_index = -1; bool command_synchronization_pending = false; @@ -590,7 +594,8 @@ private: static RDD::BarrierAccessBits _usage_to_access_bits(ResourceUsage p_usage); int32_t _add_to_command_list(int32_t p_command_index, int32_t p_list_index); void _add_adjacent_command(int32_t p_previous_command_index, int32_t p_command_index, RecordedCommand *r_command); - int32_t _add_to_write_list(int32_t p_command_index, Rect2i suberesources, int32_t p_list_index); + int32_t _add_to_slice_read_list(int32_t p_command_index, Rect2i p_subresources, int32_t p_list_index); + int32_t _add_to_write_list(int32_t p_command_index, Rect2i p_subresources, int32_t p_list_index); RecordedCommand *_allocate_command(uint32_t p_command_size, int32_t &r_command_index); DrawListInstruction *_allocate_draw_list_instruction(uint32_t p_instruction_size); ComputeListInstruction *_allocate_compute_list_instruction(uint32_t p_instruction_size); diff --git a/servers/rendering/rendering_server_default.h b/servers/rendering/rendering_server_default.h index 9ad1d8b8dc..3bb6ba1c51 100644 --- a/servers/rendering/rendering_server_default.h +++ b/servers/rendering/rendering_server_default.h @@ -315,6 +315,9 @@ public: FUNC2(mesh_set_custom_aabb, RID, const AABB &) FUNC1RC(AABB, mesh_get_custom_aabb, RID) + FUNC2(mesh_set_path, RID, const String &) + FUNC1RC(String, mesh_get_path, RID) + FUNC2(mesh_set_shadow_mesh, RID, RID) FUNC1(mesh_clear, RID) diff --git a/servers/rendering/storage/mesh_storage.h b/servers/rendering/storage/mesh_storage.h index 76e46a696a..3c1b2b495d 100644 --- a/servers/rendering/storage/mesh_storage.h +++ b/servers/rendering/storage/mesh_storage.h @@ -67,9 +67,11 @@ public: virtual void mesh_set_custom_aabb(RID p_mesh, const AABB &p_aabb) = 0; virtual AABB mesh_get_custom_aabb(RID p_mesh) const = 0; - virtual AABB mesh_get_aabb(RID p_mesh, RID p_skeleton = RID()) = 0; + virtual void mesh_set_path(RID p_mesh, const String &p_path) = 0; + virtual String mesh_get_path(RID p_mesh) const = 0; + virtual void mesh_set_shadow_mesh(RID p_mesh, RID p_shadow_mesh) = 0; virtual void mesh_clear(RID p_mesh) = 0; diff --git a/servers/rendering_server.h b/servers/rendering_server.h index b395104629..d63283ddbe 100644 --- a/servers/rendering_server.h +++ b/servers/rendering_server.h @@ -385,6 +385,9 @@ public: virtual void mesh_set_custom_aabb(RID p_mesh, const AABB &p_aabb) = 0; virtual AABB mesh_get_custom_aabb(RID p_mesh) const = 0; + virtual void mesh_set_path(RID p_mesh, const String &p_path) = 0; + virtual String mesh_get_path(RID p_mesh) const = 0; + virtual void mesh_set_shadow_mesh(RID p_mesh, RID p_shadow_mesh) = 0; virtual void mesh_clear(RID p_mesh) = 0; diff --git a/servers/xr/xr_interface.h b/servers/xr/xr_interface.h index c76d0fbf68..d7bd212449 100644 --- a/servers/xr/xr_interface.h +++ b/servers/xr/xr_interface.h @@ -75,7 +75,7 @@ public: XR_PLAY_AREA_3DOF, /* Only support orientation tracking, no positional tracking, area will center around player */ XR_PLAY_AREA_SITTING, /* Player is in seated position, limited positional tracking, fixed guardian around player */ XR_PLAY_AREA_ROOMSCALE, /* Player is free to move around, full positional tracking */ - XR_PLAY_AREA_STAGE, /* Same as roomscale but origin point is fixed to the center of the physical space, XRServer.center_on_hmd disabled */ + XR_PLAY_AREA_STAGE, /* Same as roomscale but origin point is fixed to the center of the physical space */ }; enum EnvironmentBlendMode { diff --git a/servers/xr_server.cpp b/servers/xr_server.cpp index dae342a037..0bc8dbee18 100644 --- a/servers/xr_server.cpp +++ b/servers/xr_server.cpp @@ -129,12 +129,6 @@ void XRServer::center_on_hmd(RotationMode p_rotation_mode, bool p_keep_height) { return; } - if (primary_interface->get_play_area_mode() == XRInterface::XR_PLAY_AREA_STAGE) { - // center_on_hmd is not available in this mode - reference_frame = Transform3D(); - return; - } - // clear our current reference frame or we'll end up double adjusting it reference_frame = Transform3D(); diff --git a/tests/core/input/test_input_event_key.h b/tests/core/input/test_input_event_key.h index 3317941fad..80918542ce 100644 --- a/tests/core/input/test_input_event_key.h +++ b/tests/core/input/test_input_event_key.h @@ -85,6 +85,16 @@ TEST_CASE("[InputEventKey] Key correctly stores and retrieves unicode") { CHECK(key.get_unicode() != 'y'); } +TEST_CASE("[InputEventKey] Key correctly stores and retrieves location") { + InputEventKey key; + + CHECK(key.get_location() == KeyLocation::UNSPECIFIED); + + key.set_location(KeyLocation::LEFT); + CHECK(key.get_location() == KeyLocation::LEFT); + CHECK(key.get_location() != KeyLocation::RIGHT); +} + TEST_CASE("[InputEventKey] Key correctly stores and checks echo") { InputEventKey key; @@ -144,32 +154,36 @@ TEST_CASE("[InputEventKey] Key correctly converts itself to text") { TEST_CASE("[InputEventKey] Key correctly converts its state to a string representation") { InputEventKey none_key; - CHECK(none_key.to_string() == "InputEventKey: keycode=(Unset), mods=none, physical=false, pressed=false, echo=false"); + CHECK(none_key.to_string() == "InputEventKey: keycode=(Unset), mods=none, physical=false, location=unspecified, pressed=false, echo=false"); // Set physical key to Escape. none_key.set_physical_keycode(Key::ESCAPE); - CHECK(none_key.to_string() == "InputEventKey: keycode=4194305 (Escape), mods=none, physical=true, pressed=false, echo=false"); + CHECK(none_key.to_string() == "InputEventKey: keycode=4194305 (Escape), mods=none, physical=true, location=unspecified, pressed=false, echo=false"); InputEventKey key; // Set physical to None, set keycode to Space. key.set_keycode(Key::SPACE); - CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=none, physical=false, pressed=false, echo=false"); + CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=none, physical=false, location=unspecified, pressed=false, echo=false"); + + // Set location + key.set_location(KeyLocation::RIGHT); + CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=none, physical=false, location=right, pressed=false, echo=false"); // Set pressed to true. key.set_pressed(true); - CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=none, physical=false, pressed=true, echo=false"); + CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=none, physical=false, location=right, pressed=true, echo=false"); // set echo to true. key.set_echo(true); - CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=none, physical=false, pressed=true, echo=true"); + CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=none, physical=false, location=right, pressed=true, echo=true"); // Press Ctrl and Alt. key.set_ctrl_pressed(true); key.set_alt_pressed(true); #ifdef MACOS_ENABLED - CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=Ctrl+Option, physical=false, pressed=true, echo=true"); + CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=Ctrl+Option, physical=false, location=right, pressed=true, echo=true"); #else - CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=Ctrl+Alt, physical=false, pressed=true, echo=true"); + CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=Ctrl+Alt, physical=false, location=right, pressed=true, echo=true"); #endif } @@ -291,6 +305,34 @@ TEST_CASE("[IsMatch] Keys are correctly matched") { CHECK(key2.is_match(match, true) == true); CHECK(key2.is_match(no_match, true) == false); + + // Physical key with location. + InputEventKey key3; + key3.set_keycode(Key::NONE); + key3.set_physical_keycode(Key::SHIFT); + + Ref<InputEventKey> loc_ref = key.create_reference(Key::NONE); + + loc_ref->set_keycode(Key::SHIFT); + loc_ref->set_physical_keycode(Key::SHIFT); + + CHECK(key3.is_match(loc_ref, false) == true); + key3.set_location(KeyLocation::UNSPECIFIED); + CHECK(key3.is_match(loc_ref, false) == true); + + loc_ref->set_location(KeyLocation::LEFT); + CHECK(key3.is_match(loc_ref, false) == true); + + key3.set_location(KeyLocation::LEFT); + CHECK(key3.is_match(loc_ref, false) == true); + + key3.set_location(KeyLocation::RIGHT); + CHECK(key3.is_match(loc_ref, false) == false); + + // Keycode key with location. + key3.set_physical_keycode(Key::NONE); + key3.set_keycode(Key::SHIFT); + CHECK(key3.is_match(loc_ref, false) == true); } } // namespace TestInputEventKey diff --git a/thirdparty/README.md b/thirdparty/README.md index 666e53375c..c3f0e25b9a 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -87,7 +87,7 @@ Files extracted from upstream source: ## certs - Upstream: Mozilla, via https://github.com/bagder/ca-bundle -- Version: git (3aaca635bad074a0ce5c15fa8aa0dff47f5c639a, 2023) +- Version: git (bef37a977ccb45fb4c1b213b79dd6ba438077561, 2023) - License: MPL 2.0 @@ -225,6 +225,9 @@ Files extracted from upstream source: ``` - `AUTHORS.txt` and `LICENSE.txt` +Two files (`ProcessRGB.{cpp,hpp}`) have been modified to provide ETC2_R and ETC2_RG compression, +the changes are based on the existing code. + Two files (`ProcessRgtc.{cpp,hpp}`) have been added to provide RGTC compression implementation, based on library's `ProcessDxtc.{cpp,hpp}`. @@ -605,7 +608,7 @@ that file when upgrading. ## minizip - Upstream: https://www.zlib.net -- Version: 1.3 (zlib contrib, 2023) +- Version: 1.3.1 (zlib contrib, 2024) - License: zlib Files extracted from the upstream source: @@ -618,8 +621,6 @@ Important: Some files have Godot-made changes for use in core/io. They are marked with `/* GODOT start */` and `/* GODOT end */` comments and a patch is provided in the `patches` folder. -Another patch is included to fix CVE-2023-45853. - ## misc @@ -962,7 +963,7 @@ Files extracted from upstream source: ## zlib - Upstream: https://www.zlib.net -- Version: 1.3 (2023) +- Version: 1.3.1 (2024) - License: zlib Files extracted from upstream source: diff --git a/thirdparty/certs/ca-certificates.crt b/thirdparty/certs/ca-certificates.crt index f08a6766c6..df7a026bed 100644 --- a/thirdparty/certs/ca-certificates.crt +++ b/thirdparty/certs/ca-certificates.crt @@ -1,7 +1,7 @@ ## ## Bundle of CA Root Certificates ## -## Certificate data from Mozilla as of: Fri Jun 2 21:47:52 2023 GMT +## Certificate data from Mozilla as of: Wed Dec 13 07:16:08 2023 GMT ## ## This is a bundle of X.509 certificates of public Certificate Authorities ## (CA). These were automatically extracted from Mozilla's root certificates @@ -14,7 +14,7 @@ ## Just configure this file as the SSLCACertificateFile. ## ## Conversion done with mk-ca-bundle.pl version 1.29. -## SHA256: c47475103fb05bb562bbadff0d1e72346b03236154e1448a6ca191b740f83507 +## SHA256: 1970dd65858925d68498d2356aea6d03f764422523c5887deca8ce3ba9e1f845 ## @@ -200,27 +200,6 @@ vGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeTmJlglFwjz1onl14LBQaTNx47aTbr qZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK4SVhM7JZG+Ju1zdXtg2pEto= -----END CERTIFICATE----- -Security Communication Root CA -============================== ------BEGIN CERTIFICATE----- -MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP -U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw -HhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP -U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw -ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw -8yl89f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJDKaVv0uM -DPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9Ms+k2Y7CI9eNqPPYJayX -5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/NQV3Is00qVUarH9oe4kA92819uZKAnDfd -DJZkndwi92SL32HeFZRSFaB9UslLqCHJxrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2 -JChzAgMBAAGjPzA9MB0GA1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYw -DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vGkl3g -0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfrUj94nK9NrvjVT8+a -mCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5Bw+SUEmK3TGXX8npN6o7WWWXlDLJ -s58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJUJRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ -6rBK+1YWc26sTfcioU+tHXotRSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAi -FL39vmwLAw== ------END CERTIFICATE----- - XRamp Global CA Root ==================== -----BEGIN CERTIFICATE----- @@ -669,39 +648,6 @@ YIvDQVETI53O9zJrlAGomecsMx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7r kpeDMdmztcpHWD9f -----END CERTIFICATE----- -Autoridad de Certificacion Firmaprofesional CIF A62634068 -========================================================= ------BEGIN CERTIFICATE----- -MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UEBhMCRVMxQjBA -BgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2 -MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEyMzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIw -QAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBB -NjI2MzQwNjgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDD -Utd9thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQMcas9UX4P -B99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefGL9ItWY16Ck6WaVICqjaY -7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15iNA9wBj4gGFrO93IbJWyTdBSTo3OxDqqH -ECNZXyAFGUftaI6SEspd/NYrspI8IM/hX68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyI -plD9amML9ZMWGxmPsu2bm8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctX -MbScyJCyZ/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirjaEbsX -LZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/TKI8xWVvTyQKmtFLK -bpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF6NkBiDkal4ZkQdU7hwxu+g/GvUgU -vzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVhOSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMBIGA1Ud -EwEB/wQIMAYBAf8CAQEwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNH -DhpkLzCBpgYDVR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp -cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBvACAAZABlACAA -bABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBlAGwAbwBuAGEAIAAwADgAMAAx -ADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF661LJLWhAqvdpYhKsg9VSytXjDvlMd3+xDLx -51tkljYyGOylMnfX40S2wBEqgLk9am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qk -R71kMrv2JYSiJ0L1ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaP -T481PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS3a/DTg4f -Jl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5kSeTy36LssUzAKh3ntLFl -osS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF3dvd6qJ2gHN99ZwExEWN57kci57q13XR -crHedUTnQn3iV2t93Jm8PYMo6oCTjcVMZcFwgbg4/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoR -saS8I8nkvof/uZS2+F0gStRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTD -KCOM/iczQ0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQBjLMi -6Et8Vcad+qMUu2WFbm5PEn4KPJ2V ------END CERTIFICATE----- - Izenpe.com ========== -----BEGIN CERTIFICATE----- @@ -3222,55 +3168,6 @@ AwMDaAAwZQIxALGOWiDDshliTd6wT99u0nCK8Z9+aozmut6Dacpps6kFtZaSF4fC0urQe87YQVt8 rgIwRt7qy12a7DLCZRawTDBcMPPaTnOGBtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR -----END CERTIFICATE----- -E-Tugra Global Root CA RSA v3 -============================= ------BEGIN CERTIFICATE----- -MIIF8zCCA9ugAwIBAgIUDU3FzRYilZYIfrgLfxUGNPt5EDQwDQYJKoZIhvcNAQELBQAwgYAxCzAJ -BgNVBAYTAlRSMQ8wDQYDVQQHEwZBbmthcmExGTAXBgNVBAoTEEUtVHVncmEgRUJHIEEuUy4xHTAb -BgNVBAsTFEUtVHVncmEgVHJ1c3QgQ2VudGVyMSYwJAYDVQQDEx1FLVR1Z3JhIEdsb2JhbCBSb290 -IENBIFJTQSB2MzAeFw0yMDAzMTgwOTA3MTdaFw00NTAzMTIwOTA3MTdaMIGAMQswCQYDVQQGEwJU -UjEPMA0GA1UEBxMGQW5rYXJhMRkwFwYDVQQKExBFLVR1Z3JhIEVCRyBBLlMuMR0wGwYDVQQLExRF -LVR1Z3JhIFRydXN0IENlbnRlcjEmMCQGA1UEAxMdRS1UdWdyYSBHbG9iYWwgUm9vdCBDQSBSU0Eg -djMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCiZvCJt3J77gnJY9LTQ91ew6aEOErx -jYG7FL1H6EAX8z3DeEVypi6Q3po61CBxyryfHUuXCscxuj7X/iWpKo429NEvx7epXTPcMHD4QGxL -sqYxYdE0PD0xesevxKenhOGXpOhL9hd87jwH7eKKV9y2+/hDJVDqJ4GohryPUkqWOmAalrv9c/SF -/YP9f4RtNGx/ardLAQO/rWm31zLZ9Vdq6YaCPqVmMbMWPcLzJmAy01IesGykNz709a/r4d+ABs8q -QedmCeFLl+d3vSFtKbZnwy1+7dZ5ZdHPOrbRsV5WYVB6Ws5OUDGAA5hH5+QYfERaxqSzO8bGwzrw -bMOLyKSRBfP12baqBqG3q+Sx6iEUXIOk/P+2UNOMEiaZdnDpwA+mdPy70Bt4znKS4iicvObpCdg6 -04nmvi533wEKb5b25Y08TVJ2Glbhc34XrD2tbKNSEhhw5oBOM/J+JjKsBY04pOZ2PJ8QaQ5tndLB -eSBrW88zjdGUdjXnXVXHt6woq0bM5zshtQoK5EpZ3IE1S0SVEgpnpaH/WwAH0sDM+T/8nzPyAPiM -bIedBi3x7+PmBvrFZhNb/FAHnnGGstpvdDDPk1Po3CLW3iAfYY2jLqN4MpBs3KwytQXk9TwzDdbg -h3cXTJ2w2AmoDVf3RIXwyAS+XF1a4xeOVGNpf0l0ZAWMowIDAQABo2MwYTAPBgNVHRMBAf8EBTAD -AQH/MB8GA1UdIwQYMBaAFLK0ruYt9ybVqnUtdkvAG1Mh0EjvMB0GA1UdDgQWBBSytK7mLfcm1ap1 -LXZLwBtTIdBI7zAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggIBAImocn+M684uGMQQ -gC0QDP/7FM0E4BQ8Tpr7nym/Ip5XuYJzEmMmtcyQ6dIqKe6cLcwsmb5FJ+Sxce3kOJUxQfJ9emN4 -38o2Fi+CiJ+8EUdPdk3ILY7r3y18Tjvarvbj2l0Upq7ohUSdBm6O++96SmotKygY/r+QLHUWnw/q -ln0F7psTpURs+APQ3SPh/QMSEgj0GDSz4DcLdxEBSL9htLX4GdnLTeqjjO/98Aa1bZL0SmFQhO3s -SdPkvmjmLuMxC1QLGpLWgti2omU8ZgT5Vdps+9u1FGZNlIM7zR6mK7L+d0CGq+ffCsn99t2HVhjY -sCxVYJb6CH5SkPVLpi6HfMsg2wY+oF0Dd32iPBMbKaITVaA9FCKvb7jQmhty3QUBjYZgv6Rn7rWl -DdF/5horYmbDB7rnoEgcOMPpRfunf/ztAmgayncSd6YAVSgU7NbHEqIbZULpkejLPoeJVF3Zr52X -nGnnCv8PWniLYypMfUeUP95L6VPQMPHF9p5J3zugkaOj/s1YzOrfr28oO6Bpm4/srK4rVJ2bBLFH -IK+WEj5jlB0E5y67hscMmoi/dkfv97ALl2bSRM9gUgfh1SxKOidhd8rXj+eHDjD/DLsE4mHDosiX -YY60MGo8bcIHX0pzLz/5FooBZu+6kcpSV3uu1OYP3Qt6f4ueJiDPO++BcYNZ ------END CERTIFICATE----- - -E-Tugra Global Root CA ECC v3 -============================= ------BEGIN CERTIFICATE----- -MIICpTCCAiqgAwIBAgIUJkYZdzHhT28oNt45UYbm1JeIIsEwCgYIKoZIzj0EAwMwgYAxCzAJBgNV -BAYTAlRSMQ8wDQYDVQQHEwZBbmthcmExGTAXBgNVBAoTEEUtVHVncmEgRUJHIEEuUy4xHTAbBgNV -BAsTFEUtVHVncmEgVHJ1c3QgQ2VudGVyMSYwJAYDVQQDEx1FLVR1Z3JhIEdsb2JhbCBSb290IENB -IEVDQyB2MzAeFw0yMDAzMTgwOTQ2NThaFw00NTAzMTIwOTQ2NThaMIGAMQswCQYDVQQGEwJUUjEP -MA0GA1UEBxMGQW5rYXJhMRkwFwYDVQQKExBFLVR1Z3JhIEVCRyBBLlMuMR0wGwYDVQQLExRFLVR1 -Z3JhIFRydXN0IENlbnRlcjEmMCQGA1UEAxMdRS1UdWdyYSBHbG9iYWwgUm9vdCBDQSBFQ0MgdjMw -djAQBgcqhkjOPQIBBgUrgQQAIgNiAASOmCm/xxAeJ9urA8woLNheSBkQKczLWYHMjLiSF4mDKpL2 -w6QdTGLVn9agRtwcvHbB40fQWxPa56WzZkjnIZpKT4YKfWzqTTKACrJ6CZtpS5iB4i7sAnCWH/31 -Rs7K3IKjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU/4Ixcj75xGZsrTie0bBRiKWQ -zPUwHQYDVR0OBBYEFP+CMXI++cRmbK04ntGwUYilkMz1MA4GA1UdDwEB/wQEAwIBBjAKBggqhkjO -PQQDAwNpADBmAjEA5gVYaWHlLcoNy/EZCL3W/VGSGn5jVASQkZo1kTmZ+gepZpO6yGjUij/67W4W -Aie3AjEA3VoXK3YdZUKWpqxdinlW2Iob35reX8dQj7FbcQwm32pAAOwzkSFxvmjkI6TZraE3 ------END CERTIFICATE----- - Security Communication RootCA3 ============================== -----BEGIN CERTIFICATE----- @@ -3361,3 +3258,277 @@ SR9BIgmwUVJY1is0j8USRhTFiy8shP8sbqjV8QnjAyEUxEM9fMEsxEtqSs3ph+B99iK++kpRuDCK W9f+qdJUDkpd0m2xQNz0Q9XSSpkZElaA94M04TVOSG0ED1cxMDAtsaqdAzjbBgIxAMvMh1PLet8g UXOQwKhbYdDFUDn9hf7B43j4ptZLvZuHjw/l1lOWqzzIQNph91Oj9w== -----END CERTIFICATE----- + +Sectigo Public Server Authentication Root E46 +============================================= +-----BEGIN CERTIFICATE----- +MIICOjCCAcGgAwIBAgIQQvLM2htpN0RfFf51KBC49DAKBggqhkjOPQQDAzBfMQswCQYDVQQGEwJH +QjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBTZXJ2 +ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwHhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5 +WjBfMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0 +aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUr +gQQAIgNiAAR2+pmpbiDt+dd34wc7qNs9Xzjoq1WmVk/WSOrsfy2qw7LFeeyZYX8QeccCWvkEN/U0 +NSt3zn8gj1KjAIns1aeibVvjS5KToID1AZTc8GgHHs3u/iVStSBDHBv+6xnOQ6OjQjBAMB0GA1Ud +DgQWBBTRItpMWfFLXyY4qp3W7usNw/upYTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB +/zAKBggqhkjOPQQDAwNnADBkAjAn7qRaqCG76UeXlImldCBteU/IvZNeWBj7LRoAasm4PdCkT0RH +lAFWovgzJQxC36oCMB3q4S6ILuH5px0CMk7yn2xVdOOurvulGu7t0vzCAxHrRVxgED1cf5kDW21U +SAGKcw== +-----END CERTIFICATE----- + +Sectigo Public Server Authentication Root R46 +============================================= +-----BEGIN CERTIFICATE----- +MIIFijCCA3KgAwIBAgIQdY39i658BwD6qSWn4cetFDANBgkqhkiG9w0BAQwFADBfMQswCQYDVQQG +EwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBT +ZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwHhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1 +OTU5WjBfMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1T +ZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwggIiMA0GCSqGSIb3 +DQEBAQUAA4ICDwAwggIKAoICAQCTvtU2UnXYASOgHEdCSe5jtrch/cSV1UgrJnwUUxDaef0rty2k +1Cz66jLdScK5vQ9IPXtamFSvnl0xdE8H/FAh3aTPaE8bEmNtJZlMKpnzSDBh+oF8HqcIStw+Kxwf +GExxqjWMrfhu6DtK2eWUAtaJhBOqbchPM8xQljeSM9xfiOefVNlI8JhD1mb9nxc4Q8UBUQvX4yMP +FF1bFOdLvt30yNoDN9HWOaEhUTCDsG3XME6WW5HwcCSrv0WBZEMNvSE6Lzzpng3LILVCJ8zab5vu +ZDCQOc2TZYEhMbUjUDM3IuM47fgxMMxF/mL50V0yeUKH32rMVhlATc6qu/m1dkmU8Sf4kaWD5Qaz +Yw6A3OASVYCmO2a0OYctyPDQ0RTp5A1NDvZdV3LFOxxHVp3i1fuBYYzMTYCQNFu31xR13NgESJ/A +wSiItOkcyqex8Va3e0lMWeUgFaiEAin6OJRpmkkGj80feRQXEgyDet4fsZfu+Zd4KKTIRJLpfSYF +plhym3kT2BFfrsU4YjRosoYwjviQYZ4ybPUHNs2iTG7sijbt8uaZFURww3y8nDnAtOFr94MlI1fZ +EoDlSfB1D++N6xybVCi0ITz8fAr/73trdf+LHaAZBav6+CuBQug4urv7qv094PPK306Xlynt8xhW +6aWWrL3DkJiy4Pmi1KZHQ3xtzwIDAQABo0IwQDAdBgNVHQ4EFgQUVnNYZJX5khqwEioEYnmhQBWI +IUkwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAC9c +mTz8Bl6MlC5w6tIyMY208FHVvArzZJ8HXtXBc2hkeqK5Duj5XYUtqDdFqij0lgVQYKlJfp/imTYp +E0RHap1VIDzYm/EDMrraQKFz6oOht0SmDpkBm+S8f74TlH7Kph52gDY9hAaLMyZlbcp+nv4fjFg4 +exqDsQ+8FxG75gbMY/qB8oFM2gsQa6H61SilzwZAFv97fRheORKkU55+MkIQpiGRqRxOF3yEvJ+M +0ejf5lG5Nkc/kLnHvALcWxxPDkjBJYOcCj+esQMzEhonrPcibCTRAUH4WAP+JWgiH5paPHxsnnVI +84HxZmduTILA7rpXDhjvLpr3Etiga+kFpaHpaPi8TD8SHkXoUsCjvxInebnMMTzD9joiFgOgyY9m +pFuiTdaBJQbpdqQACj7LzTWb4OE4y2BThihCQRxEV+ioratF4yUQvNs+ZUH7G6aXD+u5dHn5Hrwd +Vw1Hr8Mvn4dGp+smWg9WY7ViYG4A++MnESLn/pmPNPW56MORcr3Ywx65LvKRRFHQV80MNNVIIb/b +E/FmJUNS0nAiNs2fxBx1IK1jcmMGDw4nztJqDby1ORrp0XZ60Vzk50lJLVU3aPAaOpg+VBeHVOmm +J1CJeyAvP/+/oYtKR5j/K3tJPsMpRmAYQqszKbrAKbkTidOIijlBO8n9pu0f9GBj39ItVQGL +-----END CERTIFICATE----- + +SSL.com TLS RSA Root CA 2022 +============================ +-----BEGIN CERTIFICATE----- +MIIFiTCCA3GgAwIBAgIQb77arXO9CEDii02+1PdbkTANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQG +EwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxTU0wuY29tIFRMUyBSU0Eg +Um9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzQyMloXDTQ2MDgxOTE2MzQyMVowTjELMAkGA1UEBhMC +VVMxGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgUlNBIFJv +b3QgQ0EgMjAyMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANCkCXJPQIgSYT41I57u +9nTPL3tYPc48DRAokC+X94xI2KDYJbFMsBFMF3NQ0CJKY7uB0ylu1bUJPiYYf7ISf5OYt6/wNr/y +7hienDtSxUcZXXTzZGbVXcdotL8bHAajvI9AI7YexoS9UcQbOcGV0insS657Lb85/bRi3pZ7Qcac +oOAGcvvwB5cJOYF0r/c0WRFXCsJbwST0MXMwgsadugL3PnxEX4MN8/HdIGkWCVDi1FW24IBydm5M +R7d1VVm0U3TZlMZBrViKMWYPHqIbKUBOL9975hYsLfy/7PO0+r4Y9ptJ1O4Fbtk085zx7AGL0SDG +D6C1vBdOSHtRwvzpXGk3R2azaPgVKPC506QVzFpPulJwoxJF3ca6TvvC0PeoUidtbnm1jPx7jMEW +TO6Af77wdr5BUxIzrlo4QqvXDz5BjXYHMtWrifZOZ9mxQnUjbvPNQrL8VfVThxc7wDNY8VLS+YCk +8OjwO4s4zKTGkH8PnP2L0aPP2oOnaclQNtVcBdIKQXTbYxE3waWglksejBYSd66UNHsef8JmAOSq +g+qKkK3ONkRN0VHpvB/zagX9wHQfJRlAUW7qglFA35u5CCoGAtUjHBPW6dvbxrB6y3snm/vg1UYk +7RBLY0ulBY+6uB0rpvqR4pJSvezrZ5dtmi2fgTIFZzL7SAg/2SW4BCUvAgMBAAGjYzBhMA8GA1Ud +EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU+y437uOEeicuzRk1sTN8/9REQrkwHQYDVR0OBBYEFPsu +N+7jhHonLs0ZNbEzfP/UREK5MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAjYlt +hEUY8U+zoO9opMAdrDC8Z2awms22qyIZZtM7QbUQnRC6cm4pJCAcAZli05bg4vsMQtfhWsSWTVTN +j8pDU/0quOr4ZcoBwq1gaAafORpR2eCNJvkLTqVTJXojpBzOCBvfR4iyrT7gJ4eLSYwfqUdYe5by +iB0YrrPRpgqU+tvT5TgKa3kSM/tKWTcWQA673vWJDPFs0/dRa1419dvAJuoSc06pkZCmF8NsLzjU +o3KUQyxi4U5cMj29TH0ZR6LDSeeWP4+a0zvkEdiLA9z2tmBVGKaBUfPhqBVq6+AL8BQx1rmMRTqo +ENjwuSfr98t67wVylrXEj5ZzxOhWc5y8aVFjvO9nHEMaX3cZHxj4HCUp+UmZKbaSPaKDN7Egkaib +MOlqbLQjk2UEqxHzDh1TJElTHaE/nUiSEeJ9DU/1172iWD54nR4fK/4huxoTtrEoZP2wAgDHbICi +vRZQIA9ygV/MlP+7mea6kMvq+cYMwq7FGc4zoWtcu358NFcXrfA/rs3qr5nsLFR+jM4uElZI7xc7 +P0peYNLcdDa8pUNjyw9bowJWCZ4kLOGGgYz+qxcs+sjiMho6/4UIyYOf8kpIEFR3N+2ivEC+5BB0 +9+Rbu7nzifmPQdjH5FCQNYA+HLhNkNPU98OwoX6EyneSMSy4kLGCenROmxMmtNVQZlR4rmA= +-----END CERTIFICATE----- + +SSL.com TLS ECC Root CA 2022 +============================ +-----BEGIN CERTIFICATE----- +MIICOjCCAcCgAwIBAgIQFAP1q/s3ixdAW+JDsqXRxDAKBggqhkjOPQQDAzBOMQswCQYDVQQGEwJV +UzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxTU0wuY29tIFRMUyBFQ0MgUm9v +dCBDQSAyMDIyMB4XDTIyMDgyNTE2MzM0OFoXDTQ2MDgxOTE2MzM0N1owTjELMAkGA1UEBhMCVVMx +GDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgRUNDIFJvb3Qg +Q0EgMjAyMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABEUpNXP6wrgjzhR9qLFNoFs27iosU8NgCTWy +JGYmacCzldZdkkAZDsalE3D07xJRKF3nzL35PIXBz5SQySvOkkJYWWf9lCcQZIxPBLFNSeR7T5v1 +5wj4A4j3p8OSSxlUgaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBSJjy+j6CugFFR7 +81a4Jl9nOAuc0DAdBgNVHQ4EFgQUiY8vo+groBRUe/NWuCZfZzgLnNAwDgYDVR0PAQH/BAQDAgGG +MAoGCCqGSM49BAMDA2gAMGUCMFXjIlbp15IkWE8elDIPDAI2wv2sdDJO4fscgIijzPvX6yv/N33w +7deedWo1dlJF4AIxAMeNb0Igj762TVntd00pxCAgRWSGOlDGxK0tk/UYfXLtqc/ErFc2KAhl3zx5 +Zn6g6g== +-----END CERTIFICATE----- + +Atos TrustedRoot Root CA ECC TLS 2021 +===================================== +-----BEGIN CERTIFICATE----- +MIICFTCCAZugAwIBAgIQPZg7pmY9kGP3fiZXOATvADAKBggqhkjOPQQDAzBMMS4wLAYDVQQDDCVB +dG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgRUNDIFRMUyAyMDIxMQ0wCwYDVQQKDARBdG9zMQswCQYD +VQQGEwJERTAeFw0yMTA0MjIwOTI2MjNaFw00MTA0MTcwOTI2MjJaMEwxLjAsBgNVBAMMJUF0b3Mg +VHJ1c3RlZFJvb3QgUm9vdCBDQSBFQ0MgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYT +AkRFMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEloZYKDcKZ9Cg3iQZGeHkBQcfl+3oZIK59sRxUM6K +DP/XtXa7oWyTbIOiaG6l2b4siJVBzV3dscqDY4PMwL502eCdpO5KTlbgmClBk1IQ1SQ4AjJn8ZQS +b+/Xxd4u/RmAo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR2KCXWfeBmmnoJsmo7jjPX +NtNPojAOBgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMDaAAwZQIwW5kp85wxtolrbNa9d+F851F+ +uDrNozZffPc8dz7kUK2o59JZDCaOMDtuCCrCp1rIAjEAmeMM56PDr9NJLkaCI2ZdyQAUEv049OGY +a3cpetskz2VAv9LcjBHo9H1/IISpQuQo +-----END CERTIFICATE----- + +Atos TrustedRoot Root CA RSA TLS 2021 +===================================== +-----BEGIN CERTIFICATE----- +MIIFZDCCA0ygAwIBAgIQU9XP5hmTC/srBRLYwiqipDANBgkqhkiG9w0BAQwFADBMMS4wLAYDVQQD +DCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgUlNBIFRMUyAyMDIxMQ0wCwYDVQQKDARBdG9zMQsw +CQYDVQQGEwJERTAeFw0yMTA0MjIwOTIxMTBaFw00MTA0MTcwOTIxMDlaMEwxLjAsBgNVBAMMJUF0 +b3MgVHJ1c3RlZFJvb3QgUm9vdCBDQSBSU0EgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNV +BAYTAkRFMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtoAOxHm9BYx9sKOdTSJNy/BB +l01Z4NH+VoyX8te9j2y3I49f1cTYQcvyAh5x5en2XssIKl4w8i1mx4QbZFc4nXUtVsYvYe+W/CBG +vevUez8/fEc4BKkbqlLfEzfTFRVOvV98r61jx3ncCHvVoOX3W3WsgFWZkmGbzSoXfduP9LVq6hdK +ZChmFSlsAvFr1bqjM9xaZ6cF4r9lthawEO3NUDPJcFDsGY6wx/J0W2tExn2WuZgIWWbeKQGb9Cpt +0xU6kGpn8bRrZtkh68rZYnxGEFzedUlnnkL5/nWpo63/dgpnQOPF943HhZpZnmKaau1Fh5hnstVK +PNe0OwANwI8f4UDErmwh3El+fsqyjW22v5MvoVw+j8rtgI5Y4dtXz4U2OLJxpAmMkokIiEjxQGMY +sluMWuPD0xeqqxmjLBvk1cbiZnrXghmmOxYsL3GHX0WelXOTwkKBIROW1527k2gV+p2kHYzygeBY +Br3JtuP2iV2J+axEoctr+hbxx1A9JNr3w+SH1VbxT5Aw+kUJWdo0zuATHAR8ANSbhqRAvNncTFd+ +rrcztl524WWLZt+NyteYr842mIycg5kDcPOvdO3GDjbnvezBc6eUWsuSZIKmAMFwoW4sKeFYV+xa +fJlrJaSQOoD0IJ2azsct+bJLKZWD6TWNp0lIpw9MGZHQ9b8Q4HECAwEAAaNCMEAwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUdEmZ0f+0emhFdcN+tNzMzjkz2ggwDgYDVR0PAQH/BAQDAgGGMA0G +CSqGSIb3DQEBDAUAA4ICAQAjQ1MkYlxt/T7Cz1UAbMVWiLkO3TriJQ2VSpfKgInuKs1l+NsW4AmS +4BjHeJi78+xCUvuppILXTdiK/ORO/auQxDh1MoSf/7OwKwIzNsAQkG8dnK/haZPso0UvFJ/1TCpl +Q3IM98P4lYsU84UgYt1UU90s3BiVaU+DR3BAM1h3Egyi61IxHkzJqM7F78PRreBrAwA0JrRUITWX +AdxfG/F851X6LWh3e9NpzNMOa7pNdkTWwhWaJuywxfW70Xp0wmzNxbVe9kzmWy2B27O3Opee7c9G +slA9hGCZcbUztVdF5kJHdWoOsAgMrr3e97sPWD2PAzHoPYJQyi9eDF20l74gNAf0xBLh7tew2Vkt +afcxBPTy+av5EzH4AXcOPUIjJsyacmdRIXrMPIWo6iFqO9taPKU0nprALN+AnCng33eU0aKAQv9q +TFsR0PXNor6uzFFcw9VUewyu1rkGd4Di7wcaaMxZUa1+XGdrudviB0JbuAEFWDlN5LuYo7Ey7Nmj +1m+UI/87tyll5gfp77YZ6ufCOB0yiJA8EytuzO+rdwY0d4RPcuSBhPm5dDTedk+SKlOxJTnbPP/l +PqYO5Wue/9vsL3SD3460s6neFE3/MaNFcyT6lSnMEpcEoji2jbDwN/zIIX8/syQbPYtuzE2wFg2W +HYMfRsCbvUOZ58SWLs5fyQ== +-----END CERTIFICATE----- + +TrustAsia Global Root CA G3 +=========================== +-----BEGIN CERTIFICATE----- +MIIFpTCCA42gAwIBAgIUZPYOZXdhaqs7tOqFhLuxibhxkw8wDQYJKoZIhvcNAQEMBQAwWjELMAkG +A1UEBhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dpZXMsIEluYy4xJDAiBgNVBAMM +G1RydXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHMzAeFw0yMTA1MjAwMjEwMTlaFw00NjA1MTkwMjEw +MTlaMFoxCzAJBgNVBAYTAkNOMSUwIwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMu +MSQwIgYDVQQDDBtUcnVzdEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzMwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQDAMYJhkuSUGwoqZdC+BqmHO1ES6nBBruL7dOoKjbmzTNyPtxNST1QY4Sxz +lZHFZjtqz6xjbYdT8PfxObegQ2OwxANdV6nnRM7EoYNl9lA+sX4WuDqKAtCWHwDNBSHvBm3dIZwZ +Q0WhxeiAysKtQGIXBsaqvPPW5vxQfmZCHzyLpnl5hkA1nyDvP+uLRx+PjsXUjrYsyUQE49RDdT/V +P68czH5GX6zfZBCK70bwkPAPLfSIC7Epqq+FqklYqL9joDiR5rPmd2jE+SoZhLsO4fWvieylL1Ag +dB4SQXMeJNnKziyhWTXAyB1GJ2Faj/lN03J5Zh6fFZAhLf3ti1ZwA0pJPn9pMRJpxx5cynoTi+jm +9WAPzJMshH/x/Gr8m0ed262IPfN2dTPXS6TIi/n1Q1hPy8gDVI+lhXgEGvNz8teHHUGf59gXzhqc +D0r83ERoVGjiQTz+LISGNzzNPy+i2+f3VANfWdP3kXjHi3dqFuVJhZBFcnAvkV34PmVACxmZySYg +WmjBNb9Pp1Hx2BErW+Canig7CjoKH8GB5S7wprlppYiU5msTf9FkPz2ccEblooV7WIQn3MSAPmea +mseaMQ4w7OYXQJXZRe0Blqq/DPNL0WP3E1jAuPP6Z92bfW1K/zJMtSU7/xxnD4UiWQWRkUF3gdCF +TIcQcf+eQxuulXUtgQIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFEDk5PIj +7zjKsK5Xf/IhMBY027ySMB0GA1UdDgQWBBRA5OTyI+84yrCuV3/yITAWNNu8kjAOBgNVHQ8BAf8E +BAMCAQYwDQYJKoZIhvcNAQEMBQADggIBACY7UeFNOPMyGLS0XuFlXsSUT9SnYaP4wM8zAQLpw6o1 +D/GUE3d3NZ4tVlFEbuHGLige/9rsR82XRBf34EzC4Xx8MnpmyFq2XFNFV1pF1AWZLy4jVe5jaN/T +G3inEpQGAHUNcoTpLrxaatXeL1nHo+zSh2bbt1S1JKv0Q3jbSwTEb93mPmY+KfJLaHEih6D4sTNj +duMNhXJEIlU/HHzp/LgV6FL6qj6jITk1dImmasI5+njPtqzn59ZW/yOSLlALqbUHM/Q4X6RJpstl +cHboCoWASzY9M/eVVHUl2qzEc4Jl6VL1XP04lQJqaTDFHApXB64ipCz5xUG3uOyfT0gA+QEEVcys ++TIxxHWVBqB/0Y0n3bOppHKH/lmLmnp0Ft0WpWIp6zqW3IunaFnT63eROfjXy9mPX1onAX1daBli +2MjN9LdyR75bl87yraKZk62Uy5P2EgmVtqvXO9A/EcswFi55gORngS1d7XB4tmBZrOFdRWOPyN9y +aFvqHbgB8X7754qz41SgOAngPN5C8sLtLpvzHzW2NtjjgKGLzZlkD8Kqq7HK9W+eQ42EVJmzbsAS +ZthwEPEGNTNDqJwuuhQxzhB/HIbjj9LV+Hfsm6vxL2PZQl/gZ4FkkfGXL/xuJvYz+NO1+MRiqzFR +JQJ6+N1rZdVtTTDIZbpoFGWsJwt0ivKH +-----END CERTIFICATE----- + +TrustAsia Global Root CA G4 +=========================== +-----BEGIN CERTIFICATE----- +MIICVTCCAdygAwIBAgIUTyNkuI6XY57GU4HBdk7LKnQV1tcwCgYIKoZIzj0EAwMwWjELMAkGA1UE +BhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dpZXMsIEluYy4xJDAiBgNVBAMMG1Ry +dXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHNDAeFw0yMTA1MjAwMjEwMjJaFw00NjA1MTkwMjEwMjJa +MFoxCzAJBgNVBAYTAkNOMSUwIwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQw +IgYDVQQDDBtUcnVzdEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNi +AATxs8045CVD5d4ZCbuBeaIVXxVjAd7Cq92zphtnS4CDr5nLrBfbK5bKfFJV4hrhPVbwLxYI+hW8 +m7tH5j/uqOFMjPXTNvk4XatwmkcN4oFBButJ+bAp3TPsUKV/eSm4IJijYzBhMA8GA1UdEwEB/wQF +MAMBAf8wHwYDVR0jBBgwFoAUpbtKl86zK3+kMd6Xg1mDpm9xy94wHQYDVR0OBBYEFKW7SpfOsyt/ +pDHel4NZg6ZvccveMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNnADBkAjBe8usGzEkxn0AA +bbd+NvBNEU/zy4k6LHiRUKNbwMp1JvK/kF0LgoxgKJ/GcJpo5PECMFxYDlZ2z1jD1xCMuo6u47xk +dUfFVZDj/bpV6wfEU6s3qe4hsiFbYI89MvHVI5TWWA== +-----END CERTIFICATE----- + +CommScope Public Trust ECC Root-01 +================================== +-----BEGIN CERTIFICATE----- +MIICHTCCAaOgAwIBAgIUQ3CCd89NXTTxyq4yLzf39H91oJ4wCgYIKoZIzj0EAwMwTjELMAkGA1UE +BhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29tbVNjb3BlIFB1YmxpYyBUcnVz +dCBFQ0MgUm9vdC0wMTAeFw0yMTA0MjgxNzM1NDNaFw00NjA0MjgxNzM1NDJaME4xCzAJBgNVBAYT +AlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1c3Qg +RUNDIFJvb3QtMDEwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARLNumuV16ocNfQj3Rid8NeeqrltqLx +eP0CflfdkXmcbLlSiFS8LwS+uM32ENEp7LXQoMPwiXAZu1FlxUOcw5tjnSCDPgYLpkJEhRGnSjot +6dZoL0hOUysHP029uax3OVejQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBSOB2LAUN3GGQYARnQE9/OufXVNMDAKBggqhkjOPQQDAwNoADBlAjEAnDPfQeMjqEI2 +Jpc1XHvr20v4qotzVRVcrHgpD7oh2MSg2NED3W3ROT3Ek2DS43KyAjB8xX6I01D1HiXo+k515liW +pDVfG2XqYZpwI7UNo5uSUm9poIyNStDuiw7LR47QjRE= +-----END CERTIFICATE----- + +CommScope Public Trust ECC Root-02 +================================== +-----BEGIN CERTIFICATE----- +MIICHDCCAaOgAwIBAgIUKP2ZYEFHpgE6yhR7H+/5aAiDXX0wCgYIKoZIzj0EAwMwTjELMAkGA1UE +BhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29tbVNjb3BlIFB1YmxpYyBUcnVz +dCBFQ0MgUm9vdC0wMjAeFw0yMTA0MjgxNzQ0NTRaFw00NjA0MjgxNzQ0NTNaME4xCzAJBgNVBAYT +AlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1c3Qg +RUNDIFJvb3QtMDIwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAR4MIHoYx7l63FRD/cHB8o5mXxO1Q/M +MDALj2aTPs+9xYa9+bG3tD60B8jzljHz7aRP+KNOjSkVWLjVb3/ubCK1sK9IRQq9qEmUv4RDsNuE +SgMjGWdqb8FuvAY5N9GIIvejQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBTmGHX/72DehKT1RsfeSlXjMjZ59TAKBggqhkjOPQQDAwNnADBkAjAmc0l6tqvmSfR9 +Uj/UQQSugEODZXW5hYA4O9Zv5JOGq4/nich/m35rChJVYaoR4HkCMHfoMXGsPHED1oQmHhS48zs7 +3u1Z/GtMMH9ZzkXpc2AVmkzw5l4lIhVtwodZ0LKOag== +-----END CERTIFICATE----- + +CommScope Public Trust RSA Root-01 +================================== +-----BEGIN CERTIFICATE----- +MIIFbDCCA1SgAwIBAgIUPgNJgXUWdDGOTKvVxZAplsU5EN0wDQYJKoZIhvcNAQELBQAwTjELMAkG +A1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29tbVNjb3BlIFB1YmxpYyBU +cnVzdCBSU0EgUm9vdC0wMTAeFw0yMTA0MjgxNjQ1NTRaFw00NjA0MjgxNjQ1NTNaME4xCzAJBgNV +BAYTAlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1 +c3QgUlNBIFJvb3QtMDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwSGWjDR1C45Ft +nYSkYZYSwu3D2iM0GXb26v1VWvZVAVMP8syMl0+5UMuzAURWlv2bKOx7dAvnQmtVzslhsuitQDy6 +uUEKBU8bJoWPQ7VAtYXR1HHcg0Hz9kXHgKKEUJdGzqAMxGBWBB0HW0alDrJLpA6lfO741GIDuZNq +ihS4cPgugkY4Iw50x2tBt9Apo52AsH53k2NC+zSDO3OjWiE260f6GBfZumbCk6SP/F2krfxQapWs +vCQz0b2If4b19bJzKo98rwjyGpg/qYFlP8GMicWWMJoKz/TUyDTtnS+8jTiGU+6Xn6myY5QXjQ/c +Zip8UlF1y5mO6D1cv547KI2DAg+pn3LiLCuz3GaXAEDQpFSOm117RTYm1nJD68/A6g3czhLmfTif +BSeolz7pUcZsBSjBAg/pGG3svZwG1KdJ9FQFa2ww8esD1eo9anbCyxooSU1/ZOD6K9pzg4H/kQO9 +lLvkuI6cMmPNn7togbGEW682v3fuHX/3SZtS7NJ3Wn2RnU3COS3kuoL4b/JOHg9O5j9ZpSPcPYeo +KFgo0fEbNttPxP/hjFtyjMcmAyejOQoBqsCyMWCDIqFPEgkBEa801M/XrmLTBQe0MXXgDW1XT2mH ++VepuhX2yFJtocucH+X8eKg1mp9BFM6ltM6UCBwJrVbl2rZJmkrqYxhTnCwuwwIDAQABo0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUN12mmnQywsL5x6YVEFm4 +5P3luG0wDQYJKoZIhvcNAQELBQADggIBAK+nz97/4L1CjU3lIpbfaOp9TSp90K09FlxD533Ahuh6 +NWPxzIHIxgvoLlI1pKZJkGNRrDSsBTtXAOnTYtPZKdVUvhwQkZyybf5Z/Xn36lbQnmhUQo8mUuJM +3y+Xpi/SB5io82BdS5pYV4jvguX6r2yBS5KPQJqTRlnLX3gWsWc+QgvfKNmwrZggvkN80V4aCRck +jXtdlemrwWCrWxhkgPut4AZ9HcpZuPN4KWfGVh2vtrV0KnahP/t1MJ+UXjulYPPLXAziDslg+Mkf +Foom3ecnf+slpoq9uC02EJqxWE2aaE9gVOX2RhOOiKy8IUISrcZKiX2bwdgt6ZYD9KJ0DLwAHb/W +NyVntHKLr4W96ioDj8z7PEQkguIBpQtZtjSNMgsSDesnwv1B10A8ckYpwIzqug/xBpMu95yo9GA+ +o/E4Xo4TwbM6l4c/ksp4qRyv0LAbJh6+cOx69TOY6lz/KwsETkPdY34Op054A5U+1C0wlREQKC6/ +oAI+/15Z0wUOlV9TRe9rh9VIzRamloPh37MG88EU26fsHItdkJANclHnYfkUyq+Dj7+vsQpZXdxc +1+SWrVtgHdqul7I52Qb1dgAT+GhMIbA1xNxVssnBQVocicCMb3SgazNNtQEo/a2tiRc7ppqEvOuM +6sRxJKi6KfkIsidWNTJf6jn7MZrVGczw +-----END CERTIFICATE----- + +CommScope Public Trust RSA Root-02 +================================== +-----BEGIN CERTIFICATE----- +MIIFbDCCA1SgAwIBAgIUVBa/O345lXGN0aoApYYNK496BU4wDQYJKoZIhvcNAQELBQAwTjELMAkG +A1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29tbVNjb3BlIFB1YmxpYyBU +cnVzdCBSU0EgUm9vdC0wMjAeFw0yMTA0MjgxNzE2NDNaFw00NjA0MjgxNzE2NDJaME4xCzAJBgNV +BAYTAlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1 +c3QgUlNBIFJvb3QtMDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDh+g77aAASyE3V +rCLENQE7xVTlWXZjpX/rwcRqmL0yjReA61260WI9JSMZNRTpf4mnG2I81lDnNJUDMrG0kyI9p+Kx +7eZ7Ti6Hmw0zdQreqjXnfuU2mKKuJZ6VszKWpCtYHu8//mI0SFHRtI1CrWDaSWqVcN3SAOLMV2MC +e5bdSZdbkk6V0/nLKR8YSvgBKtJjCW4k6YnS5cciTNxzhkcAqg2Ijq6FfUrpuzNPDlJwnZXjfG2W +Wy09X6GDRl224yW4fKcZgBzqZUPckXk2LHR88mcGyYnJ27/aaL8j7dxrrSiDeS/sOKUNNwFnJ5rp +M9kzXzehxfCrPfp4sOcsn/Y+n2Dg70jpkEUeBVF4GiwSLFworA2iI540jwXmojPOEXcT1A6kHkIf +hs1w/tkuFT0du7jyU1fbzMZ0KZwYszZ1OC4PVKH4kh+Jlk+71O6d6Ts2QrUKOyrUZHk2EOH5kQMr +eyBUzQ0ZGshBMjTRsJnhkB4BQDa1t/qp5Xd1pCKBXbCL5CcSD1SIxtuFdOa3wNemKfrb3vOTlycE +VS8KbzfFPROvCgCpLIscgSjX74Yxqa7ybrjKaixUR9gqiC6vwQcQeKwRoi9C8DfF8rhW3Q5iLc4t +Vn5V8qdE9isy9COoR+jUKgF4z2rDN6ieZdIs5fq6M8EGRPbmz6UNp2YINIos8wIDAQABo0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUR9DnsSL/nSz12Vdgs7Gx +cJXvYXowDQYJKoZIhvcNAQELBQADggIBAIZpsU0v6Z9PIpNojuQhmaPORVMbc0RTAIFhzTHjCLqB +KCh6krm2qMhDnscTJk3C2OVVnJJdUNjCK9v+5qiXz1I6JMNlZFxHMaNlNRPDk7n3+VGXu6TwYofF +1gbTl4MgqX67tiHCpQ2EAOHyJxCDut0DgdXdaMNmEMjRdrSzbymeAPnCKfWxkxlSaRosTKCL4BWa +MS/TiJVZbuXEs1DIFAhKm4sTg7GkcrI7djNB3NyqpgdvHSQSn8h2vS/ZjvQs7rfSOBAkNlEv41xd +gSGn2rtO/+YHqP65DSdsu3BaVXoT6fEqSWnHX4dXTEN5bTpl6TBcQe7rd6VzEojov32u5cSoHw2O +HG1QAk8mGEPej1WFsQs3BWDJVTkSBKEqz3EWnzZRSb9wO55nnPt7eck5HHisd5FUmrh1CoFSl+Nm +YWvtPjgelmFV4ZFUjO2MJB+ByRCac5krFk5yAD9UG/iNuovnFNa2RU9g7Jauwy8CTl2dlklyALKr +dVwPaFsdZcJfMw8eD/A7hvWwTruc9+olBdytoptLFwG+Qt81IR2tq670v64fG9PiO/yzcnMcmyiQ +iRM9HcEARwmWmjgb3bHPDcK0RPOWlc4yOo80nOAXx17Org3bhzjlP1v9mxnhMUF6cKojawHhRUzN +lM47ni3niAIi9G7oyOzWPPO5std3eqx7 +-----END CERTIFICATE----- diff --git a/thirdparty/etcpak/ProcessRGB.cpp b/thirdparty/etcpak/ProcessRGB.cpp index 4dc3bf23af..0caa687bc6 100644 --- a/thirdparty/etcpak/ProcessRGB.cpp +++ b/thirdparty/etcpak/ProcessRGB.cpp @@ -4181,3 +4181,145 @@ void CompressEtc2Rgba( const uint32_t* src, uint64_t* dst, uint32_t blocks, size } while( --blocks ); } + +// -- GODOT start -- +void CompressEtc2R8( const uint32_t* src, uint64_t* dst, uint32_t blocks, size_t width ) +{ + int w = 0; + uint8_t r[4*4]; + do + { +#ifdef __SSE4_1__ + __m128 px0 = _mm_castsi128_ps( _mm_loadu_si128( (__m128i*)( src + width * 0 ) ) ); + __m128 px1 = _mm_castsi128_ps( _mm_loadu_si128( (__m128i*)( src + width * 1 ) ) ); + __m128 px2 = _mm_castsi128_ps( _mm_loadu_si128( (__m128i*)( src + width * 2 ) ) ); + __m128 px3 = _mm_castsi128_ps( _mm_loadu_si128( (__m128i*)( src + width * 3 ) ) ); + + _MM_TRANSPOSE4_PS( px0, px1, px2, px3 ); + + __m128i c0 = _mm_castps_si128( px0 ); + __m128i c1 = _mm_castps_si128( px1 ); + __m128i c2 = _mm_castps_si128( px2 ); + __m128i c3 = _mm_castps_si128( px3 ); + + __m128i mask = _mm_setr_epi32( 0x0e0a0602, -1, -1, -1 ); + + __m128i a0 = _mm_shuffle_epi8( c0, mask ); + __m128i a1 = _mm_shuffle_epi8( c1, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 3, 3, 0, 3 ) ) ); + __m128i a2 = _mm_shuffle_epi8( c2, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 3, 0, 3, 3 ) ) ); + __m128i a3 = _mm_shuffle_epi8( c3, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 0, 3, 3, 3 ) ) ); + + __m128i s0 = _mm_or_si128( a0, a1 ); + __m128i s1 = _mm_or_si128( a2, a3 ); + __m128i s2 = _mm_or_si128( s0, s1 ); + + _mm_store_si128( (__m128i*)r, s2 ); + + src += 4; +#else + auto ptr8 = r; + for( int x=0; x<4; x++ ) + { + auto v = *src; + *ptr8++ = (v & 0xff0000) >> 16; + src += width; + v = *src; + *ptr8++ = (v & 0xff0000) >> 16; + src += width; + v = *src; + *ptr8++ = (v & 0xff0000) >> 16; + src += width; + v = *src; + *ptr8++ = (v & 0xff0000) >> 16; + src -= width * 3 - 1; + } +#endif + if( ++w == width/4 ) + { + src += width * 3; + w = 0; + } + *dst++ = ProcessAlpha_ETC2( r ); + } + while( --blocks ); +} + +void CompressEtc2RG8( const uint32_t* src, uint64_t* dst, uint32_t blocks, size_t width ) +{ + int w = 0; + uint8_t rg[4*4*2]; + do + { +#ifdef __SSE4_1__ + __m128 px0 = _mm_castsi128_ps( _mm_loadu_si128( (__m128i*)( src + width * 0 ) ) ); + __m128 px1 = _mm_castsi128_ps( _mm_loadu_si128( (__m128i*)( src + width * 1 ) ) ); + __m128 px2 = _mm_castsi128_ps( _mm_loadu_si128( (__m128i*)( src + width * 2 ) ) ); + __m128 px3 = _mm_castsi128_ps( _mm_loadu_si128( (__m128i*)( src + width * 3 ) ) ); + + _MM_TRANSPOSE4_PS( px0, px1, px2, px3 ); + + __m128i c0 = _mm_castps_si128( px0 ); + __m128i c1 = _mm_castps_si128( px1 ); + __m128i c2 = _mm_castps_si128( px2 ); + __m128i c3 = _mm_castps_si128( px3 ); + + __m128i mask = _mm_setr_epi32( 0x0e0a0602, -1, -1, -1 ); + + __m128i r0 = _mm_shuffle_epi8( c0, mask ); + __m128i r1 = _mm_shuffle_epi8( c1, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 3, 3, 0, 3 ) ) ); + __m128i r2 = _mm_shuffle_epi8( c2, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 3, 0, 3, 3 ) ) ); + __m128i r3 = _mm_shuffle_epi8( c3, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 0, 3, 3, 3 ) ) ); + + __m128i s0 = _mm_or_si128( r0, r1 ); + __m128i s1 = _mm_or_si128( r2, r3 ); + __m128i s2 = _mm_or_si128( s0, s1 ); + + _mm_store_si128( (__m128i*)rg, s2 ); + + mask = _mm_setr_epi32( 0x0d090501, -1, -1, -1 ); + + r0 = _mm_shuffle_epi8( c0, mask ); + r1 = _mm_shuffle_epi8( c1, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 3, 3, 0, 3 ) ) ); + r2 = _mm_shuffle_epi8( c2, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 3, 0, 3, 3 ) ) ); + r3 = _mm_shuffle_epi8( c3, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 0, 3, 3, 3 ) ) ); + + s0 = _mm_or_si128( r0, r1 ); + s1 = _mm_or_si128( r2, r3 ); + s2 = _mm_or_si128( s0, s1 ); + + _mm_store_si128( (__m128i*)&rg[16], s2 ); + src += 4; +#else + auto ptrr = rg; + auto ptrg = ptrr + 16; + for( int x=0; x<4; x++ ) + { + auto v = *src; + *ptrr++ = (v & 0xff0000) >> 16; + *ptrg++ = (v & 0xff00) >> 8; + src += width; + v = *src; + *ptrr++ = (v & 0xff0000) >> 16; + *ptrg++ = (v & 0xff00) >> 8; + src += width; + v = *src; + *ptrr++ = (v & 0xff0000) >> 16; + *ptrg++ = (v & 0xff00) >> 8; + src += width; + v = *src; + *ptrr++ = (v & 0xff0000) >> 16; + *ptrg++ = (v & 0xff00) >> 8; + src -= width * 3 - 1; + } +#endif + if( ++w == width/4 ) + { + src += width * 3; + w = 0; + } + *dst++ = ProcessAlpha_ETC2( rg ); + *dst++ = ProcessAlpha_ETC2( &rg[16] ); + } + while( --blocks ); +} +// -- GODOT end -- diff --git a/thirdparty/etcpak/ProcessRGB.hpp b/thirdparty/etcpak/ProcessRGB.hpp index 043b46e636..050ea42562 100644 --- a/thirdparty/etcpak/ProcessRGB.hpp +++ b/thirdparty/etcpak/ProcessRGB.hpp @@ -9,5 +9,8 @@ void CompressEtc1Rgb( const uint32_t* src, uint64_t* dst, uint32_t blocks, size_ void CompressEtc1RgbDither( const uint32_t* src, uint64_t* dst, uint32_t blocks, size_t width ); void CompressEtc2Rgb( const uint32_t* src, uint64_t* dst, uint32_t blocks, size_t width, bool useHeuristics ); void CompressEtc2Rgba( const uint32_t* src, uint64_t* dst, uint32_t blocks, size_t width, bool useHeuristics ); - +// -- GODOT start -- +void CompressEtc2R8( const uint32_t* src, uint64_t* dst, uint32_t blocks, size_t width ); +void CompressEtc2RG8( const uint32_t* src, uint64_t* dst, uint32_t blocks, size_t width ); +// -- GODOT end -- #endif diff --git a/thirdparty/etcpak/ProcessRgtc.cpp b/thirdparty/etcpak/ProcessRgtc.cpp index 3a283b743b..5eec2648db 100644 --- a/thirdparty/etcpak/ProcessRgtc.cpp +++ b/thirdparty/etcpak/ProcessRgtc.cpp @@ -6,8 +6,49 @@ #include <assert.h> #include <string.h> +#if defined __AVX__ && !defined __SSE4_1__ +# define __SSE4_1__ +#endif + +#if defined __SSE4_1__ || defined __AVX2__ +# ifdef _MSC_VER +# include <intrin.h> +# else +# include <x86intrin.h> +# ifndef _mm256_cvtsi256_si32 +# define _mm256_cvtsi256_si32( v ) ( _mm_cvtsi128_si32( _mm256_castsi256_si128( v ) ) ) +# endif +# endif +#endif + static const uint8_t AlphaIndexTable[8] = { 1, 7, 6, 5, 4, 3, 2, 0 }; +static const uint8_t AlphaIndexTable_SSE[64] = { + 9, 15, 14, 13, 12, 11, 10, 8, 57, 63, 62, 61, 60, 59, 58, 56, + 49, 55, 54, 53, 52, 51, 50, 48, 41, 47, 46, 45, 44, 43, 42, 40, + 33, 39, 38, 37, 36, 35, 34, 32, 25, 31, 30, 29, 28, 27, 26, 24, + 17, 23, 22, 21, 20, 19, 18, 16, 1, 7, 6, 5, 4, 3, 2, 0, +}; + +static const uint16_t DivTableAlpha[256] = { + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xe38e, 0xcccc, 0xba2e, 0xaaaa, 0x9d89, 0x9249, 0x8888, 0x8000, + 0x7878, 0x71c7, 0x6bca, 0x6666, 0x6186, 0x5d17, 0x590b, 0x5555, 0x51eb, 0x4ec4, 0x4bda, 0x4924, 0x469e, 0x4444, 0x4210, 0x4000, + 0x3e0f, 0x3c3c, 0x3a83, 0x38e3, 0x3759, 0x35e5, 0x3483, 0x3333, 0x31f3, 0x30c3, 0x2fa0, 0x2e8b, 0x2d82, 0x2c85, 0x2b93, 0x2aaa, + 0x29cb, 0x28f5, 0x2828, 0x2762, 0x26a4, 0x25ed, 0x253c, 0x2492, 0x23ee, 0x234f, 0x22b6, 0x2222, 0x2192, 0x2108, 0x2082, 0x2000, + 0x1f81, 0x1f07, 0x1e91, 0x1e1e, 0x1dae, 0x1d41, 0x1cd8, 0x1c71, 0x1c0e, 0x1bac, 0x1b4e, 0x1af2, 0x1a98, 0x1a41, 0x19ec, 0x1999, + 0x1948, 0x18f9, 0x18ac, 0x1861, 0x1818, 0x17d0, 0x178a, 0x1745, 0x1702, 0x16c1, 0x1681, 0x1642, 0x1605, 0x15c9, 0x158e, 0x1555, + 0x151d, 0x14e5, 0x14af, 0x147a, 0x1446, 0x1414, 0x13e2, 0x13b1, 0x1381, 0x1352, 0x1323, 0x12f6, 0x12c9, 0x129e, 0x1273, 0x1249, + 0x121f, 0x11f7, 0x11cf, 0x11a7, 0x1181, 0x115b, 0x1135, 0x1111, 0x10ec, 0x10c9, 0x10a6, 0x1084, 0x1062, 0x1041, 0x1020, 0x1000, + 0x0fe0, 0x0fc0, 0x0fa2, 0x0f83, 0x0f66, 0x0f48, 0x0f2b, 0x0f0f, 0x0ef2, 0x0ed7, 0x0ebb, 0x0ea0, 0x0e86, 0x0e6c, 0x0e52, 0x0e38, + 0x0e1f, 0x0e07, 0x0dee, 0x0dd6, 0x0dbe, 0x0da7, 0x0d90, 0x0d79, 0x0d62, 0x0d4c, 0x0d36, 0x0d20, 0x0d0b, 0x0cf6, 0x0ce1, 0x0ccc, + 0x0cb8, 0x0ca4, 0x0c90, 0x0c7c, 0x0c69, 0x0c56, 0x0c43, 0x0c30, 0x0c1e, 0x0c0c, 0x0bfa, 0x0be8, 0x0bd6, 0x0bc5, 0x0bb3, 0x0ba2, + 0x0b92, 0x0b81, 0x0b70, 0x0b60, 0x0b50, 0x0b40, 0x0b30, 0x0b21, 0x0b11, 0x0b02, 0x0af3, 0x0ae4, 0x0ad6, 0x0ac7, 0x0ab8, 0x0aaa, + 0x0a9c, 0x0a8e, 0x0a80, 0x0a72, 0x0a65, 0x0a57, 0x0a4a, 0x0a3d, 0x0a30, 0x0a23, 0x0a16, 0x0a0a, 0x09fd, 0x09f1, 0x09e4, 0x09d8, + 0x09cc, 0x09c0, 0x09b4, 0x09a9, 0x099d, 0x0991, 0x0986, 0x097b, 0x0970, 0x0964, 0x095a, 0x094f, 0x0944, 0x0939, 0x092f, 0x0924, + 0x091a, 0x090f, 0x0905, 0x08fb, 0x08f1, 0x08e7, 0x08dd, 0x08d3, 0x08ca, 0x08c0, 0x08b7, 0x08ad, 0x08a4, 0x089a, 0x0891, 0x0888, + 0x087f, 0x0876, 0x086d, 0x0864, 0x085b, 0x0853, 0x084a, 0x0842, 0x0839, 0x0831, 0x0828, 0x0820, 0x0818, 0x0810, 0x0808, 0x0800, +}; + static etcpak_force_inline uint64_t ProcessAlpha( const uint8_t* src ) { uint8_t solid8 = *src; @@ -40,20 +81,72 @@ static etcpak_force_inline uint64_t ProcessAlpha( const uint8_t* src ) return max | ( min << 8 ) | ( data << 16 ); } +#ifdef __SSE4_1__ +static etcpak_force_inline uint64_t Process_Alpha_SSE( __m128i a ) +{ + __m128i solidCmp = _mm_shuffle_epi8( a, _mm_setzero_si128() ); + __m128i cmpRes = _mm_cmpeq_epi8( a, solidCmp ); + if( _mm_testc_si128( cmpRes, _mm_set1_epi32( -1 ) ) ) + { + return _mm_cvtsi128_si32( a ) & 0xFF; + } + + __m128i a1 = _mm_shuffle_epi32( a, _MM_SHUFFLE( 2, 3, 0, 1 ) ); + __m128i max1 = _mm_max_epu8( a, a1 ); + __m128i min1 = _mm_min_epu8( a, a1 ); + __m128i amax2 = _mm_shuffle_epi32( max1, _MM_SHUFFLE( 0, 0, 2, 2 ) ); + __m128i amin2 = _mm_shuffle_epi32( min1, _MM_SHUFFLE( 0, 0, 2, 2 ) ); + __m128i max2 = _mm_max_epu8( max1, amax2 ); + __m128i min2 = _mm_min_epu8( min1, amin2 ); + __m128i amax3 = _mm_alignr_epi8( max2, max2, 2 ); + __m128i amin3 = _mm_alignr_epi8( min2, min2, 2 ); + __m128i max3 = _mm_max_epu8( max2, amax3 ); + __m128i min3 = _mm_min_epu8( min2, amin3 ); + __m128i amax4 = _mm_alignr_epi8( max3, max3, 1 ); + __m128i amin4 = _mm_alignr_epi8( min3, min3, 1 ); + __m128i max = _mm_max_epu8( max3, amax4 ); + __m128i min = _mm_min_epu8( min3, amin4 ); + __m128i minmax = _mm_unpacklo_epi8( max, min ); + + __m128i r = _mm_sub_epi8( max, min ); + int range = _mm_cvtsi128_si32( r ) & 0xFF; + __m128i rv = _mm_set1_epi16( DivTableAlpha[range] ); + + __m128i v = _mm_sub_epi8( a, min ); + + __m128i lo16 = _mm_unpacklo_epi8( v, _mm_setzero_si128() ); + __m128i hi16 = _mm_unpackhi_epi8( v, _mm_setzero_si128() ); + + __m128i lomul = _mm_mulhi_epu16( lo16, rv ); + __m128i himul = _mm_mulhi_epu16( hi16, rv ); + + __m128i p0 = _mm_packus_epi16( lomul, himul ); + __m128i p1 = _mm_or_si128( _mm_and_si128( p0, _mm_set1_epi16( 0x3F ) ), _mm_srai_epi16( _mm_and_si128( p0, _mm_set1_epi16( 0x3F00 ) ), 5 ) ); + __m128i p2 = _mm_packus_epi16( p1, p1 ); + + uint64_t pi = _mm_cvtsi128_si64( p2 ); + uint64_t data = 0; + for( int i=0; i<8; i++ ) + { + uint64_t idx = AlphaIndexTable_SSE[(pi>>(i*8)) & 0x3F]; + data |= idx << (i*6); + } + return (uint64_t)(uint16_t)_mm_cvtsi128_si32( minmax ) | ( data << 16 ); +} +#endif + void CompressRgtcR(const uint32_t *src, uint64_t *dst, uint32_t blocks, size_t width) { int i = 0; auto ptr = dst; do { - uint32_t rgba[4 * 4]; - uint8_t r[4 * 4]; +#ifdef __SSE4_1__ + __m128i px0 = _mm_loadu_si128( (__m128i*)( src + width * 0 ) ); + __m128i px1 = _mm_loadu_si128( (__m128i*)( src + width * 1 ) ); + __m128i px2 = _mm_loadu_si128( (__m128i*)( src + width * 2 ) ); + __m128i px3 = _mm_loadu_si128( (__m128i*)( src + width * 3 ) ); - auto tmp = (char *)rgba; - memcpy(tmp, src + width * 0, 4 * 4); - memcpy(tmp + 4 * 4, src + width * 1, 4 * 4); - memcpy(tmp + 8 * 4, src + width * 2, 4 * 4); - memcpy(tmp + 12 * 4, src + width * 3, 4 * 4); src += 4; if (++i == width / 4) { @@ -61,11 +154,38 @@ void CompressRgtcR(const uint32_t *src, uint64_t *dst, uint32_t blocks, size_t w i = 0; } - for (int i = 0; i < 16; i++) + __m128i mask = _mm_setr_epi32( 0x0c080400, -1, -1, -1 ); + + __m128i m0 = _mm_shuffle_epi8( px0, mask ); + __m128i m1 = _mm_shuffle_epi8( px1, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 3, 3, 0, 3 ) ) ); + __m128i m2 = _mm_shuffle_epi8( px2, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 3, 0, 3, 3 ) ) ); + __m128i m3 = _mm_shuffle_epi8( px3, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 0, 3, 3, 3 ) ) ); + __m128i m4 = _mm_or_si128( m0, m1 ); + __m128i m5 = _mm_or_si128( m2, m3 ); + + *ptr++ = Process_Alpha_SSE(_mm_or_si128( m4, m5 )); +#else + uint8_t r[4 * 4]; + auto rgba = src; + for (int i = 0; i < 4; i++) + { + r[i * 4] = rgba[0] & 0xff; + r[i * 4 + 1] = rgba[1] & 0xff; + r[i * 4 + 2] = rgba[2] & 0xff; + r[i * 4 + 3] = rgba[3] & 0xff; + + rgba += width; + } + + src += 4; + if (++i == width / 4) { - r[i] = rgba[i] & 0x000000FF; + src += width * 3; + i = 0; } + *ptr++ = ProcessAlpha(r); +#endif } while (--blocks); } @@ -76,15 +196,12 @@ void CompressRgtcRG(const uint32_t *src, uint64_t *dst, uint32_t blocks, size_t auto ptr = dst; do { - uint32_t rgba[4 * 4]; - uint8_t r[4 * 4]; - uint8_t g[4 * 4]; - - auto tmp = (char *)rgba; - memcpy(tmp, src + width * 0, 4 * 4); - memcpy(tmp + 4 * 4, src + width * 1, 4 * 4); - memcpy(tmp + 8 * 4, src + width * 2, 4 * 4); - memcpy(tmp + 12 * 4, src + width * 3, 4 * 4); +#ifdef __SSE4_1__ + __m128i px0 = _mm_loadu_si128( (__m128i*)( src + width * 0 ) ); + __m128i px1 = _mm_loadu_si128( (__m128i*)( src + width * 1 ) ); + __m128i px2 = _mm_loadu_si128( (__m128i*)( src + width * 2 ) ); + __m128i px3 = _mm_loadu_si128( (__m128i*)( src + width * 3 ) ); + src += 4; if (++i == width / 4) { @@ -92,13 +209,55 @@ void CompressRgtcRG(const uint32_t *src, uint64_t *dst, uint32_t blocks, size_t i = 0; } - for (int i = 0; i < 16; i++) + __m128i mask = _mm_setr_epi32( 0x0c080400, -1, -1, -1 ); + + __m128i m0 = _mm_shuffle_epi8( px0, mask ); + __m128i m1 = _mm_shuffle_epi8( px1, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 3, 3, 0, 3 ) ) ); + __m128i m2 = _mm_shuffle_epi8( px2, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 3, 0, 3, 3 ) ) ); + __m128i m3 = _mm_shuffle_epi8( px3, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 0, 3, 3, 3 ) ) ); + __m128i m4 = _mm_or_si128( m0, m1 ); + __m128i m5 = _mm_or_si128( m2, m3 ); + + *ptr++ = Process_Alpha_SSE(_mm_or_si128( m4, m5 )); + + mask = _mm_setr_epi32( 0x0d090501, -1, -1, -1 ); + + m0 = _mm_shuffle_epi8( px0, mask ); + m1 = _mm_shuffle_epi8( px1, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 3, 3, 0, 3 ) ) ); + m2 = _mm_shuffle_epi8( px2, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 3, 0, 3, 3 ) ) ); + m3 = _mm_shuffle_epi8( px3, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 0, 3, 3, 3 ) ) ); + m4 = _mm_or_si128( m0, m1 ); + m5 = _mm_or_si128( m2, m3 ); + + *ptr++ = Process_Alpha_SSE(_mm_or_si128( m4, m5 )); +#else + uint8_t rg[4 * 4 * 2]; + auto rgba = src; + for (int i = 0; i < 4; i++) { - r[i] = rgba[i] & 0x000000FF; - g[i] = (rgba[i] & 0x0000FF00) >> 8; + rg[i * 4] = rgba[0] & 0xff; + rg[i * 4 + 1] = rgba[1] & 0xff; + rg[i * 4 + 2] = rgba[2] & 0xff; + rg[i * 4 + 3] = rgba[3] & 0xff; + + rg[16 + i * 4] = (rgba[0] & 0xff00) >> 8; + rg[16 + i * 4 + 1] = (rgba[1] & 0xff00) >> 8; + rg[16 + i * 4 + 2] = (rgba[2] & 0xff00) >> 8; + rg[16 + i * 4 + 3] = (rgba[3] & 0xff00) >> 8; + + rgba += width; } - *ptr++ = ProcessAlpha(r); - *ptr++ = ProcessAlpha(g); + + src += 4; + if (++i == width / 4) + { + src += width * 3; + i = 0; + } + + *ptr++ = ProcessAlpha(rg); + *ptr++ = ProcessAlpha(&rg[16]); +#endif } while (--blocks); } diff --git a/thirdparty/etcpak/patches/etc2-r-rg.patch b/thirdparty/etcpak/patches/etc2-r-rg.patch new file mode 100644 index 0000000000..5d6c117bf7 --- /dev/null +++ b/thirdparty/etcpak/patches/etc2-r-rg.patch @@ -0,0 +1,164 @@ +diff --git a/thirdparty/etcpak/ProcessRGB.cpp b/thirdparty/etcpak/ProcessRGB.cpp +index 4dc3bf23af..0caa687bc6 100644 +--- a/thirdparty/etcpak/ProcessRGB.cpp ++++ b/thirdparty/etcpak/ProcessRGB.cpp +@@ -4181,3 +4181,145 @@ void CompressEtc2Rgba( const uint32_t* src, uint64_t* dst, uint32_t blocks, size + } + while( --blocks ); + } ++ ++// -- GODOT start -- ++void CompressEtc2R8( const uint32_t* src, uint64_t* dst, uint32_t blocks, size_t width ) ++{ ++ int w = 0; ++ uint8_t r[4*4]; ++ do ++ { ++#ifdef __SSE4_1__ ++ __m128 px0 = _mm_castsi128_ps( _mm_loadu_si128( (__m128i*)( src + width * 0 ) ) ); ++ __m128 px1 = _mm_castsi128_ps( _mm_loadu_si128( (__m128i*)( src + width * 1 ) ) ); ++ __m128 px2 = _mm_castsi128_ps( _mm_loadu_si128( (__m128i*)( src + width * 2 ) ) ); ++ __m128 px3 = _mm_castsi128_ps( _mm_loadu_si128( (__m128i*)( src + width * 3 ) ) ); ++ ++ _MM_TRANSPOSE4_PS( px0, px1, px2, px3 ); ++ ++ __m128i c0 = _mm_castps_si128( px0 ); ++ __m128i c1 = _mm_castps_si128( px1 ); ++ __m128i c2 = _mm_castps_si128( px2 ); ++ __m128i c3 = _mm_castps_si128( px3 ); ++ ++ __m128i mask = _mm_setr_epi32( 0x0e0a0602, -1, -1, -1 ); ++ ++ __m128i a0 = _mm_shuffle_epi8( c0, mask ); ++ __m128i a1 = _mm_shuffle_epi8( c1, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 3, 3, 0, 3 ) ) ); ++ __m128i a2 = _mm_shuffle_epi8( c2, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 3, 0, 3, 3 ) ) ); ++ __m128i a3 = _mm_shuffle_epi8( c3, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 0, 3, 3, 3 ) ) ); ++ ++ __m128i s0 = _mm_or_si128( a0, a1 ); ++ __m128i s1 = _mm_or_si128( a2, a3 ); ++ __m128i s2 = _mm_or_si128( s0, s1 ); ++ ++ _mm_store_si128( (__m128i*)r, s2 ); ++ ++ src += 4; ++#else ++ auto ptr8 = r; ++ for( int x=0; x<4; x++ ) ++ { ++ auto v = *src; ++ *ptr8++ = (v & 0xff0000) >> 16; ++ src += width; ++ v = *src; ++ *ptr8++ = (v & 0xff0000) >> 16; ++ src += width; ++ v = *src; ++ *ptr8++ = (v & 0xff0000) >> 16; ++ src += width; ++ v = *src; ++ *ptr8++ = (v & 0xff0000) >> 16; ++ src -= width * 3 - 1; ++ } ++#endif ++ if( ++w == width/4 ) ++ { ++ src += width * 3; ++ w = 0; ++ } ++ *dst++ = ProcessAlpha_ETC2( r ); ++ } ++ while( --blocks ); ++} ++ ++void CompressEtc2RG8( const uint32_t* src, uint64_t* dst, uint32_t blocks, size_t width ) ++{ ++ int w = 0; ++ uint8_t rg[4*4*2]; ++ do ++ { ++#ifdef __SSE4_1__ ++ __m128 px0 = _mm_castsi128_ps( _mm_loadu_si128( (__m128i*)( src + width * 0 ) ) ); ++ __m128 px1 = _mm_castsi128_ps( _mm_loadu_si128( (__m128i*)( src + width * 1 ) ) ); ++ __m128 px2 = _mm_castsi128_ps( _mm_loadu_si128( (__m128i*)( src + width * 2 ) ) ); ++ __m128 px3 = _mm_castsi128_ps( _mm_loadu_si128( (__m128i*)( src + width * 3 ) ) ); ++ ++ _MM_TRANSPOSE4_PS( px0, px1, px2, px3 ); ++ ++ __m128i c0 = _mm_castps_si128( px0 ); ++ __m128i c1 = _mm_castps_si128( px1 ); ++ __m128i c2 = _mm_castps_si128( px2 ); ++ __m128i c3 = _mm_castps_si128( px3 ); ++ ++ __m128i mask = _mm_setr_epi32( 0x0e0a0602, -1, -1, -1 ); ++ ++ __m128i r0 = _mm_shuffle_epi8( c0, mask ); ++ __m128i r1 = _mm_shuffle_epi8( c1, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 3, 3, 0, 3 ) ) ); ++ __m128i r2 = _mm_shuffle_epi8( c2, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 3, 0, 3, 3 ) ) ); ++ __m128i r3 = _mm_shuffle_epi8( c3, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 0, 3, 3, 3 ) ) ); ++ ++ __m128i s0 = _mm_or_si128( r0, r1 ); ++ __m128i s1 = _mm_or_si128( r2, r3 ); ++ __m128i s2 = _mm_or_si128( s0, s1 ); ++ ++ _mm_store_si128( (__m128i*)rg, s2 ); ++ ++ mask = _mm_setr_epi32( 0x0d090501, -1, -1, -1 ); ++ ++ r0 = _mm_shuffle_epi8( c0, mask ); ++ r1 = _mm_shuffle_epi8( c1, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 3, 3, 0, 3 ) ) ); ++ r2 = _mm_shuffle_epi8( c2, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 3, 0, 3, 3 ) ) ); ++ r3 = _mm_shuffle_epi8( c3, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 0, 3, 3, 3 ) ) ); ++ ++ s0 = _mm_or_si128( r0, r1 ); ++ s1 = _mm_or_si128( r2, r3 ); ++ s2 = _mm_or_si128( s0, s1 ); ++ ++ _mm_store_si128( (__m128i*)&rg[16], s2 ); ++ src += 4; ++#else ++ auto ptrr = rg; ++ auto ptrg = ptrr + 16; ++ for( int x=0; x<4; x++ ) ++ { ++ auto v = *src; ++ *ptrr++ = (v & 0xff0000) >> 16; ++ *ptrg++ = (v & 0xff00) >> 8; ++ src += width; ++ v = *src; ++ *ptrr++ = (v & 0xff0000) >> 16; ++ *ptrg++ = (v & 0xff00) >> 8; ++ src += width; ++ v = *src; ++ *ptrr++ = (v & 0xff0000) >> 16; ++ *ptrg++ = (v & 0xff00) >> 8; ++ src += width; ++ v = *src; ++ *ptrr++ = (v & 0xff0000) >> 16; ++ *ptrg++ = (v & 0xff00) >> 8; ++ src -= width * 3 - 1; ++ } ++#endif ++ if( ++w == width/4 ) ++ { ++ src += width * 3; ++ w = 0; ++ } ++ *dst++ = ProcessAlpha_ETC2( rg ); ++ *dst++ = ProcessAlpha_ETC2( &rg[16] ); ++ } ++ while( --blocks ); ++} ++// -- GODOT end -- +diff --git a/thirdparty/etcpak/ProcessRGB.hpp b/thirdparty/etcpak/ProcessRGB.hpp +index 043b46e636..050ea42562 100644 +--- a/thirdparty/etcpak/ProcessRGB.hpp ++++ b/thirdparty/etcpak/ProcessRGB.hpp +@@ -9,5 +9,8 @@ void CompressEtc1Rgb( const uint32_t* src, uint64_t* dst, uint32_t blocks, size_ + void CompressEtc1RgbDither( const uint32_t* src, uint64_t* dst, uint32_t blocks, size_t width ); + void CompressEtc2Rgb( const uint32_t* src, uint64_t* dst, uint32_t blocks, size_t width, bool useHeuristics ); + void CompressEtc2Rgba( const uint32_t* src, uint64_t* dst, uint32_t blocks, size_t width, bool useHeuristics ); +- ++// -- GODOT start -- ++void CompressEtc2R8( const uint32_t* src, uint64_t* dst, uint32_t blocks, size_t width ); ++void CompressEtc2RG8( const uint32_t* src, uint64_t* dst, uint32_t blocks, size_t width ); ++// -- GODOT end -- + #endif diff --git a/thirdparty/miniupnpc/src/miniupnpcstrings.h b/thirdparty/miniupnpc/src/miniupnpcstrings.h index d89d9e443b..5ad694754f 100644 --- a/thirdparty/miniupnpc/src/miniupnpcstrings.h +++ b/thirdparty/miniupnpc/src/miniupnpcstrings.h @@ -1,9 +1,7 @@ #ifndef MINIUPNPCSTRINGS_H_INCLUDED #define MINIUPNPCSTRINGS_H_INCLUDED -#include "core/version.h" - -#define OS_STRING VERSION_NAME "/1.0" +#define OS_STRING "Godot Engine/1.0" #define MINIUPNPC_VERSION_STRING "2.2.5" #if 0 diff --git a/thirdparty/minizip/ioapi.h b/thirdparty/minizip/ioapi.h index 14266141c6..509b52da8a 100644 --- a/thirdparty/minizip/ioapi.h +++ b/thirdparty/minizip/ioapi.h @@ -144,7 +144,7 @@ typedef long (ZCALLBACK *tell_file_func) (voidpf opaque, voidpf stream) typedef long (ZCALLBACK *seek_file_func) (voidpf opaque, voidpf stream, uLong offset, int origin); -/* here is the "old" 32 bits structure structure */ +/* here is the "old" 32 bits structure */ typedef struct zlib_filefunc_def_s { open_file_func zopen_file; diff --git a/thirdparty/minizip/patches/CVE-2023-45853.patch b/thirdparty/minizip/patches/CVE-2023-45853.patch deleted file mode 100644 index 784c57766f..0000000000 --- a/thirdparty/minizip/patches/CVE-2023-45853.patch +++ /dev/null @@ -1,36 +0,0 @@ -From 73331a6a0481067628f065ffe87bb1d8f787d10c Mon Sep 17 00:00:00 2001 -From: Hans Wennborg <hans@chromium.org> -Date: Fri, 18 Aug 2023 11:05:33 +0200 -Subject: [PATCH] Reject overflows of zip header fields in minizip. - -This checks the lengths of the file name, extra field, and comment -that would be put in the zip headers, and rejects them if they are -too long. They are each limited to 65535 bytes in length by the zip -format. This also avoids possible buffer overflows if the provided -fields are too long. ---- - contrib/minizip/zip.c | 11 +++++++++++ - 1 file changed, 11 insertions(+) - -diff --git a/contrib/minizip/zip.c b/contrib/minizip/zip.c -index 3d3d4cadd..0446109b2 100644 ---- a/contrib/minizip/zip.c -+++ b/contrib/minizip/zip.c -@@ -1043,6 +1043,17 @@ extern int ZEXPORT zipOpenNewFileInZip4_64(zipFile file, const char* filename, c - return ZIP_PARAMERROR; - #endif - -+ // The filename and comment length must fit in 16 bits. -+ if ((filename!=NULL) && (strlen(filename)>0xffff)) -+ return ZIP_PARAMERROR; -+ if ((comment!=NULL) && (strlen(comment)>0xffff)) -+ return ZIP_PARAMERROR; -+ // The extra field length must fit in 16 bits. If the member also requires -+ // a Zip64 extra block, that will also need to fit within that 16-bit -+ // length, but that will be checked for later. -+ if ((size_extrafield_local>0xffff) || (size_extrafield_global>0xffff)) -+ return ZIP_PARAMERROR; -+ - zi = (zip64_internal*)file; - - if (zi->in_opened_file_inzip == 1) diff --git a/thirdparty/minizip/patches/godot-seek.patch b/thirdparty/minizip/patches/godot-seek.patch index 269536ba85..649de2fe2f 100644 --- a/thirdparty/minizip/patches/godot-seek.patch +++ b/thirdparty/minizip/patches/godot-seek.patch @@ -26,7 +26,7 @@ index 782d32469a..2e89f5f41a 100644 +*/ +/* GODOT end */ diff --git a/thirdparty/minizip/ioapi.h b/thirdparty/minizip/ioapi.h -index c588a18d03..14266141c6 100644 +index a2d2e6e60d..509b52da8a 100644 --- a/thirdparty/minizip/ioapi.h +++ b/thirdparty/minizip/ioapi.h @@ -155,6 +155,10 @@ typedef struct zlib_filefunc_def_s @@ -52,7 +52,7 @@ index c588a18d03..14266141c6 100644 void fill_fopen64_filefunc(zlib_filefunc64_def* pzlib_filefunc_def); diff --git a/thirdparty/minizip/unzip.c b/thirdparty/minizip/unzip.c -index ed763f89f1..2f8c5f3675 100644 +index ea05b7d62a..981ba3c0cb 100644 --- a/thirdparty/minizip/unzip.c +++ b/thirdparty/minizip/unzip.c @@ -152,6 +152,9 @@ typedef struct @@ -232,7 +232,7 @@ index ed763f89f1..2f8c5f3675 100644 return unzOpenCurrentFile3(file, NULL, NULL, 0, NULL); } diff --git a/thirdparty/minizip/unzip.h b/thirdparty/minizip/unzip.h -index 14105840f6..1a477ecb50 100644 +index 5cfc9c6274..0639674574 100644 --- a/thirdparty/minizip/unzip.h +++ b/thirdparty/minizip/unzip.h @@ -202,6 +202,10 @@ extern int ZEXPORT unzClose(unzFile file); @@ -261,7 +261,7 @@ index 14105840f6..1a477ecb50 100644 extern ZPOS64_T ZEXPORT unztell64(unzFile file); diff --git a/thirdparty/minizip/zip.c b/thirdparty/minizip/zip.c -index 3d3d4cadde..e859f9e42f 100644 +index 60bdffac34..1d2d918e72 100644 --- a/thirdparty/minizip/zip.c +++ b/thirdparty/minizip/zip.c @@ -820,9 +820,11 @@ extern zipFile ZEXPORT zipOpen3(const void *pathname, int append, zipcharpc* glo @@ -279,7 +279,7 @@ index 3d3d4cadde..e859f9e42f 100644 ziinit.z_filefunc = *pzlib_filefunc64_32_def; ziinit.filestream = ZOPEN64(ziinit.z_filefunc, -@@ -1171,8 +1173,10 @@ extern int ZEXPORT zipOpenNewFileInZip4_64(zipFile file, const char* filename, c +@@ -1182,8 +1184,10 @@ extern int ZEXPORT zipOpenNewFileInZip4_64(zipFile file, const char* filename, c { if(zi->ci.method == Z_DEFLATED) { diff --git a/thirdparty/minizip/unzip.c b/thirdparty/minizip/unzip.c index 41a203b164..981ba3c0cb 100644 --- a/thirdparty/minizip/unzip.c +++ b/thirdparty/minizip/unzip.c @@ -117,7 +117,7 @@ const char unz_copyright[] = " unzip 1.01 Copyright 1998-2004 Gilles Vollant - http://www.winimage.com/zLibDll"; -/* unz_file_info_interntal contain internal info about a file in zipfile*/ +/* unz_file_info64_internal contain internal info about a file in zipfile*/ typedef struct unz_file_info64_internal_s { ZPOS64_T offset_curfile;/* relative offset of local header 8 bytes */ @@ -453,7 +453,7 @@ local ZPOS64_T unz64local_SearchCentralDir64(const zlib_filefunc64_32_def* pzlib if (unz64local_getLong(pzlib_filefunc_def,filestream,&uL)!=UNZ_OK) return CENTRALDIRINVALID; - /* number of the disk with the start of the zip64 end of central directory */ + /* number of the disk with the start of the zip64 end of central directory */ if (unz64local_getLong(pzlib_filefunc_def,filestream,&uL)!=UNZ_OK) return CENTRALDIRINVALID; if (uL != 0) @@ -500,9 +500,9 @@ local unzFile unzOpenInternal(const void *path, ZPOS64_T central_pos; uLong uL; - uLong number_disk; /* number of the current dist, used for + uLong number_disk; /* number of the current disk, used for spanning ZIP, unsupported, always 0*/ - uLong number_disk_with_CD; /* number the the disk with central dir, used + uLong number_disk_with_CD; /* number the disk with central dir, used for spanning ZIP, unsupported, always 0*/ ZPOS64_T number_entry_CD; /* total number of entries in the central dir diff --git a/thirdparty/minizip/unzip.h b/thirdparty/minizip/unzip.h index 1a477ecb50..0639674574 100644 --- a/thirdparty/minizip/unzip.h +++ b/thirdparty/minizip/unzip.h @@ -310,7 +310,7 @@ extern int ZEXPORT unzGetCurrentFileInfo(unzFile file, Get Info about the current file if pfile_info!=NULL, the *pfile_info structure will contain some info about the current file - if szFileName!=NULL, the filemane string will be copied in szFileName + if szFileName!=NULL, the filename string will be copied in szFileName (fileNameBufferSize is the size of the buffer) if extraField!=NULL, the extra field information will be copied in extraField (extraFieldBufferSize is the size of the buffer). diff --git a/thirdparty/minizip/zip.c b/thirdparty/minizip/zip.c index d76c643dfb..1d2d918e72 100644 --- a/thirdparty/minizip/zip.c +++ b/thirdparty/minizip/zip.c @@ -575,7 +575,7 @@ local ZPOS64_T zip64local_SearchCentralDir64(const zlib_filefunc64_32_def* pzlib if (zip64local_getLong(pzlib_filefunc_def,filestream,&uL)!=ZIP_OK) return 0; - /* number of the disk with the start of the zip64 end of central directory */ + /* number of the disk with the start of the zip64 end of central directory */ if (zip64local_getLong(pzlib_filefunc_def,filestream,&uL)!=ZIP_OK) return 0; if (uL != 0) @@ -614,9 +614,9 @@ local int LoadCentralDirectoryRecord(zip64_internal* pziinit) { ZPOS64_T central_pos; uLong uL; - uLong number_disk; /* number of the current dist, used for + uLong number_disk; /* number of the current disk, used for spanning ZIP, unsupported, always 0*/ - uLong number_disk_with_CD; /* number the the disk with central dir, used + uLong number_disk_with_CD; /* number of the disk with central dir, used for spanning ZIP, unsupported, always 0*/ ZPOS64_T number_entry; ZPOS64_T number_entry_CD; /* total number of entries in @@ -1612,7 +1612,7 @@ extern int ZEXPORT zipCloseFileInZipRaw64(zipFile file, ZPOS64_T uncompressed_si if((uLong)(datasize + 4) > zi->ci.size_centralExtraFree) { - // we can not write more data to the buffer that we have room for. + // we cannot write more data to the buffer that we have room for. return ZIP_BADZIPFILE; } @@ -1876,7 +1876,7 @@ extern int ZEXPORT zipClose(zipFile file, const char* global_comment) { free_linkedlist(&(zi->central_dir)); pos = centraldir_pos_inzip - zi->add_position_when_writing_offset; - if(pos >= 0xffffffff || zi->number_entry > 0xFFFF) + if(pos >= 0xffffffff || zi->number_entry >= 0xFFFF) { ZPOS64_T Zip64EOCDpos = ZTELL64(zi->z_filefunc,zi->filestream); Write_Zip64EndOfCentralDirectoryRecord(zi, size_centraldir, centraldir_pos_inzip); diff --git a/thirdparty/minizip/zip.h b/thirdparty/minizip/zip.h index 5fc0841324..3e230d3405 100644 --- a/thirdparty/minizip/zip.h +++ b/thirdparty/minizip/zip.h @@ -177,9 +177,9 @@ extern int ZEXPORT zipOpenNewFileInZip64(zipFile file, filename : the filename in zip (if NULL, '-' without quote will be used *zipfi contain supplemental information if extrafield_local!=NULL and size_extrafield_local>0, extrafield_local - contains the extrafield data the the local header + contains the extrafield data for the local header if extrafield_global!=NULL and size_extrafield_global>0, extrafield_global - contains the extrafield data the the local header + contains the extrafield data for the global header if comment != NULL, comment contain the comment string method contain the compression method (0 for store, Z_DEFLATED for deflate) level contain the level of compression (can be Z_DEFAULT_COMPRESSION) diff --git a/thirdparty/zlib/deflate.c b/thirdparty/zlib/deflate.c index bd01175192..012ea8148e 100644 --- a/thirdparty/zlib/deflate.c +++ b/thirdparty/zlib/deflate.c @@ -1,5 +1,5 @@ /* deflate.c -- compress data using the deflation algorithm - * Copyright (C) 1995-2023 Jean-loup Gailly and Mark Adler + * Copyright (C) 1995-2024 Jean-loup Gailly and Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -52,7 +52,7 @@ #include "deflate.h" const char deflate_copyright[] = - " deflate 1.3 Copyright 1995-2023 Jean-loup Gailly and Mark Adler "; + " deflate 1.3.1 Copyright 1995-2024 Jean-loup Gailly and Mark Adler "; /* If you use the zlib library in a product, an acknowledgment is welcome in the documentation of your product. If for some reason you cannot @@ -493,7 +493,7 @@ int ZEXPORT deflateInit2_(z_streamp strm, int level, int method, * symbols from which it is being constructed. */ - s->pending_buf = (uchf *) ZALLOC(strm, s->lit_bufsize, 4); + s->pending_buf = (uchf *) ZALLOC(strm, s->lit_bufsize, LIT_BUFS); s->pending_buf_size = (ulg)s->lit_bufsize * 4; if (s->window == Z_NULL || s->prev == Z_NULL || s->head == Z_NULL || @@ -503,8 +503,14 @@ int ZEXPORT deflateInit2_(z_streamp strm, int level, int method, deflateEnd (strm); return Z_MEM_ERROR; } +#ifdef LIT_MEM + s->d_buf = (ushf *)(s->pending_buf + (s->lit_bufsize << 1)); + s->l_buf = s->pending_buf + (s->lit_bufsize << 2); + s->sym_end = s->lit_bufsize - 1; +#else s->sym_buf = s->pending_buf + s->lit_bufsize; s->sym_end = (s->lit_bufsize - 1) * 3; +#endif /* We avoid equality with lit_bufsize*3 because of wraparound at 64K * on 16 bit machines and because stored blocks are restricted to * 64K-1 bytes. @@ -720,9 +726,15 @@ int ZEXPORT deflatePrime(z_streamp strm, int bits, int value) { if (deflateStateCheck(strm)) return Z_STREAM_ERROR; s = strm->state; +#ifdef LIT_MEM + if (bits < 0 || bits > 16 || + (uchf *)s->d_buf < s->pending_out + ((Buf_size + 7) >> 3)) + return Z_BUF_ERROR; +#else if (bits < 0 || bits > 16 || s->sym_buf < s->pending_out + ((Buf_size + 7) >> 3)) return Z_BUF_ERROR; +#endif do { put = Buf_size - s->bi_valid; if (put > bits) @@ -1294,7 +1306,7 @@ int ZEXPORT deflateCopy(z_streamp dest, z_streamp source) { ds->window = (Bytef *) ZALLOC(dest, ds->w_size, 2*sizeof(Byte)); ds->prev = (Posf *) ZALLOC(dest, ds->w_size, sizeof(Pos)); ds->head = (Posf *) ZALLOC(dest, ds->hash_size, sizeof(Pos)); - ds->pending_buf = (uchf *) ZALLOC(dest, ds->lit_bufsize, 4); + ds->pending_buf = (uchf *) ZALLOC(dest, ds->lit_bufsize, LIT_BUFS); if (ds->window == Z_NULL || ds->prev == Z_NULL || ds->head == Z_NULL || ds->pending_buf == Z_NULL) { @@ -1305,10 +1317,15 @@ int ZEXPORT deflateCopy(z_streamp dest, z_streamp source) { zmemcpy(ds->window, ss->window, ds->w_size * 2 * sizeof(Byte)); zmemcpy((voidpf)ds->prev, (voidpf)ss->prev, ds->w_size * sizeof(Pos)); zmemcpy((voidpf)ds->head, (voidpf)ss->head, ds->hash_size * sizeof(Pos)); - zmemcpy(ds->pending_buf, ss->pending_buf, (uInt)ds->pending_buf_size); + zmemcpy(ds->pending_buf, ss->pending_buf, ds->lit_bufsize * LIT_BUFS); ds->pending_out = ds->pending_buf + (ss->pending_out - ss->pending_buf); +#ifdef LIT_MEM + ds->d_buf = (ushf *)(ds->pending_buf + (ds->lit_bufsize << 1)); + ds->l_buf = ds->pending_buf + (ds->lit_bufsize << 2); +#else ds->sym_buf = ds->pending_buf + ds->lit_bufsize; +#endif ds->l_desc.dyn_tree = ds->dyn_ltree; ds->d_desc.dyn_tree = ds->dyn_dtree; @@ -1539,13 +1556,21 @@ local uInt longest_match(deflate_state *s, IPos cur_match) { */ local void check_match(deflate_state *s, IPos start, IPos match, int length) { /* check that the match is indeed a match */ - if (zmemcmp(s->window + match, - s->window + start, length) != EQUAL) { - fprintf(stderr, " start %u, match %u, length %d\n", - start, match, length); + Bytef *back = s->window + (int)match, *here = s->window + start; + IPos len = length; + if (match == (IPos)-1) { + /* match starts one byte before the current window -- just compare the + subsequent length-1 bytes */ + back++; + here++; + len--; + } + if (zmemcmp(back, here, len) != EQUAL) { + fprintf(stderr, " start %u, match %d, length %d\n", + start, (int)match, length); do { - fprintf(stderr, "%c%c", s->window[match++], s->window[start++]); - } while (--length != 0); + fprintf(stderr, "(%02x %02x)", *back++, *here++); + } while (--len != 0); z_error("invalid match"); } if (z_verbose > 1) { diff --git a/thirdparty/zlib/deflate.h b/thirdparty/zlib/deflate.h index 8696791429..300c6ada62 100644 --- a/thirdparty/zlib/deflate.h +++ b/thirdparty/zlib/deflate.h @@ -1,5 +1,5 @@ /* deflate.h -- internal compression state - * Copyright (C) 1995-2018 Jean-loup Gailly + * Copyright (C) 1995-2024 Jean-loup Gailly * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -23,6 +23,10 @@ # define GZIP #endif +/* define LIT_MEM to slightly increase the speed of deflate (order 1% to 2%) at + the cost of a larger memory footprint */ +/* #define LIT_MEM */ + /* =========================================================================== * Internal compression state. */ @@ -217,7 +221,14 @@ typedef struct internal_state { /* Depth of each subtree used as tie breaker for trees of equal frequency */ +#ifdef LIT_MEM +# define LIT_BUFS 5 + ushf *d_buf; /* buffer for distances */ + uchf *l_buf; /* buffer for literals/lengths */ +#else +# define LIT_BUFS 4 uchf *sym_buf; /* buffer for distances and literals/lengths */ +#endif uInt lit_bufsize; /* Size of match buffer for literals/lengths. There are 4 reasons for @@ -239,7 +250,7 @@ typedef struct internal_state { * - I can't count above 4 */ - uInt sym_next; /* running index in sym_buf */ + uInt sym_next; /* running index in symbol buffer */ uInt sym_end; /* symbol table full when sym_next reaches this */ ulg opt_len; /* bit length of current block with optimal trees */ @@ -318,6 +329,25 @@ void ZLIB_INTERNAL _tr_stored_block(deflate_state *s, charf *buf, extern const uch ZLIB_INTERNAL _dist_code[]; #endif +#ifdef LIT_MEM +# define _tr_tally_lit(s, c, flush) \ + { uch cc = (c); \ + s->d_buf[s->sym_next] = 0; \ + s->l_buf[s->sym_next++] = cc; \ + s->dyn_ltree[cc].Freq++; \ + flush = (s->sym_next == s->sym_end); \ + } +# define _tr_tally_dist(s, distance, length, flush) \ + { uch len = (uch)(length); \ + ush dist = (ush)(distance); \ + s->d_buf[s->sym_next] = dist; \ + s->l_buf[s->sym_next++] = len; \ + dist--; \ + s->dyn_ltree[_length_code[len]+LITERALS+1].Freq++; \ + s->dyn_dtree[d_code(dist)].Freq++; \ + flush = (s->sym_next == s->sym_end); \ + } +#else # define _tr_tally_lit(s, c, flush) \ { uch cc = (c); \ s->sym_buf[s->sym_next++] = 0; \ @@ -337,6 +367,7 @@ void ZLIB_INTERNAL _tr_stored_block(deflate_state *s, charf *buf, s->dyn_dtree[d_code(dist)].Freq++; \ flush = (s->sym_next == s->sym_end); \ } +#endif #else # define _tr_tally_lit(s, c, flush) flush = _tr_tally(s, 0, c) # define _tr_tally_dist(s, distance, length, flush) \ diff --git a/thirdparty/zlib/gzguts.h b/thirdparty/zlib/gzguts.h index f9375047e8..eba72085bb 100644 --- a/thirdparty/zlib/gzguts.h +++ b/thirdparty/zlib/gzguts.h @@ -1,5 +1,5 @@ /* gzguts.h -- zlib internal header definitions for gz* operations - * Copyright (C) 2004-2019 Mark Adler + * Copyright (C) 2004-2024 Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -210,9 +210,5 @@ char ZLIB_INTERNAL *gz_strwinerror(DWORD error); /* GT_OFF(x), where x is an unsigned value, is true if x > maximum z_off64_t value -- needed when comparing unsigned to z_off64_t, which is signed (possible z_off64_t types off_t, off64_t, and long are all signed) */ -#ifdef INT_MAX -# define GT_OFF(x) (sizeof(int) == sizeof(z_off64_t) && (x) > INT_MAX) -#else unsigned ZLIB_INTERNAL gz_intmax(void); -# define GT_OFF(x) (sizeof(int) == sizeof(z_off64_t) && (x) > gz_intmax()) -#endif +#define GT_OFF(x) (sizeof(int) == sizeof(z_off64_t) && (x) > gz_intmax()) diff --git a/thirdparty/zlib/gzlib.c b/thirdparty/zlib/gzlib.c index 29fc4486fb..983153cc8e 100644 --- a/thirdparty/zlib/gzlib.c +++ b/thirdparty/zlib/gzlib.c @@ -1,5 +1,5 @@ /* gzlib.c -- zlib functions common to reading and writing gzip files - * Copyright (C) 2004-2019 Mark Adler + * Copyright (C) 2004-2024 Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -563,20 +563,20 @@ void ZLIB_INTERNAL gz_error(gz_statep state, int err, const char *msg) { #endif } -#ifndef INT_MAX /* portably return maximum value for an int (when limits.h presumed not available) -- we need to do this to cover cases where 2's complement not used, since C standard permits 1's complement and sign-bit representations, otherwise we could just use ((unsigned)-1) >> 1 */ unsigned ZLIB_INTERNAL gz_intmax(void) { - unsigned p, q; - - p = 1; +#ifdef INT_MAX + return INT_MAX; +#else + unsigned p = 1, q; do { q = p; p <<= 1; p++; } while (p > q); return q >> 1; -} #endif +} diff --git a/thirdparty/zlib/inflate.c b/thirdparty/zlib/inflate.c index b0757a9b24..94ecff015a 100644 --- a/thirdparty/zlib/inflate.c +++ b/thirdparty/zlib/inflate.c @@ -1387,7 +1387,7 @@ int ZEXPORT inflateSync(z_streamp strm) { /* if first time, start search in bit buffer */ if (state->mode != SYNC) { state->mode = SYNC; - state->hold <<= state->bits & 7; + state->hold >>= state->bits & 7; state->bits -= state->bits & 7; len = 0; while (state->bits >= 8) { diff --git a/thirdparty/zlib/inftrees.c b/thirdparty/zlib/inftrees.c index 8a208c2daa..98cfe16445 100644 --- a/thirdparty/zlib/inftrees.c +++ b/thirdparty/zlib/inftrees.c @@ -1,5 +1,5 @@ /* inftrees.c -- generate Huffman trees for efficient decoding - * Copyright (C) 1995-2023 Mark Adler + * Copyright (C) 1995-2024 Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -9,7 +9,7 @@ #define MAXBITS 15 const char inflate_copyright[] = - " inflate 1.3 Copyright 1995-2023 Mark Adler "; + " inflate 1.3.1 Copyright 1995-2024 Mark Adler "; /* If you use the zlib library in a product, an acknowledgment is welcome in the documentation of your product. If for some reason you cannot @@ -57,7 +57,7 @@ int ZLIB_INTERNAL inflate_table(codetype type, unsigned short FAR *lens, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0}; static const unsigned short lext[31] = { /* Length codes 257..285 extra */ 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, - 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 198, 203}; + 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 203, 77}; static const unsigned short dbase[32] = { /* Distance codes 0..29 base */ 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, diff --git a/thirdparty/zlib/inftrees.h b/thirdparty/zlib/inftrees.h index a10712d8cb..396f74b5da 100644 --- a/thirdparty/zlib/inftrees.h +++ b/thirdparty/zlib/inftrees.h @@ -41,8 +41,8 @@ typedef struct { examples/enough.c found in the zlib distribution. The arguments to that program are the number of symbols, the initial root table size, and the maximum bit length of a code. "enough 286 9 15" for literal/length codes - returns returns 852, and "enough 30 6 15" for distance codes returns 592. - The initial root table size (9 or 6) is found in the fifth argument of the + returns 852, and "enough 30 6 15" for distance codes returns 592. The + initial root table size (9 or 6) is found in the fifth argument of the inflate_table() calls in inflate.c and infback.c. If the root table size is changed, then these maximum sizes would be need to be recalculated and updated. */ diff --git a/thirdparty/zlib/trees.c b/thirdparty/zlib/trees.c index 8dbdc40bac..6a523ef34e 100644 --- a/thirdparty/zlib/trees.c +++ b/thirdparty/zlib/trees.c @@ -1,5 +1,5 @@ /* trees.c -- output deflated data using Huffman coding - * Copyright (C) 1995-2021 Jean-loup Gailly + * Copyright (C) 1995-2024 Jean-loup Gailly * detect_data_type() function provided freely by Cosmin Truta, 2006 * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -899,14 +899,19 @@ local void compress_block(deflate_state *s, const ct_data *ltree, const ct_data *dtree) { unsigned dist; /* distance of matched string */ int lc; /* match length or unmatched char (if dist == 0) */ - unsigned sx = 0; /* running index in sym_buf */ + unsigned sx = 0; /* running index in symbol buffers */ unsigned code; /* the code to send */ int extra; /* number of extra bits to send */ if (s->sym_next != 0) do { +#ifdef LIT_MEM + dist = s->d_buf[sx]; + lc = s->l_buf[sx++]; +#else dist = s->sym_buf[sx++] & 0xff; dist += (unsigned)(s->sym_buf[sx++] & 0xff) << 8; lc = s->sym_buf[sx++]; +#endif if (dist == 0) { send_code(s, lc, ltree); /* send a literal byte */ Tracecv(isgraph(lc), (stderr," '%c' ", lc)); @@ -931,8 +936,12 @@ local void compress_block(deflate_state *s, const ct_data *ltree, } } /* literal or match pair ? */ - /* Check that the overlay between pending_buf and sym_buf is ok: */ + /* Check for no overlay of pending_buf on needed symbols */ +#ifdef LIT_MEM + Assert(s->pending < 2 * (s->lit_bufsize + sx), "pendingBuf overflow"); +#else Assert(s->pending < s->lit_bufsize + sx, "pendingBuf overflow"); +#endif } while (sx < s->sym_next); @@ -1082,9 +1091,14 @@ void ZLIB_INTERNAL _tr_flush_block(deflate_state *s, charf *buf, * the current block must be flushed. */ int ZLIB_INTERNAL _tr_tally(deflate_state *s, unsigned dist, unsigned lc) { +#ifdef LIT_MEM + s->d_buf[s->sym_next] = (ush)dist; + s->l_buf[s->sym_next++] = (uch)lc; +#else s->sym_buf[s->sym_next++] = (uch)dist; s->sym_buf[s->sym_next++] = (uch)(dist >> 8); s->sym_buf[s->sym_next++] = (uch)lc; +#endif if (dist == 0) { /* lc is the unmatched char */ s->dyn_ltree[lc].Freq++; diff --git a/thirdparty/zlib/zconf.h b/thirdparty/zlib/zconf.h index fb76ffe312..62adc8d843 100644 --- a/thirdparty/zlib/zconf.h +++ b/thirdparty/zlib/zconf.h @@ -1,5 +1,5 @@ /* zconf.h -- configuration of the zlib compression library - * Copyright (C) 1995-2016 Jean-loup Gailly, Mark Adler + * Copyright (C) 1995-2024 Jean-loup Gailly, Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -300,14 +300,6 @@ # endif #endif -#ifndef Z_ARG /* function prototypes for stdarg */ -# if defined(STDC) || defined(Z_HAVE_STDARG_H) -# define Z_ARG(args) args -# else -# define Z_ARG(args) () -# endif -#endif - /* The following definitions for FAR are needed only for MSDOS mixed * model programming (small or medium model with some far allocations). * This was tested only with MSC; for other MSDOS compilers you may have diff --git a/thirdparty/zlib/zlib.h b/thirdparty/zlib/zlib.h index 6b7244f994..8d4b932eaf 100644 --- a/thirdparty/zlib/zlib.h +++ b/thirdparty/zlib/zlib.h @@ -1,7 +1,7 @@ /* zlib.h -- interface of the 'zlib' general purpose compression library - version 1.3, August 18th, 2023 + version 1.3.1, January 22nd, 2024 - Copyright (C) 1995-2023 Jean-loup Gailly and Mark Adler + Copyright (C) 1995-2024 Jean-loup Gailly and Mark Adler This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -37,11 +37,11 @@ extern "C" { #endif -#define ZLIB_VERSION "1.3" -#define ZLIB_VERNUM 0x1300 +#define ZLIB_VERSION "1.3.1" +#define ZLIB_VERNUM 0x1310 #define ZLIB_VER_MAJOR 1 #define ZLIB_VER_MINOR 3 -#define ZLIB_VER_REVISION 0 +#define ZLIB_VER_REVISION 1 #define ZLIB_VER_SUBREVISION 0 /* @@ -936,10 +936,10 @@ ZEXTERN int ZEXPORT inflateSync(z_streamp strm); inflateSync returns Z_OK if a possible full flush point has been found, Z_BUF_ERROR if no more input was provided, Z_DATA_ERROR if no flush point has been found, or Z_STREAM_ERROR if the stream structure was inconsistent. - In the success case, the application may save the current current value of - total_in which indicates where valid compressed data was found. In the - error case, the application may repeatedly call inflateSync, providing more - input each time, until success or end of the input data. + In the success case, the application may save the current value of total_in + which indicates where valid compressed data was found. In the error case, + the application may repeatedly call inflateSync, providing more input each + time, until success or end of the input data. */ ZEXTERN int ZEXPORT inflateCopy(z_streamp dest, @@ -1758,14 +1758,14 @@ ZEXTERN uLong ZEXPORT crc32_combine(uLong crc1, uLong crc2, z_off_t len2); seq1 and seq2 with lengths len1 and len2, CRC-32 check values were calculated for each, crc1 and crc2. crc32_combine() returns the CRC-32 check value of seq1 and seq2 concatenated, requiring only crc1, crc2, and - len2. + len2. len2 must be non-negative. */ /* ZEXTERN uLong ZEXPORT crc32_combine_gen(z_off_t len2); Return the operator corresponding to length len2, to be used with - crc32_combine_op(). + crc32_combine_op(). len2 must be non-negative. */ ZEXTERN uLong ZEXPORT crc32_combine_op(uLong crc1, uLong crc2, uLong op); diff --git a/thirdparty/zlib/zutil.h b/thirdparty/zlib/zutil.h index 902a304cc2..48dd7febae 100644 --- a/thirdparty/zlib/zutil.h +++ b/thirdparty/zlib/zutil.h @@ -1,5 +1,5 @@ /* zutil.h -- internal interface and configuration of the compression library - * Copyright (C) 1995-2022 Jean-loup Gailly, Mark Adler + * Copyright (C) 1995-2024 Jean-loup Gailly, Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -56,7 +56,7 @@ typedef unsigned long ulg; extern z_const char * const z_errmsg[10]; /* indexed by 2-zlib_error */ /* (size given to avoid silly warnings with Visual C++) */ -#define ERR_MSG(err) z_errmsg[Z_NEED_DICT-(err)] +#define ERR_MSG(err) z_errmsg[(err) < -6 || (err) > 2 ? 9 : 2 - (err)] #define ERR_RETURN(strm,err) \ return (strm->msg = ERR_MSG(err), (err)) @@ -137,17 +137,8 @@ extern z_const char * const z_errmsg[10]; /* indexed by 2-zlib_error */ # endif #endif -#if defined(MACOS) || defined(TARGET_OS_MAC) +#if defined(MACOS) # define OS_CODE 7 -# ifndef Z_SOLO -# if defined(__MWERKS__) && __dest_os != __be_os && __dest_os != __win32_os -# include <unix.h> /* for fdopen */ -# else -# ifndef fdopen -# define fdopen(fd,mode) NULL /* No fdopen() */ -# endif -# endif -# endif #endif #ifdef __acorn @@ -170,18 +161,6 @@ extern z_const char * const z_errmsg[10]; /* indexed by 2-zlib_error */ # define OS_CODE 19 #endif -#if defined(_BEOS_) || defined(RISCOS) -# define fdopen(fd,mode) NULL /* No fdopen() */ -#endif - -#if (defined(_MSC_VER) && (_MSC_VER > 600)) && !defined __INTERIX -# if defined(_WIN32_WCE) -# define fdopen(fd,mode) NULL /* No fdopen() */ -# else -# define fdopen(fd,type) _fdopen(fd,type) -# endif -#endif - #if defined(__BORLANDC__) && !defined(MSDOS) #pragma warn -8004 #pragma warn -8008 diff --git a/version.py b/version.py index 99e56eca90..e915a6e530 100644 --- a/version.py +++ b/version.py @@ -5,6 +5,5 @@ minor = 3 patch = 0 status = "dev" module_config = "" -year = 2023 website = "https://godotengine.org" docs = "latest" |