diff options
70 files changed, 2062 insertions, 1337 deletions
| diff --git a/SConstruct b/SConstruct index b8063589c6..a27e5490a5 100644 --- a/SConstruct +++ b/SConstruct @@ -179,9 +179,11 @@ opts.Add(BoolVariable("use_volk", "Use the volk library to load the Vulkan loade  # Advanced options  opts.Add(BoolVariable("dev", "If yes, alias for verbose=yes warnings=extra werror=yes", False)) -opts.Add(BoolVariable("progress", "Show a progress indicator during compilation", True))  opts.Add(BoolVariable("tests", "Build the unit tests", False)) +opts.Add(BoolVariable("fast_unsafe", "Enable unsafe options for faster rebuilds", False)) +opts.Add(BoolVariable("compiledb", "Generate compilation DB (`compile_commands.json`) for external tools", False))  opts.Add(BoolVariable("verbose", "Enable verbose output for the compilation", False)) +opts.Add(BoolVariable("progress", "Show a progress indicator during compilation", True))  opts.Add(EnumVariable("warnings", "Level of compilation warnings", "all", ("extra", "all", "moderate", "no")))  opts.Add(BoolVariable("werror", "Treat compiler warnings as errors", False))  opts.Add("extra_suffix", "Custom extra suffix added to the base filename of all generated binary files", "") @@ -360,6 +362,17 @@ if env_base["target"] == "debug":      # working on the engine itself.      env_base.Append(CPPDEFINES=["DEV_ENABLED"]) +# SCons speed optimization controlled by the `fast_unsafe` option, which provide +# more than 10 s speed up for incremental rebuilds. +# Unsafe as they reduce the certainty of rebuilding all changed files, so it's +# enabled by default for `debug` builds, and can be overridden from command line. +# Ref: https://github.com/SCons/scons/wiki/GoFastButton +if methods.get_cmdline_bool("fast_unsafe", env_base["target"] == "debug"): +    # Renamed to `content-timestamp` in SCons >= 4.2, keeping MD5 for compat. +    env_base.Decider("MD5-timestamp") +    env_base.SetOption("implicit_cache", 1) +    env_base.SetOption("max_drift", 60) +  if env_base["use_precise_math_checks"]:      env_base.Append(CPPDEFINES=["PRECISE_MATH_CHECKS"]) @@ -385,14 +398,15 @@ if selected_platform in platform_list:      else:          env = env_base.Clone() -    # Generating the compilation DB (`compile_commands.json`) requires SCons 4.0.0 or later. -    from SCons import __version__ as scons_raw_version +    if env["compiledb"]: +        # Generating the compilation DB (`compile_commands.json`) requires SCons 4.0.0 or later. +        from SCons import __version__ as scons_raw_version -    scons_ver = env._get_major_minor_revision(scons_raw_version) +        scons_ver = env._get_major_minor_revision(scons_raw_version) -    if scons_ver >= (4, 0, 0): -        env.Tool("compilation_db") -        env.Alias("compiledb", env.CompilationDatabase()) +        if scons_ver >= (4, 0, 0): +            env.Tool("compilation_db") +            env.Alias("compiledb", env.CompilationDatabase())      # 'dev' and 'production' are aliases to set default options if they haven't been set      # manually by the user. @@ -817,6 +831,7 @@ elif selected_platform != "":  # The following only makes sense when the 'env' is defined, and assumes it is.  if "env" in locals(): +    # 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. diff --git a/core/SCsub b/core/SCsub index c12dd4e60e..1379e9df9b 100644 --- a/core/SCsub +++ b/core/SCsub @@ -147,6 +147,7 @@ env.core_sources += thirdparty_obj  env.add_source_files(env.core_sources, "*.cpp")  env.add_source_files(env.core_sources, "script_encryption_key.gen.cpp") +env.add_source_files(env.core_sources, "version_hash.gen.cpp")  # Certificates  env.Depends( diff --git a/core/config/engine.cpp b/core/config/engine.cpp index d9abf5e5e9..ff8a8d283f 100644 --- a/core/config/engine.cpp +++ b/core/config/engine.cpp @@ -35,7 +35,6 @@  #include "core/donors.gen.h"  #include "core/license.gen.h"  #include "core/version.h" -#include "core/version_hash.gen.h"  void Engine::set_physics_ticks_per_second(int p_ips) {  	ERR_FAIL_COND_MSG(p_ips <= 0, "Engine iterations per second must be greater than 0."); @@ -95,8 +94,8 @@ Dictionary Engine::get_version_info() const {  	dict["build"] = VERSION_BUILD;  	dict["year"] = VERSION_YEAR; -	String hash = VERSION_HASH; -	dict["hash"] = hash.length() == 0 ? String("unknown") : hash; +	String hash = String(VERSION_HASH); +	dict["hash"] = hash.is_empty() ? String("unknown") : hash;  	String stringver = String(dict["major"]) + "." + String(dict["minor"]);  	if ((int)dict["patch"] != 0) { diff --git a/core/core_bind.cpp b/core/core_bind.cpp index 8d03f35617..195292f7e6 100644 --- a/core/core_bind.cpp +++ b/core/core_bind.cpp @@ -2376,21 +2376,18 @@ bool EngineDebugger::is_active() {  	return ::EngineDebugger::is_active();  } -void EngineDebugger::register_profiler(const StringName &p_name, const Callable &p_toggle, const Callable &p_add, const Callable &p_tick) { -	ERR_FAIL_COND_MSG(profilers.has(p_name) || has_profiler(p_name), "Profiler already registered: " + p_name); -	profilers.insert(p_name, ProfilerCallable(p_toggle, p_add, p_tick)); -	ProfilerCallable &p = profilers[p_name]; -	::EngineDebugger::Profiler profiler( -			&p, -			&EngineDebugger::call_toggle, -			&EngineDebugger::call_add, -			&EngineDebugger::call_tick); -	::EngineDebugger::register_profiler(p_name, profiler); +void EngineDebugger::register_profiler(const StringName &p_name, Ref<EngineProfiler> p_profiler) { +	ERR_FAIL_COND(p_profiler.is_null()); +	ERR_FAIL_COND_MSG(p_profiler->is_bound(), "Profiler already registered."); +	ERR_FAIL_COND_MSG(profilers.has(p_name) || has_profiler(p_name), "Profiler name already in use: " + p_name); +	Error err = p_profiler->bind(p_name); +	ERR_FAIL_COND_MSG(err != OK, "Profiler failed to register with error: " + itos(err)); +	profilers.insert(p_name, p_profiler);  }  void EngineDebugger::unregister_profiler(const StringName &p_name) {  	ERR_FAIL_COND_MSG(!profilers.has(p_name), "Profiler not registered: " + p_name); -	::EngineDebugger::unregister_profiler(p_name); +	profilers[p_name]->unbind();  	profilers.erase(p_name);  } @@ -2435,45 +2432,6 @@ void EngineDebugger::send_message(const String &p_msg, const Array &p_data) {  	::EngineDebugger::get_singleton()->send_message(p_msg, p_data);  } -void EngineDebugger::call_toggle(void *p_user, bool p_enable, const Array &p_opts) { -	Callable &toggle = ((ProfilerCallable *)p_user)->callable_toggle; -	if (toggle.is_null()) { -		return; -	} -	Variant enable = p_enable, opts = p_opts; -	const Variant *args[2] = { &enable, &opts }; -	Variant retval; -	Callable::CallError err; -	toggle.call(args, 2, retval, err); -	ERR_FAIL_COND_MSG(err.error != Callable::CallError::CALL_OK, "Error calling 'toggle' to callable: " + Variant::get_callable_error_text(toggle, args, 2, err)); -} - -void EngineDebugger::call_add(void *p_user, const Array &p_data) { -	Callable &add = ((ProfilerCallable *)p_user)->callable_add; -	if (add.is_null()) { -		return; -	} -	Variant data = p_data; -	const Variant *args[1] = { &data }; -	Variant retval; -	Callable::CallError err; -	add.call(args, 1, retval, err); -	ERR_FAIL_COND_MSG(err.error != Callable::CallError::CALL_OK, "Error calling 'add' to callable: " + Variant::get_callable_error_text(add, args, 1, err)); -} - -void EngineDebugger::call_tick(void *p_user, double p_frame_time, double p_idle_time, double p_physics_time, double p_physics_frame_time) { -	Callable &tick = ((ProfilerCallable *)p_user)->callable_tick; -	if (tick.is_null()) { -		return; -	} -	Variant frame_time = p_frame_time, idle_time = p_idle_time, physics_time = p_physics_time, physics_frame_time = p_physics_frame_time; -	const Variant *args[4] = { &frame_time, &idle_time, &physics_time, &physics_frame_time }; -	Variant retval; -	Callable::CallError err; -	tick.call(args, 4, retval, err); -	ERR_FAIL_COND_MSG(err.error != Callable::CallError::CALL_OK, "Error calling 'tick' to callable: " + Variant::get_callable_error_text(tick, args, 4, err)); -} -  Error EngineDebugger::call_capture(void *p_user, const String &p_cmd, const Array &p_data, bool &r_captured) {  	Callable &capture = *(Callable *)p_user;  	if (capture.is_null()) { @@ -2495,10 +2453,6 @@ EngineDebugger::~EngineDebugger() {  		::EngineDebugger::unregister_message_capture(E.key);  	}  	captures.clear(); -	for (const KeyValue<StringName, ProfilerCallable> &E : profilers) { -		::EngineDebugger::unregister_profiler(E.key); -	} -	profilers.clear();  }  EngineDebugger *EngineDebugger::singleton = nullptr; @@ -2506,8 +2460,9 @@ EngineDebugger *EngineDebugger::singleton = nullptr;  void EngineDebugger::_bind_methods() {  	ClassDB::bind_method(D_METHOD("is_active"), &EngineDebugger::is_active); -	ClassDB::bind_method(D_METHOD("register_profiler", "name", "toggle", "add", "tick"), &EngineDebugger::register_profiler); +	ClassDB::bind_method(D_METHOD("register_profiler", "name", "profiler"), &EngineDebugger::register_profiler);  	ClassDB::bind_method(D_METHOD("unregister_profiler", "name"), &EngineDebugger::unregister_profiler); +  	ClassDB::bind_method(D_METHOD("is_profiling", "name"), &EngineDebugger::is_profiling);  	ClassDB::bind_method(D_METHOD("has_profiler", "name"), &EngineDebugger::has_profiler); diff --git a/core/core_bind.h b/core/core_bind.h index ac0e92a87a..21a1fc2077 100644 --- a/core/core_bind.h +++ b/core/core_bind.h @@ -31,6 +31,7 @@  #ifndef CORE_BIND_H  #define CORE_BIND_H +#include "core/debugger/engine_profiler.h"  #include "core/io/compression.h"  #include "core/io/dir_access.h"  #include "core/io/file_access.h" @@ -673,25 +674,8 @@ public:  class EngineDebugger : public Object {  	GDCLASS(EngineDebugger, Object); -	class ProfilerCallable { -		friend class EngineDebugger; - -		Callable callable_toggle; -		Callable callable_add; -		Callable callable_tick; - -	public: -		ProfilerCallable() {} - -		ProfilerCallable(const Callable &p_toggle, const Callable &p_add, const Callable &p_tick) { -			callable_toggle = p_toggle; -			callable_add = p_add; -			callable_tick = p_tick; -		} -	}; -  	Map<StringName, Callable> captures; -	Map<StringName, ProfilerCallable> profilers; +	Map<StringName, Ref<EngineProfiler>> profilers;  protected:  	static void _bind_methods(); @@ -702,7 +686,7 @@ public:  	bool is_active(); -	void register_profiler(const StringName &p_name, const Callable &p_toggle, const Callable &p_add, const Callable &p_tick); +	void register_profiler(const StringName &p_name, Ref<EngineProfiler> p_profiler);  	void unregister_profiler(const StringName &p_name);  	bool is_profiling(const StringName &p_name);  	bool has_profiler(const StringName &p_name); @@ -715,9 +699,6 @@ public:  	void send_message(const String &p_msg, const Array &p_data); -	static void call_toggle(void *p_user, bool p_enable, const Array &p_opts); -	static void call_add(void *p_user, const Array &p_data); -	static void call_tick(void *p_user, double p_frame_time, double p_idle_time, double p_physics_time, double p_physics_frame_time);  	static Error call_capture(void *p_user, const String &p_cmd, const Array &p_data, bool &r_captured);  	EngineDebugger() { singleton = this; } diff --git a/core/debugger/debugger_marshalls.cpp b/core/debugger/debugger_marshalls.cpp index 1a746d59a3..4c69290c2e 100644 --- a/core/debugger/debugger_marshalls.cpp +++ b/core/debugger/debugger_marshalls.cpp @@ -35,159 +35,6 @@  #define CHECK_SIZE(arr, expected, what) ERR_FAIL_COND_V_MSG((uint32_t)arr.size() < (uint32_t)(expected), false, String("Malformed ") + what + " message from script debugger, message too short. Expected size: " + itos(expected) + ", actual size: " + itos(arr.size()))  #define CHECK_END(arr, expected, what) ERR_FAIL_COND_V_MSG((uint32_t)arr.size() > (uint32_t)expected, false, String("Malformed ") + what + " message from script debugger, message too long. Expected size: " + itos(expected) + ", actual size: " + itos(arr.size())) -Array DebuggerMarshalls::ResourceUsage::serialize() { -	infos.sort(); - -	Array arr; -	arr.push_back(infos.size() * 4); -	for (const ResourceInfo &E : infos) { -		arr.push_back(E.path); -		arr.push_back(E.format); -		arr.push_back(E.type); -		arr.push_back(E.vram); -	} -	return arr; -} - -bool DebuggerMarshalls::ResourceUsage::deserialize(const Array &p_arr) { -	CHECK_SIZE(p_arr, 1, "ResourceUsage"); -	uint32_t size = p_arr[0]; -	CHECK_SIZE(p_arr, size, "ResourceUsage"); -	int idx = 1; -	for (uint32_t i = 0; i < size / 4; i++) { -		ResourceInfo info; -		info.path = p_arr[idx]; -		info.format = p_arr[idx + 1]; -		info.type = p_arr[idx + 2]; -		info.vram = p_arr[idx + 3]; -		infos.push_back(info); -	} -	CHECK_END(p_arr, idx, "ResourceUsage"); -	return true; -} - -Array DebuggerMarshalls::ScriptFunctionSignature::serialize() { -	Array arr; -	arr.push_back(name); -	arr.push_back(id); -	return arr; -} - -bool DebuggerMarshalls::ScriptFunctionSignature::deserialize(const Array &p_arr) { -	CHECK_SIZE(p_arr, 2, "ScriptFunctionSignature"); -	name = p_arr[0]; -	id = p_arr[1]; -	CHECK_END(p_arr, 2, "ScriptFunctionSignature"); -	return true; -} - -Array DebuggerMarshalls::NetworkProfilerFrame::serialize() { -	Array arr; -	arr.push_back(infos.size() * 6); -	for (int i = 0; i < infos.size(); ++i) { -		arr.push_back(uint64_t(infos[i].node)); -		arr.push_back(infos[i].node_path); -		arr.push_back(infos[i].incoming_rpc); -		arr.push_back(infos[i].incoming_rset); -		arr.push_back(infos[i].outgoing_rpc); -		arr.push_back(infos[i].outgoing_rset); -	} -	return arr; -} - -bool DebuggerMarshalls::NetworkProfilerFrame::deserialize(const Array &p_arr) { -	CHECK_SIZE(p_arr, 1, "NetworkProfilerFrame"); -	uint32_t size = p_arr[0]; -	CHECK_SIZE(p_arr, size, "NetworkProfilerFrame"); -	infos.resize(size); -	int idx = 1; -	for (uint32_t i = 0; i < size / 6; ++i) { -		infos.write[i].node = uint64_t(p_arr[idx]); -		infos.write[i].node_path = p_arr[idx + 1]; -		infos.write[i].incoming_rpc = p_arr[idx + 2]; -		infos.write[i].incoming_rset = p_arr[idx + 3]; -		infos.write[i].outgoing_rpc = p_arr[idx + 4]; -		infos.write[i].outgoing_rset = p_arr[idx + 5]; -	} -	CHECK_END(p_arr, idx, "NetworkProfilerFrame"); -	return true; -} - -Array DebuggerMarshalls::ServersProfilerFrame::serialize() { -	Array arr; -	arr.push_back(frame_number); -	arr.push_back(frame_time); -	arr.push_back(idle_time); -	arr.push_back(physics_time); -	arr.push_back(physics_frame_time); -	arr.push_back(script_time); - -	arr.push_back(servers.size()); -	for (int i = 0; i < servers.size(); i++) { -		ServerInfo &s = servers[i]; -		arr.push_back(s.name); -		arr.push_back(s.functions.size() * 2); -		for (int j = 0; j < s.functions.size(); j++) { -			ServerFunctionInfo &f = s.functions[j]; -			arr.push_back(f.name); -			arr.push_back(f.time); -		} -	} - -	arr.push_back(script_functions.size() * 4); -	for (int i = 0; i < script_functions.size(); i++) { -		arr.push_back(script_functions[i].sig_id); -		arr.push_back(script_functions[i].call_count); -		arr.push_back(script_functions[i].self_time); -		arr.push_back(script_functions[i].total_time); -	} -	return arr; -} - -bool DebuggerMarshalls::ServersProfilerFrame::deserialize(const Array &p_arr) { -	CHECK_SIZE(p_arr, 7, "ServersProfilerFrame"); -	frame_number = p_arr[0]; -	frame_time = p_arr[1]; -	idle_time = p_arr[2]; -	physics_time = p_arr[3]; -	physics_frame_time = p_arr[4]; -	script_time = p_arr[5]; -	int servers_size = p_arr[6]; -	int idx = 7; -	while (servers_size) { -		CHECK_SIZE(p_arr, idx + 2, "ServersProfilerFrame"); -		servers_size--; -		ServerInfo si; -		si.name = p_arr[idx]; -		int sub_data_size = p_arr[idx + 1]; -		idx += 2; -		CHECK_SIZE(p_arr, idx + sub_data_size, "ServersProfilerFrame"); -		for (int j = 0; j < sub_data_size / 2; j++) { -			ServerFunctionInfo sf; -			sf.name = p_arr[idx]; -			sf.time = p_arr[idx + 1]; -			idx += 2; -			si.functions.push_back(sf); -		} -		servers.push_back(si); -	} -	CHECK_SIZE(p_arr, idx + 1, "ServersProfilerFrame"); -	int func_size = p_arr[idx]; -	idx += 1; -	CHECK_SIZE(p_arr, idx + func_size, "ServersProfilerFrame"); -	for (int i = 0; i < func_size / 4; i++) { -		ScriptFunctionInfo fi; -		fi.sig_id = p_arr[idx]; -		fi.call_count = p_arr[idx + 1]; -		fi.self_time = p_arr[idx + 2]; -		fi.total_time = p_arr[idx + 3]; -		script_functions.push_back(fi); -		idx += 4; -	} -	CHECK_END(p_arr, idx, "ServersProfilerFrame"); -	return true; -} -  Array DebuggerMarshalls::ScriptStackDump::serialize() {  	Array arr;  	arr.push_back(frames.size() * 3); @@ -298,33 +145,3 @@ bool DebuggerMarshalls::OutputError::deserialize(const Array &p_arr) {  	CHECK_END(p_arr, idx, "OutputError");  	return true;  } - -Array DebuggerMarshalls::VisualProfilerFrame::serialize() { -	Array arr; -	arr.push_back(frame_number); -	arr.push_back(areas.size() * 3); -	for (int i = 0; i < areas.size(); i++) { -		arr.push_back(areas[i].name); -		arr.push_back(areas[i].cpu_msec); -		arr.push_back(areas[i].gpu_msec); -	} -	return arr; -} - -bool DebuggerMarshalls::VisualProfilerFrame::deserialize(const Array &p_arr) { -	CHECK_SIZE(p_arr, 2, "VisualProfilerFrame"); -	frame_number = p_arr[0]; -	int size = p_arr[1]; -	CHECK_SIZE(p_arr, size, "VisualProfilerFrame"); -	int idx = 2; -	areas.resize(size / 3); -	RS::FrameProfileArea *w = areas.ptrw(); -	for (int i = 0; i < size / 3; i++) { -		w[i].name = p_arr[idx]; -		w[i].cpu_msec = p_arr[idx + 1]; -		w[i].gpu_msec = p_arr[idx + 2]; -		idx += 3; -	} -	CHECK_END(p_arr, idx, "VisualProfilerFrame"); -	return true; -} diff --git a/core/debugger/debugger_marshalls.h b/core/debugger/debugger_marshalls.h index fae766812a..378c3af8aa 100644 --- a/core/debugger/debugger_marshalls.h +++ b/core/debugger/debugger_marshalls.h @@ -32,86 +32,8 @@  #define DEBUGGER_MARSHARLLS_H  #include "core/object/script_language.h" -#include "servers/rendering_server.h"  struct DebuggerMarshalls { -	// Memory usage -	struct ResourceInfo { -		String path; -		String format; -		String type; -		RID id; -		int vram = 0; -		bool operator<(const ResourceInfo &p_img) const { return vram == p_img.vram ? id < p_img.id : vram > p_img.vram; } -	}; - -	struct ResourceUsage { -		List<ResourceInfo> infos; - -		Array serialize(); -		bool deserialize(const Array &p_arr); -	}; - -	// Network profiler -	struct MultiplayerNodeInfo { -		ObjectID node; -		String node_path; -		int incoming_rpc = 0; -		int incoming_rset = 0; -		int outgoing_rpc = 0; -		int outgoing_rset = 0; -	}; - -	struct NetworkProfilerFrame { -		Vector<MultiplayerNodeInfo> infos; - -		Array serialize(); -		bool deserialize(const Array &p_arr); -	}; - -	// Script Profiler -	class ScriptFunctionSignature { -	public: -		StringName name; -		int id = -1; - -		Array serialize(); -		bool deserialize(const Array &p_arr); -	}; - -	struct ScriptFunctionInfo { -		StringName name; -		int sig_id = -1; -		int call_count = 0; -		double self_time = 0; -		double total_time = 0; -	}; - -	// Servers profiler -	struct ServerFunctionInfo { -		StringName name; -		double time = 0; -	}; - -	struct ServerInfo { -		StringName name; -		List<ServerFunctionInfo> functions; -	}; - -	struct ServersProfilerFrame { -		int frame_number = 0; -		double frame_time = 0; -		double idle_time = 0; -		double physics_time = 0; -		double physics_frame_time = 0; -		double script_time = 0; -		List<ServerInfo> servers; -		Vector<ScriptFunctionInfo> script_functions; - -		Array serialize(); -		bool deserialize(const Array &p_arr); -	}; -  	struct ScriptStackVariable {  		String name;  		Variant value; @@ -145,15 +67,6 @@ struct DebuggerMarshalls {  		Array serialize();  		bool deserialize(const Array &p_arr);  	}; - -	// Visual Profiler -	struct VisualProfilerFrame { -		uint64_t frame_number = 0; -		Vector<RS::FrameProfileArea> areas; - -		Array serialize(); -		bool deserialize(const Array &p_arr); -	};  };  #endif // DEBUGGER_MARSHARLLS_H diff --git a/core/debugger/engine_profiler.cpp b/core/debugger/engine_profiler.cpp new file mode 100644 index 0000000000..c858b1febd --- /dev/null +++ b/core/debugger/engine_profiler.cpp @@ -0,0 +1,82 @@ +/*************************************************************************/ +/*  engine_profiler.cpp                                                  */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */ +/*                                                                       */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the       */ +/* "Software"), to deal in the Software without restriction, including   */ +/* without limitation the rights to use, copy, modify, merge, publish,   */ +/* distribute, sublicense, and/or sell copies of the Software, and to    */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions:                                             */ +/*                                                                       */ +/* The above copyright notice and this permission notice shall be        */ +/* included in all copies or substantial portions of the Software.       */ +/*                                                                       */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */ +/*************************************************************************/ + +#include "engine_profiler.h" + +#include "core/debugger/engine_debugger.h" + +void EngineProfiler::_bind_methods() { +	GDVIRTUAL_BIND(_toggle, "enable", "options"); +	GDVIRTUAL_BIND(_add_frame, "data"); +	GDVIRTUAL_BIND(_tick, "frame_time", "idle_time", "physics_time", "physics_frame_time"); +} + +void EngineProfiler::toggle(bool p_enable, const Array &p_array) { +	GDVIRTUAL_CALL(_toggle, p_enable, p_array); +} + +void EngineProfiler::add(const Array &p_data) { +	GDVIRTUAL_CALL(_add_frame, p_data); +} + +void EngineProfiler::tick(double p_frame_time, double p_idle_time, double p_physics_time, double p_physics_frame_time) { +	GDVIRTUAL_CALL(_tick, p_frame_time, p_idle_time, p_physics_time, p_physics_frame_time); +} + +Error EngineProfiler::bind(const String &p_name) { +	ERR_FAIL_COND_V(is_bound(), ERR_ALREADY_IN_USE); +	EngineDebugger::Profiler prof( +			this, +			[](void *p_user, bool p_enable, const Array &p_opts) { +				((EngineProfiler *)p_user)->toggle(p_enable, p_opts); +			}, +			[](void *p_user, const Array &p_data) { +				((EngineProfiler *)p_user)->add(p_data); +			}, +			[](void *p_user, double p_frame_time, double p_idle_time, double p_physics_time, double p_physics_frame_time) { +				((EngineProfiler *)p_user)->tick(p_frame_time, p_idle_time, p_physics_time, p_physics_frame_time); +			}); +	registration = p_name; +	EngineDebugger::register_profiler(p_name, prof); +	return OK; +} + +Error EngineProfiler::unbind() { +	ERR_FAIL_COND_V(!is_bound(), ERR_UNCONFIGURED); +	EngineDebugger::unregister_profiler(registration); +	registration.clear(); +	return OK; +} + +EngineProfiler::~EngineProfiler() { +	if (is_bound()) { +		unbind(); +	} +} diff --git a/core/debugger/engine_profiler.h b/core/debugger/engine_profiler.h new file mode 100644 index 0000000000..ade280a7bb --- /dev/null +++ b/core/debugger/engine_profiler.h @@ -0,0 +1,65 @@ +/*************************************************************************/ +/*  engine_profiler.h                                                    */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */ +/*                                                                       */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the       */ +/* "Software"), to deal in the Software without restriction, including   */ +/* without limitation the rights to use, copy, modify, merge, publish,   */ +/* distribute, sublicense, and/or sell copies of the Software, and to    */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions:                                             */ +/*                                                                       */ +/* The above copyright notice and this permission notice shall be        */ +/* included in all copies or substantial portions of the Software.       */ +/*                                                                       */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */ +/*************************************************************************/ + +#ifndef ENGINE_PROFILER_H +#define ENGINE_PROFILER_H + +#include "core/object/ref_counted.h" + +#include "core/object/gdvirtual.gen.inc" +#include "core/object/script_language.h" + +class EngineProfiler : public RefCounted { +	GDCLASS(EngineProfiler, RefCounted); + +private: +	String registration; + +protected: +	static void _bind_methods(); + +public: +	virtual void toggle(bool p_enable, const Array &p_opts); +	virtual void add(const Array &p_data); +	virtual void tick(double p_frame_time, double p_idle_time, double p_physics_time, double p_physics_frame_time); + +	Error bind(const String &p_name); +	Error unbind(); +	bool is_bound() const { return registration.length() > 0; } + +	GDVIRTUAL2(_toggle, bool, Array); +	GDVIRTUAL1(_add_frame, Array); +	GDVIRTUAL4(_tick, double, double, double, double); + +	EngineProfiler() {} +	virtual ~EngineProfiler(); +}; + +#endif // ENGINE_PROFILER_H diff --git a/core/debugger/remote_debugger.cpp b/core/debugger/remote_debugger.cpp index 339aa9b61f..2fce23d003 100644 --- a/core/debugger/remote_debugger.cpp +++ b/core/debugger/remote_debugger.cpp @@ -33,32 +33,13 @@  #include "core/config/project_settings.h"  #include "core/debugger/debugger_marshalls.h"  #include "core/debugger/engine_debugger.h" +#include "core/debugger/engine_profiler.h"  #include "core/debugger/script_debugger.h"  #include "core/input/input.h"  #include "core/object/script_language.h"  #include "core/os/os.h" -#include "scene/main/node.h" -#include "servers/display_server.h" - -template <typename T> -void RemoteDebugger::_bind_profiler(const String &p_name, T *p_prof) { -	EngineDebugger::Profiler prof( -			p_prof, -			[](void *p_user, bool p_enable, const Array &p_opts) { -				((T *)p_user)->toggle(p_enable, p_opts); -			}, -			[](void *p_user, const Array &p_data) { -				((T *)p_user)->add(p_data); -			}, -			[](void *p_user, double p_frame_time, double p_idle_time, double p_physics_time, double p_physics_frame_time) { -				((T *)p_user)->tick(p_frame_time, p_idle_time, p_physics_time, p_physics_frame_time); -			}); -	EngineDebugger::register_profiler(p_name, prof); -} -struct RemoteDebugger::NetworkProfiler { -public: -	typedef DebuggerMarshalls::MultiplayerNodeInfo NodeInfo; +class RemoteDebugger::MultiplayerProfiler : public EngineProfiler {  	struct BandwidthFrame {  		uint32_t timestamp;  		int packet_size; @@ -70,11 +51,6 @@ public:  	Vector<BandwidthFrame> bandwidth_out;  	uint64_t last_bandwidth_time = 0; -	Map<ObjectID, NodeInfo> multiplayer_node_data; -	uint64_t last_profile_time = 0; - -	NetworkProfiler() {} -  	int bandwidth_usage(const Vector<BandwidthFrame> &p_buffer, int p_pointer) {  		ERR_FAIL_COND_V(p_buffer.size() == 0, 0);  		int total_bandwidth = 0; @@ -96,22 +72,8 @@ public:  		return total_bandwidth;  	} -	void init_node(const ObjectID p_node) { -		if (multiplayer_node_data.has(p_node)) { -			return; -		} -		multiplayer_node_data.insert(p_node, DebuggerMarshalls::MultiplayerNodeInfo()); -		multiplayer_node_data[p_node].node = p_node; -		multiplayer_node_data[p_node].node_path = Object::cast_to<Node>(ObjectDB::get_instance(p_node))->get_path(); -		multiplayer_node_data[p_node].incoming_rpc = 0; -		multiplayer_node_data[p_node].incoming_rset = 0; -		multiplayer_node_data[p_node].outgoing_rpc = 0; -		multiplayer_node_data[p_node].outgoing_rset = 0; -	} - +public:  	void toggle(bool p_enable, const Array &p_opts) { -		multiplayer_node_data.clear(); -  		if (!p_enable) {  			bandwidth_in.clear();  			bandwidth_out.clear(); @@ -130,37 +92,18 @@ public:  	}  	void add(const Array &p_data) { -		ERR_FAIL_COND(p_data.size() < 1); -		const String type = p_data[0]; -		if (type == "node") { -			ERR_FAIL_COND(p_data.size() < 3); -			const ObjectID id = p_data[1]; -			const String what = p_data[2]; -			init_node(id); -			NodeInfo &info = multiplayer_node_data[id]; -			if (what == "rpc_in") { -				info.incoming_rpc++; -			} else if (what == "rpc_out") { -				info.outgoing_rpc++; -			} else if (what == "rset_in") { -				info.incoming_rset = 0; -			} else if (what == "rset_out") { -				info.outgoing_rset++; -			} -		} else if (type == "bandwidth") { -			ERR_FAIL_COND(p_data.size() < 4); -			const String inout = p_data[1]; -			int time = p_data[2]; -			int size = p_data[3]; -			if (inout == "in") { -				bandwidth_in.write[bandwidth_in_ptr].timestamp = time; -				bandwidth_in.write[bandwidth_in_ptr].packet_size = size; -				bandwidth_in_ptr = (bandwidth_in_ptr + 1) % bandwidth_in.size(); -			} else if (inout == "out") { -				bandwidth_out.write[bandwidth_out_ptr].timestamp = time; -				bandwidth_out.write[bandwidth_out_ptr].packet_size = size; -				bandwidth_out_ptr = (bandwidth_out_ptr + 1) % bandwidth_out.size(); -			} +		ERR_FAIL_COND(p_data.size() < 3); +		const String inout = p_data[0]; +		int time = p_data[1]; +		int size = p_data[2]; +		if (inout == "in") { +			bandwidth_in.write[bandwidth_in_ptr].timestamp = time; +			bandwidth_in.write[bandwidth_in_ptr].packet_size = size; +			bandwidth_in_ptr = (bandwidth_in_ptr + 1) % bandwidth_in.size(); +		} else if (inout == "out") { +			bandwidth_out.write[bandwidth_out_ptr].timestamp = time; +			bandwidth_out.write[bandwidth_out_ptr].packet_size = size; +			bandwidth_out_ptr = (bandwidth_out_ptr + 1) % bandwidth_out.size();  		}  	} @@ -174,208 +117,17 @@ public:  			Array arr;  			arr.push_back(incoming_bandwidth);  			arr.push_back(outgoing_bandwidth); -			EngineDebugger::get_singleton()->send_message("network:bandwidth", arr); -		} -		if (pt - last_profile_time > 100) { -			last_profile_time = pt; -			DebuggerMarshalls::NetworkProfilerFrame frame; -			for (const KeyValue<ObjectID, NodeInfo> &E : multiplayer_node_data) { -				frame.infos.push_back(E.value); -			} -			multiplayer_node_data.clear(); -			EngineDebugger::get_singleton()->send_message("network:profile_frame", frame.serialize()); -		} -	} -}; - -struct RemoteDebugger::ScriptsProfiler { -	typedef DebuggerMarshalls::ScriptFunctionSignature FunctionSignature; -	typedef DebuggerMarshalls::ScriptFunctionInfo FunctionInfo; -	struct ProfileInfoSort { -		bool operator()(ScriptLanguage::ProfilingInfo *A, ScriptLanguage::ProfilingInfo *B) const { -			return A->total_time < B->total_time; -		} -	}; -	Vector<ScriptLanguage::ProfilingInfo> info; -	Vector<ScriptLanguage::ProfilingInfo *> ptrs; -	Map<StringName, int> sig_map; -	int max_frame_functions = 16; - -	void toggle(bool p_enable, const Array &p_opts) { -		if (p_enable) { -			sig_map.clear(); -			for (int i = 0; i < ScriptServer::get_language_count(); i++) { -				ScriptServer::get_language(i)->profiling_start(); -			} -			if (p_opts.size() == 1 && p_opts[0].get_type() == Variant::INT) { -				max_frame_functions = MAX(0, int(p_opts[0])); -			} -		} else { -			for (int i = 0; i < ScriptServer::get_language_count(); i++) { -				ScriptServer::get_language(i)->profiling_stop(); -			} +			EngineDebugger::get_singleton()->send_message("multiplayer:bandwidth", arr);  		}  	} - -	void write_frame_data(Vector<FunctionInfo> &r_funcs, uint64_t &r_total, bool p_accumulated) { -		int ofs = 0; -		for (int i = 0; i < ScriptServer::get_language_count(); i++) { -			if (p_accumulated) { -				ofs += ScriptServer::get_language(i)->profiling_get_accumulated_data(&info.write[ofs], info.size() - ofs); -			} else { -				ofs += ScriptServer::get_language(i)->profiling_get_frame_data(&info.write[ofs], info.size() - ofs); -			} -		} - -		for (int i = 0; i < ofs; i++) { -			ptrs.write[i] = &info.write[i]; -		} - -		SortArray<ScriptLanguage::ProfilingInfo *, ProfileInfoSort> sa; -		sa.sort(ptrs.ptrw(), ofs); - -		int to_send = MIN(ofs, max_frame_functions); - -		// Check signatures first, and compute total time. -		r_total = 0; -		for (int i = 0; i < to_send; i++) { -			if (!sig_map.has(ptrs[i]->signature)) { -				int idx = sig_map.size(); -				FunctionSignature sig; -				sig.name = ptrs[i]->signature; -				sig.id = idx; -				EngineDebugger::get_singleton()->send_message("servers:function_signature", sig.serialize()); -				sig_map[ptrs[i]->signature] = idx; -			} -			r_total += ptrs[i]->self_time; -		} - -		// Send frame, script time, functions information then -		r_funcs.resize(to_send); - -		FunctionInfo *w = r_funcs.ptrw(); -		for (int i = 0; i < to_send; i++) { -			if (sig_map.has(ptrs[i]->signature)) { -				w[i].sig_id = sig_map[ptrs[i]->signature]; -			} -			w[i].call_count = ptrs[i]->call_count; -			w[i].total_time = ptrs[i]->total_time / 1000000.0; -			w[i].self_time = ptrs[i]->self_time / 1000000.0; -		} -	} - -	ScriptsProfiler() { -		info.resize(GLOBAL_GET("debug/settings/profiler/max_functions")); -		ptrs.resize(info.size()); -	}  }; -struct RemoteDebugger::ServersProfiler { -	bool skip_profile_frame = false; -	typedef DebuggerMarshalls::ServerInfo ServerInfo; -	typedef DebuggerMarshalls::ServerFunctionInfo ServerFunctionInfo; - -	Map<StringName, ServerInfo> server_data; -	ScriptsProfiler scripts_profiler; - -	double frame_time = 0; -	double idle_time = 0; -	double physics_time = 0; -	double physics_frame_time = 0; - -	void toggle(bool p_enable, const Array &p_opts) { -		skip_profile_frame = false; -		if (p_enable) { -			server_data.clear(); // Clear old profiling data. -		} else { -			_send_frame_data(true); // Send final frame. -		} -		scripts_profiler.toggle(p_enable, p_opts); -	} - -	void add(const Array &p_data) { -		String name = p_data[0]; -		if (!server_data.has(name)) { -			ServerInfo info; -			info.name = name; -			server_data[name] = info; -		} -		ServerInfo &srv = server_data[name]; - -		ServerFunctionInfo fi; -		fi.name = p_data[1]; -		fi.time = p_data[2]; -		srv.functions.push_back(fi); -	} - -	void tick(double p_frame_time, double p_idle_time, double p_physics_time, double p_physics_frame_time) { -		frame_time = p_frame_time; -		idle_time = p_idle_time; -		physics_time = p_physics_time; -		physics_frame_time = p_physics_frame_time; -		_send_frame_data(false); -	} - -	void _send_frame_data(bool p_final) { -		DebuggerMarshalls::ServersProfilerFrame frame; -		frame.frame_number = Engine::get_singleton()->get_process_frames(); -		frame.frame_time = frame_time; -		frame.idle_time = idle_time; -		frame.physics_time = physics_time; -		frame.physics_frame_time = physics_frame_time; -		Map<StringName, ServerInfo>::Element *E = server_data.front(); -		while (E) { -			if (!p_final) { -				frame.servers.push_back(E->get()); -			} -			E->get().functions.clear(); -			E = E->next(); -		} -		uint64_t time = 0; -		scripts_profiler.write_frame_data(frame.script_functions, time, p_final); -		frame.script_time = USEC_TO_SEC(time); -		if (skip_profile_frame) { -			skip_profile_frame = false; -			return; -		} -		if (p_final) { -			EngineDebugger::get_singleton()->send_message("servers:profile_total", frame.serialize()); -		} else { -			EngineDebugger::get_singleton()->send_message("servers:profile_frame", frame.serialize()); -		} -	} -}; - -struct RemoteDebugger::VisualProfiler { -	typedef DebuggerMarshalls::ServerInfo ServerInfo; -	typedef DebuggerMarshalls::ServerFunctionInfo ServerFunctionInfo; - -	Map<StringName, ServerInfo> server_data; - -	void toggle(bool p_enable, const Array &p_opts) { -		RS::get_singleton()->set_frame_profiling_enabled(p_enable); -	} - -	void add(const Array &p_data) {} - -	void tick(double p_frame_time, double p_idle_time, double p_physics_time, double p_physics_frame_time) { -		Vector<RS::FrameProfileArea> profile_areas = RS::get_singleton()->get_frame_profile(); -		DebuggerMarshalls::VisualProfilerFrame frame; -		if (!profile_areas.size()) { -			return; -		} - -		frame.frame_number = RS::get_singleton()->get_frame_profile_frame(); -		frame.areas.append_array(profile_areas); -		EngineDebugger::get_singleton()->send_message("visual:profile_frame", frame.serialize()); -	} -}; - -struct RemoteDebugger::PerformanceProfiler { +class RemoteDebugger::PerformanceProfiler : public EngineProfiler {  	Object *performance = nullptr;  	int last_perf_time = 0;  	uint64_t last_monitor_modification_time = 0; +public:  	void toggle(bool p_enable, const Array &p_opts) {}  	void add(const Array &p_data) {}  	void tick(double p_frame_time, double p_idle_time, double p_physics_time, double p_physics_frame_time) { @@ -421,29 +173,6 @@ struct RemoteDebugger::PerformanceProfiler {  	}  }; -void RemoteDebugger::_send_resource_usage() { -	DebuggerMarshalls::ResourceUsage usage; - -	List<RS::TextureInfo> tinfo; -	RS::get_singleton()->texture_debug_usage(&tinfo); - -	for (const RS::TextureInfo &E : tinfo) { -		DebuggerMarshalls::ResourceInfo info; -		info.path = E.path; -		info.vram = E.bytes; -		info.id = E.texture; -		info.type = "Texture"; -		if (E.depth == 0) { -			info.format = itos(E.width) + "x" + itos(E.height) + " " + Image::get_format_name(E.format); -		} else { -			info.format = itos(E.width) + "x" + itos(E.height) + "x" + itos(E.depth) + " " + Image::get_format_name(E.format); -		} -		usage.infos.push_back(info); -	} - -	EngineDebugger::get_singleton()->send_message("memory:usage", usage.serialize()); -} -  Error RemoteDebugger::_put_msg(String p_message, Array p_data) {  	Array msg;  	msg.push_back(p_message); @@ -710,18 +439,12 @@ void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) {  	msg.push_back(script_lang->debug_get_stack_level_count() > 0);  	send_message("debug_enter", msg); -	servers_profiler->skip_profile_frame = true; // Avoid frame time spike in debug. -  	Input::MouseMode mouse_mode = Input::get_singleton()->get_mouse_mode();  	if (mouse_mode != Input::MOUSE_MODE_VISIBLE) {  		Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE);  	} -	uint64_t loop_begin_usec = 0; -	uint64_t loop_time_sec = 0;  	while (is_peer_connected()) { -		loop_begin_usec = OS::get_singleton()->get_ticks_usec(); -  		flush_output();  		peer->poll(); @@ -748,7 +471,6 @@ void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) {  			} else if (command == "continue") {  				script_debugger->set_depth(-1);  				script_debugger->set_lines_left(-1); -				DisplayServer::get_singleton()->window_move_to_foreground();  				break;  			} else if (command == "break") { @@ -824,13 +546,6 @@ void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) {  			OS::get_singleton()->delay_usec(10000);  			OS::get_singleton()->process_and_drop_events();  		} - -		// This is for the camera override to stay live even when the game is paused from the editor -		loop_time_sec = (OS::get_singleton()->get_ticks_usec() - loop_begin_usec) / 1000000.0f; -		RenderingServer::get_singleton()->sync(); -		if (RenderingServer::get_singleton()->has_changed()) { -			RenderingServer::get_singleton()->draw(true, loop_time_sec * Engine::get_singleton()->get_time_scale()); -		}  	}  	send_message("debug_exit", Array()); @@ -897,8 +612,6 @@ Error RemoteDebugger::_core_capture(const String &p_cmd, const Array &p_data, bo  	} else if (p_cmd == "set_skip_breakpoints") {  		ERR_FAIL_COND_V(p_data.size() < 1, ERR_INVALID_DATA);  		script_debugger->set_skip_breakpoints(p_data[0]); -	} else if (p_cmd == "memory") { -		_send_resource_usage();  	} else if (p_cmd == "break") {  		script_debugger->debug(script_debugger->get_break_language());  	} else { @@ -928,23 +641,15 @@ RemoteDebugger::RemoteDebugger(Ref<RemoteDebuggerPeer> p_peer) {  	max_errors_per_second = GLOBAL_GET("network/limits/debugger/max_errors_per_second");  	max_warnings_per_second = GLOBAL_GET("network/limits/debugger/max_warnings_per_second"); -	// Network Profiler -	network_profiler = memnew(NetworkProfiler); -	_bind_profiler("network", network_profiler); - -	// Servers Profiler (audio/physics/...) -	servers_profiler = memnew(ServersProfiler); -	_bind_profiler("servers", servers_profiler); - -	// Visual Profiler (cpu/gpu times) -	visual_profiler = memnew(VisualProfiler); -	_bind_profiler("visual", visual_profiler); +	// Multiplayer Profiler +	multiplayer_profiler.instantiate(); +	multiplayer_profiler->bind("multiplayer");  	// Performance Profiler  	Object *perf = Engine::get_singleton()->get_singleton_object("Performance");  	if (perf) { -		performance_profiler = memnew(PerformanceProfiler(perf)); -		_bind_profiler("performance", performance_profiler); +		performance_profiler = Ref<PerformanceProfiler>(memnew(PerformanceProfiler(perf))); +		performance_profiler->bind("performance");  		profiler_enable("performance", true);  	} @@ -973,17 +678,4 @@ RemoteDebugger::RemoteDebugger(Ref<RemoteDebuggerPeer> p_peer) {  RemoteDebugger::~RemoteDebugger() {  	remove_print_handler(&phl);  	remove_error_handler(&eh); - -	EngineDebugger::get_singleton()->unregister_profiler("servers"); -	EngineDebugger::get_singleton()->unregister_profiler("network"); -	EngineDebugger::get_singleton()->unregister_profiler("visual"); -	if (EngineDebugger::has_profiler("performance")) { -		EngineDebugger::get_singleton()->unregister_profiler("performance"); -	} -	memdelete(servers_profiler); -	memdelete(network_profiler); -	memdelete(visual_profiler); -	if (performance_profiler) { -		memdelete(performance_profiler); -	}  } diff --git a/core/debugger/remote_debugger.h b/core/debugger/remote_debugger.h index bd64955c89..aada92da60 100644 --- a/core/debugger/remote_debugger.h +++ b/core/debugger/remote_debugger.h @@ -49,16 +49,11 @@ public:  private:  	typedef DebuggerMarshalls::OutputError ErrorMessage; -	struct NetworkProfiler; -	struct ServersProfiler; -	struct ScriptsProfiler; -	struct VisualProfiler; -	struct PerformanceProfiler; +	class MultiplayerProfiler; +	class PerformanceProfiler; -	NetworkProfiler *network_profiler = nullptr; -	ServersProfiler *servers_profiler = nullptr; -	VisualProfiler *visual_profiler = nullptr; -	PerformanceProfiler *performance_profiler = nullptr; +	Ref<MultiplayerProfiler> multiplayer_profiler; +	Ref<PerformanceProfiler> performance_profiler;  	Ref<RemoteDebuggerPeer> peer; @@ -97,7 +92,6 @@ private:  	bool is_peer_connected() { return peer->is_peer_connected(); }  	void flush_output(); -	void _send_resource_usage();  	void _send_stack_vars(List<String> &p_names, List<Variant> &p_vals, int p_type);  	Error _profiler_capture(const String &p_cmd, const Array &p_data, bool &r_captured); diff --git a/core/math/vector2.h b/core/math/vector2.h index 123e3dc7b6..92ac5257b0 100644 --- a/core/math/vector2.h +++ b/core/math/vector2.h @@ -60,10 +60,10 @@ struct _NO_DISCARD_ Vector2 {  	};  	_FORCE_INLINE_ real_t &operator[](int p_idx) { -		return p_idx ? y : x; +		return coord[p_idx];  	}  	_FORCE_INLINE_ const real_t &operator[](int p_idx) const { -		return p_idx ? y : x; +		return coord[p_idx];  	}  	_FORCE_INLINE_ void set_all(const real_t p_value) { diff --git a/core/math/vector2i.h b/core/math/vector2i.h index 707c8c9490..3f5f12d4dd 100644 --- a/core/math/vector2i.h +++ b/core/math/vector2i.h @@ -43,19 +43,25 @@ struct _NO_DISCARD_ Vector2i {  	};  	union { -		int32_t x = 0; -		int32_t width; -	}; -	union { -		int32_t y = 0; -		int32_t height; +		struct { +			union { +				int32_t x; +				int32_t width; +			}; +			union { +				int32_t y; +				int32_t height; +			}; +		}; + +		int32_t coord[2] = { 0 };  	};  	_FORCE_INLINE_ int32_t &operator[](int p_idx) { -		return p_idx ? y : x; +		return coord[p_idx];  	}  	_FORCE_INLINE_ const int32_t &operator[](int p_idx) const { -		return p_idx ? y : x; +		return coord[p_idx];  	}  	_FORCE_INLINE_ Vector2i::Axis min_axis_index() const { diff --git a/core/multiplayer/multiplayer_api.cpp b/core/multiplayer/multiplayer_api.cpp index c8cb333e2c..3533acd103 100644 --- a/core/multiplayer/multiplayer_api.cpp +++ b/core/multiplayer/multiplayer_api.cpp @@ -47,7 +47,6 @@ MultiplayerCacheInterface *(*MultiplayerAPI::create_default_cache_interface)(Mul  void MultiplayerAPI::profile_bandwidth(const String &p_inout, int p_size) {  	if (EngineDebugger::is_profiling("multiplayer")) {  		Array values; -		values.push_back("bandwidth");  		values.push_back(p_inout);  		values.push_back(OS::get_singleton()->get_ticks_msec());  		values.push_back(p_size); diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp index 388368d181..a4b6a589f3 100644 --- a/core/register_core_types.cpp +++ b/core/register_core_types.cpp @@ -37,6 +37,7 @@  #include "core/crypto/aes_context.h"  #include "core/crypto/crypto.h"  #include "core/crypto/hashing_context.h" +#include "core/debugger/engine_profiler.h"  #include "core/extension/native_extension.h"  #include "core/extension/native_extension_manager.h"  #include "core/input/input.h" @@ -237,6 +238,8 @@ void register_core_types() {  	GDREGISTER_VIRTUAL_CLASS(ResourceUID); +	GDREGISTER_CLASS(EngineProfiler); +  	resource_uid = memnew(ResourceUID);  	native_extension_manager = memnew(NativeExtensionManager); diff --git a/core/version.h b/core/version.h index c718d0f4d7..e22922fa66 100644 --- a/core/version.h +++ b/core/version.h @@ -68,4 +68,7 @@  // Example: "Godot v3.1.4.stable.official.mono"  #define VERSION_FULL_NAME "" VERSION_NAME " v" VERSION_FULL_BUILD +// Git commit hash, generated at build time in `core/version_hash.gen.cpp`. +extern const char *const VERSION_HASH; +  #endif // VERSION_H diff --git a/doc/classes/Color.xml b/doc/classes/Color.xml index 4e73d4d9d8..8fd01509ec 100644 --- a/doc/classes/Color.xml +++ b/doc/classes/Color.xml @@ -221,14 +221,15 @@  				Returns a new color from [code]rgba[/code], an HTML hexadecimal color string. [code]rgba[/code] is not case sensitive, and may be prefixed with a '#' character.  				[code]rgba[/code] must be a valid three-digit or six-digit hexadecimal color string, and may contain an alpha channel value. If [code]rgba[/code] does not contain an alpha channel value, an alpha channel value of 1.0 is applied.  				If [code]rgba[/code] is invalid a Color(0.0, 0.0, 0.0, 1.0) is returned. +				[b]Note:[/b] This method is not implemented in C#, but the same functionality is provided in the class constructor.  				[codeblocks]  				[gdscript]  				var green = Color.html("#00FF00FF") # set green to Color(0.0, 1.0, 0.0, 1.0)  				var blue = Color.html("#0000FF") # set blue to Color(0.0, 0.0, 1.0, 1.0)  				[/gdscript]  				[csharp] -				var green = Color.Html("#00FF00FF"); // set green to Color(0.0, 1.0, 0.0, 1.0) -				var blue = Color.Html("#0000FF"); // set blue to Color(0.0, 0.0, 1.0, 1.0) +				var green = new Color("#00FF00FF"); // set green to Color(0.0, 1.0, 0.0, 1.0) +				var blue = new Color("#0000FF"); // set blue to Color(0.0, 0.0, 1.0, 1.0)  				[/csharp]  				[/codeblocks]  			</description> diff --git a/doc/classes/EngineDebugger.xml b/doc/classes/EngineDebugger.xml index 861053b1c9..0e1bf99afc 100644 --- a/doc/classes/EngineDebugger.xml +++ b/doc/classes/EngineDebugger.xml @@ -65,14 +65,9 @@  		<method name="register_profiler">  			<return type="void" />  			<argument index="0" name="name" type="StringName" /> -			<argument index="1" name="toggle" type="Callable" /> -			<argument index="2" name="add" type="Callable" /> -			<argument index="3" name="tick" type="Callable" /> +			<argument index="1" name="profiler" type="EngineProfiler" />  			<description> -				Registers a profiler with the given [code]name[/code]. -				[code]toggle[/code] callable is called when the profiler is enabled/disabled. It must take an argument array as an argument. -				[code]add[/code] callable is called when data is added to profiler using [method EngineDebugger.profiler_add_frame_data]. It must take a data array as argument. -				[code]tick[/code] callable is called at every active profiler iteration. It must take frame time, idle time, physics time, and physics idle time as arguments. +				Registers a profiler with the given [code]name[/code]. See [EngineProfiler] for more informations.  			</description>  		</method>  		<method name="send_message"> diff --git a/doc/classes/EngineProfiler.xml b/doc/classes/EngineProfiler.xml new file mode 100644 index 0000000000..88780b1a41 --- /dev/null +++ b/doc/classes/EngineProfiler.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="EngineProfiler" inherits="RefCounted" version="4.0"> +	<brief_description> +		Base class for creating custom profilers. +	</brief_description> +	<description> +		This class can be used to implement custom profilers that are able to interact with the engine and editor debugger. +		See [EngineDebugger] and [EditorDebuggerPlugin] for more informations. +	</description> +	<tutorials> +	</tutorials> +	<methods> +		<method name="_add_frame" qualifiers="virtual"> +			<return type="void" /> +			<argument index="0" name="data" type="Array" /> +			<description> +				Called when data is added to profiler using [method EngineDebugger.profiler_add_frame_data]. +			</description> +		</method> +		<method name="_tick" qualifiers="virtual"> +			<return type="void" /> +			<argument index="0" name="frame_time" type="float" /> +			<argument index="1" name="idle_time" type="float" /> +			<argument index="2" name="physics_time" type="float" /> +			<argument index="3" name="physics_frame_time" type="float" /> +			<description> +				Called once every engine iteration when the profiler is active with informations about the current frame. +			</description> +		</method> +		<method name="_toggle" qualifiers="virtual"> +			<return type="void" /> +			<argument index="0" name="enable" type="bool" /> +			<argument index="1" name="options" type="Array" /> +			<description> +				Called when the profiler is enabled/disabled, along with a set of [code]options[/code]. +			</description> +		</method> +	</methods> +</class> diff --git a/doc/classes/Popup.xml b/doc/classes/Popup.xml index dc5dd47287..36bdd6e6e4 100644 --- a/doc/classes/Popup.xml +++ b/doc/classes/Popup.xml @@ -1,17 +1,17 @@  <?xml version="1.0" encoding="UTF-8" ?>  <class name="Popup" inherits="Window" version="4.0">  	<brief_description> -		Base container control for popups and dialogs. +		Popup is a base window container for popup-like subwindows.  	</brief_description>  	<description> -		Popup is a base [Control] used to show dialogs and popups. It's a subwindow and modal by default (see [Control]) and has helpers for custom popup behavior. +		Popup is a base window container for popup-like subwindows. It's a modal by default (see [member close_on_parent_focus]) and has helpers for custom popup behavior.  	</description>  	<tutorials>  	</tutorials>  	<members>  		<member name="borderless" type="bool" setter="set_flag" getter="get_flag" overrides="Window" default="true" />  		<member name="close_on_parent_focus" type="bool" setter="set_close_on_parent_focus" getter="get_close_on_parent_focus" default="true"> -			If [code]true[/code], the [Popup] will close when its parent is focused. +			If true, the [Popup] will close when its parent [Window] is focused.  		</member>  		<member name="transient" type="bool" setter="set_transient" getter="is_transient" overrides="Window" default="true" />  		<member name="unresizable" type="bool" setter="set_flag" getter="get_flag" overrides="Window" default="true" /> @@ -21,7 +21,7 @@  	<signals>  		<signal name="popup_hide">  			<description> -				Emitted when a popup is hidden. +				Emitted when the popup is hidden.  			</description>  		</signal>  	</signals> diff --git a/doc/classes/PopupMenu.xml b/doc/classes/PopupMenu.xml index b316f822f0..29a4cfcd46 100644 --- a/doc/classes/PopupMenu.xml +++ b/doc/classes/PopupMenu.xml @@ -4,7 +4,7 @@  		PopupMenu displays a list of options.  	</brief_description>  	<description> -		[PopupMenu] is a [Control] that displays a list of options. They are popular in toolbars or context menus. +		[PopupMenu] is a modal window used to display a list of options. They are popular in toolbars or context menus.  		The size of a [PopupMenu] can be limited by using [member Window.max_size]. If the height of the list of items is larger than the maximum height of the [PopupMenu], a [ScrollContainer] within the popup will allow the user to scroll the contents.  		If no maximum size is set, or if it is set to 0, the [PopupMenu] height will be limited by its parent rect.  	</description> diff --git a/doc/classes/Tree.xml b/doc/classes/Tree.xml index 4b051c4938..35a70ae53f 100644 --- a/doc/classes/Tree.xml +++ b/doc/classes/Tree.xml @@ -72,6 +72,13 @@  				[b]Note:[/b] Despite the name of this method, the focus cursor itself is only visible in [constant SELECT_MULTI] mode.  			</description>  		</method> +		<method name="get_button_id_at_position" qualifiers="const"> +			<return type="int" /> +			<argument index="0" name="position" type="Vector2" /> +			<description> +				Returns the button id at [code]position[/code], or -1 if no button is there. +			</description> +		</method>  		<method name="get_column_at_position" qualifiers="const">  			<return type="int" />  			<argument index="0" name="position" type="Vector2" /> diff --git a/doc/classes/TreeItem.xml b/doc/classes/TreeItem.xml index 12c91cdd10..675c534e7b 100644 --- a/doc/classes/TreeItem.xml +++ b/doc/classes/TreeItem.xml @@ -14,11 +14,11 @@  			<return type="void" />  			<argument index="0" name="column" type="int" />  			<argument index="1" name="button" type="Texture2D" /> -			<argument index="2" name="button_idx" type="int" default="-1" /> +			<argument index="2" name="id" type="int" default="-1" />  			<argument index="3" name="disabled" type="bool" default="false" />  			<argument index="4" name="tooltip" type="String" default="""" />  			<description> -				Adds a button with [Texture2D] [code]button[/code] at column [code]column[/code]. The [code]button_idx[/code] index is used to identify the button when calling other methods. If not specified, the next available index is used, which may be retrieved by calling [method get_button_count] immediately after this method. Optionally, the button can be [code]disabled[/code] and have a [code]tooltip[/code]. +				Adds a button with [Texture2D] [code]button[/code] at column [code]column[/code]. The [code]id[/code] is used to identify the button. If not specified, the next available index is used, which may be retrieved by calling [method get_button_count] immediately after this method. Optionally, the button can be [code]disabled[/code] and have a [code]tooltip[/code].  			</description>  		</method>  		<method name="call_recursive" qualifiers="vararg"> @@ -80,6 +80,14 @@  				Returns the [Texture2D] of the button at index [code]button_idx[/code] in column [code]column[/code].  			</description>  		</method> +		<method name="get_button_by_id" qualifiers="const"> +			<return type="int" /> +			<argument index="0" name="column" type="int" /> +			<argument index="1" name="id" type="int" /> +			<description> +				Returns the button index if there is a button with id [code]id[/code] in column [code]column[/code], otherwise returns -1. +			</description> +		</method>  		<method name="get_button_count" qualifiers="const">  			<return type="int" />  			<argument index="0" name="column" type="int" /> @@ -87,6 +95,14 @@  				Returns the number of buttons in column [code]column[/code]. May be used to get the most recently added button's index, if no index was specified.  			</description>  		</method> +		<method name="get_button_id" qualifiers="const"> +			<return type="int" /> +			<argument index="0" name="column" type="int" /> +			<argument index="1" name="button_idx" type="int" /> +			<description> +				Returns the id for the button at index [code]button_idx[/code] in column [code]column[/code]. +			</description> +		</method>  		<method name="get_button_tooltip" qualifiers="const">  			<return type="String" />  			<argument index="0" name="column" type="int" /> diff --git a/doc/classes/Window.xml b/doc/classes/Window.xml index ab8f51ced5..82bb74683f 100644 --- a/doc/classes/Window.xml +++ b/doc/classes/Window.xml @@ -435,7 +435,7 @@  		<constant name="CONTENT_SCALE_ASPECT_EXPAND" value="4" enum="ContentScaleAspect">  		</constant>  		<constant name="LAYOUT_DIRECTION_INHERITED" value="0" enum="LayoutDirection"> -			Automatic layout direction, determined from the parent control layout direction. +			Automatic layout direction, determined from the parent window layout direction.  		</constant>  		<constant name="LAYOUT_DIRECTION_LOCALE" value="1" enum="LayoutDirection">  			Automatic layout direction, determined from the current locale. diff --git a/drivers/vulkan/vulkan_context.cpp b/drivers/vulkan/vulkan_context.cpp index 44455b927b..689d76ba26 100644 --- a/drivers/vulkan/vulkan_context.cpp +++ b/drivers/vulkan/vulkan_context.cpp @@ -273,9 +273,9 @@ Error VulkanContext::_obtain_vulkan_version() {  		uint32_t api_version;  		VkResult res = func(&api_version);  		if (res == VK_SUCCESS) { -			vulkan_major = VK_VERSION_MAJOR(api_version); -			vulkan_minor = VK_VERSION_MINOR(api_version); -			vulkan_patch = VK_VERSION_PATCH(api_version); +			vulkan_major = VK_API_VERSION_MAJOR(api_version); +			vulkan_minor = VK_API_VERSION_MINOR(api_version); +			vulkan_patch = VK_API_VERSION_PATCH(api_version);  		} else {  			// according to the documentation this shouldn't fail with anything except a memory allocation error  			// in which case we're in deep trouble anyway diff --git a/editor/animation_bezier_editor.cpp b/editor/animation_bezier_editor.cpp index da376c588e..67cdba043a 100644 --- a/editor/animation_bezier_editor.cpp +++ b/editor/animation_bezier_editor.cpp @@ -35,6 +35,8 @@  #include "scene/gui/view_panner.h"  #include "scene/resources/text_line.h" +#include <limits.h> +  float AnimationBezierTrackEdit::_bezier_h_to_pixel(float p_h) {  	float h = p_h;  	h = (h - v_scroll) / v_zoom; @@ -55,15 +57,16 @@ static _FORCE_INLINE_ Vector2 _bezier_interp(real_t t, const Vector2 &start, con  void AnimationBezierTrackEdit::_draw_track(int p_track, const Color &p_color) {  	float scale = timeline->get_zoom_scale(); +  	int limit = timeline->get_name_limit(); -	int right_limit = get_size().width - timeline->get_buttons_width(); +	int right_limit = get_size().width;  	//selection may have altered the order of keys  	Map<float, int> key_order;  	for (int i = 0; i < animation->track_get_key_count(p_track); i++) {  		float ofs = animation->track_get_key_time(p_track, i); -		if (moving_selection && track == p_track && selection.has(i)) { +		if (moving_selection && selection.has(IntPair(p_track, i))) {  			ofs += moving_selection_offset.x;  		} @@ -82,11 +85,11 @@ void AnimationBezierTrackEdit::_draw_track(int p_track, const Color &p_color) {  		float offset = animation->track_get_key_time(p_track, i);  		float height = animation->bezier_track_get_key_value(p_track, i);  		Vector2 out_handle = animation->bezier_track_get_key_out_handle(p_track, i); -		if (track == p_track && moving_handle != 0 && moving_handle_key == i) { +		if (p_track == moving_handle_track && moving_handle != 0 && moving_handle_key == i) {  			out_handle = moving_handle_right;  		} -		if (moving_selection && track == p_track && selection.has(i)) { +		if (moving_selection && selection.has(IntPair(p_track, i))) {  			offset += moving_selection_offset.x;  			height += moving_selection_offset.y;  		} @@ -96,11 +99,11 @@ void AnimationBezierTrackEdit::_draw_track(int p_track, const Color &p_color) {  		float offset_n = animation->track_get_key_time(p_track, i_n);  		float height_n = animation->bezier_track_get_key_value(p_track, i_n);  		Vector2 in_handle = animation->bezier_track_get_key_in_handle(p_track, i_n); -		if (track == p_track && moving_handle != 0 && moving_handle_key == i_n) { +		if (p_track == moving_handle_track && moving_handle != 0 && moving_handle_key == i_n) {  			in_handle = moving_handle_left;  		} -		if (moving_selection && track == p_track && selection.has(i_n)) { +		if (moving_selection && selection.has(IntPair(p_track, i_n))) {  			offset_n += moving_selection_offset.x;  			height_n += moving_selection_offset.y;  		} @@ -221,20 +224,10 @@ void AnimationBezierTrackEdit::_notification(int p_what) {  		panner->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/animation_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EditorSettings::get_singleton()->get("editors/panning/simple_panning")));  	}  	if (p_what == NOTIFICATION_THEME_CHANGED || p_what == NOTIFICATION_ENTER_TREE) { -		close_button->set_icon(get_theme_icon(SNAME("Close"), SNAME("EditorIcons"))); -  		bezier_icon = get_theme_icon(SNAME("KeyBezierPoint"), SNAME("EditorIcons"));  		bezier_handle_icon = get_theme_icon(SNAME("KeyBezierHandle"), SNAME("EditorIcons"));  		selected_icon = get_theme_icon(SNAME("KeyBezierSelected"), SNAME("EditorIcons"));  	} -	if (p_what == NOTIFICATION_RESIZED) { -		int right_limit = get_size().width - timeline->get_buttons_width(); -		int hsep = get_theme_constant(SNAME("hseparation"), SNAME("ItemList")); -		int vsep = get_theme_constant(SNAME("vseparation"), SNAME("ItemList")); - -		right_column->set_position(Vector2(right_limit + hsep, vsep)); -		right_column->set_size(Vector2(timeline->get_buttons_width() - hsep * 2, get_size().y - vsep * 2)); -	}  	if (p_what == NOTIFICATION_DRAW) {  		if (animation.is_null()) {  			return; @@ -258,101 +251,191 @@ void AnimationBezierTrackEdit::_notification(int p_what) {  		draw_line(Point2(limit, 0), Point2(limit, get_size().height), linecolor, Math::round(EDSCALE)); -		int right_limit = get_size().width - timeline->get_buttons_width(); +		int right_limit = get_size().width; -		draw_line(Point2(right_limit, 0), Point2(right_limit, get_size().height), linecolor, Math::round(EDSCALE)); - -		String base_path = animation->track_get_path(track); -		int end = base_path.find(":"); -		if (end != -1) { -			base_path = base_path.substr(0, end + 1); -		} - -		// NAMES AND ICON  		int vofs = vsep;  		int margin = 0; -		{ -			NodePath path = animation->track_get_path(track); +		Map<int, Color> subtrack_colors; +		Color selected_track_color; +		subtracks.clear(); +		subtrack_icons.clear(); + +		Map<String, Vector<int>> track_indices; +		int track_count = animation->get_track_count(); +		for (int i = 0; i < track_count; ++i) { +			if (animation->track_get_type(i) != Animation::TrackType::TYPE_BEZIER) { +				continue; +			} -			Node *node = nullptr; +			String base_path = animation->track_get_path(i); +			if (is_filtered) { +				if (root && root->has_node(base_path)) { +					Node *node = root->get_node(base_path); +					if (!node) { +						continue; // No node, no filter. +					} +					if (!EditorNode::get_singleton()->get_editor_selection()->is_selected(node)) { +						continue; // Skip track due to not selected. +					} +				} +			} -			if (root && root->has_node(path)) { -				node = root->get_node(path); +			int end = base_path.find(":"); +			if (end != -1) { +				base_path = base_path.substr(0, end + 1);  			} +			Vector<int> indices = track_indices.has(base_path) ? track_indices[base_path] : Vector<int>(); +			indices.push_back(i); +			track_indices[base_path] = indices; +		} -			String text; +		for (const KeyValue<String, Vector<int>> &E : track_indices) { +			String base_path = E.key; -			if (node) { -				int ofs = 0; +			Vector<int> tracks = E.value; -				Ref<Texture2D> icon = EditorNode::get_singleton()->get_object_icon(node, "Node"); +			// NAMES AND ICON +			{ +				NodePath path = animation->track_get_path(tracks[0]); -				text = node->get_name(); -				ofs += hsep; -				ofs += icon->get_width(); +				Node *node = nullptr; -				TextLine text_buf = TextLine(text, font, font_size); -				text_buf.set_width(limit - ofs - hsep); +				if (root && root->has_node(path)) { +					node = root->get_node(path); +				} -				int h = MAX(text_buf.get_size().y, icon->get_height()); +				String text; -				draw_texture(icon, Point2(ofs, vofs + int(h - icon->get_height()) / 2)); +				if (node) { +					int ofs = 0; -				margin = icon->get_width(); +					Ref<Texture2D> icon = EditorNode::get_singleton()->get_object_icon(node, "Node"); -				Vector2 string_pos = Point2(ofs, vofs + (h - text_buf.get_size().y) / 2 + text_buf.get_line_ascent()); -				string_pos = string_pos.floor(); -				text_buf.draw(get_canvas_item(), string_pos, color); +					text = node->get_name(); +					ofs += hsep; -				vofs += h + vsep; -			} -		} +					TextLine text_buf = TextLine(text, font, font_size); +					text_buf.set_width(limit - ofs - icon->get_width() - hsep); -		// RELATED TRACKS TITLES +					int h = MAX(text_buf.get_size().y, icon->get_height()); -		Map<int, Color> subtrack_colors; -		subtracks.clear(); +					draw_texture(icon, Point2(ofs, vofs + int(h - icon->get_height()) / 2)); +					ofs += icon->get_width(); -		for (int i = 0; i < animation->get_track_count(); i++) { -			if (animation->track_get_type(i) != Animation::TYPE_BEZIER) { -				continue; -			} -			String path = animation->track_get_path(i); -			if (!path.begins_with(base_path)) { -				continue; //another node +					margin = icon->get_width(); + +					Vector2 string_pos = Point2(ofs, vofs); +					string_pos = string_pos.floor(); +					text_buf.draw(get_canvas_item(), string_pos, color); + +					vofs += h + vsep; +				}  			} -			path = path.replace_first(base_path, ""); -			Color cc = color; -			TextLine text_buf = TextLine(path, font, font_size); -			text_buf.set_width(limit - margin - hsep); +			Ref<Texture2D> remove = get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")); +			float remove_hpos = limit - hsep - remove->get_width(); + +			Ref<Texture2D> lock = get_theme_icon(SNAME("Lock"), SNAME("EditorIcons")); +			Ref<Texture2D> unlock = get_theme_icon(SNAME("Unlock"), SNAME("EditorIcons")); +			float lock_hpos = remove_hpos - hsep - lock->get_width(); + +			Ref<Texture2D> visible = get_theme_icon(SNAME("GuiVisibilityVisible"), SNAME("EditorIcons")); +			Ref<Texture2D> hidden = get_theme_icon(SNAME("GuiVisibilityHidden"), SNAME("EditorIcons")); +			float visibility_hpos = lock_hpos - hsep - visible->get_width(); + +			Ref<Texture2D> solo = get_theme_icon(SNAME("AudioBusSolo"), SNAME("EditorIcons")); +			float solo_hpos = visibility_hpos - hsep - solo->get_width(); + +			float buttons_width = remove->get_width() + lock->get_width() + visible->get_width() + solo->get_width() + hsep * 3; + +			for (int i = 0; i < tracks.size(); ++i) { +				// RELATED TRACKS TITLES + +				int current_track = tracks[i]; + +				String path = animation->track_get_path(current_track); +				path = path.replace_first(base_path, ""); + +				Color cc = color; +				TextLine text_buf = TextLine(path, font, font_size); +				text_buf.set_width(limit - margin - buttons_width); + +				Rect2 rect = Rect2(margin, vofs, solo_hpos - hsep - solo->get_width(), text_buf.get_size().y + vsep); -			Rect2 rect = Rect2(margin, vofs, limit - margin - hsep, text_buf.get_size().y + vsep); -			if (i != track) {  				cc.a *= 0.7; -				uint32_t hash = path.hash(); -				hash = ((hash >> 16) ^ hash) * 0x45d9f3b; -				hash = ((hash >> 16) ^ hash) * 0x45d9f3b; -				hash = (hash >> 16) ^ hash; -				float h = (hash % 65535) / 65536.0; -				Color subcolor; -				subcolor.set_hsv(h, 0.2, 0.8); -				subcolor.a = 0.5; -				draw_rect(Rect2(0, vofs + text_buf.get_size().y * 0.1, margin - hsep, text_buf.get_size().y * 0.8), subcolor); -				subtrack_colors[i] = subcolor; - -				subtracks[i] = rect; -			} else { -				Color ac = get_theme_color(SNAME("accent_color"), SNAME("Editor")); -				ac.a = 0.5; -				draw_rect(rect, ac); -			} +				float h; +				if (path.ends_with(":x")) { +					h = 0; +				} else if (path.ends_with(":y")) { +					h = 0.33f; +				} else if (path.ends_with(":z")) { +					h = 0.66f; +				} else { +					uint32_t hash = path.hash(); +					hash = ((hash >> 16) ^ hash) * 0x45d9f3b; +					hash = ((hash >> 16) ^ hash) * 0x45d9f3b; +					hash = (hash >> 16) ^ hash; +					h = (hash % 65535) / 65536.0; +				} + +				if (current_track != selected_track) { +					Color track_color; +					if (locked_tracks.has(current_track)) { +						track_color.set_hsv(h, 0, 0.4); +					} else { +						track_color.set_hsv(h, 0.2, 0.8); +					} +					track_color.a = 0.5; +					draw_rect(Rect2(0, vofs, margin - hsep, text_buf.get_size().y * 0.8), track_color); +					subtrack_colors[current_track] = track_color; + +					subtracks[current_track] = rect; +				} else { +					Color ac = get_theme_color(SNAME("accent_color"), SNAME("Editor")); +					ac.a = 0.5; +					draw_rect(rect, ac); +					if (locked_tracks.has(selected_track)) { +						selected_track_color.set_hsv(h, 0.0, 0.4); +					} else { +						selected_track_color.set_hsv(h, 0.8, 0.8); +					} +				} + +				Vector2 string_pos = Point2(margin, vofs); +				text_buf.draw(get_canvas_item(), string_pos, cc); + +				float icon_start_height = vofs + rect.size.y / 2; +				Rect2 remove_rect = Rect2(remove_hpos, icon_start_height - remove->get_height() / 2, remove->get_width(), remove->get_height()); +				draw_texture(remove, remove_rect.position); + +				Rect2 lock_rect = Rect2(lock_hpos, icon_start_height - lock->get_height() / 2, lock->get_width(), lock->get_height()); +				if (locked_tracks.has(current_track)) { +					draw_texture(lock, lock_rect.position); +				} else { +					draw_texture(unlock, lock_rect.position); +				} + +				Rect2 visible_rect = Rect2(visibility_hpos, icon_start_height - visible->get_height() / 2, visible->get_width(), visible->get_height()); +				if (hidden_tracks.has(current_track)) { +					draw_texture(hidden, visible_rect.position); +				} else { +					draw_texture(visible, visible_rect.position); +				} -			Vector2 string_pos = Point2(margin, vofs + text_buf.get_line_ascent()); -			text_buf.draw(get_canvas_item(), string_pos, cc); +				Rect2 solo_rect = Rect2(solo_hpos, icon_start_height - solo->get_height() / 2, solo->get_width(), solo->get_height()); +				draw_texture(solo, solo_rect.position); -			vofs += text_buf.get_size().y + vsep; +				Map<int, Rect2> track_icons; +				track_icons[REMOVE_ICON] = remove_rect; +				track_icons[LOCK_ICON] = lock_rect; +				track_icons[VISIBILITY_ICON] = visible_rect; +				track_icons[SOLO_ICON] = solo_rect; + +				subtrack_icons[current_track] = track_icons; + +				vofs += text_buf.get_size().y + vsep; +			}  		}  		Color accent = get_theme_color(SNAME("accent_color"), SNAME("Editor")); @@ -398,6 +481,9 @@ void AnimationBezierTrackEdit::_notification(int p_what) {  			float scale = timeline->get_zoom_scale();  			Ref<Texture2D> point = get_theme_icon(SNAME("KeyValue"), SNAME("EditorIcons"));  			for (const KeyValue<int, Color> &E : subtrack_colors) { +				if (hidden_tracks.has(E.key)) { +					continue; +				}  				_draw_track(E.key, E.value);  				for (int i = 0; i < animation->track_get_key_count(E.key); i++) { @@ -412,70 +498,116 @@ void AnimationBezierTrackEdit::_notification(int p_what) {  				}  			} -			//draw edited curve -			const Color highlight = get_theme_color(SNAME("highlight_color"), SNAME("Editor")); -			_draw_track(track, highlight); +			if (track_count > 0 && !hidden_tracks.has(selected_track)) { +				//draw edited curve +				_draw_track(selected_track, selected_track_color); +			}  		}  		//draw editor handles  		{  			edit_points.clear(); -  			float scale = timeline->get_zoom_scale(); -			for (int i = 0; i < animation->track_get_key_count(track); i++) { -				float offset = animation->track_get_key_time(track, i); -				float value = animation->bezier_track_get_key_value(track, i); -				if (moving_selection && selection.has(i)) { -					offset += moving_selection_offset.x; -					value += moving_selection_offset.y; +			for (int i = 0; i < track_count; ++i) { +				if (animation->track_get_type(i) != Animation::TrackType::TYPE_BEZIER || hidden_tracks.has(i)) { +					continue;  				} -				Vector2 pos((offset - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value)); - -				Vector2 in_vec = animation->bezier_track_get_key_in_handle(track, i); -				if (moving_handle != 0 && moving_handle_key == i) { -					in_vec = moving_handle_left; +				if (hidden_tracks.has(i) || locked_tracks.has(i)) { +					continue;  				} -				Vector2 pos_in(((offset + in_vec.x) - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value + in_vec.y)); -				Vector2 out_vec = animation->bezier_track_get_key_out_handle(track, i); +				int key_count = animation->track_get_key_count(i); +				String path = animation->track_get_path(i); -				if (moving_handle != 0 && moving_handle_key == i) { -					out_vec = moving_handle_right; +				if (is_filtered) { +					if (root && root->has_node(path)) { +						Node *node = root->get_node(path); +						if (!node) { +							continue; // No node, no filter. +						} +						if (!EditorNode::get_singleton()->get_editor_selection()->is_selected(node)) { +							continue; // Skip track due to not selected. +						} +					}  				} -				Vector2 pos_out(((offset + out_vec.x) - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value + out_vec.y)); +				for (int j = 0; j < key_count; ++j) { +					float offset = animation->track_get_key_time(i, j); +					float value = animation->bezier_track_get_key_value(i, j); + +					if (moving_selection && selection.has(IntPair(i, j))) { +						offset += moving_selection_offset.x; +						value += moving_selection_offset.y; +					} -				_draw_line_clipped(pos, pos_in, accent, limit, right_limit); -				_draw_line_clipped(pos, pos_out, accent, limit, right_limit); +					Vector2 pos((offset - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value)); -				EditPoint ep; -				if (pos.x >= limit && pos.x <= right_limit) { -					ep.point_rect.position = (pos - bezier_icon->get_size() / 2).floor(); -					ep.point_rect.size = bezier_icon->get_size(); -					if (selection.has(i)) { -						draw_texture(selected_icon, ep.point_rect.position); -						draw_string(font, ep.point_rect.position + Vector2(8, -font->get_height(font_size) - 8), TTR("Time:") + " " + TS->format_number(rtos(Math::snapped(offset, 0.001))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, accent); -						draw_string(font, ep.point_rect.position + Vector2(8, -8), TTR("Value:") + " " + TS->format_number(rtos(Math::snapped(value, 0.001))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, accent); -					} else { -						draw_texture(bezier_icon, ep.point_rect.position); +					Vector2 in_vec = animation->bezier_track_get_key_in_handle(i, j); +					if (moving_handle != 0 && moving_handle_track == i && moving_handle_key == j) { +						in_vec = moving_handle_left; +					} +					Vector2 pos_in(((offset + in_vec.x) - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value + in_vec.y)); + +					Vector2 out_vec = animation->bezier_track_get_key_out_handle(i, j); + +					if (moving_handle != 0 && moving_handle_track == i && moving_handle_key == j) { +						out_vec = moving_handle_right; +					} + +					Vector2 pos_out(((offset + out_vec.x) - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value + out_vec.y)); + +					if (i == selected_track || selection.has(IntPair(i, j))) { +						_draw_line_clipped(pos, pos_in, accent, limit, right_limit); +						_draw_line_clipped(pos, pos_out, accent, limit, right_limit); +					} + +					EditPoint ep; +					ep.track = i; +					ep.key = j; +					if (pos.x >= limit && pos.x <= right_limit) { +						ep.point_rect.position = (pos - bezier_icon->get_size() / 2).floor(); +						ep.point_rect.size = bezier_icon->get_size(); +						if (selection.has(IntPair(i, j))) { +							draw_texture(selected_icon, ep.point_rect.position); +							draw_string(font, ep.point_rect.position + Vector2(8, -font->get_height(font_size) - 8), TTR("Time:") + " " + TS->format_number(rtos(Math::snapped(offset, 0.001))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, accent); +							draw_string(font, ep.point_rect.position + Vector2(8, -8), TTR("Value:") + " " + TS->format_number(rtos(Math::snapped(value, 0.001))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, accent); +						} else { +							Color track_color = Color(1, 1, 1, 1); +							if (i != selected_track) { +								track_color = subtrack_colors[i]; +							} +							draw_texture(bezier_icon, ep.point_rect.position, track_color); +						} +						ep.point_rect = ep.point_rect.grow(ep.point_rect.size.width * 0.5); +					} +					if (i == selected_track || selection.has(IntPair(i, j))) { +						if (pos_in.x >= limit && pos_in.x <= right_limit) { +							ep.in_rect.position = (pos_in - bezier_handle_icon->get_size() / 2).floor(); +							ep.in_rect.size = bezier_handle_icon->get_size(); +							draw_texture(bezier_handle_icon, ep.in_rect.position); +							ep.in_rect = ep.in_rect.grow(ep.in_rect.size.width * 0.5); +						} +						if (pos_out.x >= limit && pos_out.x <= right_limit) { +							ep.out_rect.position = (pos_out - bezier_handle_icon->get_size() / 2).floor(); +							ep.out_rect.size = bezier_handle_icon->get_size(); +							draw_texture(bezier_handle_icon, ep.out_rect.position); +							ep.out_rect = ep.out_rect.grow(ep.out_rect.size.width * 0.5); +						} +					} +					if (!locked_tracks.has(i)) { +						edit_points.push_back(ep);  					} -					ep.point_rect = ep.point_rect.grow(ep.point_rect.size.width * 0.5); -				} -				if (pos_in.x >= limit && pos_in.x <= right_limit) { -					ep.in_rect.position = (pos_in - bezier_handle_icon->get_size() / 2).floor(); -					ep.in_rect.size = bezier_handle_icon->get_size(); -					draw_texture(bezier_handle_icon, ep.in_rect.position); -					ep.in_rect = ep.in_rect.grow(ep.in_rect.size.width * 0.5);  				} -				if (pos_out.x >= limit && pos_out.x <= right_limit) { -					ep.out_rect.position = (pos_out - bezier_handle_icon->get_size() / 2).floor(); -					ep.out_rect.size = bezier_handle_icon->get_size(); -					draw_texture(bezier_handle_icon, ep.out_rect.position); -					ep.out_rect = ep.out_rect.grow(ep.out_rect.size.width * 0.5); +			} + +			for (int i = 0; i < edit_points.size(); ++i) { +				if (edit_points[i].track == selected_track) { +					EditPoint ep = edit_points[i]; +					edit_points.remove_at(i); +					edit_points.insert(0, ep);  				} -				edit_points.push_back(ep);  			}  		} @@ -506,15 +638,7 @@ Ref<Animation> AnimationBezierTrackEdit::get_animation() const {  void AnimationBezierTrackEdit::set_animation_and_track(const Ref<Animation> &p_animation, int p_track) {  	animation = p_animation; -	track = p_track; -	if (is_connected("select_key", Callable(editor, "_key_selected"))) { -		disconnect("select_key", Callable(editor, "_key_selected")); -	} -	if (is_connected("deselect_key", Callable(editor, "_key_deselected"))) { -		disconnect("deselect_key", Callable(editor, "_key_deselected")); -	} -	connect("select_key", Callable(editor, "_key_selected"), varray(p_track), CONNECT_DEFERRED); -	connect("deselect_key", Callable(editor, "_key_deselected"), varray(p_track), CONNECT_DEFERRED); +	selected_track = p_track;  	update();  } @@ -529,11 +653,14 @@ void AnimationBezierTrackEdit::set_undo_redo(UndoRedo *p_undo_redo) {  void AnimationBezierTrackEdit::set_timeline(AnimationTimelineEdit *p_timeline) {  	timeline = p_timeline;  	timeline->connect("zoom_changed", callable_mp(this, &AnimationBezierTrackEdit::_zoom_changed)); +	timeline->connect("name_limit_changed", callable_mp(this, &AnimationBezierTrackEdit::_zoom_changed));  }  void AnimationBezierTrackEdit::set_editor(AnimationTrackEditor *p_editor) {  	editor = p_editor;  	connect("clear_selection", Callable(editor, "_clear_selection"), varray(false)); +	connect("select_key", Callable(editor, "_key_selected"), varray(), CONNECT_DEFERRED); +	connect("deselect_key", Callable(editor, "_key_deselected"), varray(), CONNECT_DEFERRED);  }  void AnimationBezierTrackEdit::_play_position_draw() { @@ -544,9 +671,11 @@ void AnimationBezierTrackEdit::_play_position_draw() {  	float scale = timeline->get_zoom_scale();  	int h = get_size().height; -	int px = (-timeline->get_value() + play_position_pos) * scale + timeline->get_name_limit(); +	int limit = timeline->get_name_limit(); + +	int px = (-timeline->get_value() + play_position_pos) * scale + limit; -	if (px >= timeline->get_name_limit() && px < (get_size().width - timeline->get_buttons_width())) { +	if (px >= limit && px < (get_size().width)) {  		Color color = get_theme_color(SNAME("accent_color"), SNAME("Editor"));  		play_position->draw_line(Point2(px, 0), Point2(px, h), color, Math::round(2 * EDSCALE));  	} @@ -565,11 +694,84 @@ void AnimationBezierTrackEdit::set_root(Node *p_root) {  	root = p_root;  } +void AnimationBezierTrackEdit::set_filtered(bool p_filtered) { +	is_filtered = p_filtered; +	if (animation == nullptr) { +		return; +	} +	String base_path = animation->track_get_path(selected_track); +	if (is_filtered) { +		if (root && root->has_node(base_path)) { +			Node *node = root->get_node(base_path); +			if (!node || !EditorNode::get_singleton()->get_editor_selection()->is_selected(node)) { +				for (int i = 0; i < animation->get_track_count(); ++i) { +					if (animation->track_get_type(i) != Animation::TrackType::TYPE_BEZIER) { +						continue; +					} + +					base_path = animation->track_get_path(i); +					if (root && root->has_node(base_path)) { +						node = root->get_node(base_path); +						if (!node) { +							continue; // No node, no filter. +						} +						if (!EditorNode::get_singleton()->get_editor_selection()->is_selected(node)) { +							continue; // Skip track due to not selected. +						} + +						set_animation_and_track(animation, i); +						break; +					} +				} +			} +		} +	} +	update(); +} +  void AnimationBezierTrackEdit::_zoom_changed() {  	update();  	play_position->update();  } +void AnimationBezierTrackEdit::_update_locked_tracks_after(int p_track) { +	if (locked_tracks.has(p_track)) { +		locked_tracks.erase(p_track); +	} + +	Vector<int> updated_locked_tracks; +	for (Set<int>::Element *E = locked_tracks.front(); E; E = E->next()) { +		updated_locked_tracks.push_back(E->get()); +	} +	locked_tracks.clear(); +	for (int i = 0; i < updated_locked_tracks.size(); ++i) { +		if (updated_locked_tracks[i] > p_track) { +			locked_tracks.insert(updated_locked_tracks[i] - 1); +		} else { +			locked_tracks.insert(updated_locked_tracks[i]); +		} +	} +} + +void AnimationBezierTrackEdit::_update_hidden_tracks_after(int p_track) { +	if (hidden_tracks.has(p_track)) { +		hidden_tracks.erase(p_track); +	} + +	Vector<int> updated_hidden_tracks; +	for (Set<int>::Element *E = hidden_tracks.front(); E; E = E->next()) { +		updated_hidden_tracks.push_back(E->get()); +	} +	hidden_tracks.clear(); +	for (int i = 0; i < updated_hidden_tracks.size(); ++i) { +		if (updated_hidden_tracks[i] > p_track) { +			hidden_tracks.insert(updated_hidden_tracks[i] - 1); +		} else { +			hidden_tracks.insert(updated_hidden_tracks[i]); +		} +	} +} +  String AnimationBezierTrackEdit::get_tooltip(const Point2 &p_pos) const {  	return Control::get_tooltip(p_pos);  } @@ -583,10 +785,10 @@ void AnimationBezierTrackEdit::_clear_selection() {  void AnimationBezierTrackEdit::_change_selected_keys_handle_mode(Animation::HandleMode p_mode) {  	undo_redo->create_action(TTR("Update Selected Key Handles"));  	double ratio = timeline->get_zoom_scale() * v_zoom; -	for (Set<int>::Element *E = selection.back(); E; E = E->prev()) { -		const int key_index = E->get(); -		undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_handle_mode", track, key_index, animation->bezier_track_get_key_handle_mode(track, key_index), ratio); -		undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_handle_mode", track, key_index, p_mode, ratio); +	for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) { +		const IntPair track_key_pair = E->get(); +		undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_handle_mode", track_key_pair.first, track_key_pair.second, animation->bezier_track_get_key_handle_mode(track_key_pair.first, track_key_pair.second), ratio); +		undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_handle_mode", track_key_pair.first, track_key_pair.second, p_mode, ratio);  	}  	undo_redo->commit_action();  } @@ -606,8 +808,8 @@ void AnimationBezierTrackEdit::_select_at_anim(const Ref<Animation> &p_anim, int  	int idx = animation->track_find_key(p_track, p_pos, true);  	ERR_FAIL_COND(idx < 0); -	selection.insert(idx); -	emit_signal(SNAME("select_key"), idx, true); +	selection.insert(IntPair(p_track, idx)); +	emit_signal(SNAME("select_key"), p_track, idx, true);  	update();  } @@ -631,14 +833,100 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {  		}  	} +	Ref<InputEventKey> key_press = p_event; + +	if (key_press.is_valid() && key_press->is_pressed()) { +		if (ED_GET_SHORTCUT("animation_bezier_editor/focus")->matches_event(p_event)) { +			SelectionSet focused_keys; +			if (selection.is_empty()) { +				for (int i = 0; i < edit_points.size(); ++i) { +					IntPair key_pair = IntPair(edit_points[i].track, edit_points[i].key); +					focused_keys.insert(key_pair); +				} +			} else { +				for (SelectionSet::Element *E = selection.front(); E; E = E->next()) { +					focused_keys.insert(E->get()); +					if (E->get().second > 0) { +						IntPair previous_key = IntPair(E->get().first, E->get().second - 1); +						focused_keys.insert(previous_key); +					} +					if (E->get().second < animation->track_get_key_count(E->get().first) - 1) { +						IntPair next_key = IntPair(E->get().first, E->get().second + 1); +						focused_keys.insert(next_key); +					} +				} +			} +			if (focused_keys.is_empty()) { +				accept_event(); +				return; +			} + +			float minimum_time = INFINITY; +			float maximum_time = -INFINITY; +			float minimum_value = INFINITY; +			float maximum_value = -INFINITY; + +			for (SelectionSet::Element *E = focused_keys.front(); E; E = E->next()) { +				IntPair key_pair = E->get(); + +				float time = animation->track_get_key_time(key_pair.first, key_pair.second); +				float value = animation->bezier_track_get_key_value(key_pair.first, key_pair.second); + +				minimum_time = MIN(time, minimum_time); +				maximum_time = MAX(time, maximum_time); +				minimum_value = MIN(value, minimum_value); +				maximum_value = MAX(value, maximum_value); +			} + +			float width = get_size().width - timeline->get_name_limit() - timeline->get_buttons_width(); +			float padding = width * 0.1; +			float desired_scale = (width - padding / 2) / (maximum_time - minimum_time); +			minimum_time = MAX(0, minimum_time - (padding / 2) / desired_scale); + +			float zv = Math::pow(100 / desired_scale, 0.125f); +			if (zv < 1) { +				zv = Math::pow(desired_scale / 100, 0.125f) - 1; +				zv = 1 - zv; +			} +			float zoom_value = timeline->get_zoom()->get_max() - zv; + +			timeline->get_zoom()->set_value(zoom_value); +			timeline->call_deferred("set_value", minimum_time); + +			v_scroll = (maximum_value + minimum_value) / 2.0; +			v_zoom = (maximum_value - minimum_value) / ((get_size().height - timeline->get_size().height) * 0.9); + +			update(); +			accept_event(); +			return; +		} else if (ED_GET_SHORTCUT("animation_bezier_editor/select_all_keys")->matches_event(p_event)) { +			for (int i = 0; i < edit_points.size(); ++i) { +				selection.insert(IntPair(edit_points[i].track, edit_points[i].key)); +			} + +			update(); +			accept_event(); +			return; +		} else if (ED_GET_SHORTCUT("animation_bezier_editor/deselect_all_keys")->matches_event(p_event)) { +			selection.clear(); + +			update(); +			accept_event(); +			return; +		} +	} +  	Ref<InputEventMouseButton> mb = p_event; +	int limit = timeline->get_name_limit();  	if (mb.is_valid() && mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) {  		menu_insert_key = mb->get_position(); -		if (menu_insert_key.x >= timeline->get_name_limit() && menu_insert_key.x <= get_size().width - timeline->get_buttons_width()) { +		if (menu_insert_key.x >= limit && menu_insert_key.x <= get_size().width) {  			Vector2 popup_pos = get_screen_position() + mb->get_position();  			menu->clear(); -			menu->add_icon_item(bezier_icon, TTR("Insert Key Here"), MENU_KEY_INSERT); +			if (!locked_tracks.has(selected_track) || locked_tracks.has(selected_track)) { +				menu->add_icon_item(bezier_icon, TTR("Insert Key Here"), MENU_KEY_INSERT); +			}  			if (selection.size()) {  				menu->add_separator();  				menu->add_icon_item(get_theme_icon(SNAME("Duplicate"), SNAME("EditorIcons")), TTR("Duplicate Selected Key(s)"), MENU_KEY_DUPLICATE); @@ -649,50 +937,163 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {  				menu->add_icon_item(get_theme_icon(SNAME("BezierHandlesBalanced"), SNAME("EditorIcons")), TTR("Make Handles Balanced"), MENU_KEY_SET_HANDLE_BALANCED);  			} -			menu->set_as_minsize(); -			menu->set_position(popup_pos); -			menu->popup(); +			if (menu->get_item_count()) { +				menu->set_as_minsize(); +				menu->set_position(popup_pos); +				menu->popup(); +			}  		}  	}  	if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {  		for (const KeyValue<int, Rect2> &E : subtracks) {  			if (E.value.has_point(mb->get_position())) { -				set_animation_and_track(animation, E.key); -				_clear_selection(); +				if (!locked_tracks.has(E.key) && !hidden_tracks.has(E.key)) { +					set_animation_and_track(animation, E.key); +					_clear_selection(); +				}  				return;  			}  		} +		for (const KeyValue<int, Map<int, Rect2>> &E : subtrack_icons) { +			int track = E.key; +			Map<int, Rect2> track_icons = E.value; +			for (const KeyValue<int, Rect2> &I : track_icons) { +				if (I.value.has_point(mb->get_position())) { +					if (I.key == REMOVE_ICON) { +						undo_redo->create_action("Remove Bezier Track"); + +						undo_redo->add_do_method(this, "_update_locked_tracks_after", track); +						undo_redo->add_do_method(this, "_update_hidden_tracks_after", track); + +						undo_redo->add_do_method(animation.ptr(), "remove_track", track); + +						undo_redo->add_undo_method(animation.ptr(), "add_track", Animation::TrackType::TYPE_BEZIER, track); +						undo_redo->add_undo_method(animation.ptr(), "track_set_path", track, animation->track_get_path(track)); + +						for (int i = 0; i < animation->track_get_key_count(track); ++i) { +							undo_redo->add_undo_method( +									animation.ptr(), +									"bezier_track_insert_key", +									track, animation->track_get_key_time(track, i), +									animation->bezier_track_get_key_value(track, i), +									animation->bezier_track_get_key_in_handle(track, i), +									animation->bezier_track_get_key_out_handle(track, i), +									animation->bezier_track_get_key_handle_mode(track, i)); +						} + +						undo_redo->commit_action(); + +						selected_track = CLAMP(selected_track, 0, animation->get_track_count() - 1); +						return; +					} else if (I.key == LOCK_ICON) { +						if (locked_tracks.has(track)) { +							locked_tracks.erase(track); +						} else { +							locked_tracks.insert(track); +							if (selected_track == track) { +								for (int i = 0; i < animation->get_track_count(); ++i) { +									if (!locked_tracks.has(i) && animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) { +										set_animation_and_track(animation, i); +										break; +									} +								} +							} +						} +						update(); +						return; +					} else if (I.key == VISIBILITY_ICON) { +						if (hidden_tracks.has(track)) { +							hidden_tracks.erase(track); +						} else { +							hidden_tracks.insert(track); +							if (selected_track == track) { +								for (int i = 0; i < animation->get_track_count(); ++i) { +									if (!hidden_tracks.has(i) && animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) { +										set_animation_and_track(animation, i); +										break; +									} +								} +							} +						} + +						Vector<int> visible_tracks; +						for (int i = 0; i < animation->get_track_count(); ++i) { +							if (!hidden_tracks.has(i) && animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) { +								visible_tracks.push_back(i); +							} +						} + +						if (visible_tracks.size() == 1) { +							solo_track = visible_tracks[0]; +						} else { +							solo_track = -1; +						} + +						update(); +						return; +					} else if (I.key == SOLO_ICON) { +						if (solo_track == track) { +							solo_track = -1; + +							hidden_tracks.clear(); +						} else { +							if (hidden_tracks.has(track)) { +								hidden_tracks.erase(track); +							} +							for (int i = 0; i < animation->get_track_count(); ++i) { +								if (animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) { +									if (i != track && !hidden_tracks.has(i)) { +										hidden_tracks.insert(i); +									} +								} +							} + +							set_animation_and_track(animation, track); +							solo_track = track; +						} +						update(); +						return; +					} +					return; +				} +			} +		} +  		for (int i = 0; i < edit_points.size(); i++) {  			//first check point  			//command makes it ignore the main point, so control point editors can be force-edited  			//path 2D editing in the 3D and 2D editors works the same way  			if (!mb->is_command_pressed()) {  				if (edit_points[i].point_rect.has_point(mb->get_position())) { +					IntPair pair = IntPair(edit_points[i].track, edit_points[i].key);  					if (mb->is_shift_pressed()) {  						//add to selection -						if (selection.has(i)) { -							selection.erase(i); +						if (selection.has(pair)) { +							selection.erase(pair);  						} else { -							selection.insert(i); +							selection.insert(pair);  						}  						update(); -						select_single_attempt = -1; -					} else if (selection.has(i)) { +						select_single_attempt = IntPair(-1, -1); +					} else if (selection.has(pair)) {  						moving_selection_attempt = true;  						moving_selection = false; -						moving_selection_from_key = i; +						moving_selection_from_key = pair.second; +						moving_selection_from_track = pair.first;  						moving_selection_offset = Vector2(); -						select_single_attempt = i; +						select_single_attempt = pair;  						update();  					} else {  						moving_selection_attempt = true;  						moving_selection = true; -						moving_selection_from_key = i; +						moving_selection_from_key = pair.second; +						moving_selection_from_track = pair.first;  						moving_selection_offset = Vector2(); +						set_animation_and_track(animation, pair.first);  						selection.clear(); -						selection.insert(i); +						selection.insert(pair);  						update();  					}  					return; @@ -701,26 +1102,27 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {  			if (edit_points[i].in_rect.has_point(mb->get_position())) {  				moving_handle = -1; -				moving_handle_key = i; -				moving_handle_left = animation->bezier_track_get_key_in_handle(track, i); -				moving_handle_right = animation->bezier_track_get_key_out_handle(track, i); +				moving_handle_key = edit_points[i].key; +				moving_handle_track = edit_points[i].track; +				moving_handle_left = animation->bezier_track_get_key_in_handle(edit_points[i].track, edit_points[i].key); +				moving_handle_right = animation->bezier_track_get_key_out_handle(edit_points[i].track, edit_points[i].key);  				update();  				return;  			}  			if (edit_points[i].out_rect.has_point(mb->get_position())) {  				moving_handle = 1; -				moving_handle_key = i; -				moving_handle_left = animation->bezier_track_get_key_in_handle(track, i); -				moving_handle_right = animation->bezier_track_get_key_out_handle(track, i); +				moving_handle_key = edit_points[i].key; +				moving_handle_track = edit_points[i].track; +				moving_handle_left = animation->bezier_track_get_key_in_handle(edit_points[i].track, edit_points[i].key); +				moving_handle_right = animation->bezier_track_get_key_out_handle(edit_points[i].track, edit_points[i].key);  				update();  				return; -				;  			}  		}  		//insert new point -		if (mb->is_command_pressed() && mb->get_position().x >= timeline->get_name_limit() && mb->get_position().x < get_size().width - timeline->get_buttons_width()) { +		if (mb->get_position().x >= limit && mb->get_position().x < get_size().width && mb->is_command_pressed()) {  			Array new_point;  			new_point.resize(6); @@ -733,34 +1135,35 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {  			new_point[4] = 0;  			new_point[5] = 0; -			float time = ((mb->get_position().x - timeline->get_name_limit()) / timeline->get_zoom_scale()) + timeline->get_value(); -			while (animation->track_find_key(track, time, true) != -1) { +			float time = ((mb->get_position().x - limit) / timeline->get_zoom_scale()) + timeline->get_value(); +			while (animation->track_find_key(selected_track, time, true) != -1) {  				time += 0.001;  			}  			undo_redo->create_action(TTR("Add Bezier Point")); -			undo_redo->add_do_method(animation.ptr(), "track_insert_key", track, time, new_point); -			undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", track, time); +			undo_redo->add_do_method(animation.ptr(), "track_insert_key", selected_track, time, new_point); +			undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", selected_track, time);  			undo_redo->commit_action();  			//then attempt to move -			int index = animation->track_find_key(track, time, true); +			int index = animation->track_find_key(selected_track, time, true);  			ERR_FAIL_COND(index == -1);  			_clear_selection(); -			selection.insert(index); +			selection.insert(IntPair(selected_track, index));  			moving_selection_attempt = true;  			moving_selection = false;  			moving_selection_from_key = index; +			moving_selection_from_track = selected_track;  			moving_selection_offset = Vector2(); -			select_single_attempt = -1; +			select_single_attempt = IntPair(-1, -1);  			update();  			return;  		}  		//box select -		if (mb->get_position().x >= timeline->get_name_limit() && mb->get_position().x < get_size().width - timeline->get_buttons_width()) { +		if (mb->get_position().x >= limit && mb->get_position().x < get_size().width) {  			box_selecting_attempt = true;  			box_selecting = false;  			box_selecting_add = false; @@ -786,14 +1189,44 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {  			}  			Rect2 selection_rect(bs_from, bs_to - bs_from); +			bool track_set = false;  			for (int i = 0; i < edit_points.size(); i++) {  				if (edit_points[i].point_rect.intersects(selection_rect)) { -					selection.insert(i); +					selection.insert(IntPair(edit_points[i].track, edit_points[i].key)); +					if (!track_set) { +						track_set = true; +						set_animation_and_track(animation, edit_points[i].track); +					}  				}  			}  		} else {  			_clear_selection(); //clicked and nothing happened, so clear the selection + +			//select by clicking on curve +			int track_count = animation->get_track_count(); + +			float animation_length = animation->get_length(); +			animation->set_length(real_t(INT_MAX)); //bezier_track_interpolate doesn't find keys if they exist beyond anim length + +			float time = ((mb->get_position().x - limit) / timeline->get_zoom_scale()) + timeline->get_value(); + +			for (int i = 0; i < track_count; ++i) { +				if (animation->track_get_type(i) != Animation::TrackType::TYPE_BEZIER || hidden_tracks.has(i) || locked_tracks.has(i)) { +					continue; +				} + +				float track_h = animation->bezier_track_interpolate(i, time); +				float track_height = _bezier_h_to_pixel(track_h); + +				if (abs(mb->get_position().y - track_height) < 10) { +					set_animation_and_track(animation, i); +					break; +				} +			} + +			animation->set_length(animation_length);  		} +  		box_selecting_attempt = false;  		box_selecting = false;  		update(); @@ -801,10 +1234,10 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {  	if (moving_handle != 0 && mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {  		undo_redo->create_action(TTR("Move Bezier Points")); -		undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_in_handle", track, moving_handle_key, moving_handle_left); -		undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_out_handle", track, moving_handle_key, moving_handle_right); -		undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_in_handle", track, moving_handle_key, animation->bezier_track_get_key_in_handle(track, moving_handle_key)); -		undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", track, moving_handle_key, animation->bezier_track_get_key_out_handle(track, moving_handle_key)); +		undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_in_handle", selected_track, moving_handle_key, moving_handle_left); +		undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_out_handle", selected_track, moving_handle_key, moving_handle_right); +		undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_in_handle", selected_track, moving_handle_key, animation->bezier_track_get_key_in_handle(selected_track, moving_handle_key)); +		undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", selected_track, moving_handle_key, animation->bezier_track_get_key_out_handle(selected_track, moving_handle_key));  		undo_redo->commit_action();  		moving_handle = 0; @@ -819,60 +1252,60 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {  			List<AnimMoveRestore> to_restore;  			// 1-remove the keys -			for (Set<int>::Element *E = selection.back(); E; E = E->prev()) { -				undo_redo->add_do_method(animation.ptr(), "track_remove_key", track, E->get()); +			for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) { +				undo_redo->add_do_method(animation.ptr(), "track_remove_key", E->get().first, E->get().second);  			}  			// 2- remove overlapped keys -			for (Set<int>::Element *E = selection.back(); E; E = E->prev()) { -				float newtime = editor->snap_time(animation->track_get_key_time(track, E->get()) + moving_selection_offset.x); +			for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) { +				float newtime = editor->snap_time(animation->track_get_key_time(E->get().first, E->get().second) + moving_selection_offset.x); -				int idx = animation->track_find_key(track, newtime, true); +				int idx = animation->track_find_key(E->get().first, newtime, true);  				if (idx == -1) {  					continue;  				} -				if (selection.has(idx)) { +				if (selection.has(IntPair(E->get().first, idx))) {  					continue; //already in selection, don't save  				} -				undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_time", track, newtime); +				undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_time", E->get().first, newtime);  				AnimMoveRestore amr; -				amr.key = animation->track_get_key_value(track, idx); -				amr.track = track; +				amr.key = animation->track_get_key_value(E->get().first, idx); +				amr.track = E->get().first;  				amr.time = newtime;  				to_restore.push_back(amr);  			}  			// 3-move the keys (re insert them) -			for (Set<int>::Element *E = selection.back(); E; E = E->prev()) { -				float newpos = editor->snap_time(animation->track_get_key_time(track, E->get()) + moving_selection_offset.x); +			for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) { +				float newpos = editor->snap_time(animation->track_get_key_time(E->get().first, E->get().second) + moving_selection_offset.x);  				/*  				if (newpos<0)  					continue; //no add at the beginning  				*/ -				Array key = animation->track_get_key_value(track, E->get()); +				Array key = animation->track_get_key_value(E->get().first, E->get().second);  				float h = key[0];  				h += moving_selection_offset.y;  				key[0] = h; -				undo_redo->add_do_method(animation.ptr(), "track_insert_key", track, newpos, key, 1); +				undo_redo->add_do_method(animation.ptr(), "track_insert_key", E->get().first, newpos, key, 1);  			}  			// 4-(undo) remove inserted keys -			for (Set<int>::Element *E = selection.back(); E; E = E->prev()) { -				float newpos = editor->snap_time(animation->track_get_key_time(track, E->get()) + moving_selection_offset.x); +			for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) { +				float newpos = editor->snap_time(animation->track_get_key_time(E->get().first, E->get().second) + moving_selection_offset.x);  				/*  				if (newpos<0)  					continue; //no remove what no inserted  				*/ -				undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", track, newpos); +				undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", E->get().first, newpos);  			}  			// 5-(undo) reinsert keys -			for (Set<int>::Element *E = selection.back(); E; E = E->prev()) { -				float oldpos = animation->track_get_key_time(track, E->get()); -				undo_redo->add_undo_method(animation.ptr(), "track_insert_key", track, oldpos, animation->track_get_key_value(track, E->get()), 1); +			for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) { +				float oldpos = animation->track_get_key_time(E->get().first, E->get().second); +				undo_redo->add_undo_method(animation.ptr(), "track_insert_key", E->get().first, oldpos, animation->track_get_key_value(E->get().first, E->get().second), 1);  			}  			// 6-(undo) reinsert overlapped keys @@ -885,20 +1318,21 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {  			// 7-reselect -			for (Set<int>::Element *E = selection.back(); E; E = E->prev()) { -				float oldpos = animation->track_get_key_time(track, E->get()); +			for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) { +				float oldpos = animation->track_get_key_time(E->get().first, E->get().second);  				float newpos = editor->snap_time(oldpos + moving_selection_offset.x); -				undo_redo->add_do_method(this, "_select_at_anim", animation, track, newpos); -				undo_redo->add_undo_method(this, "_select_at_anim", animation, track, oldpos); +				undo_redo->add_do_method(this, "_select_at_anim", animation, E->get().first, newpos); +				undo_redo->add_undo_method(this, "_select_at_anim", animation, E->get().first, oldpos);  			}  			undo_redo->commit_action();  			moving_selection = false; -		} else if (select_single_attempt != -1) { +		} else if (select_single_attempt != IntPair(-1, -1)) {  			selection.clear();  			selection.insert(select_single_attempt); +			set_animation_and_track(animation, select_single_attempt.first);  		}  		moving_selection_attempt = false; @@ -909,13 +1343,13 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {  	if (moving_selection_attempt && mm.is_valid()) {  		if (!moving_selection) {  			moving_selection = true; -			select_single_attempt = -1; +			select_single_attempt = IntPair(-1, -1);  		}  		float y = (get_size().height / 2 - mm->get_position().y) * v_zoom + v_scroll; -		float x = editor->snap_time(((mm->get_position().x - timeline->get_name_limit()) / timeline->get_zoom_scale()) + timeline->get_value()); +		float x = editor->snap_time(((mm->get_position().x - limit) / timeline->get_zoom_scale()) + timeline->get_value()); -		moving_selection_offset = Vector2(x - animation->track_get_key_time(track, moving_selection_from_key), y - animation->bezier_track_get_key_value(track, moving_selection_from_key)); +		moving_selection_offset = Vector2(x - animation->track_get_key_time(moving_selection_from_track, moving_selection_from_key), y - animation->bezier_track_get_key_value(moving_selection_from_track, moving_selection_from_key));  		update();  	} @@ -938,17 +1372,17 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {  		float y = (get_size().height / 2 - mm->get_position().y) * v_zoom + v_scroll;  		float x = editor->snap_time((mm->get_position().x - timeline->get_name_limit()) / timeline->get_zoom_scale()) + timeline->get_value(); -		Vector2 key_pos = Vector2(animation->track_get_key_time(track, moving_handle_key), animation->bezier_track_get_key_value(track, moving_handle_key)); +		Vector2 key_pos = Vector2(animation->track_get_key_time(selected_track, moving_handle_key), animation->bezier_track_get_key_value(selected_track, moving_handle_key));  		Vector2 moving_handle_value = Vector2(x, y) - key_pos; -		moving_handle_left = animation->bezier_track_get_key_in_handle(track, moving_handle_key); -		moving_handle_right = animation->bezier_track_get_key_out_handle(track, moving_handle_key); +		moving_handle_left = animation->bezier_track_get_key_in_handle(moving_handle_track, moving_handle_key); +		moving_handle_right = animation->bezier_track_get_key_out_handle(moving_handle_track, moving_handle_key);  		if (moving_handle == -1) {  			moving_handle_left = moving_handle_value; -			if (animation->bezier_track_get_key_handle_mode(track, moving_handle_key) == Animation::HANDLE_MODE_BALANCED) { +			if (animation->bezier_track_get_key_handle_mode(moving_handle_track, moving_handle_key) == Animation::HANDLE_MODE_BALANCED) {  				double ratio = timeline->get_zoom_scale() * v_zoom;  				Transform2D xform;  				xform.set_scale(Vector2(1.0, 1.0 / ratio)); @@ -961,7 +1395,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {  		} else if (moving_handle == 1) {  			moving_handle_right = moving_handle_value; -			if (animation->bezier_track_get_key_handle_mode(track, moving_handle_key) == Animation::HANDLE_MODE_BALANCED) { +			if (animation->bezier_track_get_key_handle_mode(moving_handle_track, moving_handle_key) == Animation::HANDLE_MODE_BALANCED) {  				double ratio = timeline->get_zoom_scale() * v_zoom;  				Transform2D xform;  				xform.set_scale(Vector2(1.0, 1.0 / ratio)); @@ -980,12 +1414,12 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {  		undo_redo->create_action(TTR("Move Bezier Points"));  		if (moving_handle == -1) {  			double ratio = timeline->get_zoom_scale() * v_zoom; -			undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_in_handle", track, moving_handle_key, moving_handle_left, ratio); -			undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_in_handle", track, moving_handle_key, animation->bezier_track_get_key_in_handle(track, moving_handle_key), ratio); +			undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_in_handle", moving_handle_track, moving_handle_key, moving_handle_left, ratio); +			undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_in_handle", moving_handle_track, moving_handle_key, animation->bezier_track_get_key_in_handle(moving_handle_track, moving_handle_key), ratio);  		} else if (moving_handle == 1) {  			double ratio = timeline->get_zoom_scale() * v_zoom; -			undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_out_handle", track, moving_handle_key, moving_handle_right, ratio); -			undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", track, moving_handle_key, animation->bezier_track_get_key_out_handle(track, moving_handle_key), ratio); +			undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_out_handle", moving_handle_track, moving_handle_key, moving_handle_right, ratio); +			undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", moving_handle_track, moving_handle_key, animation->bezier_track_get_key_out_handle(moving_handle_track, moving_handle_key), ratio);  		}  		undo_redo->commit_action(); @@ -1028,27 +1462,32 @@ void AnimationBezierTrackEdit::_zoom_callback(Vector2 p_scroll_vec, Vector2 p_or  void AnimationBezierTrackEdit::_menu_selected(int p_index) {  	switch (p_index) {  		case MENU_KEY_INSERT: { -			Array new_point; -			new_point.resize(6); +			if (animation->get_track_count() > 0) { +				Array new_point; +				new_point.resize(6); -			float h = (get_size().height / 2 - menu_insert_key.y) * v_zoom + v_scroll; +				float h = (get_size().height / 2 - menu_insert_key.y) * v_zoom + v_scroll; -			new_point[0] = h; -			new_point[1] = -0.25; -			new_point[2] = 0; -			new_point[3] = 0.25; -			new_point[4] = 0; -			new_point[5] = Animation::HANDLE_MODE_BALANCED; +				new_point[0] = h; +				new_point[1] = -0.25; +				new_point[2] = 0; +				new_point[3] = 0.25; +				new_point[4] = 0; +				new_point[5] = Animation::HANDLE_MODE_BALANCED; -			float time = ((menu_insert_key.x - timeline->get_name_limit()) / timeline->get_zoom_scale()) + timeline->get_value(); -			while (animation->track_find_key(track, time, true) != -1) { -				time += 0.001; -			} +				int limit = timeline->get_name_limit(); -			undo_redo->create_action(TTR("Add Bezier Point")); -			undo_redo->add_do_method(animation.ptr(), "track_insert_key", track, time, new_point); -			undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", track, time); -			undo_redo->commit_action(); +				float time = ((menu_insert_key.x - limit) / timeline->get_zoom_scale()) + timeline->get_value(); + +				while (animation->track_find_key(selected_track, time, true) != -1) { +					time += 0.001; +				} + +				undo_redo->create_action(TTR("Add Bezier Point")); +				undo_redo->add_do_method(animation.ptr(), "track_insert_key", selected_track, time, new_point); +				undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", selected_track, time); +				undo_redo->commit_action(); +			}  		} break;  		case MENU_KEY_DUPLICATE: { @@ -1072,8 +1511,8 @@ void AnimationBezierTrackEdit::duplicate_selection() {  	}  	float top_time = 1e10; -	for (Set<int>::Element *E = selection.back(); E; E = E->prev()) { -		float t = animation->track_get_key_time(track, E->get()); +	for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) { +		float t = animation->track_get_key_time(E->get().first, E->get().second);  		if (t < top_time) {  			top_time = t;  		} @@ -1083,21 +1522,21 @@ void AnimationBezierTrackEdit::duplicate_selection() {  	List<Pair<int, float>> new_selection_values; -	for (Set<int>::Element *E = selection.back(); E; E = E->prev()) { -		float t = animation->track_get_key_time(track, E->get()); +	for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) { +		float t = animation->track_get_key_time(E->get().first, E->get().second);  		float dst_time = t + (timeline->get_play_position() - top_time); -		int existing_idx = animation->track_find_key(track, dst_time, true); +		int existing_idx = animation->track_find_key(E->get().first, dst_time, true); -		undo_redo->add_do_method(animation.ptr(), "track_insert_key", track, dst_time, animation->track_get_key_value(track, E->get()), animation->track_get_key_transition(track, E->get())); -		undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", track, dst_time); +		undo_redo->add_do_method(animation.ptr(), "track_insert_key", E->get().first, dst_time, animation->track_get_key_value(E->get().first, E->get().second), animation->track_get_key_transition(E->get().first, E->get().second)); +		undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", E->get().first, dst_time);  		Pair<int, float> p; -		p.first = track; +		p.first = E->get().first;  		p.second = dst_time;  		new_selection_values.push_back(p);  		if (existing_idx != -1) { -			undo_redo->add_undo_method(animation.ptr(), "track_insert_key", track, dst_time, animation->track_get_key_value(track, existing_idx), animation->track_get_key_transition(track, existing_idx)); +			undo_redo->add_undo_method(animation.ptr(), "track_insert_key", E->get().first, dst_time, animation->track_get_key_value(E->get().first, existing_idx), animation->track_get_key_transition(E->get().first, existing_idx));  		}  	} @@ -1116,7 +1555,7 @@ void AnimationBezierTrackEdit::duplicate_selection() {  			continue;  		} -		selection.insert(existing_idx); +		selection.insert(IntPair(track, existing_idx));  	}  	update(); @@ -1126,9 +1565,9 @@ void AnimationBezierTrackEdit::delete_selection() {  	if (selection.size()) {  		undo_redo->create_action(TTR("Anim Delete Keys")); -		for (Set<int>::Element *E = selection.back(); E; E = E->prev()) { -			undo_redo->add_do_method(animation.ptr(), "track_remove_key", track, E->get()); -			undo_redo->add_undo_method(animation.ptr(), "track_insert_key", track, animation->track_get_key_time(track, E->get()), animation->track_get_key_value(track, E->get()), 1); +		for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) { +			undo_redo->add_do_method(animation.ptr(), "track_remove_key", E->get().first, E->get().second); +			undo_redo->add_undo_method(animation.ptr(), "track_insert_key", E->get().first, animation->track_get_key_time(E->get().first, E->get().second), animation->track_get_key_value(E->get().first, E->get().second), 1);  		}  		undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);  		undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation); @@ -1142,12 +1581,14 @@ void AnimationBezierTrackEdit::_bind_methods() {  	ClassDB::bind_method("_clear_selection", &AnimationBezierTrackEdit::_clear_selection);  	ClassDB::bind_method("_clear_selection_for_anim", &AnimationBezierTrackEdit::_clear_selection_for_anim);  	ClassDB::bind_method("_select_at_anim", &AnimationBezierTrackEdit::_select_at_anim); +	ClassDB::bind_method("_update_hidden_tracks_after", &AnimationBezierTrackEdit::_update_hidden_tracks_after); +	ClassDB::bind_method("_update_locked_tracks_after", &AnimationBezierTrackEdit::_update_locked_tracks_after);  	ADD_SIGNAL(MethodInfo("timeline_changed", PropertyInfo(Variant::FLOAT, "position"), PropertyInfo(Variant::BOOL, "drag")));  	ADD_SIGNAL(MethodInfo("remove_request", PropertyInfo(Variant::INT, "track")));  	ADD_SIGNAL(MethodInfo("insert_key", PropertyInfo(Variant::FLOAT, "ofs"))); -	ADD_SIGNAL(MethodInfo("select_key", PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::BOOL, "single"))); -	ADD_SIGNAL(MethodInfo("deselect_key", PropertyInfo(Variant::INT, "index"))); +	ADD_SIGNAL(MethodInfo("select_key", PropertyInfo(Variant::INT, "track"), PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::BOOL, "single"))); +	ADD_SIGNAL(MethodInfo("deselect_key", PropertyInfo(Variant::INT, "track"), PropertyInfo(Variant::INT, "index")));  	ADD_SIGNAL(MethodInfo("clear_selection"));  	ADD_SIGNAL(MethodInfo("close_request")); @@ -1170,14 +1611,9 @@ AnimationBezierTrackEdit::AnimationBezierTrackEdit() {  	set_clip_contents(true); -	close_button = memnew(Button); -	close_button->connect("pressed", Callable(this, SNAME("emit_signal")), varray(SNAME("close_request"))); -	close_button->set_text(TTR("Close")); - -	right_column = memnew(VBoxContainer); -	right_column->add_child(close_button); -	right_column->add_spacer(); -	add_child(right_column); +	ED_SHORTCUT("animation_bezier_editor/focus", TTR("Focus"), Key::F); +	ED_SHORTCUT("animation_bezier_editor/select_all_keys", TTR("Select All Keys"), KeyModifierMask::CMD | Key::A); +	ED_SHORTCUT("animation_bezier_editor/deselect_all_keys", TTR("Deselect All Keys"), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::A);  	menu = memnew(PopupMenu);  	add_child(menu); diff --git a/editor/animation_bezier_editor.h b/editor/animation_bezier_editor.h index cf719a0355..fa6fc405f2 100644 --- a/editor/animation_bezier_editor.h +++ b/editor/animation_bezier_editor.h @@ -46,9 +46,6 @@ class AnimationBezierTrackEdit : public Control {  		MENU_KEY_SET_HANDLE_BALANCED,  	}; -	VBoxContainer *right_column; -	Button *close_button; -  	AnimationTimelineEdit *timeline = nullptr;  	UndoRedo *undo_redo = nullptr;  	Node *root = nullptr; @@ -56,7 +53,7 @@ class AnimationBezierTrackEdit : public Control {  	float play_position_pos = 0;  	Ref<Animation> animation; -	int track; +	int selected_track;  	Vector<Rect2> view_rects; @@ -66,6 +63,19 @@ class AnimationBezierTrackEdit : public Control {  	Map<int, Rect2> subtracks; +	enum { +		REMOVE_ICON, +		LOCK_ICON, +		SOLO_ICON, +		VISIBILITY_ICON +	}; + +	Map<int, Map<int, Rect2>> subtrack_icons; +	Set<int> locked_tracks; +	Set<int> hidden_tracks; +	int solo_track = -1; +	bool is_filtered = false; +  	float v_scroll = 0;  	float v_zoom = 1; @@ -73,6 +83,9 @@ class AnimationBezierTrackEdit : public Control {  	void _zoom_changed(); +	void _update_locked_tracks_after(int p_track); +	void _update_hidden_tracks_after(int p_track); +  	virtual void gui_input(const Ref<InputEvent> &p_event) override;  	void _menu_selected(int p_index); @@ -80,10 +93,13 @@ class AnimationBezierTrackEdit : public Control {  	Vector2 insert_at_pos; +	typedef Pair<int, int> IntPair; +  	bool moving_selection_attempt = false; -	int select_single_attempt = -1; +	IntPair select_single_attempt;  	bool moving_selection = false;  	int moving_selection_from_key; +	int moving_selection_from_track;  	Vector2 moving_selection_offset; @@ -95,6 +111,7 @@ class AnimationBezierTrackEdit : public Control {  	int moving_handle = 0; //0 no move -1 or +1 out  	int moving_handle_key = 0; +	int moving_handle_track = 0;  	Vector2 moving_handle_left;  	Vector2 moving_handle_right;  	int moving_handle_mode; // value from Animation::HandleMode @@ -119,11 +136,25 @@ class AnimationBezierTrackEdit : public Control {  		Rect2 point_rect;  		Rect2 in_rect;  		Rect2 out_rect; +		int track; +		int key;  	};  	Vector<EditPoint> edit_points; -	Set<int> selection; +	struct SelectionCompare { +		bool operator()(const IntPair &lh, const IntPair &rh) { +			if (lh.first == rh.first) { +				return lh.second < rh.second; +			} else { +				return lh.first < rh.first; +			} +		} +	}; + +	typedef Set<IntPair, SelectionCompare> SelectionSet; + +	SelectionSet selection;  	Ref<ViewPanner> panner;  	void _scroll_callback(Vector2 p_scroll_vec, bool p_alt); @@ -151,6 +182,7 @@ public:  	void set_timeline(AnimationTimelineEdit *p_timeline);  	void set_editor(AnimationTrackEditor *p_editor);  	void set_root(Node *p_root); +	void set_filtered(bool p_filtered);  	void set_play_position(float p_pos);  	void update_play_position(); diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index 2f33619a52..de924e84dc 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -570,7 +570,7 @@ public:  				p_list->push_back(PropertyInfo(Variant::VECTOR3, "position"));  			} break;  			case Animation::TYPE_ROTATION_3D: { -				p_list->push_back(PropertyInfo(Variant::VECTOR3, "rotation")); +				p_list->push_back(PropertyInfo(Variant::QUATERNION, "rotation"));  			} break;  			case Animation::TYPE_SCALE_3D: {  				p_list->push_back(PropertyInfo(Variant::VECTOR3, "scale")); @@ -2118,23 +2118,19 @@ void AnimationTrackEdit::_notification(int p_what) {  				update_mode_rect.position.y = 0;  				update_mode_rect.size.y = get_size().height; -				ofs += update_icon->get_width() + hsep; -				update_mode_rect.size.x += hsep; +				ofs += update_icon->get_width() + hsep / 2; +				update_mode_rect.size.x += hsep / 2;  				if (animation->track_get_type(track) == Animation::TYPE_VALUE) {  					draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2));  					update_mode_rect.size.x += down_icon->get_width(); -					bezier_edit_rect = Rect2();  				} else if (animation->track_get_type(track) == Animation::TYPE_BEZIER) {  					Ref<Texture2D> bezier_icon = get_theme_icon(SNAME("EditBezier"), SNAME("EditorIcons"));  					update_mode_rect.size.x += down_icon->get_width(); -					bezier_edit_rect.position = update_mode_rect.position + (update_mode_rect.size - bezier_icon->get_size()) / 2; -					bezier_edit_rect.size = bezier_icon->get_size(); -					draw_texture(bezier_icon, bezier_edit_rect.position); +  					update_mode_rect = Rect2();  				} else {  					update_mode_rect = Rect2(); -					bezier_edit_rect = Rect2();  				}  				ofs += down_icon->get_width(); @@ -2160,8 +2156,8 @@ void AnimationTrackEdit::_notification(int p_what) {  				interp_mode_rect.position.y = 0;  				interp_mode_rect.size.y = get_size().height; -				ofs += icon->get_width() + hsep; -				interp_mode_rect.size.x += hsep; +				ofs += icon->get_width() + hsep / 2; +				interp_mode_rect.size.x += hsep / 2;  				if (!animation->track_is_compressed(track) && (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_BLEND_SHAPE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D)) {  					draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2)); @@ -2193,8 +2189,8 @@ void AnimationTrackEdit::_notification(int p_what) {  				loop_wrap_rect.position.y = 0;  				loop_wrap_rect.size.y = get_size().height; -				ofs += icon->get_width() + hsep; -				loop_wrap_rect.size.x += hsep; +				ofs += icon->get_width() + hsep / 2; +				loop_wrap_rect.size.x += hsep / 2;  				if (!animation->track_is_compressed(track) && (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_BLEND_SHAPE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D)) {  					draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2)); @@ -2213,7 +2209,7 @@ void AnimationTrackEdit::_notification(int p_what) {  				Ref<Texture2D> icon = get_theme_icon(animation->track_is_compressed(track) ? SNAME("Lock") : SNAME("Remove"), SNAME("EditorIcons")); -				remove_rect.position.x = ofs + ((get_size().width - ofs) - icon->get_width()) / 2; +				remove_rect.position.x = ofs + ((get_size().width - ofs) - icon->get_width());  				remove_rect.position.y = int(get_size().height - icon->get_height()) / 2;  				remove_rect.size = icon->get_size(); @@ -2792,11 +2788,6 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) {  			return;  		} -		if (bezier_edit_rect.has_point(pos)) { -			emit_signal(SNAME("bezier_edit")); -			accept_event(); -		} -  		// Check keyframes.  		if (!animation->track_is_compressed(track)) { // Selecting compressed keyframes for editing is not possible. @@ -3326,10 +3317,21 @@ void AnimationTrackEditor::set_animation(const Ref<Animation> &p_anim) {  		snap->set_disabled(false);  		snap_mode->set_disabled(false); +		bezier_edit_icon->set_disabled(true); +  		imported_anim_warning->hide(); +		bool import_warning_done = false; +		bool bezier_done = false;  		for (int i = 0; i < animation->get_track_count(); i++) {  			if (animation->track_is_imported(i)) {  				imported_anim_warning->show(); +				import_warning_done = true; +			} +			if (animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) { +				bezier_edit_icon->set_disabled(false); +				bezier_done = true; +			} +			if (import_warning_done && bezier_done) {  				break;  			}  		} @@ -3343,6 +3345,7 @@ void AnimationTrackEditor::set_animation(const Ref<Animation> &p_anim) {  		step->set_read_only(true);  		snap->set_disabled(true);  		snap_mode->set_disabled(true); +		bezier_edit_icon->set_disabled(true);  	}  } @@ -4167,13 +4170,15 @@ AnimationTrackEditor::TrackIndices AnimationTrackEditor::_confirm_insert(InsertD  		} break;  		case Animation::TYPE_BEZIER: {  			Array array; -			array.resize(5); +			array.resize(6);  			array[0] = p_id.value;  			array[1] = -0.25;  			array[2] = 0;  			array[3] = 0.25;  			array[4] = 0; +			array[5] = Animation::HANDLE_MODE_BALANCED;  			value = array; +			bezier_edit_icon->set_disabled(false);  		} break;  		case Animation::TYPE_ANIMATION: { @@ -4399,7 +4404,6 @@ void AnimationTrackEditor::_update_tracks() {  		track_edit->connect("insert_key", callable_mp(this, &AnimationTrackEditor::_insert_key_from_track), varray(i), CONNECT_DEFERRED);  		track_edit->connect("select_key", callable_mp(this, &AnimationTrackEditor::_key_selected), varray(i), CONNECT_DEFERRED);  		track_edit->connect("deselect_key", callable_mp(this, &AnimationTrackEditor::_key_deselected), varray(i), CONNECT_DEFERRED); -		track_edit->connect("bezier_edit", callable_mp(this, &AnimationTrackEditor::_bezier_edit), varray(i), CONNECT_DEFERRED);  		track_edit->connect("move_selection_begin", callable_mp(this, &AnimationTrackEditor::_move_selection_begin));  		track_edit->connect("move_selection", callable_mp(this, &AnimationTrackEditor::_move_selection));  		track_edit->connect("move_selection_commit", callable_mp(this, &AnimationTrackEditor::_move_selection_commit)); @@ -4515,6 +4519,7 @@ void AnimationTrackEditor::_notification(int p_what) {  	if (p_what == NOTIFICATION_THEME_CHANGED || p_what == NOTIFICATION_ENTER_TREE) {  		zoom_icon->set_texture(get_theme_icon(SNAME("Zoom"), SNAME("EditorIcons"))); +		bezier_edit_icon->set_icon(get_theme_icon(SNAME("EditBezier"), SNAME("EditorIcons")));  		snap->set_icon(get_theme_icon(SNAME("Snap"), SNAME("EditorIcons")));  		view_group->set_icon(get_theme_icon(view_group->is_pressed() ? SNAME("AnimationTrackList") : SNAME("AnimationTrackGroup"), SNAME("EditorIcons")));  		selected_filter->set_icon(get_theme_icon(SNAME("AnimationFilter"), SNAME("EditorIcons"))); @@ -4630,6 +4635,7 @@ void AnimationTrackEditor::_new_track_node_selected(NodePath p_path) {  			adding_track_path = path_to;  			prop_selector->set_type_filter(filter);  			prop_selector->select_property_from_instance(node); +			bezier_edit_icon->set_disabled(false);  		} break;  		case Animation::TYPE_AUDIO: {  			if (!node->is_class("AudioStreamPlayer") && !node->is_class("AudioStreamPlayer2D") && !node->is_class("AudioStreamPlayer3D")) { @@ -4946,7 +4952,7 @@ void AnimationTrackEditor::_add_method_key(const String &p_method) {  	EditorNode::get_singleton()->show_warning(TTR("Method not found in object: ") + p_method);  } -void AnimationTrackEditor::_key_selected(int p_key, bool p_single, int p_track) { +void AnimationTrackEditor::_key_selected(int p_track, int p_key, bool p_single) {  	ERR_FAIL_INDEX(p_track, animation->get_track_count());  	ERR_FAIL_INDEX(p_key, animation->track_get_key_count(p_track)); @@ -5294,6 +5300,20 @@ void AnimationTrackEditor::_scroll_input(const Ref<InputEvent> &p_event) {  	}  } +void AnimationTrackEditor::_toggle_bezier_edit() { +	if (bezier_edit->is_visible()) { +		_cancel_bezier_edit(); +	} else { +		int track_count = animation->get_track_count(); +		for (int i = 0; i < track_count; ++i) { +			if (animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) { +				_bezier_edit(i); +				return; +			} +		} +	} +} +  void AnimationTrackEditor::_scroll_callback(Vector2 p_scroll_vec, bool p_alt) {  	if (p_alt) {  		if (p_scroll_vec.x < 0 || p_scroll_vec.y < 0) { @@ -5322,6 +5342,7 @@ void AnimationTrackEditor::_zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin  void AnimationTrackEditor::_cancel_bezier_edit() {  	bezier_edit->hide();  	scroll->show(); +	bezier_edit_icon->set_pressed(false);  }  void AnimationTrackEditor::_bezier_edit(int p_for_track) { @@ -5908,6 +5929,7 @@ void AnimationTrackEditor::_cleanup_animation(Ref<Animation> p_animation) {  void AnimationTrackEditor::_view_group_toggle() {  	_update_tracks();  	view_group->set_icon(get_theme_icon(view_group->is_pressed() ? SNAME("AnimationTrackList") : SNAME("AnimationTrackGroup"), SNAME("EditorIcons"))); +	bezier_edit->set_filtered(selected_filter->is_pressed());  }  bool AnimationTrackEditor::is_grouping_tracks() { @@ -6153,6 +6175,15 @@ AnimationTrackEditor::AnimationTrackEditor() {  	bottom_hb->add_spacer(); +	bezier_edit_icon = memnew(Button); +	bezier_edit_icon->set_flat(true); +	bezier_edit_icon->set_disabled(true); +	bezier_edit_icon->set_toggle_mode(true); +	bezier_edit_icon->connect("pressed", callable_mp(this, &AnimationTrackEditor::_toggle_bezier_edit)); +	bezier_edit_icon->set_tooltip(TTR("Toggle between the bezier curve editor and track editor.")); + +	bottom_hb->add_child(bezier_edit_icon); +  	selected_filter = memnew(Button);  	selected_filter->set_flat(true);  	selected_filter->connect("pressed", callable_mp(this, &AnimationTrackEditor::_view_group_toggle)); // Same function works the same. diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h index 50c5c692c0..edba784310 100644 --- a/editor/animation_track_editor.h +++ b/editor/animation_track_editor.h @@ -164,7 +164,6 @@ class AnimationTrackEdit : public Control {  	Rect2 interp_mode_rect;  	Rect2 loop_wrap_rect;  	Rect2 remove_rect; -	Rect2 bezier_edit_rect;  	Ref<Texture2D> type_icon;  	Ref<Texture2D> selected_icon; @@ -300,6 +299,7 @@ class AnimationTrackEditor : public VBoxContainer {  	EditorSpinSlider *step;  	TextureRect *zoom_icon;  	Button *snap; +	Button *bezier_edit_icon;  	OptionButton *snap_mode;  	Button *imported_anim_warning; @@ -406,7 +406,7 @@ class AnimationTrackEditor : public VBoxContainer {  	Map<SelectedKey, KeyInfo> selection; -	void _key_selected(int p_key, bool p_single, int p_track); +	void _key_selected(int p_track, int p_key, bool p_single);  	void _key_deselected(int p_key, int p_track);  	bool moving_selection; @@ -431,6 +431,7 @@ class AnimationTrackEditor : public VBoxContainer {  	Vector<Ref<AnimationTrackEditPlugin>> track_edit_plugins; +	void _toggle_bezier_edit();  	void _cancel_bezier_edit();  	void _bezier_edit(int p_for_track); diff --git a/editor/debugger/editor_network_profiler.cpp b/editor/debugger/editor_network_profiler.cpp index 698e950f57..b05134144e 100644 --- a/editor/debugger/editor_network_profiler.cpp +++ b/editor/debugger/editor_network_profiler.cpp @@ -56,7 +56,7 @@ void EditorNetworkProfiler::_update_frame() {  	TreeItem *root = counters_display->create_item(); -	for (const KeyValue<ObjectID, DebuggerMarshalls::MultiplayerNodeInfo> &E : nodes_data) { +	for (const KeyValue<ObjectID, SceneDebugger::RPCNodeInfo> &E : nodes_data) {  		TreeItem *node = counters_display->create_item(root);  		for (int j = 0; j < counters_display->get_columns(); ++j) { @@ -65,9 +65,7 @@ void EditorNetworkProfiler::_update_frame() {  		node->set_text(0, E.value.node_path);  		node->set_text(1, E.value.incoming_rpc == 0 ? "-" : itos(E.value.incoming_rpc)); -		node->set_text(2, E.value.incoming_rset == 0 ? "-" : itos(E.value.incoming_rset)); -		node->set_text(3, E.value.outgoing_rpc == 0 ? "-" : itos(E.value.outgoing_rpc)); -		node->set_text(4, E.value.outgoing_rset == 0 ? "-" : itos(E.value.outgoing_rset)); +		node->set_text(2, E.value.outgoing_rpc == 0 ? "-" : itos(E.value.outgoing_rpc));  	}  } @@ -91,14 +89,12 @@ void EditorNetworkProfiler::_clear_pressed() {  	}  } -void EditorNetworkProfiler::add_node_frame_data(const DebuggerMarshalls::MultiplayerNodeInfo p_frame) { +void EditorNetworkProfiler::add_node_frame_data(const SceneDebugger::RPCNodeInfo p_frame) {  	if (!nodes_data.has(p_frame.node)) {  		nodes_data.insert(p_frame.node, p_frame);  	} else {  		nodes_data[p_frame.node].incoming_rpc += p_frame.incoming_rpc; -		nodes_data[p_frame.node].incoming_rset += p_frame.incoming_rset;  		nodes_data[p_frame.node].outgoing_rpc += p_frame.outgoing_rpc; -		nodes_data[p_frame.node].outgoing_rset += p_frame.outgoing_rset;  	}  	if (frame_delay->is_stopped()) { @@ -174,7 +170,7 @@ EditorNetworkProfiler::EditorNetworkProfiler() {  	counters_display->set_v_size_flags(SIZE_EXPAND_FILL);  	counters_display->set_hide_folding(true);  	counters_display->set_hide_root(true); -	counters_display->set_columns(5); +	counters_display->set_columns(3);  	counters_display->set_column_titles_visible(true);  	counters_display->set_column_title(0, TTR("Node"));  	counters_display->set_column_expand(0, true); @@ -184,18 +180,10 @@ EditorNetworkProfiler::EditorNetworkProfiler() {  	counters_display->set_column_expand(1, false);  	counters_display->set_column_clip_content(1, true);  	counters_display->set_column_custom_minimum_width(1, 120 * EDSCALE); -	counters_display->set_column_title(2, TTR("Incoming RSET")); +	counters_display->set_column_title(2, TTR("Outgoing RPC"));  	counters_display->set_column_expand(2, false);  	counters_display->set_column_clip_content(2, true);  	counters_display->set_column_custom_minimum_width(2, 120 * EDSCALE); -	counters_display->set_column_title(3, TTR("Outgoing RPC")); -	counters_display->set_column_expand(3, false); -	counters_display->set_column_clip_content(3, true); -	counters_display->set_column_custom_minimum_width(3, 120 * EDSCALE); -	counters_display->set_column_title(4, TTR("Outgoing RSET")); -	counters_display->set_column_expand(4, false); -	counters_display->set_column_clip_content(4, true); -	counters_display->set_column_custom_minimum_width(4, 120 * EDSCALE);  	add_child(counters_display);  	frame_delay = memnew(Timer); diff --git a/editor/debugger/editor_network_profiler.h b/editor/debugger/editor_network_profiler.h index 320dd2a826..3e95eb0de6 100644 --- a/editor/debugger/editor_network_profiler.h +++ b/editor/debugger/editor_network_profiler.h @@ -31,7 +31,7 @@  #ifndef EDITORNETWORKPROFILER_H  #define EDITORNETWORKPROFILER_H -#include "core/debugger/debugger_marshalls.h" +#include "scene/debugger/scene_debugger.h"  #include "scene/gui/box_container.h"  #include "scene/gui/button.h"  #include "scene/gui/label.h" @@ -50,7 +50,7 @@ private:  	Timer *frame_delay; -	Map<ObjectID, DebuggerMarshalls::MultiplayerNodeInfo> nodes_data; +	Map<ObjectID, SceneDebugger::RPCNodeInfo> nodes_data;  	void _update_frame(); @@ -62,7 +62,7 @@ protected:  	static void _bind_methods();  public: -	void add_node_frame_data(const DebuggerMarshalls::MultiplayerNodeInfo p_frame); +	void add_node_frame_data(const SceneDebugger::RPCNodeInfo p_frame);  	void set_bandwidth(int p_incoming, int p_outgoing);  	bool is_profiling(); diff --git a/editor/debugger/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp index 6aedfa6ccb..28e8edb26e 100644 --- a/editor/debugger/script_editor_debugger.cpp +++ b/editor/debugger/script_editor_debugger.cpp @@ -36,7 +36,6 @@  #include "core/io/marshalls.h"  #include "core/string/ustring.h"  #include "core/version.h" -#include "core/version_hash.gen.h"  #include "editor/debugger/debug_adapter/debug_adapter_protocol.h"  #include "editor/debugger/editor_network_profiler.h"  #include "editor/debugger/editor_performance_profiler.h" @@ -64,6 +63,7 @@  #include "scene/gui/texture_button.h"  #include "scene/gui/tree.h"  #include "scene/resources/packed_scene.h" +#include "servers/debugger/servers_debugger.h"  #include "servers/display_server.h"  using CameraOverride = EditorDebuggerNode::CameraOverride; @@ -128,6 +128,7 @@ void ScriptEditorDebugger::debug_continue() {  	_clear_execution();  	_put_msg("continue", Array()); +	_put_msg("servers:foreground", Array());  }  void ScriptEditorDebugger::update_tabs() { @@ -278,7 +279,7 @@ void ScriptEditorDebugger::_remote_object_property_updated(ObjectID p_id, const  }  void ScriptEditorDebugger::_video_mem_request() { -	_put_msg("core:memory", Array()); +	_put_msg("servers:memory", Array());  }  void ScriptEditorDebugger::_video_mem_export() { @@ -344,15 +345,15 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da  		if (id.is_valid()) {  			emit_signal(SNAME("remote_object_updated"), id);  		} -	} else if (p_msg == "memory:usage") { +	} else if (p_msg == "servers:memory_usage") {  		vmem_tree->clear();  		TreeItem *root = vmem_tree->create_item(); -		DebuggerMarshalls::ResourceUsage usage; +		ServersDebugger::ResourceUsage usage;  		usage.deserialize(p_data);  		uint64_t total = 0; -		for (const DebuggerMarshalls::ResourceInfo &E : usage.infos) { +		for (const ServersDebugger::ResourceInfo &E : usage.infos) {  			TreeItem *it = vmem_tree->create_item(root);  			String type = E.type;  			int bytes = E.vram; @@ -445,7 +446,7 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da  		performance_profiler->add_profile_frame(frame_data);  	} else if (p_msg == "visual:profile_frame") { -		DebuggerMarshalls::VisualProfilerFrame frame; +		ServersDebugger::VisualProfilerFrame frame;  		frame.deserialize(p_data);  		EditorVisualProfiler::Metric metric; @@ -592,13 +593,13 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da  	} else if (p_msg == "servers:function_signature") {  		// Cache a profiler signature. -		DebuggerMarshalls::ScriptFunctionSignature sig; +		ServersDebugger::ScriptFunctionSignature sig;  		sig.deserialize(p_data);  		profiler_signature[sig.id] = sig.name;  	} else if (p_msg == "servers:profile_frame" || p_msg == "servers:profile_total") {  		EditorProfiler::Metric metric; -		DebuggerMarshalls::ServersProfilerFrame frame; +		ServersDebugger::ServersProfilerFrame frame;  		frame.deserialize(p_data);  		metric.valid = true;  		metric.frame_number = frame.frame_number; @@ -642,7 +643,7 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da  		}  		for (int i = 0; i < frame.servers.size(); i++) { -			const DebuggerMarshalls::ServerInfo &srv = frame.servers[i]; +			const ServersDebugger::ServerInfo &srv = frame.servers[i];  			EditorProfiler::Metric::Category c;  			const String name = srv.name;  			c.name = name.capitalize(); @@ -709,14 +710,14 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da  			profiler->add_frame_metric(metric, true);  		} -	} else if (p_msg == "network:profile_frame") { -		DebuggerMarshalls::NetworkProfilerFrame frame; +	} else if (p_msg == "multiplayer:rpc") { +		SceneDebugger::RPCProfilerFrame frame;  		frame.deserialize(p_data);  		for (int i = 0; i < frame.infos.size(); i++) {  			network_profiler->add_node_frame_data(frame.infos[i]);  		} -	} else if (p_msg == "network:bandwidth") { +	} else if (p_msg == "multiplayer:bandwidth") {  		ERR_FAIL_COND(p_data.size() < 2);  		network_profiler->set_bandwidth(p_data[0], p_data[1]); @@ -833,6 +834,9 @@ void ScriptEditorDebugger::_notification(int p_what) {  					msg.push_back(cam->get_far());  					_put_msg("scene:override_camera_3D:transform", msg);  				} +				if (breaked) { +					_put_msg("servers:draw", Array()); +				}  			}  			const uint64_t until = OS::get_singleton()->get_ticks_msec() + 20; @@ -971,7 +975,8 @@ void ScriptEditorDebugger::_profiler_activate(bool p_enable, int p_type) {  	data.push_back(p_enable);  	switch (p_type) {  		case PROFILER_NETWORK: -			_put_msg("profiler:network", data); +			_put_msg("profiler:multiplayer", data); +			_put_msg("profiler:rpc", data);  			break;  		case PROFILER_VISUAL:  			_put_msg("profiler:visual", data); @@ -1543,19 +1548,10 @@ void ScriptEditorDebugger::_item_menu_id_pressed(int p_option) {  			const int line_number = file_line_number[1].to_int();  			// Construct a GitHub repository URL and open it in the user's default web browser. -			if (String(VERSION_HASH).length() >= 1) { -				// Git commit hash information available; use it for greater accuracy, including for development versions. -				OS::get_singleton()->shell_open(vformat("https://github.com/godotengine/godot/blob/%s/%s#L%d", -						VERSION_HASH, -						file, -						line_number)); -			} else { -				// Git commit hash information unavailable; fall back to tagged releases. -				OS::get_singleton()->shell_open(vformat("https://github.com/godotengine/godot/blob/%s-stable/%s#L%d", -						VERSION_NUMBER, -						file, -						line_number)); -			} +			// If the commit hash is available, use it for greater accuracy. Otherwise fall back to tagged release. +			String git_ref = String(VERSION_HASH).is_empty() ? String(VERSION_NUMBER) + "-stable" : String(VERSION_HASH); +			OS::get_singleton()->shell_open(vformat("https://github.com/godotengine/godot/blob/%s/%s#L%d", +					git_ref, file, line_number));  		} break;  		case ACTION_DELETE_BREAKPOINT: {  			const TreeItem *selected = breakpoints_tree->get_selected(); diff --git a/editor/editor_about.cpp b/editor/editor_about.cpp index 54377971c6..4309f55a2b 100644 --- a/editor/editor_about.cpp +++ b/editor/editor_about.cpp @@ -29,13 +29,12 @@  /*************************************************************************/  #include "editor_about.h" -#include "editor_node.h"  #include "core/authors.gen.h"  #include "core/donors.gen.h"  #include "core/license.gen.h"  #include "core/version.h" -#include "core/version_hash.gen.h" +#include "editor_node.h"  // The metadata key used to store and retrieve the version text to copy to the clipboard.  static const String META_TEXT_TO_COPY = "text_to_copy"; diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 10ce7228e0..4b2f1c5104 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -47,7 +47,6 @@  #include "core/string/print_string.h"  #include "core/string/translation.h"  #include "core/version.h" -#include "core/version_hash.gen.h"  #include "main/main.h"  #include "scene/3d/importer_mesh_instance_3d.h"  #include "scene/gui/center_container.h" diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index f79b5027cb..e35af6dd64 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -886,15 +886,13 @@ void Node3DEditorViewport::_update_name() {  	view_menu->reset_size();  } -void Node3DEditorViewport::_compute_edit(const Point2 &p_point, const bool p_auto_center) { +void Node3DEditorViewport::_compute_edit(const Point2 &p_point) {  	_edit.original_local = spatial_editor->are_local_coords_enabled();  	_edit.click_ray = _get_ray(p_point);  	_edit.click_ray_pos = _get_ray_pos(p_point);  	_edit.plane = TRANSFORM_VIEW; -	if (p_auto_center) { -		_edit.center = spatial_editor->get_gizmo_transform().origin; -	}  	spatial_editor->update_transform_gizmo(); +	_edit.center = spatial_editor->get_gizmo_transform().origin;  	Node3D *selected = spatial_editor->get_single_selected_node();  	Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr; @@ -1366,7 +1364,6 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {  		}  	} -	_edit.center = spatial_editor->get_gizmo_target_center();  	Ref<InputEventMouseButton> b = p_event;  	if (b.is_valid()) { @@ -1471,6 +1468,8 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {  			} break;  			case MouseButton::LEFT: {  				if (b->is_pressed()) { +					clicked_wants_append = b->is_shift_pressed(); +  					if (_edit.mode != TRANSFORM_NONE && _edit.instant) {  						commit_transform();  						break; // just commit the edit, stop processing the event so we don't deselect the object @@ -1600,8 +1599,6 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {  						//clicking is always deferred to either move or release -						clicked_wants_append = b->is_shift_pressed(); -  						if (clicked.is_null()) {  							//default to regionselect  							cursor.region_select = true; @@ -1730,6 +1727,12 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {  					_edit.mode = TRANSFORM_TRANSLATE;  				} +				// enable region-select if nothing has been selected yet or multi-select (shift key) is active +				if (movement_threshold_passed && (get_selected_count() == 0 || clicked_wants_append)) { +					cursor.region_select = true; +					cursor.region_begin = _edit.original_mouse_pos; +				} +  				if (cursor.region_select) {  					cursor.region_end = m->get_position();  					surface->update(); @@ -3367,7 +3370,7 @@ void Node3DEditorViewport::update_transform_gizmo_view() {  	Transform3D xform = spatial_editor->get_gizmo_transform(); -	const Transform3D camera_xform = camera->get_transform(); +	Transform3D camera_xform = camera->get_transform();  	if (xform.origin.is_equal_approx(camera_xform.origin)) {  		for (int i = 0; i < 3; i++) { @@ -3386,63 +3389,11 @@ void Node3DEditorViewport::update_transform_gizmo_view() {  	const Vector3 camz = -camera_xform.get_basis().get_axis(2).normalized();  	const Vector3 camy = -camera_xform.get_basis().get_axis(1).normalized();  	const Plane p = Plane(camz, camera_xform.origin); -	const real_t gizmo_d = CLAMP(Math::abs(p.distance_to(xform.origin)), camera->get_near() * 2, camera->get_far() / 2); +	const real_t gizmo_d = MAX(Math::abs(p.distance_to(xform.origin)), CMP_EPSILON);  	const real_t d0 = camera->unproject_position(camera_xform.origin + camz * gizmo_d).y;  	const real_t d1 = camera->unproject_position(camera_xform.origin + camz * gizmo_d + camy).y;  	const real_t dd = MAX(Math::abs(d0 - d1), CMP_EPSILON); -	// This code ensures the gizmo stays on the screen. This includes if -	// the gizmo would otherwise be behind the camera, to the sides of -	// the camera, too close to the edge of the screen, too close to -	// the camera, or too far away from the camera. First we calculate -	// where the gizmo would go on screen, then we put it there. -	const Vector3 object_position = spatial_editor->get_gizmo_target_center(); -	Vector2 gizmo_screen_position = camera->unproject_position(object_position); -	const Vector2 viewport_size = viewport->get_size(); -	// We would use "camera.is_position_behind(parent_translation)" instead of dot, -	// except that it also accounts for the near clip plane, which we don't want. -	const bool is_in_front = camera_xform.basis.get_column(2).dot(object_position - camera_xform.origin) < 0; -	const bool is_in_viewport = is_in_front && Rect2(Vector2(0, 0), viewport_size).has_point(gizmo_screen_position); -	if (spatial_editor->is_keep_gizmo_onscreen_enabled()) { -		if (!spatial_editor->is_gizmo_visible() || is_in_viewport) { -			// In this case, the gizmo is either not visible, or in the viewport -			// already, so we should hide the offscreen line. -			gizmo_offscreen_line->hide(); -		} else { -			// In this case, the point is not "normally" on screen, and -			// it should be placed in the center. -			const Vector2 half_viewport_size = viewport_size / 2; -			gizmo_screen_position = half_viewport_size; -			// The rest of this is for drawing the offscreen line. -			// One point goes in the center of the viewport. -			// Calculate where to put the other point of the line. -			Vector2 unprojected_position = camera->unproject_position(object_position); -			if (!is_in_front) { -				// When the object is behind, we need to flip and grow the line. -				unprojected_position -= half_viewport_size; -				unprojected_position = unprojected_position.normalized() * -half_viewport_size.length_squared(); -				unprojected_position += half_viewport_size; -			} -			gizmo_offscreen_line->point1 = half_viewport_size; -			gizmo_offscreen_line->point2 = unprojected_position; -			gizmo_offscreen_line->update(); -			gizmo_offscreen_line->show(); -		} -		// Update the gizmo's position using what we calculated. -		xform.origin = camera->project_position(gizmo_screen_position, gizmo_d); -	} else { -		// In this case, the user does not want the gizmo to be -		// kept on screen, so we should hide the offscreen line, -		// and just use the gizmo's unmodified position. -		gizmo_offscreen_line->hide(); -		if (is_in_viewport) { -			xform.origin = camera->project_position(gizmo_screen_position, gizmo_d); -		} else { -			xform.origin = object_position; -		} -	} -	spatial_editor->set_gizmo_transform(xform); -  	const real_t gizmo_size = EditorSettings::get_singleton()->get("editors/3d/manipulator_gizmo_size");  	// At low viewport heights, multiply the gizmo scale based on the viewport height.  	// This prevents the gizmo from growing very large and going outside the viewport. @@ -3451,6 +3402,7 @@ void Node3DEditorViewport::update_transform_gizmo_view() {  			(gizmo_size / Math::abs(dd)) * MAX(1, EDSCALE) *  			MIN(viewport_base_height, subviewport_container->get_size().height) / viewport_base_height /  			subviewport_container->get_stretch_shrink(); +	Vector3 scale = Vector3(1, 1, 1) * gizmo_scale;  	// if the determinant is zero, we should disable the gizmo from being rendered  	// this prevents supplying bad values to the renderer and then having to filter it out again @@ -3472,7 +3424,7 @@ void Node3DEditorViewport::update_transform_gizmo_view() {  		if (xform.basis.get_axis(i).normalized().dot(xform.basis.get_axis((i + 1) % 3).normalized()) < 1.0) {  			axis_angle = axis_angle.looking_at(xform.basis.get_axis(i).normalized(), xform.basis.get_axis((i + 1) % 3).normalized());  		} -		axis_angle.basis *= gizmo_scale; +		axis_angle.basis.scale(scale);  		axis_angle.origin = xform.origin;  		RenderingServer::get_singleton()->instance_set_transform(move_gizmo_instance[i], axis_angle);  		RenderingServer::get_singleton()->instance_set_visible(move_gizmo_instance[i], spatial_editor->is_gizmo_visible() && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_MOVE)); @@ -3495,7 +3447,7 @@ void Node3DEditorViewport::update_transform_gizmo_view() {  	// Rotation white outline  	xform.orthonormalize(); -	xform.basis *= gizmo_scale; +	xform.basis.scale(scale);  	RenderingServer::get_singleton()->instance_set_transform(rotate_gizmo_instance[3], xform);  	RenderingServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[3], spatial_editor->is_gizmo_visible() && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE));  } @@ -4077,7 +4029,7 @@ void Node3DEditorViewport::drop_data_fw(const Point2 &p_point, const Variant &p_  void Node3DEditorViewport::begin_transform(TransformMode p_mode, bool instant) {  	if (get_selected_count() > 0) {  		_edit.mode = p_mode; -		_compute_edit(_edit.mouse_pos, false); +		_compute_edit(_edit.mouse_pos);  		_edit.instant = instant;  		_edit.snap = spatial_editor->is_snap_enabled();  	} @@ -4480,14 +4432,15 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, Edito  	zoom_indicator_delay = 0.0;  	spatial_editor = p_spatial_editor; -	subviewport_container = memnew(SubViewportContainer); -	subviewport_container->set_stretch(true); -	add_child(subviewport_container); -	subviewport_container->set_anchors_and_offsets_preset(Control::PRESET_WIDE); +	SubViewportContainer *c = memnew(SubViewportContainer); +	subviewport_container = c; +	c->set_stretch(true); +	add_child(c); +	c->set_anchors_and_offsets_preset(Control::PRESET_WIDE);  	viewport = memnew(SubViewport);  	viewport->set_disable_input(true); -	subviewport_container->add_child(viewport); +	c->add_child(viewport);  	surface = memnew(Control);  	surface->set_drag_forwarding(this);  	add_child(surface); @@ -4500,9 +4453,6 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, Edito  	camera->make_current();  	surface->set_focus_mode(FOCUS_ALL); -	gizmo_offscreen_line = memnew(GizmoOffScreenLine); -	subviewport_container->add_child(gizmo_offscreen_line); -  	VBoxContainer *vbox = memnew(VBoxContainer);  	surface->add_child(vbox);  	vbox->set_offset(SIDE_LEFT, 10 * EDSCALE); @@ -5127,7 +5077,6 @@ void Node3DEditor::update_transform_gizmo() {  	gizmo.visible = count > 0;  	gizmo.transform.origin = (count > 0) ? gizmo_center / count : Vector3();  	gizmo.transform.basis = (count == 1) ? gizmo_basis : Basis(); -	gizmo.target_center = gizmo_center / count;  	for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) {  		viewports[i]->update_transform_gizmo_view(); @@ -5280,7 +5229,6 @@ Dictionary Node3DEditor::get_state() const {  	d["show_grid"] = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_GRID));  	d["show_origin"] = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_ORIGIN)); -	d["keep_gizmo_onscreen"] = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(MENU_KEEP_GIZMO_ONSCREEN));  	d["fov"] = get_fov();  	d["znear"] = get_znear();  	d["zfar"] = get_zfar(); @@ -5405,13 +5353,6 @@ void Node3DEditor::set_state(const Dictionary &p_state) {  			RenderingServer::get_singleton()->instance_set_visible(origin_instance, use);  		}  	} -	if (d.has("keep_gizmo_onscreen")) { -		bool use = d["keep_gizmo_onscreen"]; - -		if (use != view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(MENU_KEEP_GIZMO_ONSCREEN))) { -			_menu_item_pressed(MENU_KEEP_GIZMO_ONSCREEN); -		} -	}  	if (d.has("gizmos_status")) {  		Dictionary gizmos_status = d["gizmos_status"]; @@ -5765,13 +5706,7 @@ void Node3DEditor::_menu_item_pressed(int p_option) {  			_init_grid();  			view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(p_option), grid_enabled); -		} break; -		case MENU_KEEP_GIZMO_ONSCREEN: { -			keep_gizmo_onscreen = !view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(p_option)); -			for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) { -				get_editor_viewport(i)->update_transform_gizmo_view(); -			} -			view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(p_option), keep_gizmo_onscreen); +  		} break;  		case MENU_VIEW_CAMERA_SETTINGS: {  			settings_dialog->popup_centered(settings_vbc->get_combined_minimum_size() + Size2(50, 50)); @@ -7756,14 +7691,12 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) {  	p->add_separator();  	p->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_origin", TTR("View Origin")), MENU_VIEW_ORIGIN);  	p->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_grid", TTR("View Grid"), Key::NUMBERSIGN), MENU_VIEW_GRID); -	p->add_check_shortcut(ED_SHORTCUT("spatial_editor/keep_gizmo_onscreen", TTR("Keep Gizmo On Screen"), KeyModifierMask::CMD + KeyModifierMask::ALT + Key::G), MENU_KEEP_GIZMO_ONSCREEN);  	p->add_separator();  	p->add_shortcut(ED_SHORTCUT("spatial_editor/settings", TTR("Settings...")), MENU_VIEW_CAMERA_SETTINGS);  	p->set_item_checked(p->get_item_index(MENU_VIEW_ORIGIN), true);  	p->set_item_checked(p->get_item_index(MENU_VIEW_GRID), true); -	p->set_item_checked(p->get_item_index(MENU_KEEP_GIZMO_ONSCREEN), true);  	p->connect("id_pressed", callable_mp(this, &Node3DEditor::_menu_item_pressed)); diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h index f14f8b90b9..bbe5615570 100644 --- a/editor/plugins/node_3d_editor_plugin.h +++ b/editor/plugins/node_3d_editor_plugin.h @@ -87,19 +87,6 @@ public:  	void set_viewport(Node3DEditorViewport *p_viewport);  }; -class GizmoOffScreenLine : public Control { -	GDCLASS(GizmoOffScreenLine, Control); - -public: -	Vector2 point1; -	Vector2 point2; -	void _notification(int p_what) { -		if (p_what == NOTIFICATION_DRAW && is_visible()) { -			draw_line(point1, point2, Color(0.0f, 0.75f, 0.75f), 2); -		} -	} -}; -  class Node3DEditorViewport : public Control {  	GDCLASS(Node3DEditorViewport, Control);  	friend class Node3DEditor; @@ -214,7 +201,6 @@ private:  	CheckBox *preview_camera;  	SubViewportContainer *subviewport_container; -	GizmoOffScreenLine *gizmo_offscreen_line;  	MenuButton *view_menu;  	PopupMenu *display_submenu; @@ -251,7 +237,7 @@ private:  	};  	void _update_name(); -	void _compute_edit(const Point2 &p_point, const bool p_auto_center = true); +	void _compute_edit(const Point2 &p_point);  	void _clear_selected();  	void _select_clicked(bool p_allow_locked);  	ObjectID _select_ray(const Point2 &p_pos); @@ -521,35 +507,6 @@ class Node3DEditor : public VBoxContainer {  public:  	static const unsigned int VIEWPORTS_COUNT = 4; -	enum MenuOption { -		MENU_GROUP_SELECTED, -		MENU_KEEP_GIZMO_ONSCREEN, -		MENU_LOCK_SELECTED, -		MENU_SNAP_TO_FLOOR, -		MENU_TOOL_LIST_SELECT, -		MENU_TOOL_LOCAL_COORDS, -		MENU_TOOL_MOVE, -		MENU_TOOL_OVERRIDE_CAMERA, -		MENU_TOOL_ROTATE, -		MENU_TOOL_SCALE, -		MENU_TOOL_SELECT, -		MENU_TOOL_USE_SNAP, -		MENU_TRANSFORM_CONFIGURE_SNAP, -		MENU_TRANSFORM_DIALOG, -		MENU_UNGROUP_SELECTED, -		MENU_UNLOCK_SELECTED, -		MENU_VIEW_CAMERA_SETTINGS, -		MENU_VIEW_GIZMOS_3D_ICONS, -		MENU_VIEW_GRID, -		MENU_VIEW_ORIGIN, -		MENU_VIEW_USE_1_VIEWPORT, -		MENU_VIEW_USE_2_VIEWPORTS, -		MENU_VIEW_USE_2_VIEWPORTS_ALT, -		MENU_VIEW_USE_3_VIEWPORTS, -		MENU_VIEW_USE_3_VIEWPORTS_ALT, -		MENU_VIEW_USE_4_VIEWPORTS, -	}; -  	enum ToolMode {  		TOOL_MODE_SELECT,  		TOOL_MODE_MOVE, @@ -568,6 +525,7 @@ public:  		TOOL_OPT_USE_SNAP,  		TOOL_OPT_OVERRIDE_CAMERA,  		TOOL_OPT_MAX +  	};  private: @@ -596,7 +554,6 @@ private:  	Camera3D::Projection grid_camera_last_update_perspective = Camera3D::PROJECTION_PERSPECTIVE;  	Vector3 grid_camera_last_update_position = Vector3(); -	bool keep_gizmo_onscreen = true;  	Ref<ArrayMesh> move_gizmo[3], move_plane_gizmo[3], rotate_gizmo[4], scale_gizmo[3], scale_plane_gizmo[3], axis_gizmo[3];  	Ref<StandardMaterial3D> gizmo_color[3];  	Ref<StandardMaterial3D> plane_gizmo_color[3]; @@ -630,10 +587,37 @@ private:  	struct Gizmo {  		bool visible = false;  		real_t scale = 0; -		Vector3 target_center;  		Transform3D transform;  	} gizmo; +	enum MenuOption { +		MENU_TOOL_SELECT, +		MENU_TOOL_MOVE, +		MENU_TOOL_ROTATE, +		MENU_TOOL_SCALE, +		MENU_TOOL_LIST_SELECT, +		MENU_TOOL_LOCAL_COORDS, +		MENU_TOOL_USE_SNAP, +		MENU_TOOL_OVERRIDE_CAMERA, +		MENU_TRANSFORM_CONFIGURE_SNAP, +		MENU_TRANSFORM_DIALOG, +		MENU_VIEW_USE_1_VIEWPORT, +		MENU_VIEW_USE_2_VIEWPORTS, +		MENU_VIEW_USE_2_VIEWPORTS_ALT, +		MENU_VIEW_USE_3_VIEWPORTS, +		MENU_VIEW_USE_3_VIEWPORTS_ALT, +		MENU_VIEW_USE_4_VIEWPORTS, +		MENU_VIEW_ORIGIN, +		MENU_VIEW_GRID, +		MENU_VIEW_GIZMOS_3D_ICONS, +		MENU_VIEW_CAMERA_SETTINGS, +		MENU_LOCK_SELECTED, +		MENU_UNLOCK_SELECTED, +		MENU_GROUP_SELECTED, +		MENU_UNGROUP_SELECTED, +		MENU_SNAP_TO_FLOOR +	}; +  	Button *tool_button[TOOL_MAX];  	Button *tool_option_button[TOOL_OPT_MAX]; @@ -792,10 +776,7 @@ public:  	float get_zfar() const { return settings_zfar->get_value(); }  	float get_fov() const { return settings_fov->get_value(); } -	Vector3 get_gizmo_target_center() const { return gizmo.target_center; }  	Transform3D get_gizmo_transform() const { return gizmo.transform; } -	void set_gizmo_transform(const Transform3D &p_transform) { gizmo.transform = p_transform; } -	bool is_keep_gizmo_onscreen_enabled() const { return keep_gizmo_onscreen; }  	bool is_gizmo_visible() const;  	ToolMode get_tool_mode() const { return tool_mode; } diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index cfb42c0741..57e47a15fd 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -40,7 +40,6 @@  #include "core/os/os.h"  #include "core/string/translation.h"  #include "core/version.h" -#include "core/version_hash.gen.h"  #include "editor/editor_vcs_interface.h"  #include "editor_scale.h"  #include "editor_settings.h" diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 0e362b13c6..a4f9563840 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -1560,7 +1560,7 @@ void SceneTreeDock::perform_node_renames(Node *p_base, Map<Node *, NodePath> *p_  					for (int i = 0; i < anim->get_track_count(); i++) {  						NodePath track_np = anim->track_get_path(i); -						Node *n = root->get_node(track_np); +						Node *n = root->get_node_or_null(track_np);  						if (!n) {  							continue;  						} diff --git a/main/main.cpp b/main/main.cpp index 216d0a446a..1efe3ccd94 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -50,7 +50,6 @@  #include "core/register_core_types.h"  #include "core/string/translation.h"  #include "core/version.h" -#include "core/version_hash.gen.h"  #include "drivers/register_driver_types.h"  #include "main/app_icon.gen.h"  #include "main/main_timer_sync.h" @@ -200,7 +199,7 @@ static String unescape_cmdline(const String &p_str) {  static String get_full_version_string() {  	String hash = String(VERSION_HASH); -	if (hash.length() != 0) { +	if (!hash.is_empty()) {  		hash = "." + hash.left(9);  	}  	return String(VERSION_FULL_BUILD) + hash; @@ -2790,8 +2789,6 @@ void Main::cleanup(bool p_force) {  		ERR_FAIL_COND(!_start_success);  	} -	EngineDebugger::deinitialize(); -  	ResourceLoader::remove_custom_loaders();  	ResourceSaver::remove_custom_savers(); @@ -2834,6 +2831,8 @@ void Main::cleanup(bool p_force) {  	unregister_scene_types();  	unregister_server_types(); +	EngineDebugger::deinitialize(); +  	if (xr_server) {  		memdelete(xr_server);  	} diff --git a/methods.py b/methods.py index fbd304ddde..fe84641e9d 100644 --- a/methods.py +++ b/methods.py @@ -111,10 +111,9 @@ def update_version(module_version_string=""):      f.close()      # NOTE: It is safe to generate this file here, since this is still executed serially -    fhash = open("core/version_hash.gen.h", "w") +    fhash = open("core/version_hash.gen.cpp", "w")      fhash.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n") -    fhash.write("#ifndef VERSION_HASH_GEN_H\n") -    fhash.write("#define VERSION_HASH_GEN_H\n") +    fhash.write('#include "core/version.h"\n')      githash = ""      gitfolder = ".git" @@ -132,8 +131,7 @@ def update_version(module_version_string=""):          else:              githash = head -    fhash.write('#define VERSION_HASH "' + githash + '"\n') -    fhash.write("#endif // VERSION_HASH_GEN_H\n") +    fhash.write('const char *const VERSION_HASH = "' + githash + '";\n')      fhash.close() diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index baa39a3b80..0056fee7a4 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -57,7 +57,6 @@  #include "core/variant/typed_array.h"  #include "core/variant/variant.h"  #include "core/version.h" -#include "core/version_hash.gen.h"  #include "drivers/png/png_driver_common.h"  #include "editor/import/resource_importer_scene.h"  #include "scene/2d/node_2d.h" @@ -6655,8 +6654,8 @@ Error GLTFDocument::_serialize_version(Ref<GLTFState> state) {  	Dictionary asset;  	asset["version"] = version; -	String hash = VERSION_HASH; -	asset["generator"] = String(VERSION_FULL_NAME) + String("@") + (hash.length() == 0 ? String("unknown") : hash); +	String hash = String(VERSION_HASH); +	asset["generator"] = String(VERSION_FULL_NAME) + String("@") + (hash.is_empty() ? String("unknown") : hash);  	state->json["asset"] = asset;  	ERR_FAIL_COND_V(!asset.has("version"), Error::FAILED);  	ERR_FAIL_COND_V(!state->json.has("asset"), Error::FAILED); diff --git a/platform/iphone/godot_view.h b/platform/iphone/godot_view.h index 1c72a26b4a..fcb97fa63a 100644 --- a/platform/iphone/godot_view.h +++ b/platform/iphone/godot_view.h @@ -59,4 +59,9 @@ class String;  - (void)stopRendering;  - (void)startRendering; +- (void)godotTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event; +- (void)godotTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event; +- (void)godotTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event; +- (void)godotTouchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event; +  @end diff --git a/platform/iphone/godot_view.mm b/platform/iphone/godot_view.mm index b90c10fa84..ae92f32851 100644 --- a/platform/iphone/godot_view.mm +++ b/platform/iphone/godot_view.mm @@ -336,7 +336,7 @@ static const float earth_gravity = 9.80665;  	}  } -- (void)touchesBegan:(NSSet *)touchesSet withEvent:(UIEvent *)event { +- (void)godotTouchesBegan:(NSSet *)touchesSet withEvent:(UIEvent *)event {  	NSArray *tlist = [event.allTouches allObjects];  	for (unsigned int i = 0; i < [tlist count]; i++) {  		if ([touchesSet containsObject:[tlist objectAtIndex:i]]) { @@ -349,7 +349,7 @@ static const float earth_gravity = 9.80665;  	}  } -- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { +- (void)godotTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {  	NSArray *tlist = [event.allTouches allObjects];  	for (unsigned int i = 0; i < [tlist count]; i++) {  		if ([touches containsObject:[tlist objectAtIndex:i]]) { @@ -363,7 +363,7 @@ static const float earth_gravity = 9.80665;  	}  } -- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { +- (void)godotTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {  	NSArray *tlist = [event.allTouches allObjects];  	for (unsigned int i = 0; i < [tlist count]; i++) {  		if ([touches containsObject:[tlist objectAtIndex:i]]) { @@ -377,7 +377,7 @@ static const float earth_gravity = 9.80665;  	}  } -- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { +- (void)godotTouchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {  	NSArray *tlist = [event.allTouches allObjects];  	for (unsigned int i = 0; i < [tlist count]; i++) {  		if ([touches containsObject:[tlist objectAtIndex:i]]) { diff --git a/platform/iphone/godot_view_gesture_recognizer.mm b/platform/iphone/godot_view_gesture_recognizer.mm index b50ba5f942..a46c42765a 100644 --- a/platform/iphone/godot_view_gesture_recognizer.mm +++ b/platform/iphone/godot_view_gesture_recognizer.mm @@ -29,6 +29,7 @@  /*************************************************************************/  #import "godot_view_gesture_recognizer.h" +#import "godot_view.h"  #include "core/config/project_settings.h" @@ -58,6 +59,10 @@ const CGFloat kGLGestureMovementDistance = 0.5;  @implementation GodotViewGestureRecognizer +- (GodotView *)godotView { +	return (GodotView *)self.view; +} +  - (instancetype)init {  	self = [super init]; @@ -104,7 +109,7 @@ const CGFloat kGLGestureMovementDistance = 0.5;  	self.delayTimer = nil;  	if (self.delayedTouches) { -		[self.view touchesBegan:self.delayedTouches withEvent:self.delayedEvent]; +		[self.godotView godotTouchesBegan:self.delayedTouches withEvent:self.delayedEvent];  	}  	self.delayedTouches = nil; @@ -114,6 +119,8 @@ const CGFloat kGLGestureMovementDistance = 0.5;  - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {  	NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseBegan];  	[self delayTouches:cleared andEvent:event]; + +	[super touchesBegan:touches withEvent:event];  }  - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { @@ -123,8 +130,8 @@ const CGFloat kGLGestureMovementDistance = 0.5;  		// We should check if movement was significant enough to fire an event  		// for dragging to work correctly.  		for (UITouch *touch in cleared) { -			CGPoint from = [touch locationInView:self.view]; -			CGPoint to = [touch previousLocationInView:self.view]; +			CGPoint from = [touch locationInView:self.godotView]; +			CGPoint to = [touch previousLocationInView:self.godotView];  			CGFloat xDistance = from.x - to.x;  			CGFloat yDistance = from.y - to.y; @@ -133,7 +140,7 @@ const CGFloat kGLGestureMovementDistance = 0.5;  			// Early exit, since one of touches has moved enough to fire a drag event.  			if (distance > kGLGestureMovementDistance) {  				[self.delayTimer fire]; -				[self.view touchesMoved:cleared withEvent:event]; +				[self.godotView godotTouchesMoved:cleared withEvent:event];  				return;  			}  		} @@ -141,26 +148,32 @@ const CGFloat kGLGestureMovementDistance = 0.5;  		return;  	} -	[self.view touchesMoved:cleared withEvent:event]; +	[self.godotView touchesMoved:cleared withEvent:event]; + +	[super touchesMoved:touches withEvent:event];  }  - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {  	[self.delayTimer fire];  	NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseEnded]; -	[self.view touchesEnded:cleared withEvent:event]; +	[self.godotView godotTouchesEnded:cleared withEvent:event]; + +	[super touchesEnded:touches withEvent:event];  }  - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {  	[self.delayTimer fire]; -	[self.view touchesCancelled:touches withEvent:event]; -}; +	[self.godotView godotTouchesCancelled:touches withEvent:event]; + +	[super touchesCancelled:touches withEvent:event]; +}  - (NSSet *)copyClearedTouches:(NSSet *)touches phase:(UITouchPhase)phaseToSave {  	NSMutableSet *cleared = [touches mutableCopy];  	for (UITouch *touch in touches) { -		if (touch.phase != phaseToSave) { +		if (touch.view != self.view || touch.phase != phaseToSave) {  			[cleared removeObject:touch];  		}  	} diff --git a/platform/linuxbsd/crash_handler_linuxbsd.cpp b/platform/linuxbsd/crash_handler_linuxbsd.cpp index e9369fefdd..b4ec7924f6 100644 --- a/platform/linuxbsd/crash_handler_linuxbsd.cpp +++ b/platform/linuxbsd/crash_handler_linuxbsd.cpp @@ -33,7 +33,6 @@  #include "core/config/project_settings.h"  #include "core/os/os.h"  #include "core/version.h" -#include "core/version_hash.gen.h"  #include "main/main.h"  #ifdef DEBUG_ENABLED @@ -71,10 +70,10 @@ static void handle_crash(int sig) {  	}  	// Print the engine version just before, so that people are reminded to include the version in backtrace reports. -	if (String(VERSION_HASH).length() != 0) { -		fprintf(stderr, "Engine version: " VERSION_FULL_NAME " (" VERSION_HASH ")\n"); +	if (String(VERSION_HASH).is_empty()) { +		fprintf(stderr, "Engine version: %s\n", VERSION_FULL_NAME);  	} else { -		fprintf(stderr, "Engine version: " VERSION_FULL_NAME "\n"); +		fprintf(stderr, "Engine version: %s (%s)\n", VERSION_FULL_NAME, VERSION_HASH);  	}  	fprintf(stderr, "Dumping the backtrace. %s\n", msg.utf8().get_data());  	char **strings = backtrace_symbols(bt_buffer, size); diff --git a/platform/osx/crash_handler_osx.mm b/platform/osx/crash_handler_osx.mm index 16be941308..3e640b3bf3 100644 --- a/platform/osx/crash_handler_osx.mm +++ b/platform/osx/crash_handler_osx.mm @@ -33,7 +33,6 @@  #include "core/config/project_settings.h"  #include "core/os/os.h"  #include "core/version.h" -#include "core/version_hash.gen.h"  #include "main/main.h"  #include <string.h> @@ -94,10 +93,10 @@ static void handle_crash(int sig) {  		OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_CRASH);  	// Print the engine version just before, so that people are reminded to include the version in backtrace reports. -	if (String(VERSION_HASH).length() != 0) { -		fprintf(stderr, "Engine version: " VERSION_FULL_NAME " (" VERSION_HASH ")\n"); +	if (String(VERSION_HASH).is_empty()) { +		fprintf(stderr, "Engine version: %s\n", VERSION_FULL_NAME);  	} else { -		fprintf(stderr, "Engine version: " VERSION_FULL_NAME "\n"); +		fprintf(stderr, "Engine version: %s (%s)\n", VERSION_FULL_NAME, VERSION_HASH);  	}  	fprintf(stderr, "Dumping the backtrace. %s\n", msg.utf8().get_data());  	char **strings = backtrace_symbols(bt_buffer, size); diff --git a/platform/windows/crash_handler_windows.cpp b/platform/windows/crash_handler_windows.cpp index 71e9d9acbd..5064f6b97f 100644 --- a/platform/windows/crash_handler_windows.cpp +++ b/platform/windows/crash_handler_windows.cpp @@ -33,7 +33,6 @@  #include "core/config/project_settings.h"  #include "core/os/os.h"  #include "core/version.h" -#include "core/version_hash.gen.h"  #include "main/main.h"  #ifdef CRASH_HANDLER_EXCEPTION @@ -179,10 +178,10 @@ DWORD CrashHandlerException(EXCEPTION_POINTERS *ep) {  	}  	// Print the engine version just before, so that people are reminded to include the version in backtrace reports. -	if (String(VERSION_HASH).length() != 0) { -		fprintf(stderr, "Engine version: " VERSION_FULL_NAME " (" VERSION_HASH ")\n"); +	if (String(VERSION_HASH).is_empty()) { +		fprintf(stderr, "Engine version: %s\n", VERSION_FULL_NAME);  	} else { -		fprintf(stderr, "Engine version: " VERSION_FULL_NAME "\n"); +		fprintf(stderr, "Engine version: %s (%s)\n", VERSION_FULL_NAME, VERSION_HASH);  	}  	fprintf(stderr, "Dumping the backtrace. %s\n", msg.utf8().get_data()); diff --git a/scene/debugger/scene_debugger.cpp b/scene/debugger/scene_debugger.cpp index 41340f281b..e5b81f9d8d 100644 --- a/scene/debugger/scene_debugger.cpp +++ b/scene/debugger/scene_debugger.cpp @@ -31,30 +31,121 @@  #include "scene_debugger.h"  #include "core/debugger/engine_debugger.h" +#include "core/debugger/engine_profiler.h"  #include "core/io/marshalls.h"  #include "core/object/script_language.h"  #include "scene/main/scene_tree.h"  #include "scene/main/window.h"  #include "scene/resources/packed_scene.h" -void SceneDebugger::initialize() { +Array SceneDebugger::RPCProfilerFrame::serialize() { +	Array arr; +	arr.push_back(infos.size() * 4); +	for (int i = 0; i < infos.size(); ++i) { +		arr.push_back(uint64_t(infos[i].node)); +		arr.push_back(infos[i].node_path); +		arr.push_back(infos[i].incoming_rpc); +		arr.push_back(infos[i].outgoing_rpc); +	} +	return arr; +} + +bool SceneDebugger::RPCProfilerFrame::deserialize(const Array &p_arr) { +	ERR_FAIL_COND_V(p_arr.size() < 1, false); +	uint32_t size = p_arr[0]; +	ERR_FAIL_COND_V(size % 4, false); +	ERR_FAIL_COND_V((uint32_t)p_arr.size() != size + 1, false); +	infos.resize(size / 4); +	int idx = 1; +	for (uint32_t i = 0; i < size / 4; ++i) { +		infos.write[i].node = uint64_t(p_arr[idx]); +		infos.write[i].node_path = p_arr[idx + 1]; +		infos.write[i].incoming_rpc = p_arr[idx + 2]; +		infos.write[i].outgoing_rpc = p_arr[idx + 3]; +	} +	return true; +} + +class SceneDebugger::RPCProfiler : public EngineProfiler { +	Map<ObjectID, RPCNodeInfo> rpc_node_data; +	uint64_t last_profile_time = 0; + +	void init_node(const ObjectID p_node) { +		if (rpc_node_data.has(p_node)) { +			return; +		} +		rpc_node_data.insert(p_node, RPCNodeInfo()); +		rpc_node_data[p_node].node = p_node; +		rpc_node_data[p_node].node_path = Object::cast_to<Node>(ObjectDB::get_instance(p_node))->get_path(); +		rpc_node_data[p_node].incoming_rpc = 0; +		rpc_node_data[p_node].outgoing_rpc = 0; +	} + +public: +	void toggle(bool p_enable, const Array &p_opts) { +		rpc_node_data.clear(); +	} + +	void add(const Array &p_data) { +		ERR_FAIL_COND(p_data.size() < 2); +		const ObjectID id = p_data[0]; +		const String what = p_data[1]; +		init_node(id); +		RPCNodeInfo &info = rpc_node_data[id]; +		if (what == "rpc_in") { +			info.incoming_rpc++; +		} else if (what == "rpc_out") { +			info.outgoing_rpc++; +		} +	} + +	void tick(double p_frame_time, double p_idle_time, double p_physics_time, double p_physics_frame_time) { +		uint64_t pt = OS::get_singleton()->get_ticks_msec(); +		if (pt - last_profile_time > 100) { +			last_profile_time = pt; +			RPCProfilerFrame frame; +			for (const KeyValue<ObjectID, RPCNodeInfo> &E : rpc_node_data) { +				frame.infos.push_back(E.value); +			} +			rpc_node_data.clear(); +			EngineDebugger::get_singleton()->send_message("multiplayer:rpc", frame.serialize()); +		} +	} +}; + +SceneDebugger *SceneDebugger::singleton = nullptr; + +SceneDebugger::SceneDebugger() { +	singleton = this; +	rpc_profiler.instantiate(); +	rpc_profiler->bind("rpc");  #ifdef DEBUG_ENABLED  	LiveEditor::singleton = memnew(LiveEditor);  	EngineDebugger::register_message_capture("scene", EngineDebugger::Capture(nullptr, SceneDebugger::parse_message));  #endif  } -void SceneDebugger::deinitialize() { +SceneDebugger::~SceneDebugger() {  #ifdef DEBUG_ENABLED  	if (LiveEditor::singleton) { -		// Should be removed automatically when deiniting debugger, but just in case -		if (EngineDebugger::has_capture("scene")) { -			EngineDebugger::unregister_message_capture("scene"); -		} +		EngineDebugger::unregister_message_capture("scene");  		memdelete(LiveEditor::singleton);  		LiveEditor::singleton = nullptr;  	}  #endif +	singleton = nullptr; +} + +void SceneDebugger::initialize() { +	if (EngineDebugger::is_active()) { +		memnew(SceneDebugger); +	} +} + +void SceneDebugger::deinitialize() { +	if (singleton) { +		memdelete(singleton); +	}  }  #ifdef DEBUG_ENABLED diff --git a/scene/debugger/scene_debugger.h b/scene/debugger/scene_debugger.h index c8298391bb..dd0a17c2dc 100644 --- a/scene/debugger/scene_debugger.h +++ b/scene/debugger/scene_debugger.h @@ -32,6 +32,7 @@  #define SCENE_DEBUGGER_H  #include "core/object/class_db.h" +#include "core/object/ref_counted.h"  #include "core/string/ustring.h"  #include "core/templates/pair.h"  #include "core/variant/array.h" @@ -41,9 +42,36 @@ class Node;  class SceneDebugger {  public: +	// RPC profiler +	struct RPCNodeInfo { +		ObjectID node; +		String node_path; +		int incoming_rpc = 0; +		int outgoing_rpc = 0; +	}; + +	struct RPCProfilerFrame { +		Vector<RPCNodeInfo> infos; + +		Array serialize(); +		bool deserialize(const Array &p_arr); +	}; + +private: +	class RPCProfiler; + +	static SceneDebugger *singleton; + +	Ref<RPCProfiler> rpc_profiler; + +	SceneDebugger(); + +public:  	static void initialize();  	static void deinitialize(); +	~SceneDebugger(); +  #ifdef DEBUG_ENABLED  private:  	static void _save_node(ObjectID id, const String &p_path); diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index b26b9d1225..61a5fb999c 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -736,26 +736,7 @@ void PopupMenu::_notification(int p_what) {  			}  		} break;  		case NOTIFICATION_THEME_CHANGED: -		case Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { -			// Pass the layout direction to all submenus. -			for (int i = 0; i < items.size(); i++) { -				if (items[i].submenu.is_empty()) { -					continue; -				} - -				Node *n = get_node(items[i].submenu); -				if (!n) { -					continue; -				} - -				PopupMenu *pm = Object::cast_to<PopupMenu>(n); -				if (pm) { -					pm->set_layout_direction(get_layout_direction()); -				} -			} - -			[[fallthrough]]; -		} +		case Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED:  		case NOTIFICATION_TRANSLATION_CHANGED: {  			for (int i = 0; i < items.size(); i++) {  				items.write[i].xl_text = atr(items[i].text); diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index 9a6c87276f..a36eaaa0ee 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -905,6 +905,12 @@ String TreeItem::get_button_tooltip(int p_column, int p_idx) const {  	return cells[p_column].buttons[p_idx].tooltip;  } +int TreeItem::get_button_id(int p_column, int p_idx) const { +	ERR_FAIL_INDEX_V(p_column, cells.size(), -1); +	ERR_FAIL_INDEX_V(p_idx, cells[p_column].buttons.size(), -1); +	return cells[p_column].buttons[p_idx].id; +} +  void TreeItem::erase_button(int p_column, int p_idx) {  	ERR_FAIL_INDEX(p_column, cells.size());  	ERR_FAIL_INDEX(p_idx, cells[p_column].buttons.size()); @@ -1283,9 +1289,11 @@ void TreeItem::_bind_methods() {  	ClassDB::bind_method(D_METHOD("set_custom_as_button", "column", "enable"), &TreeItem::set_custom_as_button);  	ClassDB::bind_method(D_METHOD("is_custom_set_as_button", "column"), &TreeItem::is_custom_set_as_button); -	ClassDB::bind_method(D_METHOD("add_button", "column", "button", "button_idx", "disabled", "tooltip"), &TreeItem::add_button, DEFVAL(-1), DEFVAL(false), DEFVAL("")); +	ClassDB::bind_method(D_METHOD("add_button", "column", "button", "id", "disabled", "tooltip"), &TreeItem::add_button, DEFVAL(-1), DEFVAL(false), DEFVAL(""));  	ClassDB::bind_method(D_METHOD("get_button_count", "column"), &TreeItem::get_button_count);  	ClassDB::bind_method(D_METHOD("get_button_tooltip", "column", "button_idx"), &TreeItem::get_button_tooltip); +	ClassDB::bind_method(D_METHOD("get_button_id", "column", "button_idx"), &TreeItem::get_button_id); +	ClassDB::bind_method(D_METHOD("get_button_by_id", "column", "id"), &TreeItem::get_button_by_id);  	ClassDB::bind_method(D_METHOD("get_button", "column", "button_idx"), &TreeItem::get_button);  	ClassDB::bind_method(D_METHOD("set_button", "column", "button_idx", "button"), &TreeItem::set_button);  	ClassDB::bind_method(D_METHOD("erase_button", "column", "button_idx"), &TreeItem::erase_button); @@ -4856,6 +4864,7 @@ void Tree::_bind_methods() {  	ClassDB::bind_method(D_METHOD("get_item_at_position", "position"), &Tree::get_item_at_position);  	ClassDB::bind_method(D_METHOD("get_column_at_position", "position"), &Tree::get_column_at_position);  	ClassDB::bind_method(D_METHOD("get_drop_section_at_position", "position"), &Tree::get_drop_section_at_position); +	ClassDB::bind_method(D_METHOD("get_button_id_at_position", "position"), &Tree::get_button_id_at_position);  	ClassDB::bind_method(D_METHOD("ensure_cursor_is_visible"), &Tree::ensure_cursor_is_visible); diff --git a/scene/gui/tree.h b/scene/gui/tree.h index 255a4f0576..dc786de6dc 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -248,6 +248,7 @@ public:  	int get_button_count(int p_column) const;  	String get_button_tooltip(int p_column, int p_idx) const;  	Ref<Texture2D> get_button(int p_column, int p_idx) const; +	int get_button_id(int p_column, int p_idx) const;  	void erase_button(int p_column, int p_idx);  	int get_button_by_id(int p_column, int p_id) const;  	void set_button(int p_column, int p_idx, const Ref<Texture2D> &p_button); diff --git a/scene/main/node.cpp b/scene/main/node.cpp index 6b9d8ab211..d5bc7d111a 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -612,14 +612,15 @@ Variant Node::_rpc_bind(const Variant **p_args, int p_argcount, Callable::CallEr  		return Variant();  	} -	if (p_args[0]->get_type() != Variant::STRING_NAME) { +	Variant::Type type = p_args[0]->get_type(); +	if (type != Variant::STRING_NAME && type != Variant::STRING) {  		r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;  		r_error.argument = 0;  		r_error.expected = Variant::STRING_NAME;  		return Variant();  	} -	StringName method = *p_args[0]; +	StringName method = (*p_args[0]).operator StringName();  	rpcp(0, method, &p_args[1], p_argcount - 1); @@ -641,7 +642,8 @@ Variant Node::_rpc_id_bind(const Variant **p_args, int p_argcount, Callable::Cal  		return Variant();  	} -	if (p_args[1]->get_type() != Variant::STRING_NAME) { +	Variant::Type type = p_args[1]->get_type(); +	if (type != Variant::STRING_NAME && type != Variant::STRING) {  		r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;  		r_error.argument = 1;  		r_error.expected = Variant::STRING_NAME; @@ -649,7 +651,7 @@ Variant Node::_rpc_id_bind(const Variant **p_args, int p_argcount, Callable::Cal  	}  	int peer_id = *p_args[0]; -	StringName method = *p_args[1]; +	StringName method = (*p_args[1]).operator StringName();  	rpcp(peer_id, method, &p_args[2], p_argcount - 2); diff --git a/scene/multiplayer/scene_cache_interface.cpp b/scene/multiplayer/scene_cache_interface.cpp index de4a94470a..2f278ed864 100644 --- a/scene/multiplayer/scene_cache_interface.cpp +++ b/scene/multiplayer/scene_cache_interface.cpp @@ -107,6 +107,10 @@ void SceneCacheInterface::process_simplify_path(int p_from, const uint8_t *p_pac  	Ref<MultiplayerPeer> multiplayer_peer = multiplayer->get_multiplayer_peer();  	ERR_FAIL_COND(multiplayer_peer.is_null()); +#ifdef DEBUG_ENABLED +	multiplayer->profile_bandwidth("out", packet.size()); +#endif +  	multiplayer_peer->set_transfer_channel(0);  	multiplayer_peer->set_transfer_mode(Multiplayer::TRANSFER_MODE_RELIABLE);  	multiplayer_peer->set_target_peer(p_from); @@ -188,6 +192,10 @@ bool SceneCacheInterface::_send_confirm_path(Node *p_node, NodePath p_path, Path  		Ref<MultiplayerPeer> multiplayer_peer = multiplayer->get_multiplayer_peer();  		ERR_FAIL_COND_V(multiplayer_peer.is_null(), false); +#ifdef DEBUG_ENABLED +		multiplayer->profile_bandwidth("out", packet.size() * peers_to_add.size()); +#endif +  		for (int &E : peers_to_add) {  			multiplayer_peer->set_target_peer(E); // To all of you.  			multiplayer_peer->set_transfer_channel(0); diff --git a/scene/multiplayer/scene_replication_interface.cpp b/scene/multiplayer/scene_replication_interface.cpp index 2088a43ba7..25a704b37e 100644 --- a/scene/multiplayer/scene_replication_interface.cpp +++ b/scene/multiplayer/scene_replication_interface.cpp @@ -147,6 +147,11 @@ Error SceneReplicationInterface::_send_raw(const uint8_t *p_buffer, int p_size,  	ERR_FAIL_COND_V(!p_buffer || p_size < 1, ERR_INVALID_PARAMETER);  	ERR_FAIL_COND_V(!multiplayer, ERR_UNCONFIGURED);  	ERR_FAIL_COND_V(!multiplayer->has_multiplayer_peer(), ERR_UNCONFIGURED); + +#ifdef DEBUG_ENABLED +	multiplayer->profile_bandwidth("out", p_size); +#endif +  	Ref<MultiplayerPeer> peer = multiplayer->get_multiplayer_peer();  	peer->set_target_peer(p_peer);  	peer->set_transfer_channel(0); diff --git a/scene/multiplayer/scene_rpc_interface.cpp b/scene/multiplayer/scene_rpc_interface.cpp index 7d7f57b9a1..2239a5d81c 100644 --- a/scene/multiplayer/scene_rpc_interface.cpp +++ b/scene/multiplayer/scene_rpc_interface.cpp @@ -46,12 +46,11 @@ void SceneRPCInterface::make_default() {  #ifdef DEBUG_ENABLED  _FORCE_INLINE_ void SceneRPCInterface::_profile_node_data(const String &p_what, ObjectID p_id) { -	if (EngineDebugger::is_profiling("multiplayer")) { +	if (EngineDebugger::is_profiling("rpc")) {  		Array values; -		values.push_back("node");  		values.push_back(p_id);  		values.push_back(p_what); -		EngineDebugger::profiler_add_frame_data("multiplayer", values); +		EngineDebugger::profiler_add_frame_data("rpc", values);  	}  }  #else @@ -268,7 +267,7 @@ void SceneRPCInterface::_process_rpc(Node *p_node, const uint16_t p_rpc_method_i  	argp.resize(argc);  #ifdef DEBUG_ENABLED -	_profile_node_data("in_rpc", p_node->get_instance_id()); +	_profile_node_data("rpc_in", p_node->get_instance_id());  #endif  	int out; @@ -460,7 +459,7 @@ void SceneRPCInterface::rpcp(Object *p_obj, int p_peer_id, const StringName &p_m  	uint16_t rpc_id = UINT16_MAX;  	const Multiplayer::RPCConfig config = _get_rpc_config(node, p_method, rpc_id);  	ERR_FAIL_COND_MSG(config.name == StringName(), -			vformat("Unable to get the RPC configuration for the function \"%s\" at path: \"%s\". This happens when the method is not marked for RPCs.", p_method, node->get_path())); +			vformat("Unable to get the RPC configuration for the function \"%s\" at path: \"%s\". This happens when the method is missing or not marked for RPCs in the local script.", p_method, node->get_path()));  	if (p_peer_id == 0 || p_peer_id == node_id || (p_peer_id < 0 && p_peer_id != -node_id)) {  		if (rpc_id & (1 << 15)) {  			call_local_native = config.call_local; @@ -471,7 +470,7 @@ void SceneRPCInterface::rpcp(Object *p_obj, int p_peer_id, const StringName &p_m  	if (p_peer_id != node_id) {  #ifdef DEBUG_ENABLED -		_profile_node_data("out_rpc", node->get_instance_id()); +		_profile_node_data("rpc_out", node->get_instance_id());  #endif  		_send_rpc(node, p_peer_id, rpc_id, config, p_method, p_arg, p_argcount); diff --git a/servers/SCsub b/servers/SCsub index 76c11724d3..2cd4741d56 100644 --- a/servers/SCsub +++ b/servers/SCsub @@ -12,6 +12,7 @@ SConscript("physics_2d/SCsub")  SConscript("rendering/SCsub")  SConscript("audio/SCsub")  SConscript("text/SCsub") +SConscript("debugger/SCsub")  lib = env.add_library("servers", env.servers_sources) diff --git a/servers/debugger/SCsub b/servers/debugger/SCsub new file mode 100644 index 0000000000..86681f9c74 --- /dev/null +++ b/servers/debugger/SCsub @@ -0,0 +1,5 @@ +#!/usr/bin/env python + +Import("env") + +env.add_source_files(env.servers_sources, "*.cpp") diff --git a/servers/debugger/servers_debugger.cpp b/servers/debugger/servers_debugger.cpp new file mode 100644 index 0000000000..d1391937d9 --- /dev/null +++ b/servers/debugger/servers_debugger.cpp @@ -0,0 +1,463 @@ +/*************************************************************************/ +/*  servers_debugger.cpp                                                 */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */ +/*                                                                       */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the       */ +/* "Software"), to deal in the Software without restriction, including   */ +/* without limitation the rights to use, copy, modify, merge, publish,   */ +/* distribute, sublicense, and/or sell copies of the Software, and to    */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions:                                             */ +/*                                                                       */ +/* The above copyright notice and this permission notice shall be        */ +/* included in all copies or substantial portions of the Software.       */ +/*                                                                       */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */ +/*************************************************************************/ + +#include "servers_debugger.h" + +#include "core/config/project_settings.h" +#include "core/debugger/engine_debugger.h" +#include "core/debugger/engine_profiler.h" +#include "core/io/marshalls.h" +#include "servers/display_server.h" + +#define CHECK_SIZE(arr, expected, what) ERR_FAIL_COND_V_MSG((uint32_t)arr.size() < (uint32_t)(expected), false, String("Malformed ") + what + " message from script debugger, message too short. Expected size: " + itos(expected) + ", actual size: " + itos(arr.size())) +#define CHECK_END(arr, expected, what) ERR_FAIL_COND_V_MSG((uint32_t)arr.size() > (uint32_t)expected, false, String("Malformed ") + what + " message from script debugger, message too long. Expected size: " + itos(expected) + ", actual size: " + itos(arr.size())) + +Array ServersDebugger::ResourceUsage::serialize() { +	infos.sort(); + +	Array arr; +	arr.push_back(infos.size() * 4); +	for (const ResourceInfo &E : infos) { +		arr.push_back(E.path); +		arr.push_back(E.format); +		arr.push_back(E.type); +		arr.push_back(E.vram); +	} +	return arr; +} + +bool ServersDebugger::ResourceUsage::deserialize(const Array &p_arr) { +	CHECK_SIZE(p_arr, 1, "ResourceUsage"); +	uint32_t size = p_arr[0]; +	CHECK_SIZE(p_arr, size, "ResourceUsage"); +	int idx = 1; +	for (uint32_t i = 0; i < size / 4; i++) { +		ResourceInfo info; +		info.path = p_arr[idx]; +		info.format = p_arr[idx + 1]; +		info.type = p_arr[idx + 2]; +		info.vram = p_arr[idx + 3]; +		infos.push_back(info); +	} +	CHECK_END(p_arr, idx, "ResourceUsage"); +	return true; +} + +Array ServersDebugger::ScriptFunctionSignature::serialize() { +	Array arr; +	arr.push_back(name); +	arr.push_back(id); +	return arr; +} + +bool ServersDebugger::ScriptFunctionSignature::deserialize(const Array &p_arr) { +	CHECK_SIZE(p_arr, 2, "ScriptFunctionSignature"); +	name = p_arr[0]; +	id = p_arr[1]; +	CHECK_END(p_arr, 2, "ScriptFunctionSignature"); +	return true; +} + +Array ServersDebugger::ServersProfilerFrame::serialize() { +	Array arr; +	arr.push_back(frame_number); +	arr.push_back(frame_time); +	arr.push_back(idle_time); +	arr.push_back(physics_time); +	arr.push_back(physics_frame_time); +	arr.push_back(script_time); + +	arr.push_back(servers.size()); +	for (int i = 0; i < servers.size(); i++) { +		ServerInfo &s = servers[i]; +		arr.push_back(s.name); +		arr.push_back(s.functions.size() * 2); +		for (int j = 0; j < s.functions.size(); j++) { +			ServerFunctionInfo &f = s.functions[j]; +			arr.push_back(f.name); +			arr.push_back(f.time); +		} +	} + +	arr.push_back(script_functions.size() * 4); +	for (int i = 0; i < script_functions.size(); i++) { +		arr.push_back(script_functions[i].sig_id); +		arr.push_back(script_functions[i].call_count); +		arr.push_back(script_functions[i].self_time); +		arr.push_back(script_functions[i].total_time); +	} +	return arr; +} + +bool ServersDebugger::ServersProfilerFrame::deserialize(const Array &p_arr) { +	CHECK_SIZE(p_arr, 7, "ServersProfilerFrame"); +	frame_number = p_arr[0]; +	frame_time = p_arr[1]; +	idle_time = p_arr[2]; +	physics_time = p_arr[3]; +	physics_frame_time = p_arr[4]; +	script_time = p_arr[5]; +	int servers_size = p_arr[6]; +	int idx = 7; +	while (servers_size) { +		CHECK_SIZE(p_arr, idx + 2, "ServersProfilerFrame"); +		servers_size--; +		ServerInfo si; +		si.name = p_arr[idx]; +		int sub_data_size = p_arr[idx + 1]; +		idx += 2; +		CHECK_SIZE(p_arr, idx + sub_data_size, "ServersProfilerFrame"); +		for (int j = 0; j < sub_data_size / 2; j++) { +			ServerFunctionInfo sf; +			sf.name = p_arr[idx]; +			sf.time = p_arr[idx + 1]; +			idx += 2; +			si.functions.push_back(sf); +		} +		servers.push_back(si); +	} +	CHECK_SIZE(p_arr, idx + 1, "ServersProfilerFrame"); +	int func_size = p_arr[idx]; +	idx += 1; +	CHECK_SIZE(p_arr, idx + func_size, "ServersProfilerFrame"); +	for (int i = 0; i < func_size / 4; i++) { +		ScriptFunctionInfo fi; +		fi.sig_id = p_arr[idx]; +		fi.call_count = p_arr[idx + 1]; +		fi.self_time = p_arr[idx + 2]; +		fi.total_time = p_arr[idx + 3]; +		script_functions.push_back(fi); +		idx += 4; +	} +	CHECK_END(p_arr, idx, "ServersProfilerFrame"); +	return true; +} + +Array ServersDebugger::VisualProfilerFrame::serialize() { +	Array arr; +	arr.push_back(frame_number); +	arr.push_back(areas.size() * 3); +	for (int i = 0; i < areas.size(); i++) { +		arr.push_back(areas[i].name); +		arr.push_back(areas[i].cpu_msec); +		arr.push_back(areas[i].gpu_msec); +	} +	return arr; +} + +bool ServersDebugger::VisualProfilerFrame::deserialize(const Array &p_arr) { +	CHECK_SIZE(p_arr, 2, "VisualProfilerFrame"); +	frame_number = p_arr[0]; +	int size = p_arr[1]; +	CHECK_SIZE(p_arr, size, "VisualProfilerFrame"); +	int idx = 2; +	areas.resize(size / 3); +	RS::FrameProfileArea *w = areas.ptrw(); +	for (int i = 0; i < size / 3; i++) { +		w[i].name = p_arr[idx]; +		w[i].cpu_msec = p_arr[idx + 1]; +		w[i].gpu_msec = p_arr[idx + 2]; +		idx += 3; +	} +	CHECK_END(p_arr, idx, "VisualProfilerFrame"); +	return true; +} +class ServersDebugger::ScriptsProfiler : public EngineProfiler { +	typedef ServersDebugger::ScriptFunctionSignature FunctionSignature; +	typedef ServersDebugger::ScriptFunctionInfo FunctionInfo; +	struct ProfileInfoSort { +		bool operator()(ScriptLanguage::ProfilingInfo *A, ScriptLanguage::ProfilingInfo *B) const { +			return A->total_time < B->total_time; +		} +	}; +	Vector<ScriptLanguage::ProfilingInfo> info; +	Vector<ScriptLanguage::ProfilingInfo *> ptrs; +	Map<StringName, int> sig_map; +	int max_frame_functions = 16; + +public: +	void toggle(bool p_enable, const Array &p_opts) { +		if (p_enable) { +			sig_map.clear(); +			for (int i = 0; i < ScriptServer::get_language_count(); i++) { +				ScriptServer::get_language(i)->profiling_start(); +			} +			if (p_opts.size() == 1 && p_opts[0].get_type() == Variant::INT) { +				max_frame_functions = MAX(0, int(p_opts[0])); +			} +		} else { +			for (int i = 0; i < ScriptServer::get_language_count(); i++) { +				ScriptServer::get_language(i)->profiling_stop(); +			} +		} +	} + +	void write_frame_data(Vector<FunctionInfo> &r_funcs, uint64_t &r_total, bool p_accumulated) { +		int ofs = 0; +		for (int i = 0; i < ScriptServer::get_language_count(); i++) { +			if (p_accumulated) { +				ofs += ScriptServer::get_language(i)->profiling_get_accumulated_data(&info.write[ofs], info.size() - ofs); +			} else { +				ofs += ScriptServer::get_language(i)->profiling_get_frame_data(&info.write[ofs], info.size() - ofs); +			} +		} + +		for (int i = 0; i < ofs; i++) { +			ptrs.write[i] = &info.write[i]; +		} + +		SortArray<ScriptLanguage::ProfilingInfo *, ProfileInfoSort> sa; +		sa.sort(ptrs.ptrw(), ofs); + +		int to_send = MIN(ofs, max_frame_functions); + +		// Check signatures first, and compute total time. +		r_total = 0; +		for (int i = 0; i < to_send; i++) { +			if (!sig_map.has(ptrs[i]->signature)) { +				int idx = sig_map.size(); +				FunctionSignature sig; +				sig.name = ptrs[i]->signature; +				sig.id = idx; +				EngineDebugger::get_singleton()->send_message("servers:function_signature", sig.serialize()); +				sig_map[ptrs[i]->signature] = idx; +			} +			r_total += ptrs[i]->self_time; +		} + +		// Send frame, script time, functions information then +		r_funcs.resize(to_send); + +		FunctionInfo *w = r_funcs.ptrw(); +		for (int i = 0; i < to_send; i++) { +			if (sig_map.has(ptrs[i]->signature)) { +				w[i].sig_id = sig_map[ptrs[i]->signature]; +			} +			w[i].call_count = ptrs[i]->call_count; +			w[i].total_time = ptrs[i]->total_time / 1000000.0; +			w[i].self_time = ptrs[i]->self_time / 1000000.0; +		} +	} + +	ScriptsProfiler() { +		info.resize(GLOBAL_GET("debug/settings/profiler/max_functions")); +		ptrs.resize(info.size()); +	} +}; + +class ServersDebugger::ServersProfiler : public EngineProfiler { +	bool skip_profile_frame = false; +	typedef ServersDebugger::ServerInfo ServerInfo; +	typedef ServersDebugger::ServerFunctionInfo ServerFunctionInfo; + +	Map<StringName, ServerInfo> server_data; +	ScriptsProfiler scripts_profiler; + +	double frame_time = 0; +	double idle_time = 0; +	double physics_time = 0; +	double physics_frame_time = 0; + +	void _send_frame_data(bool p_final) { +		ServersDebugger::ServersProfilerFrame frame; +		frame.frame_number = Engine::get_singleton()->get_process_frames(); +		frame.frame_time = frame_time; +		frame.idle_time = idle_time; +		frame.physics_time = physics_time; +		frame.physics_frame_time = physics_frame_time; +		Map<StringName, ServerInfo>::Element *E = server_data.front(); +		while (E) { +			if (!p_final) { +				frame.servers.push_back(E->get()); +			} +			E->get().functions.clear(); +			E = E->next(); +		} +		uint64_t time = 0; +		scripts_profiler.write_frame_data(frame.script_functions, time, p_final); +		frame.script_time = USEC_TO_SEC(time); +		if (skip_profile_frame) { +			skip_profile_frame = false; +			return; +		} +		if (p_final) { +			EngineDebugger::get_singleton()->send_message("servers:profile_total", frame.serialize()); +		} else { +			EngineDebugger::get_singleton()->send_message("servers:profile_frame", frame.serialize()); +		} +	} + +public: +	void toggle(bool p_enable, const Array &p_opts) { +		skip_profile_frame = false; +		if (p_enable) { +			server_data.clear(); // Clear old profiling data. +		} else { +			_send_frame_data(true); // Send final frame. +		} +		scripts_profiler.toggle(p_enable, p_opts); +	} + +	void add(const Array &p_data) { +		String name = p_data[0]; +		if (!server_data.has(name)) { +			ServerInfo info; +			info.name = name; +			server_data[name] = info; +		} +		ServerInfo &srv = server_data[name]; + +		ServerFunctionInfo fi; +		fi.name = p_data[1]; +		fi.time = p_data[2]; +		srv.functions.push_back(fi); +	} + +	void tick(double p_frame_time, double p_idle_time, double p_physics_time, double p_physics_frame_time) { +		frame_time = p_frame_time; +		idle_time = p_idle_time; +		physics_time = p_physics_time; +		physics_frame_time = p_physics_frame_time; +		_send_frame_data(false); +	} + +	void skip_frame() { +		skip_profile_frame = true; +	} +}; + +class ServersDebugger::VisualProfiler : public EngineProfiler { +	typedef ServersDebugger::ServerInfo ServerInfo; +	typedef ServersDebugger::ServerFunctionInfo ServerFunctionInfo; + +	Map<StringName, ServerInfo> server_data; + +public: +	void toggle(bool p_enable, const Array &p_opts) { +		RS::get_singleton()->set_frame_profiling_enabled(p_enable); +	} + +	void add(const Array &p_data) {} + +	void tick(double p_frame_time, double p_idle_time, double p_physics_time, double p_physics_frame_time) { +		Vector<RS::FrameProfileArea> profile_areas = RS::get_singleton()->get_frame_profile(); +		ServersDebugger::VisualProfilerFrame frame; +		if (!profile_areas.size()) { +			return; +		} + +		frame.frame_number = RS::get_singleton()->get_frame_profile_frame(); +		frame.areas.append_array(profile_areas); +		EngineDebugger::get_singleton()->send_message("visual:profile_frame", frame.serialize()); +	} +}; + +ServersDebugger *ServersDebugger::singleton = nullptr; + +void ServersDebugger::initialize() { +	if (EngineDebugger::is_active()) { +		memnew(ServersDebugger); +	} +} + +void ServersDebugger::deinitialize() { +	if (singleton) { +		memdelete(singleton); +	} +} + +Error ServersDebugger::_capture(void *p_user, const String &p_cmd, const Array &p_data, bool &r_captured) { +	ERR_FAIL_COND_V(!singleton, ERR_BUG); +	r_captured = true; +	if (p_cmd == "memory") { +		singleton->_send_resource_usage(); +	} else if (p_cmd == "draw") { // Forced redraw. +		// For camera override to stay live when the game is paused from the editor. +		double delta = 0.0; +		if (singleton->last_draw_time) { +			delta = (OS::get_singleton()->get_ticks_usec() - singleton->last_draw_time) / 1000000.0; +		} +		singleton->last_draw_time = OS::get_singleton()->get_ticks_usec(); +		RenderingServer::get_singleton()->sync(); +		if (RenderingServer::get_singleton()->has_changed()) { +			RenderingServer::get_singleton()->draw(true, delta); +		} +	} else if (p_cmd == "foreground") { +		singleton->last_draw_time = 0.0; +		DisplayServer::get_singleton()->window_move_to_foreground(); +		singleton->servers_profiler->skip_frame(); +	} else { +		r_captured = false; +	} +	return OK; +} + +void ServersDebugger::_send_resource_usage() { +	ServersDebugger::ResourceUsage usage; + +	List<RS::TextureInfo> tinfo; +	RS::get_singleton()->texture_debug_usage(&tinfo); + +	for (const RS::TextureInfo &E : tinfo) { +		ServersDebugger::ResourceInfo info; +		info.path = E.path; +		info.vram = E.bytes; +		info.id = E.texture; +		info.type = "Texture"; +		if (E.depth == 0) { +			info.format = itos(E.width) + "x" + itos(E.height) + " " + Image::get_format_name(E.format); +		} else { +			info.format = itos(E.width) + "x" + itos(E.height) + "x" + itos(E.depth) + " " + Image::get_format_name(E.format); +		} +		usage.infos.push_back(info); +	} + +	EngineDebugger::get_singleton()->send_message("servers:memory_usage", usage.serialize()); +} + +ServersDebugger::ServersDebugger() { +	singleton = this; + +	// Generic servers profiler (audio/physics/...) +	servers_profiler.instantiate(); +	servers_profiler->bind("servers"); + +	// Visual Profiler (cpu/gpu times) +	visual_profiler.instantiate(); +	visual_profiler->bind("visual"); + +	EngineDebugger::Capture servers_cap(nullptr, &_capture); +	EngineDebugger::register_message_capture("servers", servers_cap); +} + +ServersDebugger::~ServersDebugger() { +	EngineDebugger::unregister_message_capture("servers"); +	singleton = nullptr; +} diff --git a/servers/debugger/servers_debugger.h b/servers/debugger/servers_debugger.h new file mode 100644 index 0000000000..d1c55dc690 --- /dev/null +++ b/servers/debugger/servers_debugger.h @@ -0,0 +1,132 @@ +/*************************************************************************/ +/*  servers_debugger.h                                                   */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */ +/*                                                                       */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the       */ +/* "Software"), to deal in the Software without restriction, including   */ +/* without limitation the rights to use, copy, modify, merge, publish,   */ +/* distribute, sublicense, and/or sell copies of the Software, and to    */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions:                                             */ +/*                                                                       */ +/* The above copyright notice and this permission notice shall be        */ +/* included in all copies or substantial portions of the Software.       */ +/*                                                                       */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */ +/*************************************************************************/ + +#ifndef SERVER_DEBUGGER_H +#define SERVER_DEBUGGER_H + +#include "core/debugger/debugger_marshalls.h" + +#include "servers/rendering_server.h" + +class ServersDebugger { +public: +	// Memory usage +	struct ResourceInfo { +		String path; +		String format; +		String type; +		RID id; +		int vram = 0; +		bool operator<(const ResourceInfo &p_img) const { return vram == p_img.vram ? id < p_img.id : vram > p_img.vram; } +	}; + +	struct ResourceUsage { +		List<ResourceInfo> infos; + +		Array serialize(); +		bool deserialize(const Array &p_arr); +	}; + +	// Script Profiler +	struct ScriptFunctionSignature { +		StringName name; +		int id = -1; + +		Array serialize(); +		bool deserialize(const Array &p_arr); +	}; + +	struct ScriptFunctionInfo { +		StringName name; +		int sig_id = -1; +		int call_count = 0; +		double self_time = 0; +		double total_time = 0; +	}; + +	// Servers profiler +	struct ServerFunctionInfo { +		StringName name; +		double time = 0; +	}; + +	struct ServerInfo { +		StringName name; +		List<ServerFunctionInfo> functions; +	}; + +	struct ServersProfilerFrame { +		int frame_number = 0; +		double frame_time = 0; +		double idle_time = 0; +		double physics_time = 0; +		double physics_frame_time = 0; +		double script_time = 0; +		List<ServerInfo> servers; +		Vector<ScriptFunctionInfo> script_functions; + +		Array serialize(); +		bool deserialize(const Array &p_arr); +	}; + +	// Visual Profiler +	struct VisualProfilerFrame { +		uint64_t frame_number = 0; +		Vector<RS::FrameProfileArea> areas; + +		Array serialize(); +		bool deserialize(const Array &p_arr); +	}; + +private: +	class ScriptsProfiler; +	class ServersProfiler; +	class VisualProfiler; + +	double last_draw_time = 0.0; +	Ref<ServersProfiler> servers_profiler; +	Ref<VisualProfiler> visual_profiler; + +	static ServersDebugger *singleton; + +	static Error _capture(void *p_user, const String &p_cmd, const Array &p_data, bool &r_captured); + +	void _send_resource_usage(); + +	ServersDebugger(); + +public: +	static void initialize(); +	static void deinitialize(); + +	~ServersDebugger(); +}; + +#endif // SERVERS_DEBUGGER_H diff --git a/servers/register_server_types.cpp b/servers/register_server_types.cpp index 3726bcde35..f405dea770 100644 --- a/servers/register_server_types.cpp +++ b/servers/register_server_types.cpp @@ -56,6 +56,7 @@  #include "camera/camera_feed.h"  #include "camera_server.h"  #include "core/extension/native_extension_manager.h" +#include "debugger/servers_debugger.h"  #include "display_server.h"  #include "navigation_server_2d.h"  #include "navigation_server_3d.h" @@ -223,6 +224,8 @@ void register_server_types() {  	GDREGISTER_CLASS(PhysicsTestMotionParameters3D);  	GDREGISTER_CLASS(PhysicsTestMotionResult3D); +	ServersDebugger::initialize(); +  	// Physics 2D  	GLOBAL_DEF(PhysicsServer2DManager::setting_property_name, "DEFAULT");  	ProjectSettings::get_singleton()->set_custom_property_info(PhysicsServer2DManager::setting_property_name, PropertyInfo(Variant::STRING, PhysicsServer2DManager::setting_property_name, PROPERTY_HINT_ENUM, "DEFAULT")); @@ -241,6 +244,8 @@ void register_server_types() {  }  void unregister_server_types() { +	ServersDebugger::deinitialize(); +  	NativeExtensionManager::get_singleton()->deinitialize_extensions(NativeExtension::INITIALIZATION_LEVEL_SERVERS);  	memdelete(shader_types); diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp index ead196b7dd..91201b2028 100644 --- a/servers/rendering/shader_language.cpp +++ b/servers/rendering/shader_language.cpp @@ -7871,7 +7871,7 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct  					if (is_sampler_type(type)) {  						if (uniform_scope == ShaderNode::Uniform::SCOPE_INSTANCE) { -							_set_error(vformat(RTR("Uniforms with '%s' qualifiers can't be of sampler type.", "instance"))); +							_set_error(vformat(RTR("The '%s' qualifier is not supported for sampler types."), "SCOPE_INSTANCE"));  							return ERR_PARSE_ERROR;  						}  						uniform2.texture_order = texture_uniforms++; @@ -7887,7 +7887,7 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct  						}  					} else {  						if (uniform_scope == ShaderNode::Uniform::SCOPE_INSTANCE && (type == TYPE_MAT2 || type == TYPE_MAT3 || type == TYPE_MAT4)) { -							_set_error(vformat(RTR("Uniforms with '%s' qualifier can't be of matrix type.", "instance"))); +							_set_error(vformat(RTR("The '%s' qualifier is not supported for matrix types."), "SCOPE_INSTANCE"));  							return ERR_PARSE_ERROR;  						}  						uniform2.texture_order = -1; diff --git a/thirdparty/README.md b/thirdparty/README.md index 34c33c3b56..f467d6a64b 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -206,7 +206,7 @@ Files extracted from upstream source:  ## harfbuzz  - Upstream: https://github.com/harfbuzz/harfbuzz -- Version: 3.3.1 (45df259538c204540819d74456d30ffb40df488a, 2022) +- Version: 3.3.2 (ac46c3248e8b0316235943175c4d4a11c24dd4a9, 2022)  - License: MIT  Files extracted from upstream source: @@ -465,7 +465,7 @@ Collection of single-file libraries used in Godot components.  ## msdfgen  - Upstream: https://github.com/Chlumsky/msdfgen -- Version: 1.9.1 (1b3b6b985094e6f12751177490add3ad11dd91a9, 2010) +- Version: 1.9.2 (64a91eec3ca3787e6f78b4c99fcd3052ad3e37c0, 2021)  - License: MIT  Files extracted from the upstream source: diff --git a/thirdparty/harfbuzz/src/hb-buffer.hh b/thirdparty/harfbuzz/src/hb-buffer.hh index adf4aa2b6f..ac45f090a5 100644 --- a/thirdparty/harfbuzz/src/hb-buffer.hh +++ b/thirdparty/harfbuzz/src/hb-buffer.hh @@ -386,11 +386,14 @@ struct hb_buffer_t    HB_INTERNAL void delete_glyph (); -  void set_glyph_flags (hb_mask_t mask, -			unsigned start = 0, -			unsigned end = (unsigned) -1, -			bool interior = false, -			bool from_out_buffer = false) +  /* Adds glyph flags in mask to infos with clusters between start and end. +   * The start index will be from out-buffer if from_out_buffer is true. +   * If interior is true, then the cluster having the minimum value is skipped. */ +  void _set_glyph_flags (hb_mask_t mask, +			 unsigned start = 0, +			 unsigned end = (unsigned) -1, +			 bool interior = false, +			 bool from_out_buffer = false)    {      end = hb_min (end, len); @@ -437,27 +440,27 @@ struct hb_buffer_t    void unsafe_to_break (unsigned int start = 0, unsigned int end = -1)    { -    set_glyph_flags (HB_GLYPH_FLAG_UNSAFE_TO_BREAK | HB_GLYPH_FLAG_UNSAFE_TO_CONCAT, -		     start, end, -		     true); +    _set_glyph_flags (HB_GLYPH_FLAG_UNSAFE_TO_BREAK | HB_GLYPH_FLAG_UNSAFE_TO_CONCAT, +		      start, end, +		      true);    }    void unsafe_to_concat (unsigned int start = 0, unsigned int end = -1)    { -    set_glyph_flags (HB_GLYPH_FLAG_UNSAFE_TO_CONCAT, -		     start, end, -		     true); +    _set_glyph_flags (HB_GLYPH_FLAG_UNSAFE_TO_CONCAT, +		      start, end, +		      true);    }    void unsafe_to_break_from_outbuffer (unsigned int start = 0, unsigned int end = -1)    { -    set_glyph_flags (HB_GLYPH_FLAG_UNSAFE_TO_BREAK | HB_GLYPH_FLAG_UNSAFE_TO_CONCAT, -		     start, end, -		     true, true); +    _set_glyph_flags (HB_GLYPH_FLAG_UNSAFE_TO_BREAK | HB_GLYPH_FLAG_UNSAFE_TO_CONCAT, +		      start, end, +		      true, true);    }    void unsafe_to_concat_from_outbuffer (unsigned int start = 0, unsigned int end = -1)    { -    set_glyph_flags (HB_GLYPH_FLAG_UNSAFE_TO_CONCAT, -		     start, end, -		     false, true); +    _set_glyph_flags (HB_GLYPH_FLAG_UNSAFE_TO_CONCAT, +		      start, end, +		      false, true);    } diff --git a/thirdparty/harfbuzz/src/hb-ot-glyf-table.hh b/thirdparty/harfbuzz/src/hb-ot-glyf-table.hh index 9bac30fff3..87a7d800c1 100644 --- a/thirdparty/harfbuzz/src/hb-ot-glyf-table.hh +++ b/thirdparty/harfbuzz/src/hb-ot-glyf-table.hh @@ -917,7 +917,7 @@ struct glyf    struct accelerator_t    { -    accelerator_t (hb_face_t *face_) +    accelerator_t (hb_face_t *face)      {        short_offset = false;        num_glyphs = 0; @@ -930,7 +930,6 @@ struct glyf  #ifndef HB_NO_VERTICAL        vmtx = nullptr;  #endif -      face = face_;        const OT::head &head = *face->table.head;        if (head.indexToLocFormat > 1 || head.glyphDataFormat > 0)  	/* Unknown format.  Leave num_glyphs=0, that takes care of disabling us. */ @@ -1287,7 +1286,6 @@ struct glyf      unsigned int num_glyphs;      hb_blob_ptr_t<loca> loca_table;      hb_blob_ptr_t<glyf> glyf_table; -    hb_face_t *face;    };    struct SubsetGlyph diff --git a/thirdparty/harfbuzz/src/hb-ot-layout-gpos-table.hh b/thirdparty/harfbuzz/src/hb-ot-layout-gpos-table.hh index e28c951f3f..2f9186a2a7 100644 --- a/thirdparty/harfbuzz/src/hb-ot-layout-gpos-table.hh +++ b/thirdparty/harfbuzz/src/hb-ot-layout-gpos-table.hh @@ -1586,7 +1586,15 @@ struct PairPosFormat2      /* Isolate simple kerning and apply it half to each side. -     * Results in better cursor positinoing / underline drawing. */ +     * Results in better cursor positinoing / underline drawing. +     * +     * Disabled, because causes issues... :-( +     * https://github.com/harfbuzz/harfbuzz/issues/3408 +     * https://github.com/harfbuzz/harfbuzz/pull/3235#issuecomment-1029814978 +     */ +#ifndef HB_SPLIT_KERN +    if (0) +#endif      {        if (!len2)        { diff --git a/thirdparty/harfbuzz/src/hb-version.h b/thirdparty/harfbuzz/src/hb-version.h index 91ccb3dcde..493a09f8cf 100644 --- a/thirdparty/harfbuzz/src/hb-version.h +++ b/thirdparty/harfbuzz/src/hb-version.h @@ -53,14 +53,14 @@ HB_BEGIN_DECLS   *   * The micro component of the library version available at compile-time.   */ -#define HB_VERSION_MICRO 1 +#define HB_VERSION_MICRO 2  /**   * HB_VERSION_STRING:   *   * A string literal containing the library version available at compile-time.   */ -#define HB_VERSION_STRING "3.3.1" +#define HB_VERSION_STRING "3.3.2"  /**   * HB_VERSION_ATLEAST: diff --git a/thirdparty/harfbuzz/src/hb.hh b/thirdparty/harfbuzz/src/hb.hh index 1f14267525..b9f5f71415 100644 --- a/thirdparty/harfbuzz/src/hb.hh +++ b/thirdparty/harfbuzz/src/hb.hh @@ -447,6 +447,7 @@ static int HB_UNUSED _hb_errno = 0;  #ifndef HB_USE_ATEXIT  #  define HB_USE_ATEXIT 0  #endif +#ifndef hb_atexit  #if !HB_USE_ATEXIT  #  define hb_atexit(_) HB_STMT_START { if (0) (_) (); } HB_STMT_END  #else /* HB_USE_ATEXIT */ @@ -457,6 +458,7 @@ static int HB_UNUSED _hb_errno = 0;  #    define hb_atexit(f) static hb_atexit_t<f> _hb_atexit_##__LINE__;  #  endif  #endif +#endif  /* Lets assert int types.  Saves trouble down the road. */  static_assert ((sizeof (hb_codepoint_t) == 4), ""); diff --git a/thirdparty/msdfgen/core/edge-coloring.cpp b/thirdparty/msdfgen/core/edge-coloring.cpp index 370f9aa38d..914f1769fd 100644 --- a/thirdparty/msdfgen/core/edge-coloring.cpp +++ b/thirdparty/msdfgen/core/edge-coloring.cpp @@ -473,7 +473,7 @@ void edgeColoringByDistance(Shape &shape, double angleThreshold, unsigned long l          edgeMatrix[i] = &edgeMatrixStorage[i*splineCount];      int nextEdge = 0;      for (; nextEdge < graphEdgeCount && !*graphEdgeDistances[nextEdge]; ++nextEdge) { -        int elem = graphEdgeDistances[nextEdge]-distanceMatrixBase; +        int elem = (int) (graphEdgeDistances[nextEdge]-distanceMatrixBase);          int row = elem/splineCount;          int col = elem%splineCount;          edgeMatrix[row][col] = 1; @@ -483,7 +483,7 @@ void edgeColoringByDistance(Shape &shape, double angleThreshold, unsigned long l      std::vector<int> coloring(2*splineCount);      colorSecondDegreeGraph(&coloring[0], &edgeMatrix[0], splineCount, seed);      for (; nextEdge < graphEdgeCount; ++nextEdge) { -        int elem = graphEdgeDistances[nextEdge]-distanceMatrixBase; +        int elem = (int) (graphEdgeDistances[nextEdge]-distanceMatrixBase);          tryAddEdge(&coloring[0], &edgeMatrix[0], splineCount, elem/splineCount, elem%splineCount, &coloring[splineCount]);      } diff --git a/thirdparty/msdfgen/core/equation-solver.cpp b/thirdparty/msdfgen/core/equation-solver.cpp index fbe906428b..4144fa3340 100644 --- a/thirdparty/msdfgen/core/equation-solver.cpp +++ b/thirdparty/msdfgen/core/equation-solver.cpp @@ -4,17 +4,15 @@  #define _USE_MATH_DEFINES  #include <cmath> -#define TOO_LARGE_RATIO 1e12 -  namespace msdfgen {  int solveQuadratic(double x[2], double a, double b, double c) { -    // a = 0 -> linear equation -    if (a == 0 || fabs(b)+fabs(c) > TOO_LARGE_RATIO*fabs(a)) { -        // a, b = 0 -> no solution -        if (b == 0 || fabs(c) > TOO_LARGE_RATIO*fabs(b)) { +    // a == 0 -> linear equation +    if (a == 0 || fabs(b) > 1e12*fabs(a)) { +        // a == 0, b == 0 -> no solution +        if (b == 0) {              if (c == 0) -                return -1; // 0 = 0 +                return -1; // 0 == 0              return 0;          }          x[0] = -c/b; @@ -35,41 +33,38 @@ int solveQuadratic(double x[2], double a, double b, double c) {  static int solveCubicNormed(double x[3], double a, double b, double c) {      double a2 = a*a; -    double q  = (a2 - 3*b)/9;  -    double r  = (a*(2*a2-9*b) + 27*c)/54; +    double q = 1/9.*(a2-3*b); +    double r = 1/54.*(a*(2*a2-9*b)+27*c);      double r2 = r*r;      double q3 = q*q*q; -    double A, B; +    a *= 1/3.;      if (r2 < q3) {          double t = r/sqrt(q3);          if (t < -1) t = -1;          if (t > 1) t = 1;          t = acos(t); -        a /= 3; q = -2*sqrt(q); -        x[0] = q*cos(t/3)-a; -        x[1] = q*cos((t+2*M_PI)/3)-a; -        x[2] = q*cos((t-2*M_PI)/3)-a; +        q = -2*sqrt(q); +        x[0] = q*cos(1/3.*t)-a; +        x[1] = q*cos(1/3.*(t+2*M_PI))-a; +        x[2] = q*cos(1/3.*(t-2*M_PI))-a;          return 3;      } else { -        A = -pow(fabs(r)+sqrt(r2-q3), 1/3.);  -        if (r < 0) A = -A; -        B = A == 0 ? 0 : q/A; -        a /= 3; -        x[0] = (A+B)-a; -        x[1] = -0.5*(A+B)-a; -        x[2] = 0.5*sqrt(3.)*(A-B); -        if (fabs(x[2]) < 1e-14) +        double u = (r < 0 ? 1 : -1)*pow(fabs(r)+sqrt(r2-q3), 1/3.);  +        double v = u == 0 ? 0 : q/u; +        x[0] = (u+v)-a; +        if (u == v || fabs(u-v) < 1e-12*fabs(u+v)) { +            x[1] = -.5*(u+v)-a;              return 2; +        }          return 1;      }  }  int solveCubic(double x[3], double a, double b, double c, double d) {      if (a != 0) { -        double bn = b/a, cn = c/a, dn = d/a; -        // Check that a isn't "almost zero" -        if (fabs(bn) < TOO_LARGE_RATIO && fabs(cn) < TOO_LARGE_RATIO && fabs(dn) < TOO_LARGE_RATIO) -            return solveCubicNormed(x, bn, cn, dn); +        double bn = b/a; +        if (fabs(bn) < 1e6) // Above this ratio, the numerical error gets larger than if we treated a as zero +            return solveCubicNormed(x, bn, c/a, d/a);      }      return solveQuadratic(x, b, c, d);  } |