summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--SConstruct3
-rw-r--r--core/core_bind.cpp16
-rw-r--r--core/core_bind.h4
-rw-r--r--core/extension/gdextension.cpp10
-rw-r--r--core/extension/gdextension.h1
-rw-r--r--core/extension/gdextension_compat_hashes.cpp73
-rw-r--r--core/extension/gdextension_interface.h1
-rw-r--r--core/math/expression.cpp10
-rw-r--r--core/object/class_db.cpp19
-rw-r--r--core/object/class_db.h4
-rw-r--r--core/object/object.cpp1
-rw-r--r--core/object/object.h4
-rw-r--r--core/object/script_language.cpp17
-rw-r--r--core/object/script_language.h2
-rw-r--r--core/object/worker_thread_pool.cpp20
-rw-r--r--core/object/worker_thread_pool.h2
-rw-r--r--core/register_core_types.cpp8
-rw-r--r--core/string/string_name.cpp5
-rw-r--r--core/string/string_name.h3
-rw-r--r--core/string/translation.compat.inc41
-rw-r--r--core/string/translation.cpp1
-rw-r--r--core/string/translation.h4
-rw-r--r--core/string/translation_server.compat.inc38
-rw-r--r--core/string/translation_server.cpp1
-rw-r--r--core/string/translation_server.h4
-rw-r--r--core/variant/array.cpp10
-rw-r--r--core/variant/array.h2
-rw-r--r--doc/classes/CompositorEffect.xml11
-rw-r--r--doc/classes/EditorContextMenuPlugin.xml42
-rw-r--r--doc/classes/EditorPlugin.xml23
-rw-r--r--doc/classes/LineEdit.xml21
-rw-r--r--doc/classes/ProjectSettings.xml3
-rw-r--r--doc/classes/ScriptEditor.xml8
-rw-r--r--drivers/metal/metal_objects.mm6
-rw-r--r--drivers/windows/file_access_windows.cpp50
-rw-r--r--editor/animation_track_editor.cpp25
-rw-r--r--editor/connections_dialog.cpp7
-rw-r--r--editor/editor_data.cpp132
-rw-r--r--editor/editor_data.h29
-rw-r--r--editor/editor_help.cpp5
-rw-r--r--editor/editor_help_search.cpp3
-rw-r--r--editor/editor_inspector.cpp5
-rw-r--r--editor/editor_interface.cpp5
-rw-r--r--editor/editor_main_screen.cpp291
-rw-r--r--editor/editor_main_screen.h (renamed from core/object/object.compat.inc)67
-rw-r--r--editor/editor_node.cpp340
-rw-r--r--editor/editor_node.h51
-rw-r--r--editor/editor_paths.cpp20
-rw-r--r--editor/editor_properties.cpp1
-rw-r--r--editor/export/editor_export_platform.cpp2
-rw-r--r--editor/filesystem_dock.cpp38
-rw-r--r--editor/inspector_dock.cpp3
-rw-r--r--editor/plugins/asset_library_editor_plugin.cpp3
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp3
-rw-r--r--editor/plugins/cpu_particles_3d_editor_plugin.cpp2
-rw-r--r--editor/plugins/editor_context_menu_plugin.cpp134
-rw-r--r--editor/plugins/editor_context_menu_plugin.h55
-rw-r--r--editor/plugins/editor_plugin.cpp18
-rw-r--r--editor/plugins/editor_plugin.h14
-rw-r--r--editor/plugins/editor_preview_plugins.cpp42
-rw-r--r--editor/plugins/gpu_particles_3d_editor_plugin.cpp2
-rw-r--r--editor/plugins/mesh_instance_3d_editor_plugin.cpp2
-rw-r--r--editor/plugins/mesh_library_editor_plugin.cpp2
-rw-r--r--editor/plugins/multimesh_editor_plugin.cpp2
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp3
-rw-r--r--editor/plugins/script_editor_plugin.cpp25
-rw-r--r--editor/plugins/skeleton_2d_editor_plugin.cpp2
-rw-r--r--editor/plugins/sprite_2d_editor_plugin.cpp2
-rw-r--r--editor/plugins/tiles/tile_map_layer_editor.cpp6
-rw-r--r--editor/project_manager.cpp43
-rw-r--r--editor/project_manager.h6
-rw-r--r--editor/project_manager/project_dialog.cpp12
-rw-r--r--editor/scene_tree_dock.cpp32
-rw-r--r--editor/scene_tree_dock.h1
-rw-r--r--editor/themes/editor_theme_manager.cpp15
-rw-r--r--main/main.cpp18
-rw-r--r--methods.py100
-rw-r--r--modules/basis_universal/image_compress_basisu.cpp89
-rw-r--r--modules/basis_universal/image_compress_basisu.h1
-rw-r--r--modules/csg/editor/csg_gizmos.cpp2
-rw-r--r--modules/fbx/fbx_document.cpp8
-rw-r--r--modules/gdscript/gdscript_editor.cpp4
-rw-r--r--modules/gdscript/gdscript_tokenizer_buffer.cpp1
-rw-r--r--modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_unique.cfg9
-rw-r--r--modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_unique.gd5
-rw-r--r--modules/gltf/gltf_document.cpp8
-rw-r--r--modules/gridmap/editor/grid_map_editor_plugin.cpp3
-rw-r--r--modules/mono/editor/editor_internal_calls.cpp3
-rw-r--r--modules/navigation/editor/navigation_mesh_editor_plugin.cpp2
-rw-r--r--platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotEditor.kt6
-rw-r--r--platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotXRGame.kt13
-rw-r--r--platform/android/java_godot_lib_jni.cpp11
-rw-r--r--platform/linuxbsd/wayland/wayland_thread.cpp2
-rw-r--r--scene/3d/path_3d.cpp52
-rw-r--r--scene/3d/path_3d.h3
-rw-r--r--scene/animation/animation_player.compat.inc10
-rw-r--r--scene/gui/control.compat.inc48
-rw-r--r--scene/gui/control.cpp1
-rw-r--r--scene/gui/control.h4
-rw-r--r--scene/gui/line_edit.cpp539
-rw-r--r--scene/gui/line_edit.h6
-rw-r--r--scene/gui/scroll_container.cpp28
-rw-r--r--scene/gui/spin_box.cpp51
-rw-r--r--scene/gui/spin_box.h3
-rw-r--r--scene/main/window.compat.inc48
-rw-r--r--scene/main/window.cpp1
-rw-r--r--scene/main/window.h4
-rw-r--r--scene/resources/3d/importer_mesh.cpp12
-rw-r--r--scene/resources/3d/importer_mesh.h2
-rw-r--r--servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp1864
-rw-r--r--servers/rendering/renderer_rd/renderer_canvas_render_rd.h201
-rw-r--r--servers/rendering/renderer_rd/shaders/canvas.glsl92
-rw-r--r--servers/rendering/renderer_rd/shaders/canvas_uniforms_inc.glsl27
-rw-r--r--servers/rendering/shader_compiler.cpp30
-rw-r--r--servers/rendering/shader_compiler.h2
-rw-r--r--servers/rendering/shader_language.cpp123
-rw-r--r--servers/rendering/shader_language.h3
-rw-r--r--servers/rendering_server.cpp1
-rw-r--r--servers/xr/xr_interface.h2
-rw-r--r--tests/core/math/test_expression.h53
-rw-r--r--tests/scene/test_path_follow_3d.h31
121 files changed, 3128 insertions, 2316 deletions
diff --git a/SConstruct b/SConstruct
index 178f2d86a2..b1819a1dab 100644
--- a/SConstruct
+++ b/SConstruct
@@ -1092,7 +1092,6 @@ if "check_c_headers" in env:
env.AppendUnique(CPPDEFINES=[headers[header]])
-# FIXME: This method mixes both cosmetic progress stuff and cache handling...
methods.show_progress(env)
# TODO: replace this with `env.Dump(format="json")`
# once we start requiring SCons 4.0 as min version.
@@ -1124,3 +1123,5 @@ def purge_flaky_files():
atexit.register(purge_flaky_files)
+
+methods.clean_cache(env)
diff --git a/core/core_bind.cpp b/core/core_bind.cpp
index 750598ab20..b27981d56b 100644
--- a/core/core_bind.cpp
+++ b/core/core_bind.cpp
@@ -57,8 +57,11 @@ Error ResourceLoader::load_threaded_request(const String &p_path, const String &
ResourceLoader::ThreadLoadStatus ResourceLoader::load_threaded_get_status(const String &p_path, Array r_progress) {
float progress = 0;
::ResourceLoader::ThreadLoadStatus tls = ::ResourceLoader::load_threaded_get_status(p_path, &progress);
- r_progress.resize(1);
- r_progress[0] = progress;
+ // Default array should never be modified, it causes the hash of the method to change.
+ if (!ClassDB::is_default_array_arg(r_progress)) {
+ r_progress.resize(1);
+ r_progress[0] = progress;
+ }
return (ThreadLoadStatus)tls;
}
@@ -131,7 +134,7 @@ ResourceUID::ID ResourceLoader::get_resource_uid(const String &p_path) {
void ResourceLoader::_bind_methods() {
ClassDB::bind_method(D_METHOD("load_threaded_request", "path", "type_hint", "use_sub_threads", "cache_mode"), &ResourceLoader::load_threaded_request, DEFVAL(""), DEFVAL(false), DEFVAL(CACHE_MODE_REUSE));
- ClassDB::bind_method(D_METHOD("load_threaded_get_status", "path", "progress"), &ResourceLoader::load_threaded_get_status, DEFVAL(Array()));
+ ClassDB::bind_method(D_METHOD("load_threaded_get_status", "path", "progress"), &ResourceLoader::load_threaded_get_status, DEFVAL_ARRAY);
ClassDB::bind_method(D_METHOD("load_threaded_get", "path"), &ResourceLoader::load_threaded_get);
ClassDB::bind_method(D_METHOD("load", "path", "type_hint", "cache_mode"), &ResourceLoader::load, DEFVAL(""), DEFVAL(CACHE_MODE_REUSE));
@@ -307,7 +310,10 @@ int OS::execute(const String &p_path, const Vector<String> &p_arguments, Array r
String pipe;
int exitcode = 0;
Error err = ::OS::get_singleton()->execute(p_path, args, &pipe, &exitcode, p_read_stderr, nullptr, p_open_console);
- r_output.push_back(pipe);
+ // Default array should never be modified, it causes the hash of the method to change.
+ if (!ClassDB::is_default_array_arg(r_output)) {
+ r_output.push_back(pipe);
+ }
if (err != OK) {
return -1;
}
@@ -618,7 +624,7 @@ void OS::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_system_font_path_for_text", "font_name", "text", "locale", "script", "weight", "stretch", "italic"), &OS::get_system_font_path_for_text, DEFVAL(String()), DEFVAL(String()), DEFVAL(400), DEFVAL(100), DEFVAL(false));
ClassDB::bind_method(D_METHOD("get_executable_path"), &OS::get_executable_path);
ClassDB::bind_method(D_METHOD("read_string_from_stdin"), &OS::read_string_from_stdin);
- ClassDB::bind_method(D_METHOD("execute", "path", "arguments", "output", "read_stderr", "open_console"), &OS::execute, DEFVAL(Array()), DEFVAL(false), DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("execute", "path", "arguments", "output", "read_stderr", "open_console"), &OS::execute, DEFVAL_ARRAY, DEFVAL(false), DEFVAL(false));
ClassDB::bind_method(D_METHOD("execute_with_pipe", "path", "arguments", "blocking"), &OS::execute_with_pipe, DEFVAL(true));
ClassDB::bind_method(D_METHOD("create_process", "path", "arguments", "open_console"), &OS::create_process, DEFVAL(false));
ClassDB::bind_method(D_METHOD("create_instance", "arguments"), &OS::create_instance);
diff --git a/core/core_bind.h b/core/core_bind.h
index e4ba0e8f56..7e2686c848 100644
--- a/core/core_bind.h
+++ b/core/core_bind.h
@@ -73,7 +73,7 @@ public:
static ResourceLoader *get_singleton() { return singleton; }
Error load_threaded_request(const String &p_path, const String &p_type_hint = "", bool p_use_sub_threads = false, CacheMode p_cache_mode = CACHE_MODE_REUSE);
- ThreadLoadStatus load_threaded_get_status(const String &p_path, Array r_progress = Array());
+ ThreadLoadStatus load_threaded_get_status(const String &p_path, Array r_progress = ClassDB::default_array_arg);
Ref<Resource> load_threaded_get(const String &p_path);
Ref<Resource> load(const String &p_path, const String &p_type_hint = "", CacheMode p_cache_mode = CACHE_MODE_REUSE);
@@ -166,7 +166,7 @@ public:
Vector<String> get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale = String(), const String &p_script = String(), int p_weight = 400, int p_stretch = 100, bool p_italic = false) const;
String get_executable_path() const;
String read_string_from_stdin();
- int execute(const String &p_path, const Vector<String> &p_arguments, Array r_output = Array(), bool p_read_stderr = false, bool p_open_console = false);
+ int execute(const String &p_path, const Vector<String> &p_arguments, Array r_output = ClassDB::default_array_arg, bool p_read_stderr = false, bool p_open_console = false);
Dictionary execute_with_pipe(const String &p_path, const Vector<String> &p_arguments, bool p_blocking = true);
int create_process(const String &p_path, const Vector<String> &p_arguments, bool p_open_console = false);
int create_instance(const Vector<String> &p_arguments);
diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp
index d4b50facb2..7cba5cb161 100644
--- a/core/extension/gdextension.cpp
+++ b/core/extension/gdextension.cpp
@@ -262,7 +262,6 @@ void GDExtension::_register_extension_class(GDExtensionClassLibraryPtr p_library
p_extension_funcs->get_virtual_func, // GDExtensionClassGetVirtual get_virtual_func;
nullptr, // GDExtensionClassGetVirtualCallData get_virtual_call_data_func;
nullptr, // GDExtensionClassCallVirtualWithData call_virtual_func;
- p_extension_funcs->get_rid_func, // GDExtensionClassGetRID get_rid;
p_extension_funcs->class_userdata, // void *class_userdata;
};
@@ -270,6 +269,7 @@ void GDExtension::_register_extension_class(GDExtensionClassLibraryPtr p_library
p_extension_funcs->notification_func, // GDExtensionClassNotification notification_func;
p_extension_funcs->free_property_list_func, // GDExtensionClassFreePropertyList free_property_list_func;
p_extension_funcs->create_instance_func, // GDExtensionClassCreateInstance create_instance_func;
+ p_extension_funcs->get_rid_func, // GDExtensionClassGetRID get_rid;
};
_register_extension_class_internal(p_library, p_class_name, p_parent_class_name, &class_info4, &legacy);
}
@@ -297,7 +297,6 @@ void GDExtension::_register_extension_class2(GDExtensionClassLibraryPtr p_librar
p_extension_funcs->get_virtual_func, // GDExtensionClassGetVirtual get_virtual_func;
p_extension_funcs->get_virtual_call_data_func, // GDExtensionClassGetVirtualCallData get_virtual_call_data_func;
p_extension_funcs->call_virtual_with_data_func, // GDExtensionClassCallVirtualWithData call_virtual_func;
- p_extension_funcs->get_rid_func, // GDExtensionClassGetRID get_rid;
p_extension_funcs->class_userdata, // void *class_userdata;
};
@@ -305,6 +304,7 @@ void GDExtension::_register_extension_class2(GDExtensionClassLibraryPtr p_librar
nullptr, // GDExtensionClassNotification notification_func;
p_extension_funcs->free_property_list_func, // GDExtensionClassFreePropertyList free_property_list_func;
p_extension_funcs->create_instance_func, // GDExtensionClassCreateInstance create_instance_func;
+ p_extension_funcs->get_rid_func, // GDExtensionClassGetRID get_rid;
};
_register_extension_class_internal(p_library, p_class_name, p_parent_class_name, &class_info4, &legacy);
}
@@ -332,7 +332,6 @@ void GDExtension::_register_extension_class3(GDExtensionClassLibraryPtr p_librar
p_extension_funcs->get_virtual_func, // GDExtensionClassGetVirtual get_virtual_func;
p_extension_funcs->get_virtual_call_data_func, // GDExtensionClassGetVirtualCallData get_virtual_call_data_func;
p_extension_funcs->call_virtual_with_data_func, // GDExtensionClassCallVirtualWithData call_virtual_func;
- p_extension_funcs->get_rid_func, // GDExtensionClassGetRID get_rid;
p_extension_funcs->class_userdata, // void *class_userdata;
};
@@ -340,6 +339,7 @@ void GDExtension::_register_extension_class3(GDExtensionClassLibraryPtr p_librar
nullptr, // GDExtensionClassNotification notification_func;
nullptr, // GDExtensionClassFreePropertyList free_property_list_func;
p_extension_funcs->create_instance_func, // GDExtensionClassCreateInstance2 create_instance_func;
+ p_extension_funcs->get_rid_func, // GDExtensionClassGetRID get_rid;
};
_register_extension_class_internal(p_library, p_class_name, p_parent_class_name, &class_info4, &legacy);
}
@@ -355,7 +355,7 @@ void GDExtension::_register_extension_class_internal(GDExtensionClassLibraryPtr
StringName class_name = *reinterpret_cast<const StringName *>(p_class_name);
StringName parent_class_name = *reinterpret_cast<const StringName *>(p_parent_class_name);
- ERR_FAIL_COND_MSG(!String(class_name).is_valid_ascii_identifier(), "Attempt to register extension class '" + class_name + "', which is not a valid class identifier.");
+ ERR_FAIL_COND_MSG(!String(class_name).is_valid_unicode_identifier(), "Attempt to register extension class '" + 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;
@@ -427,6 +427,7 @@ void GDExtension::_register_extension_class_internal(GDExtensionClassLibraryPtr
extension->gdextension.notification = p_deprecated_funcs->notification_func;
extension->gdextension.free_property_list = p_deprecated_funcs->free_property_list_func;
extension->gdextension.create_instance = p_deprecated_funcs->create_instance_func;
+ extension->gdextension.get_rid = p_deprecated_funcs->get_rid_func;
}
#endif // DISABLE_DEPRECATED
extension->gdextension.notification2 = p_extension_funcs->notification_func;
@@ -440,7 +441,6 @@ void GDExtension::_register_extension_class_internal(GDExtensionClassLibraryPtr
extension->gdextension.get_virtual = p_extension_funcs->get_virtual_func;
extension->gdextension.get_virtual_call_data = p_extension_funcs->get_virtual_call_data_func;
extension->gdextension.call_virtual_with_data = p_extension_funcs->call_virtual_with_data_func;
- extension->gdextension.get_rid = p_extension_funcs->get_rid_func;
extension->gdextension.reloadable = self->reloadable;
#ifdef TOOLS_ENABLED
diff --git a/core/extension/gdextension.h b/core/extension/gdextension.h
index 7bb4294909..706bc7e189 100644
--- a/core/extension/gdextension.h
+++ b/core/extension/gdextension.h
@@ -71,6 +71,7 @@ class GDExtension : public Resource {
GDExtensionClassNotification notification_func = nullptr;
GDExtensionClassFreePropertyList free_property_list_func = nullptr;
GDExtensionClassCreateInstance create_instance_func = nullptr;
+ GDExtensionClassGetRID get_rid_func = nullptr;
#endif // DISABLE_DEPRECATED
};
diff --git a/core/extension/gdextension_compat_hashes.cpp b/core/extension/gdextension_compat_hashes.cpp
index ebbf795070..b07f5b1858 100644
--- a/core/extension/gdextension_compat_hashes.cpp
+++ b/core/extension/gdextension_compat_hashes.cpp
@@ -103,6 +103,14 @@ void GDExtensionCompatHashes::initialize() {
mappings.insert("AcceptDialog", {
{ "add_button", 4158837846, 3328440682 },
});
+ mappings.insert("AnimatedSprite2D", {
+ { "play", 2372066587, 3269405555 },
+ { "play_backwards", 1421762485, 3323268493 },
+ });
+ mappings.insert("AnimatedSprite3D", {
+ { "play", 2372066587, 3269405555 },
+ { "play_backwards", 1421762485, 3323268493 },
+ });
mappings.insert("Animation", {
{ "add_track", 2393815928, 3843682357 },
{ "track_insert_key", 1985425300, 808952278 },
@@ -146,6 +154,12 @@ void GDExtensionCompatHashes::initialize() {
{ "travel", 3683006648, 3823612587 },
{ "start", 3683006648, 3823612587 },
});
+ mappings.insert("AnimationPlayer", {
+ { "play", 3697947785, 3118260607 },
+ { "play", 2221377757, 3118260607 },
+ { "play_backwards", 3890664824, 2787282401 },
+ { "play_with_capture", 3180464118, 1572969103 },
+ });
mappings.insert("ArrayMesh", {
{ "add_surface_from_arrays", 172284304, 1796411378 },
});
@@ -247,13 +261,20 @@ void GDExtensionCompatHashes::initialize() {
});
mappings.insert("DisplayServer", {
{ "global_menu_add_submenu_item", 3806306913, 2828985934 },
- { "global_menu_add_item", 3415468211, 3401266716 },
- { "global_menu_add_check_item", 3415468211, 3401266716 },
- { "global_menu_add_icon_item", 1700867534, 4245856523 },
- { "global_menu_add_icon_check_item", 1700867534, 4245856523 },
- { "global_menu_add_radio_check_item", 3415468211, 3401266716 },
- { "global_menu_add_icon_radio_check_item", 1700867534, 4245856523 },
- { "global_menu_add_multistate_item", 635750054, 3431222859 },
+ { "global_menu_add_item", 3415468211, 3616842746 },
+ { "global_menu_add_item", 3401266716, 3616842746 },
+ { "global_menu_add_check_item", 3415468211, 3616842746 },
+ { "global_menu_add_check_item", 3401266716, 3616842746 },
+ { "global_menu_add_icon_item", 1700867534, 3867083847 },
+ { "global_menu_add_icon_item", 4245856523, 3867083847 },
+ { "global_menu_add_icon_check_item", 1700867534, 3867083847 },
+ { "global_menu_add_icon_check_item", 4245856523, 3867083847 },
+ { "global_menu_add_radio_check_item", 3415468211, 3616842746 },
+ { "global_menu_add_radio_check_item", 3401266716, 3616842746 },
+ { "global_menu_add_icon_radio_check_item", 1700867534, 3867083847 },
+ { "global_menu_add_icon_radio_check_item", 4245856523, 3867083847 },
+ { "global_menu_add_multistate_item", 635750054, 3297554655 },
+ { "global_menu_add_multistate_item", 3431222859, 3297554655 },
{ "global_menu_add_separator", 1041533178, 3214812433 },
{ "tts_speak", 3741216677, 903992738 },
{ "is_touchscreen_available", 4162880507, 3323674545 },
@@ -286,6 +307,12 @@ void GDExtensionCompatHashes::initialize() {
{ "virtual_keyboard_show", 860410478, 3042891259 },
#endif
});
+ mappings.insert("EditorExportPlatform", {
+ { "export_project_files", 425454869, 1063735070 },
+ });
+ mappings.insert("EditorProperty", {
+ { "emit_changed", 3069422438, 1822500399 },
+ });
mappings.insert("ENetConnection", {
{ "create_host_bound", 866250949, 1515002313 },
{ "connect_to_host", 385984708, 2171300490 },
@@ -453,18 +480,35 @@ void GDExtensionCompatHashes::initialize() {
mappings.insert("MultiplayerAPI", {
{ "rpc", 1833408346, 2077486355 },
});
+ mappings.insert("NativeMenu", {
+ { "add_item", 2553375659, 980552939 },
+ { "add_check_item", 2553375659, 980552939 },
+ { "add_icon_item", 2987595282, 1372188274 },
+ { "add_icon_check_item", 2987595282, 1372188274 },
+ { "add_radio_check_item", 2553375659, 980552939 },
+ { "add_icon_radio_check_item", 2987595282, 1372188274 },
+ { "add_multistate_item", 1558592568, 2674635658 },
+ });
mappings.insert("NavigationMeshGenerator", {
- { "parse_source_geometry_data", 3703028813, 685862123 },
- { "bake_from_source_geometry_data", 3669016597, 2469318639 },
+ { "parse_source_geometry_data", 3703028813, 3172802542 },
+ { "parse_source_geometry_data", 685862123, 3172802542 },
+ { "bake_from_source_geometry_data", 3669016597, 1286748856 },
+ { "bake_from_source_geometry_data", 2469318639, 1286748856 },
});
mappings.insert("NavigationServer2D", {
{ "map_get_path", 56240621, 3146466012 },
+ { "parse_source_geometry_data", 1176164995, 1766905497 },
+ { "bake_from_source_geometry_data", 2909414286, 2179660022 },
+ { "bake_from_source_geometry_data_async", 2909414286, 2179660022 },
});
mappings.insert("NavigationServer3D", {
{ "map_get_path", 2121045993, 1187418690 },
- { "parse_source_geometry_data", 3703028813, 685862123 },
- { "bake_from_source_geometry_data", 3669016597, 2469318639 },
- { "bake_from_source_geometry_data_async", 3669016597, 2469318639 },
+ { "parse_source_geometry_data", 3703028813, 3172802542 },
+ { "parse_source_geometry_data", 685862123, 3172802542 },
+ { "bake_from_source_geometry_data", 3669016597, 1286748856 },
+ { "bake_from_source_geometry_data", 2469318639, 1286748856 },
+ { "bake_from_source_geometry_data_async", 3669016597, 1286748856 },
+ { "bake_from_source_geometry_data_async", 2469318639, 1286748856 },
});
mappings.insert("Node", {
{ "add_child", 3070154285, 3863233950 },
@@ -631,6 +675,11 @@ void GDExtensionCompatHashes::initialize() {
mappings.insert("ProjectSettings", {
{ "load_resource_pack", 3001721055, 708980503 },
});
+ mappings.insert("RDShaderFile", {
+ { "bake_from_source_geometry_data_async", 2469318639, 1286748856 },
+ { "set_bytecode", 1558064255, 1526857008 },
+ { "get_spirv", 3340165340, 2689310080 },
+ });
mappings.insert("RegEx", {
{ "search", 4087180739, 3365977994 },
{ "search_all", 3354100289, 849021363 },
diff --git a/core/extension/gdextension_interface.h b/core/extension/gdextension_interface.h
index 988974b5d5..9e3ce25698 100644
--- a/core/extension/gdextension_interface.h
+++ b/core/extension/gdextension_interface.h
@@ -392,7 +392,6 @@ typedef struct {
GDExtensionClassGetVirtualCallData get_virtual_call_data_func;
// Used to call virtual functions when `get_virtual_call_data_func` is not null.
GDExtensionClassCallVirtualWithData call_virtual_with_data_func;
- GDExtensionClassGetRID get_rid_func;
void *class_userdata; // Per-class user data, later accessible in instance bindings.
} GDExtensionClassCreationInfo4;
diff --git a/core/math/expression.cpp b/core/math/expression.cpp
index 636c2c16bf..0692ece1e6 100644
--- a/core/math/expression.cpp
+++ b/core/math/expression.cpp
@@ -30,12 +30,7 @@
#include "expression.h"
-#include "core/io/marshalls.h"
-#include "core/math/math_funcs.h"
#include "core/object/class_db.h"
-#include "core/object/ref_counted.h"
-#include "core/os/os.h"
-#include "core/variant/variant_parser.h"
Error Expression::_get_token(Token &r_token) {
while (true) {
@@ -392,7 +387,6 @@ Error Expression::_get_token(Token &r_token) {
if (is_digit(c)) {
} else if (c == 'e') {
reading = READING_EXP;
-
} else {
reading = READING_DONE;
}
@@ -419,7 +413,9 @@ Error Expression::_get_token(Token &r_token) {
is_first_char = false;
}
- str_ofs--;
+ if (c != 0) {
+ str_ofs--;
+ }
r_token.type = TK_CONSTANT;
diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp
index a65411629f..9826d73a9d 100644
--- a/core/object/class_db.cpp
+++ b/core/object/class_db.cpp
@@ -227,16 +227,12 @@ public:
#endif
bool ClassDB::_is_parent_class(const StringName &p_class, const StringName &p_inherits) {
- if (!classes.has(p_class)) {
- return false;
- }
-
- StringName inherits = p_class;
- while (inherits.operator String().length()) {
- if (inherits == p_inherits) {
+ ClassInfo *c = classes.getptr(p_class);
+ while (c) {
+ if (c->name == p_inherits) {
return true;
}
- inherits = _get_parent_class(inherits);
+ c = c->inherits_ptr;
}
return false;
@@ -2310,4 +2306,11 @@ void ClassDB::cleanup() {
native_structs.clear();
}
+// Array to use in optional parameters on methods and the DEFVAL_ARRAY macro.
+Array ClassDB::default_array_arg = Array::create_read_only();
+
+bool ClassDB::is_default_array_arg(const Array &p_array) {
+ return p_array.is_same_instance(default_array_arg);
+}
+
//
diff --git a/core/object/class_db.h b/core/object/class_db.h
index 620092a6c4..81100d7586 100644
--- a/core/object/class_db.h
+++ b/core/object/class_db.h
@@ -43,6 +43,7 @@
#include <type_traits>
#define DEFVAL(m_defval) (m_defval)
+#define DEFVAL_ARRAY DEFVAL(ClassDB::default_array_arg)
#ifdef DEBUG_METHODS_ENABLED
@@ -181,6 +182,9 @@ public:
};
static HashMap<StringName, NativeStruct> native_structs;
+ static Array default_array_arg;
+ static bool is_default_array_arg(const Array &p_array);
+
private:
// Non-locking variants of get_parent_class and is_parent_class.
static StringName _get_parent_class(const StringName &p_class);
diff --git a/core/object/object.cpp b/core/object/object.cpp
index d6b7d7a7fe..000d5328b4 100644
--- a/core/object/object.cpp
+++ b/core/object/object.cpp
@@ -29,7 +29,6 @@
/**************************************************************************/
#include "object.h"
-#include "object.compat.inc"
#include "core/extension/gdextension_manager.h"
#include "core/io/resource.h"
diff --git a/core/object/object.h b/core/object/object.h
index 19e6fc5d47..6d22f320af 100644
--- a/core/object/object.h
+++ b/core/object/object.h
@@ -693,11 +693,7 @@ protected:
virtual void _notificationv(int p_notification, bool p_reversed) {}
static void _bind_methods();
-#ifndef DISABLE_DEPRECATED
- static void _bind_compatibility_methods();
-#else
static void _bind_compatibility_methods() {}
-#endif
bool _set(const StringName &p_name, const Variant &p_property) { return false; };
bool _get(const StringName &p_name, Variant &r_property) const { return false; };
void _get_property_list(List<PropertyInfo> *p_list) const {};
diff --git a/core/object/script_language.cpp b/core/object/script_language.cpp
index 57e5195137..d5b7bc768d 100644
--- a/core/object/script_language.cpp
+++ b/core/object/script_language.cpp
@@ -41,6 +41,7 @@ ScriptLanguage *ScriptServer::_languages[MAX_LANGUAGES];
int ScriptServer::_language_count = 0;
bool ScriptServer::languages_ready = false;
Mutex ScriptServer::languages_mutex;
+thread_local bool ScriptServer::thread_entered = false;
bool ScriptServer::scripting_enabled = true;
bool ScriptServer::reload_scripts_on_save = false;
@@ -326,6 +327,10 @@ bool ScriptServer::are_languages_initialized() {
return languages_ready;
}
+bool ScriptServer::thread_is_entered() {
+ return thread_entered;
+}
+
void ScriptServer::set_reload_scripts_on_save(bool p_enable) {
reload_scripts_on_save = p_enable;
}
@@ -335,6 +340,10 @@ bool ScriptServer::is_reload_scripts_on_save_enabled() {
}
void ScriptServer::thread_enter() {
+ if (thread_entered) {
+ return;
+ }
+
MutexLock lock(languages_mutex);
if (!languages_ready) {
return;
@@ -342,9 +351,15 @@ void ScriptServer::thread_enter() {
for (int i = 0; i < _language_count; i++) {
_languages[i]->thread_enter();
}
+
+ thread_entered = true;
}
void ScriptServer::thread_exit() {
+ if (!thread_entered) {
+ return;
+ }
+
MutexLock lock(languages_mutex);
if (!languages_ready) {
return;
@@ -352,6 +367,8 @@ void ScriptServer::thread_exit() {
for (int i = 0; i < _language_count; i++) {
_languages[i]->thread_exit();
}
+
+ thread_entered = false;
}
HashMap<StringName, ScriptServer::GlobalScriptClass> ScriptServer::global_classes;
diff --git a/core/object/script_language.h b/core/object/script_language.h
index e38c344ae5..d9e2ab1d3c 100644
--- a/core/object/script_language.h
+++ b/core/object/script_language.h
@@ -54,6 +54,7 @@ class ScriptServer {
static int _language_count;
static bool languages_ready;
static Mutex languages_mutex;
+ static thread_local bool thread_entered;
static bool scripting_enabled;
static bool reload_scripts_on_save;
@@ -101,6 +102,7 @@ public:
static void init_languages();
static void finish_languages();
static bool are_languages_initialized();
+ static bool thread_is_entered();
};
class PlaceHolderScriptInstance;
diff --git a/core/object/worker_thread_pool.cpp b/core/object/worker_thread_pool.cpp
index fe7bbd474c..cf396c2676 100644
--- a/core/object/worker_thread_pool.cpp
+++ b/core/object/worker_thread_pool.cpp
@@ -63,17 +63,14 @@ void WorkerThreadPool::_process_task(Task *p_task) {
// Tasks must start with these at default values. They are free to set-and-forget otherwise.
set_current_thread_safe_for_nodes(false);
MessageQueue::set_thread_singleton_override(nullptr);
+
// Since the WorkerThreadPool is started before the script server,
// its pre-created threads can't have ScriptServer::thread_enter() called on them early.
// Therefore, we do it late at the first opportunity, so in case the task
// about to be run uses scripting, guarantees are held.
+ ScriptServer::thread_enter();
+
task_mutex.lock();
- if (!curr_thread.ready_for_scripting && ScriptServer::are_languages_initialized()) {
- task_mutex.unlock();
- ScriptServer::thread_enter();
- task_mutex.lock();
- curr_thread.ready_for_scripting = true;
- }
p_task->pool_thread_index = pool_thread_index;
prev_task = curr_thread.current_task;
curr_thread.current_task = p_task;
@@ -326,6 +323,8 @@ WorkerThreadPool::TaskID WorkerThreadPool::add_native_task(void (*p_func)(void *
}
WorkerThreadPool::TaskID WorkerThreadPool::_add_task(const Callable &p_callable, void (*p_func)(void *), void *p_userdata, BaseTemplateUserdata *p_template_userdata, bool p_high_priority, const String &p_description) {
+ ERR_FAIL_COND_V_MSG(threads.is_empty(), INVALID_TASK_ID, "Can't add a task because the WorkerThreadPool is either not initialized yet or already terminated.");
+
task_mutex.lock();
// Get a free task
Task *task = task_allocator.alloc();
@@ -514,6 +513,12 @@ void WorkerThreadPool::yield() {
int th_index = get_thread_index();
ERR_FAIL_COND_MSG(th_index == -1, "This function can only be called from a worker thread.");
_wait_collaboratively(&threads[th_index], ThreadData::YIELDING);
+
+ // If this long-lived task started before the scripting server was initialized,
+ // now is a good time to have scripting languages ready for the current thread.
+ // Otherwise, such a piece of setup won't happen unless another task has been
+ // run during the collaborative wait.
+ ScriptServer::thread_enter();
}
void WorkerThreadPool::notify_yield_over(TaskID p_task_id) {
@@ -538,6 +543,7 @@ void WorkerThreadPool::notify_yield_over(TaskID p_task_id) {
}
WorkerThreadPool::GroupID WorkerThreadPool::_add_group_task(const Callable &p_callable, void (*p_func)(void *, uint32_t), void *p_userdata, BaseTemplateUserdata *p_template_userdata, int p_elements, int p_tasks, bool p_high_priority, const String &p_description) {
+ ERR_FAIL_COND_V_MSG(threads.is_empty(), INVALID_TASK_ID, "Can't add a group task because the WorkerThreadPool is either not initialized yet or already terminated.");
ERR_FAIL_COND_V(p_elements < 0, INVALID_TASK_ID);
if (p_tasks < 0) {
p_tasks = MAX(1u, threads.size());
@@ -749,5 +755,5 @@ WorkerThreadPool::WorkerThreadPool() {
}
WorkerThreadPool::~WorkerThreadPool() {
- finish();
+ DEV_ASSERT(threads.size() == 0 && "finish() hasn't been called!");
}
diff --git a/core/object/worker_thread_pool.h b/core/object/worker_thread_pool.h
index 5be4f20927..6374dbe8c7 100644
--- a/core/object/worker_thread_pool.h
+++ b/core/object/worker_thread_pool.h
@@ -112,7 +112,6 @@ private:
uint32_t index = 0;
Thread thread;
- bool ready_for_scripting : 1;
bool signaled : 1;
bool yield_is_over : 1;
Task *current_task = nullptr;
@@ -120,7 +119,6 @@ private:
ConditionVariable cond_var;
ThreadData() :
- ready_for_scripting(false),
signaled(false),
yield_is_over(false) {}
};
diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp
index c866ff0415..220ed9da31 100644
--- a/core/register_core_types.cpp
+++ b/core/register_core_types.cpp
@@ -107,8 +107,6 @@ static Time *_time = nullptr;
static core_bind::Geometry2D *_geometry_2d = nullptr;
static core_bind::Geometry3D *_geometry_3d = nullptr;
-static WorkerThreadPool *worker_thread_pool = nullptr;
-
extern Mutex _global_mutex;
static GDExtensionManager *gdextension_manager = nullptr;
@@ -297,8 +295,6 @@ void register_core_types() {
GDREGISTER_NATIVE_STRUCT(AudioFrame, "float left;float right");
GDREGISTER_NATIVE_STRUCT(ScriptLanguageExtensionProfilingInfo, "StringName signature;uint64_t call_count;uint64_t total_time;uint64_t self_time");
- worker_thread_pool = memnew(WorkerThreadPool);
-
OS::get_singleton()->benchmark_end_measure("Core", "Register Types");
}
@@ -349,7 +345,7 @@ void register_core_singletons() {
Engine::get_singleton()->add_singleton(Engine::Singleton("Time", Time::get_singleton()));
Engine::get_singleton()->add_singleton(Engine::Singleton("GDExtensionManager", GDExtensionManager::get_singleton()));
Engine::get_singleton()->add_singleton(Engine::Singleton("ResourceUID", ResourceUID::get_singleton()));
- Engine::get_singleton()->add_singleton(Engine::Singleton("WorkerThreadPool", worker_thread_pool));
+ Engine::get_singleton()->add_singleton(Engine::Singleton("WorkerThreadPool", WorkerThreadPool::get_singleton()));
OS::get_singleton()->benchmark_end_measure("Core", "Register Singletons");
}
@@ -382,8 +378,6 @@ void unregister_core_types() {
// Destroy singletons in reverse order to ensure dependencies are not broken.
- memdelete(worker_thread_pool);
-
memdelete(_engine_debugger);
memdelete(_marshalls);
memdelete(_classdb);
diff --git a/core/string/string_name.cpp b/core/string/string_name.cpp
index 0294dbfbbc..dff19b3a41 100644
--- a/core/string/string_name.cpp
+++ b/core/string/string_name.cpp
@@ -162,6 +162,11 @@ void StringName::unref() {
_data = nullptr;
}
+uint32_t StringName::get_empty_hash() {
+ static uint32_t empty_hash = String::hash("");
+ return empty_hash;
+}
+
bool StringName::operator==(const String &p_name) const {
if (_data) {
return _data->operator==(p_name);
diff --git a/core/string/string_name.h b/core/string/string_name.h
index 288e2c7520..d4b70d311d 100644
--- a/core/string/string_name.h
+++ b/core/string/string_name.h
@@ -83,6 +83,7 @@ class StringName {
static inline Mutex mutex;
static void setup();
static void cleanup();
+ static uint32_t get_empty_hash();
static inline bool configured = false;
#ifdef DEBUG_ENABLED
struct DebugSortReferences {
@@ -139,7 +140,7 @@ public:
if (_data) {
return _data->hash;
} else {
- return 0;
+ return get_empty_hash();
}
}
_FORCE_INLINE_ const void *data_unique_pointer() const {
diff --git a/core/string/translation.compat.inc b/core/string/translation.compat.inc
deleted file mode 100644
index 68bd1831e4..0000000000
--- a/core/string/translation.compat.inc
+++ /dev/null
@@ -1,41 +0,0 @@
-/**************************************************************************/
-/* translation.compat.inc */
-/**************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/**************************************************************************/
-/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
-/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/**************************************************************************/
-
-#ifndef DISABLE_DEPRECATED
-
-void Translation::_bind_compatibility_methods() {
- ClassDB::bind_compatibility_method(D_METHOD("add_message", "src_message", "xlated_message", "context"), &Translation::add_message, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("add_plural_message", "src_message", "xlated_messages", "context"), &Translation::add_plural_message, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("get_message", "src_message", "context"), &Translation::get_message, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("get_plural_message", "src_message", "src_plural_message", "n", "context"), &Translation::get_plural_message, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("erase_message", "src_message", "context"), &Translation::erase_message, DEFVAL(""));
-}
-
-#endif
diff --git a/core/string/translation.cpp b/core/string/translation.cpp
index 33d4a1bcde..020949371f 100644
--- a/core/string/translation.cpp
+++ b/core/string/translation.cpp
@@ -29,7 +29,6 @@
/**************************************************************************/
#include "translation.h"
-#include "translation.compat.inc"
#include "core/os/os.h"
#include "core/os/thread.h"
diff --git a/core/string/translation.h b/core/string/translation.h
index 2c5baae8b7..4e8cffc90c 100644
--- a/core/string/translation.h
+++ b/core/string/translation.h
@@ -51,10 +51,6 @@ class Translation : public Resource {
protected:
static void _bind_methods();
-#ifndef DISABLE_DEPRECATED
- static void _bind_compatibility_methods();
-#endif
-
GDVIRTUAL2RC(StringName, _get_message, StringName, StringName);
GDVIRTUAL4RC(StringName, _get_plural_message, StringName, StringName, int, StringName);
diff --git a/core/string/translation_server.compat.inc b/core/string/translation_server.compat.inc
deleted file mode 100644
index 9d1ee8b9df..0000000000
--- a/core/string/translation_server.compat.inc
+++ /dev/null
@@ -1,38 +0,0 @@
-/**************************************************************************/
-/* translation_server.compat.inc */
-/**************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/**************************************************************************/
-/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
-/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/**************************************************************************/
-
-#ifndef DISABLE_DEPRECATED
-
-void TranslationServer::_bind_compatibility_methods() {
- ClassDB::bind_compatibility_method(D_METHOD("translate", "message", "context"), &TranslationServer::translate, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("translate_plural", "message", "plural_message", "n", "context"), &TranslationServer::translate_plural, DEFVAL(""));
-}
-
-#endif
diff --git a/core/string/translation_server.cpp b/core/string/translation_server.cpp
index 4ac79ad10a..d4aa152340 100644
--- a/core/string/translation_server.cpp
+++ b/core/string/translation_server.cpp
@@ -29,7 +29,6 @@
/**************************************************************************/
#include "translation_server.h"
-#include "translation_server.compat.inc"
#include "core/config/project_settings.h"
#include "core/io/resource_loader.h"
diff --git a/core/string/translation_server.h b/core/string/translation_server.h
index ebe81d9712..bb285ab19c 100644
--- a/core/string/translation_server.h
+++ b/core/string/translation_server.h
@@ -74,10 +74,6 @@ class TranslationServer : public Object {
static void _bind_methods();
-#ifndef DISABLE_DEPRECATED
- static void _bind_compatibility_methods();
-#endif
-
struct LocaleScriptInfo {
String name;
String script;
diff --git a/core/variant/array.cpp b/core/variant/array.cpp
index 54cd1eda2f..869499e668 100644
--- a/core/variant/array.cpp
+++ b/core/variant/array.cpp
@@ -803,6 +803,10 @@ bool Array::is_same_typed(const Array &p_other) const {
return _p->typed == p_other._p->typed;
}
+bool Array::is_same_instance(const Array &p_other) const {
+ return _p == p_other._p;
+}
+
uint32_t Array::get_typed_builtin() const {
return _p->typed.type;
}
@@ -815,6 +819,12 @@ Variant Array::get_typed_script() const {
return _p->typed.script;
}
+Array Array::create_read_only() {
+ Array array;
+ array.make_read_only();
+ return array;
+}
+
void Array::make_read_only() {
if (_p->read_only == nullptr) {
_p->read_only = memnew(Variant);
diff --git a/core/variant/array.h b/core/variant/array.h
index 3aa957b312..12824ee3f6 100644
--- a/core/variant/array.h
+++ b/core/variant/array.h
@@ -186,12 +186,14 @@ public:
void set_typed(uint32_t p_type, const StringName &p_class_name, const Variant &p_script);
bool is_typed() const;
bool is_same_typed(const Array &p_other) const;
+ bool is_same_instance(const Array &p_other) const;
uint32_t get_typed_builtin() const;
StringName get_typed_class_name() const;
Variant get_typed_script() const;
void make_read_only();
bool is_read_only() const;
+ static Array create_read_only();
Array(const Array &p_base, uint32_t p_type, const StringName &p_class_name, const Variant &p_script);
Array(const Array &p_from);
diff --git a/doc/classes/CompositorEffect.xml b/doc/classes/CompositorEffect.xml
index 76a3887918..9ac54edb11 100644
--- a/doc/classes/CompositorEffect.xml
+++ b/doc/classes/CompositorEffect.xml
@@ -57,6 +57,17 @@
var render_scene_buffers : RenderSceneBuffersRD = render_data.get_render_scene_buffers()
var roughness_buffer = render_scene_buffers.get_texture("forward_clustered", "normal_roughness")
[/codeblock]
+ The raw normal and roughness buffer is stored in an optimized format, different than the one available in Spatial shaders. When sampling the buffer, a conversion function must be applied. Use this function, copied from [url=https://github.com/godotengine/godot/blob/da5f39889f155658cef7f7ec3cc1abb94e17d815/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered_inc.glsl#L334-L341]here[/url]:
+ [codeblock]
+ vec4 normal_roughness_compatibility(vec4 p_normal_roughness) {
+ float roughness = p_normal_roughness.w;
+ if (roughness &gt; 0.5) {
+ roughness = 1.0 - roughness;
+ }
+ roughness /= (127.0 / 255.0);
+ return vec4(normalize(p_normal_roughness.xyz * 2.0 - 1.0) * 0.5 + 0.5, roughness);
+ }
+ [/codeblock]
</member>
<member name="needs_separate_specular" type="bool" setter="set_needs_separate_specular" getter="get_needs_separate_specular">
If [code]true[/code] this triggers specular data being rendered to a separate buffer and combined after effects have been applied, only applicable for the Forward+ renderer.
diff --git a/doc/classes/EditorContextMenuPlugin.xml b/doc/classes/EditorContextMenuPlugin.xml
index 7eeee3d7fd..71c4ca0f9b 100644
--- a/doc/classes/EditorContextMenuPlugin.xml
+++ b/doc/classes/EditorContextMenuPlugin.xml
@@ -14,7 +14,7 @@
<return type="void" />
<param index="0" name="paths" type="PackedStringArray" />
<description>
- Called when creating a context menu, custom options can be added by using the [method add_context_menu_item] function.
+ Called when creating a context menu, custom options can be added by using the [method add_context_menu_item] or [method add_context_menu_item_from_shortcut] functions. [param paths] contains currently selected paths (depending on menu), which can be used to conditionally add options.
</description>
</method>
<method name="add_context_menu_item">
@@ -22,14 +22,29 @@
<param index="0" name="name" type="String" />
<param index="1" name="callback" type="Callable" />
<param index="2" name="icon" type="Texture2D" default="null" />
- <param index="3" name="shortcut" type="Shortcut" default="null" />
<description>
- Add custom options to the context menu of the currently specified slot.
- To trigger a [param shortcut] before the context menu is created, please additionally call the [method add_menu_shortcut] function.
+ Add custom option to the context menu of the plugin's specified slot. When the option is activated, [param callback] will be called. Callback should take single [Array] argument; array contents depend on context menu slot.
[codeblock]
func _popup_menu(paths):
add_context_menu_item("File Custom options", handle, ICON)
[/codeblock]
+ If you want to assign shortcut to the menu item, use [method add_context_menu_item_from_shortcut] instead.
+ </description>
+ </method>
+ <method name="add_context_menu_item_from_shortcut">
+ <return type="void" />
+ <param index="0" name="name" type="String" />
+ <param index="1" name="shortcut" type="Shortcut" />
+ <param index="2" name="icon" type="Texture2D" default="null" />
+ <description>
+ Add custom option to the context menu of the plugin's specified slot. The option will have the [param shortcut] assigned and reuse its callback. The shortcut has to be registered beforehand with [method add_menu_shortcut].
+ [codeblock]
+ func _init():
+ add_menu_shortcut(SHORTCUT, handle)
+
+ func _popup_menu(paths):
+ add_context_menu_item_from_shortcut("File Custom options", SHORTCUT, ICON)
+ [/codeblock]
</description>
</method>
<method name="add_menu_shortcut">
@@ -37,13 +52,26 @@
<param index="0" name="shortcut" type="Shortcut" />
<param index="1" name="callback" type="Callable" />
<description>
- To register the shortcut for the context menu, call this function within the [method Object._init] function, even if the context menu has not been created yet.
- Note that this method should only be invoked from [method Object._init]; otherwise, the shortcut will not be registered correctly.
+ Registers a shortcut associated with the plugin's context menu. This method should be called once (e.g. in plugin's [method Object._init]). [param callback] will be called when user presses the specified [param shortcut] while the menu's context is in effect (e.g. FileSystem dock is focused). Callback should take single [Array] argument; array contents depend on context menu slot.
[codeblock]
func _init():
- add_menu_shortcut(SHORTCUT, handle);
+ add_menu_shortcut(SHORTCUT, handle)
[/codeblock]
</description>
</method>
</methods>
+ <constants>
+ <constant name="CONTEXT_SLOT_SCENE_TREE" value="0" enum="ContextMenuSlot">
+ Context menu of Scene dock. [method _popup_menu] will be called with a list of paths to currently selected nodes, while option callback will receive the list of currently selected nodes.
+ </constant>
+ <constant name="CONTEXT_SLOT_FILESYSTEM" value="1" enum="ContextMenuSlot">
+ Context menu of FileSystem dock. [method _popup_menu] and option callback will be called with list of paths of the currently selected files.
+ </constant>
+ <constant name="CONTEXT_SLOT_FILESYSTEM_CREATE" value="3" enum="ContextMenuSlot">
+ The "Create..." submenu of FileSystem dock's context menu. [method _popup_menu] and option callback will be called with list of paths of the currently selected files.
+ </constant>
+ <constant name="CONTEXT_SLOT_SCRIPT_EDITOR" value="2" enum="ContextMenuSlot">
+ Context menu of Scene dock. [method _popup_menu] will be called with the path to the currently edited script, while option callback will receive reference to that script.
+ </constant>
+ </constants>
</class>
diff --git a/doc/classes/EditorPlugin.xml b/doc/classes/EditorPlugin.xml
index b3191e5378..de49764f0d 100644
--- a/doc/classes/EditorPlugin.xml
+++ b/doc/classes/EditorPlugin.xml
@@ -403,11 +403,11 @@
</method>
<method name="add_context_menu_plugin">
<return type="void" />
- <param index="0" name="slot" type="int" enum="EditorPlugin.ContextMenuSlot" />
+ <param index="0" name="slot" type="int" enum="EditorContextMenuPlugin.ContextMenuSlot" />
<param index="1" name="plugin" type="EditorContextMenuPlugin" />
<description>
- Adds a plugin to the context menu. [param slot] is the position in the context menu where the plugin will be added.
- Context menus are supported for three commonly used areas: the file system, scene tree, and editor script list panel.
+ Adds a plugin to the context menu. [param slot] is the context menu where the plugin will be added.
+ See [enum EditorContextMenuPlugin.ContextMenuSlot] for available context menus. A plugin instance can belong only to a single context menu slot.
</description>
</method>
<method name="add_control_to_bottom_panel">
@@ -635,10 +635,9 @@
</method>
<method name="remove_context_menu_plugin">
<return type="void" />
- <param index="0" name="slot" type="int" enum="EditorPlugin.ContextMenuSlot" />
- <param index="1" name="plugin" type="EditorContextMenuPlugin" />
+ <param index="0" name="plugin" type="EditorContextMenuPlugin" />
<description>
- Removes a context menu plugin from the specified slot.
+ Removes the specified context menu plugin.
</description>
</method>
<method name="remove_control_from_bottom_panel">
@@ -891,17 +890,5 @@
<constant name="AFTER_GUI_INPUT_CUSTOM" value="2" enum="AfterGUIInput">
Pass the [InputEvent] to other editor plugins except the main [Node3D] one. This can be used to prevent node selection changes and work with sub-gizmos instead.
</constant>
- <constant name="CONTEXT_SLOT_SCENE_TREE" value="0" enum="ContextMenuSlot">
- Context menu slot for the SceneTree.
- </constant>
- <constant name="CONTEXT_SLOT_FILESYSTEM" value="1" enum="ContextMenuSlot">
- Context menu slot for the FileSystem.
- </constant>
- <constant name="CONTEXT_SLOT_SCRIPT_EDITOR" value="2" enum="ContextMenuSlot">
- Context menu slot for the ScriptEditor file list.
- </constant>
- <constant name="CONTEXT_SUBMENU_SLOT_FILESYSTEM_CREATE" value="3" enum="ContextMenuSlot">
- Context menu slot for the FileSystem create submenu.
- </constant>
</constants>
</class>
diff --git a/doc/classes/LineEdit.xml b/doc/classes/LineEdit.xml
index f938460c2f..a37dd47914 100644
--- a/doc/classes/LineEdit.xml
+++ b/doc/classes/LineEdit.xml
@@ -4,7 +4,14 @@
An input field for single-line text.
</brief_description>
<description>
- [LineEdit] provides an input field for editing a single line of text. It features many built-in shortcuts that are always available ([kbd]Ctrl[/kbd] here maps to [kbd]Cmd[/kbd] on macOS):
+ [LineEdit] provides an input field for editing a single line of text.
+ - When the [LineEdit] control is focused using the keyboard arrow keys, it will only gain focus and not enter edit mode.
+ - To enter edit mode, click on the control with the mouse or press the "ui_text_submit" action (default: [kbd]Enter[/kbd] or [kbd]Kp Enter[/kbd]).
+ - To exit edit mode, press "ui_text_submit" or "ui_cancel" (default: [kbd]Escape[/kbd]) actions.
+ - Check [method is_editing] and [signal editing_toggled] for more information.
+ [b]Important:[/b]
+ - Focusing the [LineEdit] with "ui_focus_next" (default: [kbd]Tab[/kbd]) or "ui_focus_prev" (default: [kbd]Shift + Tab[/kbd]) or [method Control.grab_focus] still enters edit mode (for compatibility).
+ [LineEdit] features many built-in shortcuts that are always available ([kbd]Ctrl[/kbd] here maps to [kbd]Cmd[/kbd] on macOS):
- [kbd]Ctrl + C[/kbd]: Copy
- [kbd]Ctrl + X[/kbd]: Cut
- [kbd]Ctrl + V[/kbd] or [kbd]Ctrl + Y[/kbd]: Paste/"yank"
@@ -139,6 +146,12 @@
Inserts [param text] at the caret. If the resulting value is longer than [member max_length], nothing happens.
</description>
</method>
+ <method name="is_editing" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns whether the [LineEdit] is being edited.
+ </description>
+ </method>
<method name="is_menu_visible" qualifiers="const">
<return type="bool" />
<description>
@@ -301,6 +314,12 @@
</member>
</members>
<signals>
+ <signal name="editing_toggled">
+ <param index="0" name="toggled_on" type="bool" />
+ <description>
+ Emitted when the [LineEdit] switches in or out of edit mode.
+ </description>
+ </signal>
<signal name="text_change_rejected">
<param index="0" name="rejected_substring" type="String" />
<description>
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index 08427ffe83..e5b787714f 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -2341,6 +2341,9 @@
[b]Note:[/b] This property is only read when the project starts. To change the physics FPS at runtime, set [member Engine.physics_ticks_per_second] instead.
[b]Note:[/b] Only [member physics/common/max_physics_steps_per_frame] physics ticks may be simulated per rendered frame at most. If more physics ticks have to be simulated per rendered frame to keep up with rendering, the project will appear to slow down (even if [code]delta[/code] is used consistently in physics calculations). Therefore, it is recommended to also increase [member physics/common/max_physics_steps_per_frame] if increasing [member physics/common/physics_ticks_per_second] significantly above its default value.
</member>
+ <member name="rendering/2d/batching/item_buffer_size" type="int" setter="" getter="" default="16384">
+ Maximum number of canvas item commands that can be batched into a single draw call.
+ </member>
<member name="rendering/2d/sdf/oversize" type="int" setter="" getter="" default="1">
Controls how much of the original viewport size should be covered by the 2D signed distance field. This SDF can be sampled in [CanvasItem] shaders and is used for [GPUParticles2D] collision. Higher values allow portions of occluders located outside the viewport to still be taken into account in the generated signed distance field, at the cost of performance. If you notice particles falling through [LightOccluder2D]s as the occluders leave the viewport, increase this setting.
The percentage specified is added on each axis and on both sides. For example, with the default setting of 120%, the signed distance field will cover 20% of the viewport's size outside the viewport on each side (top, right, bottom, left).
diff --git a/doc/classes/ScriptEditor.xml b/doc/classes/ScriptEditor.xml
index 5cf077c266..67a2af2932 100644
--- a/doc/classes/ScriptEditor.xml
+++ b/doc/classes/ScriptEditor.xml
@@ -99,6 +99,14 @@
[b]Note:[/b] The [EditorSyntaxHighlighter] will still be applied to scripts that are already opened.
</description>
</method>
+ <method name="update_docs_from_script">
+ <return type="void" />
+ <param index="0" name="script" type="Script" />
+ <description>
+ Updates the documentation for the given [param script] if the script's documentation is currently open.
+ [b]Note:[/b] This should be called whenever the script is changed to keep the open documentation state up to date.
+ </description>
+ </method>
</methods>
<signals>
<signal name="editor_script_changed">
diff --git a/drivers/metal/metal_objects.mm b/drivers/metal/metal_objects.mm
index abdcccf00c..d3c3d2b232 100644
--- a/drivers/metal/metal_objects.mm
+++ b/drivers/metal/metal_objects.mm
@@ -560,10 +560,10 @@ void MDCommandBuffer::_render_clear_render_area() {
}
}
uint32_t ds_index = subpass.depth_stencil_reference.attachment;
- MDAttachment const &attachment = pass.attachments[ds_index];
- bool shouldClearDepth = (ds_index != RDD::AttachmentReference::UNUSED && attachment.shouldClear(subpass, false));
- bool shouldClearStencil = (ds_index != RDD::AttachmentReference::UNUSED && attachment.shouldClear(subpass, true));
+ bool shouldClearDepth = (ds_index != RDD::AttachmentReference::UNUSED && pass.attachments[ds_index].shouldClear(subpass, false));
+ bool shouldClearStencil = (ds_index != RDD::AttachmentReference::UNUSED && pass.attachments[ds_index].shouldClear(subpass, true));
if (shouldClearDepth || shouldClearStencil) {
+ MDAttachment const &attachment = pass.attachments[ds_index];
BitField<RDD::TextureAspectBits> bits;
if (shouldClearDepth && attachment.type & MDAttachmentType::Depth) {
bits.set_flag(RDD::TEXTURE_ASPECT_DEPTH_BIT);
diff --git a/drivers/windows/file_access_windows.cpp b/drivers/windows/file_access_windows.cpp
index 0243d863f8..9d6aa13332 100644
--- a/drivers/windows/file_access_windows.cpp
+++ b/drivers/windows/file_access_windows.cpp
@@ -118,11 +118,12 @@ Error FileAccessWindows::open_internal(const String &p_path, int p_mode_flags) {
return ERR_INVALID_PARAMETER;
}
- struct _stat st;
- if (_wstat((LPCWSTR)(path.utf16().get_data()), &st) == 0) {
- if (!S_ISREG(st.st_mode)) {
- return ERR_FILE_CANT_OPEN;
- }
+ if (path.ends_with(":\\") || path.ends_with(":")) {
+ return ERR_FILE_CANT_OPEN;
+ }
+ DWORD file_attr = GetFileAttributesW((LPCWSTR)(path.utf16().get_data()));
+ if (file_attr != INVALID_FILE_ATTRIBUTES && (file_attr & FILE_ATTRIBUTE_DIRECTORY)) {
+ return ERR_FILE_CANT_OPEN;
}
#ifdef TOOLS_ENABLED
@@ -412,15 +413,40 @@ uint64_t FileAccessWindows::_get_modified_time(const String &p_file) {
file = file.substr(0, file.length() - 1);
}
- struct _stat st;
- int rv = _wstat((LPCWSTR)(file.utf16().get_data()), &st);
+ HANDLE handle = CreateFileW((LPCWSTR)(file.utf16().get_data()), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
- if (rv == 0) {
- return st.st_mtime;
- } else {
- print_verbose("Failed to get modified time for: " + p_file + "");
- return 0;
+ if (handle != INVALID_HANDLE_VALUE) {
+ FILETIME ft_create, ft_write;
+
+ bool status = GetFileTime(handle, &ft_create, nullptr, &ft_write);
+
+ CloseHandle(handle);
+
+ if (status) {
+ uint64_t ret = 0;
+
+ // If write time is invalid, fallback to creation time.
+ if (ft_write.dwHighDateTime == 0 && ft_write.dwLowDateTime == 0) {
+ ret = ft_create.dwHighDateTime;
+ ret <<= 32;
+ ret |= ft_create.dwLowDateTime;
+ } else {
+ ret = ft_write.dwHighDateTime;
+ ret <<= 32;
+ ret |= ft_write.dwLowDateTime;
+ }
+
+ const uint64_t WINDOWS_TICKS_PER_SECOND = 10000000;
+ const uint64_t TICKS_TO_UNIX_EPOCH = 116444736000000000LL;
+
+ if (ret >= TICKS_TO_UNIX_EPOCH) {
+ return (ret - TICKS_TO_UNIX_EPOCH) / WINDOWS_TICKS_PER_SECOND;
+ }
+ }
}
+
+ print_verbose("Failed to get modified time for: " + p_file);
+ return 0;
}
BitField<FileAccess::UnixPermissionFlags> FileAccessWindows::_get_unix_permissions(const String &p_file) {
diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp
index 95ba301282..55bae8e592 100644
--- a/editor/animation_track_editor.cpp
+++ b/editor/animation_track_editor.cpp
@@ -4345,6 +4345,25 @@ PropertyInfo AnimationTrackEditor::_find_hint_for_track(int p_idx, NodePath &r_b
property_info_base = property_info_base.get_named(leftover_path[i], valid);
}
+ // Hack for the fact that bezier tracks leftover paths can reference
+ // the individual components for types like vectors.
+ if (property_info_base.is_null()) {
+ if (res.is_valid()) {
+ property_info_base = res;
+ } else if (node) {
+ property_info_base = node;
+ }
+
+ if (leftover_path.size()) {
+ leftover_path.remove_at(leftover_path.size() - 1);
+ }
+
+ for (int i = 0; i < leftover_path.size() - 1; i++) {
+ bool valid;
+ property_info_base = property_info_base.get_named(leftover_path[i], valid);
+ }
+ }
+
if (property_info_base.is_null()) {
WARN_PRINT(vformat("Could not determine track hint for '%s:%s' because its base property is null.",
String(path.get_concatenated_names()), String(path.get_concatenated_subnames())));
@@ -4472,7 +4491,11 @@ AnimationTrackEditor::TrackIndices AnimationTrackEditor::_confirm_insert(InsertD
} break;
case Animation::TYPE_BEZIER: {
- int existing = animation->track_find_key(p_id.track_idx, time, Animation::FIND_MODE_APPROX);
+ int existing = -1;
+ if (p_id.track_idx < animation->get_track_count()) {
+ existing = animation->track_find_key(p_id.track_idx, time, Animation::FIND_MODE_APPROX);
+ }
+
if (existing != -1) {
Array arr = animation->track_get_key_value(p_id.track_idx, existing);
arr[0] = p_id.value;
diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp
index 5273b61f37..ce2c1a5a16 100644
--- a/editor/connections_dialog.cpp
+++ b/editor/connections_dialog.cpp
@@ -34,6 +34,7 @@
#include "core/templates/hash_set.h"
#include "editor/editor_help.h"
#include "editor/editor_inspector.h"
+#include "editor/editor_main_screen.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
@@ -1191,7 +1192,7 @@ void ConnectionsDock::_go_to_method(TreeItem &p_item) {
}
if (scr.is_valid() && ScriptEditor::get_singleton()->script_goto_method(scr, cd.method)) {
- EditorNode::get_singleton()->editor_select(EditorNode::EDITOR_SCRIPT);
+ EditorNode::get_editor_main_screen()->select(EditorMainScreen::EDITOR_SCRIPT);
}
}
@@ -1199,7 +1200,7 @@ void ConnectionsDock::_handle_class_menu_option(int p_option) {
switch (p_option) {
case CLASS_MENU_OPEN_DOCS:
ScriptEditor::get_singleton()->goto_help("class:" + class_menu_doc_class_name);
- EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT);
+ EditorNode::get_singleton()->get_editor_main_screen()->select(EditorMainScreen::EDITOR_SCRIPT);
break;
}
}
@@ -1229,7 +1230,7 @@ void ConnectionsDock::_handle_signal_menu_option(int p_option) {
} break;
case SIGNAL_MENU_OPEN_DOCS: {
ScriptEditor::get_singleton()->goto_help("class_signal:" + String(meta["class"]) + ":" + String(meta["name"]));
- EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT);
+ EditorNode::get_singleton()->get_editor_main_screen()->select(EditorMainScreen::EDITOR_SCRIPT);
} break;
}
}
diff --git a/editor/editor_data.cpp b/editor/editor_data.cpp
index e5caa6a352..ee16c61c89 100644
--- a/editor/editor_data.cpp
+++ b/editor/editor_data.cpp
@@ -522,138 +522,6 @@ EditorPlugin *EditorData::get_extension_editor_plugin(const StringName &p_class_
return plugin == nullptr ? nullptr : *plugin;
}
-void EditorData::add_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin) {
- ContextMenu cm;
- cm.p_slot = p_slot;
- cm.plugin = p_plugin;
- p_plugin->start_idx = context_menu_plugins.size() * EditorContextMenuPlugin::MAX_ITEMS;
- context_menu_plugins.push_back(cm);
-}
-
-void EditorData::remove_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin) {
- for (int i = context_menu_plugins.size() - 1; i > -1; i--) {
- if (context_menu_plugins[i].p_slot == p_slot && context_menu_plugins[i].plugin == p_plugin) {
- context_menu_plugins.remove_at(i);
- }
- }
-}
-
-int EditorData::match_context_menu_shortcut(ContextMenuSlot p_slot, const Ref<InputEvent> &p_event) {
- for (ContextMenu &cm : context_menu_plugins) {
- if (cm.p_slot != p_slot) {
- continue;
- }
- HashMap<Ref<Shortcut>, Callable> &cms = cm.plugin->context_menu_shortcuts;
- int shortcut_idx = 0;
- for (KeyValue<Ref<Shortcut>, Callable> &E : cms) {
- const Ref<Shortcut> &p_shortcut = E.key;
- if (p_shortcut->matches_event(p_event)) {
- return EditorData::CONTEXT_MENU_ITEM_ID_BASE + cm.plugin->start_idx + shortcut_idx;
- }
- shortcut_idx++;
- }
- }
- return 0;
-}
-
-void EditorData::add_options_from_plugins(PopupMenu *p_popup, ContextMenuSlot p_slot, const Vector<String> &p_paths) {
- bool add_separator = false;
-
- for (ContextMenu &cm : context_menu_plugins) {
- if (cm.p_slot != p_slot) {
- continue;
- }
- cm.plugin->clear_context_menu_items();
- cm.plugin->add_options(p_paths);
- HashMap<String, EditorContextMenuPlugin::ContextMenuItem> &items = cm.plugin->context_menu_items;
- if (items.size() > 0 && !add_separator) {
- add_separator = true;
- p_popup->add_separator();
- }
- for (KeyValue<String, EditorContextMenuPlugin::ContextMenuItem> &E : items) {
- EditorContextMenuPlugin::ContextMenuItem &item = E.value;
-
- if (item.icon.is_valid()) {
- p_popup->add_icon_item(item.icon, item.item_name, item.idx);
- const int icon_size = p_popup->get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor));
- p_popup->set_item_icon_max_width(-1, icon_size);
- } else {
- p_popup->add_item(item.item_name, item.idx);
- }
- if (item.shortcut.is_valid()) {
- p_popup->set_item_shortcut(-1, item.shortcut, true);
- }
- }
- }
-}
-
-template <typename T>
-void EditorData::invoke_plugin_callback(ContextMenuSlot p_slot, int p_option, const T &p_arg) {
- Variant arg = p_arg;
- Variant *argptr = &arg;
-
- for (int i = 0; i < context_menu_plugins.size(); i++) {
- if (context_menu_plugins[i].p_slot != p_slot || context_menu_plugins[i].plugin.is_null()) {
- continue;
- }
- Ref<EditorContextMenuPlugin> plugin = context_menu_plugins[i].plugin;
-
- // Shortcut callback.
- int shortcut_idx = 0;
- int shortcut_base_idx = EditorData::CONTEXT_MENU_ITEM_ID_BASE + plugin->start_idx;
- for (KeyValue<Ref<Shortcut>, Callable> &E : plugin->context_menu_shortcuts) {
- if (shortcut_base_idx + shortcut_idx == p_option) {
- const Callable &callable = E.value;
- Callable::CallError ce;
- Variant result;
- callable.callp((const Variant **)&argptr, 1, result, ce);
- }
- shortcut_idx++;
- }
- if (p_option < shortcut_base_idx + shortcut_idx) {
- return;
- }
-
- HashMap<String, EditorContextMenuPlugin::ContextMenuItem> &items = plugin->context_menu_items;
- for (KeyValue<String, EditorContextMenuPlugin::ContextMenuItem> &E : items) {
- EditorContextMenuPlugin::ContextMenuItem &item = E.value;
-
- if (p_option != item.idx || !item.callable.is_valid()) {
- continue;
- }
-
- Callable::CallError ce;
- Variant result;
- item.callable.callp((const Variant **)&argptr, 1, result, ce);
-
- if (ce.error != Callable::CallError::CALL_OK) {
- String err = Variant::get_callable_error_text(item.callable, nullptr, 0, ce);
- ERR_PRINT("Error calling function from context menu: " + err);
- }
- }
- }
- // Invoke submenu items.
- if (p_slot == CONTEXT_SLOT_FILESYSTEM) {
- invoke_plugin_callback(CONTEXT_SUBMENU_SLOT_FILESYSTEM_CREATE, p_option, p_arg);
- }
-}
-
-void EditorData::filesystem_options_pressed(ContextMenuSlot p_slot, int p_option, const Vector<String> &p_selected) {
- invoke_plugin_callback(p_slot, p_option, p_selected);
-}
-
-void EditorData::scene_tree_options_pressed(ContextMenuSlot p_slot, int p_option, const List<Node *> &p_selected) {
- TypedArray<Node> nodes;
- for (Node *selected : p_selected) {
- nodes.append(selected);
- }
- invoke_plugin_callback(p_slot, p_option, nodes);
-}
-
-void EditorData::script_editor_options_pressed(ContextMenuSlot p_slot, int p_option, const Ref<Resource> &p_script) {
- invoke_plugin_callback(p_slot, p_option, p_script);
-}
-
void EditorData::add_custom_type(const String &p_type, const String &p_inherits, const Ref<Script> &p_script, const Ref<Texture2D> &p_icon) {
ERR_FAIL_COND_MSG(p_script.is_null(), "It's not a reference to a valid Script object.");
CustomType ct;
diff --git a/editor/editor_data.h b/editor/editor_data.h
index 754d44c479..5b49304c73 100644
--- a/editor/editor_data.h
+++ b/editor/editor_data.h
@@ -37,7 +37,6 @@
class ConfigFile;
class EditorPlugin;
class EditorUndoRedoManager;
-class EditorContextMenuPlugin;
class PopupMenu;
/**
@@ -125,22 +124,6 @@ public:
uint64_t last_checked_version = 0;
};
- enum ContextMenuSlot {
- CONTEXT_SLOT_SCENE_TREE,
- CONTEXT_SLOT_FILESYSTEM,
- CONTEXT_SLOT_SCRIPT_EDITOR,
- CONTEXT_SUBMENU_SLOT_FILESYSTEM_CREATE,
- };
-
- inline static constexpr int CONTEXT_MENU_ITEM_ID_BASE = 1000;
-
- struct ContextMenu {
- int p_slot;
- Ref<EditorContextMenuPlugin> plugin;
- };
-
- Vector<ContextMenu> context_menu_plugins;
-
private:
Vector<EditorPlugin *> editor_plugins;
HashMap<StringName, EditorPlugin *> extension_editor_plugins;
@@ -195,18 +178,6 @@ public:
bool has_extension_editor_plugin(const StringName &p_class_name);
EditorPlugin *get_extension_editor_plugin(const StringName &p_class_name);
- // Context menu plugin.
- void add_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin);
- void remove_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin);
- int match_context_menu_shortcut(ContextMenuSlot p_slot, const Ref<InputEvent> &p_event);
-
- void add_options_from_plugins(PopupMenu *p_popup, ContextMenuSlot p_slot, const Vector<String> &p_paths);
- void filesystem_options_pressed(ContextMenuSlot p_slot, int p_option, const Vector<String> &p_selected);
- void scene_tree_options_pressed(ContextMenuSlot p_slot, int p_option, const List<Node *> &p_selected);
- void script_editor_options_pressed(ContextMenuSlot p_slot, int p_option, const Ref<Resource> &p_script);
- template <typename T>
- void invoke_plugin_callback(ContextMenuSlot p_slot, int p_option, const T &p_arg);
-
void add_undo_redo_inspector_hook_callback(Callable p_callable); // Callbacks should have this signature: void (Object* undo_redo, Object *modified_object, String property, Variant new_value)
void remove_undo_redo_inspector_hook_callback(Callable p_callable);
const Vector<Callable> get_undo_redo_inspector_hook_callback();
diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp
index fe758bf99b..f5f7b8f51c 100644
--- a/editor/editor_help.cpp
+++ b/editor/editor_help.cpp
@@ -39,6 +39,7 @@
#include "core/string/string_builder.h"
#include "core/version_generated.gen.h"
#include "editor/doc_data_compressed.gen.h"
+#include "editor/editor_main_screen.h"
#include "editor/editor_node.h"
#include "editor/editor_paths.h"
#include "editor/editor_property_name_processor.h"
@@ -2310,7 +2311,7 @@ void EditorHelp::_update_doc() {
void EditorHelp::_request_help(const String &p_string) {
Error err = _goto_desc(p_string);
if (err == OK) {
- EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT);
+ EditorNode::get_singleton()->get_editor_main_screen()->select(EditorMainScreen::EDITOR_SCRIPT);
}
}
@@ -3605,7 +3606,7 @@ void EditorHelpBit::_update_labels() {
}
void EditorHelpBit::_go_to_help(const String &p_what) {
- EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT);
+ EditorNode::get_singleton()->get_editor_main_screen()->select(EditorMainScreen::EDITOR_SCRIPT);
ScriptEditor::get_singleton()->goto_help(p_what);
emit_signal(SNAME("request_hide"));
}
diff --git a/editor/editor_help_search.cpp b/editor/editor_help_search.cpp
index 987c7f9c31..11be765bc2 100644
--- a/editor/editor_help_search.cpp
+++ b/editor/editor_help_search.cpp
@@ -32,6 +32,7 @@
#include "core/os/keyboard.h"
#include "editor/editor_feature_profile.h"
+#include "editor/editor_main_screen.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
@@ -201,7 +202,7 @@ void EditorHelpSearch::_confirmed() {
}
// Activate the script editor and emit the signal with the documentation link to display.
- EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT);
+ EditorNode::get_singleton()->get_editor_main_screen()->select(EditorMainScreen::EDITOR_SCRIPT);
emit_signal(SNAME("go_to_help"), item->get_metadata(0));
diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp
index a1cae374aa..4a2c41bc9c 100644
--- a/editor/editor_inspector.cpp
+++ b/editor/editor_inspector.cpp
@@ -34,6 +34,7 @@
#include "core/os/keyboard.h"
#include "editor/doc_tools.h"
#include "editor/editor_feature_profile.h"
+#include "editor/editor_main_screen.h"
#include "editor/editor_node.h"
#include "editor/editor_property_name_processor.h"
#include "editor/editor_settings.h"
@@ -1037,7 +1038,7 @@ void EditorProperty::menu_option(int p_option) {
} break;
case MENU_OPEN_DOCUMENTATION: {
ScriptEditor::get_singleton()->goto_help(doc_path);
- EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT);
+ EditorNode::get_singleton()->get_editor_main_screen()->select(EditorMainScreen::EDITOR_SCRIPT);
} break;
}
}
@@ -1297,7 +1298,7 @@ void EditorInspectorCategory::_handle_menu_option(int p_option) {
switch (p_option) {
case MENU_OPEN_DOCS:
ScriptEditor::get_singleton()->goto_help("class:" + doc_class_name);
- EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT);
+ EditorNode::get_singleton()->get_editor_main_screen()->select(EditorMainScreen::EDITOR_SCRIPT);
break;
}
}
diff --git a/editor/editor_interface.cpp b/editor/editor_interface.cpp
index 86b66ef410..fa6198f695 100644
--- a/editor/editor_interface.cpp
+++ b/editor/editor_interface.cpp
@@ -33,6 +33,7 @@
#include "editor/editor_command_palette.h"
#include "editor/editor_feature_profile.h"
+#include "editor/editor_main_screen.h"
#include "editor/editor_node.h"
#include "editor/editor_paths.h"
#include "editor/editor_resource_preview.h"
@@ -215,7 +216,7 @@ Control *EditorInterface::get_base_control() const {
}
VBoxContainer *EditorInterface::get_editor_main_screen() const {
- return EditorNode::get_singleton()->get_main_screen_control();
+ return EditorNode::get_singleton()->get_editor_main_screen()->get_control();
}
ScriptEditor *EditorInterface::get_script_editor() const {
@@ -232,7 +233,7 @@ SubViewport *EditorInterface::get_editor_viewport_3d(int p_idx) const {
}
void EditorInterface::set_main_screen_editor(const String &p_name) {
- EditorNode::get_singleton()->select_editor_by_name(p_name);
+ EditorNode::get_singleton()->get_editor_main_screen()->select_by_name(p_name);
}
void EditorInterface::set_distraction_free_mode(bool p_enter) {
diff --git a/editor/editor_main_screen.cpp b/editor/editor_main_screen.cpp
new file mode 100644
index 0000000000..77bbee5a7f
--- /dev/null
+++ b/editor/editor_main_screen.cpp
@@ -0,0 +1,291 @@
+/**************************************************************************/
+/* editor_main_screen.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "editor_main_screen.h"
+
+#include "core/io/config_file.h"
+#include "editor/editor_node.h"
+#include "editor/editor_settings.h"
+#include "editor/editor_string_names.h"
+#include "editor/plugins/editor_plugin.h"
+#include "scene/gui/box_container.h"
+#include "scene/gui/button.h"
+
+void EditorMainScreen::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_READY: {
+ if (EDITOR_3D < buttons.size() && buttons[EDITOR_3D]->is_visible()) {
+ // If the 3D editor is enabled, use this as the default.
+ select(EDITOR_3D);
+ return;
+ }
+
+ // Switch to the first main screen plugin that is enabled. Usually this is
+ // 2D, but may be subsequent ones if 2D is disabled in the feature profile.
+ for (int i = 0; i < buttons.size(); i++) {
+ Button *editor_button = buttons[i];
+ if (editor_button->is_visible()) {
+ select(i);
+ return;
+ }
+ }
+
+ select(-1);
+ } break;
+ case NOTIFICATION_THEME_CHANGED: {
+ for (int i = 0; i < buttons.size(); i++) {
+ Button *tb = buttons[i];
+ EditorPlugin *p_editor = editor_table[i];
+ Ref<Texture2D> icon = p_editor->get_icon();
+
+ if (icon.is_valid()) {
+ tb->set_icon(icon);
+ } else if (has_theme_icon(p_editor->get_name(), EditorStringName(EditorIcons))) {
+ tb->set_icon(get_theme_icon(p_editor->get_name(), EditorStringName(EditorIcons)));
+ }
+ }
+ } break;
+ }
+}
+
+void EditorMainScreen::set_button_container(HBoxContainer *p_button_hb) {
+ button_hb = p_button_hb;
+}
+
+void EditorMainScreen::save_layout_to_config(Ref<ConfigFile> p_config_file, const String &p_section) const {
+ int selected_main_editor_idx = -1;
+ for (int i = 0; i < buttons.size(); i++) {
+ if (buttons[i]->is_pressed()) {
+ selected_main_editor_idx = i;
+ break;
+ }
+ }
+ if (selected_main_editor_idx != -1) {
+ p_config_file->set_value(p_section, "selected_main_editor_idx", selected_main_editor_idx);
+ } else {
+ p_config_file->set_value(p_section, "selected_main_editor_idx", Variant());
+ }
+}
+
+void EditorMainScreen::load_layout_from_config(Ref<ConfigFile> p_config_file, const String &p_section) {
+ int selected_main_editor_idx = p_config_file->get_value(p_section, "selected_main_editor_idx", -1);
+ if (selected_main_editor_idx >= 0 && selected_main_editor_idx < buttons.size()) {
+ callable_mp(this, &EditorMainScreen::select).call_deferred(selected_main_editor_idx);
+ }
+}
+
+void EditorMainScreen::set_button_enabled(int p_index, bool p_enabled) {
+ ERR_FAIL_INDEX(p_index, buttons.size());
+ buttons[p_index]->set_visible(p_enabled);
+ if (!p_enabled && buttons[p_index]->is_pressed()) {
+ select(EDITOR_2D);
+ }
+}
+
+bool EditorMainScreen::is_button_enabled(int p_index) const {
+ ERR_FAIL_INDEX_V(p_index, buttons.size(), false);
+ return buttons[p_index]->is_visible();
+}
+
+int EditorMainScreen::_get_current_main_editor() const {
+ for (int i = 0; i < editor_table.size(); i++) {
+ if (editor_table[i] == selected_plugin) {
+ return i;
+ }
+ }
+
+ return 0;
+}
+
+void EditorMainScreen::select_next() {
+ int editor = _get_current_main_editor();
+
+ do {
+ if (editor == editor_table.size() - 1) {
+ editor = 0;
+ } else {
+ editor++;
+ }
+ } while (!buttons[editor]->is_visible());
+
+ select(editor);
+}
+
+void EditorMainScreen::select_prev() {
+ int editor = _get_current_main_editor();
+
+ do {
+ if (editor == 0) {
+ editor = editor_table.size() - 1;
+ } else {
+ editor--;
+ }
+ } while (!buttons[editor]->is_visible());
+
+ select(editor);
+}
+
+void EditorMainScreen::select_by_name(const String &p_name) {
+ ERR_FAIL_COND(p_name.is_empty());
+
+ for (int i = 0; i < buttons.size(); i++) {
+ if (buttons[i]->get_text() == p_name) {
+ select(i);
+ return;
+ }
+ }
+
+ ERR_FAIL_MSG("The editor name '" + p_name + "' was not found.");
+}
+
+void EditorMainScreen::select(int p_index) {
+ if (EditorNode::get_singleton()->is_changing_scene()) {
+ return;
+ }
+
+ ERR_FAIL_INDEX(p_index, editor_table.size());
+
+ if (!buttons[p_index]->is_visible()) { // Button hidden, no editor.
+ return;
+ }
+
+ for (int i = 0; i < buttons.size(); i++) {
+ buttons[i]->set_pressed_no_signal(i == p_index);
+ }
+
+ EditorPlugin *new_editor = editor_table[p_index];
+ ERR_FAIL_NULL(new_editor);
+
+ if (selected_plugin == new_editor) {
+ return;
+ }
+
+ if (selected_plugin) {
+ selected_plugin->make_visible(false);
+ }
+
+ selected_plugin = new_editor;
+ selected_plugin->make_visible(true);
+ selected_plugin->selected_notify();
+
+ EditorData &editor_data = EditorNode::get_editor_data();
+ int plugin_count = editor_data.get_editor_plugin_count();
+ for (int i = 0; i < plugin_count; i++) {
+ editor_data.get_editor_plugin(i)->notify_main_screen_changed(selected_plugin->get_name());
+ }
+
+ EditorNode::get_singleton()->update_distraction_free_mode();
+}
+
+int EditorMainScreen::get_selected_index() const {
+ for (int i = 0; i < editor_table.size(); i++) {
+ if (selected_plugin == editor_table[i]) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+int EditorMainScreen::get_plugin_index(EditorPlugin *p_editor) const {
+ int screen = -1;
+ for (int i = 0; i < editor_table.size(); i++) {
+ if (p_editor == editor_table[i]) {
+ screen = i;
+ break;
+ }
+ }
+ return screen;
+}
+
+EditorPlugin *EditorMainScreen::get_selected_plugin() const {
+ return selected_plugin;
+}
+
+VBoxContainer *EditorMainScreen::get_control() const {
+ return main_screen_vbox;
+}
+
+void EditorMainScreen::add_main_plugin(EditorPlugin *p_editor) {
+ Button *tb = memnew(Button);
+ tb->set_toggle_mode(true);
+ tb->set_theme_type_variation("MainScreenButton");
+ tb->set_name(p_editor->get_name());
+ tb->set_text(p_editor->get_name());
+
+ Ref<Texture2D> icon = p_editor->get_icon();
+ if (icon.is_null() && has_theme_icon(p_editor->get_name(), EditorStringName(EditorIcons))) {
+ icon = get_editor_theme_icon(p_editor->get_name());
+ }
+ if (icon.is_valid()) {
+ tb->set_icon(icon);
+ // Make sure the control is updated if the icon is reimported.
+ icon->connect_changed(callable_mp((Control *)tb, &Control::update_minimum_size));
+ }
+
+ tb->connect(SceneStringName(pressed), callable_mp(this, &EditorMainScreen::select).bind(buttons.size()));
+
+ buttons.push_back(tb);
+ button_hb->add_child(tb);
+ editor_table.push_back(p_editor);
+}
+
+void EditorMainScreen::remove_main_plugin(EditorPlugin *p_editor) {
+ // Remove the main editor button and update the bindings of
+ // all buttons behind it to point to the correct main window.
+ for (int i = buttons.size() - 1; i >= 0; i--) {
+ if (p_editor->get_name() == buttons[i]->get_text()) {
+ if (buttons[i]->is_pressed()) {
+ select(EDITOR_SCRIPT);
+ }
+
+ memdelete(buttons[i]);
+ buttons.remove_at(i);
+
+ break;
+ } else {
+ buttons[i]->disconnect(SceneStringName(pressed), callable_mp(this, &EditorMainScreen::select));
+ buttons[i]->connect(SceneStringName(pressed), callable_mp(this, &EditorMainScreen::select).bind(i - 1));
+ }
+ }
+
+ if (selected_plugin == p_editor) {
+ selected_plugin = nullptr;
+ }
+
+ editor_table.erase(p_editor);
+}
+
+EditorMainScreen::EditorMainScreen() {
+ main_screen_vbox = memnew(VBoxContainer);
+ main_screen_vbox->set_name("MainScreen");
+ main_screen_vbox->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ main_screen_vbox->add_theme_constant_override("separation", 0);
+ add_child(main_screen_vbox);
+}
diff --git a/core/object/object.compat.inc b/editor/editor_main_screen.h
index bf1e99fc9b..153a182bc2 100644
--- a/core/object/object.compat.inc
+++ b/editor/editor_main_screen.h
@@ -1,5 +1,5 @@
/**************************************************************************/
-/* object.compat.inc */
+/* editor_main_screen.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,13 +28,64 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
-#ifndef DISABLE_DEPRECATED
+#ifndef EDITOR_MAIN_SCREEN_H
+#define EDITOR_MAIN_SCREEN_H
-#include "core/object/class_db.h"
+#include "scene/gui/panel_container.h"
-void Object::_bind_compatibility_methods() {
- ClassDB::bind_compatibility_method(D_METHOD("tr", "message", "context"), &Object::tr, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("tr_n", "message", "plural_message", "n", "context"), &Object::tr_n, DEFVAL(""));
-}
+class Button;
+class ConfigFile;
+class EditorPlugin;
+class HBoxContainer;
+class VBoxContainer;
-#endif
+class EditorMainScreen : public PanelContainer {
+ GDCLASS(EditorMainScreen, PanelContainer);
+
+public:
+ enum EditorTable {
+ EDITOR_2D = 0,
+ EDITOR_3D,
+ EDITOR_SCRIPT,
+ EDITOR_ASSETLIB,
+ };
+
+private:
+ VBoxContainer *main_screen_vbox = nullptr;
+ EditorPlugin *selected_plugin = nullptr;
+
+ HBoxContainer *button_hb = nullptr;
+ Vector<Button *> buttons;
+ Vector<EditorPlugin *> editor_table;
+
+ int _get_current_main_editor() const;
+
+protected:
+ void _notification(int p_what);
+
+public:
+ void set_button_container(HBoxContainer *p_button_hb);
+
+ void save_layout_to_config(Ref<ConfigFile> p_config_file, const String &p_section) const;
+ void load_layout_from_config(Ref<ConfigFile> p_config_file, const String &p_section);
+
+ void set_button_enabled(int p_index, bool p_enabled);
+ bool is_button_enabled(int p_index) const;
+
+ void select_next();
+ void select_prev();
+ void select_by_name(const String &p_name);
+ void select(int p_index);
+ int get_selected_index() const;
+ int get_plugin_index(EditorPlugin *p_editor) const;
+ EditorPlugin *get_selected_plugin() const;
+
+ VBoxContainer *get_control() const;
+
+ void add_main_plugin(EditorPlugin *p_editor);
+ void remove_main_plugin(EditorPlugin *p_editor);
+
+ EditorMainScreen();
+};
+
+#endif // EDITOR_MAIN_SCREEN_H
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index 39291138a6..363d07008a 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -45,6 +45,7 @@
#include "core/string/translation_server.h"
#include "core/version.h"
#include "editor/editor_string_names.h"
+#include "editor/plugins/editor_context_menu_plugin.h"
#include "main/main.h"
#include "scene/3d/bone_attachment_3d.h"
#include "scene/animation/animation_tree.h"
@@ -88,6 +89,7 @@
#include "editor/editor_interface.h"
#include "editor/editor_layouts_dialog.h"
#include "editor/editor_log.h"
+#include "editor/editor_main_screen.h"
#include "editor/editor_native_shader_source_visualizer.h"
#include "editor/editor_paths.h"
#include "editor/editor_properties.h"
@@ -348,19 +350,19 @@ void EditorNode::shortcut_input(const Ref<InputEvent> &p_event) {
if (ED_IS_SHORTCUT("editor/filter_files", p_event)) {
FileSystemDock::get_singleton()->focus_on_filter();
} else if (ED_IS_SHORTCUT("editor/editor_2d", p_event)) {
- editor_select(EDITOR_2D);
+ editor_main_screen->select(EditorMainScreen::EDITOR_2D);
} else if (ED_IS_SHORTCUT("editor/editor_3d", p_event)) {
- editor_select(EDITOR_3D);
+ editor_main_screen->select(EditorMainScreen::EDITOR_3D);
} else if (ED_IS_SHORTCUT("editor/editor_script", p_event)) {
- editor_select(EDITOR_SCRIPT);
+ editor_main_screen->select(EditorMainScreen::EDITOR_SCRIPT);
} else if (ED_IS_SHORTCUT("editor/editor_help", p_event)) {
emit_signal(SNAME("request_help_search"), "");
} else if (ED_IS_SHORTCUT("editor/editor_assetlib", p_event) && AssetLibraryEditorPlugin::is_available()) {
- editor_select(EDITOR_ASSETLIB);
+ editor_main_screen->select(EditorMainScreen::EDITOR_ASSETLIB);
} else if (ED_IS_SHORTCUT("editor/editor_next", p_event)) {
- _editor_select_next();
+ editor_main_screen->select_next();
} else if (ED_IS_SHORTCUT("editor/editor_prev", p_event)) {
- _editor_select_prev();
+ editor_main_screen->select_prev();
} else if (ED_IS_SHORTCUT("editor/command_palette", p_event)) {
_open_command_palette();
} else if (ED_IS_SHORTCUT("editor/toggle_last_opened_bottom_panel", p_event)) {
@@ -489,26 +491,6 @@ void EditorNode::_gdextensions_reloaded() {
EditorHelp::generate_doc();
}
-void EditorNode::_select_default_main_screen_plugin() {
- if (EDITOR_3D < main_editor_buttons.size() && main_editor_buttons[EDITOR_3D]->is_visible()) {
- // If the 3D editor is enabled, use this as the default.
- editor_select(EDITOR_3D);
- return;
- }
-
- // Switch to the first main screen plugin that is enabled. Usually this is
- // 2D, but may be subsequent ones if 2D is disabled in the feature profile.
- for (int i = 0; i < main_editor_buttons.size(); i++) {
- Button *editor_button = main_editor_buttons[i];
- if (editor_button->is_visible()) {
- editor_select(i);
- return;
- }
- }
-
- editor_select(-1);
-}
-
void EditorNode::_update_theme(bool p_skip_creation) {
if (!p_skip_creation) {
theme = EditorThemeManager::generate_theme(theme);
@@ -546,7 +528,7 @@ void EditorNode::_update_theme(bool p_skip_creation) {
main_vbox->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT, Control::PRESET_MODE_MINSIZE, theme->get_constant(SNAME("window_border_margin"), EditorStringName(Editor)));
main_vbox->add_theme_constant_override("separation", theme->get_constant(SNAME("top_bar_separation"), EditorStringName(Editor)));
- scene_root_parent->add_theme_style_override(SceneStringName(panel), theme->get_stylebox(SNAME("Content"), EditorStringName(EditorStyles)));
+ editor_main_screen->add_theme_style_override(SceneStringName(panel), theme->get_stylebox(SNAME("Content"), EditorStringName(EditorStyles)));
bottom_panel->add_theme_style_override(SceneStringName(panel), theme->get_stylebox(SNAME("BottomPanel"), EditorStringName(EditorStyles)));
distraction_free->set_icon(theme->get_icon(SNAME("DistractionFree"), EditorStringName(EditorIcons)));
distraction_free->add_theme_style_override(SceneStringName(pressed), theme->get_stylebox(CoreStringName(normal), "FlatMenuButton"));
@@ -560,18 +542,6 @@ void EditorNode::_update_theme(bool p_skip_creation) {
bottom_panel->add_theme_style_override(SceneStringName(panel), theme->get_stylebox(SNAME("BottomPanelDebuggerOverride"), EditorStringName(EditorStyles)));
}
- for (int i = 0; i < main_editor_buttons.size(); i++) {
- Button *tb = main_editor_buttons[i];
- EditorPlugin *p_editor = editor_table[i];
- Ref<Texture2D> icon = p_editor->get_icon();
-
- if (icon.is_valid()) {
- tb->set_icon(icon);
- } else if (theme->has_icon(p_editor->get_name(), EditorStringName(EditorIcons))) {
- tb->set_icon(theme->get_icon(p_editor->get_name(), EditorStringName(EditorIcons)));
- }
- }
-
_update_renderer_color();
}
@@ -748,8 +718,6 @@ void EditorNode::_notification(int p_what) {
feature_profile_manager->notify_changed();
- _select_default_main_screen_plugin();
-
// Save the project after opening to mark it as last modified, except in headless mode.
if (DisplayServer::get_singleton()->window_can_draw()) {
ProjectSettings::get_singleton()->save();
@@ -1293,38 +1261,10 @@ void EditorNode::_node_renamed() {
}
}
-void EditorNode::_editor_select_next() {
- int editor = _get_current_main_editor();
-
- do {
- if (editor == editor_table.size() - 1) {
- editor = 0;
- } else {
- editor++;
- }
- } while (!main_editor_buttons[editor]->is_visible());
-
- editor_select(editor);
-}
-
void EditorNode::_open_command_palette() {
command_palette->open_popup();
}
-void EditorNode::_editor_select_prev() {
- int editor = _get_current_main_editor();
-
- do {
- if (editor == 0) {
- editor = editor_table.size() - 1;
- } else {
- editor--;
- }
- } while (!main_editor_buttons[editor]->is_visible());
-
- editor_select(editor);
-}
-
Error EditorNode::load_resource(const String &p_resource, bool p_ignore_broken_deps) {
dependency_errors.clear();
@@ -2645,16 +2585,11 @@ void EditorNode::_edit_current(bool p_skip_foreign, bool p_skip_inspector_update
if (!inspector_only) {
EditorPlugin *main_plugin = editor_data.get_handling_main_editor(current_obj);
- int plugin_index = 0;
- for (; plugin_index < editor_table.size(); plugin_index++) {
- if (editor_table[plugin_index] == main_plugin) {
- if (!main_editor_buttons[plugin_index]->is_visible()) {
- main_plugin = nullptr; // If button is not visible, then no plugin is active.
- }
-
- break;
- }
+ int plugin_index = editor_main_screen->get_plugin_index(main_plugin);
+ if (main_plugin && plugin_index >= 0 && !editor_main_screen->is_button_enabled(plugin_index)) {
+ main_plugin = nullptr;
}
+ EditorPlugin *editor_plugin_screen = editor_main_screen->get_selected_plugin();
ObjectID editor_owner_id = editor_owner->get_instance_id();
if (main_plugin && !skip_main_plugin) {
@@ -2671,7 +2606,7 @@ void EditorNode::_edit_current(bool p_skip_foreign, bool p_skip_inspector_update
editor_plugin_screen->edit(nullptr);
active_plugins[editor_owner_id].erase(editor_plugin_screen);
// Update screen main_plugin.
- editor_select(plugin_index);
+ editor_main_screen->select(plugin_index);
main_plugin->edit(current_obj);
} else {
editor_plugin_screen->edit(current_obj);
@@ -3294,9 +3229,9 @@ void EditorNode::_screenshot(bool p_use_utc) {
}
void EditorNode::_save_screenshot(NodePath p_path) {
- Control *editor_main_screen = EditorInterface::get_singleton()->get_editor_main_screen();
- ERR_FAIL_NULL_MSG(editor_main_screen, "Cannot get the editor main screen control.");
- Viewport *viewport = editor_main_screen->get_viewport();
+ Control *main_screen_control = editor_main_screen->get_control();
+ ERR_FAIL_NULL_MSG(main_screen_control, "Cannot get the editor main screen control.");
+ Viewport *viewport = main_screen_control->get_viewport();
ERR_FAIL_NULL_MSG(viewport, "Cannot get a viewport from the editor main screen.");
Ref<ViewportTexture> texture = viewport->get_texture();
ERR_FAIL_COND_MSG(texture.is_null(), "Cannot get a viewport texture from the editor main screen.");
@@ -3501,97 +3436,9 @@ void EditorNode::_update_file_menu_closed() {
file_menu->set_item_disabled(file_menu->get_item_index(FILE_OPEN_PREV), false);
}
-VBoxContainer *EditorNode::get_main_screen_control() {
- return main_screen_vbox;
-}
-
-void EditorNode::editor_select(int p_which) {
- static bool selecting = false;
- if (selecting || changing_scene) {
- return;
- }
-
- ERR_FAIL_INDEX(p_which, editor_table.size());
-
- if (!main_editor_buttons[p_which]->is_visible()) { // Button hidden, no editor.
- return;
- }
-
- selecting = true;
-
- for (int i = 0; i < main_editor_buttons.size(); i++) {
- main_editor_buttons[i]->set_pressed(i == p_which);
- }
-
- selecting = false;
-
- EditorPlugin *new_editor = editor_table[p_which];
- ERR_FAIL_NULL(new_editor);
-
- if (editor_plugin_screen == new_editor) {
- return;
- }
-
- if (editor_plugin_screen) {
- editor_plugin_screen->make_visible(false);
- }
-
- editor_plugin_screen = new_editor;
- editor_plugin_screen->make_visible(true);
- editor_plugin_screen->selected_notify();
-
- int plugin_count = editor_data.get_editor_plugin_count();
- for (int i = 0; i < plugin_count; i++) {
- editor_data.get_editor_plugin(i)->notify_main_screen_changed(editor_plugin_screen->get_name());
- }
-
- if (EDITOR_GET("interface/editor/separate_distraction_mode")) {
- if (p_which == EDITOR_SCRIPT) {
- set_distraction_free_mode(script_distraction_free);
- } else {
- set_distraction_free_mode(scene_distraction_free);
- }
- }
-}
-
-void EditorNode::select_editor_by_name(const String &p_name) {
- ERR_FAIL_COND(p_name.is_empty());
-
- for (int i = 0; i < main_editor_buttons.size(); i++) {
- if (main_editor_buttons[i]->get_text() == p_name) {
- editor_select(i);
- return;
- }
- }
-
- ERR_FAIL_MSG("The editor name '" + p_name + "' was not found.");
-}
-
void EditorNode::add_editor_plugin(EditorPlugin *p_editor, bool p_config_changed) {
if (p_editor->has_main_screen()) {
- Button *tb = memnew(Button);
- tb->set_toggle_mode(true);
- tb->set_theme_type_variation("MainScreenButton");
- tb->set_name(p_editor->get_name());
- tb->set_text(p_editor->get_name());
-
- Ref<Texture2D> icon = p_editor->get_icon();
- if (icon.is_null() && singleton->theme->has_icon(p_editor->get_name(), EditorStringName(EditorIcons))) {
- icon = singleton->theme->get_icon(p_editor->get_name(), EditorStringName(EditorIcons));
- }
- if (icon.is_valid()) {
- tb->set_icon(icon);
- // Make sure the control is updated if the icon is reimported.
- icon->connect_changed(callable_mp((Control *)tb, &Control::update_minimum_size));
- }
-
- tb->connect(SceneStringName(pressed), callable_mp(singleton, &EditorNode::editor_select).bind(singleton->main_editor_buttons.size()));
-
- singleton->main_editor_buttons.push_back(tb);
- singleton->main_editor_button_hb->add_child(tb);
- singleton->editor_table.push_back(p_editor);
-
- singleton->distraction_free->move_to_front();
+ singleton->editor_main_screen->add_main_plugin(p_editor);
}
singleton->editor_data.add_editor_plugin(p_editor);
singleton->add_child(p_editor);
@@ -3602,29 +3449,7 @@ void EditorNode::add_editor_plugin(EditorPlugin *p_editor, bool p_config_changed
void EditorNode::remove_editor_plugin(EditorPlugin *p_editor, bool p_config_changed) {
if (p_editor->has_main_screen()) {
- // Remove the main editor button and update the bindings of
- // all buttons behind it to point to the correct main window.
- for (int i = singleton->main_editor_buttons.size() - 1; i >= 0; i--) {
- if (p_editor->get_name() == singleton->main_editor_buttons[i]->get_text()) {
- if (singleton->main_editor_buttons[i]->is_pressed()) {
- singleton->editor_select(EDITOR_SCRIPT);
- }
-
- memdelete(singleton->main_editor_buttons[i]);
- singleton->main_editor_buttons.remove_at(i);
-
- break;
- } else {
- singleton->main_editor_buttons[i]->disconnect(SceneStringName(pressed), callable_mp(singleton, &EditorNode::editor_select));
- singleton->main_editor_buttons[i]->connect(SceneStringName(pressed), callable_mp(singleton, &EditorNode::editor_select).bind(i - 1));
- }
- }
-
- if (singleton->editor_plugin_screen == p_editor) {
- singleton->editor_plugin_screen = nullptr;
- }
-
- singleton->editor_table.erase(p_editor);
+ singleton->editor_main_screen->remove_main_plugin(p_editor);
}
p_editor->make_visible(false);
p_editor->clear();
@@ -3848,19 +3673,8 @@ void EditorNode::set_edited_scene_root(Node *p_scene, bool p_auto_add) {
}
}
-int EditorNode::_get_current_main_editor() {
- for (int i = 0; i < editor_table.size(); i++) {
- if (editor_table[i] == editor_plugin_screen) {
- return i;
- }
- }
-
- return 0;
-}
-
Dictionary EditorNode::_get_main_scene_state() {
Dictionary state;
- state["main_tab"] = _get_current_main_editor();
state["scene_tree_offset"] = SceneTreeDock::get_singleton()->get_tree_editor()->get_scene_tree()->get_vscroll_bar()->get_value();
state["property_edit_offset"] = InspectorDock::get_inspector_singleton()->get_scroll_offset();
state["node_filter"] = SceneTreeDock::get_singleton()->get_filter();
@@ -3874,32 +3688,19 @@ void EditorNode::_set_main_scene_state(Dictionary p_state, Node *p_for_scene) {
changing_scene = false;
- int current_tab = -1;
- for (int i = 0; i < editor_table.size(); i++) {
- if (editor_plugin_screen == editor_table[i]) {
- current_tab = i;
- break;
- }
- }
-
- if (p_state.has("editor_index")) {
- int index = p_state["editor_index"];
- if (current_tab < 2) { // If currently in spatial/2d, only switch to spatial/2d. If currently in script, stay there.
- if (index < 2 || !get_edited_scene()) {
- editor_select(index);
- }
- }
- }
-
if (get_edited_scene()) {
+ int current_tab = editor_main_screen->get_selected_index();
if (current_tab < 2) {
- Node *editor_node = SceneTreeDock::get_singleton()->get_tree_editor()->get_selected();
- editor_node = editor_node == nullptr ? get_edited_scene() : editor_node;
+ // Switch between 2D and 3D if currently in 2D or 3D.
+ Node *selected_node = SceneTreeDock::get_singleton()->get_tree_editor()->get_selected();
+ if (!selected_node) {
+ selected_node = get_edited_scene();
+ }
- if (Object::cast_to<CanvasItem>(editor_node)) {
- editor_select(EDITOR_2D);
- } else if (Object::cast_to<Node3D>(editor_node)) {
- editor_select(EDITOR_3D);
+ if (Object::cast_to<CanvasItem>(selected_node)) {
+ editor_main_screen->select(EditorMainScreen::EDITOR_2D);
+ } else if (Object::cast_to<Node3D>(selected_node)) {
+ editor_main_screen->select(EditorMainScreen::EDITOR_3D);
}
}
}
@@ -5357,18 +5158,7 @@ void EditorNode::_save_central_editor_layout_to_config(Ref<ConfigFile> p_config_
// Main editor (plugin).
- int selected_main_editor_idx = -1;
- for (int i = 0; i < main_editor_buttons.size(); i++) {
- if (main_editor_buttons[i]->is_pressed()) {
- selected_main_editor_idx = i;
- break;
- }
- }
- if (selected_main_editor_idx != -1) {
- p_config_file->set_value(EDITOR_NODE_CONFIG_SECTION, "selected_main_editor_idx", selected_main_editor_idx);
- } else {
- p_config_file->set_value(EDITOR_NODE_CONFIG_SECTION, "selected_main_editor_idx", Variant());
- }
+ editor_main_screen->save_layout_to_config(p_config_file, EDITOR_NODE_CONFIG_SECTION);
}
void EditorNode::_load_central_editor_layout_from_config(Ref<ConfigFile> p_config_file) {
@@ -5390,12 +5180,7 @@ void EditorNode::_load_central_editor_layout_from_config(Ref<ConfigFile> p_confi
// Main editor (plugin).
- if (p_config_file->has_section_key(EDITOR_NODE_CONFIG_SECTION, "selected_main_editor_idx")) {
- int selected_main_editor_idx = p_config_file->get_value(EDITOR_NODE_CONFIG_SECTION, "selected_main_editor_idx");
- if (selected_main_editor_idx >= 0 && selected_main_editor_idx < main_editor_buttons.size()) {
- callable_mp(this, &EditorNode::editor_select).call_deferred(selected_main_editor_idx);
- }
- }
+ editor_main_screen->load_layout_from_config(p_config_file, EDITOR_NODE_CONFIG_SECTION);
}
void EditorNode::_save_window_settings_to_config(Ref<ConfigFile> p_layout, const String &p_section) {
@@ -5716,15 +5501,9 @@ void EditorNode::_cancel_close_scene_tab() {
void EditorNode::_toggle_distraction_free_mode() {
if (EDITOR_GET("interface/editor/separate_distraction_mode")) {
- int screen = -1;
- for (int i = 0; i < editor_table.size(); i++) {
- if (editor_plugin_screen == editor_table[i]) {
- screen = i;
- break;
- }
- }
+ int screen = editor_main_screen->get_selected_index();
- if (screen == EDITOR_SCRIPT) {
+ if (screen == EditorMainScreen::EDITOR_SCRIPT) {
script_distraction_free = !script_distraction_free;
set_distraction_free_mode(script_distraction_free);
} else {
@@ -5736,6 +5515,18 @@ void EditorNode::_toggle_distraction_free_mode() {
}
}
+void EditorNode::update_distraction_free_mode() {
+ if (!EDITOR_GET("interface/editor/separate_distraction_mode")) {
+ return;
+ }
+ int screen = editor_main_screen->get_selected_index();
+ if (screen == EditorMainScreen::EDITOR_SCRIPT) {
+ set_distraction_free_mode(script_distraction_free);
+ } else {
+ set_distraction_free_mode(scene_distraction_free);
+ }
+}
+
void EditorNode::set_distraction_free_mode(bool p_enter) {
distraction_free->set_pressed(p_enter);
@@ -6625,25 +6416,20 @@ void EditorNode::_feature_profile_changed() {
editor_dock_manager->set_dock_enabled(ImportDock::get_singleton(), !fs_dock_disabled && !profile->is_feature_disabled(EditorFeatureProfile::FEATURE_IMPORT_DOCK));
editor_dock_manager->set_dock_enabled(history_dock, !profile->is_feature_disabled(EditorFeatureProfile::FEATURE_HISTORY_DOCK));
- main_editor_buttons[EDITOR_3D]->set_visible(!profile->is_feature_disabled(EditorFeatureProfile::FEATURE_3D));
- main_editor_buttons[EDITOR_SCRIPT]->set_visible(!profile->is_feature_disabled(EditorFeatureProfile::FEATURE_SCRIPT));
+ editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_3D, !profile->is_feature_disabled(EditorFeatureProfile::FEATURE_3D));
+ editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_SCRIPT, !profile->is_feature_disabled(EditorFeatureProfile::FEATURE_SCRIPT));
if (AssetLibraryEditorPlugin::is_available()) {
- main_editor_buttons[EDITOR_ASSETLIB]->set_visible(!profile->is_feature_disabled(EditorFeatureProfile::FEATURE_ASSET_LIB));
- }
- if ((profile->is_feature_disabled(EditorFeatureProfile::FEATURE_3D) && singleton->main_editor_buttons[EDITOR_3D]->is_pressed()) ||
- (profile->is_feature_disabled(EditorFeatureProfile::FEATURE_SCRIPT) && singleton->main_editor_buttons[EDITOR_SCRIPT]->is_pressed()) ||
- (AssetLibraryEditorPlugin::is_available() && profile->is_feature_disabled(EditorFeatureProfile::FEATURE_ASSET_LIB) && singleton->main_editor_buttons[EDITOR_ASSETLIB]->is_pressed())) {
- editor_select(EDITOR_2D);
+ editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_ASSETLIB, !profile->is_feature_disabled(EditorFeatureProfile::FEATURE_ASSET_LIB));
}
} else {
editor_dock_manager->set_dock_enabled(ImportDock::get_singleton(), true);
editor_dock_manager->set_dock_enabled(NodeDock::get_singleton(), true);
editor_dock_manager->set_dock_enabled(FileSystemDock::get_singleton(), true);
editor_dock_manager->set_dock_enabled(history_dock, true);
- main_editor_buttons[EDITOR_3D]->set_visible(true);
- main_editor_buttons[EDITOR_SCRIPT]->set_visible(true);
+ editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_3D, true);
+ editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_SCRIPT, true);
if (AssetLibraryEditorPlugin::is_available()) {
- main_editor_buttons[EDITOR_ASSETLIB]->set_visible(true);
+ editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_ASSETLIB, true);
}
}
}
@@ -6999,6 +6785,8 @@ EditorNode::EditorNode() {
EditorFileSystem *efs = memnew(EditorFileSystem);
add_child(efs);
+ EditorContextMenuPluginManager::create();
+
// Used for previews.
FileDialog::get_icon_func = _file_dialog_get_icon;
FileDialog::register_func = _file_dialog_register;
@@ -7178,12 +6966,11 @@ EditorNode::EditorNode() {
scene_tabs->add_extra_button(distraction_free);
distraction_free->connect(SceneStringName(pressed), callable_mp(this, &EditorNode::_toggle_distraction_free_mode));
- scene_root_parent = memnew(PanelContainer);
- scene_root_parent->set_custom_minimum_size(Size2(0, 80) * EDSCALE);
- scene_root_parent->add_theme_style_override(SceneStringName(panel), theme->get_stylebox(SNAME("Content"), EditorStringName(EditorStyles)));
- scene_root_parent->set_draw_behind_parent(true);
- srt->add_child(scene_root_parent);
- scene_root_parent->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ editor_main_screen = memnew(EditorMainScreen);
+ editor_main_screen->set_custom_minimum_size(Size2(0, 80) * EDSCALE);
+ editor_main_screen->set_draw_behind_parent(true);
+ srt->add_child(editor_main_screen);
+ editor_main_screen->set_v_size_flags(Control::SIZE_EXPAND_FILL);
scene_root = memnew(SubViewport);
scene_root->set_embedding_subwindows(true);
@@ -7191,12 +6978,6 @@ EditorNode::EditorNode() {
scene_root->set_disable_input(true);
scene_root->set_as_audio_listener_2d(true);
- main_screen_vbox = memnew(VBoxContainer);
- main_screen_vbox->set_name("MainScreen");
- main_screen_vbox->set_v_size_flags(Control::SIZE_EXPAND_FILL);
- main_screen_vbox->add_theme_constant_override("separation", 0);
- scene_root_parent->add_child(main_screen_vbox);
-
bool global_menu = !bool(EDITOR_GET("interface/editor/use_embedded_menu")) && NativeMenu::get_singleton()->has_feature(NativeMenu::FEATURE_GLOBAL_MENU);
bool can_expand = bool(EDITOR_GET("interface/editor/expand_to_title")) && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_EXTEND_TO_TITLE);
@@ -7390,8 +7171,9 @@ EditorNode::EditorNode() {
left_spacer->add_child(project_title);
}
- main_editor_button_hb = memnew(HBoxContainer);
+ HBoxContainer *main_editor_button_hb = memnew(HBoxContainer);
main_editor_button_hb->set_mouse_filter(Control::MOUSE_FILTER_STOP);
+ editor_main_screen->set_button_container(main_editor_button_hb);
title_bar->add_child(main_editor_button_hb);
// Options are added and handled by DebuggerEditorPlugin.
@@ -7864,7 +7646,6 @@ EditorNode::EditorNode() {
update_spinner_step_msec = OS::get_singleton()->get_ticks_msec();
update_spinner_step_frame = Engine::get_singleton()->get_frames_drawn();
- editor_plugin_screen = nullptr;
editor_plugins_over = memnew(EditorPluginList);
editor_plugins_force_over = memnew(EditorPluginList);
editor_plugins_force_input_forwarding = memnew(EditorPluginList);
@@ -8006,6 +7787,7 @@ EditorNode::~EditorNode() {
EditorInspector::cleanup_plugins();
EditorTranslationParser::get_singleton()->clean_parsers();
ResourceImporterScene::clean_up_importer_plugins();
+ EditorContextMenuPluginManager::cleanup();
remove_print_handler(&print_handler);
EditorHelp::cleanup_doc();
diff --git a/editor/editor_node.h b/editor/editor_node.h
index f1b50cf3fa..63d7d58d50 100644
--- a/editor/editor_node.h
+++ b/editor/editor_node.h
@@ -42,27 +42,17 @@ typedef void (*EditorPluginInitializeCallback)();
typedef bool (*EditorBuildCallback)();
class AcceptDialog;
-class CenterContainer;
-class CheckBox;
class ColorPicker;
class ConfirmationDialog;
class Control;
class FileDialog;
-class HBoxContainer;
-class HSplitContainer;
-class LinkButton;
class MenuBar;
class MenuButton;
-class Node2D;
class OptionButton;
class Panel;
class PanelContainer;
-class PopupPanel;
class RichTextLabel;
class SubViewport;
-class TabBar;
-class TabContainer;
-class TextureRect;
class TextureProgressBar;
class Tree;
class VBoxContainer;
@@ -83,44 +73,33 @@ class EditorCommandPalette;
class EditorDockManager;
class EditorExport;
class EditorExportPreset;
-class EditorExtensionManager;
class EditorFeatureProfileManager;
class EditorFileDialog;
class EditorFolding;
-class EditorInspector;
class EditorLayoutsDialog;
class EditorLog;
+class EditorMainScreen;
class EditorNativeShaderSourceVisualizer;
class EditorPluginList;
class EditorQuickOpen;
-class EditorPropertyResource;
class EditorResourcePreview;
class EditorResourceConversionPlugin;
class EditorRunBar;
-class EditorRunNative;
class EditorSceneTabs;
class EditorSelectionHistory;
class EditorSettingsDialog;
class EditorTitleBar;
-class EditorToaster;
-class EditorUndoRedoManager;
class ExportTemplateManager;
class FBXImporterManager;
class FileSystemDock;
class HistoryDock;
-class ImportDock;
-class NodeDock;
class OrphanResourcesDialog;
-class PluginConfigDialog;
class ProgressDialog;
class ProjectExportDialog;
class ProjectSettingsEditor;
-class RunSettingsDialog;
class SceneImportSettingsDialog;
-class ScriptCreateDialog;
class SurfaceUpgradeTool;
class SurfaceUpgradeDialog;
-class WindowWrapper;
struct EditorProgress {
String task;
@@ -135,13 +114,6 @@ class EditorNode : public Node {
GDCLASS(EditorNode, Node);
public:
- enum EditorTable {
- EDITOR_2D = 0,
- EDITOR_3D,
- EDITOR_SCRIPT,
- EDITOR_ASSETLIB
- };
-
enum SceneNameCasing {
SCENE_NAME_CASING_AUTO,
SCENE_NAME_CASING_PASCAL_CASE,
@@ -286,7 +258,6 @@ private:
EditorExport *editor_export = nullptr;
EditorLog *log = nullptr;
EditorNativeShaderSourceVisualizer *native_shader_source_visualizer = nullptr;
- EditorPlugin *editor_plugin_screen = nullptr;
EditorPluginList *editor_plugins_force_input_forwarding = nullptr;
EditorPluginList *editor_plugins_force_over = nullptr;
EditorPluginList *editor_plugins_over = nullptr;
@@ -308,7 +279,6 @@ private:
HashMap<ObjectID, HashSet<EditorPlugin *>> active_plugins;
bool is_main_screen_editing = false;
- PanelContainer *scene_root_parent = nullptr;
Control *gui_base = nullptr;
VBoxContainer *main_vbox = nullptr;
OptionButton *renderer = nullptr;
@@ -349,7 +319,6 @@ private:
Control *right_menu_spacer = nullptr;
EditorTitleBar *title_bar = nullptr;
EditorRunBar *project_run_bar = nullptr;
- VBoxContainer *main_screen_vbox = nullptr;
MenuBar *main_menu = nullptr;
PopupMenu *apple_menu = nullptr;
PopupMenu *file_menu = nullptr;
@@ -423,9 +392,7 @@ private:
String current_path;
MenuButton *update_spinner = nullptr;
- HBoxContainer *main_editor_button_hb = nullptr;
- Vector<Button *> main_editor_buttons;
- Vector<EditorPlugin *> editor_table;
+ EditorMainScreen *editor_main_screen = nullptr;
AudioStreamPreviewGenerator *audio_preview_gen = nullptr;
ProgressDialog *progress_dialog = nullptr;
@@ -575,8 +542,6 @@ private:
void _sources_changed(bool p_exist);
void _node_renamed();
- void _editor_select_next();
- void _editor_select_prev();
void _save_editor_states(const String &p_file, int p_idx = -1);
void _load_editor_plugin_states_from_config(const Ref<ConfigFile> &p_config_file);
void _update_title();
@@ -649,8 +614,6 @@ private:
Dictionary _get_main_scene_state();
void _set_main_scene_state(Dictionary p_state, Node *p_for_scene);
- int _get_current_main_editor();
-
void _save_editor_layout();
void _load_editor_layout();
@@ -688,7 +651,6 @@ private:
void _pick_main_scene_custom_action(const String &p_custom_action_name);
void _immediate_dialog_confirmed();
- void _select_default_main_screen_plugin();
void _begin_first_scan();
@@ -707,9 +669,6 @@ public:
void init_plugins();
void _on_plugin_ready(Object *p_script, const String &p_activate_name);
- void editor_select(int p_which);
- void set_visible_editor(EditorTable p_table) { editor_select(p_table); }
-
bool call_build();
// This is a very naive estimation, but we need something now. Will be reworked later.
@@ -724,6 +683,7 @@ public:
static EditorTitleBar *get_title_bar() { return singleton->title_bar; }
static VSplitContainer *get_top_split() { return singleton->top_split; }
static EditorBottomPanel *get_bottom_panel() { return singleton->bottom_panel; }
+ static EditorMainScreen *get_editor_main_screen() { return singleton->editor_main_screen; }
static String adjust_scene_name_casing(const String &p_root_name);
static String adjust_script_name_casing(const String &p_file_name, ScriptLanguage::ScriptNameCasing p_auto_casing);
@@ -755,7 +715,6 @@ public:
static void cleanup();
- EditorPlugin *get_editor_plugin_screen() { return editor_plugin_screen; }
EditorPluginList *get_editor_plugins_force_input_forwarding() { return editor_plugins_force_input_forwarding; }
EditorPluginList *get_editor_plugins_force_over() { return editor_plugins_force_over; }
EditorPluginList *get_editor_plugins_over() { return editor_plugins_over; }
@@ -769,6 +728,7 @@ public:
void new_inherited_scene() { _menu_option_confirm(FILE_NEW_INHERITED_SCENE, false); }
+ void update_distraction_free_mode();
void set_distraction_free_mode(bool p_enter);
bool is_distraction_free_mode_enabled() const;
@@ -791,8 +751,6 @@ public:
void push_node_item(Node *p_node);
void hide_unused_editors(const Object *p_editing_owner = nullptr);
- void select_editor_by_name(const String &p_name);
-
void open_request(const String &p_path);
void edit_foreign_resource(Ref<Resource> p_resource);
@@ -802,7 +760,6 @@ public:
bool is_changing_scene() const;
- VBoxContainer *get_main_screen_control();
SubViewport *get_scene_root() { return scene_root; } // Root of the scene being edited.
void set_edited_scene(Node *p_scene);
diff --git a/editor/editor_paths.cpp b/editor/editor_paths.cpp
index 7f24e8fd2e..ff869f8a8a 100644
--- a/editor/editor_paths.cpp
+++ b/editor/editor_paths.cpp
@@ -245,7 +245,7 @@ EditorPaths::EditorPaths() {
}
}
- // Check that the project data directory '.gdignore' file exists
+ // Check that the project data directory `.gdignore` file exists.
String project_data_gdignore_file_path = project_data_dir.path_join(".gdignore");
if (!FileAccess::exists(project_data_gdignore_file_path)) {
// Add an empty .gdignore file to avoid scan.
@@ -253,10 +253,26 @@ EditorPaths::EditorPaths() {
if (f.is_valid()) {
f->store_line("");
} else {
- ERR_PRINT("Failed to create file " + project_data_gdignore_file_path);
+ ERR_PRINT("Failed to create file " + project_data_gdignore_file_path.quote() + ".");
}
}
+ // Check that `.editorconfig` file exists.
+ String project_editorconfig_path = "res://.editorconfig";
+ if (!FileAccess::exists(project_editorconfig_path)) {
+ Ref<FileAccess> f = FileAccess::open(project_editorconfig_path, FileAccess::WRITE);
+ if (f.is_valid()) {
+ f->store_line("root = true");
+ f->store_line("");
+ f->store_line("[*]");
+ f->store_line("charset = utf-8");
+ f->close();
+ } else {
+ ERR_PRINT("Failed to create file " + project_editorconfig_path.quote() + ".");
+ }
+ FileAccess::set_hidden_attribute(project_editorconfig_path, true);
+ }
+
Engine::get_singleton()->set_shader_cache_path(project_data_dir);
// Editor metadata dir.
diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp
index 6e375dc1fc..2e46068e07 100644
--- a/editor/editor_properties.cpp
+++ b/editor/editor_properties.cpp
@@ -81,7 +81,6 @@ void EditorPropertyText::_text_submitted(const String &p_string) {
}
if (text->has_focus()) {
- text->release_focus();
_text_changed(p_string);
}
}
diff --git a/editor/export/editor_export_platform.cpp b/editor/export/editor_export_platform.cpp
index 7ad589a58d..983f4ee36a 100644
--- a/editor/export/editor_export_platform.cpp
+++ b/editor/export/editor_export_platform.cpp
@@ -270,7 +270,7 @@ Error EditorExportPlatform::_save_zip_file(void *p_userdata, const String &p_pat
Ref<ImageTexture> EditorExportPlatform::get_option_icon(int p_index) const {
Ref<Theme> theme = EditorNode::get_singleton()->get_editor_theme();
ERR_FAIL_COND_V(theme.is_null(), Ref<ImageTexture>());
- if (EditorNode::get_singleton()->get_main_screen_control()->is_layout_rtl()) {
+ if (EditorNode::get_singleton()->get_gui_base()->is_layout_rtl()) {
return theme->get_icon(SNAME("PlayBackwards"), EditorStringName(EditorIcons));
} else {
return theme->get_icon(SNAME("Play"), EditorStringName(EditorIcons));
diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp
index f7e81b329c..68d74236bb 100644
--- a/editor/filesystem_dock.cpp
+++ b/editor/filesystem_dock.cpp
@@ -2557,9 +2557,12 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected
String dir = ProjectSettings::get_singleton()->globalize_path(fpath);
ScriptEditor::get_singleton()->open_text_file_create_dialog(dir);
} break;
- default:
- EditorNode::get_editor_data().filesystem_options_pressed(EditorData::CONTEXT_SLOT_FILESYSTEM, p_option, p_selected);
- break;
+
+ default: {
+ if (!EditorContextMenuPluginManager::get_singleton()->activate_custom_option(EditorContextMenuPlugin::CONTEXT_SLOT_FILESYSTEM, p_option, p_selected)) {
+ EditorContextMenuPluginManager::get_singleton()->activate_custom_option(EditorContextMenuPlugin::CONTEXT_SLOT_FILESYSTEM_CREATE, p_option, p_selected);
+ }
+ }
}
}
@@ -3183,7 +3186,7 @@ void FileSystemDock::_file_and_folders_fill_popup(PopupMenu *p_popup, const Vect
new_menu->add_icon_item(get_editor_theme_icon(SNAME("Object")), TTR("Resource..."), FILE_NEW_RESOURCE);
new_menu->add_icon_item(get_editor_theme_icon(SNAME("TextFile")), TTR("TextFile..."), FILE_NEW_TEXTFILE);
- EditorNode::get_editor_data().add_options_from_plugins(new_menu, EditorData::CONTEXT_SUBMENU_SLOT_FILESYSTEM_CREATE, p_paths);
+ EditorContextMenuPluginManager::get_singleton()->add_options_from_plugins(new_menu, EditorContextMenuPlugin::CONTEXT_SLOT_FILESYSTEM_CREATE, p_paths);
p_popup->add_separator();
}
@@ -3323,8 +3326,7 @@ void FileSystemDock::_file_and_folders_fill_popup(PopupMenu *p_popup, const Vect
current_path = fpath;
}
-
- EditorNode::get_editor_data().add_options_from_plugins(p_popup, EditorData::CONTEXT_SLOT_FILESYSTEM, p_paths);
+ EditorContextMenuPluginManager::get_singleton()->add_options_from_plugins(p_popup, EditorContextMenuPlugin::CONTEXT_SLOT_FILESYSTEM, p_paths);
}
void FileSystemDock::_tree_rmb_select(const Vector2 &p_pos, MouseButton p_button) {
@@ -3559,11 +3561,16 @@ void FileSystemDock::_tree_gui_input(Ref<InputEvent> p_event) {
} else if (ED_IS_SHORTCUT("editor/open_search", p_event)) {
focus_on_filter();
} else {
- int match_option = EditorNode::get_editor_data().match_context_menu_shortcut(EditorData::CONTEXT_SLOT_FILESYSTEM, p_event);
- if (match_option) {
- _tree_rmb_option(match_option);
+ Callable custom_callback = EditorContextMenuPluginManager::get_singleton()->match_custom_shortcut(EditorContextMenuPlugin::CONTEXT_SLOT_FILESYSTEM, p_event);
+ if (!custom_callback.is_valid()) {
+ custom_callback = EditorContextMenuPluginManager::get_singleton()->match_custom_shortcut(EditorContextMenuPlugin::CONTEXT_SLOT_FILESYSTEM_CREATE, p_event);
+ }
+
+ if (custom_callback.is_valid()) {
+ EditorContextMenuPluginManager::get_singleton()->invoke_callback(custom_callback, _tree_get_selected(false));
+ } else {
+ return;
}
- return;
}
accept_event();
@@ -3631,7 +3638,16 @@ void FileSystemDock::_file_list_gui_input(Ref<InputEvent> p_event) {
} else if (ED_IS_SHORTCUT("editor/open_search", p_event)) {
focus_on_filter();
} else {
- return;
+ Callable custom_callback = EditorContextMenuPluginManager::get_singleton()->match_custom_shortcut(EditorContextMenuPlugin::CONTEXT_SLOT_FILESYSTEM, p_event);
+ if (!custom_callback.is_valid()) {
+ custom_callback = EditorContextMenuPluginManager::get_singleton()->match_custom_shortcut(EditorContextMenuPlugin::CONTEXT_SLOT_FILESYSTEM_CREATE, p_event);
+ }
+
+ if (custom_callback.is_valid()) {
+ EditorContextMenuPluginManager::get_singleton()->invoke_callback(custom_callback, files->get_selected_items());
+ } else {
+ return;
+ }
}
accept_event();
diff --git a/editor/inspector_dock.cpp b/editor/inspector_dock.cpp
index dc07403213..3a3598c0b9 100644
--- a/editor/inspector_dock.cpp
+++ b/editor/inspector_dock.cpp
@@ -30,6 +30,7 @@
#include "inspector_dock.h"
+#include "editor/editor_main_screen.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
@@ -97,7 +98,7 @@ void InspectorDock::_menu_option_confirm(int p_option, bool p_confirmed) {
case OBJECT_REQUEST_HELP: {
if (current) {
- EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT);
+ EditorNode::get_singleton()->get_editor_main_screen()->select(EditorMainScreen::EDITOR_SCRIPT);
emit_signal(SNAME("request_help"), current->get_class());
}
} break;
diff --git a/editor/plugins/asset_library_editor_plugin.cpp b/editor/plugins/asset_library_editor_plugin.cpp
index e6fe554923..f31346fe67 100644
--- a/editor/plugins/asset_library_editor_plugin.cpp
+++ b/editor/plugins/asset_library_editor_plugin.cpp
@@ -35,6 +35,7 @@
#include "core/io/stream_peer_tls.h"
#include "core/os/keyboard.h"
#include "core/version.h"
+#include "editor/editor_main_screen.h"
#include "editor/editor_node.h"
#include "editor/editor_paths.h"
#include "editor/editor_settings.h"
@@ -1794,7 +1795,7 @@ void AssetLibraryEditorPlugin::make_visible(bool p_visible) {
AssetLibraryEditorPlugin::AssetLibraryEditorPlugin() {
addon_library = memnew(EditorAssetLibrary);
addon_library->set_v_size_flags(Control::SIZE_EXPAND_FILL);
- EditorNode::get_singleton()->get_main_screen_control()->add_child(addon_library);
+ EditorNode::get_singleton()->get_editor_main_screen()->get_control()->add_child(addon_library);
addon_library->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
addon_library->hide();
}
diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp
index 02146c1d07..e9a796dae7 100644
--- a/editor/plugins/canvas_item_editor_plugin.cpp
+++ b/editor/plugins/canvas_item_editor_plugin.cpp
@@ -34,6 +34,7 @@
#include "core/input/input.h"
#include "core/os/keyboard.h"
#include "editor/debugger/editor_debugger_node.h"
+#include "editor/editor_main_screen.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
@@ -5737,7 +5738,7 @@ void CanvasItemEditorPlugin::_notification(int p_what) {
CanvasItemEditorPlugin::CanvasItemEditorPlugin() {
canvas_item_editor = memnew(CanvasItemEditor);
canvas_item_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL);
- EditorNode::get_singleton()->get_main_screen_control()->add_child(canvas_item_editor);
+ EditorNode::get_singleton()->get_editor_main_screen()->get_control()->add_child(canvas_item_editor);
canvas_item_editor->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
canvas_item_editor->hide();
}
diff --git a/editor/plugins/cpu_particles_3d_editor_plugin.cpp b/editor/plugins/cpu_particles_3d_editor_plugin.cpp
index 69f287c134..e0ce330813 100644
--- a/editor/plugins/cpu_particles_3d_editor_plugin.cpp
+++ b/editor/plugins/cpu_particles_3d_editor_plugin.cpp
@@ -208,7 +208,7 @@ void CPUParticles3DEditorPlugin::make_visible(bool p_visible) {
CPUParticles3DEditorPlugin::CPUParticles3DEditorPlugin() {
particles_editor = memnew(CPUParticles3DEditor);
- EditorNode::get_singleton()->get_main_screen_control()->add_child(particles_editor);
+ EditorNode::get_singleton()->get_gui_base()->add_child(particles_editor);
particles_editor->hide();
}
diff --git a/editor/plugins/editor_context_menu_plugin.cpp b/editor/plugins/editor_context_menu_plugin.cpp
index 86d3c0a896..0648327fab 100644
--- a/editor/plugins/editor_context_menu_plugin.cpp
+++ b/editor/plugins/editor_context_menu_plugin.cpp
@@ -32,9 +32,11 @@
#include "core/input/shortcut.h"
#include "editor/editor_node.h"
+#include "editor/editor_string_names.h"
+#include "scene/gui/popup_menu.h"
#include "scene/resources/texture.h"
-void EditorContextMenuPlugin::add_options(const Vector<String> &p_paths) {
+void EditorContextMenuPlugin::get_options(const Vector<String> &p_paths) {
GDVIRTUAL_CALL(_popup_menu, p_paths);
}
@@ -42,24 +44,142 @@ void EditorContextMenuPlugin::add_menu_shortcut(const Ref<Shortcut> &p_shortcut,
context_menu_shortcuts.insert(p_shortcut, p_callable);
}
-void EditorContextMenuPlugin::add_context_menu_item(const String &p_name, const Callable &p_callable, const Ref<Texture2D> &p_texture, const Ref<Shortcut> &p_shortcut) {
+void EditorContextMenuPlugin::add_context_menu_item(const String &p_name, const Callable &p_callable, const Ref<Texture2D> &p_texture) {
ERR_FAIL_COND_MSG(context_menu_items.has(p_name), "Context menu item already registered.");
ERR_FAIL_COND_MSG(context_menu_items.size() == MAX_ITEMS, "Maximum number of context menu items reached.");
+
ContextMenuItem item;
item.item_name = p_name;
item.callable = p_callable;
item.icon = p_texture;
- item.shortcut = p_shortcut;
- item.idx = EditorData::CONTEXT_MENU_ITEM_ID_BASE + start_idx + context_menu_shortcuts.size() + context_menu_items.size();
context_menu_items.insert(p_name, item);
}
-void EditorContextMenuPlugin::clear_context_menu_items() {
- context_menu_items.clear();
+void EditorContextMenuPlugin::add_context_menu_item_from_shortcut(const String &p_name, const Ref<Shortcut> &p_shortcut, const Ref<Texture2D> &p_texture) {
+ Callable *callback = context_menu_shortcuts.getptr(p_shortcut);
+ ERR_FAIL_NULL_MSG(callback, "Shortcut not registered. Use add_menu_shortcut() first.");
+
+ ContextMenuItem item;
+ item.item_name = p_name;
+ item.callable = *callback;
+ item.icon = p_texture;
+ item.shortcut = p_shortcut;
+ context_menu_items.insert(p_name, item);
}
void EditorContextMenuPlugin::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_menu_shortcut", "shortcut", "callback"), &EditorContextMenuPlugin::add_menu_shortcut);
- ClassDB::bind_method(D_METHOD("add_context_menu_item", "name", "callback", "icon", "shortcut"), &EditorContextMenuPlugin::add_context_menu_item, DEFVAL(Ref<Texture2D>()), DEFVAL(Ref<Shortcut>()));
+ ClassDB::bind_method(D_METHOD("add_context_menu_item", "name", "callback", "icon"), &EditorContextMenuPlugin::add_context_menu_item, DEFVAL(Ref<Texture2D>()));
+ ClassDB::bind_method(D_METHOD("add_context_menu_item_from_shortcut", "name", "shortcut", "icon"), &EditorContextMenuPlugin::add_context_menu_item_from_shortcut, DEFVAL(Ref<Texture2D>()));
+
GDVIRTUAL_BIND(_popup_menu, "paths");
+
+ BIND_ENUM_CONSTANT(CONTEXT_SLOT_SCENE_TREE);
+ BIND_ENUM_CONSTANT(CONTEXT_SLOT_FILESYSTEM);
+ BIND_ENUM_CONSTANT(CONTEXT_SLOT_FILESYSTEM_CREATE);
+ BIND_ENUM_CONSTANT(CONTEXT_SLOT_SCRIPT_EDITOR);
+}
+
+void EditorContextMenuPluginManager::add_plugin(EditorContextMenuPlugin::ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin) {
+ ERR_FAIL_COND(p_plugin.is_null());
+ ERR_FAIL_COND(plugin_list.has(p_plugin));
+
+ p_plugin->slot = p_slot;
+ plugin_list.push_back(p_plugin);
+}
+
+void EditorContextMenuPluginManager::remove_plugin(const Ref<EditorContextMenuPlugin> &p_plugin) {
+ ERR_FAIL_COND(p_plugin.is_null());
+ ERR_FAIL_COND(!plugin_list.has(p_plugin));
+
+ plugin_list.erase(p_plugin);
+}
+
+void EditorContextMenuPluginManager::add_options_from_plugins(PopupMenu *p_popup, ContextMenuSlot p_slot, const Vector<String> &p_paths) {
+ bool separator_added = false;
+ const int icon_size = p_popup->get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor));
+ int id = EditorContextMenuPlugin::BASE_ID;
+
+ for (Ref<EditorContextMenuPlugin> &plugin : plugin_list) {
+ if (plugin->slot != p_slot) {
+ continue;
+ }
+ plugin->context_menu_items.clear();
+ plugin->get_options(p_paths);
+
+ HashMap<String, EditorContextMenuPlugin::ContextMenuItem> &items = plugin->context_menu_items;
+ if (items.size() > 0 && !separator_added) {
+ separator_added = true;
+ p_popup->add_separator();
+ }
+
+ for (KeyValue<String, EditorContextMenuPlugin::ContextMenuItem> &E : items) {
+ EditorContextMenuPlugin::ContextMenuItem &item = E.value;
+ item.id = id;
+
+ if (item.icon.is_valid()) {
+ p_popup->add_icon_item(item.icon, item.item_name, id);
+ p_popup->set_item_icon_max_width(-1, icon_size);
+ } else {
+ p_popup->add_item(item.item_name, id);
+ }
+ if (item.shortcut.is_valid()) {
+ p_popup->set_item_shortcut(-1, item.shortcut, true);
+ }
+ id++;
+ }
+ }
+}
+
+Callable EditorContextMenuPluginManager::match_custom_shortcut(EditorContextMenuPlugin::ContextMenuSlot p_slot, const Ref<InputEvent> &p_event) {
+ for (Ref<EditorContextMenuPlugin> &plugin : plugin_list) {
+ if (plugin->slot != p_slot) {
+ continue;
+ }
+
+ for (KeyValue<Ref<Shortcut>, Callable> &E : plugin->context_menu_shortcuts) {
+ if (E.key->matches_event(p_event)) {
+ return E.value;
+ }
+ }
+ }
+ return Callable();
+}
+
+bool EditorContextMenuPluginManager::activate_custom_option(ContextMenuSlot p_slot, int p_option, const Variant &p_arg) {
+ for (Ref<EditorContextMenuPlugin> &plugin : plugin_list) {
+ if (plugin->slot != p_slot) {
+ continue;
+ }
+
+ for (KeyValue<String, EditorContextMenuPlugin::ContextMenuItem> &E : plugin->context_menu_items) {
+ if (E.value.id == p_option) {
+ invoke_callback(E.value.callable, p_arg);
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+void EditorContextMenuPluginManager::invoke_callback(const Callable &p_callback, const Variant &p_arg) {
+ const Variant *argptr = &p_arg;
+ Callable::CallError ce;
+ Variant result;
+ p_callback.callp(&argptr, 1, result, ce);
+
+ if (ce.error != Callable::CallError::CALL_OK) {
+ ERR_FAIL_MSG("Failed to execute context menu callback: " + Variant::get_callable_error_text(p_callback, &argptr, 1, ce) + ".");
+ }
+}
+
+void EditorContextMenuPluginManager::create() {
+ ERR_FAIL_COND(singleton != nullptr);
+ singleton = memnew(EditorContextMenuPluginManager);
+}
+
+void EditorContextMenuPluginManager::cleanup() {
+ ERR_FAIL_NULL(singleton);
+ memdelete(singleton);
+ singleton = nullptr;
}
diff --git a/editor/plugins/editor_context_menu_plugin.h b/editor/plugins/editor_context_menu_plugin.h
index db05e6c6c7..0232d254ba 100644
--- a/editor/plugins/editor_context_menu_plugin.h
+++ b/editor/plugins/editor_context_menu_plugin.h
@@ -34,19 +34,33 @@
#include "core/object/gdvirtual.gen.inc"
#include "core/object/ref_counted.h"
-class Texture2D;
+class InputEvent;
+class PopupMenu;
class Shortcut;
+class Texture2D;
class EditorContextMenuPlugin : public RefCounted {
GDCLASS(EditorContextMenuPlugin, RefCounted);
-public:
- int start_idx;
+ friend class EditorContextMenuPluginManager;
inline static constexpr int MAX_ITEMS = 100;
+public:
+ enum ContextMenuSlot {
+ CONTEXT_SLOT_SCENE_TREE,
+ CONTEXT_SLOT_FILESYSTEM,
+ CONTEXT_SLOT_SCRIPT_EDITOR,
+ CONTEXT_SLOT_FILESYSTEM_CREATE,
+ };
+ inline static constexpr int BASE_ID = 2000;
+
+private:
+ int slot = -1;
+
+public:
struct ContextMenuItem {
- int idx = 0;
+ int id = 0;
String item_name;
Callable callable;
Ref<Texture2D> icon;
@@ -61,10 +75,37 @@ protected:
GDVIRTUAL1(_popup_menu, Vector<String>);
public:
- virtual void add_options(const Vector<String> &p_paths);
+ virtual void get_options(const Vector<String> &p_paths);
+
void add_menu_shortcut(const Ref<Shortcut> &p_shortcut, const Callable &p_callable);
- void add_context_menu_item(const String &p_name, const Callable &p_callable, const Ref<Texture2D> &p_texture, const Ref<Shortcut> &p_shortcut);
- void clear_context_menu_items();
+ void add_context_menu_item(const String &p_name, const Callable &p_callable, const Ref<Texture2D> &p_texture);
+ void add_context_menu_item_from_shortcut(const String &p_name, const Ref<Shortcut> &p_shortcut, const Ref<Texture2D> &p_texture);
+};
+
+VARIANT_ENUM_CAST(EditorContextMenuPlugin::ContextMenuSlot);
+
+class EditorContextMenuPluginManager : public Object {
+ GDCLASS(EditorContextMenuPluginManager, Object);
+
+ using ContextMenuSlot = EditorContextMenuPlugin::ContextMenuSlot;
+ static inline EditorContextMenuPluginManager *singleton = nullptr;
+
+ LocalVector<Ref<EditorContextMenuPlugin>> plugin_list;
+
+public:
+ static EditorContextMenuPluginManager *get_singleton() { return singleton; }
+
+ void add_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin);
+ void remove_plugin(const Ref<EditorContextMenuPlugin> &p_plugin);
+
+ void add_options_from_plugins(PopupMenu *p_popup, ContextMenuSlot p_slot, const Vector<String> &p_paths);
+ Callable match_custom_shortcut(ContextMenuSlot p_slot, const Ref<InputEvent> &p_event);
+ bool activate_custom_option(ContextMenuSlot p_slot, int p_option, const Variant &p_arg);
+
+ void invoke_callback(const Callable &p_callback, const Variant &p_arg);
+
+ static void create();
+ static void cleanup();
};
#endif // EDITOR_CONTEXT_MENU_PLUGIN_H
diff --git a/editor/plugins/editor_plugin.cpp b/editor/plugins/editor_plugin.cpp
index 0ee67e08ba..c8426bce73 100644
--- a/editor/plugins/editor_plugin.cpp
+++ b/editor/plugins/editor_plugin.cpp
@@ -47,7 +47,6 @@
#include "editor/import/editor_import_plugin.h"
#include "editor/inspector_dock.h"
#include "editor/plugins/canvas_item_editor_plugin.h"
-#include "editor/plugins/editor_context_menu_plugin.h"
#include "editor/plugins/editor_debugger_plugin.h"
#include "editor/plugins/editor_resource_conversion_plugin.h"
#include "editor/plugins/node_3d_editor_plugin.h"
@@ -491,14 +490,12 @@ void EditorPlugin::remove_scene_post_import_plugin(const Ref<EditorScenePostImpo
ResourceImporterScene::remove_post_importer_plugin(p_plugin);
}
-void EditorPlugin::add_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin) {
- ERR_FAIL_COND(p_plugin.is_null());
- EditorNode::get_editor_data().add_context_menu_plugin(EditorData::ContextMenuSlot(p_slot), p_plugin);
+void EditorPlugin::add_context_menu_plugin(EditorContextMenuPlugin::ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin) {
+ EditorContextMenuPluginManager::get_singleton()->add_plugin(p_slot, p_plugin);
}
-void EditorPlugin::remove_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin) {
- ERR_FAIL_COND(p_plugin.is_null());
- EditorNode::get_editor_data().remove_context_menu_plugin(EditorData::ContextMenuSlot(p_slot), p_plugin);
+void EditorPlugin::remove_context_menu_plugin(const Ref<EditorContextMenuPlugin> &p_plugin) {
+ EditorContextMenuPluginManager::get_singleton()->remove_plugin(p_plugin);
}
int find(const PackedStringArray &a, const String &v) {
@@ -641,7 +638,7 @@ void EditorPlugin::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_input_event_forwarding_always_enabled"), &EditorPlugin::set_input_event_forwarding_always_enabled);
ClassDB::bind_method(D_METHOD("set_force_draw_over_forwarding_enabled"), &EditorPlugin::set_force_draw_over_forwarding_enabled);
ClassDB::bind_method(D_METHOD("add_context_menu_plugin", "slot", "plugin"), &EditorPlugin::add_context_menu_plugin);
- ClassDB::bind_method(D_METHOD("remove_context_menu_plugin", "slot", "plugin"), &EditorPlugin::remove_context_menu_plugin);
+ ClassDB::bind_method(D_METHOD("remove_context_menu_plugin", "plugin"), &EditorPlugin::remove_context_menu_plugin);
ClassDB::bind_method(D_METHOD("get_editor_interface"), &EditorPlugin::get_editor_interface);
ClassDB::bind_method(D_METHOD("get_script_create_dialog"), &EditorPlugin::get_script_create_dialog);
@@ -707,11 +704,6 @@ void EditorPlugin::_bind_methods() {
BIND_ENUM_CONSTANT(AFTER_GUI_INPUT_PASS);
BIND_ENUM_CONSTANT(AFTER_GUI_INPUT_STOP);
BIND_ENUM_CONSTANT(AFTER_GUI_INPUT_CUSTOM);
-
- BIND_ENUM_CONSTANT(CONTEXT_SLOT_SCENE_TREE);
- BIND_ENUM_CONSTANT(CONTEXT_SLOT_FILESYSTEM);
- BIND_ENUM_CONSTANT(CONTEXT_SLOT_SCRIPT_EDITOR);
- BIND_ENUM_CONSTANT(CONTEXT_SUBMENU_SLOT_FILESYSTEM_CREATE);
}
EditorUndoRedoManager *EditorPlugin::get_undo_redo() {
diff --git a/editor/plugins/editor_plugin.h b/editor/plugins/editor_plugin.h
index 35f65f03cd..b43cbca661 100644
--- a/editor/plugins/editor_plugin.h
+++ b/editor/plugins/editor_plugin.h
@@ -32,6 +32,7 @@
#define EDITOR_PLUGIN_H
#include "core/io/config_file.h"
+#include "editor/plugins/editor_context_menu_plugin.h"
#include "scene/3d/camera_3d.h"
#include "scene/gui/control.h"
@@ -53,7 +54,6 @@ class EditorToolAddons;
class EditorTranslationParserPlugin;
class EditorUndoRedoManager;
class ScriptCreateDialog;
-class EditorContextMenuPlugin;
class EditorPlugin : public Node {
GDCLASS(EditorPlugin, Node);
@@ -103,13 +103,6 @@ public:
AFTER_GUI_INPUT_CUSTOM,
};
- enum ContextMenuSlot {
- CONTEXT_SLOT_SCENE_TREE,
- CONTEXT_SLOT_FILESYSTEM,
- CONTEXT_SLOT_SCRIPT_EDITOR,
- CONTEXT_SUBMENU_SLOT_FILESYSTEM_CREATE,
- };
-
protected:
void _notification(int p_what);
@@ -257,8 +250,8 @@ public:
void add_resource_conversion_plugin(const Ref<EditorResourceConversionPlugin> &p_plugin);
void remove_resource_conversion_plugin(const Ref<EditorResourceConversionPlugin> &p_plugin);
- void add_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin);
- void remove_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin);
+ void add_context_menu_plugin(EditorContextMenuPlugin::ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin);
+ void remove_context_menu_plugin(const Ref<EditorContextMenuPlugin> &p_plugin);
void enable_plugin();
void disable_plugin();
@@ -270,7 +263,6 @@ public:
VARIANT_ENUM_CAST(EditorPlugin::CustomControlContainer);
VARIANT_ENUM_CAST(EditorPlugin::DockSlot);
VARIANT_ENUM_CAST(EditorPlugin::AfterGUIInput);
-VARIANT_ENUM_CAST(EditorPlugin::ContextMenuSlot);
typedef EditorPlugin *(*EditorPluginCreateFunc)();
diff --git a/editor/plugins/editor_preview_plugins.cpp b/editor/plugins/editor_preview_plugins.cpp
index 7ac924571d..a2c36b1f3c 100644
--- a/editor/plugins/editor_preview_plugins.cpp
+++ b/editor/plugins/editor_preview_plugins.cpp
@@ -76,7 +76,7 @@ void post_process_preview(Ref<Image> p_image) {
}
bool EditorTexturePreviewPlugin::handles(const String &p_type) const {
- return ClassDB::is_parent_class(p_type, "Texture2D");
+ return ClassDB::is_parent_class(p_type, "Texture2D") || ClassDB::is_parent_class(p_type, "Texture3D") || ClassDB::is_parent_class(p_type, "TextureLayered");
}
bool EditorTexturePreviewPlugin::generate_small_preview_automatically() const {
@@ -85,9 +85,13 @@ bool EditorTexturePreviewPlugin::generate_small_preview_automatically() const {
Ref<Texture2D> EditorTexturePreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const {
Ref<Image> img;
- Ref<AtlasTexture> atex = p_from;
- if (atex.is_valid()) {
- Ref<Texture2D> tex = atex->get_atlas();
+
+ Ref<AtlasTexture> tex_atlas = p_from;
+ Ref<Texture3D> tex_3d = p_from;
+ Ref<TextureLayered> tex_lyr = p_from;
+
+ if (tex_atlas.is_valid()) {
+ Ref<Texture2D> tex = tex_atlas->get_atlas();
if (!tex.is_valid()) {
return Ref<Texture2D>();
}
@@ -97,11 +101,36 @@ Ref<Texture2D> EditorTexturePreviewPlugin::generate(const Ref<Resource> &p_from,
return Ref<Texture2D>();
}
- if (!atex->get_region().has_area()) {
+ if (!tex_atlas->get_region().has_area()) {
+ return Ref<Texture2D>();
+ }
+
+ img = atlas->get_region(tex_atlas->get_region());
+
+ } else if (tex_3d.is_valid()) {
+ if (tex_3d->get_depth() == 0) {
+ return Ref<Texture2D>();
+ }
+
+ const int mid_depth = (tex_3d->get_depth() - 1) / 2;
+
+ Vector<Ref<Image>> data = tex_3d->get_data();
+ if (!data.is_empty() && data[mid_depth].is_valid()) {
+ img = data[mid_depth]->duplicate();
+ }
+
+ } else if (tex_lyr.is_valid()) {
+ if (tex_lyr->get_layers() == 0) {
return Ref<Texture2D>();
}
- img = atlas->get_region(atex->get_region());
+ const int mid_layer = (tex_lyr->get_layers() - 1) / 2;
+
+ Ref<Image> data = tex_lyr->get_layer_data(mid_layer);
+ if (data.is_valid()) {
+ img = data->duplicate();
+ }
+
} else {
Ref<Texture2D> tex = p_from;
if (tex.is_valid()) {
@@ -115,6 +144,7 @@ Ref<Texture2D> EditorTexturePreviewPlugin::generate(const Ref<Resource> &p_from,
if (img.is_null() || img->is_empty()) {
return Ref<Texture2D>();
}
+
p_metadata["dimensions"] = img->get_size();
img->clear_mipmaps();
diff --git a/editor/plugins/gpu_particles_3d_editor_plugin.cpp b/editor/plugins/gpu_particles_3d_editor_plugin.cpp
index 4e9be0aa53..e711f44ccf 100644
--- a/editor/plugins/gpu_particles_3d_editor_plugin.cpp
+++ b/editor/plugins/gpu_particles_3d_editor_plugin.cpp
@@ -452,7 +452,7 @@ void GPUParticles3DEditorPlugin::make_visible(bool p_visible) {
GPUParticles3DEditorPlugin::GPUParticles3DEditorPlugin() {
particles_editor = memnew(GPUParticles3DEditor);
- EditorNode::get_singleton()->get_main_screen_control()->add_child(particles_editor);
+ EditorNode::get_singleton()->get_gui_base()->add_child(particles_editor);
particles_editor->hide();
}
diff --git a/editor/plugins/mesh_instance_3d_editor_plugin.cpp b/editor/plugins/mesh_instance_3d_editor_plugin.cpp
index 369d6ab009..eda6cdffb1 100644
--- a/editor/plugins/mesh_instance_3d_editor_plugin.cpp
+++ b/editor/plugins/mesh_instance_3d_editor_plugin.cpp
@@ -689,7 +689,7 @@ void MeshInstance3DEditorPlugin::make_visible(bool p_visible) {
MeshInstance3DEditorPlugin::MeshInstance3DEditorPlugin() {
mesh_editor = memnew(MeshInstance3DEditor);
- EditorNode::get_singleton()->get_main_screen_control()->add_child(mesh_editor);
+ EditorNode::get_singleton()->get_gui_base()->add_child(mesh_editor);
mesh_editor->options->hide();
}
diff --git a/editor/plugins/mesh_library_editor_plugin.cpp b/editor/plugins/mesh_library_editor_plugin.cpp
index 58cc670475..d6650bd08f 100644
--- a/editor/plugins/mesh_library_editor_plugin.cpp
+++ b/editor/plugins/mesh_library_editor_plugin.cpp
@@ -308,7 +308,7 @@ void MeshLibraryEditorPlugin::make_visible(bool p_visible) {
MeshLibraryEditorPlugin::MeshLibraryEditorPlugin() {
mesh_library_editor = memnew(MeshLibraryEditor);
- EditorNode::get_singleton()->get_main_screen_control()->add_child(mesh_library_editor);
+ EditorNode::get_singleton()->get_gui_base()->add_child(mesh_library_editor);
mesh_library_editor->set_anchors_and_offsets_preset(Control::PRESET_TOP_WIDE);
mesh_library_editor->set_end(Point2(0, 22));
mesh_library_editor->hide();
diff --git a/editor/plugins/multimesh_editor_plugin.cpp b/editor/plugins/multimesh_editor_plugin.cpp
index 76ffdb02e1..729ceccd25 100644
--- a/editor/plugins/multimesh_editor_plugin.cpp
+++ b/editor/plugins/multimesh_editor_plugin.cpp
@@ -384,7 +384,7 @@ void MultiMeshEditorPlugin::make_visible(bool p_visible) {
MultiMeshEditorPlugin::MultiMeshEditorPlugin() {
multimesh_editor = memnew(MultiMeshEditor);
- EditorNode::get_singleton()->get_main_screen_control()->add_child(multimesh_editor);
+ EditorNode::get_singleton()->get_gui_base()->add_child(multimesh_editor);
multimesh_editor->options->hide();
}
diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp
index 35a8741b19..f58dfbb5a5 100644
--- a/editor/plugins/node_3d_editor_plugin.cpp
+++ b/editor/plugins/node_3d_editor_plugin.cpp
@@ -37,6 +37,7 @@
#include "core/math/projection.h"
#include "core/os/keyboard.h"
#include "editor/debugger/editor_debugger_node.h"
+#include "editor/editor_main_screen.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
@@ -9372,7 +9373,7 @@ Vector<Node3D *> Node3DEditor::gizmo_bvh_frustum_query(const Vector<Plane> &p_fr
Node3DEditorPlugin::Node3DEditorPlugin() {
spatial_editor = memnew(Node3DEditor);
spatial_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL);
- EditorNode::get_singleton()->get_main_screen_control()->add_child(spatial_editor);
+ EditorNode::get_singleton()->get_editor_main_screen()->get_control()->add_child(spatial_editor);
spatial_editor->hide();
}
diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp
index 93c8ae5438..253005c5ce 100644
--- a/editor/plugins/script_editor_plugin.cpp
+++ b/editor/plugins/script_editor_plugin.cpp
@@ -44,6 +44,7 @@
#include "editor/editor_command_palette.h"
#include "editor/editor_help_search.h"
#include "editor/editor_interface.h"
+#include "editor/editor_main_screen.h"
#include "editor/editor_node.h"
#include "editor/editor_paths.h"
#include "editor/editor_script.h"
@@ -1399,13 +1400,12 @@ void ScriptEditor::_menu_option(int p_option) {
}
}
- // Context menu options.
- if (p_option >= EditorData::CONTEXT_MENU_ITEM_ID_BASE) {
+ if (p_option >= EditorContextMenuPlugin::BASE_ID) {
Ref<Resource> resource;
if (current) {
resource = current->get_edited_resource();
}
- EditorNode::get_editor_data().script_editor_options_pressed(EditorData::CONTEXT_SLOT_SCRIPT_EDITOR, p_option, resource);
+ EditorContextMenuPluginManager::get_singleton()->activate_custom_option(EditorContextMenuPlugin::CONTEXT_SLOT_SCRIPT_EDITOR, p_option, resource);
return;
}
@@ -3315,10 +3315,16 @@ void ScriptEditor::shortcut_input(const Ref<InputEvent> &p_event) {
_menu_option(WINDOW_MOVE_DOWN);
accept_event();
}
- // Context menu shortcuts.
- int match_option = EditorNode::get_editor_data().match_context_menu_shortcut(EditorData::CONTEXT_SLOT_SCRIPT_EDITOR, p_event);
- if (match_option) {
- _menu_option(match_option);
+
+ Callable custom_callback = EditorContextMenuPluginManager::get_singleton()->match_custom_shortcut(EditorContextMenuPlugin::CONTEXT_SLOT_SCRIPT_EDITOR, p_event);
+ if (custom_callback.is_valid()) {
+ Ref<Resource> resource;
+ ScriptEditorBase *current = _get_current_editor();
+ if (current) {
+ resource = current->get_edited_resource();
+ }
+ EditorContextMenuPluginManager::get_singleton()->invoke_callback(custom_callback, resource);
+ accept_event();
}
}
@@ -3387,7 +3393,7 @@ void ScriptEditor::_make_script_list_context_menu() {
selected_paths.push_back(path);
}
}
- EditorNode::get_editor_data().add_options_from_plugins(context_menu, EditorData::CONTEXT_SLOT_SCRIPT_EDITOR, selected_paths);
+ EditorContextMenuPluginManager::get_singleton()->add_options_from_plugins(context_menu, EditorContextMenuPlugin::CONTEXT_SLOT_SCRIPT_EDITOR, selected_paths);
context_menu->set_position(get_screen_position() + get_local_mouse_position());
context_menu->reset_size();
@@ -4052,6 +4058,7 @@ void ScriptEditor::_bind_methods() {
ClassDB::bind_method(D_METHOD("open_script_create_dialog", "base_name", "base_path"), &ScriptEditor::open_script_create_dialog);
ClassDB::bind_method(D_METHOD("goto_help", "topic"), &ScriptEditor::goto_help);
+ ClassDB::bind_method(D_METHOD("update_docs_from_script", "script"), &ScriptEditor::update_docs_from_script);
ADD_SIGNAL(MethodInfo("editor_script_changed", PropertyInfo(Variant::OBJECT, "script", PROPERTY_HINT_RESOURCE_TYPE, "Script")));
ADD_SIGNAL(MethodInfo("script_close", PropertyInfo(Variant::OBJECT, "script", PROPERTY_HINT_RESOURCE_TYPE, "Script")));
@@ -4612,7 +4619,7 @@ ScriptEditorPlugin::ScriptEditorPlugin() {
Ref<Shortcut> make_floating_shortcut = ED_SHORTCUT_AND_COMMAND("script_editor/make_floating", TTR("Make Floating"));
window_wrapper->set_wrapped_control(script_editor, make_floating_shortcut);
- EditorNode::get_singleton()->get_main_screen_control()->add_child(window_wrapper);
+ EditorNode::get_singleton()->get_editor_main_screen()->get_control()->add_child(window_wrapper);
window_wrapper->set_v_size_flags(Control::SIZE_EXPAND_FILL);
window_wrapper->hide();
window_wrapper->connect("window_visibility_changed", callable_mp(this, &ScriptEditorPlugin::_window_visibility_changed));
diff --git a/editor/plugins/skeleton_2d_editor_plugin.cpp b/editor/plugins/skeleton_2d_editor_plugin.cpp
index 97c5c0c7dd..8f54641dcb 100644
--- a/editor/plugins/skeleton_2d_editor_plugin.cpp
+++ b/editor/plugins/skeleton_2d_editor_plugin.cpp
@@ -129,7 +129,7 @@ void Skeleton2DEditorPlugin::make_visible(bool p_visible) {
Skeleton2DEditorPlugin::Skeleton2DEditorPlugin() {
sprite_editor = memnew(Skeleton2DEditor);
- EditorNode::get_singleton()->get_main_screen_control()->add_child(sprite_editor);
+ EditorNode::get_singleton()->get_gui_base()->add_child(sprite_editor);
make_visible(false);
//sprite_editor->options->hide();
diff --git a/editor/plugins/sprite_2d_editor_plugin.cpp b/editor/plugins/sprite_2d_editor_plugin.cpp
index 3647fa2d59..c7db243662 100644
--- a/editor/plugins/sprite_2d_editor_plugin.cpp
+++ b/editor/plugins/sprite_2d_editor_plugin.cpp
@@ -673,7 +673,7 @@ void Sprite2DEditorPlugin::make_visible(bool p_visible) {
Sprite2DEditorPlugin::Sprite2DEditorPlugin() {
sprite_editor = memnew(Sprite2DEditor);
- EditorNode::get_singleton()->get_main_screen_control()->add_child(sprite_editor);
+ EditorNode::get_singleton()->get_gui_base()->add_child(sprite_editor);
make_visible(false);
//sprite_editor->options->hide();
diff --git a/editor/plugins/tiles/tile_map_layer_editor.cpp b/editor/plugins/tiles/tile_map_layer_editor.cpp
index 7e6746dd3c..dcfd92f6f9 100644
--- a/editor/plugins/tiles/tile_map_layer_editor.cpp
+++ b/editor/plugins/tiles/tile_map_layer_editor.cpp
@@ -1364,11 +1364,13 @@ void TileMapLayerEditorTilesPlugin::_stop_dragging() {
Vector2i coords;
HashMap<Vector2i, TileMapCell> cells_undo;
for (int i = 0; i < selection_used_cells.size(); i++) {
- coords = tile_set->map_pattern(top_left, selection_used_cells[i], selection_pattern);
- cells_undo[coords] = TileMapCell(drag_modified[coords].source_id, drag_modified[coords].get_atlas_coords(), drag_modified[coords].alternative_tile);
coords = tile_set->map_pattern(top_left + offset, selection_used_cells[i], selection_pattern);
cells_undo[coords] = TileMapCell(edited_layer->get_cell_source_id(coords), edited_layer->get_cell_atlas_coords(coords), edited_layer->get_cell_alternative_tile(coords));
}
+ for (int i = 0; i < selection_used_cells.size(); i++) {
+ coords = tile_set->map_pattern(top_left, selection_used_cells[i], selection_pattern);
+ cells_undo[coords] = TileMapCell(drag_modified[coords].source_id, drag_modified[coords].get_atlas_coords(), drag_modified[coords].alternative_tile);
+ }
// Build the list of cells to do.
HashMap<Vector2i, TileMapCell> cells_do;
diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp
index 55c361de4b..279590563a 100644
--- a/editor/project_manager.cpp
+++ b/editor/project_manager.cpp
@@ -580,7 +580,7 @@ void ProjectManager::_open_selected_projects_ask() {
if (!unsupported_features.is_empty()) {
String warning_message = "";
for (int i = 0; i < unsupported_features.size(); i++) {
- String feature = unsupported_features[i];
+ const String &feature = unsupported_features[i];
if (feature == "Double Precision") {
warning_message += TTR("Warning: This project uses double precision floats, but this version of\nGodot uses single precision floats. Opening this project may cause data loss.\n\n");
unsupported_features.remove_at(i);
@@ -590,6 +590,7 @@ void ProjectManager::_open_selected_projects_ask() {
unsupported_features.remove_at(i);
i--;
} else if (ProjectList::project_feature_looks_like_version(feature)) {
+ version_convert_feature = feature;
warning_message += vformat(TTR("Warning: This project was last edited in Godot %s. Opening will change it to Godot %s.\n\n"), Variant(feature), Variant(VERSION_BRANCH));
unsupported_features.remove_at(i);
i--;
@@ -610,6 +611,16 @@ void ProjectManager::_open_selected_projects_ask() {
_open_selected_projects();
}
+void ProjectManager::_open_selected_projects_with_migration() {
+#ifndef DISABLE_DEPRECATED
+ if (project_list->get_selected_projects().size() == 1) {
+ // Only migrate if a single project is opened.
+ _minor_project_migrate();
+ }
+#endif
+ _open_selected_projects();
+}
+
void ProjectManager::_install_project(const String &p_zip_path, const String &p_title) {
project_dialog->set_mode(ProjectDialog::MODE_INSTALL);
project_dialog->set_zip_path(p_zip_path);
@@ -877,6 +888,34 @@ void ProjectManager::add_new_tag(const String &p_tag) {
// Project converter/migration tool.
+#ifndef DISABLE_DEPRECATED
+void ProjectManager::_minor_project_migrate() {
+ const ProjectList::Item migrated_project = project_list->get_selected_projects()[0];
+
+ if (version_convert_feature.begins_with("4.3")) {
+ // Migrate layout after scale changes.
+ const float edscale = EDSCALE;
+ if (edscale != 1.0) {
+ Ref<ConfigFile> layout_file;
+ layout_file.instantiate();
+
+ const String layout_path = migrated_project.path.path_join(".godot/editor/editor_layout.cfg");
+ Error err = layout_file->load(layout_path);
+ if (err == OK) {
+ for (int i = 0; i < 4; i++) {
+ const String key = "dock_hsplit_" + itos(i + 1);
+ int old_value = layout_file->get_value("docks", key, 0);
+ if (old_value != 0) {
+ layout_file->set_value("docks", key, old_value / edscale);
+ }
+ }
+ layout_file->save(layout_path);
+ }
+ }
+ }
+}
+#endif
+
void ProjectManager::_full_convert_button_pressed() {
ask_update_settings->hide();
ask_full_convert_dialog->popup_centered(Size2i(600.0 * EDSCALE, 0));
@@ -1487,7 +1526,7 @@ ProjectManager::ProjectManager() {
ask_update_settings = memnew(ConfirmationDialog);
ask_update_settings->set_autowrap(true);
- ask_update_settings->get_ok_button()->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_open_selected_projects));
+ ask_update_settings->get_ok_button()->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_open_selected_projects_with_migration));
full_convert_button = ask_update_settings->add_button(TTR("Convert Full Project"), !GLOBAL_GET("gui/common/swap_cancel_ok"));
full_convert_button->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_full_convert_button_pressed));
add_child(ask_update_settings);
diff --git a/editor/project_manager.h b/editor/project_manager.h
index 669b5d8b6c..aad51d0e98 100644
--- a/editor/project_manager.h
+++ b/editor/project_manager.h
@@ -176,6 +176,7 @@ class ProjectManager : public Control {
void _run_project_confirm();
void _open_selected_projects();
void _open_selected_projects_ask();
+ void _open_selected_projects_with_migration();
void _install_project(const String &p_zip_path, const String &p_title);
void _import_project();
@@ -223,6 +224,11 @@ class ProjectManager : public Control {
ConfirmationDialog *ask_update_settings = nullptr;
Button *full_convert_button = nullptr;
+ String version_convert_feature;
+
+#ifndef DISABLE_DEPRECATED
+ void _minor_project_migrate();
+#endif
void _full_convert_button_pressed();
void _perform_full_project_conversion();
diff --git a/editor/project_manager/project_dialog.cpp b/editor/project_manager/project_dialog.cpp
index 9b009c0a89..7acda16890 100644
--- a/editor/project_manager/project_dialog.cpp
+++ b/editor/project_manager/project_dialog.cpp
@@ -162,7 +162,7 @@ void ProjectDialog::_validate_path() {
}
}
- if (target_path.is_empty() || target_path.is_relative_path()) {
+ if (target_path.is_relative_path()) {
_set_message(TTR("The path specified is invalid."), MESSAGE_ERROR, target_path_input_type);
return;
}
@@ -352,7 +352,7 @@ void ProjectDialog::_install_path_changed() {
void ProjectDialog::_browse_project_path() {
String path = project_path->get_text();
- if (path.is_empty()) {
+ if (path.is_relative_path()) {
path = EDITOR_GET("filesystem/directories/default_project_path");
}
if (mode == MODE_IMPORT && install_path->is_visible_in_tree()) {
@@ -382,12 +382,16 @@ void ProjectDialog::_browse_project_path() {
void ProjectDialog::_browse_install_path() {
ERR_FAIL_COND_MSG(mode != MODE_IMPORT, "Install path is only used for MODE_IMPORT.");
+ String path = install_path->get_text();
+ if (path.is_relative_path() || !DirAccess::dir_exists_absolute(path)) {
+ path = EDITOR_GET("filesystem/directories/default_project_path");
+ }
if (create_dir->is_pressed()) {
// Select parent directory of install path.
- fdialog_install->set_current_dir(install_path->get_text().get_base_dir());
+ fdialog_install->set_current_dir(path.get_base_dir());
} else {
// Select install path.
- fdialog_install->set_current_dir(install_path->get_text());
+ fdialog_install->set_current_dir(path);
}
fdialog_install->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_DIR);
diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp
index 2a39b11815..9f56c586a2 100644
--- a/editor/scene_tree_dock.cpp
+++ b/editor/scene_tree_dock.cpp
@@ -37,6 +37,7 @@
#include "core/os/keyboard.h"
#include "editor/debugger/editor_debugger_node.h"
#include "editor/editor_feature_profile.h"
+#include "editor/editor_main_screen.h"
#include "editor/editor_node.h"
#include "editor/editor_paths.h"
#include "editor/editor_quick_open.h"
@@ -214,11 +215,12 @@ void SceneTreeDock::shortcut_input(const Ref<InputEvent> &p_event) {
} else if (ED_IS_SHORTCUT("scene_tree/delete", p_event)) {
_tool_selected(TOOL_ERASE);
} else {
- int match_option = EditorNode::get_editor_data().match_context_menu_shortcut(EditorData::CONTEXT_SLOT_SCENE_TREE, p_event);
- if (match_option) {
- _tool_selected(match_option);
+ Callable custom_callback = EditorContextMenuPluginManager::get_singleton()->match_custom_shortcut(EditorContextMenuPlugin::CONTEXT_SLOT_SCENE_TREE, p_event);
+ if (custom_callback.is_valid()) {
+ EditorContextMenuPluginManager::get_singleton()->invoke_callback(custom_callback, _get_selection_array());
+ } else {
+ return;
}
- return;
}
// Tool selection was successful, accept the event to stop propagation.
@@ -1197,7 +1199,7 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
ScriptEditor::get_singleton()->goto_help("class_name:" + class_name);
}
- EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT);
+ EditorNode::get_singleton()->get_editor_main_screen()->select(EditorMainScreen::EDITOR_SCRIPT);
} break;
case TOOL_AUTO_EXPAND: {
scene_tree->set_auto_expand_selected(!EDITOR_GET("docks/scene_tree/auto_expand_to_selected"), true);
@@ -1491,10 +1493,8 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
} break;
default: {
- // Editor context plugin.
- if (p_tool >= EditorData::CONTEXT_MENU_ITEM_ID_BASE) {
- List<Node *> selection = editor_selection->get_selected_node_list();
- EditorNode::get_editor_data().scene_tree_options_pressed(EditorData::CONTEXT_SLOT_SCENE_TREE, p_tool, selection);
+ if (p_tool >= EditorContextMenuPlugin::BASE_ID) {
+ EditorContextMenuPluginManager::get_singleton()->activate_custom_option(EditorContextMenuPlugin::CONTEXT_SLOT_SCENE_TREE, p_tool, _get_selection_array());
break;
}
@@ -3366,6 +3366,18 @@ void SceneTreeDock::_normalize_drop(Node *&to_node, int &to_pos, int p_type) {
}
}
+Array SceneTreeDock::_get_selection_array() {
+ List<Node *> selection = editor_selection->get_selected_node_list();
+ TypedArray<Node> array;
+ array.resize(selection.size());
+
+ int i = 0;
+ for (const Node *E : selection) {
+ array[i++] = E;
+ }
+ return array;
+}
+
void SceneTreeDock::_files_dropped(const Vector<String> &p_files, NodePath p_to, int p_type) {
Node *node = get_node(p_to);
ERR_FAIL_NULL(node);
@@ -3766,7 +3778,7 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) {
String node_path = root->get_path().rel_path_to(E->get()->get_path());
p_paths.push_back(node_path);
}
- EditorNode::get_editor_data().add_options_from_plugins(menu, EditorData::CONTEXT_SLOT_SCENE_TREE, p_paths);
+ EditorContextMenuPluginManager::get_singleton()->add_options_from_plugins(menu, EditorContextMenuPlugin::CONTEXT_SLOT_SCENE_TREE, p_paths);
menu->reset_size();
menu->set_position(p_menu_pos);
diff --git a/editor/scene_tree_dock.h b/editor/scene_tree_dock.h
index 1807ec5896..d0cdaf8dd1 100644
--- a/editor/scene_tree_dock.h
+++ b/editor/scene_tree_dock.h
@@ -262,6 +262,7 @@ class SceneTreeDock : public VBoxContainer {
bool _has_tracks_to_delete(Node *p_node, List<Node *> &p_to_delete) const;
void _normalize_drop(Node *&to_node, int &to_pos, int p_type);
+ Array _get_selection_array();
void _nodes_dragged(const Array &p_nodes, NodePath p_to, int p_type);
void _files_dropped(const Vector<String> &p_files, NodePath p_to, int p_type);
diff --git a/editor/themes/editor_theme_manager.cpp b/editor/themes/editor_theme_manager.cpp
index 3041857d83..17bcbacfc2 100644
--- a/editor/themes/editor_theme_manager.cpp
+++ b/editor/themes/editor_theme_manager.cpp
@@ -1415,21 +1415,24 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the
p_theme->set_icon("decrement_highlight", "VScrollBar", empty_icon);
p_theme->set_icon("decrement_pressed", "VScrollBar", empty_icon);
+ // Slider
+ const int background_margin = MAX(2, p_config.base_margin / 2);
+
// HSlider.
p_theme->set_icon("grabber_highlight", "HSlider", p_theme->get_icon(SNAME("GuiSliderGrabberHl"), EditorStringName(EditorIcons)));
p_theme->set_icon("grabber", "HSlider", p_theme->get_icon(SNAME("GuiSliderGrabber"), EditorStringName(EditorIcons)));
- p_theme->set_stylebox("slider", "HSlider", make_flat_stylebox(p_config.dark_color_3, 0, p_config.base_margin / 2, 0, p_config.base_margin / 2, p_config.corner_radius));
- p_theme->set_stylebox("grabber_area", "HSlider", make_flat_stylebox(p_config.contrast_color_1, 0, p_config.base_margin / 2, 0, p_config.base_margin / 2, p_config.corner_radius));
- p_theme->set_stylebox("grabber_area_highlight", "HSlider", make_flat_stylebox(p_config.contrast_color_1, 0, p_config.base_margin / 2, 0, p_config.base_margin / 2));
+ p_theme->set_stylebox("slider", "HSlider", make_flat_stylebox(p_config.dark_color_3, 0, background_margin, 0, background_margin, p_config.corner_radius));
+ p_theme->set_stylebox("grabber_area", "HSlider", make_flat_stylebox(p_config.contrast_color_1, 0, background_margin, 0, background_margin, p_config.corner_radius));
+ p_theme->set_stylebox("grabber_area_highlight", "HSlider", make_flat_stylebox(p_config.contrast_color_1, 0, background_margin, 0, background_margin));
p_theme->set_constant("center_grabber", "HSlider", 0);
p_theme->set_constant("grabber_offset", "HSlider", 0);
// VSlider.
p_theme->set_icon("grabber", "VSlider", p_theme->get_icon(SNAME("GuiSliderGrabber"), EditorStringName(EditorIcons)));
p_theme->set_icon("grabber_highlight", "VSlider", p_theme->get_icon(SNAME("GuiSliderGrabberHl"), EditorStringName(EditorIcons)));
- p_theme->set_stylebox("slider", "VSlider", make_flat_stylebox(p_config.dark_color_3, p_config.base_margin / 2, 0, p_config.base_margin / 2, 0, p_config.corner_radius));
- p_theme->set_stylebox("grabber_area", "VSlider", make_flat_stylebox(p_config.contrast_color_1, p_config.base_margin / 2, 0, p_config.base_margin / 2, 0, p_config.corner_radius));
- p_theme->set_stylebox("grabber_area_highlight", "VSlider", make_flat_stylebox(p_config.contrast_color_1, p_config.base_margin / 2, 0, p_config.base_margin / 2, 0));
+ p_theme->set_stylebox("slider", "VSlider", make_flat_stylebox(p_config.dark_color_3, background_margin, 0, background_margin, 0, p_config.corner_radius));
+ p_theme->set_stylebox("grabber_area", "VSlider", make_flat_stylebox(p_config.contrast_color_1, background_margin, 0, background_margin, 0, p_config.corner_radius));
+ p_theme->set_stylebox("grabber_area_highlight", "VSlider", make_flat_stylebox(p_config.contrast_color_1, background_margin, 0, background_margin, 0));
p_theme->set_constant("center_grabber", "VSlider", 0);
p_theme->set_constant("grabber_offset", "VSlider", 0);
}
diff --git a/main/main.cpp b/main/main.cpp
index e59f6d03b0..af0d7b5804 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -140,6 +140,7 @@ static Engine *engine = nullptr;
static ProjectSettings *globals = nullptr;
static Input *input = nullptr;
static InputMap *input_map = nullptr;
+static WorkerThreadPool *worker_thread_pool = nullptr;
static TranslationServer *translation_server = nullptr;
static Performance *performance = nullptr;
static PackedData *packed_data = nullptr;
@@ -690,6 +691,8 @@ Error Main::test_setup() {
register_core_settings(); // Here globals are present.
+ worker_thread_pool = memnew(WorkerThreadPool);
+
translation_server = memnew(TranslationServer);
tsman = memnew(TextServerManager);
@@ -800,6 +803,8 @@ void Main::test_cleanup() {
ResourceSaver::remove_custom_savers();
PropertyListHelper::clear_base_helpers();
+ WorkerThreadPool::get_singleton()->finish();
+
#ifdef TOOLS_ENABLED
GDExtensionManager::get_singleton()->deinitialize_extensions(GDExtension::INITIALIZATION_LEVEL_EDITOR);
uninitialize_modules(MODULE_INITIALIZATION_LEVEL_EDITOR);
@@ -841,6 +846,9 @@ void Main::test_cleanup() {
if (physics_server_2d_manager) {
memdelete(physics_server_2d_manager);
}
+ if (worker_thread_pool) {
+ memdelete(worker_thread_pool);
+ }
if (globals) {
memdelete(globals);
}
@@ -931,6 +939,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
register_core_settings(); //here globals are present
+ worker_thread_pool = memnew(WorkerThreadPool);
translation_server = memnew(TranslationServer);
performance = memnew(Performance);
GDREGISTER_CLASS(Performance);
@@ -2621,6 +2630,10 @@ error:
if (translation_server) {
memdelete(translation_server);
}
+ if (worker_thread_pool) {
+ worker_thread_pool->finish();
+ memdelete(worker_thread_pool);
+ }
if (globals) {
memdelete(globals);
}
@@ -4502,6 +4515,8 @@ void Main::cleanup(bool p_force) {
ResourceLoader::clear_translation_remaps();
ResourceLoader::clear_path_remaps();
+ WorkerThreadPool::get_singleton()->finish();
+
ScriptServer::finish_languages();
// Sync pending commands that may have been queued from a different thread during ScriptServer finalization
@@ -4592,6 +4607,9 @@ void Main::cleanup(bool p_force) {
if (physics_server_2d_manager) {
memdelete(physics_server_2d_manager);
}
+ if (worker_thread_pool) {
+ memdelete(worker_thread_pool);
+ }
if (globals) {
memdelete(globals);
}
diff --git a/methods.py b/methods.py
index bfd08cfc7b..36462fd30b 100644
--- a/methods.py
+++ b/methods.py
@@ -906,21 +906,18 @@ def show_progress(env):
node_count_fname = str(env.Dir("#")) + "/.scons_node_count"
import math
- import time
class cache_progress:
- # The default is 1 GB cache and 12 hours half life
- def __init__(self, path=None, limit=1073741824, half_life=43200):
+ # The default is 1 GB cache
+ def __init__(self, path=None, limit=pow(1024, 3)):
self.path = path
self.limit = limit
- self.exponent_scale = math.log(2) / half_life
if env["verbose"] and path is not None:
screen.write(
"Current cache limit is {} (used: {})\n".format(
self.convert_size(limit), self.convert_size(self.get_size(path))
)
)
- self.delete(self.file_list())
def __call__(self, node, *args, **kw):
nonlocal node_count, node_count_max, node_count_interval, node_count_fname, show_progress
@@ -937,12 +934,66 @@ def show_progress(env):
screen.write("\r[Initial build] ")
screen.flush()
+ def convert_size(self, size_bytes):
+ if size_bytes == 0:
+ return "0 bytes"
+ size_name = ("bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
+ i = int(math.floor(math.log(size_bytes, 1024)))
+ p = math.pow(1024, i)
+ s = round(size_bytes / p, 2)
+ return "%s %s" % (int(s) if i == 0 else s, size_name[i])
+
+ def get_size(self, start_path="."):
+ total_size = 0
+ for dirpath, dirnames, filenames in os.walk(start_path):
+ for f in filenames:
+ fp = os.path.join(dirpath, f)
+ total_size += os.path.getsize(fp)
+ return total_size
+
+ def progress_finish(target, source, env):
+ nonlocal node_count, progressor
+ try:
+ with open(node_count_fname, "w", encoding="utf-8", newline="\n") as f:
+ f.write("%d\n" % node_count)
+ except Exception:
+ pass
+
+ try:
+ with open(node_count_fname, "r", encoding="utf-8") as f:
+ node_count_max = int(f.readline())
+ except Exception:
+ pass
+
+ cache_directory = os.environ.get("SCONS_CACHE")
+ # Simple cache pruning, attached to SCons' progress callback. Trim the
+ # cache directory to a size not larger than cache_limit.
+ cache_limit = float(os.getenv("SCONS_CACHE_LIMIT", 1024)) * 1024 * 1024
+ progressor = cache_progress(cache_directory, cache_limit)
+ Progress(progressor, interval=node_count_interval)
+
+ progress_finish_command = Command("progress_finish", [], progress_finish)
+ AlwaysBuild(progress_finish_command)
+
+
+def clean_cache(env):
+ import atexit
+ import time
+
+ class cache_clean:
+ def __init__(self, path=None, limit=pow(1024, 3)):
+ self.path = path
+ self.limit = limit
+
+ def clean(self):
+ self.delete(self.file_list())
+
def delete(self, files):
if len(files) == 0:
return
if env["verbose"]:
# Utter something
- screen.write("\rPurging %d %s from cache...\n" % (len(files), len(files) > 1 and "files" or "file"))
+ print("Purging %d %s from cache..." % (len(files), "files" if len(files) > 1 else "file"))
[os.remove(f) for f in files]
def file_list(self):
@@ -976,47 +1027,20 @@ def show_progress(env):
else:
return [x[0] for x in file_stat[mark:]]
- def convert_size(self, size_bytes):
- if size_bytes == 0:
- return "0 bytes"
- size_name = ("bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
- i = int(math.floor(math.log(size_bytes, 1024)))
- p = math.pow(1024, i)
- s = round(size_bytes / p, 2)
- return "%s %s" % (int(s) if i == 0 else s, size_name[i])
-
- def get_size(self, start_path="."):
- total_size = 0
- for dirpath, dirnames, filenames in os.walk(start_path):
- for f in filenames:
- fp = os.path.join(dirpath, f)
- total_size += os.path.getsize(fp)
- return total_size
-
- def progress_finish(target, source, env):
- nonlocal node_count, progressor
+ def cache_finally():
+ nonlocal cleaner
try:
- with open(node_count_fname, "w", encoding="utf-8", newline="\n") as f:
- f.write("%d\n" % node_count)
- progressor.delete(progressor.file_list())
+ cleaner.clean()
except Exception:
pass
- try:
- with open(node_count_fname, "r", encoding="utf-8") as f:
- node_count_max = int(f.readline())
- except Exception:
- pass
-
cache_directory = os.environ.get("SCONS_CACHE")
# Simple cache pruning, attached to SCons' progress callback. Trim the
# cache directory to a size not larger than cache_limit.
cache_limit = float(os.getenv("SCONS_CACHE_LIMIT", 1024)) * 1024 * 1024
- progressor = cache_progress(cache_directory, cache_limit)
- Progress(progressor, interval=node_count_interval)
+ cleaner = cache_clean(cache_directory, cache_limit)
- progress_finish_command = Command("progress_finish", [], progress_finish)
- AlwaysBuild(progress_finish_command)
+ atexit.register(cache_finally)
def dump(env):
diff --git a/modules/basis_universal/image_compress_basisu.cpp b/modules/basis_universal/image_compress_basisu.cpp
index 8167fe8c73..ab20d00b5b 100644
--- a/modules/basis_universal/image_compress_basisu.cpp
+++ b/modules/basis_universal/image_compress_basisu.cpp
@@ -84,14 +84,12 @@ Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedCha
decompress_format = BASIS_DECOMPRESS_RGBA;
} break;
case Image::USED_CHANNELS_R: {
- decompress_format = BASIS_DECOMPRESS_RGB;
+ decompress_format = BASIS_DECOMPRESS_R;
} break;
case Image::USED_CHANNELS_RG: {
- // Currently RG textures are compressed as DXT5/ETC2_RGBA8 with a RA -> RG swizzle,
- // as BasisUniversal didn't use to support ETC2_RG11 transcoding.
params.m_force_alpha = true;
image->convert_rg_to_ra_rgba8();
- decompress_format = BASIS_DECOMPRESS_RG_AS_RA;
+ decompress_format = BASIS_DECOMPRESS_RG;
} break;
case Image::USED_CHANNELS_RGB: {
decompress_format = BASIS_DECOMPRESS_RGB;
@@ -219,15 +217,68 @@ Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size) {
// Get supported compression formats.
bool bptc_supported = RS::get_singleton()->has_os_feature("bptc");
bool astc_supported = RS::get_singleton()->has_os_feature("astc");
+ bool rgtc_supported = RS::get_singleton()->has_os_feature("rgtc");
bool s3tc_supported = RS::get_singleton()->has_os_feature("s3tc");
bool etc2_supported = RS::get_singleton()->has_os_feature("etc2");
bool needs_ra_rg_swap = false;
+ bool needs_rg_trim = false;
+
+ BasisDecompressFormat decompress_format = (BasisDecompressFormat)(*(uint32_t *)(src_ptr));
- switch (*(uint32_t *)(src_ptr)) {
+ switch (decompress_format) {
+ case BASIS_DECOMPRESS_R: {
+ if (rgtc_supported) {
+ basisu_format = basist::transcoder_texture_format::cTFBC4_R;
+ image_format = Image::FORMAT_RGTC_R;
+ } else if (s3tc_supported) {
+ basisu_format = basist::transcoder_texture_format::cTFBC1;
+ image_format = Image::FORMAT_DXT1;
+ } else if (etc2_supported) {
+ basisu_format = basist::transcoder_texture_format::cTFETC2_EAC_R11;
+ image_format = Image::FORMAT_ETC2_R11;
+ } else {
+ // No supported VRAM compression formats, decompress.
+ basisu_format = basist::transcoder_texture_format::cTFRGBA32;
+ image_format = Image::FORMAT_RGBA8;
+ needs_rg_trim = true;
+ }
+
+ } break;
case BASIS_DECOMPRESS_RG: {
- // RGTC transcoding is currently performed with RG_AS_RA, fail.
- ERR_FAIL_V(image);
+ if (rgtc_supported) {
+ basisu_format = basist::transcoder_texture_format::cTFBC5_RG;
+ image_format = Image::FORMAT_RGTC_RG;
+ } else if (s3tc_supported) {
+ basisu_format = basist::transcoder_texture_format::cTFBC3;
+ image_format = Image::FORMAT_DXT5_RA_AS_RG;
+ } else if (etc2_supported) {
+ basisu_format = basist::transcoder_texture_format::cTFETC2_EAC_RG11;
+ image_format = Image::FORMAT_ETC2_RG11;
+ } else {
+ // No supported VRAM compression formats, decompress.
+ basisu_format = basist::transcoder_texture_format::cTFRGBA32;
+ image_format = Image::FORMAT_RGBA8;
+ needs_ra_rg_swap = true;
+ needs_rg_trim = true;
+ }
+
+ } break;
+ case BASIS_DECOMPRESS_RG_AS_RA: {
+ if (s3tc_supported) {
+ basisu_format = basist::transcoder_texture_format::cTFBC3;
+ image_format = Image::FORMAT_DXT5_RA_AS_RG;
+ } else if (etc2_supported) {
+ basisu_format = basist::transcoder_texture_format::cTFETC2;
+ image_format = Image::FORMAT_ETC2_RA_AS_RG;
+ } else {
+ // No supported VRAM compression formats, decompress.
+ basisu_format = basist::transcoder_texture_format::cTFRGBA32;
+ image_format = Image::FORMAT_RGBA8;
+ needs_ra_rg_swap = true;
+ needs_rg_trim = true;
+ }
+
} break;
case BASIS_DECOMPRESS_RGB: {
if (bptc_supported) {
@@ -267,20 +318,7 @@ Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size) {
basisu_format = basist::transcoder_texture_format::cTFRGBA32;
image_format = Image::FORMAT_RGBA8;
}
- } break;
- case BASIS_DECOMPRESS_RG_AS_RA: {
- if (s3tc_supported) {
- basisu_format = basist::transcoder_texture_format::cTFBC3;
- image_format = Image::FORMAT_DXT5_RA_AS_RG;
- } else if (etc2_supported) {
- basisu_format = basist::transcoder_texture_format::cTFETC2;
- image_format = Image::FORMAT_ETC2_RA_AS_RG;
- } else {
- // No supported VRAM compression formats, decompress.
- basisu_format = basist::transcoder_texture_format::cTFRGBA32;
- image_format = Image::FORMAT_RGBA8;
- needs_ra_rg_swap = true;
- }
+
} break;
}
@@ -324,6 +362,15 @@ Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size) {
image->convert_ra_rgba8_to_rg();
}
+ if (needs_rg_trim) {
+ // Remove unnecessary color channels from uncompressed textures.
+ if (decompress_format == BASIS_DECOMPRESS_R) {
+ image->convert(Image::FORMAT_R8);
+ } else if (decompress_format == BASIS_DECOMPRESS_RG || decompress_format == BASIS_DECOMPRESS_RG_AS_RA) {
+ image->convert(Image::FORMAT_RG8);
+ }
+ }
+
return image;
}
diff --git a/modules/basis_universal/image_compress_basisu.h b/modules/basis_universal/image_compress_basisu.h
index ac5d62ae73..5e36d448f6 100644
--- a/modules/basis_universal/image_compress_basisu.h
+++ b/modules/basis_universal/image_compress_basisu.h
@@ -38,6 +38,7 @@ enum BasisDecompressFormat {
BASIS_DECOMPRESS_RGB,
BASIS_DECOMPRESS_RGBA,
BASIS_DECOMPRESS_RG_AS_RA,
+ BASIS_DECOMPRESS_R,
};
void basis_universal_init();
diff --git a/modules/csg/editor/csg_gizmos.cpp b/modules/csg/editor/csg_gizmos.cpp
index 72676f4a40..3712441e51 100644
--- a/modules/csg/editor/csg_gizmos.cpp
+++ b/modules/csg/editor/csg_gizmos.cpp
@@ -541,7 +541,7 @@ EditorPluginCSG::EditorPluginCSG() {
Node3DEditor::get_singleton()->add_gizmo_plugin(gizmo_plugin);
csg_shape_editor = memnew(CSGShapeEditor);
- EditorNode::get_singleton()->get_main_screen_control()->add_child(csg_shape_editor);
+ EditorNode::get_singleton()->get_gui_base()->add_child(csg_shape_editor);
}
#endif // TOOLS_ENABLED
diff --git a/modules/fbx/fbx_document.cpp b/modules/fbx/fbx_document.cpp
index 8d4d0234da..ce097092fb 100644
--- a/modules/fbx/fbx_document.cpp
+++ b/modules/fbx/fbx_document.cpp
@@ -288,14 +288,8 @@ String FBXDocument::_gen_unique_name(HashSet<String> &unique_names, const String
}
String FBXDocument::_sanitize_animation_name(const String &p_name) {
- // Animations disallow the normal node invalid characters as well as "," and "["
- // (See animation/animation_player.cpp::add_animation)
-
- // TODO: Consider adding invalid_characters or a validate_animation_name to animation_player to mirror Node.
String anim_name = p_name.validate_node_name();
- anim_name = anim_name.replace(",", "");
- anim_name = anim_name.replace("[", "");
- return anim_name;
+ return AnimationLibrary::validate_library_name(anim_name);
}
String FBXDocument::_gen_unique_animation_name(Ref<FBXState> p_state, const String &p_name) {
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index 524f528f76..cf1cd55355 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -3486,10 +3486,10 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
opt = opt.substr(1);
}
- // The path needs quotes if at least one of its components (excluding `/` separations)
+ // The path needs quotes if at least one of its components (excluding `%` prefix and `/` separations)
// is not a valid identifier.
bool path_needs_quote = false;
- for (const String &part : opt.split("/")) {
+ for (const String &part : opt.trim_prefix("%").split("/")) {
if (!part.is_valid_ascii_identifier()) {
path_needs_quote = true;
break;
diff --git a/modules/gdscript/gdscript_tokenizer_buffer.cpp b/modules/gdscript/gdscript_tokenizer_buffer.cpp
index e53bc5bc41..2046480f0e 100644
--- a/modules/gdscript/gdscript_tokenizer_buffer.cpp
+++ b/modules/gdscript/gdscript_tokenizer_buffer.cpp
@@ -296,6 +296,7 @@ Vector<uint8_t> GDScriptTokenizerBuffer::parse_code_string(const String &p_code,
encode_uint32(identifier_map.size(), &contents.write[0]);
encode_uint32(constant_map.size(), &contents.write[4]);
encode_uint32(token_lines.size(), &contents.write[8]);
+ encode_uint32(0, &contents.write[12]); // Unused, kept for compatibility. Please remove at next `TOKENIZER_VERSION` increment.
encode_uint32(token_counter, &contents.write[16]);
int buf_pos = 20;
diff --git a/modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_unique.cfg b/modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_unique.cfg
new file mode 100644
index 0000000000..36c150f6e3
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_unique.cfg
@@ -0,0 +1,9 @@
+[input]
+scene="res://completion/get_node/get_node.tscn"
+[output]
+include=[
+ {"display": "%UniqueA"},
+]
+exclude=[
+ {"display": "\"%UniqueA\""},
+]
diff --git a/modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_unique.gd b/modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_unique.gd
new file mode 100644
index 0000000000..def050e938
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_unique.gd
@@ -0,0 +1,5 @@
+extends Node
+
+func a():
+ $➡
+ pass
diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp
index 4653df7afe..cf1a1ea4b3 100644
--- a/modules/gltf/gltf_document.cpp
+++ b/modules/gltf/gltf_document.cpp
@@ -491,14 +491,8 @@ String GLTFDocument::_gen_unique_name(Ref<GLTFState> p_state, const String &p_na
}
String GLTFDocument::_sanitize_animation_name(const String &p_name) {
- // Animations disallow the normal node invalid characters as well as "," and "["
- // (See animation/animation_player.cpp::add_animation)
-
- // TODO: Consider adding invalid_characters or a validate_animation_name to animation_player to mirror Node.
String anim_name = p_name.validate_node_name();
- anim_name = anim_name.replace(",", "");
- anim_name = anim_name.replace("[", "");
- return anim_name;
+ return AnimationLibrary::validate_library_name(anim_name);
}
String GLTFDocument::_gen_unique_animation_name(Ref<GLTFState> p_state, const String &p_name) {
diff --git a/modules/gridmap/editor/grid_map_editor_plugin.cpp b/modules/gridmap/editor/grid_map_editor_plugin.cpp
index ea63e07104..6b010335e6 100644
--- a/modules/gridmap/editor/grid_map_editor_plugin.cpp
+++ b/modules/gridmap/editor/grid_map_editor_plugin.cpp
@@ -34,6 +34,7 @@
#include "core/input/input.h"
#include "core/os/keyboard.h"
+#include "editor/editor_main_screen.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
@@ -958,7 +959,7 @@ void GridMapEditor::edit(GridMap *p_gridmap) {
_update_selection_transform();
_update_paste_indicator();
- spatial_editor = Object::cast_to<Node3DEditorPlugin>(EditorNode::get_singleton()->get_editor_plugin_screen());
+ spatial_editor = Object::cast_to<Node3DEditorPlugin>(EditorNode::get_singleton()->get_editor_main_screen()->get_selected_plugin());
if (!node) {
set_process(false);
diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp
index 7322a47630..df240a5965 100644
--- a/modules/mono/editor/editor_internal_calls.cpp
+++ b/modules/mono/editor/editor_internal_calls.cpp
@@ -41,6 +41,7 @@
#include "core/os/os.h"
#include "core/version.h"
#include "editor/debugger/editor_debugger_node.h"
+#include "editor/editor_main_screen.h"
#include "editor/editor_node.h"
#include "editor/editor_paths.h"
#include "editor/editor_settings.h"
@@ -165,7 +166,7 @@ bool godot_icall_Internal_ScriptEditorEdit(Resource *p_resource, int32_t p_line,
}
void godot_icall_Internal_EditorNodeShowScriptScreen() {
- EditorNode::get_singleton()->editor_select(EditorNode::EDITOR_SCRIPT);
+ EditorNode::get_editor_main_screen()->select(EditorMainScreen::EDITOR_SCRIPT);
}
void godot_icall_Internal_EditorRunPlay() {
diff --git a/modules/navigation/editor/navigation_mesh_editor_plugin.cpp b/modules/navigation/editor/navigation_mesh_editor_plugin.cpp
index f37ed9b168..7f0cbc7b5e 100644
--- a/modules/navigation/editor/navigation_mesh_editor_plugin.cpp
+++ b/modules/navigation/editor/navigation_mesh_editor_plugin.cpp
@@ -176,7 +176,7 @@ void NavigationMeshEditorPlugin::make_visible(bool p_visible) {
NavigationMeshEditorPlugin::NavigationMeshEditorPlugin() {
navigation_mesh_editor = memnew(NavigationMeshEditor);
- EditorNode::get_singleton()->get_main_screen_control()->add_child(navigation_mesh_editor);
+ EditorNode::get_singleton()->get_gui_base()->add_child(navigation_mesh_editor);
add_control_to_container(CONTAINER_SPATIAL_EDITOR_MENU, navigation_mesh_editor->bake_hbox);
navigation_mesh_editor->hide();
navigation_mesh_editor->bake_hbox->hide();
diff --git a/platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotEditor.kt b/platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotEditor.kt
index 9f0440e87d..328ff9a3bd 100644
--- a/platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotEditor.kt
+++ b/platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotEditor.kt
@@ -45,15 +45,13 @@ open class GodotEditor : BaseGodotEditor() {
internal val XR_RUN_GAME_INFO = EditorWindowInfo(GodotXRGame::class.java, 1667, ":GodotXRGame")
- internal const val USE_ANCHOR_API_PERMISSION = "com.oculus.permission.USE_ANCHOR_API"
internal const val USE_SCENE_PERMISSION = "com.oculus.permission.USE_SCENE"
}
override fun getExcludedPermissions(): MutableSet<String> {
val excludedPermissions = super.getExcludedPermissions()
- // The USE_ANCHOR_API and USE_SCENE permissions are requested when the "xr/openxr/enabled"
- // project setting is enabled.
- excludedPermissions.add(USE_ANCHOR_API_PERMISSION)
+ // The USE_SCENE permission is requested when the "xr/openxr/enabled" project setting
+ // is enabled.
excludedPermissions.add(USE_SCENE_PERMISSION)
return excludedPermissions
}
diff --git a/platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotXRGame.kt b/platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotXRGame.kt
index d71fbb53f2..5db2879aad 100644
--- a/platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotXRGame.kt
+++ b/platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotXRGame.kt
@@ -31,7 +31,6 @@
package org.godotengine.editor
import org.godotengine.godot.GodotLib
-import org.godotengine.godot.utils.PermissionsUtil
import org.godotengine.godot.xr.XRMode
/**
@@ -62,8 +61,16 @@ open class GodotXRGame: GodotGame() {
val openxrEnabled = GodotLib.getGlobal("xr/openxr/enabled").toBoolean()
if (openxrEnabled) {
- permissionsToEnable.add(USE_ANCHOR_API_PERMISSION)
- permissionsToEnable.add(USE_SCENE_PERMISSION)
+ // We only request permissions when the `automatically_request_runtime_permissions`
+ // project setting is enabled.
+ // If the project setting is not defined, we fall-back to the default behavior which is
+ // to automatically request permissions.
+ val automaticallyRequestPermissionsSetting = GodotLib.getGlobal("xr/openxr/extensions/automatically_request_runtime_permissions")
+ val automaticPermissionsRequestEnabled = automaticallyRequestPermissionsSetting.isNullOrEmpty() ||
+ automaticallyRequestPermissionsSetting.toBoolean()
+ if (automaticPermissionsRequestEnabled) {
+ permissionsToEnable.add(USE_SCENE_PERMISSION)
+ }
}
return permissionsToEnable
diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp
index 390677df22..6086f67a1e 100644
--- a/platform/android/java_godot_lib_jni.cpp
+++ b/platform/android/java_godot_lib_jni.cpp
@@ -472,19 +472,22 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusout(JNIEnv *env,
JNIEXPORT jstring JNICALL Java_org_godotengine_godot_GodotLib_getGlobal(JNIEnv *env, jclass clazz, jstring path) {
String js = jstring_to_string(path, env);
- return env->NewStringUTF(GLOBAL_GET(js).operator String().utf8().get_data());
+ Variant setting_with_override = GLOBAL_GET(js);
+ String setting_value = (setting_with_override.get_type() == Variant::NIL) ? "" : setting_with_override;
+ return env->NewStringUTF(setting_value.utf8().get_data());
}
JNIEXPORT jstring JNICALL Java_org_godotengine_godot_GodotLib_getEditorSetting(JNIEnv *env, jclass clazz, jstring p_setting_key) {
- String editor_setting = "";
+ String editor_setting_value = "";
#ifdef TOOLS_ENABLED
String godot_setting_key = jstring_to_string(p_setting_key, env);
- editor_setting = EDITOR_GET(godot_setting_key).operator String();
+ Variant editor_setting = EDITOR_GET(godot_setting_key);
+ editor_setting_value = (editor_setting.get_type() == Variant::NIL) ? "" : editor_setting;
#else
WARN_PRINT("Access to the Editor Settings in only available on Editor builds");
#endif
- return env->NewStringUTF(editor_setting.utf8().get_data());
+ return env->NewStringUTF(editor_setting_value.utf8().get_data());
}
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_callobject(JNIEnv *env, jclass clazz, jlong ID, jstring method, jobjectArray params) {
diff --git a/platform/linuxbsd/wayland/wayland_thread.cpp b/platform/linuxbsd/wayland/wayland_thread.cpp
index 66be313ff6..08b20c5b42 100644
--- a/platform/linuxbsd/wayland/wayland_thread.cpp
+++ b/platform/linuxbsd/wayland/wayland_thread.cpp
@@ -1395,6 +1395,8 @@ void WaylandThread::_wl_pointer_on_leave(void *data, struct wl_pointer *wl_point
ss->pointed_surface = nullptr;
+ ss->pointer_data_buffer.pressed_button_mask.clear();
+
Ref<WindowEventMessage> msg;
msg.instantiate();
msg->event = DisplayServer::WINDOW_EVENT_MOUSE_EXIT;
diff --git a/scene/3d/path_3d.cpp b/scene/3d/path_3d.cpp
index 20d646fe1e..64259a24b0 100644
--- a/scene/3d/path_3d.cpp
+++ b/scene/3d/path_3d.cpp
@@ -216,24 +216,7 @@ void Path3D::_bind_methods() {
ADD_SIGNAL(MethodInfo("curve_changed"));
}
-// Update transform, in deferred mode by default to avoid superfluity.
-void PathFollow3D::update_transform(bool p_immediate) {
- transform_dirty = true;
-
- if (p_immediate) {
- _update_transform();
- } else {
- callable_mp(this, &PathFollow3D::_update_transform).call_deferred();
- }
-}
-
-// Update transform immediately .
-void PathFollow3D::_update_transform() {
- if (!transform_dirty) {
- return;
- }
- transform_dirty = false;
-
+void PathFollow3D::update_transform() {
if (!path) {
return;
}
@@ -286,9 +269,7 @@ void PathFollow3D::_notification(int p_what) {
Node *parent = get_parent();
if (parent) {
path = Object::cast_to<Path3D>(parent);
- if (path) {
- update_transform();
- }
+ update_transform();
}
} break;
@@ -414,6 +395,9 @@ void PathFollow3D::_bind_methods() {
void PathFollow3D::set_progress(real_t p_progress) {
ERR_FAIL_COND(!isfinite(p_progress));
+ if (progress == p_progress) {
+ return;
+ }
progress = p_progress;
if (path) {
@@ -435,10 +419,11 @@ void PathFollow3D::set_progress(real_t p_progress) {
}
void PathFollow3D::set_h_offset(real_t p_h_offset) {
- h_offset = p_h_offset;
- if (path) {
- update_transform();
+ if (h_offset == p_h_offset) {
+ return;
}
+ h_offset = p_h_offset;
+ update_transform();
}
real_t PathFollow3D::get_h_offset() const {
@@ -446,10 +431,11 @@ real_t PathFollow3D::get_h_offset() const {
}
void PathFollow3D::set_v_offset(real_t p_v_offset) {
- v_offset = p_v_offset;
- if (path) {
- update_transform();
+ if (v_offset == p_v_offset) {
+ return;
}
+ v_offset = p_v_offset;
+ update_transform();
}
real_t PathFollow3D::get_v_offset() const {
@@ -476,6 +462,9 @@ real_t PathFollow3D::get_progress_ratio() const {
}
void PathFollow3D::set_rotation_mode(RotationMode p_rotation_mode) {
+ if (rotation_mode == p_rotation_mode) {
+ return;
+ }
rotation_mode = p_rotation_mode;
update_configuration_warnings();
@@ -487,6 +476,9 @@ PathFollow3D::RotationMode PathFollow3D::get_rotation_mode() const {
}
void PathFollow3D::set_use_model_front(bool p_use_model_front) {
+ if (use_model_front == p_use_model_front) {
+ return;
+ }
use_model_front = p_use_model_front;
update_transform();
}
@@ -496,6 +488,9 @@ bool PathFollow3D::is_using_model_front() const {
}
void PathFollow3D::set_loop(bool p_loop) {
+ if (loop == p_loop) {
+ return;
+ }
loop = p_loop;
update_transform();
}
@@ -505,6 +500,9 @@ bool PathFollow3D::has_loop() const {
}
void PathFollow3D::set_tilt_enabled(bool p_enabled) {
+ if (tilt_enabled == p_enabled) {
+ return;
+ }
tilt_enabled = p_enabled;
update_transform();
}
diff --git a/scene/3d/path_3d.h b/scene/3d/path_3d.h
index 0c9111bb8e..fb4f301375 100644
--- a/scene/3d/path_3d.h
+++ b/scene/3d/path_3d.h
@@ -90,7 +90,6 @@ protected:
void _validate_property(PropertyInfo &p_property) const;
void _notification(int p_what);
- void _update_transform();
static void _bind_methods();
@@ -124,7 +123,7 @@ public:
PackedStringArray get_configuration_warnings() const override;
- void update_transform(bool p_immediate = false);
+ void update_transform();
static Transform3D correct_posture(Transform3D p_transform, PathFollow3D::RotationMode p_rotation_mode);
diff --git a/scene/animation/animation_player.compat.inc b/scene/animation/animation_player.compat.inc
index 39efacc4ca..974eb2a7d8 100644
--- a/scene/animation/animation_player.compat.inc
+++ b/scene/animation/animation_player.compat.inc
@@ -58,14 +58,6 @@ void AnimationPlayer::_seek_bind_compat_80813(double p_time, bool p_update) {
seek(p_time, p_update, false);
}
-void AnimationPlayer::_play_compat_84906(const StringName &p_name, double p_custom_blend, float p_custom_scale, bool p_from_end) {
- play(p_name, p_custom_blend, p_custom_scale, p_from_end);
-}
-
-void AnimationPlayer::_play_backwards_compat_84906(const StringName &p_name, double p_custom_blend) {
- play_backwards(p_name, p_custom_blend);
-}
-
void AnimationPlayer::_bind_compatibility_methods() {
ClassDB::bind_method(D_METHOD("set_process_callback", "mode"), &AnimationPlayer::_set_process_callback_bind_compat_80813);
ClassDB::bind_method(D_METHOD("get_process_callback"), &AnimationPlayer::_get_process_callback_bind_compat_80813);
@@ -74,8 +66,6 @@ void AnimationPlayer::_bind_compatibility_methods() {
ClassDB::bind_method(D_METHOD("set_root", "path"), &AnimationPlayer::_set_root_bind_compat_80813);
ClassDB::bind_method(D_METHOD("get_root"), &AnimationPlayer::_get_root_bind_compat_80813);
ClassDB::bind_compatibility_method(D_METHOD("seek", "seconds", "update"), &AnimationPlayer::_seek_bind_compat_80813, DEFVAL(false));
- ClassDB::bind_compatibility_method(D_METHOD("play", "name", "custom_blend", "custom_speed", "from_end"), &AnimationPlayer::_play_compat_84906, DEFVAL(""), DEFVAL(-1), DEFVAL(1.0), DEFVAL(false));
- ClassDB::bind_compatibility_method(D_METHOD("play_backwards", "name", "custom_blend"), &AnimationPlayer::_play_backwards_compat_84906, DEFVAL(""), DEFVAL(-1));
BIND_ENUM_CONSTANT(ANIMATION_PROCESS_PHYSICS);
BIND_ENUM_CONSTANT(ANIMATION_PROCESS_IDLE);
BIND_ENUM_CONSTANT(ANIMATION_PROCESS_MANUAL);
diff --git a/scene/gui/control.compat.inc b/scene/gui/control.compat.inc
deleted file mode 100644
index 96ee720d90..0000000000
--- a/scene/gui/control.compat.inc
+++ /dev/null
@@ -1,48 +0,0 @@
-/**************************************************************************/
-/* control.compat.inc */
-/**************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/**************************************************************************/
-/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
-/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/**************************************************************************/
-
-#ifndef DISABLE_DEPRECATED
-
-void Control::_bind_compatibility_methods() {
- ClassDB::bind_compatibility_method(D_METHOD("get_theme_icon", "name", "theme_type"), &Control::get_theme_icon, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("get_theme_stylebox", "name", "theme_type"), &Control::get_theme_stylebox, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("get_theme_font", "name", "theme_type"), &Control::get_theme_font, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("get_theme_font_size", "name", "theme_type"), &Control::get_theme_font_size, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("get_theme_color", "name", "theme_type"), &Control::get_theme_color, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("get_theme_constant", "name", "theme_type"), &Control::get_theme_constant, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("has_theme_icon", "name", "theme_type"), &Control::has_theme_icon, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("has_theme_stylebox", "name", "theme_type"), &Control::has_theme_stylebox, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("has_theme_font", "name", "theme_type"), &Control::has_theme_font, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("has_theme_font_size", "name", "theme_type"), &Control::has_theme_font_size, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("has_theme_color", "name", "theme_type"), &Control::has_theme_color, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("has_theme_constant", "name", "theme_type"), &Control::has_theme_constant, DEFVAL(""));
-}
-
-#endif
diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp
index e030828595..cecddebe88 100644
--- a/scene/gui/control.cpp
+++ b/scene/gui/control.cpp
@@ -29,7 +29,6 @@
/**************************************************************************/
#include "control.h"
-#include "control.compat.inc"
#include "container.h"
#include "core/config/project_settings.h"
diff --git a/scene/gui/control.h b/scene/gui/control.h
index c784d4330d..2655b14562 100644
--- a/scene/gui/control.h
+++ b/scene/gui/control.h
@@ -348,10 +348,6 @@ protected:
void _notification(int p_notification);
static void _bind_methods();
-#ifndef DISABLE_DEPRECATED
- static void _bind_compatibility_methods();
-#endif
-
// Exposed virtual methods.
GDVIRTUAL1RC(bool, _has_point, Vector2)
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index c2818edd9c..3b5d4fc33e 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -45,6 +45,70 @@
#include "editor/editor_settings.h"
#endif
+void LineEdit::_edit() {
+ if (!is_inside_tree()) {
+ return;
+ }
+
+ if (!has_focus()) {
+ grab_focus();
+ }
+
+ if (!editable || editing) {
+ return;
+ }
+
+ editing = true;
+ _validate_caret_can_draw();
+
+ DisplayServer::WindowID wid = get_window() ? get_window()->get_window_id() : DisplayServer::INVALID_WINDOW_ID;
+ if (wid != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) {
+ DisplayServer::get_singleton()->window_set_ime_active(true, wid);
+ Point2 pos = Point2(get_caret_pixel_pos().x, (get_size().y + theme_cache.font->get_height(theme_cache.font_size)) / 2) + get_global_position();
+ if (get_window()->get_embedder()) {
+ pos += get_viewport()->get_popup_base_transform().get_origin();
+ }
+ DisplayServer::get_singleton()->window_set_ime_position(pos, wid);
+ }
+
+ show_virtual_keyboard();
+ queue_redraw();
+ emit_signal(SNAME("editing_toggled"), true);
+}
+
+void LineEdit::_unedit() {
+ if (!editing) {
+ return;
+ }
+
+ editing = false;
+ _validate_caret_can_draw();
+
+ DisplayServer::WindowID wid = get_window() ? get_window()->get_window_id() : DisplayServer::INVALID_WINDOW_ID;
+ if (wid != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) {
+ DisplayServer::get_singleton()->window_set_ime_position(Point2(), wid);
+ DisplayServer::get_singleton()->window_set_ime_active(false, wid);
+ }
+ ime_text = "";
+ ime_selection = Point2();
+ _shape();
+ set_caret_column(caret_column); // Update scroll_offset.
+
+ if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) {
+ DisplayServer::get_singleton()->virtual_keyboard_hide();
+ }
+
+ if (deselect_on_focus_loss_enabled && !selection.drag_attempt) {
+ deselect();
+ }
+
+ emit_signal(SNAME("editing_toggled"), false);
+}
+
+bool LineEdit::is_editing() const {
+ return editing;
+}
+
void LineEdit::_swap_current_input_direction() {
if (input_direction == TEXT_DIRECTION_LTR) {
input_direction = TEXT_DIRECTION_RTL;
@@ -52,7 +116,6 @@ void LineEdit::_swap_current_input_direction() {
input_direction = TEXT_DIRECTION_LTR;
}
set_caret_column(get_caret_column());
- queue_redraw();
}
void LineEdit::_move_caret_left(bool p_select, bool p_move_by_word) {
@@ -240,6 +303,11 @@ void LineEdit::_delete(bool p_word, bool p_all_to_right) {
}
void LineEdit::unhandled_key_input(const Ref<InputEvent> &p_event) {
+ // Return to prevent editing if just focused.
+ if (!editing) {
+ return;
+ }
+
Ref<InputEventKey> k = p_event;
if (k.is_valid()) {
@@ -265,26 +333,38 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
Ref<InputEventMouseButton> b = p_event;
- if (b.is_valid()) {
- if (ime_text.length() != 0) {
- // Ignore mouse clicks in IME input mode.
- return;
- }
- if (b->is_pressed() && b->get_button_index() == MouseButton::RIGHT && context_menu_enabled) {
- _update_context_menu();
- menu->set_position(get_screen_position() + get_local_mouse_position());
- menu->reset_size();
- menu->popup();
- grab_focus();
+ // Ignore mouse clicks in IME input mode.
+ if (b.is_valid() && ime_text.is_empty()) {
+ if (b->is_pressed() && b->get_button_index() == MouseButton::RIGHT) {
+ if (editable && !selection.enabled) {
+ set_caret_at_pixel_pos(b->get_position().x);
+ }
+
+ if (context_menu_enabled) {
+ _update_context_menu();
+ menu->set_position(get_screen_position() + get_local_mouse_position());
+ menu->reset_size();
+ menu->popup();
+ }
+
+ if (editable && !editing) {
+ _edit();
+ }
+
accept_event();
return;
}
- if (is_middle_mouse_paste_enabled() && b->is_pressed() && b->get_button_index() == MouseButton::MIDDLE && is_editable() && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) {
+ if (editable && is_middle_mouse_paste_enabled() && b->is_pressed() && b->get_button_index() == MouseButton::MIDDLE && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) {
String paste_buffer = DisplayServer::get_singleton()->clipboard_get_primary().strip_escapes();
deselect();
set_caret_at_pixel_pos(b->get_position().x);
+
+ if (!editing) {
+ _edit();
+ }
+
if (!paste_buffer.is_empty()) {
insert_text_at_caret(paste_buffer);
@@ -295,7 +375,6 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
text_changed_dirty = true;
}
}
- grab_focus();
accept_event();
return;
}
@@ -304,10 +383,13 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
return;
}
- _reset_caret_blink_timer();
+ if (editing) {
+ _reset_caret_blink_timer();
+ }
+
if (b->is_pressed()) {
accept_event(); // Don't pass event further when clicked on text field.
- if (!text.is_empty() && is_editable() && _is_over_clear_button(b->get_position())) {
+ if (editable && !text.is_empty() && _is_over_clear_button(b->get_position())) {
clear_button_status.press_attempt = true;
clear_button_status.pressing_inside = true;
queue_redraw();
@@ -330,7 +412,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
const int triple_click_tolerance = 5;
const bool is_triple_click = !b->is_double_click() && (OS::get_singleton()->get_ticks_msec() - last_dblclk) < triple_click_timeout && b->get_position().distance_to(last_dblclk_pos) < triple_click_tolerance;
- if (is_triple_click && text.length()) {
+ if (is_triple_click && !text.is_empty()) {
// Triple-click select all.
selection.enabled = true;
selection.begin = 0;
@@ -377,13 +459,17 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
}
}
+ if (editable && !editing) {
+ _edit();
+ return;
+ }
queue_redraw();
} else {
if (selection.enabled && !pass && b->get_button_index() == MouseButton::LEFT && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) {
DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text());
}
- if (!text.is_empty() && is_editable() && clear_button_enabled) {
+ if (editable && !text.is_empty() && clear_button_enabled) {
bool press_attempt = clear_button_status.press_attempt;
clear_button_status.press_attempt = false;
if (press_attempt && clear_button_status.pressing_inside && _is_over_clear_button(b->get_position())) {
@@ -416,7 +502,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
Ref<InputEventMouseMotion> m = p_event;
if (m.is_valid()) {
- if (!text.is_empty() && is_editable() && clear_button_enabled) {
+ if (editable && !text.is_empty() && clear_button_enabled) {
bool last_press_inside = clear_button_status.pressing_inside;
clear_button_status.pressing_inside = clear_button_status.press_attempt && _is_over_clear_button(m->get_position());
if (last_press_inside != clear_button_status.pressing_inside) {
@@ -462,221 +548,240 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
Ref<InputEventKey> k = p_event;
- if (k.is_valid()) {
- if (!k->is_pressed()) {
- if (alt_start && k->get_keycode() == Key::ALT) {
- alt_start = false;
- if ((alt_code > 0x31 && alt_code < 0xd800) || (alt_code > 0xdfff && alt_code <= 0x10ffff)) {
- char32_t ucodestr[2] = { (char32_t)alt_code, 0 };
- insert_text_at_caret(ucodestr);
- }
- accept_event();
- return;
- }
- return;
- }
-
- // Alt + Unicode input:
- if (k->is_alt_pressed()) {
- if (!alt_start) {
- if (k->get_keycode() == Key::KP_ADD) {
- alt_start = true;
- alt_code = 0;
- accept_event();
- return;
- }
- } else {
- if (k->get_keycode() >= Key::KEY_0 && k->get_keycode() <= Key::KEY_9) {
- alt_code = alt_code << 4;
- alt_code += (uint32_t)(k->get_keycode() - Key::KEY_0);
- }
- if (k->get_keycode() >= Key::KP_0 && k->get_keycode() <= Key::KP_9) {
- alt_code = alt_code << 4;
- alt_code += (uint32_t)(k->get_keycode() - Key::KP_0);
- }
- if (k->get_keycode() >= Key::A && k->get_keycode() <= Key::F) {
- alt_code = alt_code << 4;
- alt_code += (uint32_t)(k->get_keycode() - Key::A) + 10;
- }
- accept_event();
- return;
- }
- }
+ if (k.is_null()) {
+ return;
+ }
- if (context_menu_enabled) {
- if (k->is_action("ui_menu", true)) {
- _update_context_menu();
- Point2 pos = Point2(get_caret_pixel_pos().x, (get_size().y + theme_cache.font->get_height(theme_cache.font_size)) / 2);
- menu->set_position(get_screen_position() + pos);
- menu->reset_size();
- menu->popup();
- menu->grab_focus();
+ if (editable && !editing && k->is_action_pressed("ui_text_submit", false)) {
+ _edit();
+ return;
+ }
- accept_event();
- return;
- }
- }
+ if (!editing) {
+ return;
+ }
- // Default is ENTER and KP_ENTER. Cannot use ui_accept as default includes SPACE.
- if (k->is_action("ui_text_submit", false)) {
- emit_signal(SNAME("text_submitted"), text);
- if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) {
- DisplayServer::get_singleton()->virtual_keyboard_hide();
+ if (!k->is_pressed()) {
+ if (alt_start && k->get_keycode() == Key::ALT) {
+ alt_start = false;
+ if ((alt_code > 0x31 && alt_code < 0xd800) || (alt_code > 0xdfff && alt_code <= 0x10ffff)) {
+ char32_t ucodestr[2] = { (char32_t)alt_code, 0 };
+ insert_text_at_caret(ucodestr);
}
accept_event();
return;
}
+ return;
+ }
- if (k->is_action("ui_cancel")) {
- callable_mp((Control *)this, &Control::release_focus).call_deferred();
- accept_event();
- return;
- }
-
- if (is_shortcut_keys_enabled()) {
- if (k->is_action("ui_copy", true)) {
- copy_text();
+ // Alt + Unicode input:
+ if (k->is_alt_pressed()) {
+ if (!alt_start) {
+ if (k->get_keycode() == Key::KP_ADD) {
+ alt_start = true;
+ alt_code = 0;
accept_event();
return;
}
-
- if (k->is_action("ui_text_select_all", true)) {
- select();
- accept_event();
- return;
+ } else {
+ if (k->get_keycode() >= Key::KEY_0 && k->get_keycode() <= Key::KEY_9) {
+ alt_code = alt_code << 4;
+ alt_code += (uint32_t)(k->get_keycode() - Key::KEY_0);
}
-
- // Cut / Paste
- if (k->is_action("ui_cut", true)) {
- cut_text();
- accept_event();
- return;
+ if (k->get_keycode() >= Key::KP_0 && k->get_keycode() <= Key::KP_9) {
+ alt_code = alt_code << 4;
+ alt_code += (uint32_t)(k->get_keycode() - Key::KP_0);
}
-
- if (k->is_action("ui_paste", true)) {
- paste_text();
- accept_event();
- return;
+ if (k->get_keycode() >= Key::A && k->get_keycode() <= Key::F) {
+ alt_code = alt_code << 4;
+ alt_code += (uint32_t)(k->get_keycode() - Key::A) + 10;
}
-
- // Undo / Redo
- if (k->is_action("ui_undo", true)) {
- undo();
- accept_event();
- return;
- }
-
- if (k->is_action("ui_redo", true)) {
- redo();
- accept_event();
- return;
- }
- }
-
- // BACKSPACE
- if (k->is_action("ui_text_backspace_all_to_left", true)) {
- _backspace(false, true);
- accept_event();
- return;
- }
- if (k->is_action("ui_text_backspace_word", true)) {
- _backspace(true);
- accept_event();
- return;
- }
- if (k->is_action("ui_text_backspace", true)) {
- _backspace();
accept_event();
return;
}
+ }
+
+ if (context_menu_enabled) {
+ if (k->is_action("ui_menu", true)) {
+ _update_context_menu();
+ Point2 pos = Point2(get_caret_pixel_pos().x, (get_size().y + theme_cache.font->get_height(theme_cache.font_size)) / 2);
+ menu->set_position(get_screen_position() + pos);
+ menu->reset_size();
+ menu->popup();
+ menu->grab_focus();
- // DELETE
- if (k->is_action("ui_text_delete_all_to_right", true)) {
- _delete(false, true);
- accept_event();
- return;
- }
- if (k->is_action("ui_text_delete_word", true)) {
- _delete(true);
accept_event();
return;
}
- if (k->is_action("ui_text_delete", true)) {
- _delete();
- accept_event();
- return;
+ }
+
+ // Default is ENTER and KP_ENTER. Cannot use ui_accept as default includes SPACE.
+ if (k->is_action_pressed("ui_text_submit")) {
+ emit_signal(SNAME("text_submitted"), text);
+ if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) {
+ DisplayServer::get_singleton()->virtual_keyboard_hide();
}
- // Cursor Movement
+ if (editing) {
+ _unedit();
+ }
- k = k->duplicate();
- bool shift_pressed = k->is_shift_pressed();
- // Remove shift or else actions will not match. Use above variable for selection.
- k->set_shift_pressed(false);
+ accept_event();
+ return;
+ }
- if (k->is_action("ui_text_caret_word_left", true)) {
- _move_caret_left(shift_pressed, true);
- accept_event();
- return;
+ if (k->is_action("ui_cancel")) {
+ if (editing) {
+ _unedit();
}
- if (k->is_action("ui_text_caret_left", true)) {
- _move_caret_left(shift_pressed);
+
+ accept_event();
+ return;
+ }
+
+ if (is_shortcut_keys_enabled()) {
+ if (k->is_action("ui_copy", true)) {
+ copy_text();
accept_event();
return;
}
- if (k->is_action("ui_text_caret_word_right", true)) {
- _move_caret_right(shift_pressed, true);
+
+ if (k->is_action("ui_text_select_all", true)) {
+ select();
accept_event();
return;
}
- if (k->is_action("ui_text_caret_right", true)) {
- _move_caret_right(shift_pressed, false);
+
+ // Cut / Paste
+ if (k->is_action("ui_cut", true)) {
+ cut_text();
accept_event();
return;
}
- // Up = Home, Down = End
- if (k->is_action("ui_text_caret_up", true) || k->is_action("ui_text_caret_line_start", true) || k->is_action("ui_text_caret_page_up", true)) {
- _move_caret_start(shift_pressed);
+ if (k->is_action("ui_paste", true)) {
+ paste_text();
accept_event();
return;
}
- if (k->is_action("ui_text_caret_down", true) || k->is_action("ui_text_caret_line_end", true) || k->is_action("ui_text_caret_page_down", true)) {
- _move_caret_end(shift_pressed);
+
+ // Undo / Redo
+ if (k->is_action("ui_undo", true)) {
+ undo();
accept_event();
return;
}
- // Misc
- if (k->is_action("ui_swap_input_direction", true)) {
- _swap_current_input_direction();
+ if (k->is_action("ui_redo", true)) {
+ redo();
accept_event();
return;
}
+ }
- _reset_caret_blink_timer();
+ // BACKSPACE
+ if (k->is_action("ui_text_backspace_all_to_left", true)) {
+ _backspace(false, true);
+ accept_event();
+ return;
+ }
+ if (k->is_action("ui_text_backspace_word", true)) {
+ _backspace(true);
+ accept_event();
+ return;
+ }
+ if (k->is_action("ui_text_backspace", true)) {
+ _backspace();
+ accept_event();
+ return;
+ }
- // Allow unicode handling if:
- // * No Modifiers are pressed (except shift)
- bool allow_unicode_handling = !(k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed());
+ // DELETE
+ if (k->is_action("ui_text_delete_all_to_right", true)) {
+ _delete(false, true);
+ accept_event();
+ return;
+ }
+ if (k->is_action("ui_text_delete_word", true)) {
+ _delete(true);
+ accept_event();
+ return;
+ }
+ if (k->is_action("ui_text_delete", true)) {
+ _delete();
+ accept_event();
+ return;
+ }
- if (allow_unicode_handling && editable && k->get_unicode() >= 32) {
- // Handle Unicode if no modifiers are active.
- selection_delete();
- char32_t ucodestr[2] = { (char32_t)k->get_unicode(), 0 };
- int prev_len = text.length();
- insert_text_at_caret(ucodestr);
- if (text.length() != prev_len) {
- if (!text_changed_dirty) {
- if (is_inside_tree()) {
- callable_mp(this, &LineEdit::_text_changed).call_deferred();
- }
- text_changed_dirty = true;
+ // Cursor Movement
+
+ k = k->duplicate();
+ bool shift_pressed = k->is_shift_pressed();
+ // Remove shift or else actions will not match. Use above variable for selection.
+ k->set_shift_pressed(false);
+
+ if (k->is_action("ui_text_caret_word_left", true)) {
+ _move_caret_left(shift_pressed, true);
+ accept_event();
+ return;
+ }
+ if (k->is_action("ui_text_caret_left", true)) {
+ _move_caret_left(shift_pressed);
+ accept_event();
+ return;
+ }
+ if (k->is_action("ui_text_caret_word_right", true)) {
+ _move_caret_right(shift_pressed, true);
+ accept_event();
+ return;
+ }
+ if (k->is_action("ui_text_caret_right", true)) {
+ _move_caret_right(shift_pressed, false);
+ accept_event();
+ return;
+ }
+
+ // Up = Home, Down = End
+ if (k->is_action("ui_text_caret_up", true) || k->is_action("ui_text_caret_line_start", true) || k->is_action("ui_text_caret_page_up", true)) {
+ _move_caret_start(shift_pressed);
+ accept_event();
+ return;
+ }
+ if (k->is_action("ui_text_caret_down", true) || k->is_action("ui_text_caret_line_end", true) || k->is_action("ui_text_caret_page_down", true)) {
+ _move_caret_end(shift_pressed);
+ accept_event();
+ return;
+ }
+
+ // Misc
+ if (k->is_action("ui_swap_input_direction", true)) {
+ _swap_current_input_direction();
+ accept_event();
+ return;
+ }
+
+ _reset_caret_blink_timer();
+
+ // Allow unicode handling if:
+ // * No Modifiers are pressed (except shift)
+ bool allow_unicode_handling = !(k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed());
+
+ if (allow_unicode_handling && editable && k->get_unicode() >= 32) {
+ // Handle Unicode if no modifiers are active.
+ selection_delete();
+ char32_t ucodestr[2] = { (char32_t)k->get_unicode(), 0 };
+ int prev_len = text.length();
+ insert_text_at_caret(ucodestr);
+ if (text.length() != prev_len) {
+ if (!text_changed_dirty) {
+ if (is_inside_tree()) {
+ callable_mp(this, &LineEdit::_text_changed).call_deferred();
}
+ text_changed_dirty = true;
}
- accept_event();
- return;
}
+ accept_event();
+ return;
}
}
@@ -1107,7 +1212,7 @@ void LineEdit::_notification(int p_what) {
}
}
- if (has_focus()) {
+ if (editing) {
DisplayServer::WindowID wid = get_window() ? get_window()->get_window_id() : DisplayServer::INVALID_WINDOW_ID;
if (wid != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) {
DisplayServer::get_singleton()->window_set_ime_active(true, wid);
@@ -1121,8 +1226,6 @@ void LineEdit::_notification(int p_what) {
} break;
case NOTIFICATION_FOCUS_ENTER: {
- _validate_caret_can_draw();
-
if (select_all_on_focus) {
if (Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) {
// Select all when the mouse button is up.
@@ -1132,43 +1235,20 @@ void LineEdit::_notification(int p_what) {
}
}
- DisplayServer::WindowID wid = get_window() ? get_window()->get_window_id() : DisplayServer::INVALID_WINDOW_ID;
- if (wid != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) {
- DisplayServer::get_singleton()->window_set_ime_active(true, wid);
- Point2 pos = Point2(get_caret_pixel_pos().x, (get_size().y + theme_cache.font->get_height(theme_cache.font_size)) / 2) + get_global_position();
- if (get_window()->get_embedder()) {
- pos += get_viewport()->get_popup_base_transform().get_origin();
- }
- DisplayServer::get_singleton()->window_set_ime_position(pos, wid);
+ // Only allow editing if the LineEdit is not focused with arrow keys.
+ if (!(Input::get_singleton()->is_action_pressed("ui_up") || Input::get_singleton()->is_action_pressed("ui_down") || Input::get_singleton()->is_action_pressed("ui_left") || Input::get_singleton()->is_action_pressed("ui_right"))) {
+ _edit();
}
-
- show_virtual_keyboard();
} break;
case NOTIFICATION_FOCUS_EXIT: {
- _validate_caret_can_draw();
-
- DisplayServer::WindowID wid = get_window() ? get_window()->get_window_id() : DisplayServer::INVALID_WINDOW_ID;
- if (wid != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) {
- DisplayServer::get_singleton()->window_set_ime_position(Point2(), wid);
- DisplayServer::get_singleton()->window_set_ime_active(false, wid);
- }
- ime_text = "";
- ime_selection = Point2();
- _shape();
- set_caret_column(caret_column); // Update scroll_offset.
-
- if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) {
- DisplayServer::get_singleton()->virtual_keyboard_hide();
- }
-
- if (deselect_on_focus_loss_enabled && !selection.drag_attempt) {
- deselect();
+ if (editing) {
+ _unedit();
}
} break;
case MainLoop::NOTIFICATION_OS_IME_UPDATE: {
- if (has_focus()) {
+ if (editing) {
ime_text = DisplayServer::get_singleton()->ime_get_text();
ime_selection = DisplayServer::get_singleton()->ime_get_selection();
@@ -1178,8 +1258,6 @@ void LineEdit::_notification(int p_what) {
_shape();
set_caret_column(caret_column); // Update scroll_offset.
-
- queue_redraw();
}
} break;
@@ -1419,7 +1497,7 @@ Vector2 LineEdit::get_caret_pixel_pos() {
Vector2 ret;
CaretInfo caret;
// Get position of the start of caret.
- if (ime_text.length() != 0 && ime_selection.x != 0) {
+ if (!ime_text.is_empty() && ime_selection.x != 0) {
caret = TS->shaped_text_get_carets(text_rid, caret_column + ime_selection.x);
} else {
caret = TS->shaped_text_get_carets(text_rid, caret_column);
@@ -1432,7 +1510,7 @@ Vector2 LineEdit::get_caret_pixel_pos() {
}
// Get position of the end of caret.
- if (ime_text.length() != 0) {
+ if (!ime_text.is_empty()) {
if (ime_selection.y != 0) {
caret = TS->shaped_text_get_carets(text_rid, caret_column + ime_selection.x + ime_selection.y);
} else {
@@ -1525,11 +1603,11 @@ void LineEdit::_validate_caret_can_draw() {
draw_caret = true;
caret_blink_timer = 0.0;
}
- caret_can_draw = editable && (window_has_focus || (menu && menu->has_focus())) && (has_focus() || caret_force_displayed);
+ caret_can_draw = editing && (window_has_focus || (menu && menu->has_focus())) && (has_focus() || caret_force_displayed);
}
void LineEdit::delete_char() {
- if ((text.length() <= 0) || (caret_column == 0)) {
+ if (text.is_empty() || caret_column == 0) {
return;
}
@@ -1661,7 +1739,7 @@ void LineEdit::clear() {
_text_changed();
// This should reset virtual keyboard state if needed.
- if (has_focus()) {
+ if (editing) {
show_virtual_keyboard();
}
}
@@ -1834,7 +1912,8 @@ Size2 LineEdit::get_minimum_size() const {
Size2 min_size;
// Minimum size of text.
- float em_space_size = font->get_char_size('M', font_size).x;
+ // W is wider than M in most fonts, Using M may result in hiding the last digit when using float values in SpinBox, ie. ColorPicker RAW values.
+ float em_space_size = font->get_char_size('W', font_size).x;
min_size.width = theme_cache.minimum_character_width * em_space_size;
if (expand_to_text_length) {
@@ -1932,7 +2011,8 @@ void LineEdit::select_all() {
return;
}
- if (!text.length()) {
+ if (text.is_empty()) {
+ set_caret_column(0);
return;
}
@@ -1948,6 +2028,10 @@ void LineEdit::set_editable(bool p_editable) {
}
editable = p_editable;
+
+ if (!editable && editing) {
+ _unedit();
+ }
_validate_caret_can_draw();
update_minimum_size();
@@ -2328,6 +2412,7 @@ void LineEdit::_emit_text_change() {
emit_signal(SceneStringName(text_changed), text);
text_changed_dirty = false;
}
+
PackedStringArray LineEdit::get_configuration_warnings() const {
PackedStringArray warnings = Control::get_configuration_warnings();
if (secret_character.length() > 1) {
@@ -2347,13 +2432,13 @@ void LineEdit::_shape() {
TS->shaped_text_clear(text_rid);
String t;
- if (text.length() == 0 && ime_text.length() == 0) {
+ if (text.is_empty() && ime_text.is_empty()) {
t = placeholder_translated;
} else if (pass) {
- String s = (secret_character.length() > 0) ? secret_character.left(1) : U"•";
+ String s = secret_character.is_empty() ? U"•" : secret_character.left(1);
t = s.repeat(text.length() + ime_text.length());
} else {
- if (ime_text.length() > 0) {
+ if (!ime_text.is_empty()) {
t = text.substr(0, caret_column) + ime_text + text.substr(caret_column, text.length());
} else {
t = text;
@@ -2562,6 +2647,7 @@ void LineEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_horizontal_alignment", "alignment"), &LineEdit::set_horizontal_alignment);
ClassDB::bind_method(D_METHOD("get_horizontal_alignment"), &LineEdit::get_horizontal_alignment);
+ ClassDB::bind_method(D_METHOD("is_editing"), &LineEdit::is_editing);
ClassDB::bind_method(D_METHOD("clear"), &LineEdit::clear);
ClassDB::bind_method(D_METHOD("select", "from", "to"), &LineEdit::select, DEFVAL(0), DEFVAL(-1));
ClassDB::bind_method(D_METHOD("select_all"), &LineEdit::select_all);
@@ -2642,6 +2728,7 @@ void LineEdit::_bind_methods() {
ADD_SIGNAL(MethodInfo("text_changed", PropertyInfo(Variant::STRING, "new_text")));
ADD_SIGNAL(MethodInfo("text_change_rejected", PropertyInfo(Variant::STRING, "rejected_substring")));
ADD_SIGNAL(MethodInfo("text_submitted", PropertyInfo(Variant::STRING, "new_text")));
+ ADD_SIGNAL(MethodInfo("editing_toggled", PropertyInfo(Variant::BOOL, "toggled_on")));
BIND_ENUM_CONSTANT(MENU_CUT);
BIND_ENUM_CONSTANT(MENU_COPY);
diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h
index 993bc727e4..984512745a 100644
--- a/scene/gui/line_edit.h
+++ b/scene/gui/line_edit.h
@@ -86,6 +86,7 @@ public:
private:
HorizontalAlignment alignment = HORIZONTAL_ALIGNMENT_LEFT;
+ bool editing = false;
bool editable = false;
bool pass = false;
bool text_changed_dirty = false;
@@ -205,6 +206,9 @@ private:
float base_scale = 1.0;
} theme_cache;
+ void _edit();
+ void _unedit();
+
void _clear_undo_stack();
void _clear_redo();
void _create_undo_state();
@@ -257,6 +261,8 @@ protected:
virtual void gui_input(const Ref<InputEvent> &p_event) override;
public:
+ bool is_editing() const;
+
void set_horizontal_alignment(HorizontalAlignment p_alignment);
HorizontalAlignment get_horizontal_alignment() const;
diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp
index d96809b67a..f1902bade4 100644
--- a/scene/gui/scroll_container.cpp
+++ b/scene/gui/scroll_container.cpp
@@ -35,8 +35,6 @@
#include "scene/theme/theme_db.h"
Size2 ScrollContainer::get_minimum_size() const {
- Size2 min_size;
-
// Calculated in this function, as it needs to traverse all child controls once to calculate;
// and needs to be calculated before being used by update_scrollbars().
largest_child_min_size = Size2();
@@ -55,21 +53,23 @@ Size2 ScrollContainer::get_minimum_size() const {
largest_child_min_size = largest_child_min_size.max(child_min_size);
}
+ Size2 min_size;
+ const Size2 size = get_size();
+
if (horizontal_scroll_mode == SCROLL_MODE_DISABLED) {
- min_size.x = MAX(min_size.x, largest_child_min_size.x);
- }
- if (vertical_scroll_mode == SCROLL_MODE_DISABLED) {
- min_size.y = MAX(min_size.y, largest_child_min_size.y);
+ min_size.x = largest_child_min_size.x;
+ bool v_scroll_show = vertical_scroll_mode == SCROLL_MODE_SHOW_ALWAYS || (vertical_scroll_mode == SCROLL_MODE_AUTO && largest_child_min_size.y > size.y);
+ if (v_scroll_show && v_scroll->get_parent() == this) {
+ min_size.x += v_scroll->get_minimum_size().x;
+ }
}
- bool h_scroll_show = horizontal_scroll_mode == SCROLL_MODE_SHOW_ALWAYS || (horizontal_scroll_mode == SCROLL_MODE_AUTO && largest_child_min_size.x > min_size.x);
- bool v_scroll_show = vertical_scroll_mode == SCROLL_MODE_SHOW_ALWAYS || (vertical_scroll_mode == SCROLL_MODE_AUTO && largest_child_min_size.y > min_size.y);
-
- if (h_scroll_show && h_scroll->get_parent() == this) {
- min_size.y += h_scroll->get_minimum_size().y;
- }
- if (v_scroll_show && v_scroll->get_parent() == this) {
- min_size.x += v_scroll->get_minimum_size().x;
+ if (vertical_scroll_mode == SCROLL_MODE_DISABLED) {
+ min_size.y = largest_child_min_size.y;
+ bool h_scroll_show = horizontal_scroll_mode == SCROLL_MODE_SHOW_ALWAYS || (horizontal_scroll_mode == SCROLL_MODE_AUTO && largest_child_min_size.x > size.x);
+ if (h_scroll_show && h_scroll->get_parent() == this) {
+ min_size.y += h_scroll->get_minimum_size().y;
+ }
}
min_size += theme_cache.panel_style->get_minimum_size();
diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp
index ac81f0de56..01c2b9bffe 100644
--- a/scene/gui/spin_box.cpp
+++ b/scene/gui/spin_box.cpp
@@ -46,7 +46,7 @@ void SpinBox::_update_text(bool p_keep_line_edit) {
value = TS->format_number(value);
}
- if (!line_edit->has_focus()) {
+ if (!line_edit->is_editing()) {
if (!prefix.is_empty()) {
value = prefix + " " + value;
}
@@ -197,13 +197,13 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) {
}
} break;
case MouseButton::WHEEL_UP: {
- if (line_edit->has_focus()) {
+ if (line_edit->is_editing()) {
set_value(get_value() + step * mb->get_factor());
accept_event();
}
} break;
case MouseButton::WHEEL_DOWN: {
- if (line_edit->has_focus()) {
+ if (line_edit->is_editing()) {
set_value(get_value() - step * mb->get_factor());
accept_event();
}
@@ -253,34 +253,26 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) {
}
}
-void SpinBox::_line_edit_focus_enter() {
- int col = line_edit->get_caret_column();
- _update_text();
- line_edit->set_caret_column(col);
+void SpinBox::_line_edit_editing_toggled(bool p_toggled_on) {
+ if (p_toggled_on) {
+ int col = line_edit->get_caret_column();
+ _update_text();
+ line_edit->set_caret_column(col);
- // LineEdit text might change and it clears any selection. Have to re-select here.
- if (line_edit->is_select_all_on_focus() && !Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) {
- line_edit->select_all();
- }
-}
+ // LineEdit text might change and it clears any selection. Have to re-select here.
+ if (line_edit->is_select_all_on_focus() && !Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) {
+ line_edit->select_all();
+ }
+ } else {
+ // Discontinue because the focus_exit was caused by canceling.
+ if (Input::get_singleton()->is_action_pressed("ui_cancel")) {
+ _update_text();
+ return;
+ }
-void SpinBox::_line_edit_focus_exit() {
- // Discontinue because the focus_exit was caused by left-clicking the arrows.
- const Viewport *viewport = get_viewport();
- if (!viewport || viewport->gui_get_focus_owner() == get_line_edit()) {
- return;
- }
- // Discontinue because the focus_exit was caused by right-click context menu.
- if (line_edit->is_menu_visible()) {
- return;
- }
- // Discontinue because the focus_exit was caused by canceling.
- if (Input::get_singleton()->is_action_pressed("ui_cancel")) {
- _update_text();
- return;
+ line_edit->deselect();
+ _text_submitted(line_edit->get_text());
}
-
- _text_submitted(line_edit->get_text());
}
inline void SpinBox::_compute_sizes() {
@@ -602,8 +594,7 @@ SpinBox::SpinBox() {
line_edit->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_LEFT);
line_edit->connect("text_submitted", callable_mp(this, &SpinBox::_text_submitted), CONNECT_DEFERRED);
- line_edit->connect(SceneStringName(focus_entered), callable_mp(this, &SpinBox::_line_edit_focus_enter), CONNECT_DEFERRED);
- line_edit->connect(SceneStringName(focus_exited), callable_mp(this, &SpinBox::_line_edit_focus_exit), CONNECT_DEFERRED);
+ line_edit->connect("editing_toggled", callable_mp(this, &SpinBox::_line_edit_editing_toggled), CONNECT_DEFERRED);
line_edit->connect(SceneStringName(gui_input), callable_mp(this, &SpinBox::_line_edit_input));
range_click_timer = memnew(Timer);
diff --git a/scene/gui/spin_box.h b/scene/gui/spin_box.h
index 592805f43a..294dc3e5d5 100644
--- a/scene/gui/spin_box.h
+++ b/scene/gui/spin_box.h
@@ -86,8 +86,7 @@ class SpinBox : public Range {
bool down_button_disabled = false;
} state_cache;
- void _line_edit_focus_enter();
- void _line_edit_focus_exit();
+ void _line_edit_editing_toggled(bool p_toggled_on);
inline void _compute_sizes();
inline int _get_widest_button_icon_width();
diff --git a/scene/main/window.compat.inc b/scene/main/window.compat.inc
deleted file mode 100644
index 0bba01bb1b..0000000000
--- a/scene/main/window.compat.inc
+++ /dev/null
@@ -1,48 +0,0 @@
-/**************************************************************************/
-/* window.compat.inc */
-/**************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/**************************************************************************/
-/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
-/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/**************************************************************************/
-
-#ifndef DISABLE_DEPRECATED
-
-void Window::_bind_compatibility_methods() {
- ClassDB::bind_compatibility_method(D_METHOD("get_theme_icon", "name", "theme_type"), &Window::get_theme_icon, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("get_theme_stylebox", "name", "theme_type"), &Window::get_theme_stylebox, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("get_theme_font", "name", "theme_type"), &Window::get_theme_font, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("get_theme_font_size", "name", "theme_type"), &Window::get_theme_font_size, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("get_theme_color", "name", "theme_type"), &Window::get_theme_color, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("get_theme_constant", "name", "theme_type"), &Window::get_theme_constant, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("has_theme_icon", "name", "theme_type"), &Window::has_theme_icon, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("has_theme_stylebox", "name", "theme_type"), &Window::has_theme_stylebox, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("has_theme_font", "name", "theme_type"), &Window::has_theme_font, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("has_theme_font_size", "name", "theme_type"), &Window::has_theme_font_size, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("has_theme_color", "name", "theme_type"), &Window::has_theme_color, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("has_theme_constant", "name", "theme_type"), &Window::has_theme_constant, DEFVAL(""));
-}
-
-#endif
diff --git a/scene/main/window.cpp b/scene/main/window.cpp
index aaa34a4840..dafbbb867b 100644
--- a/scene/main/window.cpp
+++ b/scene/main/window.cpp
@@ -29,7 +29,6 @@
/**************************************************************************/
#include "window.h"
-#include "window.compat.inc"
#include "core/config/project_settings.h"
#include "core/debugger/engine_debugger.h"
diff --git a/scene/main/window.h b/scene/main/window.h
index 33d593711f..84d2febe51 100644
--- a/scene/main/window.h
+++ b/scene/main/window.h
@@ -247,10 +247,6 @@ protected:
void _notification(int p_what);
static void _bind_methods();
-#ifndef DISABLE_DEPRECATED
- static void _bind_compatibility_methods();
-#endif
-
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
void _get_property_list(List<PropertyInfo> *p_list) const;
diff --git a/scene/resources/3d/importer_mesh.cpp b/scene/resources/3d/importer_mesh.cpp
index 4f4c485db3..ac31cfd0ee 100644
--- a/scene/resources/3d/importer_mesh.cpp
+++ b/scene/resources/3d/importer_mesh.cpp
@@ -34,6 +34,7 @@
#include "core/math/convex_hull.h"
#include "core/math/random_pcg.h"
#include "core/math/static_raycaster.h"
+#include "scene/resources/animation_library.h"
#include "scene/resources/surface_tool.h"
#include <cstdint>
@@ -134,9 +135,18 @@ void ImporterMesh::Surface::_split_normals(Array &r_arrays, const LocalVector<in
}
}
+String ImporterMesh::validate_blend_shape_name(const String &p_name) {
+ String name = p_name;
+ const char *characters = ":";
+ for (const char *p = characters; *p; p++) {
+ name = name.replace(String::chr(*p), "_");
+ }
+ return name;
+}
+
void ImporterMesh::add_blend_shape(const String &p_name) {
ERR_FAIL_COND(surfaces.size() > 0);
- blend_shapes.push_back(p_name);
+ blend_shapes.push_back(validate_blend_shape_name(p_name));
}
int ImporterMesh::get_blend_shape_count() const {
diff --git a/scene/resources/3d/importer_mesh.h b/scene/resources/3d/importer_mesh.h
index 5eb4ee884e..c7e3a059d6 100644
--- a/scene/resources/3d/importer_mesh.h
+++ b/scene/resources/3d/importer_mesh.h
@@ -95,6 +95,8 @@ public:
int get_blend_shape_count() const;
String get_blend_shape_name(int p_blend_shape) const;
+ static String validate_blend_shape_name(const String &p_name);
+
void add_surface(Mesh::PrimitiveType p_primitive, const Array &p_arrays, const TypedArray<Array> &p_blend_shapes = Array(), const Dictionary &p_lods = Dictionary(), const Ref<Material> &p_material = Ref<Material>(), const String &p_name = String(), const uint64_t p_flags = 0);
int get_surface_count() const;
diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
index f51b4ae8d0..b0851efe5f 100644
--- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
@@ -351,710 +351,12 @@ void RendererCanvasRenderRD::free_polygon(PolygonID p_polygon) {
////////////////////
-void RendererCanvasRenderRD::_bind_canvas_texture(RD::DrawListID p_draw_list, RID p_texture, RS::CanvasItemTextureFilter p_base_filter, RS::CanvasItemTextureRepeat p_base_repeat, RID &r_last_texture, PushConstant &push_constant, Size2 &r_texpixel_size, bool p_texture_is_data) {
- if (p_texture == RID()) {
- p_texture = default_canvas_texture;
- }
-
- if (r_last_texture == p_texture) {
- return; //nothing to do, its the same
- }
-
- RID uniform_set;
- Color specular_shininess;
- Size2i size;
- bool use_normal;
- bool use_specular;
-
- bool success = RendererRD::TextureStorage::get_singleton()->canvas_texture_get_uniform_set(p_texture, p_base_filter, p_base_repeat, shader.default_version_rd_shader, CANVAS_TEXTURE_UNIFORM_SET, bool(push_constant.flags & FLAGS_CONVERT_ATTRIBUTES_TO_LINEAR), uniform_set, size, specular_shininess, use_normal, use_specular, p_texture_is_data);
- //something odd happened
- if (!success) {
- _bind_canvas_texture(p_draw_list, default_canvas_texture, p_base_filter, p_base_repeat, r_last_texture, push_constant, r_texpixel_size);
- return;
- }
-
- RD::get_singleton()->draw_list_bind_uniform_set(p_draw_list, uniform_set, CANVAS_TEXTURE_UNIFORM_SET);
-
- if (specular_shininess.a < 0.999) {
- push_constant.flags |= FLAGS_DEFAULT_SPECULAR_MAP_USED;
- } else {
- push_constant.flags &= ~FLAGS_DEFAULT_SPECULAR_MAP_USED;
- }
-
- if (use_normal) {
- push_constant.flags |= FLAGS_DEFAULT_NORMAL_MAP_USED;
- } else {
- push_constant.flags &= ~FLAGS_DEFAULT_NORMAL_MAP_USED;
- }
-
- push_constant.specular_shininess = uint32_t(CLAMP(specular_shininess.a * 255.0, 0, 255)) << 24;
- push_constant.specular_shininess |= uint32_t(CLAMP(specular_shininess.b * 255.0, 0, 255)) << 16;
- push_constant.specular_shininess |= uint32_t(CLAMP(specular_shininess.g * 255.0, 0, 255)) << 8;
- push_constant.specular_shininess |= uint32_t(CLAMP(specular_shininess.r * 255.0, 0, 255));
-
- r_texpixel_size.x = 1.0 / float(size.x);
- r_texpixel_size.y = 1.0 / float(size.y);
-
- push_constant.color_texture_pixel_size[0] = r_texpixel_size.x;
- push_constant.color_texture_pixel_size[1] = r_texpixel_size.y;
-
- r_last_texture = p_texture;
-}
-
_FORCE_INLINE_ static uint32_t _indices_to_primitives(RS::PrimitiveType p_primitive, uint32_t p_indices) {
static const uint32_t divisor[RS::PRIMITIVE_MAX] = { 1, 2, 1, 3, 1 };
static const uint32_t subtractor[RS::PRIMITIVE_MAX] = { 0, 0, 1, 0, 1 };
return (p_indices - subtractor[p_primitive]) / divisor[p_primitive];
}
-void RendererCanvasRenderRD::_render_item(RD::DrawListID p_draw_list, RID p_render_target, const Item *p_item, RD::FramebufferFormatID p_framebuffer_format, const Transform2D &p_canvas_transform_inverse, Item *&current_clip, Light *p_lights, PipelineVariants *p_pipeline_variants, bool &r_sdf_used, const Point2 &p_repeat_offset, RenderingMethod::RenderInfo *r_render_info) {
- //create an empty push constant
- RendererRD::TextureStorage *texture_storage = RendererRD::TextureStorage::get_singleton();
- RendererRD::MeshStorage *mesh_storage = RendererRD::MeshStorage::get_singleton();
- RendererRD::ParticlesStorage *particles_storage = RendererRD::ParticlesStorage::get_singleton();
-
- RS::CanvasItemTextureFilter current_filter = default_filter;
- RS::CanvasItemTextureRepeat current_repeat = default_repeat;
-
- if (p_item->texture_filter != RS::CANVAS_ITEM_TEXTURE_FILTER_DEFAULT) {
- current_filter = p_item->texture_filter;
- }
-
- if (p_item->texture_repeat != RS::CANVAS_ITEM_TEXTURE_REPEAT_DEFAULT) {
- current_repeat = p_item->texture_repeat;
- }
-
- PushConstant push_constant;
- Transform2D base_transform = p_item->final_transform;
- if (p_item->repeat_source_item && (p_repeat_offset.x || p_repeat_offset.y)) {
- base_transform.columns[2] += p_item->repeat_source_item->final_transform.basis_xform(p_repeat_offset);
- }
- base_transform = p_canvas_transform_inverse * base_transform;
-
- Transform2D draw_transform;
- _update_transform_2d_to_mat2x3(base_transform, push_constant.world);
-
- Color base_color = p_item->final_modulate;
- bool use_linear_colors = texture_storage->render_target_is_using_hdr(p_render_target);
-
- for (int i = 0; i < 4; i++) {
- push_constant.modulation[i] = 0;
- push_constant.ninepatch_margins[i] = 0;
- push_constant.src_rect[i] = 0;
- push_constant.dst_rect[i] = 0;
- }
- push_constant.flags = 0;
- push_constant.color_texture_pixel_size[0] = 0;
- push_constant.color_texture_pixel_size[1] = 0;
-
- push_constant.pad[0] = 0;
- push_constant.pad[1] = 0;
-
- push_constant.lights[0] = 0;
- push_constant.lights[1] = 0;
- push_constant.lights[2] = 0;
- push_constant.lights[3] = 0;
-
- uint32_t base_flags = 0;
- base_flags |= use_linear_colors ? FLAGS_CONVERT_ATTRIBUTES_TO_LINEAR : 0;
-
- uint16_t light_count = 0;
- PipelineLightMode light_mode;
-
- {
- Light *light = p_lights;
-
- while (light) {
- if (light->render_index_cache >= 0 && p_item->light_mask & light->item_mask && p_item->z_final >= light->z_min && p_item->z_final <= light->z_max && p_item->global_rect_cache.intersects_transformed(light->xform_cache, light->rect_cache)) {
- uint32_t light_index = light->render_index_cache;
- push_constant.lights[light_count >> 2] |= light_index << ((light_count & 3) * 8);
-
- light_count++;
-
- if (light_count == MAX_LIGHTS_PER_ITEM - 1) {
- break;
- }
- }
- light = light->next_ptr;
- }
-
- base_flags |= light_count << FLAGS_LIGHT_COUNT_SHIFT;
- }
-
- light_mode = (light_count > 0 || using_directional_lights) ? PIPELINE_LIGHT_MODE_ENABLED : PIPELINE_LIGHT_MODE_DISABLED;
-
- PipelineVariants *pipeline_variants = p_pipeline_variants;
-
- bool reclip = false;
-
- RID last_texture;
- Size2 texpixel_size;
-
- bool skipping = false;
-
- const Item::Command *c = p_item->commands;
- while (c) {
- if (skipping && c->type != Item::Command::TYPE_ANIMATION_SLICE) {
- c = c->next;
- continue;
- }
-
- push_constant.flags = base_flags | (push_constant.flags & (FLAGS_DEFAULT_NORMAL_MAP_USED | FLAGS_DEFAULT_SPECULAR_MAP_USED)); // Reset on each command for safety, keep canvastexture binding config.
-
- switch (c->type) {
- case Item::Command::TYPE_RECT: {
- const Item::CommandRect *rect = static_cast<const Item::CommandRect *>(c);
-
- if (rect->flags & CANVAS_RECT_TILE) {
- current_repeat = RenderingServer::CanvasItemTextureRepeat::CANVAS_ITEM_TEXTURE_REPEAT_ENABLED;
- }
-
- Color modulated = rect->modulate * base_color;
- if (use_linear_colors) {
- modulated = modulated.srgb_to_linear();
- }
-
- //bind pipeline
- if (rect->flags & CANVAS_RECT_LCD) {
- RID pipeline = pipeline_variants->variants[light_mode][PIPELINE_VARIANT_QUAD_LCD_BLEND].get_render_pipeline(RD::INVALID_ID, p_framebuffer_format);
- RD::get_singleton()->draw_list_bind_render_pipeline(p_draw_list, pipeline);
- RD::get_singleton()->draw_list_set_blend_constants(p_draw_list, modulated);
- } else {
- RID pipeline = pipeline_variants->variants[light_mode][PIPELINE_VARIANT_QUAD].get_render_pipeline(RD::INVALID_ID, p_framebuffer_format);
- RD::get_singleton()->draw_list_bind_render_pipeline(p_draw_list, pipeline);
- }
-
- //bind textures
-
- _bind_canvas_texture(p_draw_list, rect->texture, current_filter, current_repeat, last_texture, push_constant, texpixel_size, bool(rect->flags & CANVAS_RECT_MSDF));
-
- Rect2 src_rect;
- Rect2 dst_rect;
-
- if (rect->texture != RID()) {
- src_rect = (rect->flags & CANVAS_RECT_REGION) ? Rect2(rect->source.position * texpixel_size, rect->source.size * texpixel_size) : Rect2(0, 0, 1, 1);
- dst_rect = Rect2(rect->rect.position, rect->rect.size);
-
- if (dst_rect.size.width < 0) {
- dst_rect.position.x += dst_rect.size.width;
- dst_rect.size.width *= -1;
- }
- if (dst_rect.size.height < 0) {
- dst_rect.position.y += dst_rect.size.height;
- dst_rect.size.height *= -1;
- }
-
- if (rect->flags & CANVAS_RECT_FLIP_H) {
- src_rect.size.x *= -1;
- push_constant.flags |= FLAGS_FLIP_H;
- }
-
- if (rect->flags & CANVAS_RECT_FLIP_V) {
- src_rect.size.y *= -1;
- push_constant.flags |= FLAGS_FLIP_V;
- }
-
- if (rect->flags & CANVAS_RECT_TRANSPOSE) {
- push_constant.flags |= FLAGS_TRANSPOSE_RECT;
- }
-
- if (rect->flags & CANVAS_RECT_CLIP_UV) {
- push_constant.flags |= FLAGS_CLIP_RECT_UV;
- }
-
- } else {
- dst_rect = Rect2(rect->rect.position, rect->rect.size);
-
- if (dst_rect.size.width < 0) {
- dst_rect.position.x += dst_rect.size.width;
- dst_rect.size.width *= -1;
- }
- if (dst_rect.size.height < 0) {
- dst_rect.position.y += dst_rect.size.height;
- dst_rect.size.height *= -1;
- }
-
- src_rect = Rect2(0, 0, 1, 1);
- }
-
- if (rect->flags & CANVAS_RECT_MSDF) {
- push_constant.flags |= FLAGS_USE_MSDF;
- push_constant.msdf[0] = rect->px_range; // Pixel range.
- push_constant.msdf[1] = rect->outline; // Outline size.
- push_constant.msdf[2] = 0.f; // Reserved.
- push_constant.msdf[3] = 0.f; // Reserved.
- } else if (rect->flags & CANVAS_RECT_LCD) {
- push_constant.flags |= FLAGS_USE_LCD;
- }
-
- push_constant.modulation[0] = modulated.r;
- push_constant.modulation[1] = modulated.g;
- push_constant.modulation[2] = modulated.b;
- push_constant.modulation[3] = modulated.a;
-
- push_constant.src_rect[0] = src_rect.position.x;
- push_constant.src_rect[1] = src_rect.position.y;
- push_constant.src_rect[2] = src_rect.size.width;
- push_constant.src_rect[3] = src_rect.size.height;
-
- push_constant.dst_rect[0] = dst_rect.position.x;
- push_constant.dst_rect[1] = dst_rect.position.y;
- push_constant.dst_rect[2] = dst_rect.size.width;
- push_constant.dst_rect[3] = dst_rect.size.height;
-
- RD::get_singleton()->draw_list_set_push_constant(p_draw_list, &push_constant, sizeof(PushConstant));
- RD::get_singleton()->draw_list_bind_index_array(p_draw_list, shader.quad_index_array);
- RD::get_singleton()->draw_list_draw(p_draw_list, true);
-
- if (r_render_info) {
- r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_OBJECTS_IN_FRAME]++;
- r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_PRIMITIVES_IN_FRAME] += 2;
- r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_DRAW_CALLS_IN_FRAME]++;
- }
-
- } break;
-
- case Item::Command::TYPE_NINEPATCH: {
- const Item::CommandNinePatch *np = static_cast<const Item::CommandNinePatch *>(c);
-
- //bind pipeline
- {
- RID pipeline = pipeline_variants->variants[light_mode][PIPELINE_VARIANT_NINEPATCH].get_render_pipeline(RD::INVALID_ID, p_framebuffer_format);
- RD::get_singleton()->draw_list_bind_render_pipeline(p_draw_list, pipeline);
- }
-
- //bind textures
-
- _bind_canvas_texture(p_draw_list, np->texture, current_filter, current_repeat, last_texture, push_constant, texpixel_size);
-
- Rect2 src_rect;
- Rect2 dst_rect(np->rect.position.x, np->rect.position.y, np->rect.size.x, np->rect.size.y);
-
- if (np->texture == RID()) {
- texpixel_size = Size2(1, 1);
- src_rect = Rect2(0, 0, 1, 1);
-
- } else {
- if (np->source != Rect2()) {
- src_rect = Rect2(np->source.position.x * texpixel_size.width, np->source.position.y * texpixel_size.height, np->source.size.x * texpixel_size.width, np->source.size.y * texpixel_size.height);
- push_constant.color_texture_pixel_size[0] = 1.0 / np->source.size.width;
- push_constant.color_texture_pixel_size[1] = 1.0 / np->source.size.height;
-
- } else {
- src_rect = Rect2(0, 0, 1, 1);
- }
- }
-
- Color modulated = np->color * base_color;
- if (use_linear_colors) {
- modulated = modulated.srgb_to_linear();
- }
-
- push_constant.modulation[0] = modulated.r;
- push_constant.modulation[1] = modulated.g;
- push_constant.modulation[2] = modulated.b;
- push_constant.modulation[3] = modulated.a;
-
- push_constant.src_rect[0] = src_rect.position.x;
- push_constant.src_rect[1] = src_rect.position.y;
- push_constant.src_rect[2] = src_rect.size.width;
- push_constant.src_rect[3] = src_rect.size.height;
-
- push_constant.dst_rect[0] = dst_rect.position.x;
- push_constant.dst_rect[1] = dst_rect.position.y;
- push_constant.dst_rect[2] = dst_rect.size.width;
- push_constant.dst_rect[3] = dst_rect.size.height;
-
- push_constant.flags |= int(np->axis_x) << FLAGS_NINEPATCH_H_MODE_SHIFT;
- push_constant.flags |= int(np->axis_y) << FLAGS_NINEPATCH_V_MODE_SHIFT;
-
- if (np->draw_center) {
- push_constant.flags |= FLAGS_NINEPACH_DRAW_CENTER;
- }
-
- push_constant.ninepatch_margins[0] = np->margin[SIDE_LEFT];
- push_constant.ninepatch_margins[1] = np->margin[SIDE_TOP];
- push_constant.ninepatch_margins[2] = np->margin[SIDE_RIGHT];
- push_constant.ninepatch_margins[3] = np->margin[SIDE_BOTTOM];
-
- RD::get_singleton()->draw_list_set_push_constant(p_draw_list, &push_constant, sizeof(PushConstant));
- RD::get_singleton()->draw_list_bind_index_array(p_draw_list, shader.quad_index_array);
- RD::get_singleton()->draw_list_draw(p_draw_list, true);
-
- if (r_render_info) {
- r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_OBJECTS_IN_FRAME]++;
- r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_PRIMITIVES_IN_FRAME] += 2;
- r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_DRAW_CALLS_IN_FRAME]++;
- }
-
- // Restore if overridden.
- push_constant.color_texture_pixel_size[0] = texpixel_size.x;
- push_constant.color_texture_pixel_size[1] = texpixel_size.y;
-
- } break;
- case Item::Command::TYPE_POLYGON: {
- const Item::CommandPolygon *polygon = static_cast<const Item::CommandPolygon *>(c);
-
- PolygonBuffers *pb = polygon_buffers.polygons.getptr(polygon->polygon.polygon_id);
- ERR_CONTINUE(!pb);
- //bind pipeline
- {
- static const PipelineVariant variant[RS::PRIMITIVE_MAX] = { PIPELINE_VARIANT_ATTRIBUTE_POINTS, PIPELINE_VARIANT_ATTRIBUTE_LINES, PIPELINE_VARIANT_ATTRIBUTE_LINES_STRIP, PIPELINE_VARIANT_ATTRIBUTE_TRIANGLES, PIPELINE_VARIANT_ATTRIBUTE_TRIANGLE_STRIP };
- ERR_CONTINUE(polygon->primitive < 0 || polygon->primitive >= RS::PRIMITIVE_MAX);
- RID pipeline = pipeline_variants->variants[light_mode][variant[polygon->primitive]].get_render_pipeline(pb->vertex_format_id, p_framebuffer_format);
- RD::get_singleton()->draw_list_bind_render_pipeline(p_draw_list, pipeline);
- }
-
- if (polygon->primitive == RS::PRIMITIVE_LINES) {
- //not supported in most hardware, so pointless
- //RD::get_singleton()->draw_list_set_line_width(p_draw_list, polygon->line_width);
- }
-
- //bind textures
-
- _bind_canvas_texture(p_draw_list, polygon->texture, current_filter, current_repeat, last_texture, push_constant, texpixel_size);
-
- Color color = base_color;
- if (use_linear_colors) {
- color = color.srgb_to_linear();
- }
-
- push_constant.modulation[0] = color.r;
- push_constant.modulation[1] = color.g;
- push_constant.modulation[2] = color.b;
- push_constant.modulation[3] = color.a;
-
- for (int j = 0; j < 4; j++) {
- push_constant.src_rect[j] = 0;
- push_constant.dst_rect[j] = 0;
- push_constant.ninepatch_margins[j] = 0;
- }
-
- RD::get_singleton()->draw_list_set_push_constant(p_draw_list, &push_constant, sizeof(PushConstant));
- RD::get_singleton()->draw_list_bind_vertex_array(p_draw_list, pb->vertex_array);
- if (pb->indices.is_valid()) {
- RD::get_singleton()->draw_list_bind_index_array(p_draw_list, pb->indices);
- }
- RD::get_singleton()->draw_list_draw(p_draw_list, pb->indices.is_valid());
-
- if (r_render_info) {
- r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_OBJECTS_IN_FRAME]++;
- r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_PRIMITIVES_IN_FRAME] += _indices_to_primitives(polygon->primitive, pb->primitive_count);
- r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_DRAW_CALLS_IN_FRAME]++;
- }
-
- } break;
- case Item::Command::TYPE_PRIMITIVE: {
- const Item::CommandPrimitive *primitive = static_cast<const Item::CommandPrimitive *>(c);
-
- //bind pipeline
- {
- static const PipelineVariant variant[4] = { PIPELINE_VARIANT_PRIMITIVE_POINTS, PIPELINE_VARIANT_PRIMITIVE_LINES, PIPELINE_VARIANT_PRIMITIVE_TRIANGLES, PIPELINE_VARIANT_PRIMITIVE_TRIANGLES };
- ERR_CONTINUE(primitive->point_count == 0 || primitive->point_count > 4);
- RID pipeline = pipeline_variants->variants[light_mode][variant[primitive->point_count - 1]].get_render_pipeline(RD::INVALID_ID, p_framebuffer_format);
- RD::get_singleton()->draw_list_bind_render_pipeline(p_draw_list, pipeline);
- }
-
- //bind textures
-
- _bind_canvas_texture(p_draw_list, primitive->texture, current_filter, current_repeat, last_texture, push_constant, texpixel_size);
-
- RD::get_singleton()->draw_list_bind_index_array(p_draw_list, primitive_arrays.index_array[MIN(3u, primitive->point_count) - 1]);
-
- for (uint32_t j = 0; j < MIN(3u, primitive->point_count); j++) {
- push_constant.points[j * 2 + 0] = primitive->points[j].x;
- push_constant.points[j * 2 + 1] = primitive->points[j].y;
- push_constant.uvs[j * 2 + 0] = primitive->uvs[j].x;
- push_constant.uvs[j * 2 + 1] = primitive->uvs[j].y;
- Color col = primitive->colors[j] * base_color;
- if (use_linear_colors) {
- col = col.srgb_to_linear();
- }
- push_constant.colors[j * 2 + 0] = (uint32_t(Math::make_half_float(col.g)) << 16) | Math::make_half_float(col.r);
- push_constant.colors[j * 2 + 1] = (uint32_t(Math::make_half_float(col.a)) << 16) | Math::make_half_float(col.b);
- }
- RD::get_singleton()->draw_list_set_push_constant(p_draw_list, &push_constant, sizeof(PushConstant));
- RD::get_singleton()->draw_list_draw(p_draw_list, true);
-
- if (r_render_info) {
- r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_OBJECTS_IN_FRAME]++;
- r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_PRIMITIVES_IN_FRAME]++;
- r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_DRAW_CALLS_IN_FRAME]++;
- }
-
- if (primitive->point_count == 4) {
- for (uint32_t j = 1; j < 3; j++) {
- //second half of triangle
- push_constant.points[j * 2 + 0] = primitive->points[j + 1].x;
- push_constant.points[j * 2 + 1] = primitive->points[j + 1].y;
- push_constant.uvs[j * 2 + 0] = primitive->uvs[j + 1].x;
- push_constant.uvs[j * 2 + 1] = primitive->uvs[j + 1].y;
- Color col = primitive->colors[j + 1] * base_color;
- if (use_linear_colors) {
- col = col.srgb_to_linear();
- }
- push_constant.colors[j * 2 + 0] = (uint32_t(Math::make_half_float(col.g)) << 16) | Math::make_half_float(col.r);
- push_constant.colors[j * 2 + 1] = (uint32_t(Math::make_half_float(col.a)) << 16) | Math::make_half_float(col.b);
- }
-
- RD::get_singleton()->draw_list_set_push_constant(p_draw_list, &push_constant, sizeof(PushConstant));
- RD::get_singleton()->draw_list_draw(p_draw_list, true);
-
- if (r_render_info) {
- r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_OBJECTS_IN_FRAME]++;
- r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_PRIMITIVES_IN_FRAME]++;
- r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_DRAW_CALLS_IN_FRAME]++;
- }
- }
-
- } break;
- case Item::Command::TYPE_MESH:
- case Item::Command::TYPE_MULTIMESH:
- case Item::Command::TYPE_PARTICLES: {
- RID mesh;
- RID mesh_instance;
- RID texture;
- Color modulate(1, 1, 1, 1);
- float world_backup[6];
- int instance_count = 1;
-
- for (int j = 0; j < 6; j++) {
- world_backup[j] = push_constant.world[j];
- }
-
- if (c->type == Item::Command::TYPE_MESH) {
- const Item::CommandMesh *m = static_cast<const Item::CommandMesh *>(c);
- mesh = m->mesh;
- mesh_instance = m->mesh_instance;
- texture = m->texture;
- modulate = m->modulate;
- _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;
- mesh = mesh_storage->multimesh_get_mesh(multimesh);
- texture = mm->texture;
-
- if (mesh_storage->multimesh_get_transform_format(multimesh) != RS::MULTIMESH_TRANSFORM_2D) {
- break;
- }
-
- instance_count = mesh_storage->multimesh_get_instances_to_draw(multimesh);
-
- if (instance_count == 0) {
- break;
- }
-
- RID uniform_set = mesh_storage->multimesh_get_2d_uniform_set(multimesh, shader.default_version_rd_shader, TRANSFORMS_UNIFORM_SET);
- RD::get_singleton()->draw_list_bind_uniform_set(p_draw_list, uniform_set, TRANSFORMS_UNIFORM_SET);
- push_constant.flags |= 1; //multimesh, trails disabled
- if (mesh_storage->multimesh_uses_colors(multimesh)) {
- push_constant.flags |= FLAGS_INSTANCING_HAS_COLORS;
- }
- if (mesh_storage->multimesh_uses_custom_data(multimesh)) {
- push_constant.flags |= FLAGS_INSTANCING_HAS_CUSTOM_DATA;
- }
- } else if (c->type == Item::Command::TYPE_PARTICLES) {
- const Item::CommandParticles *pt = static_cast<const Item::CommandParticles *>(c);
- ERR_BREAK(particles_storage->particles_get_mode(pt->particles) != RS::PARTICLES_MODE_2D);
- particles_storage->particles_request_process(pt->particles);
-
- if (particles_storage->particles_is_inactive(pt->particles) || particles_storage->particles_get_frame_counter(pt->particles) == 0) {
- break;
- }
-
- RenderingServerDefault::redraw_request(); // active particles means redraw request
-
- int dpc = particles_storage->particles_get_draw_passes(pt->particles);
- if (dpc == 0) {
- break; //nothing to draw
- }
- uint32_t divisor = 1;
- instance_count = particles_storage->particles_get_amount(pt->particles, divisor);
-
- RID uniform_set = particles_storage->particles_get_instance_buffer_uniform_set(pt->particles, shader.default_version_rd_shader, TRANSFORMS_UNIFORM_SET);
- RD::get_singleton()->draw_list_bind_uniform_set(p_draw_list, uniform_set, TRANSFORMS_UNIFORM_SET);
-
- push_constant.flags |= divisor;
- instance_count /= divisor;
-
- push_constant.flags |= FLAGS_INSTANCING_HAS_COLORS;
- push_constant.flags |= FLAGS_INSTANCING_HAS_CUSTOM_DATA;
-
- mesh = particles_storage->particles_get_draw_pass_mesh(pt->particles, 0); //higher ones are ignored
- texture = pt->texture;
-
- if (particles_storage->particles_has_collision(pt->particles) && texture_storage->render_target_is_sdf_enabled(p_render_target)) {
- //pass collision information
- Transform2D xform = p_item->final_transform;
-
- RID sdf_texture = texture_storage->render_target_get_sdf_texture(p_render_target);
-
- Rect2 to_screen;
- {
- Rect2 sdf_rect = texture_storage->render_target_get_sdf_rect(p_render_target);
-
- to_screen.size = Vector2(1.0 / sdf_rect.size.width, 1.0 / sdf_rect.size.height);
- to_screen.position = -sdf_rect.position * to_screen.size;
- }
-
- particles_storage->particles_set_canvas_sdf_collision(pt->particles, true, xform, to_screen, sdf_texture);
- } else {
- particles_storage->particles_set_canvas_sdf_collision(pt->particles, false, Transform2D(), Rect2(), RID());
- }
-
- // Signal that SDF texture needs to be updated.
- r_sdf_used |= particles_storage->particles_has_collision(pt->particles);
- }
-
- if (mesh.is_null()) {
- break;
- }
-
- _bind_canvas_texture(p_draw_list, texture, current_filter, current_repeat, last_texture, push_constant, texpixel_size);
-
- uint32_t surf_count = mesh_storage->mesh_get_surface_count(mesh);
- static const PipelineVariant variant[RS::PRIMITIVE_MAX] = { PIPELINE_VARIANT_ATTRIBUTE_POINTS, PIPELINE_VARIANT_ATTRIBUTE_LINES, PIPELINE_VARIANT_ATTRIBUTE_LINES_STRIP, PIPELINE_VARIANT_ATTRIBUTE_TRIANGLES, PIPELINE_VARIANT_ATTRIBUTE_TRIANGLE_STRIP };
-
- Color modulated = modulate * base_color;
- if (use_linear_colors) {
- modulated = modulated.srgb_to_linear();
- }
-
- push_constant.modulation[0] = modulated.r;
- push_constant.modulation[1] = modulated.g;
- push_constant.modulation[2] = modulated.b;
- push_constant.modulation[3] = modulated.a;
-
- for (int j = 0; j < 4; j++) {
- push_constant.src_rect[j] = 0;
- push_constant.dst_rect[j] = 0;
- push_constant.ninepatch_margins[j] = 0;
- }
-
- for (uint32_t j = 0; j < surf_count; j++) {
- void *surface = mesh_storage->mesh_get_surface(mesh, j);
-
- RS::PrimitiveType primitive = mesh_storage->mesh_surface_get_primitive(surface);
- ERR_CONTINUE(primitive < 0 || primitive >= RS::PRIMITIVE_MAX);
-
- uint64_t input_mask = pipeline_variants->variants[light_mode][variant[primitive]].get_vertex_input_mask();
-
- RID vertex_array;
- RD::VertexFormatID vertex_format = RD::INVALID_FORMAT_ID;
-
- if (mesh_instance.is_valid()) {
- mesh_storage->mesh_instance_surface_get_vertex_arrays_and_format(mesh_instance, j, input_mask, false, vertex_array, vertex_format);
- } else {
- mesh_storage->mesh_surface_get_vertex_arrays_and_format(surface, input_mask, false, vertex_array, vertex_format);
- }
-
- RID pipeline = pipeline_variants->variants[light_mode][variant[primitive]].get_render_pipeline(vertex_format, p_framebuffer_format);
- RD::get_singleton()->draw_list_bind_render_pipeline(p_draw_list, pipeline);
-
- RID index_array = mesh_storage->mesh_surface_get_index_array(surface, 0);
-
- if (index_array.is_valid()) {
- RD::get_singleton()->draw_list_bind_index_array(p_draw_list, index_array);
- }
-
- RD::get_singleton()->draw_list_bind_vertex_array(p_draw_list, vertex_array);
- RD::get_singleton()->draw_list_set_push_constant(p_draw_list, &push_constant, sizeof(PushConstant));
-
- RD::get_singleton()->draw_list_draw(p_draw_list, index_array.is_valid(), instance_count);
-
- if (r_render_info) {
- r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_OBJECTS_IN_FRAME]++;
- r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_PRIMITIVES_IN_FRAME] += _indices_to_primitives(primitive, mesh_storage->mesh_surface_get_vertices_drawn_count(surface)) * instance_count;
- r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_DRAW_CALLS_IN_FRAME]++;
- }
- }
-
- 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;
- case Item::Command::TYPE_CLIP_IGNORE: {
- const Item::CommandClipIgnore *ci = static_cast<const Item::CommandClipIgnore *>(c);
- if (current_clip) {
- if (ci->ignore != reclip) {
- if (ci->ignore) {
- RD::get_singleton()->draw_list_disable_scissor(p_draw_list);
- reclip = true;
- } else {
- RD::get_singleton()->draw_list_enable_scissor(p_draw_list, current_clip->final_clip_rect);
- reclip = false;
- }
- }
- }
-
- } break;
- case Item::Command::TYPE_ANIMATION_SLICE: {
- const Item::CommandAnimationSlice *as = static_cast<const Item::CommandAnimationSlice *>(c);
- double current_time = RendererCompositorRD::get_singleton()->get_total_time();
- double local_time = Math::fposmod(current_time - as->offset, as->animation_length);
- skipping = !(local_time >= as->slice_begin && local_time < as->slice_end);
-
- RenderingServerDefault::redraw_request(); // animation visible means redraw request
- } break;
- }
-
- c = c->next;
- }
-#ifdef DEBUG_ENABLED
- if (debug_redraw && p_item->debug_redraw_time > 0.0) {
- Color dc = debug_redraw_color;
- dc.a *= p_item->debug_redraw_time / debug_redraw_time;
-
- RID pipeline = pipeline_variants->variants[PIPELINE_LIGHT_MODE_DISABLED][PIPELINE_VARIANT_QUAD].get_render_pipeline(RD::INVALID_ID, p_framebuffer_format);
- RD::get_singleton()->draw_list_bind_render_pipeline(p_draw_list, pipeline);
-
- //bind textures
-
- _bind_canvas_texture(p_draw_list, RID(), current_filter, current_repeat, last_texture, push_constant, texpixel_size);
-
- Rect2 src_rect;
- Rect2 dst_rect;
-
- dst_rect = Rect2(Vector2(), p_item->rect.size);
- src_rect = Rect2(0, 0, 1, 1);
-
- push_constant.modulation[0] = dc.r;
- push_constant.modulation[1] = dc.g;
- push_constant.modulation[2] = dc.b;
- push_constant.modulation[3] = dc.a;
-
- push_constant.src_rect[0] = src_rect.position.x;
- push_constant.src_rect[1] = src_rect.position.y;
- push_constant.src_rect[2] = src_rect.size.width;
- push_constant.src_rect[3] = src_rect.size.height;
-
- push_constant.dst_rect[0] = dst_rect.position.x;
- push_constant.dst_rect[1] = dst_rect.position.y;
- push_constant.dst_rect[2] = dst_rect.size.width;
- push_constant.dst_rect[3] = dst_rect.size.height;
-
- RD::get_singleton()->draw_list_set_push_constant(p_draw_list, &push_constant, sizeof(PushConstant));
- RD::get_singleton()->draw_list_bind_index_array(p_draw_list, shader.quad_index_array);
- RD::get_singleton()->draw_list_draw(p_draw_list, true);
-
- p_item->debug_redraw_time -= RSG::rasterizer->get_frame_delta_time();
-
- RenderingServerDefault::redraw_request();
- }
-#endif
- if (current_clip && reclip) {
- //will make it re-enable clipping if needed afterwards
- current_clip = nullptr;
- }
-}
-
RID RendererCanvasRenderRD::_create_base_uniform_set(RID p_to_render_target, bool p_backbuffer) {
RendererRD::TextureStorage *texture_storage = RendererRD::TextureStorage::get_singleton();
RendererRD::MaterialStorage *material_storage = RendererRD::MaterialStorage::get_singleton();
@@ -1148,127 +450,6 @@ RID RendererCanvasRenderRD::_create_base_uniform_set(RID p_to_render_target, boo
return uniform_set;
}
-void RendererCanvasRenderRD::_render_items(RID p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, bool &r_sdf_used, bool p_to_backbuffer, RenderingMethod::RenderInfo *r_render_info) {
- RendererRD::MaterialStorage *material_storage = RendererRD::MaterialStorage::get_singleton();
- RendererRD::TextureStorage *texture_storage = RendererRD::TextureStorage::get_singleton();
-
- Item *current_clip = nullptr;
-
- Transform2D canvas_transform_inverse = p_canvas_transform_inverse;
-
- RID framebuffer;
- RID fb_uniform_set;
- bool clear = false;
- Vector<Color> clear_colors;
-
- if (p_to_backbuffer) {
- framebuffer = texture_storage->render_target_get_rd_backbuffer_framebuffer(p_to_render_target);
- fb_uniform_set = texture_storage->render_target_get_backbuffer_uniform_set(p_to_render_target);
- } else {
- framebuffer = texture_storage->render_target_get_rd_framebuffer(p_to_render_target);
- texture_storage->render_target_set_msaa_needs_resolve(p_to_render_target, false); // If MSAA is enabled, our framebuffer will be resolved!
-
- if (texture_storage->render_target_is_clear_requested(p_to_render_target)) {
- clear = true;
- clear_colors.push_back(texture_storage->render_target_get_clear_request_color(p_to_render_target));
- texture_storage->render_target_disable_clear_request(p_to_render_target);
- }
- // TODO: Obtain from framebuffer format eventually when this is implemented.
- fb_uniform_set = texture_storage->render_target_get_framebuffer_uniform_set(p_to_render_target);
- }
-
- if (fb_uniform_set.is_null() || !RD::get_singleton()->uniform_set_is_valid(fb_uniform_set)) {
- fb_uniform_set = _create_base_uniform_set(p_to_render_target, p_to_backbuffer);
- }
-
- RD::FramebufferFormatID fb_format = RD::get_singleton()->framebuffer_get_format(framebuffer);
-
- RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(framebuffer, clear ? RD::INITIAL_ACTION_CLEAR : RD::INITIAL_ACTION_LOAD, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_LOAD, RD::FINAL_ACTION_DISCARD, clear_colors, 1, 0, Rect2(), RDD::BreadcrumbMarker::UI_PASS);
-
- RD::get_singleton()->draw_list_bind_uniform_set(draw_list, fb_uniform_set, BASE_UNIFORM_SET);
- RD::get_singleton()->draw_list_bind_uniform_set(draw_list, state.default_transforms_uniform_set, TRANSFORMS_UNIFORM_SET);
-
- RID prev_material;
-
- PipelineVariants *pipeline_variants = &shader.pipeline_variants;
-
- for (int i = 0; i < p_item_count; i++) {
- Item *ci = items[i];
-
- if (current_clip != ci->final_clip_owner) {
- current_clip = ci->final_clip_owner;
-
- //setup clip
- if (current_clip) {
- RD::get_singleton()->draw_list_enable_scissor(draw_list, current_clip->final_clip_rect);
-
- } else {
- RD::get_singleton()->draw_list_disable_scissor(draw_list);
- }
- }
-
- RID material = ci->material_owner == nullptr ? ci->material : ci->material_owner->material;
-
- if (ci->use_canvas_group) {
- if (ci->canvas_group->mode == RS::CANVAS_GROUP_MODE_CLIP_AND_DRAW) {
- material = default_clip_children_material;
- } else {
- if (material.is_null()) {
- if (ci->canvas_group->mode == RS::CANVAS_GROUP_MODE_CLIP_ONLY) {
- material = default_clip_children_material;
- } else {
- material = default_canvas_group_material;
- }
- }
- }
- }
-
- if (material != prev_material) {
- CanvasMaterialData *material_data = nullptr;
- if (material.is_valid()) {
- material_data = static_cast<CanvasMaterialData *>(material_storage->material_get_data(material, RendererRD::MaterialStorage::SHADER_TYPE_2D));
- }
-
- if (material_data) {
- if (material_data->shader_data->version.is_valid() && material_data->shader_data->valid) {
- pipeline_variants = &material_data->shader_data->pipeline_variants;
- // Update uniform set.
- RID uniform_set = texture_storage->render_target_is_using_hdr(p_to_render_target) ? material_data->uniform_set : material_data->uniform_set_srgb;
- if (uniform_set.is_valid() && RD::get_singleton()->uniform_set_is_valid(uniform_set)) { // Material may not have a uniform set.
- RD::get_singleton()->draw_list_bind_uniform_set(draw_list, uniform_set, MATERIAL_UNIFORM_SET);
- material_data->set_as_used();
- }
- } else {
- pipeline_variants = &shader.pipeline_variants;
- }
- } else {
- pipeline_variants = &shader.pipeline_variants;
- }
- }
-
- if (!ci->repeat_size.x && !ci->repeat_size.y) {
- _render_item(draw_list, p_to_render_target, ci, fb_format, canvas_transform_inverse, current_clip, p_lights, pipeline_variants, r_sdf_used, Point2(), r_render_info);
- } else {
- Point2 start_pos = ci->repeat_size * -(ci->repeat_times / 2);
- Point2 offset;
-
- int repeat_times_x = ci->repeat_size.x ? ci->repeat_times : 0;
- int repeat_times_y = ci->repeat_size.y ? ci->repeat_times : 0;
- for (int ry = 0; ry <= repeat_times_y; ry++) {
- offset.y = start_pos.y + ry * ci->repeat_size.y;
- for (int rx = 0; rx <= repeat_times_x; rx++) {
- offset.x = start_pos.x + rx * ci->repeat_size.x;
- _render_item(draw_list, p_to_render_target, ci, fb_format, canvas_transform_inverse, current_clip, p_lights, pipeline_variants, r_sdf_used, offset, r_render_info);
- }
- }
- }
-
- prev_material = material;
- }
-
- RD::get_singleton()->draw_list_end();
-}
-
void RendererCanvasRenderRD::canvas_render_items(RID p_to_render_target, Item *p_item_list, const Color &p_modulate, Light *p_light_list, Light *p_directional_light_list, const Transform2D &p_canvas_transform, RenderingServer::CanvasItemTextureFilter p_default_filter, RenderingServer::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_vertices_to_pixel, bool &r_sdf_used, RenderingMethod::RenderInfo *r_render_info) {
RendererRD::TextureStorage *texture_storage = RendererRD::TextureStorage::get_singleton();
RendererRD::MaterialStorage *material_storage = RendererRD::MaterialStorage::get_singleton();
@@ -1509,11 +690,18 @@ void RendererCanvasRenderRD::canvas_render_items(RID p_to_render_target, Item *p
Item *canvas_group_owner = nullptr;
bool skip_item = false;
+ state.last_instance_index = 0;
+
bool update_skeletons = false;
bool time_used = false;
bool backbuffer_cleared = false;
+ RenderTarget to_render_target;
+ to_render_target.render_target = p_to_render_target;
+ bool use_linear_colors = texture_storage->render_target_is_using_hdr(p_to_render_target);
+ to_render_target.base_flags = use_linear_colors ? FLAGS_CONVERT_ATTRIBUTES_TO_LINEAR : 0;
+
while (ci) {
if (ci->copy_back_buffer && canvas_group_owner == nullptr) {
backbuffer_copy = true;
@@ -1572,8 +760,7 @@ void RendererCanvasRenderRD::canvas_render_items(RID p_to_render_target, Item *p
mesh_storage->update_mesh_instances();
update_skeletons = false;
}
-
- _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, false, r_render_info);
+ _render_batch_items(to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, false, r_render_info);
item_count = 0;
if (ci->canvas_group_owner->canvas_group->mode != RS::CANVAS_GROUP_MODE_TRANSPARENT) {
@@ -1605,7 +792,7 @@ void RendererCanvasRenderRD::canvas_render_items(RID p_to_render_target, Item *p
update_skeletons = false;
}
- _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, true, r_render_info);
+ _render_batch_items(to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, true, r_render_info);
item_count = 0;
if (ci->canvas_group->blur_mipmaps) {
@@ -1629,7 +816,7 @@ void RendererCanvasRenderRD::canvas_render_items(RID p_to_render_target, Item *p
update_skeletons = false;
}
- _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, false, r_render_info);
+ _render_batch_items(to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, false, r_render_info);
item_count = 0;
texture_storage->render_target_copy_to_back_buffer(p_to_render_target, back_buffer_rect, backbuffer_gen_mipmaps);
@@ -1659,7 +846,7 @@ void RendererCanvasRenderRD::canvas_render_items(RID p_to_render_target, Item *p
update_skeletons = false;
}
- _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, canvas_group_owner != nullptr, r_render_info);
+ _render_batch_items(to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, canvas_group_owner != nullptr, r_render_info);
//then reset
item_count = 0;
}
@@ -1670,6 +857,9 @@ void RendererCanvasRenderRD::canvas_render_items(RID p_to_render_target, Item *p
if (time_used) {
RenderingServerDefault::redraw_request();
}
+
+ state.current_data_buffer_index = (state.current_data_buffer_index + 1) % state.canvas_instance_data_buffers.size();
+ state.current_instance_buffer_index = 0;
}
RID RendererCanvasRenderRD::light_create() {
@@ -2635,7 +1825,7 @@ RendererCanvasRenderRD::RendererCanvasRenderRD() {
actions.base_uniform_string = "material.";
actions.default_filter = ShaderLanguage::FILTER_LINEAR;
actions.default_repeat = ShaderLanguage::REPEAT_DISABLE;
- actions.base_varying_index = 4;
+ actions.base_varying_index = 5;
actions.global_buffer_array_variable = "global_shader_uniforms.data";
@@ -2843,7 +2033,19 @@ void fragment() {
material_storage->material_set_shader(default_clip_children_material, default_clip_children_shader);
}
- static_assert(sizeof(PushConstant) == 128);
+ {
+ state.max_instances_per_buffer = uint32_t(GLOBAL_GET("rendering/2d/batching/item_buffer_size"));
+ state.max_instance_buffer_size = state.max_instances_per_buffer * sizeof(InstanceData);
+ state.canvas_instance_data_buffers.resize(3);
+ state.canvas_instance_batches.reserve(200);
+
+ for (int i = 0; i < 3; i++) {
+ DataBuffer db;
+ db.instance_buffers.push_back(RD::get_singleton()->storage_buffer_create(state.max_instance_buffer_size));
+ state.canvas_instance_data_buffers[i] = db;
+ }
+ state.instance_data_array = memnew_arr(InstanceData, state.max_instances_per_buffer);
+ }
}
bool RendererCanvasRenderRD::free(RID p_rid) {
@@ -2893,6 +2095,1009 @@ void RendererCanvasRenderRD::set_debug_redraw(bool p_enabled, double p_time, con
debug_redraw_color = p_color;
}
+void RendererCanvasRenderRD::_render_batch_items(RenderTarget p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, bool &r_sdf_used, bool p_to_backbuffer, RenderingMethod::RenderInfo *r_render_info) {
+ // Record batches
+ uint32_t instance_index = 0;
+ {
+ RendererRD::MaterialStorage *material_storage = RendererRD::MaterialStorage::get_singleton();
+ Item *current_clip = nullptr;
+
+ // Record Batches.
+ // First item always forms its own batch.
+ bool batch_broken = false;
+ Batch *current_batch = _new_batch(batch_broken);
+ // Override the start position and index as we want to start from where we finished off last time.
+ current_batch->start = state.last_instance_index;
+
+ for (int i = 0; i < p_item_count; i++) {
+ Item *ci = items[i];
+
+ if (ci->final_clip_owner != current_batch->clip) {
+ current_batch = _new_batch(batch_broken);
+ current_batch->clip = ci->final_clip_owner;
+ current_clip = ci->final_clip_owner;
+ }
+
+ RID material = ci->material_owner == nullptr ? ci->material : ci->material_owner->material;
+
+ if (ci->use_canvas_group) {
+ if (ci->canvas_group->mode == RS::CANVAS_GROUP_MODE_CLIP_AND_DRAW) {
+ material = default_clip_children_material;
+ } else {
+ if (material.is_null()) {
+ if (ci->canvas_group->mode == RS::CANVAS_GROUP_MODE_CLIP_ONLY) {
+ material = default_clip_children_material;
+ } else {
+ material = default_canvas_group_material;
+ }
+ }
+ }
+ }
+
+ if (material != current_batch->material) {
+ current_batch = _new_batch(batch_broken);
+
+ CanvasMaterialData *material_data = nullptr;
+ if (material.is_valid()) {
+ material_data = static_cast<CanvasMaterialData *>(material_storage->material_get_data(material, RendererRD::MaterialStorage::SHADER_TYPE_2D));
+ }
+
+ current_batch->material = material;
+ current_batch->material_data = material_data;
+ }
+
+ Transform2D base_transform = p_canvas_transform_inverse * ci->final_transform;
+ if (!ci->repeat_size.x && !ci->repeat_size.y) {
+ _record_item_commands(ci, p_to_render_target, base_transform, current_clip, p_lights, instance_index, batch_broken, r_sdf_used);
+ } else {
+ Point2 start_pos = ci->repeat_size * -(ci->repeat_times / 2);
+ Point2 end_pos = ci->repeat_size * ci->repeat_times + ci->repeat_size + start_pos;
+ Point2 pos = start_pos;
+ do {
+ do {
+ Transform2D transform = base_transform * Transform2D(0, pos / ci->xform_curr.get_scale());
+ _record_item_commands(ci, p_to_render_target, transform, current_clip, p_lights, instance_index, batch_broken, r_sdf_used);
+ pos.y += ci->repeat_size.y;
+ } while (pos.y < end_pos.y);
+
+ pos.x += ci->repeat_size.x;
+ pos.y = start_pos.y;
+ } while (pos.x < end_pos.x);
+ }
+ }
+
+ // Copy over remaining data needed for rendering.
+ if (instance_index > 0) {
+ RD::get_singleton()->buffer_update(
+ state.canvas_instance_data_buffers[state.current_data_buffer_index].instance_buffers[state.current_instance_buffer_index],
+ state.last_instance_index * sizeof(InstanceData),
+ instance_index * sizeof(InstanceData),
+ state.instance_data_array);
+ }
+ }
+
+ if (state.canvas_instance_batches.is_empty()) {
+ // Nothing to render, just return.
+ return;
+ }
+
+ // Render batches
+
+ RendererRD::TextureStorage *texture_storage = RendererRD::TextureStorage::get_singleton();
+
+ RID framebuffer;
+ RID fb_uniform_set;
+ bool clear = false;
+ Vector<Color> clear_colors;
+
+ if (p_to_backbuffer) {
+ framebuffer = texture_storage->render_target_get_rd_backbuffer_framebuffer(p_to_render_target.render_target);
+ fb_uniform_set = texture_storage->render_target_get_backbuffer_uniform_set(p_to_render_target.render_target);
+ } else {
+ framebuffer = texture_storage->render_target_get_rd_framebuffer(p_to_render_target.render_target);
+ texture_storage->render_target_set_msaa_needs_resolve(p_to_render_target.render_target, false); // If MSAA is enabled, our framebuffer will be resolved!
+
+ if (texture_storage->render_target_is_clear_requested(p_to_render_target.render_target)) {
+ clear = true;
+ clear_colors.push_back(texture_storage->render_target_get_clear_request_color(p_to_render_target.render_target));
+ texture_storage->render_target_disable_clear_request(p_to_render_target.render_target);
+ }
+ // TODO: Obtain from framebuffer format eventually when this is implemented.
+ fb_uniform_set = texture_storage->render_target_get_framebuffer_uniform_set(p_to_render_target.render_target);
+ }
+
+ if (fb_uniform_set.is_null() || !RD::get_singleton()->uniform_set_is_valid(fb_uniform_set)) {
+ fb_uniform_set = _create_base_uniform_set(p_to_render_target.render_target, p_to_backbuffer);
+ }
+
+ RD::FramebufferFormatID fb_format = RD::get_singleton()->framebuffer_get_format(framebuffer);
+
+ RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(framebuffer, clear ? RD::INITIAL_ACTION_CLEAR : RD::INITIAL_ACTION_LOAD, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_LOAD, RD::FINAL_ACTION_DISCARD, clear_colors);
+
+ RD::get_singleton()->draw_list_bind_uniform_set(draw_list, fb_uniform_set, BASE_UNIFORM_SET);
+ RD::get_singleton()->draw_list_bind_uniform_set(draw_list, state.default_transforms_uniform_set, TRANSFORMS_UNIFORM_SET);
+
+ Item *current_clip = nullptr;
+ state.current_tex_uniform_set = RID();
+
+ for (uint32_t i = 0; i <= state.current_batch_index; i++) {
+ Batch *current_batch = &state.canvas_instance_batches[i];
+ // Skipping when there is no instances.
+ if (current_batch->instance_count == 0) {
+ continue;
+ }
+
+ //setup clip
+ if (current_clip != current_batch->clip) {
+ current_clip = current_batch->clip;
+ if (current_clip) {
+ RD::get_singleton()->draw_list_enable_scissor(draw_list, current_clip->final_clip_rect);
+ } else {
+ RD::get_singleton()->draw_list_disable_scissor(draw_list);
+ }
+ }
+
+ PipelineVariants *pipeline_variants = &shader.pipeline_variants;
+
+ CanvasMaterialData *material_data = current_batch->material_data;
+ if (material_data) {
+ if (material_data->shader_data->version.is_valid() && material_data->shader_data->valid) {
+ pipeline_variants = &material_data->shader_data->pipeline_variants;
+ // Update uniform set.
+ RID uniform_set = texture_storage->render_target_is_using_hdr(p_to_render_target.render_target) ? material_data->uniform_set : material_data->uniform_set_srgb;
+ if (uniform_set.is_valid() && RD::get_singleton()->uniform_set_is_valid(uniform_set)) { // Material may not have a uniform set.
+ RD::get_singleton()->draw_list_bind_uniform_set(draw_list, uniform_set, MATERIAL_UNIFORM_SET);
+ material_data->set_as_used();
+ }
+ }
+ }
+
+ _render_batch(draw_list, pipeline_variants, fb_format, p_lights, current_batch, r_render_info);
+ }
+
+ RD::get_singleton()->draw_list_end();
+
+ state.current_batch_index = 0;
+ state.canvas_instance_batches.clear();
+ state.last_instance_index += instance_index;
+}
+
+void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTarget p_render_target, const Transform2D &p_base_transform, Item *&r_current_clip, Light *p_lights, uint32_t &r_index, bool &r_batch_broken, bool &r_sdf_used) {
+ Batch *current_batch = &state.canvas_instance_batches[state.current_batch_index];
+
+ RenderingServer::CanvasItemTextureFilter texture_filter = p_item->texture_filter == RS::CANVAS_ITEM_TEXTURE_FILTER_DEFAULT ? default_filter : p_item->texture_filter;
+ RenderingServer::CanvasItemTextureRepeat texture_repeat = p_item->texture_repeat == RS::CANVAS_ITEM_TEXTURE_REPEAT_DEFAULT ? default_repeat : p_item->texture_repeat;
+
+ Transform2D base_transform = p_base_transform;
+
+ float world[6];
+ Transform2D draw_transform; // Used by transform command
+ _update_transform_2d_to_mat2x3(base_transform, world);
+
+ Color base_color = p_item->final_modulate;
+ bool use_linear_colors = bool(p_render_target.base_flags & FLAGS_CONVERT_ATTRIBUTES_TO_LINEAR);
+ uint32_t base_flags = p_render_target.base_flags;
+
+ bool reclip = false;
+
+ bool skipping = false;
+
+ // TODO: consider making lights a per-batch property and then baking light operations in the shader for better performance.
+ uint32_t lights[4] = { 0, 0, 0, 0 };
+
+ uint16_t light_count = 0;
+ PipelineLightMode light_mode;
+
+ {
+ Light *light = p_lights;
+
+ while (light) {
+ if (light->render_index_cache >= 0 && p_item->light_mask & light->item_mask && p_item->z_final >= light->z_min && p_item->z_final <= light->z_max && p_item->global_rect_cache.intersects_transformed(light->xform_cache, light->rect_cache)) {
+ uint32_t light_index = light->render_index_cache;
+ lights[light_count >> 2] |= light_index << ((light_count & 3) * 8);
+
+ light_count++;
+
+ if (light_count == state.max_lights_per_item - 1) {
+ break;
+ }
+ }
+ light = light->next_ptr;
+ }
+
+ base_flags |= light_count << FLAGS_LIGHT_COUNT_SHIFT;
+ }
+
+ light_mode = (light_count > 0 || using_directional_lights) ? PIPELINE_LIGHT_MODE_ENABLED : PIPELINE_LIGHT_MODE_DISABLED;
+
+ if (light_mode != current_batch->light_mode) {
+ current_batch = _new_batch(r_batch_broken);
+ current_batch->light_mode = light_mode;
+ }
+
+ // new_instance_data should be called after the current_batch is set.
+ auto new_instance_data = [&]() -> InstanceData * {
+ InstanceData *instance_data = &state.instance_data_array[r_index];
+ // Zero out most fields.
+ for (int i = 0; i < 4; i++) {
+ instance_data->modulation[i] = 0.0;
+ instance_data->ninepatch_margins[i] = 0.0;
+ instance_data->src_rect[i] = 0.0;
+ instance_data->dst_rect[i] = 0.0;
+ }
+
+ instance_data->pad[0] = 0.0;
+ instance_data->pad[1] = 0.0;
+
+ instance_data->lights[0] = lights[0];
+ instance_data->lights[1] = lights[1];
+ instance_data->lights[2] = lights[2];
+ instance_data->lights[3] = lights[3];
+
+ for (int i = 0; i < 6; i++) {
+ instance_data->world[i] = world[i];
+ }
+
+ instance_data->flags = base_flags | current_batch->tex_flags; // Reset on each command for safety, keep canvas texture binding config.
+
+ instance_data->color_texture_pixel_size[0] = current_batch->tex_texpixel_size.width;
+ instance_data->color_texture_pixel_size[1] = current_batch->tex_texpixel_size.height;
+ instance_data->specular_shininess = current_batch->tex_specular_shininess;
+
+ return instance_data;
+ };
+
+ const Item::Command *c = p_item->commands;
+ while (c) {
+ if (skipping && c->type != Item::Command::TYPE_ANIMATION_SLICE) {
+ c = c->next;
+ continue;
+ }
+
+ switch (c->type) {
+ case Item::Command::TYPE_RECT: {
+ const Item::CommandRect *rect = static_cast<const Item::CommandRect *>(c);
+
+ // 1: If commands are different, start a new batch.
+ if (current_batch->command_type != Item::Command::TYPE_RECT) {
+ current_batch = _new_batch(r_batch_broken);
+ current_batch->command_type = Item::Command::TYPE_RECT;
+ current_batch->command = c;
+ // default variant
+ current_batch->pipeline_variant = PIPELINE_VARIANT_QUAD;
+ }
+
+ if (bool(rect->flags & CANVAS_RECT_TILE)) {
+ texture_repeat = RenderingServer::CanvasItemTextureRepeat::CANVAS_ITEM_TEXTURE_REPEAT_ENABLED;
+ }
+
+ bool has_msdf = bool(rect->flags & CANVAS_RECT_MSDF);
+ TextureState tex_state(rect->texture, texture_filter, texture_repeat, has_msdf, use_linear_colors);
+
+ if (tex_state != current_batch->tex_state) {
+ current_batch = _new_batch(r_batch_broken);
+ current_batch->set_tex_state(tex_state);
+ _prepare_batch_texture(current_batch, rect->texture);
+ }
+
+ Color modulated = rect->modulate * base_color;
+ if (use_linear_colors) {
+ modulated = modulated.srgb_to_linear();
+ }
+
+ bool has_blend = bool(rect->flags & CANVAS_RECT_LCD);
+ // Start a new batch if the blend mode has changed,
+ // or blend mode is enabled and the modulation has changed.
+ if (has_blend != current_batch->has_blend || (has_blend && modulated != current_batch->modulate)) {
+ current_batch = _new_batch(r_batch_broken);
+ current_batch->has_blend = has_blend;
+ current_batch->modulate = modulated;
+ current_batch->pipeline_variant = has_blend ? PIPELINE_VARIANT_QUAD_LCD_BLEND : PIPELINE_VARIANT_QUAD;
+ }
+
+ InstanceData *instance_data = new_instance_data();
+ Rect2 src_rect;
+ Rect2 dst_rect;
+
+ if (rect->texture.is_valid()) {
+ src_rect = (rect->flags & CANVAS_RECT_REGION) ? Rect2(rect->source.position * current_batch->tex_texpixel_size, rect->source.size * current_batch->tex_texpixel_size) : Rect2(0, 0, 1, 1);
+ dst_rect = Rect2(rect->rect.position, rect->rect.size);
+
+ if (dst_rect.size.width < 0) {
+ dst_rect.position.x += dst_rect.size.width;
+ dst_rect.size.width *= -1;
+ }
+ if (dst_rect.size.height < 0) {
+ dst_rect.position.y += dst_rect.size.height;
+ dst_rect.size.height *= -1;
+ }
+
+ if (rect->flags & CANVAS_RECT_FLIP_H) {
+ src_rect.size.x *= -1;
+ instance_data->flags |= FLAGS_FLIP_H;
+ }
+
+ if (rect->flags & CANVAS_RECT_FLIP_V) {
+ src_rect.size.y *= -1;
+ instance_data->flags |= FLAGS_FLIP_V;
+ }
+
+ if (rect->flags & CANVAS_RECT_TRANSPOSE) {
+ instance_data->flags |= FLAGS_TRANSPOSE_RECT;
+ }
+
+ if (rect->flags & CANVAS_RECT_CLIP_UV) {
+ instance_data->flags |= FLAGS_CLIP_RECT_UV;
+ }
+
+ } else {
+ dst_rect = Rect2(rect->rect.position, rect->rect.size);
+
+ if (dst_rect.size.width < 0) {
+ dst_rect.position.x += dst_rect.size.width;
+ dst_rect.size.width *= -1;
+ }
+ if (dst_rect.size.height < 0) {
+ dst_rect.position.y += dst_rect.size.height;
+ dst_rect.size.height *= -1;
+ }
+
+ src_rect = Rect2(0, 0, 1, 1);
+ }
+
+ if (has_msdf) {
+ instance_data->flags |= FLAGS_USE_MSDF;
+ instance_data->msdf[0] = rect->px_range; // Pixel range.
+ instance_data->msdf[1] = rect->outline; // Outline size.
+ instance_data->msdf[2] = 0.f; // Reserved.
+ instance_data->msdf[3] = 0.f; // Reserved.
+ } else if (rect->flags & CANVAS_RECT_LCD) {
+ instance_data->flags |= FLAGS_USE_LCD;
+ }
+
+ instance_data->modulation[0] = modulated.r;
+ instance_data->modulation[1] = modulated.g;
+ instance_data->modulation[2] = modulated.b;
+ instance_data->modulation[3] = modulated.a;
+
+ instance_data->src_rect[0] = src_rect.position.x;
+ instance_data->src_rect[1] = src_rect.position.y;
+ instance_data->src_rect[2] = src_rect.size.width;
+ instance_data->src_rect[3] = src_rect.size.height;
+
+ instance_data->dst_rect[0] = dst_rect.position.x;
+ instance_data->dst_rect[1] = dst_rect.position.y;
+ instance_data->dst_rect[2] = dst_rect.size.width;
+ instance_data->dst_rect[3] = dst_rect.size.height;
+
+ _add_to_batch(r_index, r_batch_broken, current_batch);
+ } break;
+
+ case Item::Command::TYPE_NINEPATCH: {
+ const Item::CommandNinePatch *np = static_cast<const Item::CommandNinePatch *>(c);
+
+ if (current_batch->command_type != Item::Command::TYPE_NINEPATCH) {
+ current_batch = _new_batch(r_batch_broken);
+ current_batch->command_type = Item::Command::TYPE_NINEPATCH;
+ current_batch->command = c;
+ current_batch->pipeline_variant = PipelineVariant::PIPELINE_VARIANT_NINEPATCH;
+ }
+
+ TextureState tex_state(np->texture, texture_filter, texture_repeat, false, use_linear_colors);
+ if (tex_state != current_batch->tex_state) {
+ current_batch = _new_batch(r_batch_broken);
+ current_batch->set_tex_state(tex_state);
+ _prepare_batch_texture(current_batch, np->texture);
+ }
+
+ InstanceData *instance_data = new_instance_data();
+
+ Rect2 src_rect;
+ Rect2 dst_rect(np->rect.position.x, np->rect.position.y, np->rect.size.x, np->rect.size.y);
+
+ if (np->texture.is_null()) {
+ src_rect = Rect2(0, 0, 1, 1);
+ } else {
+ if (np->source != Rect2()) {
+ src_rect = Rect2(np->source.position.x * current_batch->tex_texpixel_size.width, np->source.position.y * current_batch->tex_texpixel_size.height, np->source.size.x * current_batch->tex_texpixel_size.width, np->source.size.y * current_batch->tex_texpixel_size.height);
+ instance_data->color_texture_pixel_size[0] = 1.0 / np->source.size.width;
+ instance_data->color_texture_pixel_size[1] = 1.0 / np->source.size.height;
+ } else {
+ src_rect = Rect2(0, 0, 1, 1);
+ }
+ }
+
+ Color modulated = np->color * base_color;
+ if (use_linear_colors) {
+ modulated = modulated.srgb_to_linear();
+ }
+
+ instance_data->modulation[0] = modulated.r;
+ instance_data->modulation[1] = modulated.g;
+ instance_data->modulation[2] = modulated.b;
+ instance_data->modulation[3] = modulated.a;
+
+ instance_data->src_rect[0] = src_rect.position.x;
+ instance_data->src_rect[1] = src_rect.position.y;
+ instance_data->src_rect[2] = src_rect.size.width;
+ instance_data->src_rect[3] = src_rect.size.height;
+
+ instance_data->dst_rect[0] = dst_rect.position.x;
+ instance_data->dst_rect[1] = dst_rect.position.y;
+ instance_data->dst_rect[2] = dst_rect.size.width;
+ instance_data->dst_rect[3] = dst_rect.size.height;
+
+ instance_data->flags |= int(np->axis_x) << FLAGS_NINEPATCH_H_MODE_SHIFT;
+ instance_data->flags |= int(np->axis_y) << FLAGS_NINEPATCH_V_MODE_SHIFT;
+
+ if (np->draw_center) {
+ instance_data->flags |= FLAGS_NINEPACH_DRAW_CENTER;
+ }
+
+ instance_data->ninepatch_margins[0] = np->margin[SIDE_LEFT];
+ instance_data->ninepatch_margins[1] = np->margin[SIDE_TOP];
+ instance_data->ninepatch_margins[2] = np->margin[SIDE_RIGHT];
+ instance_data->ninepatch_margins[3] = np->margin[SIDE_BOTTOM];
+
+ _add_to_batch(r_index, r_batch_broken, current_batch);
+ } break;
+
+ case Item::Command::TYPE_POLYGON: {
+ const Item::CommandPolygon *polygon = static_cast<const Item::CommandPolygon *>(c);
+
+ // Polygon's can't be batched, so always create a new batch
+ current_batch = _new_batch(r_batch_broken);
+
+ current_batch->command_type = Item::Command::TYPE_POLYGON;
+ current_batch->command = c;
+
+ TextureState tex_state(polygon->texture, texture_filter, texture_repeat, false, use_linear_colors);
+ if (tex_state != current_batch->tex_state) {
+ current_batch = _new_batch(r_batch_broken);
+ current_batch->set_tex_state(tex_state);
+ _prepare_batch_texture(current_batch, polygon->texture);
+ }
+
+ // pipeline variant
+ {
+ static const PipelineVariant variant[RS::PRIMITIVE_MAX] = { PIPELINE_VARIANT_ATTRIBUTE_POINTS, PIPELINE_VARIANT_ATTRIBUTE_LINES, PIPELINE_VARIANT_ATTRIBUTE_LINES_STRIP, PIPELINE_VARIANT_ATTRIBUTE_TRIANGLES, PIPELINE_VARIANT_ATTRIBUTE_TRIANGLE_STRIP };
+ ERR_CONTINUE(polygon->primitive < 0 || polygon->primitive >= RS::PRIMITIVE_MAX);
+ current_batch->pipeline_variant = variant[polygon->primitive];
+ }
+
+ InstanceData *instance_data = new_instance_data();
+
+ Color color = base_color;
+ if (use_linear_colors) {
+ color = color.srgb_to_linear();
+ }
+
+ instance_data->modulation[0] = color.r;
+ instance_data->modulation[1] = color.g;
+ instance_data->modulation[2] = color.b;
+ instance_data->modulation[3] = color.a;
+
+ _add_to_batch(r_index, r_batch_broken, current_batch);
+ } break;
+
+ case Item::Command::TYPE_PRIMITIVE: {
+ const Item::CommandPrimitive *primitive = static_cast<const Item::CommandPrimitive *>(c);
+
+ if (primitive->point_count != current_batch->primitive_points || current_batch->command_type != Item::Command::TYPE_PRIMITIVE) {
+ current_batch = _new_batch(r_batch_broken);
+ current_batch->command_type = Item::Command::TYPE_PRIMITIVE;
+ current_batch->command = c;
+ current_batch->primitive_points = primitive->point_count;
+
+ static const PipelineVariant variant[4] = { PIPELINE_VARIANT_PRIMITIVE_POINTS, PIPELINE_VARIANT_PRIMITIVE_LINES, PIPELINE_VARIANT_PRIMITIVE_TRIANGLES, PIPELINE_VARIANT_PRIMITIVE_TRIANGLES };
+ ERR_CONTINUE(primitive->point_count == 0 || primitive->point_count > 4);
+ current_batch->pipeline_variant = variant[primitive->point_count - 1];
+
+ TextureState tex_state(primitive->texture, texture_filter, texture_repeat, false, use_linear_colors);
+ if (tex_state != current_batch->tex_state) {
+ current_batch = _new_batch(r_batch_broken);
+ current_batch->set_tex_state(tex_state);
+ _prepare_batch_texture(current_batch, primitive->texture);
+ }
+ }
+
+ InstanceData *instance_data = new_instance_data();
+
+ for (uint32_t j = 0; j < MIN(3u, primitive->point_count); j++) {
+ instance_data->points[j * 2 + 0] = primitive->points[j].x;
+ instance_data->points[j * 2 + 1] = primitive->points[j].y;
+ instance_data->uvs[j * 2 + 0] = primitive->uvs[j].x;
+ instance_data->uvs[j * 2 + 1] = primitive->uvs[j].y;
+ Color col = primitive->colors[j] * base_color;
+ if (use_linear_colors) {
+ col = col.srgb_to_linear();
+ }
+ instance_data->colors[j * 2 + 0] = (uint32_t(Math::make_half_float(col.g)) << 16) | Math::make_half_float(col.r);
+ instance_data->colors[j * 2 + 1] = (uint32_t(Math::make_half_float(col.a)) << 16) | Math::make_half_float(col.b);
+ }
+
+ _add_to_batch(r_index, r_batch_broken, current_batch);
+
+ if (primitive->point_count == 4) {
+ instance_data = new_instance_data();
+
+ for (uint32_t j = 0; j < 3; j++) {
+ int offset = j == 0 ? 0 : 1;
+ // Second triangle in the quad. Uses vertices 0, 2, 3.
+ instance_data->points[j * 2 + 0] = primitive->points[j + offset].x;
+ instance_data->points[j * 2 + 1] = primitive->points[j + offset].y;
+ instance_data->uvs[j * 2 + 0] = primitive->uvs[j + offset].x;
+ instance_data->uvs[j * 2 + 1] = primitive->uvs[j + offset].y;
+ Color col = primitive->colors[j] * base_color;
+ if (use_linear_colors) {
+ col = col.srgb_to_linear();
+ }
+ instance_data->colors[j * 2 + 0] = (uint32_t(Math::make_half_float(col.g)) << 16) | Math::make_half_float(col.r);
+ instance_data->colors[j * 2 + 1] = (uint32_t(Math::make_half_float(col.a)) << 16) | Math::make_half_float(col.b);
+ }
+
+ _add_to_batch(r_index, r_batch_broken, current_batch);
+ }
+ } break;
+
+ case Item::Command::TYPE_MESH:
+ case Item::Command::TYPE_MULTIMESH:
+ case Item::Command::TYPE_PARTICLES: {
+ // Mesh's can't be batched, so always create a new batch
+ current_batch = _new_batch(r_batch_broken);
+ current_batch->command = c;
+ current_batch->command_type = c->type;
+
+ InstanceData *instance_data = nullptr;
+
+ Color modulate(1, 1, 1, 1);
+ if (c->type == Item::Command::TYPE_MESH) {
+ const Item::CommandMesh *m = static_cast<const Item::CommandMesh *>(c);
+ TextureState tex_state(m->texture, texture_filter, texture_repeat, false, use_linear_colors);
+ current_batch->set_tex_state(tex_state);
+ _prepare_batch_texture(current_batch, m->texture);
+ instance_data = new_instance_data();
+
+ current_batch->mesh_instance_count = 1;
+ _update_transform_2d_to_mat2x3(base_transform * draw_transform * m->transform, instance_data->world);
+ modulate = m->modulate;
+ } else if (c->type == Item::Command::TYPE_MULTIMESH) {
+ RendererRD::MeshStorage *mesh_storage = RendererRD::MeshStorage::get_singleton();
+
+ const Item::CommandMultiMesh *mm = static_cast<const Item::CommandMultiMesh *>(c);
+ RID multimesh = mm->multimesh;
+
+ if (mesh_storage->multimesh_get_transform_format(multimesh) != RS::MULTIMESH_TRANSFORM_2D) {
+ break;
+ }
+
+ current_batch->mesh_instance_count = mesh_storage->multimesh_get_instances_to_draw(multimesh);
+ if (current_batch->mesh_instance_count == 0) {
+ break;
+ }
+
+ TextureState tex_state(mm->texture, texture_filter, texture_repeat, false, use_linear_colors);
+ current_batch->set_tex_state(tex_state);
+ _prepare_batch_texture(current_batch, mm->texture);
+ instance_data = new_instance_data();
+
+ instance_data->flags |= 1; // multimesh, trails disabled
+
+ if (mesh_storage->multimesh_uses_colors(mm->multimesh)) {
+ instance_data->flags |= FLAGS_INSTANCING_HAS_COLORS;
+ }
+ if (mesh_storage->multimesh_uses_custom_data(mm->multimesh)) {
+ instance_data->flags |= FLAGS_INSTANCING_HAS_CUSTOM_DATA;
+ }
+ } else if (c->type == Item::Command::TYPE_PARTICLES) {
+ RendererRD::TextureStorage *texture_storage = RendererRD::TextureStorage::get_singleton();
+ RendererRD::ParticlesStorage *particles_storage = RendererRD::ParticlesStorage::get_singleton();
+
+ const Item::CommandParticles *pt = static_cast<const Item::CommandParticles *>(c);
+ TextureState tex_state(pt->texture, texture_filter, texture_repeat, false, use_linear_colors);
+ current_batch->set_tex_state(tex_state);
+ _prepare_batch_texture(current_batch, pt->texture);
+
+ instance_data = new_instance_data();
+
+ uint32_t divisor = 1;
+ current_batch->mesh_instance_count = particles_storage->particles_get_amount(pt->particles, divisor);
+ instance_data->flags |= (divisor & FLAGS_INSTANCING_MASK);
+ current_batch->mesh_instance_count /= divisor;
+
+ RID particles = pt->particles;
+
+ instance_data->flags |= FLAGS_INSTANCING_HAS_COLORS;
+ instance_data->flags |= FLAGS_INSTANCING_HAS_CUSTOM_DATA;
+
+ if (particles_storage->particles_has_collision(particles) && texture_storage->render_target_is_sdf_enabled(p_render_target.render_target)) {
+ // Pass collision information.
+ Transform2D xform = p_item->final_transform;
+
+ RID sdf_texture = texture_storage->render_target_get_sdf_texture(p_render_target.render_target);
+
+ Rect2 to_screen;
+ {
+ Rect2 sdf_rect = texture_storage->render_target_get_sdf_rect(p_render_target.render_target);
+
+ to_screen.size = Vector2(1.0 / sdf_rect.size.width, 1.0 / sdf_rect.size.height);
+ to_screen.position = -sdf_rect.position * to_screen.size;
+ }
+
+ particles_storage->particles_set_canvas_sdf_collision(pt->particles, true, xform, to_screen, sdf_texture);
+ } else {
+ particles_storage->particles_set_canvas_sdf_collision(pt->particles, false, Transform2D(), Rect2(), RID());
+ }
+ r_sdf_used |= particles_storage->particles_has_collision(particles);
+ }
+
+ Color modulated = modulate * base_color;
+ if (use_linear_colors) {
+ modulated = modulated.srgb_to_linear();
+ }
+
+ instance_data->modulation[0] = modulated.r;
+ instance_data->modulation[1] = modulated.g;
+ instance_data->modulation[2] = modulated.b;
+ instance_data->modulation[3] = modulated.a;
+
+ _add_to_batch(r_index, r_batch_broken, current_batch);
+ } 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, world);
+ } break;
+
+ case Item::Command::TYPE_CLIP_IGNORE: {
+ const Item::CommandClipIgnore *ci = static_cast<const Item::CommandClipIgnore *>(c);
+ if (r_current_clip) {
+ if (ci->ignore != reclip) {
+ current_batch = _new_batch(r_batch_broken);
+ if (ci->ignore) {
+ current_batch->clip = nullptr;
+ reclip = true;
+ } else {
+ current_batch->clip = r_current_clip;
+ reclip = false;
+ }
+ }
+ }
+ } break;
+
+ case Item::Command::TYPE_ANIMATION_SLICE: {
+ const Item::CommandAnimationSlice *as = static_cast<const Item::CommandAnimationSlice *>(c);
+ double current_time = RSG::rasterizer->get_total_time();
+ double local_time = Math::fposmod(current_time - as->offset, as->animation_length);
+ skipping = !(local_time >= as->slice_begin && local_time < as->slice_end);
+
+ RenderingServerDefault::redraw_request(); // animation visible means redraw request
+ } break;
+ }
+
+ c = c->next;
+ r_batch_broken = false;
+ }
+
+ if (r_current_clip && reclip) {
+ // will make it re-enable clipping if needed afterwards
+ r_current_clip = nullptr;
+ }
+}
+
+void RendererCanvasRenderRD::_render_batch(RD::DrawListID p_draw_list, PipelineVariants *p_pipeline_variants, RenderingDevice::FramebufferFormatID p_framebuffer_format, Light *p_lights, Batch const *p_batch, RenderingMethod::RenderInfo *r_render_info) {
+ UniformSetCacheRD *uniform_set_cache = UniformSetCacheRD::get_singleton();
+ ERR_FAIL_NULL(uniform_set_cache);
+
+ ERR_FAIL_NULL(p_batch->command);
+
+ _bind_canvas_texture(p_draw_list, p_batch->tex_uniform_set);
+
+ switch (p_batch->command_type) {
+ case Item::Command::TYPE_RECT:
+ case Item::Command::TYPE_NINEPATCH: {
+ RID pipeline = p_pipeline_variants->variants[p_batch->light_mode][p_batch->pipeline_variant].get_render_pipeline(RD::INVALID_ID, p_framebuffer_format);
+ RD::get_singleton()->draw_list_bind_render_pipeline(p_draw_list, pipeline);
+ if (p_batch->has_blend) {
+ RD::get_singleton()->draw_list_set_blend_constants(p_draw_list, p_batch->modulate);
+ }
+
+ PushConstant push_constant;
+ push_constant.base_instance_index = p_batch->start;
+ RD::get_singleton()->draw_list_set_push_constant(p_draw_list, &push_constant, sizeof(PushConstant));
+
+ RD::Uniform u_instance_data(RD::UNIFORM_TYPE_STORAGE_BUFFER, 0, state.canvas_instance_data_buffers[state.current_data_buffer_index].instance_buffers[p_batch->instance_buffer_index]);
+ RD::get_singleton()->draw_list_bind_uniform_set(
+ p_draw_list,
+ uniform_set_cache->get_cache(shader.default_version_rd_shader, INSTANCE_DATA_UNIFORM_SET, u_instance_data),
+ INSTANCE_DATA_UNIFORM_SET);
+
+ RD::get_singleton()->draw_list_bind_index_array(p_draw_list, shader.quad_index_array);
+ RD::get_singleton()->draw_list_draw(p_draw_list, true, p_batch->instance_count);
+
+ if (r_render_info) {
+ r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_OBJECTS_IN_FRAME] += p_batch->instance_count;
+ r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_PRIMITIVES_IN_FRAME] += 2 * p_batch->instance_count;
+ r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_DRAW_CALLS_IN_FRAME]++;
+ }
+ } break;
+
+ case Item::Command::TYPE_POLYGON: {
+ const Item::CommandPolygon *polygon = static_cast<const Item::CommandPolygon *>(p_batch->command);
+
+ PolygonBuffers *pb = polygon_buffers.polygons.getptr(polygon->polygon.polygon_id);
+ ERR_FAIL_NULL(pb);
+
+ RID pipeline = p_pipeline_variants->variants[p_batch->light_mode][p_batch->pipeline_variant].get_render_pipeline(pb->vertex_format_id, p_framebuffer_format);
+ RD::get_singleton()->draw_list_bind_render_pipeline(p_draw_list, pipeline);
+
+ PushConstant push_constant;
+ push_constant.base_instance_index = p_batch->start;
+ RD::get_singleton()->draw_list_set_push_constant(p_draw_list, &push_constant, sizeof(PushConstant));
+
+ RD::Uniform u_instance_data(RD::UNIFORM_TYPE_STORAGE_BUFFER, 0, state.canvas_instance_data_buffers[state.current_data_buffer_index].instance_buffers[p_batch->instance_buffer_index]);
+ RD::get_singleton()->draw_list_bind_uniform_set(
+ p_draw_list,
+ uniform_set_cache->get_cache(shader.default_version_rd_shader, INSTANCE_DATA_UNIFORM_SET, u_instance_data),
+ INSTANCE_DATA_UNIFORM_SET);
+
+ RD::get_singleton()->draw_list_bind_vertex_array(p_draw_list, pb->vertex_array);
+ if (pb->indices.is_valid()) {
+ RD::get_singleton()->draw_list_bind_index_array(p_draw_list, pb->indices);
+ }
+
+ RD::get_singleton()->draw_list_draw(p_draw_list, pb->indices.is_valid());
+ if (r_render_info) {
+ r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_OBJECTS_IN_FRAME]++;
+ r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_PRIMITIVES_IN_FRAME] += _indices_to_primitives(polygon->primitive, pb->primitive_count);
+ r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_DRAW_CALLS_IN_FRAME]++;
+ }
+ } break;
+
+ case Item::Command::TYPE_PRIMITIVE: {
+ const Item::CommandPrimitive *primitive = static_cast<const Item::CommandPrimitive *>(p_batch->command);
+
+ RID pipeline = p_pipeline_variants->variants[p_batch->light_mode][p_batch->pipeline_variant].get_render_pipeline(RD::INVALID_ID, p_framebuffer_format);
+ RD::get_singleton()->draw_list_bind_render_pipeline(p_draw_list, pipeline);
+
+ PushConstant push_constant;
+ push_constant.base_instance_index = p_batch->start;
+ RD::get_singleton()->draw_list_set_push_constant(p_draw_list, &push_constant, sizeof(PushConstant));
+
+ RD::Uniform u_instance_data(RD::UNIFORM_TYPE_STORAGE_BUFFER, 0, state.canvas_instance_data_buffers[state.current_data_buffer_index].instance_buffers[p_batch->instance_buffer_index]);
+ RD::get_singleton()->draw_list_bind_uniform_set(
+ p_draw_list,
+ uniform_set_cache->get_cache(shader.default_version_rd_shader, INSTANCE_DATA_UNIFORM_SET, u_instance_data),
+ INSTANCE_DATA_UNIFORM_SET);
+
+ RD::get_singleton()->draw_list_bind_index_array(p_draw_list, primitive_arrays.index_array[MIN(3u, primitive->point_count) - 1]);
+ uint32_t instance_count = p_batch->instance_count;
+ RD::get_singleton()->draw_list_draw(p_draw_list, true, instance_count);
+
+ if (r_render_info) {
+ const RenderingServer::PrimitiveType rs_primitive[5] = { RS::PRIMITIVE_POINTS, RS::PRIMITIVE_POINTS, RS::PRIMITIVE_LINES, RS::PRIMITIVE_TRIANGLES, RS::PRIMITIVE_TRIANGLES };
+ r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_OBJECTS_IN_FRAME] += instance_count;
+ r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_PRIMITIVES_IN_FRAME] += _indices_to_primitives(rs_primitive[p_batch->primitive_points], p_batch->primitive_points) * instance_count;
+ r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_DRAW_CALLS_IN_FRAME]++;
+ }
+ } break;
+
+ case Item::Command::TYPE_MESH:
+ case Item::Command::TYPE_MULTIMESH:
+ case Item::Command::TYPE_PARTICLES: {
+ RendererRD::MeshStorage *mesh_storage = RendererRD::MeshStorage::get_singleton();
+ RendererRD::ParticlesStorage *particles_storage = RendererRD::ParticlesStorage::get_singleton();
+
+ RID mesh;
+ RID mesh_instance;
+
+ if (p_batch->command_type == Item::Command::TYPE_MESH) {
+ const Item::CommandMesh *m = static_cast<const Item::CommandMesh *>(p_batch->command);
+ mesh = m->mesh;
+ mesh_instance = m->mesh_instance;
+ } else if (p_batch->command_type == Item::Command::TYPE_MULTIMESH) {
+ const Item::CommandMultiMesh *mm = static_cast<const Item::CommandMultiMesh *>(p_batch->command);
+ RID multimesh = mm->multimesh;
+ mesh = mesh_storage->multimesh_get_mesh(multimesh);
+
+ RID uniform_set = mesh_storage->multimesh_get_2d_uniform_set(multimesh, shader.default_version_rd_shader, TRANSFORMS_UNIFORM_SET);
+ RD::get_singleton()->draw_list_bind_uniform_set(p_draw_list, uniform_set, TRANSFORMS_UNIFORM_SET);
+ } else if (p_batch->command_type == Item::Command::TYPE_PARTICLES) {
+ const Item::CommandParticles *pt = static_cast<const Item::CommandParticles *>(p_batch->command);
+ RID particles = pt->particles;
+ mesh = particles_storage->particles_get_draw_pass_mesh(particles, 0);
+
+ ERR_BREAK(particles_storage->particles_get_mode(particles) != RS::PARTICLES_MODE_2D);
+ particles_storage->particles_request_process(particles);
+
+ if (particles_storage->particles_is_inactive(particles)) {
+ break;
+ }
+
+ RenderingServerDefault::redraw_request(); // Active particles means redraw request.
+
+ int dpc = particles_storage->particles_get_draw_passes(particles);
+ if (dpc == 0) {
+ break; // Nothing to draw.
+ }
+
+ RID uniform_set = particles_storage->particles_get_instance_buffer_uniform_set(pt->particles, shader.default_version_rd_shader, TRANSFORMS_UNIFORM_SET);
+ RD::get_singleton()->draw_list_bind_uniform_set(p_draw_list, uniform_set, TRANSFORMS_UNIFORM_SET);
+ }
+
+ if (mesh.is_null()) {
+ break;
+ }
+
+ RD::Uniform u_instance_data(RD::UNIFORM_TYPE_STORAGE_BUFFER, 0, state.canvas_instance_data_buffers[state.current_data_buffer_index].instance_buffers[p_batch->instance_buffer_index]);
+ RD::get_singleton()->draw_list_bind_uniform_set(
+ p_draw_list,
+ uniform_set_cache->get_cache(shader.default_version_rd_shader, INSTANCE_DATA_UNIFORM_SET, u_instance_data),
+ INSTANCE_DATA_UNIFORM_SET);
+
+ uint32_t surf_count = mesh_storage->mesh_get_surface_count(mesh);
+ static const PipelineVariant variant[RS::PRIMITIVE_MAX] = { PIPELINE_VARIANT_ATTRIBUTE_POINTS, PIPELINE_VARIANT_ATTRIBUTE_LINES, PIPELINE_VARIANT_ATTRIBUTE_LINES_STRIP, PIPELINE_VARIANT_ATTRIBUTE_TRIANGLES, PIPELINE_VARIANT_ATTRIBUTE_TRIANGLE_STRIP };
+
+ for (uint32_t j = 0; j < surf_count; j++) {
+ void *surface = mesh_storage->mesh_get_surface(mesh, j);
+
+ RS::PrimitiveType primitive = mesh_storage->mesh_surface_get_primitive(surface);
+ ERR_CONTINUE(primitive < 0 || primitive >= RS::PRIMITIVE_MAX);
+
+ uint64_t input_mask = p_pipeline_variants->variants[p_batch->light_mode][variant[primitive]].get_vertex_input_mask();
+
+ RID vertex_array;
+ RD::VertexFormatID vertex_format = RD::INVALID_FORMAT_ID;
+
+ if (mesh_instance.is_valid()) {
+ mesh_storage->mesh_instance_surface_get_vertex_arrays_and_format(mesh_instance, j, input_mask, false, vertex_array, vertex_format);
+ } else {
+ mesh_storage->mesh_surface_get_vertex_arrays_and_format(surface, input_mask, false, vertex_array, vertex_format);
+ }
+
+ RID pipeline = p_pipeline_variants->variants[p_batch->light_mode][variant[primitive]].get_render_pipeline(vertex_format, p_framebuffer_format);
+ RD::get_singleton()->draw_list_bind_render_pipeline(p_draw_list, pipeline);
+
+ PushConstant push_constant;
+ push_constant.base_instance_index = p_batch->start;
+ RD::get_singleton()->draw_list_set_push_constant(p_draw_list, &push_constant, sizeof(PushConstant));
+
+ RID index_array = mesh_storage->mesh_surface_get_index_array(surface, 0);
+
+ if (index_array.is_valid()) {
+ RD::get_singleton()->draw_list_bind_index_array(p_draw_list, index_array);
+ }
+
+ RD::get_singleton()->draw_list_bind_vertex_array(p_draw_list, vertex_array);
+ RD::get_singleton()->draw_list_draw(p_draw_list, index_array.is_valid(), p_batch->mesh_instance_count);
+
+ if (r_render_info) {
+ r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_OBJECTS_IN_FRAME]++;
+ r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_PRIMITIVES_IN_FRAME] += _indices_to_primitives(primitive, mesh_storage->mesh_surface_get_vertices_drawn_count(surface)) * p_batch->mesh_instance_count;
+ r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_DRAW_CALLS_IN_FRAME]++;
+ }
+ }
+ } break;
+ case Item::Command::TYPE_TRANSFORM:
+ case Item::Command::TYPE_CLIP_IGNORE:
+ case Item::Command::TYPE_ANIMATION_SLICE: {
+ // Can ignore these as they only impact batch creation.
+ } break;
+ }
+}
+
+RendererCanvasRenderRD::Batch *RendererCanvasRenderRD::_new_batch(bool &r_batch_broken) {
+ if (state.canvas_instance_batches.size() == 0) {
+ state.canvas_instance_batches.push_back(Batch());
+ return state.canvas_instance_batches.ptr();
+ }
+
+ if (r_batch_broken || state.canvas_instance_batches[state.current_batch_index].instance_count == 0) {
+ return &state.canvas_instance_batches[state.current_batch_index];
+ }
+
+ r_batch_broken = true;
+
+ // Copy the properties of the current batch, we will manually update the things that changed.
+ Batch new_batch = state.canvas_instance_batches[state.current_batch_index];
+ new_batch.instance_count = 0;
+ new_batch.start = state.canvas_instance_batches[state.current_batch_index].start + state.canvas_instance_batches[state.current_batch_index].instance_count;
+ new_batch.instance_buffer_index = state.current_instance_buffer_index;
+ state.current_batch_index++;
+ state.canvas_instance_batches.push_back(new_batch);
+ return &state.canvas_instance_batches[state.current_batch_index];
+}
+
+void RendererCanvasRenderRD::_add_to_batch(uint32_t &r_index, bool &r_batch_broken, Batch *&r_current_batch) {
+ r_current_batch->instance_count++;
+ r_index++;
+ if (r_index + state.last_instance_index >= state.max_instances_per_buffer) {
+ // Copy over all data needed for rendering right away
+ // then go back to recording item commands.
+ RD::get_singleton()->buffer_update(
+ state.canvas_instance_data_buffers[state.current_data_buffer_index].instance_buffers[state.current_instance_buffer_index],
+ state.last_instance_index * sizeof(InstanceData),
+ r_index * sizeof(InstanceData),
+ state.instance_data_array);
+ _allocate_instance_buffer();
+ r_index = 0;
+ state.last_instance_index = 0;
+ r_batch_broken = false; // Force a new batch to be created
+ r_current_batch = _new_batch(r_batch_broken);
+ r_current_batch->start = 0;
+ }
+}
+
+void RendererCanvasRenderRD::_allocate_instance_buffer() {
+ state.current_instance_buffer_index++;
+
+ if (state.current_instance_buffer_index < state.canvas_instance_data_buffers[state.current_data_buffer_index].instance_buffers.size()) {
+ // We already allocated another buffer in a previous frame, so we can just use it.
+ return;
+ }
+
+ // Allocate a new buffer.
+ RID buf = RD::get_singleton()->storage_buffer_create(state.max_instance_buffer_size);
+ state.canvas_instance_data_buffers[state.current_data_buffer_index].instance_buffers.push_back(buf);
+}
+
+void RendererCanvasRenderRD::_prepare_batch_texture(Batch *p_current_batch, RID p_texture) const {
+ if (p_texture.is_null()) {
+ p_texture = default_canvas_texture;
+ }
+
+ Color specular_shininess;
+ bool use_normal;
+ bool use_specular;
+ Size2i size;
+ bool success = RendererRD::TextureStorage::get_singleton()->canvas_texture_get_uniform_set(
+ p_texture,
+ p_current_batch->tex_state.texture_filter(),
+ p_current_batch->tex_state.texture_repeat(),
+ shader.default_version_rd_shader,
+ CANVAS_TEXTURE_UNIFORM_SET,
+ p_current_batch->tex_state.linear_colors(),
+ p_current_batch->tex_uniform_set,
+ size,
+ specular_shininess,
+ use_normal,
+ use_specular,
+ p_current_batch->tex_state.texture_is_data());
+ // something odd happened
+ if (!success) {
+ _prepare_batch_texture(p_current_batch, default_canvas_texture);
+ return;
+ }
+
+ // cache values to be copied to instance data
+ if (specular_shininess.a < 0.999) {
+ p_current_batch->tex_flags |= FLAGS_DEFAULT_SPECULAR_MAP_USED;
+ }
+
+ if (use_normal) {
+ p_current_batch->tex_flags |= FLAGS_DEFAULT_NORMAL_MAP_USED;
+ }
+
+ uint8_t a = uint8_t(CLAMP(specular_shininess.a * 255.0, 0.0, 255.0));
+ uint8_t b = uint8_t(CLAMP(specular_shininess.b * 255.0, 0.0, 255.0));
+ uint8_t g = uint8_t(CLAMP(specular_shininess.g * 255.0, 0.0, 255.0));
+ uint8_t r = uint8_t(CLAMP(specular_shininess.r * 255.0, 0.0, 255.0));
+ p_current_batch->tex_specular_shininess = uint32_t(a) << 24 | uint32_t(b) << 16 | uint32_t(g) << 8 | uint32_t(r);
+
+ p_current_batch->tex_texpixel_size = Vector2(1.0 / float(size.width), 1.0 / float(size.height));
+}
+
+void RendererCanvasRenderRD::_bind_canvas_texture(RD::DrawListID p_draw_list, RID p_uniform_set) {
+ if (state.current_tex_uniform_set == p_uniform_set) {
+ return;
+ }
+
+ state.current_tex_uniform_set = p_uniform_set;
+
+ RD::get_singleton()->draw_list_bind_uniform_set(p_draw_list, p_uniform_set, CANVAS_TEXTURE_UNIFORM_SET);
+}
+
RendererCanvasRenderRD::~RendererCanvasRenderRD() {
RendererRD::MaterialStorage *material_storage = RendererRD::MaterialStorage::get_singleton();
//canvas state
@@ -2936,6 +3141,13 @@ RendererCanvasRenderRD::~RendererCanvasRenderRD() {
}
RD::get_singleton()->free(state.shadow_texture);
+ memdelete_arr(state.instance_data_array);
+ for (uint32_t i = 0; i < state.canvas_instance_data_buffers.size(); i++) {
+ for (uint32_t j = 0; j < state.canvas_instance_data_buffers[i].instance_buffers.size(); j++) {
+ RD::get_singleton()->free(state.canvas_instance_data_buffers[i].instance_buffers[j]);
+ }
+ }
+
RendererRD::TextureStorage::get_singleton()->canvas_texture_free(default_canvas_texture);
//pipelines don't need freeing, they are all gone after shaders are gone
}
diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.h b/servers/rendering/renderer_rd/renderer_canvas_render_rd.h
index 9deb4814c7..87de07464e 100644
--- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.h
+++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.h
@@ -46,6 +46,7 @@ class RendererCanvasRenderRD : public RendererCanvasRender {
MATERIAL_UNIFORM_SET = 1,
TRANSFORMS_UNIFORM_SET = 2,
CANVAS_TEXTURE_UNIFORM_SET = 3,
+ INSTANCE_DATA_UNIFORM_SET = 4,
};
const int SAMPLERS_BINDING_FIRST_INDEX = 10;
@@ -335,6 +336,146 @@ class RendererCanvasRenderRD : public RendererCanvasRender {
//state that does not vary across rendering all items
+ struct InstanceData {
+ float world[6];
+ uint32_t flags;
+ uint32_t specular_shininess;
+ union {
+ //rect
+ struct {
+ float modulation[4];
+ union {
+ float msdf[4];
+ float ninepatch_margins[4];
+ };
+ float dst_rect[4];
+ float src_rect[4];
+ float pad[2];
+ };
+ //primitive
+ struct {
+ float points[6]; // vec2 points[3]
+ float uvs[6]; // vec2 points[3]
+ uint32_t colors[6]; // colors encoded as half
+ };
+ };
+ float color_texture_pixel_size[2];
+ uint32_t lights[4];
+ };
+
+ struct PushConstant {
+ uint32_t base_instance_index;
+ uint32_t pad1;
+ uint32_t pad2;
+ uint32_t pad3;
+ };
+
+ // TextureState is used to determine when a new batch is required due to a change of texture state.
+ struct TextureState {
+ static const uint32_t FILTER_SHIFT = 0;
+ static const uint32_t FILTER_BITS = 3;
+ static const uint32_t FILTER_MASK = (1 << FILTER_BITS) - 1;
+ static const uint32_t REPEAT_SHIFT = FILTER_BITS;
+ static const uint32_t REPEAT_BITS = 2;
+ static const uint32_t REPEAT_MASK = (1 << REPEAT_BITS) - 1;
+ static const uint32_t TEXTURE_IS_DATA_SHIFT = REPEAT_SHIFT + REPEAT_BITS;
+ static const uint32_t TEXTURE_IS_DATA_BITS = 1;
+ static const uint32_t TEXTURE_IS_DATA_MASK = (1 << TEXTURE_IS_DATA_BITS) - 1;
+ static const uint32_t LINEAR_COLORS_SHIFT = TEXTURE_IS_DATA_SHIFT + TEXTURE_IS_DATA_BITS;
+ static const uint32_t LINEAR_COLORS_BITS = 1;
+ static const uint32_t LINEAR_COLORS_MASK = (1 << LINEAR_COLORS_BITS) - 1;
+
+ RID texture;
+ uint32_t other = 0;
+
+ TextureState() {}
+
+ TextureState(RID p_texture, RS::CanvasItemTextureFilter p_base_filter, RS::CanvasItemTextureRepeat p_base_repeat, bool p_texture_is_data, bool p_use_linear_colors) {
+ texture = p_texture;
+ other = (((uint32_t)p_base_filter & FILTER_MASK) << FILTER_SHIFT) |
+ (((uint32_t)p_base_repeat & REPEAT_MASK) << REPEAT_SHIFT) |
+ (((uint32_t)p_texture_is_data & TEXTURE_IS_DATA_MASK) << TEXTURE_IS_DATA_SHIFT) |
+ (((uint32_t)p_use_linear_colors & LINEAR_COLORS_MASK) << LINEAR_COLORS_SHIFT);
+ }
+
+ _FORCE_INLINE_ RS::CanvasItemTextureFilter texture_filter() const {
+ return (RS::CanvasItemTextureFilter)((other >> FILTER_SHIFT) & FILTER_MASK);
+ }
+
+ _FORCE_INLINE_ RS::CanvasItemTextureRepeat texture_repeat() const {
+ return (RS::CanvasItemTextureRepeat)((other >> REPEAT_SHIFT) & REPEAT_MASK);
+ }
+
+ _FORCE_INLINE_ bool linear_colors() const {
+ return (other >> LINEAR_COLORS_SHIFT) & LINEAR_COLORS_MASK;
+ }
+
+ _FORCE_INLINE_ bool texture_is_data() const {
+ return (other >> TEXTURE_IS_DATA_SHIFT) & TEXTURE_IS_DATA_MASK;
+ }
+
+ bool operator==(const TextureState &p_val) const {
+ return (texture == p_val.texture) && (other == p_val.other);
+ }
+
+ bool operator!=(const TextureState &p_val) const {
+ return (texture != p_val.texture) || (other != p_val.other);
+ }
+ };
+
+ struct Batch {
+ // Position in the UBO measured in bytes
+ uint32_t start = 0;
+ uint32_t instance_count = 0;
+ uint32_t instance_buffer_index = 0;
+
+ TextureState tex_state;
+ RID tex_uniform_set;
+
+ // The following tex_ prefixed fields are used to cache the texture data for the current batch.
+ // These values are applied to new InstanceData for the batch
+
+ // The cached specular shininess derived from the current texture.
+ uint32_t tex_specular_shininess = 0;
+ // The cached texture flags, such as FLAGS_DEFAULT_SPECULAR_MAP_USED and FLAGS_DEFAULT_NORMAL_MAP_USED
+ uint32_t tex_flags = 0;
+ // The cached texture pixel size.
+ Vector2 tex_texpixel_size;
+
+ Color modulate = Color(1.0, 1.0, 1.0, 1.0);
+
+ Item *clip = nullptr;
+
+ RID material;
+ CanvasMaterialData *material_data = nullptr;
+ PipelineLightMode light_mode = PipelineLightMode::PIPELINE_LIGHT_MODE_DISABLED;
+ PipelineVariant pipeline_variant = PipelineVariant::PIPELINE_VARIANT_QUAD;
+
+ const Item::Command *command = nullptr;
+ Item::Command::Type command_type = Item::Command::TYPE_ANIMATION_SLICE; // Can default to any type that doesn't form a batch.
+
+ // batch-specific data
+ union {
+ // TYPE_PRIMITIVE
+ uint32_t primitive_points = 0;
+ // TYPE_PARTICLES
+ uint32_t mesh_instance_count;
+ };
+ bool has_blend = false;
+
+ void set_tex_state(TextureState &p_tex_state) {
+ tex_state = p_tex_state;
+ tex_uniform_set = RID();
+ tex_texpixel_size = Size2();
+ tex_specular_shininess = 0;
+ tex_flags = 0;
+ }
+ };
+
+ struct DataBuffer {
+ LocalVector<RID> instance_buffers;
+ };
+
struct State {
//state buffer
struct Buffer {
@@ -357,6 +498,19 @@ class RendererCanvasRenderRD : public RendererCanvasRender {
uint32_t pad2;
};
+ LocalVector<DataBuffer> canvas_instance_data_buffers;
+ LocalVector<Batch> canvas_instance_batches;
+ uint32_t current_data_buffer_index = 0;
+ uint32_t current_instance_buffer_index = 0;
+ uint32_t current_batch_index = 0;
+ uint32_t last_instance_index = 0;
+ InstanceData *instance_data_array = nullptr;
+
+ uint32_t max_instances_per_buffer = 16384;
+ uint32_t max_instance_buffer_size = 16384 * sizeof(InstanceData);
+
+ RID current_tex_uniform_set;
+
LightUniform *light_uniforms = nullptr;
RID lights_uniform_buffer;
@@ -376,33 +530,6 @@ class RendererCanvasRenderRD : public RendererCanvasRender {
} state;
- struct PushConstant {
- float world[6];
- uint32_t flags;
- uint32_t specular_shininess;
- union {
- //rect
- struct {
- float modulation[4];
- union {
- float msdf[4];
- float ninepatch_margins[4];
- };
- float dst_rect[4];
- float src_rect[4];
- float pad[2];
- };
- //primitive
- struct {
- float points[6]; // vec2 points[3]
- float uvs[6]; // vec2 points[3]
- uint32_t colors[6]; // colors encoded as half
- };
- };
- float color_texture_pixel_size[2];
- uint32_t lights[4];
- };
-
Item *items[MAX_RENDER_ITEMS];
bool using_directional_lights = false;
@@ -422,9 +549,23 @@ class RendererCanvasRenderRD : public RendererCanvasRender {
Color debug_redraw_color;
double debug_redraw_time = 1.0;
- inline void _bind_canvas_texture(RD::DrawListID p_draw_list, RID p_texture, RS::CanvasItemTextureFilter p_base_filter, RS::CanvasItemTextureRepeat p_base_repeat, RID &r_last_texture, PushConstant &push_constant, Size2 &r_texpixel_size, bool p_texture_is_data = false); //recursive, so regular inline used instead.
- void _render_item(RenderingDevice::DrawListID p_draw_list, RID p_render_target, const Item *p_item, RenderingDevice::FramebufferFormatID p_framebuffer_format, const Transform2D &p_canvas_transform_inverse, Item *&current_clip, Light *p_lights, PipelineVariants *p_pipeline_variants, bool &r_sdf_used, const Point2 &p_repeat_offset, RenderingMethod::RenderInfo *r_render_info = nullptr);
- void _render_items(RID p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, bool &r_sdf_used, bool p_to_backbuffer = false, RenderingMethod::RenderInfo *r_render_info = nullptr);
+ // A structure to store cached render target information
+ struct RenderTarget {
+ // Current render target for the canvas.
+ RID render_target;
+ // The base flags for each InstanceData, derived from the render target.
+ // Either FLAGS_CONVERT_ATTRIBUTES_TO_LINEAR or 0
+ uint32_t base_flags = 0;
+ };
+
+ void _render_batch_items(RenderTarget p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, bool &r_sdf_used, bool p_to_backbuffer = false, RenderingMethod::RenderInfo *r_render_info = nullptr);
+ void _record_item_commands(const Item *p_item, RenderTarget p_render_target, const Transform2D &p_base_transform, Item *&r_current_clip, Light *p_lights, uint32_t &r_index, bool &r_batch_broken, bool &r_sdf_used);
+ void _render_batch(RD::DrawListID p_draw_list, PipelineVariants *p_pipeline_variants, RenderingDevice::FramebufferFormatID p_framebuffer_format, Light *p_lights, Batch const *p_batch, RenderingMethod::RenderInfo *r_render_info = nullptr);
+ void _prepare_batch_texture(Batch *p_current_batch, RID p_texture) const;
+ void _bind_canvas_texture(RD::DrawListID p_draw_list, RID p_uniform_set);
+ [[nodiscard]] Batch *_new_batch(bool &r_batch_broken);
+ void _add_to_batch(uint32_t &r_index, bool &r_batch_broken, Batch *&r_current_batch);
+ void _allocate_instance_buffer();
_FORCE_INLINE_ void _update_transform_2d_to_mat2x4(const Transform2D &p_transform, float *p_mat2x4);
_FORCE_INLINE_ void _update_transform_2d_to_mat2x3(const Transform2D &p_transform, float *p_mat2x3);
diff --git a/servers/rendering/renderer_rd/shaders/canvas.glsl b/servers/rendering/renderer_rd/shaders/canvas.glsl
index 4426d9eb66..2154d56faf 100644
--- a/servers/rendering/renderer_rd/shaders/canvas.glsl
+++ b/servers/rendering/renderer_rd/shaders/canvas.glsl
@@ -24,6 +24,12 @@ layout(location = 11) in vec4 weight_attrib;
#include "canvas_uniforms_inc.glsl"
+#ifndef USE_ATTRIBUTES
+
+layout(location = 4) out flat uint instance_index_interp;
+
+#endif // USE_ATTRIBUTES
+
layout(location = 0) out vec2 uv_interp;
layout(location = 1) out vec4 color_interp;
layout(location = 2) out vec2 vertex_interp;
@@ -59,6 +65,14 @@ void main() {
vec4 custom1 = vec4(0.0);
#endif
+#ifdef USE_ATTRIBUTES
+ uint instance_index = params.base_instance_index;
+#else
+ uint instance_index = gl_InstanceIndex + params.base_instance_index;
+ instance_index_interp = instance_index;
+#endif // USE_ATTRIBUTES
+ const InstanceData draw_data = instances.data[instance_index];
+
#ifdef USE_PRIMITIVE
//weird bug,
@@ -117,13 +131,10 @@ void main() {
mat4 model_matrix = mat4(vec4(draw_data.world_x, 0.0, 0.0), vec4(draw_data.world_y, 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), vec4(draw_data.world_ofs, 0.0, 1.0));
-#define FLAGS_INSTANCING_MASK 0x7F
-#define FLAGS_INSTANCING_HAS_COLORS (1 << 7)
-#define FLAGS_INSTANCING_HAS_CUSTOM_DATA (1 << 8)
+#ifdef USE_ATTRIBUTES
uint instancing = draw_data.flags & FLAGS_INSTANCING_MASK;
-#ifdef USE_ATTRIBUTES
if (instancing > 1) {
// trails
@@ -160,38 +171,27 @@ void main() {
vertex = new_vertex;
color *= pcolor;
- } else
-#endif // USE_ATTRIBUTES
- {
- if (instancing == 1) {
- uint stride = 2;
- {
- if (bool(draw_data.flags & FLAGS_INSTANCING_HAS_COLORS)) {
- stride += 1;
- }
- if (bool(draw_data.flags & FLAGS_INSTANCING_HAS_CUSTOM_DATA)) {
- stride += 1;
- }
- }
-
- uint offset = stride * gl_InstanceIndex;
+ } else if (instancing == 1) {
+ uint stride = 2 + bitfieldExtract(draw_data.flags, FLAGS_INSTANCING_HAS_COLORS_SHIFT, 1) + bitfieldExtract(draw_data.flags, FLAGS_INSTANCING_HAS_CUSTOM_DATA_SHIFT, 1);
- mat4 matrix = mat4(transforms.data[offset + 0], transforms.data[offset + 1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0));
- offset += 2;
+ uint offset = stride * gl_InstanceIndex;
- if (bool(draw_data.flags & FLAGS_INSTANCING_HAS_COLORS)) {
- color *= transforms.data[offset];
- offset += 1;
- }
+ mat4 matrix = mat4(transforms.data[offset + 0], transforms.data[offset + 1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0));
+ offset += 2;
- if (bool(draw_data.flags & FLAGS_INSTANCING_HAS_CUSTOM_DATA)) {
- instance_custom = transforms.data[offset];
- }
+ if (bool(draw_data.flags & FLAGS_INSTANCING_HAS_COLORS)) {
+ color *= transforms.data[offset];
+ offset += 1;
+ }
- matrix = transpose(matrix);
- model_matrix = model_matrix * matrix;
+ if (bool(draw_data.flags & FLAGS_INSTANCING_HAS_CUSTOM_DATA)) {
+ instance_custom = transforms.data[offset];
}
+
+ matrix = transpose(matrix);
+ model_matrix = model_matrix * matrix;
}
+#endif // USE_ATTRIBUTES
#ifdef USE_POINT_SIZE
float point_size = 1.0;
@@ -241,6 +241,10 @@ void main() {
#include "canvas_uniforms_inc.glsl"
+#ifndef USE_ATTRIBUTES
+layout(location = 4) in flat uint instance_index;
+#endif // USE_ATTRIBUTES
+
layout(location = 0) in vec2 uv_interp;
layout(location = 1) in vec4 color_interp;
layout(location = 2) in vec2 vertex_interp;
@@ -320,6 +324,12 @@ vec4 light_compute(
#ifdef USE_NINEPATCH
float map_ninepatch_axis(float pixel, float draw_size, float tex_pixel_size, float margin_begin, float margin_end, int np_repeat, inout int draw_center) {
+#ifdef USE_ATTRIBUTES
+ const InstanceData draw_data = instances.data[params.base_instance_index];
+#else
+ const InstanceData draw_data = instances.data[instance_index];
+#endif // USE_ATTRIBUTES
+
float tex_size = 1.0 / tex_pixel_size;
if (pixel < margin_begin) {
@@ -327,9 +337,7 @@ float map_ninepatch_axis(float pixel, float draw_size, float tex_pixel_size, flo
} else if (pixel >= draw_size - margin_end) {
return (tex_size - (draw_size - pixel)) * tex_pixel_size;
} else {
- if (!bool(draw_data.flags & FLAGS_NINEPACH_DRAW_CENTER)) {
- draw_center--;
- }
+ draw_center -= 1 - int(bitfieldExtract(draw_data.flags, FLAGS_NINEPACH_DRAW_CENTER_SHIFT, 1));
// np_repeat is passed as uniform using NinePatchRect::AxisStretchMode enum.
if (np_repeat == 0) { // Stretch.
@@ -462,14 +470,20 @@ void main() {
vec2 uv = uv_interp;
vec2 vertex = vertex_interp;
+#ifdef USE_ATTRIBUTES
+ const InstanceData draw_data = instances.data[params.base_instance_index];
+#else
+ const InstanceData draw_data = instances.data[instance_index];
+#endif // USE_ATTRIBUTES
+
#if !defined(USE_ATTRIBUTES) && !defined(USE_PRIMITIVE)
#ifdef USE_NINEPATCH
int draw_center = 2;
uv = vec2(
- map_ninepatch_axis(pixel_size_interp.x, abs(draw_data.dst_rect.z), draw_data.color_texture_pixel_size.x, draw_data.ninepatch_margins.x, draw_data.ninepatch_margins.z, int(draw_data.flags >> FLAGS_NINEPATCH_H_MODE_SHIFT) & 0x3, draw_center),
- map_ninepatch_axis(pixel_size_interp.y, abs(draw_data.dst_rect.w), draw_data.color_texture_pixel_size.y, draw_data.ninepatch_margins.y, draw_data.ninepatch_margins.w, int(draw_data.flags >> FLAGS_NINEPATCH_V_MODE_SHIFT) & 0x3, draw_center));
+ map_ninepatch_axis(pixel_size_interp.x, abs(draw_data.dst_rect.z), draw_data.color_texture_pixel_size.x, draw_data.ninepatch_margins.x, draw_data.ninepatch_margins.z, int(bitfieldExtract(draw_data.flags, FLAGS_NINEPATCH_H_MODE_SHIFT, 2)), draw_center),
+ map_ninepatch_axis(pixel_size_interp.y, abs(draw_data.dst_rect.w), draw_data.color_texture_pixel_size.y, draw_data.ninepatch_margins.y, draw_data.ninepatch_margins.w, int(bitfieldExtract(draw_data.flags, FLAGS_NINEPATCH_V_MODE_SHIFT, 2)), draw_center));
if (draw_center == 0) {
color.a = 0.0;
@@ -519,8 +533,8 @@ void main() {
color *= texture(sampler2D(color_texture, texture_sampler), uv);
}
- uint light_count = (draw_data.flags >> FLAGS_LIGHT_COUNT_SHIFT) & 0xF; //max 16 lights
- bool using_light = light_count > 0 || canvas_data.directional_light_count > 0;
+ uint light_count = bitfieldExtract(draw_data.flags, FLAGS_LIGHT_COUNT_SHIFT, 4); //max 16 lights
+ bool using_light = (light_count + canvas_data.directional_light_count) > 0;
vec3 normal;
@@ -652,9 +666,7 @@ void main() {
if (i >= light_count) {
break;
}
- uint light_base = draw_data.lights[i >> 2];
- light_base >>= (i & 3) * 8;
- light_base &= 0xFF;
+ uint light_base = bitfieldExtract(draw_data.lights[i >> 2], (int(i) & 0x3) * 8, 8);
vec2 tex_uv = (vec4(vertex, 0.0, 1.0) * mat4(light_array.data[light_base].texture_matrix[0], light_array.data[light_base].texture_matrix[1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0))).xy; //multiply inverse given its transposed. Optimizer removes useless operations.
vec2 tex_uv_atlas = tex_uv * light_array.data[light_base].atlas_rect.zw + light_array.data[light_base].atlas_rect.xy;
diff --git a/servers/rendering/renderer_rd/shaders/canvas_uniforms_inc.glsl b/servers/rendering/renderer_rd/shaders/canvas_uniforms_inc.glsl
index 8649f4710b..7cf5b4576e 100644
--- a/servers/rendering/renderer_rd/shaders/canvas_uniforms_inc.glsl
+++ b/servers/rendering/renderer_rd/shaders/canvas_uniforms_inc.glsl
@@ -7,13 +7,16 @@
//1 means enabled, 2+ means trails in use
#define FLAGS_INSTANCING_MASK 0x7F
-#define FLAGS_INSTANCING_HAS_COLORS (1 << 7)
-#define FLAGS_INSTANCING_HAS_CUSTOM_DATA (1 << 8)
+#define FLAGS_INSTANCING_HAS_COLORS_SHIFT 7
+#define FLAGS_INSTANCING_HAS_COLORS (1 << FLAGS_INSTANCING_HAS_COLORS_SHIFT)
+#define FLAGS_INSTANCING_HAS_CUSTOM_DATA_SHIFT 8
+#define FLAGS_INSTANCING_HAS_CUSTOM_DATA (1 << FLAGS_INSTANCING_HAS_CUSTOM_DATA_SHIFT)
#define FLAGS_CLIP_RECT_UV (1 << 9)
#define FLAGS_TRANSPOSE_RECT (1 << 10)
#define FLAGS_CONVERT_ATTRIBUTES_TO_LINEAR (1 << 11)
-#define FLAGS_NINEPACH_DRAW_CENTER (1 << 12)
+#define FLAGS_NINEPACH_DRAW_CENTER_SHIFT 12
+#define FLAGS_NINEPACH_DRAW_CENTER (1 << FLAGS_NINEPACH_DRAW_CENTER_SHIFT)
#define FLAGS_NINEPATCH_H_MODE_SHIFT 16
#define FLAGS_NINEPATCH_V_MODE_SHIFT 18
@@ -29,9 +32,7 @@
#define FLAGS_FLIP_H (1 << 30)
#define FLAGS_FLIP_V (1 << 31)
-// Push Constant
-
-layout(push_constant, std430) uniform DrawData {
+struct InstanceData {
vec2 world_x;
vec2 world_y;
vec2 world_ofs;
@@ -51,8 +52,20 @@ layout(push_constant, std430) uniform DrawData {
#endif
vec2 color_texture_pixel_size;
uint lights[4];
+};
+
+layout(set = 4, binding = 0, std430) restrict readonly buffer DrawData {
+ InstanceData data[];
+}
+instances;
+
+layout(push_constant, std430) uniform Params {
+ uint base_instance_index; // base index to instance data
+ uint pad1;
+ uint pad2;
+ uint pad3;
}
-draw_data;
+params;
// In vulkan, sets should always be ordered using the following logic:
// Lower Sets: Sets that change format and layout less often
diff --git a/servers/rendering/shader_compiler.cpp b/servers/rendering/shader_compiler.cpp
index 43703f8656..29183eac6f 100644
--- a/servers/rendering/shader_compiler.cpp
+++ b/servers/rendering/shader_compiler.cpp
@@ -185,7 +185,7 @@ static String f2sp0(float p_float) {
return num;
}
-static String get_constant_text(SL::DataType p_type, const Vector<SL::Scalar> &p_values) {
+static String get_constant_text(SL::DataType p_type, const Vector<SL::Scalar> &p_values, bool p_is_op) {
switch (p_type) {
case SL::TYPE_BOOL:
return p_values[0].boolean ? "true" : "false";
@@ -205,7 +205,7 @@ static String get_constant_text(SL::DataType p_type, const Vector<SL::Scalar> &p
}
case SL::TYPE_INT:
- return itos(p_values[0].sint);
+ return itos(p_is_op ? Math::abs(p_values[0].sint) : p_values[0].sint); // To prevent writing unary minus twice in operator expression parsing.
case SL::TYPE_IVEC2:
case SL::TYPE_IVEC3:
case SL::TYPE_IVEC4: {
@@ -238,7 +238,7 @@ static String get_constant_text(SL::DataType p_type, const Vector<SL::Scalar> &p
return text;
} break;
case SL::TYPE_FLOAT:
- return f2sp0(p_values[0].real);
+ return f2sp0(p_is_op ? Math::abs(p_values[0].real) : p_values[0].real); // To prevent writing unary minus twice in operator expression parsing.
case SL::TYPE_VEC2:
case SL::TYPE_VEC3:
case SL::TYPE_VEC4: {
@@ -446,7 +446,7 @@ static String _get_global_shader_uniform_from_type_and_index(const String &p_buf
}
}
-String ShaderCompiler::_dump_node_code(const SL::Node *p_node, int p_level, GeneratedCode &r_gen_code, IdentifierActions &p_actions, const DefaultIdentifierActions &p_default_actions, bool p_assigning, bool p_use_scope) {
+String ShaderCompiler::_dump_node_code(const SL::Node *p_node, int p_level, GeneratedCode &r_gen_code, IdentifierActions &p_actions, const DefaultIdentifierActions &p_default_actions, bool p_assigning, bool p_use_scope, bool p_is_op) {
String code;
switch (p_node->type) {
@@ -1090,7 +1090,7 @@ String ShaderCompiler::_dump_node_code(const SL::Node *p_node, int p_level, Gene
SL::ConstantNode *cnode = (SL::ConstantNode *)p_node;
if (cnode->array_size == 0) {
- return get_constant_text(cnode->datatype, cnode->values);
+ return get_constant_text(cnode->datatype, cnode->values, p_is_op);
} else {
if (cnode->get_datatype() == SL::TYPE_STRUCT) {
code += _mkid(cnode->struct_name);
@@ -1128,18 +1128,18 @@ String ShaderCompiler::_dump_node_code(const SL::Node *p_node, int p_level, Gene
case SL::OP_ASSIGN_BIT_AND:
case SL::OP_ASSIGN_BIT_OR:
case SL::OP_ASSIGN_BIT_XOR:
- code = _dump_node_code(onode->arguments[0], p_level, r_gen_code, p_actions, p_default_actions, true) + _opstr(onode->op) + _dump_node_code(onode->arguments[1], p_level, r_gen_code, p_actions, p_default_actions, p_assigning);
+ code = _dump_node_code(onode->arguments[0], p_level, r_gen_code, p_actions, p_default_actions, true, true, true) + _opstr(onode->op) + _dump_node_code(onode->arguments[1], p_level, r_gen_code, p_actions, p_default_actions, p_assigning, true, true);
break;
case SL::OP_BIT_INVERT:
case SL::OP_NEGATE:
case SL::OP_NOT:
case SL::OP_DECREMENT:
case SL::OP_INCREMENT:
- code = _opstr(onode->op) + _dump_node_code(onode->arguments[0], p_level, r_gen_code, p_actions, p_default_actions, p_assigning);
+ code = _opstr(onode->op) + _dump_node_code(onode->arguments[0], p_level, r_gen_code, p_actions, p_default_actions, p_assigning, true, true);
break;
case SL::OP_POST_DECREMENT:
case SL::OP_POST_INCREMENT:
- code = _dump_node_code(onode->arguments[0], p_level, r_gen_code, p_actions, p_default_actions, p_assigning) + _opstr(onode->op);
+ code = _dump_node_code(onode->arguments[0], p_level, r_gen_code, p_actions, p_default_actions, p_assigning, true, true) + _opstr(onode->op);
break;
case SL::OP_CALL:
case SL::OP_STRUCT:
@@ -1235,7 +1235,7 @@ String ShaderCompiler::_dump_node_code(const SL::Node *p_node, int p_level, Gene
}
}
- String node_code = _dump_node_code(onode->arguments[i], p_level, r_gen_code, p_actions, p_default_actions, p_assigning);
+ String node_code = _dump_node_code(onode->arguments[i], p_level, r_gen_code, p_actions, p_default_actions, p_assigning, true, true);
if (is_texture_func && i == 1) {
// If we're doing a texture lookup we need to check our texture argument
StringName texture_uniform;
@@ -1352,19 +1352,19 @@ String ShaderCompiler::_dump_node_code(const SL::Node *p_node, int p_level, Gene
}
} break;
case SL::OP_INDEX: {
- code += _dump_node_code(onode->arguments[0], p_level, r_gen_code, p_actions, p_default_actions, p_assigning);
+ code += _dump_node_code(onode->arguments[0], p_level, r_gen_code, p_actions, p_default_actions, p_assigning, true, true);
code += "[";
- code += _dump_node_code(onode->arguments[1], p_level, r_gen_code, p_actions, p_default_actions, p_assigning);
+ code += _dump_node_code(onode->arguments[1], p_level, r_gen_code, p_actions, p_default_actions, p_assigning, true, true);
code += "]";
} break;
case SL::OP_SELECT_IF: {
code += "(";
- code += _dump_node_code(onode->arguments[0], p_level, r_gen_code, p_actions, p_default_actions, p_assigning);
+ code += _dump_node_code(onode->arguments[0], p_level, r_gen_code, p_actions, p_default_actions, p_assigning, true, true);
code += "?";
- code += _dump_node_code(onode->arguments[1], p_level, r_gen_code, p_actions, p_default_actions, p_assigning);
+ code += _dump_node_code(onode->arguments[1], p_level, r_gen_code, p_actions, p_default_actions, p_assigning, true, true);
code += ":";
- code += _dump_node_code(onode->arguments[2], p_level, r_gen_code, p_actions, p_default_actions, p_assigning);
+ code += _dump_node_code(onode->arguments[2], p_level, r_gen_code, p_actions, p_default_actions, p_assigning, true, true);
code += ")";
} break;
@@ -1376,7 +1376,7 @@ String ShaderCompiler::_dump_node_code(const SL::Node *p_node, int p_level, Gene
if (p_use_scope) {
code += "(";
}
- code += _dump_node_code(onode->arguments[0], p_level, r_gen_code, p_actions, p_default_actions, p_assigning) + " " + _opstr(onode->op) + " " + _dump_node_code(onode->arguments[1], p_level, r_gen_code, p_actions, p_default_actions, p_assigning);
+ code += _dump_node_code(onode->arguments[0], p_level, r_gen_code, p_actions, p_default_actions, p_assigning) + " " + _opstr(onode->op) + " " + _dump_node_code(onode->arguments[1], p_level, r_gen_code, p_actions, p_default_actions, p_assigning, true, true);
if (p_use_scope) {
code += ")";
}
diff --git a/servers/rendering/shader_compiler.h b/servers/rendering/shader_compiler.h
index 66106d7eb7..bf0cfd7032 100644
--- a/servers/rendering/shader_compiler.h
+++ b/servers/rendering/shader_compiler.h
@@ -109,7 +109,7 @@ private:
String _get_sampler_name(ShaderLanguage::TextureFilter p_filter, ShaderLanguage::TextureRepeat p_repeat);
void _dump_function_deps(const ShaderLanguage::ShaderNode *p_node, const StringName &p_for_func, const HashMap<StringName, String> &p_func_code, String &r_to_add, HashSet<StringName> &added);
- String _dump_node_code(const ShaderLanguage::Node *p_node, int p_level, GeneratedCode &r_gen_code, IdentifierActions &p_actions, const DefaultIdentifierActions &p_default_actions, bool p_assigning, bool p_scope = true);
+ String _dump_node_code(const ShaderLanguage::Node *p_node, int p_level, GeneratedCode &r_gen_code, IdentifierActions &p_actions, const DefaultIdentifierActions &p_default_actions, bool p_assigning, bool p_scope = true, bool p_is_op = false);
const ShaderLanguage::ShaderNode *shader = nullptr;
const ShaderLanguage::FunctionNode *function = nullptr;
diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp
index 2249cd2010..5a3c5d2fd0 100644
--- a/servers/rendering/shader_language.cpp
+++ b/servers/rendering/shader_language.cpp
@@ -8071,12 +8071,10 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const FunctionInfo &p_fun
if (!n) {
return ERR_PARSE_ERROR;
}
- {
- const ShaderLanguage::DataType switch_type = n->get_datatype();
- if (switch_type != TYPE_INT && switch_type != TYPE_UINT) {
- _set_error(RTR("Expected an integer expression."));
- return ERR_PARSE_ERROR;
- }
+ const ShaderLanguage::DataType data_type = n->get_datatype();
+ if (data_type != TYPE_INT && data_type != TYPE_UINT) {
+ _set_error(RTR("Expected an integer or unsigned integer expression."));
+ return ERR_PARSE_ERROR;
}
tk = _get_token();
if (tk.type != TK_PARENTHESIS_CLOSE) {
@@ -8091,11 +8089,22 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const FunctionInfo &p_fun
BlockNode *switch_block = alloc_node<BlockNode>();
switch_block->block_type = BlockNode::BLOCK_TYPE_SWITCH;
switch_block->parent_block = p_block;
+ switch_block->expected_type = data_type;
cf->expressions.push_back(n);
cf->blocks.push_back(switch_block);
p_block->statements.push_back(cf);
- int prev_type = TK_CF_CASE;
+ pos = _get_tkpos();
+ tk = _get_token();
+ TokenType prev_type;
+ if (tk.type == TK_CF_CASE || tk.type == TK_CF_DEFAULT) {
+ prev_type = tk.type;
+ _set_tkpos(pos);
+ } else {
+ _set_expected_error("case", "default");
+ return ERR_PARSE_ERROR;
+ }
+
while (true) { // Go-through multiple cases.
if (_parse_block(switch_block, p_function_info, true, true, false) != OK) {
@@ -8117,43 +8126,6 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const FunctionInfo &p_fun
_set_tkpos(pos);
continue;
} else {
- HashSet<int> constants;
- for (ShaderLanguage::Node *statement : switch_block->statements) { // Checks for duplicates.
- ControlFlowNode *flow = static_cast<ControlFlowNode *>(statement);
- if (flow) {
- if (flow->flow_op == FLOW_OP_CASE) {
- if (flow->expressions[0]->type == Node::NODE_TYPE_CONSTANT) {
- ConstantNode *cn = static_cast<ConstantNode *>(flow->expressions[0]);
- if (!cn || cn->values.is_empty()) {
- return ERR_PARSE_ERROR;
- }
- if (constants.has(cn->values[0].sint)) {
- _set_error(vformat(RTR("Duplicated case label: %d."), cn->values[0].sint));
- return ERR_PARSE_ERROR;
- }
- constants.insert(cn->values[0].sint);
- } else if (flow->expressions[0]->type == Node::NODE_TYPE_VARIABLE) {
- VariableNode *vn = static_cast<VariableNode *>(flow->expressions[0]);
- if (!vn) {
- return ERR_PARSE_ERROR;
- }
- Vector<Scalar> v = { Scalar() };
- _find_identifier(p_block, false, p_function_info, vn->name, nullptr, nullptr, nullptr, nullptr, nullptr, &v);
- if (constants.has(v[0].sint)) {
- _set_error(vformat(RTR("Duplicated case label: %d."), v[0].sint));
- return ERR_PARSE_ERROR;
- }
- constants.insert(v[0].sint);
- }
- } else if (flow->flow_op == FLOW_OP_DEFAULT) {
- continue;
- } else {
- return ERR_PARSE_ERROR;
- }
- } else {
- return ERR_PARSE_ERROR;
- }
- }
break;
}
}
@@ -8184,36 +8156,77 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const FunctionInfo &p_fun
if (!tk.is_integer_constant()) {
bool correct_constant_expression = false;
- DataType data_type;
if (tk.type == TK_IDENTIFIER) {
+ DataType data_type;
bool is_const;
+
_find_identifier(p_block, false, p_function_info, tk.text, &data_type, nullptr, &is_const);
- if (is_const) {
- if (data_type == TYPE_INT) {
- correct_constant_expression = true;
- }
+ if (is_const && data_type == p_block->expected_type) {
+ correct_constant_expression = true;
}
}
+
if (!correct_constant_expression) {
- _set_error(RTR("Expected an integer constant."));
+ if (p_block->expected_type == TYPE_UINT) {
+ _set_error(RTR("Expected an unsigned integer constant."));
+ } else {
+ _set_error(RTR("Expected an integer constant."));
+ }
return ERR_PARSE_ERROR;
}
VariableNode *vn = alloc_node<VariableNode>();
vn->name = tk.text;
+ {
+ Vector<Scalar> v;
+ DataType data_type;
+
+ _find_identifier(p_block, false, p_function_info, vn->name, &data_type, nullptr, nullptr, nullptr, nullptr, &v);
+ if (data_type == TYPE_INT) {
+ if (p_block->constants.has(v[0].sint)) {
+ _set_error(vformat(RTR("Duplicated case label: %d."), v[0].sint));
+ return ERR_PARSE_ERROR;
+ }
+ p_block->constants.insert(v[0].sint);
+ } else {
+ if (p_block->constants.has(v[0].uint)) {
+ _set_error(vformat(RTR("Duplicated case label: %d."), v[0].uint));
+ return ERR_PARSE_ERROR;
+ }
+ p_block->constants.insert(v[0].uint);
+ }
+ }
n = vn;
} else {
+ ConstantNode *cn = alloc_node<ConstantNode>();
Scalar v;
- if (tk.type == TK_UINT_CONSTANT) {
+ if (p_block->expected_type == TYPE_UINT) {
+ if (tk.type != TK_UINT_CONSTANT) {
+ _set_error(RTR("Expected an unsigned integer constant."));
+ return ERR_PARSE_ERROR;
+ }
v.uint = (uint32_t)tk.constant;
+ if (p_block->constants.has(v.uint)) {
+ _set_error(vformat(RTR("Duplicated case label: %d."), v.uint));
+ return ERR_PARSE_ERROR;
+ }
+ p_block->constants.insert(v.uint);
+ cn->datatype = TYPE_UINT;
} else {
- v.sint = (int)tk.constant * sign;
+ if (tk.type != TK_INT_CONSTANT) {
+ _set_error(RTR("Expected an integer constant."));
+ return ERR_PARSE_ERROR;
+ }
+ v.sint = (int32_t)tk.constant * sign;
+ if (p_block->constants.has(v.sint)) {
+ _set_error(vformat(RTR("Duplicated case label: %d."), v.sint));
+ return ERR_PARSE_ERROR;
+ }
+ p_block->constants.insert(v.sint);
+ cn->datatype = TYPE_INT;
}
-
- ConstantNode *cn = alloc_node<ConstantNode>();
cn->values.push_back(v);
- cn->datatype = (tk.type == TK_UINT_CONSTANT ? TYPE_UINT : TYPE_INT);
n = cn;
}
diff --git a/servers/rendering/shader_language.h b/servers/rendering/shader_language.h
index ba02e181b9..b0d579dfe7 100644
--- a/servers/rendering/shader_language.h
+++ b/servers/rendering/shader_language.h
@@ -544,6 +544,9 @@ public:
bool use_comma_between_statements = false;
bool use_op_eval = true;
+ DataType expected_type = TYPE_VOID;
+ HashSet<int> constants;
+
BlockNode() :
Node(NODE_TYPE_BLOCK) {}
};
diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp
index 1848d5602e..f354e83893 100644
--- a/servers/rendering_server.cpp
+++ b/servers/rendering_server.cpp
@@ -3548,6 +3548,7 @@ void RenderingServer::init() {
GLOBAL_DEF("rendering/lights_and_shadows/positional_shadow/soft_shadow_filter_quality.mobile", 0);
GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/2d/shadow_atlas/size", PROPERTY_HINT_RANGE, "128,16384"), 2048);
+ GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "rendering/2d/batching/item_buffer_size", PROPERTY_HINT_RANGE, "128,1048576,1"), 16384);
// Number of commands that can be drawn per frame.
GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "rendering/gl_compatibility/item_buffer_size", PROPERTY_HINT_RANGE, "128,1048576,1"), 16384);
diff --git a/servers/xr/xr_interface.h b/servers/xr/xr_interface.h
index 7beec219bb..6b6a67a04c 100644
--- a/servers/xr/xr_interface.h
+++ b/servers/xr/xr_interface.h
@@ -44,7 +44,7 @@ struct BlitToScreen;
The idea is that we subclass this class, implement the logic, and then instantiate a singleton of each interface
when Godot starts. These instances do not initialize themselves but register themselves with the AR/VR server.
- If the user wants to enable AR/VR the choose the interface they want to use and initialize it.
+ If the user wants to enable AR/VR, they can choose the interface they want to use and initialize it.
Note that we may make this into a fully instantiable class for GDExtension support.
*/
diff --git a/tests/core/math/test_expression.h b/tests/core/math/test_expression.h
index 512d7932f9..c3e4280491 100644
--- a/tests/core/math/test_expression.h
+++ b/tests/core/math/test_expression.h
@@ -122,6 +122,59 @@ TEST_CASE("[Expression] Floating-point arithmetic") {
"Float multiplication-addition-subtraction-division should return the expected result.");
}
+TEST_CASE("[Expression] Floating-point notation") {
+ Expression expression;
+
+ CHECK_MESSAGE(
+ expression.parse("2.") == OK,
+ "The expression should parse successfully.");
+ CHECK_MESSAGE(
+ double(expression.execute()) == doctest::Approx(2.0),
+ "The expression should return the expected result.");
+
+ CHECK_MESSAGE(
+ expression.parse("(2.)") == OK,
+ "The expression should parse successfully.");
+ CHECK_MESSAGE(
+ double(expression.execute()) == doctest::Approx(2.0),
+ "The expression should return the expected result.");
+
+ CHECK_MESSAGE(
+ expression.parse(".3") == OK,
+ "The expression should parse successfully.");
+ CHECK_MESSAGE(
+ double(expression.execute()) == doctest::Approx(0.3),
+ "The expression should return the expected result.");
+
+ CHECK_MESSAGE(
+ expression.parse("2.+5.") == OK,
+ "The expression should parse successfully.");
+ CHECK_MESSAGE(
+ double(expression.execute()) == doctest::Approx(7.0),
+ "The expression should return the expected result.");
+
+ CHECK_MESSAGE(
+ expression.parse(".3-.8") == OK,
+ "The expression should parse successfully.");
+ CHECK_MESSAGE(
+ double(expression.execute()) == doctest::Approx(-0.5),
+ "The expression should return the expected result.");
+
+ CHECK_MESSAGE(
+ expression.parse("2.+.2") == OK,
+ "The expression should parse successfully.");
+ CHECK_MESSAGE(
+ double(expression.execute()) == doctest::Approx(2.2),
+ "The expression should return the expected result.");
+
+ CHECK_MESSAGE(
+ expression.parse(".0*0.") == OK,
+ "The expression should parse successfully.");
+ CHECK_MESSAGE(
+ double(expression.execute()) == doctest::Approx(0.0),
+ "The expression should return the expected result.");
+}
+
TEST_CASE("[Expression] Scientific notation") {
Expression expression;
diff --git a/tests/scene/test_path_follow_3d.h b/tests/scene/test_path_follow_3d.h
index d08af3a70c..6a384bec2b 100644
--- a/tests/scene/test_path_follow_3d.h
+++ b/tests/scene/test_path_follow_3d.h
@@ -60,39 +60,30 @@ TEST_CASE("[SceneTree][PathFollow3D] Sampling with progress ratio") {
SceneTree::get_singleton()->get_root()->add_child(path);
path_follow_3d->set_progress_ratio(0);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(0, 0, 0), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress_ratio(0.125);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(50, 0, 0), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress_ratio(0.25);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(100, 0, 0), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress_ratio(0.375);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(100, 50, 0), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress_ratio(0.5);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(100, 100, 0), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress_ratio(0.625);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(100, 100, 50), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress_ratio(0.75);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(100, 100, 100), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress_ratio(0.875);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(100, 50, 100), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress_ratio(1);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(100, 0, 100), path_follow_3d->get_transform().get_origin()));
memdelete(path);
@@ -113,39 +104,30 @@ TEST_CASE("[SceneTree][PathFollow3D] Sampling with progress") {
SceneTree::get_singleton()->get_root()->add_child(path);
path_follow_3d->set_progress(0);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(0, 0, 0), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress(50);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(50, 0, 0), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress(100);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(100, 0, 0), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress(150);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(100, 50, 0), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress(200);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(100, 100, 0), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress(250);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(100, 100, 50), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress(300);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(100, 100, 100), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress(350);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(100, 50, 100), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress(400);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(100, 0, 100), path_follow_3d->get_transform().get_origin()));
memdelete(path);
@@ -163,13 +145,11 @@ TEST_CASE("[SceneTree][PathFollow3D] Removal of a point in curve") {
SceneTree::get_singleton()->get_root()->add_child(path);
path_follow_3d->set_progress_ratio(0.5);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(100, 0, 0), path_follow_3d->get_transform().get_origin()));
curve->remove_point(1);
path_follow_3d->set_progress_ratio(0.5);
- path_follow_3d->update_transform(true);
CHECK_MESSAGE(
is_equal_approx(Vector3(50, 50, 0), path_follow_3d->get_transform().get_origin()),
"Path follow's position should be updated after removing a point from the curve");
@@ -270,47 +250,36 @@ TEST_CASE("[SceneTree][PathFollow3D] Calculate forward vector") {
path_follow_3d->set_rotation_mode(PathFollow3D::RotationMode::ROTATION_ORIENTED);
path_follow_3d->set_progress(-50);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(-1, 0, 0), path_follow_3d->get_transform().get_basis().get_column(2)));
path_follow_3d->set_progress(0);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(-1, 0, 0), path_follow_3d->get_transform().get_basis().get_column(2)));
path_follow_3d->set_progress(50);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(-1, 0, 0), path_follow_3d->get_transform().get_basis().get_column(2)));
path_follow_3d->set_progress(100);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(-1, 0, 0), path_follow_3d->get_transform().get_basis().get_column(2)));
path_follow_3d->set_progress(100 + dist_cube_100 / 2);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(-0.577348, -0.577348, 0.577348), path_follow_3d->get_transform().get_basis().get_column(2)));
path_follow_3d->set_progress(100 + dist_cube_100 - 0.01);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(-0.577348, -0.577348, 0.577348), path_follow_3d->get_transform().get_basis().get_column(2)));
path_follow_3d->set_progress(250 + dist_cube_100);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(0, 0, -1), path_follow_3d->get_transform().get_basis().get_column(2)));
path_follow_3d->set_progress(400 + dist_cube_100 - 0.01);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(0, 0, -1), path_follow_3d->get_transform().get_basis().get_column(2)));
path_follow_3d->set_progress(400 + 1.5 * dist_cube_100);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(0.577348, 0.577348, 0.577348), path_follow_3d->get_transform().get_basis().get_column(2)));
path_follow_3d->set_progress(400 + 2 * dist_cube_100 - 0.01);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(0.577348, 0.577348, 0.577348), path_follow_3d->get_transform().get_basis().get_column(2)));
path_follow_3d->set_progress(500 + 2 * dist_cube_100);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(1, 0, 0), path_follow_3d->get_transform().get_basis().get_column(2)));
memdelete(path);