diff options
91 files changed, 1464 insertions, 1137 deletions
diff --git a/.github/workflows/windows_builds.yml b/.github/workflows/windows_builds.yml index 2794c83e22..18ed92b57f 100644 --- a/.github/workflows/windows_builds.yml +++ b/.github/workflows/windows_builds.yml @@ -28,7 +28,7 @@ jobs: target: editor tests: true # Skip debug symbols, they're way too big with MSVC. - sconsflags: debug_symbols=no vsproj=yes windows_subsystem=console + sconsflags: debug_symbols=no vsproj=yes vsproj_gen_only=no windows_subsystem=console bin: "./bin/godot.windows.editor.x86_64.exe" - name: Template (target=template_release) diff --git a/.gitignore b/.gitignore index 3c6f279a9c..f43b81f286 100644 --- a/.gitignore +++ b/.gitignore @@ -132,6 +132,10 @@ cppcheck-cppcheck-build-dir/ *.pydevproject *.launch +# Emacs +\#*\# +.\#* + # GCOV code coverage *.gcda *.gcno @@ -367,3 +371,4 @@ $RECYCLE.BIN/ *.msm *.msp *.lnk +*.generated.props diff --git a/SConstruct b/SConstruct index 6a4dea2c09..f0f53ddc65 100644 --- a/SConstruct +++ b/SConstruct @@ -1000,9 +1000,6 @@ if selected_platform in platform_list: # Microsoft Visual Studio Project Generation if env["vsproj"]: - if os.name != "nt": - print("Error: The `vsproj` option is only usable on Windows with Visual Studio.") - Exit(255) env["CPPPATH"] = [Dir(path) for path in env["CPPPATH"]] methods.generate_vs_project(env, ARGUMENTS, env["vsproj_name"]) methods.generate_cpp_hint_file("cpp.hint") diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index 90e2e27320..329af9068e 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -1410,8 +1410,8 @@ ProjectSettings::ProjectSettings() { GLOBAL_DEF("display/window/size/extend_to_title", false); GLOBAL_DEF("display/window/size/no_focus", false); - GLOBAL_DEF(PropertyInfo(Variant::INT, "display/window/size/window_width_override", PROPERTY_HINT_RANGE, "1,7680,1,or_greater"), 0); // 8K resolution - GLOBAL_DEF(PropertyInfo(Variant::INT, "display/window/size/window_height_override", PROPERTY_HINT_RANGE, "1,4320,1,or_greater"), 0); // 8K resolution + GLOBAL_DEF(PropertyInfo(Variant::INT, "display/window/size/window_width_override", PROPERTY_HINT_RANGE, "0,7680,1,or_greater"), 0); // 8K resolution + GLOBAL_DEF(PropertyInfo(Variant::INT, "display/window/size/window_height_override", PROPERTY_HINT_RANGE, "0,4320,1,or_greater"), 0); // 8K resolution GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true); GLOBAL_DEF("display/window/energy_saving/keep_screen_on.editor", false); diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp index 2904e54b22..aba96befd6 100644 --- a/core/extension/gdextension.cpp +++ b/core/extension/gdextension.cpp @@ -794,6 +794,9 @@ void GDExtension::deinitialize_library(InitializationLevel p_level) { ERR_FAIL_COND(p_level > int32_t(level_initialized)); level_initialized = int32_t(p_level) - 1; + + ERR_FAIL_NULL(initialization.deinitialize); + initialization.deinitialize(initialization.userdata, GDExtensionInitializationLevel(p_level)); } diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp index 197c8813a7..2fb3bda87d 100644 --- a/core/io/resource_loader.cpp +++ b/core/io/resource_loader.cpp @@ -36,6 +36,7 @@ #include "core/object/script_language.h" #include "core/os/condition_variable.h" #include "core/os/os.h" +#include "core/os/safe_binary_mutex.h" #include "core/string/print_string.h" #include "core/string/translation.h" #include "core/variant/variant_parser.h" diff --git a/core/io/resource_loader.h b/core/io/resource_loader.h index 0ab81cd0a8..3099d9aab3 100644 --- a/core/io/resource_loader.h +++ b/core/io/resource_loader.h @@ -39,6 +39,9 @@ class ConditionVariable; +template <int Tag> +class SafeBinaryMutex; + class ResourceFormatLoader : public RefCounted { GDCLASS(ResourceFormatLoader, RefCounted); diff --git a/core/object/object.cpp b/core/object/object.cpp index 3901c4835d..cc33d0ab8a 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -1683,6 +1683,7 @@ void Object::_bind_methods() { BIND_CONSTANT(NOTIFICATION_POSTINITIALIZE); BIND_CONSTANT(NOTIFICATION_PREDELETE); + BIND_CONSTANT(NOTIFICATION_EXTENSION_RELOADED); BIND_ENUM_CONSTANT(CONNECT_DEFERRED); BIND_ENUM_CONSTANT(CONNECT_PERSIST); diff --git a/core/os/mutex.h b/core/os/mutex.h index 69f494d9cd..a4eab0cd86 100644 --- a/core/os/mutex.h +++ b/core/os/mutex.h @@ -70,56 +70,6 @@ public: } }; -// A very special kind of mutex, used in scenarios where these -// requirements hold at the same time: -// - Must be used with a condition variable (only binary mutexes are suitable). -// - Must have recursive semnantics (or simulate, as this one does). -// The implementation keeps the lock count in TS. Therefore, only -// one object of each version of the template can exists; hence the Tag argument. -// Tags must be unique across the Godot codebase. -// Also, don't forget to declare the thread_local variable on each use. -template <int Tag> -class SafeBinaryMutex { - friend class MutexLock<SafeBinaryMutex>; - - using StdMutexType = THREADING_NAMESPACE::mutex; - - mutable THREADING_NAMESPACE::mutex mutex; - static thread_local uint32_t count; - -public: - _ALWAYS_INLINE_ void lock() const { - if (++count == 1) { - mutex.lock(); - } - } - - _ALWAYS_INLINE_ void unlock() const { - DEV_ASSERT(count); - if (--count == 0) { - mutex.unlock(); - } - } - - _ALWAYS_INLINE_ bool try_lock() const { - if (count) { - count++; - return true; - } else { - if (mutex.try_lock()) { - count++; - return true; - } else { - return false; - } - } - } - - ~SafeBinaryMutex() { - DEV_ASSERT(!count); - } -}; - template <class MutexT> class MutexLock { friend class ConditionVariable; @@ -131,24 +81,6 @@ public: lock(p_mutex.mutex) {} }; -// This specialization is needed so manual locking and MutexLock can be used -// at the same time on a SafeBinaryMutex. -template <int Tag> -class MutexLock<SafeBinaryMutex<Tag>> { - friend class ConditionVariable; - - THREADING_NAMESPACE::unique_lock<THREADING_NAMESPACE::mutex> lock; - -public: - _ALWAYS_INLINE_ explicit MutexLock(const SafeBinaryMutex<Tag> &p_mutex) : - lock(p_mutex.mutex) { - SafeBinaryMutex<Tag>::count++; - }; - _ALWAYS_INLINE_ ~MutexLock() { - SafeBinaryMutex<Tag>::count--; - }; -}; - using Mutex = MutexImpl<THREADING_NAMESPACE::recursive_mutex>; // Recursive, for general use using BinaryMutex = MutexImpl<THREADING_NAMESPACE::mutex>; // Non-recursive, handle with care @@ -168,24 +100,12 @@ public: bool try_lock() const { return true; } }; -template <int Tag> -class SafeBinaryMutex : public MutexImpl { - static thread_local uint32_t count; -}; - template <class MutexT> class MutexLock { public: MutexLock(const MutexT &p_mutex) {} }; -template <int Tag> -class MutexLock<SafeBinaryMutex<Tag>> { -public: - MutexLock(const SafeBinaryMutex<Tag> &p_mutex) {} - ~MutexLock() {} -}; - using Mutex = MutexImpl; using BinaryMutex = MutexImpl; diff --git a/core/os/safe_binary_mutex.h b/core/os/safe_binary_mutex.h new file mode 100644 index 0000000000..1e98cc074c --- /dev/null +++ b/core/os/safe_binary_mutex.h @@ -0,0 +1,124 @@ +/**************************************************************************/ +/* safe_binary_mutex.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 SAFE_BINARY_MUTEX_H +#define SAFE_BINARY_MUTEX_H + +#include "core/error/error_macros.h" +#include "core/os/mutex.h" +#include "core/typedefs.h" + +#ifdef THREADS_ENABLED + +// A very special kind of mutex, used in scenarios where these +// requirements hold at the same time: +// - Must be used with a condition variable (only binary mutexes are suitable). +// - Must have recursive semnantics (or simulate, as this one does). +// The implementation keeps the lock count in TS. Therefore, only +// one object of each version of the template can exists; hence the Tag argument. +// Tags must be unique across the Godot codebase. +// Also, don't forget to declare the thread_local variable on each use. +template <int Tag> +class SafeBinaryMutex { + friend class MutexLock<SafeBinaryMutex>; + + using StdMutexType = THREADING_NAMESPACE::mutex; + + mutable THREADING_NAMESPACE::mutex mutex; + static thread_local uint32_t count; + +public: + _ALWAYS_INLINE_ void lock() const { + if (++count == 1) { + mutex.lock(); + } + } + + _ALWAYS_INLINE_ void unlock() const { + DEV_ASSERT(count); + if (--count == 0) { + mutex.unlock(); + } + } + + _ALWAYS_INLINE_ bool try_lock() const { + if (count) { + count++; + return true; + } else { + if (mutex.try_lock()) { + count++; + return true; + } else { + return false; + } + } + } + + ~SafeBinaryMutex() { + DEV_ASSERT(!count); + } +}; + +// This specialization is needed so manual locking and MutexLock can be used +// at the same time on a SafeBinaryMutex. +template <int Tag> +class MutexLock<SafeBinaryMutex<Tag>> { + friend class ConditionVariable; + + THREADING_NAMESPACE::unique_lock<THREADING_NAMESPACE::mutex> lock; + +public: + _ALWAYS_INLINE_ explicit MutexLock(const SafeBinaryMutex<Tag> &p_mutex) : + lock(p_mutex.mutex) { + SafeBinaryMutex<Tag>::count++; + }; + _ALWAYS_INLINE_ ~MutexLock() { + SafeBinaryMutex<Tag>::count--; + }; +}; + +#else // No threads. + +template <int Tag> +class SafeBinaryMutex : public MutexImpl { + static thread_local uint32_t count; +}; + +template <int Tag> +class MutexLock<SafeBinaryMutex<Tag>> { +public: + MutexLock(const SafeBinaryMutex<Tag> &p_mutex) {} + ~MutexLock() {} +}; + +#endif // THREADS_ENABLED + +#endif // SAFE_BINARY_MUTEX_H diff --git a/doc/classes/Callable.xml b/doc/classes/Callable.xml index 3550a6b7bd..8dd0cfa135 100644 --- a/doc/classes/Callable.xml +++ b/doc/classes/Callable.xml @@ -98,10 +98,18 @@ <return type="void" /> <description> Calls the method represented by this [Callable] in deferred mode, i.e. at the end of the current frame. Arguments can be passed and should match the method's signature. - [codeblock] + [codeblocks] + [gdscript] func _ready(): grab_focus.call_deferred() - [/codeblock] + [/gdscript] + [csharp] + public override void _Ready() + { + Callable.From(GrabFocus).CallDeferred(); + } + [/csharp] + [/codeblocks] See also [method Object.call_deferred]. </description> </method> diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml index b824bc4fde..3be64e2f62 100644 --- a/doc/classes/DisplayServer.xml +++ b/doc/classes/DisplayServer.xml @@ -1713,7 +1713,7 @@ Display server supports setting the mouse cursor shape to a custom image. [b]Windows, macOS, Linux (X11/Wayland), Web[/b] </constant> <constant name="FEATURE_NATIVE_DIALOG" value="9" enum="Feature"> - Display server supports spawning dialogs using the operating system's native look-and-feel. [b]macOS[/b] + Display server supports spawning dialogs using the operating system's native look-and-feel. [b]Windows, macOS, Linux (X11/Wayland)[/b] </constant> <constant name="FEATURE_IME" value="10" enum="Feature"> Display server supports [url=https://en.wikipedia.org/wiki/Input_method]Input Method Editor[/url], which is commonly used for inputting Chinese/Japanese/Korean text. This is handled by the operating system, rather than by Godot. [b]Windows, macOS, Linux (X11)[/b] diff --git a/doc/classes/EditorExportPlatform.xml b/doc/classes/EditorExportPlatform.xml index 6801ac672c..0e5de79b25 100644 --- a/doc/classes/EditorExportPlatform.xml +++ b/doc/classes/EditorExportPlatform.xml @@ -8,7 +8,7 @@ Used in scripting by [EditorExportPlugin] to configure platform-specific customization of scenes and resources. See [method EditorExportPlugin._begin_customize_scenes] and [method EditorExportPlugin._begin_customize_resources] for more details. </description> <tutorials> - <link title="$DOCS_URL/tutorials/platform/consoles.html">Console support in Godot</link> + <link title="Console support in Godot">$DOCS_URL/tutorials/platform/consoles.html</link> </tutorials> <methods> <method name="get_os_name" qualifiers="const"> diff --git a/doc/classes/HeightMapShape3D.xml b/doc/classes/HeightMapShape3D.xml index ff120fa557..ba79cbc89a 100644 --- a/doc/classes/HeightMapShape3D.xml +++ b/doc/classes/HeightMapShape3D.xml @@ -9,9 +9,23 @@ </description> <tutorials> </tutorials> + <methods> + <method name="get_max_height" qualifiers="const"> + <return type="float" /> + <description> + Returns the largest height value found in [member map_data]. Recalculates only when [member map_data] changes. + </description> + </method> + <method name="get_min_height" qualifiers="const"> + <return type="float" /> + <description> + Returns the smallest height value found in [member map_data]. Recalculates only when [member map_data] changes. + </description> + </method> + </methods> <members> <member name="map_data" type="PackedFloat32Array" setter="set_map_data" getter="get_map_data" default="PackedFloat32Array(0, 0, 0, 0)"> - Height map data, pool array must be of [member map_width] * [member map_depth] size. + Height map data. The array's size must be equal to [member map_width] multiplied by [member map_depth]. </member> <member name="map_depth" type="int" setter="set_map_depth" getter="get_map_depth" default="2"> Number of vertices in the depth of the height map. Changing this will resize the [member map_data]. diff --git a/doc/classes/Object.xml b/doc/classes/Object.xml index 5fa43f868e..f66709cc5d 100644 --- a/doc/classes/Object.xml +++ b/doc/classes/Object.xml @@ -1056,6 +1056,9 @@ <constant name="NOTIFICATION_PREDELETE" value="1"> Notification received when the object is about to be deleted. Can act as the deconstructor of some programming languages. </constant> + <constant name="NOTIFICATION_EXTENSION_RELOADED" value="2"> + Notification received when the object finishes hot reloading. This notification is only sent for extensions classes and derived. + </constant> <constant name="CONNECT_DEFERRED" value="1" enum="ConnectFlags"> Deferred connections trigger their [Callable]s on idle time (at the end of the frame), rather than instantly. </constant> diff --git a/doc/classes/PhysicsBody2D.xml b/doc/classes/PhysicsBody2D.xml index adfdfdc445..eef159d44d 100644 --- a/doc/classes/PhysicsBody2D.xml +++ b/doc/classes/PhysicsBody2D.xml @@ -23,6 +23,12 @@ Returns an array of nodes that were added as collision exceptions for this body. </description> </method> + <method name="get_gravity" qualifiers="const"> + <return type="Vector2" /> + <description> + Returns the gravity vector computed from all sources that can affect the body, including all gravity overrides from [Area2D] nodes and the global world gravity. + </description> + </method> <method name="move_and_collide"> <return type="KinematicCollision2D" /> <param index="0" name="motion" type="Vector2" /> diff --git a/doc/classes/PhysicsBody3D.xml b/doc/classes/PhysicsBody3D.xml index ff994fe6c5..866b3e298c 100644 --- a/doc/classes/PhysicsBody3D.xml +++ b/doc/classes/PhysicsBody3D.xml @@ -31,6 +31,12 @@ Returns an array of nodes that were added as collision exceptions for this body. </description> </method> + <method name="get_gravity" qualifiers="const"> + <return type="Vector3" /> + <description> + Returns the gravity vector computed from all sources that can affect the body, including all gravity overrides from [Area3D] nodes and the global world gravity. + </description> + </method> <method name="move_and_collide"> <return type="KinematicCollision3D" /> <param index="0" name="motion" type="Vector3" /> diff --git a/doc/classes/XRInterface.xml b/doc/classes/XRInterface.xml index 00ce6e2a8b..6fb43d77d9 100644 --- a/doc/classes/XRInterface.xml +++ b/doc/classes/XRInterface.xml @@ -102,16 +102,18 @@ Is [code]true[/code] if this interface has been initialized. </description> </method> - <method name="is_passthrough_enabled"> + <method name="is_passthrough_enabled" is_deprecated="true"> <return type="bool" /> <description> Is [code]true[/code] if passthrough is enabled. + [i]Deprecated.[/i] Check if [member environment_blend_mode] is [constant XRInterface.XR_ENV_BLEND_MODE_ALPHA_BLEND], instead. </description> </method> - <method name="is_passthrough_supported"> + <method name="is_passthrough_supported" is_deprecated="true"> <return type="bool" /> <description> Is [code]true[/code] if this interface supports passthrough. + [i]Deprecated.[/i] Check that [constant XRInterface.XR_ENV_BLEND_MODE_ALPHA_BLEND] is supported using [method get_supported_environment_blend_modes], instead. </description> </method> <method name="set_environment_blend_mode"> @@ -144,17 +146,19 @@ [b]Note:[/b] Changing this after the interface has already been initialized can be jarring for the player, so it's recommended to recenter on the HMD with [method XRServer.center_on_hmd] (if switching to [constant XRInterface.XR_PLAY_AREA_STAGE]) or make the switch during a scene change. </description> </method> - <method name="start_passthrough"> + <method name="start_passthrough" is_deprecated="true"> <return type="bool" /> <description> Starts passthrough, will return [code]false[/code] if passthrough couldn't be started. [b]Note:[/b] The viewport used for XR must have a transparent background, otherwise passthrough may not properly render. + [i]Deprecated.[/i] Set the [member environment_blend_mode] to [constant XRInterface.XR_ENV_BLEND_MODE_ALPHA_BLEND], instead. </description> </method> - <method name="stop_passthrough"> + <method name="stop_passthrough" is_deprecated="true"> <return type="void" /> <description> Stops passthrough. + [i]Deprecated.[/i] Set the [member environment_blend_mode] to [constant XRInterface.XR_ENV_BLEND_MODE_OPAQUE], instead. </description> </method> <method name="supports_play_area_mode"> diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index 0711bbe84e..b97729db7b 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -2816,53 +2816,53 @@ void EditorInspector::update_tree() { // `hint_script` should contain a native class name or a script path. // Otherwise the category was probably added via `@export_category` or `_get_property_list()`. + // Do not add an icon, do not change the current class (`doc_name`) for custom categories. if (p.hint_string.is_empty()) { category->label = p.name; category->set_tooltip_text(p.name); - continue; // Do not add an icon, do not change the current class (`doc_name`). - } + } else { + String type = p.name; + String label = p.name; + doc_name = p.name; - String type = p.name; - String label = p.name; - doc_name = p.name; - - // Use category's owner script to update some of its information. - if (!EditorNode::get_editor_data().is_type_recognized(type) && p.hint_string.length() && ResourceLoader::exists(p.hint_string)) { - Ref<Script> scr = ResourceLoader::load(p.hint_string, "Script"); - if (scr.is_valid()) { - StringName script_name = EditorNode::get_editor_data().script_class_get_name(scr->get_path()); - - // Update the docs reference and the label based on the script. - Vector<DocData::ClassDoc> docs = scr->get_documentation(); - if (!docs.is_empty()) { - // The documentation of a GDScript's main class is at the end of the array. - // Hacky because this isn't necessarily always guaranteed. - doc_name = docs[docs.size() - 1].name; - } - if (script_name != StringName()) { - label = script_name; - } + // Use category's owner script to update some of its information. + if (!EditorNode::get_editor_data().is_type_recognized(type) && ResourceLoader::exists(p.hint_string)) { + Ref<Script> scr = ResourceLoader::load(p.hint_string, "Script"); + if (scr.is_valid()) { + StringName script_name = EditorNode::get_editor_data().script_class_get_name(scr->get_path()); - // Find the icon corresponding to the script. - if (script_name != StringName()) { - category->icon = EditorNode::get_singleton()->get_class_icon(script_name); - } else { - category->icon = EditorNode::get_singleton()->get_object_icon(scr.ptr(), "Object"); + // Update the docs reference and the label based on the script. + Vector<DocData::ClassDoc> docs = scr->get_documentation(); + if (!docs.is_empty()) { + // The documentation of a GDScript's main class is at the end of the array. + // Hacky because this isn't necessarily always guaranteed. + doc_name = docs[docs.size() - 1].name; + } + if (script_name != StringName()) { + label = script_name; + } + + // Find the icon corresponding to the script. + if (script_name != StringName()) { + category->icon = EditorNode::get_singleton()->get_class_icon(script_name); + } else { + category->icon = EditorNode::get_singleton()->get_object_icon(scr.ptr(), "Object"); + } } } - } - if (category->icon.is_null() && !type.is_empty()) { - category->icon = EditorNode::get_singleton()->get_class_icon(type); - } + if (category->icon.is_null() && !type.is_empty()) { + category->icon = EditorNode::get_singleton()->get_class_icon(type); + } - // Set the category label. - category->label = label; - category->doc_class_name = doc_name; + // Set the category label. + category->label = label; + category->doc_class_name = doc_name; - if (use_doc_hints) { - // `|` separator used in `EditorHelpTooltip` for formatting. - category->set_tooltip_text("class|" + doc_name + "||"); + if (use_doc_hints) { + // `|` separator used in `EditorHelpTooltip` for formatting. + category->set_tooltip_text("class|" + doc_name + "||"); + } } // Add editors at the start of a category. diff --git a/editor/plugins/animation_blend_space_1d_editor.cpp b/editor/plugins/animation_blend_space_1d_editor.cpp index 5875b46c19..92b3569c06 100644 --- a/editor/plugins/animation_blend_space_1d_editor.cpp +++ b/editor/plugins/animation_blend_space_1d_editor.cpp @@ -804,6 +804,7 @@ AnimationNodeBlendSpace1DEditor::AnimationNodeBlendSpace1DEditor() { animations_menu = memnew(PopupMenu); menu->add_child(animations_menu); animations_menu->set_name("AddAnimations"); + animations_menu->set_auto_translate(false); animations_menu->connect("index_pressed", callable_mp(this, &AnimationNodeBlendSpace1DEditor::_add_animation_type)); open_file = memnew(EditorFileDialog); diff --git a/editor/plugins/animation_blend_space_2d_editor.cpp b/editor/plugins/animation_blend_space_2d_editor.cpp index 9d922f4608..9e82368a71 100644 --- a/editor/plugins/animation_blend_space_2d_editor.cpp +++ b/editor/plugins/animation_blend_space_2d_editor.cpp @@ -1083,6 +1083,7 @@ AnimationNodeBlendSpace2DEditor::AnimationNodeBlendSpace2DEditor() { animations_menu = memnew(PopupMenu); menu->add_child(animations_menu); animations_menu->set_name("AddAnimations"); + animations_menu->set_auto_translate(false); animations_menu->connect("index_pressed", callable_mp(this, &AnimationNodeBlendSpace2DEditor::_add_animation_type)); open_file = memnew(EditorFileDialog); diff --git a/editor/plugins/animation_blend_tree_editor_plugin.cpp b/editor/plugins/animation_blend_tree_editor_plugin.cpp index 8786c4cb20..99151ac760 100644 --- a/editor/plugins/animation_blend_tree_editor_plugin.cpp +++ b/editor/plugins/animation_blend_tree_editor_plugin.cpp @@ -239,6 +239,7 @@ void AnimationNodeBlendTreeEditor::update_graph() { MenuButton *mb = memnew(MenuButton); mb->set_text(anim->get_animation()); mb->set_icon(get_editor_theme_icon(SNAME("Animation"))); + mb->set_auto_translate(false); mb->set_disabled(read_only); Array options; diff --git a/editor/plugins/animation_state_machine_editor.cpp b/editor/plugins/animation_state_machine_editor.cpp index bbfc1f2f99..9f8fbc6dfc 100644 --- a/editor/plugins/animation_state_machine_editor.cpp +++ b/editor/plugins/animation_state_machine_editor.cpp @@ -1779,6 +1779,7 @@ AnimationNodeStateMachineEditor::AnimationNodeStateMachineEditor() { animations_menu = memnew(PopupMenu); menu->add_child(animations_menu); animations_menu->set_name("AddAnimations"); + animations_menu->set_auto_translate(false); animations_menu->connect("index_pressed", callable_mp(this, &AnimationNodeStateMachineEditor::_add_animation_type)); connect_menu = memnew(PopupMenu); diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index 1f9f23b5d0..1f3d886f12 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -5661,7 +5661,7 @@ void CanvasItemEditorViewport::_create_preview(const Vector<String> &files) cons Ref<PackedScene> scene = Ref<PackedScene>(Object::cast_to<PackedScene>(*res)); if (texture != nullptr || scene != nullptr) { bool root_node_selected = EditorNode::get_singleton()->get_editor_selection()->is_selected(EditorNode::get_singleton()->get_edited_scene()); - String desc = TTR("Drag and drop to add as child of current scene's root node.") + "\n" + vformat(TTR("Hold %s when dropping to add as child of selected node."), keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL)); + String desc = TTR("Drag and drop to add as child of selected node.") + "\n" + TTR("Hold Alt when dropping to add as child of root node."); if (!root_node_selected) { desc += "\n" + TTR("Hold Shift when dropping to add as sibling of selected node."); } @@ -5672,7 +5672,7 @@ void CanvasItemEditorViewport::_create_preview(const Vector<String> &files) cons preview_node->add_child(sprite); label->show(); label_desc->show(); - desc += "\n" + TTR("Hold Alt when dropping to add as a different node type."); + desc += "\n" + TTR("Hold Alt + Shift when dropping to add as a different node type."); label_desc->set_text(desc); } else { if (scene.is_valid()) { @@ -5965,7 +5965,6 @@ bool CanvasItemEditorViewport::_only_packed_scenes_selected() const { void CanvasItemEditorViewport::drop_data(const Point2 &p_point, const Variant &p_data) { bool is_shift = Input::get_singleton()->is_key_pressed(Key::SHIFT); - bool is_ctrl = Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL); bool is_alt = Input::get_singleton()->is_key_pressed(Key::ALT); selected_files.clear(); @@ -5981,9 +5980,9 @@ void CanvasItemEditorViewport::drop_data(const Point2 &p_point, const Variant &p Node *root_node = EditorNode::get_singleton()->get_edited_scene(); if (selected_nodes.size() > 0) { Node *selected_node = selected_nodes[0]; - target_node = root_node; - if (is_ctrl) { - target_node = selected_node; + target_node = selected_node; + if (is_alt) { + target_node = root_node; } else if (is_shift && selected_node != root_node) { target_node = selected_node->get_parent(); } @@ -5997,7 +5996,7 @@ void CanvasItemEditorViewport::drop_data(const Point2 &p_point, const Variant &p drop_pos = p_point; - if (is_alt && !_only_packed_scenes_selected()) { + if (is_alt && is_shift && !_only_packed_scenes_selected()) { _show_resource_type_selector(); } else { _perform_drop_data(); @@ -6030,6 +6029,10 @@ void CanvasItemEditorViewport::_notification(int p_what) { case NOTIFICATION_EXIT_TREE: { disconnect("mouse_exited", callable_mp(this, &CanvasItemEditorViewport::_on_mouse_exit)); } break; + + case NOTIFICATION_DRAG_END: { + _remove_preview(); + } break; } } diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index 0a4dfd2e0d..b097523c2f 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -1749,8 +1749,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { _edit.mode = TRANSFORM_NONE; _edit.original = spatial_editor->get_gizmo_transform(); // To prevent to break when flipping with scale. - bool node_selected = spatial_editor->get_single_selected_node(); - bool can_select_gizmos = node_selected; + bool can_select_gizmos = spatial_editor->get_single_selected_node(); { int idx = view_menu->get_popup()->get_item_index(VIEW_GIZMOS); @@ -1840,6 +1839,8 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { clicked = ObjectID(); + bool node_selected = get_selected_count() > 0; + if (node_selected && ((spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT && b->is_command_or_control_pressed()) || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE)) { begin_transform(TRANSFORM_ROTATE, false); break; @@ -1985,9 +1986,8 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { } else { const bool movement_threshold_passed = _edit.original_mouse_pos.distance_to(_edit.mouse_pos) > 8 * EDSCALE; - // enable region-select if nothing has been selected yet or multi-select (shift key) is active - if (selection_in_progress && movement_threshold_passed) { - if (get_selected_count() == 0 || clicked_wants_append) { + if (selection_in_progress && movement_threshold_passed && clicked.is_valid()) { + if (clicked_wants_append || !editor_selection->is_selected(Object::cast_to<Node>(ObjectDB::get_instance(clicked)))) { cursor.region_select = true; cursor.region_begin = _edit.original_mouse_pos; clicked = ObjectID(); @@ -2790,8 +2790,7 @@ void Node3DEditorViewport::_notification(int p_what) { } Transform3D t = sp->get_global_gizmo_transform(); - VisualInstance3D *vi = Object::cast_to<VisualInstance3D>(sp); - AABB new_aabb = vi ? vi->get_aabb() : _calculate_spatial_bounds(sp); + AABB new_aabb = _calculate_spatial_bounds(sp); exist = true; if (se->last_xform == t && se->aabb == new_aabb && !se->last_xform_dirty) { @@ -2837,7 +2836,7 @@ void Node3DEditorViewport::_notification(int p_what) { last_message = message; } - message_time -= get_physics_process_delta_time(); + message_time -= get_process_delta_time(); if (message_time < 0) { surface->queue_redraw(); } @@ -3018,6 +3017,8 @@ void Node3DEditorViewport::_notification(int p_what) { // Clear preview material when dropped outside applicable object. if (spatial_editor->get_preview_material().is_valid() && !is_drag_successful()) { _remove_preview_material(); + } else { + _remove_preview_node(); } } break; } @@ -4087,35 +4088,35 @@ Vector3 Node3DEditorViewport::_get_instance_position(const Point2 &p_pos) const return world_pos + world_ray * FALLBACK_DISTANCE; } -AABB Node3DEditorViewport::_calculate_spatial_bounds(const Node3D *p_parent, bool p_exclude_top_level_transform) { +AABB Node3DEditorViewport::_calculate_spatial_bounds(const Node3D *p_parent, const Node3D *p_top_level_parent) { AABB bounds; + if (!p_top_level_parent) { + p_top_level_parent = p_parent; + } + + if (!p_parent) { + return AABB(Vector3(-0.2, -0.2, -0.2), Vector3(0.4, 0.4, 0.4)); + } + + Transform3D xform_to_top_level_parent_space = p_top_level_parent->get_global_transform().affine_inverse() * p_parent->get_global_transform(); + const VisualInstance3D *visual_instance = Object::cast_to<VisualInstance3D>(p_parent); if (visual_instance) { bounds = visual_instance->get_aabb(); + } else { + bounds = AABB(); } + bounds = xform_to_top_level_parent_space.xform(bounds); for (int i = 0; i < p_parent->get_child_count(); i++) { Node3D *child = Object::cast_to<Node3D>(p_parent->get_child(i)); if (child) { - AABB child_bounds = _calculate_spatial_bounds(child, false); - - if (bounds.size == Vector3() && p_parent) { - bounds = child_bounds; - } else { - bounds.merge_with(child_bounds); - } + AABB child_bounds = _calculate_spatial_bounds(child, p_top_level_parent); + bounds.merge_with(child_bounds); } } - if (bounds.size == Vector3() && !p_parent) { - bounds = AABB(Vector3(-0.2, -0.2, -0.2), Vector3(0.4, 0.4, 0.4)); - } - - if (!p_exclude_top_level_transform) { - bounds = p_parent->get_transform().xform(bounds); - } - return bounds; } @@ -4531,7 +4532,7 @@ void Node3DEditorViewport::drop_data_fw(const Point2 &p_point, const Variant &p_ } bool is_shift = Input::get_singleton()->is_key_pressed(Key::SHIFT); - bool is_ctrl = Input::get_singleton()->is_key_pressed(Key::CTRL); + bool is_alt = Input::get_singleton()->is_key_pressed(Key::ALT); selected_files.clear(); Dictionary d = p_data; @@ -4541,15 +4542,15 @@ void Node3DEditorViewport::drop_data_fw(const Point2 &p_point, const Variant &p_ List<Node *> selected_nodes = EditorNode::get_singleton()->get_editor_selection()->get_selected_node_list(); Node *root_node = EditorNode::get_singleton()->get_edited_scene(); - if (selected_nodes.size() == 1) { + if (selected_nodes.size() > 0) { Node *selected_node = selected_nodes[0]; - target_node = root_node; - if (is_ctrl) { - target_node = selected_node; + target_node = selected_node; + if (is_alt) { + target_node = root_node; } else if (is_shift && selected_node != root_node) { target_node = selected_node->get_parent(); } - } else if (selected_nodes.size() == 0) { + } else { if (root_node) { target_node = root_node; } else { @@ -4557,11 +4558,6 @@ void Node3DEditorViewport::drop_data_fw(const Point2 &p_point, const Variant &p_ SceneTreeDock::get_singleton()->add_root_node(memnew(Node3D)); target_node = get_tree()->get_edited_scene_root(); } - } else { - accept->set_text(TTR("Cannot drag and drop into multiple selected nodes.")); - accept->popup_centered(); - _remove_preview_node(); - return; } drop_pos = p_point; @@ -6501,20 +6497,20 @@ void vertex() { // Points are already in world space, so no need for MODEL_MATRIX anymore. vec4 clip_a = PROJECTION_MATRIX * (VIEW_MATRIX * vec4(point_a, 1.0)); vec4 clip_b = PROJECTION_MATRIX * (VIEW_MATRIX * vec4(point_b, 1.0)); - + vec2 screen_a = VIEWPORT_SIZE * (0.5 * clip_a.xy / clip_a.w + 0.5); vec2 screen_b = VIEWPORT_SIZE * (0.5 * clip_b.xy / clip_b.w + 0.5); - + vec2 x_basis = normalize(screen_b - screen_a); vec2 y_basis = vec2(-x_basis.y, x_basis.x); - + float width = 3.0; vec2 screen_point_a = screen_a + width * (VERTEX.x * x_basis + VERTEX.y * y_basis); vec2 screen_point_b = screen_b + width * (VERTEX.x * x_basis + VERTEX.y * y_basis); vec2 screen_point_final = mix(screen_point_a, screen_point_b, VERTEX.z); - + vec4 clip_final = mix(clip_a, clip_b, VERTEX.z); - + POSITION = vec4(clip_final.w * ((2.0 * screen_point_final) / VIEWPORT_SIZE - 1.0), clip_final.z, clip_final.w); UV = VERTEX.yz * clip_final.w; diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h index 455376b659..ed42e8e5ab 100644 --- a/editor/plugins/node_3d_editor_plugin.h +++ b/editor/plugins/node_3d_editor_plugin.h @@ -434,7 +434,7 @@ private: Point2i _get_warped_mouse_motion(const Ref<InputEventMouseMotion> &p_ev_mouse_motion) const; Vector3 _get_instance_position(const Point2 &p_pos) const; - static AABB _calculate_spatial_bounds(const Node3D *p_parent, bool p_exclude_top_level_transform = true); + static AABB _calculate_spatial_bounds(const Node3D *p_parent, const Node3D *p_top_level_parent = nullptr); Node *_sanitize_preview_node(Node *p_node) const; diff --git a/editor/plugins/texture_region_editor_plugin.cpp b/editor/plugins/texture_region_editor_plugin.cpp index 2a8b357559..a3d578b437 100644 --- a/editor/plugins/texture_region_editor_plugin.cpp +++ b/editor/plugins/texture_region_editor_plugin.cpp @@ -226,8 +226,8 @@ void TextureRegionEditor::_texture_overlay_draw() { hscroll->set_value((hscroll->get_min() + hscroll->get_max() - hscroll->get_page()) / 2); vscroll->set_value((vscroll->get_min() + vscroll->get_max() - vscroll->get_page()) / 2); // This ensures that the view is updated correctly. - callable_mp(this, &TextureRegionEditor::_pan_callback).bind(Vector2(1, 0)).call_deferred(); - callable_mp(this, &TextureRegionEditor::_scroll_changed).bind(0.0).call_deferred(); + callable_mp(this, &TextureRegionEditor::_pan_callback).call_deferred(Vector2(1, 0), Ref<InputEvent>()); + callable_mp(this, &TextureRegionEditor::_scroll_changed).call_deferred(0.0); request_center = false; } diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 4d998118e2..5b789c9445 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -674,10 +674,10 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); if (selection.size() == 1) { - undo_redo->create_action(TTR("Move Node In Parent")); + undo_redo->create_action(TTR("Move Node in Parent")); } if (selection.size() > 1) { - undo_redo->create_action(TTR("Move Nodes In Parent")); + undo_redo->create_action(TTR("Move Nodes in Parent")); } for (int i = 0; i < selection.size(); i++) { @@ -1824,7 +1824,6 @@ bool SceneTreeDock::_check_node_path_recursive(Node *p_root_node, Variant &r_var EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->add_do_property(resource, propertyname, updated_variant); undo_redo->add_undo_property(resource, propertyname, old_variant); - resource->set(propertyname, updated_variant); } } break; @@ -1968,7 +1967,6 @@ void SceneTreeDock::perform_node_renames(Node *p_base, HashMap<Node *, NodePath> EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->add_do_property(p_base, propertyname, updated_variant); undo_redo->add_undo_property(p_base, propertyname, old_variant); - p_base->set(propertyname, updated_variant); } } @@ -2849,7 +2847,6 @@ void SceneTreeDock::perform_node_replace(Node *p_base, Node *p_node, Node *p_by_ EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->add_do_property(p_base, propertyname, updated_variant); undo_redo->add_undo_property(p_base, propertyname, old_variant); - p_base->set(propertyname, updated_variant); if (!warn_message.is_empty()) { String node_path = (String(edited_scene->get_name()) + "/" + String(edited_scene->get_path_to(p_base))).trim_suffix("/."); WARN_PRINT(warn_message + vformat("Removing the node from variable \"%s\" on node \"%s\".", propertyname, node_path)); @@ -4134,7 +4131,7 @@ SceneTreeDock::SceneTreeDock(Node *p_scene_root, EditorSelection *p_editor_selec ED_SHORTCUT("scene_tree/make_root", TTR("Make Scene Root")); ED_SHORTCUT("scene_tree/save_branch_as_scene", TTR("Save Branch as Scene")); ED_SHORTCUT("scene_tree/copy_node_path", TTR("Copy Node Path"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::C); - ED_SHORTCUT("scene_tree/show_in_file_system", TTR("Show In FileSystem")); + ED_SHORTCUT("scene_tree/show_in_file_system", TTR("Show in FileSystem")); ED_SHORTCUT("scene_tree/toggle_unique_name", TTR("Toggle Access as Unique Name")); ED_SHORTCUT("scene_tree/delete_no_confirm", TTR("Delete (No Confirm)"), KeyModifierMask::SHIFT | Key::KEY_DELETE); ED_SHORTCUT("scene_tree/delete", TTR("Delete"), Key::KEY_DELETE); diff --git a/main/main.cpp b/main/main.cpp index 0625874520..bc2a6107b5 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -238,6 +238,11 @@ bool profile_gpu = false; static const String NULL_DISPLAY_DRIVER("headless"); static const String NULL_AUDIO_DRIVER("Dummy"); +// The length of the longest column in the command-line help we should align to +// (excluding the 2-space left and right margins). +// Currently, this is `--export-release <preset> <path>`. +static const int OPTION_COLUMN_LENGTH = 32; + /* Helper methods */ bool Main::is_cmdline_tool() { @@ -381,159 +386,241 @@ void finalize_theme_db() { #define MAIN_PRINT(m_txt) #endif +/** + * Prints a copyright notice in the command-line help with colored text. A newline is + * automatically added at the end. + */ +void Main::print_help_copyright(const char *p_notice) { + OS::get_singleton()->print("\u001b[90m%s\u001b[0m\n", p_notice); +} + +/** + * Prints a title in the command-line help with colored text. A newline is + * automatically added at beginning and at the end. + */ +void Main::print_help_title(const char *p_title) { + OS::get_singleton()->print("\n\u001b[1;93m%s:\u001b[0m\n", p_title); +} + +/** + * Returns the option string with required and optional arguments colored separately from the rest of the option. + * This color replacement must be done *after* calling `rpad()` for the length padding to be done correctly. + */ +String Main::format_help_option(const char *p_option) { + return (String(p_option) + .rpad(OPTION_COLUMN_LENGTH) + .replace("[", "\u001b[96m[") + .replace("]", "]\u001b[0m") + .replace("<", "\u001b[95m<") + .replace(">", ">\u001b[0m")); +} + +/** + * Prints an option in the command-line help with colored text. No newline is + * added at the end. `p_availability` denotes which build types the argument is + * available in. Support in release export templates implies support in debug + * export templates and editor. Support in debug export templates implies + * support in editor. + */ +void Main::print_help_option(const char *p_option, const char *p_description, CLIOptionAvailability p_availability) { + const bool option_empty = (p_option && !p_option[0]); + if (!option_empty) { + const char *availability_badge = ""; + switch (p_availability) { + case CLI_OPTION_AVAILABILITY_EDITOR: + availability_badge = "\u001b[1;91mE"; + break; + case CLI_OPTION_AVAILABILITY_TEMPLATE_DEBUG: + availability_badge = "\u001b[1;94mD"; + break; + case CLI_OPTION_AVAILABILITY_TEMPLATE_RELEASE: + availability_badge = "\u001b[1;92mR"; + break; + case CLI_OPTION_AVAILABILITY_HIDDEN: + // Use for multiline option names (but not when the option name is empty). + availability_badge = " "; + break; + } + OS::get_singleton()->print( + " \u001b[92m%s %s\u001b[0m %s", + format_help_option(p_option).utf8().ptr(), + availability_badge, + p_description); + } else { + // Make continuation lines for descriptions faint if the option name is empty. + OS::get_singleton()->print( + " \u001b[92m%s \u001b[0m \u001b[90m%s", + format_help_option(p_option).utf8().ptr(), + p_description); + } +} + void Main::print_help(const char *p_binary) { - print_line(String(VERSION_NAME) + " v" + get_full_version_string() + " - " + String(VERSION_WEBSITE)); - OS::get_singleton()->print("Free and open source software under the terms of the MIT license.\n"); - OS::get_singleton()->print("(c) 2014-present Godot Engine contributors.\n"); - OS::get_singleton()->print("(c) 2007-2014 Juan Linietsky, Ariel Manzur.\n"); - OS::get_singleton()->print("\n"); - OS::get_singleton()->print("Usage: %s [options] [path to scene or 'project.godot' file]\n", p_binary); - OS::get_singleton()->print("\n"); + print_line("\u001b[38;5;39m" + String(VERSION_NAME) + "\u001b[0m v" + get_full_version_string() + " - \u001b[4m" + String(VERSION_WEBSITE) + "\u001b[0m"); + print_help_copyright("Free and open source software under the terms of the MIT license."); + print_help_copyright("(c) 2014-present Godot Engine contributors. (c) 2007-present Juan Linietsky, Ariel Manzur."); - OS::get_singleton()->print("General options:\n"); - OS::get_singleton()->print(" -h, --help Display this help message.\n"); - OS::get_singleton()->print(" --version Display the version string.\n"); - OS::get_singleton()->print(" -v, --verbose Use verbose stdout mode.\n"); - OS::get_singleton()->print(" -q, --quiet Quiet mode, silences stdout messages. Errors are still displayed.\n"); - OS::get_singleton()->print("\n"); + print_help_title("Usage"); + OS::get_singleton()->print(" %s \u001b[96m[options] [path to scene or \"project.godot\" file]\u001b[0m\n", p_binary); + +#if defined(TOOLS_ENABLED) + print_help_title("Option legend (this build = editor)"); +#elif defined(DEBUG_ENABLED) + print_help_title("Option legend (this build = debug export template)"); +#else + print_help_title("Option legend (this build = release export template)"); +#endif + + OS::get_singleton()->print(" \u001b[1;92mR\u001b[0m Available in editor builds, debug export templates and release export templates.\n"); +#ifdef DEBUG_ENABLED + OS::get_singleton()->print(" \u001b[1;94mD\u001b[0m Available in editor builds and debug export templates only.\n"); +#endif +#ifdef TOOLS_ENABLED + OS::get_singleton()->print(" \u001b[1;91mE\u001b[0m Only available in editor builds.\n"); +#endif - OS::get_singleton()->print("Run options:\n"); - OS::get_singleton()->print(" --, ++ Separator for user-provided arguments. Following arguments are not used by the engine, but can be read from `OS.get_cmdline_user_args()`.\n"); + print_help_title("General options"); + print_help_option("-h, --help", "Display this help message.\n"); + print_help_option("--version", "Display the version string.\n"); + print_help_option("-v, --verbose", "Use verbose stdout mode.\n"); + print_help_option("--quiet", "Quiet mode, silences stdout messages. Errors are still displayed.\n"); + + print_help_title("Run options"); + print_help_option("--, ++", "Separator for user-provided arguments. Following arguments are not used by the engine, but can be read from `OS.get_cmdline_user_args()`.\n"); #ifdef TOOLS_ENABLED - OS::get_singleton()->print(" -e, --editor Start the editor instead of running the scene.\n"); - OS::get_singleton()->print(" -p, --project-manager Start the project manager, even if a project is auto-detected.\n"); - OS::get_singleton()->print(" --debug-server <uri> Start the editor debug server (<protocol>://<host/IP>[:<port>], e.g. tcp://127.0.0.1:6007)\n"); + print_help_option("-e, --editor", "Start the editor instead of running the scene.\n", CLI_OPTION_AVAILABILITY_EDITOR); + print_help_option("-p, --project-manager", "Start the project manager, even if a project is auto-detected.\n", CLI_OPTION_AVAILABILITY_EDITOR); + print_help_option("--debug-server <uri>", "Start the editor debug server (<protocol>://<host/IP>[:port], e.g. tcp://127.0.0.1:6007)\n", CLI_OPTION_AVAILABILITY_EDITOR); #if defined(MODULE_GDSCRIPT_ENABLED) && !defined(GDSCRIPT_NO_LSP) - OS::get_singleton()->print(" --lsp-port <port> Use the specified port for the language server protocol. The port must be between 0 to 65535.\n"); + print_help_option("--lsp-port <port>", "Use the specified port for the GDScript language server protocol. The port should be between 1025 and 49150.\n", CLI_OPTION_AVAILABILITY_EDITOR); #endif // MODULE_GDSCRIPT_ENABLED && !GDSCRIPT_NO_LSP -#endif // TOOLS_ENABLED - OS::get_singleton()->print(" --quit Quit after the first iteration.\n"); - OS::get_singleton()->print(" --quit-after <int> Quit after the given number of iterations. Set to 0 to disable.\n"); - OS::get_singleton()->print(" -l, --language <locale> Use a specific locale (<locale> being a two-letter code).\n"); - OS::get_singleton()->print(" --path <directory> Path to a project (<directory> must contain a 'project.godot' file).\n"); - OS::get_singleton()->print(" -u, --upwards Scan folders upwards for project.godot file.\n"); - OS::get_singleton()->print(" --main-pack <file> Path to a pack (.pck) file to load.\n"); - OS::get_singleton()->print(" --render-thread <mode> Render thread mode ['unsafe', 'safe', 'separate'].\n"); - OS::get_singleton()->print(" --remote-fs <address> Remote filesystem (<host/IP>[:<port>] address).\n"); - OS::get_singleton()->print(" --remote-fs-password <password> Password for remote filesystem.\n"); - - OS::get_singleton()->print(" --audio-driver <driver> Audio driver ["); +#endif + print_help_option("--quit", "Quit after the first iteration.\n"); + print_help_option("--quit-after <int>", "Quit after the given number of iterations. Set to 0 to disable.\n"); + print_help_option("-l, --language <locale>", "Use a specific locale (<locale> being a two-letter code).\n"); + print_help_option("--path <directory>", "Path to a project (<directory> must contain a \"project.godot\" file).\n"); + print_help_option("-u, --upwards", "Scan folders upwards for project.godot file.\n"); + print_help_option("--main-pack <file>", "Path to a pack (.pck) file to load.\n"); + print_help_option("--render-thread <mode>", "Render thread mode (\"unsafe\", \"safe\", \"separate\").\n"); + print_help_option("--remote-fs <address>", "Remote filesystem (<host/IP>[:<port>] address).\n"); + print_help_option("--remote-fs-password <password>", "Password for remote filesystem.\n"); + + print_help_option("--audio-driver <driver>", "Audio driver ["); for (int i = 0; i < AudioDriverManager::get_driver_count(); i++) { if (i > 0) { OS::get_singleton()->print(", "); } - OS::get_singleton()->print("'%s'", AudioDriverManager::get_driver(i)->get_name()); + OS::get_singleton()->print("\"%s\"", AudioDriverManager::get_driver(i)->get_name()); } OS::get_singleton()->print("].\n"); - OS::get_singleton()->print(" --display-driver <driver> Display driver (and rendering driver) ["); + print_help_option("--display-driver <driver>", "Display driver (and rendering driver) ["); for (int i = 0; i < DisplayServer::get_create_function_count(); i++) { if (i > 0) { OS::get_singleton()->print(", "); } - OS::get_singleton()->print("'%s' (", DisplayServer::get_create_function_name(i)); + OS::get_singleton()->print("\"%s\" (", DisplayServer::get_create_function_name(i)); Vector<String> rd = DisplayServer::get_create_function_rendering_drivers(i); for (int j = 0; j < rd.size(); j++) { if (j > 0) { OS::get_singleton()->print(", "); } - OS::get_singleton()->print("'%s'", rd[j].utf8().get_data()); + OS::get_singleton()->print("\"%s\"", rd[j].utf8().get_data()); } OS::get_singleton()->print(")"); } OS::get_singleton()->print("].\n"); - OS::get_singleton()->print(" --audio-output-latency <ms> Override audio output latency in milliseconds (default is 15 ms).\n"); - OS::get_singleton()->print(" Lower values make sound playback more reactive but increase CPU usage, and may result in audio cracking if the CPU can't keep up.\n"); - - OS::get_singleton()->print(" --rendering-method <renderer> Renderer name. Requires driver support.\n"); - OS::get_singleton()->print(" --rendering-driver <driver> Rendering driver (depends on display driver).\n"); - OS::get_singleton()->print(" --gpu-index <device_index> Use a specific GPU (run with --verbose to get available device list).\n"); - OS::get_singleton()->print(" --text-driver <driver> Text driver (Fonts, BiDi, shaping).\n"); - OS::get_singleton()->print(" --tablet-driver <driver> Pen tablet input driver.\n"); - OS::get_singleton()->print(" --headless Enable headless mode (--display-driver headless --audio-driver Dummy). Useful for servers and with --script.\n"); - OS::get_singleton()->print(" --log-file <file> Write output/error log to the specified path instead of the default location defined by the project.\n"); - OS::get_singleton()->print(" <file> path should be absolute or relative to the project directory.\n"); - OS::get_singleton()->print(" --write-movie <file> Write a video to the specified path (usually with .avi or .png extension).\n"); - OS::get_singleton()->print(" --fixed-fps is forced when enabled, but it can be used to change movie FPS.\n"); - OS::get_singleton()->print(" --disable-vsync can speed up movie writing but makes interaction more difficult.\n"); - OS::get_singleton()->print(" --quit-after can be used to specify the number of frames to write.\n"); - - OS::get_singleton()->print("\n"); - - OS::get_singleton()->print("Display options:\n"); - OS::get_singleton()->print(" -f, --fullscreen Request fullscreen mode.\n"); - OS::get_singleton()->print(" -m, --maximized Request a maximized window.\n"); - OS::get_singleton()->print(" -w, --windowed Request windowed mode.\n"); - OS::get_singleton()->print(" -t, --always-on-top Request an always-on-top window.\n"); - OS::get_singleton()->print(" --resolution <W>x<H> Request window resolution.\n"); - OS::get_singleton()->print(" --position <X>,<Y> Request window position (if set, screen argument is ignored).\n"); - OS::get_singleton()->print(" --screen <N> Request window screen.\n"); - OS::get_singleton()->print(" --single-window Use a single window (no separate subwindows).\n"); - OS::get_singleton()->print(" --xr-mode <mode> Select XR (Extended Reality) mode ['default', 'off', 'on'].\n"); - OS::get_singleton()->print("\n"); - - OS::get_singleton()->print("Debug options:\n"); - OS::get_singleton()->print(" -d, --debug Debug (local stdout debugger).\n"); - OS::get_singleton()->print(" -b, --breakpoints Breakpoint list as source::line comma-separated pairs, no spaces (use %%20 instead).\n"); - OS::get_singleton()->print(" --profiling Enable profiling in the script debugger.\n"); - OS::get_singleton()->print(" --gpu-profile Show a GPU profile of the tasks that took the most time during frame rendering.\n"); - OS::get_singleton()->print(" --gpu-validation Enable graphics API validation layers for debugging.\n"); + print_help_option("--audio-output-latency <ms>", "Override audio output latency in milliseconds (default is 15 ms).\n"); + print_help_option("", "Lower values make sound playback more reactive but increase CPU usage, and may result in audio cracking if the CPU can't keep up.\n"); + + print_help_option("--rendering-method <renderer>", "Renderer name. Requires driver support.\n"); + print_help_option("--rendering-driver <driver>", "Rendering driver (depends on display driver).\n"); + print_help_option("--gpu-index <device_index>", "Use a specific GPU (run with --verbose to get a list of available devices).\n"); + print_help_option("--text-driver <driver>", "Text driver (used for font rendering, bidirectional support and shaping).\n"); + print_help_option("--tablet-driver <driver>", "Pen tablet input driver.\n"); + print_help_option("--headless", "Enable headless mode (--display-driver headless --audio-driver Dummy). Useful for servers and with --script.\n"); + print_help_option("--log-file <file>", "Write output/error log to the specified path instead of the default location defined by the project.\n"); + print_help_option("", "<file> path should be absolute or relative to the project directory.\n"); + print_help_option("--write-movie <file>", "Write a video to the specified path (usually with .avi or .png extension).\n"); + print_help_option("", "--fixed-fps is forced when enabled, but it can be used to change movie FPS.\n"); + print_help_option("", "--disable-vsync can speed up movie writing but makes interaction more difficult.\n"); + print_help_option("", "--quit-after can be used to specify the number of frames to write.\n"); + + print_help_title("Display options"); + print_help_option("-f, --fullscreen", "Request fullscreen mode.\n"); + print_help_option("-m, --maximized", "Request a maximized window.\n"); + print_help_option("-w, --windowed", "Request windowed mode.\n"); + print_help_option("-t, --always-on-top", "Request an always-on-top window.\n"); + print_help_option("--resolution <W>x<H>", "Request window resolution.\n"); + print_help_option("--position <X>,<Y>", "Request window position.\n"); + print_help_option("--screen <N>", "Request window screen.\n"); + print_help_option("--single-window", "Use a single window (no separate subwindows).\n"); + print_help_option("--xr-mode <mode>", "Select XR (Extended Reality) mode [\"default\", \"off\", \"on\"].\n"); + + print_help_title("Debug options"); + print_help_option("-d, --debug", "Debug (local stdout debugger).\n"); + print_help_option("-b, --breakpoints", "Breakpoint list as source::line comma-separated pairs, no spaces (use %%20 instead).\n"); + print_help_option("--profiling", "Enable profiling in the script debugger.\n"); + print_help_option("--gpu-profile", "Show a GPU profile of the tasks that took the most time during frame rendering.\n"); + print_help_option("--gpu-validation", "Enable graphics API validation layers for debugging.\n"); #ifdef DEBUG_ENABLED - OS::get_singleton()->print(" --gpu-abort Abort on graphics API usage errors (usually validation layer errors). May help see the problem if your system freezes.\n"); + print_help_option("--gpu-abort", "Abort on graphics API usage errors (usually validation layer errors). May help see the problem if your system freezes.\n", CLI_OPTION_AVAILABILITY_TEMPLATE_DEBUG); #endif - OS::get_singleton()->print(" --generate-spirv-debug-info Generate SPIR-V debug information. This allows source-level shader debugging with RenderDoc.\n"); - OS::get_singleton()->print(" --remote-debug <uri> Remote debug (<protocol>://<host/IP>[:<port>], e.g. tcp://127.0.0.1:6007).\n"); - OS::get_singleton()->print(" --single-threaded-scene Scene tree runs in single-threaded mode. Sub-thread groups are disabled and run on the main thread.\n"); + print_help_option("--generate-spirv-debug-info", "Generate SPIR-V debug information. This allows source-level shader debugging with RenderDoc.\n"); + print_help_option("--remote-debug <uri>", "Remote debug (<protocol>://<host/IP>[:<port>], e.g. tcp://127.0.0.1:6007).\n"); + print_help_option("--single-threaded-scene", "Force scene tree to run in single-threaded mode. Sub-thread groups are disabled and run on the main thread.\n"); #if defined(DEBUG_ENABLED) - OS::get_singleton()->print(" --debug-collisions Show collision shapes when running the scene.\n"); - OS::get_singleton()->print(" --debug-paths Show path lines when running the scene.\n"); - OS::get_singleton()->print(" --debug-navigation Show navigation polygons when running the scene.\n"); - OS::get_singleton()->print(" --debug-avoidance Show navigation avoidance debug visuals when running the scene.\n"); - OS::get_singleton()->print(" --debug-stringnames Print all StringName allocations to stdout when the engine quits.\n"); - OS::get_singleton()->print(" --debug-canvas-item-redraw Display a rectangle each time a canvas item requests a redraw (useful to troubleshoot low processor mode).\n"); -#endif - OS::get_singleton()->print(" --max-fps <fps> Set a maximum number of frames per second rendered (can be used to limit power usage). A value of 0 results in unlimited framerate.\n"); - OS::get_singleton()->print(" --frame-delay <ms> Simulate high CPU load (delay each frame by <ms> milliseconds). Do not use as a FPS limiter; use --max-fps instead.\n"); - OS::get_singleton()->print(" --time-scale <scale> Force time scale (higher values are faster, 1.0 is normal speed).\n"); - OS::get_singleton()->print(" --disable-vsync Forces disabling of vertical synchronization, even if enabled in the project settings. Does not override driver-level V-Sync enforcement.\n"); - OS::get_singleton()->print(" --disable-render-loop Disable render loop so rendering only occurs when called explicitly from script.\n"); - OS::get_singleton()->print(" --disable-crash-handler Disable crash handler when supported by the platform code.\n"); - OS::get_singleton()->print(" --fixed-fps <fps> Force a fixed number of frames per second. This setting disables real-time synchronization.\n"); - OS::get_singleton()->print(" --delta-smoothing <enable> Enable or disable frame delta smoothing ['enable', 'disable'].\n"); - OS::get_singleton()->print(" --print-fps Print the frames per second to the stdout.\n"); - OS::get_singleton()->print("\n"); + print_help_option("--debug-collisions", "Show collision shapes when running the scene.\n", CLI_OPTION_AVAILABILITY_TEMPLATE_DEBUG); + print_help_option("--debug-paths", "Show path lines when running the scene.\n", CLI_OPTION_AVAILABILITY_TEMPLATE_DEBUG); + print_help_option("--debug-navigation", "Show navigation polygons when running the scene.\n", CLI_OPTION_AVAILABILITY_TEMPLATE_DEBUG); + print_help_option("--debug-avoidance", "Show navigation avoidance debug visuals when running the scene.\n", CLI_OPTION_AVAILABILITY_TEMPLATE_DEBUG); + print_help_option("--debug-stringnames", "Print all StringName allocations to stdout when the engine quits.\n", CLI_OPTION_AVAILABILITY_TEMPLATE_DEBUG); + print_help_option("--debug-canvas-item-redraw", "Display a rectangle each time a canvas item requests a redraw (useful to troubleshoot low processor mode).\n", CLI_OPTION_AVAILABILITY_TEMPLATE_DEBUG); - OS::get_singleton()->print("Standalone tools:\n"); - OS::get_singleton()->print(" -s, --script <script> Run a script.\n"); - OS::get_singleton()->print(" --main-loop <main_loop_name> Run a MainLoop specified by its global class name.\n"); - OS::get_singleton()->print(" --check-only Only parse for errors and quit (use with --script).\n"); +#endif + print_help_option("--max-fps <fps>", "Set a maximum number of frames per second rendered (can be used to limit power usage). A value of 0 results in unlimited framerate.\n"); + print_help_option("--frame-delay <ms>", "Simulate high CPU load (delay each frame by <ms> milliseconds). Do not use as a FPS limiter; use --max-fps instead.\n"); + print_help_option("--time-scale <scale>", "Force time scale (higher values are faster, 1.0 is normal speed).\n"); + print_help_option("--disable-vsync", "Forces disabling of vertical synchronization, even if enabled in the project settings. Does not override driver-level V-Sync enforcement.\n"); + print_help_option("--disable-render-loop", "Disable render loop so rendering only occurs when called explicitly from script.\n"); + print_help_option("--disable-crash-handler", "Disable crash handler when supported by the platform code.\n"); + print_help_option("--fixed-fps <fps>", "Force a fixed number of frames per second. This setting disables real-time synchronization.\n"); + print_help_option("--delta-smoothing <enable>", "Enable or disable frame delta smoothing [\"enable\", \"disable\"].\n"); + print_help_option("--print-fps", "Print the frames per second to the stdout.\n"); + + print_help_title("Standalone tools"); + print_help_option("-s, --script <script>", "Run a script.\n"); + print_help_option("--main-loop <main_loop_name>", "Run a MainLoop specified by its global class name.\n"); + print_help_option("--check-only", "Only parse for errors and quit (use with --script).\n"); #ifdef TOOLS_ENABLED - OS::get_singleton()->print(" --export-release <preset> <path> Export the project in release mode using the given preset and output path. The preset name should match one defined in export_presets.cfg.\n"); - OS::get_singleton()->print(" <path> should be absolute or relative to the project directory, and include the filename for the binary (e.g. 'builds/game.exe').\n"); - OS::get_singleton()->print(" The target directory must exist.\n"); - OS::get_singleton()->print(" --export-debug <preset> <path> Export the project in debug mode using the given preset and output path. See --export-release description for other considerations.\n"); - OS::get_singleton()->print(" --export-pack <preset> <path> Export the project data only using the given preset and output path. The <path> extension determines whether it will be in PCK or ZIP format.\n"); - OS::get_singleton()->print(" --install-android-build-template Install the android build template. Used in conjunction with --export-release or --export-debug.\n"); + print_help_option("--export-release <preset> <path>", "Export the project in release mode using the given preset and output path. The preset name should match one defined in \"export_presets.cfg\".\n", CLI_OPTION_AVAILABILITY_EDITOR); + print_help_option("", "<path> should be absolute or relative to the project directory, and include the filename for the binary (e.g. \"builds/game.exe\").\n"); + print_help_option("", "The target directory must exist.\n"); + print_help_option("--export-debug <preset> <path>", "Export the project in debug mode using the given preset and output path. See --export-release description for other considerations.\n", CLI_OPTION_AVAILABILITY_EDITOR); + print_help_option("--export-pack <preset> <path>", "Export the project data only using the given preset and output path. The <path> extension determines whether it will be in PCK or ZIP format.\n", CLI_OPTION_AVAILABILITY_EDITOR); + print_help_option("--install-android-build-template", "Install the Android build template. Used in conjunction with --export-release or --export-debug.\n", CLI_OPTION_AVAILABILITY_EDITOR); #ifndef DISABLE_DEPRECATED - OS::get_singleton()->print(" --convert-3to4 [<max_file_kb>] [<max_line_size>]\n"); - OS::get_singleton()->print(" Converts project from Godot 3.x to Godot 4.x.\n"); - OS::get_singleton()->print(" --validate-conversion-3to4 [<max_file_kb>] [<max_line_size>]\n"); - OS::get_singleton()->print(" Shows what elements will be renamed when converting project from Godot 3.x to Godot 4.x.\n"); + // Commands are long; split the description to a second line. + print_help_option("--convert-3to4 ", "\n", CLI_OPTION_AVAILABILITY_HIDDEN); + print_help_option(" [max_file_kb] [max_line_size]", "Converts project from Godot 3.x to Godot 4.x.\n", CLI_OPTION_AVAILABILITY_EDITOR); + print_help_option("--validate-conversion-3to4 ", "\n", CLI_OPTION_AVAILABILITY_HIDDEN); + print_help_option(" [max_file_kb] [max_line_size]", "Shows what elements will be renamed when converting project from Godot 3.x to Godot 4.x.\n", CLI_OPTION_AVAILABILITY_EDITOR); #endif // DISABLE_DEPRECATED - OS::get_singleton()->print(" --doctool [<path>] Dump the engine API reference to the given <path> (defaults to current dir) in XML format, merging if existing files are found.\n"); - OS::get_singleton()->print(" --no-docbase Disallow dumping the base types (used with --doctool).\n"); + print_help_option("--doctool [path]", "Dump the engine API reference to the given <path> (defaults to current directory) in XML format, merging if existing files are found.\n", CLI_OPTION_AVAILABILITY_EDITOR); + print_help_option("--no-docbase", "Disallow dumping the base types (used with --doctool).\n", CLI_OPTION_AVAILABILITY_EDITOR); #ifdef MODULE_GDSCRIPT_ENABLED - OS::get_singleton()->print(" --gdscript-docs <path> Rather than dumping the engine API, generate API reference from the inline documentation in the GDScript files found in <path> (used with --doctool).\n"); + print_help_option("--gdscript-docs <path>", "Rather than dumping the engine API, generate API reference from the inline documentation in the GDScript files found in <path> (used with --doctool).\n", CLI_OPTION_AVAILABILITY_EDITOR); #endif - OS::get_singleton()->print(" --build-solutions Build the scripting solutions (e.g. for C# projects). Implies --editor and requires a valid project to edit.\n"); - OS::get_singleton()->print(" --dump-gdextension-interface Generate GDExtension header file 'gdextension_interface.h' in the current folder. This file is the base file required to implement a GDExtension.\n"); - OS::get_singleton()->print(" --dump-extension-api Generate JSON dump of the Godot API for GDExtension bindings named 'extension_api.json' in the current folder.\n"); - OS::get_singleton()->print(" --dump-extension-api-with-docs Generate JSON dump of the Godot API like the previous option, but including documentation.\n"); - OS::get_singleton()->print(" --validate-extension-api <path> Validate an extension API file dumped (with one of the two previous options) from a previous version of the engine to ensure API compatibility. If incompatibilities or errors are detected, the return code will be non zero.\n"); - OS::get_singleton()->print(" --benchmark Benchmark the run time and print it to console.\n"); - OS::get_singleton()->print(" --benchmark-file <path> Benchmark the run time and save it to a given file in JSON format. The path should be absolute.\n"); + print_help_option("--build-solutions", "Build the scripting solutions (e.g. for C# projects). Implies --editor and requires a valid project to edit.\n", CLI_OPTION_AVAILABILITY_EDITOR); + print_help_option("--dump-gdextension-interface", "Generate a GDExtension header file \"gdextension_interface.h\" in the current folder. This file is the base file required to implement a GDExtension.\n", CLI_OPTION_AVAILABILITY_EDITOR); + print_help_option("--dump-extension-api", "Generate a JSON dump of the Godot API for GDExtension bindings named \"extension_api.json\" in the current folder.\n", CLI_OPTION_AVAILABILITY_EDITOR); + print_help_option("--dump-extension-api-with-docs", "Generate JSON dump of the Godot API like the previous option, but including documentation.\n", CLI_OPTION_AVAILABILITY_EDITOR); + print_help_option("--validate-extension-api <path>", "Validate an extension API file dumped (with one of the two previous options) from a previous version of the engine to ensure API compatibility.\n", CLI_OPTION_AVAILABILITY_EDITOR); + print_help_option("", "If incompatibilities or errors are detected, the exit code will be non-zero.\n"); + print_help_option("--benchmark", "Benchmark the run time and print it to console.\n", CLI_OPTION_AVAILABILITY_EDITOR); + print_help_option("--benchmark-file <path>", "Benchmark the run time and save it to a given file in JSON format. The path should be absolute.\n", CLI_OPTION_AVAILABILITY_EDITOR); #ifdef TESTS_ENABLED - OS::get_singleton()->print(" --test [--help] Run unit tests. Use --test --help for more information.\n"); + print_help_option("--test [--help]", "Run unit tests. Use --test --help for more information.\n", CLI_OPTION_AVAILABILITY_EDITOR); #endif #endif OS::get_singleton()->print("\n"); diff --git a/main/main.h b/main/main.h index cc0655cd02..09cc0feae6 100644 --- a/main/main.h +++ b/main/main.h @@ -39,6 +39,17 @@ template <class T> class Vector; class Main { + enum CLIOptionAvailability { + CLI_OPTION_AVAILABILITY_EDITOR, + CLI_OPTION_AVAILABILITY_TEMPLATE_DEBUG, + CLI_OPTION_AVAILABILITY_TEMPLATE_RELEASE, + CLI_OPTION_AVAILABILITY_HIDDEN, + }; + + static void print_help_copyright(const char *p_notice); + static void print_help_title(const char *p_title); + static void print_help_option(const char *p_option, const char *p_description, CLIOptionAvailability p_availability = CLI_OPTION_AVAILABILITY_TEMPLATE_RELEASE); + static String format_help_option(const char *p_option); static void print_help(const char *p_binary); static uint64_t last_ticks; static uint32_t hide_print_fps_attempts; diff --git a/methods.py b/methods.py index f36591d211..c22b1f11e4 100644 --- a/methods.py +++ b/methods.py @@ -774,161 +774,6 @@ def add_to_vs_project(env, sources): env.vs_srcs += [basename + ".cpp"] -def generate_vs_project(env, original_args, project_name="godot"): - batch_file = find_visual_c_batch_file(env) - filtered_args = original_args.copy() - # Ignore the "vsproj" option to not regenerate the VS project on every build - filtered_args.pop("vsproj", None) - # The "platform" option is ignored because only the Windows platform is currently supported for VS projects - filtered_args.pop("platform", None) - # The "target" option is ignored due to the way how targets configuration is performed for VS projects (there is a separate project configuration for each target) - filtered_args.pop("target", None) - # The "progress" option is ignored as the current compilation progress indication doesn't work in VS - filtered_args.pop("progress", None) - - if batch_file: - - class ModuleConfigs(Mapping): - # This version information (Win32, x64, Debug, Release) seems to be - # required for Visual Studio to understand that it needs to generate an NMAKE - # project. Do not modify without knowing what you are doing. - PLATFORMS = ["Win32", "x64"] - PLATFORM_IDS = ["x86_32", "x86_64"] - CONFIGURATIONS = ["editor", "template_release", "template_debug"] - DEV_SUFFIX = ".dev" if env["dev_build"] else "" - - @staticmethod - def for_every_variant(value): - return [value for _ in range(len(ModuleConfigs.CONFIGURATIONS) * len(ModuleConfigs.PLATFORMS))] - - def __init__(self): - shared_targets_array = [] - self.names = [] - self.arg_dict = { - "variant": [], - "runfile": shared_targets_array, - "buildtarget": shared_targets_array, - "cpppaths": [], - "cppdefines": [], - "cmdargs": [], - } - self.add_mode() # default - - def add_mode( - self, - name: str = "", - includes: str = "", - cli_args: str = "", - defines=None, - ): - if defines is None: - defines = [] - self.names.append(name) - self.arg_dict["variant"] += [ - f'{config}{f"_[{name}]" if name else ""}|{platform}' - for config in ModuleConfigs.CONFIGURATIONS - for platform in ModuleConfigs.PLATFORMS - ] - self.arg_dict["runfile"] += [ - f'bin\\godot.windows.{config}{ModuleConfigs.DEV_SUFFIX}{".double" if env["precision"] == "double" else ""}.{plat_id}{f".{name}" if name else ""}.exe' - for config in ModuleConfigs.CONFIGURATIONS - for plat_id in ModuleConfigs.PLATFORM_IDS - ] - self.arg_dict["cpppaths"] += ModuleConfigs.for_every_variant(env["CPPPATH"] + [includes]) - self.arg_dict["cppdefines"] += ModuleConfigs.for_every_variant(list(env["CPPDEFINES"]) + defines) - self.arg_dict["cmdargs"] += ModuleConfigs.for_every_variant(cli_args) - - def build_commandline(self, commands): - configuration_getter = ( - "$(Configuration" - + "".join([f'.Replace("{name}", "")' for name in self.names[1:]]) - + '.Replace("_[]", "")' - + ")" - ) - - common_build_prefix = [ - 'cmd /V /C set "plat=$(PlatformTarget)"', - '(if "$(PlatformTarget)"=="x64" (set "plat=x86_amd64"))', - 'call "' + batch_file + '" !plat!', - ] - - # Windows allows us to have spaces in paths, so we need - # to double quote off the directory. However, the path ends - # in a backslash, so we need to remove this, lest it escape the - # last double quote off, confusing MSBuild - common_build_postfix = [ - "--directory=\"$(ProjectDir.TrimEnd('\\'))\"", - "platform=windows", - f"target={configuration_getter}", - "progress=no", - ] - - for arg, value in filtered_args.items(): - common_build_postfix.append(f"{arg}={value}") - - result = " ^& ".join(common_build_prefix + [" ".join([commands] + common_build_postfix)]) - return result - - # Mappings interface definitions - - def __iter__(self) -> Iterator[str]: - for x in self.arg_dict: - yield x - - def __len__(self) -> int: - return len(self.names) - - def __getitem__(self, k: str): - return self.arg_dict[k] - - add_to_vs_project(env, env.core_sources) - add_to_vs_project(env, env.drivers_sources) - add_to_vs_project(env, env.main_sources) - add_to_vs_project(env, env.modules_sources) - add_to_vs_project(env, env.scene_sources) - add_to_vs_project(env, env.servers_sources) - if env["tests"]: - add_to_vs_project(env, env.tests_sources) - if env.editor_build: - add_to_vs_project(env, env.editor_sources) - - for header in glob_recursive("**/*.h"): - env.vs_incs.append(str(header)) - - module_configs = ModuleConfigs() - - if env.get("module_mono_enabled"): - mono_defines = [("GD_MONO_HOT_RELOAD",)] if env.editor_build else [] - module_configs.add_mode( - "mono", - cli_args="module_mono_enabled=yes", - defines=mono_defines, - ) - - scons_cmd = "scons" - - path_to_venv = os.getenv("VIRTUAL_ENV") - path_to_scons_exe = Path(str(path_to_venv)) / "Scripts" / "scons.exe" - if path_to_venv and path_to_scons_exe.exists(): - scons_cmd = str(path_to_scons_exe) - - env["MSVSBUILDCOM"] = module_configs.build_commandline(scons_cmd) - env["MSVSREBUILDCOM"] = module_configs.build_commandline(f"{scons_cmd} vsproj=yes") - env["MSVSCLEANCOM"] = module_configs.build_commandline(f"{scons_cmd} --clean") - if not env.get("MSVS"): - env["MSVS"]["PROJECTSUFFIX"] = ".vcxproj" - env["MSVS"]["SOLUTIONSUFFIX"] = ".sln" - env.MSVSProject( - target=["#" + project_name + env["MSVSPROJECTSUFFIX"]], - incs=env.vs_incs, - srcs=env.vs_srcs, - auto_build_solution=1, - **module_configs, - ) - else: - print("Could not locate Visual Studio batch file to set up the build environment. Not generating VS project.") - - def precious_program(env, program, sources, **args): program = env.ProgramOriginal(program, sources, **args) env.Precious(program) @@ -1229,3 +1074,456 @@ def dump(env): with open(".scons_env.json", "w") as f: dump(env.Dictionary(), f, indent=4, default=non_serializable) + + +# Custom Visual Studio project generation logic that supports any platform that has a msvs.py +# script, so Visual Studio can be used to run scons for any platform, with the right defines per target. +# Invoked with scons vsproj=yes +# +# Only platforms that opt in to vs proj generation by having a msvs.py file in the platform folder are included. +# Platforms with a msvs.py file will be added to the solution, but only the current active platform+target+arch +# will have a build configuration generated, because we only know what the right defines/includes/flags/etc are +# on the active build target. +# +# Platforms that don't support an editor target will have a dummy editor target that won't do anything on build, +# but will have the files and configuration for the windows editor target. +# +# To generate build configuration files for all platforms+targets+arch combinations, users can call +# scons vsproj=yes +# for each combination of platform+target+arch. This will generate the relevant vs project files but +# skip the build process. This lets project files be quickly generated even if there are build errors. +# +# To generate AND build from the command line: +# scons vsproj=yes vsproj_gen_only=yes +def generate_vs_project(env, original_args, project_name="godot"): + # Augmented glob_recursive that also fills the dirs argument with traversed directories that have content. + def glob_recursive_2(pattern, dirs, node="."): + from SCons import Node + from SCons.Script import Glob + + results = [] + for f in Glob(str(node) + "/*", source=True): + if type(f) is Node.FS.Dir: + results += glob_recursive_2(pattern, dirs, f) + r = Glob(str(node) + "/" + pattern, source=True) + if len(r) > 0 and not str(node) in dirs: + d = "" + for part in str(node).split("\\"): + d += part + if not d in dirs: + dirs.append(d) + d += "\\" + results += r + return results + + def get_bool(args, option, default): + from SCons.Variables.BoolVariable import _text2bool + + val = args.get(option, default) + if val is not None: + try: + return _text2bool(val) + except: + return default + else: + return default + + def format_key_value(v): + if type(v) in [tuple, list]: + return v[0] if len(v) == 1 else f"{v[0]}={v[1]}" + return v + + filtered_args = original_args.copy() + + # Ignore the "vsproj" option to not regenerate the VS project on every build + filtered_args.pop("vsproj", None) + + # This flag allows users to regenerate the proj files but skip the building process. + # This lets projects be regenerated even if there are build errors. + filtered_args.pop("vsproj_gen_only", None) + + # The "progress" option is ignored as the current compilation progress indication doesn't work in VS + filtered_args.pop("progress", None) + + # We add these three manually because they might not be explicitly passed in, and it's important to always set them. + filtered_args.pop("platform", None) + filtered_args.pop("target", None) + filtered_args.pop("arch", None) + + platform = env["platform"] + target = env["target"] + arch = env["arch"] + + vs_configuration = {} + common_build_prefix = [] + confs = [] + for x in sorted(glob.glob("platform/*")): + # Only platforms that opt in to vs proj generation are included. + if not os.path.isdir(x) or not os.path.exists(x + "/msvs.py"): + continue + tmppath = "./" + x + sys.path.insert(0, tmppath) + import msvs + + vs_plats = [] + vs_confs = [] + try: + platform_name = x[9:] + vs_plats = msvs.get_platforms() + vs_confs = msvs.get_configurations() + val = [] + for plat in vs_plats: + val += [{"platform": plat[0], "architecture": plat[1]}] + + vsconf = {"platform": platform_name, "targets": vs_confs, "arches": val} + confs += [vsconf] + + # Save additional information about the configuration for the actively selected platform, + # so we can generate the platform-specific props file with all the build commands/defines/etc + if platform == platform_name: + common_build_prefix = msvs.get_build_prefix(env) + vs_configuration = vsconf + except Exception: + pass + + sys.path.remove(tmppath) + sys.modules.pop("msvs") + + headers = [] + headers_dirs = [] + for file in glob_recursive_2("*.h", headers_dirs): + headers.append(str(file).replace("/", "\\")) + for file in glob_recursive_2("*.hpp", headers_dirs): + headers.append(str(file).replace("/", "\\")) + + sources = [] + sources_dirs = [] + for file in glob_recursive_2("*.cpp", sources_dirs): + sources.append(str(file).replace("/", "\\")) + for file in glob_recursive_2("*.c", sources_dirs): + sources.append(str(file).replace("/", "\\")) + + others = [] + others_dirs = [] + for file in glob_recursive_2("*.natvis", others_dirs): + others.append(str(file).replace("/", "\\")) + for file in glob_recursive_2("*.glsl", others_dirs): + others.append(str(file).replace("/", "\\")) + + skip_filters = False + import hashlib + import json + + md5 = hashlib.md5( + json.dumps(headers + headers_dirs + sources + sources_dirs + others + others_dirs, sort_keys=True).encode( + "utf-8" + ) + ).hexdigest() + + if os.path.exists(f"{project_name}.vcxproj.filters"): + existing_filters = open(f"{project_name}.vcxproj.filters", "r").read() + match = re.search(r"(?ms)^<!-- CHECKSUM$.([0-9a-f]{32})", existing_filters) + if match is not None and md5 == match.group(1): + skip_filters = True + + import uuid + + # Don't regenerate the filters file if nothing has changed, so we keep the existing UUIDs. + if not skip_filters: + print(f"Regenerating {project_name}.vcxproj.filters") + + filters_template = open("misc/msvs/vcxproj.filters.template", "r").read() + for i in range(1, 10): + filters_template = filters_template.replace(f"%%UUID{i}%%", str(uuid.uuid4())) + + filters = "" + + for d in headers_dirs: + filters += f'<Filter Include="Header Files\\{d}"><UniqueIdentifier>{{{str(uuid.uuid4())}}}</UniqueIdentifier></Filter>\n' + for d in sources_dirs: + filters += f'<Filter Include="Source Files\\{d}"><UniqueIdentifier>{{{str(uuid.uuid4())}}}</UniqueIdentifier></Filter>\n' + for d in others_dirs: + filters += f'<Filter Include="Other Files\\{d}"><UniqueIdentifier>{{{str(uuid.uuid4())}}}</UniqueIdentifier></Filter>\n' + + filters_template = filters_template.replace("%%FILTERS%%", filters) + + filters = "" + for file in headers: + filters += ( + f'<ClInclude Include="{file}"><Filter>Header Files\\{os.path.dirname(file)}</Filter></ClInclude>\n' + ) + filters_template = filters_template.replace("%%INCLUDES%%", filters) + + filters = "" + for file in sources: + filters += ( + f'<ClCompile Include="{file}"><Filter>Source Files\\{os.path.dirname(file)}</Filter></ClCompile>\n' + ) + + filters_template = filters_template.replace("%%COMPILES%%", filters) + + filters = "" + for file in others: + filters += f'<None Include="{file}"><Filter>Other Files\\{os.path.dirname(file)}</Filter></None>\n' + filters_template = filters_template.replace("%%OTHERS%%", filters) + + filters_template = filters_template.replace("%%HASH%%", md5) + + with open(f"{project_name}.vcxproj.filters", "w") as f: + f.write(filters_template) + + envsources = [] + + envsources += env.core_sources + envsources += env.drivers_sources + envsources += env.main_sources + envsources += env.modules_sources + envsources += env.scene_sources + envsources += env.servers_sources + if env.editor_build: + envsources += env.editor_sources + envsources += env.platform_sources + + headers_active = [] + sources_active = [] + others_active = [] + for x in envsources: + fname = "" + if type(x) == type(""): + fname = env.File(x).path + else: + # Some object files might get added directly as a File object and not a list. + try: + fname = env.File(x)[0].path + except: + fname = x.path + pass + + if fname: + fname = fname.replace("\\\\", "/") + parts = os.path.splitext(fname) + basename = parts[0] + ext = parts[1] + idx = fname.find(env["OBJSUFFIX"]) + if ext in [".h", ".hpp"]: + headers_active += [fname] + elif ext in [".c", ".cpp"]: + sources_active += [fname] + elif idx > 0: + basename = fname[:idx] + if os.path.isfile(basename + ".h"): + headers_active += [basename + ".h"] + elif os.path.isfile(basename + ".hpp"): + headers_active += [basename + ".hpp"] + elif basename.endswith(".gen") and os.path.isfile(basename[:-4] + ".h"): + headers_active += [basename[:-4] + ".h"] + if os.path.isfile(basename + ".c"): + sources_active += [basename + ".c"] + elif os.path.isfile(basename + ".cpp"): + sources_active += [basename + ".cpp"] + else: + fname = os.path.relpath(os.path.abspath(fname), env.Dir("").abspath) + others_active += [fname] + + all_items = [] + properties = [] + activeItems = [] + extraItems = [] + + set_headers = set(headers_active) + set_sources = set(sources_active) + set_others = set(others_active) + for file in headers: + all_items.append(f'<ClInclude Include="{file}">') + all_items.append( + f" <ExcludedFromBuild Condition=\"!$(ActiveProjectItemList.Contains(';{file};'))\">true</ExcludedFromBuild>" + ) + all_items.append("</ClInclude>") + if file in set_headers: + activeItems.append(file) + + for file in sources: + all_items.append(f'<ClCompile Include="{file}">') + all_items.append( + f" <ExcludedFromBuild Condition=\"!$(ActiveProjectItemList.Contains(';{file};'))\">true</ExcludedFromBuild>" + ) + all_items.append("</ClCompile>") + if file in set_sources: + activeItems.append(file) + + for file in others: + all_items.append(f'<None Include="{file}">') + all_items.append( + f" <ExcludedFromBuild Condition=\"!$(ActiveProjectItemList.Contains(';{file};'))\">true</ExcludedFromBuild>" + ) + all_items.append("</None>") + if file in set_others: + activeItems.append(file) + + if vs_configuration: + vsconf = "" + for a in vs_configuration["arches"]: + if arch == a["architecture"]: + vsconf = f'{target}|{a["platform"]}' + break + + condition = "'$(Configuration)|$(Platform)'=='" + vsconf + "'" + properties.append("<ActiveProjectItemList>;" + ";".join(activeItems) + ";</ActiveProjectItemList>") + output = f'bin\\godot{env["PROGSUFFIX"]}' + + props_template = open("misc/msvs/props.template", "r").read() + + props_template = props_template.replace("%%VSCONF%%", vsconf) + props_template = props_template.replace("%%CONDITION%%", condition) + props_template = props_template.replace("%%PROPERTIES%%", "\n ".join(properties)) + props_template = props_template.replace("%%EXTRA_ITEMS%%", "\n ".join(extraItems)) + + props_template = props_template.replace("%%OUTPUT%%", output) + + props_template = props_template.replace( + "%%DEFINES%%", ";".join([format_key_value(v) for v in list(env["CPPDEFINES"])]) + ) + props_template = props_template.replace("%%INCLUDES%%", ";".join([str(j) for j in env["CPPPATH"]])) + props_template = props_template.replace( + "%%OPTIONS%%", + " ".join(env["CCFLAGS"]) + " " + " ".join([x for x in env["CXXFLAGS"] if not x.startswith("$")]), + ) + + # Windows allows us to have spaces in paths, so we need + # to double quote off the directory. However, the path ends + # in a backslash, so we need to remove this, lest it escape the + # last double quote off, confusing MSBuild + common_build_postfix = [ + "--directory="$(ProjectDir.TrimEnd('\\'))"", + "progress=no", + f"platform={platform}", + f"target={target}", + f"arch={arch}", + ] + + for arg, value in filtered_args.items(): + common_build_postfix.append(f"{arg}={value}") + + cmd_rebuild = [ + "vsproj=yes", + f"vsproj_name={project_name}", + ] + common_build_postfix + + cmd_clean = [ + "--clean", + ] + common_build_postfix + + commands = "scons" + if len(common_build_prefix) == 0: + commands = "echo Starting SCons && cmd /V /C " + commands + else: + common_build_prefix[0] = "echo Starting SCons && cmd /V /C " + common_build_prefix[0] + + cmd = " ^& ".join(common_build_prefix + [" ".join([commands] + common_build_postfix)]) + props_template = props_template.replace("%%BUILD%%", cmd) + + cmd = " ^& ".join(common_build_prefix + [" ".join([commands] + cmd_rebuild)]) + props_template = props_template.replace("%%REBUILD%%", cmd) + + cmd = " ^& ".join(common_build_prefix + [" ".join([commands] + cmd_clean)]) + props_template = props_template.replace("%%CLEAN%%", cmd) + + with open(f"{project_name}.{platform}.{target}.{arch}.generated.props", "w") as f: + f.write(props_template) + + proj_uuid = str(uuid.uuid4()) + sln_uuid = str(uuid.uuid4()) + + if os.path.exists(f"{project_name}.sln"): + for line in open(f"{project_name}.sln", "r").read().splitlines(): + if line.startswith('Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}")'): + proj_uuid = re.search( + r"\"{(\b[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-\b[0-9a-fA-F]{12}\b)}\"$", + line, + ).group(1) + elif line.strip().startswith("SolutionGuid ="): + sln_uuid = re.search( + r"{(\b[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-\b[0-9a-fA-F]{12}\b)}", line + ).group(1) + break + + configurations = [] + imports = [] + properties = [] + section1 = [] + section2 = [] + for conf in confs: + godot_platform = conf["platform"] + for p in conf["arches"]: + sln_plat = p["platform"] + proj_plat = sln_plat + godot_arch = p["architecture"] + + # Redirect editor configurations for non-Windows platforms to the Windows one, so the solution has all the permutations + # and VS doesn't complain about missing project configurations. + # These configurations are disabled, so they show up but won't build. + if godot_platform != "windows": + section1 += [f"editor|{sln_plat} = editor|{proj_plat}"] + section2 += [ + f"{{{proj_uuid}}}.editor|{proj_plat}.ActiveCfg = editor|{proj_plat}", + ] + + for t in conf["targets"]: + godot_target = t + + # Windows x86 is a special little flower that requires a project platform == Win32 but a solution platform == x86. + if godot_platform == "windows" and godot_target == "editor" and godot_arch == "x86_32": + sln_plat = "x86" + + configurations += [ + f'<ProjectConfiguration Include="{godot_target}|{proj_plat}">', + f" <Configuration>{godot_target}</Configuration>", + f" <Platform>{proj_plat}</Platform>", + "</ProjectConfiguration>", + ] + + if godot_platform != "windows": + configurations += [ + f'<ProjectConfiguration Include="editor|{proj_plat}">', + f" <Configuration>editor</Configuration>", + f" <Platform>{proj_plat}</Platform>", + "</ProjectConfiguration>", + ] + + p = f"{project_name}.{godot_platform}.{godot_target}.{godot_arch}.generated.props" + imports += [ + f'<Import Project="$(MSBuildProjectDirectory)\\{p}" Condition="Exists(\'$(MSBuildProjectDirectory)\\{p}\')"/>' + ] + + section1 += [f"{godot_target}|{sln_plat} = {godot_target}|{sln_plat}"] + + section2 += [ + f"{{{proj_uuid}}}.{godot_target}|{sln_plat}.ActiveCfg = {godot_target}|{proj_plat}", + f"{{{proj_uuid}}}.{godot_target}|{sln_plat}.Build.0 = {godot_target}|{proj_plat}", + ] + + section1 = sorted(section1) + section2 = sorted(section2) + + proj_template = open("misc/msvs/vcxproj.template", "r").read() + + proj_template = proj_template.replace("%%UUID%%", proj_uuid) + proj_template = proj_template.replace("%%CONFS%%", "\n ".join(configurations)) + proj_template = proj_template.replace("%%IMPORTS%%", "\n ".join(imports)) + proj_template = proj_template.replace("%%DEFAULT_ITEMS%%", "\n ".join(all_items)) + proj_template = proj_template.replace("%%PROPERTIES%%", "\n ".join(properties)) + + with open(f"{project_name}.vcxproj", "w") as f: + f.write(proj_template) + + sln_template = open("misc/msvs/sln.template", "r").read() + sln_template = sln_template.replace("%%NAME%%", project_name) + sln_template = sln_template.replace("%%UUID%%", proj_uuid) + sln_template = sln_template.replace("%%SLNUUID%%", sln_uuid) + sln_template = sln_template.replace("%%SECTION1%%", "\n ".join(section1)) + sln_template = sln_template.replace("%%SECTION2%%", "\n ".join(section2)) + with open(f"{project_name}.sln", "w") as f: + f.write(sln_template) + + if get_bool(original_args, "vsproj_gen_only", True): + sys.exit() diff --git a/misc/msvs/props.template b/misc/msvs/props.template new file mode 100644 index 0000000000..9ecd49a25e --- /dev/null +++ b/misc/msvs/props.template @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="17.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='%%VSCONF%%'"> + <NMakeBuildCommandLine>%%BUILD%%</NMakeBuildCommandLine> + <NMakeReBuildCommandLine>%%REBUILD%%</NMakeReBuildCommandLine> + <NMakeCleanCommandLine>%%CLEAN%%</NMakeCleanCommandLine> + <NMakeOutput>%%OUTPUT%%</NMakeOutput> + <NMakePreprocessorDefinitions>%%DEFINES%%</NMakePreprocessorDefinitions> + <NMakeIncludeSearchPath>%%INCLUDES%%</NMakeIncludeSearchPath> + <NMakeForcedIncludes>$(NMakeForcedIncludes)</NMakeForcedIncludes> + <NMakeAssemblySearchPath>$(NMakeAssemblySearchPath)</NMakeAssemblySearchPath> + <NMakeForcedUsingAssemblies>$(NMakeForcedUsingAssemblies)</NMakeForcedUsingAssemblies> + <AdditionalOptions>%%OPTIONS%%</AdditionalOptions> + </PropertyGroup> + <PropertyGroup Condition="%%CONDITION%%"> + %%PROPERTIES%% + </PropertyGroup> + <ItemGroup Condition="%%CONDITION%%"> + %%EXTRA_ITEMS%% + </ItemGroup> +</Project>
\ No newline at end of file diff --git a/misc/msvs/sln.template b/misc/msvs/sln.template new file mode 100644 index 0000000000..7d05548c6e --- /dev/null +++ b/misc/msvs/sln.template @@ -0,0 +1,20 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.7.34221.43 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "%%NAME%%", "%%NAME%%.vcxproj", "{%%UUID%%}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + %%SECTION1%% + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + %%SECTION2%% + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {%%SLNUUID%%} + EndGlobalSection +EndGlobal diff --git a/misc/msvs/vcxproj.filters.template b/misc/msvs/vcxproj.filters.template new file mode 100644 index 0000000000..d57eeee811 --- /dev/null +++ b/misc/msvs/vcxproj.filters.template @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="17.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <Filter Include="Source Files"> + <UniqueIdentifier>%%UUID1%%</UniqueIdentifier> + </Filter> + <Filter Include="Header Files"> + <UniqueIdentifier>%%UUID2%%</UniqueIdentifier> + </Filter> + <Filter Include="Resource Files"> + <UniqueIdentifier>%%UUID3%%</UniqueIdentifier> + </Filter> + <Filter Include="Scripts"> + <UniqueIdentifier>%%UUID4%%</UniqueIdentifier> + </Filter> + %%FILTERS%% + </ItemGroup> + <ItemGroup> + %%COMPILES%% + </ItemGroup> + <ItemGroup> + %%INCLUDES%% + </ItemGroup> + <ItemGroup> + %%OTHERS%% + </ItemGroup> +</Project> +<!-- CHECKSUM +%%HASH%% +-->
\ No newline at end of file diff --git a/misc/msvs/vcxproj.template b/misc/msvs/vcxproj.template new file mode 100644 index 0000000000..a1cf22bfb9 --- /dev/null +++ b/misc/msvs/vcxproj.template @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup Label="ProjectConfigurations"> + %%CONFS%% + </ItemGroup> + <PropertyGroup Label="Globals"> + <ProjectGuid>{%%UUID%%}</ProjectGuid> + <RootNamespace>godot</RootNamespace> + <Keyword>MakeFileProj</Keyword> + <VCProjectUpgraderObjectName>NoUpgrade</VCProjectUpgraderObjectName> + </PropertyGroup> + <PropertyGroup> + %%PROPERTIES%% + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Label="Configuration"> + <ConfigurationType>Makefile</ConfigurationType> + <UseOfMfc>false</UseOfMfc> + <PlatformToolset>v143</PlatformToolset> + <OutDir>$(SolutionDir)\bin\$(Platform)\$(Configuration)\</OutDir> + <IntDir>obj\$(Platform)\$(Configuration)\</IntDir> + <LayoutDir>$(OutDir)\Layout</LayoutDir> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> + <ImportGroup Label="ExtensionSettings"> + </ImportGroup> + <ImportGroup Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <PropertyGroup Label="UserMacros" /> + <PropertyGroup> + <_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion> + <ActiveProjectItemList></ActiveProjectItemList> + </PropertyGroup> + %%IMPORTS%% + <ItemGroup Condition="'$(IncludeListImported)'==''"> + %%DEFAULT_ITEMS%% + </ItemGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> + <ImportGroup Label="ExtensionTargets"> + </ImportGroup> +</Project>
\ No newline at end of file diff --git a/misc/scripts/black_format.sh b/misc/scripts/black_format.sh index 3a64284eb6..48dc14c734 100755 --- a/misc/scripts/black_format.sh +++ b/misc/scripts/black_format.sh @@ -20,7 +20,7 @@ fi # A diff has been created, notify the user, clean up, and exit. printf "\n\e[1;33m*** The following changes must be made to comply with the formatting rules:\e[0m\n\n" # Perl commands replace trailing spaces with `·` and tabs with `<TAB>`. -printf "$diff\n" | perl -pe 's/(.*[^ ])( +)(\e\[m)$/my $spaces="·" x length($2); sprintf("$1$spaces$3")/ge' | perl -pe 's/(.*[^\t])(\t+)(\e\[m)$/my $tabs="<TAB>" x length($2); sprintf("$1$tabs$3")/ge' +printf "%s\n" "$diff" | perl -pe 's/(.*[^ ])( +)(\e\[m)$/my $spaces="·" x length($2); sprintf("$1$spaces$3")/ge' | perl -pe 's/(.*[^\t])(\t+)(\e\[m)$/my $tabs="<TAB>" x length($2); sprintf("$1$tabs$3")/ge' printf "\n\e[1;91m*** Please fix your commit(s) with 'git commit --amend' or 'git rebase -i <hash>'\e[0m\n" exit 1 diff --git a/misc/scripts/clang_format.sh b/misc/scripts/clang_format.sh index 40d94d4276..8b59519606 100755 --- a/misc/scripts/clang_format.sh +++ b/misc/scripts/clang_format.sh @@ -47,7 +47,7 @@ fi # A diff has been created, notify the user, clean up, and exit. printf "\n\e[1;33m*** The following changes must be made to comply with the formatting rules:\e[0m\n\n" # Perl commands replace trailing spaces with `·` and tabs with `<TAB>`. -printf "$diff\n" | perl -pe 's/(.*[^ ])( +)(\e\[m)$/my $spaces="·" x length($2); sprintf("$1$spaces$3")/ge' | perl -pe 's/(.*[^\t])(\t+)(\e\[m)$/my $tabs="<TAB>" x length($2); sprintf("$1$tabs$3")/ge' +printf "%s\n" "$diff" | perl -pe 's/(.*[^ ])( +)(\e\[m)$/my $spaces="·" x length($2); sprintf("$1$spaces$3")/ge' | perl -pe 's/(.*[^\t])(\t+)(\e\[m)$/my $tabs="<TAB>" x length($2); sprintf("$1$tabs$3")/ge' printf "\n\e[1;91m*** Please fix your commit(s) with 'git commit --amend' or 'git rebase -i <hash>'\e[0m\n" exit 1 diff --git a/misc/scripts/clang_tidy.sh b/misc/scripts/clang_tidy.sh index c4811b903c..0c6998b491 100755 --- a/misc/scripts/clang_tidy.sh +++ b/misc/scripts/clang_tidy.sh @@ -27,7 +27,7 @@ fi # A diff has been created, notify the user, clean up, and exit. printf "\n\e[1;33m*** The following changes must be made to comply with the formatting rules:\e[0m\n\n" # Perl commands replace trailing spaces with `·` and tabs with `<TAB>`. -printf "$diff\n" | perl -pe 's/(.*[^ ])( +)(\e\[m)$/my $spaces="·" x length($2); sprintf("$1$spaces$3")/ge' | perl -pe 's/(.*[^\t])(\t+)(\e\[m)$/my $tabs="<TAB>" x length($2); sprintf("$1$tabs$3")/ge' +printf "%s\n" "$diff" | perl -pe 's/(.*[^ ])( +)(\e\[m)$/my $spaces="·" x length($2); sprintf("$1$spaces$3")/ge' | perl -pe 's/(.*[^\t])(\t+)(\e\[m)$/my $tabs="<TAB>" x length($2); sprintf("$1$tabs$3")/ge' printf "\n\e[1;91m*** Please fix your commit(s) with 'git commit --amend' or 'git rebase -i <hash>'\e[0m\n" exit 1 diff --git a/misc/scripts/dotnet_format.sh b/misc/scripts/dotnet_format.sh index cac00f5cb1..e2b4ba5e43 100755 --- a/misc/scripts/dotnet_format.sh +++ b/misc/scripts/dotnet_format.sh @@ -31,7 +31,7 @@ fi # A diff has been created, notify the user, clean up, and exit. printf "\n\e[1;33m*** The following changes must be made to comply with the formatting rules:\e[0m\n\n" # Perl commands replace trailing spaces with `·` and tabs with `<TAB>`. -printf "$diff\n" | perl -pe 's/(.*[^ ])( +)(\e\[m)$/my $spaces="·" x length($2); sprintf("$1$spaces$3")/ge' | perl -pe 's/(.*[^\t])(\t+)(\e\[m)$/my $tabs="<TAB>" x length($2); sprintf("$1$tabs$3")/ge' +printf "%s\n" "$diff" | perl -pe 's/(.*[^ ])( +)(\e\[m)$/my $spaces="·" x length($2); sprintf("$1$spaces$3")/ge' | perl -pe 's/(.*[^\t])(\t+)(\e\[m)$/my $tabs="<TAB>" x length($2); sprintf("$1$tabs$3")/ge' printf "\n\e[1;91m*** Please fix your commit(s) with 'git commit --amend' or 'git rebase -i <hash>'\e[0m\n" exit 1 diff --git a/misc/scripts/file_format.sh b/misc/scripts/file_format.sh index 94a3affbd7..ad58657883 100755 --- a/misc/scripts/file_format.sh +++ b/misc/scripts/file_format.sh @@ -82,7 +82,7 @@ then # A diff has been created, notify the user, clean up, and exit. printf "\n\e[1;33m*** The following changes must be made to comply with the formatting rules:\e[0m\n\n" # Perl commands replace trailing spaces with `·` and tabs with `<TAB>`. - printf "$diff\n" | perl -pe 's/(.*[^ ])( +)(\e\[m)$/my $spaces="·" x length($2); sprintf("$1$spaces$3")/ge' | perl -pe 's/(.*[^\t])(\t+)(\e\[m)$/my $tabs="<TAB>" x length($2); sprintf("$1$tabs$3")/ge' + printf "%s\n" "$diff" | perl -pe 's/(.*[^ ])( +)(\e\[m)$/my $spaces="·" x length($2); sprintf("$1$spaces$3")/ge' | perl -pe 's/(.*[^\t])(\t+)(\e\[m)$/my $tabs="<TAB>" x length($2); sprintf("$1$tabs$3")/ge' fi printf "\n\e[1;91m*** Please fix your commit(s) with 'git commit --amend' or 'git rebase -i <hash>'\e[0m\n" diff --git a/misc/scripts/header_guards.sh b/misc/scripts/header_guards.sh index ce0b3f334d..a79ccd4bee 100755 --- a/misc/scripts/header_guards.sh +++ b/misc/scripts/header_guards.sh @@ -81,7 +81,7 @@ fi # A diff has been created, notify the user, clean up, and exit. printf "\n\e[1;33m*** The following changes must be made to comply with the formatting rules:\e[0m\n\n" # Perl commands replace trailing spaces with `·` and tabs with `<TAB>`. -printf "$diff\n" | perl -pe 's/(.*[^ ])( +)(\e\[m)$/my $spaces="·" x length($2); sprintf("$1$spaces$3")/ge' | perl -pe 's/(.*[^\t])(\t+)(\e\[m)$/my $tabs="<TAB>" x length($2); sprintf("$1$tabs$3")/ge' +printf "%s\n" "$diff" | perl -pe 's/(.*[^ ])( +)(\e\[m)$/my $spaces="·" x length($2); sprintf("$1$spaces$3")/ge' | perl -pe 's/(.*[^\t])(\t+)(\e\[m)$/my $tabs="<TAB>" x length($2); sprintf("$1$tabs$3")/ge' printf "\n\e[1;91m*** Please fix your commit(s) with 'git commit --amend' or 'git rebase -i <hash>'\e[0m\n" exit 1 diff --git a/modules/gdscript/editor/script_templates/CharacterBody2D/basic_movement.gd b/modules/gdscript/editor/script_templates/CharacterBody2D/basic_movement.gd index 28ab080dd2..bd4816827f 100644 --- a/modules/gdscript/editor/script_templates/CharacterBody2D/basic_movement.gd +++ b/modules/gdscript/editor/script_templates/CharacterBody2D/basic_movement.gd @@ -6,14 +6,11 @@ extends _BASE_ const SPEED = 300.0 const JUMP_VELOCITY = -400.0 -# Get the gravity from the project settings to be synced with RigidBody nodes. -var gravity: int = ProjectSettings.get_setting("physics/2d/default_gravity") - func _physics_process(delta: float) -> void: # Add the gravity. if not is_on_floor(): - velocity.y += gravity * delta + velocity += get_gravity() * delta # Handle jump. if Input.is_action_just_pressed("ui_accept") and is_on_floor(): diff --git a/modules/gdscript/editor/script_templates/CharacterBody3D/basic_movement.gd b/modules/gdscript/editor/script_templates/CharacterBody3D/basic_movement.gd index 9b0e4be4ed..f9c4f70a24 100644 --- a/modules/gdscript/editor/script_templates/CharacterBody3D/basic_movement.gd +++ b/modules/gdscript/editor/script_templates/CharacterBody3D/basic_movement.gd @@ -6,14 +6,11 @@ extends _BASE_ const SPEED = 5.0 const JUMP_VELOCITY = 4.5 -# Get the gravity from the project settings to be synced with RigidBody nodes. -var gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity") - func _physics_process(delta: float) -> void: # Add the gravity. if not is_on_floor(): - velocity.y -= gravity * delta + velocity += get_gravity() * delta # Handle jump. if Input.is_action_just_pressed("ui_accept") and is_on_floor(): diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index 98a3a1268f..29cf7bc6ca 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/modules/gdscript/gdscript_tokenizer.cpp @@ -672,6 +672,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() { bool has_decimal = false; bool has_exponent = false; bool has_error = false; + bool need_digits = false; bool (*digit_check_func)(char32_t) = is_digit; // Sign before hexadecimal or binary. @@ -686,11 +687,13 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() { // Hexadecimal. base = 16; digit_check_func = is_hex_digit; + need_digits = true; _advance(); } else if (_peek() == 'b') { // Binary. base = 2; digit_check_func = is_binary_digit; + need_digits = true; _advance(); } } @@ -717,6 +720,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() { } previous_was_underscore = true; } else { + need_digits = false; previous_was_underscore = false; } _advance(); @@ -820,6 +824,16 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() { } } + if (need_digits) { + // No digits in hex or bin literal. + Token error = make_error(vformat(R"(Expected %s digit after "0%c".)", (base == 16 ? "hexadecimal" : "binary"), (base == 16 ? 'x' : 'b'))); + error.start_column = column; + error.leftmost_column = column; + error.end_column = column + 1; + error.rightmost_column = column + 1; + return error; + } + // Detect extra decimal point. if (!has_error && has_decimal && _peek() == '.' && _peek(1) != '.') { Token error = make_error("Cannot use a decimal point twice in a number."); diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index f2dccf6d68..73e26fbef5 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -5982,8 +5982,13 @@ void GLTFDocument::_generate_skeleton_bone_node(Ref<GLTFState> p_state, const GL p_scene_parent = bone_attachment; } if (skeleton->get_parent() == nullptr) { - p_scene_parent->add_child(skeleton, true); - skeleton->set_owner(p_scene_root); + if (p_scene_root) { + p_scene_parent->add_child(skeleton, true); + skeleton->set_owner(p_scene_root); + } else { + p_scene_parent = skeleton; + p_scene_root = skeleton; + } } } @@ -6582,7 +6587,7 @@ float GLTFDocument::get_max_component(const Color &p_color) { return MAX(MAX(r, g), b); } -void GLTFDocument::_process_mesh_instances(Ref<GLTFState> p_state) { +void GLTFDocument::_process_mesh_instances(Ref<GLTFState> p_state, Node *p_scene_root) { for (GLTFNodeIndex node_i = 0; node_i < p_state->nodes.size(); ++node_i) { Ref<GLTFNode> node = p_state->nodes[node_i]; @@ -6607,7 +6612,7 @@ void GLTFDocument::_process_mesh_instances(Ref<GLTFState> p_state) { mi->get_parent()->remove_child(mi); skeleton->add_child(mi, true); - mi->set_owner(skeleton->get_owner()); + mi->set_owner(p_scene_root); mi->set_skin(p_state->skins.write[skin_i]->godot_skin); mi->set_skeleton_path(mi->get_path_to(skeleton)); @@ -7461,7 +7466,7 @@ Node *GLTFDocument::generate_scene(Ref<GLTFState> p_state, float p_bake_fps, boo Error err = OK; Node *root = _generate_scene_node_tree(p_state); ERR_FAIL_NULL_V(root, nullptr); - _process_mesh_instances(p_state); + _process_mesh_instances(p_state, root); if (p_state->get_create_animations() && p_state->animations.size()) { AnimationPlayer *ap = memnew(AnimationPlayer); root->add_child(ap, true); diff --git a/modules/gltf/gltf_document.h b/modules/gltf/gltf_document.h index f321bb7111..04c85f3b07 100644 --- a/modules/gltf/gltf_document.h +++ b/modules/gltf/gltf_document.h @@ -326,7 +326,7 @@ public: Error _parse_gltf_state(Ref<GLTFState> p_state, const String &p_search_path); Error _parse_asset_header(Ref<GLTFState> p_state); Error _parse_gltf_extensions(Ref<GLTFState> p_state); - void _process_mesh_instances(Ref<GLTFState> p_state); + void _process_mesh_instances(Ref<GLTFState> p_state, Node *p_scene_root); Node *_generate_scene_node_tree(Ref<GLTFState> p_state); void _generate_scene_node(Ref<GLTFState> p_state, const GLTFNodeIndex p_node_index, Node *p_scene_parent, Node *p_scene_root); void _generate_skeleton_bone_node(Ref<GLTFState> p_state, const GLTFNodeIndex p_node_index, Node *p_scene_parent, Node *p_scene_root); diff --git a/modules/mono/editor/script_templates/CharacterBody2D/basic_movement.cs b/modules/mono/editor/script_templates/CharacterBody2D/basic_movement.cs index 87468fb433..698157c6b4 100644 --- a/modules/mono/editor/script_templates/CharacterBody2D/basic_movement.cs +++ b/modules/mono/editor/script_templates/CharacterBody2D/basic_movement.cs @@ -8,20 +8,21 @@ public partial class _CLASS_ : _BASE_ public const float Speed = 300.0f; public const float JumpVelocity = -400.0f; - // Get the gravity from the project settings to be synced with RigidBody nodes. - public float gravity = ProjectSettings.GetSetting("physics/2d/default_gravity").AsSingle(); - public override void _PhysicsProcess(double delta) { Vector2 velocity = Velocity; // Add the gravity. if (!IsOnFloor()) - velocity.Y += gravity * (float)delta; + { + velocity += GetGravity() * (float)delta; + } // Handle Jump. if (Input.IsActionJustPressed("ui_accept") && IsOnFloor()) + { velocity.Y = JumpVelocity; + } // Get the input direction and handle the movement/deceleration. // As good practice, you should replace UI actions with custom gameplay actions. diff --git a/modules/mono/editor/script_templates/CharacterBody3D/basic_movement.cs b/modules/mono/editor/script_templates/CharacterBody3D/basic_movement.cs index ddeb9d7e00..30dabd31d9 100644 --- a/modules/mono/editor/script_templates/CharacterBody3D/basic_movement.cs +++ b/modules/mono/editor/script_templates/CharacterBody3D/basic_movement.cs @@ -8,20 +8,21 @@ public partial class _CLASS_ : _BASE_ public const float Speed = 5.0f; public const float JumpVelocity = 4.5f; - // Get the gravity from the project settings to be synced with RigidBody nodes. - public float gravity = ProjectSettings.GetSetting("physics/3d/default_gravity").AsSingle(); - public override void _PhysicsProcess(double delta) { Vector3 velocity = Velocity; // Add the gravity. if (!IsOnFloor()) - velocity.Y -= gravity * (float)delta; + { + velocity += GetGravity() * (float)delta; + } // Handle Jump. if (Input.IsActionJustPressed("ui_accept") && IsOnFloor()) + { velocity.Y = JumpVelocity; + } // Get the input direction and handle the movement/deceleration. // As good practice, you should replace UI actions with custom gameplay actions. diff --git a/modules/multiplayer/multiplayer_synchronizer.cpp b/modules/multiplayer/multiplayer_synchronizer.cpp index 12b4ac540d..02e3a11964 100644 --- a/modules/multiplayer/multiplayer_synchronizer.cpp +++ b/modules/multiplayer/multiplayer_synchronizer.cpp @@ -49,11 +49,11 @@ void MultiplayerSynchronizer::_stop() { } #endif root_node_cache = ObjectID(); - reset(); Node *node = is_inside_tree() ? get_node_or_null(root_path) : nullptr; if (node) { get_multiplayer()->object_configuration_remove(node, this); } + reset(); } void MultiplayerSynchronizer::_start() { diff --git a/modules/multiplayer/scene_replication_interface.cpp b/modules/multiplayer/scene_replication_interface.cpp index 31307e648d..35ff62dd06 100644 --- a/modules/multiplayer/scene_replication_interface.cpp +++ b/modules/multiplayer/scene_replication_interface.cpp @@ -189,9 +189,6 @@ void SceneReplicationInterface::_node_ready(const ObjectID &p_oid) { spawned_nodes.insert(oid); if (_has_authority(spawner)) { - if (tobj.net_id == 0) { - tobj.net_id = ++last_net_id; - } _update_spawn_visibility(0, oid); } } @@ -249,6 +246,7 @@ Error SceneReplicationInterface::on_replication_start(Object *p_obj, Variant p_c uint32_t net_id = pending_sync_net_ids[0]; pending_sync_net_ids.pop_front(); peers_info[pending_spawn_remote].recv_sync_ids[net_id] = sync->get_instance_id(); + sync->set_net_id(net_id); // Try to apply spawn state (before ready). if (pending_buffer_size > 0) { @@ -472,9 +470,13 @@ Error SceneReplicationInterface::_make_spawn_packet(Node *p_node, MultiplayerSpa ERR_FAIL_COND_V(!multiplayer || !p_node || !p_spawner, ERR_BUG); const ObjectID oid = p_node->get_instance_id(); - const TrackedNode *tnode = tracked_nodes.getptr(oid); + TrackedNode *tnode = tracked_nodes.getptr(oid); ERR_FAIL_NULL_V(tnode, ERR_INVALID_PARAMETER); + if (tnode->net_id == 0) { + // Ensure the node has an ID. + tnode->net_id = ++last_net_id; + } uint32_t nid = tnode->net_id; ERR_FAIL_COND_V(!nid, ERR_UNCONFIGURED); diff --git a/modules/openxr/doc_classes/OpenXRAPIExtension.xml b/modules/openxr/doc_classes/OpenXRAPIExtension.xml index e795311651..1bc3a1a3fc 100644 --- a/modules/openxr/doc_classes/OpenXRAPIExtension.xml +++ b/modules/openxr/doc_classes/OpenXRAPIExtension.xml @@ -75,6 +75,12 @@ Returns the id of the system, which is a [url=https://registry.khronos.org/OpenXR/specs/1.0/man/html/XrSystemId.html]XrSystemId[/url] cast to an integer. </description> </method> + <method name="is_environment_blend_mode_alpha_supported"> + <return type="int" enum="OpenXRAPIExtension.OpenXRAlphaBlendModeSupport" /> + <description> + Returns [enum OpenXRAPIExtension.OpenXRAlphaBlendModeSupport] denoting if [constant XRInterface.XR_ENV_BLEND_MODE_ALPHA_BLEND] is really support, emulated or not supported at all. + </description> + </method> <method name="is_initialized"> <return type="bool" /> <description> @@ -94,6 +100,20 @@ Returns [code]true[/code] if OpenXR is enabled. </description> </method> + <method name="register_composition_layer_provider"> + <return type="void" /> + <param index="0" name="extension" type="OpenXRExtensionWrapperExtension" /> + <description> + Registers the given extension as a composition layer provider. + </description> + </method> + <method name="set_emulate_environment_blend_mode_alpha_blend"> + <return type="void" /> + <param index="0" name="enabled" type="bool" /> + <description> + If set to [code]true[/code], an OpenXR extension is loaded which is capable of emulating the [constant XRInterface.XR_ENV_BLEND_MODE_ALPHA_BLEND] blend mode. + </description> + </method> <method name="transform_from_pose"> <return type="Transform3D" /> <param index="0" name="pose" type="const void*" /> @@ -101,6 +121,13 @@ Creates a [Transform3D] from an [url=https://registry.khronos.org/OpenXR/specs/1.0/man/html/XrPosef.html]XrPosef[/url]. </description> </method> + <method name="unregister_composition_layer_provider"> + <return type="void" /> + <param index="0" name="extension" type="OpenXRExtensionWrapperExtension" /> + <description> + Unregisters the given extension as a composition layer provider. + </description> + </method> <method name="xr_result"> <return type="bool" /> <param index="0" name="result" type="int" /> @@ -111,4 +138,15 @@ </description> </method> </methods> + <constants> + <constant name="OPENXR_ALPHA_BLEND_MODE_SUPPORT_NONE" value="0" enum="OpenXRAlphaBlendModeSupport"> + Means that [constant XRInterface.XR_ENV_BLEND_MODE_ALPHA_BLEND] isn't supported at all. + </constant> + <constant name="OPENXR_ALPHA_BLEND_MODE_SUPPORT_REAL" value="1" enum="OpenXRAlphaBlendModeSupport"> + Means that [constant XRInterface.XR_ENV_BLEND_MODE_ALPHA_BLEND] is really supported. + </constant> + <constant name="OPENXR_ALPHA_BLEND_MODE_SUPPORT_EMULATING" value="2" enum="OpenXRAlphaBlendModeSupport"> + Means that [constant XRInterface.XR_ENV_BLEND_MODE_ALPHA_BLEND] is emulated. + </constant> + </constants> </class> diff --git a/modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml b/modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml index fa93704a0a..b923d9244d 100644 --- a/modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml +++ b/modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml @@ -9,6 +9,12 @@ <tutorials> </tutorials> <methods> + <method name="_get_composition_layer" qualifiers="virtual"> + <return type="int" /> + <description> + Returns a pointer to a [code]XrCompositionLayerBaseHeader[/code] struct to provide a composition layer. This will only be called if the extension previously registered itself with [method OpenXRAPIExtension.register_composition_layer_provider]. + </description> + </method> <method name="_get_requested_extensions" qualifiers="virtual"> <return type="Dictionary" /> <description> diff --git a/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp b/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp index 23238cabb9..5ad7a97eca 100644 --- a/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp +++ b/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp @@ -39,6 +39,7 @@ void OpenXRExtensionWrapperExtension::_bind_methods() { GDVIRTUAL_BIND(_set_session_create_and_get_next_pointer, "next_pointer"); GDVIRTUAL_BIND(_set_swapchain_create_info_and_get_next_pointer, "next_pointer"); GDVIRTUAL_BIND(_set_hand_joint_locations_and_get_next_pointer, "hand_index", "next_pointer"); + GDVIRTUAL_BIND(_get_composition_layer); GDVIRTUAL_BIND(_on_register_metadata); GDVIRTUAL_BIND(_on_before_instance_created); GDVIRTUAL_BIND(_on_instance_created, "instance"); @@ -128,6 +129,16 @@ void *OpenXRExtensionWrapperExtension::set_hand_joint_locations_and_get_next_poi return nullptr; } +XrCompositionLayerBaseHeader *OpenXRExtensionWrapperExtension::get_composition_layer() { + uint64_t pointer; + + if (GDVIRTUAL_CALL(_get_composition_layer, pointer)) { + return reinterpret_cast<XrCompositionLayerBaseHeader *>(pointer); + } + + return nullptr; +} + void OpenXRExtensionWrapperExtension::on_register_metadata() { GDVIRTUAL_CALL(_on_register_metadata); } diff --git a/modules/openxr/extensions/openxr_extension_wrapper_extension.h b/modules/openxr/extensions/openxr_extension_wrapper_extension.h index 6acf229e16..4d8b19f4fd 100644 --- a/modules/openxr/extensions/openxr_extension_wrapper_extension.h +++ b/modules/openxr/extensions/openxr_extension_wrapper_extension.h @@ -39,7 +39,7 @@ #include "core/os/thread_safe.h" #include "core/variant/native_ptr.h" -class OpenXRExtensionWrapperExtension : public Object, OpenXRExtensionWrapper { +class OpenXRExtensionWrapperExtension : public Object, public OpenXRExtensionWrapper, public OpenXRCompositionLayerProvider { GDCLASS(OpenXRExtensionWrapperExtension, Object); protected: @@ -59,6 +59,7 @@ public: virtual void *set_session_create_and_get_next_pointer(void *p_next_pointer) override; virtual void *set_swapchain_create_info_and_get_next_pointer(void *p_next_pointer) override; virtual void *set_hand_joint_locations_and_get_next_pointer(int p_hand_index, void *p_next_pointer) override; + virtual XrCompositionLayerBaseHeader *get_composition_layer() override; //TODO workaround as GDExtensionPtr<void> return type results in build error in godot-cpp GDVIRTUAL1R(uint64_t, _set_system_properties_and_get_next_pointer, GDExtensionPtr<void>); @@ -66,6 +67,7 @@ public: GDVIRTUAL1R(uint64_t, _set_session_create_and_get_next_pointer, GDExtensionPtr<void>); GDVIRTUAL1R(uint64_t, _set_swapchain_create_info_and_get_next_pointer, GDExtensionPtr<void>); GDVIRTUAL2R(uint64_t, _set_hand_joint_locations_and_get_next_pointer, int, GDExtensionPtr<void>); + GDVIRTUAL0R(uint64_t, _get_composition_layer); virtual void on_register_metadata() override; virtual void on_before_instance_created() override; diff --git a/modules/openxr/extensions/openxr_fb_passthrough_extension_wrapper.cpp b/modules/openxr/extensions/openxr_fb_passthrough_extension_wrapper.cpp deleted file mode 100644 index 3da0ffd9c7..0000000000 --- a/modules/openxr/extensions/openxr_fb_passthrough_extension_wrapper.cpp +++ /dev/null @@ -1,246 +0,0 @@ -/**************************************************************************/ -/* openxr_fb_passthrough_extension_wrapper.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_fb_passthrough_extension_wrapper.h" - -#include "core/os/os.h" -#include "scene/main/viewport.h" -#include "scene/main/window.h" - -using namespace godot; - -OpenXRFbPassthroughExtensionWrapper *OpenXRFbPassthroughExtensionWrapper::singleton = nullptr; - -OpenXRFbPassthroughExtensionWrapper *OpenXRFbPassthroughExtensionWrapper::get_singleton() { - return singleton; -} - -OpenXRFbPassthroughExtensionWrapper::OpenXRFbPassthroughExtensionWrapper() { - singleton = this; -} - -OpenXRFbPassthroughExtensionWrapper::~OpenXRFbPassthroughExtensionWrapper() { - cleanup(); -} - -HashMap<String, bool *> OpenXRFbPassthroughExtensionWrapper::get_requested_extensions() { - HashMap<String, bool *> request_extensions; - - request_extensions[XR_FB_PASSTHROUGH_EXTENSION_NAME] = &fb_passthrough_ext; - request_extensions[XR_FB_TRIANGLE_MESH_EXTENSION_NAME] = &fb_triangle_mesh_ext; - - return request_extensions; -} - -void OpenXRFbPassthroughExtensionWrapper::cleanup() { - fb_passthrough_ext = false; - fb_triangle_mesh_ext = false; -} - -Viewport *OpenXRFbPassthroughExtensionWrapper::get_main_viewport() { - MainLoop *main_loop = OS::get_singleton()->get_main_loop(); - if (!main_loop) { - print_error("Unable to retrieve main loop"); - return nullptr; - } - - SceneTree *scene_tree = Object::cast_to<SceneTree>(main_loop); - if (!scene_tree) { - print_error("Unable to retrieve scene tree"); - return nullptr; - } - - Viewport *viewport = scene_tree->get_root()->get_viewport(); - return viewport; -} - -void OpenXRFbPassthroughExtensionWrapper::on_instance_created(const XrInstance instance) { - if (fb_passthrough_ext) { - bool result = initialize_fb_passthrough_extension(instance); - if (!result) { - print_error("Failed to initialize fb_passthrough extension"); - fb_passthrough_ext = false; - } - } - - if (fb_triangle_mesh_ext) { - bool result = initialize_fb_triangle_mesh_extension(instance); - if (!result) { - print_error("Failed to initialize fb_triangle_mesh extension"); - fb_triangle_mesh_ext = false; - } - } - - if (fb_passthrough_ext) { - OpenXRAPI::get_singleton()->register_composition_layer_provider(this); - } -} - -bool OpenXRFbPassthroughExtensionWrapper::is_passthrough_enabled() { - return fb_passthrough_ext && passthrough_handle != XR_NULL_HANDLE && passthrough_layer != XR_NULL_HANDLE; -} - -bool OpenXRFbPassthroughExtensionWrapper::start_passthrough() { - if (passthrough_handle == XR_NULL_HANDLE) { - return false; - } - - if (is_passthrough_enabled()) { - return true; - } - - // Start the passthrough feature - XrResult result = xrPassthroughStartFB(passthrough_handle); - if (!is_valid_passthrough_result(result, "Failed to start passthrough")) { - stop_passthrough(); - return false; - } - - // Create the passthrough layer - XrPassthroughLayerCreateInfoFB passthrough_layer_config = { - XR_TYPE_PASSTHROUGH_LAYER_CREATE_INFO_FB, - nullptr, - passthrough_handle, - XR_PASSTHROUGH_IS_RUNNING_AT_CREATION_BIT_FB, - XR_PASSTHROUGH_LAYER_PURPOSE_RECONSTRUCTION_FB, - }; - result = xrCreatePassthroughLayerFB(OpenXRAPI::get_singleton()->get_session(), &passthrough_layer_config, &passthrough_layer); - if (!is_valid_passthrough_result(result, "Failed to create the passthrough layer")) { - stop_passthrough(); - return false; - } - - // Check if the the viewport has transparent background - Viewport *viewport = get_main_viewport(); - if (viewport && !viewport->has_transparent_background()) { - print_error("Main viewport doesn't have transparent background! Passthrough may not properly render."); - } - - return true; -} - -void OpenXRFbPassthroughExtensionWrapper::on_session_created(const XrSession session) { - if (fb_passthrough_ext) { - // Create the passthrough feature and start it. - XrPassthroughCreateInfoFB passthrough_create_info = { - XR_TYPE_PASSTHROUGH_CREATE_INFO_FB, - nullptr, - 0, - }; - - XrResult result = xrCreatePassthroughFB(OpenXRAPI::get_singleton()->get_session(), &passthrough_create_info, &passthrough_handle); - if (!OpenXRAPI::get_singleton()->xr_result(result, "Failed to create passthrough")) { - passthrough_handle = XR_NULL_HANDLE; - return; - } - } -} - -XrCompositionLayerBaseHeader *OpenXRFbPassthroughExtensionWrapper::get_composition_layer() { - if (is_passthrough_enabled()) { - composition_passthrough_layer.layerHandle = passthrough_layer; - return (XrCompositionLayerBaseHeader *)&composition_passthrough_layer; - } else { - return nullptr; - } -} - -void OpenXRFbPassthroughExtensionWrapper::stop_passthrough() { - if (!fb_passthrough_ext) { - return; - } - - XrResult result; - if (passthrough_layer != XR_NULL_HANDLE) { - // Destroy the layer - result = xrDestroyPassthroughLayerFB(passthrough_layer); - OpenXRAPI::get_singleton()->xr_result(result, "Unable to destroy passthrough layer"); - passthrough_layer = XR_NULL_HANDLE; - } - - if (passthrough_handle != XR_NULL_HANDLE) { - result = xrPassthroughPauseFB(passthrough_handle); - OpenXRAPI::get_singleton()->xr_result(result, "Unable to stop passthrough feature"); - } -} - -void OpenXRFbPassthroughExtensionWrapper::on_session_destroyed() { - if (fb_passthrough_ext) { - stop_passthrough(); - - XrResult result; - if (passthrough_handle != XR_NULL_HANDLE) { - result = xrDestroyPassthroughFB(passthrough_handle); - OpenXRAPI::get_singleton()->xr_result(result, "Unable to destroy passthrough feature"); - passthrough_handle = XR_NULL_HANDLE; - } - } -} - -void OpenXRFbPassthroughExtensionWrapper::on_instance_destroyed() { - if (fb_passthrough_ext) { - OpenXRAPI::get_singleton()->unregister_composition_layer_provider(this); - } - cleanup(); -} - -bool OpenXRFbPassthroughExtensionWrapper::initialize_fb_passthrough_extension(const XrInstance p_instance) { - ERR_FAIL_NULL_V(OpenXRAPI::get_singleton(), false); - - EXT_INIT_XR_FUNC_V(xrCreatePassthroughFB); - EXT_INIT_XR_FUNC_V(xrDestroyPassthroughFB); - EXT_INIT_XR_FUNC_V(xrPassthroughStartFB); - EXT_INIT_XR_FUNC_V(xrPassthroughPauseFB); - EXT_INIT_XR_FUNC_V(xrCreatePassthroughLayerFB); - EXT_INIT_XR_FUNC_V(xrDestroyPassthroughLayerFB); - EXT_INIT_XR_FUNC_V(xrPassthroughLayerPauseFB); - EXT_INIT_XR_FUNC_V(xrPassthroughLayerResumeFB); - EXT_INIT_XR_FUNC_V(xrPassthroughLayerSetStyleFB); - EXT_INIT_XR_FUNC_V(xrCreateGeometryInstanceFB); - EXT_INIT_XR_FUNC_V(xrDestroyGeometryInstanceFB); - EXT_INIT_XR_FUNC_V(xrGeometryInstanceSetTransformFB); - - return true; -} - -bool OpenXRFbPassthroughExtensionWrapper::initialize_fb_triangle_mesh_extension(const XrInstance p_instance) { - ERR_FAIL_NULL_V(OpenXRAPI::get_singleton(), false); - - EXT_INIT_XR_FUNC_V(xrCreateTriangleMeshFB); - EXT_INIT_XR_FUNC_V(xrDestroyTriangleMeshFB); - EXT_INIT_XR_FUNC_V(xrTriangleMeshGetVertexBufferFB); - EXT_INIT_XR_FUNC_V(xrTriangleMeshGetIndexBufferFB); - EXT_INIT_XR_FUNC_V(xrTriangleMeshBeginUpdateFB); - EXT_INIT_XR_FUNC_V(xrTriangleMeshEndUpdateFB); - EXT_INIT_XR_FUNC_V(xrTriangleMeshBeginVertexBufferUpdateFB); - EXT_INIT_XR_FUNC_V(xrTriangleMeshEndVertexBufferUpdateFB); - - return true; -} diff --git a/modules/openxr/extensions/openxr_fb_passthrough_extension_wrapper.h b/modules/openxr/extensions/openxr_fb_passthrough_extension_wrapper.h deleted file mode 100644 index 045e424202..0000000000 --- a/modules/openxr/extensions/openxr_fb_passthrough_extension_wrapper.h +++ /dev/null @@ -1,231 +0,0 @@ -/**************************************************************************/ -/* openxr_fb_passthrough_extension_wrapper.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_FB_PASSTHROUGH_EXTENSION_WRAPPER_H -#define OPENXR_FB_PASSTHROUGH_EXTENSION_WRAPPER_H - -#include "../openxr_api.h" -#include "../util.h" -#include "openxr_composition_layer_provider.h" -#include "openxr_extension_wrapper.h" - -#include <map> - -class Viewport; - -// Wrapper for the set of Facebook XR passthrough extensions. -class OpenXRFbPassthroughExtensionWrapper : public OpenXRExtensionWrapper, public OpenXRCompositionLayerProvider { -public: - OpenXRFbPassthroughExtensionWrapper(); - ~OpenXRFbPassthroughExtensionWrapper(); - - virtual HashMap<String, bool *> get_requested_extensions() override; - - void on_instance_created(const XrInstance instance) override; - - void on_session_created(const XrSession session) override; - - void on_session_destroyed() override; - - void on_instance_destroyed() override; - - XrCompositionLayerBaseHeader *get_composition_layer() override; - - bool is_passthrough_supported() { - return fb_passthrough_ext; - } - - bool is_passthrough_enabled(); - - bool start_passthrough(); - - void stop_passthrough(); - - static OpenXRFbPassthroughExtensionWrapper *get_singleton(); - -private: - // Create a passthrough feature - EXT_PROTO_XRRESULT_FUNC3(xrCreatePassthroughFB, - (XrSession), session, - (const XrPassthroughCreateInfoFB *), create_info, - (XrPassthroughFB *), feature_out) - - // Destroy a previously created passthrough feature - EXT_PROTO_XRRESULT_FUNC1(xrDestroyPassthroughFB, (XrPassthroughFB), feature) - - //*** Passthrough feature state management functions ********* - // Start the passthrough feature - EXT_PROTO_XRRESULT_FUNC1(xrPassthroughStartFB, (XrPassthroughFB), passthrough) - // Pause the passthrough feature - EXT_PROTO_XRRESULT_FUNC1(xrPassthroughPauseFB, (XrPassthroughFB), passthrough) - - EXT_PROTO_XRRESULT_FUNC3(xrCreatePassthroughLayerFB, (XrSession), session, - (const XrPassthroughLayerCreateInfoFB *), config, - (XrPassthroughLayerFB *), layer_out) - - EXT_PROTO_XRRESULT_FUNC1(xrDestroyPassthroughLayerFB, (XrPassthroughLayerFB), layer) - - EXT_PROTO_XRRESULT_FUNC1(xrPassthroughLayerPauseFB, (XrPassthroughLayerFB), layer) - EXT_PROTO_XRRESULT_FUNC1(xrPassthroughLayerResumeFB, (XrPassthroughLayerFB), layer) - - // Set the style of an existing passthrough layer. If the enabled feature set - // doesn’t change, this is a lightweight operation that can be called in every - // frame to animate the style. Changes that may incur a bigger cost: - // - Enabling/disabling the color mapping, or changing the type of mapping - // (monochromatic to RGBA or back). - // - Changing `textureOpacityFactor` from 0 to non-zero or vice versa - // - Changing `edgeColor[3]` from 0 to non-zero or vice versa - // NOTE: For XR_FB_passthrough, all color values are treated as linear. - EXT_PROTO_XRRESULT_FUNC2(xrPassthroughLayerSetStyleFB, - (XrPassthroughLayerFB), layer, - (const XrPassthroughStyleFB *), style) - - // Create a geometry instance to be used as a projection surface for passthrough. - // A geometry instance assigns a triangle mesh as part of the specified layer's - // projection surface. - // The operation is only valid if the passthrough layer's purpose has been set to - // `XR_PASSTHROUGH_LAYER_PURPOSE_PROJECTED_FB`. Otherwise, the call this function will - // result in an error. In the specified layer, Passthrough will be visible where the view - // is covered by the user-specified geometries. - // - // A triangle mesh object can be instantiated multiple times - in the same or different layers' - // projection surface. Each instantiation has its own transformation, which - // can be updated using `xrGeometryInstanceSetTransformFB`. - EXT_PROTO_XRRESULT_FUNC3(xrCreateGeometryInstanceFB, - (XrSession), session, - (const XrGeometryInstanceCreateInfoFB *), create_info, - (XrGeometryInstanceFB *), out_geometry_instance) - - // Destroys a previously created geometry instance from passthrough rendering. - // This removes the geometry instance from passthrough rendering. - // The operation has no effect on other instances or the underlying mesh. - EXT_PROTO_XRRESULT_FUNC1(xrDestroyGeometryInstanceFB, (XrGeometryInstanceFB), instance) - - // Update the transformation of a passthrough geometry instance. - EXT_PROTO_XRRESULT_FUNC2(xrGeometryInstanceSetTransformFB, - (XrGeometryInstanceFB), instance, - (const XrGeometryInstanceTransformFB *), transformation) - - // Create a triangle mesh geometry object. - // Depending on the behavior flags, the mesh could be created immutable (data is assigned - // at creation and cannot be changed) or mutable (the mesh is created empty and can be updated - // by calling begin/end update functions). - EXT_PROTO_XRRESULT_FUNC3(xrCreateTriangleMeshFB, - (XrSession), session, - (const XrTriangleMeshCreateInfoFB *), create_info, - (XrTriangleMeshFB *), out_triangle_mesh) - - // Destroy an `XrTriangleMeshFB` object along with its data. The mesh buffers must not be - // accessed anymore after their parent mesh object has been destroyed. - EXT_PROTO_XRRESULT_FUNC1(xrDestroyTriangleMeshFB, (XrTriangleMeshFB), mesh) - - // Retrieve a pointer to the vertex buffer. The vertex buffer is structured as an array of 3 floats - // per vertex representing x, y, and z: `[x0, y0, z0, x1, y1, z1, ...]`. The size of the buffer is - // `maxVertexCount * 3` floats. The application must call `xrTriangleMeshBeginUpdateFB` or - // `xrTriangleMeshBeginVertexBufferUpdateFB` before making modifications to the vertex - // buffer. The buffer location is guaranteed to remain constant over the lifecycle of the mesh - // object. - EXT_PROTO_XRRESULT_FUNC2(xrTriangleMeshGetVertexBufferFB, - (XrTriangleMeshFB), mesh, - (XrVector3f **), out_vertex_buffer) - - // Retrieve the index buffer that defines the topology of the triangle mesh. Each triplet of - // consecutive elements point to three vertices in the vertex buffer and thus form a triangle. The - // size of each element is `indexElementSize` bytes, and thus the size of the buffer is - // `maxTriangleCount * 3 * indexElementSize` bytes. The application must call - // `xrTriangleMeshBeginUpdateFB` before making modifications to the index buffer. The buffer - // location is guaranteed to remain constant over the lifecycle of the mesh object. - EXT_PROTO_XRRESULT_FUNC2(xrTriangleMeshGetIndexBufferFB, - (XrTriangleMeshFB), mesh, - (uint32_t **), out_index_buffer) - - // Begin updating the mesh buffer data. The application must call this function before it makes any - // modifications to the buffers retrieved by `xrTriangleMeshGetVertexBufferFB` and - // `xrTriangleMeshGetIndexBufferFB`. If only the vertex buffer needs to be updated, - // `xrTriangleMeshBeginVertexBufferUpdateFB` can be used instead. To commit the - // modifications, the application must call `xrTriangleMeshEndUpdateFB`. - EXT_PROTO_XRRESULT_FUNC1(xrTriangleMeshBeginUpdateFB, (XrTriangleMeshFB), mesh) - - // Signal the API that the application has finished updating the mesh buffers after a call to - // `xrTriangleMeshBeginUpdateFB`. `vertexCount` and `triangleCount` specify the actual - // number of primitives that make up the mesh after the update. They must be larger than zero but - // smaller or equal to the maximum counts defined at create time. Buffer data beyond these counts - // is ignored. - EXT_PROTO_XRRESULT_FUNC3(xrTriangleMeshEndUpdateFB, - (XrTriangleMeshFB), mesh, - (uint32_t), vertexCount, - (uint32_t), triangle_count) - - // Update the vertex positions of a triangle mesh. Can only be called once the mesh topology has - // been set using `xrTriangleMeshBeginUpdateFB`/`xrTriangleMeshEndUpdateFB`. The - // vertex count is defined by the last invocation to `xrTriangleMeshEndUpdateFB`. Once the - // modification is done, `xrTriangleMeshEndVertexBufferUpdateFB` must be called. - EXT_PROTO_XRRESULT_FUNC2(xrTriangleMeshBeginVertexBufferUpdateFB, - (XrTriangleMeshFB), mesh, - (uint32_t *), out_vertex_count) - - // Signal the API that the contents of the vertex buffer data has been updated - // after a call to `xrTriangleMeshBeginVertexBufferUpdateFB`. - EXT_PROTO_XRRESULT_FUNC1(xrTriangleMeshEndVertexBufferUpdateFB, (XrTriangleMeshFB), mesh) - - bool initialize_fb_passthrough_extension(const XrInstance instance); - - bool initialize_fb_triangle_mesh_extension(const XrInstance instance); - - void cleanup(); - - // TODO: Temporary workaround (https://github.com/GodotVR/godot_openxr/issues/138) - // Address a bug in the passthrough api where XR_ERROR_UNEXPECTED_STATE_PASSTHROUGH_FB is - // returned even when the operation is valid on Meta Quest devices. - // The issue should be addressed on that platform in OS release v37. - inline bool is_valid_passthrough_result(XrResult result, const char *format) { - return OpenXRAPI::get_singleton()->xr_result(result, format) || result == XR_ERROR_UNEXPECTED_STATE_PASSTHROUGH_FB; - } - - Viewport *get_main_viewport(); - - static OpenXRFbPassthroughExtensionWrapper *singleton; - - bool fb_passthrough_ext = false; // required for any passthrough functionality - bool fb_triangle_mesh_ext = false; // only use for projected passthrough - - XrPassthroughFB passthrough_handle = XR_NULL_HANDLE; - XrPassthroughLayerFB passthrough_layer = XR_NULL_HANDLE; - - XrCompositionLayerPassthroughFB composition_passthrough_layer = { - XR_TYPE_COMPOSITION_LAYER_PASSTHROUGH_FB, - nullptr, - XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT, - XR_NULL_HANDLE, - XR_NULL_HANDLE, - }; -}; - -#endif // OPENXR_FB_PASSTHROUGH_EXTENSION_WRAPPER_H diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp index 491a419525..80ddfe703f 100644 --- a/modules/openxr/openxr_api.cpp +++ b/modules/openxr/openxr_api.cpp @@ -56,7 +56,6 @@ #include "extensions/openxr_composition_layer_depth_extension.h" #include "extensions/openxr_fb_display_refresh_rate_extension.h" #include "extensions/openxr_fb_foveation_extension.h" -#include "extensions/openxr_fb_passthrough_extension_wrapper.h" #include "extensions/openxr_fb_update_swapchain_extension.h" #ifdef ANDROID_ENABLED @@ -3122,11 +3121,30 @@ bool OpenXRAPI::is_environment_blend_mode_supported(XrEnvironmentBlendMode p_ble } bool OpenXRAPI::set_environment_blend_mode(XrEnvironmentBlendMode p_blend_mode) { + if (emulate_environment_blend_mode_alpha_blend && p_blend_mode == XR_ENVIRONMENT_BLEND_MODE_ALPHA_BLEND) { + requested_environment_blend_mode = XR_ENVIRONMENT_BLEND_MODE_ALPHA_BLEND; + environment_blend_mode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE; + return true; + } // We allow setting this when not initialized and will check if it is supported when initializing. // After OpenXR is initialized we verify we're setting a supported blend mode. - if (!is_initialized() || is_environment_blend_mode_supported(p_blend_mode)) { + else if (!is_initialized() || is_environment_blend_mode_supported(p_blend_mode)) { + requested_environment_blend_mode = p_blend_mode; environment_blend_mode = p_blend_mode; return true; } return false; } + +void OpenXRAPI::set_emulate_environment_blend_mode_alpha_blend(bool p_enabled) { + emulate_environment_blend_mode_alpha_blend = p_enabled; +} + +OpenXRAPI::OpenXRAlphaBlendModeSupport OpenXRAPI::is_environment_blend_mode_alpha_blend_supported() { + if (is_environment_blend_mode_supported(XR_ENVIRONMENT_BLEND_MODE_ALPHA_BLEND)) { + return OPENXR_ALPHA_BLEND_MODE_SUPPORT_REAL; + } else if (emulate_environment_blend_mode_alpha_blend) { + return OPENXR_ALPHA_BLEND_MODE_SUPPORT_EMULATING; + } + return OPENXR_ALPHA_BLEND_MODE_SUPPORT_NONE; +} diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h index 5e5a3d4663..d39e6e0b2e 100644 --- a/modules/openxr/openxr_api.h +++ b/modules/openxr/openxr_api.h @@ -104,8 +104,10 @@ private: // blend mode XrEnvironmentBlendMode environment_blend_mode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE; + XrEnvironmentBlendMode requested_environment_blend_mode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE; uint32_t num_supported_environment_blend_modes = 0; XrEnvironmentBlendMode *supported_environment_blend_modes = nullptr; + bool emulate_environment_blend_mode_alpha_blend = false; // state XrInstance instance = XR_NULL_HANDLE; @@ -429,7 +431,16 @@ public: const XrEnvironmentBlendMode *get_supported_environment_blend_modes(uint32_t &count); bool is_environment_blend_mode_supported(XrEnvironmentBlendMode p_blend_mode) const; bool set_environment_blend_mode(XrEnvironmentBlendMode p_blend_mode); - XrEnvironmentBlendMode get_environment_blend_mode() const { return environment_blend_mode; } + XrEnvironmentBlendMode get_environment_blend_mode() const { return requested_environment_blend_mode; } + + enum OpenXRAlphaBlendModeSupport { + OPENXR_ALPHA_BLEND_MODE_SUPPORT_NONE = 0, + OPENXR_ALPHA_BLEND_MODE_SUPPORT_REAL = 1, + OPENXR_ALPHA_BLEND_MODE_SUPPORT_EMULATING = 2, + }; + + void set_emulate_environment_blend_mode_alpha_blend(bool p_enabled); + OpenXRAlphaBlendModeSupport is_environment_blend_mode_alpha_blend_supported(); OpenXRAPI(); ~OpenXRAPI(); diff --git a/modules/openxr/openxr_api_extension.cpp b/modules/openxr/openxr_api_extension.cpp index f0f0835f78..d9e282e218 100644 --- a/modules/openxr/openxr_api_extension.cpp +++ b/modules/openxr/openxr_api_extension.cpp @@ -30,6 +30,8 @@ #include "openxr_api_extension.h" +#include "extensions/openxr_extension_wrapper_extension.h" + void OpenXRAPIExtension::_bind_methods() { ClassDB::bind_method(D_METHOD("get_instance"), &OpenXRAPIExtension::get_instance); ClassDB::bind_method(D_METHOD("get_system_id"), &OpenXRAPIExtension::get_system_id); @@ -48,6 +50,16 @@ void OpenXRAPIExtension::_bind_methods() { ClassDB::bind_method(D_METHOD("get_play_space"), &OpenXRAPIExtension::get_play_space); ClassDB::bind_method(D_METHOD("get_next_frame_time"), &OpenXRAPIExtension::get_next_frame_time); ClassDB::bind_method(D_METHOD("can_render"), &OpenXRAPIExtension::can_render); + + ClassDB::bind_method(D_METHOD("register_composition_layer_provider", "extension"), &OpenXRAPIExtension::register_composition_layer_provider); + ClassDB::bind_method(D_METHOD("unregister_composition_layer_provider", "extension"), &OpenXRAPIExtension::unregister_composition_layer_provider); + + ClassDB::bind_method(D_METHOD("set_emulate_environment_blend_mode_alpha_blend", "enabled"), &OpenXRAPIExtension::set_emulate_environment_blend_mode_alpha_blend); + ClassDB::bind_method(D_METHOD("is_environment_blend_mode_alpha_supported"), &OpenXRAPIExtension::is_environment_blend_mode_alpha_blend_supported); + + BIND_ENUM_CONSTANT(OPENXR_ALPHA_BLEND_MODE_SUPPORT_NONE); + BIND_ENUM_CONSTANT(OPENXR_ALPHA_BLEND_MODE_SUPPORT_REAL); + BIND_ENUM_CONSTANT(OPENXR_ALPHA_BLEND_MODE_SUPPORT_EMULATING); } uint64_t OpenXRAPIExtension::get_instance() { @@ -126,5 +138,25 @@ bool OpenXRAPIExtension::can_render() { return OpenXRAPI::get_singleton()->can_render(); } +void OpenXRAPIExtension::register_composition_layer_provider(OpenXRExtensionWrapperExtension *p_extension) { + ERR_FAIL_NULL(OpenXRAPI::get_singleton()); + OpenXRAPI::get_singleton()->register_composition_layer_provider(p_extension); +} + +void OpenXRAPIExtension::unregister_composition_layer_provider(OpenXRExtensionWrapperExtension *p_extension) { + ERR_FAIL_NULL(OpenXRAPI::get_singleton()); + OpenXRAPI::get_singleton()->unregister_composition_layer_provider(p_extension); +} + +void OpenXRAPIExtension::set_emulate_environment_blend_mode_alpha_blend(bool p_enabled) { + ERR_FAIL_NULL(OpenXRAPI::get_singleton()); + OpenXRAPI::get_singleton()->set_emulate_environment_blend_mode_alpha_blend(p_enabled); +} + +OpenXRAPIExtension::OpenXRAlphaBlendModeSupport OpenXRAPIExtension::is_environment_blend_mode_alpha_blend_supported() { + ERR_FAIL_NULL_V(OpenXRAPI::get_singleton(), OPENXR_ALPHA_BLEND_MODE_SUPPORT_NONE); + return (OpenXRAPIExtension::OpenXRAlphaBlendModeSupport)OpenXRAPI::get_singleton()->is_environment_blend_mode_alpha_blend_supported(); +} + OpenXRAPIExtension::OpenXRAPIExtension() { } diff --git a/modules/openxr/openxr_api_extension.h b/modules/openxr/openxr_api_extension.h index 98f87c7aa1..82344c1d06 100644 --- a/modules/openxr/openxr_api_extension.h +++ b/modules/openxr/openxr_api_extension.h @@ -38,6 +38,8 @@ #include "core/os/thread_safe.h" #include "core/variant/native_ptr.h" +class OpenXRExtensionWrapperExtension; + class OpenXRAPIExtension : public RefCounted { GDCLASS(OpenXRAPIExtension, RefCounted); @@ -70,7 +72,21 @@ public: int64_t get_next_frame_time(); bool can_render(); + void register_composition_layer_provider(OpenXRExtensionWrapperExtension *p_extension); + void unregister_composition_layer_provider(OpenXRExtensionWrapperExtension *p_extension); + + enum OpenXRAlphaBlendModeSupport { + OPENXR_ALPHA_BLEND_MODE_SUPPORT_NONE = 0, + OPENXR_ALPHA_BLEND_MODE_SUPPORT_REAL = 1, + OPENXR_ALPHA_BLEND_MODE_SUPPORT_EMULATING = 2, + }; + + void set_emulate_environment_blend_mode_alpha_blend(bool p_enabled); + OpenXRAlphaBlendModeSupport is_environment_blend_mode_alpha_blend_supported(); + OpenXRAPIExtension(); }; +VARIANT_ENUM_CAST(OpenXRAPIExtension::OpenXRAlphaBlendModeSupport); + #endif // OPENXR_API_EXTENSION_H diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp index ebcd331f3d..05c53ad52f 100644 --- a/modules/openxr/openxr_interface.cpp +++ b/modules/openxr/openxr_interface.cpp @@ -1158,21 +1158,19 @@ void OpenXRInterface::end_frame() { } bool OpenXRInterface::is_passthrough_supported() { - return passthrough_wrapper != nullptr && passthrough_wrapper->is_passthrough_supported(); + return get_supported_environment_blend_modes().find(XR_ENV_BLEND_MODE_ALPHA_BLEND); } bool OpenXRInterface::is_passthrough_enabled() { - return passthrough_wrapper != nullptr && passthrough_wrapper->is_passthrough_enabled(); + return get_environment_blend_mode() == XR_ENV_BLEND_MODE_ALPHA_BLEND; } bool OpenXRInterface::start_passthrough() { - return passthrough_wrapper != nullptr && passthrough_wrapper->start_passthrough(); + return set_environment_blend_mode(XR_ENV_BLEND_MODE_ALPHA_BLEND); } void OpenXRInterface::stop_passthrough() { - if (passthrough_wrapper) { - passthrough_wrapper->stop_passthrough(); - } + set_environment_blend_mode(XR_ENV_BLEND_MODE_OPAQUE); } Array OpenXRInterface::get_supported_environment_blend_modes() { @@ -1204,6 +1202,11 @@ Array OpenXRInterface::get_supported_environment_blend_modes() { WARN_PRINT("Unsupported blend mode found: " + String::num_int64(int64_t(env_blend_modes[i]))); } } + + if (openxr_api->is_environment_blend_mode_alpha_blend_supported() == OpenXRAPI::OPENXR_ALPHA_BLEND_MODE_SUPPORT_EMULATING) { + modes.push_back(XR_ENV_BLEND_MODE_ALPHA_BLEND); + } + return modes; } @@ -1422,8 +1425,6 @@ OpenXRInterface::OpenXRInterface() { _set_default_pos(head_transform, 1.0, 0); _set_default_pos(transform_for_view[0], 1.0, 1); _set_default_pos(transform_for_view[1], 1.0, 2); - - passthrough_wrapper = OpenXRFbPassthroughExtensionWrapper::get_singleton(); } OpenXRInterface::~OpenXRInterface() { diff --git a/modules/openxr/openxr_interface.h b/modules/openxr/openxr_interface.h index ca95fdf04d..aee9751d6b 100644 --- a/modules/openxr/openxr_interface.h +++ b/modules/openxr/openxr_interface.h @@ -32,7 +32,6 @@ #define OPENXR_INTERFACE_H #include "action_map/openxr_action_map.h" -#include "extensions/openxr_fb_passthrough_extension_wrapper.h" #include "extensions/openxr_hand_tracking_extension.h" #include "openxr_api.h" @@ -49,7 +48,6 @@ private: OpenXRAPI *openxr_api = nullptr; bool initialized = false; XRInterface::TrackingStatus tracking_state; - OpenXRFbPassthroughExtensionWrapper *passthrough_wrapper = nullptr; // At a minimum we need a tracker for our head Ref<XRPositionalTracker> head; diff --git a/modules/openxr/register_types.cpp b/modules/openxr/register_types.cpp index 04411a0c57..a0a93c8694 100644 --- a/modules/openxr/register_types.cpp +++ b/modules/openxr/register_types.cpp @@ -44,7 +44,6 @@ #include "extensions/openxr_composition_layer_depth_extension.h" #include "extensions/openxr_eye_gaze_interaction.h" #include "extensions/openxr_fb_display_refresh_rate_extension.h" -#include "extensions/openxr_fb_passthrough_extension_wrapper.h" #include "extensions/openxr_hand_tracking_extension.h" #include "extensions/openxr_htc_controller_extension.h" #include "extensions/openxr_htc_vive_tracker_extension.h" @@ -114,7 +113,6 @@ void initialize_openxr_module(ModuleInitializationLevel p_level) { OpenXRAPI::register_extension_wrapper(memnew(OpenXRHTCControllerExtension)); OpenXRAPI::register_extension_wrapper(memnew(OpenXRHTCViveTrackerExtension)); OpenXRAPI::register_extension_wrapper(memnew(OpenXRHuaweiControllerExtension)); - OpenXRAPI::register_extension_wrapper(memnew(OpenXRFbPassthroughExtensionWrapper)); OpenXRAPI::register_extension_wrapper(memnew(OpenXRDisplayRefreshRateExtension)); OpenXRAPI::register_extension_wrapper(memnew(OpenXRWMRControllerExtension)); OpenXRAPI::register_extension_wrapper(memnew(OpenXRML2ControllerExtension)); diff --git a/platform/macos/detect.py b/platform/macos/detect.py index 4a8e9cd956..0d1e40fb3d 100644 --- a/platform/macos/detect.py +++ b/platform/macos/detect.py @@ -67,13 +67,14 @@ def get_mvk_sdk_path(): if not os.path.exists(dirname): return "" + ver_min = ver_parse("1.3.231.0") ver_num = ver_parse("0.0.0.0") files = os.listdir(dirname) lib_name_out = dirname for file in files: if os.path.isdir(os.path.join(dirname, file)): ver_comp = ver_parse(file) - if ver_comp > ver_num: + if ver_comp > ver_num and ver_comp >= ver_min: # Try new SDK location. lib_name = os.path.join( os.path.join(dirname, file), "macOS/lib/MoltenVK.xcframework/macos-arm64_x86_64/" diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm index b471fc827b..cad8435cbb 100644 --- a/platform/macos/display_server_macos.mm +++ b/platform/macos/display_server_macos.mm @@ -1676,6 +1676,10 @@ void DisplayServerMacOS::global_menu_set_item_text(const String &p_menu_root, in NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { [menu_item setTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]]; + NSMenu *sub_menu = [menu_item submenu]; + if (sub_menu) { + [sub_menu setTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]]; + } } } } diff --git a/platform/windows/SCsub b/platform/windows/SCsub index 7aaf70e625..6010d4ba76 100644 --- a/platform/windows/SCsub +++ b/platform/windows/SCsub @@ -7,6 +7,8 @@ from pathlib import Path from platform_methods import run_in_subprocess import platform_windows_builders +sources = [] + common_win = [ "godot_windows.cpp", "crash_handler_windows.cpp", @@ -43,7 +45,8 @@ res_file = "godot_res.rc" res_target = "godot_res" + env["OBJSUFFIX"] res_obj = env.RES(res_target, res_file) -sources = common_win + res_obj +env.add_source_files(sources, common_win) +sources += res_obj prog = env.add_program("#bin/godot", sources, PROGSUFFIX=env["PROGSUFFIX"]) arrange_program_clean(prog) @@ -65,6 +68,7 @@ if env["windows_subsystem"] == "gui": prog_wrap = env_wrap.add_program("#bin/godot", common_win_wrap + res_wrap_obj, PROGSUFFIX=env["PROGSUFFIX_WRAP"]) arrange_program_clean(prog_wrap) env_wrap.Depends(prog_wrap, prog) + sources += common_win_wrap + res_wrap_obj # Microsoft Visual Studio Project Generation if env["vsproj"]: @@ -134,3 +138,5 @@ if not os.getenv("VCINSTALLDIR"): env.AddPostAction(prog, run_in_subprocess(platform_windows_builders.make_debug_mingw)) if env["windows_subsystem"] == "gui": env.AddPostAction(prog_wrap, run_in_subprocess(platform_windows_builders.make_debug_mingw)) + +env.platform_sources += sources diff --git a/platform/windows/msvs.py b/platform/windows/msvs.py new file mode 100644 index 0000000000..2d5ebe811a --- /dev/null +++ b/platform/windows/msvs.py @@ -0,0 +1,20 @@ +import methods + + +# Tuples with the name of the arch that will be used in VS, mapped to our internal arch names. +# For Windows platforms, Win32 is what VS wants. For other platforms, it can be different. +def get_platforms(): + return [("Win32", "x86_32"), ("x64", "x86_64")] + + +def get_configurations(): + return ["editor", "template_debug", "template_release"] + + +def get_build_prefix(env): + batch_file = methods.find_visual_c_batch_file(env) + return [ + "set "plat=$(PlatformTarget)"", + "(if "$(PlatformTarget)"=="x64" (set "plat=x86_amd64"))", + f"call "{batch_file}" !plat!", + ] diff --git a/scene/2d/physics_body_2d.cpp b/scene/2d/physics_body_2d.cpp index 6af5a8dd80..7a131916e8 100644 --- a/scene/2d/physics_body_2d.cpp +++ b/scene/2d/physics_body_2d.cpp @@ -35,6 +35,7 @@ void PhysicsBody2D::_bind_methods() { ClassDB::bind_method(D_METHOD("move_and_collide", "motion", "test_only", "safe_margin", "recovery_as_collision"), &PhysicsBody2D::_move, DEFVAL(false), DEFVAL(0.08), DEFVAL(false)); ClassDB::bind_method(D_METHOD("test_move", "from", "motion", "collision", "safe_margin", "recovery_as_collision"), &PhysicsBody2D::test_move, DEFVAL(Variant()), DEFVAL(0.08), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("get_gravity"), &PhysicsBody2D::get_gravity); ClassDB::bind_method(D_METHOD("get_collision_exceptions"), &PhysicsBody2D::get_collision_exceptions); ClassDB::bind_method(D_METHOD("add_collision_exception_with", "body"), &PhysicsBody2D::add_collision_exception_with); @@ -145,6 +146,10 @@ bool PhysicsBody2D::test_move(const Transform2D &p_from, const Vector2 &p_motion return PhysicsServer2D::get_singleton()->body_test_motion(get_rid(), parameters, r); } +Vector2 PhysicsBody2D::get_gravity() const { + return PhysicsServer2D::get_singleton()->body_get_direct_state(get_rid())->get_total_gravity(); +} + TypedArray<PhysicsBody2D> PhysicsBody2D::get_collision_exceptions() { List<RID> exceptions; PhysicsServer2D::get_singleton()->body_get_collision_exceptions(get_rid(), &exceptions); diff --git a/scene/2d/physics_body_2d.h b/scene/2d/physics_body_2d.h index 208e72c40f..62636b02f4 100644 --- a/scene/2d/physics_body_2d.h +++ b/scene/2d/physics_body_2d.h @@ -52,6 +52,7 @@ protected: public: bool move_and_collide(const PhysicsServer2D::MotionParameters &p_parameters, PhysicsServer2D::MotionResult &r_result, bool p_test_only = false, bool p_cancel_sliding = true); bool test_move(const Transform2D &p_from, const Vector2 &p_motion, const Ref<KinematicCollision2D> &r_collision = Ref<KinematicCollision2D>(), real_t p_margin = 0.08, bool p_recovery_as_collision = false); + Vector2 get_gravity() const; TypedArray<PhysicsBody2D> get_collision_exceptions(); void add_collision_exception_with(Node *p_node); //must be physicsbody diff --git a/scene/3d/physics_body_3d.cpp b/scene/3d/physics_body_3d.cpp index 38c1f232ad..1f78928b94 100644 --- a/scene/3d/physics_body_3d.cpp +++ b/scene/3d/physics_body_3d.cpp @@ -35,6 +35,7 @@ void PhysicsBody3D::_bind_methods() { ClassDB::bind_method(D_METHOD("move_and_collide", "motion", "test_only", "safe_margin", "recovery_as_collision", "max_collisions"), &PhysicsBody3D::_move, DEFVAL(false), DEFVAL(0.001), DEFVAL(false), DEFVAL(1)); ClassDB::bind_method(D_METHOD("test_move", "from", "motion", "collision", "safe_margin", "recovery_as_collision", "max_collisions"), &PhysicsBody3D::test_move, DEFVAL(Variant()), DEFVAL(0.001), DEFVAL(false), DEFVAL(1)); + ClassDB::bind_method(D_METHOD("get_gravity"), &PhysicsBody3D::get_gravity); ClassDB::bind_method(D_METHOD("set_axis_lock", "axis", "lock"), &PhysicsBody3D::set_axis_lock); ClassDB::bind_method(D_METHOD("get_axis_lock", "axis"), &PhysicsBody3D::get_axis_lock); @@ -182,10 +183,15 @@ bool PhysicsBody3D::test_move(const Transform3D &p_from, const Vector3 &p_motion PhysicsServer3D::MotionParameters parameters(p_from, p_motion, p_margin); parameters.recovery_as_collision = p_recovery_as_collision; + parameters.max_collisions = p_max_collisions; return PhysicsServer3D::get_singleton()->body_test_motion(get_rid(), parameters, r); } +Vector3 PhysicsBody3D::get_gravity() const { + return PhysicsServer3D::get_singleton()->body_get_direct_state(get_rid())->get_total_gravity(); +} + void PhysicsBody3D::set_axis_lock(PhysicsServer3D::BodyAxis p_axis, bool p_lock) { if (p_lock) { locked_axis |= p_axis; diff --git a/scene/3d/physics_body_3d.h b/scene/3d/physics_body_3d.h index e8d5ef2103..e8373d5907 100644 --- a/scene/3d/physics_body_3d.h +++ b/scene/3d/physics_body_3d.h @@ -55,6 +55,7 @@ protected: public: bool move_and_collide(const PhysicsServer3D::MotionParameters &p_parameters, PhysicsServer3D::MotionResult &r_result, bool p_test_only = false, bool p_cancel_sliding = true); bool test_move(const Transform3D &p_from, const Vector3 &p_motion, const Ref<KinematicCollision3D> &r_collision = Ref<KinematicCollision3D>(), real_t p_margin = 0.001, bool p_recovery_as_collision = false, int p_max_collisions = 1); + Vector3 get_gravity() const; void set_axis_lock(PhysicsServer3D::BodyAxis p_axis, bool p_lock); bool get_axis_lock(PhysicsServer3D::BodyAxis p_axis) const; diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 540c999131..e2925920a2 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -7333,7 +7333,7 @@ void TextEdit::_update_scrollbars() { int visible_rows = get_visible_line_count(); int total_rows = draw_placeholder ? placeholder_wraped_rows.size() - 1 : get_total_visible_line_count(); - if (scroll_past_end_of_file_enabled) { + if (scroll_past_end_of_file_enabled && !fit_content_height) { total_rows += visible_rows - 1; } diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index 10f4962c48..73a49fd427 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -2587,12 +2587,8 @@ int Tree::_count_selected_items(TreeItem *p_from) const { } } - if (p_from->get_first_child()) { - count += _count_selected_items(p_from->get_first_child()); - } - - if (p_from->get_next()) { - count += _count_selected_items(p_from->get_next()); + for (TreeItem *c = p_from->get_first_child(); c; c = c->get_next()) { + count += _count_selected_items(c); } return count; diff --git a/scene/resources/height_map_shape_3d.cpp b/scene/resources/height_map_shape_3d.cpp index 718d701811..35c905bd43 100644 --- a/scene/resources/height_map_shape_3d.cpp +++ b/scene/resources/height_map_shape_3d.cpp @@ -179,6 +179,14 @@ Vector<real_t> HeightMapShape3D::get_map_data() const { return map_data; } +real_t HeightMapShape3D::get_min_height() const { + return min_height; +} + +real_t HeightMapShape3D::get_max_height() const { + return max_height; +} + void HeightMapShape3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_map_width", "width"), &HeightMapShape3D::set_map_width); ClassDB::bind_method(D_METHOD("get_map_width"), &HeightMapShape3D::get_map_width); @@ -186,6 +194,8 @@ void HeightMapShape3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_map_depth"), &HeightMapShape3D::get_map_depth); ClassDB::bind_method(D_METHOD("set_map_data", "data"), &HeightMapShape3D::set_map_data); ClassDB::bind_method(D_METHOD("get_map_data"), &HeightMapShape3D::get_map_data); + ClassDB::bind_method(D_METHOD("get_min_height"), &HeightMapShape3D::get_min_height); + ClassDB::bind_method(D_METHOD("get_max_height"), &HeightMapShape3D::get_max_height); ADD_PROPERTY(PropertyInfo(Variant::INT, "map_width", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater"), "set_map_width", "get_map_width"); ADD_PROPERTY(PropertyInfo(Variant::INT, "map_depth", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater"), "set_map_depth", "get_map_depth"); diff --git a/scene/resources/height_map_shape_3d.h b/scene/resources/height_map_shape_3d.h index 5fe00ec0b1..eff025c816 100644 --- a/scene/resources/height_map_shape_3d.h +++ b/scene/resources/height_map_shape_3d.h @@ -54,6 +54,9 @@ public: void set_map_data(Vector<real_t> p_new); Vector<real_t> get_map_data() const; + real_t get_min_height() const; + real_t get_max_height() const; + virtual Vector<Vector3> get_debug_mesh_lines() const override; virtual real_t get_enclosing_radius() const override; diff --git a/thirdparty/README.md b/thirdparty/README.md index 1988744340..0bf1ad5b06 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -862,7 +862,7 @@ instead of `miniz.h` as an external dependency. ## thorvg - Upstream: https://github.com/thorvg/thorvg -- Version: 0.12.3 (9d79f0ccef632fd3b43b8ea02def529b6a8d2288, 2024) +- Version: 0.12.4 (331839d49368e19ca15f35abee5ac541dbf23637, 2024) - License: MIT Files extracted from upstream source: diff --git a/thirdparty/thorvg/inc/config.h b/thirdparty/thorvg/inc/config.h index ef65b1500d..e50971e297 100644 --- a/thirdparty/thorvg/inc/config.h +++ b/thirdparty/thorvg/inc/config.h @@ -9,5 +9,5 @@ // For internal debugging: //#define THORVG_LOG_ENABLED -#define THORVG_VERSION_STRING "0.12.3" +#define THORVG_VERSION_STRING "0.12.4" #endif diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp index f618a3c827..67c87ba149 100644 --- a/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp +++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp @@ -51,8 +51,8 @@ #define _USE_MATH_DEFINES //Math Constants are not defined in Standard C/C++. #include <cstring> -#include <math.h> #include <ctype.h> +#include "tvgMath.h" #include "tvgShape.h" #include "tvgSvgLoaderCommon.h" #include "tvgSvgPath.h" @@ -471,9 +471,16 @@ static bool _processCommand(Array<PathCommand>* cmds, Array<Point>* pts, char cm } case 'a': case 'A': { - _pathAppendArcTo(cmds, pts, cur, curCtl, arr[5], arr[6], arr[0], arr[1], arr[2], arr[3], arr[4]); - *cur = *curCtl = {arr[5], arr[6]}; - *isQuadratic = false; + if (mathZero(arr[0]) || mathZero(arr[1])) { + Point p = {arr[5], arr[6]}; + cmds->push(PathCommand::LineTo); + pts->push(p); + *cur = {arr[5], arr[6]}; + } else if (!mathEqual(cur->x, arr[5]) || !mathEqual(cur->y, arr[6])) { + _pathAppendArcTo(cmds, pts, cur, curCtl, arr[5], arr[6], fabsf(arr[0]), fabsf(arr[1]), arr[2], arr[3], arr[4]); + *cur = *curCtl = {arr[5], arr[6]}; + *isQuadratic = false; + } break; } default: { diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp index 4645c4a58b..c187af38d5 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp @@ -63,7 +63,7 @@ struct SwTask : Task return region; } - virtual bool dispose() = 0; + virtual void dispose() = 0; virtual bool clip(SwRleData* target) = 0; virtual SwRleData* rle() = 0; @@ -196,10 +196,9 @@ struct SwShapeTask : SwTask shapeDelOutline(&shape, mpool, tid); } - bool dispose() override + void dispose() override { shapeFree(&shape); - return true; } }; @@ -250,10 +249,9 @@ struct SwSceneTask : SwTask } } - bool dispose() override + void dispose() override { rleFree(sceneRle); - return true; } }; @@ -318,10 +316,9 @@ struct SwImageTask : SwTask imageDelOutline(&image, mpool, tid); } - bool dispose() override + void dispose() override { imageFree(&image); - return true; } }; @@ -703,17 +700,15 @@ ColorSpace SwRenderer::colorSpace() } -bool SwRenderer::dispose(RenderData data) +void SwRenderer::dispose(RenderData data) { auto task = static_cast<SwTask*>(data); - if (!task) return true; + if (!task) return; task->done(); task->dispose(); if (task->pushed) task->disposed = true; else delete(task); - - return true; } diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.h b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.h index 83d942388f..02359e4a39 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.h +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.h @@ -43,7 +43,7 @@ public: bool renderShape(RenderData data) override; bool renderImage(RenderData data) override; bool postRender() override; - bool dispose(RenderData data) override; + void dispose(RenderData data) override; RenderRegion region(RenderData data) override; RenderRegion viewport() override; bool viewport(const RenderRegion& vp) override; diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwShape.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwShape.cpp index 45322c07b4..d3b715eab8 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwShape.cpp +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwShape.cpp @@ -122,7 +122,9 @@ static void _dashLineTo(SwDashStroke& dash, const Point* to, const Matrix* trans Line cur = {dash.ptCur, *to}; auto len = _lineLength(cur.pt1, cur.pt2); - if (len < dash.curLen) { + if (mathZero(len)) { + _outlineMoveTo(*dash.outline, &dash.ptCur, transform); + } else if (len < dash.curLen) { dash.curLen -= len; if (!dash.curOpGap) { if (dash.move) { @@ -179,7 +181,9 @@ static void _dashCubicTo(SwDashStroke& dash, const Point* ctrl1, const Point* ct Bezier cur = {dash.ptCur, *ctrl1, *ctrl2, *to}; auto len = bezLength(cur); - if (len < dash.curLen) { + if (mathZero(len)) { + _outlineMoveTo(*dash.outline, &dash.ptCur, transform); + } else if (len < dash.curLen) { dash.curLen -= len; if (!dash.curOpGap) { if (dash.move) { diff --git a/thirdparty/thorvg/src/renderer/tvgCanvas.h b/thirdparty/thorvg/src/renderer/tvgCanvas.h index 25181de47e..2d87a7e0a1 100644 --- a/thirdparty/thorvg/src/renderer/tvgCanvas.h +++ b/thirdparty/thorvg/src/renderer/tvgCanvas.h @@ -33,14 +33,30 @@ struct Canvas::Impl bool refresh = false; //if all paints should be updated by force. bool drawing = false; //on drawing condition? - Impl(RenderMethod* pRenderer):renderer(pRenderer) + Impl(RenderMethod* pRenderer) : renderer(pRenderer) { + renderer->ref(); } ~Impl() { - clear(true); - delete(renderer); + //make it sure any deffered jobs + if (renderer) { + renderer->sync(); + renderer->clear(); + } + + clearPaints(); + + if (renderer && (renderer->unref() == 0)) delete(renderer); + } + + void clearPaints() + { + for (auto paint : paints) { + if (P(paint)->unref() == 0) delete(paint); + } + paints.clear(); } Result push(unique_ptr<Paint> paint) @@ -62,15 +78,8 @@ struct Canvas::Impl if (!renderer || !renderer->clear()) return Result::InsufficientCondition; //Free paints - if (free) { - for (auto paint : paints) { - P(paint)->unref(); - if (paint->pImpl->dispose(*renderer) && P(paint)->refCnt == 0) { - delete(paint); - } - } - paints.clear(); - } + if (free) clearPaints(); + drawing = false; return Result::Success; @@ -94,7 +103,7 @@ struct Canvas::Impl //Optimize Me: Can we skip the searching? for (auto paint2 : paints) { if (paint2 == paint) { - paint->pImpl->update(*renderer, nullptr, clips, 255, flag); + paint->pImpl->update(renderer, nullptr, clips, 255, flag); return Result::Success; } } @@ -102,7 +111,7 @@ struct Canvas::Impl //Update all retained paint nodes } else { for (auto paint : paints) { - paint->pImpl->update(*renderer, nullptr, clips, 255, flag); + paint->pImpl->update(renderer, nullptr, clips, 255, flag); } } @@ -117,7 +126,7 @@ struct Canvas::Impl bool rendered = false; for (auto paint : paints) { - if (paint->pImpl->render(*renderer)) rendered = true; + if (paint->pImpl->render(renderer)) rendered = true; } if (!rendered || !renderer->postRender()) return Result::InsufficientCondition; diff --git a/thirdparty/thorvg/src/renderer/tvgPaint.cpp b/thirdparty/thorvg/src/renderer/tvgPaint.cpp index 563db3b44a..13ec4183d6 100644 --- a/thirdparty/thorvg/src/renderer/tvgPaint.cpp +++ b/thirdparty/thorvg/src/renderer/tvgPaint.cpp @@ -106,7 +106,7 @@ static bool _compFastTrack(Paint* cmpTarget, const RenderTransform* pTransform, } -RenderRegion Paint::Impl::bounds(RenderMethod& renderer) const +RenderRegion Paint::Impl::bounds(RenderMethod* renderer) const { RenderRegion ret; PAINT_METHOD(ret, bounds(renderer)); @@ -114,16 +114,6 @@ RenderRegion Paint::Impl::bounds(RenderMethod& renderer) const } -bool Paint::Impl::dispose(RenderMethod& renderer) -{ - if (compData) compData->target->pImpl->dispose(renderer); - - bool ret; - PAINT_METHOD(ret, dispose(renderer)); - return ret; -} - - Iterator* Paint::Impl::iterator() { Iterator* ret; @@ -198,7 +188,7 @@ bool Paint::Impl::translate(float x, float y) } -bool Paint::Impl::render(RenderMethod& renderer) +bool Paint::Impl::render(RenderMethod* renderer) { Compositor* cmp = nullptr; @@ -210,27 +200,33 @@ bool Paint::Impl::render(RenderMethod& renderer) if (MASK_REGION_MERGING(compData->method)) region.add(P(compData->target)->bounds(renderer)); if (region.w == 0 || region.h == 0) return true; - cmp = renderer.target(region, COMPOSITE_TO_COLORSPACE(renderer, compData->method)); - if (renderer.beginComposite(cmp, CompositeMethod::None, 255)) { + cmp = renderer->target(region, COMPOSITE_TO_COLORSPACE(renderer, compData->method)); + if (renderer->beginComposite(cmp, CompositeMethod::None, 255)) { compData->target->pImpl->render(renderer); } } - if (cmp) renderer.beginComposite(cmp, compData->method, compData->target->pImpl->opacity); + if (cmp) renderer->beginComposite(cmp, compData->method, compData->target->pImpl->opacity); - renderer.blend(blendMethod); + renderer->blend(blendMethod); bool ret; PAINT_METHOD(ret, render(renderer)); - if (cmp) renderer.endComposite(cmp); + if (cmp) renderer->endComposite(cmp); return ret; } -RenderData Paint::Impl::update(RenderMethod& renderer, const RenderTransform* pTransform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper) +RenderData Paint::Impl::update(RenderMethod* renderer, const RenderTransform* pTransform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper) { + if (this->renderer != renderer) { + if (this->renderer) TVGERR("RENDERER", "paint's renderer has been changed!"); + renderer->ref(); + this->renderer = renderer; + } + if (renderFlag & RenderUpdateFlag::Transform) { if (!rTransform) return nullptr; rTransform->update(); @@ -265,9 +261,9 @@ RenderData Paint::Impl::update(RenderMethod& renderer, const RenderTransform* pT if (tryFastTrack) { RenderRegion viewport2; if ((compFastTrack = _compFastTrack(target, pTransform, target->pImpl->rTransform, viewport2))) { - viewport = renderer.viewport(); + viewport = renderer->viewport(); viewport2.intersect(viewport); - renderer.viewport(viewport2); + renderer->viewport(viewport2); target->pImpl->ctxFlag |= ContextFlag::FastTrack; } } @@ -289,7 +285,7 @@ RenderData Paint::Impl::update(RenderMethod& renderer, const RenderTransform* pT PAINT_METHOD(rd, update(renderer, &outTransform, clips, opacity, newFlag, clipper)); /* 3. Composition Post Processing */ - if (compFastTrack) renderer.viewport(viewport); + if (compFastTrack) renderer->viewport(viewport); else if (childClipper) clips.pop(); return rd; diff --git a/thirdparty/thorvg/src/renderer/tvgPaint.h b/thirdparty/thorvg/src/renderer/tvgPaint.h index 4ec9fa7f7e..c7eb68b198 100644 --- a/thirdparty/thorvg/src/renderer/tvgPaint.h +++ b/thirdparty/thorvg/src/renderer/tvgPaint.h @@ -50,16 +50,15 @@ namespace tvg Paint* paint = nullptr; RenderTransform* rTransform = nullptr; Composite* compData = nullptr; - BlendMethod blendMethod = BlendMethod::Normal; //uint8_t + RenderMethod* renderer = nullptr; + BlendMethod blendMethod = BlendMethod::Normal; //uint8_t uint8_t renderFlag = RenderUpdateFlag::None; uint8_t ctxFlag = ContextFlag::Invalid; uint8_t id; uint8_t opacity = 255; - uint8_t refCnt = 0; + uint8_t refCnt = 0; //reference count - Impl(Paint* pnt) : paint(pnt) - { - } + Impl(Paint* pnt) : paint(pnt) {} ~Impl() { @@ -68,18 +67,19 @@ namespace tvg free(compData); } delete(rTransform); + if (renderer && (renderer->unref() == 0)) delete(renderer); } uint8_t ref() { if (refCnt == 255) TVGERR("RENDERER", "Corrupted reference count!"); - return (++refCnt); + return ++refCnt; } uint8_t unref() { if (refCnt == 0) TVGERR("RENDERER", "Corrupted reference count!"); - return (--refCnt); + return --refCnt; } bool transform(const Matrix& m) @@ -131,15 +131,14 @@ namespace tvg return true; } - RenderRegion bounds(RenderMethod& renderer) const; - bool dispose(RenderMethod& renderer); + RenderRegion bounds(RenderMethod* renderer) const; Iterator* iterator(); bool rotate(float degree); bool scale(float factor); bool translate(float x, float y); bool bounds(float* x, float* y, float* w, float* h, bool transformed, bool stroking); - RenderData update(RenderMethod& renderer, const RenderTransform* pTransform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper = false); - bool render(RenderMethod& renderer); + RenderData update(RenderMethod* renderer, const RenderTransform* pTransform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper = false); + bool render(RenderMethod* renderer); Paint* duplicate(); }; } diff --git a/thirdparty/thorvg/src/renderer/tvgPicture.cpp b/thirdparty/thorvg/src/renderer/tvgPicture.cpp index 7f120593f4..cfa1bccd08 100644 --- a/thirdparty/thorvg/src/renderer/tvgPicture.cpp +++ b/thirdparty/thorvg/src/renderer/tvgPicture.cpp @@ -69,18 +69,18 @@ bool Picture::Impl::needComposition(uint8_t opacity) } -bool Picture::Impl::render(RenderMethod &renderer) +bool Picture::Impl::render(RenderMethod* renderer) { bool ret = false; - if (surface) return renderer.renderImage(rd); + if (surface) return renderer->renderImage(rd); else if (paint) { Compositor* cmp = nullptr; if (needComp) { - cmp = renderer.target(bounds(renderer), renderer.colorSpace()); - renderer.beginComposite(cmp, CompositeMethod::None, 255); + cmp = renderer->target(bounds(renderer), renderer->colorSpace()); + renderer->beginComposite(cmp, CompositeMethod::None, 255); } ret = paint->pImpl->render(renderer); - if (cmp) renderer.endComposite(cmp); + if (cmp) renderer->endComposite(cmp); } return ret; } @@ -95,9 +95,9 @@ bool Picture::Impl::size(float w, float h) } -RenderRegion Picture::Impl::bounds(RenderMethod& renderer) +RenderRegion Picture::Impl::bounds(RenderMethod* renderer) { - if (rd) return renderer.region(rd); + if (rd) return renderer->region(rd); if (paint) return paint->pImpl->bounds(renderer); return {0, 0, 0, 0}; } diff --git a/thirdparty/thorvg/src/renderer/tvgPicture.h b/thirdparty/thorvg/src/renderer/tvgPicture.h index 26a171ba66..91c16eb44e 100644 --- a/thirdparty/thorvg/src/renderer/tvgPicture.h +++ b/thirdparty/thorvg/src/renderer/tvgPicture.h @@ -70,9 +70,9 @@ struct Picture::Impl RenderTransform resizeTransform(const RenderTransform* pTransform); bool needComposition(uint8_t opacity); - bool render(RenderMethod &renderer); + bool render(RenderMethod* renderer); bool size(float w, float h); - RenderRegion bounds(RenderMethod& renderer); + RenderRegion bounds(RenderMethod* renderer); Result load(ImageLoader* ploader); Impl(Picture* p) : picture(p) @@ -82,24 +82,21 @@ struct Picture::Impl ~Impl() { LoaderMgr::retrieve(loader); + if (surface) { + if (auto renderer = PP(picture)->renderer) { + renderer->dispose(rd); + } + } delete(paint); } - bool dispose(RenderMethod& renderer) - { - if (paint) paint->pImpl->dispose(renderer); - else if (surface) renderer.dispose(rd); - rd = nullptr; - return true; - } - - RenderData update(RenderMethod &renderer, const RenderTransform* pTransform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper) + RenderData update(RenderMethod* renderer, const RenderTransform* pTransform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper) { auto flag = load(); if (surface) { auto transform = resizeTransform(pTransform); - rd = renderer.prepare(surface, &rm, rd, &transform, clips, opacity, static_cast<RenderUpdateFlag>(pFlag | flag)); + rd = renderer->prepare(surface, &rm, rd, &transform, clips, opacity, static_cast<RenderUpdateFlag>(pFlag | flag)); } else if (paint) { if (resizing) { loader->resize(paint, w, h); diff --git a/thirdparty/thorvg/src/renderer/tvgRender.h b/thirdparty/thorvg/src/renderer/tvgRender.h index 3437f9cbff..210382efb4 100644 --- a/thirdparty/thorvg/src/renderer/tvgRender.h +++ b/thirdparty/thorvg/src/renderer/tvgRender.h @@ -261,7 +261,23 @@ struct RenderShape class RenderMethod { +private: + uint32_t refCnt = 0; //reference count + Key key; + public: + uint32_t ref() + { + ScopedLock lock(key); + return (++refCnt); + } + + uint32_t unref() + { + ScopedLock lock(key); + return (--refCnt); + } + virtual ~RenderMethod() {} virtual RenderData prepare(const RenderShape& rshape, RenderData data, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags, bool clipper) = 0; virtual RenderData prepare(const Array<RenderData>& scene, RenderData data, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags) = 0; @@ -270,7 +286,7 @@ public: virtual bool renderShape(RenderData data) = 0; virtual bool renderImage(RenderData data) = 0; virtual bool postRender() = 0; - virtual bool dispose(RenderData data) = 0; + virtual void dispose(RenderData data) = 0; virtual RenderRegion region(RenderData data) = 0; virtual RenderRegion viewport() = 0; virtual bool viewport(const RenderRegion& vp) = 0; @@ -322,7 +338,7 @@ static inline uint8_t CHANNEL_SIZE(ColorSpace cs) } } -static inline ColorSpace COMPOSITE_TO_COLORSPACE(RenderMethod& renderer, CompositeMethod method) +static inline ColorSpace COMPOSITE_TO_COLORSPACE(RenderMethod* renderer, CompositeMethod method) { switch(method) { case CompositeMethod::AlphaMask: @@ -335,7 +351,7 @@ static inline ColorSpace COMPOSITE_TO_COLORSPACE(RenderMethod& renderer, Composi //TODO: Optimize Luma/InvLuma colorspace to Grayscale8 case CompositeMethod::LumaMask: case CompositeMethod::InvLumaMask: - return renderer.colorSpace(); + return renderer->colorSpace(); default: TVGERR("RENDERER", "Unsupported Composite Size! = %d", (int)method); return ColorSpace::Unsupported; diff --git a/thirdparty/thorvg/src/renderer/tvgScene.h b/thirdparty/thorvg/src/renderer/tvgScene.h index 1a5600c231..2267a2f71b 100644 --- a/thirdparty/thorvg/src/renderer/tvgScene.h +++ b/thirdparty/thorvg/src/renderer/tvgScene.h @@ -59,7 +59,6 @@ struct SceneIterator : Iterator struct Scene::Impl { list<Paint*> paints; - RenderMethod* renderer = nullptr; //keep it for explicit clear RenderData rd = nullptr; Scene* scene = nullptr; uint8_t opacity; //for composition @@ -74,19 +73,10 @@ struct Scene::Impl for (auto paint : paints) { if (P(paint)->unref() == 0) delete(paint); } - } - bool dispose(RenderMethod& renderer) - { - for (auto paint : paints) { - paint->pImpl->dispose(renderer); + if (auto renderer = PP(scene)->renderer) { + renderer->dispose(rd); } - - renderer.dispose(rd); - this->renderer = nullptr; - this->rd = nullptr; - - return true; } bool needComposition(uint8_t opacity) @@ -111,7 +101,7 @@ struct Scene::Impl return true; } - RenderData update(RenderMethod &renderer, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flag, bool clipper) + RenderData update(RenderMethod* renderer, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flag, bool clipper) { if ((needComp = needComposition(opacity))) { /* Overriding opacity value. If this scene is half-translucent, @@ -120,14 +110,12 @@ struct Scene::Impl opacity = 255; } - this->renderer = &renderer; - if (clipper) { Array<RenderData> rds(paints.size()); for (auto paint : paints) { rds.push(paint->pImpl->update(renderer, transform, clips, opacity, flag, true)); } - rd = renderer.prepare(rds, rd, transform, clips, opacity, flag); + rd = renderer->prepare(rds, rd, transform, clips, opacity, flag); return rd; } else { for (auto paint : paints) { @@ -137,13 +125,13 @@ struct Scene::Impl } } - bool render(RenderMethod& renderer) + bool render(RenderMethod* renderer) { Compositor* cmp = nullptr; if (needComp) { - cmp = renderer.target(bounds(renderer), renderer.colorSpace()); - renderer.beginComposite(cmp, CompositeMethod::None, opacity); + cmp = renderer->target(bounds(renderer), renderer->colorSpace()); + renderer->beginComposite(cmp, CompositeMethod::None, opacity); needComp = false; } @@ -151,12 +139,12 @@ struct Scene::Impl if (!paint->pImpl->render(renderer)) return false; } - if (cmp) renderer.endComposite(cmp); + if (cmp) renderer->endComposite(cmp); return true; } - RenderRegion bounds(RenderMethod& renderer) const + RenderRegion bounds(RenderMethod* renderer) const { if (paints.empty()) return {0, 0, 0, 0}; @@ -226,14 +214,10 @@ struct Scene::Impl void clear(bool free) { - auto dispose = renderer ? true : false; - for (auto paint : paints) { - if (dispose) free &= P(paint)->dispose(*renderer); if (P(paint)->unref() == 0 && free) delete(paint); } paints.clear(); - renderer = nullptr; } Iterator* iterator() diff --git a/thirdparty/thorvg/src/renderer/tvgShape.h b/thirdparty/thorvg/src/renderer/tvgShape.h index a7f1226690..1a7a29a999 100644 --- a/thirdparty/thorvg/src/renderer/tvgShape.h +++ b/thirdparty/thorvg/src/renderer/tvgShape.h @@ -41,25 +41,25 @@ struct Shape::Impl { } - bool dispose(RenderMethod& renderer) + ~Impl() { - renderer.dispose(rd); - rd = nullptr; - return true; + if (auto renderer = PP(shape)->renderer) { + renderer->dispose(rd); + } } - bool render(RenderMethod& renderer) + bool render(RenderMethod* renderer) { Compositor* cmp = nullptr; bool ret; if (needComp) { - cmp = renderer.target(bounds(renderer), renderer.colorSpace()); - renderer.beginComposite(cmp, CompositeMethod::None, opacity); + cmp = renderer->target(bounds(renderer), renderer->colorSpace()); + renderer->beginComposite(cmp, CompositeMethod::None, opacity); needComp = false; } - ret = renderer.renderShape(rd); - if (cmp) renderer.endComposite(cmp); + ret = renderer->renderShape(rd); + if (cmp) renderer->endComposite(cmp); return ret; } @@ -83,7 +83,7 @@ struct Shape::Impl return true; } - RenderData update(RenderMethod& renderer, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper) + RenderData update(RenderMethod* renderer, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper) { if ((needComp = needComposition(opacity))) { /* Overriding opacity value. If this scene is half-translucent, @@ -92,14 +92,14 @@ struct Shape::Impl opacity = 255; } - rd = renderer.prepare(rs, rd, transform, clips, opacity, static_cast<RenderUpdateFlag>(pFlag | flag), clipper); + rd = renderer->prepare(rs, rd, transform, clips, opacity, static_cast<RenderUpdateFlag>(pFlag | flag), clipper); flag = RenderUpdateFlag::None; return rd; } - RenderRegion bounds(RenderMethod& renderer) + RenderRegion bounds(RenderMethod* renderer) { - return renderer.region(rd); + return renderer->region(rd); } bool bounds(float* x, float* y, float* w, float* h, bool stroking) diff --git a/thirdparty/thorvg/src/renderer/tvgText.h b/thirdparty/thorvg/src/renderer/tvgText.h index b9f7ef6079..f4fb12259a 100644 --- a/thirdparty/thorvg/src/renderer/tvgText.h +++ b/thirdparty/thorvg/src/renderer/tvgText.h @@ -35,7 +35,6 @@ struct Text::Impl { - RenderData rd = nullptr; FontLoader* loader = nullptr; Shape* paint = nullptr; char* utf8 = nullptr; @@ -92,12 +91,13 @@ struct Text::Impl return Result::Success; } - RenderRegion bounds(RenderMethod& renderer) + RenderRegion bounds(RenderMethod* renderer) { - return renderer.region(rd); + if (paint) return P(paint)->bounds(renderer); + else return {0, 0, 0, 0}; } - bool render(RenderMethod& renderer) + bool render(RenderMethod* renderer) { if (paint) return PP(paint)->render(renderer); return false; @@ -120,7 +120,7 @@ struct Text::Impl return false; } - RenderData update(RenderMethod& renderer, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper) + RenderData update(RenderMethod* renderer, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper) { if (!load()) return nullptr; @@ -142,8 +142,7 @@ struct Text::Impl P(static_cast<RadialGradient*>(fill))->fr *= scale; } } - rd = PP(paint)->update(renderer, transform, clips, opacity, pFlag, clipper); - return rd; + return PP(paint)->update(renderer, transform, clips, opacity, pFlag, clipper); } bool bounds(float* x, float* y, float* w, float* h, TVG_UNUSED bool stroking) @@ -153,13 +152,6 @@ struct Text::Impl return true; } - bool dispose(RenderMethod& renderer) - { - renderer.dispose(rd); - this->rd = nullptr; - return true; - } - Paint* duplicate() { load(); diff --git a/thirdparty/thorvg/update-thorvg.sh b/thirdparty/thorvg/update-thorvg.sh index 4f485d0921..be82de797d 100755 --- a/thirdparty/thorvg/update-thorvg.sh +++ b/thirdparty/thorvg/update-thorvg.sh @@ -1,6 +1,6 @@ #!/bin/bash -e -VERSION=0.12.3 +VERSION=0.12.4 cd thirdparty/thorvg/ || true rm -rf AUTHORS LICENSE inc/ src/ *.zip *.tar.gz tmp/ |