summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--SConstruct9
-rw-r--r--core/SCsub1
-rw-r--r--core/config/engine.cpp5
-rw-r--r--core/config/engine.h3
-rw-r--r--core/extension/SCsub7
-rw-r--r--core/extension/extension_api_dump.cpp805
-rw-r--r--core/extension/extension_api_dump.h (renamed from platform/javascript/http_client.h.inc)30
-rw-r--r--core/extension/gdnative_interface.cpp688
-rw-r--r--core/extension/gdnative_interface.h427
-rw-r--r--core/extension/native_extension.cpp411
-rw-r--r--core/extension/native_extension.h94
-rw-r--r--core/extension/native_extension_manager.cpp130
-rw-r--r--core/extension/native_extension_manager.h71
-rw-r--r--core/io/http_client.cpp702
-rw-r--r--core/io/http_client.h83
-rw-r--r--core/io/http_client_tcp.cpp666
-rw-r--r--core/io/http_client_tcp.h92
-rw-r--r--core/object/SCsub5
-rw-r--r--core/object/class_db.cpp11
-rw-r--r--core/object/class_db.h4
-rw-r--r--core/object/make_virtuals.py152
-rw-r--r--core/object/method_bind.cpp29
-rw-r--r--core/object/method_bind.h2
-rw-r--r--core/object/object.cpp38
-rw-r--r--core/object/object.h46
-rw-r--r--core/object/ref_counted.h4
-rw-r--r--core/register_core_types.cpp44
-rw-r--r--core/register_core_types.h1
-rw-r--r--core/templates/rid_owner.h3
-rw-r--r--core/variant/binder_common.h2
-rw-r--r--core/variant/method_ptrcall.h14
-rw-r--r--core/variant/variant.h4
-rw-r--r--core/variant/variant_call.cpp19
-rw-r--r--core/variant/variant_op.cpp4
-rw-r--r--core/variant/variant_setget.cpp4
-rw-r--r--core/variant/variant_utility.cpp19
-rw-r--r--doc/classes/@GlobalScope.xml2
-rw-r--r--doc/classes/CodeEdit.xml40
-rw-r--r--doc/classes/Color.xml40
-rw-r--r--doc/classes/Control.xml16
-rw-r--r--doc/classes/CylinderMesh.xml10
-rw-r--r--doc/classes/NativeExtension.xml57
-rw-r--r--doc/classes/NativeExtensionManager.xml61
-rw-r--r--doc/classes/Plane.xml14
-rw-r--r--doc/classes/Quaternion.xml32
-rw-r--r--doc/classes/RDFramebufferPass.xml35
-rw-r--r--doc/classes/RenderingDevice.xml50
-rw-r--r--doc/classes/TextEdit.xml58
-rw-r--r--doc/classes/Tween.xml3
-rw-r--r--doc/classes/Vector2.xml24
-rw-r--r--doc/classes/Vector2i.xml24
-rw-r--r--doc/classes/Vector3.xml24
-rw-r--r--doc/classes/Vector3i.xml24
-rw-r--r--doc/classes/float.xml33
-rw-r--r--doc/classes/int.xml45
-rw-r--r--drivers/vulkan/rendering_device_vulkan.cpp781
-rw-r--r--drivers/vulkan/rendering_device_vulkan.h109
-rw-r--r--editor/code_editor.cpp20
-rw-r--r--editor/editor_node.cpp4
-rw-r--r--editor/editor_properties_array_dict.cpp2
-rw-r--r--editor/node_3d_editor_gizmos.cpp4
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp2
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp2
-rw-r--r--editor/plugins/occluder_instance_3d_editor_plugin.cpp2
-rw-r--r--editor/plugins/script_editor_plugin.cpp16
-rw-r--r--editor/plugins/script_editor_plugin.h1
-rw-r--r--editor/plugins/script_text_editor.cpp6
-rw-r--r--editor/plugins/shader_editor_plugin.cpp10
-rw-r--r--editor/plugins/text_editor.cpp4
-rw-r--r--editor/plugins/theme_editor_plugin.cpp31
-rw-r--r--editor/plugins/theme_editor_plugin.h4
-rw-r--r--editor/project_export.cpp12
-rw-r--r--main/main.cpp24
-rw-r--r--modules/gdnative/include/gdnative/variant.h2
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs4
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/PackedSceneExtensions.cs8
-rw-r--r--modules/opensimplex/noise_texture.cpp2
-rw-r--r--modules/visual_script/visual_script_editor.cpp152
-rw-r--r--modules/visual_script/visual_script_editor.h3
-rw-r--r--modules/visual_script/visual_script_nodes.cpp96
-rw-r--r--platform/android/export/export.cpp40
-rw-r--r--platform/android/java/app/build.gradle14
-rw-r--r--platform/android/java/app/config.gradle33
-rw-r--r--platform/android/java/build.gradle3
-rw-r--r--platform/android/java/gradle/wrapper/gradle-wrapper.properties6
-rw-r--r--platform/iphone/export/export.cpp41
-rw-r--r--platform/iphone/plugin/godot_plugin_config.h81
-rw-r--r--platform/javascript/export/export.cpp7
-rw-r--r--platform/javascript/http_client_javascript.cpp101
-rw-r--r--platform/javascript/http_client_javascript.h108
-rw-r--r--platform/osx/display_server_osx.mm6
-rw-r--r--scene/2d/polygon_2d.cpp2
-rw-r--r--scene/gui/code_edit.cpp506
-rw-r--r--scene/gui/code_edit.h34
-rw-r--r--scene/gui/control.cpp25
-rw-r--r--scene/gui/control.h3
-rw-r--r--scene/gui/graph_edit.cpp4
-rw-r--r--scene/gui/graph_edit.h1
-rw-r--r--scene/gui/label.cpp1
-rw-r--r--scene/gui/rich_text_label.cpp2
-rw-r--r--scene/gui/scroll_container.cpp4
-rw-r--r--scene/gui/scroll_container.h2
-rw-r--r--scene/gui/text_edit.cpp541
-rw-r--r--scene/gui/text_edit.h34
-rw-r--r--scene/gui/texture_progress_bar.cpp140
-rw-r--r--scene/gui/texture_progress_bar.h3
-rw-r--r--scene/gui/tree.cpp106
-rw-r--r--scene/main/http_request.cpp5
-rw-r--r--scene/main/viewport.cpp2
-rw-r--r--scene/register_scene_types.cpp5
-rw-r--r--scene/resources/primitive_meshes.cpp6
-rw-r--r--scene/resources/texture.cpp1
-rw-r--r--scene/scene_string_names.cpp1
-rw-r--r--scene/scene_string_names.h1
-rw-r--r--servers/register_server_types.cpp26
-rw-r--r--servers/rendering/renderer_rd/pipeline_cache_rd.cpp7
-rw-r--r--servers/rendering/renderer_rd/pipeline_cache_rd.h9
-rw-r--r--servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp5
-rw-r--r--servers/rendering/renderer_rd/shaders/canvas.glsl2
-rw-r--r--servers/rendering/rendering_device.cpp72
-rw-r--r--servers/rendering/rendering_device.h30
-rw-r--r--servers/rendering/rendering_device_binds.h28
122 files changed, 6442 insertions, 2128 deletions
diff --git a/SConstruct b/SConstruct
index f8e3a68edd..e5646a7d0a 100644
--- a/SConstruct
+++ b/SConstruct
@@ -551,11 +551,10 @@ if selected_platform in platform_list:
if env["target"] == "release":
if env["tools"]:
- print("Tools can only be built with targets 'debug' and 'release_debug'.")
+ print("Error: The editor can only be built with `target=debug` or `target=release_debug`.")
Exit(255)
suffix += ".opt"
env.Append(CPPDEFINES=["NDEBUG"])
-
elif env["target"] == "release_debug":
if env["tools"]:
suffix += ".opt.tools"
@@ -563,8 +562,14 @@ if selected_platform in platform_list:
suffix += ".opt.debug"
else:
if env["tools"]:
+ print(
+ "Note: Building a debug binary (which will run slowly). Use `target=release_debug` to build an optimized release binary."
+ )
suffix += ".tools"
else:
+ print(
+ "Note: Building a debug binary (which will run slowly). Use `target=release` to build an optimized release binary."
+ )
suffix += ".debug"
if env["arch"] != "":
diff --git a/core/SCsub b/core/SCsub
index e3ba46be02..d9167b8f83 100644
--- a/core/SCsub
+++ b/core/SCsub
@@ -186,6 +186,7 @@ SConscript("io/SCsub")
SConscript("debugger/SCsub")
SConscript("input/SCsub")
SConscript("variant/SCsub")
+SConscript("extension/SCsub")
SConscript("object/SCsub")
SConscript("templates/SCsub")
SConscript("string/SCsub")
diff --git a/core/config/engine.cpp b/core/config/engine.cpp
index 13cfd51570..ad31966a65 100644
--- a/core/config/engine.cpp
+++ b/core/config/engine.cpp
@@ -236,9 +236,10 @@ Engine::Engine() {
singleton = this;
}
-Engine::Singleton::Singleton(const StringName &p_name, Object *p_ptr) :
+Engine::Singleton::Singleton(const StringName &p_name, Object *p_ptr, const StringName &p_class_name) :
name(p_name),
- ptr(p_ptr) {
+ ptr(p_ptr),
+ class_name(p_class_name) {
#ifdef DEBUG_ENABLED
RefCounted *rc = Object::cast_to<RefCounted>(p_ptr);
if (rc && !rc->is_referenced()) {
diff --git a/core/config/engine.h b/core/config/engine.h
index ecf07952ab..970cfb03e8 100644
--- a/core/config/engine.h
+++ b/core/config/engine.h
@@ -41,7 +41,8 @@ public:
struct Singleton {
StringName name;
Object *ptr;
- Singleton(const StringName &p_name = StringName(), Object *p_ptr = nullptr);
+ StringName class_name; //used for binding generation hinting
+ Singleton(const StringName &p_name = StringName(), Object *p_ptr = nullptr, const StringName &p_class_name = StringName());
};
private:
diff --git a/core/extension/SCsub b/core/extension/SCsub
new file mode 100644
index 0000000000..a3a54250c1
--- /dev/null
+++ b/core/extension/SCsub
@@ -0,0 +1,7 @@
+#!/usr/bin/env python
+
+Import("env")
+
+env_extension = env.Clone()
+
+env_extension.add_source_files(env.core_sources, "*.cpp")
diff --git a/core/extension/extension_api_dump.cpp b/core/extension/extension_api_dump.cpp
new file mode 100644
index 0000000000..3c132a619d
--- /dev/null
+++ b/core/extension/extension_api_dump.cpp
@@ -0,0 +1,805 @@
+/*************************************************************************/
+/* extension_api_dump.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "extension_api_dump.h"
+#include "core/config/engine.h"
+#include "core/core_constants.h"
+#include "core/io/file_access.h"
+#include "core/io/json.h"
+#include "core/templates/pair.h"
+#include "core/version.h"
+
+#ifdef TOOLS_ENABLED
+
+Dictionary NativeExtensionAPIDump::generate_extension_api() {
+ Dictionary api_dump;
+
+ {
+ //header
+ Dictionary header;
+ header["version_major"] = VERSION_MAJOR;
+ header["version_minor"] = VERSION_MINOR;
+#if VERSION_PATCH
+ header["version_patch"] = VERSION_PATCH;
+#else
+ header["version_patch"] = 0;
+#endif
+ header["version_status"] = VERSION_STATUS;
+ header["version_build"] = VERSION_BUILD;
+ header["version_full_name"] = VERSION_FULL_NAME;
+
+ api_dump["header"] = header;
+ }
+
+ const uint32_t vec3_elems = 3;
+ const uint32_t ptrsize_32 = 4;
+ const uint32_t ptrsize_64 = 4;
+ static const char *build_config_name[4] = { "float_32", "float_64", "double_32", "double_64" };
+
+ {
+ //type sizes
+ struct {
+ Variant::Type type;
+ uint32_t size_32_bits_real_float;
+ uint32_t size_64_bits_real_float;
+ uint32_t size_32_bits_real_double;
+ uint32_t size_64_bits_real_double;
+ } type_size_array[Variant::VARIANT_MAX + 1] = {
+ { Variant::NIL, 0, 0, 0, 0 },
+ { Variant::BOOL, sizeof(uint32_t), sizeof(uint32_t), sizeof(uint32_t), sizeof(uint32_t) },
+ { Variant::INT, sizeof(int64_t), sizeof(int64_t), sizeof(int64_t), sizeof(int64_t) },
+ { Variant::FLOAT, sizeof(double), sizeof(double), sizeof(double), sizeof(double) },
+ { Variant::STRING, ptrsize_32, ptrsize_64, ptrsize_32, ptrsize_64 },
+ { Variant::VECTOR2, 2 * sizeof(float), 2 * sizeof(float), 2 * sizeof(double), 2 * sizeof(double) },
+ { Variant::VECTOR2I, 2 * sizeof(int32_t), 2 * sizeof(int32_t), 2 * sizeof(int32_t), 2 * sizeof(int32_t) },
+ { Variant::RECT2, 4 * sizeof(float), 4 * sizeof(float), 4 * sizeof(double), 4 * sizeof(double) },
+ { Variant::RECT2I, 4 * sizeof(int32_t), 4 * sizeof(int32_t), 4 * sizeof(int32_t), 4 * sizeof(int32_t) },
+ { Variant::VECTOR3, vec3_elems * sizeof(float), vec3_elems * sizeof(float), vec3_elems * sizeof(double), vec3_elems * sizeof(double) },
+ { Variant::VECTOR3I, 3 * sizeof(int32_t), 3 * sizeof(int32_t), 3 * sizeof(int32_t), 3 * sizeof(int32_t) },
+ { Variant::TRANSFORM2D, 6 * sizeof(float), 6 * sizeof(float), 6 * sizeof(double), 6 * sizeof(double) },
+ { Variant::PLANE, (vec3_elems + 1) * sizeof(float), (vec3_elems + 1) * sizeof(float), (vec3_elems + 1) * sizeof(double), (vec3_elems + 1) * sizeof(double) },
+ { Variant::QUATERNION, 4 * sizeof(float), 4 * sizeof(float), 4 * sizeof(double), 4 * sizeof(double) },
+ { Variant::AABB, (vec3_elems * 2) * sizeof(float), (vec3_elems * 2) * sizeof(float), (vec3_elems * 2) * sizeof(double), (vec3_elems * 2) * sizeof(double) },
+ { Variant::BASIS, (vec3_elems * 3) * sizeof(float), (vec3_elems * 3) * sizeof(float), (vec3_elems * 3) * sizeof(double), (vec3_elems * 3) * sizeof(double) },
+ { Variant::TRANSFORM3D, (vec3_elems * 4) * sizeof(float), (vec3_elems * 4) * sizeof(float), (vec3_elems * 4) * sizeof(double), (vec3_elems * 4) * sizeof(double) },
+ { Variant::COLOR, 4 * sizeof(float), 4 * sizeof(float), 4 * sizeof(float), 4 * sizeof(float) },
+ { Variant::STRING_NAME, ptrsize_32, ptrsize_64, ptrsize_32, ptrsize_64 },
+ { Variant::NODE_PATH, ptrsize_32, ptrsize_64, ptrsize_32, ptrsize_64 },
+ { Variant::RID, sizeof(uint64_t), sizeof(uint64_t), sizeof(uint64_t), sizeof(uint64_t) },
+ { Variant::OBJECT, ptrsize_32, ptrsize_64, ptrsize_32, ptrsize_64 },
+ { Variant::CALLABLE, sizeof(Callable), sizeof(Callable), sizeof(Callable), sizeof(Callable) }, //harcoded align
+ { Variant::SIGNAL, sizeof(Signal), sizeof(Signal), sizeof(Signal), sizeof(Signal) }, //harcoded align
+ { Variant::DICTIONARY, ptrsize_32, ptrsize_64, ptrsize_32, ptrsize_64 },
+ { Variant::ARRAY, ptrsize_32, ptrsize_64, ptrsize_32, ptrsize_64 },
+ { Variant::PACKED_BYTE_ARRAY, ptrsize_32, ptrsize_64, ptrsize_32, ptrsize_64 },
+ { Variant::PACKED_INT32_ARRAY, ptrsize_32, ptrsize_64, ptrsize_32, ptrsize_64 },
+ { Variant::PACKED_INT64_ARRAY, ptrsize_32, ptrsize_64, ptrsize_32, ptrsize_64 },
+ { Variant::PACKED_FLOAT32_ARRAY, ptrsize_32, ptrsize_64, ptrsize_32, ptrsize_64 },
+ { Variant::PACKED_FLOAT64_ARRAY, ptrsize_32, ptrsize_64, ptrsize_32, ptrsize_64 },
+ { Variant::PACKED_STRING_ARRAY, ptrsize_32, ptrsize_64, ptrsize_32, ptrsize_64 },
+ { Variant::PACKED_VECTOR2_ARRAY, ptrsize_32, ptrsize_64, ptrsize_32, ptrsize_64 },
+ { Variant::PACKED_VECTOR3_ARRAY, ptrsize_32, ptrsize_64, ptrsize_32, ptrsize_64 },
+ { Variant::PACKED_COLOR_ARRAY, ptrsize_32, ptrsize_64, ptrsize_32, ptrsize_64 },
+ { Variant::VARIANT_MAX, sizeof(uint64_t) + sizeof(float) * 4, sizeof(uint64_t) + sizeof(float) * 4, sizeof(uint64_t) + sizeof(double) * 4, sizeof(uint64_t) + sizeof(double) * 4 },
+ };
+
+ Array core_type_sizes;
+
+ for (int i = 0; i < 4; i++) {
+ Dictionary d;
+ d["build_configuration"] = build_config_name[i];
+ Array sizes;
+ for (int j = 0; j < Variant::VARIANT_MAX; j++) {
+ Variant::Type t = type_size_array[j].type;
+ String name = t == Variant::VARIANT_MAX ? String("Variant") : Variant::get_type_name(t);
+ Dictionary d2;
+ d2["name"] = name;
+ uint32_t size;
+ switch (i) {
+ case 0:
+ size = type_size_array[j].size_32_bits_real_float;
+ break;
+ case 1:
+ size = type_size_array[j].size_64_bits_real_float;
+ break;
+ case 2:
+ size = type_size_array[j].size_32_bits_real_double;
+ break;
+ case 3:
+ size = type_size_array[j].size_64_bits_real_double;
+ break;
+ }
+ d2["size"] = size;
+ sizes.push_back(d2);
+ }
+ d["sizes"] = sizes;
+ core_type_sizes.push_back(d);
+ }
+ api_dump["builtin_class_sizes"] = core_type_sizes;
+ }
+
+ {
+ //member offsets sizes
+ struct {
+ Variant::Type type;
+ const char *member;
+ uint32_t offset_32_bits_real_float;
+ uint32_t offset_64_bits_real_float;
+ uint32_t offset_32_bits_real_double;
+ uint32_t offset_64_bits_real_double;
+ } member_offset_array[] = {
+ { Variant::VECTOR2, "x", 0, 0, 0, 0 },
+ { Variant::VECTOR2, "y", sizeof(float), sizeof(float), sizeof(double), sizeof(double) },
+ { Variant::VECTOR2I, "x", 0, 0, 0, 0 },
+ { Variant::VECTOR2I, "y", sizeof(int32_t), sizeof(int32_t), sizeof(int32_t), sizeof(int32_t) },
+ { Variant::RECT2, "position", 0, 0, 0, 0 },
+ { Variant::RECT2, "size", 2 * sizeof(Vector2), 2 * sizeof(float), 2 * sizeof(double), 2 * sizeof(double) },
+ { Variant::RECT2I, "position", 0, 0, 0, 0 },
+ { Variant::RECT2I, "size", 2 * sizeof(int32_t), 2 * sizeof(int32_t), 2 * sizeof(int32_t), 2 * sizeof(int32_t) },
+ { Variant::VECTOR3, "x", 0, 0, 0, 0 },
+ { Variant::VECTOR3, "y", sizeof(float), sizeof(float), sizeof(double), sizeof(double) },
+ { Variant::VECTOR3, "z", 2 * sizeof(float), 2 * sizeof(float), 2 * sizeof(double), 2 * sizeof(double) },
+ { Variant::VECTOR3I, "x", 0, 0, 0, 0 },
+ { Variant::VECTOR3I, "y", sizeof(int32_t), sizeof(int32_t), sizeof(int32_t), sizeof(int32_t) },
+ { Variant::VECTOR3I, "z", 2 * sizeof(int32_t), 2 * sizeof(int32_t), 2 * sizeof(int32_t), 2 * sizeof(int32_t) },
+ { Variant::TRANSFORM2D, "x", 0, 0, 0, 0 },
+ { Variant::TRANSFORM2D, "y", 2 * sizeof(float), 2 * sizeof(float), 2 * sizeof(double), 2 * sizeof(double) },
+ { Variant::TRANSFORM2D, "origin", 4 * sizeof(float), 4 * sizeof(float), 4 * sizeof(double), 4 * sizeof(double) },
+ { Variant::PLANE, "normal", 0, 0, 0, 0 },
+ { Variant::PLANE, "d", vec3_elems * sizeof(float), vec3_elems * sizeof(float), vec3_elems * sizeof(double), vec3_elems * sizeof(double) },
+ { Variant::QUATERNION, "x", 0, 0, 0, 0 },
+ { Variant::QUATERNION, "y", sizeof(float), sizeof(float), sizeof(double), sizeof(double) },
+ { Variant::QUATERNION, "z", 2 * sizeof(float), 2 * sizeof(float), 2 * sizeof(double), 2 * sizeof(double) },
+ { Variant::QUATERNION, "w", 3 * sizeof(float), 3 * sizeof(float), 3 * sizeof(double), 3 * sizeof(double) },
+ { Variant::AABB, "position", 0, 0, 0, 0 },
+ { Variant::AABB, "size", vec3_elems * sizeof(float), vec3_elems * sizeof(float), vec3_elems * sizeof(double), vec3_elems * sizeof(double) },
+ //rememer that basis vectors are flipped!
+ { Variant::BASIS, "x", 0, 0, 0, 0 },
+ { Variant::BASIS, "y", vec3_elems * sizeof(float), vec3_elems * sizeof(float), vec3_elems * sizeof(double), vec3_elems * sizeof(double) },
+ { Variant::BASIS, "z", vec3_elems * 2 * sizeof(float), vec3_elems * 2 * sizeof(float), vec3_elems * 2 * sizeof(double), vec3_elems * 2 * sizeof(double) },
+ { Variant::TRANSFORM3D, "basis", 0, 0, 0, 0 },
+ { Variant::TRANSFORM3D, "origin", (vec3_elems * 3) * sizeof(float), (vec3_elems * 3) * sizeof(float), (vec3_elems * 3) * sizeof(double), (vec3_elems * 3) * sizeof(double) },
+ { Variant::COLOR, "x", 0, 0, 0, 0 },
+ { Variant::COLOR, "y", sizeof(float), sizeof(float), sizeof(float), sizeof(float) },
+ { Variant::COLOR, "z", 2 * sizeof(float), 2 * sizeof(float), 2 * sizeof(float), 2 * sizeof(float) },
+ { Variant::COLOR, "w", 3 * sizeof(float), 3 * sizeof(float), 3 * sizeof(float), 3 * sizeof(float) },
+ { Variant::NIL, nullptr, 0, 0, 0, 0 },
+ };
+
+ Array core_type_member_offsets;
+
+ for (int i = 0; i < 4; i++) {
+ Dictionary d;
+ d["build_configuration"] = build_config_name[i];
+ Array type_offsets;
+ uint32_t idx = 0;
+
+ Variant::Type last_type = Variant::NIL;
+
+ Dictionary d2;
+ Array members;
+
+ while (true) {
+ Variant::Type t = member_offset_array[idx].type;
+ if (t != last_type) {
+ if (last_type != Variant::NIL) {
+ d2["members"] = members;
+ type_offsets.push_back(d2);
+ }
+ if (t == Variant::NIL) {
+ break;
+ }
+
+ String name = t == Variant::VARIANT_MAX ? String("Variant") : Variant::get_type_name(t);
+ d2 = Dictionary();
+ members = Array();
+ d2["name"] = name;
+ last_type = t;
+ }
+ Dictionary d3;
+ uint32_t offset;
+ switch (i) {
+ case 0:
+ offset = member_offset_array[idx].offset_32_bits_real_float;
+ break;
+ case 1:
+ offset = member_offset_array[idx].offset_64_bits_real_float;
+ break;
+ case 2:
+ offset = member_offset_array[idx].offset_32_bits_real_double;
+ break;
+ case 3:
+ offset = member_offset_array[idx].offset_64_bits_real_double;
+ break;
+ }
+ d3["member"] = member_offset_array[idx].member;
+ d3["offset"] = offset;
+ members.push_back(d3);
+ idx++;
+ }
+ d["classes"] = type_offsets;
+ core_type_member_offsets.push_back(d);
+ }
+ api_dump["builtin_class_member_offsets"] = core_type_member_offsets;
+ }
+
+ {
+ // global enums and constants
+ Array constants;
+ Map<String, List<Pair<String, int>>> enum_list;
+
+ for (int i = 0; i < CoreConstants::get_global_constant_count(); i++) {
+ int value = CoreConstants::get_global_constant_value(i);
+ String enum_name = CoreConstants::get_global_constant_enum(i);
+ String name = CoreConstants::get_global_constant_name(i);
+ if (enum_name != String()) {
+ enum_list[enum_name].push_back(Pair<String, int>(name, value));
+ } else {
+ Dictionary d;
+ d["name"] = name;
+ d["value"] = value;
+ constants.push_back(d);
+ }
+ }
+
+ api_dump["global_constants"] = constants;
+
+ Array enums;
+ for (Map<String, List<Pair<String, int>>>::Element *E = enum_list.front(); E; E = E->next()) {
+ Dictionary d1;
+ d1["name"] = E->key();
+ Array values;
+ for (List<Pair<String, int>>::Element *F = E->get().front(); F; F = F->next()) {
+ Dictionary d2;
+ d2["name"] = F->get().first;
+ d2["value"] = F->get().second;
+ values.push_back(d2);
+ }
+ d1["values"] = values;
+ enums.push_back(d1);
+ }
+
+ api_dump["global_enums"] = enums;
+ }
+ {
+ Array utility_funcs;
+
+ List<StringName> utility_func_names;
+ Variant::get_utility_function_list(&utility_func_names);
+
+ for (List<StringName>::Element *E = utility_func_names.front(); E; E = E->next()) {
+ StringName name = E->get();
+ Dictionary func;
+ func["name"] = String(name);
+ if (Variant::has_utility_function_return_value(name)) {
+ Variant::Type rt = Variant::get_utility_function_return_type(name);
+ func["return_type"] = rt == Variant::NIL ? String("Variant") : Variant::get_type_name(rt);
+ }
+ switch (Variant::get_utility_function_type(name)) {
+ case Variant::UTILITY_FUNC_TYPE_MATH:
+ func["category"] = "math";
+ break;
+ case Variant::UTILITY_FUNC_TYPE_RANDOM:
+ func["category"] = "random";
+ break;
+ case Variant::UTILITY_FUNC_TYPE_GENERAL:
+ func["category"] = "general";
+ break;
+ }
+ bool vararg = Variant::is_utility_function_vararg(name);
+ func["is_vararg"] = Variant::is_utility_function_vararg(name);
+ func["hash"] = Variant::get_utility_function_hash(name);
+ Array arguments;
+ int argcount = Variant::get_utility_function_argument_count(name);
+ for (int i = 0; i < argcount; i++) {
+ Dictionary arg;
+ String argname = vararg ? "arg" + itos(i + 1) : Variant::get_utility_function_argument_name(name, i);
+ arg["name"] = argname;
+ Variant::Type argtype = Variant::get_utility_function_argument_type(name, i);
+ arg["type"] = argtype == Variant::NIL ? String("Variant") : Variant::get_type_name(argtype);
+ //no default value support in utility functions
+ arguments.push_back(arg);
+ }
+
+ if (arguments.size()) {
+ func["arguments"] = arguments;
+ }
+
+ utility_funcs.push_back(func);
+ }
+
+ api_dump["utility_functions"] = utility_funcs;
+ }
+
+ {
+ // builtin types
+
+ Array builtins;
+
+ for (int i = 0; i < Variant::VARIANT_MAX; i++) {
+ if (i == Variant::OBJECT) {
+ continue;
+ }
+
+ Variant::Type type = Variant::Type(i);
+
+ Dictionary d;
+ d["name"] = Variant::get_type_name(type);
+ if (Variant::has_indexing(type)) {
+ Variant::Type index_type = Variant::get_indexed_element_type(type);
+ d["indexing_return_type"] = index_type == Variant::NIL ? String("Variant") : Variant::get_type_name(index_type);
+ }
+
+ {
+ //members
+ Array members;
+
+ List<StringName> member_names;
+ Variant::get_member_list(type, &member_names);
+ for (List<StringName>::Element *E = member_names.front(); E; E = E->next()) {
+ StringName member_name = E->get();
+ Dictionary d2;
+ d2["name"] = String(member_name);
+ d2["type"] = Variant::get_type_name(Variant::get_member_type(type, member_name));
+ members.push_back(d2);
+ }
+ if (members.size()) {
+ d["members"] = members;
+ }
+ }
+ {
+ //constants
+ Array constants;
+
+ List<StringName> constant_names;
+ Variant::get_constants_for_type(type, &constant_names);
+ for (List<StringName>::Element *E = constant_names.front(); E; E = E->next()) {
+ StringName constant_name = E->get();
+ Dictionary d2;
+ d2["name"] = String(constant_name);
+ Variant constant = Variant::get_constant_value(type, constant_name);
+ d2["type"] = Variant::get_type_name(constant.get_type());
+ d2["value"] = constant.get_construct_string();
+ constants.push_back(d2);
+ }
+ if (constants.size()) {
+ d["constants"] = constants;
+ }
+ }
+ {
+ //operators
+ Array operators;
+
+ for (int j = 0; j < Variant::VARIANT_MAX; j++) {
+ for (int k = 0; k < Variant::OP_MAX; k++) {
+ Variant::Type rt = Variant::get_operator_return_type(Variant::Operator(k), type, Variant::Type(j));
+ if (rt != Variant::NIL) {
+ Dictionary d2;
+ d2["name"] = Variant::get_operator_name(Variant::Operator(k));
+ if (k != Variant::OP_NEGATE && k != Variant::OP_POSITIVE && k != Variant::OP_NOT && k != Variant::OP_BIT_NEGATE) {
+ d2["right_type"] = Variant::get_type_name(Variant::Type(j));
+ }
+ operators.push_back(d2);
+ }
+ }
+ }
+ if (operators.size()) {
+ d["operators"] = operators;
+ }
+ }
+ {
+ //methods
+ Array methods;
+
+ List<StringName> method_names;
+ Variant::get_builtin_method_list(type, &method_names);
+ for (List<StringName>::Element *E = method_names.front(); E; E = E->next()) {
+ StringName method_name = E->get();
+ Dictionary d2;
+ d2["name"] = String(method_name);
+ if (Variant::has_builtin_method_return_value(type, method_name)) {
+ Variant::Type ret_type = Variant::get_builtin_method_return_type(type, method_name);
+ d2["return_type"] = ret_type == Variant::NIL ? String("Variant") : Variant::get_type_name(ret_type);
+ }
+ d2["is_vararg"] = Variant::is_builtin_method_vararg(type, method_name);
+ d2["is_const"] = Variant::is_builtin_method_const(type, method_name);
+ d2["is_static"] = Variant::is_builtin_method_static(type, method_name);
+ d2["hash"] = Variant::get_builtin_method_hash(type, method_name);
+
+ Vector<Variant> default_args = Variant::get_builtin_method_default_arguments(type, method_name);
+
+ Array arguments;
+ int argcount = Variant::get_builtin_method_argument_count(type, method_name);
+ for (int j = 0; j < argcount; j++) {
+ Dictionary d3;
+ d3["name"] = Variant::get_builtin_method_argument_name(type, method_name, j);
+ Variant::Type argtype = Variant::get_builtin_method_argument_type(type, method_name, j);
+ d3["type"] = argtype == Variant::NIL ? String("Variant") : Variant::get_type_name(argtype);
+
+ if (j >= (argcount - default_args.size())) {
+ int dargidx = j - (argcount - default_args.size());
+ d3["default_value"] = default_args[dargidx].get_construct_string();
+ }
+ arguments.push_back(d3);
+ }
+
+ if (arguments.size()) {
+ d2["arguments"] = arguments;
+ }
+
+ methods.push_back(d2);
+ }
+ if (methods.size()) {
+ d["methods"] = methods;
+ }
+ }
+ {
+ //constructors
+ Array constructors;
+
+ for (int j = 0; j < Variant::get_constructor_count(type); j++) {
+ Dictionary d2;
+ d2["index"] = j;
+
+ Array arguments;
+ int argcount = Variant::get_constructor_argument_count(type, j);
+ for (int k = 0; k < argcount; k++) {
+ Dictionary d3;
+ d3["name"] = Variant::get_constructor_argument_name(type, j, k);
+ d3["type"] = Variant::get_type_name(Variant::get_constructor_argument_type(type, j, k));
+ arguments.push_back(d3);
+ }
+ if (arguments.size()) {
+ d2["arguments"] = arguments;
+ }
+ constructors.push_back(d2);
+ }
+
+ if (constructors.size()) {
+ d["constructors"] = constructors;
+ }
+ }
+
+ builtins.push_back(d);
+ }
+
+ api_dump["builtin_classes"] = builtins;
+ }
+
+ {
+ // classes
+ Array classes;
+
+ List<StringName> class_list;
+
+ ClassDB::get_class_list(&class_list);
+
+ class_list.sort_custom<StringName::AlphCompare>();
+
+ for (List<StringName>::Element *E = class_list.front(); E; E = E->next()) {
+ Dictionary d;
+ StringName class_name = E->get();
+ d["name"] = String(class_name);
+ d["is_refcounted"] = ClassDB::is_parent_class(class_name, "RefCounted");
+ d["is_instantiable"] = ClassDB::can_instantiate(class_name);
+ StringName parent_class = ClassDB::get_parent_class(class_name);
+ if (parent_class != StringName()) {
+ d["inherits"] = String(parent_class);
+ }
+
+ {
+ ClassDB::APIType api = ClassDB::get_api_type(class_name);
+ static const char *api_type[5] = { "core", "editor", "extension", "editor_extension" };
+ d["api_type"] = api_type[api];
+ }
+
+ {
+ //constants
+ Array constants;
+ List<String> constant_list;
+ ClassDB::get_integer_constant_list(class_name, &constant_list, true);
+ for (List<String>::Element *F = constant_list.front(); F; F = F->next()) {
+ StringName enum_name = ClassDB::get_integer_constant_enum(class_name, F->get());
+ if (enum_name != StringName()) {
+ continue; //enums will be handled on their own
+ }
+
+ Dictionary d2;
+ d2["name"] = String(F->get());
+ d2["value"] = ClassDB::get_integer_constant(class_name, F->get());
+
+ constants.push_back(d2);
+ }
+
+ if (constants.size()) {
+ d["constants"] = constants;
+ }
+ }
+ {
+ //enum
+ Array enums;
+ List<StringName> enum_list;
+ ClassDB::get_enum_list(class_name, &enum_list, true);
+ for (List<StringName>::Element *F = enum_list.front(); F; F = F->next()) {
+ Dictionary d2;
+ d2["name"] = String(F->get());
+
+ Array values;
+ List<StringName> enum_constant_list;
+ ClassDB::get_enum_constants(class_name, F->get(), &enum_constant_list, true);
+ for (List<StringName>::Element *G = enum_constant_list.front(); G; G = G->next()) {
+ Dictionary d3;
+ d3["name"] = String(G->get());
+ d3["value"] = ClassDB::get_integer_constant(class_name, G->get());
+ values.push_back(d3);
+ }
+
+ d2["values"] = values;
+
+ enums.push_back(d2);
+ }
+
+ if (enums.size()) {
+ d["enums"] = enums;
+ }
+ }
+ {
+ //methods
+ Array methods;
+ List<MethodInfo> method_list;
+ ClassDB::get_method_list(class_name, &method_list, true);
+ for (List<MethodInfo>::Element *F = method_list.front(); F; F = F->next()) {
+ StringName method_name = F->get().name;
+ if (F->get().flags & METHOD_FLAG_VIRTUAL) {
+ //virtual method
+ const MethodInfo &mi = F->get();
+ Dictionary d2;
+ d2["name"] = String(method_name);
+ d2["is_const"] = (F->get().flags & METHOD_FLAG_CONST) ? true : false;
+ d2["is_vararg"] = false;
+ d2["is_virtual"] = true;
+ // virtual functions have no hash since no MethodBind is involved
+ bool has_return = mi.return_val.type != Variant::NIL || (mi.return_val.usage & PROPERTY_USAGE_NIL_IS_VARIANT);
+ Array arguments;
+ for (int i = (has_return ? -1 : 0); i < mi.arguments.size(); i++) {
+ PropertyInfo pinfo = i == -1 ? mi.return_val : mi.arguments[i];
+ Dictionary d3;
+
+ if (i >= 0) {
+ d3["name"] = pinfo.name;
+ }
+ if (pinfo.class_name != StringName()) {
+ d3["type"] = String(pinfo.class_name);
+ } else {
+ Variant::Type type = pinfo.type;
+ if (type == Variant::NIL) {
+ d3["type"] = "Variant";
+ } else {
+ d3["type"] = Variant::get_type_name(type);
+ }
+ }
+
+ if (i == -1) {
+ d2["return_value"] = d3;
+ } else {
+ arguments.push_back(d3);
+ }
+ }
+
+ if (arguments.size()) {
+ d2["arguments"] = arguments;
+ }
+
+ methods.push_back(d2);
+
+ } else if (F->get().name.begins_with("_")) {
+ //hidden method, ignore
+
+ } else {
+ Dictionary d2;
+ d2["name"] = String(method_name);
+
+ MethodBind *method = ClassDB::get_method(class_name, method_name);
+ if (!method) {
+ continue;
+ }
+
+ d2["is_const"] = method->is_const();
+ d2["is_vararg"] = method->is_vararg();
+ d2["is_virtual"] = false;
+ d2["hash"] = method->get_hash();
+
+ Vector<Variant> default_args = method->get_default_arguments();
+
+ Array arguments;
+ for (int i = (method->has_return() ? -1 : 0); i < method->get_argument_count(); i++) {
+ PropertyInfo pinfo = i == -1 ? method->get_return_info() : method->get_argument_info(i);
+ Dictionary d3;
+
+ if (i >= 0) {
+ d3["name"] = pinfo.name;
+ }
+ if (pinfo.class_name != StringName()) {
+ d3["type"] = String(pinfo.class_name);
+ } else {
+ Variant::Type type = pinfo.type;
+ if (type == Variant::NIL) {
+ d3["type"] = "Variant";
+ } else {
+ d3["type"] = Variant::get_type_name(type);
+ }
+ }
+
+ if (method->get_argument_meta(i) > 0) {
+ static const char *argmeta[11] = { "none", "int8", "int16", "int32", "int64", "uint8", "uint16", "uint32", "uint64", "float", "double" };
+ d3["meta"] = argmeta[method->get_argument_meta(i)];
+ }
+
+ if (i >= 0 && i >= (method->get_argument_count() - default_args.size())) {
+ int dargidx = i - (method->get_argument_count() - default_args.size());
+ d3["default_value"] = default_args[dargidx].get_construct_string();
+ }
+
+ if (i == -1) {
+ d2["return_value"] = d3;
+ } else {
+ arguments.push_back(d3);
+ }
+ }
+
+ if (arguments.size()) {
+ d2["arguments"] = arguments;
+ }
+
+ methods.push_back(d2);
+ }
+ }
+
+ if (methods.size()) {
+ d["methods"] = methods;
+ }
+ }
+
+ {
+ //signals
+ Array signals;
+ List<MethodInfo> signal_list;
+ ClassDB::get_signal_list(class_name, &signal_list, true);
+ for (List<MethodInfo>::Element *F = signal_list.front(); F; F = F->next()) {
+ StringName signal_name = F->get().name;
+ Dictionary d2;
+ d2["name"] = String(signal_name);
+
+ Array arguments;
+
+ for (int i = 0; i < F->get().arguments.size(); i++) {
+ Dictionary d3;
+ d3["name"] = F->get().arguments[i].name;
+ Variant::Type type = F->get().arguments[i].type;
+ if (F->get().arguments[i].class_name != StringName()) {
+ d3["type"] = String(F->get().arguments[i].class_name);
+ } else if (type == Variant::NIL) {
+ d3["type"] = "Variant";
+ } else {
+ d3["type"] = Variant::get_type_name(type);
+ }
+ arguments.push_back(d3);
+ }
+ if (arguments.size()) {
+ d2["arguments"] = arguments;
+ }
+
+ signals.push_back(d2);
+ }
+
+ if (signals.size()) {
+ d["signals"] = signals;
+ }
+ }
+ {
+ //properties
+ Array properties;
+ List<PropertyInfo> property_list;
+ ClassDB::get_property_list(class_name, &property_list, true);
+ for (List<PropertyInfo>::Element *F = property_list.front(); F; F = F->next()) {
+ if (F->get().usage & PROPERTY_USAGE_CATEGORY || F->get().usage & PROPERTY_USAGE_GROUP || F->get().usage & PROPERTY_USAGE_SUBGROUP) {
+ continue; //not real properties
+ }
+ if (F->get().name.begins_with("_")) {
+ continue; //hidden property
+ }
+ StringName property_name = F->get().name;
+ Dictionary d2;
+ d2["name"] = String(property_name);
+
+ if (F->get().class_name != StringName()) {
+ d2["type"] = String(F->get().class_name);
+ } else if (F->get().type == Variant::NIL && F->get().usage & PROPERTY_USAGE_NIL_IS_VARIANT) {
+ d2["type"] = "Variant";
+ } else {
+ d2["type"] = Variant::get_type_name(F->get().type);
+ }
+
+ d2["setter"] = ClassDB::get_property_setter(class_name, F->get().name);
+ d2["getter"] = ClassDB::get_property_getter(class_name, F->get().name);
+ d2["index"] = ClassDB::get_property_index(class_name, F->get().name);
+ properties.push_back(d2);
+ }
+
+ if (properties.size()) {
+ d["properties"] = properties;
+ }
+ }
+
+ classes.push_back(d);
+ }
+
+ api_dump["classes"] = classes;
+ }
+
+ {
+ // singletons
+
+ Array singletons;
+ List<Engine::Singleton> singleton_list;
+ Engine::get_singleton()->get_singletons(&singleton_list);
+
+ for (List<Engine::Singleton>::Element *E = singleton_list.front(); E; E = E->next()) {
+ const Engine::Singleton &s = E->get();
+ Dictionary d;
+ d["name"] = s.name;
+ if (s.class_name != StringName()) {
+ d["type"] = String(s.class_name);
+ } else {
+ d["type"] = String(s.ptr->get_class());
+ }
+ singletons.push_back(d);
+ }
+
+ if (singletons.size()) {
+ api_dump["singletons"] = singletons;
+ }
+ }
+
+ return api_dump;
+}
+
+void NativeExtensionAPIDump::generate_extension_json_file(const String &p_path) {
+ Dictionary api = generate_extension_api();
+ Ref<JSON> json;
+ json.instantiate();
+
+ String text = json->stringify(api, "\t", false);
+ FileAccessRef fa = FileAccess::open(p_path, FileAccess::WRITE);
+ CharString cs = text.ascii();
+ fa->store_buffer((const uint8_t *)cs.ptr(), cs.length());
+ fa->close();
+}
+#endif
diff --git a/platform/javascript/http_client.h.inc b/core/extension/extension_api_dump.h
index 6544d41c98..a7825c10a9 100644
--- a/platform/javascript/http_client.h.inc
+++ b/core/extension/extension_api_dump.h
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* http_client.h.inc */
+/* extension_api_dump.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,24 +28,18 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-// HTTPClient's additional private members in the javascript platform
+#ifndef API_DUMP_H
+#define API_DUMP_H
-Error make_request(Method p_method, const String &p_url, const Vector<String> &p_headers, const uint8_t *p_body, int p_body_len);
-static void _parse_headers(int p_len, const char **p_headers, void *p_ref);
+#include "core/extension/native_extension.h"
-int js_id = 0;
-// 64 KiB by default (favors fast download speeds at the cost of memory usage).
-int read_limit = 65536;
-Status status = STATUS_DISCONNECTED;
+#ifdef TOOLS_ENABLED
-String host;
-int port = -1;
-bool use_tls = false;
-
-int polled_response_code = 0;
-Vector<String> response_headers;
-Vector<uint8_t> response_buffer;
-
-#ifdef DEBUG_ENABLED
-uint64_t last_polling_frame = 0;
+class NativeExtensionAPIDump {
+public:
+ static Dictionary generate_extension_api();
+ static void generate_extension_json_file(const String &p_path);
+};
#endif
+
+#endif // API_DUMP_H
diff --git a/core/extension/gdnative_interface.cpp b/core/extension/gdnative_interface.cpp
new file mode 100644
index 0000000000..324933d7b7
--- /dev/null
+++ b/core/extension/gdnative_interface.cpp
@@ -0,0 +1,688 @@
+/*************************************************************************/
+/* gdnative_interface.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "gdnative_interface.h"
+
+#include "core/config/engine.h"
+#include "core/object/class_db.h"
+#include "core/os/memory.h"
+#include "core/variant/variant.h"
+#include "core/version.h"
+
+// Memory Functions
+static void *gdnative_alloc(size_t p_size) {
+ return memalloc(p_size);
+}
+
+static void *gdnative_realloc(void *p_mem, size_t p_size) {
+ return memrealloc(p_mem, p_size);
+}
+
+static void gdnative_free(void *p_mem) {
+ memfree(p_mem);
+}
+
+// Helper print functions.
+static void gdnative_print_error(const char *p_description, const char *p_function, const char *p_file, int32_t p_line) {
+ _err_print_error(p_function, p_file, p_line, p_description, ERR_HANDLER_ERROR);
+}
+static void gdnative_print_warning(const char *p_description, const char *p_function, const char *p_file, int32_t p_line) {
+ _err_print_error(p_function, p_file, p_line, p_description, ERR_HANDLER_WARNING);
+}
+static void gdnative_print_script_error(const char *p_description, const char *p_function, const char *p_file, int32_t p_line) {
+ _err_print_error(p_function, p_file, p_line, p_description, ERR_HANDLER_SCRIPT);
+}
+
+// Variant functions
+
+static void gdnative_variant_new_copy(GDNativeVariantPtr r_dest, const GDNativeVariantPtr p_src) {
+ memnew_placement(reinterpret_cast<Variant *>(r_dest), Variant(*reinterpret_cast<Variant *>(p_src)));
+}
+static void gdnative_variant_new_nil(GDNativeVariantPtr r_dest) {
+ memnew_placement(reinterpret_cast<Variant *>(r_dest), Variant);
+}
+static void gdnative_variant_destroy(GDNativeVariantPtr p_self) {
+ reinterpret_cast<Variant *>(p_self)->~Variant();
+}
+
+// variant type
+
+#define memnew_placement_custom(m_placement, m_class, m_constr) _post_initialize(new (m_placement, sizeof(m_class), "") m_constr)
+
+static void gdnative_variant_call(GDNativeVariantPtr p_self, const GDNativeStringNamePtr p_method, const GDNativeVariantPtr *p_args, const GDNativeInt p_argcount, GDNativeVariantPtr r_return, GDNativeCallError *r_error) {
+ Variant *self = (Variant *)p_self;
+ const StringName *method = (const StringName *)p_method;
+ const Variant **args = (const Variant **)p_args;
+ Variant ret;
+ Callable::CallError error;
+ self->call(*method, args, p_argcount, ret, error);
+ memnew_placement_custom(r_return, Variant, Variant(ret));
+
+ if (r_error) {
+ r_error->error = (GDNativeCallErrorType)(error.error);
+ r_error->argument = error.argument;
+ r_error->expected = error.expected;
+ }
+}
+
+static void gdnative_variant_call_static(GDNativeVariantType p_type, const GDNativeStringNamePtr p_method, const GDNativeVariantPtr *p_args, const GDNativeInt p_argcount, GDNativeVariantPtr r_return, GDNativeCallError *r_error) {
+ Variant::Type type = (Variant::Type)p_type;
+ const StringName *method = (const StringName *)p_method;
+ const Variant **args = (const Variant **)p_args;
+ Variant ret;
+ Callable::CallError error;
+ Variant::call_static(type, *method, args, p_argcount, ret, error);
+ memnew_placement_custom(r_return, Variant, Variant(ret));
+
+ if (r_error) {
+ r_error->error = (GDNativeCallErrorType)error.error;
+ r_error->argument = error.argument;
+ r_error->expected = error.expected;
+ }
+}
+
+static void gdnative_variant_evaluate(GDNativeVariantOperator p_op, const GDNativeVariantPtr p_a, const GDNativeVariantPtr p_b, GDNativeVariantPtr r_return, GDNativeBool *r_valid) {
+ Variant::Operator op = (Variant::Operator)p_op;
+ const Variant *a = (const Variant *)p_a;
+ const Variant *b = (const Variant *)p_b;
+ Variant *ret = (Variant *)r_return;
+ bool valid;
+ Variant::evaluate(op, *a, *b, *ret, valid);
+ *r_valid = valid;
+}
+
+static void gdnative_variant_set(GDNativeVariantPtr p_self, const GDNativeVariantPtr p_key, const GDNativeVariantPtr p_value, GDNativeBool *r_valid) {
+ Variant *self = (Variant *)p_self;
+ const Variant *key = (const Variant *)p_key;
+ const Variant *value = (const Variant *)p_value;
+
+ bool valid;
+ self->set(*key, *value, &valid);
+ *r_valid = valid;
+}
+
+static void gdnative_variant_set_named(GDNativeVariantPtr p_self, const GDNativeStringNamePtr p_key, const GDNativeVariantPtr p_value, GDNativeBool *r_valid) {
+ Variant *self = (Variant *)p_self;
+ const StringName *key = (const StringName *)p_key;
+ const Variant *value = (const Variant *)p_value;
+
+ bool valid;
+ self->set_named(*key, *value, valid);
+ *r_valid = valid;
+}
+
+static void gdnative_variant_set_keyed(GDNativeVariantPtr p_self, const GDNativeVariantPtr p_key, const GDNativeVariantPtr p_value, GDNativeBool *r_valid) {
+ Variant *self = (Variant *)p_self;
+ const Variant *key = (const Variant *)p_key;
+ const Variant *value = (const Variant *)p_value;
+
+ bool valid;
+ self->set_keyed(*key, *value, valid);
+ *r_valid = valid;
+}
+
+static void gdnative_variant_set_indexed(GDNativeVariantPtr p_self, GDNativeInt p_index, const GDNativeVariantPtr p_value, GDNativeBool *r_valid, GDNativeBool *r_oob) {
+ Variant *self = (Variant *)p_self;
+ const Variant *value = (const Variant *)p_value;
+
+ bool valid;
+ bool oob;
+ self->set_indexed(p_index, value, valid, oob);
+ *r_valid = valid;
+ *r_oob = oob;
+}
+
+static void gdnative_variant_get(const GDNativeVariantPtr p_self, const GDNativeVariantPtr p_key, GDNativeVariantPtr r_ret, GDNativeBool *r_valid) {
+ const Variant *self = (const Variant *)p_self;
+ const Variant *key = (const Variant *)p_key;
+
+ bool valid;
+ memnew_placement_custom(r_ret, Variant, Variant(self->get(*key, &valid)));
+ *r_valid = valid;
+}
+
+static void gdnative_variant_get_named(const GDNativeVariantPtr p_self, const GDNativeStringNamePtr p_key, GDNativeVariantPtr r_ret, GDNativeBool *r_valid) {
+ const Variant *self = (const Variant *)p_self;
+ const StringName *key = (const StringName *)p_key;
+
+ bool valid;
+ memnew_placement_custom(r_ret, Variant, Variant(self->get_named(*key, valid)));
+ *r_valid = valid;
+}
+
+static void gdnative_variant_get_keyed(const GDNativeVariantPtr p_self, const GDNativeVariantPtr p_key, GDNativeVariantPtr r_ret, GDNativeBool *r_valid) {
+ const Variant *self = (const Variant *)p_self;
+ const Variant *key = (const Variant *)p_key;
+
+ bool valid;
+ memnew_placement_custom(r_ret, Variant, Variant(self->get_keyed(*key, valid)));
+ *r_valid = valid;
+}
+
+static void gdnative_variant_get_indexed(const GDNativeVariantPtr p_self, GDNativeInt p_index, GDNativeVariantPtr r_ret, GDNativeBool *r_valid, GDNativeBool *r_oob) {
+ const Variant *self = (const Variant *)p_self;
+
+ bool valid;
+ bool oob;
+ memnew_placement_custom(r_ret, Variant, Variant(self->get_indexed(p_index, valid, oob)));
+ *r_valid = valid;
+ *r_oob = oob;
+}
+
+/// Iteration.
+static GDNativeBool gdnative_variant_iter_init(const GDNativeVariantPtr p_self, GDNativeVariantPtr r_iter, GDNativeBool *r_valid) {
+ const Variant *self = (const Variant *)p_self;
+ Variant *iter = (Variant *)r_iter;
+
+ bool valid;
+ bool ret = self->iter_init(*iter, valid);
+ *r_valid = valid;
+ return ret;
+}
+
+static GDNativeBool gdnative_variant_iter_next(const GDNativeVariantPtr p_self, GDNativeVariantPtr r_iter, GDNativeBool *r_valid) {
+ const Variant *self = (const Variant *)p_self;
+ Variant *iter = (Variant *)r_iter;
+
+ bool valid;
+ bool ret = self->iter_next(*iter, valid);
+ *r_valid = valid;
+ return ret;
+}
+
+static void gdnative_variant_iter_get(const GDNativeVariantPtr p_self, GDNativeVariantPtr r_iter, GDNativeVariantPtr r_ret, GDNativeBool *r_valid) {
+ const Variant *self = (const Variant *)p_self;
+ Variant *iter = (Variant *)r_iter;
+
+ bool valid;
+ memnew_placement_custom(r_ret, Variant, Variant(self->iter_next(*iter, valid)));
+ *r_valid = valid;
+}
+
+/// Variant functions.
+static GDNativeBool gdnative_variant_hash_compare(const GDNativeVariantPtr p_self, const GDNativeVariantPtr p_other) {
+ const Variant *self = (const Variant *)p_self;
+ const Variant *other = (const Variant *)p_other;
+ return self->hash_compare(*other);
+}
+
+static GDNativeBool gdnative_variant_booleanize(const GDNativeVariantPtr p_self) {
+ const Variant *self = (const Variant *)p_self;
+ return self->booleanize();
+}
+
+static void gdnative_variant_blend(const GDNativeVariantPtr p_a, const GDNativeVariantPtr p_b, float p_c, GDNativeVariantPtr r_dst) {
+ const Variant *a = (const Variant *)p_a;
+ const Variant *b = (const Variant *)p_b;
+ memnew_placement(r_dst, Variant);
+ Variant::blend(*a, *b, p_c, *(Variant *)r_dst);
+}
+
+static void gdnative_variant_interpolate(const GDNativeVariantPtr p_a, const GDNativeVariantPtr p_b, float p_c, GDNativeVariantPtr r_dst) {
+ const Variant *a = (const Variant *)p_a;
+ const Variant *b = (const Variant *)p_b;
+ memnew_placement(r_dst, Variant);
+ Variant::interpolate(*a, *b, p_c, *(Variant *)r_dst);
+}
+
+static void gdnative_variant_duplicate(const GDNativeVariantPtr p_self, GDNativeVariantPtr r_ret, GDNativeBool p_deep) {
+ const Variant *self = (const Variant *)p_self;
+ memnew_placement_custom(r_ret, Variant, Variant(self->duplicate(p_deep)));
+}
+
+static void gdnative_variant_stringify(const GDNativeVariantPtr p_self, GDNativeStringPtr r_ret) {
+ const Variant *self = (const Variant *)p_self;
+ memnew_placement_custom(r_ret, String, String(*self));
+}
+
+static GDNativeVariantType gdnative_variant_get_type(const GDNativeVariantPtr p_self) {
+ const Variant *self = (const Variant *)p_self;
+ return (GDNativeVariantType)self->get_type();
+}
+
+static GDNativeBool gdnative_variant_has_method(const GDNativeVariantPtr p_self, const GDNativeStringNamePtr p_method) {
+ const Variant *self = (const Variant *)p_self;
+ const StringName *method = (const StringName *)p_method;
+ return self->has_method(*method);
+}
+
+static GDNativeBool gdnative_variant_has_member(GDNativeVariantType p_type, const GDNativeStringNamePtr p_member) {
+ return Variant::has_member((Variant::Type)p_type, *((const StringName *)p_member));
+}
+
+static GDNativeBool gdnative_variant_has_key(const GDNativeVariantPtr p_self, const GDNativeVariantPtr p_key, GDNativeBool *r_valid) {
+ const Variant *self = (const Variant *)p_self;
+ const Variant *key = (const Variant *)p_key;
+ bool valid;
+ return self->has_key(*key, valid);
+ *r_valid = valid;
+}
+
+static void gdnative_variant_get_type_name(GDNativeVariantType p_type, GDNativeStringPtr r_ret) {
+ String name = Variant::get_type_name((Variant::Type)p_type);
+ memnew_placement_custom(r_ret, String, String(name));
+}
+
+static GDNativeBool gdnative_variant_can_convert(GDNativeVariantType p_from, GDNativeVariantType p_to) {
+ return Variant::can_convert((Variant::Type)p_from, (Variant::Type)p_to);
+}
+
+static GDNativeBool gdnative_variant_can_convert_strict(GDNativeVariantType p_from, GDNativeVariantType p_to) {
+ return Variant::can_convert_strict((Variant::Type)p_from, (Variant::Type)p_to);
+}
+
+// ptrcalls
+static GDNativePtrOperatorEvaluator gdnative_variant_get_ptr_operator_evaluator(GDNativeVariantOperator p_operator, GDNativeVariantType p_type_a, GDNativeVariantType p_type_b) {
+ return (GDNativePtrOperatorEvaluator)Variant::get_ptr_operator_evaluator(Variant::Operator(p_operator), Variant::Type(p_type_a), Variant::Type(p_type_b));
+}
+static GDNativePtrBuiltInMethod gdnative_variant_get_ptr_builtin_method(GDNativeVariantType p_type, const char *p_method, GDNativeInt p_hash) {
+ StringName method = p_method;
+ uint32_t hash = Variant::get_builtin_method_hash(Variant::Type(p_type), method);
+ if (hash != p_hash) {
+ ERR_PRINT_ONCE("Error getting method " + String(method) + ", hash mismatch.");
+ return nullptr;
+ }
+
+ return (GDNativePtrBuiltInMethod)Variant::get_ptr_builtin_method(Variant::Type(p_type), method);
+}
+static GDNativePtrConstructor gdnative_variant_get_ptr_constructor(GDNativeVariantType p_type, int32_t p_constructor) {
+ return (GDNativePtrConstructor)Variant::get_ptr_constructor(Variant::Type(p_type), p_constructor);
+}
+static void gdnative_variant_construct(GDNativeVariantType p_type, GDNativeVariantPtr p_base, const GDNativeVariantPtr *p_args, int32_t p_argument_count, GDNativeCallError *r_error) {
+ memnew_placement(p_base, Variant);
+
+ Callable::CallError error;
+ Variant::construct(Variant::Type(p_type), *(Variant *)p_base, (const Variant **)p_args, p_argument_count, error);
+
+ if (r_error) {
+ r_error->error = (GDNativeCallErrorType)(error.error);
+ r_error->argument = error.argument;
+ r_error->expected = error.expected;
+ }
+}
+static GDNativePtrSetter gdnative_variant_get_ptr_setter(GDNativeVariantType p_type, const char *p_member) {
+ return (GDNativePtrSetter)Variant::get_member_ptr_setter(Variant::Type(p_type), p_member);
+}
+static GDNativePtrGetter gdnative_variant_get_ptr_getter(GDNativeVariantType p_type, const char *p_member) {
+ return (GDNativePtrGetter)Variant::get_member_ptr_getter(Variant::Type(p_type), p_member);
+}
+static GDNativePtrIndexedSetter gdnative_variant_get_ptr_indexed_setter(GDNativeVariantType p_type) {
+ return (GDNativePtrIndexedSetter)Variant::get_member_ptr_indexed_setter(Variant::Type(p_type));
+}
+static GDNativePtrIndexedGetter gdnative_variant_get_ptr_indexed_getter(GDNativeVariantType p_type) {
+ return (GDNativePtrIndexedGetter)Variant::get_member_ptr_indexed_getter(Variant::Type(p_type));
+}
+static GDNativePtrKeyedSetter gdnative_variant_get_ptr_keyed_setter(GDNativeVariantType p_type) {
+ return (GDNativePtrKeyedSetter)Variant::get_member_ptr_keyed_setter(Variant::Type(p_type));
+}
+static GDNativePtrKeyedGetter gdnative_variant_get_ptr_keyed_getter(GDNativeVariantType p_type) {
+ return (GDNativePtrKeyedGetter)Variant::get_member_ptr_keyed_getter(Variant::Type(p_type));
+}
+static GDNativePtrKeyedChecker gdnative_variant_get_ptr_keyed_checker(GDNativeVariantType p_type) {
+ return (GDNativePtrKeyedChecker)Variant::get_member_ptr_keyed_checker(Variant::Type(p_type));
+}
+static void gdnative_variant_get_constant_value(GDNativeVariantType p_type, const char *p_constant, GDNativeVariantPtr r_ret) {
+ memnew_placement_custom(r_ret, Variant, Variant(Variant::get_constant_value(Variant::Type(p_type), p_constant)));
+}
+static GDNativePtrUtilityFunction gdnative_variant_get_ptr_utility_function(const char *p_function, GDNativeInt p_hash) {
+ StringName function = p_function;
+ uint32_t hash = Variant::get_utility_function_hash(function);
+ if (hash != p_hash) {
+ ERR_PRINT_ONCE("Error getting utility function " + String(function) + ", hash mismatch.");
+ return nullptr;
+ }
+ return (GDNativePtrUtilityFunction)Variant::get_ptr_utility_function(function);
+}
+
+//string helpers
+
+static void gdnative_string_new_with_latin1_chars(GDNativeStringPtr r_dest, const char *p_contents) {
+ String *dest = (String *)r_dest;
+ memnew_placement(dest, String);
+ *dest = String(p_contents);
+}
+
+static void gdnative_string_new_with_utf8_chars(GDNativeStringPtr r_dest, const char *p_contents) {
+ String *dest = (String *)r_dest;
+ memnew_placement(dest, String);
+ dest->parse_utf8(p_contents);
+}
+
+static void gdnative_string_new_with_utf16_chars(GDNativeStringPtr r_dest, const char16_t *p_contents) {
+ String *dest = (String *)r_dest;
+ memnew_placement(dest, String);
+ dest->parse_utf16(p_contents);
+}
+
+static void gdnative_string_new_with_utf32_chars(GDNativeStringPtr r_dest, const char32_t *p_contents) {
+ String *dest = (String *)r_dest;
+ memnew_placement(dest, String);
+ *dest = String((const char32_t *)p_contents);
+}
+
+static void gdnative_string_new_with_wide_chars(GDNativeStringPtr r_dest, const wchar_t *p_contents) {
+ String *dest = (String *)r_dest;
+ if (sizeof(wchar_t) == 2) {
+ // wchar_t is 16 bit, parse.
+ memnew_placement(dest, String);
+ dest->parse_utf16((const char16_t *)p_contents);
+ } else {
+ // wchar_t is 32 bit, copy.
+ memnew_placement(dest, String);
+ *dest = String((const char32_t *)p_contents);
+ }
+}
+
+static void gdnative_string_new_with_latin1_chars_and_len(GDNativeStringPtr r_dest, const char *p_contents, const GDNativeInt p_size) {
+ String *dest = (String *)r_dest;
+ memnew_placement(dest, String);
+ *dest = String(p_contents, p_size);
+}
+
+static void gdnative_string_new_with_utf8_chars_and_len(GDNativeStringPtr r_dest, const char *p_contents, const GDNativeInt p_size) {
+ String *dest = (String *)r_dest;
+ memnew_placement(dest, String);
+ dest->parse_utf8(p_contents, p_size);
+}
+
+static void gdnative_string_new_with_utf16_chars_and_len(GDNativeStringPtr r_dest, const char16_t *p_contents, const GDNativeInt p_size) {
+ String *dest = (String *)r_dest;
+ memnew_placement(dest, String);
+ dest->parse_utf16(p_contents, p_size);
+}
+
+static void gdnative_string_new_with_utf32_chars_and_len(GDNativeStringPtr r_dest, const char32_t *p_contents, const GDNativeInt p_size) {
+ String *dest = (String *)r_dest;
+ memnew_placement(dest, String);
+ *dest = String((const char32_t *)p_contents, p_size);
+}
+
+static void gdnative_string_new_with_wide_chars_and_len(GDNativeStringPtr r_dest, const wchar_t *p_contents, const GDNativeInt p_size) {
+ String *dest = (String *)r_dest;
+ if (sizeof(wchar_t) == 2) {
+ // wchar_t is 16 bit, parse.
+ memnew_placement(dest, String);
+ dest->parse_utf16((const char16_t *)p_contents, p_size);
+ } else {
+ // wchar_t is 32 bit, copy.
+ memnew_placement(dest, String);
+ *dest = String((const char32_t *)p_contents, p_size);
+ }
+}
+
+static GDNativeInt gdnative_string_to_latin1_chars(const GDNativeStringPtr p_self, char *r_text, GDNativeInt p_max_write_length) {
+ String *self = (String *)p_self;
+ CharString cs = self->ascii(true);
+ GDNativeInt len = cs.length();
+ if (r_text) {
+ const char *s_text = cs.ptr();
+ for (GDNativeInt i = 0; i < MIN(len, p_max_write_length); i++) {
+ r_text[i] = s_text[i];
+ }
+ }
+ return len;
+}
+static GDNativeInt gdnative_string_to_utf8_chars(const GDNativeStringPtr p_self, char *r_text, GDNativeInt p_max_write_length) {
+ String *self = (String *)p_self;
+ CharString cs = self->utf8();
+ GDNativeInt len = cs.length();
+ if (r_text) {
+ const char *s_text = cs.ptr();
+ for (GDNativeInt i = 0; i < MIN(len, p_max_write_length); i++) {
+ r_text[i] = s_text[i];
+ }
+ }
+ return len;
+}
+static GDNativeInt gdnative_string_to_utf16_chars(const GDNativeStringPtr p_self, char16_t *r_text, GDNativeInt p_max_write_length) {
+ String *self = (String *)p_self;
+ Char16String cs = self->utf16();
+ GDNativeInt len = cs.length();
+ if (r_text) {
+ const char16_t *s_text = cs.ptr();
+ for (GDNativeInt i = 0; i < MIN(len, p_max_write_length); i++) {
+ r_text[i] = s_text[i];
+ }
+ }
+ return len;
+}
+static GDNativeInt gdnative_string_to_utf32_chars(const GDNativeStringPtr p_self, char32_t *r_text, GDNativeInt p_max_write_length) {
+ String *self = (String *)p_self;
+ GDNativeInt len = self->length();
+ if (r_text) {
+ const char32_t *s_text = self->ptr();
+ for (GDNativeInt i = 0; i < MIN(len, p_max_write_length); i++) {
+ r_text[i] = s_text[i];
+ }
+ }
+ return len;
+}
+static GDNativeInt gdnative_string_to_wide_chars(const GDNativeStringPtr p_self, wchar_t *r_text, GDNativeInt p_max_write_length) {
+ if (sizeof(wchar_t) == 4) {
+ return gdnative_string_to_utf32_chars(p_self, (char32_t *)r_text, p_max_write_length);
+ } else {
+ return gdnative_string_to_utf16_chars(p_self, (char16_t *)r_text, p_max_write_length);
+ }
+}
+
+static char32_t *gdnative_string_operator_index(GDNativeStringPtr p_self, GDNativeInt p_index) {
+ String *self = (String *)p_self;
+ ERR_FAIL_INDEX_V(p_index, self->length() + 1, nullptr);
+ return &self->ptrw()[p_index];
+}
+
+static const char32_t *gdnative_string_operator_index_const(const GDNativeStringPtr p_self, GDNativeInt p_index) {
+ const String *self = (const String *)p_self;
+ ERR_FAIL_INDEX_V(p_index, self->length() + 1, nullptr);
+ return &self->ptr()[p_index];
+}
+
+/* OBJECT API */
+
+static void gdnative_object_method_bind_ptrcall(GDNativeMethodBindPtr p_method_bind, GDNativeObjectPtr p_instance, const GDNativeTypePtr *p_args, GDNativeTypePtr p_ret) {
+ MethodBind *mb = (MethodBind *)p_method_bind;
+ Object *o = (Object *)p_instance;
+ mb->ptrcall(o, (const void **)p_args, p_ret);
+}
+
+static void gdnative_object_destroy(GDNativeObjectPtr p_o) {
+ memdelete((Object *)p_o);
+}
+
+static GDNativeObjectPtr gdnative_global_get_singleton(const char *p_name) {
+ return (GDNativeObjectPtr)Engine::get_singleton()->get_singleton_object(String(p_name));
+}
+
+static GDNativeObjectPtr gdnative_object_get_instance_from_id(GDObjectInstanceID p_instance_id) {
+ return (GDNativeObjectPtr)ObjectDB::get_instance(ObjectID(p_instance_id));
+}
+
+static GDNativeObjectPtr gdnative_object_cast_to(const GDNativeObjectPtr p_object, void *p_class_tag) {
+ if (!p_object) {
+ return nullptr;
+ }
+ Object *o = (Object *)p_object;
+
+ return o->is_class_ptr(p_class_tag) ? (GDNativeObjectPtr)o : (GDNativeObjectPtr) nullptr;
+}
+
+static GDObjectInstanceID gdnative_object_get_instance_id(const GDNativeObjectPtr p_object) {
+ const Object *o = (const Object *)p_object;
+ return (GDObjectInstanceID)o->get_instance_id();
+}
+
+static GDNativeMethodBindPtr gdnative_classdb_get_method_bind(const char *p_classname, const char *p_methodname, GDNativeInt p_hash) {
+ MethodBind *mb = ClassDB::get_method(StringName(p_classname), StringName(p_methodname));
+ ERR_FAIL_COND_V(!mb, nullptr);
+ if (mb->get_hash() != p_hash) {
+ ERR_PRINT_ONCE("Hash mismatch for method '" + String(p_classname) + "." + String(p_methodname) + "'.");
+ return nullptr;
+ }
+ // MethodBind *mb = ClassDB::get_method("Node", "get_name");
+ return (GDNativeMethodBindPtr)mb;
+}
+
+static GDNativeClassConstructor gdnative_classdb_get_constructor(const char *p_classname) {
+ ClassDB::ClassInfo *class_info = ClassDB::classes.getptr(StringName(p_classname));
+ if (class_info) {
+ return (GDNativeClassConstructor)class_info->creation_func;
+ }
+ return nullptr;
+}
+
+static void *gdnative_classdb_get_class_tag(const char *p_classname) {
+ ClassDB::ClassInfo *class_info = ClassDB::classes.getptr(p_classname);
+ return class_info ? class_info->class_ptr : nullptr;
+}
+
+void gdnative_setup_interface(GDNativeInterface *p_interface) {
+ GDNativeInterface &gdni = *p_interface;
+
+ gdni.version_major = VERSION_MAJOR;
+ gdni.version_minor = VERSION_MINOR;
+#if VERSION_PATCH
+ gdni.version_patch = VERSION_PATCH;
+#else
+ gdni.version_patch = 0;
+#endif
+ gdni.version_string = VERSION_FULL_NAME;
+
+ /* GODOT CORE */
+
+ gdni.mem_alloc = gdnative_alloc;
+ gdni.mem_realloc = gdnative_realloc;
+ gdni.mem_free = gdnative_free;
+
+ gdni.print_error = gdnative_print_error;
+ gdni.print_warning = gdnative_print_warning;
+ gdni.print_script_error = gdnative_print_script_error;
+
+ /* GODOT VARIANT */
+
+ // variant general
+ gdni.variant_new_copy = gdnative_variant_new_copy;
+ gdni.variant_new_nil = gdnative_variant_new_nil;
+ gdni.variant_destroy = gdnative_variant_destroy;
+
+ gdni.variant_call = gdnative_variant_call;
+ gdni.variant_call_static = gdnative_variant_call_static;
+ gdni.variant_evaluate = gdnative_variant_evaluate;
+ gdni.variant_set = gdnative_variant_set;
+ gdni.variant_set_named = gdnative_variant_set_named;
+ gdni.variant_set_keyed = gdnative_variant_set_keyed;
+ gdni.variant_set_indexed = gdnative_variant_set_indexed;
+ gdni.variant_get = gdnative_variant_get;
+ gdni.variant_get_named = gdnative_variant_get_named;
+ gdni.variant_get_keyed = gdnative_variant_get_keyed;
+ gdni.variant_get_indexed = gdnative_variant_get_indexed;
+ gdni.variant_iter_init = gdnative_variant_iter_init;
+ gdni.variant_iter_next = gdnative_variant_iter_next;
+ gdni.variant_iter_get = gdnative_variant_iter_get;
+ gdni.variant_hash_compare = gdnative_variant_hash_compare;
+ gdni.variant_booleanize = gdnative_variant_booleanize;
+ gdni.variant_blend = gdnative_variant_blend;
+ gdni.variant_interpolate = gdnative_variant_interpolate;
+ gdni.variant_duplicate = gdnative_variant_duplicate;
+ gdni.variant_stringify = gdnative_variant_stringify;
+
+ gdni.variant_get_type = gdnative_variant_get_type;
+ gdni.variant_has_method = gdnative_variant_has_method;
+ gdni.variant_has_member = gdnative_variant_has_member;
+ gdni.variant_has_key = gdnative_variant_has_key;
+ gdni.variant_get_type_name = gdnative_variant_get_type_name;
+ gdni.variant_can_convert = gdnative_variant_can_convert;
+ gdni.variant_can_convert_strict = gdnative_variant_can_convert_strict;
+
+ //ptrcalls
+#if 0
+ GDNativeVariantFromTypeConstructorFunc (*get_variant_from_type_constructor)(GDNativeVariantType p_type);
+ GDNativeTypeFromVariantConstructorFunc (*get_variant_to_type_constructor)(GDNativeVariantType p_type);
+#endif
+
+ gdni.variant_get_ptr_operator_evaluator = gdnative_variant_get_ptr_operator_evaluator;
+ gdni.variant_get_ptr_builtin_method = gdnative_variant_get_ptr_builtin_method;
+ gdni.variant_get_ptr_constructor = gdnative_variant_get_ptr_constructor;
+ gdni.variant_construct = gdnative_variant_construct;
+ gdni.variant_get_ptr_setter = gdnative_variant_get_ptr_setter;
+ gdni.variant_get_ptr_getter = gdnative_variant_get_ptr_getter;
+ gdni.variant_get_ptr_indexed_setter = gdnative_variant_get_ptr_indexed_setter;
+ gdni.variant_get_ptr_indexed_getter = gdnative_variant_get_ptr_indexed_getter;
+ gdni.variant_get_ptr_keyed_setter = gdnative_variant_get_ptr_keyed_setter;
+ gdni.variant_get_ptr_keyed_getter = gdnative_variant_get_ptr_keyed_getter;
+ gdni.variant_get_ptr_keyed_checker = gdnative_variant_get_ptr_keyed_checker;
+ gdni.variant_get_constant_value = gdnative_variant_get_constant_value;
+ gdni.variant_get_ptr_utility_function = gdnative_variant_get_ptr_utility_function;
+
+ // extra utilities
+
+ gdni.string_new_with_latin1_chars = gdnative_string_new_with_latin1_chars;
+ gdni.string_new_with_utf8_chars = gdnative_string_new_with_utf8_chars;
+ gdni.string_new_with_utf16_chars = gdnative_string_new_with_utf16_chars;
+ gdni.string_new_with_utf32_chars = gdnative_string_new_with_utf32_chars;
+ gdni.string_new_with_wide_chars = gdnative_string_new_with_wide_chars;
+ gdni.string_new_with_latin1_chars_and_len = gdnative_string_new_with_latin1_chars_and_len;
+ gdni.string_new_with_utf8_chars_and_len = gdnative_string_new_with_utf8_chars_and_len;
+ gdni.string_new_with_utf16_chars_and_len = gdnative_string_new_with_utf16_chars_and_len;
+ gdni.string_new_with_utf32_chars_and_len = gdnative_string_new_with_utf32_chars_and_len;
+ gdni.string_new_with_wide_chars_and_len = gdnative_string_new_with_wide_chars_and_len;
+ gdni.string_to_latin1_chars = gdnative_string_to_latin1_chars;
+ gdni.string_to_utf8_chars = gdnative_string_to_utf8_chars;
+ gdni.string_to_utf16_chars = gdnative_string_to_utf16_chars;
+ gdni.string_to_utf32_chars = gdnative_string_to_utf32_chars;
+ gdni.string_to_wide_chars = gdnative_string_to_wide_chars;
+ gdni.string_operator_index = gdnative_string_operator_index;
+ gdni.string_operator_index_const = gdnative_string_operator_index_const;
+
+ /* OBJECT */
+
+ gdni.object_method_bind_ptrcall = gdnative_object_method_bind_ptrcall;
+ gdni.object_destroy = gdnative_object_destroy;
+ gdni.global_get_singleton = gdnative_global_get_singleton;
+
+ gdni.object_cast_to = gdnative_object_cast_to;
+ gdni.object_get_instance_from_id = gdnative_object_get_instance_from_id;
+ gdni.object_get_instance_id = gdnative_object_get_instance_id;
+
+ /* CLASSDB */
+
+ gdni.classdb_get_constructor = gdnative_classdb_get_constructor;
+ gdni.classdb_get_method_bind = gdnative_classdb_get_method_bind;
+ gdni.classdb_get_class_tag = gdnative_classdb_get_class_tag;
+
+ /* CLASSDB EXTENSION */
+
+ //these are filled by implementation, since it will want to keep track of registered classes
+ gdni.classdb_register_extension_class = nullptr;
+ gdni.classdb_register_extension_class_method = nullptr;
+ gdni.classdb_register_extension_class_integer_constant = nullptr;
+ gdni.classdb_register_extension_class_property = nullptr;
+ gdni.classdb_register_extension_class_signal = nullptr;
+ gdni.classdb_unregister_extension_class = nullptr;
+}
diff --git a/core/extension/gdnative_interface.h b/core/extension/gdnative_interface.h
new file mode 100644
index 0000000000..318912e889
--- /dev/null
+++ b/core/extension/gdnative_interface.h
@@ -0,0 +1,427 @@
+/*************************************************************************/
+/* gdnative_interface.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef GDNATIVE_INTERFACE_H
+#define GDNATIVE_INTERFACE_H
+
+/* This is a C class header, you can copy it and use it directly in your own binders.
+ * Together with the JSON file, you should be able to generate any binder.
+ */
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* VARIANT TYPES */
+
+typedef enum {
+ GDNATIVE_VARIANT_TYPE_NIL,
+
+ /* atomic types */
+ GDNATIVE_VARIANT_TYPE_BOOL,
+ GDNATIVE_VARIANT_TYPE_INT,
+ GDNATIVE_VARIANT_TYPE_FLOAT,
+ GDNATIVE_VARIANT_TYPE_STRING,
+
+ /* math types */
+ GDNATIVE_VARIANT_TYPE_VECTOR2,
+ GDNATIVE_VARIANT_TYPE_VECTOR2I,
+ GDNATIVE_VARIANT_TYPE_RECT2,
+ GDNATIVE_VARIANT_TYPE_RECT2I,
+ GDNATIVE_VARIANT_TYPE_VECTOR3,
+ GDNATIVE_VARIANT_TYPE_VECTOR3I,
+ GDNATIVE_VARIANT_TYPE_TRANSFORM2D,
+ GDNATIVE_VARIANT_TYPE_PLANE,
+ GDNATIVE_VARIANT_TYPE_QUATERNION,
+ GDNATIVE_VARIANT_TYPE_AABB,
+ GDNATIVE_VARIANT_TYPE_BASIS,
+ GDNATIVE_VARIANT_TYPE_TRANSFORM3D,
+
+ /* misc types */
+ GDNATIVE_VARIANT_TYPE_COLOR,
+ GDNATIVE_VARIANT_TYPE_STRING_NAME,
+ GDNATIVE_VARIANT_TYPE_NODE_PATH,
+ GDNATIVE_VARIANT_TYPE_RID,
+ GDNATIVE_VARIANT_TYPE_OBJECT,
+ GDNATIVE_VARIANT_TYPE_CALLABLE,
+ GDNATIVE_VARIANT_TYPE_SIGNAL,
+ GDNATIVE_VARIANT_TYPE_DICTIONARY,
+ GDNATIVE_VARIANT_TYPE_ARRAY,
+
+ /* typed arrays */
+ GDNATIVE_VARIANT_TYPE_PACKED_BYTE_ARRAY,
+ GDNATIVE_VARIANT_TYPE_PACKED_INT32_ARRAY,
+ GDNATIVE_VARIANT_TYPE_PACKED_INT64_ARRAY,
+ GDNATIVE_VARIANT_TYPE_PACKED_FLOAT32_ARRAY,
+ GDNATIVE_VARIANT_TYPE_PACKED_FLOAT64_ARRAY,
+ GDNATIVE_VARIANT_TYPE_PACKED_STRING_ARRAY,
+ GDNATIVE_VARIANT_TYPE_PACKED_VECTOR2_ARRAY,
+ GDNATIVE_VARIANT_TYPE_PACKED_VECTOR3_ARRAY,
+ GDNATIVE_VARIANT_TYPE_PACKED_COLOR_ARRAY,
+
+ GDNATIVE_VARIANT_TYPE_VARIANT_MAX
+} GDNativeVariantType;
+
+typedef enum {
+ /* comparison */
+ GDNATIVE_VARIANT_OP_EQUAL,
+ GDNATIVE_VARIANT_OP_NOT_EQUAL,
+ GDNATIVE_VARIANT_OP_LESS,
+ GDNATIVE_VARIANT_OP_LESS_EQUAL,
+ GDNATIVE_VARIANT_OP_GREATER,
+ GDNATIVE_VARIANT_OP_GREATER_EQUAL,
+ /* mathematic */
+ GDNATIVE_VARIANT_OP_ADD,
+ GDNATIVE_VARIANT_OP_SUBTRACT,
+ GDNATIVE_VARIANT_OP_MULTIPLY,
+ GDNATIVE_VARIANT_OP_DIVIDE,
+ GDNATIVE_VARIANT_OP_NEGATE,
+ GDNATIVE_VARIANT_OP_POSITIVE,
+ GDNATIVE_VARIANT_OP_MODULE,
+ /* bitwise */
+ GDNATIVE_VARIANT_OP_SHIFT_LEFT,
+ GDNATIVE_VARIANT_OP_SHIFT_RIGHT,
+ GDNATIVE_VARIANT_OP_BIT_AND,
+ GDNATIVE_VARIANT_OP_BIT_OR,
+ GDNATIVE_VARIANT_OP_BIT_XOR,
+ GDNATIVE_VARIANT_OP_BIT_NEGATE,
+ /* logic */
+ GDNATIVE_VARIANT_OP_AND,
+ GDNATIVE_VARIANT_OP_OR,
+ GDNATIVE_VARIANT_OP_XOR,
+ GDNATIVE_VARIANT_OP_NOT,
+ /* containment */
+ GDNATIVE_VARIANT_OP_IN,
+ GDNATIVE_VARIANT_OP_MAX
+
+} GDNativeVariantOperator;
+
+typedef void *GDNativeVariantPtr;
+typedef void *GDNativeStringNamePtr;
+typedef void *GDNativeStringPtr;
+typedef void *GDNativeObjectPtr;
+typedef void *GDNativeTypePtr;
+typedef void *GDNativeMethodBindPtr;
+typedef int64_t GDNativeInt;
+typedef uint32_t GDNativeBool;
+typedef uint64_t GDObjectInstanceID;
+
+/* VARIANT DATA I/O */
+
+typedef enum {
+ NATIVE_CALL_OK,
+ NATIVE_CALL_ERROR_INVALID_METHOD,
+ NATIVE_CALL_ERROR_INVALID_ARGUMENT, /* expected is variant type */
+ NATIVE_CALL_ERROR_TOO_MANY_ARGUMENTS, /* expected is number of arguments */
+ NATIVE_CALL_ERROR_TOO_FEW_ARGUMENTS, /* expected is number of arguments */
+ NATIVE_CALL_ERROR_INSTANCE_IS_NULL,
+
+} GDNativeCallErrorType;
+
+typedef struct {
+ GDNativeCallErrorType error;
+ int32_t argument;
+ int32_t expected;
+} GDNativeCallError;
+
+typedef void (*GDNativeVariantFromTypeConstructorFunc)(GDNativeVariantPtr, GDNativeTypePtr);
+typedef void (*GDNativeTypeFromVariantConstructorFunc)(GDNativeTypePtr, GDNativeVariantPtr);
+typedef void (*GDNativePtrOperatorEvaluator)(const GDNativeTypePtr p_left, const GDNativeTypePtr p_right, GDNativeTypePtr r_result);
+typedef void (*GDNativePtrBuiltInMethod)(GDNativeTypePtr p_base, const GDNativeTypePtr *p_args, GDNativeTypePtr r_return, int p_argument_count);
+typedef void (*GDNativePtrConstructor)(GDNativeTypePtr p_base, const GDNativeTypePtr *p_args);
+typedef void (*GDNativePtrSetter)(GDNativeTypePtr p_base, const GDNativeTypePtr p_value);
+typedef void (*GDNativePtrGetter)(const GDNativeTypePtr p_base, GDNativeTypePtr r_value);
+typedef void (*GDNativePtrIndexedSetter)(GDNativeTypePtr p_base, GDNativeInt p_index, const GDNativeTypePtr p_value);
+typedef void (*GDNativePtrIndexedGetter)(const GDNativeTypePtr p_base, GDNativeInt p_index, GDNativeTypePtr r_value);
+typedef void (*GDNativePtrKeyedSetter)(GDNativeTypePtr p_base, const GDNativeTypePtr p_key, const GDNativeTypePtr p_value);
+typedef void (*GDNativePtrKeyedGetter)(const GDNativeTypePtr p_base, const GDNativeTypePtr p_key, GDNativeTypePtr r_value);
+typedef uint32_t (*GDNativePtrKeyedChecker)(const GDNativeVariantPtr p_base, const GDNativeVariantPtr p_key);
+typedef void (*GDNativePtrUtilityFunction)(GDNativeTypePtr r_return, const GDNativeTypePtr *p_arguments, int p_argument_count);
+
+typedef GDNativeObjectPtr (*GDNativeClassConstructor)();
+
+/* EXTENSION CLASSES */
+
+typedef void *GDExtensionClassInstancePtr;
+
+typedef GDNativeBool (*GDNativeExtensionClassSet)(GDExtensionClassInstancePtr p_instance, const GDNativeStringNamePtr p_name, const GDNativeVariantPtr p_value);
+typedef GDNativeBool (*GDNativeExtensionClassGet)(GDExtensionClassInstancePtr p_instance, const GDNativeStringNamePtr p_name, GDNativeVariantPtr r_ret);
+
+typedef struct {
+ uint32_t type;
+ const char *name;
+ const char *class_name;
+ uint32_t hint;
+ const char *hint_string;
+ uint32_t usage;
+} GDNativePropertyInfo;
+
+typedef const GDNativePropertyInfo *(*GDNativeExtensionClassGetPropertyList)(GDExtensionClassInstancePtr p_instance, uint32_t *r_count);
+typedef void (*GDNativeExtensionClassFreePropertyList)(GDExtensionClassInstancePtr p_instance, const GDNativePropertyInfo *p_list);
+typedef void (*GDNativeExtensionClassNotification)(GDExtensionClassInstancePtr p_instance, int32_t p_what);
+typedef const char *(*GDNativeExtensionClassToString)(GDExtensionClassInstancePtr p_instance);
+typedef void (*GDNativeExtensionClassReference)(GDExtensionClassInstancePtr p_instance);
+typedef void (*GDNativeExtensionClassUnreference)(GDExtensionClassInstancePtr p_instance);
+typedef void (*GDNativeExtensionClassCallVirtual)(GDExtensionClassInstancePtr p_instance, const GDNativeTypePtr *p_args, GDNativeTypePtr r_ret);
+typedef GDExtensionClassInstancePtr (*GDNativeExtensionClassCreateInstance)(void *p_userdata);
+typedef void (*GDNativeExtensionClassFreeInstance)(void *p_userdata, GDExtensionClassInstancePtr p_instance);
+typedef GDNativeExtensionClassCallVirtual (*GDNativeExtensionClassGetVirtual)(void *p_userdata, const char *p_name);
+
+typedef struct {
+ GDNativeExtensionClassSet set_func;
+ GDNativeExtensionClassGet get_func;
+ GDNativeExtensionClassGetPropertyList get_property_list_func;
+ GDNativeExtensionClassFreePropertyList free_property_list_func;
+ GDNativeExtensionClassNotification notification_func;
+ GDNativeExtensionClassToString to_string_func;
+ GDNativeExtensionClassReference reference_func;
+ GDNativeExtensionClassUnreference unreference_func;
+ GDNativeExtensionClassCreateInstance create_instance_func; /* this one is mandatory */
+ GDNativeExtensionClassFreeInstance free_instance_func; /* this one is mandatory */
+ GDNativeExtensionClassGetVirtual get_firtual_func;
+ void *class_userdata;
+} GDNativeExtensionClassCreationInfo;
+
+typedef void *GDNativeExtensionClassLibraryPtr;
+
+typedef const GDNativePropertyInfo *(*GDNativeExtensionClassGetPropertyList)(GDExtensionClassInstancePtr p_instance, uint32_t *r_count);
+
+/* Method */
+
+typedef enum {
+ GDNATIVE_EXTENSION_METHOD_FLAG_NORMAL = 1,
+ GDNATIVE_EXTENSION_METHOD_FLAG_EDITOR = 2,
+ GDNATIVE_EXTENSION_METHOD_FLAG_NOSCRIPT = 4,
+ GDNATIVE_EXTENSION_METHOD_FLAG_CONST = 8,
+ GDNATIVE_EXTENSION_METHOD_FLAG_REVERSE = 16, /* used for events */
+ GDNATIVE_EXTENSION_METHOD_FLAG_VIRTUAL = 32,
+ GDNATIVE_EXTENSION_METHOD_FLAG_FROM_SCRIPT = 64,
+ GDNATIVE_EXTENSION_METHOD_FLAG_VARARG = 128,
+ GDNATIVE_EXTENSION_METHOD_FLAG_STATIC = 256,
+ GDNATIVE_EXTENSION_METHOD_FLAGS_DEFAULT = GDNATIVE_EXTENSION_METHOD_FLAG_NORMAL,
+} GDNativeExtensionClassMethodFlags;
+
+typedef enum {
+ GDNATIVE_EXTENSION_METHOD_ARGUMENT_METADATA_NONE,
+ GDNATIVE_EXTENSION_METHOD_ARGUMENT_METADATA_INT_IS_INT8,
+ GDNATIVE_EXTENSION_METHOD_ARGUMENT_METADATA_INT_IS_INT16,
+ GDNATIVE_EXTENSION_METHOD_ARGUMENT_METADATA_INT_IS_INT32,
+ GDNATIVE_EXTENSION_METHOD_ARGUMENT_METADATA_INT_IS_INT64,
+ GDNATIVE_EXTENSION_METHOD_ARGUMENT_METADATA_INT_IS_UINT8,
+ GDNATIVE_EXTENSION_METHOD_ARGUMENT_METADATA_INT_IS_UINT16,
+ GDNATIVE_EXTENSION_METHOD_ARGUMENT_METADATA_INT_IS_UINT32,
+ GDNATIVE_EXTENSION_METHOD_ARGUMENT_METADATA_INT_IS_UINT64,
+ GDNATIVE_EXTENSION_METHOD_ARGUMENT_METADATA_REAL_IS_FLOAT,
+ GDNATIVE_EXTENSION_METHOD_ARGUMENT_METADATA_REAL_IS_DOUBLE
+} GDNativeExtensionClassMethodArgumentMetadata;
+
+typedef void (*GDNativeExtensionClassMethodCall)(GDExtensionClassInstancePtr p_instance, const GDNativeVariantPtr *p_args, const GDNativeInt p_argument_count, GDNativeVariantPtr r_return, GDNativeCallError *r_error);
+typedef void (*GDNativeExtensionClassMethodPtrCall)(GDExtensionClassInstancePtr p_instance, const GDNativeTypePtr *p_args, GDNativeTypePtr r_ret);
+
+/* passing -1 as argument in the following functions refers to the return type */
+typedef GDNativeVariantType (*GDNativeExtensionClassMethodGetArgumentType)(void *p_method_userdata, int32_t p_argument);
+typedef void (*GDNativeExtensionClassMethodGetArgumentInfo)(void *p_method_userdata, int32_t p_argument, GDNativePropertyInfo *r_info);
+typedef GDNativeExtensionClassMethodArgumentMetadata (*GDNativeExtensionClassMethodGetArgumentMetadata)(void *p_method_userdata, int32_t p_argument);
+
+typedef struct {
+ const char *name;
+ void *method_userdata;
+ GDNativeExtensionClassMethodCall call_func;
+ GDNativeExtensionClassMethodPtrCall ptrcall_func;
+ uint32_t method_flags; /* GDNativeExtensionClassMethodFlags */
+ uint32_t argument_count;
+ GDNativeBool has_return_value;
+ GDNativeExtensionClassMethodGetArgumentType get_argument_type_func;
+ GDNativeExtensionClassMethodGetArgumentInfo get_argument_info_func; /* name and hint information for the argument can be omitted in release builds. Class name should always be present if it applies. */
+ GDNativeExtensionClassMethodGetArgumentMetadata get_argument_metadata_func;
+ uint32_t default_argument_count;
+ GDNativeVariantPtr *default_arguments;
+} GDNativeExtensionClassMethodInfo;
+
+/* INTERFACE */
+
+typedef struct {
+ uint32_t version_major;
+ uint32_t version_minor;
+ uint32_t version_patch;
+ const char *version_string;
+
+ /* GODOT CORE */
+ void *(*mem_alloc)(size_t p_bytes);
+ void *(*mem_realloc)(void *p_ptr, size_t p_bytes);
+ void (*mem_free)(void *p_ptr);
+
+ void (*print_error)(const char *p_description, const char *p_function, const char *p_file, int32_t p_line);
+ void (*print_warning)(const char *p_description, const char *p_function, const char *p_file, int32_t p_line);
+ void (*print_script_error)(const char *p_description, const char *p_function, const char *p_file, int32_t p_line);
+
+ /* GODOT VARIANT */
+
+ /* variant general */
+ void (*variant_new_copy)(GDNativeVariantPtr r_dest, const GDNativeVariantPtr p_src);
+ void (*variant_new_nil)(GDNativeVariantPtr r_dest);
+ void (*variant_destroy)(GDNativeVariantPtr p_self);
+
+ /* variant type */
+ void (*variant_call)(GDNativeVariantPtr p_self, const GDNativeStringNamePtr p_method, const GDNativeVariantPtr *p_args, const GDNativeInt p_argument_count, GDNativeVariantPtr r_return, GDNativeCallError *r_error);
+ void (*variant_call_static)(GDNativeVariantType p_type, const GDNativeStringNamePtr p_method, const GDNativeVariantPtr *p_args, const GDNativeInt p_argument_count, GDNativeVariantPtr r_return, GDNativeCallError *r_error);
+ void (*variant_evaluate)(GDNativeVariantOperator p_op, const GDNativeVariantPtr p_a, const GDNativeVariantPtr p_b, GDNativeVariantPtr r_return, GDNativeBool *r_valid);
+ void (*variant_set)(GDNativeVariantPtr p_self, const GDNativeVariantPtr p_key, const GDNativeVariantPtr p_value, GDNativeBool *r_valid);
+ void (*variant_set_named)(GDNativeVariantPtr p_self, const GDNativeStringNamePtr p_key, const GDNativeVariantPtr p_value, GDNativeBool *r_valid);
+ void (*variant_set_keyed)(GDNativeVariantPtr p_self, const GDNativeVariantPtr p_key, const GDNativeVariantPtr p_value, GDNativeBool *r_valid);
+ void (*variant_set_indexed)(GDNativeVariantPtr p_self, GDNativeInt p_index, const GDNativeVariantPtr p_value, GDNativeBool *r_valid, GDNativeBool *r_oob);
+ void (*variant_get)(const GDNativeVariantPtr p_self, const GDNativeVariantPtr p_key, GDNativeVariantPtr r_ret, GDNativeBool *r_valid);
+ void (*variant_get_named)(const GDNativeVariantPtr p_self, const GDNativeStringNamePtr p_key, GDNativeVariantPtr r_ret, GDNativeBool *r_valid);
+ void (*variant_get_keyed)(const GDNativeVariantPtr p_self, const GDNativeVariantPtr p_key, GDNativeVariantPtr r_ret, GDNativeBool *r_valid);
+ void (*variant_get_indexed)(const GDNativeVariantPtr p_self, GDNativeInt p_index, GDNativeVariantPtr r_ret, GDNativeBool *r_valid, GDNativeBool *r_oob);
+ GDNativeBool (*variant_iter_init)(const GDNativeVariantPtr p_self, GDNativeVariantPtr r_iter, GDNativeBool *r_valid);
+ GDNativeBool (*variant_iter_next)(const GDNativeVariantPtr p_self, GDNativeVariantPtr r_iter, GDNativeBool *r_valid);
+ void (*variant_iter_get)(const GDNativeVariantPtr p_self, GDNativeVariantPtr r_iter, GDNativeVariantPtr r_ret, GDNativeBool *r_valid);
+ GDNativeBool (*variant_hash_compare)(const GDNativeVariantPtr p_self, const GDNativeVariantPtr p_other);
+ GDNativeBool (*variant_booleanize)(const GDNativeVariantPtr p_self);
+ void (*variant_blend)(const GDNativeVariantPtr p_a, const GDNativeVariantPtr p_b, float p_c, GDNativeVariantPtr r_dst);
+ void (*variant_interpolate)(const GDNativeVariantPtr p_a, const GDNativeVariantPtr p_b, float p_c, GDNativeVariantPtr r_dst);
+ void (*variant_duplicate)(const GDNativeVariantPtr p_self, GDNativeVariantPtr r_ret, GDNativeBool p_deep);
+ void (*variant_stringify)(const GDNativeVariantPtr p_self, GDNativeStringPtr r_ret);
+
+ GDNativeVariantType (*variant_get_type)(const GDNativeVariantPtr p_self);
+ GDNativeBool (*variant_has_method)(const GDNativeVariantPtr p_self, const GDNativeStringNamePtr p_method);
+ GDNativeBool (*variant_has_member)(GDNativeVariantType p_type, const GDNativeStringNamePtr p_member);
+ GDNativeBool (*variant_has_key)(const GDNativeVariantPtr p_self, const GDNativeVariantPtr p_key, GDNativeBool *r_valid);
+ void (*variant_get_type_name)(GDNativeVariantType p_type, GDNativeStringPtr r_name);
+ GDNativeBool (*variant_can_convert)(GDNativeVariantType p_from, GDNativeVariantType p_to);
+ GDNativeBool (*variant_can_convert_strict)(GDNativeVariantType p_from, GDNativeVariantType p_to);
+
+ /* ptrcalls */
+ GDNativeVariantFromTypeConstructorFunc (*get_variant_from_type_constructor)(GDNativeVariantType p_type);
+ GDNativeTypeFromVariantConstructorFunc (*get_variant_to_type_constructor)(GDNativeVariantType p_type);
+ GDNativePtrOperatorEvaluator (*variant_get_ptr_operator_evaluator)(GDNativeVariantOperator p_operator, GDNativeVariantType p_type_a, GDNativeVariantType p_type_b);
+ GDNativePtrBuiltInMethod (*variant_get_ptr_builtin_method)(GDNativeVariantType p_type, const char *p_method, GDNativeInt p_hash);
+ GDNativePtrConstructor (*variant_get_ptr_constructor)(GDNativeVariantType p_type, int32_t p_constructor);
+ void (*variant_construct)(GDNativeVariantType p_type, GDNativeVariantPtr p_base, const GDNativeVariantPtr *p_args, int32_t p_argument_count, GDNativeCallError *r_error);
+ GDNativePtrSetter (*variant_get_ptr_setter)(GDNativeVariantType p_type, const char *p_member);
+ GDNativePtrGetter (*variant_get_ptr_getter)(GDNativeVariantType p_type, const char *p_member);
+ GDNativePtrIndexedSetter (*variant_get_ptr_indexed_setter)(GDNativeVariantType p_type);
+ GDNativePtrIndexedGetter (*variant_get_ptr_indexed_getter)(GDNativeVariantType p_type);
+ GDNativePtrKeyedSetter (*variant_get_ptr_keyed_setter)(GDNativeVariantType p_type);
+ GDNativePtrKeyedGetter (*variant_get_ptr_keyed_getter)(GDNativeVariantType p_type);
+ GDNativePtrKeyedChecker (*variant_get_ptr_keyed_checker)(GDNativeVariantType p_type);
+ void (*variant_get_constant_value)(GDNativeVariantType p_type, const char *p_constant, GDNativeVariantPtr r_ret);
+ GDNativePtrUtilityFunction (*variant_get_ptr_utility_function)(const char *p_function, GDNativeInt p_hash);
+
+ /* extra utilities */
+
+ void (*string_new_with_latin1_chars)(GDNativeStringPtr r_dest, const char *p_contents);
+ void (*string_new_with_utf8_chars)(GDNativeStringPtr r_dest, const char *p_contents);
+ void (*string_new_with_utf16_chars)(GDNativeStringPtr r_dest, const char16_t *p_contents);
+ void (*string_new_with_utf32_chars)(GDNativeStringPtr r_dest, const char32_t *p_contents);
+ void (*string_new_with_wide_chars)(GDNativeStringPtr r_dest, const wchar_t *p_contents);
+ void (*string_new_with_latin1_chars_and_len)(GDNativeStringPtr r_dest, const char *p_contents, const GDNativeInt p_size);
+ void (*string_new_with_utf8_chars_and_len)(GDNativeStringPtr r_dest, const char *p_contents, const GDNativeInt p_size);
+ void (*string_new_with_utf16_chars_and_len)(GDNativeStringPtr r_dest, const char16_t *p_contents, const GDNativeInt p_size);
+ void (*string_new_with_utf32_chars_and_len)(GDNativeStringPtr r_dest, const char32_t *p_contents, const GDNativeInt p_size);
+ void (*string_new_with_wide_chars_and_len)(GDNativeStringPtr r_dest, const wchar_t *p_contents, const GDNativeInt p_size);
+ /* Information about the following functions:
+ * - The return value is the resulting encoded string length.
+ * - The length returned is in characters, not in bytes. It also does not include a trailing zero.
+ * - These functions also do not write trailing zero, If you need it, write it yourself at the position indicated by the length (and make sure to allocate it).
+ * - Passing NULL in r_text means only the length is computed (again, without including trailing zero).
+ * - p_max_write_length argument is in characters, not bytes. It will be ignored if r_text is NULL.
+ * - p_max_write_length argument does not affect the return value, it's only to cap write length.
+ */
+ GDNativeInt (*string_to_latin1_chars)(const GDNativeStringPtr p_self, char *r_text, GDNativeInt p_max_write_length);
+ GDNativeInt (*string_to_utf8_chars)(const GDNativeStringPtr p_self, char *r_text, GDNativeInt p_max_write_length);
+ GDNativeInt (*string_to_utf16_chars)(const GDNativeStringPtr p_self, char16_t *r_text, GDNativeInt p_max_write_length);
+ GDNativeInt (*string_to_utf32_chars)(const GDNativeStringPtr p_self, char32_t *r_text, GDNativeInt p_max_write_length);
+ GDNativeInt (*string_to_wide_chars)(const GDNativeStringPtr p_self, wchar_t *r_text, GDNativeInt p_max_write_length);
+ char32_t *(*string_operator_index)(GDNativeStringPtr p_self, GDNativeInt p_index);
+ const char32_t *(*string_operator_index_const)(const GDNativeStringPtr p_self, GDNativeInt p_index);
+
+ /* OBJECT */
+
+ void (*object_method_bind_ptrcall)(GDNativeMethodBindPtr p_method_bind, GDNativeObjectPtr p_instance, const GDNativeTypePtr *p_args, GDNativeTypePtr r_ret);
+ void (*object_destroy)(GDNativeObjectPtr p_o);
+ GDNativeObjectPtr (*global_get_singleton)(const char *p_name);
+
+ GDNativeObjectPtr (*object_cast_to)(const GDNativeObjectPtr p_object, void *p_class_tag);
+ GDNativeObjectPtr (*object_get_instance_from_id)(GDObjectInstanceID p_instance_id);
+ GDObjectInstanceID (*object_get_instance_id)(const GDNativeObjectPtr p_object);
+
+ /* CLASSDB */
+
+ GDNativeClassConstructor (*classdb_get_constructor)(const char *p_classname);
+ GDNativeMethodBindPtr (*classdb_get_method_bind)(const char *p_classname, const char *p_methodname, GDNativeInt p_hash);
+ void *(*classdb_get_class_tag)(const char *p_classname);
+
+ /* CLASSDB EXTENSION */
+
+ void (*classdb_register_extension_class)(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name, const char *p_parent_class_name, const GDNativeExtensionClassCreationInfo *p_extension_funcs);
+ void (*classdb_register_extension_class_method)(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name, const GDNativeExtensionClassMethodInfo *p_method_info);
+ void (*classdb_register_extension_class_integer_constant)(const GDNativeExtensionClassLibraryPtr p_library, const char *p_enum_name, const char *p_class_name, const char *p_constant_name, uint32_t p_constant_value);
+ void (*classdb_register_extension_class_property)(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name, const GDNativePropertyInfo *p_info, const char *p_setter, const char *p_getter);
+ void (*classdb_register_extension_class_signal)(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name, const char *p_signal_name, const GDNativePropertyInfo *p_argument_info, GDNativeInt p_argument_count);
+ void (*classdb_unregister_extension_class)(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name); /* Unregistering a parent class before a class that inherits it will result in failure. Inheritors must be unregistered first. */
+} GDNativeInterface;
+
+/* INITIALIZATION */
+
+typedef enum {
+ GDNATIVE_INITIALIZATION_CORE,
+ GDNATIVE_INITIALIZATION_SERVERS,
+ GDNATIVE_INITIALIZATION_SCENE,
+ GDNATIVE_INITIALIZATION_EDITOR,
+} GDNativeInitializationLevel;
+
+typedef struct {
+ /* Minimum initialization level required.
+ * If Core or Servers, the extension needs editor or game restart to take effect */
+ GDNativeInitializationLevel minimum_initialization_level;
+ /* Up to the user to supply when initializing */
+ void *userdata;
+ /* This function will be called multiple times for each initialization level. */
+ void (*initialize)(void *userdata, GDNativeInitializationLevel p_level);
+ void (*deinitialize)(void *userdata, GDNativeInitializationLevel p_level);
+} GDNativeInitialization;
+
+/* Define a C function prototype that implements the function below and expose it to dlopen() (or similar).
+ * It will be called on initialization. The name must be an unique one specified in the .gdextension config file.
+ */
+
+typedef GDNativeBool (*GDNativeInitializationFunction)(const GDNativeInterface *p_interface, const GDNativeExtensionClassLibraryPtr p_library, GDNativeInitialization *r_initialization);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/core/extension/native_extension.cpp b/core/extension/native_extension.cpp
new file mode 100644
index 0000000000..65718a7507
--- /dev/null
+++ b/core/extension/native_extension.cpp
@@ -0,0 +1,411 @@
+/*************************************************************************/
+/* native_extension.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "native_extension.h"
+#include "core/io/config_file.h"
+#include "core/object/class_db.h"
+#include "core/object/method_bind.h"
+#include "core/os/os.h"
+
+class NativeExtensionMethodBind : public MethodBind {
+ GDNativeExtensionClassMethodCall call_func;
+ GDNativeExtensionClassMethodPtrCall ptrcall_func;
+ GDNativeExtensionClassMethodGetArgumentType get_argument_type_func;
+ GDNativeExtensionClassMethodGetArgumentInfo get_argument_info_func;
+ GDNativeExtensionClassMethodGetArgumentMetadata get_argument_metadata_func;
+ void *method_userdata;
+ bool vararg;
+
+protected:
+ virtual Variant::Type _gen_argument_type(int p_arg) const {
+ return Variant::Type(get_argument_type_func(method_userdata, p_arg));
+ }
+ virtual PropertyInfo _gen_argument_type_info(int p_arg) const {
+ GDNativePropertyInfo pinfo;
+ get_argument_info_func(method_userdata, p_arg, &pinfo);
+ PropertyInfo ret;
+ ret.type = Variant::Type(pinfo.type);
+ ret.name = pinfo.name;
+ ret.class_name = pinfo.class_name;
+ ret.hint = PropertyHint(pinfo.hint);
+ ret.usage = pinfo.usage;
+ ret.class_name = pinfo.class_name;
+ return ret;
+ }
+
+public:
+ virtual GodotTypeInfo::Metadata get_argument_meta(int p_arg) const {
+ return GodotTypeInfo::Metadata(get_argument_metadata_func(method_userdata, p_arg));
+ }
+
+ virtual Variant call(Object *p_object, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
+ Variant ret;
+ GDExtensionClassInstancePtr extension_instance = p_object->_get_extension_instance();
+ GDNativeCallError ce;
+ call_func(extension_instance, (const GDNativeVariantPtr *)p_args, p_arg_count, (GDNativeVariantPtr)&ret, &ce);
+ r_error.error = Callable::CallError::Error(ce.error);
+ r_error.argument = ce.argument;
+ r_error.expected = ce.expected;
+ return ret;
+ }
+ virtual void ptrcall(Object *p_object, const void **p_args, void *r_ret) {
+ ERR_FAIL_COND_MSG(vararg, "Vararg methods don't have ptrcall support. This is most likely an engine bug.");
+ GDExtensionClassInstancePtr extension_instance = p_object->_get_extension_instance();
+ ptrcall_func(extension_instance, (const GDNativeTypePtr *)p_args, (GDNativeTypePtr)r_ret);
+ }
+
+ virtual bool is_vararg() const {
+ return false;
+ }
+ NativeExtensionMethodBind(const GDNativeExtensionClassMethodInfo *p_method_info) {
+ method_userdata = p_method_info->method_userdata;
+ call_func = p_method_info->call_func;
+ ptrcall_func = p_method_info->ptrcall_func;
+ get_argument_type_func = p_method_info->get_argument_type_func;
+ get_argument_info_func = p_method_info->get_argument_info_func;
+ get_argument_metadata_func = p_method_info->get_argument_metadata_func;
+
+ vararg = p_method_info->method_flags & GDNATIVE_EXTENSION_METHOD_FLAG_VARARG;
+
+ _set_returns(p_method_info->has_return_value);
+ _set_const(p_method_info->method_flags & GDNATIVE_EXTENSION_METHOD_FLAG_CONST);
+#ifdef DEBUG_METHODS_ENABLED
+ _generate_argument_types(p_method_info->argument_count);
+#endif
+ set_argument_count(p_method_info->argument_count);
+ }
+};
+
+static GDNativeInterface gdnative_interface;
+
+void NativeExtension::_register_extension_class(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name, const char *p_parent_class_name, const GDNativeExtensionClassCreationInfo *p_extension_funcs) {
+ NativeExtension *self = (NativeExtension *)p_library;
+
+ StringName class_name = p_class_name;
+ ERR_FAIL_COND_MSG(String(class_name).is_valid_identifier(), "Attempt to register extension clas '" + class_name + "', which is not a valid class identifier.");
+ ERR_FAIL_COND_MSG(ClassDB::class_exists(class_name), "Attempt to register extension class '" + class_name + "', which appears to be already registered.");
+
+ Extension *parent_extension = nullptr;
+ StringName parent_class_name = p_parent_class_name;
+
+ if (self->extension_classes.has(parent_class_name)) {
+ parent_extension = &self->extension_classes[parent_class_name];
+ } else if (ClassDB::class_exists(parent_class_name)) {
+ if (ClassDB::get_api_type(parent_class_name) == ClassDB::API_EXTENSION || ClassDB::get_api_type(parent_class_name) == ClassDB::API_EDITOR_EXTENSION) {
+ ERR_PRINT("Unimplemented yet");
+ //inheriting from another extension
+ } else {
+ //inheriting from engine class
+ }
+ } else {
+ ERR_FAIL_MSG("Attempt to register an extension class '" + String(class_name) + "' using non-existing parent class '" + String(parent_class_name) + "'");
+ }
+
+ self->extension_classes[class_name] = Extension();
+
+ Extension *extension = &self->extension_classes[class_name];
+
+ if (parent_extension) {
+ extension->native_extension.parent = &parent_extension->native_extension;
+ parent_extension->native_extension.children.push_back(&extension->native_extension);
+ }
+
+ extension->native_extension.parent_class_name = parent_class_name;
+ extension->native_extension.class_name = class_name;
+ extension->native_extension.editor_class = self->level_initialized == INITIALIZATION_LEVEL_EDITOR;
+ extension->native_extension.set = p_extension_funcs->set_func;
+ extension->native_extension.get = p_extension_funcs->get_func;
+ extension->native_extension.get_property_list = p_extension_funcs->get_property_list_func;
+ extension->native_extension.free_property_list = p_extension_funcs->free_property_list_func;
+ extension->native_extension.notification = p_extension_funcs->notification_func;
+ extension->native_extension.to_string = p_extension_funcs->to_string_func;
+ extension->native_extension.reference = p_extension_funcs->reference_func;
+ extension->native_extension.unreference = p_extension_funcs->unreference_func;
+ extension->native_extension.class_userdata = p_extension_funcs->class_userdata;
+ extension->native_extension.create_instance = p_extension_funcs->create_instance_func;
+ extension->native_extension.free_instance = p_extension_funcs->free_instance_func;
+
+ ClassDB::register_extension_class(&extension->native_extension);
+}
+void NativeExtension::_register_extension_class_method(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name, const GDNativeExtensionClassMethodInfo *p_method_info) {
+ NativeExtension *self = (NativeExtension *)p_library;
+
+ StringName class_name = p_class_name;
+ StringName method_name = p_method_info->name;
+ ERR_FAIL_COND_MSG(self->extension_classes.has(class_name), "Attempt to register extension method '" + String(method_name) + "' for unexisting class '" + class_name + "'.");
+
+ //Extension *extension = &self->extension_classes[class_name];
+
+ NativeExtensionMethodBind *method = memnew(NativeExtensionMethodBind(p_method_info));
+
+ ClassDB::bind_method_custom(class_name, method);
+}
+void NativeExtension::_register_extension_class_integer_constant(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name, const char *p_enum_name, const char *p_constant_name, uint32_t p_constant_value) {
+ NativeExtension *self = (NativeExtension *)p_library;
+
+ StringName class_name = p_class_name;
+ ERR_FAIL_COND_MSG(self->extension_classes.has(class_name), "Attempt to register extension constant '" + String(p_constant_name) + "' for unexisting class '" + class_name + "'.");
+
+ //Extension *extension = &self->extension_classes[class_name];
+
+ ClassDB::bind_integer_constant(class_name, p_enum_name, p_constant_name, p_constant_value);
+}
+void NativeExtension::_register_extension_class_property(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name, const GDNativePropertyInfo *p_info, const char *p_setter, const char *p_getter) {
+ NativeExtension *self = (NativeExtension *)p_library;
+
+ StringName class_name = p_class_name;
+ ERR_FAIL_COND_MSG(self->extension_classes.has(class_name), "Attempt to register extension class property '" + String(p_info->name) + "' for unexisting class '" + class_name + "'.");
+
+ //Extension *extension = &self->extension_classes[class_name];
+ PropertyInfo pinfo;
+ pinfo.type = Variant::Type(p_info->type);
+ pinfo.name = p_info->name;
+ pinfo.class_name = p_info->class_name;
+ pinfo.hint = PropertyHint(p_info->hint);
+ pinfo.hint_string = p_info->hint_string;
+ pinfo.usage = p_info->usage;
+
+ ClassDB::add_property(class_name, pinfo, p_setter, p_getter);
+}
+
+void NativeExtension::_register_extension_class_signal(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name, const char *p_signal_name, const GDNativePropertyInfo *p_argument_info, GDNativeInt p_argument_count) {
+ NativeExtension *self = (NativeExtension *)p_library;
+
+ StringName class_name = p_class_name;
+ ERR_FAIL_COND_MSG(self->extension_classes.has(class_name), "Attempt to register extension class signal '" + String(p_signal_name) + "' for unexisting class '" + class_name + "'.");
+
+ MethodInfo s;
+ s.name = p_signal_name;
+ for (int i = 0; i < p_argument_count; i++) {
+ PropertyInfo arg;
+ arg.type = Variant::Type(p_argument_info[i].type);
+ arg.name = p_argument_info[i].name;
+ arg.class_name = p_argument_info[i].class_name;
+ arg.hint = PropertyHint(p_argument_info[i].hint);
+ arg.hint_string = p_argument_info[i].hint_string;
+ arg.usage = p_argument_info[i].usage;
+ s.arguments.push_back(arg);
+ }
+ ClassDB::add_signal(class_name, s);
+}
+
+void NativeExtension::_unregister_extension_class(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name) {
+ NativeExtension *self = (NativeExtension *)p_library;
+
+ StringName class_name = p_class_name;
+ ERR_FAIL_COND_MSG(self->extension_classes.has(class_name), "Attempt to unregister unexisting extension class '" + class_name + "'.");
+ Extension *ext = &self->extension_classes[class_name];
+ ERR_FAIL_COND_MSG(ext->native_extension.children.size(), "Attempt to unregister class '" + class_name + "' while other extension classes inherit from it.");
+
+ ClassDB::unregister_extension_class(class_name);
+ if (ext->native_extension.parent != nullptr) {
+ ext->native_extension.parent->children.erase(&ext->native_extension);
+ }
+ self->extension_classes.erase(class_name);
+}
+
+Error NativeExtension::open_library(const String &p_path, const String &p_entry_symbol) {
+ Error err = OS::get_singleton()->open_dynamic_library(p_path, library, true);
+ if (err != OK) {
+ return err;
+ }
+
+ void *entry_funcptr = nullptr;
+
+ err = OS::get_singleton()->get_dynamic_library_symbol_handle(library, p_entry_symbol, entry_funcptr, false);
+
+ if (err != OK) {
+ OS::get_singleton()->close_dynamic_library(library);
+ return err;
+ }
+
+ GDNativeInitializationFunction initialization_function = (GDNativeInitializationFunction)entry_funcptr;
+
+ initialization_function(&gdnative_interface, this, &initialization);
+ level_initialized = -1;
+ return OK;
+}
+
+void NativeExtension::close_library() {
+ ERR_FAIL_COND(library == nullptr);
+ OS::get_singleton()->close_dynamic_library(library);
+
+ library = nullptr;
+}
+
+bool NativeExtension::is_library_open() const {
+ return library != nullptr;
+}
+
+NativeExtension::InitializationLevel NativeExtension::get_minimum_library_initialization_level() const {
+ ERR_FAIL_COND_V(library == nullptr, INITIALIZATION_LEVEL_CORE);
+ return InitializationLevel(initialization.minimum_initialization_level);
+}
+void NativeExtension::initialize_library(InitializationLevel p_level) {
+ ERR_FAIL_COND(library == nullptr);
+ ERR_FAIL_COND(p_level <= int32_t(level_initialized));
+
+ level_initialized = int32_t(p_level);
+
+ ERR_FAIL_COND(initialization.initialize == nullptr);
+
+ initialization.initialize(initialization.userdata, GDNativeInitializationLevel(p_level));
+}
+void NativeExtension::deinitialize_library(InitializationLevel p_level) {
+ ERR_FAIL_COND(library == nullptr);
+ ERR_FAIL_COND(p_level > int32_t(level_initialized));
+
+ level_initialized = int32_t(p_level) - 1;
+ initialization.deinitialize(initialization.userdata, GDNativeInitializationLevel(p_level));
+}
+
+void NativeExtension::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("open_library", "path", "entry_symbol"), &NativeExtension::open_library);
+ ClassDB::bind_method(D_METHOD("close_library"), &NativeExtension::close_library);
+ ClassDB::bind_method(D_METHOD("is_library_open"), &NativeExtension::is_library_open);
+
+ ClassDB::bind_method(D_METHOD("get_minimum_library_initialization_level"), &NativeExtension::get_minimum_library_initialization_level);
+ ClassDB::bind_method(D_METHOD("initialize_library", "level"), &NativeExtension::initialize_library);
+
+ BIND_ENUM_CONSTANT(INITIALIZATION_LEVEL_CORE);
+ BIND_ENUM_CONSTANT(INITIALIZATION_LEVEL_SERVERS);
+ BIND_ENUM_CONSTANT(INITIALIZATION_LEVEL_SCENE);
+ BIND_ENUM_CONSTANT(INITIALIZATION_LEVEL_EDITOR);
+}
+
+NativeExtension::NativeExtension() {
+}
+
+NativeExtension::~NativeExtension() {
+ if (library != nullptr) {
+ close_library();
+ }
+}
+
+extern void gdnative_setup_interface(GDNativeInterface *p_interface);
+
+void NativeExtension::initialize_native_extensions() {
+ gdnative_setup_interface(&gdnative_interface);
+
+ gdnative_interface.classdb_register_extension_class = _register_extension_class;
+ gdnative_interface.classdb_register_extension_class_method = _register_extension_class_method;
+ gdnative_interface.classdb_register_extension_class_integer_constant = _register_extension_class_integer_constant;
+ gdnative_interface.classdb_register_extension_class_property = _register_extension_class_property;
+ gdnative_interface.classdb_register_extension_class_signal = _register_extension_class_signal;
+ gdnative_interface.classdb_unregister_extension_class = _unregister_extension_class;
+}
+
+RES NativeExtensionResourceLoader::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) {
+ Ref<ConfigFile> config;
+ config.instantiate();
+
+ Error err = config->load(p_path);
+
+ if (r_error) {
+ *r_error = err;
+ }
+
+ if (err != OK) {
+ return RES();
+ }
+
+ if (!config->has_section_key("configuration", "entry_symbol")) {
+ if (r_error) {
+ *r_error = ERR_INVALID_DATA;
+ }
+ return RES();
+ }
+
+ String entry_symbol = config->get_value("configuration", "entry_symbol");
+
+ List<String> libraries;
+
+ config->get_section_keys("libraries", &libraries);
+
+ String library_path;
+
+ for (List<String>::Element *E = libraries.front(); E; E = E->next()) {
+ Vector<String> tags = E->get().split(".");
+ bool all_tags_met = true;
+ for (int i = 0; i < tags.size(); i++) {
+ String tag = tags[i].strip_edges();
+ if (!OS::get_singleton()->has_feature(tag)) {
+ all_tags_met = false;
+ break;
+ }
+ }
+
+ if (all_tags_met) {
+ library_path = config->get_value("libraries", E->get());
+ break;
+ }
+ }
+
+ if (library_path != String()) {
+ if (r_error) {
+ *r_error = ERR_FILE_NOT_FOUND;
+ }
+ return RES();
+ }
+
+ if (!library_path.is_resource_file()) {
+ library_path = p_path.get_base_dir().plus_file(library_path);
+ }
+
+ Ref<NativeExtension> lib;
+ lib.instantiate();
+ err = lib->open_library(library_path, entry_symbol);
+
+ if (r_error) {
+ *r_error = err;
+ }
+
+ if (err != OK) {
+ return RES();
+ }
+
+ return lib;
+}
+
+void NativeExtensionResourceLoader::get_recognized_extensions(List<String> *p_extensions) const {
+ p_extensions->push_back("gdextension");
+}
+
+bool NativeExtensionResourceLoader::handles_type(const String &p_type) const {
+ return p_type == "NativeExtension";
+}
+
+String NativeExtensionResourceLoader::get_resource_type(const String &p_path) const {
+ String el = p_path.get_extension().to_lower();
+ if (el == "gdextension") {
+ return "NativeExtension";
+ }
+ return "";
+}
diff --git a/core/extension/native_extension.h b/core/extension/native_extension.h
new file mode 100644
index 0000000000..0a23848eb2
--- /dev/null
+++ b/core/extension/native_extension.h
@@ -0,0 +1,94 @@
+/*************************************************************************/
+/* native_extension.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef NATIVE_EXTENSION_H
+#define NATIVE_EXTENSION_H
+
+#include "core/extension/gdnative_interface.h"
+#include "core/io/resource_loader.h"
+#include "core/object/ref_counted.h"
+
+class NativeExtension : public RefCounted {
+ GDCLASS(NativeExtension, RefCounted)
+
+ void *library = nullptr; // pointer if valid,
+
+ struct Extension {
+ ObjectNativeExtension native_extension;
+ };
+
+ Map<StringName, Extension> extension_classes;
+
+ static void _register_extension_class(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name, const char *p_parent_class_name, const GDNativeExtensionClassCreationInfo *p_extension_funcs);
+ static void _register_extension_class_method(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name, const GDNativeExtensionClassMethodInfo *p_method_info);
+ static void _register_extension_class_integer_constant(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name, const char *p_enum_name, const char *p_constant_name, uint32_t p_constant_value);
+ static void _register_extension_class_property(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name, const GDNativePropertyInfo *p_info, const char *p_setter, const char *p_getter);
+ static void _register_extension_class_signal(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name, const char *p_signal_name, const GDNativePropertyInfo *p_argument_info, GDNativeInt p_argument_count);
+ static void _unregister_extension_class(const GDNativeExtensionClassLibraryPtr p_library, const char *p_class_name);
+
+ GDNativeInitialization initialization;
+ int32_t level_initialized = -1;
+
+protected:
+ static void _bind_methods();
+
+public:
+ Error open_library(const String &p_path, const String &p_entry_symbol);
+ void close_library();
+
+ enum InitializationLevel {
+ INITIALIZATION_LEVEL_CORE,
+ INITIALIZATION_LEVEL_SERVERS,
+ INITIALIZATION_LEVEL_SCENE,
+ INITIALIZATION_LEVEL_EDITOR,
+ };
+
+ bool is_library_open() const;
+
+ InitializationLevel get_minimum_library_initialization_level() const;
+ void initialize_library(InitializationLevel p_level);
+ void deinitialize_library(InitializationLevel p_level);
+
+ static void initialize_native_extensions();
+ NativeExtension();
+ ~NativeExtension();
+};
+
+VARIANT_ENUM_CAST(NativeExtension::InitializationLevel)
+
+class NativeExtensionResourceLoader : public ResourceFormatLoader {
+public:
+ virtual RES load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE);
+ virtual void get_recognized_extensions(List<String> *p_extensions) const;
+ virtual bool handles_type(const String &p_type) const;
+ virtual String get_resource_type(const String &p_path) const;
+};
+
+#endif // NATIVEEXTENSION_H
diff --git a/core/extension/native_extension_manager.cpp b/core/extension/native_extension_manager.cpp
new file mode 100644
index 0000000000..7be2593845
--- /dev/null
+++ b/core/extension/native_extension_manager.cpp
@@ -0,0 +1,130 @@
+/*************************************************************************/
+/* native_extension_manager.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "native_extension_manager.h"
+
+NativeExtensionManager::LoadStatus NativeExtensionManager::load_extension(const String &p_path) {
+ if (native_extension_map.has(p_path)) {
+ return LOAD_STATUS_ALREADY_LOADED;
+ }
+ Ref<NativeExtension> extension = ResourceLoader::load(p_path);
+ if (extension.is_null()) {
+ return LOAD_STATUS_FAILED;
+ }
+
+ if (level >= 0) { //already initialized up to some level
+ int32_t minimum_level = extension->get_minimum_library_initialization_level();
+ if (minimum_level < MIN(level, NativeExtension::INITIALIZATION_LEVEL_SCENE)) {
+ return LOAD_STATUS_NEEDS_RESTART;
+ }
+ //initialize up to current level
+ for (int32_t i = minimum_level; i < level; i++) {
+ extension->initialize_library(NativeExtension::InitializationLevel(level));
+ }
+ }
+ native_extension_map[p_path] = extension;
+ return LOAD_STATUS_OK;
+}
+
+NativeExtensionManager::LoadStatus NativeExtensionManager::reload_extension(const String &p_path) {
+ return LOAD_STATUS_OK; //TODO
+}
+NativeExtensionManager::LoadStatus NativeExtensionManager::unload_extension(const String &p_path) {
+ if (!native_extension_map.has(p_path)) {
+ return LOAD_STATUS_NOT_LOADED;
+ }
+
+ Ref<NativeExtension> extension = native_extension_map[p_path];
+
+ if (level >= 0) { //already initialized up to some level
+ int32_t minimum_level = extension->get_minimum_library_initialization_level();
+ if (minimum_level < MIN(level, NativeExtension::INITIALIZATION_LEVEL_SCENE)) {
+ return LOAD_STATUS_NEEDS_RESTART;
+ }
+ //initialize up to current level
+ for (int32_t i = level; i >= minimum_level; i--) {
+ extension->deinitialize_library(NativeExtension::InitializationLevel(level));
+ }
+ }
+ native_extension_map.erase(p_path);
+ return LOAD_STATUS_OK;
+}
+Vector<String> NativeExtensionManager::get_loaded_extensions() const {
+ Vector<String> ret;
+ for (const Map<String, Ref<NativeExtension>>::Element *E = native_extension_map.front(); E; E = E->next()) {
+ ret.push_back(E->key());
+ }
+ return ret;
+}
+Ref<NativeExtension> NativeExtensionManager::get_extension(const String &p_path) {
+ Map<String, Ref<NativeExtension>>::Element *E = native_extension_map.find(p_path);
+ ERR_FAIL_COND_V(!E, Ref<NativeExtension>());
+ return E->get();
+}
+
+void NativeExtensionManager::initialize_extensions(NativeExtension::InitializationLevel p_level) {
+ ERR_FAIL_COND(int32_t(p_level) - 1 != level);
+ for (Map<String, Ref<NativeExtension>>::Element *E = native_extension_map.front(); E; E = E->next()) {
+ E->get()->initialize_library(p_level);
+ }
+ level = p_level;
+}
+
+void NativeExtensionManager::deinitialize_extensions(NativeExtension::InitializationLevel p_level) {
+ ERR_FAIL_COND(int32_t(p_level) != level);
+ for (Map<String, Ref<NativeExtension>>::Element *E = native_extension_map.front(); E; E = E->next()) {
+ E->get()->deinitialize_library(p_level);
+ }
+ level = int32_t(p_level) - 1;
+}
+
+NativeExtensionManager *NativeExtensionManager::get_singleton() {
+ return singleton;
+}
+void NativeExtensionManager::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("load_extension", "path"), &NativeExtensionManager::load_extension);
+ ClassDB::bind_method(D_METHOD("reload_extension", "path"), &NativeExtensionManager::reload_extension);
+ ClassDB::bind_method(D_METHOD("unload_extension", "path"), &NativeExtensionManager::unload_extension);
+ ClassDB::bind_method(D_METHOD("get_loaded_extensions"), &NativeExtensionManager::get_loaded_extensions);
+ ClassDB::bind_method(D_METHOD("get_extension", "path"), &NativeExtensionManager::get_extension);
+
+ BIND_ENUM_CONSTANT(LOAD_STATUS_OK);
+ BIND_ENUM_CONSTANT(LOAD_STATUS_FAILED);
+ BIND_ENUM_CONSTANT(LOAD_STATUS_ALREADY_LOADED);
+ BIND_ENUM_CONSTANT(LOAD_STATUS_NOT_LOADED);
+ BIND_ENUM_CONSTANT(LOAD_STATUS_NEEDS_RESTART);
+}
+
+NativeExtensionManager *NativeExtensionManager::singleton = nullptr;
+
+NativeExtensionManager::NativeExtensionManager() {
+ ERR_FAIL_COND(singleton != nullptr);
+ singleton = this;
+}
diff --git a/core/extension/native_extension_manager.h b/core/extension/native_extension_manager.h
new file mode 100644
index 0000000000..78465bd5cf
--- /dev/null
+++ b/core/extension/native_extension_manager.h
@@ -0,0 +1,71 @@
+/*************************************************************************/
+/* native_extension_manager.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef NATIVE_EXTENSION_MANAGER_H
+#define NATIVE_EXTENSION_MANAGER_H
+
+#include "core/extension/native_extension.h"
+
+class NativeExtensionManager : public Object {
+ GDCLASS(NativeExtensionManager, Object);
+
+ int32_t level = -1;
+ Map<String, Ref<NativeExtension>> native_extension_map;
+
+ static void _bind_methods();
+
+ static NativeExtensionManager *singleton;
+
+public:
+ enum LoadStatus {
+ LOAD_STATUS_OK,
+ LOAD_STATUS_FAILED,
+ LOAD_STATUS_ALREADY_LOADED,
+ LOAD_STATUS_NOT_LOADED,
+ LOAD_STATUS_NEEDS_RESTART,
+ };
+
+ LoadStatus load_extension(const String &p_path);
+ LoadStatus reload_extension(const String &p_path);
+ LoadStatus unload_extension(const String &p_path);
+ Vector<String> get_loaded_extensions() const;
+ Ref<NativeExtension> get_extension(const String &p_path);
+
+ void initialize_extensions(NativeExtension::InitializationLevel p_level);
+ void deinitialize_extensions(NativeExtension::InitializationLevel p_level);
+
+ static NativeExtensionManager *get_singleton();
+
+ NativeExtensionManager();
+};
+
+VARIANT_ENUM_CAST(NativeExtensionManager::LoadStatus)
+
+#endif // NATIVEEXTENSIONMANAGER_H
diff --git a/core/io/http_client.cpp b/core/io/http_client.cpp
index 78f04e57d3..8000dd4290 100644
--- a/core/io/http_client.cpp
+++ b/core/io/http_client.cpp
@@ -30,9 +30,6 @@
#include "http_client.h"
-#include "core/io/stream_peer_ssl.h"
-#include "core/version.h"
-
const char *HTTPClient::_methods[METHOD_MAX] = {
"GET",
"HEAD",
@@ -45,698 +42,23 @@ const char *HTTPClient::_methods[METHOD_MAX] = {
"PATCH"
};
-#ifndef JAVASCRIPT_ENABLED
-Error HTTPClient::connect_to_host(const String &p_host, int p_port, bool p_ssl, bool p_verify_host) {
- close();
-
- conn_port = p_port;
- conn_host = p_host;
-
- ssl = p_ssl;
- ssl_verify_host = p_verify_host;
-
- String host_lower = conn_host.to_lower();
- if (host_lower.begins_with("http://")) {
- conn_host = conn_host.substr(7, conn_host.length() - 7);
- } else if (host_lower.begins_with("https://")) {
- ssl = true;
- conn_host = conn_host.substr(8, conn_host.length() - 8);
- }
-
- ERR_FAIL_COND_V(conn_host.length() < HOST_MIN_LEN, ERR_INVALID_PARAMETER);
-
- if (conn_port < 0) {
- if (ssl) {
- conn_port = PORT_HTTPS;
- } else {
- conn_port = PORT_HTTP;
- }
- }
-
- connection = tcp_connection;
-
- if (conn_host.is_valid_ip_address()) {
- // Host contains valid IP
- Error err = tcp_connection->connect_to_host(IPAddress(conn_host), p_port);
- if (err) {
- status = STATUS_CANT_CONNECT;
- return err;
- }
-
- status = STATUS_CONNECTING;
- } else {
- // Host contains hostname and needs to be resolved to IP
- resolving = IP::get_singleton()->resolve_hostname_queue_item(conn_host);
- status = STATUS_RESOLVING;
+HTTPClient *HTTPClient::create() {
+ if (_create) {
+ return _create();
}
-
- return OK;
+ return nullptr;
}
-void HTTPClient::set_connection(const Ref<StreamPeer> &p_connection) {
- ERR_FAIL_COND_MSG(p_connection.is_null(), "Connection is not a reference to a valid StreamPeer object.");
-
- if (ssl) {
- ERR_FAIL_NULL_MSG(Object::cast_to<StreamPeerSSL>(p_connection.ptr()),
- "Connection is not a reference to a valid StreamPeerSSL object.");
- }
-
- if (connection == p_connection) {
- return;
- }
-
- close();
- connection = p_connection;
- status = STATUS_CONNECTED;
+Error HTTPClient::_request_raw(Method p_method, const String &p_url, const Vector<String> &p_headers, const Vector<uint8_t> &p_body) {
+ int size = p_body.size();
+ return request(p_method, p_url, p_headers, size > 0 ? p_body.ptr() : nullptr, size);
}
-Ref<StreamPeer> HTTPClient::get_connection() const {
- return connection;
+Error HTTPClient::_request(Method p_method, const String &p_url, const Vector<String> &p_headers, const String &p_body) {
+ int size = p_body.length();
+ return request(p_method, p_url, p_headers, size > 0 ? (const uint8_t *)p_body.utf8().get_data() : nullptr, size);
}
-static bool _check_request_url(HTTPClient::Method p_method, const String &p_url) {
- switch (p_method) {
- case HTTPClient::METHOD_CONNECT: {
- // Authority in host:port format, as in RFC7231
- int pos = p_url.find_char(':');
- return 0 < pos && pos < p_url.length() - 1;
- }
- case HTTPClient::METHOD_OPTIONS: {
- if (p_url == "*") {
- return true;
- }
- [[fallthrough]];
- }
- default:
- // Absolute path or absolute URL
- return p_url.begins_with("/") || p_url.begins_with("http://") || p_url.begins_with("https://");
- }
-}
-
-Error HTTPClient::request_raw(Method p_method, const String &p_url, const Vector<String> &p_headers, const Vector<uint8_t> &p_body) {
- ERR_FAIL_INDEX_V(p_method, METHOD_MAX, ERR_INVALID_PARAMETER);
- ERR_FAIL_COND_V(!_check_request_url(p_method, p_url), ERR_INVALID_PARAMETER);
- ERR_FAIL_COND_V(status != STATUS_CONNECTED, ERR_INVALID_PARAMETER);
- ERR_FAIL_COND_V(connection.is_null(), ERR_INVALID_DATA);
-
- String request = String(_methods[p_method]) + " " + p_url + " HTTP/1.1\r\n";
- bool add_host = true;
- bool add_clen = p_body.size() > 0;
- bool add_uagent = true;
- bool add_accept = true;
- for (int i = 0; i < p_headers.size(); i++) {
- request += p_headers[i] + "\r\n";
- if (add_host && p_headers[i].findn("Host:") == 0) {
- add_host = false;
- }
- if (add_clen && p_headers[i].findn("Content-Length:") == 0) {
- add_clen = false;
- }
- if (add_uagent && p_headers[i].findn("User-Agent:") == 0) {
- add_uagent = false;
- }
- if (add_accept && p_headers[i].findn("Accept:") == 0) {
- add_accept = false;
- }
- }
- if (add_host) {
- if ((ssl && conn_port == PORT_HTTPS) || (!ssl && conn_port == PORT_HTTP)) {
- // Don't append the standard ports
- request += "Host: " + conn_host + "\r\n";
- } else {
- request += "Host: " + conn_host + ":" + itos(conn_port) + "\r\n";
- }
- }
- if (add_clen) {
- request += "Content-Length: " + itos(p_body.size()) + "\r\n";
- // Should it add utf8 encoding?
- }
- if (add_uagent) {
- request += "User-Agent: GodotEngine/" + String(VERSION_FULL_BUILD) + " (" + OS::get_singleton()->get_name() + ")\r\n";
- }
- if (add_accept) {
- request += "Accept: */*\r\n";
- }
- request += "\r\n";
- CharString cs = request.utf8();
-
- Vector<uint8_t> data;
- data.resize(cs.length());
- {
- uint8_t *data_write = data.ptrw();
- for (int i = 0; i < cs.length(); i++) {
- data_write[i] = cs[i];
- }
- }
-
- data.append_array(p_body);
-
- const uint8_t *r = data.ptr();
- Error err = connection->put_data(&r[0], data.size());
-
- if (err) {
- close();
- status = STATUS_CONNECTION_ERROR;
- return err;
- }
-
- status = STATUS_REQUESTING;
- head_request = p_method == METHOD_HEAD;
-
- return OK;
-}
-
-Error HTTPClient::request(Method p_method, const String &p_url, const Vector<String> &p_headers, const String &p_body) {
- ERR_FAIL_INDEX_V(p_method, METHOD_MAX, ERR_INVALID_PARAMETER);
- ERR_FAIL_COND_V(!_check_request_url(p_method, p_url), ERR_INVALID_PARAMETER);
- ERR_FAIL_COND_V(status != STATUS_CONNECTED, ERR_INVALID_PARAMETER);
- ERR_FAIL_COND_V(connection.is_null(), ERR_INVALID_DATA);
-
- String request = String(_methods[p_method]) + " " + p_url + " HTTP/1.1\r\n";
- bool add_host = true;
- bool add_uagent = true;
- bool add_accept = true;
- bool add_clen = p_body.length() > 0;
- for (int i = 0; i < p_headers.size(); i++) {
- request += p_headers[i] + "\r\n";
- if (add_host && p_headers[i].findn("Host:") == 0) {
- add_host = false;
- }
- if (add_clen && p_headers[i].findn("Content-Length:") == 0) {
- add_clen = false;
- }
- if (add_uagent && p_headers[i].findn("User-Agent:") == 0) {
- add_uagent = false;
- }
- if (add_accept && p_headers[i].findn("Accept:") == 0) {
- add_accept = false;
- }
- }
- if (add_host) {
- if ((ssl && conn_port == PORT_HTTPS) || (!ssl && conn_port == PORT_HTTP)) {
- // Don't append the standard ports
- request += "Host: " + conn_host + "\r\n";
- } else {
- request += "Host: " + conn_host + ":" + itos(conn_port) + "\r\n";
- }
- }
- if (add_clen) {
- request += "Content-Length: " + itos(p_body.utf8().length()) + "\r\n";
- // Should it add utf8 encoding?
- }
- if (add_uagent) {
- request += "User-Agent: GodotEngine/" + String(VERSION_FULL_BUILD) + " (" + OS::get_singleton()->get_name() + ")\r\n";
- }
- if (add_accept) {
- request += "Accept: */*\r\n";
- }
- request += "\r\n";
- request += p_body;
-
- CharString cs = request.utf8();
- Error err = connection->put_data((const uint8_t *)cs.ptr(), cs.length());
- if (err) {
- close();
- status = STATUS_CONNECTION_ERROR;
- return err;
- }
-
- status = STATUS_REQUESTING;
- head_request = p_method == METHOD_HEAD;
-
- return OK;
-}
-
-bool HTTPClient::has_response() const {
- return response_headers.size() != 0;
-}
-
-bool HTTPClient::is_response_chunked() const {
- return chunked;
-}
-
-int HTTPClient::get_response_code() const {
- return response_num;
-}
-
-Error HTTPClient::get_response_headers(List<String> *r_response) {
- if (!response_headers.size()) {
- return ERR_INVALID_PARAMETER;
- }
-
- for (int i = 0; i < response_headers.size(); i++) {
- r_response->push_back(response_headers[i]);
- }
-
- response_headers.clear();
-
- return OK;
-}
-
-void HTTPClient::close() {
- if (tcp_connection->get_status() != StreamPeerTCP::STATUS_NONE) {
- tcp_connection->disconnect_from_host();
- }
-
- connection.unref();
- status = STATUS_DISCONNECTED;
- head_request = false;
- if (resolving != IP::RESOLVER_INVALID_ID) {
- IP::get_singleton()->erase_resolve_item(resolving);
- resolving = IP::RESOLVER_INVALID_ID;
- }
-
- response_headers.clear();
- response_str.clear();
- body_size = -1;
- body_left = 0;
- chunk_left = 0;
- chunk_trailer_part = false;
- read_until_eof = false;
- response_num = 0;
- handshaking = false;
-}
-
-Error HTTPClient::poll() {
- switch (status) {
- case STATUS_RESOLVING: {
- ERR_FAIL_COND_V(resolving == IP::RESOLVER_INVALID_ID, ERR_BUG);
-
- IP::ResolverStatus rstatus = IP::get_singleton()->get_resolve_item_status(resolving);
- switch (rstatus) {
- case IP::RESOLVER_STATUS_WAITING:
- return OK; // Still resolving
-
- case IP::RESOLVER_STATUS_DONE: {
- IPAddress host = IP::get_singleton()->get_resolve_item_address(resolving);
- Error err = tcp_connection->connect_to_host(host, conn_port);
- IP::get_singleton()->erase_resolve_item(resolving);
- resolving = IP::RESOLVER_INVALID_ID;
- if (err) {
- status = STATUS_CANT_CONNECT;
- return err;
- }
-
- status = STATUS_CONNECTING;
- } break;
- case IP::RESOLVER_STATUS_NONE:
- case IP::RESOLVER_STATUS_ERROR: {
- IP::get_singleton()->erase_resolve_item(resolving);
- resolving = IP::RESOLVER_INVALID_ID;
- close();
- status = STATUS_CANT_RESOLVE;
- return ERR_CANT_RESOLVE;
- } break;
- }
- } break;
- case STATUS_CONNECTING: {
- StreamPeerTCP::Status s = tcp_connection->get_status();
- switch (s) {
- case StreamPeerTCP::STATUS_CONNECTING: {
- return OK;
- } break;
- case StreamPeerTCP::STATUS_CONNECTED: {
- if (ssl) {
- Ref<StreamPeerSSL> ssl;
- if (!handshaking) {
- // Connect the StreamPeerSSL and start handshaking
- ssl = Ref<StreamPeerSSL>(StreamPeerSSL::create());
- ssl->set_blocking_handshake_enabled(false);
- Error err = ssl->connect_to_stream(tcp_connection, ssl_verify_host, conn_host);
- if (err != OK) {
- close();
- status = STATUS_SSL_HANDSHAKE_ERROR;
- return ERR_CANT_CONNECT;
- }
- connection = ssl;
- handshaking = true;
- } else {
- // We are already handshaking, which means we can use your already active SSL connection
- ssl = static_cast<Ref<StreamPeerSSL>>(connection);
- if (ssl.is_null()) {
- close();
- status = STATUS_SSL_HANDSHAKE_ERROR;
- return ERR_CANT_CONNECT;
- }
-
- ssl->poll(); // Try to finish the handshake
- }
-
- if (ssl->get_status() == StreamPeerSSL::STATUS_CONNECTED) {
- // Handshake has been successful
- handshaking = false;
- status = STATUS_CONNECTED;
- return OK;
- } else if (ssl->get_status() != StreamPeerSSL::STATUS_HANDSHAKING) {
- // Handshake has failed
- close();
- status = STATUS_SSL_HANDSHAKE_ERROR;
- return ERR_CANT_CONNECT;
- }
- // ... we will need to poll more for handshake to finish
- } else {
- status = STATUS_CONNECTED;
- }
- return OK;
- } break;
- case StreamPeerTCP::STATUS_ERROR:
- case StreamPeerTCP::STATUS_NONE: {
- close();
- status = STATUS_CANT_CONNECT;
- return ERR_CANT_CONNECT;
- } break;
- }
- } break;
- case STATUS_BODY:
- case STATUS_CONNECTED: {
- // Check if we are still connected
- if (ssl) {
- Ref<StreamPeerSSL> tmp = connection;
- tmp->poll();
- if (tmp->get_status() != StreamPeerSSL::STATUS_CONNECTED) {
- status = STATUS_CONNECTION_ERROR;
- return ERR_CONNECTION_ERROR;
- }
- } else if (tcp_connection->get_status() != StreamPeerTCP::STATUS_CONNECTED) {
- status = STATUS_CONNECTION_ERROR;
- return ERR_CONNECTION_ERROR;
- }
- // Connection established, requests can now be made
- return OK;
- } break;
- case STATUS_REQUESTING: {
- while (true) {
- uint8_t byte;
- int rec = 0;
- Error err = _get_http_data(&byte, 1, rec);
- if (err != OK) {
- close();
- status = STATUS_CONNECTION_ERROR;
- return ERR_CONNECTION_ERROR;
- }
-
- if (rec == 0) {
- return OK; // Still requesting, keep trying!
- }
-
- response_str.push_back(byte);
- int rs = response_str.size();
- if (
- (rs >= 2 && response_str[rs - 2] == '\n' && response_str[rs - 1] == '\n') ||
- (rs >= 4 && response_str[rs - 4] == '\r' && response_str[rs - 3] == '\n' && response_str[rs - 2] == '\r' && response_str[rs - 1] == '\n')) {
- // End of response, parse.
- response_str.push_back(0);
- String response;
- response.parse_utf8((const char *)response_str.ptr());
- Vector<String> responses = response.split("\n");
- body_size = -1;
- chunked = false;
- body_left = 0;
- chunk_left = 0;
- chunk_trailer_part = false;
- read_until_eof = false;
- response_str.clear();
- response_headers.clear();
- response_num = RESPONSE_OK;
-
- // Per the HTTP 1.1 spec, keep-alive is the default.
- // Not following that specification breaks standard implementations.
- // Broken web servers should be fixed.
- bool keep_alive = true;
-
- for (int i = 0; i < responses.size(); i++) {
- String header = responses[i].strip_edges();
- String s = header.to_lower();
- if (s.length() == 0) {
- continue;
- }
- if (s.begins_with("content-length:")) {
- body_size = s.substr(s.find(":") + 1, s.length()).strip_edges().to_int();
- body_left = body_size;
-
- } else if (s.begins_with("transfer-encoding:")) {
- String encoding = header.substr(header.find(":") + 1, header.length()).strip_edges();
- if (encoding == "chunked") {
- chunked = true;
- }
- } else if (s.begins_with("connection: close")) {
- keep_alive = false;
- }
-
- if (i == 0 && responses[i].begins_with("HTTP")) {
- String num = responses[i].get_slicec(' ', 1);
- response_num = num.to_int();
- } else {
- response_headers.push_back(header);
- }
- }
-
- // This is a HEAD request, we won't receive anything.
- if (head_request) {
- body_size = 0;
- body_left = 0;
- }
-
- if (body_size != -1 || chunked) {
- status = STATUS_BODY;
- } else if (!keep_alive) {
- read_until_eof = true;
- status = STATUS_BODY;
- } else {
- status = STATUS_CONNECTED;
- }
- return OK;
- }
- }
- } break;
- case STATUS_DISCONNECTED: {
- return ERR_UNCONFIGURED;
- } break;
- case STATUS_CONNECTION_ERROR:
- case STATUS_SSL_HANDSHAKE_ERROR: {
- return ERR_CONNECTION_ERROR;
- } break;
- case STATUS_CANT_CONNECT: {
- return ERR_CANT_CONNECT;
- } break;
- case STATUS_CANT_RESOLVE: {
- return ERR_CANT_RESOLVE;
- } break;
- }
-
- return OK;
-}
-
-int HTTPClient::get_response_body_length() const {
- return body_size;
-}
-
-PackedByteArray HTTPClient::read_response_body_chunk() {
- ERR_FAIL_COND_V(status != STATUS_BODY, PackedByteArray());
-
- PackedByteArray ret;
- Error err = OK;
-
- if (chunked) {
- while (true) {
- if (chunk_trailer_part) {
- // We need to consume the trailer part too or keep-alive will break
- uint8_t b;
- int rec = 0;
- err = _get_http_data(&b, 1, rec);
-
- if (rec == 0) {
- break;
- }
-
- chunk.push_back(b);
- int cs = chunk.size();
- if ((cs >= 2 && chunk[cs - 2] == '\r' && chunk[cs - 1] == '\n')) {
- if (cs == 2) {
- // Finally over
- chunk_trailer_part = false;
- status = STATUS_CONNECTED;
- chunk.clear();
- break;
- } else {
- // We do not process nor return the trailer data
- chunk.clear();
- }
- }
- } else if (chunk_left == 0) {
- // Reading length
- uint8_t b;
- int rec = 0;
- err = _get_http_data(&b, 1, rec);
-
- if (rec == 0) {
- break;
- }
-
- chunk.push_back(b);
-
- if (chunk.size() > 32) {
- ERR_PRINT("HTTP Invalid chunk hex len");
- status = STATUS_CONNECTION_ERROR;
- break;
- }
-
- if (chunk.size() > 2 && chunk[chunk.size() - 2] == '\r' && chunk[chunk.size() - 1] == '\n') {
- int len = 0;
- for (int i = 0; i < chunk.size() - 2; i++) {
- char c = chunk[i];
- int v = 0;
- if (c >= '0' && c <= '9') {
- v = c - '0';
- } else if (c >= 'a' && c <= 'f') {
- v = c - 'a' + 10;
- } else if (c >= 'A' && c <= 'F') {
- v = c - 'A' + 10;
- } else {
- ERR_PRINT("HTTP Chunk len not in hex!!");
- status = STATUS_CONNECTION_ERROR;
- break;
- }
- len <<= 4;
- len |= v;
- if (len > (1 << 24)) {
- ERR_PRINT("HTTP Chunk too big!! >16mb");
- status = STATUS_CONNECTION_ERROR;
- break;
- }
- }
-
- if (len == 0) {
- // End reached!
- chunk_trailer_part = true;
- chunk.clear();
- break;
- }
-
- chunk_left = len + 2;
- chunk.resize(chunk_left);
- }
- } else {
- int rec = 0;
- err = _get_http_data(&chunk.write[chunk.size() - chunk_left], chunk_left, rec);
- if (rec == 0) {
- break;
- }
- chunk_left -= rec;
-
- if (chunk_left == 0) {
- if (chunk[chunk.size() - 2] != '\r' || chunk[chunk.size() - 1] != '\n') {
- ERR_PRINT("HTTP Invalid chunk terminator (not \\r\\n)");
- status = STATUS_CONNECTION_ERROR;
- break;
- }
-
- ret.resize(chunk.size() - 2);
- uint8_t *w = ret.ptrw();
- memcpy(w, chunk.ptr(), chunk.size() - 2);
- chunk.clear();
- }
-
- break;
- }
- }
-
- } else {
- int to_read = !read_until_eof ? MIN(body_left, read_chunk_size) : read_chunk_size;
- ret.resize(to_read);
- int _offset = 0;
- while (to_read > 0) {
- int rec = 0;
- {
- uint8_t *w = ret.ptrw();
- err = _get_http_data(w + _offset, to_read, rec);
- }
- if (rec <= 0) { // Ended up reading less
- ret.resize(_offset);
- break;
- } else {
- _offset += rec;
- to_read -= rec;
- if (!read_until_eof) {
- body_left -= rec;
- }
- }
- if (err != OK) {
- break;
- }
- }
- }
-
- if (err != OK) {
- close();
-
- if (err == ERR_FILE_EOF) {
- status = STATUS_DISCONNECTED; // Server disconnected
- } else {
- status = STATUS_CONNECTION_ERROR;
- }
- } else if (body_left == 0 && !chunked && !read_until_eof) {
- status = STATUS_CONNECTED;
- }
-
- return ret;
-}
-
-HTTPClient::Status HTTPClient::get_status() const {
- return status;
-}
-
-void HTTPClient::set_blocking_mode(bool p_enable) {
- blocking = p_enable;
-}
-
-bool HTTPClient::is_blocking_mode_enabled() const {
- return blocking;
-}
-
-Error HTTPClient::_get_http_data(uint8_t *p_buffer, int p_bytes, int &r_received) {
- if (blocking) {
- // We can't use StreamPeer.get_data, since when reaching EOF we will get an
- // error without knowing how many bytes we received.
- Error err = ERR_FILE_EOF;
- int read = 0;
- int left = p_bytes;
- r_received = 0;
- while (left > 0) {
- err = connection->get_partial_data(p_buffer + r_received, left, read);
- if (err == OK) {
- r_received += read;
- } else if (err == ERR_FILE_EOF) {
- r_received += read;
- return err;
- } else {
- return err;
- }
- left -= read;
- }
- return err;
- } else {
- return connection->get_partial_data(p_buffer, p_bytes, r_received);
- }
-}
-
-void HTTPClient::set_read_chunk_size(int p_size) {
- ERR_FAIL_COND(p_size < 256 || p_size > (1 << 24));
- read_chunk_size = p_size;
-}
-
-int HTTPClient::get_read_chunk_size() const {
- return read_chunk_size;
-}
-
-HTTPClient::HTTPClient() {
- tcp_connection.instantiate();
-}
-
-HTTPClient::~HTTPClient() {}
-
-#endif // #ifndef JAVASCRIPT_ENABLED
-
String HTTPClient::query_string_from_dict(const Dictionary &p_dict) {
String query = "";
Array keys = p_dict.keys();
@@ -802,8 +124,8 @@ void HTTPClient::_bind_methods() {
ClassDB::bind_method(D_METHOD("connect_to_host", "host", "port", "use_ssl", "verify_host"), &HTTPClient::connect_to_host, DEFVAL(-1), DEFVAL(false), DEFVAL(true));
ClassDB::bind_method(D_METHOD("set_connection", "connection"), &HTTPClient::set_connection);
ClassDB::bind_method(D_METHOD("get_connection"), &HTTPClient::get_connection);
- ClassDB::bind_method(D_METHOD("request_raw", "method", "url", "headers", "body"), &HTTPClient::request_raw);
- ClassDB::bind_method(D_METHOD("request", "method", "url", "headers", "body"), &HTTPClient::request, DEFVAL(String()));
+ ClassDB::bind_method(D_METHOD("request_raw", "method", "url", "headers", "body"), &HTTPClient::_request_raw);
+ ClassDB::bind_method(D_METHOD("request", "method", "url", "headers", "body"), &HTTPClient::_request, DEFVAL(String()));
ClassDB::bind_method(D_METHOD("close"), &HTTPClient::close);
ClassDB::bind_method(D_METHOD("has_response"), &HTTPClient::has_response);
diff --git a/core/io/http_client.h b/core/io/http_client.h
index f70999836f..718c3a905e 100644
--- a/core/io/http_client.h
+++ b/core/io/http_client.h
@@ -142,7 +142,7 @@ public:
};
-private:
+protected:
static const char *_methods[METHOD_MAX];
static const int HOST_MIN_LEN = 4;
@@ -152,79 +152,48 @@ private:
};
-#ifndef JAVASCRIPT_ENABLED
- Status status = STATUS_DISCONNECTED;
- IP::ResolverID resolving = IP::RESOLVER_INVALID_ID;
- int conn_port = -1;
- String conn_host;
- bool ssl = false;
- bool ssl_verify_host = false;
- bool blocking = false;
- bool handshaking = false;
- bool head_request = false;
-
- Vector<uint8_t> response_str;
-
- bool chunked = false;
- Vector<uint8_t> chunk;
- int chunk_left = 0;
- bool chunk_trailer_part = false;
- int body_size = -1;
- int body_left = 0;
- bool read_until_eof = false;
-
- Ref<StreamPeerTCP> tcp_connection;
- Ref<StreamPeer> connection;
-
- int response_num = 0;
- Vector<String> response_headers;
- // 64 KiB by default (favors fast download speeds at the cost of memory usage).
- int read_chunk_size = 65536;
-
- Error _get_http_data(uint8_t *p_buffer, int p_bytes, int &r_received);
-
-#else
-#include "platform/javascript/http_client.h.inc"
-#endif
-
PackedStringArray _get_response_headers();
Dictionary _get_response_headers_as_dictionary();
+ Error _request_raw(Method p_method, const String &p_url, const Vector<String> &p_headers, const Vector<uint8_t> &p_body);
+ Error _request(Method p_method, const String &p_url, const Vector<String> &p_headers, const String &p_body = String());
+
+ static HTTPClient *(*_create)();
static void _bind_methods();
public:
- Error connect_to_host(const String &p_host, int p_port = -1, bool p_ssl = false, bool p_verify_host = true);
+ static HTTPClient *create();
- void set_connection(const Ref<StreamPeer> &p_connection);
- Ref<StreamPeer> get_connection() const;
+ String query_string_from_dict(const Dictionary &p_dict);
- Error request_raw(Method p_method, const String &p_url, const Vector<String> &p_headers, const Vector<uint8_t> &p_body);
- Error request(Method p_method, const String &p_url, const Vector<String> &p_headers, const String &p_body = String());
+ virtual Error request(Method p_method, const String &p_url, const Vector<String> &p_headers, const uint8_t *p_body, int p_body_size) = 0;
+ virtual Error connect_to_host(const String &p_host, int p_port = -1, bool p_ssl = false, bool p_verify_host = true) = 0;
- void close();
+ virtual void set_connection(const Ref<StreamPeer> &p_connection) = 0;
+ virtual Ref<StreamPeer> get_connection() const = 0;
- Status get_status() const;
+ virtual void close() = 0;
- bool has_response() const;
- bool is_response_chunked() const;
- int get_response_code() const;
- Error get_response_headers(List<String> *r_response);
- int get_response_body_length() const;
+ virtual Status get_status() const = 0;
- PackedByteArray read_response_body_chunk(); // Can't get body as partial text because of most encodings UTF8, gzip, etc.
+ virtual bool has_response() const = 0;
+ virtual bool is_response_chunked() const = 0;
+ virtual int get_response_code() const = 0;
+ virtual Error get_response_headers(List<String> *r_response) = 0;
+ virtual int get_response_body_length() const = 0;
- void set_blocking_mode(bool p_enable); // Useful mostly if running in a thread
- bool is_blocking_mode_enabled() const;
+ virtual PackedByteArray read_response_body_chunk() = 0; // Can't get body as partial text because of most encodings UTF8, gzip, etc.
- void set_read_chunk_size(int p_size);
- int get_read_chunk_size() const;
+ virtual void set_blocking_mode(bool p_enable) = 0; // Useful mostly if running in a thread
+ virtual bool is_blocking_mode_enabled() const = 0;
- Error poll();
+ virtual void set_read_chunk_size(int p_size) = 0;
+ virtual int get_read_chunk_size() const = 0;
- String query_string_from_dict(const Dictionary &p_dict);
+ virtual Error poll() = 0;
- HTTPClient();
- ~HTTPClient();
+ HTTPClient() {}
+ virtual ~HTTPClient() {}
};
VARIANT_ENUM_CAST(HTTPClient::ResponseCode)
diff --git a/core/io/http_client_tcp.cpp b/core/io/http_client_tcp.cpp
new file mode 100644
index 0000000000..f9b3165a07
--- /dev/null
+++ b/core/io/http_client_tcp.cpp
@@ -0,0 +1,666 @@
+/*************************************************************************/
+/* http_client_tcp.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef JAVASCRIPT_ENABLED
+
+#include "http_client_tcp.h"
+
+#include "core/io/stream_peer_ssl.h"
+#include "core/version.h"
+
+HTTPClient *HTTPClientTCP::_create_func() {
+ return memnew(HTTPClientTCP);
+}
+
+Error HTTPClientTCP::connect_to_host(const String &p_host, int p_port, bool p_ssl, bool p_verify_host) {
+ close();
+
+ conn_port = p_port;
+ conn_host = p_host;
+
+ ssl = p_ssl;
+ ssl_verify_host = p_verify_host;
+
+ String host_lower = conn_host.to_lower();
+ if (host_lower.begins_with("http://")) {
+ conn_host = conn_host.substr(7, conn_host.length() - 7);
+ } else if (host_lower.begins_with("https://")) {
+ ssl = true;
+ conn_host = conn_host.substr(8, conn_host.length() - 8);
+ }
+
+ ERR_FAIL_COND_V(conn_host.length() < HOST_MIN_LEN, ERR_INVALID_PARAMETER);
+
+ if (conn_port < 0) {
+ if (ssl) {
+ conn_port = PORT_HTTPS;
+ } else {
+ conn_port = PORT_HTTP;
+ }
+ }
+
+ connection = tcp_connection;
+
+ if (conn_host.is_valid_ip_address()) {
+ // Host contains valid IP
+ Error err = tcp_connection->connect_to_host(IPAddress(conn_host), p_port);
+ if (err) {
+ status = STATUS_CANT_CONNECT;
+ return err;
+ }
+
+ status = STATUS_CONNECTING;
+ } else {
+ // Host contains hostname and needs to be resolved to IP
+ resolving = IP::get_singleton()->resolve_hostname_queue_item(conn_host);
+ status = STATUS_RESOLVING;
+ }
+
+ return OK;
+}
+
+void HTTPClientTCP::set_connection(const Ref<StreamPeer> &p_connection) {
+ ERR_FAIL_COND_MSG(p_connection.is_null(), "Connection is not a reference to a valid StreamPeer object.");
+
+ if (ssl) {
+ ERR_FAIL_NULL_MSG(Object::cast_to<StreamPeerSSL>(p_connection.ptr()),
+ "Connection is not a reference to a valid StreamPeerSSL object.");
+ }
+
+ if (connection == p_connection) {
+ return;
+ }
+
+ close();
+ connection = p_connection;
+ status = STATUS_CONNECTED;
+}
+
+Ref<StreamPeer> HTTPClientTCP::get_connection() const {
+ return connection;
+}
+
+static bool _check_request_url(HTTPClientTCP::Method p_method, const String &p_url) {
+ switch (p_method) {
+ case HTTPClientTCP::METHOD_CONNECT: {
+ // Authority in host:port format, as in RFC7231
+ int pos = p_url.find_char(':');
+ return 0 < pos && pos < p_url.length() - 1;
+ }
+ case HTTPClientTCP::METHOD_OPTIONS: {
+ if (p_url == "*") {
+ return true;
+ }
+ [[fallthrough]];
+ }
+ default:
+ // Absolute path or absolute URL
+ return p_url.begins_with("/") || p_url.begins_with("http://") || p_url.begins_with("https://");
+ }
+}
+
+Error HTTPClientTCP::request(Method p_method, const String &p_url, const Vector<String> &p_headers, const uint8_t *p_body, int p_body_size) {
+ ERR_FAIL_INDEX_V(p_method, METHOD_MAX, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(!_check_request_url(p_method, p_url), ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(status != STATUS_CONNECTED, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(connection.is_null(), ERR_INVALID_DATA);
+
+ String request = String(_methods[p_method]) + " " + p_url + " HTTP/1.1\r\n";
+ bool add_host = true;
+ bool add_clen = p_body_size > 0;
+ bool add_uagent = true;
+ bool add_accept = true;
+ for (int i = 0; i < p_headers.size(); i++) {
+ request += p_headers[i] + "\r\n";
+ if (add_host && p_headers[i].findn("Host:") == 0) {
+ add_host = false;
+ }
+ if (add_clen && p_headers[i].findn("Content-Length:") == 0) {
+ add_clen = false;
+ }
+ if (add_uagent && p_headers[i].findn("User-Agent:") == 0) {
+ add_uagent = false;
+ }
+ if (add_accept && p_headers[i].findn("Accept:") == 0) {
+ add_accept = false;
+ }
+ }
+ if (add_host) {
+ if ((ssl && conn_port == PORT_HTTPS) || (!ssl && conn_port == PORT_HTTP)) {
+ // Don't append the standard ports
+ request += "Host: " + conn_host + "\r\n";
+ } else {
+ request += "Host: " + conn_host + ":" + itos(conn_port) + "\r\n";
+ }
+ }
+ if (add_clen) {
+ request += "Content-Length: " + itos(p_body_size) + "\r\n";
+ // Should it add utf8 encoding?
+ }
+ if (add_uagent) {
+ request += "User-Agent: GodotEngine/" + String(VERSION_FULL_BUILD) + " (" + OS::get_singleton()->get_name() + ")\r\n";
+ }
+ if (add_accept) {
+ request += "Accept: */*\r\n";
+ }
+ request += "\r\n";
+ CharString cs = request.utf8();
+
+ Vector<uint8_t> data;
+ data.resize(cs.length() + p_body_size);
+ memcpy(data.ptrw(), cs.get_data(), cs.length());
+ if (p_body_size > 0) {
+ memcpy(data.ptrw() + cs.length(), p_body, p_body_size);
+ }
+
+ // TODO Implement non-blocking requests.
+ Error err = connection->put_data(data.ptr(), data.size());
+
+ if (err) {
+ close();
+ status = STATUS_CONNECTION_ERROR;
+ return err;
+ }
+
+ status = STATUS_REQUESTING;
+ head_request = p_method == METHOD_HEAD;
+
+ return OK;
+}
+
+bool HTTPClientTCP::has_response() const {
+ return response_headers.size() != 0;
+}
+
+bool HTTPClientTCP::is_response_chunked() const {
+ return chunked;
+}
+
+int HTTPClientTCP::get_response_code() const {
+ return response_num;
+}
+
+Error HTTPClientTCP::get_response_headers(List<String> *r_response) {
+ if (!response_headers.size()) {
+ return ERR_INVALID_PARAMETER;
+ }
+
+ for (int i = 0; i < response_headers.size(); i++) {
+ r_response->push_back(response_headers[i]);
+ }
+
+ response_headers.clear();
+
+ return OK;
+}
+
+void HTTPClientTCP::close() {
+ if (tcp_connection->get_status() != StreamPeerTCP::STATUS_NONE) {
+ tcp_connection->disconnect_from_host();
+ }
+
+ connection.unref();
+ status = STATUS_DISCONNECTED;
+ head_request = false;
+ if (resolving != IP::RESOLVER_INVALID_ID) {
+ IP::get_singleton()->erase_resolve_item(resolving);
+ resolving = IP::RESOLVER_INVALID_ID;
+ }
+
+ response_headers.clear();
+ response_str.clear();
+ body_size = -1;
+ body_left = 0;
+ chunk_left = 0;
+ chunk_trailer_part = false;
+ read_until_eof = false;
+ response_num = 0;
+ handshaking = false;
+}
+
+Error HTTPClientTCP::poll() {
+ switch (status) {
+ case STATUS_RESOLVING: {
+ ERR_FAIL_COND_V(resolving == IP::RESOLVER_INVALID_ID, ERR_BUG);
+
+ IP::ResolverStatus rstatus = IP::get_singleton()->get_resolve_item_status(resolving);
+ switch (rstatus) {
+ case IP::RESOLVER_STATUS_WAITING:
+ return OK; // Still resolving
+
+ case IP::RESOLVER_STATUS_DONE: {
+ IPAddress host = IP::get_singleton()->get_resolve_item_address(resolving);
+ Error err = tcp_connection->connect_to_host(host, conn_port);
+ IP::get_singleton()->erase_resolve_item(resolving);
+ resolving = IP::RESOLVER_INVALID_ID;
+ if (err) {
+ status = STATUS_CANT_CONNECT;
+ return err;
+ }
+
+ status = STATUS_CONNECTING;
+ } break;
+ case IP::RESOLVER_STATUS_NONE:
+ case IP::RESOLVER_STATUS_ERROR: {
+ IP::get_singleton()->erase_resolve_item(resolving);
+ resolving = IP::RESOLVER_INVALID_ID;
+ close();
+ status = STATUS_CANT_RESOLVE;
+ return ERR_CANT_RESOLVE;
+ } break;
+ }
+ } break;
+ case STATUS_CONNECTING: {
+ StreamPeerTCP::Status s = tcp_connection->get_status();
+ switch (s) {
+ case StreamPeerTCP::STATUS_CONNECTING: {
+ return OK;
+ } break;
+ case StreamPeerTCP::STATUS_CONNECTED: {
+ if (ssl) {
+ Ref<StreamPeerSSL> ssl;
+ if (!handshaking) {
+ // Connect the StreamPeerSSL and start handshaking
+ ssl = Ref<StreamPeerSSL>(StreamPeerSSL::create());
+ ssl->set_blocking_handshake_enabled(false);
+ Error err = ssl->connect_to_stream(tcp_connection, ssl_verify_host, conn_host);
+ if (err != OK) {
+ close();
+ status = STATUS_SSL_HANDSHAKE_ERROR;
+ return ERR_CANT_CONNECT;
+ }
+ connection = ssl;
+ handshaking = true;
+ } else {
+ // We are already handshaking, which means we can use your already active SSL connection
+ ssl = static_cast<Ref<StreamPeerSSL>>(connection);
+ if (ssl.is_null()) {
+ close();
+ status = STATUS_SSL_HANDSHAKE_ERROR;
+ return ERR_CANT_CONNECT;
+ }
+
+ ssl->poll(); // Try to finish the handshake
+ }
+
+ if (ssl->get_status() == StreamPeerSSL::STATUS_CONNECTED) {
+ // Handshake has been successful
+ handshaking = false;
+ status = STATUS_CONNECTED;
+ return OK;
+ } else if (ssl->get_status() != StreamPeerSSL::STATUS_HANDSHAKING) {
+ // Handshake has failed
+ close();
+ status = STATUS_SSL_HANDSHAKE_ERROR;
+ return ERR_CANT_CONNECT;
+ }
+ // ... we will need to poll more for handshake to finish
+ } else {
+ status = STATUS_CONNECTED;
+ }
+ return OK;
+ } break;
+ case StreamPeerTCP::STATUS_ERROR:
+ case StreamPeerTCP::STATUS_NONE: {
+ close();
+ status = STATUS_CANT_CONNECT;
+ return ERR_CANT_CONNECT;
+ } break;
+ }
+ } break;
+ case STATUS_BODY:
+ case STATUS_CONNECTED: {
+ // Check if we are still connected
+ if (ssl) {
+ Ref<StreamPeerSSL> tmp = connection;
+ tmp->poll();
+ if (tmp->get_status() != StreamPeerSSL::STATUS_CONNECTED) {
+ status = STATUS_CONNECTION_ERROR;
+ return ERR_CONNECTION_ERROR;
+ }
+ } else if (tcp_connection->get_status() != StreamPeerTCP::STATUS_CONNECTED) {
+ status = STATUS_CONNECTION_ERROR;
+ return ERR_CONNECTION_ERROR;
+ }
+ // Connection established, requests can now be made
+ return OK;
+ } break;
+ case STATUS_REQUESTING: {
+ while (true) {
+ uint8_t byte;
+ int rec = 0;
+ Error err = _get_http_data(&byte, 1, rec);
+ if (err != OK) {
+ close();
+ status = STATUS_CONNECTION_ERROR;
+ return ERR_CONNECTION_ERROR;
+ }
+
+ if (rec == 0) {
+ return OK; // Still requesting, keep trying!
+ }
+
+ response_str.push_back(byte);
+ int rs = response_str.size();
+ if (
+ (rs >= 2 && response_str[rs - 2] == '\n' && response_str[rs - 1] == '\n') ||
+ (rs >= 4 && response_str[rs - 4] == '\r' && response_str[rs - 3] == '\n' && response_str[rs - 2] == '\r' && response_str[rs - 1] == '\n')) {
+ // End of response, parse.
+ response_str.push_back(0);
+ String response;
+ response.parse_utf8((const char *)response_str.ptr());
+ Vector<String> responses = response.split("\n");
+ body_size = -1;
+ chunked = false;
+ body_left = 0;
+ chunk_left = 0;
+ chunk_trailer_part = false;
+ read_until_eof = false;
+ response_str.clear();
+ response_headers.clear();
+ response_num = RESPONSE_OK;
+
+ // Per the HTTP 1.1 spec, keep-alive is the default.
+ // Not following that specification breaks standard implementations.
+ // Broken web servers should be fixed.
+ bool keep_alive = true;
+
+ for (int i = 0; i < responses.size(); i++) {
+ String header = responses[i].strip_edges();
+ String s = header.to_lower();
+ if (s.length() == 0) {
+ continue;
+ }
+ if (s.begins_with("content-length:")) {
+ body_size = s.substr(s.find(":") + 1, s.length()).strip_edges().to_int();
+ body_left = body_size;
+
+ } else if (s.begins_with("transfer-encoding:")) {
+ String encoding = header.substr(header.find(":") + 1, header.length()).strip_edges();
+ if (encoding == "chunked") {
+ chunked = true;
+ }
+ } else if (s.begins_with("connection: close")) {
+ keep_alive = false;
+ }
+
+ if (i == 0 && responses[i].begins_with("HTTP")) {
+ String num = responses[i].get_slicec(' ', 1);
+ response_num = num.to_int();
+ } else {
+ response_headers.push_back(header);
+ }
+ }
+
+ // This is a HEAD request, we won't receive anything.
+ if (head_request) {
+ body_size = 0;
+ body_left = 0;
+ }
+
+ if (body_size != -1 || chunked) {
+ status = STATUS_BODY;
+ } else if (!keep_alive) {
+ read_until_eof = true;
+ status = STATUS_BODY;
+ } else {
+ status = STATUS_CONNECTED;
+ }
+ return OK;
+ }
+ }
+ } break;
+ case STATUS_DISCONNECTED: {
+ return ERR_UNCONFIGURED;
+ } break;
+ case STATUS_CONNECTION_ERROR:
+ case STATUS_SSL_HANDSHAKE_ERROR: {
+ return ERR_CONNECTION_ERROR;
+ } break;
+ case STATUS_CANT_CONNECT: {
+ return ERR_CANT_CONNECT;
+ } break;
+ case STATUS_CANT_RESOLVE: {
+ return ERR_CANT_RESOLVE;
+ } break;
+ }
+
+ return OK;
+}
+
+int HTTPClientTCP::get_response_body_length() const {
+ return body_size;
+}
+
+PackedByteArray HTTPClientTCP::read_response_body_chunk() {
+ ERR_FAIL_COND_V(status != STATUS_BODY, PackedByteArray());
+
+ PackedByteArray ret;
+ Error err = OK;
+
+ if (chunked) {
+ while (true) {
+ if (chunk_trailer_part) {
+ // We need to consume the trailer part too or keep-alive will break
+ uint8_t b;
+ int rec = 0;
+ err = _get_http_data(&b, 1, rec);
+
+ if (rec == 0) {
+ break;
+ }
+
+ chunk.push_back(b);
+ int cs = chunk.size();
+ if ((cs >= 2 && chunk[cs - 2] == '\r' && chunk[cs - 1] == '\n')) {
+ if (cs == 2) {
+ // Finally over
+ chunk_trailer_part = false;
+ status = STATUS_CONNECTED;
+ chunk.clear();
+ break;
+ } else {
+ // We do not process nor return the trailer data
+ chunk.clear();
+ }
+ }
+ } else if (chunk_left == 0) {
+ // Reading length
+ uint8_t b;
+ int rec = 0;
+ err = _get_http_data(&b, 1, rec);
+
+ if (rec == 0) {
+ break;
+ }
+
+ chunk.push_back(b);
+
+ if (chunk.size() > 32) {
+ ERR_PRINT("HTTP Invalid chunk hex len");
+ status = STATUS_CONNECTION_ERROR;
+ break;
+ }
+
+ if (chunk.size() > 2 && chunk[chunk.size() - 2] == '\r' && chunk[chunk.size() - 1] == '\n') {
+ int len = 0;
+ for (int i = 0; i < chunk.size() - 2; i++) {
+ char c = chunk[i];
+ int v = 0;
+ if (c >= '0' && c <= '9') {
+ v = c - '0';
+ } else if (c >= 'a' && c <= 'f') {
+ v = c - 'a' + 10;
+ } else if (c >= 'A' && c <= 'F') {
+ v = c - 'A' + 10;
+ } else {
+ ERR_PRINT("HTTP Chunk len not in hex!!");
+ status = STATUS_CONNECTION_ERROR;
+ break;
+ }
+ len <<= 4;
+ len |= v;
+ if (len > (1 << 24)) {
+ ERR_PRINT("HTTP Chunk too big!! >16mb");
+ status = STATUS_CONNECTION_ERROR;
+ break;
+ }
+ }
+
+ if (len == 0) {
+ // End reached!
+ chunk_trailer_part = true;
+ chunk.clear();
+ break;
+ }
+
+ chunk_left = len + 2;
+ chunk.resize(chunk_left);
+ }
+ } else {
+ int rec = 0;
+ err = _get_http_data(&chunk.write[chunk.size() - chunk_left], chunk_left, rec);
+ if (rec == 0) {
+ break;
+ }
+ chunk_left -= rec;
+
+ if (chunk_left == 0) {
+ if (chunk[chunk.size() - 2] != '\r' || chunk[chunk.size() - 1] != '\n') {
+ ERR_PRINT("HTTP Invalid chunk terminator (not \\r\\n)");
+ status = STATUS_CONNECTION_ERROR;
+ break;
+ }
+
+ ret.resize(chunk.size() - 2);
+ uint8_t *w = ret.ptrw();
+ memcpy(w, chunk.ptr(), chunk.size() - 2);
+ chunk.clear();
+ }
+
+ break;
+ }
+ }
+
+ } else {
+ int to_read = !read_until_eof ? MIN(body_left, read_chunk_size) : read_chunk_size;
+ ret.resize(to_read);
+ int _offset = 0;
+ while (to_read > 0) {
+ int rec = 0;
+ {
+ uint8_t *w = ret.ptrw();
+ err = _get_http_data(w + _offset, to_read, rec);
+ }
+ if (rec <= 0) { // Ended up reading less
+ ret.resize(_offset);
+ break;
+ } else {
+ _offset += rec;
+ to_read -= rec;
+ if (!read_until_eof) {
+ body_left -= rec;
+ }
+ }
+ if (err != OK) {
+ break;
+ }
+ }
+ }
+
+ if (err != OK) {
+ close();
+
+ if (err == ERR_FILE_EOF) {
+ status = STATUS_DISCONNECTED; // Server disconnected
+ } else {
+ status = STATUS_CONNECTION_ERROR;
+ }
+ } else if (body_left == 0 && !chunked && !read_until_eof) {
+ status = STATUS_CONNECTED;
+ }
+
+ return ret;
+}
+
+HTTPClientTCP::Status HTTPClientTCP::get_status() const {
+ return status;
+}
+
+void HTTPClientTCP::set_blocking_mode(bool p_enable) {
+ blocking = p_enable;
+}
+
+bool HTTPClientTCP::is_blocking_mode_enabled() const {
+ return blocking;
+}
+
+Error HTTPClientTCP::_get_http_data(uint8_t *p_buffer, int p_bytes, int &r_received) {
+ if (blocking) {
+ // We can't use StreamPeer.get_data, since when reaching EOF we will get an
+ // error without knowing how many bytes we received.
+ Error err = ERR_FILE_EOF;
+ int read = 0;
+ int left = p_bytes;
+ r_received = 0;
+ while (left > 0) {
+ err = connection->get_partial_data(p_buffer + r_received, left, read);
+ if (err == OK) {
+ r_received += read;
+ } else if (err == ERR_FILE_EOF) {
+ r_received += read;
+ return err;
+ } else {
+ return err;
+ }
+ left -= read;
+ }
+ return err;
+ } else {
+ return connection->get_partial_data(p_buffer, p_bytes, r_received);
+ }
+}
+
+void HTTPClientTCP::set_read_chunk_size(int p_size) {
+ ERR_FAIL_COND(p_size < 256 || p_size > (1 << 24));
+ read_chunk_size = p_size;
+}
+
+int HTTPClientTCP::get_read_chunk_size() const {
+ return read_chunk_size;
+}
+
+HTTPClientTCP::HTTPClientTCP() {
+ tcp_connection.instantiate();
+}
+
+HTTPClient *(*HTTPClient::_create)() = HTTPClientTCP::_create_func;
+
+#endif // #ifndef JAVASCRIPT_ENABLED
diff --git a/core/io/http_client_tcp.h b/core/io/http_client_tcp.h
new file mode 100644
index 0000000000..e178399fbe
--- /dev/null
+++ b/core/io/http_client_tcp.h
@@ -0,0 +1,92 @@
+/*************************************************************************/
+/* http_client_tcp.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef HTTP_CLIENT_TCP_H
+#define HTTP_CLIENT_TCP_H
+
+#include "http_client.h"
+
+class HTTPClientTCP : public HTTPClient {
+private:
+ Status status = STATUS_DISCONNECTED;
+ IP::ResolverID resolving = IP::RESOLVER_INVALID_ID;
+ int conn_port = -1;
+ String conn_host;
+ bool ssl = false;
+ bool ssl_verify_host = false;
+ bool blocking = false;
+ bool handshaking = false;
+ bool head_request = false;
+
+ Vector<uint8_t> response_str;
+
+ bool chunked = false;
+ Vector<uint8_t> chunk;
+ int chunk_left = 0;
+ bool chunk_trailer_part = false;
+ int body_size = -1;
+ int body_left = 0;
+ bool read_until_eof = false;
+
+ Ref<StreamPeerTCP> tcp_connection;
+ Ref<StreamPeer> connection;
+
+ int response_num = 0;
+ Vector<String> response_headers;
+ // 64 KiB by default (favors fast download speeds at the cost of memory usage).
+ int read_chunk_size = 65536;
+
+ Error _get_http_data(uint8_t *p_buffer, int p_bytes, int &r_received);
+
+public:
+ static HTTPClient *_create_func();
+
+ Error request(Method p_method, const String &p_url, const Vector<String> &p_headers, const uint8_t *p_body, int p_body_size) override;
+
+ Error connect_to_host(const String &p_host, int p_port = -1, bool p_ssl = false, bool p_verify_host = true) override;
+ void set_connection(const Ref<StreamPeer> &p_connection) override;
+ Ref<StreamPeer> get_connection() const override;
+ void close() override;
+ Status get_status() const override;
+ bool has_response() const override;
+ bool is_response_chunked() const override;
+ int get_response_code() const override;
+ Error get_response_headers(List<String> *r_response) override;
+ int get_response_body_length() const override;
+ PackedByteArray read_response_body_chunk() override;
+ void set_blocking_mode(bool p_enable) override;
+ bool is_blocking_mode_enabled() const override;
+ void set_read_chunk_size(int p_size) override;
+ int get_read_chunk_size() const override;
+ Error poll() override;
+ HTTPClientTCP();
+};
+
+#endif // HTTP_CLIENT_TCP_H
diff --git a/core/object/SCsub b/core/object/SCsub
index 5d429960e5..dc116aeb19 100644
--- a/core/object/SCsub
+++ b/core/object/SCsub
@@ -2,6 +2,11 @@
Import("env")
+import make_virtuals
+from platform_methods import run_in_subprocess
+
+env.CommandNoCache(["gdvirtual.gen.inc"], "make_virtuals.py", run_in_subprocess(make_virtuals.run))
+
env_object = env.Clone()
env_object.add_source_files(env.core_sources, "*.cpp")
diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp
index df36587662..a10405dfae 100644
--- a/core/object/class_db.cpp
+++ b/core/object/class_db.cpp
@@ -503,9 +503,9 @@ void ClassDB::add_compatibility_class(const StringName &p_class, const StringNam
thread_local bool initializing_with_extension = false;
thread_local ObjectNativeExtension *initializing_extension = nullptr;
-thread_local void *initializing_extension_instance = nullptr;
+thread_local GDExtensionClassInstancePtr initializing_extension_instance = nullptr;
-void ClassDB::instance_get_native_extension_data(ObjectNativeExtension **r_extension, void **r_extension_instance) {
+void ClassDB::instance_get_native_extension_data(ObjectNativeExtension **r_extension, GDExtensionClassInstancePtr *r_extension_instance) {
if (initializing_with_extension) {
*r_extension = initializing_extension;
*r_extension_instance = initializing_extension_instance;
@@ -539,7 +539,7 @@ Object *ClassDB::instantiate(const StringName &p_class) {
if (ti->native_extension) {
initializing_with_extension = true;
initializing_extension = ti->native_extension;
- initializing_extension_instance = ti->native_extension->create_instance(ti->native_extension->create_instance_userdata);
+ initializing_extension_instance = ti->native_extension->create_instance(ti->native_extension->class_userdata);
}
return ti->creation_func();
}
@@ -1603,6 +1603,11 @@ void ClassDB::register_extension_class(ObjectNativeExtension *p_extension) {
classes[p_extension->class_name] = c;
}
+void ClassDB::unregister_extension_class(const StringName &p_class) {
+ ERR_FAIL_COND(!classes.has(p_class));
+ classes.erase(p_class);
+}
+
RWLock ClassDB::lock;
void ClassDB::cleanup_defaults() {
diff --git a/core/object/class_db.h b/core/object/class_db.h
index a4af535149..af528bfde7 100644
--- a/core/object/class_db.h
+++ b/core/object/class_db.h
@@ -204,6 +204,7 @@ public:
}
static void register_extension_class(ObjectNativeExtension *p_extension);
+ static void unregister_extension_class(const StringName &p_class);
template <class T>
static Object *_create_ptr_func() {
@@ -232,7 +233,8 @@ public:
static bool is_parent_class(const StringName &p_class, const StringName &p_inherits);
static bool can_instantiate(const StringName &p_class);
static Object *instantiate(const StringName &p_class);
- static void instance_get_native_extension_data(ObjectNativeExtension **r_extension, void **r_extension_instance);
+ static void instance_get_native_extension_data(ObjectNativeExtension **r_extension, GDExtensionClassInstancePtr *r_extension_instance);
+
static APIType get_api_type(const StringName &p_class);
static uint64_t get_api_hash(APIType p_api);
diff --git a/core/object/make_virtuals.py b/core/object/make_virtuals.py
new file mode 100644
index 0000000000..2c6b8cddc9
--- /dev/null
+++ b/core/object/make_virtuals.py
@@ -0,0 +1,152 @@
+proto = """
+#define GDVIRTUAL$VER($RET m_name $ARG) \\
+GDNativeExtensionClassCallVirtual _gdvirtual_##m_name = (_get_extension() && _get_extension()->get_virtual) ? _get_extension()->get_virtual(_get_extension()->class_userdata, #m_name) : (GDNativeExtensionClassCallVirtual) nullptr;\\
+StringName _gdvirtual_##m_name##_sn = #m_name;\\
+bool _gdvirtual_##m_name##_call($CALLARGS) $CONST { \\
+ ScriptInstance *script_instance = ((Object*)(this))->get_script_instance();\\
+ if (script_instance) {\\
+ Callable::CallError ce; \\
+ $CALLSIARGS\\
+ $CALLSIBEGINscript_instance->call(_gdvirtual_##m_name##_sn, $CALLSIARGPASS, ce);\\
+ if (ce.error == Callable::CallError::CALL_OK) {\\
+ $CALLSIRET\\
+ return true;\\
+ } \\
+ }\\
+ if (_gdvirtual_##m_name) {\\
+ $CALLPTRARGS\\
+ $CALLPTRRETDEF\\
+ _gdvirtual_##m_name(_get_extension_instance(),$CALLPTRARGPASS,$CALLPTRRETPASS);\\
+ $CALLPTRRET\\
+ return true;\\
+ }\\
+\\
+ return false;\\
+}\\
+\\
+_FORCE_INLINE_ static MethodInfo _gdvirtual_##m_name##_get_method_info() { \\
+ MethodInfo method_info;\\
+ method_info.name = #m_name;\\
+ method_info.flags = METHOD_FLAG_VIRTUAL;\\
+ $FILL_METHOD_INFO\\
+ return method_info;\\
+}
+
+
+"""
+
+
+def generate_version(argcount, const=False, returns=False):
+ s = proto
+ sproto = str(argcount)
+ method_info = ""
+ if returns:
+ sproto += "R"
+ s = s.replace("$RET", "m_ret, ")
+ s = s.replace("$CALLPTRRETDEF", "PtrToArg<m_ret>::EncodeT ret;")
+ method_info += "\tmethod_info.return_val = GetTypeInfo<m_ret>::get_class_info();\\\n"
+ else:
+ s = s.replace("$RET", "")
+ s = s.replace("$CALLPTRRETDEF", "")
+
+ if const:
+ sproto += "C"
+ s = s.replace("$CONST", "const")
+ method_info += "\tmethod_info.flags|=METHOD_FLAG_CONST;\\\n"
+ else:
+ s = s.replace("$CONST", "")
+
+ s = s.replace("$VER", sproto)
+ argtext = ""
+ callargtext = ""
+ callsiargs = ""
+ callsiargptrs = ""
+ callptrargsptr = ""
+ if argcount > 0:
+ argtext += ", "
+ callsiargs = "Variant vargs[" + str(argcount) + "]={"
+ callsiargptrs = "\t\tconst Variant *vargptrs[" + str(argcount) + "]={"
+ callptrargsptr = "\t\tconst GDNativeTypePtr argptrs[" + str(argcount) + "]={"
+ callptrargs = ""
+ for i in range(argcount):
+ if i > 0:
+ argtext += ", "
+ callargtext += ", "
+ callsiargs += ", "
+ callsiargptrs += ", "
+ callptrargs += "\t\t"
+ callptrargsptr += ", "
+ argtext += "m_type" + str(i + 1)
+ callargtext += "const m_type" + str(i + 1) + "& arg" + str(i + 1)
+ callsiargs += "Variant(arg" + str(i + 1) + ")"
+ callsiargptrs += "&vargs[" + str(i) + "]"
+ callptrargs += (
+ "PtrToArg<m_type" + str(i + 1) + ">::EncodeT argval" + str(i + 1) + " = arg" + str(i + 1) + ";\\\n"
+ )
+ callptrargsptr += "&argval" + str(i + 1)
+ method_info += "\tmethod_info.arguments.push_back(GetTypeInfo<m_type" + str(i + 1) + ">::get_class_info());\\\n"
+
+ if argcount:
+ callsiargs += "};\\\n"
+ callsiargptrs += "};\\\n"
+ s = s.replace("$CALLSIARGS", callsiargs + callsiargptrs)
+ s = s.replace("$CALLSIARGPASS", "(const Variant **)vargptrs," + str(argcount))
+ callptrargsptr += "};\\\n"
+ s = s.replace("$CALLPTRARGS", callptrargs + callptrargsptr)
+ s = s.replace("$CALLPTRARGPASS", "(const GDNativeTypePtr*)argptrs")
+ else:
+ s = s.replace("$CALLSIARGS", "")
+ s = s.replace("$CALLSIARGPASS", "nullptr, 0")
+ s = s.replace("$CALLPTRARGS", "")
+ s = s.replace("$CALLPTRARGPASS", "nullptr")
+
+ if returns:
+ if argcount > 0:
+ callargtext += ","
+ callargtext += " m_ret& r_ret"
+ s = s.replace("$CALLSIBEGIN", "Variant ret = ")
+ s = s.replace("$CALLSIRET", "r_ret = ret;")
+ s = s.replace("$CALLPTRRETPASS", "&ret")
+ s = s.replace("$CALLPTRRET", "r_ret = ret;")
+ else:
+ s = s.replace("$CALLSIBEGIN", "")
+ s = s.replace("$CALLSIRET", "")
+ s = s.replace("$CALLPTRRETPASS", "nullptr")
+ s = s.replace("$CALLPTRRET", "")
+
+ s = s.replace("$ARG", argtext)
+ s = s.replace("$CALLARGS", callargtext)
+ s = s.replace("$FILL_METHOD_INFO", method_info)
+
+ return s
+
+
+def run(target, source, env):
+
+ max_versions = 12
+
+ txt = """
+#ifndef GDVIRTUAL_GEN_H
+#define GDVIRTUAL_GEN_H
+
+
+"""
+
+ for i in range(max_versions + 1):
+
+ txt += "/* " + str(i) + " Arguments */\n\n"
+ txt += generate_version(i, False, False)
+ txt += generate_version(i, False, True)
+ txt += generate_version(i, True, False)
+ txt += generate_version(i, True, True)
+
+ txt += "#endif"
+
+ with open(target[0], "w") as f:
+ f.write(txt)
+
+
+if __name__ == "__main__":
+ from platform_methods import subprocess_main
+
+ subprocess_main(globals())
diff --git a/core/object/method_bind.cpp b/core/object/method_bind.cpp
index 9c5ed60708..c53104fe3f 100644
--- a/core/object/method_bind.cpp
+++ b/core/object/method_bind.cpp
@@ -34,6 +34,35 @@
#include "method_bind.h"
+uint32_t MethodBind::get_hash() const {
+ uint32_t hash = hash_djb2_one_32(has_return() ? 1 : 0);
+ hash = hash_djb2_one_32(get_argument_count(), hash);
+
+#ifndef _MSC_VER
+#warning This needs proper class name and argument type for hashing
+#endif
+#if 0
+
+ for (int i = (has_return() ? -1 : 0); i < get_argument_count(); i++) {
+ PropertyInfo pi = i == -1 ? get_return_info() : get_argument_info(i);
+ hash = hash_djb2_one_32(get_argument_type(i), hash);
+ if (pi.class_name != StringName()) {
+ hash = hash_djb2_one_32(pi.class_name.operator String().hash(), hash);
+ }
+ }
+#endif
+ hash = hash_djb2_one_32(get_default_argument_count(), hash);
+ for (int i = 0; i < get_default_argument_count(); i++) {
+ Variant v = get_default_argument(i);
+ hash = hash_djb2_one_32(v.hash(), hash);
+ }
+
+ hash = hash_djb2_one_32(is_const(), hash);
+ hash = hash_djb2_one_32(is_vararg(), hash);
+
+ return hash;
+}
+
#ifdef DEBUG_METHODS_ENABLED
PropertyInfo MethodBind::get_argument_info(int p_argument) const {
ERR_FAIL_INDEX_V(p_argument, get_argument_count(), PropertyInfo());
diff --git a/core/object/method_bind.h b/core/object/method_bind.h
index 7030ae201b..92b964772a 100644
--- a/core/object/method_bind.h
+++ b/core/object/method_bind.h
@@ -135,6 +135,8 @@ public:
void set_default_arguments(const Vector<Variant> &p_defargs);
+ uint32_t get_hash() const;
+
MethodBind();
virtual ~MethodBind();
};
diff --git a/core/object/object.cpp b/core/object/object.cpp
index bad40f14d9..1c8db89e5e 100644
--- a/core/object/object.cpp
+++ b/core/object/object.cpp
@@ -386,12 +386,20 @@ void Object::set(const StringName &p_name, const Variant &p_value, bool *r_valid
}
if (_extension && _extension->set) {
- if (_extension->set(_extension_instance, &p_name, &p_value)) {
+// C style pointer casts should never trigger a compiler warning because the risk is assumed by the user, so GCC should keep quiet about it.
+#if defined(__GNUC__) && !defined(__clang__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wignored-qualifiers"
+#endif
+ if (_extension->set(_extension_instance, (const GDNativeStringNamePtr)&p_name, (const GDNativeVariantPtr)&p_value)) {
if (r_valid) {
*r_valid = true;
}
return;
}
+#if defined(__GNUC__) && !defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
}
//try built-in setgetter
@@ -459,14 +467,22 @@ Variant Object::get(const StringName &p_name, bool *r_valid) const {
return ret;
}
}
-
if (_extension && _extension->get) {
- if (_extension->get(_extension_instance, &p_name, &ret)) {
+// C style pointer casts should never trigger a compiler warning because the risk is assumed by the user, so GCC should keep quiet about it.
+#if defined(__GNUC__) && !defined(__clang__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wignored-qualifiers"
+#endif
+
+ if (_extension->get(_extension_instance, (const GDNativeStringNamePtr)&p_name, (GDNativeVariantPtr)&ret)) {
if (r_valid) {
*r_valid = true;
}
return ret;
}
+#if defined(__GNUC__) && !defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
}
//try built-in setgetter
@@ -616,7 +632,7 @@ void Object::get_property_list(List<PropertyInfo> *p_list, bool p_reversed) cons
if (_extension && _extension->get_property_list) {
uint32_t pcount;
- const ObjectNativeExtension::PInfo *pinfo = _extension->get_property_list(_extension_instance, &pcount);
+ const GDNativePropertyInfo *pinfo = _extension->get_property_list(_extension_instance, &pcount);
for (uint32_t i = 0; i < pcount; i++) {
p_list->push_back(PropertyInfo(Variant::Type(pinfo[i].type), pinfo[i].class_name, PropertyHint(pinfo[i].hint), pinfo[i].hint_string, pinfo[i].usage, pinfo[i].class_name));
}
@@ -920,7 +936,7 @@ void Object::set_meta(const String &p_name, const Variant &p_value) {
}
Variant Object::get_meta(const String &p_name) const {
- ERR_FAIL_COND_V(!metadata.has(p_name), Variant());
+ ERR_FAIL_COND_V_MSG(!metadata.has(p_name), Variant(), "The object does not have any 'meta' values with the key '" + p_name + "'.");
return metadata[p_name];
}
@@ -1287,10 +1303,10 @@ void Object::get_signals_connected_to_this(List<Connection> *p_connections) cons
}
Error Object::connect(const StringName &p_signal, const Callable &p_callable, const Vector<Variant> &p_binds, uint32_t p_flags) {
- ERR_FAIL_COND_V(p_callable.is_null(), ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V_MSG(p_callable.is_null(), ERR_INVALID_PARAMETER, "Cannot connect to '" + p_signal + "': the provided callable is null.");
Object *target_object = p_callable.get_object();
- ERR_FAIL_COND_V(!target_object, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V_MSG(!target_object, ERR_INVALID_PARAMETER, "Cannot connect to '" + p_signal + "' to callable '" + p_callable + "': the callable object is null.");
SignalData *s = signal_map.getptr(p_signal);
if (!s) {
@@ -1348,7 +1364,7 @@ Error Object::connect(const StringName &p_signal, const Callable &p_callable, co
}
bool Object::is_connected(const StringName &p_signal, const Callable &p_callable) const {
- ERR_FAIL_COND_V(p_callable.is_null(), false);
+ ERR_FAIL_COND_V_MSG(p_callable.is_null(), false, "Cannot determine if connected to '" + p_signal + "': the provided callable is null.");
const SignalData *s = signal_map.getptr(p_signal);
if (!s) {
bool signal_is_valid = ClassDB::has_signal(get_class_name(), p_signal);
@@ -1375,10 +1391,10 @@ void Object::disconnect(const StringName &p_signal, const Callable &p_callable)
}
void Object::_disconnect(const StringName &p_signal, const Callable &p_callable, bool p_force) {
- ERR_FAIL_COND(p_callable.is_null());
+ ERR_FAIL_COND_MSG(p_callable.is_null(), "Cannot disconnect from '" + p_signal + "': the provided callable is null.");
Object *target_object = p_callable.get_object();
- ERR_FAIL_COND(!target_object);
+ ERR_FAIL_COND_MSG(!target_object, "Cannot disconnect '" + p_signal + "' from callable '" + p_callable + "': the callable object is null.");
SignalData *s = signal_map.getptr(p_signal);
if (!s) {
@@ -1812,7 +1828,7 @@ Object::~Object() {
script_instance = nullptr;
if (_extension && _extension->free_instance) {
- _extension->free_instance(_extension->create_instance_userdata, _extension_instance);
+ _extension->free_instance(_extension->class_userdata, _extension_instance);
_extension = nullptr;
_extension_instance = nullptr;
}
diff --git a/core/object/object.h b/core/object/object.h
index 65621a47ca..461ed482fe 100644
--- a/core/object/object.h
+++ b/core/object/object.h
@@ -31,6 +31,7 @@
#ifndef OBJECT_H
#define OBJECT_H
+#include "core/extension/gdnative_interface.h"
#include "core/object/object_id.h"
#include "core/os/rw_lock.h"
#include "core/os/spin_lock.h"
@@ -244,29 +245,18 @@ class MethodBind;
struct ObjectNativeExtension {
ObjectNativeExtension *parent = nullptr;
+ List<ObjectNativeExtension *> children;
StringName parent_class_name;
StringName class_name;
bool editor_class = false;
- bool (*set)(void *instance, const void *name, const void *value) = nullptr;
- bool (*get)(void *instance, const void *name, void *ret_variant) = nullptr;
- struct PInfo {
- uint32_t type;
- const char *name;
- const char *class_name;
- uint32_t hint;
- const char *hint_string;
- uint32_t usage;
- };
- const PInfo *(*get_property_list)(void *instance, uint32_t *count) = nullptr;
- void (*free_property_list)(void *instance, const PInfo *) = nullptr;
-
- //call is not used, as all methods registered in ClassDB
-
- void (*notification)(void *instance, int32_t what) = nullptr;
- const char *(*to_string)(void *instance) = nullptr;
-
- void (*reference)(void *instance) = nullptr;
- void (*unreference)(void *instance) = nullptr;
+ GDNativeExtensionClassSet set;
+ GDNativeExtensionClassGet get;
+ GDNativeExtensionClassGetPropertyList get_property_list;
+ GDNativeExtensionClassFreePropertyList free_property_list;
+ GDNativeExtensionClassNotification notification;
+ GDNativeExtensionClassToString to_string;
+ GDNativeExtensionClassReference reference;
+ GDNativeExtensionClassReference unreference;
_FORCE_INLINE_ bool is_class(const String &p_class) const {
const ObjectNativeExtension *e = this;
@@ -278,11 +268,16 @@ struct ObjectNativeExtension {
}
return false;
}
- void *create_instance_userdata = nullptr;
- void *(*create_instance)(void *create_instance_userdata) = nullptr;
- void (*free_instance)(void *create_instance_userdata, void *instance) = nullptr;
+ void *class_userdata = nullptr;
+
+ GDNativeExtensionClassCreateInstance create_instance;
+ GDNativeExtensionClassFreeInstance free_instance;
+ GDNativeExtensionClassGetVirtual get_virtual;
};
+#define GDVIRTUAL_CALL(m_name, ...) _gdvirtual_##m_name##_call(__VA_ARGS__)
+#define GDVIRTUAL_BIND(m_name) ClassDB::add_virtual_method(get_class_static(), _gdvirtual_##m_name##_get_method_info());
+
/*
the following is an incomprehensible blob of hacks and workarounds to compensate for many of the fallencies in C++. As a plus, this macro pretty much alone defines the object model.
*/
@@ -497,7 +492,7 @@ private:
friend void postinitialize_handler(Object *);
ObjectNativeExtension *_extension = nullptr;
- void *_extension_instance = nullptr;
+ GDExtensionClassInstancePtr _extension_instance = nullptr;
struct SignalData {
struct Slot {
@@ -554,8 +549,9 @@ private:
Object(bool p_reference);
protected:
+ friend class NativeExtensionMethodBind;
_ALWAYS_INLINE_ const ObjectNativeExtension *_get_extension() const { return _extension; }
- _ALWAYS_INLINE_ void *_get_extension_instance() const { return _extension_instance; }
+ _ALWAYS_INLINE_ GDExtensionClassInstancePtr _get_extension_instance() const { return _extension_instance; }
virtual void _initialize_classv() { initialize_class(); }
virtual bool _setv(const StringName &p_name, const Variant &p_property) { return false; };
virtual bool _getv(const StringName &p_name, Variant &r_property) const { return false; };
diff --git a/core/object/ref_counted.h b/core/object/ref_counted.h
index 61780eb061..e0af2c18bb 100644
--- a/core/object/ref_counted.h
+++ b/core/object/ref_counted.h
@@ -258,6 +258,8 @@ struct PtrToArg<Ref<T>> {
return Ref<T>(const_cast<T *>(reinterpret_cast<const T *>(p_ptr)));
}
+ typedef Ref<T> EncodeT;
+
_FORCE_INLINE_ static void encode(Ref<T> p_val, const void *p_ptr) {
*(Ref<RefCounted> *)p_ptr = p_val;
}
@@ -265,6 +267,8 @@ struct PtrToArg<Ref<T>> {
template <class T>
struct PtrToArg<const Ref<T> &> {
+ typedef Ref<T> EncodeT;
+
_FORCE_INLINE_ static Ref<T> convert(const void *p_ptr) {
return Ref<T>((T *)p_ptr);
}
diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp
index a81f8ce8d1..1cbb0bb597 100644
--- a/core/register_core_types.cpp
+++ b/core/register_core_types.cpp
@@ -37,6 +37,8 @@
#include "core/crypto/aes_context.h"
#include "core/crypto/crypto.h"
#include "core/crypto/hashing_context.h"
+#include "core/extension/native_extension.h"
+#include "core/extension/native_extension_manager.h"
#include "core/input/input.h"
#include "core/input/input_map.h"
#include "core/io/config_file.h"
@@ -95,6 +97,8 @@ static _Geometry3D *_geometry_3d = nullptr;
extern Mutex _global_mutex;
+static NativeExtensionManager *native_extension_manager = nullptr;
+
extern void register_global_constants();
extern void unregister_global_constants();
@@ -153,14 +157,20 @@ void register_core_types() {
ClassDB::register_class<InputEventPanGesture>();
ClassDB::register_class<InputEventMIDI>();
+ // Network
+ ClassDB::register_virtual_class<IP>();
+
ClassDB::register_virtual_class<StreamPeer>();
ClassDB::register_class<StreamPeerBuffer>();
ClassDB::register_class<StreamPeerTCP>();
ClassDB::register_class<TCPServer>();
+
+ ClassDB::register_virtual_class<PacketPeer>();
+ ClassDB::register_class<PacketPeerStream>();
ClassDB::register_class<PacketPeerUDP>();
ClassDB::register_class<UDPServer>();
- ClassDB::register_custom_instance_class<PacketPeerDTLS>();
- ClassDB::register_custom_instance_class<DTLSServer>();
+
+ ClassDB::register_custom_instance_class<HTTPClient>();
// Crypto
ClassDB::register_class<HashingContext>();
@@ -170,22 +180,20 @@ void register_core_types() {
ClassDB::register_custom_instance_class<HMACContext>();
ClassDB::register_custom_instance_class<Crypto>();
ClassDB::register_custom_instance_class<StreamPeerSSL>();
+ ClassDB::register_custom_instance_class<PacketPeerDTLS>();
+ ClassDB::register_custom_instance_class<DTLSServer>();
resource_format_saver_crypto.instantiate();
ResourceSaver::add_resource_format_saver(resource_format_saver_crypto);
resource_format_loader_crypto.instantiate();
ResourceLoader::add_resource_format_loader(resource_format_loader_crypto);
- ClassDB::register_virtual_class<IP>();
- ClassDB::register_virtual_class<PacketPeer>();
- ClassDB::register_class<PacketPeerStream>();
ClassDB::register_virtual_class<NetworkedMultiplayerPeer>();
ClassDB::register_class<MultiplayerAPI>();
ClassDB::register_class<MainLoop>();
ClassDB::register_class<Translation>();
ClassDB::register_class<OptimizedTranslation>();
ClassDB::register_class<UndoRedo>();
- ClassDB::register_class<HTTPClient>();
ClassDB::register_class<TriangleMesh>();
ClassDB::register_class<ResourceFormatLoader>();
@@ -213,6 +221,12 @@ void register_core_types() {
ClassDB::register_virtual_class<ResourceImporter>();
+ ClassDB::register_class<NativeExtension>();
+
+ ClassDB::register_virtual_class<NativeExtensionManager>();
+
+ native_extension_manager = memnew(NativeExtensionManager);
+
ip = IP::create();
_geometry_2d = memnew(_Geometry2D);
@@ -257,7 +271,7 @@ void register_core_singletons() {
ClassDB::register_class<Time>();
Engine::get_singleton()->add_singleton(Engine::Singleton("ProjectSettings", ProjectSettings::get_singleton()));
- Engine::get_singleton()->add_singleton(Engine::Singleton("IP", IP::get_singleton()));
+ Engine::get_singleton()->add_singleton(Engine::Singleton("IP", IP::get_singleton(), "IP"));
Engine::get_singleton()->add_singleton(Engine::Singleton("Geometry2D", _Geometry2D::get_singleton()));
Engine::get_singleton()->add_singleton(Engine::Singleton("Geometry3D", _Geometry3D::get_singleton()));
Engine::get_singleton()->add_singleton(Engine::Singleton("ResourceLoader", _ResourceLoader::get_singleton()));
@@ -271,9 +285,25 @@ void register_core_singletons() {
Engine::get_singleton()->add_singleton(Engine::Singleton("InputMap", InputMap::get_singleton()));
Engine::get_singleton()->add_singleton(Engine::Singleton("EngineDebugger", _EngineDebugger::get_singleton()));
Engine::get_singleton()->add_singleton(Engine::Singleton("Time", Time::get_singleton()));
+ Engine::get_singleton()->add_singleton(Engine::Singleton("NativeExtensionManager", NativeExtensionManager::get_singleton()));
+}
+
+void register_core_extensions() {
+ //harcoded for now
+ if (ProjectSettings::get_singleton()->has_setting("native_extensions/paths")) {
+ Vector<String> paths = ProjectSettings::get_singleton()->get("native_extensions/paths");
+ for (int i = 0; i < paths.size(); i++) {
+ NativeExtensionManager::LoadStatus status = native_extension_manager->load_extension(paths[i]);
+ ERR_CONTINUE_MSG(status != NativeExtensionManager::LOAD_STATUS_OK, "Error loading extension: " + paths[i]);
+ }
+ }
+ native_extension_manager->initialize_extensions(NativeExtension::INITIALIZATION_LEVEL_CORE);
}
void unregister_core_types() {
+ native_extension_manager->deinitialize_extensions(NativeExtension::INITIALIZATION_LEVEL_CORE);
+
+ memdelete(native_extension_manager);
memdelete(_resource_loader);
memdelete(_resource_saver);
memdelete(_os);
diff --git a/core/register_core_types.h b/core/register_core_types.h
index baf7ddbe65..830f05607d 100644
--- a/core/register_core_types.h
+++ b/core/register_core_types.h
@@ -33,6 +33,7 @@
void register_core_types();
void register_core_settings();
+void register_core_extensions();
void register_core_singletons();
void unregister_core_types();
diff --git a/core/templates/rid_owner.h b/core/templates/rid_owner.h
index c4aa93c394..31278b71bd 100644
--- a/core/templates/rid_owner.h
+++ b/core/templates/rid_owner.h
@@ -351,6 +351,9 @@ public:
for (size_t i = 0; i < max_alloc; i++) {
uint64_t validator = validator_chunks[i / elements_in_chunk][i % elements_in_chunk];
+ if (validator & 0x80000000) {
+ continue; //uninitialized
+ }
if (validator != 0xFFFFFFFF) {
chunks[i / elements_in_chunk][i % elements_in_chunk].~T();
}
diff --git a/core/variant/binder_common.h b/core/variant/binder_common.h
index 0885777429..ef5867c685 100644
--- a/core/variant/binder_common.h
+++ b/core/variant/binder_common.h
@@ -77,6 +77,7 @@ struct VariantCaster<const T &> {
_FORCE_INLINE_ static m_enum convert(const void *p_ptr) { \
return m_enum(*reinterpret_cast<const int *>(p_ptr)); \
} \
+ typedef int64_t EncodeT; \
_FORCE_INLINE_ static void encode(m_enum p_val, const void *p_ptr) { \
*(int *)p_ptr = p_val; \
} \
@@ -117,6 +118,7 @@ struct PtrToArg<char32_t> {
_FORCE_INLINE_ static char32_t convert(const void *p_ptr) {
return char32_t(*reinterpret_cast<const int *>(p_ptr));
}
+ typedef int64_t EncodeT;
_FORCE_INLINE_ static void encode(char32_t p_val, const void *p_ptr) {
*(int *)p_ptr = p_val;
}
diff --git a/core/variant/method_ptrcall.h b/core/variant/method_ptrcall.h
index d4ec5e570c..7852187b77 100644
--- a/core/variant/method_ptrcall.h
+++ b/core/variant/method_ptrcall.h
@@ -45,6 +45,7 @@ struct PtrToArg {};
_FORCE_INLINE_ static m_type convert(const void *p_ptr) { \
return *reinterpret_cast<const m_type *>(p_ptr); \
} \
+ typedef m_type EncodeT; \
_FORCE_INLINE_ static void encode(m_type p_val, void *p_ptr) { \
*((m_type *)p_ptr) = p_val; \
} \
@@ -54,6 +55,7 @@ struct PtrToArg {};
_FORCE_INLINE_ static m_type convert(const void *p_ptr) { \
return *reinterpret_cast<const m_type *>(p_ptr); \
} \
+ typedef m_type EncodeT; \
_FORCE_INLINE_ static void encode(m_type p_val, void *p_ptr) { \
*((m_type *)p_ptr) = p_val; \
} \
@@ -65,6 +67,7 @@ struct PtrToArg {};
_FORCE_INLINE_ static m_type convert(const void *p_ptr) { \
return static_cast<m_type>(*reinterpret_cast<const m_conv *>(p_ptr)); \
} \
+ typedef m_conv EncodeT; \
_FORCE_INLINE_ static void encode(m_type p_val, void *p_ptr) { \
*((m_conv *)p_ptr) = static_cast<m_conv>(p_val); \
} \
@@ -74,6 +77,7 @@ struct PtrToArg {};
_FORCE_INLINE_ static m_type convert(const void *p_ptr) { \
return static_cast<m_type>(*reinterpret_cast<const m_conv *>(p_ptr)); \
} \
+ typedef m_conv EncodeT; \
_FORCE_INLINE_ static void encode(m_type p_val, void *p_ptr) { \
*((m_conv *)p_ptr) = static_cast<m_conv>(p_val); \
} \
@@ -85,6 +89,7 @@ struct PtrToArg {};
_FORCE_INLINE_ static m_type convert(const void *p_ptr) { \
return *reinterpret_cast<const m_type *>(p_ptr); \
} \
+ typedef m_type EncodeT; \
_FORCE_INLINE_ static void encode(const m_type &p_val, void *p_ptr) { \
*((m_type *)p_ptr) = p_val; \
} \
@@ -94,12 +99,13 @@ struct PtrToArg {};
_FORCE_INLINE_ static m_type convert(const void *p_ptr) { \
return *reinterpret_cast<const m_type *>(p_ptr); \
} \
+ typedef m_type EncodeT; \
_FORCE_INLINE_ static void encode(const m_type &p_val, void *p_ptr) { \
*((m_type *)p_ptr) = p_val; \
} \
}
-MAKE_PTRARG(bool);
+MAKE_PTRARGCONV(bool, uint32_t);
// Integer types.
MAKE_PTRARGCONV(uint8_t, int64_t);
MAKE_PTRARGCONV(int8_t, int64_t);
@@ -153,7 +159,7 @@ struct PtrToArg<T *> {
_FORCE_INLINE_ static T *convert(const void *p_ptr) {
return const_cast<T *>(reinterpret_cast<const T *>(p_ptr));
}
-
+ typedef Object *EncodeT;
_FORCE_INLINE_ static void encode(T *p_var, void *p_ptr) {
*((T **)p_ptr) = p_var;
}
@@ -164,7 +170,7 @@ struct PtrToArg<const T *> {
_FORCE_INLINE_ static const T *convert(const void *p_ptr) {
return reinterpret_cast<const T *>(p_ptr);
}
-
+ typedef const Object *EncodeT;
_FORCE_INLINE_ static void encode(T *p_var, void *p_ptr) {
*((T **)p_ptr) = p_var;
}
@@ -177,7 +183,7 @@ struct PtrToArg<ObjectID> {
_FORCE_INLINE_ static const ObjectID convert(const void *p_ptr) {
return ObjectID(*reinterpret_cast<const uint64_t *>(p_ptr));
}
-
+ typedef uint64_t EncodeT;
_FORCE_INLINE_ static void encode(const ObjectID &p_val, void *p_ptr) {
*((uint64_t *)p_ptr) = p_val;
}
diff --git a/core/variant/variant.h b/core/variant/variant.h
index 125173ea5c..373fe32921 100644
--- a/core/variant/variant.h
+++ b/core/variant/variant.h
@@ -499,6 +499,7 @@ public:
static bool is_builtin_method_vararg(Variant::Type p_type, const StringName &p_method);
static void get_builtin_method_list(Variant::Type p_type, List<StringName> *p_list);
static int get_builtin_method_count(Variant::Type p_type);
+ static uint32_t get_builtin_method_hash(Variant::Type p_type, const StringName &p_method);
void call(const StringName &p_method, const Variant **p_args, int p_argcount, Variant &r_ret, Callable::CallError &r_error);
Variant call(const StringName &p_method, const Variant &p_arg1 = Variant(), const Variant &p_arg2 = Variant(), const Variant &p_arg3 = Variant(), const Variant &p_arg4 = Variant(), const Variant &p_arg5 = Variant());
@@ -586,7 +587,7 @@ public:
typedef void (*PTRKeyedSetter)(void *base, const void *key, const void *value);
typedef void (*PTRKeyedGetter)(const void *base, const void *key, void *value);
- typedef bool (*PTRKeyedChecker)(const void *base, const void *key);
+ typedef uint32_t (*PTRKeyedChecker)(const void *base, const void *key);
static PTRKeyedSetter get_member_ptr_keyed_setter(Variant::Type p_type);
static PTRKeyedGetter get_member_ptr_keyed_getter(Variant::Type p_type);
@@ -631,6 +632,7 @@ public:
static bool has_utility_function_return_value(const StringName &p_name);
static Variant::Type get_utility_function_return_type(const StringName &p_name);
static bool is_utility_function_vararg(const StringName &p_name);
+ static uint32_t get_utility_function_hash(const StringName &p_name);
static void get_utility_function_list(List<StringName> *r_functions);
static int get_utility_function_count();
diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp
index 9c7cd23d72..95c4e7121b 100644
--- a/core/variant/variant_call.cpp
+++ b/core/variant/variant_call.cpp
@@ -1126,6 +1126,25 @@ bool Variant::is_builtin_method_vararg(Variant::Type p_type, const StringName &p
return method->is_vararg;
}
+uint32_t Variant::get_builtin_method_hash(Variant::Type p_type, const StringName &p_method) {
+ ERR_FAIL_INDEX_V(p_type, Variant::VARIANT_MAX, 0);
+ const VariantBuiltInMethodInfo *method = builtin_method_info[p_type].lookup_ptr(p_method);
+ ERR_FAIL_COND_V(!method, 0);
+ uint32_t hash = hash_djb2_one_32(method->is_const);
+ hash = hash_djb2_one_32(method->is_static, hash);
+ hash = hash_djb2_one_32(method->is_vararg, hash);
+ hash = hash_djb2_one_32(method->has_return_type, hash);
+ if (method->has_return_type) {
+ hash = hash_djb2_one_32(method->return_type, hash);
+ }
+ hash = hash_djb2_one_32(method->argument_count, hash);
+ for (int i = 0; i < method->argument_count; i++) {
+ hash = method->get_argument_type(i);
+ }
+
+ return hash;
+}
+
void Variant::get_method_list(List<MethodInfo> *p_list) const {
if (type == OBJECT) {
Object *obj = get_validated_object();
diff --git a/core/variant/variant_op.cpp b/core/variant/variant_op.cpp
index 4a1950c9bf..16c7428781 100644
--- a/core/variant/variant_op.cpp
+++ b/core/variant/variant_op.cpp
@@ -661,8 +661,8 @@ static const char *_op_names[Variant::OP_MAX] = {
"-",
"*",
"/",
- "-",
- "+",
+ "unary-",
+ "unary+",
"%",
"<<",
">>",
diff --git a/core/variant/variant_setget.cpp b/core/variant/variant_setget.cpp
index 5cb329e69a..de1deace63 100644
--- a/core/variant/variant_setget.cpp
+++ b/core/variant/variant_setget.cpp
@@ -871,7 +871,7 @@ struct VariantKeyedSetGetDictionary {
*r_valid = true;
return VariantGetInternalPtr<Dictionary>::get_ptr(base)->has(*key);
}
- static bool ptr_has(const void *base, const void *key) {
+ static uint32_t ptr_has(const void *base, const void *key) {
/* avoid ptrconvert for performance*/
const Dictionary &v = *reinterpret_cast<const Dictionary *>(base);
return v.has(PtrToArg<Variant>::convert(key));
@@ -921,7 +921,7 @@ struct VariantKeyedSetGetObject {
obj->getvar(*key, &exists);
return exists;
}
- static bool ptr_has(const void *base, const void *key) {
+ static uint32_t ptr_has(const void *base, const void *key) {
const Object *obj = PtrToArg<Object *>::convert(base);
ERR_FAIL_COND_V(!obj, false);
bool valid;
diff --git a/core/variant/variant_utility.cpp b/core/variant/variant_utility.cpp
index 5d1efb4166..1f69e81d99 100644
--- a/core/variant/variant_utility.cpp
+++ b/core/variant/variant_utility.cpp
@@ -1348,8 +1348,8 @@ String Variant::get_utility_function_argument_name(const StringName &p_name, int
if (!bfi) {
return String();
}
- ERR_FAIL_COND_V(bfi->is_vararg, String());
ERR_FAIL_INDEX_V(p_arg, bfi->argnames.size(), String());
+ ERR_FAIL_COND_V(bfi->is_vararg, String());
return bfi->argnames[p_arg];
}
@@ -1379,6 +1379,23 @@ bool Variant::is_utility_function_vararg(const StringName &p_name) {
return bfi->is_vararg;
}
+uint32_t Variant::get_utility_function_hash(const StringName &p_name) {
+ const VariantUtilityFunctionInfo *bfi = utility_function_table.lookup_ptr(p_name);
+ ERR_FAIL_COND_V(!bfi, 0);
+
+ uint32_t hash = hash_djb2_one_32(bfi->is_vararg);
+ hash = hash_djb2_one_32(bfi->returns_value, hash);
+ if (bfi->returns_value) {
+ hash = hash_djb2_one_32(bfi->return_type, hash);
+ }
+ hash = hash_djb2_one_32(bfi->argcount, hash);
+ for (int i = 0; i < bfi->argcount; i++) {
+ hash = hash_djb2_one_32(bfi->get_arg_type(i), hash);
+ }
+
+ return hash;
+}
+
void Variant::get_utility_function_list(List<StringName> *r_functions) {
for (List<StringName>::Element *E = utility_function_name_table.front(); E; E = E->next()) {
r_functions->push_back(E->get());
diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml
index fa118bec54..d0e594a78b 100644
--- a/doc/classes/@GlobalScope.xml
+++ b/doc/classes/@GlobalScope.xml
@@ -1224,6 +1224,8 @@
<member name="Marshalls" type="Marshalls" setter="" getter="">
The [Marshalls] singleton.
</member>
+ <member name="NativeExtensionManager" type="NativeExtensionManager" setter="" getter="">
+ </member>
<member name="NavigationMeshGenerator" type="NavigationMeshGenerator" setter="" getter="">
The [NavigationMeshGenerator] singleton.
</member>
diff --git a/doc/classes/CodeEdit.xml b/doc/classes/CodeEdit.xml
index c7dbd86c55..30fd47cdb6 100644
--- a/doc/classes/CodeEdit.xml
+++ b/doc/classes/CodeEdit.xml
@@ -143,6 +143,20 @@
Inserts the selected entry into the text. If [code]replace[/code] is true, any existing text is replaced rather then merged.
</description>
</method>
+ <method name="do_indent">
+ <return type="void">
+ </return>
+ <description>
+ Perform an indent as if the user activated the "ui_text_indent" action.
+ </description>
+ </method>
+ <method name="do_unindent">
+ <return type="void">
+ </return>
+ <description>
+ Perform an unindent as if the user activated the "ui_text_unindent" action.
+ </description>
+ </method>
<method name="fold_all_lines">
<return type="void">
</return>
@@ -278,6 +292,13 @@
Returns [code]true[/code] if string [code]start_key[/code] exists.
</description>
</method>
+ <method name="indent_lines">
+ <return type="void">
+ </return>
+ <description>
+ Indents selected lines, or in the case of no selection the caret line by one.
+ </description>
+ </method>
<method name="is_in_comment" qualifiers="const">
<return type="int">
</return>
@@ -441,6 +462,13 @@
Unfolds all lines that were previously folded.
</description>
</method>
+ <method name="unindent_lines">
+ <return type="void">
+ </return>
+ <description>
+ Unindents selected lines, or in the case of no selection the caret line by one.
+ </description>
+ </method>
<method name="update_code_completion_options">
<return type="void">
</return>
@@ -475,6 +503,18 @@
</member>
<member name="draw_line_numbers" type="bool" setter="set_draw_line_numbers" getter="is_draw_line_numbers_enabled" default="false">
</member>
+ <member name="indent_automatic" type="bool" setter="set_auto_indent_enabled" getter="is_auto_indent_enabled" default="false">
+ Sets whether automatic indent are enabled, this will add an extra indent if a prefix or brace is found.
+ </member>
+ <member name="indent_automatic_prefixes" type="String[]" setter="set_auto_indent_prefixes" getter="get_auto_indent_prefixes" default="[&quot;(&quot;, &quot;:&quot;, &quot;[&quot;, &quot;{&quot;]">
+ Prefixes to trigger an automatic indent.
+ </member>
+ <member name="indent_size" type="int" setter="set_indent_size" getter="get_indent_size" default="4">
+ Size of tabs, if [code]indent_use_spaces[/code] is enabled the amount of spaces to use.
+ </member>
+ <member name="indent_use_spaces" type="bool" setter="set_indent_using_spaces" getter="is_indent_using_spaces" default="false">
+ Use spaces instead of tabs for indentation.
+ </member>
<member name="layout_direction" type="int" setter="set_layout_direction" getter="get_layout_direction" override="true" enum="Control.LayoutDirection" default="2" />
<member name="line_folding" type="bool" setter="set_line_folding_enabled" getter="is_line_folding_enabled" default="true">
Sets whether line folding is allowed.
diff --git a/doc/classes/Color.xml b/doc/classes/Color.xml
index 29c21b3213..7b1415e40d 100644
--- a/doc/classes/Color.xml
+++ b/doc/classes/Color.xml
@@ -80,14 +80,16 @@
</argument>
<argument index="2" name="b" type="float">
</argument>
+ <argument index="3" name="a" type="float">
+ </argument>
<description>
- Constructs a [Color] from RGB values, typically between 0 and 1. Alpha will be 1.
+ Constructs a [Color] from RGBA values, typically between 0 and 1.
[codeblocks]
[gdscript]
- var color = Color(0.2, 1.0, 0.7) # Similar to `Color8(51, 255, 178, 255)`
+ var color = Color(0.2, 1.0, 0.7, 0.8) # Similar to `Color8(51, 255, 178, 204)`
[/gdscript]
[csharp]
- var color = new Color(0.2f, 1.0f, 0.7f); // Similar to `Color.Color8(51, 255, 178, 255)`
+ var color = new Color(0.2f, 1.0f, 0.7f, 0.8f); // Similar to `Color.Color8(51, 255, 178, 255, 204)`
[/csharp]
[/codeblocks]
</description>
@@ -101,16 +103,14 @@
</argument>
<argument index="2" name="b" type="float">
</argument>
- <argument index="3" name="a" type="float">
- </argument>
<description>
- Constructs a [Color] from RGBA values, typically between 0 and 1.
+ Constructs a [Color] from RGB values, typically between 0 and 1. Alpha will be 1.
[codeblocks]
[gdscript]
- var color = Color(0.2, 1.0, 0.7, 0.8) # Similar to `Color8(51, 255, 178, 204)`
+ var color = Color(0.2, 1.0, 0.7) # Similar to `Color8(51, 255, 178, 255)`
[/gdscript]
[csharp]
- var color = new Color(0.2f, 1.0f, 0.7f, 0.8f); // Similar to `Color.Color8(51, 255, 178, 255, 204)`
+ var color = new Color(0.2f, 1.0f, 0.7f); // Similar to `Color.Color8(51, 255, 178, 255)`
[/csharp]
[/codeblocks]
</description>
@@ -349,12 +349,6 @@
<method name="operator +" qualifiers="operator">
<return type="Color">
</return>
- <description>
- </description>
- </method>
- <method name="operator +" qualifiers="operator">
- <return type="Color">
- </return>
<argument index="0" name="right" type="Color">
</argument>
<description>
@@ -363,12 +357,6 @@
<method name="operator -" qualifiers="operator">
<return type="Color">
</return>
- <description>
- </description>
- </method>
- <method name="operator -" qualifiers="operator">
- <return type="Color">
- </return>
<argument index="0" name="right" type="Color">
</argument>
<description>
@@ -414,6 +402,18 @@
<description>
</description>
</method>
+ <method name="operator unary+" qualifiers="operator">
+ <return type="Color">
+ </return>
+ <description>
+ </description>
+ </method>
+ <method name="operator unary-" qualifiers="operator">
+ <return type="Color">
+ </return>
+ <description>
+ </description>
+ </method>
<method name="to_abgr32" qualifiers="const">
<return type="int">
</return>
diff --git a/doc/classes/Control.xml b/doc/classes/Control.xml
index 9addd5965a..2692108d94 100644
--- a/doc/classes/Control.xml
+++ b/doc/classes/Control.xml
@@ -50,14 +50,6 @@
[/codeblocks]
</description>
</method>
- <method name="_clips_input" qualifiers="virtual">
- <return type="bool">
- </return>
- <description>
- Virtual method to be implemented by the user. Returns whether [method _gui_input] should not be called for children controls outside this control's rectangle. Input will be clipped to the Rect of this [Control]. Similar to [member rect_clip_content], but doesn't affect visibility.
- If not overridden, defaults to [code]false[/code].
- </description>
- </method>
<method name="_drop_data" qualifiers="virtual">
<return type="void">
</return>
@@ -155,13 +147,13 @@
* control has [member mouse_filter] set to [constant MOUSE_FILTER_IGNORE];
* control is obstructed by another [Control] on top of it, which doesn't have [member mouse_filter] set to [constant MOUSE_FILTER_IGNORE];
* control's parent has [member mouse_filter] set to [constant MOUSE_FILTER_STOP] or has accepted the event;
- * it happens outside parent's rectangle and the parent has either [member rect_clip_content] or [method _clips_input] enabled.
+ * it happens outside parent's rectangle and the parent has either [member rect_clip_content] enabled.
</description>
</method>
- <method name="_has_point" qualifiers="virtual">
+ <method name="_has_point" qualifiers="virtual const">
<return type="bool">
</return>
- <argument index="0" name="point" type="Vector2">
+ <argument index="0" name="" type="Vector2">
</argument>
<description>
Virtual method to be implemented by the user. Returns whether the given [code]point[/code] is inside this control.
@@ -1135,7 +1127,7 @@
Offsets are often controlled by one or multiple parent [Container] nodes, so you should not modify them manually if your node is a direct child of a [Container]. Offsets update automatically when you move or resize the node.
</member>
<member name="rect_clip_content" type="bool" setter="set_clip_contents" getter="is_clipping_contents" default="false">
- Enables whether rendering of [CanvasItem] based children should be clipped to this control's rectangle. If [code]true[/code], parts of a child which would be visibly outside of this control's rectangle will not be rendered.
+ Enables whether rendering of [CanvasItem] based children should be clipped to this control's rectangle. If [code]true[/code], parts of a child which would be visibly outside of this control's rectangle will not be rendered and won't receive input.
</member>
<member name="rect_global_position" type="Vector2" setter="_set_global_position" getter="get_global_position">
The node's global position, relative to the world (usually to the top-left corner of the window).
diff --git a/doc/classes/CylinderMesh.xml b/doc/classes/CylinderMesh.xml
index 3a81e98c3a..827fb5c10c 100644
--- a/doc/classes/CylinderMesh.xml
+++ b/doc/classes/CylinderMesh.xml
@@ -4,7 +4,7 @@
Class representing a cylindrical [PrimitiveMesh].
</brief_description>
<description>
- Class representing a cylindrical [PrimitiveMesh]. This class can be used to create cones by setting either the [member top_radius] or [member bottom_radius] properties to 0.0.
+ Class representing a cylindrical [PrimitiveMesh]. This class can be used to create cones by setting either the [member top_radius] or [member bottom_radius] properties to [code]0.0[/code].
</description>
<tutorials>
</tutorials>
@@ -12,19 +12,19 @@
</methods>
<members>
<member name="bottom_radius" type="float" setter="set_bottom_radius" getter="get_bottom_radius" default="1.0">
- Bottom radius of the cylinder.
+ Bottom radius of the cylinder. If set to [code]0.0[/code], the bottom faces will not be generated, resulting in a conic shape.
</member>
<member name="height" type="float" setter="set_height" getter="get_height" default="2.0">
Full height of the cylinder.
</member>
<member name="radial_segments" type="int" setter="set_radial_segments" getter="get_radial_segments" default="64">
- Number of radial segments on the cylinder.
+ Number of radial segments on the cylinder. Higher values result in a more detailed cylinder/cone at the cost of performance.
</member>
<member name="rings" type="int" setter="set_rings" getter="get_rings" default="4">
- Number of edge rings along the height of the cylinder.
+ Number of edge rings along the height of the cylinder. Changing [member rings] does not have any visual impact unless a shader or procedural mesh tool is used to alter the vertex data. Higher values result in more subdivisions, which can be used to create smoother-looking effects with shaders or procedural mesh tools (at the cost of performance). When not altering the vertex data using a shader or procedural mesh tool, [member rings] should be kept to its default value.
</member>
<member name="top_radius" type="float" setter="set_top_radius" getter="get_top_radius" default="1.0">
- Top radius of the cylinder.
+ Top radius of the cylinder. If set to [code]0.0[/code], the top faces will not be generated, resulting in a conic shape.
</member>
</members>
<constants>
diff --git a/doc/classes/NativeExtension.xml b/doc/classes/NativeExtension.xml
new file mode 100644
index 0000000000..c48af7df7b
--- /dev/null
+++ b/doc/classes/NativeExtension.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="NativeExtension" inherits="RefCounted" version="4.0">
+ <brief_description>
+ </brief_description>
+ <description>
+ </description>
+ <tutorials>
+ </tutorials>
+ <methods>
+ <method name="close_library">
+ <return type="void">
+ </return>
+ <description>
+ </description>
+ </method>
+ <method name="get_minimum_library_initialization_level" qualifiers="const">
+ <return type="int" enum="NativeExtension.InitializationLevel">
+ </return>
+ <description>
+ </description>
+ </method>
+ <method name="initialize_library">
+ <return type="void">
+ </return>
+ <argument index="0" name="level" type="int" enum="NativeExtension.InitializationLevel">
+ </argument>
+ <description>
+ </description>
+ </method>
+ <method name="is_library_open" qualifiers="const">
+ <return type="bool">
+ </return>
+ <description>
+ </description>
+ </method>
+ <method name="open_library">
+ <return type="int" enum="Error">
+ </return>
+ <argument index="0" name="path" type="String">
+ </argument>
+ <argument index="1" name="entry_symbol" type="String">
+ </argument>
+ <description>
+ </description>
+ </method>
+ </methods>
+ <constants>
+ <constant name="INITIALIZATION_LEVEL_CORE" value="0" enum="InitializationLevel">
+ </constant>
+ <constant name="INITIALIZATION_LEVEL_SERVERS" value="1" enum="InitializationLevel">
+ </constant>
+ <constant name="INITIALIZATION_LEVEL_SCENE" value="2" enum="InitializationLevel">
+ </constant>
+ <constant name="INITIALIZATION_LEVEL_EDITOR" value="3" enum="InitializationLevel">
+ </constant>
+ </constants>
+</class>
diff --git a/doc/classes/NativeExtensionManager.xml b/doc/classes/NativeExtensionManager.xml
new file mode 100644
index 0000000000..ba9018ff4c
--- /dev/null
+++ b/doc/classes/NativeExtensionManager.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="NativeExtensionManager" inherits="Object" version="4.0">
+ <brief_description>
+ </brief_description>
+ <description>
+ </description>
+ <tutorials>
+ </tutorials>
+ <methods>
+ <method name="get_extension">
+ <return type="NativeExtension">
+ </return>
+ <argument index="0" name="path" type="String">
+ </argument>
+ <description>
+ </description>
+ </method>
+ <method name="get_loaded_extensions" qualifiers="const">
+ <return type="PackedStringArray">
+ </return>
+ <description>
+ </description>
+ </method>
+ <method name="load_extension">
+ <return type="int" enum="NativeExtensionManager.LoadStatus">
+ </return>
+ <argument index="0" name="path" type="String">
+ </argument>
+ <description>
+ </description>
+ </method>
+ <method name="reload_extension">
+ <return type="int" enum="NativeExtensionManager.LoadStatus">
+ </return>
+ <argument index="0" name="path" type="String">
+ </argument>
+ <description>
+ </description>
+ </method>
+ <method name="unload_extension">
+ <return type="int" enum="NativeExtensionManager.LoadStatus">
+ </return>
+ <argument index="0" name="path" type="String">
+ </argument>
+ <description>
+ </description>
+ </method>
+ </methods>
+ <constants>
+ <constant name="LOAD_STATUS_OK" value="0" enum="LoadStatus">
+ </constant>
+ <constant name="LOAD_STATUS_FAILED" value="1" enum="LoadStatus">
+ </constant>
+ <constant name="LOAD_STATUS_ALREADY_LOADED" value="2" enum="LoadStatus">
+ </constant>
+ <constant name="LOAD_STATUS_NOT_LOADED" value="3" enum="LoadStatus">
+ </constant>
+ <constant name="LOAD_STATUS_NEEDS_RESTART" value="4" enum="LoadStatus">
+ </constant>
+ </constants>
+</class>
diff --git a/doc/classes/Plane.xml b/doc/classes/Plane.xml
index cca5793fc7..12869061c4 100644
--- a/doc/classes/Plane.xml
+++ b/doc/classes/Plane.xml
@@ -169,23 +169,23 @@
<description>
</description>
</method>
- <method name="operator +" qualifiers="operator">
- <return type="Plane">
+ <method name="operator ==" qualifiers="operator">
+ <return type="bool">
</return>
+ <argument index="0" name="right" type="Plane">
+ </argument>
<description>
</description>
</method>
- <method name="operator -" qualifiers="operator">
+ <method name="operator unary+" qualifiers="operator">
<return type="Plane">
</return>
<description>
</description>
</method>
- <method name="operator ==" qualifiers="operator">
- <return type="bool">
+ <method name="operator unary-" qualifiers="operator">
+ <return type="Plane">
</return>
- <argument index="0" name="right" type="Plane">
- </argument>
<description>
</description>
</method>
diff --git a/doc/classes/Quaternion.xml b/doc/classes/Quaternion.xml
index 660204ee7d..06434ab268 100644
--- a/doc/classes/Quaternion.xml
+++ b/doc/classes/Quaternion.xml
@@ -177,17 +177,17 @@
</description>
</method>
<method name="operator *" qualifiers="operator">
- <return type="Quaternion">
+ <return type="Vector3">
</return>
- <argument index="0" name="right" type="Quaternion">
+ <argument index="0" name="right" type="Vector3">
</argument>
<description>
</description>
</method>
<method name="operator *" qualifiers="operator">
- <return type="Vector3">
+ <return type="Quaternion">
</return>
- <argument index="0" name="right" type="Vector3">
+ <argument index="0" name="right" type="Quaternion">
</argument>
<description>
</description>
@@ -211,12 +211,6 @@
<method name="operator +" qualifiers="operator">
<return type="Quaternion">
</return>
- <description>
- </description>
- </method>
- <method name="operator +" qualifiers="operator">
- <return type="Quaternion">
- </return>
<argument index="0" name="right" type="Quaternion">
</argument>
<description>
@@ -225,12 +219,6 @@
<method name="operator -" qualifiers="operator">
<return type="Quaternion">
</return>
- <description>
- </description>
- </method>
- <method name="operator -" qualifiers="operator">
- <return type="Quaternion">
- </return>
<argument index="0" name="right" type="Quaternion">
</argument>
<description>
@@ -268,6 +256,18 @@
<description>
</description>
</method>
+ <method name="operator unary+" qualifiers="operator">
+ <return type="Quaternion">
+ </return>
+ <description>
+ </description>
+ </method>
+ <method name="operator unary-" qualifiers="operator">
+ <return type="Quaternion">
+ </return>
+ <description>
+ </description>
+ </method>
<method name="slerp" qualifiers="const">
<return type="Quaternion">
</return>
diff --git a/doc/classes/RDFramebufferPass.xml b/doc/classes/RDFramebufferPass.xml
new file mode 100644
index 0000000000..c26c41f93f
--- /dev/null
+++ b/doc/classes/RDFramebufferPass.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="RDFramebufferPass" inherits="RefCounted" version="4.0">
+ <brief_description>
+ Framebuffer pass attachment description.
+ </brief_description>
+ <description>
+ This class contains the list of attachment descriptions for a framebuffer pass. Each points with an index to a previously supplied list of texture attachments.
+ Multipass framebuffers can optimize some configurations in mobile, on desktop they provide little to no advantage.
+ </description>
+ <tutorials>
+ </tutorials>
+ <methods>
+ </methods>
+ <members>
+ <member name="color_attachments" type="PackedInt32Array" setter="set_color_attachments" getter="get_color_attachments" default="PackedInt32Array()">
+ Color attachments in order starting from 0. If this attachment is not used by the shader, pass ATTACHMENT_UNUSED to skip.
+ </member>
+ <member name="depth_attachment" type="int" setter="set_depth_attachment" getter="get_depth_attachment" default="-1">
+ Depth attachment. ATTACHMENT_UNUSED should be used if no depth buffer is required for this pass.
+ </member>
+ <member name="input_attachments" type="PackedInt32Array" setter="set_input_attachments" getter="get_input_attachments" default="PackedInt32Array()">
+ Used for multipass framebuffers (more than one render pass). Converts an attachment to an input. Make sure to also supply it properly in the [RDUniform] for the uniform set.
+ </member>
+ <member name="preserve_attachments" type="PackedInt32Array" setter="set_preserve_attachments" getter="get_preserve_attachments" default="PackedInt32Array()">
+ Attachments to preserve in this pass (otherwise they are erased).
+ </member>
+ <member name="resolve_attachments" type="PackedInt32Array" setter="set_resolve_attachments" getter="get_resolve_attachments" default="PackedInt32Array()">
+ If the color attachments are multisampled, non-multisampled resolve attachments can be provided.
+ </member>
+ </members>
+ <constants>
+ <constant name="ATTACHMENT_UNUSED" value="-1">
+ </constant>
+ </constants>
+</class>
diff --git a/doc/classes/RenderingDevice.xml b/doc/classes/RenderingDevice.xml
index dc56e6fd5d..b66b945025 100644
--- a/doc/classes/RenderingDevice.xml
+++ b/doc/classes/RenderingDevice.xml
@@ -341,13 +341,29 @@
<description>
</description>
</method>
+ <method name="draw_list_switch_to_next_pass">
+ <return type="int">
+ </return>
+ <description>
+ </description>
+ </method>
+ <method name="draw_list_switch_to_next_pass_split">
+ <return type="PackedInt64Array">
+ </return>
+ <argument index="0" name="splits" type="int">
+ </argument>
+ <description>
+ </description>
+ </method>
<method name="framebuffer_create">
<return type="RID">
</return>
- <argument index="0" name="textures" type="Array">
+ <argument index="0" name="textures" type="RID[]">
</argument>
<argument index="1" name="validate_with_format" type="int" default="-1">
</argument>
+ <argument index="2" name="view_count" type="int" default="1">
+ </argument>
<description>
</description>
</method>
@@ -363,11 +379,27 @@
<description>
</description>
</method>
+ <method name="framebuffer_create_multipass">
+ <return type="RID">
+ </return>
+ <argument index="0" name="textures" type="RID[]">
+ </argument>
+ <argument index="1" name="passes" type="RDFramebufferPass[]">
+ </argument>
+ <argument index="2" name="validate_with_format" type="int" default="-1">
+ </argument>
+ <argument index="3" name="view_count" type="int" default="1">
+ </argument>
+ <description>
+ </description>
+ </method>
<method name="framebuffer_format_create">
<return type="int">
</return>
<argument index="0" name="attachments" type="RDAttachmentFormat[]">
</argument>
+ <argument index="1" name="view_count" type="int" default="1">
+ </argument>
<description>
</description>
</method>
@@ -379,11 +411,25 @@
<description>
</description>
</method>
+ <method name="framebuffer_format_create_multipass">
+ <return type="int">
+ </return>
+ <argument index="0" name="attachments" type="RDAttachmentFormat[]">
+ </argument>
+ <argument index="1" name="passes" type="RDFramebufferPass[]">
+ </argument>
+ <argument index="2" name="view_count" type="int" default="1">
+ </argument>
+ <description>
+ </description>
+ </method>
<method name="framebuffer_format_get_texture_samples">
<return type="int" enum="RenderingDevice.TextureSamples">
</return>
<argument index="0" name="format" type="int">
</argument>
+ <argument index="1" name="render_pass" type="int" default="0">
+ </argument>
<description>
</description>
</method>
@@ -524,6 +570,8 @@
</argument>
<argument index="8" name="dynamic_state_flags" type="int" default="0">
</argument>
+ <argument index="9" name="for_render_pass" type="int" default="0">
+ </argument>
<description>
</description>
</method>
diff --git a/doc/classes/TextEdit.xml b/doc/classes/TextEdit.xml
index 03e4556c92..6a3c124c60 100644
--- a/doc/classes/TextEdit.xml
+++ b/doc/classes/TextEdit.xml
@@ -10,6 +10,12 @@
<tutorials>
</tutorials>
<methods>
+ <method name="_backspace" qualifiers="virtual">
+ <return type="void">
+ </return>
+ <description>
+ </description>
+ </method>
<method name="add_gutter">
<return type="void">
</return>
@@ -18,6 +24,12 @@
<description>
</description>
</method>
+ <method name="backspace">
+ <return type="void">
+ </return>
+ <description>
+ </description>
+ </method>
<method name="center_viewport_to_cursor">
<return type="void">
</return>
@@ -96,6 +108,12 @@
Cut's the current selection.
</description>
</method>
+ <method name="delete_selection">
+ <return type="void">
+ </return>
+ <description>
+ </description>
+ </method>
<method name="deselect">
<return type="void">
</return>
@@ -110,6 +128,14 @@
Gets the caret pixel draw poistion.
</description>
</method>
+ <method name="get_first_non_whitespace_column" qualifiers="const">
+ <return type="int">
+ </return>
+ <argument index="0" name="line" type="int">
+ </argument>
+ <description>
+ </description>
+ </method>
<method name="get_gutter_count" qualifiers="const">
<return type="int">
</return>
@@ -140,6 +166,14 @@
<description>
</description>
</method>
+ <method name="get_indent_level" qualifiers="const">
+ <return type="int">
+ </return>
+ <argument index="0" name="line" type="int">
+ </argument>
+ <description>
+ </description>
+ </method>
<method name="get_line" qualifiers="const">
<return type="String">
</return>
@@ -274,6 +308,12 @@
Returns the selection end line.
</description>
</method>
+ <method name="get_tab_size" qualifiers="const">
+ <return type="int">
+ </return>
+ <description>
+ </description>
+ </method>
<method name="get_visible_line_count" qualifiers="const">
<return type="int">
</return>
@@ -354,6 +394,16 @@
Triggers a right-click menu action by the specified index. See [enum MenuItems] for a list of available indexes.
</description>
</method>
+ <method name="merge_gutters">
+ <return type="void">
+ </return>
+ <argument index="0" name="from_line" type="int">
+ </argument>
+ <argument index="1" name="to_line" type="int">
+ </argument>
+ <description>
+ </description>
+ </method>
<method name="paste">
<return type="void">
</return>
@@ -611,6 +661,14 @@
<description>
</description>
</method>
+ <method name="set_tab_size">
+ <return type="void">
+ </return>
+ <argument index="0" name="size" type="int">
+ </argument>
+ <description>
+ </description>
+ </method>
<method name="undo">
<return type="void">
</return>
diff --git a/doc/classes/Tween.xml b/doc/classes/Tween.xml
index ed193b9f7e..253822cf32 100644
--- a/doc/classes/Tween.xml
+++ b/doc/classes/Tween.xml
@@ -72,10 +72,9 @@
<argument index="0" name="delta" type="float">
</argument>
<description>
- Processes the [Tween] by given [code]delta[/code] value, in seconds. Mostly useful when the [Tween] is paused, for controlling it manually.
+ Processes the [Tween] by given [code]delta[/code] value, in seconds. Mostly useful when the [Tween] is paused, for controlling it manually. Can also be used to end the [Tween] animation immediately, by using [code]delta[/code] longer than the whole duration.
Returns [code]true[/code] if the [Tween] still has [Tweener]s that haven't finished.
[b]Note:[/b] The [Tween] will become invalid after finished, but you can call [method stop] after the step, to keep it and reset.
- [b]Note:[/b] [method custom_step] will process only one step of the [Tween]. If the [code]delta[/code] is greater than the remaining time, the excessive time will not have any effect.
</description>
</method>
<method name="interpolate_value">
diff --git a/doc/classes/Vector2.xml b/doc/classes/Vector2.xml
index 07d09c31dc..498aefbef0 100644
--- a/doc/classes/Vector2.xml
+++ b/doc/classes/Vector2.xml
@@ -304,12 +304,6 @@
<method name="operator +" qualifiers="operator">
<return type="Vector2">
</return>
- <description>
- </description>
- </method>
- <method name="operator +" qualifiers="operator">
- <return type="Vector2">
- </return>
<argument index="0" name="right" type="Vector2">
</argument>
<description>
@@ -318,12 +312,6 @@
<method name="operator -" qualifiers="operator">
<return type="Vector2">
</return>
- <description>
- </description>
- </method>
- <method name="operator -" qualifiers="operator">
- <return type="Vector2">
- </return>
<argument index="0" name="right" type="Vector2">
</argument>
<description>
@@ -401,6 +389,18 @@
<description>
</description>
</method>
+ <method name="operator unary+" qualifiers="operator">
+ <return type="Vector2">
+ </return>
+ <description>
+ </description>
+ </method>
+ <method name="operator unary-" qualifiers="operator">
+ <return type="Vector2">
+ </return>
+ <description>
+ </description>
+ </method>
<method name="orthogonal" qualifiers="const">
<return type="Vector2">
</return>
diff --git a/doc/classes/Vector2i.xml b/doc/classes/Vector2i.xml
index 930ec944ba..5f190de8ca 100644
--- a/doc/classes/Vector2i.xml
+++ b/doc/classes/Vector2i.xml
@@ -126,12 +126,6 @@
<method name="operator +" qualifiers="operator">
<return type="Vector2i">
</return>
- <description>
- </description>
- </method>
- <method name="operator +" qualifiers="operator">
- <return type="Vector2i">
- </return>
<argument index="0" name="right" type="Vector2i">
</argument>
<description>
@@ -140,12 +134,6 @@
<method name="operator -" qualifiers="operator">
<return type="Vector2i">
</return>
- <description>
- </description>
- </method>
- <method name="operator -" qualifiers="operator">
- <return type="Vector2i">
- </return>
<argument index="0" name="right" type="Vector2i">
</argument>
<description>
@@ -223,6 +211,18 @@
<description>
</description>
</method>
+ <method name="operator unary+" qualifiers="operator">
+ <return type="Vector2i">
+ </return>
+ <description>
+ </description>
+ </method>
+ <method name="operator unary-" qualifiers="operator">
+ <return type="Vector2i">
+ </return>
+ <description>
+ </description>
+ </method>
<method name="sign" qualifiers="const">
<return type="Vector2i">
</return>
diff --git a/doc/classes/Vector3.xml b/doc/classes/Vector3.xml
index eb1fd5f098..1361666c18 100644
--- a/doc/classes/Vector3.xml
+++ b/doc/classes/Vector3.xml
@@ -318,12 +318,6 @@
<method name="operator +" qualifiers="operator">
<return type="Vector3">
</return>
- <description>
- </description>
- </method>
- <method name="operator +" qualifiers="operator">
- <return type="Vector3">
- </return>
<argument index="0" name="right" type="Vector3">
</argument>
<description>
@@ -332,12 +326,6 @@
<method name="operator -" qualifiers="operator">
<return type="Vector3">
</return>
- <description>
- </description>
- </method>
- <method name="operator -" qualifiers="operator">
- <return type="Vector3">
- </return>
<argument index="0" name="right" type="Vector3">
</argument>
<description>
@@ -415,6 +403,18 @@
<description>
</description>
</method>
+ <method name="operator unary+" qualifiers="operator">
+ <return type="Vector3">
+ </return>
+ <description>
+ </description>
+ </method>
+ <method name="operator unary-" qualifiers="operator">
+ <return type="Vector3">
+ </return>
+ <description>
+ </description>
+ </method>
<method name="outer" qualifiers="const">
<return type="Basis">
</return>
diff --git a/doc/classes/Vector3i.xml b/doc/classes/Vector3i.xml
index 8b45a62afa..e08bafa665 100644
--- a/doc/classes/Vector3i.xml
+++ b/doc/classes/Vector3i.xml
@@ -134,12 +134,6 @@
<method name="operator +" qualifiers="operator">
<return type="Vector3i">
</return>
- <description>
- </description>
- </method>
- <method name="operator +" qualifiers="operator">
- <return type="Vector3i">
- </return>
<argument index="0" name="right" type="Vector3i">
</argument>
<description>
@@ -148,12 +142,6 @@
<method name="operator -" qualifiers="operator">
<return type="Vector3i">
</return>
- <description>
- </description>
- </method>
- <method name="operator -" qualifiers="operator">
- <return type="Vector3i">
- </return>
<argument index="0" name="right" type="Vector3i">
</argument>
<description>
@@ -231,6 +219,18 @@
<description>
</description>
</method>
+ <method name="operator unary+" qualifiers="operator">
+ <return type="Vector3i">
+ </return>
+ <description>
+ </description>
+ </method>
+ <method name="operator unary-" qualifiers="operator">
+ <return type="Vector3i">
+ </return>
+ <description>
+ </description>
+ </method>
<method name="sign" qualifiers="const">
<return type="Vector3i">
</return>
diff --git a/doc/classes/float.xml b/doc/classes/float.xml
index f75c130039..585c847d22 100644
--- a/doc/classes/float.xml
+++ b/doc/classes/float.xml
@@ -145,16 +145,6 @@
<method name="operator +" qualifiers="operator">
<return type="float">
</return>
- <description>
- Unary plus operator. Doesn't have any effect.
- [codeblock]
- var a = +2.5 # a is 2.5.
- [/codeblock]
- </description>
- </method>
- <method name="operator +" qualifiers="operator">
- <return type="float">
- </return>
<argument index="0" name="right" type="float">
</argument>
<description>
@@ -173,17 +163,6 @@
<method name="operator -" qualifiers="operator">
<return type="float">
</return>
- <description>
- Unary minus operator. Negates the number.
- [codeblock]
- var a = -2.5 # a is -2.5.
- print(-a) # 2.5
- [/codeblock]
- </description>
- </method>
- <method name="operator -" qualifiers="operator">
- <return type="float">
- </return>
<argument index="0" name="right" type="float">
</argument>
<description>
@@ -308,6 +287,18 @@
Returns [code]true[/code] if this [float] is greater than or equal to the given [int].
</description>
</method>
+ <method name="operator unary+" qualifiers="operator">
+ <return type="float">
+ </return>
+ <description>
+ </description>
+ </method>
+ <method name="operator unary-" qualifiers="operator">
+ <return type="float">
+ </return>
+ <description>
+ </description>
+ </method>
</methods>
<constants>
</constants>
diff --git a/doc/classes/int.xml b/doc/classes/int.xml
index b0ad963998..95918c9007 100644
--- a/doc/classes/int.xml
+++ b/doc/classes/int.xml
@@ -126,21 +126,21 @@
</description>
</method>
<method name="operator *" qualifiers="operator">
- <return type="float">
+ <return type="int">
</return>
- <argument index="0" name="right" type="float">
+ <argument index="0" name="right" type="int">
</argument>
<description>
- Multiplies an [int] and a [float]. The result is a [float].
+ Multiplies two [int]s.
</description>
</method>
<method name="operator *" qualifiers="operator">
- <return type="int">
+ <return type="float">
</return>
- <argument index="0" name="right" type="int">
+ <argument index="0" name="right" type="float">
</argument>
<description>
- Multiplies two [int]s.
+ Multiplies an [int] and a [float]. The result is a [float].
</description>
</method>
<method name="operator *" qualifiers="operator">
@@ -204,16 +204,6 @@
</description>
</method>
<method name="operator +" qualifiers="operator">
- <return type="int">
- </return>
- <description>
- Unary plus operator. Doesn't have any effect.
- [codeblock]
- var a = +1 # a is 1.
- [/codeblock]
- </description>
- </method>
- <method name="operator +" qualifiers="operator">
<return type="float">
</return>
<argument index="0" name="right" type="float">
@@ -232,17 +222,6 @@
</description>
</method>
<method name="operator -" qualifiers="operator">
- <return type="int">
- </return>
- <description>
- Unary minus operator. Negates the number.
- [codeblock]
- var a = -1 # a is -1.
- print(-a) # 1
- [/codeblock]
- </description>
- </method>
- <method name="operator -" qualifiers="operator">
<return type="float">
</return>
<argument index="0" name="right" type="float">
@@ -414,6 +393,18 @@
[/codeblock]
</description>
</method>
+ <method name="operator unary+" qualifiers="operator">
+ <return type="int">
+ </return>
+ <description>
+ </description>
+ </method>
+ <method name="operator unary-" qualifiers="operator">
+ <return type="int">
+ </return>
+ <description>
+ </description>
+ </method>
<method name="operator |" qualifiers="operator">
<return type="int">
</return>
diff --git a/drivers/vulkan/rendering_device_vulkan.cpp b/drivers/vulkan/rendering_device_vulkan.cpp
index c69b516f37..1539962803 100644
--- a/drivers/vulkan/rendering_device_vulkan.cpp
+++ b/drivers/vulkan/rendering_device_vulkan.cpp
@@ -3244,12 +3244,7 @@ bool RenderingDeviceVulkan::texture_is_format_supported_for_usage(DataFormat p_f
/**** ATTACHMENT ****/
/********************/
-VkRenderPass RenderingDeviceVulkan::_render_pass_create(const Vector<AttachmentFormat> &p_format, InitialAction p_initial_color_action, FinalAction p_final_color_action, InitialAction p_initial_depth_action, FinalAction p_final_depth_action, int *r_color_attachment_count, uint32_t p_view_count) {
- Vector<VkAttachmentDescription> attachments;
- Vector<VkAttachmentReference> color_references;
- Vector<VkAttachmentReference> depth_stencil_references;
- Vector<VkAttachmentReference> resolve_references;
-
+VkRenderPass RenderingDeviceVulkan::_render_pass_create(const Vector<AttachmentFormat> &p_attachments, const Vector<FramebufferPass> &p_passes, InitialAction p_initial_action, FinalAction p_final_action, InitialAction p_initial_depth_action, FinalAction p_final_depth_action, uint32_t p_view_count, Vector<TextureSamples> *r_samples) {
// Set up dependencies from/to external equivalent to the default (implicit) one, and then amend them
const VkPipelineStageFlags default_access_mask = VK_ACCESS_INPUT_ATTACHMENT_READ_BIT |
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT |
@@ -3262,27 +3257,31 @@ VkRenderPass RenderingDeviceVulkan::_render_pass_create(const Vector<AttachmentF
{ 0, VK_SUBPASS_EXTERNAL, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, default_access_mask, 0, 0 } };
VkSubpassDependency &dependency_from_external = dependencies[0];
VkSubpassDependency &dependency_to_external = dependencies[1];
+ LocalVector<int32_t> attachment_last_pass;
+ attachment_last_pass.resize(p_attachments.size());
- for (int i = 0; i < p_format.size(); i++) {
- ERR_FAIL_INDEX_V(p_format[i].format, DATA_FORMAT_MAX, VK_NULL_HANDLE);
- ERR_FAIL_INDEX_V(p_format[i].samples, TEXTURE_SAMPLES_MAX, VK_NULL_HANDLE);
- ERR_FAIL_COND_V_MSG(!(p_format[i].usage_flags & (TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | TEXTURE_USAGE_RESOLVE_ATTACHMENT_BIT)),
+ Vector<VkAttachmentDescription> attachments;
+
+ for (int i = 0; i < p_attachments.size(); i++) {
+ ERR_FAIL_INDEX_V(p_attachments[i].format, DATA_FORMAT_MAX, VK_NULL_HANDLE);
+ ERR_FAIL_INDEX_V(p_attachments[i].samples, TEXTURE_SAMPLES_MAX, VK_NULL_HANDLE);
+ ERR_FAIL_COND_V_MSG(!(p_attachments[i].usage_flags & (TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | TEXTURE_USAGE_RESOLVE_ATTACHMENT_BIT)),
VK_NULL_HANDLE, "Texture format for index (" + itos(i) + ") requires an attachment (depth, stencil or resolve) bit set.");
VkAttachmentDescription description = {};
description.flags = 0;
- description.format = vulkan_formats[p_format[i].format];
- description.samples = rasterization_sample_count[p_format[i].samples];
+ description.format = vulkan_formats[p_attachments[i].format];
+ description.samples = rasterization_sample_count[p_attachments[i].samples];
- bool is_depth_stencil = p_format[i].usage_flags & TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
- bool is_sampled = p_format[i].usage_flags & TEXTURE_USAGE_SAMPLING_BIT;
- bool is_storage = p_format[i].usage_flags & TEXTURE_USAGE_STORAGE_BIT;
+ bool is_sampled = p_attachments[i].usage_flags & TEXTURE_USAGE_SAMPLING_BIT;
+ bool is_storage = p_attachments[i].usage_flags & TEXTURE_USAGE_STORAGE_BIT;
+ bool is_depth = p_attachments[i].usage_flags & TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
// For each UNDEFINED, assume the prior use was a *read*, as we'd be discarding the output of a write
// Also, each UNDEFINED will do an immediate layout transition (write), s.t. we must ensure execution synchronization vs.
// the read. If this is a performance issue, one could track the actual last accessor of each resource, adding only that
// stage
- switch (is_depth_stencil ? p_initial_depth_action : p_initial_color_action) {
+ switch (is_depth ? p_initial_depth_action : p_initial_action) {
case INITIAL_ACTION_CLEAR_REGION:
case INITIAL_ACTION_CLEAR: {
description.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
@@ -3291,11 +3290,11 @@ VkRenderPass RenderingDeviceVulkan::_render_pass_create(const Vector<AttachmentF
dependency_from_external.srcStageMask |= reading_stages;
} break;
case INITIAL_ACTION_KEEP: {
- if (p_format[i].usage_flags & TEXTURE_USAGE_COLOR_ATTACHMENT_BIT) {
+ if (p_attachments[i].usage_flags & TEXTURE_USAGE_COLOR_ATTACHMENT_BIT) {
description.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
description.initialLayout = is_sampled ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : (is_storage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
- } else if (p_format[i].usage_flags & TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) {
+ } else if (p_attachments[i].usage_flags & TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) {
description.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
description.initialLayout = is_sampled ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : (is_storage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
@@ -3308,11 +3307,11 @@ VkRenderPass RenderingDeviceVulkan::_render_pass_create(const Vector<AttachmentF
}
} break;
case INITIAL_ACTION_DROP: {
- if (p_format[i].usage_flags & TEXTURE_USAGE_COLOR_ATTACHMENT_BIT) {
+ if (p_attachments[i].usage_flags & TEXTURE_USAGE_COLOR_ATTACHMENT_BIT) {
description.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
description.initialLayout = is_sampled ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : (is_storage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
- } else if (p_format[i].usage_flags & TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) {
+ } else if (p_attachments[i].usage_flags & TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) {
description.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
description.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; //don't care what is there
description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
@@ -3326,11 +3325,11 @@ VkRenderPass RenderingDeviceVulkan::_render_pass_create(const Vector<AttachmentF
} break;
case INITIAL_ACTION_CLEAR_REGION_CONTINUE:
case INITIAL_ACTION_CONTINUE: {
- if (p_format[i].usage_flags & TEXTURE_USAGE_COLOR_ATTACHMENT_BIT) {
+ if (p_attachments[i].usage_flags & TEXTURE_USAGE_COLOR_ATTACHMENT_BIT) {
description.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
description.initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
- } else if (p_format[i].usage_flags & TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) {
+ } else if (p_attachments[i].usage_flags & TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) {
description.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
description.initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
@@ -3346,14 +3345,14 @@ VkRenderPass RenderingDeviceVulkan::_render_pass_create(const Vector<AttachmentF
}
}
- switch (is_depth_stencil ? p_final_depth_action : p_final_color_action) {
+ switch (is_depth ? p_final_depth_action : p_final_action) {
case FINAL_ACTION_READ: {
- if (p_format[i].usage_flags & TEXTURE_USAGE_COLOR_ATTACHMENT_BIT) {
+ if (p_attachments[i].usage_flags & TEXTURE_USAGE_COLOR_ATTACHMENT_BIT) {
description.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
description.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
description.finalLayout = is_sampled ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : (is_storage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
update_external_dependency_for_store(dependency_to_external, is_sampled, is_storage, false);
- } else if (p_format[i].usage_flags & TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) {
+ } else if (p_attachments[i].usage_flags & TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) {
description.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
description.stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE;
description.finalLayout = is_sampled ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : (is_storage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
@@ -3366,11 +3365,11 @@ VkRenderPass RenderingDeviceVulkan::_render_pass_create(const Vector<AttachmentF
}
} break;
case FINAL_ACTION_DISCARD: {
- if (p_format[i].usage_flags & TEXTURE_USAGE_COLOR_ATTACHMENT_BIT) {
+ if (p_attachments[i].usage_flags & TEXTURE_USAGE_COLOR_ATTACHMENT_BIT) {
description.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
description.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
description.finalLayout = is_sampled ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : (is_storage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
- } else if (p_format[i].usage_flags & TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) {
+ } else if (p_attachments[i].usage_flags & TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) {
description.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
description.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
description.finalLayout = is_sampled ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : (is_storage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
@@ -3381,11 +3380,11 @@ VkRenderPass RenderingDeviceVulkan::_render_pass_create(const Vector<AttachmentF
}
} break;
case FINAL_ACTION_CONTINUE: {
- if (p_format[i].usage_flags & TEXTURE_USAGE_COLOR_ATTACHMENT_BIT) {
+ if (p_attachments[i].usage_flags & TEXTURE_USAGE_COLOR_ATTACHMENT_BIT) {
description.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
description.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
description.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
- } else if (p_format[i].usage_flags & TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) {
+ } else if (p_attachments[i].usage_flags & TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) {
description.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
description.stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE;
description.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
@@ -3401,27 +3400,191 @@ VkRenderPass RenderingDeviceVulkan::_render_pass_create(const Vector<AttachmentF
}
}
+ attachment_last_pass[i] = -1;
+
attachments.push_back(description);
+ }
+
+ LocalVector<VkSubpassDescription> subpasses;
+ LocalVector<LocalVector<VkAttachmentReference>> color_reference_array;
+ LocalVector<LocalVector<VkAttachmentReference>> input_reference_array;
+ LocalVector<LocalVector<VkAttachmentReference>> resolve_reference_array;
+ LocalVector<LocalVector<uint32_t>> preserve_reference_array;
+ LocalVector<VkAttachmentReference> depth_reference_array;
- VkAttachmentReference reference;
- reference.attachment = i;
+ subpasses.resize(p_passes.size());
+ color_reference_array.resize(p_passes.size());
+ input_reference_array.resize(p_passes.size());
+ resolve_reference_array.resize(p_passes.size());
+ preserve_reference_array.resize(p_passes.size());
+ depth_reference_array.resize(p_passes.size());
- if (p_format[i].usage_flags & TEXTURE_USAGE_COLOR_ATTACHMENT_BIT) {
- reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+ LocalVector<VkSubpassDependency> subpass_dependencies;
+
+ for (int i = 0; i < p_passes.size(); i++) {
+ const FramebufferPass *pass = &p_passes[i];
+
+ LocalVector<VkAttachmentReference> &color_references = color_reference_array[i];
+
+ TextureSamples texture_samples = TEXTURE_SAMPLES_1;
+ bool is_multisample_first = true;
+
+ for (int j = 0; j < pass->color_attachments.size(); j++) {
+ int32_t attachment = pass->color_attachments[j];
+ VkAttachmentReference reference;
+ if (attachment == FramebufferPass::ATTACHMENT_UNUSED) {
+ reference.attachment = VK_ATTACHMENT_UNUSED;
+ reference.layout = VK_IMAGE_LAYOUT_UNDEFINED;
+ } else {
+ ERR_FAIL_INDEX_V_MSG(attachment, p_attachments.size(), VK_NULL_HANDLE, "Invalid framebuffer format attachment(" + itos(attachment) + "), in pass (" + itos(i) + "), color attachment (" + itos(j) + ").");
+ ERR_FAIL_COND_V_MSG(!(p_attachments[attachment].usage_flags & TEXTURE_USAGE_COLOR_ATTACHMENT_BIT), VK_NULL_HANDLE, "Invalid framebuffer format attachment(" + itos(attachment) + "), in pass (" + itos(i) + "), it's marked as depth, but it's not usable as color attachment.");
+ ERR_FAIL_COND_V_MSG(attachment_last_pass[attachment] == i, VK_NULL_HANDLE, "Invalid framebuffer format attachment(" + itos(attachment) + "), in pass (" + itos(i) + "), it already was used for something else before in this pass.");
+
+ if (is_multisample_first) {
+ texture_samples = p_attachments[attachment].samples;
+ is_multisample_first = false;
+ } else {
+ ERR_FAIL_COND_V_MSG(texture_samples != p_attachments[attachment].samples, VK_NULL_HANDLE, "Invalid framebuffer format attachment(" + itos(attachment) + "), in pass (" + itos(i) + "), if an attachment is marked as multisample, all of them should be multisample and use the same number of samples.");
+ }
+ reference.attachment = attachment;
+ reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+ attachment_last_pass[attachment] = i;
+ }
color_references.push_back(reference);
- } else if (p_format[i].usage_flags & TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) {
- reference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
- depth_stencil_references.push_back(reference);
- } else if (p_format[i].usage_flags & TEXTURE_USAGE_RESOLVE_ATTACHMENT_BIT) {
- reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+ }
+
+ LocalVector<VkAttachmentReference> &input_references = input_reference_array[i];
+
+ for (int j = 0; j < pass->input_attachments.size(); j++) {
+ int32_t attachment = pass->input_attachments[j];
+ VkAttachmentReference reference;
+ if (attachment == FramebufferPass::ATTACHMENT_UNUSED) {
+ reference.attachment = VK_ATTACHMENT_UNUSED;
+ reference.layout = VK_IMAGE_LAYOUT_UNDEFINED;
+ } else {
+ ERR_FAIL_INDEX_V_MSG(attachment, p_attachments.size(), VK_NULL_HANDLE, "Invalid framebuffer format attachment(" + itos(attachment) + "), in pass (" + itos(i) + "), input attachment (" + itos(j) + ").");
+ ERR_FAIL_COND_V_MSG(!(p_attachments[attachment].usage_flags & TEXTURE_USAGE_COLOR_ATTACHMENT_BIT), VK_NULL_HANDLE, "Invalid framebuffer format attachment(" + itos(attachment) + "), in pass (" + itos(i) + "), it's marked as depth, but it's not usable as input attachment.");
+ ERR_FAIL_COND_V_MSG(attachment_last_pass[attachment] == i, VK_NULL_HANDLE, "Invalid framebuffer format attachment(" + itos(attachment) + "), in pass (" + itos(i) + "), it already was used for something else before in this pass.");
+ reference.attachment = attachment;
+ reference.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+ attachment_last_pass[attachment] = i;
+ }
+ input_references.push_back(reference);
+ }
+
+ LocalVector<VkAttachmentReference> &resolve_references = resolve_reference_array[i];
+
+ if (pass->resolve_attachments.size() > 0) {
+ ERR_FAIL_COND_V_MSG(pass->resolve_attachments.size() != pass->color_attachments.size(), VK_NULL_HANDLE, "The amount of resolve attachments (" + itos(pass->resolve_attachments.size()) + ") must match the number of color attachments (" + itos(pass->color_attachments.size()) + ").");
+ ERR_FAIL_COND_V_MSG(texture_samples == TEXTURE_SAMPLES_1, VK_NULL_HANDLE, "Resolve attachments specified, but color attachments are not multisample.");
+ }
+ for (int j = 0; j < pass->resolve_attachments.size(); j++) {
+ int32_t attachment = pass->resolve_attachments[j];
+ VkAttachmentReference reference;
+ if (attachment == FramebufferPass::ATTACHMENT_UNUSED) {
+ reference.attachment = VK_ATTACHMENT_UNUSED;
+ reference.layout = VK_IMAGE_LAYOUT_UNDEFINED;
+ } else {
+ ERR_FAIL_INDEX_V_MSG(attachment, p_attachments.size(), VK_NULL_HANDLE, "Invalid framebuffer format attachment(" + itos(attachment) + "), in pass (" + itos(i) + "), resolve attachment (" + itos(j) + ").");
+ ERR_FAIL_COND_V_MSG(pass->color_attachments[j] == FramebufferPass::ATTACHMENT_UNUSED, VK_NULL_HANDLE, "Invalid framebuffer format attachment(" + itos(attachment) + "), in pass (" + itos(i) + "), resolve attachment (" + itos(j) + "), the respective color attachment is marked as unused.");
+ ERR_FAIL_COND_V_MSG(!(p_attachments[attachment].usage_flags & TEXTURE_USAGE_COLOR_ATTACHMENT_BIT), VK_NULL_HANDLE, "Invalid framebuffer format attachment(" + itos(attachment) + "), in pass (" + itos(i) + "), it's marked as depth, but it's not usable as resolve attachment.");
+ ERR_FAIL_COND_V_MSG(attachment_last_pass[attachment] == i, VK_NULL_HANDLE, "Invalid framebuffer format attachment(" + itos(attachment) + "), in pass (" + itos(i) + "), it already was used for something else before in this pass.");
+ bool multisample = p_attachments[attachment].samples > TEXTURE_SAMPLES_1;
+ ERR_FAIL_COND_V_MSG(multisample, VK_NULL_HANDLE, "Invalid framebuffer format attachment(" + itos(attachment) + "), in pass (" + itos(i) + "), resolve attachments can't be multisample.");
+ reference.attachment = attachment;
+ reference.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+ attachment_last_pass[attachment] = i;
+ }
resolve_references.push_back(reference);
- // if resolves are done, we need to ensure the copy is safe
- dependency_to_external.dstStageMask |= VK_PIPELINE_STAGE_TRANSFER_BIT;
- dependency_to_external.dstAccessMask |= VK_ACCESS_TRANSFER_READ_BIT;
+ }
+
+ LocalVector<uint32_t> &preserve_references = preserve_reference_array[i];
+
+ for (int j = 0; j < pass->preserve_attachments.size(); j++) {
+ int32_t attachment = pass->preserve_attachments[j];
+
+ ERR_FAIL_COND_V_MSG(attachment == FramebufferPass::ATTACHMENT_UNUSED, VK_NULL_HANDLE, "Invalid framebuffer format attachment(" + itos(attachment) + "), in pass (" + itos(i) + "), preserve attachment (" + itos(j) + "). Preserve attachments can't be unused.");
+
+ ERR_FAIL_INDEX_V_MSG(attachment, p_attachments.size(), VK_NULL_HANDLE, "Invalid framebuffer format attachment(" + itos(attachment) + "), in pass (" + itos(i) + "), preserve attachment (" + itos(j) + ").");
+ ERR_FAIL_COND_V_MSG(attachment_last_pass[attachment] == i, VK_NULL_HANDLE, "Invalid framebuffer format attachment(" + itos(attachment) + "), in pass (" + itos(i) + "), it already was used for something else before in this pass.");
+
+ attachment_last_pass[attachment] = i;
+
+ preserve_references.push_back(attachment);
+ }
+
+ VkAttachmentReference &depth_stencil_reference = depth_reference_array[i];
+
+ if (pass->depth_attachment != FramebufferPass::ATTACHMENT_UNUSED) {
+ int32_t attachment = pass->depth_attachment;
+ ERR_FAIL_INDEX_V_MSG(attachment, p_attachments.size(), VK_NULL_HANDLE, "Invalid framebuffer depth format attachment(" + itos(attachment) + "), in pass (" + itos(i) + "), depth attachment.");
+ ERR_FAIL_COND_V_MSG(!(p_attachments[attachment].usage_flags & TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT), VK_NULL_HANDLE, "Invalid framebuffer depth format attachment(" + itos(attachment) + "), in pass (" + itos(i) + "), it's marked as depth, but it's not a depth attachment.");
+ ERR_FAIL_COND_V_MSG(attachment_last_pass[attachment] == i, VK_NULL_HANDLE, "Invalid framebuffer depth format attachment(" + itos(attachment) + "), in pass (" + itos(i) + "), it already was used for something else before in this pass.");
+ depth_stencil_reference.attachment = attachment;
+ depth_stencil_reference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+ attachment_last_pass[attachment] = i;
+
+ if (!is_multisample_first) {
+ ERR_FAIL_COND_V_MSG(texture_samples != p_attachments[attachment].samples, VK_NULL_HANDLE, "Invalid framebuffer depth format attachment(" + itos(attachment) + "), in pass (" + itos(i) + "), if an attachment is marked as multisample, all of them should be multisample and use the same number of samples including the depth.");
+ }
+
+ } else {
+ depth_stencil_reference.attachment = VK_ATTACHMENT_UNUSED;
+ depth_stencil_reference.layout = VK_IMAGE_LAYOUT_UNDEFINED;
+ }
+
+ VkSubpassDescription &subpass = subpasses[i];
+ subpass.flags = 0;
+ subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+ subpass.inputAttachmentCount = input_references.size();
+ if (input_references.size()) {
+ subpass.pInputAttachments = input_references.ptr();
+ } else {
+ subpass.pInputAttachments = nullptr;
+ }
+ subpass.colorAttachmentCount = color_references.size();
+ if (color_references.size()) {
+ subpass.pColorAttachments = color_references.ptr();
+ } else {
+ subpass.pColorAttachments = nullptr;
+ }
+ if (depth_stencil_reference.attachment != VK_ATTACHMENT_UNUSED) {
+ subpass.pDepthStencilAttachment = &depth_stencil_reference;
} else {
- ERR_FAIL_V_MSG(VK_NULL_HANDLE, "Texture index " + itos(i) + " is neither color, depth stencil or resolve so it can't be used as attachment.");
+ subpass.pDepthStencilAttachment = nullptr;
}
+ if (resolve_references.size()) {
+ subpass.pResolveAttachments = resolve_references.ptr();
+ } else {
+ subpass.pResolveAttachments = nullptr;
+ }
+
+ subpass.preserveAttachmentCount = preserve_references.size();
+ if (preserve_references.size()) {
+ subpass.pPreserveAttachments = preserve_references.ptr();
+ } else {
+ subpass.pPreserveAttachments = nullptr;
+ }
+
+ if (r_samples) {
+ r_samples->push_back(texture_samples);
+ }
+
+ if (i > 0) {
+ VkSubpassDependency dependency;
+ dependency.srcSubpass = i - 1;
+ dependency.dstSubpass = i;
+ dependency.srcStageMask = 0;
+ dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
+ dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+
+ dependency.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
+ dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_INPUT_ATTACHMENT_READ_BIT;
+ dependency.dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+ subpass_dependencies.push_back(dependency);
+ }
+ /*
// NOTE: Big Mallet Approach -- any layout transition causes a full barrier
if (reference.layout != description.initialLayout) {
// NOTE: this should be smarter based on the texture's knowledge of its previous role
@@ -3433,41 +3596,28 @@ VkRenderPass RenderingDeviceVulkan::_render_pass_create(const Vector<AttachmentF
dependency_to_external.dstStageMask |= VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
dependency_to_external.dstAccessMask |= VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
}
+ */
}
- ERR_FAIL_COND_V_MSG(depth_stencil_references.size() > 1, VK_NULL_HANDLE,
- "Formats can only have one depth/stencil attachment, supplied (" + itos(depth_stencil_references.size()) + ").");
-
- ERR_FAIL_COND_V_MSG(resolve_references.size() > 1, VK_NULL_HANDLE,
- "Formats can only have one resolve attachment, supplied (" + itos(resolve_references.size()) + ").");
-
- VkSubpassDescription subpass;
- subpass.flags = 0;
- subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
- subpass.inputAttachmentCount = 0; //unsupported for now
- subpass.pInputAttachments = nullptr;
- subpass.colorAttachmentCount = color_references.size();
- subpass.pColorAttachments = color_references.ptr();
- subpass.pDepthStencilAttachment = depth_stencil_references.ptr();
- subpass.pResolveAttachments = resolve_references.ptr();
- subpass.preserveAttachmentCount = 0;
- subpass.pPreserveAttachments = nullptr;
-
VkRenderPassCreateInfo render_pass_create_info;
render_pass_create_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
render_pass_create_info.pNext = nullptr;
render_pass_create_info.flags = 0;
render_pass_create_info.attachmentCount = attachments.size();
render_pass_create_info.pAttachments = attachments.ptr();
- render_pass_create_info.subpassCount = 1;
- render_pass_create_info.pSubpasses = &subpass;
+ render_pass_create_info.subpassCount = subpasses.size();
+ render_pass_create_info.pSubpasses = subpasses.ptr();
// Commenting this because it seems it just avoids raster and compute to work at the same time.
// Other barriers seem to be protecting the render pass fine.
// render_pass_create_info.dependencyCount = 2;
// render_pass_create_info.pDependencies = dependencies;
- render_pass_create_info.dependencyCount = 0;
- render_pass_create_info.pDependencies = nullptr;
+ render_pass_create_info.dependencyCount = subpass_dependencies.size();
+ if (subpass_dependencies.size()) {
+ render_pass_create_info.pDependencies = subpass_dependencies.ptr();
+ } else {
+ render_pass_create_info.pDependencies = nullptr;
+ }
const uint32_t view_mask = (1 << p_view_count) - 1;
const uint32_t correlation_mask = (1 << p_view_count) - 1;
@@ -3498,17 +3648,29 @@ VkRenderPass RenderingDeviceVulkan::_render_pass_create(const Vector<AttachmentF
VkResult res = vkCreateRenderPass(device, &render_pass_create_info, nullptr, &render_pass);
ERR_FAIL_COND_V_MSG(res, VK_NULL_HANDLE, "vkCreateRenderPass failed with error " + itos(res) + ".");
- if (r_color_attachment_count) {
- *r_color_attachment_count = color_references.size();
- }
return render_pass;
}
RenderingDevice::FramebufferFormatID RenderingDeviceVulkan::framebuffer_format_create(const Vector<AttachmentFormat> &p_format, uint32_t p_view_count) {
+ FramebufferPass pass;
+ for (int i = 0; i < p_format.size(); i++) {
+ if (p_format[i].usage_flags & TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) {
+ pass.depth_attachment = i;
+ } else {
+ pass.color_attachments.push_back(i);
+ }
+ }
+
+ Vector<FramebufferPass> passes;
+ passes.push_back(pass);
+ return framebuffer_format_create_multipass(p_format, passes, p_view_count);
+}
+RenderingDevice::FramebufferFormatID RenderingDeviceVulkan::framebuffer_format_create_multipass(const Vector<AttachmentFormat> &p_attachments, Vector<FramebufferPass> &p_passes, uint32_t p_view_count) {
_THREAD_SAFE_METHOD_
FramebufferFormatKey key;
- key.attachments = p_format;
+ key.attachments = p_attachments;
+ key.passes = p_passes;
key.view_count = p_view_count;
const Map<FramebufferFormatKey, FramebufferFormatID>::Element *E = framebuffer_format_cache.find(key);
@@ -3517,8 +3679,8 @@ RenderingDevice::FramebufferFormatID RenderingDeviceVulkan::framebuffer_format_c
return E->get();
}
- int color_references;
- VkRenderPass render_pass = _render_pass_create(p_format, INITIAL_ACTION_CLEAR, FINAL_ACTION_READ, INITIAL_ACTION_CLEAR, FINAL_ACTION_READ, &color_references, p_view_count); //actions don't matter for this use case
+ Vector<TextureSamples> samples;
+ VkRenderPass render_pass = _render_pass_create(p_attachments, p_passes, INITIAL_ACTION_CLEAR, FINAL_ACTION_READ, INITIAL_ACTION_CLEAR, FINAL_ACTION_READ, p_view_count, &samples); //actions don't matter for this use case
if (render_pass == VK_NULL_HANDLE) { //was likely invalid
return INVALID_ID;
@@ -3528,9 +3690,8 @@ RenderingDevice::FramebufferFormatID RenderingDeviceVulkan::framebuffer_format_c
E = framebuffer_format_cache.insert(key, id);
FramebufferFormat fb_format;
fb_format.E = E;
- fb_format.color_attachments = color_references;
fb_format.render_pass = render_pass;
- fb_format.samples = p_format[0].samples;
+ fb_format.pass_samples = samples;
fb_format.view_count = p_view_count;
framebuffer_formats[id] = fb_format;
return id;
@@ -3538,6 +3699,7 @@ RenderingDevice::FramebufferFormatID RenderingDeviceVulkan::framebuffer_format_c
RenderingDevice::FramebufferFormatID RenderingDeviceVulkan::framebuffer_format_create_empty(TextureSamples p_samples) {
FramebufferFormatKey key;
+ key.passes.push_back(FramebufferPass());
const Map<FramebufferFormatKey, FramebufferFormatID>::Element *E = framebuffer_format_cache.find(key);
if (E) {
@@ -3583,18 +3745,18 @@ RenderingDevice::FramebufferFormatID RenderingDeviceVulkan::framebuffer_format_c
FramebufferFormat fb_format;
fb_format.E = E;
- fb_format.color_attachments = 0;
fb_format.render_pass = render_pass;
- fb_format.samples = p_samples;
+ fb_format.pass_samples.push_back(p_samples);
framebuffer_formats[id] = fb_format;
return id;
}
-RenderingDevice::TextureSamples RenderingDeviceVulkan::framebuffer_format_get_texture_samples(FramebufferFormatID p_format) {
+RenderingDevice::TextureSamples RenderingDeviceVulkan::framebuffer_format_get_texture_samples(FramebufferFormatID p_format, uint32_t p_pass) {
Map<FramebufferFormatID, FramebufferFormat>::Element *E = framebuffer_formats.find(p_format);
ERR_FAIL_COND_V(!E, TEXTURE_SAMPLES_1);
+ ERR_FAIL_COND_V(p_pass >= uint32_t(E->get().pass_samples.size()), TEXTURE_SAMPLES_1);
- return E->get().samples;
+ return E->get().pass_samples[p_pass];
}
/***********************/
@@ -3615,6 +3777,30 @@ RID RenderingDeviceVulkan::framebuffer_create_empty(const Size2i &p_size, Textur
RID RenderingDeviceVulkan::framebuffer_create(const Vector<RID> &p_texture_attachments, FramebufferFormatID p_format_check, uint32_t p_view_count) {
_THREAD_SAFE_METHOD_
+ FramebufferPass pass;
+
+ for (int i = 0; i < p_texture_attachments.size(); i++) {
+ Texture *texture = texture_owner.getornull(p_texture_attachments[i]);
+ ERR_FAIL_COND_V_MSG(!texture, RID(), "Texture index supplied for framebuffer (" + itos(i) + ") is not a valid texture.");
+
+ ERR_FAIL_COND_V_MSG(texture->layers != p_view_count, RID(), "Layers of our texture doesn't match view count for this framebuffer");
+
+ if (texture->usage_flags & TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) {
+ pass.depth_attachment = i;
+ } else {
+ pass.color_attachments.push_back(i);
+ }
+ }
+
+ Vector<FramebufferPass> passes;
+ passes.push_back(pass);
+
+ return framebuffer_create_multipass(p_texture_attachments, passes, p_format_check, p_view_count);
+}
+
+RID RenderingDeviceVulkan::framebuffer_create_multipass(const Vector<RID> &p_texture_attachments, Vector<FramebufferPass> &p_passes, FramebufferFormatID p_format_check, uint32_t p_view_count) {
+ _THREAD_SAFE_METHOD_
+
Vector<AttachmentFormat> attachments;
Size2i size;
@@ -3639,7 +3825,7 @@ RID RenderingDeviceVulkan::framebuffer_create(const Vector<RID> &p_texture_attac
attachments.push_back(af);
}
- FramebufferFormatID format_id = framebuffer_format_create(attachments, p_view_count);
+ FramebufferFormatID format_id = framebuffer_format_create_multipass(attachments, p_passes, p_view_count);
if (format_id == INVALID_ID) {
return RID();
}
@@ -4401,8 +4587,9 @@ RID RenderingDeviceVulkan::shader_create(const Vector<ShaderStageData> &p_stages
"Reflection of SPIR-V shader stage '" + String(shader_stage_names[p_stages[i].shader_stage]) + "' failed obtaining output variables.");
for (uint32_t j = 0; j < ov_count; j++) {
- if (output_vars[j]) {
- fragment_outputs = MAX(fragment_outputs, output_vars[j]->location + 1);
+ const SpvReflectInterfaceVariable *refvar = output_vars[j];
+ if (refvar != nullptr && refvar->built_in != SpvBuiltInFragDepth) {
+ fragment_outputs |= 1 << refvar->location;
}
}
}
@@ -4453,7 +4640,7 @@ RID RenderingDeviceVulkan::shader_create(const Vector<ShaderStageData> &p_stages
Shader shader;
shader.vertex_input_mask = vertex_input_mask;
- shader.fragment_outputs = fragment_outputs;
+ shader.fragment_output_mask = fragment_outputs;
shader.push_constant = push_constant;
shader.is_compute = is_compute;
shader.compute_local_size[0] = compute_local_size[0];
@@ -5194,6 +5381,49 @@ RID RenderingDeviceVulkan::uniform_set_create(const Vector<Uniform> &p_uniforms,
write.pTexelBufferView = nullptr;
} break;
case UNIFORM_TYPE_INPUT_ATTACHMENT: {
+ ERR_FAIL_COND_V_MSG(shader->is_compute, RID(), "InputAttachment (binding: " + itos(uniform.binding) + ") supplied for compute shader (this is not allowed).");
+
+ if (uniform.ids.size() != set_uniform.length) {
+ if (set_uniform.length > 1) {
+ ERR_FAIL_V_MSG(RID(), "InputAttachment (binding: " + itos(uniform.binding) + ") is an array of (" + itos(set_uniform.length) + ") textures, so it should be provided equal number of texture IDs to satisfy it (IDs provided: " + itos(uniform.ids.size()) + ").");
+ } else {
+ ERR_FAIL_V_MSG(RID(), "InputAttachment (binding: " + itos(uniform.binding) + ") should provide one ID referencing a texture (IDs provided: " + itos(uniform.ids.size()) + ").");
+ }
+ }
+
+ Vector<VkDescriptorImageInfo> image_info;
+
+ for (int j = 0; j < uniform.ids.size(); j++) {
+ Texture *texture = texture_owner.getornull(uniform.ids[j]);
+
+ ERR_FAIL_COND_V_MSG(!texture, RID(),
+ "InputAttachment (binding: " + itos(uniform.binding) + ", index " + itos(j) + ") is not a valid texture.");
+
+ ERR_FAIL_COND_V_MSG(!(texture->usage_flags & TEXTURE_USAGE_SAMPLING_BIT), RID(),
+ "InputAttachment (binding: " + itos(uniform.binding) + ", index " + itos(j) + ") needs the TEXTURE_USAGE_SAMPLING_BIT usage flag set in order to be used as uniform.");
+
+ VkDescriptorImageInfo img_info;
+ img_info.sampler = VK_NULL_HANDLE;
+ img_info.imageView = texture->view;
+
+ if (texture->owner.is_valid()) {
+ texture = texture_owner.getornull(texture->owner);
+ ERR_FAIL_COND_V(!texture, RID()); //bug, should never happen
+ }
+
+ img_info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+
+ image_info.push_back(img_info);
+ }
+
+ write.dstArrayElement = 0;
+ write.descriptorCount = uniform.ids.size();
+ write.descriptorType = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT;
+ write.pImageInfo = image_infos.push_back(image_info)->get().ptr();
+ write.pBufferInfo = nullptr;
+ write.pTexelBufferView = nullptr;
+
+ type_size = uniform.ids.size();
} break;
default: {
}
@@ -5404,7 +5634,7 @@ Vector<uint8_t> RenderingDeviceVulkan::buffer_get_data(RID p_buffer) {
/**** RENDER PIPELINE ****/
/*************************/
-RID RenderingDeviceVulkan::render_pipeline_create(RID p_shader, FramebufferFormatID p_framebuffer_format, VertexFormatID p_vertex_format, RenderPrimitive p_render_primitive, const PipelineRasterizationState &p_rasterization_state, const PipelineMultisampleState &p_multisample_state, const PipelineDepthStencilState &p_depth_stencil_state, const PipelineColorBlendState &p_blend_state, int p_dynamic_state_flags) {
+RID RenderingDeviceVulkan::render_pipeline_create(RID p_shader, FramebufferFormatID p_framebuffer_format, VertexFormatID p_vertex_format, RenderPrimitive p_render_primitive, const PipelineRasterizationState &p_rasterization_state, const PipelineMultisampleState &p_multisample_state, const PipelineDepthStencilState &p_depth_stencil_state, const PipelineColorBlendState &p_blend_state, int p_dynamic_state_flags, uint32_t p_for_render_pass) {
_THREAD_SAFE_METHOD_
//needs a shader
@@ -5423,8 +5653,16 @@ RID RenderingDeviceVulkan::render_pipeline_create(RID p_shader, FramebufferForma
{ //validate shader vs framebuffer
- ERR_FAIL_COND_V_MSG(shader->fragment_outputs != fb_format.color_attachments, RID(),
- "Mismatch fragment output bindings (" + itos(shader->fragment_outputs) + ") and framebuffer color buffers (" + itos(fb_format.color_attachments) + ") when binding both in render pipeline.");
+ ERR_FAIL_COND_V_MSG(p_for_render_pass >= uint32_t(fb_format.E->key().passes.size()), RID(), "Render pass requested for pipeline creation (" + itos(p_for_render_pass) + ") is out of bounds");
+ const FramebufferPass &pass = fb_format.E->key().passes[p_for_render_pass];
+ uint32_t output_mask = 0;
+ for (int i = 0; i < pass.color_attachments.size(); i++) {
+ if (pass.color_attachments[i] != FramebufferPass::ATTACHMENT_UNUSED) {
+ output_mask |= 1 << i;
+ }
+ }
+ ERR_FAIL_COND_V_MSG(shader->fragment_output_mask != output_mask, RID(),
+ "Mismatch fragment shader output mask (" + itos(shader->fragment_output_mask) + ") and framebuffer color output mask (" + itos(output_mask) + ") when binding both in render pipeline.");
}
//vertex
VkPipelineVertexInputStateCreateInfo pipeline_vertex_input_state_create_info;
@@ -5609,44 +5847,53 @@ RID RenderingDeviceVulkan::render_pipeline_create(RID p_shader, FramebufferForma
ERR_FAIL_INDEX_V(p_blend_state.logic_op, LOGIC_OP_MAX, RID());
color_blend_state_create_info.logicOp = logic_operations[p_blend_state.logic_op];
- ERR_FAIL_COND_V(fb_format.color_attachments != p_blend_state.attachments.size(), RID());
-
Vector<VkPipelineColorBlendAttachmentState> attachment_states;
+ {
+ const FramebufferPass &pass = fb_format.E->key().passes[p_for_render_pass];
+
+ for (int i = 0; i < pass.color_attachments.size(); i++) {
+ if (pass.color_attachments[i] != FramebufferPass::ATTACHMENT_UNUSED) {
+ int idx = attachment_states.size();
+
+ ERR_FAIL_INDEX_V(idx, p_blend_state.attachments.size(), RID());
+ VkPipelineColorBlendAttachmentState state;
+ state.blendEnable = p_blend_state.attachments[idx].enable_blend;
+
+ ERR_FAIL_INDEX_V(p_blend_state.attachments[idx].src_color_blend_factor, BLEND_FACTOR_MAX, RID());
+ state.srcColorBlendFactor = blend_factors[p_blend_state.attachments[idx].src_color_blend_factor];
+ ERR_FAIL_INDEX_V(p_blend_state.attachments[idx].dst_color_blend_factor, BLEND_FACTOR_MAX, RID());
+ state.dstColorBlendFactor = blend_factors[p_blend_state.attachments[idx].dst_color_blend_factor];
+ ERR_FAIL_INDEX_V(p_blend_state.attachments[idx].color_blend_op, BLEND_OP_MAX, RID());
+ state.colorBlendOp = blend_operations[p_blend_state.attachments[idx].color_blend_op];
+
+ ERR_FAIL_INDEX_V(p_blend_state.attachments[idx].src_alpha_blend_factor, BLEND_FACTOR_MAX, RID());
+ state.srcAlphaBlendFactor = blend_factors[p_blend_state.attachments[idx].src_alpha_blend_factor];
+ ERR_FAIL_INDEX_V(p_blend_state.attachments[idx].dst_alpha_blend_factor, BLEND_FACTOR_MAX, RID());
+ state.dstAlphaBlendFactor = blend_factors[p_blend_state.attachments[idx].dst_alpha_blend_factor];
+ ERR_FAIL_INDEX_V(p_blend_state.attachments[idx].alpha_blend_op, BLEND_OP_MAX, RID());
+ state.alphaBlendOp = blend_operations[p_blend_state.attachments[idx].alpha_blend_op];
+
+ state.colorWriteMask = 0;
+ if (p_blend_state.attachments[idx].write_r) {
+ state.colorWriteMask |= VK_COLOR_COMPONENT_R_BIT;
+ }
+ if (p_blend_state.attachments[idx].write_g) {
+ state.colorWriteMask |= VK_COLOR_COMPONENT_G_BIT;
+ }
+ if (p_blend_state.attachments[idx].write_b) {
+ state.colorWriteMask |= VK_COLOR_COMPONENT_B_BIT;
+ }
+ if (p_blend_state.attachments[idx].write_a) {
+ state.colorWriteMask |= VK_COLOR_COMPONENT_A_BIT;
+ }
- for (int i = 0; i < p_blend_state.attachments.size(); i++) {
- VkPipelineColorBlendAttachmentState state;
- state.blendEnable = p_blend_state.attachments[i].enable_blend;
-
- ERR_FAIL_INDEX_V(p_blend_state.attachments[i].src_color_blend_factor, BLEND_FACTOR_MAX, RID());
- state.srcColorBlendFactor = blend_factors[p_blend_state.attachments[i].src_color_blend_factor];
- ERR_FAIL_INDEX_V(p_blend_state.attachments[i].dst_color_blend_factor, BLEND_FACTOR_MAX, RID());
- state.dstColorBlendFactor = blend_factors[p_blend_state.attachments[i].dst_color_blend_factor];
- ERR_FAIL_INDEX_V(p_blend_state.attachments[i].color_blend_op, BLEND_OP_MAX, RID());
- state.colorBlendOp = blend_operations[p_blend_state.attachments[i].color_blend_op];
-
- ERR_FAIL_INDEX_V(p_blend_state.attachments[i].src_alpha_blend_factor, BLEND_FACTOR_MAX, RID());
- state.srcAlphaBlendFactor = blend_factors[p_blend_state.attachments[i].src_alpha_blend_factor];
- ERR_FAIL_INDEX_V(p_blend_state.attachments[i].dst_alpha_blend_factor, BLEND_FACTOR_MAX, RID());
- state.dstAlphaBlendFactor = blend_factors[p_blend_state.attachments[i].dst_alpha_blend_factor];
- ERR_FAIL_INDEX_V(p_blend_state.attachments[i].alpha_blend_op, BLEND_OP_MAX, RID());
- state.alphaBlendOp = blend_operations[p_blend_state.attachments[i].alpha_blend_op];
-
- state.colorWriteMask = 0;
- if (p_blend_state.attachments[i].write_r) {
- state.colorWriteMask |= VK_COLOR_COMPONENT_R_BIT;
- }
- if (p_blend_state.attachments[i].write_g) {
- state.colorWriteMask |= VK_COLOR_COMPONENT_G_BIT;
- }
- if (p_blend_state.attachments[i].write_b) {
- state.colorWriteMask |= VK_COLOR_COMPONENT_B_BIT;
- }
- if (p_blend_state.attachments[i].write_a) {
- state.colorWriteMask |= VK_COLOR_COMPONENT_A_BIT;
+ attachment_states.push_back(state);
+ idx++;
+ };
}
- attachment_states.push_back(state);
- };
+ ERR_FAIL_COND_V(attachment_states.size() != p_blend_state.attachments.size(), RID());
+ }
color_blend_state_create_info.attachmentCount = attachment_states.size();
color_blend_state_create_info.pAttachments = attachment_states.ptr();
@@ -5719,7 +5966,7 @@ RID RenderingDeviceVulkan::render_pipeline_create(RID p_shader, FramebufferForma
graphics_pipeline_create_info.layout = shader->pipeline_layout;
graphics_pipeline_create_info.renderPass = fb_format.render_pass;
- graphics_pipeline_create_info.subpass = 0;
+ graphics_pipeline_create_info.subpass = p_for_render_pass;
graphics_pipeline_create_info.basePipelineHandle = VK_NULL_HANDLE;
graphics_pipeline_create_info.basePipelineIndex = 0;
@@ -5736,6 +5983,7 @@ RID RenderingDeviceVulkan::render_pipeline_create(RID p_shader, FramebufferForma
#ifdef DEBUG_ENABLED
pipeline.validation.dynamic_state = p_dynamic_state_flags;
pipeline.validation.framebuffer_format = p_framebuffer_format;
+ pipeline.validation.render_pass = p_for_render_pass;
pipeline.validation.vertex_format = p_vertex_format;
pipeline.validation.uses_restart_indices = input_assembly_create_info.primitiveRestartEnable;
@@ -5874,13 +6122,14 @@ RenderingDevice::DrawListID RenderingDeviceVulkan::draw_list_begin_for_screen(Di
ERR_FAIL_COND_V_MSG(compute_list != nullptr, INVALID_ID, "Only one draw/compute list can be active at the same time.");
VkCommandBuffer command_buffer = frames[frame].draw_command_buffer;
- draw_list = memnew(DrawList);
- draw_list->command_buffer = command_buffer;
+
+ Size2i size = Size2i(context->window_get_width(p_screen), context->window_get_height(p_screen));
+
+ _draw_list_allocate(Rect2i(Vector2i(), size), 0, 0);
#ifdef DEBUG_ENABLED
- draw_list->validation.framebuffer_format = screen_get_framebuffer_format();
+ draw_list_framebuffer_format = screen_get_framebuffer_format();
#endif
- draw_list_count = 0;
- draw_list_split = false;
+ draw_list_subpass_count = 1;
VkRenderPassBeginInfo render_pass_begin;
render_pass_begin.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
@@ -5888,8 +6137,8 @@ RenderingDevice::DrawListID RenderingDeviceVulkan::draw_list_begin_for_screen(Di
render_pass_begin.renderPass = context->window_get_render_pass(p_screen);
render_pass_begin.framebuffer = context->window_get_framebuffer(p_screen);
- render_pass_begin.renderArea.extent.width = context->window_get_width(p_screen);
- render_pass_begin.renderArea.extent.height = context->window_get_height(p_screen);
+ render_pass_begin.renderArea.extent.width = size.width;
+ render_pass_begin.renderArea.extent.height = size.height;
render_pass_begin.renderArea.offset.x = 0;
render_pass_begin.renderArea.offset.y = 0;
@@ -5929,7 +6178,7 @@ RenderingDevice::DrawListID RenderingDeviceVulkan::draw_list_begin_for_screen(Di
return int64_t(ID_TYPE_DRAW_LIST) << ID_BASE_SHIFT;
}
-Error RenderingDeviceVulkan::_draw_list_setup_framebuffer(Framebuffer *p_framebuffer, InitialAction p_initial_color_action, FinalAction p_final_color_action, InitialAction p_initial_depth_action, FinalAction p_final_depth_action, VkFramebuffer *r_framebuffer, VkRenderPass *r_render_pass) {
+Error RenderingDeviceVulkan::_draw_list_setup_framebuffer(Framebuffer *p_framebuffer, InitialAction p_initial_color_action, FinalAction p_final_color_action, InitialAction p_initial_depth_action, FinalAction p_final_depth_action, VkFramebuffer *r_framebuffer, VkRenderPass *r_render_pass, uint32_t *r_subpass_count) {
Framebuffer::VersionKey vk;
vk.initial_color_action = p_initial_color_action;
vk.final_color_action = p_final_color_action;
@@ -5941,7 +6190,7 @@ Error RenderingDeviceVulkan::_draw_list_setup_framebuffer(Framebuffer *p_framebu
//need to create this version
Framebuffer::Version version;
- version.render_pass = _render_pass_create(framebuffer_formats[p_framebuffer->format_id].E->key().attachments, p_initial_color_action, p_final_color_action, p_initial_depth_action, p_final_depth_action, nullptr, p_framebuffer->view_count);
+ version.render_pass = _render_pass_create(framebuffer_formats[p_framebuffer->format_id].E->key().attachments, framebuffer_formats[p_framebuffer->format_id].E->key().passes, p_initial_color_action, p_final_color_action, p_initial_depth_action, p_final_depth_action, p_framebuffer->view_count);
VkFramebufferCreateInfo framebuffer_create_info;
framebuffer_create_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
@@ -5965,11 +6214,14 @@ Error RenderingDeviceVulkan::_draw_list_setup_framebuffer(Framebuffer *p_framebu
VkResult err = vkCreateFramebuffer(device, &framebuffer_create_info, nullptr, &version.framebuffer);
ERR_FAIL_COND_V_MSG(err, ERR_CANT_CREATE, "vkCreateFramebuffer failed with error " + itos(err) + ".");
+ version.subpass_count = framebuffer_formats[p_framebuffer->format_id].E->key().passes.size();
+
p_framebuffer->framebuffers.insert(vk, version);
}
const Framebuffer::Version &version = p_framebuffer->framebuffers[vk];
*r_framebuffer = version.framebuffer;
*r_render_pass = version.render_pass;
+ *r_subpass_count = version.subpass_count;
return OK;
}
@@ -6159,15 +6411,23 @@ RenderingDevice::DrawListID RenderingDeviceVulkan::draw_list_begin(RID p_framebu
if (p_initial_color_action == INITIAL_ACTION_CLEAR) { //check clear values
- int color_attachments = framebuffer_formats[framebuffer->format_id].color_attachments;
- ERR_FAIL_COND_V_MSG(p_clear_color_values.size() != color_attachments, INVALID_ID,
- "Clear color values supplied (" + itos(p_clear_color_values.size()) + ") differ from the amount required for framebuffer (" + itos(color_attachments) + ").");
+ int color_count = 0;
+ for (int i = 0; i < framebuffer->texture_ids.size(); i++) {
+ Texture *texture = texture_owner.getornull(framebuffer->texture_ids[i]);
+
+ if (texture->usage_flags & TEXTURE_USAGE_COLOR_ATTACHMENT_BIT) {
+ color_count++;
+ }
+ }
+
+ ERR_FAIL_COND_V_MSG(p_clear_color_values.size() != color_count, INVALID_ID,
+ "Clear color values supplied (" + itos(p_clear_color_values.size()) + ") differ from the amount required for framebuffer color attachments (" + itos(color_count) + ").");
}
VkFramebuffer vkframebuffer;
VkRenderPass render_pass;
- Error err = _draw_list_setup_framebuffer(framebuffer, p_initial_color_action, p_final_color_action, p_initial_depth_action, p_final_depth_action, &vkframebuffer, &render_pass);
+ Error err = _draw_list_setup_framebuffer(framebuffer, p_initial_color_action, p_final_color_action, p_initial_depth_action, p_final_depth_action, &vkframebuffer, &render_pass, &draw_list_subpass_count);
ERR_FAIL_COND_V(err != OK, INVALID_ID);
VkCommandBuffer command_buffer = frames[frame].draw_command_buffer;
@@ -6177,13 +6437,14 @@ RenderingDevice::DrawListID RenderingDeviceVulkan::draw_list_begin(RID p_framebu
return INVALID_ID;
}
- draw_list = memnew(DrawList);
- draw_list->command_buffer = command_buffer;
+ draw_list_render_pass = render_pass;
+ draw_list_vkframebuffer = vkframebuffer;
+
+ _draw_list_allocate(Rect2i(viewport_offset, viewport_size), 0, 0);
#ifdef DEBUG_ENABLED
- draw_list->validation.framebuffer_format = framebuffer->format_id;
+ draw_list_framebuffer_format = framebuffer->format_id;
#endif
- draw_list_count = 0;
- draw_list_split = false;
+ draw_list_current_subpass = 0;
if (needs_clear_color || needs_clear_depth) {
_draw_list_insert_clear_region(draw_list, framebuffer, viewport_offset, viewport_size, needs_clear_color, p_clear_color_values, needs_clear_depth, p_clear_depth, p_clear_stencil);
@@ -6207,7 +6468,6 @@ RenderingDevice::DrawListID RenderingDeviceVulkan::draw_list_begin(RID p_framebu
vkCmdSetScissor(command_buffer, 0, 1, &scissor);
- draw_list->viewport = Rect2i(viewport_offset, viewport_size);
return int64_t(ID_TYPE_DRAW_LIST) << ID_BASE_SHIFT;
}
@@ -6249,47 +6509,23 @@ Error RenderingDeviceVulkan::draw_list_begin_split(RID p_framebuffer, uint32_t p
if (p_initial_color_action == INITIAL_ACTION_CLEAR) { //check clear values
- int color_attachments = framebuffer_formats[framebuffer->format_id].color_attachments;
- ERR_FAIL_COND_V_MSG(p_clear_color_values.size() != color_attachments, ERR_INVALID_PARAMETER,
- "Clear color values supplied (" + itos(p_clear_color_values.size()) + ") differ from the amount required for framebuffer (" + itos(color_attachments) + ").");
- }
-
- if (p_splits > (uint32_t)split_draw_list_allocators.size()) {
- uint32_t from = split_draw_list_allocators.size();
- split_draw_list_allocators.resize(p_splits);
- for (uint32_t i = from; i < p_splits; i++) {
- VkCommandPoolCreateInfo cmd_pool_info;
- cmd_pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
- cmd_pool_info.pNext = nullptr;
- cmd_pool_info.queueFamilyIndex = context->get_graphics_queue();
- cmd_pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
-
- VkResult res = vkCreateCommandPool(device, &cmd_pool_info, nullptr, &split_draw_list_allocators.write[i].command_pool);
- ERR_FAIL_COND_V_MSG(res, ERR_CANT_CREATE, "vkCreateCommandPool failed with error " + itos(res) + ".");
-
- for (int j = 0; j < frame_count; j++) {
- VkCommandBuffer command_buffer;
-
- VkCommandBufferAllocateInfo cmdbuf;
- //no command buffer exists, create it.
- cmdbuf.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
- cmdbuf.pNext = nullptr;
- cmdbuf.commandPool = split_draw_list_allocators[i].command_pool;
- cmdbuf.level = VK_COMMAND_BUFFER_LEVEL_SECONDARY;
- cmdbuf.commandBufferCount = 1;
-
- VkResult err = vkAllocateCommandBuffers(device, &cmdbuf, &command_buffer);
- ERR_FAIL_COND_V_MSG(err, ERR_CANT_CREATE, "vkAllocateCommandBuffers failed with error " + itos(err) + ".");
+ int color_count = 0;
+ for (int i = 0; i < framebuffer->texture_ids.size(); i++) {
+ Texture *texture = texture_owner.getornull(framebuffer->texture_ids[i]);
- split_draw_list_allocators.write[i].command_buffers.push_back(command_buffer);
+ if (texture->usage_flags & TEXTURE_USAGE_COLOR_ATTACHMENT_BIT) {
+ color_count++;
}
}
+
+ ERR_FAIL_COND_V_MSG(p_clear_color_values.size() != color_count, ERR_INVALID_PARAMETER,
+ "Clear color values supplied (" + itos(p_clear_color_values.size()) + ") differ from the amount required for framebuffer (" + itos(color_count) + ").");
}
VkFramebuffer vkframebuffer;
VkRenderPass render_pass;
- Error err = _draw_list_setup_framebuffer(framebuffer, p_initial_color_action, p_final_color_action, p_initial_depth_action, p_final_depth_action, &vkframebuffer, &render_pass);
+ Error err = _draw_list_setup_framebuffer(framebuffer, p_initial_color_action, p_final_color_action, p_initial_depth_action, p_final_depth_action, &vkframebuffer, &render_pass, &draw_list_subpass_count);
ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE);
VkCommandBuffer frame_command_buffer = frames[frame].draw_command_buffer;
@@ -6299,53 +6535,24 @@ Error RenderingDeviceVulkan::draw_list_begin_split(RID p_framebuffer, uint32_t p
return ERR_CANT_CREATE;
}
- draw_list = memnew_arr(DrawList, p_splits);
- draw_list_count = p_splits;
- draw_list_split = true;
-
- for (uint32_t i = 0; i < p_splits; i++) {
- //take a command buffer and initialize it
- VkCommandBuffer command_buffer = split_draw_list_allocators[i].command_buffers[frame];
-
- VkCommandBufferInheritanceInfo inheritance_info;
- inheritance_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO;
- inheritance_info.pNext = nullptr;
- inheritance_info.renderPass = render_pass;
- inheritance_info.subpass = 0;
- inheritance_info.framebuffer = vkframebuffer;
- inheritance_info.occlusionQueryEnable = false;
- inheritance_info.queryFlags = 0; //?
- inheritance_info.pipelineStatistics = 0;
-
- VkCommandBufferBeginInfo cmdbuf_begin;
- cmdbuf_begin.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
- cmdbuf_begin.pNext = nullptr;
- cmdbuf_begin.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT | VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT;
- cmdbuf_begin.pInheritanceInfo = &inheritance_info;
-
- VkResult res = vkResetCommandBuffer(command_buffer, 0);
- if (res) {
- memdelete_arr(draw_list);
- draw_list = nullptr;
- ERR_FAIL_V_MSG(ERR_CANT_CREATE, "vkResetCommandBuffer failed with error " + itos(res) + ".");
- }
-
- res = vkBeginCommandBuffer(command_buffer, &cmdbuf_begin);
- if (res) {
- memdelete_arr(draw_list);
- draw_list = nullptr;
- ERR_FAIL_V_MSG(ERR_CANT_CREATE, "vkBeginCommandBuffer failed with error " + itos(res) + ".");
- }
+ draw_list_current_subpass = 0;
- draw_list[i].command_buffer = command_buffer;
#ifdef DEBUG_ENABLED
- draw_list[i].validation.framebuffer_format = framebuffer->format_id;
+ draw_list_framebuffer_format = framebuffer->format_id;
#endif
+ draw_list_render_pass = render_pass;
+ draw_list_vkframebuffer = vkframebuffer;
- if (i == 0 && (needs_clear_color || needs_clear_depth)) {
- _draw_list_insert_clear_region(draw_list, framebuffer, viewport_offset, viewport_size, needs_clear_color, p_clear_color_values, needs_clear_depth, p_clear_depth, p_clear_stencil);
- }
+ err = _draw_list_allocate(Rect2i(viewport_offset, viewport_size), p_splits, 0);
+ if (err != OK) {
+ return err;
+ }
+ if (needs_clear_color || needs_clear_depth) {
+ _draw_list_insert_clear_region(&draw_list[0], framebuffer, viewport_offset, viewport_size, needs_clear_color, p_clear_color_values, needs_clear_depth, p_clear_depth, p_clear_stencil);
+ }
+
+ for (uint32_t i = 0; i < p_splits; i++) {
VkViewport viewport;
viewport.x = viewport_offset.x;
viewport.y = viewport_offset.y;
@@ -6354,7 +6561,7 @@ Error RenderingDeviceVulkan::draw_list_begin_split(RID p_framebuffer, uint32_t p
viewport.minDepth = 0;
viewport.maxDepth = 1.0;
- vkCmdSetViewport(command_buffer, 0, 1, &viewport);
+ vkCmdSetViewport(draw_list[i].command_buffer, 0, 1, &viewport);
VkRect2D scissor;
scissor.offset.x = viewport_offset.x;
@@ -6362,10 +6569,8 @@ Error RenderingDeviceVulkan::draw_list_begin_split(RID p_framebuffer, uint32_t p
scissor.extent.width = viewport_size.width;
scissor.extent.height = viewport_size.height;
- vkCmdSetScissor(command_buffer, 0, 1, &scissor);
+ vkCmdSetScissor(draw_list[i].command_buffer, 0, 1, &scissor);
r_split_ids[i] = (int64_t(ID_TYPE_SPLIT_DRAW_LIST) << ID_BASE_SHIFT) + i;
-
- draw_list[i].viewport = Rect2i(viewport_offset, viewport_size);
}
return OK;
@@ -6410,7 +6615,7 @@ void RenderingDeviceVulkan::draw_list_bind_render_pipeline(DrawListID p_list, RI
const RenderPipeline *pipeline = render_pipeline_owner.getornull(p_render_pipeline);
ERR_FAIL_COND(!pipeline);
#ifdef DEBUG_ENABLED
- ERR_FAIL_COND(pipeline->validation.framebuffer_format != dl->validation.framebuffer_format);
+ ERR_FAIL_COND(pipeline->validation.framebuffer_format != draw_list_framebuffer_format && pipeline->validation.render_pass != draw_list_current_subpass);
#endif
if (p_render_pipeline == dl->state.pipeline) {
@@ -6749,30 +6954,162 @@ void RenderingDeviceVulkan::draw_list_disable_scissor(DrawListID p_list) {
vkCmdSetScissor(dl->command_buffer, 0, 1, &scissor);
}
-void RenderingDeviceVulkan::draw_list_end(uint32_t p_post_barrier) {
- _THREAD_SAFE_METHOD_
+RenderingDevice::DrawListID RenderingDeviceVulkan::draw_list_switch_to_next_pass() {
+ ERR_FAIL_COND_V(draw_list == nullptr, INVALID_ID);
+ ERR_FAIL_COND_V(draw_list_current_subpass >= draw_list_subpass_count - 1, INVALID_FORMAT_ID);
- ERR_FAIL_COND_MSG(!draw_list, "Immediate draw list is already inactive.");
+ draw_list_current_subpass++;
+
+ Rect2i viewport;
+ _draw_list_free(&viewport);
+
+ vkCmdNextSubpass(frames[frame].draw_command_buffer, VK_SUBPASS_CONTENTS_INLINE);
+
+ _draw_list_allocate(viewport, 0, draw_list_current_subpass);
+
+ return int64_t(ID_TYPE_DRAW_LIST) << ID_BASE_SHIFT;
+}
+Error RenderingDeviceVulkan::draw_list_switch_to_next_pass_split(uint32_t p_splits, DrawListID *r_split_ids) {
+ ERR_FAIL_COND_V(draw_list == nullptr, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(draw_list_current_subpass >= draw_list_subpass_count - 1, ERR_INVALID_PARAMETER);
+
+ draw_list_current_subpass++;
+
+ Rect2i viewport;
+ _draw_list_free(&viewport);
+
+ vkCmdNextSubpass(frames[frame].draw_command_buffer, VK_SUBPASS_CONTENTS_INLINE);
+
+ _draw_list_allocate(viewport, p_splits, draw_list_current_subpass);
+
+ for (uint32_t i = 0; i < p_splits; i++) {
+ r_split_ids[i] = (int64_t(ID_TYPE_SPLIT_DRAW_LIST) << ID_BASE_SHIFT) + i;
+ }
+
+ return OK;
+}
+Error RenderingDeviceVulkan::_draw_list_allocate(const Rect2i &p_viewport, uint32_t p_splits, uint32_t p_subpass) {
+ if (p_splits == 0) {
+ draw_list = memnew(DrawList);
+ draw_list->command_buffer = frames[frame].draw_command_buffer;
+ draw_list->viewport = p_viewport;
+ draw_list_count = 0;
+ draw_list_split = false;
+ } else {
+ if (p_splits > (uint32_t)split_draw_list_allocators.size()) {
+ uint32_t from = split_draw_list_allocators.size();
+ split_draw_list_allocators.resize(p_splits);
+ for (uint32_t i = from; i < p_splits; i++) {
+ VkCommandPoolCreateInfo cmd_pool_info;
+ cmd_pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
+ cmd_pool_info.pNext = nullptr;
+ cmd_pool_info.queueFamilyIndex = context->get_graphics_queue();
+ cmd_pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
+
+ VkResult res = vkCreateCommandPool(device, &cmd_pool_info, nullptr, &split_draw_list_allocators.write[i].command_pool);
+ ERR_FAIL_COND_V_MSG(res, ERR_CANT_CREATE, "vkCreateCommandPool failed with error " + itos(res) + ".");
+
+ for (int j = 0; j < frame_count; j++) {
+ VkCommandBuffer command_buffer;
+
+ VkCommandBufferAllocateInfo cmdbuf;
+ //no command buffer exists, create it.
+ cmdbuf.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
+ cmdbuf.pNext = nullptr;
+ cmdbuf.commandPool = split_draw_list_allocators[i].command_pool;
+ cmdbuf.level = VK_COMMAND_BUFFER_LEVEL_SECONDARY;
+ cmdbuf.commandBufferCount = 1;
+
+ VkResult err = vkAllocateCommandBuffers(device, &cmdbuf, &command_buffer);
+ ERR_FAIL_COND_V_MSG(err, ERR_CANT_CREATE, "vkAllocateCommandBuffers failed with error " + itos(err) + ".");
+
+ split_draw_list_allocators.write[i].command_buffers.push_back(command_buffer);
+ }
+ }
+ }
+ draw_list = memnew_arr(DrawList, p_splits);
+ draw_list_count = p_splits;
+ draw_list_split = true;
+
+ for (uint32_t i = 0; i < p_splits; i++) {
+ //take a command buffer and initialize it
+ VkCommandBuffer command_buffer = split_draw_list_allocators[i].command_buffers[frame];
+
+ VkCommandBufferInheritanceInfo inheritance_info;
+ inheritance_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO;
+ inheritance_info.pNext = nullptr;
+ inheritance_info.renderPass = draw_list_render_pass;
+ inheritance_info.subpass = p_subpass;
+ inheritance_info.framebuffer = draw_list_vkframebuffer;
+ inheritance_info.occlusionQueryEnable = false;
+ inheritance_info.queryFlags = 0; //?
+ inheritance_info.pipelineStatistics = 0;
+
+ VkCommandBufferBeginInfo cmdbuf_begin;
+ cmdbuf_begin.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+ cmdbuf_begin.pNext = nullptr;
+ cmdbuf_begin.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT | VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT;
+ cmdbuf_begin.pInheritanceInfo = &inheritance_info;
+
+ VkResult res = vkResetCommandBuffer(command_buffer, 0);
+ if (res) {
+ memdelete_arr(draw_list);
+ draw_list = nullptr;
+ ERR_FAIL_V_MSG(ERR_CANT_CREATE, "vkResetCommandBuffer failed with error " + itos(res) + ".");
+ }
+
+ res = vkBeginCommandBuffer(command_buffer, &cmdbuf_begin);
+ if (res) {
+ memdelete_arr(draw_list);
+ draw_list = nullptr;
+ ERR_FAIL_V_MSG(ERR_CANT_CREATE, "vkBeginCommandBuffer failed with error " + itos(res) + ".");
+ }
+
+ draw_list[i].command_buffer = command_buffer;
+ draw_list[i].viewport = p_viewport;
+ }
+ }
+
+ return OK;
+}
+
+void RenderingDeviceVulkan::_draw_list_free(Rect2i *r_last_viewport) {
if (draw_list_split) {
//send all command buffers
VkCommandBuffer *command_buffers = (VkCommandBuffer *)alloca(sizeof(VkCommandBuffer) * draw_list_count);
for (uint32_t i = 0; i < draw_list_count; i++) {
vkEndCommandBuffer(draw_list[i].command_buffer);
command_buffers[i] = draw_list[i].command_buffer;
+ if (r_last_viewport) {
+ if (i == 0 || draw_list[i].viewport_set) {
+ *r_last_viewport = draw_list[i].viewport;
+ }
+ }
}
vkCmdExecuteCommands(frames[frame].draw_command_buffer, draw_list_count, command_buffers);
- vkCmdEndRenderPass(frames[frame].draw_command_buffer);
memdelete_arr(draw_list);
draw_list = nullptr;
} else {
+ if (r_last_viewport) {
+ *r_last_viewport = draw_list->viewport;
+ }
//just end the list
- vkCmdEndRenderPass(draw_list->command_buffer);
memdelete(draw_list);
draw_list = nullptr;
}
+}
+
+void RenderingDeviceVulkan::draw_list_end(uint32_t p_post_barrier) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND_MSG(!draw_list, "Immediate draw list is already inactive.");
+
+ _draw_list_free();
+
+ vkCmdEndRenderPass(frames[frame].draw_command_buffer);
for (int i = 0; i < draw_list_bound_textures.size(); i++) {
Texture *texture = texture_owner.getornull(draw_list_bound_textures[i]);
diff --git a/drivers/vulkan/rendering_device_vulkan.h b/drivers/vulkan/rendering_device_vulkan.h
index f4fe9cf956..9f5830103d 100644
--- a/drivers/vulkan/rendering_device_vulkan.h
+++ b/drivers/vulkan/rendering_device_vulkan.h
@@ -234,12 +234,87 @@ class RenderingDeviceVulkan : public RenderingDevice {
struct FramebufferFormatKey {
Vector<AttachmentFormat> attachments;
+ Vector<FramebufferPass> passes;
uint32_t view_count = 1;
bool operator<(const FramebufferFormatKey &p_key) const {
if (view_count != p_key.view_count) {
return view_count < p_key.view_count;
}
+ uint32_t pass_size = passes.size();
+ uint32_t key_pass_size = p_key.passes.size();
+ if (pass_size != key_pass_size) {
+ return pass_size < key_pass_size;
+ }
+ const FramebufferPass *pass_ptr = passes.ptr();
+ const FramebufferPass *key_pass_ptr = p_key.passes.ptr();
+
+ for (uint32_t i = 0; i < pass_size; i++) {
+ { //compare color attachments
+ uint32_t attachment_size = pass_ptr[i].color_attachments.size();
+ uint32_t key_attachment_size = key_pass_ptr[i].color_attachments.size();
+ if (attachment_size != key_attachment_size) {
+ return attachment_size < key_attachment_size;
+ }
+ const int32_t *pass_attachment_ptr = pass_ptr[i].color_attachments.ptr();
+ const int32_t *key_pass_attachment_ptr = key_pass_ptr[i].color_attachments.ptr();
+
+ for (uint32_t j = 0; j < attachment_size; j++) {
+ if (pass_attachment_ptr[j] != key_pass_attachment_ptr[j]) {
+ return pass_attachment_ptr[j] < key_pass_attachment_ptr[j];
+ }
+ }
+ }
+ { //compare input attachments
+ uint32_t attachment_size = pass_ptr[i].input_attachments.size();
+ uint32_t key_attachment_size = key_pass_ptr[i].input_attachments.size();
+ if (attachment_size != key_attachment_size) {
+ return attachment_size < key_attachment_size;
+ }
+ const int32_t *pass_attachment_ptr = pass_ptr[i].input_attachments.ptr();
+ const int32_t *key_pass_attachment_ptr = key_pass_ptr[i].input_attachments.ptr();
+
+ for (uint32_t j = 0; j < attachment_size; j++) {
+ if (pass_attachment_ptr[j] != key_pass_attachment_ptr[j]) {
+ return pass_attachment_ptr[j] < key_pass_attachment_ptr[j];
+ }
+ }
+ }
+ { //compare resolve attachments
+ uint32_t attachment_size = pass_ptr[i].resolve_attachments.size();
+ uint32_t key_attachment_size = key_pass_ptr[i].resolve_attachments.size();
+ if (attachment_size != key_attachment_size) {
+ return attachment_size < key_attachment_size;
+ }
+ const int32_t *pass_attachment_ptr = pass_ptr[i].resolve_attachments.ptr();
+ const int32_t *key_pass_attachment_ptr = key_pass_ptr[i].resolve_attachments.ptr();
+
+ for (uint32_t j = 0; j < attachment_size; j++) {
+ if (pass_attachment_ptr[j] != key_pass_attachment_ptr[j]) {
+ return pass_attachment_ptr[j] < key_pass_attachment_ptr[j];
+ }
+ }
+ }
+ { //compare preserve attachments
+ uint32_t attachment_size = pass_ptr[i].preserve_attachments.size();
+ uint32_t key_attachment_size = key_pass_ptr[i].preserve_attachments.size();
+ if (attachment_size != key_attachment_size) {
+ return attachment_size < key_attachment_size;
+ }
+ const int32_t *pass_attachment_ptr = pass_ptr[i].preserve_attachments.ptr();
+ const int32_t *key_pass_attachment_ptr = key_pass_ptr[i].preserve_attachments.ptr();
+
+ for (uint32_t j = 0; j < attachment_size; j++) {
+ if (pass_attachment_ptr[j] != key_pass_attachment_ptr[j]) {
+ return pass_attachment_ptr[j] < key_pass_attachment_ptr[j];
+ }
+ }
+ }
+ if (pass_ptr[i].depth_attachment != key_pass_ptr[i].depth_attachment) {
+ return pass_ptr[i].depth_attachment < key_pass_ptr[i].depth_attachment;
+ }
+ }
+
int as = attachments.size();
int bs = p_key.attachments.size();
if (as != bs) {
@@ -266,16 +341,14 @@ class RenderingDeviceVulkan : public RenderingDevice {
}
};
- VkRenderPass _render_pass_create(const Vector<AttachmentFormat> &p_format, InitialAction p_initial_action, FinalAction p_final_action, InitialAction p_initial_depth_action, FinalAction p_final_depthcolor_action, int *r_color_attachment_count = nullptr, uint32_t p_view_count = 1);
-
+ VkRenderPass _render_pass_create(const Vector<AttachmentFormat> &p_attachments, const Vector<FramebufferPass> &p_passes, InitialAction p_initial_action, FinalAction p_final_action, InitialAction p_initial_depth_action, FinalAction p_final_depth_action, uint32_t p_view_count = 1, Vector<TextureSamples> *r_samples = nullptr);
// This is a cache and it's never freed, it ensures
// IDs for a given format are always unique.
Map<FramebufferFormatKey, FramebufferFormatID> framebuffer_format_cache;
struct FramebufferFormat {
const Map<FramebufferFormatKey, FramebufferFormatID>::Element *E;
VkRenderPass render_pass = VK_NULL_HANDLE; //here for constructing shaders, never used, see section (7.2. Render Pass Compatibility from Vulkan spec)
- int color_attachments = 0; //used for pipeline validation
- TextureSamples samples;
+ Vector<TextureSamples> pass_samples;
uint32_t view_count = 1; // number of views
};
@@ -289,6 +362,7 @@ class RenderingDeviceVulkan : public RenderingDevice {
InitialAction initial_depth_action;
FinalAction final_depth_action;
uint32_t view_count;
+
bool operator<(const VersionKey &p_key) const {
if (initial_color_action == p_key.initial_color_action) {
if (final_color_action == p_key.final_color_action) {
@@ -316,6 +390,7 @@ class RenderingDeviceVulkan : public RenderingDevice {
struct Version {
VkFramebuffer framebuffer = VK_NULL_HANDLE;
VkRenderPass render_pass = VK_NULL_HANDLE; //this one is owned
+ uint32_t subpass_count = 1;
};
Map<VersionKey, Version> framebuffers;
@@ -536,7 +611,7 @@ class RenderingDeviceVulkan : public RenderingDevice {
};
uint32_t vertex_input_mask = 0; //inputs used, this is mostly for validation
- int fragment_outputs = 0;
+ uint32_t fragment_output_mask = 0;
struct PushConstant {
uint32_t push_constant_size = 0;
@@ -680,6 +755,7 @@ class RenderingDeviceVulkan : public RenderingDevice {
#ifdef DEBUG_ENABLED
struct Validation {
FramebufferFormatID framebuffer_format = 0;
+ uint32_t render_pass = 0;
uint32_t dynamic_state = 0;
VertexFormatID vertex_format = 0;
bool uses_restart_indices = false;
@@ -735,6 +811,7 @@ class RenderingDeviceVulkan : public RenderingDevice {
struct DrawList {
VkCommandBuffer command_buffer = VK_NULL_HANDLE; // If persistent, this is owned, otherwise it's shared with the ringbuffer.
Rect2i viewport;
+ bool viewport_set = false;
struct SetState {
uint32_t pipeline_expected_format = 0;
@@ -758,7 +835,6 @@ class RenderingDeviceVulkan : public RenderingDevice {
#ifdef DEBUG_ENABLED
struct Validation {
bool active = true; // Means command buffer was not closed, so you can keep adding things.
- FramebufferFormatID framebuffer_format = INVALID_ID;
// Actual render pass values.
uint32_t dynamic_state = 0;
VertexFormatID vertex_format = INVALID_ID;
@@ -794,7 +870,15 @@ class RenderingDeviceVulkan : public RenderingDevice {
};
DrawList *draw_list = nullptr; // One for regular draw lists, multiple for split.
+ uint32_t draw_list_subpass_count = 0;
uint32_t draw_list_count = 0;
+ VkRenderPass draw_list_render_pass;
+ VkFramebuffer draw_list_vkframebuffer;
+#ifdef DEBUG_ENABLED
+ FramebufferFormatID draw_list_framebuffer_format = INVALID_ID;
+#endif
+ uint32_t draw_list_current_subpass = 0;
+
bool draw_list_split = false;
Vector<RID> draw_list_bound_textures;
Vector<RID> draw_list_storage_textures;
@@ -802,10 +886,12 @@ class RenderingDeviceVulkan : public RenderingDevice {
bool draw_list_unbind_depth_textures = false;
void _draw_list_insert_clear_region(DrawList *draw_list, Framebuffer *framebuffer, Point2i viewport_offset, Point2i viewport_size, bool p_clear_color, const Vector<Color> &p_clear_colors, bool p_clear_depth, float p_depth, uint32_t p_stencil);
- Error _draw_list_setup_framebuffer(Framebuffer *p_framebuffer, InitialAction p_initial_color_action, FinalAction p_final_color_action, InitialAction p_initial_depth_action, FinalAction p_final_depth_action, VkFramebuffer *r_framebuffer, VkRenderPass *r_render_pass);
+ Error _draw_list_setup_framebuffer(Framebuffer *p_framebuffer, InitialAction p_initial_color_action, FinalAction p_final_color_action, InitialAction p_initial_depth_action, FinalAction p_final_depth_action, VkFramebuffer *r_framebuffer, VkRenderPass *r_render_pass, uint32_t *r_subpass_count);
Error _draw_list_render_pass_begin(Framebuffer *framebuffer, InitialAction p_initial_color_action, FinalAction p_final_color_action, InitialAction p_initial_depth_action, FinalAction p_final_depth_action, const Vector<Color> &p_clear_colors, float p_clear_depth, uint32_t p_clear_stencil, Point2i viewport_offset, Point2i viewport_size, VkFramebuffer vkframebuffer, VkRenderPass render_pass, VkCommandBuffer command_buffer, VkSubpassContents subpass_contents, const Vector<RID> &p_storage_textures);
_FORCE_INLINE_ DrawList *_get_draw_list_ptr(DrawListID p_id);
Buffer *_get_buffer_from_owner(RID p_buffer, VkPipelineStageFlags &dst_stage_mask, VkAccessFlags &dst_access, uint32_t p_post_barrier);
+ Error _draw_list_allocate(const Rect2i &p_viewport, uint32_t p_splits, uint32_t p_subpass);
+ void _draw_list_free(Rect2i *r_last_viewport = nullptr);
/**********************/
/**** COMPUTE LIST ****/
@@ -951,10 +1037,12 @@ public:
/*********************/
virtual FramebufferFormatID framebuffer_format_create(const Vector<AttachmentFormat> &p_format, uint32_t p_view_count = 1);
+ virtual FramebufferFormatID framebuffer_format_create_multipass(const Vector<AttachmentFormat> &p_attachments, Vector<FramebufferPass> &p_passes, uint32_t p_view_count = 1);
virtual FramebufferFormatID framebuffer_format_create_empty(TextureSamples p_samples = TEXTURE_SAMPLES_1);
- virtual TextureSamples framebuffer_format_get_texture_samples(FramebufferFormatID p_format);
+ virtual TextureSamples framebuffer_format_get_texture_samples(FramebufferFormatID p_format, uint32_t p_pass = 0);
virtual RID framebuffer_create(const Vector<RID> &p_texture_attachments, FramebufferFormatID p_format_check = INVALID_ID, uint32_t p_view_count = 1);
+ virtual RID framebuffer_create_multipass(const Vector<RID> &p_texture_attachments, Vector<FramebufferPass> &p_passes, FramebufferFormatID p_format_check = INVALID_ID, uint32_t p_view_count = 1);
virtual RID framebuffer_create_empty(const Size2i &p_size, TextureSamples p_samples = TEXTURE_SAMPLES_1, FramebufferFormatID p_format_check = INVALID_ID);
virtual FramebufferFormatID framebuffer_get_format(RID p_framebuffer);
@@ -1005,7 +1093,7 @@ public:
/**** RENDER PIPELINE ****/
/*************************/
- virtual RID render_pipeline_create(RID p_shader, FramebufferFormatID p_framebuffer_format, VertexFormatID p_vertex_format, RenderPrimitive p_render_primitive, const PipelineRasterizationState &p_rasterization_state, const PipelineMultisampleState &p_multisample_state, const PipelineDepthStencilState &p_depth_stencil_state, const PipelineColorBlendState &p_blend_state, int p_dynamic_state_flags = 0);
+ virtual RID render_pipeline_create(RID p_shader, FramebufferFormatID p_framebuffer_format, VertexFormatID p_vertex_format, RenderPrimitive p_render_primitive, const PipelineRasterizationState &p_rasterization_state, const PipelineMultisampleState &p_multisample_state, const PipelineDepthStencilState &p_depth_stencil_state, const PipelineColorBlendState &p_blend_state, int p_dynamic_state_flags = 0, uint32_t p_for_render_pass = 0);
virtual bool render_pipeline_is_valid(RID p_pipeline);
/**************************/
@@ -1044,6 +1132,9 @@ public:
virtual void draw_list_enable_scissor(DrawListID p_list, const Rect2 &p_rect);
virtual void draw_list_disable_scissor(DrawListID p_list);
+ virtual DrawListID draw_list_switch_to_next_pass();
+ virtual Error draw_list_switch_to_next_pass_split(uint32_t p_splits, DrawListID *r_split_ids);
+
virtual void draw_list_end(uint32_t p_post_barrier = BARRIER_MASK_ALL);
/***********************/
diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp
index e0e9c6e2d1..16f55312bb 100644
--- a/editor/code_editor.cpp
+++ b/editor/code_editor.cpp
@@ -105,6 +105,11 @@ void FindReplaceBar::_notification(int p_what) {
hide_button->set_custom_minimum_size(hide_button->get_normal_texture()->get_size());
} else if (p_what == NOTIFICATION_THEME_CHANGED) {
matches_label->add_theme_color_override("font_color", results_count > 0 ? get_theme_color("font_color", "Label") : get_theme_color("error_color", "Editor"));
+ } else if (p_what == NOTIFICATION_PREDELETE) {
+ if (base_text_editor) {
+ base_text_editor->remove_find_replace_bar();
+ base_text_editor = nullptr;
+ }
}
}
@@ -595,6 +600,10 @@ void FindReplaceBar::set_text_edit(CodeTextEditor *p_text_editor) {
text_editor = nullptr;
}
+ if (!p_text_editor) {
+ return;
+ }
+
results_count = -1;
base_text_editor = p_text_editor;
text_editor = base_text_editor->get_text_editor();
@@ -930,7 +939,7 @@ void CodeTextEditor::update_editor_settings() {
text_editor->set_highlight_current_line(EditorSettings::get_singleton()->get("text_editor/highlighting/highlight_current_line"));
text_editor->set_indent_using_spaces(EditorSettings::get_singleton()->get("text_editor/indent/type"));
text_editor->set_indent_size(EditorSettings::get_singleton()->get("text_editor/indent/size"));
- text_editor->set_auto_indent(EditorSettings::get_singleton()->get("text_editor/indent/auto_indent"));
+ text_editor->set_auto_indent_enabled(EditorSettings::get_singleton()->get("text_editor/indent/auto_indent"));
text_editor->set_draw_tabs(EditorSettings::get_singleton()->get("text_editor/indent/draw_tabs"));
text_editor->set_draw_spaces(EditorSettings::get_singleton()->get("text_editor/indent/draw_spaces"));
text_editor->set_smooth_scroll_enabled(EditorSettings::get_singleton()->get("text_editor/navigation/smooth_scrolling"));
@@ -1255,7 +1264,7 @@ void CodeTextEditor::_delete_line(int p_line) {
text_editor->cursor_set_line(1);
text_editor->cursor_set_column(0);
}
- text_editor->backspace_at_cursor();
+ text_editor->backspace();
text_editor->unfold_line(p_line);
text_editor->cursor_set_line(p_line);
}
@@ -1667,6 +1676,11 @@ void CodeTextEditor::_notification(int p_what) {
}
set_process_input(is_visible_in_tree());
} break;
+ case NOTIFICATION_PREDELETE: {
+ if (find_replace_bar) {
+ find_replace_bar->set_text_edit(nullptr);
+ }
+ } break;
default:
break;
}
@@ -1809,7 +1823,7 @@ CodeTextEditor::CodeTextEditor() {
text_editor->set_draw_line_numbers(true);
text_editor->set_brace_matching(true);
- text_editor->set_auto_indent(true);
+ text_editor->set_auto_indent_enabled(true);
status_bar = memnew(HBoxContainer);
add_child(status_bar);
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index f8c2f2910b..b962ea6e3e 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -32,6 +32,7 @@
#include "core/config/project_settings.h"
#include "core/core_bind.h"
+#include "core/extension/native_extension_manager.h"
#include "core/input/input.h"
#include "core/io/config_file.h"
#include "core/io/file_access.h"
@@ -3771,9 +3772,12 @@ void EditorNode::register_editor_types() {
ClassDB::register_class<EditorScenePostImport>();
//ClassDB::register_type<EditorImportExport>();
ClassDB::register_class<EditorDebuggerPlugin>();
+
+ NativeExtensionManager::get_singleton()->initialize_extensions(NativeExtension::INITIALIZATION_LEVEL_EDITOR);
}
void EditorNode::unregister_editor_types() {
+ NativeExtensionManager::get_singleton()->deinitialize_extensions(NativeExtension::INITIALIZATION_LEVEL_EDITOR);
_init_callbacks.clear();
if (EditorPaths::get_singleton()) {
EditorPaths::free();
diff --git a/editor/editor_properties_array_dict.cpp b/editor/editor_properties_array_dict.cpp
index 5c5b5fe0ab..9987aaf3fe 100644
--- a/editor/editor_properties_array_dict.cpp
+++ b/editor/editor_properties_array_dict.cpp
@@ -550,6 +550,8 @@ void EditorPropertyArray::_length_changed(double p_page) {
void EditorPropertyArray::setup(Variant::Type p_array_type, const String &p_hint_string) {
array_type = p_array_type;
+ // The format of p_hint_string is:
+ // subType/subTypeHint:nextSubtype ... etc
if (array_type == Variant::ARRAY && !p_hint_string.is_empty()) {
int hint_subtype_separator = p_hint_string.find(":");
if (hint_subtype_separator >= 0) {
diff --git a/editor/node_3d_editor_gizmos.cpp b/editor/node_3d_editor_gizmos.cpp
index 4d61e982c1..2a399f4b03 100644
--- a/editor/node_3d_editor_gizmos.cpp
+++ b/editor/node_3d_editor_gizmos.cpp
@@ -600,8 +600,6 @@ bool EditorNode3DGizmo::intersect_ray(Camera3D *p_camera, const Point2 &p_point,
r_normal = -p_camera->project_ray_normal(p_point);
return true;
}
-
- return false;
}
if (collision_segments.size()) {
@@ -652,8 +650,6 @@ bool EditorNode3DGizmo::intersect_ray(Camera3D *p_camera, const Point2 &p_point,
r_normal = -p_camera->project_ray_normal(p_point);
return true;
}
-
- return false;
}
if (collision_mesh.is_valid()) {
diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp
index 29012d1180..24d835887e 100644
--- a/editor/plugins/canvas_item_editor_plugin.cpp
+++ b/editor/plugins/canvas_item_editor_plugin.cpp
@@ -5478,7 +5478,7 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) {
p = view_menu->get_popup();
p->set_hide_on_checkable_item_selection(false);
- p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_grid", TTR("Always Show Grid"), KEY_MASK_CTRL | KEY_G), SHOW_GRID);
+ p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_grid", TTR("Always Show Grid"), KEY_MASK_CMD | KEY_G), SHOW_GRID);
p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_helpers", TTR("Show Helpers"), KEY_H), SHOW_HELPERS);
p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_rulers", TTR("Show Rulers")), SHOW_RULERS);
p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_guides", TTR("Show Guides"), KEY_Y), SHOW_GUIDES);
diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp
index 14087801f5..c3b121d242 100644
--- a/editor/plugins/node_3d_editor_plugin.cpp
+++ b/editor/plugins/node_3d_editor_plugin.cpp
@@ -6917,7 +6917,7 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) {
p->add_separator();
p->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_origin", TTR("View Origin")), MENU_VIEW_ORIGIN);
- p->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_grid", TTR("View Grid")), MENU_VIEW_GRID);
+ p->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_grid", TTR("View Grid"), KEY_MASK_CMD + KEY_G), MENU_VIEW_GRID);
p->add_separator();
p->add_shortcut(ED_SHORTCUT("spatial_editor/settings", TTR("Settings...")), MENU_VIEW_CAMERA_SETTINGS);
diff --git a/editor/plugins/occluder_instance_3d_editor_plugin.cpp b/editor/plugins/occluder_instance_3d_editor_plugin.cpp
index 0821f140b3..b0cafd83be 100644
--- a/editor/plugins/occluder_instance_3d_editor_plugin.cpp
+++ b/editor/plugins/occluder_instance_3d_editor_plugin.cpp
@@ -56,7 +56,7 @@ void OccluderInstance3DEditorPlugin::_bake_select_file(const String &p_file) {
} break;
case OccluderInstance3D::BAKE_ERROR_NO_MESHES: {
- EditorNode::get_singleton()->show_warning(TTR("No meshes to bake."));
+ EditorNode::get_singleton()->show_warning(TTR("No meshes to bake.\nMake sure there is at least one MeshInstance3D node in the scene whose visual layers are part of the OccluderInstance3D's Bake Mask property."));
break;
}
default: {
diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp
index 89b486c4df..498d5b0711 100644
--- a/editor/plugins/script_editor_plugin.cpp
+++ b/editor/plugins/script_editor_plugin.cpp
@@ -760,6 +760,7 @@ void ScriptEditor::_close_tab(int p_idx, bool p_save, bool p_history_back) {
_update_members_overview_visibility();
_update_help_overview_visibility();
_save_layout();
+ _update_find_replace_bar();
}
void ScriptEditor::_close_current_tab(bool p_save) {
@@ -829,6 +830,7 @@ void ScriptEditor::_close_all_tabs() {
_close_current_tab(false);
}
+ _update_find_replace_bar();
}
void ScriptEditor::_ask_close_current_unsaved_tab(ScriptEditorBase *current) {
@@ -1640,15 +1642,13 @@ void ScriptEditor::ensure_select_current() {
ScriptEditorBase *se = _get_current_editor();
if (se) {
se->enable_editor();
- se->set_find_replace_bar(find_replace_bar);
if (!grab_focus_block && is_visible_in_tree()) {
se->ensure_focus();
}
- } else {
- find_replace_bar->hide();
}
}
+ _update_find_replace_bar();
_update_selected_editor_menu();
}
@@ -2520,6 +2520,16 @@ void ScriptEditor::_file_removed(const String &p_removed_file) {
}
}
+void ScriptEditor::_update_find_replace_bar() {
+ ScriptEditorBase *se = _get_current_editor();
+ if (se) {
+ se->set_find_replace_bar(find_replace_bar);
+ } else {
+ find_replace_bar->set_text_edit(nullptr);
+ find_replace_bar->hide();
+ }
+}
+
void ScriptEditor::_autosave_scripts() {
save_all_scripts();
}
diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h
index a03a4b393b..72a649ffbf 100644
--- a/editor/plugins/script_editor_plugin.h
+++ b/editor/plugins/script_editor_plugin.h
@@ -328,6 +328,7 @@ class ScriptEditor : public PanelContainer {
void _show_error_dialog(String p_path);
void _close_tab(int p_idx, bool p_save = true, bool p_history_back = true);
+ void _update_find_replace_bar();
void _close_current_tab(bool p_save = true);
void _close_discard_current_tab(const String &p_str);
diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp
index b36e198b7b..5fc1f74089 100644
--- a/editor/plugins/script_text_editor.cpp
+++ b/editor/plugins/script_text_editor.cpp
@@ -524,7 +524,7 @@ void ScriptTextEditor::_validate_script() {
if (safe_lines.has(i + 1)) {
te->set_line_gutter_item_color(i, line_number_gutter, safe_line_number_color);
last_is_safe = true;
- } else if (last_is_safe && (te->is_line_comment(i) || te->get_line(i).strip_edges().is_empty())) {
+ } else if (last_is_safe && (te->is_in_comment(i) != -1 || te->get_line(i).strip_edges().is_empty())) {
te->set_line_gutter_item_color(i, line_number_gutter, safe_line_number_color);
} else {
te->set_line_gutter_item_color(i, line_number_gutter, default_line_number_color);
@@ -1038,7 +1038,7 @@ void ScriptTextEditor::_edit_option(int p_op) {
return;
}
- tx->indent_selected_lines_left();
+ tx->unindent_lines();
} break;
case EDIT_INDENT_RIGHT: {
Ref<Script> scr = script;
@@ -1046,7 +1046,7 @@ void ScriptTextEditor::_edit_option(int p_op) {
return;
}
- tx->indent_selected_lines_right();
+ tx->indent_lines();
} break;
case EDIT_DELETE_LINE: {
code_editor->delete_lines();
diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp
index 3cc71c7626..173f1dd7fb 100644
--- a/editor/plugins/shader_editor_plugin.cpp
+++ b/editor/plugins/shader_editor_plugin.cpp
@@ -323,19 +323,13 @@ void ShaderEditor::_menu_option(int p_option) {
if (shader.is_null()) {
return;
}
-
- CodeEdit *tx = shader_editor->get_text_editor();
- tx->indent_selected_lines_left();
-
+ shader_editor->get_text_editor()->unindent_lines();
} break;
case EDIT_INDENT_RIGHT: {
if (shader.is_null()) {
return;
}
-
- CodeEdit *tx = shader_editor->get_text_editor();
- tx->indent_selected_lines_right();
-
+ shader_editor->get_text_editor()->indent_lines();
} break;
case EDIT_DELETE_LINE: {
shader_editor->delete_lines();
diff --git a/editor/plugins/text_editor.cpp b/editor/plugins/text_editor.cpp
index e906f62fd3..d62be993af 100644
--- a/editor/plugins/text_editor.cpp
+++ b/editor/plugins/text_editor.cpp
@@ -320,10 +320,10 @@ void TextEditor::_edit_option(int p_op) {
code_editor->move_lines_down();
} break;
case EDIT_INDENT_LEFT: {
- tx->indent_selected_lines_left();
+ tx->unindent_lines();
} break;
case EDIT_INDENT_RIGHT: {
- tx->indent_selected_lines_right();
+ tx->indent_lines();
} break;
case EDIT_DELETE_LINE: {
code_editor->delete_lines();
diff --git a/editor/plugins/theme_editor_plugin.cpp b/editor/plugins/theme_editor_plugin.cpp
index 22b39c59f5..4ab5887cd7 100644
--- a/editor/plugins/theme_editor_plugin.cpp
+++ b/editor/plugins/theme_editor_plugin.cpp
@@ -2263,7 +2263,7 @@ void ThemeTypeEditor::_update_type_items() {
} else {
item_editor->set_edited_resource(RES());
}
- item_editor->connect("resource_selected", callable_mp(this, &ThemeTypeEditor::_edit_resource_item), varray(item_control));
+ item_editor->connect("resource_selected", callable_mp(this, &ThemeTypeEditor::_edit_resource_item));
item_editor->connect("resource_changed", callable_mp(this, &ThemeTypeEditor::_font_item_changed), varray(E.key()));
} else {
if (Theme::get_default()->has_font(E.key(), edited_type)) {
@@ -2334,7 +2334,7 @@ void ThemeTypeEditor::_update_type_items() {
} else {
item_editor->set_edited_resource(RES());
}
- item_editor->connect("resource_selected", callable_mp(this, &ThemeTypeEditor::_edit_resource_item), varray(item_control));
+ item_editor->connect("resource_selected", callable_mp(this, &ThemeTypeEditor::_edit_resource_item));
item_editor->connect("resource_changed", callable_mp(this, &ThemeTypeEditor::_icon_item_changed), varray(E.key()));
} else {
if (Theme::get_default()->has_icon(E.key(), edited_type)) {
@@ -2381,7 +2381,7 @@ void ThemeTypeEditor::_update_type_items() {
} else {
item_editor->set_edited_resource(RES());
}
- item_editor->connect("resource_selected", callable_mp(this, &ThemeTypeEditor::_edit_resource_item), varray(item_control));
+ item_editor->connect("resource_selected", callable_mp(this, &ThemeTypeEditor::_edit_resource_item));
item_editor->connect("resource_changed", callable_mp(this, &ThemeTypeEditor::_stylebox_item_changed), varray(leading_stylebox.item_name));
stylebox_items_list->add_child(item_control);
@@ -2408,7 +2408,7 @@ void ThemeTypeEditor::_update_type_items() {
} else {
item_editor->set_edited_resource(RES());
}
- item_editor->connect("resource_selected", callable_mp(this, &ThemeTypeEditor::_edit_resource_item), varray(item_control));
+ item_editor->connect("resource_selected", callable_mp(this, &ThemeTypeEditor::_edit_resource_item));
item_editor->connect("resource_changed", callable_mp(this, &ThemeTypeEditor::_stylebox_item_changed), varray(E.key()));
Button *pin_leader_button = memnew(Button);
@@ -2417,7 +2417,7 @@ void ThemeTypeEditor::_update_type_items() {
pin_leader_button->set_icon(get_theme_icon("Pin", "EditorIcons"));
pin_leader_button->set_tooltip(TTR("Pin this StyleBox as a main style. Editing its properties will update the same properties in all other StyleBoxes of this type."));
item_control->add_child(pin_leader_button);
- pin_leader_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_pin_leading_stylebox), varray(stylebox_value, E.key()));
+ pin_leader_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_pin_leading_stylebox), varray(item_editor, E.key()));
} else {
if (Theme::get_default()->has_stylebox(E.key(), edited_type)) {
item_editor->set_edited_resource(Theme::get_default()->get_stylebox(E.key(), edited_type));
@@ -2612,6 +2612,10 @@ void ThemeTypeEditor::_item_remove_cbk(int p_data_type, String p_item_name) {
} break;
case Theme::DATA_TYPE_STYLEBOX: {
edited_theme->clear_stylebox(p_item_name, edited_type);
+
+ if (leading_stylebox.pinned && leading_stylebox.item_name == p_item_name) {
+ _unpin_leading_stylebox();
+ }
} break;
}
}
@@ -2661,6 +2665,10 @@ void ThemeTypeEditor::_item_rename_confirmed(int p_data_type, String p_item_name
} break;
case Theme::DATA_TYPE_STYLEBOX: {
edited_theme->rename_stylebox(p_item_name, new_name, edited_type);
+
+ if (leading_stylebox.pinned && leading_stylebox.item_name == p_item_name) {
+ leading_stylebox.item_name = new_name;
+ }
} break;
}
}
@@ -2695,7 +2703,7 @@ void ThemeTypeEditor::_font_size_item_changed(float p_value, String p_item_name)
edited_theme->set_font_size(p_item_name, edited_type, int(p_value));
}
-void ThemeTypeEditor::_edit_resource_item(RES p_resource, Control *p_editor) {
+void ThemeTypeEditor::_edit_resource_item(RES p_resource) {
EditorNode::get_singleton()->edit_resource(p_resource);
}
@@ -2723,16 +2731,21 @@ void ThemeTypeEditor::_stylebox_item_changed(Ref<StyleBox> p_value, String p_ite
}
}
-void ThemeTypeEditor::_pin_leading_stylebox(Ref<StyleBox> p_stylebox, String p_item_name) {
+void ThemeTypeEditor::_pin_leading_stylebox(Control *p_editor, String p_item_name) {
if (leading_stylebox.stylebox.is_valid()) {
leading_stylebox.stylebox->disconnect("changed", callable_mp(this, &ThemeTypeEditor::_update_stylebox_from_leading));
}
+ Ref<StyleBox> stylebox;
+ if (Object::cast_to<EditorResourcePicker>(p_editor)) {
+ stylebox = Object::cast_to<EditorResourcePicker>(p_editor)->get_edited_resource();
+ }
+
LeadingStylebox leader;
leader.pinned = true;
leader.item_name = p_item_name;
- leader.stylebox = p_stylebox;
- leader.ref_stylebox = (p_stylebox.is_valid() ? p_stylebox->duplicate() : RES());
+ leader.stylebox = stylebox;
+ leader.ref_stylebox = (stylebox.is_valid() ? stylebox->duplicate() : RES());
leading_stylebox = leader;
if (leading_stylebox.stylebox.is_valid()) {
diff --git a/editor/plugins/theme_editor_plugin.h b/editor/plugins/theme_editor_plugin.h
index 77baf46395..cdedbbec8d 100644
--- a/editor/plugins/theme_editor_plugin.h
+++ b/editor/plugins/theme_editor_plugin.h
@@ -329,11 +329,11 @@ class ThemeTypeEditor : public MarginContainer {
void _color_item_changed(Color p_value, String p_item_name);
void _constant_item_changed(float p_value, String p_item_name);
void _font_size_item_changed(float p_value, String p_item_name);
- void _edit_resource_item(RES p_resource, Control *p_editor);
+ void _edit_resource_item(RES p_resource);
void _font_item_changed(Ref<Font> p_value, String p_item_name);
void _icon_item_changed(Ref<Texture2D> p_value, String p_item_name);
void _stylebox_item_changed(Ref<StyleBox> p_value, String p_item_name);
- void _pin_leading_stylebox(Ref<StyleBox> p_stylebox, String p_item_name);
+ void _pin_leading_stylebox(Control *p_editor, String p_item_name);
void _unpin_leading_stylebox();
void _update_stylebox_from_leading();
diff --git a/editor/project_export.cpp b/editor/project_export.cpp
index ec65694772..75736a0723 100644
--- a/editor/project_export.cpp
+++ b/editor/project_export.cpp
@@ -1110,9 +1110,9 @@ ProjectExportDialog::ProjectExportDialog() {
exclude_filters->connect("text_changed", callable_mp(this, &ProjectExportDialog::_filter_changed));
script_mode = memnew(OptionButton);
- resources_vb->add_margin_child(TTR("Script Export Mode:"), script_mode);
+ resources_vb->add_margin_child(TTR("GDScript Export Mode:"), script_mode);
script_mode->add_item(TTR("Text"), (int)EditorExportPreset::MODE_SCRIPT_TEXT);
- script_mode->add_item(TTR("Compiled"), (int)EditorExportPreset::MODE_SCRIPT_COMPILED);
+ script_mode->add_item(TTR("Compiled Bytecode (Faster Loading)"), (int)EditorExportPreset::MODE_SCRIPT_COMPILED);
script_mode->connect("item_selected", callable_mp(this, &ProjectExportDialog::_script_export_mode_changed));
// Feature tags.
@@ -1137,12 +1137,12 @@ ProjectExportDialog::ProjectExportDialog() {
enc_pck = memnew(CheckButton);
enc_pck->connect("toggled", callable_mp(this, &ProjectExportDialog::_enc_pck_changed));
- enc_pck->set_text(TTR("Encrypt exported PCK"));
+ enc_pck->set_text(TTR("Encrypt Exported PCK"));
sec_vb->add_child(enc_pck);
enc_directory = memnew(CheckButton);
enc_directory->connect("toggled", callable_mp(this, &ProjectExportDialog::_enc_directory_changed));
- enc_directory->set_text("Encrypt index (file names and info).");
+ enc_directory->set_text("Encrypt Index (File Names and Info)");
sec_vb->add_child(enc_directory);
enc_in_filters = memnew(LineEdit);
@@ -1160,9 +1160,9 @@ ProjectExportDialog::ProjectExportDialog() {
script_key = memnew(LineEdit);
script_key->connect("text_changed", callable_mp(this, &ProjectExportDialog::_script_encryption_key_changed));
script_key_error = memnew(Label);
- script_key_error->set_text("- " + TTR("Invalid Encryption Key (must be 64 characters long)"));
+ script_key_error->set_text("- " + TTR("Invalid Encryption Key (must be 64 hexadecimal characters long)"));
script_key_error->add_theme_color_override("font_color", EditorNode::get_singleton()->get_gui_base()->get_theme_color("error_color", "Editor"));
- sec_vb->add_margin_child(TTR("Encryption Key (256-bits as hex):"), script_key);
+ sec_vb->add_margin_child(TTR("Encryption Key (256-bits as hexadecimal):"), script_key);
sec_vb->add_child(script_key_error);
sections->add_child(sec_vb);
diff --git a/main/main.cpp b/main/main.cpp
index f2820fee07..9e3b5c9ba2 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -34,6 +34,7 @@
#include "core/core_string_names.h"
#include "core/crypto/crypto.h"
#include "core/debugger/engine_debugger.h"
+#include "core/extension/extension_api_dump.h"
#include "core/input/input.h"
#include "core/input/input_map.h"
#include "core/io/dir_access.h"
@@ -174,7 +175,9 @@ static int frame_delay = 0;
static bool disable_render_loop = false;
static int fixed_fps = -1;
static bool print_fps = false;
-
+#ifdef TOOLS_ENABLED
+static bool dump_extension_api = false;
+#endif
bool profile_gpu = false;
/* Helper methods */
@@ -406,6 +409,8 @@ Error Main::test_setup() {
translation_server = memnew(TranslationServer);
+ register_core_extensions();
+
// From `Main::setup2()`.
preregister_module_types();
preregister_server_types();
@@ -887,7 +892,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
auto_build_solutions = true;
editor = true;
cmdline_tool = true;
-#ifdef DEBUG_METHODS_ENABLED
+
} else if (I->get() == "--gdnative-generate-json-api" || I->get() == "--gdnative-generate-json-builtin-api") {
// Register as an editor instance to use low-end fallback if relevant.
editor = true;
@@ -895,7 +900,13 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
// We still pass it to the main arguments since the argument handling itself is not done in this function
main_args.push_back(I->get());
-#endif
+ } else if (I->get() == "--dump-extension-api") {
+ // Register as an editor instance to use low-end fallback if relevant.
+ editor = true;
+ cmdline_tool = true;
+ dump_extension_api = true;
+ print_line("dump extension?");
+ main_args.push_back(I->get());
} else if (I->get() == "--export" || I->get() == "--export-debug" ||
I->get() == "--export-pack") { // Export project
// Actually handling is done in start().
@@ -1198,6 +1209,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
OS::get_singleton()->set_cmdline(execpath, main_args);
+ register_core_extensions(); //before display
+
GLOBAL_DEF("rendering/driver/driver_name", "Vulkan");
ProjectSettings::get_singleton()->set_custom_property_info("rendering/driver/driver_name",
PropertyInfo(Variant::STRING,
@@ -2004,6 +2017,11 @@ bool Main::start() {
return false;
}
+
+ if (dump_extension_api) {
+ NativeExtensionAPIDump::generate_extension_json_file("extension_api.json");
+ return false;
+ }
#endif
if (script == "" && game_path == "" && String(GLOBAL_GET("application/run/main_scene")) != "") {
diff --git a/modules/gdnative/include/gdnative/variant.h b/modules/gdnative/include/gdnative/variant.h
index dd4f76cf57..a88bd2878a 100644
--- a/modules/gdnative/include/gdnative/variant.h
+++ b/modules/gdnative/include/gdnative/variant.h
@@ -164,7 +164,7 @@ typedef void (*godot_validated_keyed_getter)(const godot_variant *p_base, const
typedef bool (*godot_validated_keyed_checker)(const godot_variant *p_base, const godot_variant *p_key, bool *r_valid);
typedef void (*godot_ptr_keyed_setter)(void *p_base, const void *p_key, const void *p_value);
typedef void (*godot_ptr_keyed_getter)(const void *p_base, const void *p_key, void *r_value);
-typedef bool (*godot_ptr_keyed_checker)(const godot_variant *p_base, const godot_variant *p_key);
+typedef uint32_t (*godot_ptr_keyed_checker)(const godot_variant *p_base, const godot_variant *p_key);
typedef void (*godot_validated_utility_function)(godot_variant *r_return, const godot_variant **p_arguments, int p_argument_count);
typedef void (*godot_ptr_utility_function)(void *r_return, const void **p_arguments, int p_argument_count);
diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs
index 21fe2706b2..451ce39f5c 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs
@@ -114,12 +114,12 @@ namespace GodotTools.Ides
if (Utils.OS.IsMacOS && editorId == ExternalEditorId.VisualStudioForMac)
{
vsForMacInstance = (vsForMacInstance?.IsDisposed ?? true ? null : vsForMacInstance) ??
- new MonoDevelop.Instantiate(solutionPath, MonoDevelop.EditorId.VisualStudioForMac);
+ new MonoDevelop.Instance(solutionPath, MonoDevelop.EditorId.VisualStudioForMac);
return vsForMacInstance;
}
monoDevelInstance = (monoDevelInstance?.IsDisposed ?? true ? null : monoDevelInstance) ??
- new MonoDevelop.Instantiate(solutionPath, MonoDevelop.EditorId.MonoDevelop);
+ new MonoDevelop.Instance(solutionPath, MonoDevelop.EditorId.MonoDevelop);
return monoDevelInstance;
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/PackedSceneExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/PackedSceneExtensions.cs
index 763f470504..214bbf5179 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/PackedSceneExtensions.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/PackedSceneExtensions.cs
@@ -8,9 +8,9 @@ namespace Godot
/// `Node.NotificationInstanced` notification on the root node.
/// </summary>
/// <typeparam name="T">The type to cast to. Should be a descendant of Node.</typeparam>
- public T Instance<T>(PackedScene.GenEditState editState = (PackedScene.GenEditState)0) where T : class
+ public T Instantiate<T>(PackedScene.GenEditState editState = (PackedScene.GenEditState)0) where T : class
{
- return (T)(object)Instance(editState);
+ return (T)(object)Instantiate(editState);
}
/// <summary>
@@ -19,9 +19,9 @@ namespace Godot
/// `Node.NotificationInstanced` notification on the root node.
/// </summary>
/// <typeparam name="T">The type to cast to. Should be a descendant of Node.</typeparam>
- public T InstanceOrNull<T>(PackedScene.GenEditState editState = (PackedScene.GenEditState)0) where T : class
+ public T InstantiateOrNull<T>(PackedScene.GenEditState editState = (PackedScene.GenEditState)0) where T : class
{
- return Instance(editState) as T;
+ return Instantiate(editState) as T;
}
}
}
diff --git a/modules/opensimplex/noise_texture.cpp b/modules/opensimplex/noise_texture.cpp
index 9e0155da94..66c52ffbf9 100644
--- a/modules/opensimplex/noise_texture.cpp
+++ b/modules/opensimplex/noise_texture.cpp
@@ -187,6 +187,7 @@ Ref<OpenSimplexNoise> NoiseTexture::get_noise() {
}
void NoiseTexture::set_width(int p_width) {
+ ERR_FAIL_COND(p_width <= 0);
if (p_width == size.x) {
return;
}
@@ -195,6 +196,7 @@ void NoiseTexture::set_width(int p_width) {
}
void NoiseTexture::set_height(int p_height) {
+ ERR_FAIL_COND(p_height <= 0);
if (p_height == size.y) {
return;
}
diff --git a/modules/visual_script/visual_script_editor.cpp b/modules/visual_script/visual_script_editor.cpp
index 51de8afc53..69920a81b6 100644
--- a/modules/visual_script/visual_script_editor.cpp
+++ b/modules/visual_script/visual_script_editor.cpp
@@ -1300,7 +1300,7 @@ void VisualScriptEditor::_create_function_dialog() {
void VisualScriptEditor::_create_function() {
String name = _validate_name((func_name_box->get_text() == "") ? "new_func" : func_name_box->get_text());
selected = name;
- Vector2 ofs = _get_available_pos();
+ Vector2 pos = _get_available_pos();
Ref<VisualScriptFunction> func_node;
func_node.instantiate();
@@ -1322,7 +1322,7 @@ void VisualScriptEditor::_create_function() {
undo_redo->create_action(TTR("Add Function"));
undo_redo->add_do_method(script.ptr(), "add_function", name, func_node_id);
undo_redo->add_undo_method(script.ptr(), "remove_function", name);
- undo_redo->add_do_method(script.ptr(), "add_node", func_node_id, func_node, ofs);
+ undo_redo->add_do_method(script.ptr(), "add_node", func_node_id, func_node, pos);
undo_redo->add_undo_method(script.ptr(), "remove_node", func_node_id);
undo_redo->add_do_method(this, "_update_members");
undo_redo->add_undo_method(this, "_update_members");
@@ -1417,7 +1417,7 @@ void VisualScriptEditor::_member_button(Object *p_item, int p_column, int p_butt
} else if (p_button == 0) {
String name = _validate_name("new_function");
selected = name;
- Vector2 ofs = _get_available_pos();
+ Vector2 pos = _get_available_pos();
Ref<VisualScriptFunction> func_node;
func_node.instantiate();
@@ -1426,7 +1426,7 @@ void VisualScriptEditor::_member_button(Object *p_item, int p_column, int p_butt
undo_redo->create_action(TTR("Add Function"));
undo_redo->add_do_method(script.ptr(), "add_function", name, fn_id);
- undo_redo->add_do_method(script.ptr(), "add_node", fn_id, func_node, ofs);
+ undo_redo->add_do_method(script.ptr(), "add_node", fn_id, func_node, pos);
undo_redo->add_undo_method(script.ptr(), "remove_function", name);
undo_redo->add_do_method(script.ptr(), "remove_node", fn_id);
undo_redo->add_do_method(this, "_update_members");
@@ -1621,17 +1621,19 @@ void VisualScriptEditor::_expression_text_changed(const String &p_text, int p_id
updating_graph = false;
}
-Vector2 VisualScriptEditor::_get_available_pos(bool centered, Vector2 ofs) const {
- if (centered) {
- ofs = graph->get_scroll_ofs() + graph->get_size() * 0.5;
- }
-
+Vector2 VisualScriptEditor::_get_pos_in_graph(Vector2 p_point) const {
+ Vector2 pos = (graph->get_scroll_ofs() + p_point) / (graph->get_zoom() * EDSCALE);
if (graph->is_using_snap()) {
int snap = graph->get_snap();
- ofs = ofs.snapped(Vector2(snap, snap));
+ pos = pos.snapped(Vector2(snap, snap));
}
+ return pos;
+}
- ofs /= EDSCALE;
+Vector2 VisualScriptEditor::_get_available_pos(bool p_centered, Vector2 p_pos) const {
+ if (p_centered) {
+ p_pos = _get_pos_in_graph(graph->get_size() * 0.5);
+ }
while (true) {
bool exists = false;
@@ -1639,8 +1641,8 @@ Vector2 VisualScriptEditor::_get_available_pos(bool centered, Vector2 ofs) const
script->get_node_list(&existing);
for (List<int>::Element *E = existing.front(); E; E = E->next()) {
Point2 pos = script->get_node_position(E->get());
- if (pos.distance_to(ofs) < 50) {
- ofs += Vector2(graph->get_snap(), graph->get_snap());
+ if (pos.distance_to(p_pos) < 50) {
+ p_pos += Vector2(graph->get_snap(), graph->get_snap());
exists = true;
break;
}
@@ -1651,7 +1653,7 @@ Vector2 VisualScriptEditor::_get_available_pos(bool centered, Vector2 ofs) const
break;
}
- return ofs;
+ return p_pos;
}
String VisualScriptEditor::_validate_name(const String &p_name) const {
@@ -2049,16 +2051,9 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da
return;
}
- Vector2 ofs = graph->get_scroll_ofs() + p_point;
-
- if (graph->is_using_snap()) {
- int snap = graph->get_snap();
- ofs = ofs.snapped(Vector2(snap, snap));
- }
-
- ofs /= EDSCALE;
+ Vector2 pos = _get_pos_in_graph(p_point);
- int new_id = _create_new_node_from_name(d["node_type"], ofs);
+ int new_id = _create_new_node_from_name(d["node_type"], pos);
Node *node = graph->get_node(itos(new_id));
if (node) {
@@ -2073,13 +2068,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da
#else
bool use_set = Input::get_singleton()->is_key_pressed(KEY_CTRL);
#endif
- Vector2 ofs = graph->get_scroll_ofs() + p_point;
- if (graph->is_using_snap()) {
- int snap = graph->get_snap();
- ofs = ofs.snapped(Vector2(snap, snap));
- }
-
- ofs /= EDSCALE;
+ Vector2 pos = _get_pos_in_graph(p_point);
Ref<VisualScriptNode> vnode;
if (use_set) {
@@ -2097,7 +2086,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da
int new_id = script->get_available_id();
undo_redo->create_action(TTR("Add Node"));
- undo_redo->add_do_method(script.ptr(), "add_node", new_id, vnode, ofs);
+ undo_redo->add_do_method(script.ptr(), "add_node", new_id, vnode, pos);
undo_redo->add_undo_method(script.ptr(), "remove_node", new_id);
undo_redo->add_do_method(this, "_update_graph");
undo_redo->add_undo_method(this, "_update_graph");
@@ -2111,13 +2100,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da
}
if (String(d["type"]) == "visual_script_function_drag") {
- Vector2 ofs = graph->get_scroll_ofs() + p_point;
- if (graph->is_using_snap()) {
- int snap = graph->get_snap();
- ofs = ofs.snapped(Vector2(snap, snap));
- }
-
- ofs /= EDSCALE;
+ Vector2 pos = _get_pos_in_graph(p_point);
Ref<VisualScriptFunctionCall> vnode;
vnode.instantiate();
@@ -2126,7 +2109,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da
int new_id = script->get_available_id();
undo_redo->create_action(TTR("Add Node"));
- undo_redo->add_do_method(script.ptr(), "add_node", new_id, vnode, ofs);
+ undo_redo->add_do_method(script.ptr(), "add_node", new_id, vnode, pos);
undo_redo->add_do_method(vnode.ptr(), "set_base_type", script->get_instance_base_type());
undo_redo->add_do_method(vnode.ptr(), "set_function", d["function"]);
@@ -2143,13 +2126,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da
}
if (String(d["type"]) == "visual_script_signal_drag") {
- Vector2 ofs = graph->get_scroll_ofs() + p_point;
- if (graph->is_using_snap()) {
- int snap = graph->get_snap();
- ofs = ofs.snapped(Vector2(snap, snap));
- }
-
- ofs /= EDSCALE;
+ Vector2 pos = _get_pos_in_graph(p_point);
Ref<VisualScriptEmitSignal> vnode;
vnode.instantiate();
@@ -2158,7 +2135,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da
int new_id = script->get_available_id();
undo_redo->create_action(TTR("Add Node"));
- undo_redo->add_do_method(script.ptr(), "add_node", new_id, vnode, ofs);
+ undo_redo->add_do_method(script.ptr(), "add_node", new_id, vnode, pos);
undo_redo->add_undo_method(script.ptr(), "remove_node", new_id);
undo_redo->add_do_method(this, "_update_graph");
undo_redo->add_undo_method(this, "_update_graph");
@@ -2172,13 +2149,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da
}
if (String(d["type"]) == "resource") {
- Vector2 ofs = graph->get_scroll_ofs() + p_point;
- if (graph->is_using_snap()) {
- int snap = graph->get_snap();
- ofs = ofs.snapped(Vector2(snap, snap));
- }
-
- ofs /= EDSCALE;
+ Vector2 pos = _get_pos_in_graph(p_point);
Ref<VisualScriptPreload> prnode;
prnode.instantiate();
@@ -2187,7 +2158,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da
int new_id = script->get_available_id();
undo_redo->create_action(TTR("Add Preload Node"));
- undo_redo->add_do_method(script.ptr(), "add_node", new_id, prnode, ofs);
+ undo_redo->add_do_method(script.ptr(), "add_node", new_id, prnode, pos);
undo_redo->add_undo_method(script.ptr(), "remove_node", new_id);
undo_redo->add_do_method(this, "_update_graph");
undo_redo->add_undo_method(this, "_update_graph");
@@ -2201,13 +2172,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da
}
if (String(d["type"]) == "files") {
- Vector2 ofs = graph->get_scroll_ofs() + p_point;
- if (graph->is_using_snap()) {
- int snap = graph->get_snap();
- ofs = ofs.snapped(Vector2(snap, snap));
- }
-
- ofs /= EDSCALE;
+ Vector2 pos = _get_pos_in_graph(p_point);
Array files = d["files"];
@@ -2227,11 +2192,11 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da
prnode.instantiate();
prnode->set_preload(res);
- undo_redo->add_do_method(script.ptr(), "add_node", new_id, prnode, ofs);
+ undo_redo->add_do_method(script.ptr(), "add_node", new_id, prnode, pos);
undo_redo->add_undo_method(script.ptr(), "remove_node", new_id);
new_ids.push_back(new_id);
new_id++;
- ofs += Vector2(20, 20) * EDSCALE;
+ pos += Vector2(20, 20);
}
undo_redo->add_do_method(this, "_update_graph");
@@ -2264,13 +2229,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da
Array nodes = d["nodes"];
- Vector2 ofs = graph->get_scroll_ofs() + p_point;
-
- if (graph->is_using_snap()) {
- int snap = graph->get_snap();
- ofs = ofs.snapped(Vector2(snap, snap));
- }
- ofs /= EDSCALE;
+ Vector2 pos = _get_pos_in_graph(p_point);
undo_redo->create_action(TTR("Add Node(s) From Tree"));
int base_id = script->get_available_id();
@@ -2305,11 +2264,11 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da
selecting_method_id = base_id;
}
- undo_redo->add_do_method(script.ptr(), "add_node", base_id, n, ofs);
+ undo_redo->add_do_method(script.ptr(), "add_node", base_id, n, pos);
undo_redo->add_undo_method(script.ptr(), "remove_node", base_id);
base_id++;
- ofs += Vector2(25, 25);
+ pos += Vector2(25, 25);
}
undo_redo->add_do_method(this, "_update_graph");
undo_redo->add_undo_method(this, "_update_graph");
@@ -2331,14 +2290,8 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da
}
Node *node = Object::cast_to<Node>(obj);
- Vector2 ofs = graph->get_scroll_ofs() + p_point;
-
- if (graph->is_using_snap()) {
- int snap = graph->get_snap();
- ofs = ofs.snapped(Vector2(snap, snap));
- }
+ Vector2 pos = _get_pos_in_graph(p_point);
- ofs /= EDSCALE;
#ifdef OSX_ENABLED
bool use_get = Input::get_singleton()->is_key_pressed(KEY_META);
#else
@@ -2375,7 +2328,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da
vnode = pget;
}
- undo_redo->add_do_method(script.ptr(), "add_node", base_id, vnode, ofs);
+ undo_redo->add_do_method(script.ptr(), "add_node", base_id, vnode, pos);
undo_redo->add_do_method(vnode.ptr(), "set_property", d["property"]);
if (!use_get) {
undo_redo->add_do_method(vnode.ptr(), "set_default_input_value", 0, d["value"]);
@@ -2420,7 +2373,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da
}
vnode = pget;
}
- undo_redo->add_do_method(script.ptr(), "add_node", base_id, vnode, ofs);
+ undo_redo->add_do_method(script.ptr(), "add_node", base_id, vnode, pos);
undo_redo->add_do_method(vnode.ptr(), "set_property", d["property"]);
if (!use_get) {
undo_redo->add_do_method(vnode.ptr(), "set_default_input_value", 0, d["value"]);
@@ -3009,7 +2962,7 @@ void VisualScriptEditor::_graph_connect_to_empty(const String &p_from, int p_fro
if (!vsn.is_valid()) {
return;
}
- if (vsn->get_output_value_port_count()) {
+ if (vsn->get_output_value_port_count() || vsn->get_output_sequence_port_count()) {
port_action_pos = p_release_pos;
}
@@ -3075,13 +3028,6 @@ VisualScriptNode::TypeGuess VisualScriptEditor::_guess_output_type(int p_port_ac
}
void VisualScriptEditor::_port_action_menu(int p_option) {
- Vector2 ofs = graph->get_scroll_ofs() + port_action_pos;
- if (graph->is_using_snap()) {
- int snap = graph->get_snap();
- ofs = ofs.snapped(Vector2(snap, snap));
- }
- ofs /= EDSCALE;
-
Set<int> vn;
switch (p_option) {
@@ -3172,13 +3118,7 @@ void VisualScriptEditor::connect_data(Ref<VisualScriptNode> vnode_old, Ref<Visua
}
void VisualScriptEditor::_selected_connect_node(const String &p_text, const String &p_category, const bool p_connecting) {
- Vector2 ofs = graph->get_scroll_ofs() + port_action_pos;
- if (graph->is_using_snap()) {
- int snap = graph->get_snap();
- ofs = ofs.snapped(Vector2(snap, snap));
- }
- ofs /= EDSCALE;
- ofs /= graph->get_zoom();
+ Vector2 pos = _get_pos_in_graph(port_action_pos);
Set<int> vn;
@@ -3216,7 +3156,7 @@ void VisualScriptEditor::_selected_connect_node(const String &p_text, const Stri
}
undo_redo->create_action(TTR("Add Node"));
- undo_redo->add_do_method(script.ptr(), "add_node", new_id, vnode_new, ofs);
+ undo_redo->add_do_method(script.ptr(), "add_node", new_id, vnode_new, pos);
if (vnode_old.is_valid() && p_connecting) {
connect_seq(vnode_old, vnode_new, new_id);
connect_data(vnode_old, vnode_new, new_id);
@@ -3279,7 +3219,7 @@ void VisualScriptEditor::_selected_connect_node(const String &p_text, const Stri
int new_id = script->get_available_id();
undo_redo->create_action(TTR("Add Node"));
- undo_redo->add_do_method(script.ptr(), "add_node", new_id, vnode, ofs);
+ undo_redo->add_do_method(script.ptr(), "add_node", new_id, vnode, pos);
undo_redo->add_undo_method(script.ptr(), "remove_node", new_id);
undo_redo->add_do_method(this, "_update_graph", new_id);
undo_redo->add_undo_method(this, "_update_graph", new_id);
@@ -3478,9 +3418,9 @@ void VisualScriptEditor::_selected_new_virtual_method(const String &p_text, cons
func_node->add_argument(minfo.arguments[i].type, minfo.arguments[i].name, -1, minfo.arguments[i].hint, minfo.arguments[i].hint_string);
}
- Vector2 ofs = _get_available_pos();
+ Vector2 pos = _get_available_pos();
- undo_redo->add_do_method(script.ptr(), "add_node", fn_id, func_node, ofs);
+ undo_redo->add_do_method(script.ptr(), "add_node", fn_id, func_node, pos);
undo_redo->add_undo_method(script.ptr(), "remove_node", fn_id);
if (minfo.return_val.type != Variant::NIL || minfo.return_val.usage & PROPERTY_USAGE_NIL_IS_VARIANT) {
Ref<VisualScriptReturn> ret_node;
@@ -3489,7 +3429,7 @@ void VisualScriptEditor::_selected_new_virtual_method(const String &p_text, cons
ret_node->set_enable_return_value(true);
ret_node->set_name(name);
int nid = script->get_available_id() + 1;
- undo_redo->add_do_method(script.ptr(), "add_node", nid, ret_node, _get_available_pos(false, ofs + Vector2(500, 0)));
+ undo_redo->add_do_method(script.ptr(), "add_node", nid, ret_node, _get_available_pos(false, pos + Vector2(500, 0)));
undo_redo->add_undo_method(script.ptr(), "remove_node", nid);
}
@@ -3993,7 +3933,7 @@ void VisualScriptEditor::_menu_option(int p_what) {
{
String new_fn = _validate_name("new_function");
- Vector2 ofs = _get_available_pos(false, script->get_node_position(start_node) - Vector2(80, 150));
+ Vector2 pos = _get_available_pos(false, script->get_node_position(start_node) - Vector2(80, 150));
Ref<VisualScriptFunction> func_node;
func_node.instantiate();
@@ -4002,7 +3942,7 @@ void VisualScriptEditor::_menu_option(int p_what) {
undo_redo->create_action(TTR("Create Function"));
undo_redo->add_do_method(script.ptr(), "add_function", new_fn, fn_id);
- undo_redo->add_do_method(script.ptr(), "add_node", fn_id, func_node, ofs);
+ undo_redo->add_do_method(script.ptr(), "add_node", fn_id, func_node, pos);
undo_redo->add_undo_method(script.ptr(), "remove_function", new_fn);
undo_redo->add_undo_method(script.ptr(), "remove_node", fn_id);
undo_redo->add_do_method(this, "_update_members");
@@ -4045,8 +3985,8 @@ void VisualScriptEditor::_menu_option(int p_what) {
int ret_id = fn_id + (m++);
selections.insert(ret_id);
- Vector2 ofsi = _get_available_pos(false, script->get_node_position(G->get()) + Vector2(80, -100));
- undo_redo->add_do_method(script.ptr(), "add_node", ret_id, ret_node, ofsi);
+ Vector2 posi = _get_available_pos(false, script->get_node_position(G->get()) + Vector2(80, -100));
+ undo_redo->add_do_method(script.ptr(), "add_node", ret_id, ret_node, posi);
undo_redo->add_undo_method(script.ptr(), "remove_node", ret_id);
undo_redo->add_do_method(script.ptr(), "sequence_connect", G->get(), 0, ret_id);
diff --git a/modules/visual_script/visual_script_editor.h b/modules/visual_script/visual_script_editor.h
index ca06b807cc..1f0f087be7 100644
--- a/modules/visual_script/visual_script_editor.h
+++ b/modules/visual_script/visual_script_editor.h
@@ -226,7 +226,8 @@ class VisualScriptEditor : public ScriptEditorBase {
void _update_node_size(int p_id);
void _port_name_focus_out(const Node *p_name_box, int p_id, int p_port, bool is_input);
- Vector2 _get_available_pos(bool centered = true, Vector2 ofs = Vector2()) const;
+ Vector2 _get_pos_in_graph(Vector2 p_point) const;
+ Vector2 _get_available_pos(bool p_centered = true, Vector2 p_pos = Vector2()) const;
bool node_has_sequence_connections(int p_id);
diff --git a/modules/visual_script/visual_script_nodes.cpp b/modules/visual_script/visual_script_nodes.cpp
index 60a132c6a5..60392d8f42 100644
--- a/modules/visual_script/visual_script_nodes.cpp
+++ b/modules/visual_script/visual_script_nodes.cpp
@@ -946,39 +946,69 @@ static const char *op_names[] = {
};
String VisualScriptOperator::get_caption() const {
- static const char32_t *op_names[] = {
- //comparison
- U"A = B", //OP_EQUAL,
- U"A \u2260 B", //OP_NOT_EQUAL,
- U"A < B", //OP_LESS,
- U"A \u2264 B", //OP_LESS_EQUAL,
- U"A > B", //OP_GREATER,
- U"A \u2265 B", //OP_GREATER_EQUAL,
- //mathematic
- U"A + B", //OP_ADD,
- U"A - B", //OP_SUBTRACT,
- U"A \u00D7 B", //OP_MULTIPLY,
- U"A \u00F7 B", //OP_DIVIDE,
- U"\u00AC A", //OP_NEGATE,
- U"+ A", //OP_POSITIVE,
- U"A mod B", //OP_MODULE,
- U"A .. B", //OP_STRING_CONCAT,
- //bitwise
- U"A << B", //OP_SHIFT_LEFT,
- U"A >> B", //OP_SHIFT_RIGHT,
- U"A & B", //OP_BIT_AND,
- U"A | B", //OP_BIT_OR,
- U"A ^ B", //OP_BIT_XOR,
- U"~A", //OP_BIT_NEGATE,
- //logic
- U"A and B", //OP_AND,
- U"A or B", //OP_OR,
- U"A xor B", //OP_XOR,
- U"not A", //OP_NOT,
- U"A in B", //OP_IN,
-
- };
- return op_names[op];
+ switch (op) {
+ // comparison
+ case Variant::OP_EQUAL:
+ return U"A = B";
+ case Variant::OP_NOT_EQUAL:
+ return U"A \u2260 B";
+ case Variant::OP_LESS:
+ return U"A < B";
+ case Variant::OP_LESS_EQUAL:
+ return U"A \u2264 B";
+ case Variant::OP_GREATER:
+ return U"A > B";
+ case Variant::OP_GREATER_EQUAL:
+ return U"A \u2265 B";
+
+ // mathematic
+ case Variant::OP_ADD:
+ return U"A + B";
+ case Variant::OP_SUBTRACT:
+ return U"A - B";
+ case Variant::OP_MULTIPLY:
+ return U"A \u00D7 B";
+ case Variant::OP_DIVIDE:
+ return U"A \u00F7 B";
+ case Variant::OP_NEGATE:
+ return U"\u00AC A";
+ case Variant::OP_POSITIVE:
+ return U"+ A";
+ case Variant::OP_MODULE:
+ return U"A mod B";
+
+ // bitwise
+ case Variant::OP_SHIFT_LEFT:
+ return U"A << B";
+ case Variant::OP_SHIFT_RIGHT:
+ return U"A >> B";
+ case Variant::OP_BIT_AND:
+ return U"A & B";
+ case Variant::OP_BIT_OR:
+ return U"A | B";
+ case Variant::OP_BIT_XOR:
+ return U"A ^ B";
+ case Variant::OP_BIT_NEGATE:
+ return U"~A";
+
+ // logic
+ case Variant::OP_AND:
+ return U"A and B";
+ case Variant::OP_OR:
+ return U"A or B";
+ case Variant::OP_XOR:
+ return U"A xor B";
+ case Variant::OP_NOT:
+ return U"not A";
+ case Variant::OP_IN:
+ return U"A in B";
+
+ default: {
+ ERR_FAIL_V_MSG(
+ U"Unknown node",
+ U"Unknown node type encountered, caption not available.");
+ }
+ }
}
void VisualScriptOperator::set_operator(Variant::Operator p_op) {
diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp
index 923435eec5..d01232665b 100644
--- a/platform/android/export/export.cpp
+++ b/platform/android/export/export.cpp
@@ -2568,19 +2568,35 @@ public:
// Sensitive additions must be done below the logging statement.
print_verbose("Build Android project using gradle command: " + String("\n") + build_command + " " + join_list(cmdline, String(" ")));
- if (should_sign && !p_debug) {
- // Pass the release keystore info as well
- String release_keystore = p_preset->get("keystore/release");
- String release_username = p_preset->get("keystore/release_user");
- String release_password = p_preset->get("keystore/release_password");
- if (!FileAccess::exists(release_keystore)) {
- EditorNode::add_io_error("Could not find keystore, unable to export.");
- return ERR_FILE_CANT_OPEN;
- }
+ if (should_sign) {
+ if (p_debug) {
+ String debug_keystore = p_preset->get("keystore/debug");
+ String debug_password = p_preset->get("keystore/debug_password");
+ String debug_user = p_preset->get("keystore/debug_user");
+
+ if (debug_keystore.is_empty()) {
+ debug_keystore = EditorSettings::get_singleton()->get("export/android/debug_keystore");
+ debug_password = EditorSettings::get_singleton()->get("export/android/debug_keystore_pass");
+ debug_user = EditorSettings::get_singleton()->get("export/android/debug_keystore_user");
+ }
+
+ cmdline.push_back("-Pdebug_keystore_file=" + debug_keystore); // argument to specify the debug keystore file.
+ cmdline.push_back("-Pdebug_keystore_alias=" + debug_user); // argument to specify the debug keystore alias.
+ cmdline.push_back("-Pdebug_keystore_password=" + debug_password); // argument to specify the debug keystore password.
+ } else {
+ // Pass the release keystore info as well
+ String release_keystore = p_preset->get("keystore/release");
+ String release_username = p_preset->get("keystore/release_user");
+ String release_password = p_preset->get("keystore/release_password");
+ if (!FileAccess::exists(release_keystore)) {
+ EditorNode::add_io_error("Could not find keystore, unable to export.");
+ return ERR_FILE_CANT_OPEN;
+ }
- cmdline.push_back("-Prelease_keystore_file=" + release_keystore); // argument to specify the release keystore file.
- cmdline.push_back("-Prelease_keystore_alias=" + release_username); // argument to specify the release keystore alias.
- cmdline.push_back("-Prelease_keystore_password=" + release_password); // argument to specity the release keystore password.
+ cmdline.push_back("-Prelease_keystore_file=" + release_keystore); // argument to specify the release keystore file.
+ cmdline.push_back("-Prelease_keystore_alias=" + release_username); // argument to specify the release keystore alias.
+ cmdline.push_back("-Prelease_keystore_password=" + release_password); // argument to specify the release keystore password.
+ }
}
int result = EditorNode::get_singleton()->execute_and_show_output(TTR("Building Android Project (gradle)"), build_command, cmdline);
diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle
index 1b1fb47bd8..18e07c3762 100644
--- a/platform/android/java/app/build.gradle
+++ b/platform/android/java/app/build.gradle
@@ -6,7 +6,7 @@ buildscript {
repositories {
google()
- jcenter()
+ mavenCentral()
}
dependencies {
classpath libraries.androidGradlePlugin
@@ -18,9 +18,8 @@ apply plugin: 'com.android.application'
allprojects {
repositories {
- mavenCentral()
google()
- jcenter()
+ mavenCentral()
// Godot user plugins custom maven repos
String[] mavenRepos = getGodotPluginsMavenRepos()
@@ -113,6 +112,15 @@ android {
}
signingConfigs {
+ debug {
+ if (hasCustomDebugKeystore()) {
+ storeFile new File(getDebugKeystoreFile())
+ storePassword getDebugKeystorePassword()
+ keyAlias getDebugKeyAlias()
+ keyPassword getDebugKeystorePassword()
+ }
+ }
+
release {
File keystoreFile = new File(getReleaseKeystoreFile())
if (keystoreFile.isFile()) {
diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle
index b278d15bdf..81fc87b7ef 100644
--- a/platform/android/java/app/config.gradle
+++ b/platform/android/java/app/config.gradle
@@ -1,11 +1,11 @@
ext.versions = [
- androidGradlePlugin: '4.0.1',
+ androidGradlePlugin: '4.2.1',
compileSdk : 29,
minSdk : 18,
targetSdk : 29,
buildTools : '30.0.3',
supportCoreUtils : '1.0.0',
- kotlinVersion : '1.4.10',
+ kotlinVersion : '1.5.10',
v4Support : '1.0.0',
javaVersion : 1.8,
ndkVersion : '21.4.7075529' // Also update 'platform/android/detect.py#get_project_ndk_version()' when this is updated.
@@ -191,6 +191,35 @@ ext.getGodotPluginsLocalBinaries = { ->
return binDeps
}
+ext.getDebugKeystoreFile = { ->
+ String keystoreFile = project.hasProperty("debug_keystore_file") ? project.property("debug_keystore_file") : ""
+ if (keystoreFile == null || keystoreFile.isEmpty()) {
+ keystoreFile = "."
+ }
+ return keystoreFile
+}
+
+ext.hasCustomDebugKeystore = { ->
+ File keystoreFile = new File(getDebugKeystoreFile())
+ return keystoreFile.isFile()
+}
+
+ext.getDebugKeystorePassword = { ->
+ String keystorePassword = project.hasProperty("debug_keystore_password") ? project.property("debug_keystore_password") : ""
+ if (keystorePassword == null || keystorePassword.isEmpty()) {
+ keystorePassword = "android"
+ }
+ return keystorePassword
+}
+
+ext.getDebugKeyAlias = { ->
+ String keyAlias = project.hasProperty("debug_keystore_alias") ? project.property("debug_keystore_alias") : ""
+ if (keyAlias == null || keyAlias.isEmpty()) {
+ keyAlias = "androiddebugkey"
+ }
+ return keyAlias
+}
+
ext.getReleaseKeystoreFile = { ->
String keystoreFile = project.hasProperty("release_keystore_file") ? project.property("release_keystore_file") : ""
if (keystoreFile == null || keystoreFile.isEmpty()) {
diff --git a/platform/android/java/build.gradle b/platform/android/java/build.gradle
index a28888d80d..ee24a46d9f 100644
--- a/platform/android/java/build.gradle
+++ b/platform/android/java/build.gradle
@@ -5,7 +5,7 @@ buildscript {
repositories {
google()
- jcenter()
+ mavenCentral()
}
dependencies {
classpath libraries.androidGradlePlugin
@@ -16,7 +16,6 @@ buildscript {
allprojects {
repositories {
google()
- jcenter()
mavenCentral()
}
}
diff --git a/platform/android/java/gradle/wrapper/gradle-wrapper.properties b/platform/android/java/gradle/wrapper/gradle-wrapper.properties
index a7d8a0f310..74c5636f8a 100644
--- a/platform/android/java/gradle/wrapper/gradle-wrapper.properties
+++ b/platform/android/java/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Mon Sep 02 02:44:30 PDT 2019
+#Wed Jun 23 23:42:22 PDT 2021
distributionBase=GRADLE_USER_HOME
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip
distributionPath=wrapper/dists
-zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
+zipStoreBase=GRADLE_USER_HOME
diff --git a/platform/iphone/export/export.cpp b/platform/iphone/export/export.cpp
index 8e977e1783..1d1961ac2f 100644
--- a/platform/iphone/export/export.cpp
+++ b/platform/iphone/export/export.cpp
@@ -367,6 +367,26 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options)
for (int i = 0; i < found_plugins.size(); i++) {
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "plugins/" + found_plugins[i].name), false));
}
+
+ for (int i = 0; i < found_plugins.size(); i++) {
+ // Editable plugin plist values
+ PluginConfigIOS plugin = found_plugins[i];
+ const String *K = nullptr;
+
+ while ((K = plugin.plist.next(K))) {
+ String key = *K;
+ PluginConfigIOS::PlistItem item = plugin.plist[key];
+ switch (item.type) {
+ case PluginConfigIOS::PlistItemType::STRING_INPUT: {
+ String preset_name = "plugins_plist/" + key;
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, preset_name), item.value));
+ } break;
+ default:
+ continue;
+ }
+ }
+ }
+
plugins_changed.clear();
plugins = found_plugins;
@@ -1467,13 +1487,28 @@ Error EditorExportPlatformIOS::_export_ios_plugins(const Ref<EditorExportPreset>
while ((K = plugin.plist.next(K))) {
String key = *K;
- String value = plugin.plist[key];
+ PluginConfigIOS::PlistItem item = plugin.plist[key];
+
+ String value;
+
+ switch (item.type) {
+ case PluginConfigIOS::PlistItemType::STRING_INPUT: {
+ String preset_name = "plugins_plist/" + key;
+ String input_value = p_preset->get(preset_name);
+ value = "<string>" + input_value + "</string>";
+ } break;
+ default:
+ value = item.value;
+ break;
+ }
if (key.is_empty() || value.is_empty()) {
continue;
}
- plist_values[key] = value;
+ String plist_key = "<key>" + key + "</key>";
+
+ plist_values[plist_key] = value;
}
// CPP Code
@@ -1500,7 +1535,7 @@ Error EditorExportPlatformIOS::_export_ios_plugins(const Ref<EditorExportPreset>
continue;
}
- p_config_data.plist_content += "<key>" + key + "</key><string>" + value + "</string>\n";
+ p_config_data.plist_content += key + value + "\n";
}
}
diff --git a/platform/iphone/plugin/godot_plugin_config.h b/platform/iphone/plugin/godot_plugin_config.h
index 4d0c67bfff..06770260aa 100644
--- a/platform/iphone/plugin/godot_plugin_config.h
+++ b/platform/iphone/plugin/godot_plugin_config.h
@@ -70,6 +70,20 @@ struct PluginConfigIOS {
inline static const char *PLIST_SECTION = "plist";
+ enum PlistItemType {
+ UNKNOWN,
+ STRING,
+ INTEGER,
+ BOOLEAN,
+ RAW,
+ STRING_INPUT,
+ };
+
+ struct PlistItem {
+ PlistItemType type;
+ String value;
+ };
+
// Set to true when the config file is properly loaded.
bool valid_config = false;
bool supports_targets = false;
@@ -93,8 +107,10 @@ struct PluginConfigIOS {
Vector<String> linker_flags;
// Optional plist section
- // Supports only string types for now
- HashMap<String, String> plist;
+ // String value is default value.
+ // Currently supports `string`, `boolean`, `integer`, `raw`, `string_input` types
+ // <name>:<type> = <value>
+ HashMap<String, PlistItem> plist;
};
static inline String resolve_local_dependency_path(String plugin_config_dir, String dependency_path) {
@@ -273,13 +289,68 @@ static inline PluginConfigIOS load_plugin_config(Ref<ConfigFile> config_file, co
config_file->get_section_keys(PluginConfigIOS::PLIST_SECTION, &keys);
for (int i = 0; i < keys.size(); i++) {
- String value = config_file->get_value(PluginConfigIOS::PLIST_SECTION, keys[i], String());
+ Vector<String> key_components = keys[i].split(":");
+
+ String key_value = "";
+ PluginConfigIOS::PlistItemType key_type = PluginConfigIOS::PlistItemType::UNKNOWN;
+
+ if (key_components.size() == 1) {
+ key_value = key_components[0];
+ key_type = PluginConfigIOS::PlistItemType::STRING;
+ } else if (key_components.size() == 2) {
+ key_value = key_components[0];
+
+ if (key_components[1].to_lower() == "string") {
+ key_type = PluginConfigIOS::PlistItemType::STRING;
+ } else if (key_components[1].to_lower() == "integer") {
+ key_type = PluginConfigIOS::PlistItemType::INTEGER;
+ } else if (key_components[1].to_lower() == "boolean") {
+ key_type = PluginConfigIOS::PlistItemType::BOOLEAN;
+ } else if (key_components[1].to_lower() == "raw") {
+ key_type = PluginConfigIOS::PlistItemType::RAW;
+ } else if (key_components[1].to_lower() == "string_input") {
+ key_type = PluginConfigIOS::PlistItemType::STRING_INPUT;
+ }
+ }
- if (value.is_empty()) {
+ if (key_value.is_empty() || key_type == PluginConfigIOS::PlistItemType::UNKNOWN) {
continue;
}
- plugin_config.plist[keys[i]] = value;
+ String value;
+
+ switch (key_type) {
+ case PluginConfigIOS::PlistItemType::STRING: {
+ String raw_value = config_file->get_value(PluginConfigIOS::PLIST_SECTION, keys[i], String());
+ value = "<string>" + raw_value + "</string>";
+ } break;
+ case PluginConfigIOS::PlistItemType::INTEGER: {
+ int raw_value = config_file->get_value(PluginConfigIOS::PLIST_SECTION, keys[i], 0);
+ Dictionary value_dictionary;
+ String value_format = "<integer>$value</integer>";
+ value_dictionary["value"] = raw_value;
+ value = value_format.format(value_dictionary, "$_");
+ } break;
+ case PluginConfigIOS::PlistItemType::BOOLEAN:
+ if (config_file->get_value(PluginConfigIOS::PLIST_SECTION, keys[i], false)) {
+ value = "<true/>";
+ } else {
+ value = "<false/>";
+ }
+ break;
+ case PluginConfigIOS::PlistItemType::RAW: {
+ String raw_value = config_file->get_value(PluginConfigIOS::PLIST_SECTION, keys[i], String());
+ value = raw_value;
+ } break;
+ case PluginConfigIOS::PlistItemType::STRING_INPUT: {
+ String raw_value = config_file->get_value(PluginConfigIOS::PLIST_SECTION, keys[i], String());
+ value = raw_value;
+ } break;
+ default:
+ continue;
+ }
+
+ plugin_config.plist[key_value] = PluginConfigIOS::PlistItem{ key_type, value };
}
}
diff --git a/platform/javascript/export/export.cpp b/platform/javascript/export/export.cpp
index 58cecfef63..51c72e10eb 100644
--- a/platform/javascript/export/export.cpp
+++ b/platform/javascript/export/export.cpp
@@ -135,8 +135,11 @@ public:
// Wrong protocol
ERR_FAIL_COND_MSG(req[0] != "GET" || req[2] != "HTTP/1.1", "Invalid method or HTTP version.");
- const String req_file = req[1].get_file();
- const String req_ext = req[1].get_extension();
+ const int query_index = req[1].find_char('?');
+ const String path = (query_index == -1) ? req[1] : req[1].substr(0, query_index);
+
+ const String req_file = path.get_file();
+ const String req_ext = path.get_extension();
const String cache_path = EditorPaths::get_singleton()->get_cache_dir().plus_file("web");
const String filepath = cache_path.plus_file(req_file);
diff --git a/platform/javascript/http_client_javascript.cpp b/platform/javascript/http_client_javascript.cpp
index a6cf4b0eb8..f7d78abcea 100644
--- a/platform/javascript/http_client_javascript.cpp
+++ b/platform/javascript/http_client_javascript.cpp
@@ -28,45 +28,19 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#include "core/io/http_client.h"
+#include "http_client_javascript.h"
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#include "stddef.h"
-
-typedef enum {
- GODOT_JS_FETCH_STATE_REQUESTING = 0,
- GODOT_JS_FETCH_STATE_BODY = 1,
- GODOT_JS_FETCH_STATE_DONE = 2,
- GODOT_JS_FETCH_STATE_ERROR = -1,
-} godot_js_fetch_state_t;
-
-extern int godot_js_fetch_create(const char *p_method, const char *p_url, const char **p_headers, int p_headers_len, const uint8_t *p_body, int p_body_len);
-extern int godot_js_fetch_read_headers(int p_id, void (*parse_callback)(int p_size, const char **p_headers, void *p_ref), void *p_ref);
-extern int godot_js_fetch_read_chunk(int p_id, uint8_t *p_buf, int p_buf_size);
-extern void godot_js_fetch_free(int p_id);
-extern godot_js_fetch_state_t godot_js_fetch_state_get(int p_id);
-extern int godot_js_fetch_body_length_get(int p_id);
-extern int godot_js_fetch_http_status_get(int p_id);
-extern int godot_js_fetch_is_chunked(int p_id);
-
-#ifdef __cplusplus
-}
-#endif
-
-void HTTPClient::_parse_headers(int p_len, const char **p_headers, void *p_ref) {
- HTTPClient *client = static_cast<HTTPClient *>(p_ref);
+void HTTPClientJavaScript::_parse_headers(int p_len, const char **p_headers, void *p_ref) {
+ HTTPClientJavaScript *client = static_cast<HTTPClientJavaScript *>(p_ref);
for (int i = 0; i < p_len; i++) {
client->response_headers.push_back(String::utf8(p_headers[i]));
}
}
-Error HTTPClient::connect_to_host(const String &p_host, int p_port, bool p_ssl, bool p_verify_host) {
+Error HTTPClientJavaScript::connect_to_host(const String &p_host, int p_port, bool p_ssl, bool p_verify_host) {
close();
if (p_ssl && !p_verify_host) {
- WARN_PRINT("Disabling HTTPClient's host verification is not supported for the HTML5 platform, host will be verified");
+ WARN_PRINT("Disabling HTTPClientJavaScript's host verification is not supported for the HTML5 platform, host will be verified");
}
port = p_port;
@@ -97,15 +71,15 @@ Error HTTPClient::connect_to_host(const String &p_host, int p_port, bool p_ssl,
return OK;
}
-void HTTPClient::set_connection(const Ref<StreamPeer> &p_connection) {
- ERR_FAIL_MSG("Accessing an HTTPClient's StreamPeer is not supported for the HTML5 platform.");
+void HTTPClientJavaScript::set_connection(const Ref<StreamPeer> &p_connection) {
+ ERR_FAIL_MSG("Accessing an HTTPClientJavaScript's StreamPeer is not supported for the HTML5 platform.");
}
-Ref<StreamPeer> HTTPClient::get_connection() const {
- ERR_FAIL_V_MSG(REF(), "Accessing an HTTPClient's StreamPeer is not supported for the HTML5 platform.");
+Ref<StreamPeer> HTTPClientJavaScript::get_connection() const {
+ ERR_FAIL_V_MSG(REF(), "Accessing an HTTPClientJavaScript's StreamPeer is not supported for the HTML5 platform.");
}
-Error HTTPClient::make_request(Method p_method, const String &p_url, const Vector<String> &p_headers, const uint8_t *p_body, int p_body_len) {
+Error HTTPClientJavaScript::request(Method p_method, const String &p_url, const Vector<String> &p_headers, const uint8_t *p_body, int p_body_len) {
ERR_FAIL_INDEX_V(p_method, METHOD_MAX, ERR_INVALID_PARAMETER);
ERR_FAIL_COND_V_MSG(p_method == METHOD_TRACE || p_method == METHOD_CONNECT, ERR_UNAVAILABLE, "HTTP methods TRACE and CONNECT are not supported for the HTML5 platform.");
ERR_FAIL_COND_V(status != STATUS_CONNECTED, ERR_INVALID_PARAMETER);
@@ -128,22 +102,7 @@ Error HTTPClient::make_request(Method p_method, const String &p_url, const Vecto
return OK;
}
-Error HTTPClient::request_raw(Method p_method, const String &p_url, const Vector<String> &p_headers, const Vector<uint8_t> &p_body) {
- if (p_body.is_empty()) {
- return make_request(p_method, p_url, p_headers, nullptr, 0);
- }
- return make_request(p_method, p_url, p_headers, p_body.ptr(), p_body.size());
-}
-
-Error HTTPClient::request(Method p_method, const String &p_url, const Vector<String> &p_headers, const String &p_body) {
- if (p_body.is_empty()) {
- return make_request(p_method, p_url, p_headers, nullptr, 0);
- }
- const CharString cs = p_body.utf8();
- return make_request(p_method, p_url, p_headers, (const uint8_t *)cs.get_data(), cs.size() - 1);
-}
-
-void HTTPClient::close() {
+void HTTPClientJavaScript::close() {
host = "";
port = -1;
use_tls = false;
@@ -157,23 +116,23 @@ void HTTPClient::close() {
}
}
-HTTPClient::Status HTTPClient::get_status() const {
+HTTPClientJavaScript::Status HTTPClientJavaScript::get_status() const {
return status;
}
-bool HTTPClient::has_response() const {
+bool HTTPClientJavaScript::has_response() const {
return response_headers.size() > 0;
}
-bool HTTPClient::is_response_chunked() const {
+bool HTTPClientJavaScript::is_response_chunked() const {
return godot_js_fetch_is_chunked(js_id);
}
-int HTTPClient::get_response_code() const {
+int HTTPClientJavaScript::get_response_code() const {
return polled_response_code;
}
-Error HTTPClient::get_response_headers(List<String> *r_response) {
+Error HTTPClientJavaScript::get_response_headers(List<String> *r_response) {
if (!response_headers.size()) {
return ERR_INVALID_PARAMETER;
}
@@ -184,11 +143,11 @@ Error HTTPClient::get_response_headers(List<String> *r_response) {
return OK;
}
-int HTTPClient::get_response_body_length() const {
+int HTTPClientJavaScript::get_response_body_length() const {
return godot_js_fetch_body_length_get(js_id);
}
-PackedByteArray HTTPClient::read_response_body_chunk() {
+PackedByteArray HTTPClientJavaScript::read_response_body_chunk() {
ERR_FAIL_COND_V(status != STATUS_BODY, PackedByteArray());
if (response_buffer.size() != read_limit) {
@@ -213,23 +172,23 @@ PackedByteArray HTTPClient::read_response_body_chunk() {
return chunk;
}
-void HTTPClient::set_blocking_mode(bool p_enable) {
- ERR_FAIL_COND_MSG(p_enable, "HTTPClient blocking mode is not supported for the HTML5 platform.");
+void HTTPClientJavaScript::set_blocking_mode(bool p_enable) {
+ ERR_FAIL_COND_MSG(p_enable, "HTTPClientJavaScript blocking mode is not supported for the HTML5 platform.");
}
-bool HTTPClient::is_blocking_mode_enabled() const {
+bool HTTPClientJavaScript::is_blocking_mode_enabled() const {
return false;
}
-void HTTPClient::set_read_chunk_size(int p_size) {
+void HTTPClientJavaScript::set_read_chunk_size(int p_size) {
read_limit = p_size;
}
-int HTTPClient::get_read_chunk_size() const {
+int HTTPClientJavaScript::get_read_chunk_size() const {
return read_limit;
}
-Error HTTPClient::poll() {
+Error HTTPClientJavaScript::poll() {
switch (status) {
case STATUS_DISCONNECTED:
return ERR_UNCONFIGURED;
@@ -263,7 +222,7 @@ Error HTTPClient::poll() {
#ifdef DEBUG_ENABLED
// forcing synchronous requests is not possible on the web
if (last_polling_frame == Engine::get_singleton()->get_process_frames()) {
- WARN_PRINT("HTTPClient polled multiple times in one frame, "
+ WARN_PRINT("HTTPClientJavaScript polled multiple times in one frame, "
"but request cannot progress more than once per "
"frame on the HTML5 platform.");
}
@@ -294,9 +253,15 @@ Error HTTPClient::poll() {
return OK;
}
-HTTPClient::HTTPClient() {
+HTTPClient *HTTPClientJavaScript::_create_func() {
+ return memnew(HTTPClientJavaScript);
+}
+
+HTTPClient *(*HTTPClient::_create)() = HTTPClientJavaScript::_create_func;
+
+HTTPClientJavaScript::HTTPClientJavaScript() {
}
-HTTPClient::~HTTPClient() {
+HTTPClientJavaScript::~HTTPClientJavaScript() {
close();
}
diff --git a/platform/javascript/http_client_javascript.h b/platform/javascript/http_client_javascript.h
new file mode 100644
index 0000000000..33f91f67b6
--- /dev/null
+++ b/platform/javascript/http_client_javascript.h
@@ -0,0 +1,108 @@
+/*************************************************************************/
+/* http_client_javascript.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef HTTP_CLIENT_JAVASCRIPT_H
+#define HTTP_CLIENT_JAVASCRIPT_H
+
+#include "core/io/http_client.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "stddef.h"
+
+typedef enum {
+ GODOT_JS_FETCH_STATE_REQUESTING = 0,
+ GODOT_JS_FETCH_STATE_BODY = 1,
+ GODOT_JS_FETCH_STATE_DONE = 2,
+ GODOT_JS_FETCH_STATE_ERROR = -1,
+} godot_js_fetch_state_t;
+
+extern int godot_js_fetch_create(const char *p_method, const char *p_url, const char **p_headers, int p_headers_len, const uint8_t *p_body, int p_body_len);
+extern int godot_js_fetch_read_headers(int p_id, void (*parse_callback)(int p_size, const char **p_headers, void *p_ref), void *p_ref);
+extern int godot_js_fetch_read_chunk(int p_id, uint8_t *p_buf, int p_buf_size);
+extern void godot_js_fetch_free(int p_id);
+extern godot_js_fetch_state_t godot_js_fetch_state_get(int p_id);
+extern int godot_js_fetch_body_length_get(int p_id);
+extern int godot_js_fetch_http_status_get(int p_id);
+extern int godot_js_fetch_is_chunked(int p_id);
+
+#ifdef __cplusplus
+}
+#endif
+
+class HTTPClientJavaScript : public HTTPClient {
+private:
+ int js_id = 0;
+ Status status = STATUS_DISCONNECTED;
+
+ // 64 KiB by default (favors fast download speeds at the cost of memory usage).
+ int read_limit = 65536;
+
+ String host;
+ int port = -1;
+ bool use_tls = false;
+
+ int polled_response_code = 0;
+ Vector<String> response_headers;
+ Vector<uint8_t> response_buffer;
+
+#ifdef DEBUG_ENABLED
+ uint64_t last_polling_frame = 0;
+#endif
+
+ static void _parse_headers(int p_len, const char **p_headers, void *p_ref);
+
+public:
+ static HTTPClient *_create_func();
+
+ Error request(Method p_method, const String &p_url, const Vector<String> &p_headers, const uint8_t *p_body, int p_body_size) override;
+
+ Error connect_to_host(const String &p_host, int p_port = -1, bool p_ssl = false, bool p_verify_host = true) override;
+ void set_connection(const Ref<StreamPeer> &p_connection) override;
+ Ref<StreamPeer> get_connection() const override;
+ void close() override;
+ Status get_status() const override;
+ bool has_response() const override;
+ bool is_response_chunked() const override;
+ int get_response_code() const override;
+ Error get_response_headers(List<String> *r_response) override;
+ int get_response_body_length() const override;
+ PackedByteArray read_response_body_chunk() override;
+ void set_blocking_mode(bool p_enable) override;
+ bool is_blocking_mode_enabled() const override;
+ void set_read_chunk_size(int p_size) override;
+ int get_read_chunk_size() const override;
+ Error poll() override;
+ HTTPClientJavaScript();
+ ~HTTPClientJavaScript();
+};
+#endif // HTTP_CLIENT_JAVASCRIPT_H
diff --git a/platform/osx/display_server_osx.mm b/platform/osx/display_server_osx.mm
index ec51ec78d3..4a672a4b17 100644
--- a/platform/osx/display_server_osx.mm
+++ b/platform/osx/display_server_osx.mm
@@ -2120,6 +2120,12 @@ void DisplayServerOSX::mouse_set_mode(MouseMode p_mode) {
ignore_warp = true;
warp_events.clear();
mouse_mode = p_mode;
+
+ if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) {
+ CursorShape p_shape = cursor_shape;
+ cursor_shape = DisplayServer::CURSOR_MAX;
+ cursor_set_shape(p_shape);
+ }
}
DisplayServer::MouseMode DisplayServerOSX::mouse_get_mode() const {
diff --git a/scene/2d/polygon_2d.cpp b/scene/2d/polygon_2d.cpp
index 1eec2a3833..860e95b51e 100644
--- a/scene/2d/polygon_2d.cpp
+++ b/scene/2d/polygon_2d.cpp
@@ -365,7 +365,7 @@ void Polygon2D::_notification(int p_what) {
arr[RS::ARRAY_INDEX] = index_array;
RS::get_singleton()->mesh_add_surface_from_arrays(mesh, RS::PRIMITIVE_TRIANGLES, arr, Array(), Dictionary(), RS::ARRAY_FLAG_USE_2D_VERTICES);
- RS::get_singleton()->canvas_item_add_mesh(get_canvas_item(), mesh, Transform2D(), Color(), texture.is_valid() ? texture->get_rid() : RID());
+ RS::get_singleton()->canvas_item_add_mesh(get_canvas_item(), mesh, Transform2D(), Color(1, 1, 1), texture.is_valid() ? texture->get_rid() : RID());
}
} break;
diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp
index ff7ea17008..ba1534ed5c 100644
--- a/scene/gui/code_edit.cpp
+++ b/scene/gui/code_edit.cpp
@@ -360,7 +360,7 @@ void CodeEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
return;
}
if (k->is_action("ui_text_backspace", true)) {
- backspace_at_cursor();
+ backspace();
_filter_code_completion_candidates();
accept_event();
return;
@@ -387,14 +387,34 @@ void CodeEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
set_code_hint("");
}
- /* Override input to unfold lines where needed. */
- if (!is_readonly()) {
- if (k->is_action("ui_text_newline_above", true) || k->is_action("ui_text_newline_blank", true) || k->is_action("ui_text_newline", true)) {
- unfold_line(cursor_get_line());
- }
- if (cursor_get_line() > 0 && k->is_action("ui_text_backspace", true)) {
- unfold_line(cursor_get_line() - 1);
- }
+ /* Indentation */
+ if (k->is_action("ui_text_indent", true)) {
+ do_indent();
+ accept_event();
+ return;
+ }
+
+ if (k->is_action("ui_text_dedent", true)) {
+ do_unindent();
+ accept_event();
+ return;
+ }
+
+ // Override new line actions, for auto indent
+ if (k->is_action("ui_text_newline_above", true)) {
+ _new_line(false, true);
+ accept_event();
+ return;
+ }
+ if (k->is_action("ui_text_newline_blank", true)) {
+ _new_line(false);
+ accept_event();
+ return;
+ }
+ if (k->is_action("ui_text_newline", true)) {
+ _new_line();
+ accept_event();
+ return;
}
/* Remove shift otherwise actions will not match. */
@@ -439,6 +459,443 @@ Control::CursorShape CodeEdit::get_cursor_shape(const Point2 &p_pos) const {
return TextEdit::get_cursor_shape(p_pos);
}
+/* Indent management */
+void CodeEdit::set_indent_size(const int p_size) {
+ ERR_FAIL_COND_MSG(p_size <= 0, "Indend size must be greater than 0.");
+ if (indent_size == p_size) {
+ return;
+ }
+
+ indent_size = p_size;
+ if (indent_using_spaces) {
+ indent_text = String(" ").repeat(p_size);
+ } else {
+ indent_text = "\t";
+ }
+ set_tab_size(p_size);
+}
+
+int CodeEdit::get_indent_size() const {
+ return indent_size;
+}
+
+void CodeEdit::set_indent_using_spaces(const bool p_use_spaces) {
+ indent_using_spaces = p_use_spaces;
+ if (indent_using_spaces) {
+ indent_text = String(" ").repeat(indent_size);
+ } else {
+ indent_text = "\t";
+ }
+}
+
+bool CodeEdit::is_indent_using_spaces() const {
+ return indent_using_spaces;
+}
+
+void CodeEdit::set_auto_indent_enabled(bool p_enabled) {
+ auto_indent = p_enabled;
+}
+
+bool CodeEdit::is_auto_indent_enabled() const {
+ return auto_indent;
+}
+
+void CodeEdit::set_auto_indent_prefixes(const TypedArray<String> &p_prefixes) {
+ auto_indent_prefixes.clear();
+ for (int i = 0; i < p_prefixes.size(); i++) {
+ const String prefix = p_prefixes[i];
+ auto_indent_prefixes.insert(prefix[0]);
+ }
+}
+
+TypedArray<String> CodeEdit::get_auto_indent_prefixes() const {
+ TypedArray<String> prefixes;
+ for (const Set<char32_t>::Element *E = auto_indent_prefixes.front(); E; E = E->next()) {
+ prefixes.push_back(String::chr(E->get()));
+ }
+ return prefixes;
+}
+
+void CodeEdit::do_indent() {
+ if (is_readonly()) {
+ return;
+ }
+
+ if (is_selection_active()) {
+ indent_lines();
+ return;
+ }
+
+ if (!indent_using_spaces) {
+ _insert_text_at_cursor("\t");
+ return;
+ }
+
+ int spaces_to_add = _calculate_spaces_till_next_right_indent(cursor_get_column());
+ if (spaces_to_add > 0) {
+ _insert_text_at_cursor(String(" ").repeat(spaces_to_add));
+ }
+}
+
+void CodeEdit::indent_lines() {
+ if (is_readonly()) {
+ return;
+ }
+
+ begin_complex_operation();
+
+ /* This value informs us by how much we changed selection position by indenting right. */
+ /* Default is 1 for tab indentation. */
+ int selection_offset = 1;
+
+ int start_line = cursor_get_line();
+ int end_line = start_line;
+ if (is_selection_active()) {
+ start_line = get_selection_from_line();
+ end_line = get_selection_to_line();
+
+ /* Ignore the last line if the selection is not past the first column. */
+ if (get_selection_to_column() == 0) {
+ selection_offset = 0;
+ end_line--;
+ }
+ }
+
+ for (int i = start_line; i <= end_line; i++) {
+ const String line_text = get_line(i);
+ if (line_text.size() == 0 && is_selection_active()) {
+ continue;
+ }
+
+ if (!indent_using_spaces) {
+ set_line(i, '\t' + line_text);
+ continue;
+ }
+
+ /* We don't really care where selection is - we just need to know indentation level at the beginning of the line. */
+ /* Since we will add this many spaces, we want to move the whole selection and caret by this much. */
+ int spaces_to_add = _calculate_spaces_till_next_right_indent(get_first_non_whitespace_column(i));
+ set_line(i, String(" ").repeat(spaces_to_add) + line_text);
+ selection_offset = spaces_to_add;
+ }
+
+ /* Fix selection and caret being off after shifting selection right.*/
+ if (is_selection_active()) {
+ select(start_line, get_selection_from_column() + selection_offset, get_selection_to_line(), get_selection_to_column() + selection_offset);
+ }
+ cursor_set_column(cursor_get_column() + selection_offset, false);
+
+ end_complex_operation();
+}
+
+void CodeEdit::do_unindent() {
+ if (is_readonly()) {
+ return;
+ }
+
+ int cc = cursor_get_column();
+
+ if (is_selection_active() || cc <= 0) {
+ unindent_lines();
+ return;
+ }
+
+ int cl = cursor_get_line();
+ const String &line = get_line(cl);
+
+ if (line[cc - 1] == '\t') {
+ _remove_text(cl, cc - 1, cl, cc);
+ cursor_set_column(MAX(0, cc - 1));
+ return;
+ }
+
+ if (line[cc - 1] != ' ') {
+ return;
+ }
+
+ int spaces_to_remove = _calculate_spaces_till_next_left_indent(cc);
+ if (spaces_to_remove > 0) {
+ for (int i = 1; i <= spaces_to_remove; i++) {
+ if (line[cc - i] != ' ') {
+ spaces_to_remove = i - 1;
+ break;
+ }
+ }
+ _remove_text(cl, cc - spaces_to_remove, cl, cc);
+ cursor_set_column(MAX(0, cc - spaces_to_remove));
+ }
+}
+
+void CodeEdit::unindent_lines() {
+ if (is_readonly()) {
+ return;
+ }
+
+ begin_complex_operation();
+
+ /* Moving caret and selection after unindenting can get tricky because */
+ /* changing content of line can move caret and selection on its own (if new line ends before previous position of either), */
+ /* therefore we just remember initial values and at the end of the operation offset them by number of removed characters. */
+ int removed_characters = 0;
+ int initial_selection_end_column = 0;
+ int initial_cursor_column = cursor_get_column();
+
+ int start_line = cursor_get_line();
+ int end_line = start_line;
+ if (is_selection_active()) {
+ start_line = get_selection_from_line();
+ end_line = get_selection_to_line();
+
+ /* Ignore the last line if the selection is not past the first column. */
+ initial_selection_end_column = get_selection_to_column();
+ if (initial_selection_end_column == 0) {
+ end_line--;
+ }
+ }
+
+ bool first_line_edited = false;
+ bool last_line_edited = false;
+
+ for (int i = start_line; i <= end_line; i++) {
+ String line_text = get_line(i);
+
+ if (line_text.begins_with("\t")) {
+ line_text = line_text.substr(1, line_text.length());
+
+ set_line(i, line_text);
+ removed_characters = 1;
+
+ first_line_edited = (i == start_line) ? true : first_line_edited;
+ last_line_edited = (i == end_line) ? true : last_line_edited;
+ continue;
+ }
+
+ if (line_text.begins_with(" ")) {
+ /* When unindenting we aim to remove spaces before line that has selection no matter what is selected, */
+ /* Here we remove only enough spaces to align text to nearest full multiple of indentation_size. */
+ /* In case where selection begins at the start of indentation_size multiple we remove whole indentation level. */
+ int spaces_to_remove = _calculate_spaces_till_next_left_indent(get_first_non_whitespace_column(i));
+ line_text = line_text.substr(spaces_to_remove, line_text.length());
+
+ set_line(i, line_text);
+ removed_characters = spaces_to_remove;
+
+ first_line_edited = (i == start_line) ? true : first_line_edited;
+ last_line_edited = (i == end_line) ? true : last_line_edited;
+ }
+ }
+
+ if (is_selection_active()) {
+ /* Fix selection being off by one on the first line. */
+ if (first_line_edited) {
+ select(get_selection_from_line(), get_selection_from_column() - removed_characters, get_selection_to_line(), initial_selection_end_column);
+ }
+
+ /* Fix selection being off by one on the last line. */
+ if (last_line_edited) {
+ select(get_selection_from_line(), get_selection_from_column(), get_selection_to_line(), initial_selection_end_column - removed_characters);
+ }
+ }
+ cursor_set_column(initial_cursor_column - removed_characters, false);
+
+ end_complex_operation();
+}
+
+int CodeEdit::_calculate_spaces_till_next_left_indent(int p_column) const {
+ int spaces_till_indent = p_column % indent_size;
+ if (spaces_till_indent == 0) {
+ spaces_till_indent = indent_size;
+ }
+ return spaces_till_indent;
+}
+
+int CodeEdit::_calculate_spaces_till_next_right_indent(int p_column) const {
+ return indent_size - p_column % indent_size;
+}
+
+/* TODO: remove once brace completion is refactored. */
+static char32_t _get_right_pair_symbol(char32_t c) {
+ if (c == '"') {
+ return '"';
+ }
+ if (c == '\'') {
+ return '\'';
+ }
+ if (c == '(') {
+ return ')';
+ }
+ if (c == '[') {
+ return ']';
+ }
+ if (c == '{') {
+ return '}';
+ }
+ return 0;
+}
+
+static bool _is_pair_left_symbol(char32_t c) {
+ return c == '"' ||
+ c == '\'' ||
+ c == '(' ||
+ c == '[' ||
+ c == '{';
+}
+
+void CodeEdit::_new_line(bool p_split_current_line, bool p_above) {
+ if (is_readonly()) {
+ return;
+ }
+
+ const int cc = cursor_get_column();
+ const int cl = cursor_get_line();
+ const String line = get_line(cl);
+
+ String ins = "\n";
+
+ /* Append current indentation. */
+ int space_count = 0;
+ int line_col = 0;
+ for (; line_col < cc; line_col++) {
+ if (line[line_col] == '\t') {
+ ins += indent_text;
+ space_count = 0;
+ continue;
+ }
+
+ if (line[line_col] == ' ') {
+ space_count++;
+
+ if (space_count == indent_size) {
+ ins += indent_text;
+ space_count = 0;
+ }
+ continue;
+ }
+ break;
+ }
+
+ if (is_line_folded(cl)) {
+ unfold_line(cl);
+ }
+
+ /* Indent once again if the previous line needs it, ie ':'. */
+ /* Then add an addition new line for any closing pairs aka '()'. */
+ /* Skip this in comments or if we are going above. */
+ bool brace_indent = false;
+ if (auto_indent && !p_above && cc > 0 && is_in_comment(cl) == -1) {
+ bool should_indent = false;
+ char32_t indent_char = ' ';
+
+ for (; line_col < cc; line_col++) {
+ char32_t c = line[line_col];
+ if (auto_indent_prefixes.has(c)) {
+ should_indent = true;
+ indent_char = c;
+ continue;
+ }
+
+ /* Make sure this is the last char, trailing whitespace or comments are okay. */
+ if (should_indent && (!_is_whitespace(c) && is_in_comment(cl, cc) == -1)) {
+ should_indent = false;
+ }
+ }
+
+ if (should_indent) {
+ ins += indent_text;
+
+ /* TODO: Change when brace completion is refactored. */
+ char32_t closing_char = _get_right_pair_symbol(indent_char);
+ if (closing_char != 0 && closing_char == line[cc]) {
+ /* No need to move the brace below if we are not taking the text with us. */
+ if (p_split_current_line) {
+ brace_indent = true;
+ ins += "\n" + ins.substr(1, ins.length() - 2);
+ } else {
+ brace_indent = false;
+ ins = "\n" + ins.substr(1, ins.length() - 2);
+ }
+ }
+ }
+ }
+
+ begin_complex_operation();
+
+ bool first_line = false;
+ if (!p_split_current_line) {
+ if (p_above) {
+ if (cl > 0) {
+ cursor_set_line(cl - 1, false);
+ cursor_set_column(get_line(cursor_get_line()).length());
+ } else {
+ cursor_set_column(0);
+ first_line = true;
+ }
+ } else {
+ cursor_set_column(line.length());
+ }
+ }
+
+ insert_text_at_cursor(ins);
+
+ if (first_line) {
+ cursor_set_line(0);
+ } else if (brace_indent) {
+ cursor_set_line(cursor_get_line() - 1, false);
+ cursor_set_column(get_line(cursor_get_line()).length());
+ }
+
+ end_complex_operation();
+}
+
+void CodeEdit::backspace() {
+ if (is_readonly()) {
+ return;
+ }
+
+ int cc = cursor_get_column();
+ int cl = cursor_get_line();
+
+ if (cc == 0 && cl == 0) {
+ return;
+ }
+
+ if (is_selection_active()) {
+ delete_selection();
+ return;
+ }
+
+ if (cl > 0 && is_line_hidden(cl - 1)) {
+ unfold_line(cursor_get_line() - 1);
+ }
+
+ int prev_line = cc ? cl : cl - 1;
+ int prev_column = cc ? (cc - 1) : (get_line(cl - 1).length());
+
+ merge_gutters(cl, prev_line);
+
+ /* TODO: Change when brace completion is refactored. */
+ if (auto_brace_completion_enabled && cc > 0 && _is_pair_left_symbol(get_line(cl)[cc - 1])) {
+ _consume_backspace_for_pair_symbol(prev_line, prev_column);
+ cursor_set_line(prev_line, false, true);
+ cursor_set_column(prev_column);
+ return;
+ }
+
+ /* For space indentation we need to do a simple unindent if there are no chars to the left, acting in the */
+ /* same way as tabs. */
+ if (indent_using_spaces && cc != 0) {
+ if (get_first_non_whitespace_column(cl) > cc) {
+ prev_column = cc - _calculate_spaces_till_next_left_indent(cc);
+ prev_line = cl;
+ }
+ }
+
+ _remove_text(prev_line, prev_column, cl, cc);
+
+ cursor_set_line(prev_line, false, true);
+ cursor_set_column(prev_column);
+}
+
/* Main Gutter */
void CodeEdit::_update_draw_main_gutter() {
set_gutter_draw(main_gutter, draw_breakpoints || draw_bookmarks || draw_executing_lines);
@@ -1286,6 +1743,25 @@ void CodeEdit::cancel_code_completion() {
}
void CodeEdit::_bind_methods() {
+ /* Indent management */
+ ClassDB::bind_method(D_METHOD("set_indent_size", "size"), &CodeEdit::set_indent_size);
+ ClassDB::bind_method(D_METHOD("get_indent_size"), &CodeEdit::get_indent_size);
+
+ ClassDB::bind_method(D_METHOD("set_indent_using_spaces", "use_spaces"), &CodeEdit::set_indent_using_spaces);
+ ClassDB::bind_method(D_METHOD("is_indent_using_spaces"), &CodeEdit::is_indent_using_spaces);
+
+ ClassDB::bind_method(D_METHOD("set_auto_indent_enabled", "enable"), &CodeEdit::set_auto_indent_enabled);
+ ClassDB::bind_method(D_METHOD("is_auto_indent_enabled"), &CodeEdit::is_auto_indent_enabled);
+
+ ClassDB::bind_method(D_METHOD("set_auto_indent_prefixes", "prefixes"), &CodeEdit::set_auto_indent_prefixes);
+ ClassDB::bind_method(D_METHOD("get_auto_indent_prefixes"), &CodeEdit::get_auto_indent_prefixes);
+
+ ClassDB::bind_method(D_METHOD("do_indent"), &CodeEdit::do_indent);
+ ClassDB::bind_method(D_METHOD("do_unindent"), &CodeEdit::do_unindent);
+
+ ClassDB::bind_method(D_METHOD("indent_lines"), &CodeEdit::indent_lines);
+ ClassDB::bind_method(D_METHOD("unindent_lines"), &CodeEdit::unindent_lines);
+
/* Main Gutter */
ClassDB::bind_method(D_METHOD("_main_gutter_draw_callback"), &CodeEdit::_main_gutter_draw_callback);
@@ -1436,6 +1912,12 @@ void CodeEdit::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "code_completion_enabled"), "set_code_completion_enabled", "is_code_completion_enabled");
ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "code_completion_prefixes"), "set_code_completion_prefixes", "get_code_comletion_prefixes");
+ ADD_GROUP("Indentation", "indent_");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "indent_size"), "set_indent_size", "get_indent_size");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "indent_use_spaces"), "set_indent_using_spaces", "is_indent_using_spaces");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "indent_automatic"), "set_auto_indent_enabled", "is_auto_indent_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "indent_automatic_prefixes"), "set_auto_indent_prefixes", "get_auto_indent_prefixes");
+
/* Signals */
ADD_SIGNAL(MethodInfo("breakpoint_toggled", PropertyInfo(Variant::INT, "line")));
ADD_SIGNAL(MethodInfo("request_code_completion"));
@@ -2062,6 +2544,12 @@ void CodeEdit::_lines_edited_from(int p_from_line, int p_to_line) {
}
CodeEdit::CodeEdit() {
+ /* Indent management */
+ auto_indent_prefixes.insert(':');
+ auto_indent_prefixes.insert('{');
+ auto_indent_prefixes.insert('[');
+ auto_indent_prefixes.insert('(');
+
/* Text Direction */
set_layout_direction(LAYOUT_DIRECTION_LTR);
set_text_direction(TEXT_DIRECTION_LTR);
diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h
index 52b3f52a03..25b518402b 100644
--- a/scene/gui/code_edit.h
+++ b/scene/gui/code_edit.h
@@ -53,6 +53,19 @@ public:
};
private:
+ /* Indent management */
+ int indent_size = 4;
+ String indent_text = "\t";
+
+ bool auto_indent = false;
+ Set<char32_t> auto_indent_prefixes;
+
+ bool indent_using_spaces = false;
+ int _calculate_spaces_till_next_left_indent(int p_column) const;
+ int _calculate_spaces_till_next_right_indent(int p_column) const;
+
+ void _new_line(bool p_split_current_line = true, bool p_above = false);
+
/* Main Gutter */
enum MainGutterType {
MAIN_GUTTER_BREAKPOINT = 0x01,
@@ -206,6 +219,27 @@ protected:
public:
virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const override;
+ /* Indent management */
+ void set_indent_size(const int p_size);
+ int get_indent_size() const;
+
+ void set_indent_using_spaces(const bool p_use_spaces);
+ bool is_indent_using_spaces() const;
+
+ void set_auto_indent_enabled(bool p_enabled);
+ bool is_auto_indent_enabled() const;
+
+ void set_auto_indent_prefixes(const TypedArray<String> &p_prefixes);
+ TypedArray<String> get_auto_indent_prefixes() const;
+
+ void do_indent();
+ void do_unindent();
+
+ void indent_lines();
+ void unindent_lines();
+
+ virtual void backspace() override;
+
/* Main Gutter */
void set_draw_breakpoints_gutter(bool p_draw);
bool is_drawing_breakpoints_gutter() const;
diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp
index 64a3f1ca77..0ae1bb04b7 100644
--- a/scene/gui/control.cpp
+++ b/scene/gui/control.cpp
@@ -650,27 +650,11 @@ void Control::_notification(int p_notification) {
}
}
-bool Control::clips_input() const {
- if (get_script_instance()) {
- return get_script_instance()->call(SceneStringNames::get_singleton()->_clips_input);
- }
- return false;
-}
-
bool Control::has_point(const Point2 &p_point) const {
- if (get_script_instance()) {
- Variant v = p_point;
- const Variant *p = &v;
- Callable::CallError ce;
- Variant ret = get_script_instance()->call(SceneStringNames::get_singleton()->_has_point, &p, 1, ce);
- if (ce.error == Callable::CallError::CALL_OK) {
- return ret;
- }
+ bool ret;
+ if (GDVIRTUAL_CALL(_has_point, p_point, ret)) {
+ return ret;
}
- /*if (has_stylebox("mask")) {
- Ref<StyleBox> mask = get_stylebox("mask");
- return mask->test_mask(p_point,Rect2(Point2(),get_size()));
- }*/
return Rect2(Point2(), get_size()).has_point(p_point);
}
@@ -2784,7 +2768,6 @@ void Control::_bind_methods() {
BIND_VMETHOD(MethodInfo(
PropertyInfo(Variant::OBJECT, "control", PROPERTY_HINT_RESOURCE_TYPE, "Control"),
"_make_custom_tooltip", PropertyInfo(Variant::STRING, "for_text")));
- BIND_VMETHOD(MethodInfo(Variant::BOOL, "_clips_input"));
ADD_GROUP("Anchor", "anchor_");
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anchor_left", PROPERTY_HINT_RANGE, "0,1,0.001,or_lesser,or_greater"), "_set_anchor", "get_anchor", SIDE_LEFT);
@@ -2940,5 +2923,5 @@ void Control::_bind_methods() {
ADD_SIGNAL(MethodInfo("minimum_size_changed"));
ADD_SIGNAL(MethodInfo("theme_changed"));
- BIND_VMETHOD(MethodInfo(Variant::BOOL, "_has_point", PropertyInfo(Variant::VECTOR2, "point")));
+ GDVIRTUAL_BIND(_has_point);
}
diff --git a/scene/gui/control.h b/scene/gui/control.h
index a05025c32d..87fad96571 100644
--- a/scene/gui/control.h
+++ b/scene/gui/control.h
@@ -32,6 +32,7 @@
#define CONTROL_H
#include "core/math/transform_2d.h"
+#include "core/object/gdvirtual.gen.inc"
#include "core/templates/rid.h"
#include "scene/gui/shortcut.h"
#include "scene/main/canvas_item.h"
@@ -264,6 +265,7 @@ private:
static bool has_theme_item_in_types(Control *p_theme_owner, Window *p_theme_owner_window, Theme::DataType p_data_type, const StringName &p_name, List<StringName> p_theme_types);
_FORCE_INLINE_ void _get_theme_type_dependencies(const StringName &p_theme_type, List<StringName> *p_list) const;
+ GDVIRTUAL1RC(bool, _has_point, Vector2)
protected:
virtual void add_child_notify(Node *p_child) override;
virtual void remove_child_notify(Node *p_child) override;
@@ -329,7 +331,6 @@ public:
virtual Size2 get_minimum_size() const;
virtual Size2 get_combined_minimum_size() const;
virtual bool has_point(const Point2 &p_point) const;
- virtual bool clips_input() const;
virtual void set_drag_forwarding(Control *p_target);
virtual Variant get_drag_data(const Point2 &p_point);
virtual bool can_drop_data(const Point2 &p_point, const Variant &p_data) const;
diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp
index 5ef89e38f0..39aa6749e7 100644
--- a/scene/gui/graph_edit.cpp
+++ b/scene/gui/graph_edit.cpp
@@ -239,10 +239,6 @@ void GraphEdit::disconnect_node(const StringName &p_from, int p_from_port, const
}
}
-bool GraphEdit::clips_input() const {
- return true;
-}
-
void GraphEdit::get_connection_list(List<Connection> *r_connections) const {
*r_connections = connections;
}
diff --git a/scene/gui/graph_edit.h b/scene/gui/graph_edit.h
index e8300f901c..5251de1722 100644
--- a/scene/gui/graph_edit.h
+++ b/scene/gui/graph_edit.h
@@ -235,7 +235,6 @@ protected:
virtual void add_child_notify(Node *p_child) override;
virtual void remove_child_notify(Node *p_child) override;
void _notification(int p_what);
- virtual bool clips_input() const override;
public:
Error connect_node(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port);
diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp
index 0ce0130ad5..6580d794d1 100644
--- a/scene/gui/label.cpp
+++ b/scene/gui/label.cpp
@@ -453,6 +453,7 @@ void Label::set_text(const String &p_string) {
visible_chars = get_total_character_count() * percent_visible;
}
update();
+ minimum_size_changed();
}
void Label::set_text_direction(Control::TextDirection p_text_direction) {
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index 7db6665e82..f32ad2144a 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -4085,7 +4085,7 @@ void RichTextLabel::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selection_enabled"), "set_selection_enabled", "is_selection_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "override_selected_font_color"), "set_override_selected_font_color", "is_overriding_selected_font_color");
- ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "custom_effects", PROPERTY_HINT_ARRAY_TYPE, "RichTextEffect", (PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE)), "set_effects", "get_effects");
+ ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "custom_effects", PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:%s", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "RichTextEffect"), (PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE)), "set_effects", "get_effects");
ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "language"), "set_language", "get_language");
diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp
index 5f872644ab..95445bdb7e 100644
--- a/scene/gui/scroll_container.cpp
+++ b/scene/gui/scroll_container.cpp
@@ -32,10 +32,6 @@
#include "core/os/os.h"
#include "scene/main/window.h"
-bool ScrollContainer::clips_input() const {
- return true;
-}
-
Size2 ScrollContainer::get_minimum_size() const {
Ref<StyleBox> sb = get_theme_stylebox("bg");
Size2 min_size;
diff --git a/scene/gui/scroll_container.h b/scene/gui/scroll_container.h
index c77a0d62f5..4733fdabca 100644
--- a/scene/gui/scroll_container.h
+++ b/scene/gui/scroll_container.h
@@ -108,8 +108,6 @@ public:
VScrollBar *get_v_scrollbar();
void ensure_control_visible(Control *p_control);
- virtual bool clips_input() const override;
-
TypedArray<String> get_configuration_warnings() const override;
ScrollContainer();
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index b504da2224..370fdd8b88 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -102,14 +102,6 @@ static char32_t _get_right_pair_symbol(char32_t c) {
return 0;
}
-static int _find_first_non_whitespace_column_of_line(const String &line) {
- int left = 0;
- while (left < line.length() && _is_whitespace(line[left])) {
- left++;
- }
- return left;
-}
-
///////////////////////////////////////////////////////////////////////////////
void TextEdit::Text::set_font(const Ref<Font> &p_font) {
@@ -120,8 +112,12 @@ void TextEdit::Text::set_font_size(int p_font_size) {
font_size = p_font_size;
}
-void TextEdit::Text::set_indent_size(int p_indent_size) {
- indent_size = p_indent_size;
+void TextEdit::Text::set_tab_size(int p_tab_size) {
+ tab_size = p_tab_size;
+}
+
+int TextEdit::Text::get_tab_size() const {
+ return tab_size;
}
void TextEdit::Text::set_font_features(const Dictionary &p_features) {
@@ -204,9 +200,9 @@ void TextEdit::Text::invalidate_cache(int p_line, int p_column, const String &p_
}
// Apply tab align.
- if (indent_size > 0) {
+ if (tab_size > 0) {
Vector<float> tabs;
- tabs.push_back(font->get_char_size(' ', 0, font_size).width * indent_size);
+ tabs.push_back(font->get_char_size(' ', 0, font_size).width * tab_size);
text.write[p_line].data_buf->tab_align(tabs);
}
}
@@ -214,9 +210,9 @@ void TextEdit::Text::invalidate_cache(int p_line, int p_column, const String &p_
void TextEdit::Text::invalidate_all_lines() {
for (int i = 0; i < text.size(); i++) {
text.write[i].data_buf->set_width(width);
- if (indent_size > 0) {
+ if (tab_size > 0) {
Vector<float> tabs;
- tabs.push_back(font->get_char_size(' ', 0, font_size).width * indent_size);
+ tabs.push_back(font->get_char_size(' ', 0, font_size).width * tab_size);
text.write[i].data_buf->tab_align(tabs);
}
}
@@ -822,7 +818,7 @@ void TextEdit::_notification(int p_what) {
if (draw_minimap) {
int minimap_visible_lines = _get_minimap_visible_rows();
int minimap_line_height = (minimap_char_size.y + minimap_line_spacing);
- int minimap_tab_size = minimap_char_size.x * indent_size;
+ int minimap_tab_size = minimap_char_size.x * text.get_tab_size();
// calculate viewport size and y offset
int viewport_height = (draw_amount - 1) * minimap_line_height;
@@ -1695,7 +1691,13 @@ void TextEdit::_consume_backspace_for_pair_symbol(int prev_line, int prev_column
}
}
-void TextEdit::backspace_at_cursor() {
+void TextEdit::backspace() {
+ ScriptInstance *si = get_script_instance();
+ if (si && si->has_method("_backspace")) {
+ si->call("_backspace");
+ return;
+ }
+
if (readonly) {
return;
}
@@ -1704,34 +1706,15 @@ void TextEdit::backspace_at_cursor() {
return;
}
+ if (is_selection_active()) {
+ delete_selection();
+ return;
+ }
+
int prev_line = cursor.column ? cursor.line : cursor.line - 1;
int prev_column = cursor.column ? (cursor.column - 1) : (text[cursor.line - 1].length());
- if (cursor.line != prev_line) {
- for (int i = 0; i < gutters.size(); i++) {
- if (!gutters[i].overwritable) {
- continue;
- }
-
- if (text.get_line_gutter_text(cursor.line, i) != "") {
- text.set_line_gutter_text(prev_line, i, text.get_line_gutter_text(cursor.line, i));
- text.set_line_gutter_item_color(prev_line, i, text.get_line_gutter_item_color(cursor.line, i));
- }
-
- if (text.get_line_gutter_icon(cursor.line, i).is_valid()) {
- text.set_line_gutter_icon(prev_line, i, text.get_line_gutter_icon(cursor.line, i));
- text.set_line_gutter_item_color(prev_line, i, text.get_line_gutter_item_color(cursor.line, i));
- }
-
- if (text.get_line_gutter_metadata(cursor.line, i) != "") {
- text.set_line_gutter_metadata(prev_line, i, text.get_line_gutter_metadata(cursor.line, i));
- }
-
- if (text.is_line_gutter_clickable(cursor.line, i)) {
- text.set_line_gutter_clickable(prev_line, i, true);
- }
- }
- }
+ merge_gutters(cursor.line, prev_line);
if (is_line_hidden(cursor.line)) {
set_line_as_hidden(prev_line, true);
@@ -1742,168 +1725,13 @@ void TextEdit::backspace_at_cursor() {
_is_pair_left_symbol(text[cursor.line][cursor.column - 1])) {
_consume_backspace_for_pair_symbol(prev_line, prev_column);
} else {
- // Handle space indentation.
- if (cursor.column != 0 && indent_using_spaces) {
- // Check if there are no other chars before cursor, just indentation.
- bool unindent = true;
- int i = 0;
- while (i < cursor.column && i < text[cursor.line].length()) {
- if (!_is_whitespace(text[cursor.line][i])) {
- unindent = false;
- break;
- }
- i++;
- }
-
- // Then we can remove all spaces as a single character.
- if (unindent) {
- // We want to remove spaces up to closest indent, or whole indent if cursor is pointing at it.
- int spaces_to_delete = _calculate_spaces_till_next_left_indent(cursor.column);
- prev_column = cursor.column - spaces_to_delete;
- _remove_text(cursor.line, prev_column, cursor.line, cursor.column);
- } else {
- _remove_text(prev_line, prev_column, cursor.line, cursor.column);
- }
- } else {
- _remove_text(prev_line, prev_column, cursor.line, cursor.column);
- }
+ _remove_text(prev_line, prev_column, cursor.line, cursor.column);
}
cursor_set_line(prev_line, false, true);
cursor_set_column(prev_column);
}
-void TextEdit::indent_selected_lines_right() {
- int start_line;
- int end_line;
-
- // This value informs us by how much we changed selection position by indenting right.
- // Default is 1 for tab indentation.
- int selection_offset = 1;
- begin_complex_operation();
-
- if (is_selection_active()) {
- start_line = get_selection_from_line();
- end_line = get_selection_to_line();
- } else {
- start_line = cursor.line;
- end_line = start_line;
- }
-
- // Ignore if the cursor is not past the first column.
- if (is_selection_active() && get_selection_to_column() == 0) {
- selection_offset = 0;
- end_line--;
- }
-
- for (int i = start_line; i <= end_line; i++) {
- String line_text = get_line(i);
- if (line_text.size() == 0 && is_selection_active()) {
- continue;
- }
- if (indent_using_spaces) {
- // We don't really care where selection is - we just need to know indentation level at the beginning of the line.
- int left = _find_first_non_whitespace_column_of_line(line_text);
- int spaces_to_add = _calculate_spaces_till_next_right_indent(left);
- // Since we will add these many spaces, we want to move the whole selection and cursor by this much.
- selection_offset = spaces_to_add;
- for (int j = 0; j < spaces_to_add; j++) {
- line_text = ' ' + line_text;
- }
- } else {
- line_text = '\t' + line_text;
- }
- set_line(i, line_text);
- }
-
- // Fix selection and cursor being off after shifting selection right.
- if (is_selection_active()) {
- select(selection.from_line, selection.from_column + selection_offset, selection.to_line, selection.to_column + selection_offset);
- }
- cursor_set_column(cursor.column + selection_offset, false);
- end_complex_operation();
- update();
-}
-
-void TextEdit::indent_selected_lines_left() {
- int start_line;
- int end_line;
-
- // Moving cursor and selection after unindenting can get tricky because
- // changing content of line can move cursor and selection on its own (if new line ends before previous position of either),
- // therefore we just remember initial values and at the end of the operation offset them by number of removed characters.
- int removed_characters = 0;
- int initial_selection_end_column = selection.to_column;
- int initial_cursor_column = cursor.column;
-
- begin_complex_operation();
-
- if (is_selection_active()) {
- start_line = get_selection_from_line();
- end_line = get_selection_to_line();
- } else {
- start_line = cursor.line;
- end_line = start_line;
- }
-
- // Ignore if the cursor is not past the first column.
- if (is_selection_active() && get_selection_to_column() == 0) {
- end_line--;
- }
- String first_line_text = get_line(start_line);
- String last_line_text = get_line(end_line);
-
- for (int i = start_line; i <= end_line; i++) {
- String line_text = get_line(i);
-
- if (line_text.begins_with("\t")) {
- line_text = line_text.substr(1, line_text.length());
- set_line(i, line_text);
- removed_characters = 1;
- } else if (line_text.begins_with(" ")) {
- // When unindenting we aim to remove spaces before line that has selection no matter what is selected,
- // so we start of by finding first non whitespace character of line
- int left = _find_first_non_whitespace_column_of_line(line_text);
-
- // Here we remove only enough spaces to align text to nearest full multiple of indentation_size.
- // In case where selection begins at the start of indentation_size multiple we remove whole indentation level.
- int spaces_to_remove = _calculate_spaces_till_next_left_indent(left);
-
- line_text = line_text.substr(spaces_to_remove, line_text.length());
- set_line(i, line_text);
- removed_characters = spaces_to_remove;
- }
- }
-
- if (is_selection_active()) {
- // Fix selection being off by one on the first line.
- if (first_line_text != get_line(start_line)) {
- select(selection.from_line, selection.from_column - removed_characters,
- selection.to_line, initial_selection_end_column);
- }
- // Fix selection being off by one on the last line.
- if (last_line_text != get_line(end_line)) {
- select(selection.from_line, selection.from_column,
- selection.to_line, initial_selection_end_column - removed_characters);
- }
- }
- cursor_set_column(initial_cursor_column - removed_characters, false);
- end_complex_operation();
- update();
-}
-
-int TextEdit::_calculate_spaces_till_next_left_indent(int column) {
- int spaces_till_indent = column % indent_size;
- if (spaces_till_indent == 0) {
- spaces_till_indent = indent_size;
- }
- return spaces_till_indent;
-}
-
-int TextEdit::_calculate_spaces_till_next_right_indent(int column) {
- return indent_size - column % indent_size;
-}
-
void TextEdit::_swap_current_input_direction() {
if (input_direction == TEXT_DIRECTION_LTR) {
input_direction = TEXT_DIRECTION_RTL;
@@ -1919,90 +1747,8 @@ void TextEdit::_new_line(bool p_split_current_line, bool p_above) {
return;
}
- String ins = "\n";
-
- // Keep indentation.
- int space_count = 0;
- for (int i = 0; i < cursor.column; i++) {
- if (text[cursor.line][i] == '\t') {
- if (indent_using_spaces) {
- ins += space_indent;
- } else {
- ins += "\t";
- }
- space_count = 0;
- } else if (text[cursor.line][i] == ' ') {
- space_count++;
-
- if (space_count == indent_size) {
- if (indent_using_spaces) {
- ins += space_indent;
- } else {
- ins += "\t";
- }
- space_count = 0;
- }
- } else {
- break;
- }
- }
-
- bool brace_indent = false;
-
- // No need to indent if we are going upwards.
- if (auto_indent && !p_above) {
- // Indent once again if previous line will end with ':','{','[','(' and the line is not a comment
- // (i.e. colon/brace precedes current cursor position).
- if (cursor.column > 0) {
- bool indent_char_found = false;
- bool should_indent = false;
- char indent_char = ':';
- char c = text[cursor.line][cursor.column];
-
- for (int i = 0; i < cursor.column; i++) {
- c = text[cursor.line][i];
- switch (c) {
- case ':':
- case '{':
- case '[':
- case '(':
- indent_char_found = true;
- should_indent = true;
- indent_char = c;
- continue;
- }
-
- if (indent_char_found && is_line_comment(cursor.line)) {
- should_indent = true;
- break;
- } else if (indent_char_found && !_is_whitespace(c)) {
- should_indent = false;
- indent_char_found = false;
- }
- }
-
- if (!is_line_comment(cursor.line) && should_indent) {
- if (indent_using_spaces) {
- ins += space_indent;
- } else {
- ins += "\t";
- }
-
- // No need to move the brace below if we are not taking the text with us.
- char32_t closing_char = _get_right_pair_symbol(indent_char);
- if ((closing_char != 0) && (closing_char == text[cursor.line][cursor.column])) {
- if (p_split_current_line) {
- brace_indent = true;
- ins += "\n" + ins.substr(1, ins.length() - 2);
- } else {
- brace_indent = false;
- ins = "\n" + ins.substr(1, ins.length() - 2);
- }
- }
- }
- }
- }
begin_complex_operation();
+
bool first_line = false;
if (!p_split_current_line) {
if (p_above) {
@@ -2018,83 +1764,13 @@ void TextEdit::_new_line(bool p_split_current_line, bool p_above) {
}
}
- insert_text_at_cursor(ins);
+ insert_text_at_cursor("\n");
if (first_line) {
cursor_set_line(0);
- } else if (brace_indent) {
- cursor_set_line(cursor.line - 1, false);
- cursor_set_column(text[cursor.line].length());
- }
- end_complex_operation();
-}
-
-void TextEdit::_indent_right() {
- if (readonly) {
- return;
- }
-
- if (is_selection_active()) {
- indent_selected_lines_right();
- } else {
- // Simple indent.
- if (indent_using_spaces) {
- // Insert only as much spaces as needed till next indentation level.
- int spaces_to_add = _calculate_spaces_till_next_right_indent(cursor.column);
- String indent_to_insert = String();
- for (int i = 0; i < spaces_to_add; i++) {
- indent_to_insert = ' ' + indent_to_insert;
- }
- _insert_text_at_cursor(indent_to_insert);
- } else {
- _insert_text_at_cursor("\t");
- }
- }
-}
-
-void TextEdit::_indent_left() {
- if (readonly) {
- return;
}
- if (is_selection_active()) {
- indent_selected_lines_left();
- } else {
- // Simple unindent.
- int cc = cursor.column;
- const String &line = text[cursor.line];
-
- int left = _find_first_non_whitespace_column_of_line(line);
- cc = MIN(cc, left);
-
- while (cc < indent_size && cc < left && line[cc] == ' ') {
- cc++;
- }
-
- if (cc > 0 && cc <= text[cursor.line].length()) {
- if (text[cursor.line][cc - 1] == '\t') {
- // Tabs unindentation.
- _remove_text(cursor.line, cc - 1, cursor.line, cc);
- if (cursor.column >= left) {
- cursor_set_column(MAX(0, cursor.column - 1));
- }
- update();
- } else {
- // Spaces unindentation.
- int spaces_to_remove = _calculate_spaces_till_next_left_indent(cc);
- if (spaces_to_remove > 0) {
- _remove_text(cursor.line, cc - spaces_to_remove, cursor.line, cc);
- if (cursor.column > left - spaces_to_remove) { // Inside text?
- cursor_set_column(MAX(0, cursor.column - spaces_to_remove));
- }
- update();
- }
- }
- } else if (cc == 0 && line.length() > 0 && line[0] == '\t') {
- _remove_text(cursor.line, 0, cursor.line, 1);
- update();
- }
- }
+ end_complex_operation();
}
void TextEdit::_move_cursor_left(bool p_select, bool p_move_by_word) {
@@ -2331,20 +2007,24 @@ void TextEdit::_move_cursor_page_down(bool p_select) {
}
}
-void TextEdit::_backspace(bool p_word, bool p_all_to_left) {
+void TextEdit::_do_backspace(bool p_word, bool p_all_to_left) {
if (readonly) {
return;
}
- if (is_selection_active()) {
- _delete_selection();
+ if (is_selection_active() || (!p_all_to_left && !p_word)) {
+ backspace();
return;
}
+
if (p_all_to_left) {
int cursor_current_column = cursor.column;
cursor.column = 0;
_remove_text(cursor.line, 0, cursor.line, cursor_current_column);
- } else if (p_word) {
+ return;
+ }
+
+ if (p_word) {
int line = cursor.line;
int column = cursor.column;
@@ -2360,8 +2040,7 @@ void TextEdit::_backspace(bool p_word, bool p_all_to_left) {
cursor_set_line(line, false);
cursor_set_column(column);
- } else {
- backspace_at_cursor();
+ return;
}
}
@@ -2371,7 +2050,7 @@ void TextEdit::_delete(bool p_word, bool p_all_to_right) {
}
if (is_selection_active()) {
- _delete_selection();
+ delete_selection();
return;
}
int curline_len = text[cursor.line].length();
@@ -2416,15 +2095,16 @@ void TextEdit::_delete(bool p_word, bool p_all_to_right) {
update();
}
-void TextEdit::_delete_selection() {
- if (is_selection_active()) {
- selection.active = false;
- update();
- _remove_text(selection.from_line, selection.from_column, selection.to_line, selection.to_column);
- cursor_set_line(selection.from_line, false, false);
- cursor_set_column(selection.from_column);
- update();
+void TextEdit::delete_selection() {
+ if (!is_selection_active()) {
+ return;
}
+
+ selection.active = false;
+ _remove_text(selection.from_line, selection.from_column, selection.to_line, selection.to_column);
+ cursor_set_line(selection.from_line, false, false);
+ cursor_set_column(selection.from_column);
+ update();
}
void TextEdit::_move_cursor_document_start(bool p_select) {
@@ -2459,7 +2139,7 @@ void TextEdit::_move_cursor_document_end(bool p_select) {
void TextEdit::_handle_unicode_character(uint32_t unicode, bool p_had_selection) {
if (p_had_selection) {
- _delete_selection();
+ delete_selection();
}
// Remove the old character if in insert mode and no selection.
@@ -2942,31 +2622,19 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
return;
}
- // INDENTATION.
- if (k->is_action("ui_text_dedent", true)) {
- _indent_left();
- accept_event();
- return;
- }
- if (k->is_action("ui_text_indent", true)) {
- _indent_right();
- accept_event();
- return;
- }
-
// BACKSPACE AND DELETE.
if (k->is_action("ui_text_backspace_all_to_left", true)) {
- _backspace(false, true);
+ _do_backspace(false, true);
accept_event();
return;
}
if (k->is_action("ui_text_backspace_word", true)) {
- _backspace(true);
+ _do_backspace(true);
accept_event();
return;
}
if (k->is_action("ui_text_backspace", true)) {
- _backspace();
+ _do_backspace();
accept_event();
return;
}
@@ -4540,6 +4208,39 @@ bool TextEdit::is_gutter_overwritable(int p_gutter) const {
return gutters[p_gutter].overwritable;
}
+void TextEdit::merge_gutters(int p_from_line, int p_to_line) {
+ ERR_FAIL_INDEX(p_from_line, text.size());
+ ERR_FAIL_INDEX(p_to_line, text.size());
+ if (p_from_line == p_to_line) {
+ return;
+ }
+
+ for (int i = 0; i < gutters.size(); i++) {
+ if (!gutters[i].overwritable) {
+ continue;
+ }
+
+ if (text.get_line_gutter_text(p_from_line, i) != "") {
+ text.set_line_gutter_text(p_to_line, i, text.get_line_gutter_text(p_from_line, i));
+ text.set_line_gutter_item_color(p_to_line, i, text.get_line_gutter_item_color(p_from_line, i));
+ }
+
+ if (text.get_line_gutter_icon(p_from_line, i).is_valid()) {
+ text.set_line_gutter_icon(p_to_line, i, text.get_line_gutter_icon(p_from_line, i));
+ text.set_line_gutter_item_color(p_to_line, i, text.get_line_gutter_item_color(p_from_line, i));
+ }
+
+ if (text.get_line_gutter_metadata(p_from_line, i) != "") {
+ text.set_line_gutter_metadata(p_to_line, i, text.get_line_gutter_metadata(p_from_line, i));
+ }
+
+ if (text.is_line_gutter_clickable(p_from_line, i)) {
+ text.set_line_gutter_clickable(p_to_line, i, true);
+ }
+ }
+ update();
+}
+
void TextEdit::set_gutter_custom_draw(int p_gutter, Object *p_object, const StringName &p_callback) {
ERR_FAIL_INDEX(p_gutter, gutters.size());
ERR_FAIL_NULL(p_object);
@@ -4625,10 +4326,6 @@ Color TextEdit::get_line_background_color(int p_line) {
return text.get_line_background_color(p_line);
}
-void TextEdit::set_auto_indent(bool p_auto_indent) {
- auto_indent = p_auto_indent;
-}
-
void TextEdit::cut() {
if (readonly) {
return;
@@ -4644,7 +4341,7 @@ void TextEdit::cut() {
_remove_text(cursor.line, 0, cursor.line + 1, 0);
} else {
_remove_text(cursor.line, 0, cursor.line, text[cursor.line].length());
- backspace_at_cursor();
+ backspace();
cursor_set_line(cursor.line + 1);
}
@@ -5191,7 +4888,6 @@ int TextEdit::get_last_unhidden_line() const {
int TextEdit::get_indent_level(int p_line) const {
ERR_FAIL_INDEX_V(p_line, text.size(), 0);
- // Counts number of tabs and spaces before line starts.
int tab_count = 0;
int whitespace_count = 0;
int line_length = text[p_line].size();
@@ -5204,28 +4900,17 @@ int TextEdit::get_indent_level(int p_line) const {
break;
}
}
- return tab_count * indent_size + whitespace_count;
+ return tab_count * text.get_tab_size() + whitespace_count;
}
-bool TextEdit::is_line_comment(int p_line) const {
- // Checks to see if this line is the start of a comment.
- ERR_FAIL_INDEX_V(p_line, text.size(), false);
+int TextEdit::get_first_non_whitespace_column(int p_line) const {
+ ERR_FAIL_INDEX_V(p_line, text.size(), 0);
- int line_length = text[p_line].size();
- for (int i = 0; i < line_length - 1; i++) {
- if (_is_whitespace(text[p_line][i])) {
- continue;
- }
- if (_is_symbol(text[p_line][i])) {
- if (text[p_line][i] == '\\') {
- i++; // Skip quoted anything.
- continue;
- }
- return text[p_line][i] == '#' || (i + 1 < line_length && text[p_line][i] == '/' && text[p_line][i + 1] == '/');
- }
- break;
+ int col = 0;
+ while (col < text[p_line].length() && _is_whitespace(text[p_line][col])) {
+ col++;
}
- return false;
+ return col;
}
int TextEdit::get_line_count() const {
@@ -5401,32 +5086,18 @@ void TextEdit::_push_current_op() {
}
}
-void TextEdit::set_indent_using_spaces(const bool p_use_spaces) {
- indent_using_spaces = p_use_spaces;
-}
-
-bool TextEdit::is_indent_using_spaces() const {
- return indent_using_spaces;
-}
-
-void TextEdit::set_indent_size(const int p_size) {
- ERR_FAIL_COND_MSG(p_size <= 0, "Indend size must be greater than 0.");
- if (indent_size != p_size) {
- indent_size = p_size;
- text.set_indent_size(p_size);
- text.invalidate_all_lines();
- }
-
- space_indent = "";
- for (int i = 0; i < p_size; i++) {
- space_indent += " ";
+void TextEdit::set_tab_size(const int p_size) {
+ ERR_FAIL_COND_MSG(p_size <= 0, "Tab size must be greater than 0.");
+ if (p_size == text.get_tab_size()) {
+ return;
}
-
+ text.set_tab_size(p_size);
+ text.invalidate_all_lines();
update();
}
-int TextEdit::get_indent_size() {
- return indent_size;
+int TextEdit::get_tab_size() const {
+ return text.get_tab_size();
}
void TextEdit::set_draw_tabs(bool p_draw) {
@@ -6019,6 +5690,11 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_language", "language"), &TextEdit::set_language);
ClassDB::bind_method(D_METHOD("get_language"), &TextEdit::get_language);
+ ClassDB::bind_method(D_METHOD("get_first_non_whitespace_column", "line"), &TextEdit::get_first_non_whitespace_column);
+ ClassDB::bind_method(D_METHOD("get_indent_level", "line"), &TextEdit::get_indent_level);
+ ClassDB::bind_method(D_METHOD("set_tab_size", "size"), &TextEdit::set_tab_size);
+ ClassDB::bind_method(D_METHOD("get_tab_size"), &TextEdit::get_tab_size);
+
ClassDB::bind_method(D_METHOD("set_text", "text"), &TextEdit::set_text);
ClassDB::bind_method(D_METHOD("insert_text_at_cursor", "text"), &TextEdit::insert_text_at_cursor);
@@ -6073,6 +5749,10 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_selecting_enabled", "enable"), &TextEdit::set_selecting_enabled);
ClassDB::bind_method(D_METHOD("is_selecting_enabled"), &TextEdit::is_selecting_enabled);
+ ClassDB::bind_method(D_METHOD("delete_selection"), &TextEdit::delete_selection);
+ ClassDB::bind_method(D_METHOD("backspace"), &TextEdit::backspace);
+ BIND_VMETHOD(MethodInfo("_backspace"));
+
ClassDB::bind_method(D_METHOD("cut"), &TextEdit::cut);
ClassDB::bind_method(D_METHOD("copy"), &TextEdit::copy);
ClassDB::bind_method(D_METHOD("paste"), &TextEdit::paste);
@@ -6128,6 +5808,7 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_gutter_clickable", "gutter"), &TextEdit::is_gutter_clickable);
ClassDB::bind_method(D_METHOD("set_gutter_overwritable", "gutter", "overwritable"), &TextEdit::set_gutter_overwritable);
ClassDB::bind_method(D_METHOD("is_gutter_overwritable", "gutter"), &TextEdit::is_gutter_overwritable);
+ ClassDB::bind_method(D_METHOD("merge_gutters", "from_line", "to_line"), &TextEdit::merge_gutters);
ClassDB::bind_method(D_METHOD("set_gutter_custom_draw", "column", "object", "callback"), &TextEdit::set_gutter_custom_draw);
// Line gutters.
@@ -6254,7 +5935,7 @@ TextEdit::TextEdit() {
_update_caches();
set_default_cursor_shape(CURSOR_IBEAM);
- text.set_indent_size(indent_size);
+ text.set_tab_size(text.get_tab_size());
text.clear();
h_scroll = memnew(HScrollBar);
diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h
index 27577c48d8..146de50275 100644
--- a/scene/gui/text_edit.h
+++ b/scene/gui/text_edit.h
@@ -112,11 +112,12 @@ private:
int width = -1;
- int indent_size = 4;
+ int tab_size = 4;
int gutter_count = 0;
public:
- void set_indent_size(int p_indent_size);
+ void set_tab_size(int p_tab_size);
+ int get_tab_size() const;
void set_font(const Ref<Font> &p_font);
void set_font_size(int p_font_size);
void set_font_features(const Dictionary &p_features);
@@ -259,9 +260,6 @@ private:
int max_chars = 0;
bool readonly = true; // Initialise to opposite first, so we get past the early-out in set_readonly.
- bool indent_using_spaces = false;
- int indent_size = 4;
- String space_indent = " ";
Timer *caret_blink_timer;
bool caret_blink_enabled = false;
@@ -296,7 +294,6 @@ private:
bool scroll_past_end_of_file_enabled = false;
bool brace_matching_enabled = false;
bool highlight_current_line = false;
- bool auto_indent = false;
String cut_copy_line;
bool insert_mode = false;
@@ -420,14 +417,9 @@ private:
void _clear();
- int _calculate_spaces_till_next_left_indent(int column);
- int _calculate_spaces_till_next_right_indent(int column);
-
// Methods used in shortcuts
void _swap_current_input_direction();
void _new_line(bool p_split_current = true, bool p_above = false);
- void _indent_right();
- void _indent_left();
void _move_cursor_left(bool p_select, bool p_move_by_word = false);
void _move_cursor_right(bool p_select, bool p_move_by_word = false);
void _move_cursor_up(bool p_select);
@@ -436,9 +428,8 @@ private:
void _move_cursor_to_line_end(bool p_select);
void _move_cursor_page_up(bool p_select);
void _move_cursor_page_down(bool p_select);
- void _backspace(bool p_word = false, bool p_all_to_left = false);
+ void _do_backspace(bool p_word = false, bool p_all_to_left = false);
void _delete(bool p_word = false, bool p_all_to_right = false);
- void _delete_selection();
void _move_cursor_document_start(bool p_select);
void _move_cursor_document_end(bool p_select);
void _handle_unicode_character(uint32_t unicode, bool p_had_selection);
@@ -522,6 +513,8 @@ public:
void set_gutter_overwritable(int p_gutter, bool p_overwritable);
bool is_gutter_overwritable(int p_gutter) const;
+ void merge_gutters(int p_from_line, int p_to_line);
+
void set_gutter_custom_draw(int p_gutter, Object *p_object, const StringName &p_callback);
// Line gutters.
@@ -635,12 +628,9 @@ public:
bool has_ime_text() const;
void set_line(int line, String new_text);
int get_row_height() const;
- void backspace_at_cursor();
- void indent_selected_lines_left();
- void indent_selected_lines_right();
int get_indent_level(int p_line) const;
- bool is_line_comment(int p_line) const;
+ int get_first_non_whitespace_column(int p_line) const;
inline void set_scroll_pass_end_of_file(bool p_enabled) {
scroll_past_end_of_file_enabled = p_enabled;
@@ -653,7 +643,6 @@ public:
brace_matching_enabled = p_enabled;
update();
}
- void set_auto_indent(bool p_auto_indent);
void center_viewport_to_cursor();
@@ -699,6 +688,9 @@ public:
void clear();
+ void delete_selection();
+
+ virtual void backspace();
void cut();
void copy();
void paste();
@@ -730,10 +722,8 @@ public:
void redo();
void clear_undo_history();
- void set_indent_using_spaces(const bool p_use_spaces);
- bool is_indent_using_spaces() const;
- void set_indent_size(const int p_size);
- int get_indent_size();
+ void set_tab_size(const int p_size);
+ int get_tab_size() const;
void set_draw_tabs(bool p_draw);
bool is_drawing_tabs() const;
void set_draw_spaces(bool p_draw);
diff --git a/scene/gui/texture_progress_bar.cpp b/scene/gui/texture_progress_bar.cpp
index 46ce9d5ca9..5e5dec3579 100644
--- a/scene/gui/texture_progress_bar.cpp
+++ b/scene/gui/texture_progress_bar.cpp
@@ -221,43 +221,87 @@ void TextureProgressBar::draw_nine_patch_stretched(const Ref<Texture2D> &p_textu
double width_texture = 0.0;
double first_section_size = 0.0;
double last_section_size = 0.0;
- switch (mode) {
- case FILL_LEFT_TO_RIGHT:
- case FILL_RIGHT_TO_LEFT: {
+ switch (p_mode) {
+ case FILL_LEFT_TO_RIGHT: {
width_total = dst_rect.size.x;
width_texture = texture_size.x;
first_section_size = topleft.x;
last_section_size = bottomright.x;
} break;
- case FILL_TOP_TO_BOTTOM:
- case FILL_BOTTOM_TO_TOP: {
+ case FILL_RIGHT_TO_LEFT: {
+ width_total = dst_rect.size.x;
+ width_texture = texture_size.x;
+ // In contrast to `FILL_LEFT_TO_RIGHT`, `first_section_size` and `last_section_size` should switch value.
+ first_section_size = bottomright.x;
+ last_section_size = topleft.x;
+ } break;
+ case FILL_TOP_TO_BOTTOM: {
width_total = dst_rect.size.y;
width_texture = texture_size.y;
first_section_size = topleft.y;
last_section_size = bottomright.y;
} break;
+ case FILL_BOTTOM_TO_TOP: {
+ width_total = dst_rect.size.y;
+ width_texture = texture_size.y;
+ // Similar to `FILL_RIGHT_TO_LEFT`.
+ first_section_size = bottomright.y;
+ last_section_size = topleft.y;
+ } break;
case FILL_BILINEAR_LEFT_AND_RIGHT: {
- // TODO: Implement
+ width_total = dst_rect.size.x;
+ width_texture = texture_size.x;
+ first_section_size = topleft.x;
+ last_section_size = bottomright.x;
} break;
case FILL_BILINEAR_TOP_AND_BOTTOM: {
- // TODO: Implement
+ width_total = dst_rect.size.y;
+ width_texture = texture_size.y;
+ first_section_size = topleft.y;
+ last_section_size = bottomright.y;
} break;
case FILL_CLOCKWISE:
case FILL_CLOCKWISE_AND_COUNTER_CLOCKWISE:
case FILL_COUNTER_CLOCKWISE: {
- // Those modes are circular, not relevant for nine patch
+ // Those modes are circular, not relevant for nine patch.
} break;
+ case FILL_MODE_MAX:
+ break;
}
double width_filled = width_total * p_ratio;
double middle_section_size = MAX(0.0, width_texture - first_section_size - last_section_size);
- middle_section_size *= MIN(1.0, (MAX(0.0, width_filled - first_section_size) / MAX(1.0, width_total - first_section_size - last_section_size)));
- last_section_size = MAX(0.0, last_section_size - (width_total - width_filled));
- first_section_size = MIN(first_section_size, width_filled);
- width_texture = MIN(width_texture, first_section_size + middle_section_size + last_section_size);
+ // Maximum middle texture size.
+ double max_middle_texture_size = middle_section_size;
+
+ // Maximum real middle texture size.
+ double max_middle_real_size = MAX(0.0, width_total - (first_section_size + last_section_size));
+
+ switch (p_mode) {
+ case FILL_BILINEAR_LEFT_AND_RIGHT:
+ case FILL_BILINEAR_TOP_AND_BOTTOM: {
+ last_section_size = MAX(0.0, last_section_size - (width_total - width_filled) * 0.5);
+ first_section_size = MAX(0.0, first_section_size - (width_total - width_filled) * 0.5);
+
+ // When `width_filled` increases, `middle_section_size` only increases when either of `first_section_size` and `last_section_size` is zero.
+ // Also, it should always be smaller than or equal to `(width_total - (first_section_size + last_section_size))`.
+ double real_middle_size = width_filled - first_section_size - last_section_size;
+ middle_section_size *= MIN(max_middle_real_size, real_middle_size) / max_middle_real_size;
+
+ width_texture = MIN(width_texture, first_section_size + middle_section_size + last_section_size);
+ } break;
+ case FILL_MODE_MAX:
+ break;
+ default: {
+ middle_section_size *= MIN(1.0, (MAX(0.0, width_filled - first_section_size) / MAX(1.0, width_total - first_section_size - last_section_size)));
+ last_section_size = MAX(0.0, last_section_size - (width_total - width_filled));
+ first_section_size = MIN(first_section_size, width_filled);
+ width_texture = MIN(width_texture, first_section_size + middle_section_size + last_section_size);
+ }
+ }
- switch (mode) {
+ switch (p_mode) {
case FILL_LEFT_TO_RIGHT: {
src_rect.size.x = width_texture;
dst_rect.size.x = width_filled;
@@ -287,16 +331,32 @@ void TextureProgressBar::draw_nine_patch_stretched(const Ref<Texture2D> &p_textu
bottomright.y = first_section_size;
} break;
case FILL_BILINEAR_LEFT_AND_RIGHT: {
- // TODO: Implement
+ double center_mapped_from_real_width = (width_total * 0.5 - topleft.x) / max_middle_real_size * max_middle_texture_size + topleft.x;
+ double drift_from_unscaled_center = (src_rect.size.x * 0.5 - center_mapped_from_real_width) * (last_section_size - first_section_size) / (bottomright.x - topleft.x);
+ src_rect.position.x += center_mapped_from_real_width + drift_from_unscaled_center - width_texture * 0.5;
+ src_rect.size.x = width_texture;
+ dst_rect.position.x += (width_total - width_filled) * 0.5;
+ dst_rect.size.x = width_filled;
+ topleft.x = first_section_size;
+ bottomright.x = last_section_size;
} break;
case FILL_BILINEAR_TOP_AND_BOTTOM: {
- // TODO: Implement
+ double center_mapped_from_real_width = (width_total * 0.5 - topleft.y) / max_middle_real_size * max_middle_texture_size + topleft.y;
+ double drift_from_unscaled_center = (src_rect.size.y * 0.5 - center_mapped_from_real_width) * (last_section_size - first_section_size) / (bottomright.y - topleft.y);
+ src_rect.position.y += center_mapped_from_real_width + drift_from_unscaled_center - width_texture * 0.5;
+ src_rect.size.y = width_texture;
+ dst_rect.position.y += (width_total - width_filled) * 0.5;
+ dst_rect.size.y = width_filled;
+ topleft.y = first_section_size;
+ bottomright.y = last_section_size;
} break;
case FILL_CLOCKWISE:
case FILL_CLOCKWISE_AND_COUNTER_CLOCKWISE:
case FILL_COUNTER_CLOCKWISE: {
- // Those modes are circular, not relevant for nine patch
+ // Those modes are circular, not relevant for nine patch.
} break;
+ case FILL_MODE_MAX:
+ break;
}
}
@@ -310,19 +370,34 @@ void TextureProgressBar::_notification(int p_what) {
const float corners[12] = { -0.125, -0.375, -0.625, -0.875, 0.125, 0.375, 0.625, 0.875, 1.125, 1.375, 1.625, 1.875 };
switch (p_what) {
case NOTIFICATION_DRAW: {
- if (nine_patch_stretch && (mode == FILL_LEFT_TO_RIGHT || mode == FILL_RIGHT_TO_LEFT || mode == FILL_TOP_TO_BOTTOM || mode == FILL_BOTTOM_TO_TOP)) {
+ if (nine_patch_stretch && (mode == FILL_LEFT_TO_RIGHT || mode == FILL_RIGHT_TO_LEFT || mode == FILL_TOP_TO_BOTTOM || mode == FILL_BOTTOM_TO_TOP || mode == FILL_BILINEAR_LEFT_AND_RIGHT || mode == FILL_BILINEAR_TOP_AND_BOTTOM)) {
if (under.is_valid()) {
- draw_nine_patch_stretched(under, FILL_LEFT_TO_RIGHT, 1.0, tint_under);
+ draw_nine_patch_stretched(under, mode, 1.0, tint_under);
}
if (progress.is_valid()) {
draw_nine_patch_stretched(progress, mode, get_as_ratio(), tint_progress);
}
if (over.is_valid()) {
- draw_nine_patch_stretched(over, FILL_LEFT_TO_RIGHT, 1.0, tint_over);
+ draw_nine_patch_stretched(over, mode, 1.0, tint_over);
}
} else {
if (under.is_valid()) {
- draw_texture(under, Point2(), tint_under);
+ switch (mode) {
+ case FILL_CLOCKWISE:
+ case FILL_COUNTER_CLOCKWISE:
+ case FILL_CLOCKWISE_AND_COUNTER_CLOCKWISE: {
+ if (nine_patch_stretch) {
+ Rect2 region = Rect2(Point2(), get_size());
+ draw_texture_rect(under, region, false, tint_under);
+ } else {
+ draw_texture(under, Point2(), tint_under);
+ }
+ } break;
+ case FILL_MODE_MAX:
+ break;
+ default:
+ draw_texture(under, Point2(), tint_under);
+ }
}
if (progress.is_valid()) {
Size2 s = progress->get_size();
@@ -353,7 +428,7 @@ void TextureProgressBar::_notification(int p_what) {
float val = get_as_ratio() * rad_max_degrees / 360;
if (val == 1) {
Rect2 region = Rect2(Point2(), s);
- draw_texture_rect_region(progress, region, region, tint_progress);
+ draw_texture_rect(progress, region, false, tint_progress);
} else if (val != 0) {
Array pts;
float direction = mode == FILL_COUNTER_CLOCKWISE ? -1 : 1;
@@ -416,12 +491,29 @@ void TextureProgressBar::_notification(int p_what) {
Rect2 region = Rect2(Point2(0, s.y / 2 - s.y * get_as_ratio() / 2), Size2(s.x, s.y * get_as_ratio()));
draw_texture_rect_region(progress, region, region, tint_progress);
} break;
+ case FILL_MODE_MAX:
+ break;
default:
draw_texture_rect_region(progress, Rect2(Point2(), Size2(s.x * get_as_ratio(), s.y)), Rect2(Point2(), Size2(s.x * get_as_ratio(), s.y)), tint_progress);
}
}
if (over.is_valid()) {
- draw_texture(over, Point2(), tint_over);
+ switch (mode) {
+ case FILL_CLOCKWISE:
+ case FILL_COUNTER_CLOCKWISE:
+ case FILL_CLOCKWISE_AND_COUNTER_CLOCKWISE: {
+ if (nine_patch_stretch) {
+ Rect2 region = Rect2(Point2(), get_size());
+ draw_texture_rect(over, region, false, tint_over);
+ } else {
+ draw_texture(over, Point2(), tint_over);
+ }
+ } break;
+ case FILL_MODE_MAX:
+ break;
+ default:
+ draw_texture(over, Point2(), tint_over);
+ }
}
}
} break;
@@ -429,7 +521,7 @@ void TextureProgressBar::_notification(int p_what) {
}
void TextureProgressBar::set_fill_mode(int p_fill) {
- ERR_FAIL_INDEX(p_fill, 9);
+ ERR_FAIL_INDEX(p_fill, FILL_MODE_MAX);
mode = (FillMode)p_fill;
update();
}
@@ -512,7 +604,7 @@ void TextureProgressBar::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_under", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_under_texture", "get_under_texture");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_over", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_over_texture", "get_over_texture");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_progress", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_progress_texture", "get_progress_texture");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "fill_mode", PROPERTY_HINT_ENUM, "Left to Right,Right to Left,Top to Bottom,Bottom to Top,Clockwise,Counter Clockwise,Bilinear (Left and Right),Bilinear (Top and Bottom), Clockwise and Counter Clockwise"), "set_fill_mode", "get_fill_mode");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "fill_mode", PROPERTY_HINT_ENUM, "Left to Right,Right to Left,Top to Bottom,Bottom to Top,Clockwise,Counter Clockwise,Bilinear (Left and Right),Bilinear (Top and Bottom),Clockwise and Counter Clockwise"), "set_fill_mode", "get_fill_mode");
ADD_GROUP("Tint", "tint_");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "tint_under"), "set_tint_under", "get_tint_under");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "tint_over"), "set_tint_over", "get_tint_over");
diff --git a/scene/gui/texture_progress_bar.h b/scene/gui/texture_progress_bar.h
index a3883a7017..d147c43a26 100644
--- a/scene/gui/texture_progress_bar.h
+++ b/scene/gui/texture_progress_bar.h
@@ -54,7 +54,8 @@ public:
FILL_COUNTER_CLOCKWISE,
FILL_BILINEAR_LEFT_AND_RIGHT,
FILL_BILINEAR_TOP_AND_BOTTOM,
- FILL_CLOCKWISE_AND_COUNTER_CLOCKWISE
+ FILL_CLOCKWISE_AND_COUNTER_CLOCKWISE,
+ FILL_MODE_MAX,
};
void set_fill_mode(int p_fill);
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index b02b4edd8e..f677b3592a 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -1846,78 +1846,76 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
int prev_hl_ofs = base_ofs;
while (c) {
- if (cache.draw_relationship_lines > 0 && (!hide_root || c->parent != root)) {
- int root_ofs = children_pos.x + ((p_item->disable_folding || hide_folding) ? cache.hseparation : cache.item_margin);
- int parent_ofs = p_pos.x + cache.item_margin;
- Point2i root_pos = Point2i(root_ofs, children_pos.y + label_h / 2) - cache.offset + p_draw_ofs;
+ if (htotal >= 0) {
+ int child_h = draw_item(children_pos, p_draw_ofs, p_draw_size, c);
- if (c->get_first_child() != nullptr) {
- root_pos -= Point2i(cache.arrow->get_width(), 0);
- }
+ // Draw relationship lines.
+ if (cache.draw_relationship_lines > 0 && (!hide_root || c->parent != root)) {
+ int root_ofs = children_pos.x + ((p_item->disable_folding || hide_folding) ? cache.hseparation : cache.item_margin);
+ int parent_ofs = p_pos.x + cache.item_margin;
+ Point2i root_pos = Point2i(root_ofs, children_pos.y + label_h / 2) - cache.offset + p_draw_ofs;
- float line_width = cache.relationship_line_width;
- float parent_line_width = cache.parent_hl_line_width;
- float children_line_width = cache.children_hl_line_width;
+ if (c->get_first_child() != nullptr) {
+ root_pos -= Point2i(cache.arrow->get_width(), 0);
+ }
+
+ float line_width = cache.relationship_line_width;
+ float parent_line_width = cache.parent_hl_line_width;
+ float children_line_width = cache.children_hl_line_width;
#ifdef TOOLS_ENABLED
- line_width *= Math::round(EDSCALE);
- parent_line_width *= Math::round(EDSCALE);
- children_line_width *= Math::round(EDSCALE);
+ line_width *= Math::round(EDSCALE);
+ parent_line_width *= Math::round(EDSCALE);
+ children_line_width *= Math::round(EDSCALE);
#endif
- Point2i parent_pos = Point2i(parent_ofs - cache.arrow->get_width() / 2, p_pos.y + label_h / 2 + cache.arrow->get_height() / 2) - cache.offset + p_draw_ofs;
+ Point2i parent_pos = Point2i(parent_ofs - cache.arrow->get_width() / 2, p_pos.y + label_h / 2 + cache.arrow->get_height() / 2) - cache.offset + p_draw_ofs;
- int more_prev_ofs = 0;
+ int more_prev_ofs = 0;
- if (root_pos.y + line_width >= 0) {
- if (rtl) {
- root_pos.x = get_size().width - root_pos.x;
- parent_pos.x = get_size().width - parent_pos.x;
- }
+ if (root_pos.y + line_width >= 0) {
+ if (rtl) {
+ root_pos.x = get_size().width - root_pos.x;
+ parent_pos.x = get_size().width - parent_pos.x;
+ }
- // Order of parts on this bend: the horizontal line first, then the vertical line.
- if (_is_branch_selected(c)) {
- // If this item or one of its children is selected, we draw the line using parent highlight style.
- RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(parent_line_width / 2), root_pos.y), cache.parent_hl_line_color, parent_line_width);
- RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(parent_line_width / 2)), Point2i(parent_pos.x, prev_hl_ofs), cache.parent_hl_line_color, parent_line_width);
-
- more_prev_ofs = cache.parent_hl_line_margin;
- prev_hl_ofs = root_pos.y + Math::floor(parent_line_width / 2);
- } else if (p_item->is_selected(0)) {
- // If parent item is selected (but this item is not), we draw the line using children highlight style.
- // Siblings of the selected branch can be drawn with a slight offset and their vertical line must appear as highlighted.
- if (_is_sibling_branch_selected(c)) {
- RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(parent_line_width / 2), root_pos.y), cache.children_hl_line_color, children_line_width);
+ // Order of parts on this bend: the horizontal line first, then the vertical line.
+ if (_is_branch_selected(c)) {
+ // If this item or one of its children is selected, we draw the line using parent highlight style.
+ RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(parent_line_width / 2), root_pos.y), cache.parent_hl_line_color, parent_line_width);
RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(parent_line_width / 2)), Point2i(parent_pos.x, prev_hl_ofs), cache.parent_hl_line_color, parent_line_width);
+ more_prev_ofs = cache.parent_hl_line_margin;
prev_hl_ofs = root_pos.y + Math::floor(parent_line_width / 2);
+ } else if (p_item->is_selected(0)) {
+ // If parent item is selected (but this item is not), we draw the line using children highlight style.
+ // Siblings of the selected branch can be drawn with a slight offset and their vertical line must appear as highlighted.
+ if (_is_sibling_branch_selected(c)) {
+ RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(parent_line_width / 2), root_pos.y), cache.children_hl_line_color, children_line_width);
+ RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(parent_line_width / 2)), Point2i(parent_pos.x, prev_hl_ofs), cache.parent_hl_line_color, parent_line_width);
+
+ prev_hl_ofs = root_pos.y + Math::floor(parent_line_width / 2);
+ } else {
+ RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(children_line_width / 2), root_pos.y), cache.children_hl_line_color, children_line_width);
+ RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(children_line_width / 2)), Point2i(parent_pos.x, prev_ofs + Math::floor(children_line_width / 2)), cache.children_hl_line_color, children_line_width);
+ }
} else {
- RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(children_line_width / 2), root_pos.y), cache.children_hl_line_color, children_line_width);
- RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(children_line_width / 2)), Point2i(parent_pos.x, prev_ofs + Math::floor(children_line_width / 2)), cache.children_hl_line_color, children_line_width);
- }
- } else {
- // If nothing of the above is true, we draw the line using normal style.
- // Siblings of the selected branch can be drawn with a slight offset and their vertical line must appear as highlighted.
- if (_is_sibling_branch_selected(c)) {
- RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + cache.parent_hl_line_margin, root_pos.y), cache.relationship_line_color, line_width);
- RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(parent_line_width / 2)), Point2i(parent_pos.x, prev_hl_ofs), cache.parent_hl_line_color, parent_line_width);
+ // If nothing of the above is true, we draw the line using normal style.
+ // Siblings of the selected branch can be drawn with a slight offset and their vertical line must appear as highlighted.
+ if (_is_sibling_branch_selected(c)) {
+ RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + cache.parent_hl_line_margin, root_pos.y), cache.relationship_line_color, line_width);
+ RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(parent_line_width / 2)), Point2i(parent_pos.x, prev_hl_ofs), cache.parent_hl_line_color, parent_line_width);
- prev_hl_ofs = root_pos.y + Math::floor(parent_line_width / 2);
- } else {
- RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(line_width / 2), root_pos.y), cache.relationship_line_color, line_width);
- RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(line_width / 2)), Point2i(parent_pos.x, prev_ofs + Math::floor(line_width / 2)), cache.relationship_line_color, line_width);
+ prev_hl_ofs = root_pos.y + Math::floor(parent_line_width / 2);
+ } else {
+ RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(line_width / 2), root_pos.y), cache.relationship_line_color, line_width);
+ RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(line_width / 2)), Point2i(parent_pos.x, prev_ofs + Math::floor(line_width / 2)), cache.relationship_line_color, line_width);
+ }
}
}
- }
- if (htotal < 0) {
- return -1;
+ prev_ofs = root_pos.y + more_prev_ofs;
}
- prev_ofs = root_pos.y + more_prev_ofs;
- }
-
- if (htotal >= 0) {
- int child_h = draw_item(children_pos, p_draw_ofs, p_draw_size, c);
if (child_h < 0) {
if (cache.draw_relationship_lines == 0) {
diff --git a/scene/main/http_request.cpp b/scene/main/http_request.cpp
index f3c7d128ba..775dfa4c46 100644
--- a/scene/main/http_request.cpp
+++ b/scene/main/http_request.cpp
@@ -322,7 +322,8 @@ bool HTTPRequest::_update_connection() {
} else {
// Did not request yet, do request
- Error err = client->request_raw(method, request_string, headers, request_data);
+ int size = request_data.size();
+ Error err = client->request(method, request_string, headers, size > 0 ? request_data.ptr() : nullptr, size);
if (err != OK) {
call_deferred("_request_done", RESULT_CONNECTION_ERROR, 0, PackedStringArray(), PackedByteArray());
return true;
@@ -627,7 +628,7 @@ void HTTPRequest::_bind_methods() {
}
HTTPRequest::HTTPRequest() {
- client.instantiate();
+ client = Ref<HTTPClient>(HTTPClient::create());
timer = memnew(Timer);
timer->set_one_shot(true);
timer->connect("timeout", callable_mp(this, &HTTPRequest::_timeout));
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index e31135b46a..4c9c1c0055 100644
--- a/scene/main/viewport.cpp
+++ b/scene/main/viewport.cpp
@@ -1746,7 +1746,7 @@ Control *Viewport::_gui_find_control_at_pos(CanvasItem *p_node, const Point2 &p_
Control *c = Object::cast_to<Control>(p_node);
- if (!c || !c->clips_input() || c->has_point(matrix.affine_inverse().xform(p_global))) {
+ if (!c || !c->is_clipping_contents() || c->has_point(matrix.affine_inverse().xform(p_global))) {
for (int i = p_node->get_child_count() - 1; i >= 0; i--) {
CanvasItem *ci = Object::cast_to<CanvasItem>(p_node->get_child(i));
if (!ci || ci->is_set_as_top_level()) {
diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp
index 3a2b34c6c8..8d0283d536 100644
--- a/scene/register_scene_types.cpp
+++ b/scene/register_scene_types.cpp
@@ -31,6 +31,7 @@
#include "register_scene_types.h"
#include "core/config/project_settings.h"
+#include "core/extension/native_extension_manager.h"
#include "core/object/class_db.h"
#include "core/os/os.h"
#include "scene/2d/animated_sprite_2d.h"
@@ -1040,9 +1041,13 @@ void register_scene_types() {
}
}
SceneDebugger::initialize();
+
+ NativeExtensionManager::get_singleton()->initialize_extensions(NativeExtension::INITIALIZATION_LEVEL_SCENE);
}
void unregister_scene_types() {
+ NativeExtensionManager::get_singleton()->deinitialize_extensions(NativeExtension::INITIALIZATION_LEVEL_SCENE);
+
SceneDebugger::deinitialize();
clear_default_theme();
diff --git a/scene/resources/primitive_meshes.cpp b/scene/resources/primitive_meshes.cpp
index a745df522b..2b2ebb5c16 100644
--- a/scene/resources/primitive_meshes.cpp
+++ b/scene/resources/primitive_meshes.cpp
@@ -866,9 +866,9 @@ void CylinderMesh::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_rings", "rings"), &CylinderMesh::set_rings);
ClassDB::bind_method(D_METHOD("get_rings"), &CylinderMesh::get_rings);
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "top_radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater"), "set_top_radius", "get_top_radius");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bottom_radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater"), "set_bottom_radius", "get_bottom_radius");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater"), "set_height", "get_height");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "top_radius", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), "set_top_radius", "get_top_radius");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bottom_radius", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), "set_bottom_radius", "get_bottom_radius");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater"), "set_height", "get_height");
ADD_PROPERTY(PropertyInfo(Variant::INT, "radial_segments", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_radial_segments", "get_radial_segments");
ADD_PROPERTY(PropertyInfo(Variant::INT, "rings", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_rings", "get_rings");
}
diff --git a/scene/resources/texture.cpp b/scene/resources/texture.cpp
index b8dd8b1c14..acc85cf7df 100644
--- a/scene/resources/texture.cpp
+++ b/scene/resources/texture.cpp
@@ -1595,6 +1595,7 @@ void GradientTexture::_update() {
}
void GradientTexture::set_width(int p_width) {
+ ERR_FAIL_COND(p_width <= 0);
width = p_width;
_queue_update();
}
diff --git a/scene/scene_string_names.cpp b/scene/scene_string_names.cpp
index 8acab79c9e..2e0af89c1a 100644
--- a/scene/scene_string_names.cpp
+++ b/scene/scene_string_names.cpp
@@ -103,7 +103,6 @@ SceneStringNames::SceneStringNames() {
_update_scroll = StaticCString::create("_update_scroll");
_update_xform = StaticCString::create("_update_xform");
- _clips_input = StaticCString::create("_clips_input");
_structured_text_parser = StaticCString::create("_structured_text_parser");
_proxgroup_add = StaticCString::create("_proxgroup_add");
diff --git a/scene/scene_string_names.h b/scene/scene_string_names.h
index 0c528a45f7..01865b0d2f 100644
--- a/scene/scene_string_names.h
+++ b/scene/scene_string_names.h
@@ -129,7 +129,6 @@ public:
StringName _update_scroll;
StringName _update_xform;
- StringName _clips_input;
StringName _structured_text_parser;
StringName _proxgroup_add;
diff --git a/servers/register_server_types.cpp b/servers/register_server_types.cpp
index deb230c4fb..59c7dcc7d2 100644
--- a/servers/register_server_types.cpp
+++ b/servers/register_server_types.cpp
@@ -55,6 +55,7 @@
#include "audio_server.h"
#include "camera/camera_feed.h"
#include "camera_server.h"
+#include "core/extension/native_extension_manager.h"
#include "display_server.h"
#include "navigation_server_2d.h"
#include "navigation_server_3d.h"
@@ -194,6 +195,7 @@ void register_server_types() {
ClassDB::register_class<RDTextureFormat>();
ClassDB::register_class<RDTextureView>();
ClassDB::register_class<RDAttachmentFormat>();
+ ClassDB::register_class<RDFramebufferPass>();
ClassDB::register_class<RDSamplerState>();
ClassDB::register_class<RDVertexAttribute>();
ClassDB::register_class<RDUniform>();
@@ -232,22 +234,26 @@ void register_server_types() {
PhysicsServer3DManager::register_server("GodotPhysics3D", &_createGodotPhysics3DCallback);
PhysicsServer3DManager::set_default_server("GodotPhysics3D");
+
+ NativeExtensionManager::get_singleton()->initialize_extensions(NativeExtension::INITIALIZATION_LEVEL_SERVERS);
}
void unregister_server_types() {
+ NativeExtensionManager::get_singleton()->deinitialize_extensions(NativeExtension::INITIALIZATION_LEVEL_SERVERS);
+
memdelete(shader_types);
TextServer::finish_hex_code_box_fonts();
}
void register_server_singletons() {
- Engine::get_singleton()->add_singleton(Engine::Singleton("DisplayServer", DisplayServer::get_singleton()));
- Engine::get_singleton()->add_singleton(Engine::Singleton("RenderingServer", RenderingServer::get_singleton()));
- Engine::get_singleton()->add_singleton(Engine::Singleton("AudioServer", AudioServer::get_singleton()));
- Engine::get_singleton()->add_singleton(Engine::Singleton("PhysicsServer2D", PhysicsServer2D::get_singleton()));
- Engine::get_singleton()->add_singleton(Engine::Singleton("PhysicsServer3D", PhysicsServer3D::get_singleton()));
- Engine::get_singleton()->add_singleton(Engine::Singleton("NavigationServer2D", NavigationServer2D::get_singleton_mut()));
- Engine::get_singleton()->add_singleton(Engine::Singleton("NavigationServer3D", NavigationServer3D::get_singleton_mut()));
- Engine::get_singleton()->add_singleton(Engine::Singleton("TextServerManager", TextServerManager::get_singleton()));
- Engine::get_singleton()->add_singleton(Engine::Singleton("XRServer", XRServer::get_singleton()));
- Engine::get_singleton()->add_singleton(Engine::Singleton("CameraServer", CameraServer::get_singleton()));
+ Engine::get_singleton()->add_singleton(Engine::Singleton("DisplayServer", DisplayServer::get_singleton(), "DisplayServer"));
+ Engine::get_singleton()->add_singleton(Engine::Singleton("RenderingServer", RenderingServer::get_singleton(), "RenderingServer"));
+ Engine::get_singleton()->add_singleton(Engine::Singleton("AudioServer", AudioServer::get_singleton(), "AudioServer"));
+ Engine::get_singleton()->add_singleton(Engine::Singleton("PhysicsServer2D", PhysicsServer2D::get_singleton(), "PhysicsServer2D"));
+ Engine::get_singleton()->add_singleton(Engine::Singleton("PhysicsServer3D", PhysicsServer3D::get_singleton(), "PhysicsServer3D"));
+ Engine::get_singleton()->add_singleton(Engine::Singleton("NavigationServer2D", NavigationServer2D::get_singleton_mut(), "NavigationServer2D"));
+ Engine::get_singleton()->add_singleton(Engine::Singleton("NavigationServer3D", NavigationServer3D::get_singleton_mut(), "NavigationServer3D"));
+ Engine::get_singleton()->add_singleton(Engine::Singleton("TextServerManager", TextServerManager::get_singleton(), "TextServerManager"));
+ Engine::get_singleton()->add_singleton(Engine::Singleton("XRServer", XRServer::get_singleton(), "XRServer"));
+ Engine::get_singleton()->add_singleton(Engine::Singleton("CameraServer", CameraServer::get_singleton(), "CameraServer"));
}
diff --git a/servers/rendering/renderer_rd/pipeline_cache_rd.cpp b/servers/rendering/renderer_rd/pipeline_cache_rd.cpp
index b2b919c40e..22888ddbe5 100644
--- a/servers/rendering/renderer_rd/pipeline_cache_rd.cpp
+++ b/servers/rendering/renderer_rd/pipeline_cache_rd.cpp
@@ -31,20 +31,21 @@
#include "pipeline_cache_rd.h"
#include "core/os/memory.h"
-RID PipelineCacheRD::_generate_version(RD::VertexFormatID p_vertex_format_id, RD::FramebufferFormatID p_framebuffer_format_id, bool p_wireframe) {
+RID PipelineCacheRD::_generate_version(RD::VertexFormatID p_vertex_format_id, RD::FramebufferFormatID p_framebuffer_format_id, bool p_wireframe, uint32_t p_render_pass) {
RD::PipelineMultisampleState multisample_state_version = multisample_state;
- multisample_state_version.sample_count = RD::get_singleton()->framebuffer_format_get_texture_samples(p_framebuffer_format_id);
+ multisample_state_version.sample_count = RD::get_singleton()->framebuffer_format_get_texture_samples(p_framebuffer_format_id, p_render_pass);
RD::PipelineRasterizationState raster_state_version = rasterization_state;
raster_state_version.wireframe = p_wireframe;
- RID pipeline = RD::get_singleton()->render_pipeline_create(shader, p_framebuffer_format_id, p_vertex_format_id, render_primitive, raster_state_version, multisample_state_version, depth_stencil_state, blend_state, dynamic_state_flags);
+ RID pipeline = RD::get_singleton()->render_pipeline_create(shader, p_framebuffer_format_id, p_vertex_format_id, render_primitive, raster_state_version, multisample_state_version, depth_stencil_state, blend_state, dynamic_state_flags, p_render_pass);
ERR_FAIL_COND_V(pipeline.is_null(), RID());
versions = (Version *)memrealloc(versions, sizeof(Version) * (version_count + 1));
versions[version_count].framebuffer_id = p_framebuffer_format_id;
versions[version_count].vertex_id = p_vertex_format_id;
versions[version_count].wireframe = p_wireframe;
versions[version_count].pipeline = pipeline;
+ versions[version_count].render_pass = p_render_pass;
version_count++;
return pipeline;
}
diff --git a/servers/rendering/renderer_rd/pipeline_cache_rd.h b/servers/rendering/renderer_rd/pipeline_cache_rd.h
index b1c8f21ecc..387a8a038f 100644
--- a/servers/rendering/renderer_rd/pipeline_cache_rd.h
+++ b/servers/rendering/renderer_rd/pipeline_cache_rd.h
@@ -50,6 +50,7 @@ class PipelineCacheRD {
struct Version {
RD::VertexFormatID vertex_id;
RD::FramebufferFormatID framebuffer_id;
+ uint32_t render_pass;
bool wireframe;
RID pipeline;
};
@@ -57,7 +58,7 @@ class PipelineCacheRD {
Version *versions;
uint32_t version_count;
- RID _generate_version(RD::VertexFormatID p_vertex_format_id, RD::FramebufferFormatID p_framebuffer_format_id, bool p_wireframe);
+ RID _generate_version(RD::VertexFormatID p_vertex_format_id, RD::FramebufferFormatID p_framebuffer_format_id, bool p_wireframe, uint32_t p_render_pass);
void _clear();
@@ -65,7 +66,7 @@ public:
void setup(RID p_shader, RD::RenderPrimitive p_primitive, const RD::PipelineRasterizationState &p_rasterization_state, RD::PipelineMultisampleState p_multisample, const RD::PipelineDepthStencilState &p_depth_stencil_state, const RD::PipelineColorBlendState &p_blend_state, int p_dynamic_state_flags = 0);
void update_shader(RID p_shader);
- _FORCE_INLINE_ RID get_render_pipeline(RD::VertexFormatID p_vertex_format_id, RD::FramebufferFormatID p_framebuffer_format_id, bool p_wireframe = false) {
+ _FORCE_INLINE_ RID get_render_pipeline(RD::VertexFormatID p_vertex_format_id, RD::FramebufferFormatID p_framebuffer_format_id, bool p_wireframe = false, uint32_t p_render_pass = 0) {
#ifdef DEBUG_ENABLED
ERR_FAIL_COND_V_MSG(shader.is_null(), RID(),
"Attempted to use an unused shader variant (shader is null),");
@@ -74,13 +75,13 @@ public:
spin_lock.lock();
RID result;
for (uint32_t i = 0; i < version_count; i++) {
- if (versions[i].vertex_id == p_vertex_format_id && versions[i].framebuffer_id == p_framebuffer_format_id && versions[i].wireframe == p_wireframe) {
+ if (versions[i].vertex_id == p_vertex_format_id && versions[i].framebuffer_id == p_framebuffer_format_id && versions[i].wireframe == p_wireframe && versions[i].render_pass == p_render_pass) {
result = versions[i].pipeline;
spin_lock.unlock();
return result;
}
}
- result = _generate_version(p_vertex_format_id, p_framebuffer_format_id, p_wireframe);
+ result = _generate_version(p_vertex_format_id, p_framebuffer_format_id, p_wireframe, p_render_pass);
spin_lock.unlock();
return result;
}
diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
index fe6d3be82e..026969c09f 100644
--- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
@@ -408,6 +408,7 @@ void RendererCanvasRenderRD::_render_item(RD::DrawListID p_draw_list, RID p_rend
PushConstant push_constant;
Transform2D base_transform = p_canvas_transform_inverse * p_item->final_transform;
+ Transform2D draw_transform;
_update_transform_2d_to_mat2x3(base_transform, push_constant.world);
Color base_color = p_item->final_modulate;
@@ -731,7 +732,7 @@ void RendererCanvasRenderRD::_render_item(RD::DrawListID p_draw_list, RID p_rend
mesh_instance = m->mesh_instance;
texture = m->texture;
modulate = m->modulate;
- _update_transform_2d_to_mat2x3(base_transform * m->transform, push_constant.world);
+ _update_transform_2d_to_mat2x3(base_transform * draw_transform * m->transform, push_constant.world);
} else if (c->type == Item::Command::TYPE_MULTIMESH) {
const Item::CommandMultiMesh *mm = static_cast<const Item::CommandMultiMesh *>(c);
RID multimesh = mm->multimesh;
@@ -864,10 +865,10 @@ void RendererCanvasRenderRD::_render_item(RD::DrawListID p_draw_list, RID p_rend
for (int j = 0; j < 6; j++) {
push_constant.world[j] = world_backup[j];
}
-
} break;
case Item::Command::TYPE_TRANSFORM: {
const Item::CommandTransform *transform = static_cast<const Item::CommandTransform *>(c);
+ draw_transform = transform->xform;
_update_transform_2d_to_mat2x3(base_transform * transform->xform, push_constant.world);
} break;
diff --git a/servers/rendering/renderer_rd/shaders/canvas.glsl b/servers/rendering/renderer_rd/shaders/canvas.glsl
index 2186bd174b..a443bcdcb8 100644
--- a/servers/rendering/renderer_rd/shaders/canvas.glsl
+++ b/servers/rendering/renderer_rd/shaders/canvas.glsl
@@ -65,7 +65,7 @@ void main() {
#elif defined(USE_ATTRIBUTES)
vec2 vertex = vertex_attrib;
- vec4 color = color_attrib;
+ vec4 color = color_attrib * draw_data.modulation;
vec2 uv = uv_attrib;
uvec4 bones = bone_attrib;
diff --git a/servers/rendering/rendering_device.cpp b/servers/rendering/rendering_device.cpp
index ddf2ee9fe3..c52d97a3de 100644
--- a/servers/rendering/rendering_device.cpp
+++ b/servers/rendering/rendering_device.cpp
@@ -98,7 +98,7 @@ RID RenderingDevice::_texture_create_shared_from_slice(const Ref<RDTextureView>
return texture_create_shared_from_slice(p_view->base, p_with_texture, p_layer, p_mipmap, p_slice_type);
}
-RenderingDevice::FramebufferFormatID RenderingDevice::_framebuffer_format_create(const TypedArray<RDAttachmentFormat> &p_attachments) {
+RenderingDevice::FramebufferFormatID RenderingDevice::_framebuffer_format_create(const TypedArray<RDAttachmentFormat> &p_attachments, uint32_t p_view_count) {
Vector<AttachmentFormat> attachments;
attachments.resize(p_attachments.size());
@@ -107,12 +107,43 @@ RenderingDevice::FramebufferFormatID RenderingDevice::_framebuffer_format_create
ERR_FAIL_COND_V(af.is_null(), INVALID_FORMAT_ID);
attachments.write[i] = af->base;
}
- return framebuffer_format_create(attachments);
+ return framebuffer_format_create(attachments, p_view_count);
}
-RID RenderingDevice::_framebuffer_create(const Array &p_textures, FramebufferFormatID p_format_check) {
+RenderingDevice::FramebufferFormatID RenderingDevice::_framebuffer_format_create_multipass(const TypedArray<RDAttachmentFormat> &p_attachments, const TypedArray<RDFramebufferPass> &p_passes, uint32_t p_view_count) {
+ Vector<AttachmentFormat> attachments;
+ attachments.resize(p_attachments.size());
+
+ for (int i = 0; i < p_attachments.size(); i++) {
+ Ref<RDAttachmentFormat> af = p_attachments[i];
+ ERR_FAIL_COND_V(af.is_null(), INVALID_FORMAT_ID);
+ attachments.write[i] = af->base;
+ }
+
+ Vector<FramebufferPass> passes;
+ for (int i = 0; i < p_passes.size(); i++) {
+ Ref<RDFramebufferPass> pass = p_passes[i];
+ ERR_CONTINUE(pass.is_null());
+ passes.push_back(pass->base);
+ }
+
+ return framebuffer_format_create_multipass(attachments, passes, p_view_count);
+}
+
+RID RenderingDevice::_framebuffer_create(const TypedArray<RID> &p_textures, FramebufferFormatID p_format_check, uint32_t p_view_count) {
Vector<RID> textures = Variant(p_textures);
- return framebuffer_create(textures, p_format_check);
+ return framebuffer_create(textures, p_format_check, p_view_count);
+}
+
+RID RenderingDevice::_framebuffer_create_multipass(const TypedArray<RID> &p_textures, const TypedArray<RDFramebufferPass> &p_passes, FramebufferFormatID p_format_check, uint32_t p_view_count) {
+ Vector<RID> textures = Variant(p_textures);
+ Vector<FramebufferPass> passes;
+ for (int i = 0; i < p_passes.size(); i++) {
+ Ref<RDFramebufferPass> pass = p_passes[i];
+ ERR_CONTINUE(pass.is_null());
+ passes.push_back(pass->base);
+ }
+ return framebuffer_create_multipass(textures, passes, p_format_check, p_view_count);
}
RID RenderingDevice::_sampler_create(const Ref<RDSamplerState> &p_state) {
@@ -190,7 +221,7 @@ Error RenderingDevice::_buffer_update(RID p_buffer, uint32_t p_offset, uint32_t
return buffer_update(p_buffer, p_offset, p_size, p_data.ptr(), p_post_barrier);
}
-RID RenderingDevice::_render_pipeline_create(RID p_shader, FramebufferFormatID p_framebuffer_format, VertexFormatID p_vertex_format, RenderPrimitive p_render_primitive, const Ref<RDPipelineRasterizationState> &p_rasterization_state, const Ref<RDPipelineMultisampleState> &p_multisample_state, const Ref<RDPipelineDepthStencilState> &p_depth_stencil_state, const Ref<RDPipelineColorBlendState> &p_blend_state, int p_dynamic_state_flags) {
+RID RenderingDevice::_render_pipeline_create(RID p_shader, FramebufferFormatID p_framebuffer_format, VertexFormatID p_vertex_format, RenderPrimitive p_render_primitive, const Ref<RDPipelineRasterizationState> &p_rasterization_state, const Ref<RDPipelineMultisampleState> &p_multisample_state, const Ref<RDPipelineDepthStencilState> &p_depth_stencil_state, const Ref<RDPipelineColorBlendState> &p_blend_state, int p_dynamic_state_flags, uint32_t p_for_render_pass) {
PipelineRasterizationState rasterization_state;
if (p_rasterization_state.is_valid()) {
rasterization_state = p_rasterization_state->base;
@@ -221,7 +252,7 @@ RID RenderingDevice::_render_pipeline_create(RID p_shader, FramebufferFormatID p
}
}
- return render_pipeline_create(p_shader, p_framebuffer_format, p_vertex_format, p_render_primitive, rasterization_state, multisample_state, depth_stencil_state, color_blend_state, p_dynamic_state_flags);
+ return render_pipeline_create(p_shader, p_framebuffer_format, p_vertex_format, p_render_primitive, rasterization_state, multisample_state, depth_stencil_state, color_blend_state, p_dynamic_state_flags, p_for_render_pass);
}
Vector<int64_t> RenderingDevice::_draw_list_begin_split(RID p_framebuffer, uint32_t p_splits, InitialAction p_initial_color_action, FinalAction p_final_color_action, InitialAction p_initial_depth_action, FinalAction p_final_depth_action, const Vector<Color> &p_clear_color_values, float p_clear_depth, uint32_t p_clear_stencil, const Rect2 &p_region, const TypedArray<RID> &p_storage_textures) {
@@ -242,6 +273,22 @@ Vector<int64_t> RenderingDevice::_draw_list_begin_split(RID p_framebuffer, uint3
return split_ids;
}
+Vector<int64_t> RenderingDevice::_draw_list_switch_to_next_pass_split(uint32_t p_splits) {
+ Vector<DrawListID> splits;
+ splits.resize(p_splits);
+
+ Error err = draw_list_switch_to_next_pass_split(p_splits, splits.ptrw());
+ ERR_FAIL_COND_V(err != OK, Vector<int64_t>());
+
+ Vector<int64_t> split_ids;
+ split_ids.resize(splits.size());
+ for (int i = 0; i < splits.size(); i++) {
+ split_ids.write[i] = splits[i];
+ }
+
+ return split_ids;
+}
+
void RenderingDevice::_draw_list_set_push_constant(DrawListID p_list, const Vector<uint8_t> &p_data, uint32_t p_data_size) {
ERR_FAIL_COND((uint32_t)p_data.size() > p_data_size);
draw_list_set_push_constant(p_list, p_data.ptr(), p_data_size);
@@ -269,10 +316,12 @@ void RenderingDevice::_bind_methods() {
ClassDB::bind_method(D_METHOD("texture_clear", "texture", "color", "base_mipmap", "mipmap_count", "base_layer", "layer_count", "post_barrier"), &RenderingDevice::texture_clear, DEFVAL(BARRIER_MASK_ALL));
ClassDB::bind_method(D_METHOD("texture_resolve_multisample", "from_texture", "to_texture", "post_barrier"), &RenderingDevice::texture_resolve_multisample, DEFVAL(BARRIER_MASK_ALL));
- ClassDB::bind_method(D_METHOD("framebuffer_format_create", "attachments"), &RenderingDevice::_framebuffer_format_create);
+ ClassDB::bind_method(D_METHOD("framebuffer_format_create", "attachments", "view_count"), &RenderingDevice::_framebuffer_format_create, DEFVAL(1));
+ ClassDB::bind_method(D_METHOD("framebuffer_format_create_multipass", "attachments", "passes", "view_count"), &RenderingDevice::_framebuffer_format_create_multipass, DEFVAL(1));
ClassDB::bind_method(D_METHOD("framebuffer_format_create_empty", "samples"), &RenderingDevice::framebuffer_format_create_empty, DEFVAL(TEXTURE_SAMPLES_1));
- ClassDB::bind_method(D_METHOD("framebuffer_format_get_texture_samples", "format"), &RenderingDevice::framebuffer_format_get_texture_samples);
- ClassDB::bind_method(D_METHOD("framebuffer_create", "textures", "validate_with_format"), &RenderingDevice::_framebuffer_create, DEFVAL(INVALID_FORMAT_ID));
+ ClassDB::bind_method(D_METHOD("framebuffer_format_get_texture_samples", "format", "render_pass"), &RenderingDevice::framebuffer_format_get_texture_samples, DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("framebuffer_create", "textures", "validate_with_format", "view_count"), &RenderingDevice::_framebuffer_create, DEFVAL(INVALID_FORMAT_ID), DEFVAL(1));
+ ClassDB::bind_method(D_METHOD("framebuffer_create_multipass", "textures", "passes", "validate_with_format", "view_count"), &RenderingDevice::_framebuffer_create_multipass, DEFVAL(INVALID_FORMAT_ID), DEFVAL(1));
ClassDB::bind_method(D_METHOD("framebuffer_create_empty", "size", "samples", "validate_with_format"), &RenderingDevice::framebuffer_create_empty, DEFVAL(TEXTURE_SAMPLES_1), DEFVAL(INVALID_FORMAT_ID));
ClassDB::bind_method(D_METHOD("framebuffer_get_format", "framebuffer"), &RenderingDevice::framebuffer_get_format);
@@ -299,7 +348,7 @@ void RenderingDevice::_bind_methods() {
ClassDB::bind_method(D_METHOD("buffer_clear", "buffer", "offset", "size_bytes", "post_barrier"), &RenderingDevice::buffer_clear, DEFVAL(BARRIER_MASK_ALL));
ClassDB::bind_method(D_METHOD("buffer_get_data", "buffer"), &RenderingDevice::buffer_get_data);
- ClassDB::bind_method(D_METHOD("render_pipeline_create", "shader", "framebuffer_format", "vertex_format", "primitive", "rasterization_state", "multisample_state", "stencil_state", "color_blend_state", "dynamic_state_flags"), &RenderingDevice::_render_pipeline_create, DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("render_pipeline_create", "shader", "framebuffer_format", "vertex_format", "primitive", "rasterization_state", "multisample_state", "stencil_state", "color_blend_state", "dynamic_state_flags", "for_render_pass"), &RenderingDevice::_render_pipeline_create, DEFVAL(0), DEFVAL(0));
ClassDB::bind_method(D_METHOD("render_pipeline_is_valid", "render_pipeline"), &RenderingDevice::render_pipeline_is_valid);
ClassDB::bind_method(D_METHOD("compute_pipeline_create", "shader"), &RenderingDevice::compute_pipeline_create);
@@ -325,6 +374,9 @@ void RenderingDevice::_bind_methods() {
ClassDB::bind_method(D_METHOD("draw_list_enable_scissor", "draw_list", "rect"), &RenderingDevice::draw_list_enable_scissor, DEFVAL(Rect2i()));
ClassDB::bind_method(D_METHOD("draw_list_disable_scissor", "draw_list"), &RenderingDevice::draw_list_disable_scissor);
+ ClassDB::bind_method(D_METHOD("draw_list_switch_to_next_pass"), &RenderingDevice::draw_list_switch_to_next_pass);
+ ClassDB::bind_method(D_METHOD("draw_list_switch_to_next_pass_split", "splits"), &RenderingDevice::_draw_list_switch_to_next_pass_split);
+
ClassDB::bind_method(D_METHOD("draw_list_end", "post_barrier"), &RenderingDevice::draw_list_end, DEFVAL(BARRIER_MASK_ALL));
ClassDB::bind_method(D_METHOD("compute_list_begin", "allow_draw_overlap"), &RenderingDevice::compute_list_begin, DEFVAL(false));
diff --git a/servers/rendering/rendering_device.h b/servers/rendering/rendering_device.h
index c13dc01dd7..be7e127491 100644
--- a/servers/rendering/rendering_device.h
+++ b/servers/rendering/rendering_device.h
@@ -47,6 +47,7 @@ class RDPipelineRasterizationState;
class RDPipelineMultisampleState;
class RDPipelineDepthStencilState;
class RDPipelineColorBlendState;
+class RDFramebufferPass;
class RenderingDevice : public Object {
GDCLASS(RenderingDevice, Object)
@@ -517,10 +518,23 @@ public:
// This ID is warranted to be unique for the same formats, does not need to be freed
virtual FramebufferFormatID framebuffer_format_create(const Vector<AttachmentFormat> &p_format, uint32_t p_view_count = 1) = 0;
+ struct FramebufferPass {
+ enum {
+ ATTACHMENT_UNUSED = -1
+ };
+ Vector<int32_t> color_attachments;
+ Vector<int32_t> input_attachments;
+ Vector<int32_t> resolve_attachments;
+ Vector<int32_t> preserve_attachments;
+ int32_t depth_attachment = ATTACHMENT_UNUSED;
+ };
+
+ virtual FramebufferFormatID framebuffer_format_create_multipass(const Vector<AttachmentFormat> &p_attachments, Vector<FramebufferPass> &p_passes, uint32_t p_view_count = 1) = 0;
virtual FramebufferFormatID framebuffer_format_create_empty(TextureSamples p_samples = TEXTURE_SAMPLES_1) = 0;
- virtual TextureSamples framebuffer_format_get_texture_samples(FramebufferFormatID p_format) = 0;
+ virtual TextureSamples framebuffer_format_get_texture_samples(FramebufferFormatID p_format, uint32_t p_pass = 0) = 0;
virtual RID framebuffer_create(const Vector<RID> &p_texture_attachments, FramebufferFormatID p_format_check = INVALID_ID, uint32_t p_view_count = 1) = 0;
+ virtual RID framebuffer_create_multipass(const Vector<RID> &p_texture_attachments, Vector<FramebufferPass> &p_passes, FramebufferFormatID p_format_check = INVALID_ID, uint32_t p_view_count = 1) = 0;
virtual RID framebuffer_create_empty(const Size2i &p_size, TextureSamples p_samples = TEXTURE_SAMPLES_1, FramebufferFormatID p_format_check = INVALID_ID) = 0;
virtual FramebufferFormatID framebuffer_get_format(RID p_framebuffer) = 0;
@@ -962,7 +976,7 @@ public:
};
virtual bool render_pipeline_is_valid(RID p_pipeline) = 0;
- virtual RID render_pipeline_create(RID p_shader, FramebufferFormatID p_framebuffer_format, VertexFormatID p_vertex_format, RenderPrimitive p_render_primitive, const PipelineRasterizationState &p_rasterization_state, const PipelineMultisampleState &p_multisample_state, const PipelineDepthStencilState &p_depth_stencil_state, const PipelineColorBlendState &p_blend_state, int p_dynamic_state_flags = 0) = 0;
+ virtual RID render_pipeline_create(RID p_shader, FramebufferFormatID p_framebuffer_format, VertexFormatID p_vertex_format, RenderPrimitive p_render_primitive, const PipelineRasterizationState &p_rasterization_state, const PipelineMultisampleState &p_multisample_state, const PipelineDepthStencilState &p_depth_stencil_state, const PipelineColorBlendState &p_blend_state, int p_dynamic_state_flags = 0, uint32_t p_for_render_pass = 0) = 0;
/**************************/
/**** COMPUTE PIPELINE ****/
@@ -1018,6 +1032,9 @@ public:
virtual void draw_list_enable_scissor(DrawListID p_list, const Rect2 &p_rect) = 0;
virtual void draw_list_disable_scissor(DrawListID p_list) = 0;
+ virtual DrawListID draw_list_switch_to_next_pass() = 0;
+ virtual Error draw_list_switch_to_next_pass_split(uint32_t p_splits, DrawListID *r_split_ids) = 0;
+
virtual void draw_list_end(uint32_t p_post_barrier = BARRIER_MASK_ALL) = 0;
/***********************/
@@ -1134,8 +1151,10 @@ protected:
RID _texture_create_shared(const Ref<RDTextureView> &p_view, RID p_with_texture);
RID _texture_create_shared_from_slice(const Ref<RDTextureView> &p_view, RID p_with_texture, uint32_t p_layer, uint32_t p_mipmap, TextureSliceType p_slice_type = TEXTURE_SLICE_2D);
- FramebufferFormatID _framebuffer_format_create(const TypedArray<RDAttachmentFormat> &p_attachments);
- RID _framebuffer_create(const Array &p_textures, FramebufferFormatID p_format_check = INVALID_ID);
+ FramebufferFormatID _framebuffer_format_create(const TypedArray<RDAttachmentFormat> &p_attachments, uint32_t p_view_count);
+ FramebufferFormatID _framebuffer_format_create_multipass(const TypedArray<RDAttachmentFormat> &p_attachments, const TypedArray<RDFramebufferPass> &p_passes, uint32_t p_view_count);
+ RID _framebuffer_create(const TypedArray<RID> &p_textures, FramebufferFormatID p_format_check = INVALID_ID, uint32_t p_view_count = 1);
+ RID _framebuffer_create_multipass(const TypedArray<RID> &p_textures, const TypedArray<RDFramebufferPass> &p_passes, FramebufferFormatID p_format_check = INVALID_ID, uint32_t p_view_count = 1);
RID _sampler_create(const Ref<RDSamplerState> &p_state);
VertexFormatID _vertex_format_create(const TypedArray<RDVertexAttribute> &p_vertex_formats);
RID _vertex_array_create(uint32_t p_vertex_count, VertexFormatID p_vertex_format, const TypedArray<RID> &p_src_buffers);
@@ -1146,11 +1165,12 @@ protected:
Error _buffer_update(RID p_buffer, uint32_t p_offset, uint32_t p_size, const Vector<uint8_t> &p_data, uint32_t p_post_barrier = BARRIER_MASK_ALL);
- RID _render_pipeline_create(RID p_shader, FramebufferFormatID p_framebuffer_format, VertexFormatID p_vertex_format, RenderPrimitive p_render_primitive, const Ref<RDPipelineRasterizationState> &p_rasterization_state, const Ref<RDPipelineMultisampleState> &p_multisample_state, const Ref<RDPipelineDepthStencilState> &p_depth_stencil_state, const Ref<RDPipelineColorBlendState> &p_blend_state, int p_dynamic_state_flags = 0);
+ RID _render_pipeline_create(RID p_shader, FramebufferFormatID p_framebuffer_format, VertexFormatID p_vertex_format, RenderPrimitive p_render_primitive, const Ref<RDPipelineRasterizationState> &p_rasterization_state, const Ref<RDPipelineMultisampleState> &p_multisample_state, const Ref<RDPipelineDepthStencilState> &p_depth_stencil_state, const Ref<RDPipelineColorBlendState> &p_blend_state, int p_dynamic_state_flags = 0, uint32_t p_for_render_pass = 0);
Vector<int64_t> _draw_list_begin_split(RID p_framebuffer, uint32_t p_splits, InitialAction p_initial_color_action, FinalAction p_final_color_action, InitialAction p_initial_depth_action, FinalAction p_final_depth_action, const Vector<Color> &p_clear_color_values = Vector<Color>(), float p_clear_depth = 1.0, uint32_t p_clear_stencil = 0, const Rect2 &p_region = Rect2(), const TypedArray<RID> &p_storage_textures = TypedArray<RID>());
void _draw_list_set_push_constant(DrawListID p_list, const Vector<uint8_t> &p_data, uint32_t p_data_size);
void _compute_list_set_push_constant(ComputeListID p_list, const Vector<uint8_t> &p_data, uint32_t p_data_size);
+ Vector<int64_t> _draw_list_switch_to_next_pass_split(uint32_t p_splits);
};
VARIANT_ENUM_CAST(RenderingDevice::ShaderStage)
diff --git a/servers/rendering/rendering_device_binds.h b/servers/rendering/rendering_device_binds.h
index 912674e309..dc59568ce9 100644
--- a/servers/rendering/rendering_device_binds.h
+++ b/servers/rendering/rendering_device_binds.h
@@ -128,6 +128,34 @@ protected:
}
};
+class RDFramebufferPass : public RefCounted {
+ GDCLASS(RDFramebufferPass, RefCounted)
+ friend class RenderingDevice;
+
+ RD::FramebufferPass base;
+
+public:
+ RD_SETGET(PackedInt32Array, color_attachments)
+ RD_SETGET(PackedInt32Array, input_attachments)
+ RD_SETGET(PackedInt32Array, resolve_attachments)
+ RD_SETGET(PackedInt32Array, preserve_attachments)
+ RD_SETGET(int32_t, depth_attachment)
+protected:
+ enum {
+ ATTACHMENT_UNUSED = -1
+ };
+
+ static void _bind_methods() {
+ RD_BIND(Variant::PACKED_INT32_ARRAY, RDFramebufferPass, color_attachments);
+ RD_BIND(Variant::PACKED_INT32_ARRAY, RDFramebufferPass, input_attachments);
+ RD_BIND(Variant::PACKED_INT32_ARRAY, RDFramebufferPass, resolve_attachments);
+ RD_BIND(Variant::PACKED_INT32_ARRAY, RDFramebufferPass, preserve_attachments);
+ RD_BIND(Variant::INT, RDFramebufferPass, depth_attachment);
+
+ BIND_CONSTANT(ATTACHMENT_UNUSED);
+ }
+};
+
class RDSamplerState : public RefCounted {
GDCLASS(RDSamplerState, RefCounted)
friend class RenderingDevice;