summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/windows_builds.yml2
-rw-r--r--.gitignore5
-rw-r--r--SConstruct3
-rw-r--r--core/config/project_settings.cpp4
-rw-r--r--core/extension/gdextension.cpp3
-rw-r--r--core/io/resource_loader.cpp1
-rw-r--r--core/io/resource_loader.h3
-rw-r--r--core/object/object.cpp1
-rw-r--r--core/os/mutex.h80
-rw-r--r--core/os/safe_binary_mutex.h124
-rw-r--r--doc/classes/Callable.xml12
-rw-r--r--doc/classes/DisplayServer.xml2
-rw-r--r--doc/classes/EditorExportPlatform.xml2
-rw-r--r--doc/classes/HeightMapShape3D.xml16
-rw-r--r--doc/classes/Object.xml3
-rw-r--r--doc/classes/PhysicsBody2D.xml6
-rw-r--r--doc/classes/PhysicsBody3D.xml6
-rw-r--r--doc/classes/XRInterface.xml12
-rw-r--r--editor/editor_inspector.cpp74
-rw-r--r--editor/plugins/animation_blend_space_1d_editor.cpp1
-rw-r--r--editor/plugins/animation_blend_space_2d_editor.cpp1
-rw-r--r--editor/plugins/animation_blend_tree_editor_plugin.cpp1
-rw-r--r--editor/plugins/animation_state_machine_editor.cpp1
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp17
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp76
-rw-r--r--editor/plugins/node_3d_editor_plugin.h2
-rw-r--r--editor/plugins/texture_region_editor_plugin.cpp4
-rw-r--r--editor/scene_tree_dock.cpp9
-rw-r--r--main/main.cpp321
-rw-r--r--main/main.h11
-rw-r--r--methods.py608
-rw-r--r--misc/msvs/props.template21
-rw-r--r--misc/msvs/sln.template20
-rw-r--r--misc/msvs/vcxproj.filters.template30
-rw-r--r--misc/msvs/vcxproj.template42
-rwxr-xr-xmisc/scripts/black_format.sh2
-rwxr-xr-xmisc/scripts/clang_format.sh2
-rwxr-xr-xmisc/scripts/clang_tidy.sh2
-rwxr-xr-xmisc/scripts/dotnet_format.sh2
-rwxr-xr-xmisc/scripts/file_format.sh2
-rwxr-xr-xmisc/scripts/header_guards.sh2
-rw-r--r--modules/gdscript/editor/script_templates/CharacterBody2D/basic_movement.gd5
-rw-r--r--modules/gdscript/editor/script_templates/CharacterBody3D/basic_movement.gd5
-rw-r--r--modules/gdscript/gdscript_tokenizer.cpp14
-rw-r--r--modules/gltf/gltf_document.cpp15
-rw-r--r--modules/gltf/gltf_document.h2
-rw-r--r--modules/mono/editor/script_templates/CharacterBody2D/basic_movement.cs9
-rw-r--r--modules/mono/editor/script_templates/CharacterBody3D/basic_movement.cs9
-rw-r--r--modules/multiplayer/multiplayer_synchronizer.cpp2
-rw-r--r--modules/multiplayer/scene_replication_interface.cpp10
-rw-r--r--modules/openxr/doc_classes/OpenXRAPIExtension.xml38
-rw-r--r--modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml6
-rw-r--r--modules/openxr/extensions/openxr_extension_wrapper_extension.cpp11
-rw-r--r--modules/openxr/extensions/openxr_extension_wrapper_extension.h4
-rw-r--r--modules/openxr/extensions/openxr_fb_passthrough_extension_wrapper.cpp246
-rw-r--r--modules/openxr/extensions/openxr_fb_passthrough_extension_wrapper.h231
-rw-r--r--modules/openxr/openxr_api.cpp22
-rw-r--r--modules/openxr/openxr_api.h13
-rw-r--r--modules/openxr/openxr_api_extension.cpp32
-rw-r--r--modules/openxr/openxr_api_extension.h16
-rw-r--r--modules/openxr/openxr_interface.cpp17
-rw-r--r--modules/openxr/openxr_interface.h2
-rw-r--r--modules/openxr/register_types.cpp2
-rw-r--r--platform/macos/detect.py3
-rw-r--r--platform/macos/display_server_macos.mm4
-rw-r--r--platform/windows/SCsub8
-rw-r--r--platform/windows/msvs.py20
-rw-r--r--scene/2d/physics_body_2d.cpp5
-rw-r--r--scene/2d/physics_body_2d.h1
-rw-r--r--scene/3d/physics_body_3d.cpp6
-rw-r--r--scene/3d/physics_body_3d.h1
-rw-r--r--scene/gui/text_edit.cpp2
-rw-r--r--scene/gui/tree.cpp8
-rw-r--r--scene/resources/height_map_shape_3d.cpp10
-rw-r--r--scene/resources/height_map_shape_3d.h3
-rw-r--r--thirdparty/README.md2
-rw-r--r--thirdparty/thorvg/inc/config.h2
-rw-r--r--thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp15
-rw-r--r--thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp17
-rw-r--r--thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.h2
-rw-r--r--thirdparty/thorvg/src/renderer/sw_engine/tvgSwShape.cpp8
-rw-r--r--thirdparty/thorvg/src/renderer/tvgCanvas.h39
-rw-r--r--thirdparty/thorvg/src/renderer/tvgPaint.cpp38
-rw-r--r--thirdparty/thorvg/src/renderer/tvgPaint.h21
-rw-r--r--thirdparty/thorvg/src/renderer/tvgPicture.cpp14
-rw-r--r--thirdparty/thorvg/src/renderer/tvgPicture.h21
-rw-r--r--thirdparty/thorvg/src/renderer/tvgRender.h22
-rw-r--r--thirdparty/thorvg/src/renderer/tvgScene.h34
-rw-r--r--thirdparty/thorvg/src/renderer/tvgShape.h26
-rw-r--r--thirdparty/thorvg/src/renderer/tvgText.h20
-rwxr-xr-xthirdparty/thorvg/update-thorvg.sh2
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=&quot;$(ProjectDir.TrimEnd(&apos;\\&apos;))&quot;",
+ "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 &amp;&amp; cmd /V /C " + commands
+ else:
+ common_build_prefix[0] = "echo Starting SCons &amp;&amp; cmd /V /C " + common_build_prefix[0]
+
+ cmd = " ^&amp; ".join(common_build_prefix + [" ".join([commands] + common_build_postfix)])
+ props_template = props_template.replace("%%BUILD%%", cmd)
+
+ cmd = " ^&amp; ".join(common_build_prefix + [" ".join([commands] + cmd_rebuild)])
+ props_template = props_template.replace("%%REBUILD%%", cmd)
+
+ cmd = " ^&amp; ".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 &quot;plat=$(PlatformTarget)&quot;",
+ "(if &quot;$(PlatformTarget)&quot;==&quot;x64&quot; (set &quot;plat=x86_amd64&quot;))",
+ f"call &quot;{batch_file}&quot; !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/