diff options
176 files changed, 3766 insertions, 1202 deletions
diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index 4d2006d42a..473e4a0a89 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -82,7 +82,7 @@ String ProjectSettings::get_imported_files_path() const { // Returns the features that a project must have when opened with this build of Godot. // This is used by the project manager to provide the initial_settings for config/features. const PackedStringArray ProjectSettings::get_required_features() { - PackedStringArray features = PackedStringArray(); + PackedStringArray features; features.append(VERSION_BRANCH); #ifdef REAL_T_IS_DOUBLE features.append("Double Precision"); @@ -115,7 +115,7 @@ const PackedStringArray ProjectSettings::_get_supported_features() { // Returns the features that this project needs but this build of Godot lacks. const PackedStringArray ProjectSettings::get_unsupported_features(const PackedStringArray &p_project_features) { - PackedStringArray unsupported_features = PackedStringArray(); + PackedStringArray unsupported_features; PackedStringArray supported_features = singleton->_get_supported_features(); for (int i = 0; i < p_project_features.size(); i++) { if (!supported_features.has(p_project_features[i])) { @@ -1251,7 +1251,7 @@ ProjectSettings::ProjectSettings() { GLOBAL_DEF_RST("audio/general/3d_panning_strength", 1.0f); custom_prop_info["audio/general/3d_panning_strength"] = PropertyInfo(Variant::FLOAT, "audio/general/3d_panning_strength", PROPERTY_HINT_RANGE, "0,4,0.01"); - PackedStringArray extensions = PackedStringArray(); + PackedStringArray extensions; extensions.push_back("gd"); if (Engine::get_singleton()->has_singleton("GodotSharp")) { extensions.push_back("cs"); diff --git a/core/debugger/remote_debugger.cpp b/core/debugger/remote_debugger.cpp index 23ee977df4..a2e3fab82c 100644 --- a/core/debugger/remote_debugger.cpp +++ b/core/debugger/remote_debugger.cpp @@ -39,89 +39,6 @@ #include "core/object/script_language.h" #include "core/os/os.h" -class RemoteDebugger::MultiplayerProfiler : public EngineProfiler { - struct BandwidthFrame { - uint32_t timestamp; - int packet_size; - }; - - int bandwidth_in_ptr = 0; - Vector<BandwidthFrame> bandwidth_in; - int bandwidth_out_ptr = 0; - Vector<BandwidthFrame> bandwidth_out; - uint64_t last_bandwidth_time = 0; - - int bandwidth_usage(const Vector<BandwidthFrame> &p_buffer, int p_pointer) { - ERR_FAIL_COND_V(p_buffer.size() == 0, 0); - int total_bandwidth = 0; - - uint64_t timestamp = OS::get_singleton()->get_ticks_msec(); - uint64_t final_timestamp = timestamp - 1000; - - int i = (p_pointer + p_buffer.size() - 1) % p_buffer.size(); - - while (i != p_pointer && p_buffer[i].packet_size > 0) { - if (p_buffer[i].timestamp < final_timestamp) { - return total_bandwidth; - } - total_bandwidth += p_buffer[i].packet_size; - i = (i + p_buffer.size() - 1) % p_buffer.size(); - } - - ERR_FAIL_COND_V_MSG(i == p_pointer, total_bandwidth, "Reached the end of the bandwidth profiler buffer, values might be inaccurate."); - return total_bandwidth; - } - -public: - void toggle(bool p_enable, const Array &p_opts) { - if (!p_enable) { - bandwidth_in.clear(); - bandwidth_out.clear(); - } else { - bandwidth_in_ptr = 0; - bandwidth_in.resize(16384); // ~128kB - for (int i = 0; i < bandwidth_in.size(); ++i) { - bandwidth_in.write[i].packet_size = -1; - } - bandwidth_out_ptr = 0; - bandwidth_out.resize(16384); // ~128kB - for (int i = 0; i < bandwidth_out.size(); ++i) { - bandwidth_out.write[i].packet_size = -1; - } - } - } - - void add(const Array &p_data) { - 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(); - } - } - - void tick(double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time) { - uint64_t pt = OS::get_singleton()->get_ticks_msec(); - if (pt - last_bandwidth_time > 200) { - last_bandwidth_time = pt; - int incoming_bandwidth = bandwidth_usage(bandwidth_in, bandwidth_in_ptr); - int outgoing_bandwidth = bandwidth_usage(bandwidth_out, bandwidth_out_ptr); - - Array arr; - arr.push_back(incoming_bandwidth); - arr.push_back(outgoing_bandwidth); - EngineDebugger::get_singleton()->send_message("multiplayer:bandwidth", arr); - } - } -}; - class RemoteDebugger::PerformanceProfiler : public EngineProfiler { Object *performance = nullptr; int last_perf_time = 0; @@ -659,10 +576,6 @@ 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"); - // Multiplayer Profiler - multiplayer_profiler.instantiate(); - multiplayer_profiler->bind("multiplayer"); - // Performance Profiler Object *perf = Engine::get_singleton()->get_singleton_object("Performance"); if (perf) { diff --git a/core/debugger/remote_debugger.h b/core/debugger/remote_debugger.h index fe4bbe86ea..944229d361 100644 --- a/core/debugger/remote_debugger.h +++ b/core/debugger/remote_debugger.h @@ -50,10 +50,8 @@ public: private: typedef DebuggerMarshalls::OutputError ErrorMessage; - class MultiplayerProfiler; class PerformanceProfiler; - Ref<MultiplayerProfiler> multiplayer_profiler; Ref<PerformanceProfiler> performance_profiler; Ref<RemoteDebuggerPeer> peer; diff --git a/core/input/input_event.cpp b/core/input/input_event.cpp index 9dc071de9c..0a32b4bf68 100644 --- a/core/input/input_event.cpp +++ b/core/input/input_event.cpp @@ -1562,7 +1562,7 @@ String InputEventMIDI::as_text() const { } String InputEventMIDI::to_string() { - return vformat("InputEventMIDI: channel=%d, message=%d, pitch=%d, velocity=%d, pressure=%d", channel, message, pitch, velocity, pressure); + return vformat("InputEventMIDI: channel=%d, message=%d, pitch=%d, velocity=%d, pressure=%d, controller_number=%d, controller_value=%d", channel, message, pitch, velocity, pressure, controller_number, controller_value); } void InputEventMIDI::_bind_methods() { diff --git a/core/io/xml_parser.cpp b/core/io/xml_parser.cpp index abae48fdd8..16b7f3a1f6 100644 --- a/core/io/xml_parser.cpp +++ b/core/io/xml_parser.cpp @@ -41,11 +41,11 @@ static inline bool _is_white_space(char c) { } //! sets the state that text was found. Returns true if set should be set -bool XMLParser::_set_text(char *start, char *end) { +bool XMLParser::_set_text(const char *start, const char *end) { // check if text is more than 2 characters, and if not, check if there is // only white space, so that this text won't be reported if (end - start < 3) { - char *p = start; + const char *p = start; for (; p != end; ++p) { if (!_is_white_space(*p)) { break; @@ -92,7 +92,7 @@ void XMLParser::_parse_closing_xml_element() { void XMLParser::_ignore_definition() { node_type = NODE_UNKNOWN; - char *F = P; + const char *F = P; // move until end marked with '>' reached while (*P && *P != '>') { next_char(); @@ -123,8 +123,8 @@ bool XMLParser::_parse_cdata() { return true; } - char *cDataBegin = P; - char *cDataEnd = nullptr; + const char *cDataBegin = P; + const char *cDataEnd = nullptr; // find end of CDATA while (*P && !cDataEnd) { @@ -152,9 +152,9 @@ void XMLParser::_parse_comment() { node_type = NODE_COMMENT; P += 1; - char *pEndOfInput = data + length; - char *pCommentBegin; - char *pCommentEnd; + const char *pEndOfInput = data + length; + const char *pCommentBegin; + const char *pCommentEnd; if (P + 1 < pEndOfInput && P[0] == '-' && P[1] == '-') { // Comment, use '-->' as end. @@ -293,7 +293,7 @@ void XMLParser::_parse_opening_xml_element() { } void XMLParser::_parse_current_node() { - char *start = P; + const char *start = P; node_offset = P - data; // more forward until '<' found @@ -458,15 +458,36 @@ bool XMLParser::is_empty() const { Error XMLParser::open_buffer(const Vector<uint8_t> &p_buffer) { ERR_FAIL_COND_V(p_buffer.size() == 0, ERR_INVALID_DATA); - if (data) { - memdelete_arr(data); + if (data_copy) { + memdelete_arr(data_copy); + data_copy = nullptr; } length = p_buffer.size(); - data = memnew_arr(char, length + 1); - memcpy(data, p_buffer.ptr(), length); - data[length] = 0; + data_copy = memnew_arr(char, length + 1); + memcpy(data_copy, p_buffer.ptr(), length); + data_copy[length] = 0; + data = data_copy; + P = data; + current_line = 0; + + return OK; +} + +Error XMLParser::_open_buffer(const uint8_t *p_buffer, size_t p_size) { + ERR_FAIL_COND_V(p_size == 0, ERR_INVALID_DATA); + ERR_FAIL_COND_V(!p_buffer, ERR_INVALID_DATA); + + if (data_copy) { + memdelete_arr(data_copy); + data_copy = nullptr; + } + + length = p_size; + data = (const char *)p_buffer; P = data; + current_line = 0; + return OK; } @@ -479,13 +500,15 @@ Error XMLParser::open(const String &p_path) { length = file->get_length(); ERR_FAIL_COND_V(length < 1, ERR_FILE_CORRUPT); - if (data) { - memdelete_arr(data); + if (data_copy) { + memdelete_arr(data_copy); + data_copy = nullptr; } - data = memnew_arr(char, length + 1); - file->get_buffer((uint8_t *)data, length); - data[length] = 0; + data_copy = memnew_arr(char, length + 1); + file->get_buffer((uint8_t *)data_copy, length); + data_copy[length] = 0; + data = data_copy; P = data; current_line = 0; @@ -512,8 +535,9 @@ void XMLParser::skip_section() { } void XMLParser::close() { - if (data) { + if (data_copy) { memdelete_arr(data); + data_copy = nullptr; } data = nullptr; length = 0; @@ -528,7 +552,8 @@ int XMLParser::get_current_line() const { } XMLParser::~XMLParser() { - if (data) { - memdelete_arr(data); + if (data_copy) { + memdelete_arr(data_copy); + data_copy = nullptr; } } diff --git a/core/io/xml_parser.h b/core/io/xml_parser.h index aea252ddc7..be44f771dc 100644 --- a/core/io/xml_parser.h +++ b/core/io/xml_parser.h @@ -65,8 +65,9 @@ public: }; private: - char *data = nullptr; - char *P = nullptr; + char *data_copy = nullptr; + const char *data = nullptr; + const char *P = nullptr; uint64_t length = 0; uint64_t current_line = 0; String node_name; @@ -81,7 +82,7 @@ private: Vector<Attribute> attributes; - bool _set_text(char *start, char *end); + bool _set_text(const char *start, const char *end); void _parse_closing_xml_element(); void _ignore_definition(); bool _parse_cdata(); @@ -118,6 +119,7 @@ public: Error open(const String &p_path); Error open_buffer(const Vector<uint8_t> &p_buffer); + Error _open_buffer(const uint8_t *p_buffer, size_t p_size); void close(); diff --git a/core/math/expression.cpp b/core/math/expression.cpp index dcec3929fe..26b809e7f2 100644 --- a/core/math/expression.cpp +++ b/core/math/expression.cpp @@ -1420,7 +1420,7 @@ bool Expression::_execute(const Array &p_inputs, Object *p_instance, Expression: Callable::CallError ce; Variant::call_utility_function(bifunc->func, &r_ret, (const Variant **)argp.ptr(), argp.size(), ce); if (ce.error != Callable::CallError::CALL_OK) { - r_error_str = "Builtin Call Failed. " + Variant::get_call_error_text(bifunc->func, (const Variant **)argp.ptr(), argp.size(), ce); + r_error_str = "Builtin call failed: " + Variant::get_call_error_text(bifunc->func, (const Variant **)argp.ptr(), argp.size(), ce); return true; } diff --git a/core/object/undo_redo.cpp b/core/object/undo_redo.cpp index aa66e86bc0..9f8a1de697 100644 --- a/core/object/undo_redo.cpp +++ b/core/object/undo_redo.cpp @@ -423,6 +423,10 @@ String UndoRedo::get_current_action_name() const { return actions[current_action].name; } +int UndoRedo::get_action_level() const { + return action_level; +} + bool UndoRedo::has_undo() const { return current_action >= 0; } diff --git a/core/object/undo_redo.h b/core/object/undo_redo.h index c7c58697c3..9c6d2d10ed 100644 --- a/core/object/undo_redo.h +++ b/core/object/undo_redo.h @@ -120,6 +120,7 @@ public: bool redo(); bool undo(); String get_current_action_name() const; + int get_action_level() const; int get_history_count(); int get_current_action(); diff --git a/core/string/optimized_translation.cpp b/core/string/optimized_translation.cpp index b130c2fc79..2fb3b54e27 100644 --- a/core/string/optimized_translation.cpp +++ b/core/string/optimized_translation.cpp @@ -267,6 +267,39 @@ StringName OptimizedTranslation::get_message(const StringName &p_src_text, const } } +Vector<String> OptimizedTranslation::get_translated_message_list() const { + Vector<String> msgs; + + const int *htr = hash_table.ptr(); + const uint32_t *htptr = (const uint32_t *)&htr[0]; + const int *btr = bucket_table.ptr(); + const uint32_t *btptr = (const uint32_t *)&btr[0]; + const uint8_t *sr = strings.ptr(); + const char *sptr = (const char *)&sr[0]; + + for (int i = 0; i < hash_table.size(); i++) { + uint32_t p = htptr[i]; + if (p != 0xFFFFFFFF) { + const Bucket &bucket = *(const Bucket *)&btptr[p]; + for (int j = 0; j < bucket.size; j++) { + if (bucket.elem[j].comp_size == bucket.elem[j].uncomp_size) { + String rstr; + rstr.parse_utf8(&sptr[bucket.elem[j].str_offset], bucket.elem[j].uncomp_size); + msgs.push_back(rstr); + } else { + CharString uncomp; + uncomp.resize(bucket.elem[j].uncomp_size + 1); + smaz_decompress(&sptr[bucket.elem[j].str_offset], bucket.elem[j].comp_size, uncomp.ptrw(), bucket.elem[j].uncomp_size); + String rstr; + rstr.parse_utf8(uncomp.get_data()); + msgs.push_back(rstr); + } + } + } + } + return msgs; +} + StringName OptimizedTranslation::get_plural_message(const StringName &p_src_text, const StringName &p_plural_text, int p_n, const StringName &p_context) const { // The use of plurals translation is not yet supported in OptimizedTranslation. return get_message(p_src_text, p_context); diff --git a/core/string/optimized_translation.h b/core/string/optimized_translation.h index f3dbfe8f5c..1cd12782d0 100644 --- a/core/string/optimized_translation.h +++ b/core/string/optimized_translation.h @@ -81,6 +81,7 @@ protected: public: virtual StringName get_message(const StringName &p_src_text, const StringName &p_context = "") const override; //overridable for other implementations virtual StringName get_plural_message(const StringName &p_src_text, const StringName &p_plural_text, int p_n, const StringName &p_context = "") const override; + virtual Vector<String> get_translated_message_list() const override; void generate(const Ref<Translation> &p_from); OptimizedTranslation() {} diff --git a/core/string/translation.cpp b/core/string/translation.cpp index 2bed3543dc..d1ac91957a 100644 --- a/core/string/translation.cpp +++ b/core/string/translation.cpp @@ -59,6 +59,18 @@ Vector<String> Translation::_get_message_list() const { return msgs; } +Vector<String> Translation::get_translated_message_list() const { + Vector<String> msgs; + msgs.resize(translation_map.size()); + int idx = 0; + for (const KeyValue<StringName, StringName> &E : translation_map) { + msgs.set(idx, E.value); + idx += 1; + } + + return msgs; +} + void Translation::_set_messages(const Dictionary &p_messages) { List<Variant> keys; p_messages.get_key_list(&keys); @@ -140,6 +152,7 @@ void Translation::_bind_methods() { ClassDB::bind_method(D_METHOD("get_plural_message", "src_message", "src_plural_message", "n", "context"), &Translation::get_plural_message, DEFVAL("")); ClassDB::bind_method(D_METHOD("erase_message", "src_message", "context"), &Translation::erase_message, DEFVAL("")); ClassDB::bind_method(D_METHOD("get_message_list"), &Translation::_get_message_list); + ClassDB::bind_method(D_METHOD("get_translated_message_list"), &Translation::get_translated_message_list); ClassDB::bind_method(D_METHOD("get_message_count"), &Translation::get_message_count); ClassDB::bind_method(D_METHOD("_set_messages", "messages"), &Translation::_set_messages); ClassDB::bind_method(D_METHOD("_get_messages"), &Translation::_get_messages); diff --git a/core/string/translation.h b/core/string/translation.h index 9a369b0b05..5e8344baac 100644 --- a/core/string/translation.h +++ b/core/string/translation.h @@ -64,6 +64,7 @@ public: virtual void erase_message(const StringName &p_src_text, const StringName &p_context = ""); virtual void get_message_list(List<StringName> *r_messages) const; virtual int get_message_count() const; + virtual Vector<String> get_translated_message_list() const; Translation() {} }; diff --git a/core/string/translation_po.cpp b/core/string/translation_po.cpp index fa656b634d..724c1d42bc 100644 --- a/core/string/translation_po.cpp +++ b/core/string/translation_po.cpp @@ -103,6 +103,23 @@ void TranslationPO::_set_messages(const Dictionary &p_messages) { } } +Vector<String> TranslationPO::get_translated_message_list() const { + Vector<String> msgs; + for (const KeyValue<StringName, HashMap<StringName, Vector<StringName>>> &E : translation_map) { + if (E.key != StringName()) { + continue; + } + + for (const KeyValue<StringName, Vector<StringName>> &E2 : E.value) { + for (const StringName &E3 : E2.value) { + msgs.push_back(E3); + } + } + } + + return msgs; +} + Vector<String> TranslationPO::_get_message_list() const { // Return all keys in translation_map. diff --git a/core/string/translation_po.h b/core/string/translation_po.h index 7d63af2246..c50ea85744 100644 --- a/core/string/translation_po.h +++ b/core/string/translation_po.h @@ -70,6 +70,7 @@ protected: static void _bind_methods(); public: + Vector<String> get_translated_message_list() const override; void get_message_list(List<StringName> *r_messages) const override; int get_message_count() const override; void add_message(const StringName &p_src_text, const StringName &p_xlated_text, const StringName &p_context = "") override; diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp index 2ba389fc4d..8993f9667d 100644 --- a/core/string/ustring.cpp +++ b/core/string/ustring.cpp @@ -2839,7 +2839,7 @@ String String::substr(int p_from, int p_chars) const { return String(*this); } - String s = String(); + String s; s.copy_from_unchecked(&get_data()[p_from], p_chars); return s; } @@ -3922,7 +3922,6 @@ String String::c_unescape() const { escaped = escaped.replace("\\v", "\v"); escaped = escaped.replace("\\'", "\'"); escaped = escaped.replace("\\\"", "\""); - escaped = escaped.replace("\\?", "\?"); escaped = escaped.replace("\\\\", "\\"); return escaped; @@ -3939,7 +3938,6 @@ String String::c_escape() const { escaped = escaped.replace("\t", "\\t"); escaped = escaped.replace("\v", "\\v"); escaped = escaped.replace("\'", "\\'"); - escaped = escaped.replace("\?", "\\?"); escaped = escaped.replace("\"", "\\\""); return escaped; diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp index b4528e67d1..c1166a0a14 100644 --- a/core/variant/variant.cpp +++ b/core/variant/variant.cpp @@ -3609,16 +3609,16 @@ String Variant::get_call_error_text(Object *p_base, const StringName &p_method, if (ce.error == Callable::CallError::CALL_ERROR_INVALID_ARGUMENT) { int errorarg = ce.argument; if (p_argptrs) { - err_text = "Cannot convert argument " + itos(errorarg + 1) + " from " + Variant::get_type_name(p_argptrs[errorarg]->get_type()) + " to " + Variant::get_type_name(Variant::Type(ce.expected)) + "."; + err_text = "Cannot convert argument " + itos(errorarg + 1) + " from " + Variant::get_type_name(p_argptrs[errorarg]->get_type()) + " to " + Variant::get_type_name(Variant::Type(ce.expected)); } else { - err_text = "Cannot convert argument " + itos(errorarg + 1) + " from [missing argptr, type unknown] to " + Variant::get_type_name(Variant::Type(ce.expected)) + "."; + err_text = "Cannot convert argument " + itos(errorarg + 1) + " from [missing argptr, type unknown] to " + Variant::get_type_name(Variant::Type(ce.expected)); } } else if (ce.error == Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS) { - err_text = "Method expected " + itos(ce.argument) + " arguments, but called with " + itos(p_argcount) + "."; + err_text = "Method expected " + itos(ce.argument) + " arguments, but called with " + itos(p_argcount); } else if (ce.error == Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS) { - err_text = "Method expected " + itos(ce.argument) + " arguments, but called with " + itos(p_argcount) + "."; + err_text = "Method expected " + itos(ce.argument) + " arguments, but called with " + itos(p_argcount); } else if (ce.error == Callable::CallError::CALL_ERROR_INVALID_METHOD) { - err_text = "Method not found."; + err_text = "Method not found"; } else if (ce.error == Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL) { err_text = "Instance is null"; } else if (ce.error == Callable::CallError::CALL_ERROR_METHOD_NOT_CONST) { diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp index 91af2bab85..a231a956bf 100644 --- a/core/variant/variant_call.cpp +++ b/core/variant/variant_call.cpp @@ -2438,7 +2438,7 @@ static void _register_variant_builtin_methods() { _VariantCall::add_variant_constant(Variant::TRANSFORM2D, "FLIP_X", Transform2D(-1, 0, 0, 1, 0, 0)); _VariantCall::add_variant_constant(Variant::TRANSFORM2D, "FLIP_Y", Transform2D(1, 0, 0, -1, 0, 0)); - Transform3D identity_transform = Transform3D(); + Transform3D identity_transform; Transform3D flip_x_transform = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0); Transform3D flip_y_transform = Transform3D(1, 0, 0, 0, -1, 0, 0, 0, 1, 0, 0, 0); Transform3D flip_z_transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, -1, 0, 0, 0); @@ -2447,7 +2447,7 @@ static void _register_variant_builtin_methods() { _VariantCall::add_variant_constant(Variant::TRANSFORM3D, "FLIP_Y", flip_y_transform); _VariantCall::add_variant_constant(Variant::TRANSFORM3D, "FLIP_Z", flip_z_transform); - Basis identity_basis = Basis(); + Basis identity_basis; Basis flip_x_basis = Basis(-1, 0, 0, 0, 1, 0, 0, 0, 1); Basis flip_y_basis = Basis(1, 0, 0, 0, -1, 0, 0, 0, 1); Basis flip_z_basis = Basis(1, 0, 0, 0, 1, 0, 0, 0, -1); diff --git a/doc/classes/CharacterBody2D.xml b/doc/classes/CharacterBody2D.xml index 0111ef804e..7395556d05 100644 --- a/doc/classes/CharacterBody2D.xml +++ b/doc/classes/CharacterBody2D.xml @@ -10,7 +10,7 @@ </description> <tutorials> <link title="Kinematic character (2D)">$DOCS_URL/tutorials/physics/kinematic_character_2d.html</link> - <link title="Using KinematicBody2D">$DOCS_URL/tutorials/physics/using_kinematic_body_2d.html</link> + <link title="Using CharacterBody2D">$DOCS_URL/tutorials/physics/using_character_body_2d.html</link> <link title="2D Kinematic Character Demo">https://godotengine.org/asset-library/asset/113</link> <link title="2D Platformer Demo">https://godotengine.org/asset-library/asset/120</link> </tutorials> diff --git a/doc/classes/Control.xml b/doc/classes/Control.xml index e1dd2819b2..2159001a30 100644 --- a/doc/classes/Control.xml +++ b/doc/classes/Control.xml @@ -1048,7 +1048,7 @@ </member> <member name="scale" type="Vector2" setter="set_scale" getter="get_scale" default="Vector2(1, 1)"> The node's scale, relative to its [member size]. Change this property to scale the node around its [member pivot_offset]. The Control's [member tooltip_text] will also scale according to this value. - [b]Note:[/b] This property is mainly intended to be used for animation purposes. Text inside the Control will look pixelated or blurry when the Control is scaled. To support multiple resolutions in your project, use an appropriate viewport stretch mode as described in the [url=$DOCS_URL/tutorials/viewports/multiple_resolutions.html]documentation[/url] instead of scaling Controls individually. + [b]Note:[/b] This property is mainly intended to be used for animation purposes. Text inside the Control will look pixelated or blurry when the Control is scaled. To support multiple resolutions in your project, use an appropriate viewport stretch mode as described in the [url=$DOCS_URL/tutorials/rendering/multiple_resolutions.html]documentation[/url] instead of scaling Controls individually. [b]Note:[/b] If the Control node is a child of a [Container] node, the scale will be reset to [code]Vector2(1, 1)[/code] when the scene is instantiated. To set the Control's scale when it's instantiated, wait for one frame using [code]await get_tree().process_frame[/code] then set its [member scale] property. </member> <member name="shortcut_context" type="Node" setter="set_shortcut_context" getter="get_shortcut_context"> diff --git a/doc/classes/DirectionalLight2D.xml b/doc/classes/DirectionalLight2D.xml index a1b8ea86be..7a54980c19 100644 --- a/doc/classes/DirectionalLight2D.xml +++ b/doc/classes/DirectionalLight2D.xml @@ -1,8 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="DirectionalLight2D" inherits="Light2D" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> + Directional light from a distance. </brief_description> <description> + A directional light is a type of [Light2D] node that models an infinite number of parallel rays covering the entire scene. It is used for lights with strong intensity that are located far away from the scene (for example: to model sunlight or moonlight). </description> <tutorials> </tutorials> @@ -11,6 +13,7 @@ The height of the light. Used with 2D normal mapping. Ranges from 0 (parallel to the plane) to 1 (perpendicular to the plane). </member> <member name="max_distance" type="float" setter="set_max_distance" getter="get_max_distance" default="10000.0"> + Maximum distance this light covers. Increasing this value will make directional shadows visible from further away, at the cost of lower overall shadow detail and performance (due to more objects being included in shadow rendering). </member> </members> </class> diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml index 76c67fd704..0039301bf6 100644 --- a/doc/classes/DisplayServer.xml +++ b/doc/classes/DisplayServer.xml @@ -1638,8 +1638,9 @@ Regardless of the platform, enabling full screen will change the window size to match the monitor's size. Therefore, make sure your project supports [url=$DOCS_URL/tutorials/rendering/multiple_resolutions.html]multiple resolutions[/url] when enabling full screen mode. </constant> <constant name="WINDOW_MODE_EXCLUSIVE_FULLSCREEN" value="4" enum="WindowMode"> - Exclusive full screen window mode. This mode is implemented on Windows only. On other platforms, it is equivalent to [constant WINDOW_MODE_FULLSCREEN]. - Only one window in exclusive full screen mode can be visible on a given screen at a time. If multiple windows are in exclusive full screen mode for the same screen, the last one being set to this mode takes precedence. + Exclusive full screen window mode. This mode is implemented on Windows and macOS only. On other platforms, it is equivalent to [constant WINDOW_MODE_FULLSCREEN]. + [b]On Windows:[/b] Only one window in exclusive full screen mode can be visible on a given screen at a time. If multiple windows are in exclusive full screen mode for the same screen, the last one being set to this mode takes precedence. + [b]On macOS:[/b] Exclusive full-screen mode prevents Dock and Menu from showing up when the mouse pointer is hovering the edge of the screen. Regardless of the platform, enabling full screen will change the window size to match the monitor's size. Therefore, make sure your project supports [url=$DOCS_URL/tutorials/rendering/multiple_resolutions.html]multiple resolutions[/url] when enabling full screen mode. </constant> <constant name="WINDOW_FLAG_RESIZE_DISABLED" value="0" enum="WindowFlags"> diff --git a/doc/classes/EditorDebuggerPlugin.xml b/doc/classes/EditorDebuggerPlugin.xml index c3e0a995c6..10da1edd56 100644 --- a/doc/classes/EditorDebuggerPlugin.xml +++ b/doc/classes/EditorDebuggerPlugin.xml @@ -1,88 +1,88 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="EditorDebuggerPlugin" inherits="Control" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> +<class name="EditorDebuggerPlugin" inherits="RefCounted" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> A base class to implement debugger plugins. </brief_description> <description> [EditorDebuggerPlugin] provides functions related to the editor side of the debugger. - You don't need to instantiate this class; that is automatically handled by the debugger. [Control] nodes can be added as child nodes to provide a GUI for the plugin. - Do not free or reparent this node, otherwise it becomes unusable. - To use [EditorDebuggerPlugin], register it using the [method EditorPlugin.add_debugger_plugin] method first. + To interact with the debugger, an instance of this class must be added to the editor via [method EditorPlugin.add_debugger_plugin]. + Once added, the [method _setup_session] callback will be called for every [EditorDebuggerSession] available to the plugin, and when new ones are created (the sessions may be inactive during this stage). + You can retrieve the available [EditorDebuggerSession]s via [method get_sessions] or get a specific one via [method get_session]. + [codeblocks] + [gdscript] + @tool + extends EditorPlugin + + class ExampleEditorDebugger extends EditorDebuggerPlugin: + + func _has_capture(prefix): + # Return true if you wish to handle message with this prefix. + return prefix == "my_plugin" + + func _capture(message, data, session_id): + if message == "my_plugin:ping": + get_session(session_id).send_message("my_plugin:echo", data) + + func _setup_session(session_id): + # Add a new tab in the debugger session UI containing a label. + var label = Label.new() + label.name = "Example plugin" + label.text = "Example plugin" + var session = get_session(session_id) + # Listens to the session started and stopped signals. + session.started.connect(func (): print("Session started")) + session.stopped.connect(func (): print("Session stopped")) + session.add_session_tab(label) + + var debugger = ExampleEditorDebugger.new() + + func _enter_tree(): + add_debugger_plugin(debugger) + + func _exit_tree(): + remove_debugger_plugin(debugger) + [/gdscript] + [/codeblocks] </description> <tutorials> </tutorials> <methods> - <method name="has_capture"> - <return type="bool" /> - <param index="0" name="name" type="StringName" /> - <description> - Returns [code]true[/code] if a message capture with given name is present otherwise [code]false[/code]. - </description> - </method> - <method name="is_breaked"> - <return type="bool" /> - <description> - Returns [code]true[/code] if the game is in break state otherwise [code]false[/code]. - </description> - </method> - <method name="is_debuggable"> + <method name="_capture" qualifiers="virtual"> <return type="bool" /> + <param index="0" name="message" type="String" /> + <param index="1" name="data" type="Array" /> + <param index="2" name="session_id" type="int" /> <description> - Returns [code]true[/code] if the game can be debugged otherwise [code]false[/code]. + Override this method to process incoming messages. The [param session_id] is the ID of the [EditorDebuggerSession] that received the message (which you can retrieve via [method get_session]). </description> </method> - <method name="is_session_active"> + <method name="_has_capture" qualifiers="virtual const"> <return type="bool" /> + <param index="0" name="capture" type="String" /> <description> - Returns [code]true[/code] if there is an instance of the game running with the attached debugger otherwise [code]false[/code]. + Override this method to enable receiving messages from the debugger. If [param capture] is "my_message" then messages starting with "my_message:" will be passes to the [method _capture] method. </description> </method> - <method name="register_message_capture"> + <method name="_setup_session" qualifiers="virtual"> <return type="void" /> - <param index="0" name="name" type="StringName" /> - <param index="1" name="callable" type="Callable" /> + <param index="0" name="session_id" type="int" /> <description> - Registers a message capture with given [param name]. If [param name] is "my_message" then messages starting with "my_message:" will be called with the given callable. - Callable must accept a message string and a data array as argument. If the message and data are valid then callable must return [code]true[/code] otherwise [code]false[/code]. + Override this method to be notified whenever a new [EditorDebuggerSession] is created (the session may be inactive during this stage). </description> </method> - <method name="send_message"> - <return type="void" /> - <param index="0" name="message" type="String" /> - <param index="1" name="data" type="Array" /> + <method name="get_session"> + <return type="EditorDebuggerSession" /> + <param index="0" name="id" type="int" /> <description> - Sends a message with given [param message] and [param data] array. + Returns the [EditorDebuggerSession] with the given [param id]. </description> </method> - <method name="unregister_message_capture"> - <return type="void" /> - <param index="0" name="name" type="StringName" /> + <method name="get_sessions"> + <return type="Array" /> <description> - Unregisters the message capture with given name. + Returns an array of [EditorDebuggerSession] currently available to this debugger plugin. + Note: Not sessions in the array may be inactive, check their state via [method EditorDebuggerSession.is_active] </description> </method> </methods> - <signals> - <signal name="breaked"> - <param index="0" name="can_debug" type="bool" /> - <description> - Emitted when the game enters a break state. - </description> - </signal> - <signal name="continued"> - <description> - Emitted when the game exists a break state. - </description> - </signal> - <signal name="started"> - <description> - Emitted when the debugging starts. - </description> - </signal> - <signal name="stopped"> - <description> - Emitted when the debugging stops. - </description> - </signal> - </signals> </class> diff --git a/doc/classes/EditorDebuggerSession.xml b/doc/classes/EditorDebuggerSession.xml new file mode 100644 index 0000000000..faf528c143 --- /dev/null +++ b/doc/classes/EditorDebuggerSession.xml @@ -0,0 +1,86 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="EditorDebuggerSession" inherits="RefCounted" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> + <brief_description> + A class to interact with the editor debugger. + </brief_description> + <description> + This class cannot be directly instantiated and must be retrieved via a [EditorDebuggerPlugin]. + You can add tabs to the session UI via [method add_session_tab], send messages via [method send_message], and toggle [EngineProfiler]s via [method toggle_profiler]. + </description> + <tutorials> + </tutorials> + <methods> + <method name="add_session_tab"> + <return type="void" /> + <param index="0" name="control" type="Control" /> + <description> + Adds the given [param control] to the debug session UI in the debugger bottom panel. + </description> + </method> + <method name="is_active"> + <return type="bool" /> + <description> + Returns [code]true[/code] if the debug session is currently attached to a remote instance. + </description> + </method> + <method name="is_breaked"> + <return type="bool" /> + <description> + Returns [code]true[/code] if the attached remote instance is currently in the debug loop. + </description> + </method> + <method name="is_debuggable"> + <return type="bool" /> + <description> + Returns [code]true[/code] if the attached remote instance can be debugged. + </description> + </method> + <method name="remove_session_tab"> + <return type="void" /> + <param index="0" name="control" type="Control" /> + <description> + Removes the given [param control] from the debug session UI in the debugger bottom panel. + </description> + </method> + <method name="send_message"> + <return type="void" /> + <param index="0" name="message" type="String" /> + <param index="1" name="data" type="Array" default="[]" /> + <description> + Sends the given [param message] to the attached remote instance, optionally passing additionally [param data]. See [EngineDebugger] for how to retrieve those messages. + </description> + </method> + <method name="toggle_profiler"> + <return type="void" /> + <param index="0" name="profiler" type="String" /> + <param index="1" name="enable" type="bool" /> + <param index="2" name="data" type="Array" default="[]" /> + <description> + Toggle the given [param profiler] on the attached remote instance, optionally passing additionally [param data]. See [EngineProfiler] for more details. + </description> + </method> + </methods> + <signals> + <signal name="breaked"> + <param index="0" name="can_debug" type="bool" /> + <description> + Emitted when the attached remote instance enters a break state. If [param can_debug] is [code]true[/code], the remote instance will enter the debug loop. + </description> + </signal> + <signal name="continued"> + <description> + Emitted when the attached remote instance exits a break state. + </description> + </signal> + <signal name="started"> + <description> + Emitted when a remote instance is attached to this session (i.e. the session becomes active). + </description> + </signal> + <signal name="stopped"> + <description> + Emitted when a remote instance is detached from this session (i.e. the session becomes inactive). + </description> + </signal> + </signals> +</class> diff --git a/doc/classes/EditorInterface.xml b/doc/classes/EditorInterface.xml index bb319cb5a3..5d4b83bc27 100644 --- a/doc/classes/EditorInterface.xml +++ b/doc/classes/EditorInterface.xml @@ -48,6 +48,12 @@ [b]Warning:[/b] Removing and freeing this node will render a part of the editor useless and may cause a crash. </description> </method> + <method name="get_current_directory" qualifiers="const"> + <return type="String" /> + <description> + Returns the current directory being viewed in the [FileSystemDock]. If a file is selected, its base directory will be returned using [method String.get_base_dir] instead. + </description> + </method> <method name="get_current_path" qualifiers="const"> <return type="String" /> <description> @@ -131,10 +137,10 @@ [b]Warning:[/b] Removing and freeing this node will render a part of the editor useless and may cause a crash. </description> </method> - <method name="get_selected_path" qualifiers="const"> - <return type="String" /> + <method name="get_selected_paths" qualifiers="const"> + <return type="PackedStringArray" /> <description> - Returns the path of the directory currently selected in the [FileSystemDock]. If a file is selected, its base directory will be returned using [method String.get_base_dir] instead. + Returns an array containing the paths of the currently selected files (and directories) in the [FileSystemDock]. </description> </method> <method name="get_selection"> diff --git a/doc/classes/EditorNode3DGizmoPlugin.xml b/doc/classes/EditorNode3DGizmoPlugin.xml index 24e1a2da7c..3ab13ec5c0 100644 --- a/doc/classes/EditorNode3DGizmoPlugin.xml +++ b/doc/classes/EditorNode3DGizmoPlugin.xml @@ -8,7 +8,7 @@ To use [EditorNode3DGizmoPlugin], register it using the [method EditorPlugin.add_node_3d_gizmo_plugin] method first. </description> <tutorials> - <link title="Node3D gizmo plugins">$DOCS_URL/tutorials/plugins/editor/spatial_gizmos.html</link> + <link title="Node3D gizmo plugins">$DOCS_URL/tutorials/plugins/editor/3d_gizmos.html</link> </tutorials> <methods> <method name="_can_be_hidden" qualifiers="virtual const"> diff --git a/doc/classes/EditorPlugin.xml b/doc/classes/EditorPlugin.xml index 2289a373bf..806588d100 100644 --- a/doc/classes/EditorPlugin.xml +++ b/doc/classes/EditorPlugin.xml @@ -415,7 +415,7 @@ </method> <method name="add_debugger_plugin"> <return type="void" /> - <param index="0" name="script" type="Script" /> + <param index="0" name="script" type="EditorDebuggerPlugin" /> <description> Adds a [Script] as debugger plugin to the Debugger. The script must extend [EditorDebuggerPlugin]. </description> @@ -599,7 +599,7 @@ </method> <method name="remove_debugger_plugin"> <return type="void" /> - <param index="0" name="script" type="Script" /> + <param index="0" name="script" type="EditorDebuggerPlugin" /> <description> Removes the debugger plugin with given script from the Debugger. </description> diff --git a/doc/classes/EditorUndoRedoManager.xml b/doc/classes/EditorUndoRedoManager.xml index c1e771d653..133ee9db0d 100644 --- a/doc/classes/EditorUndoRedoManager.xml +++ b/doc/classes/EditorUndoRedoManager.xml @@ -110,7 +110,7 @@ <signals> <signal name="history_changed"> <description> - Emitted when the list of actions in any history has changed, either when an action is commited or a history is cleared. + Emitted when the list of actions in any history has changed, either when an action is committed or a history is cleared. </description> </signal> <signal name="version_changed"> diff --git a/doc/classes/GeometryInstance3D.xml b/doc/classes/GeometryInstance3D.xml index 86d52ae9be..90a983d28b 100644 --- a/doc/classes/GeometryInstance3D.xml +++ b/doc/classes/GeometryInstance3D.xml @@ -13,6 +13,7 @@ <return type="Variant" /> <param index="0" name="name" type="StringName" /> <description> + Get the value of a shader parameter as set on this instance. </description> </method> <method name="set_custom_aabb"> @@ -27,6 +28,7 @@ <param index="0" name="name" type="StringName" /> <param index="1" name="value" type="Variant" /> <description> + Set the value of a shader parameter for this instance only. </description> </method> </methods> @@ -45,8 +47,11 @@ [b]Note:[/b] Lights' bake mode will also affect the global illumination rendering. See [member Light3D.light_bake_mode]. </member> <member name="ignore_occlusion_culling" type="bool" setter="set_ignore_occlusion_culling" getter="is_ignoring_occlusion_culling" default="false"> + If [code]true[/code], disables occlusion culling for this instance. Useful for gizmos that must be rendered even when occlusion culling is in use. </member> <member name="lod_bias" type="float" setter="set_lod_bias" getter="get_lod_bias" default="1.0"> + Changes how quickly the mesh transitions to a lower level of detail. A value of 0 will force the mesh to its lowest level of detail, a value of 1 will use the default settings, and larger values will keep the mesh in a higher level of detail at farther distances. + Useful for testing level of detail transitions in the editor. </member> <member name="material_overlay" type="Material" setter="set_material_overlay" getter="get_material_overlay"> The material overlay for the whole geometry. diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml index b83bff5d5e..f910112a76 100644 --- a/doc/classes/Node.xml +++ b/doc/classes/Node.xml @@ -606,7 +606,7 @@ <return type="void" /> <param index="0" name="group" type="StringName" /> <description> - Removes a node from a group. See notes in the description, and the group methods in [SceneTree]. + Removes a node from the [param group]. Does nothing if the node is not in the [param group]. See notes in the description, and the group methods in [SceneTree]. </description> </method> <method name="replace_by"> diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml index f4a5288481..ea9b83d2aa 100644 --- a/doc/classes/OS.xml +++ b/doc/classes/OS.xml @@ -467,7 +467,7 @@ <return type="bool" /> <param index="0" name="tag_name" type="String" /> <description> - Returns [code]true[/code] if the feature for the given feature tag is supported in the currently running instance, depending on the platform, build, etc. Can be used to check whether you're currently running a debug build, on a certain platform or arch, etc. Refer to the [url=$DOCS_URL/getting_started/workflow/export/feature_tags.html]Feature Tags[/url] documentation for more details. + Returns [code]true[/code] if the feature for the given feature tag is supported in the currently running instance, depending on the platform, build, etc. Can be used to check whether you're currently running a debug build, on a certain platform or arch, etc. Refer to the [url=$DOCS_URL/tutorials/export/feature_tags.html]Feature Tags[/url] documentation for more details. [b]Note:[/b] Tag names are case-sensitive. </description> </method> diff --git a/doc/classes/PrimitiveMesh.xml b/doc/classes/PrimitiveMesh.xml index 7a411b27ac..b1c8907d8e 100644 --- a/doc/classes/PrimitiveMesh.xml +++ b/doc/classes/PrimitiveMesh.xml @@ -34,6 +34,9 @@ </method> </methods> <members> + <member name="add_uv2" type="bool" setter="set_add_uv2" getter="get_add_uv2" default="false"> + If set, generates UV2 UV coordinates applying a padding using the [member uv2_padding] setting. UV2 is needed for lightmapping. + </member> <member name="custom_aabb" type="AABB" setter="set_custom_aabb" getter="get_custom_aabb" default="AABB(0, 0, 0, 0, 0, 0)"> Overrides the [AABB] with one defined by user for use with frustum culling. Especially useful to avoid unexpected culling when using a shader to offset vertices. </member> @@ -44,5 +47,8 @@ <member name="material" type="Material" setter="set_material" getter="get_material"> The current [Material] of the primitive mesh. </member> + <member name="uv2_padding" type="float" setter="set_uv2_padding" getter="get_uv2_padding" default="2.0"> + If [member add_uv2] is set, specifies the padding in pixels applied along seams of the mesh. If at generation the size of the lightmap texture can't be determined, the UVs are calculated assuming a texture size of 1024x1024. + </member> </members> </class> diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 4699131c41..8c88feac01 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -555,7 +555,7 @@ Position offset for tooltips, relative to the mouse cursor's hotspot. </member> <member name="display/window/dpi/allow_hidpi" type="bool" setter="" getter="" default="true"> - If [code]true[/code], allows HiDPI display on Windows, macOS, Android, iOS and Web. If [code]false[/code], the platform's low-DPI fallback will be used on HiDPI displays, which causes the window to be displayed in a blurry or pixelated manner (and can cause various window management bugs). Therefore, it is recommended to make your project scale to [url=$DOCS_URL/tutorials/viewports/multiple_resolutions.html]multiple resolutions[/url] instead of disabling this setting. + If [code]true[/code], allows HiDPI display on Windows, macOS, Android, iOS and Web. If [code]false[/code], the platform's low-DPI fallback will be used on HiDPI displays, which causes the window to be displayed in a blurry or pixelated manner (and can cause various window management bugs). Therefore, it is recommended to make your project scale to [url=$DOCS_URL/tutorials/rendering/multiple_resolutions.html]multiple resolutions[/url] instead of disabling this setting. [b]Note:[/b] This setting has no effect on Linux as DPI-awareness fallbacks are not supported there. </member> <member name="display/window/energy_saving/keep_screen_on" type="bool" setter="" getter="" default="true"> @@ -2016,6 +2016,9 @@ <member name="rendering/lightmapping/bake_quality/ultra_quality_ray_count" type="int" setter="" getter="" default="1024"> The number of rays to use for baking lightmaps with [LightmapGI] when [member LightmapGI.quality] is [constant LightmapGI.BAKE_QUALITY_ULTRA]. </member> + <member name="rendering/lightmapping/primitive_meshes/texel_size" type="float" setter="" getter="" default="0.2"> + The texel_size that is used to calculate the [member Mesh.lightmap_size_hint] on [PrimitiveMesh] resources if [member PrimitiveMesh.add_uv2] is enabled. + </member> <member name="rendering/lightmapping/probe_capture/update_speed" type="float" setter="" getter="" default="15"> The framerate-independent update speed when representing dynamic object lighting from [LightmapProbe]s. Higher values make dynamic object lighting update faster. Higher values can prevent fast-moving objects from having "outdated" indirect lighting displayed on them, at the cost of possible flickering when an object moves from a bright area to a shaded area. </member> diff --git a/doc/classes/String.xml b/doc/classes/String.xml index b0b4f74b46..999913b1ac 100644 --- a/doc/classes/String.xml +++ b/doc/classes/String.xml @@ -80,7 +80,7 @@ <method name="c_unescape" qualifiers="const"> <return type="String" /> <description> - Returns a copy of the string with escaped characters replaced by their meanings. Supported escape sequences are [code]\'[/code], [code]\"[/code], [code]\?[/code], [code]\\[/code], [code]\a[/code], [code]\b[/code], [code]\f[/code], [code]\n[/code], [code]\r[/code], [code]\t[/code], [code]\v[/code]. + Returns a copy of the string with escaped characters replaced by their meanings. Supported escape sequences are [code]\'[/code], [code]\"[/code], [code]\\[/code], [code]\a[/code], [code]\b[/code], [code]\f[/code], [code]\n[/code], [code]\r[/code], [code]\t[/code], [code]\v[/code]. [b]Note:[/b] Unlike the GDScript parser, this method doesn't support the [code]\uXXXX[/code] escape sequence. </description> </method> diff --git a/doc/classes/Translation.xml b/doc/classes/Translation.xml index 314be9adf8..ae2f8d8dff 100644 --- a/doc/classes/Translation.xml +++ b/doc/classes/Translation.xml @@ -88,6 +88,12 @@ The number [param n] is the number or quantity of the plural object. It will be used to guide the translation system to fetch the correct plural form for the selected language. </description> </method> + <method name="get_translated_message_list" qualifiers="const"> + <return type="PackedStringArray" /> + <description> + Returns all the messages (translated text). + </description> + </method> </methods> <members> <member name="locale" type="String" setter="set_locale" getter="get_locale" default=""en""> diff --git a/doc/classes/Tree.xml b/doc/classes/Tree.xml index 532f6703b2..bf79821e2d 100644 --- a/doc/classes/Tree.xml +++ b/doc/classes/Tree.xml @@ -293,6 +293,14 @@ Sets language code of column title used for line-breaking and text shaping algorithms, if left empty current locale is used instead. </description> </method> + <method name="set_selected"> + <return type="void" /> + <param index="0" name="item" type="TreeItem" /> + <param index="1" name="column" type="int" /> + <description> + Selects the specified [TreeItem] and column. + </description> + </method> </methods> <members> <member name="allow_reselect" type="bool" setter="set_allow_reselect" getter="get_allow_reselect" default="false"> diff --git a/doc/classes/XRInterfaceExtension.xml b/doc/classes/XRInterfaceExtension.xml index c6b215daa3..0fe54e947f 100644 --- a/doc/classes/XRInterfaceExtension.xml +++ b/doc/classes/XRInterfaceExtension.xml @@ -141,13 +141,6 @@ Returns [code]true[/code] if this interface has been initialized. </description> </method> - <method name="_notification" qualifiers="virtual"> - <return type="void" /> - <param index="0" name="what" type="int" /> - <description> - Informs the interface of an applicable system notification. - </description> - </method> <method name="_post_draw_viewport" qualifiers="virtual"> <return type="void" /> <param index="0" name="render_target" type="RID" /> diff --git a/drivers/gles3/rasterizer_canvas_gles3.h b/drivers/gles3/rasterizer_canvas_gles3.h index d672d05e14..b7f3a4b801 100644 --- a/drivers/gles3/rasterizer_canvas_gles3.h +++ b/drivers/gles3/rasterizer_canvas_gles3.h @@ -254,7 +254,7 @@ public: uint32_t start = 0; uint32_t instance_count = 0; - RID tex = RID(); + RID tex; RS::CanvasItemTextureFilter filter = RS::CANVAS_ITEM_TEXTURE_FILTER_MAX; RS::CanvasItemTextureRepeat repeat = RS::CANVAS_ITEM_TEXTURE_REPEAT_MAX; @@ -263,7 +263,7 @@ public: Item *clip = nullptr; - RID material = RID(); + RID material; GLES3::CanvasMaterialData *material_data = nullptr; CanvasShaderGLES3::ShaderVariant shader_variant = CanvasShaderGLES3::MODE_QUAD; @@ -303,7 +303,7 @@ public: bool using_directional_lights = false; - RID current_tex = RID(); + RID current_tex; RS::CanvasItemTextureFilter current_filter_mode = RS::CANVAS_ITEM_TEXTURE_FILTER_MAX; RS::CanvasItemTextureRepeat current_repeat_mode = RS::CANVAS_ITEM_TEXTURE_REPEAT_MAX; diff --git a/drivers/gles3/rasterizer_scene_gles3.h b/drivers/gles3/rasterizer_scene_gles3.h index 3a759425e2..4bd0a7188d 100644 --- a/drivers/gles3/rasterizer_scene_gles3.h +++ b/drivers/gles3/rasterizer_scene_gles3.h @@ -99,9 +99,9 @@ struct RenderDataGLES3 { Ref<RenderSceneBuffersGLES3> render_buffers; bool transparent_bg = false; - Transform3D cam_transform = Transform3D(); - Transform3D inv_cam_transform = Transform3D(); - Projection cam_projection = Projection(); + Transform3D cam_transform; + Transform3D inv_cam_transform; + Projection cam_projection; bool cam_orthogonal = false; // For stereo rendering @@ -115,9 +115,9 @@ struct RenderDataGLES3 { const PagedArray<RenderGeometryInstance *> *instances = nullptr; const PagedArray<RID> *lights = nullptr; const PagedArray<RID> *reflection_probes = nullptr; - RID environment = RID(); - RID camera_attributes = RID(); - RID reflection_probe = RID(); + RID environment; + RID camera_attributes; + RID reflection_probe; int reflection_probe_pass = 0; float lod_distance_multiplier = 0.0; diff --git a/drivers/gles3/storage/material_storage.cpp b/drivers/gles3/storage/material_storage.cpp index 3ea61e252b..aaf223df1d 100644 --- a/drivers/gles3/storage/material_storage.cpp +++ b/drivers/gles3/storage/material_storage.cpp @@ -3358,6 +3358,9 @@ void SceneShaderData::set_code(const String &p_code) { actions.usage_flag_pointers["ALPHA"] = &uses_alpha; actions.usage_flag_pointers["ALPHA_SCISSOR_THRESHOLD"] = &uses_alpha_clip; + // Use alpha clip pipeline for alpha hash/dither. + // This prevents sorting issues inherent to alpha blending and allows such materials to cast shadows. + actions.usage_flag_pointers["ALPHA_HASH_SCALE"] = &uses_alpha_clip; actions.render_mode_flags["depth_prepass_alpha"] = &uses_depth_pre_pass; actions.usage_flag_pointers["SSS_STRENGTH"] = &uses_sss; diff --git a/drivers/gles3/storage/texture_storage.h b/drivers/gles3/storage/texture_storage.h index 7714e72f62..c465576347 100644 --- a/drivers/gles3/storage/texture_storage.h +++ b/drivers/gles3/storage/texture_storage.h @@ -129,7 +129,7 @@ struct Texture { bool is_external = false; bool is_render_target = false; - RID proxy_to = RID(); + RID proxy_to; Vector<RID> proxies; String path; diff --git a/drivers/vulkan/rendering_device_vulkan.cpp b/drivers/vulkan/rendering_device_vulkan.cpp index 0ff4ca31e6..c7c5fadbeb 100644 --- a/drivers/vulkan/rendering_device_vulkan.cpp +++ b/drivers/vulkan/rendering_device_vulkan.cpp @@ -9186,7 +9186,7 @@ void RenderingDeviceVulkan::_free_pending_resources(int p_frame) { Texture *texture = &frames[p_frame].textures_to_dispose_of.front()->get(); if (texture->bound) { - WARN_PRINT("Deleted a texture while it was bound.."); + WARN_PRINT("Deleted a texture while it was bound."); } vkDestroyImageView(device, texture->view, nullptr); if (texture->owner.is_null()) { diff --git a/editor/action_map_editor.h b/editor/action_map_editor.h index d56ee6f9eb..ad9980c4ef 100644 --- a/editor/action_map_editor.h +++ b/editor/action_map_editor.h @@ -47,8 +47,8 @@ class ActionMapEditor : public Control { public: struct ActionInfo { - String name = String(); - Dictionary action = Dictionary(); + String name; + Dictionary action; Ref<Texture2D> icon = Ref<Texture2D>(); bool editable = true; @@ -67,8 +67,8 @@ private: // Storing which action/event is currently being edited in the InputEventConfigurationDialog. - Dictionary current_action = Dictionary(); - String current_action_name = String(); + Dictionary current_action; + String current_action_name; int current_action_event_index = -1; // Popups diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index 44ecda103e..0c8176a44b 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -3613,7 +3613,7 @@ void AnimationTrackEditor::_animation_track_remove_request(int p_track, Ref<Anim } int idx = p_track; if (idx >= 0 && idx < p_from_animation->get_track_count()) { - undo_redo->create_action(TTR("Remove Anim Track")); + undo_redo->create_action(TTR("Remove Anim Track"), UndoRedo::MERGE_DISABLE, p_from_animation.ptr()); // Remove corresponding reset tracks if they are no longer needed. AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player(); diff --git a/editor/audio_stream_preview.cpp b/editor/audio_stream_preview.cpp index b9e52ad7ad..b84e58125e 100644 --- a/editor/audio_stream_preview.cpp +++ b/editor/audio_stream_preview.cpp @@ -42,6 +42,10 @@ float AudioStreamPreview::get_max(float p_time, float p_time_next) const { } int max = preview.size() / 2; + if (max == 0) { + return 0; + } + int time_from = p_time / length * max; int time_to = p_time_next / length * max; time_from = CLAMP(time_from, 0, max - 1); @@ -69,6 +73,10 @@ float AudioStreamPreview::get_min(float p_time, float p_time_next) const { } int max = preview.size() / 2; + if (max == 0) { + return 0; + } + int time_from = p_time / length * max; int time_to = p_time_next / length * max; time_from = CLAMP(time_from, 0, max - 1); diff --git a/editor/debugger/editor_debugger_inspector.cpp b/editor/debugger/editor_debugger_inspector.cpp index 371aaf8617..c64f23aba0 100644 --- a/editor/debugger/editor_debugger_inspector.cpp +++ b/editor/debugger/editor_debugger_inspector.cpp @@ -230,7 +230,7 @@ void EditorDebuggerInspector::add_stack_variable(const Array &p_array) { Variant v = var.value; PropertyHint h = PROPERTY_HINT_NONE; - String hs = String(); + String hs; if (v.get_type() == Variant::OBJECT) { v = Object::cast_to<EncodedObjectAsID>(v)->get_object_id(); diff --git a/editor/debugger/editor_debugger_node.cpp b/editor/debugger/editor_debugger_node.cpp index 68aff328ed..49fbaa81bc 100644 --- a/editor/debugger/editor_debugger_node.cpp +++ b/editor/debugger/editor_debugger_node.cpp @@ -119,8 +119,8 @@ ScriptEditorDebugger *EditorDebuggerNode::_add_debugger() { } if (!debugger_plugins.is_empty()) { - for (const Ref<Script> &i : debugger_plugins) { - node->add_debugger_plugin(i); + for (Ref<EditorDebuggerPlugin> plugin : debugger_plugins) { + plugin->create_session(node); } } @@ -723,22 +723,36 @@ EditorDebuggerNode::CameraOverride EditorDebuggerNode::get_camera_override() { return camera_override; } -void EditorDebuggerNode::add_debugger_plugin(const Ref<Script> &p_script) { - ERR_FAIL_COND_MSG(debugger_plugins.has(p_script), "Debugger plugin already exists."); - ERR_FAIL_COND_MSG(p_script.is_null(), "Debugger plugin script is null"); - ERR_FAIL_COND_MSG(p_script->get_instance_base_type() == StringName(), "Debugger plugin script has error."); - ERR_FAIL_COND_MSG(String(p_script->get_instance_base_type()) != "EditorDebuggerPlugin", "Base type of debugger plugin is not 'EditorDebuggerPlugin'."); - ERR_FAIL_COND_MSG(!p_script->is_tool(), "Debugger plugin script is not in tool mode."); - debugger_plugins.insert(p_script); +void EditorDebuggerNode::add_debugger_plugin(const Ref<EditorDebuggerPlugin> &p_plugin) { + ERR_FAIL_COND_MSG(p_plugin.is_null(), "Debugger plugin is null."); + ERR_FAIL_COND_MSG(debugger_plugins.has(p_plugin), "Debugger plugin already exists."); + debugger_plugins.insert(p_plugin); + + Ref<EditorDebuggerPlugin> plugin = p_plugin; for (int i = 0; get_debugger(i); i++) { - get_debugger(i)->add_debugger_plugin(p_script); + plugin->create_session(get_debugger(i)); } } -void EditorDebuggerNode::remove_debugger_plugin(const Ref<Script> &p_script) { - ERR_FAIL_COND_MSG(!debugger_plugins.has(p_script), "Debugger plugin doesn't exists."); - debugger_plugins.erase(p_script); - for (int i = 0; get_debugger(i); i++) { - get_debugger(i)->remove_debugger_plugin(p_script); +void EditorDebuggerNode::remove_debugger_plugin(const Ref<EditorDebuggerPlugin> &p_plugin) { + ERR_FAIL_COND_MSG(p_plugin.is_null(), "Debugger plugin is null."); + ERR_FAIL_COND_MSG(!debugger_plugins.has(p_plugin), "Debugger plugin doesn't exists."); + debugger_plugins.erase(p_plugin); + Ref<EditorDebuggerPlugin>(p_plugin)->clear(); +} + +bool EditorDebuggerNode::plugins_capture(ScriptEditorDebugger *p_debugger, const String &p_message, const Array &p_data) { + int session_index = tabs->get_tab_idx_from_control(p_debugger); + ERR_FAIL_COND_V(session_index < 0, false); + int colon_index = p_message.find_char(':'); + ERR_FAIL_COND_V_MSG(colon_index < 1, false, "Invalid message received."); + + const String cap = p_message.substr(0, colon_index); + bool parsed = false; + for (Ref<EditorDebuggerPlugin> plugin : debugger_plugins) { + if (plugin->has_capture(cap)) { + parsed |= plugin->capture(p_message, p_data, session_index); + } } + return parsed; } diff --git a/editor/debugger/editor_debugger_node.h b/editor/debugger/editor_debugger_node.h index 305f18a652..259efeb47b 100644 --- a/editor/debugger/editor_debugger_node.h +++ b/editor/debugger/editor_debugger_node.h @@ -36,6 +36,7 @@ class Button; class DebugAdapterParser; +class EditorDebuggerPlugin; class EditorDebuggerTree; class EditorDebuggerRemoteObject; class MenuButton; @@ -113,7 +114,7 @@ private: CameraOverride camera_override = OVERRIDE_NONE; HashMap<Breakpoint, bool, Breakpoint> breakpoints; - HashSet<Ref<Script>> debugger_plugins; + HashSet<Ref<EditorDebuggerPlugin>> debugger_plugins; ScriptEditorDebugger *_add_debugger(); EditorDebuggerRemoteObject *get_inspected_remote_object(); @@ -205,8 +206,9 @@ public: Error start(const String &p_uri = "tcp://"); void stop(); - void add_debugger_plugin(const Ref<Script> &p_script); - void remove_debugger_plugin(const Ref<Script> &p_script); + bool plugins_capture(ScriptEditorDebugger *p_debugger, const String &p_message, const Array &p_data); + void add_debugger_plugin(const Ref<EditorDebuggerPlugin> &p_plugin); + void remove_debugger_plugin(const Ref<EditorDebuggerPlugin> &p_plugin); }; #endif // EDITOR_DEBUGGER_NODE_H diff --git a/editor/debugger/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp index f1f34b8ebb..fd77ad784a 100644 --- a/editor/debugger/script_editor_debugger.cpp +++ b/editor/debugger/script_editor_debugger.cpp @@ -37,7 +37,6 @@ #include "core/string/ustring.h" #include "core/version.h" #include "editor/debugger/debug_adapter/debug_adapter_protocol.h" -#include "editor/debugger/editor_network_profiler.h" #include "editor/debugger/editor_performance_profiler.h" #include "editor/debugger/editor_profiler.h" #include "editor/debugger/editor_visual_profiler.h" @@ -52,6 +51,7 @@ #include "editor/plugins/node_3d_editor_plugin.h" #include "main/performance.h" #include "scene/3d/camera_3d.h" +#include "scene/debugger/scene_debugger.h" #include "scene/gui/dialogs.h" #include "scene/gui/label.h" #include "scene/gui/line_edit.h" @@ -713,17 +713,6 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da profiler->add_frame_metric(metric, true); } - } 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 == "multiplayer:bandwidth") { - ERR_FAIL_COND(p_data.size() < 2); - network_profiler->set_bandwidth(p_data[0], p_data[1]); - } else if (p_msg == "request_quit") { emit_signal(SNAME("stop_requested")); _stop_and_notify(); @@ -741,22 +730,7 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da int colon_index = p_msg.find_char(':'); ERR_FAIL_COND_MSG(colon_index < 1, "Invalid message received"); - bool parsed = false; - const String cap = p_msg.substr(0, colon_index); - HashMap<StringName, Callable>::Iterator element = captures.find(cap); - if (element) { - Callable &c = element->value; - ERR_FAIL_COND_MSG(c.is_null(), "Invalid callable registered: " + cap); - Variant cmd = p_msg.substr(colon_index + 1), cmd_data = p_data; - const Variant *args[2] = { &cmd, &cmd_data }; - Variant retval; - Callable::CallError err; - c.callp(args, 2, retval, err); - ERR_FAIL_COND_MSG(err.error != Callable::CallError::CALL_OK, "Error calling 'capture' to callable: " + Variant::get_callable_error_text(c, args, 2, err)); - ERR_FAIL_COND_MSG(retval.get_type() != Variant::BOOL, "Error calling 'capture' to callable: " + String(c) + ". Return type is not bool."); - parsed = retval; - } - + bool parsed = EditorDebuggerNode::get_singleton()->plugins_capture(this, p_msg, p_data); if (!parsed) { WARN_PRINT("unknown message " + p_msg); } @@ -982,10 +956,6 @@ void ScriptEditorDebugger::_profiler_activate(bool p_enable, int p_type) { Array msg_data; msg_data.push_back(p_enable); switch (p_type) { - case PROFILER_NETWORK: - _put_msg("profiler:multiplayer", msg_data); - _put_msg("profiler:rpc", msg_data); - break; case PROFILER_VISUAL: _put_msg("profiler:visual", msg_data); break; @@ -1658,41 +1628,25 @@ void ScriptEditorDebugger::_bind_methods() { ADD_SIGNAL(MethodInfo("errors_cleared")); } -void ScriptEditorDebugger::add_debugger_plugin(const Ref<Script> &p_script) { - if (!debugger_plugins.has(p_script)) { - EditorDebuggerPlugin *plugin = memnew(EditorDebuggerPlugin()); - plugin->attach_debugger(this); - plugin->set_script(p_script); - tabs->add_child(plugin); - debugger_plugins.insert(p_script, plugin); - } +void ScriptEditorDebugger::add_debugger_tab(Control *p_control) { + tabs->add_child(p_control); } -void ScriptEditorDebugger::remove_debugger_plugin(const Ref<Script> &p_script) { - if (debugger_plugins.has(p_script)) { - tabs->remove_child(debugger_plugins[p_script]); - debugger_plugins[p_script]->detach_debugger(false); - memdelete(debugger_plugins[p_script]); - debugger_plugins.erase(p_script); - } +void ScriptEditorDebugger::remove_debugger_tab(Control *p_control) { + int idx = tabs->get_tab_idx_from_control(p_control); + ERR_FAIL_COND(idx < 0); + p_control->queue_free(); } void ScriptEditorDebugger::send_message(const String &p_message, const Array &p_args) { _put_msg(p_message, p_args); } -void ScriptEditorDebugger::register_message_capture(const StringName &p_name, const Callable &p_callable) { - ERR_FAIL_COND_MSG(has_capture(p_name), "Capture already registered: " + p_name); - captures.insert(p_name, p_callable); -} - -void ScriptEditorDebugger::unregister_message_capture(const StringName &p_name) { - ERR_FAIL_COND_MSG(!has_capture(p_name), "Capture not registered: " + p_name); - captures.erase(p_name); -} - -bool ScriptEditorDebugger::has_capture(const StringName &p_name) { - return captures.has(p_name); +void ScriptEditorDebugger::toggle_profiler(const String &p_profiler, bool p_enable, const Array &p_data) { + Array msg_data; + msg_data.push_back(p_enable); + msg_data.append_array(p_data); + _put_msg("profiler:" + p_profiler, msg_data); } ScriptEditorDebugger::ScriptEditorDebugger() { @@ -1904,13 +1858,6 @@ ScriptEditorDebugger::ScriptEditorDebugger() { visual_profiler->connect("enable_profiling", callable_mp(this, &ScriptEditorDebugger::_profiler_activate).bind(PROFILER_VISUAL)); } - { //network profiler - network_profiler = memnew(EditorNetworkProfiler); - network_profiler->set_name(TTR("Network Profiler")); - tabs->add_child(network_profiler); - network_profiler->connect("enable_profiling", callable_mp(this, &ScriptEditorDebugger::_profiler_activate).bind(PROFILER_NETWORK)); - } - { //monitors performance_profiler = memnew(EditorPerformanceProfiler); tabs->add_child(performance_profiler); diff --git a/editor/debugger/script_editor_debugger.h b/editor/debugger/script_editor_debugger.h index aa0a50ff03..22d6e48294 100644 --- a/editor/debugger/script_editor_debugger.h +++ b/editor/debugger/script_editor_debugger.h @@ -50,7 +50,6 @@ class ItemList; class EditorProfiler; class EditorFileDialog; class EditorVisualProfiler; -class EditorNetworkProfiler; class EditorPerformanceProfiler; class SceneDebuggerTree; class EditorDebuggerPlugin; @@ -72,7 +71,6 @@ private: }; enum ProfilerType { - PROFILER_NETWORK, PROFILER_VISUAL, PROFILER_SCRIPTS_SERVERS }; @@ -151,7 +149,6 @@ private: EditorProfiler *profiler = nullptr; EditorVisualProfiler *visual_profiler = nullptr; - EditorNetworkProfiler *network_profiler = nullptr; EditorPerformanceProfiler *performance_profiler = nullptr; OS::ProcessID remote_pid = 0; @@ -163,10 +160,6 @@ private: EditorDebuggerNode::CameraOverride camera_override; - HashMap<Ref<Script>, EditorDebuggerPlugin *> debugger_plugins; - - HashMap<StringName, Callable> captures; - void _stack_dump_frame_selected(); void _file_selected(const String &p_file); @@ -286,14 +279,11 @@ public: virtual Size2 get_minimum_size() const override; - void add_debugger_plugin(const Ref<Script> &p_script); - void remove_debugger_plugin(const Ref<Script> &p_script); + void add_debugger_tab(Control *p_control); + void remove_debugger_tab(Control *p_control); void send_message(const String &p_message, const Array &p_args); - - void register_message_capture(const StringName &p_name, const Callable &p_callable); - void unregister_message_capture(const StringName &p_name); - bool has_capture(const StringName &p_name); + void toggle_profiler(const String &p_profiler, bool p_enable, const Array &p_data); ScriptEditorDebugger(); ~ScriptEditorDebugger(); diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index 89398409a2..973e4acbcb 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -54,10 +54,14 @@ void EditorHelp::_update_theme() { qualifier_color = get_theme_color(SNAME("qualifier_color"), SNAME("EditorHelp")); type_color = get_theme_color(SNAME("type_color"), SNAME("EditorHelp")); + class_desc->add_theme_style_override("normal", get_theme_stylebox(SNAME("background"), SNAME("EditorHelp"))); + class_desc->add_theme_style_override("focus", get_theme_stylebox(SNAME("background"), SNAME("EditorHelp"))); class_desc->add_theme_color_override("selection_color", get_theme_color(SNAME("selection_color"), SNAME("EditorHelp"))); class_desc->add_theme_constant_override("line_separation", get_theme_constant(SNAME("line_separation"), SNAME("EditorHelp"))); class_desc->add_theme_constant_override("table_h_separation", get_theme_constant(SNAME("table_h_separation"), SNAME("EditorHelp"))); class_desc->add_theme_constant_override("table_v_separation", get_theme_constant(SNAME("table_v_separation"), SNAME("EditorHelp"))); + class_desc->add_theme_constant_override("text_highlight_h_padding", get_theme_constant(SNAME("text_highlight_h_padding"), SNAME("EditorHelp"))); + class_desc->add_theme_constant_override("text_highlight_v_padding", get_theme_constant(SNAME("text_highlight_v_padding"), SNAME("EditorHelp"))); doc_font = get_theme_font(SNAME("doc"), SNAME("EditorFonts")); doc_bold_font = get_theme_font(SNAME("doc_bold"), SNAME("EditorFonts")); @@ -1786,9 +1790,19 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control Ref<Font> doc_code_font = p_owner_node->get_theme_font(SNAME("doc_source"), SNAME("EditorFonts")); Ref<Font> doc_kbd_font = p_owner_node->get_theme_font(SNAME("doc_keyboard"), SNAME("EditorFonts")); - Color link_color = p_owner_node->get_theme_color(SNAME("link_color"), SNAME("EditorHelp")); - Color code_color = p_owner_node->get_theme_color(SNAME("code_color"), SNAME("EditorHelp")); - Color kbd_color = p_owner_node->get_theme_color(SNAME("kbd_color"), SNAME("EditorHelp")); + const Color type_color = p_owner_node->get_theme_color(SNAME("type_color"), SNAME("EditorHelp")); + const Color code_color = p_owner_node->get_theme_color(SNAME("code_color"), SNAME("EditorHelp")); + const Color kbd_color = p_owner_node->get_theme_color(SNAME("kbd_color"), SNAME("EditorHelp")); + const Color code_dark_color = Color(code_color, 0.8); + + const Color link_color = p_owner_node->get_theme_color(SNAME("link_color"), SNAME("EditorHelp")); + const Color link_method_color = p_owner_node->get_theme_color(SNAME("accent_color"), SNAME("Editor")); + const Color link_property_color = link_color.lerp(p_owner_node->get_theme_color(SNAME("accent_color"), SNAME("Editor")), 0.25); + const Color link_annotation_color = link_color.lerp(p_owner_node->get_theme_color(SNAME("accent_color"), SNAME("Editor")), 0.5); + + const Color code_bg_color = p_owner_node->get_theme_color(SNAME("code_bg_color"), SNAME("EditorHelp")); + const Color kbd_bg_color = p_owner_node->get_theme_color(SNAME("kbd_bg_color"), SNAME("EditorHelp")); + const Color param_bg_color = p_owner_node->get_theme_color(SNAME("param_bg_color"), SNAME("EditorHelp")); String bbcode = p_bbcode.dedent().replace("\t", "").replace("\r", "").strip_edges(); @@ -1921,14 +1935,21 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control const String link_tag = tag.substr(0, tag_end); const String link_target = tag.substr(tag_end + 1, tag.length()).lstrip(" "); - // Use monospace font with translucent colored background color to make clickable references + // Use monospace font to make clickable references // easier to distinguish from inline code and other text. p_rt->push_font(doc_code_font); - p_rt->push_color(link_color); - p_rt->push_bgcolor(code_color * Color(1, 1, 1, 0.15)); + + Color target_color = link_color; + if (link_tag == "method") { + target_color = link_method_color; + } else if (link_tag == "member" || link_tag == "signal" || link_tag == "theme property") { + target_color = link_property_color; + } else if (link_tag == "annotation") { + target_color = link_annotation_color; + } + p_rt->push_color(target_color); p_rt->push_meta("@" + link_tag + " " + link_target); - p_rt->add_text(link_target + (tag.begins_with("method ") ? "()" : "")); - p_rt->pop(); + p_rt->add_text(link_target + (link_tag == "method" ? "()" : "")); p_rt->pop(); p_rt->pop(); p_rt->pop(); @@ -1940,7 +1961,7 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control // Use monospace font with translucent background color to make code easier to distinguish from other text. p_rt->push_font(doc_code_font); - p_rt->push_bgcolor(Color(0.5, 0.5, 0.5, 0.15)); + p_rt->push_bgcolor(param_bg_color); p_rt->push_color(code_color); p_rt->add_text(param_name); p_rt->pop(); @@ -1951,17 +1972,15 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control } else if (doc->class_list.has(tag)) { // Class reference tag such as [Node2D] or [SceneTree]. - // Use monospace font with translucent colored background color to make clickable references + // Use monospace font to make clickable references // easier to distinguish from inline code and other text. p_rt->push_font(doc_code_font); - p_rt->push_color(link_color); - p_rt->push_bgcolor(code_color * Color(1, 1, 1, 0.15)); + p_rt->push_color(type_color); p_rt->push_meta("#" + tag); p_rt->add_text(tag); p_rt->pop(); p_rt->pop(); p_rt->pop(); - p_rt->pop(); pos = brk_end + 1; } else if (tag == "b") { @@ -1975,30 +1994,30 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag == "code") { - // Use monospace font with translucent background color to make code easier to distinguish from other text. + // Use monospace font with darkened background color to make code easier to distinguish from other text. p_rt->push_font(doc_code_font); - p_rt->push_bgcolor(Color(0.5, 0.5, 0.5, 0.15)); - p_rt->push_color(code_color); + p_rt->push_bgcolor(code_bg_color); + p_rt->push_color(code_color.lerp(p_owner_node->get_theme_color(SNAME("error_color"), SNAME("Editor")), 0.6)); code_tag = true; pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag == "codeblock") { - // Use monospace font with translucent background color to make code easier to distinguish from other text. + // Use monospace font with darkened background color to make code easier to distinguish from other text. // Use a single-column table with cell row background color instead of `[bgcolor]`. // This makes the background color highlight cover the entire block, rather than individual lines. p_rt->push_font(doc_code_font); p_rt->push_table(1); p_rt->push_cell(); - p_rt->set_cell_row_background_color(Color(0.5, 0.5, 0.5, 0.15), Color(0.5, 0.5, 0.5, 0.15)); + p_rt->set_cell_row_background_color(code_bg_color, Color(code_bg_color, 0.99)); p_rt->set_cell_padding(Rect2(10 * EDSCALE, 10 * EDSCALE, 10 * EDSCALE, 10 * EDSCALE)); - p_rt->push_color(code_color); + p_rt->push_color(code_dark_color); codeblock_tag = true; pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag == "kbd") { // Use keyboard font with custom color and background color. p_rt->push_font(doc_kbd_font); - p_rt->push_bgcolor(Color(0.5, 0.5, 0.5, 0.15)); + p_rt->push_bgcolor(kbd_bg_color); p_rt->push_color(kbd_color); code_tag = true; // Though not strictly a code tag, logic is similar. pos = brk_end + 1; diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 227f35597c..cbad9aa737 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -4174,6 +4174,7 @@ void EditorNode::register_editor_types() { GDREGISTER_CLASS(EditorScenePostImport); GDREGISTER_CLASS(EditorCommandPalette); GDREGISTER_CLASS(EditorDebuggerPlugin); + GDREGISTER_ABSTRACT_CLASS(EditorDebuggerSession); } void EditorNode::unregister_editor_types() { @@ -5762,7 +5763,7 @@ void EditorNode::_global_menu_new_window(const Variant &p_tag) { } void EditorNode::_dropped_files(const Vector<String> &p_files) { - String to_path = ProjectSettings::get_singleton()->globalize_path(FileSystemDock::get_singleton()->get_selected_path()); + String to_path = ProjectSettings::get_singleton()->globalize_path(FileSystemDock::get_singleton()->get_current_directory()); _add_dropped_files_recursive(p_files, to_path); diff --git a/editor/editor_plugin.cpp b/editor/editor_plugin.cpp index 683731f982..e11bc3c252 100644 --- a/editor/editor_plugin.cpp +++ b/editor/editor_plugin.cpp @@ -43,6 +43,7 @@ #include "editor/import/editor_import_plugin.h" #include "editor/import/resource_importer_scene.h" #include "editor/plugins/canvas_item_editor_plugin.h" +#include "editor/plugins/editor_debugger_plugin.h" #include "editor/plugins/node_3d_editor_plugin.h" #include "editor/plugins/script_editor_plugin.h" #include "editor/project_settings_editor.h" @@ -242,14 +243,18 @@ void EditorInterface::select_file(const String &p_file) { FileSystemDock::get_singleton()->select_file(p_file); } -String EditorInterface::get_selected_path() const { - return FileSystemDock::get_singleton()->get_selected_path(); +Vector<String> EditorInterface::get_selected_paths() const { + return FileSystemDock::get_singleton()->get_selected_paths(); } String EditorInterface::get_current_path() const { return FileSystemDock::get_singleton()->get_current_path(); } +String EditorInterface::get_current_directory() const { + return FileSystemDock::get_singleton()->get_current_directory(); +} + void EditorInterface::inspect_object(Object *p_obj, const String &p_for_property, bool p_inspector_only) { EditorNode::get_singleton()->push_item(p_obj, p_for_property, p_inspector_only); } @@ -367,8 +372,9 @@ void EditorInterface::_bind_methods() { ClassDB::bind_method(D_METHOD("get_editor_main_screen"), &EditorInterface::get_editor_main_screen); ClassDB::bind_method(D_METHOD("make_mesh_previews", "meshes", "preview_size"), &EditorInterface::_make_mesh_previews); ClassDB::bind_method(D_METHOD("select_file", "file"), &EditorInterface::select_file); - ClassDB::bind_method(D_METHOD("get_selected_path"), &EditorInterface::get_selected_path); + ClassDB::bind_method(D_METHOD("get_selected_paths"), &EditorInterface::get_selected_paths); ClassDB::bind_method(D_METHOD("get_current_path"), &EditorInterface::get_current_path); + ClassDB::bind_method(D_METHOD("get_current_directory"), &EditorInterface::get_current_directory); ClassDB::bind_method(D_METHOD("get_file_system_dock"), &EditorInterface::get_file_system_dock); ClassDB::bind_method(D_METHOD("get_editor_paths"), &EditorInterface::get_editor_paths); ClassDB::bind_method(D_METHOD("get_command_palette"), &EditorInterface::get_command_palette); @@ -841,12 +847,12 @@ ScriptCreateDialog *EditorPlugin::get_script_create_dialog() { return SceneTreeDock::get_singleton()->get_script_create_dialog(); } -void EditorPlugin::add_debugger_plugin(const Ref<Script> &p_script) { - EditorDebuggerNode::get_singleton()->add_debugger_plugin(p_script); +void EditorPlugin::add_debugger_plugin(const Ref<EditorDebuggerPlugin> &p_plugin) { + EditorDebuggerNode::get_singleton()->add_debugger_plugin(p_plugin); } -void EditorPlugin::remove_debugger_plugin(const Ref<Script> &p_script) { - EditorDebuggerNode::get_singleton()->remove_debugger_plugin(p_script); +void EditorPlugin::remove_debugger_plugin(const Ref<EditorDebuggerPlugin> &p_plugin) { + EditorDebuggerNode::get_singleton()->remove_debugger_plugin(p_plugin); } void EditorPlugin::_editor_project_settings_changed() { diff --git a/editor/editor_plugin.h b/editor/editor_plugin.h index 2d8f0c47cb..9c639164f2 100644 --- a/editor/editor_plugin.h +++ b/editor/editor_plugin.h @@ -39,6 +39,7 @@ class Node3D; class Button; class PopupMenu; class EditorCommandPalette; +class EditorDebuggerPlugin; class EditorExport; class EditorExportPlugin; class EditorFileSystem; @@ -93,8 +94,9 @@ public: EditorCommandPalette *get_command_palette() const; void select_file(const String &p_file); - String get_selected_path() const; + Vector<String> get_selected_paths() const; String get_current_path() const; + String get_current_directory() const; void inspect_object(Object *p_obj, const String &p_for_property = String(), bool p_inspector_only = false); @@ -302,8 +304,8 @@ public: void add_autoload_singleton(const String &p_name, const String &p_path); void remove_autoload_singleton(const String &p_name); - void add_debugger_plugin(const Ref<Script> &p_script); - void remove_debugger_plugin(const Ref<Script> &p_script); + void add_debugger_plugin(const Ref<EditorDebuggerPlugin> &p_plugin); + void remove_debugger_plugin(const Ref<EditorDebuggerPlugin> &p_plugin); void enable_plugin(); void disable_plugin(); diff --git a/editor/editor_properties.h b/editor/editor_properties.h index 20b130f57f..ad36e01544 100644 --- a/editor/editor_properties.h +++ b/editor/editor_properties.h @@ -639,7 +639,7 @@ class EditorPropertyQuaternion : public EditorProperty { EditorSpinSlider *euler[3]; Button *edit_button = nullptr; - Vector3 edit_euler = Vector3(); + Vector3 edit_euler; void _value_changed(double p_val, const String &p_name); void _edit_custom_value(); diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp index 17b5eb3314..692deb3beb 100644 --- a/editor/editor_themes.cpp +++ b/editor/editor_themes.cpp @@ -1491,6 +1491,11 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_stylebox("normal", "RichTextLabel", style_tree_bg); // Editor help. + Ref<StyleBoxFlat> style_editor_help = style_default->duplicate(); + style_editor_help->set_bg_color(dark_color_2); + style_editor_help->set_border_color(dark_color_3); + theme->set_stylebox("background", "EditorHelp", style_editor_help); + theme->set_color("title_color", "EditorHelp", accent_color); theme->set_color("headline_color", "EditorHelp", mono_color); theme->set_color("text_color", "EditorHelp", font_color); @@ -1503,9 +1508,14 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_color("link_color", "EditorHelp", accent_color.lerp(mono_color, 0.8)); theme->set_color("code_color", "EditorHelp", accent_color.lerp(mono_color, 0.6)); theme->set_color("kbd_color", "EditorHelp", accent_color.lerp(property_color, 0.6)); + theme->set_color("code_bg_color", "EditorHelp", dark_color_3); + theme->set_color("kbd_bg_color", "EditorHelp", dark_color_1); + theme->set_color("param_bg_color", "EditorHelp", dark_color_1); theme->set_constant("line_separation", "EditorHelp", Math::round(6 * EDSCALE)); theme->set_constant("table_h_separation", "EditorHelp", 16 * EDSCALE); theme->set_constant("table_v_separation", "EditorHelp", 6 * EDSCALE); + theme->set_constant("text_highlight_h_padding", "EditorHelp", 1 * EDSCALE); + theme->set_constant("text_highlight_v_padding", "EditorHelp", 2 * EDSCALE); // Panel theme->set_stylebox("panel", "Panel", make_flat_stylebox(dark_color_1, 6, 4, 6, 4, corner_width)); diff --git a/editor/editor_undo_redo_manager.cpp b/editor/editor_undo_redo_manager.cpp index 8dfd8e0528..4bfa9b686c 100644 --- a/editor/editor_undo_redo_manager.cpp +++ b/editor/editor_undo_redo_manager.cpp @@ -110,9 +110,14 @@ EditorUndoRedoManager::History &EditorUndoRedoManager::get_history_for_object(Ob } void EditorUndoRedoManager::create_action_for_history(const String &p_name, int p_history_id, UndoRedo::MergeMode p_mode) { - pending_action.action_name = p_name; - pending_action.timestamp = OS::get_singleton()->get_unix_time(); - pending_action.merge_mode = p_mode; + if (pending_action.history_id != INVALID_HISTORY) { + // Nested action. + p_history_id = pending_action.history_id; + } else { + pending_action.action_name = p_name; + pending_action.timestamp = OS::get_singleton()->get_unix_time(); + pending_action.merge_mode = p_mode; + } if (p_history_id != INVALID_HISTORY) { pending_action.history_id = p_history_id; @@ -229,6 +234,12 @@ void EditorUndoRedoManager::commit_action(bool p_execute) { history.undo_redo->commit_action(p_execute); history.redo_stack.clear(); + if (history.undo_redo->get_action_level() > 0) { + // Nested action. + is_committing = false; + return; + } + if (!history.undo_stack.is_empty()) { const Action &prev_action = history.undo_stack.back()->get(); if (pending_action.merge_mode != UndoRedo::MERGE_DISABLE && pending_action.merge_mode == prev_action.merge_mode && pending_action.action_name == prev_action.action_name) { diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index 30fa1e8a0d..bda5dd68c4 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -512,7 +512,15 @@ void FileSystemDock::_tree_multi_selected(Object *p_item, int p_column, bool p_s } } -String FileSystemDock::get_selected_path() const { +Vector<String> FileSystemDock::get_selected_paths() const { + return _tree_get_selected(false); +} + +String FileSystemDock::get_current_path() const { + return path; +} + +String FileSystemDock::get_current_directory() const { if (path.ends_with("/")) { return path; } else { @@ -520,10 +528,6 @@ String FileSystemDock::get_selected_path() const { } } -String FileSystemDock::get_current_path() const { - return path; -} - void FileSystemDock::_set_current_path_text(const String &p_path) { if (p_path == "Favorites") { current_path->set_text(TTR("Favorites")); @@ -1727,7 +1731,7 @@ void FileSystemDock::_move_operation_confirm(const String &p_to_path, bool p_ove } } -Vector<String> FileSystemDock::_tree_get_selected(bool remove_self_inclusion) { +Vector<String> FileSystemDock::_tree_get_selected(bool remove_self_inclusion) const { // Build a list of selected items with the active one at the first position. Vector<String> selected_strings; diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h index f39ca9e74d..ebb6f1e2a0 100644 --- a/editor/filesystem_dock.h +++ b/editor/filesystem_dock.h @@ -297,12 +297,12 @@ private: void _update_display_mode(bool p_force = false); - Vector<String> _tree_get_selected(bool remove_self_inclusion = true); + Vector<String> _tree_get_selected(bool remove_self_inclusion = true) const; bool _is_file_type_disabled_by_feature_profile(const StringName &p_class); void _feature_profile_changed(); - Vector<String> _remove_self_included_paths(Vector<String> selected_strings); + static Vector<String> _remove_self_included_paths(Vector<String> selected_strings); private: static FileSystemDock *singleton; @@ -315,9 +315,11 @@ protected: static void _bind_methods(); public: - String get_selected_path() const; + Vector<String> get_selected_paths() const; String get_current_path() const; + String get_current_directory() const; + void navigate_to_path(const String &p_path); void focus_on_filter(); diff --git a/editor/import/dynamic_font_import_settings.cpp b/editor/import/dynamic_font_import_settings.cpp index f61ff5182d..8f15becd95 100644 --- a/editor/import/dynamic_font_import_settings.cpp +++ b/editor/import/dynamic_font_import_settings.cpp @@ -30,6 +30,7 @@ #include "dynamic_font_import_settings.h" +#include "core/config/project_settings.h" #include "editor/editor_file_dialog.h" #include "editor/editor_file_system.h" #include "editor/editor_inspector.h" @@ -528,6 +529,12 @@ void DynamicFontImportSettings::_variation_selected() { label_glyphs->set_text(TTR("Preloaded glyphs: ") + itos(import_variation_data->selected_glyphs.size())); _range_selected(); _change_text_opts(); + + btn_fill->set_disabled(false); + btn_fill_locales->set_disabled(false); + } else { + btn_fill->set_disabled(true); + btn_fill_locales->set_disabled(true); } } @@ -551,6 +558,15 @@ void DynamicFontImportSettings::_variation_remove(Object *p_item, int p_column, } _variations_validate(); + + vars_item = vars_list->get_selected(); + if (vars_item) { + btn_fill->set_disabled(false); + btn_fill_locales->set_disabled(false); + } else { + btn_fill->set_disabled(true); + btn_fill_locales->set_disabled(true); + } } void DynamicFontImportSettings::_variation_changed(const String &p_edited_property) { @@ -623,6 +639,27 @@ void DynamicFontImportSettings::_change_text_opts() { text_edit->add_theme_font_override("font", font_main_text); } +void DynamicFontImportSettings::_glyph_update_lbl() { + Ref<DynamicFontImportSettingsData> import_variation_data; + + TreeItem *vars_item = vars_list->get_selected(); + if (vars_item) { + import_variation_data = vars_item->get_metadata(0); + } + if (import_variation_data.is_null()) { + return; + } + + int linked_glyphs = 0; + for (const char32_t &c : import_variation_data->selected_chars) { + if (import_variation_data->selected_glyphs.has(font_main->get_glyph_index(16, c))) { + linked_glyphs++; + } + } + int unlinked_glyphs = import_variation_data->selected_glyphs.size() - linked_glyphs; + label_glyphs->set_text(TTR("Preloaded glyphs:") + " " + itos(unlinked_glyphs + import_variation_data->selected_chars.size())); +} + void DynamicFontImportSettings::_glyph_clear() { Ref<DynamicFontImportSettingsData> import_variation_data; @@ -635,7 +672,7 @@ void DynamicFontImportSettings::_glyph_clear() { } import_variation_data->selected_glyphs.clear(); - label_glyphs->set_text(TTR("Preloaded glyphs:") + " " + itos(import_variation_data->selected_glyphs.size())); + _glyph_update_lbl(); _range_selected(); } @@ -662,7 +699,7 @@ void DynamicFontImportSettings::_glyph_text_selected() { } } TS->free_rid(text_rid); - label_glyphs->set_text(TTR("Preloaded glyphs:") + " " + itos(import_variation_data->selected_glyphs.size())); + _glyph_update_lbl(); } _range_selected(); } @@ -699,7 +736,7 @@ void DynamicFontImportSettings::_glyph_selected() { item->clear_custom_bg_color(glyph_table->get_selected_column()); } } - label_glyphs->set_text(TTR("Preloaded glyphs:") + " " + itos(import_variation_data->selected_glyphs.size())); + _glyph_update_lbl(); item = glyph_tree->get_selected(); ERR_FAIL_NULL(item); @@ -800,7 +837,7 @@ void DynamicFontImportSettings::_edit_range(int32_t p_start, int32_t p_end) { col = 0; } } - label_glyphs->set_text(TTR("Preloaded glyphs:") + " " + itos(import_variation_data->selected_glyphs.size())); + _glyph_update_lbl(); } bool DynamicFontImportSettings::_char_update(int32_t p_char) { @@ -947,10 +984,73 @@ void DynamicFontImportSettings::_re_import() { EditorFileSystem::get_singleton()->reimport_file_with_custom_parameters(base_path, "font_data_dynamic", main_settings); } +void DynamicFontImportSettings::_locale_edited() { + TreeItem *item = locale_tree->get_selected(); + ERR_FAIL_NULL(item); + item->set_checked(0, !item->is_checked(0)); +} + +void DynamicFontImportSettings::_process_locales() { + Ref<DynamicFontImportSettingsData> import_variation_data; + + TreeItem *vars_item = vars_list->get_selected(); + if (vars_item) { + import_variation_data = vars_item->get_metadata(0); + } + if (import_variation_data.is_null()) { + return; + } + + for (int i = 0; i < locale_root->get_child_count(); i++) { + TreeItem *item = locale_root->get_child(i); + if (item) { + if (item->is_checked(0)) { + String locale = item->get_text(0); + Ref<Translation> tr = ResourceLoader::load(locale); + if (tr.is_valid()) { + Vector<String> messages = tr->get_translated_message_list(); + for (const String &E : messages) { + RID text_rid = TS->create_shaped_text(); + if (text_rid.is_valid()) { + TS->shaped_text_add_string(text_rid, E, font_main->get_rids(), 16, Dictionary(), tr->get_locale()); + TS->shaped_text_shape(text_rid); + const Glyph *gl = TS->shaped_text_get_glyphs(text_rid); + const int gl_size = TS->shaped_text_get_glyph_count(text_rid); + + for (int j = 0; j < gl_size; j++) { + if (gl[j].font_rid.is_valid() && gl[j].index != 0) { + import_variation_data->selected_glyphs.insert(gl[j].index); + } + } + TS->free_rid(text_rid); + } + } + } + } + } + } + + _glyph_update_lbl(); + _range_selected(); +} + void DynamicFontImportSettings::open_settings(const String &p_path) { // Load base font data. Vector<uint8_t> font_data = FileAccess::get_file_as_array(p_path); + // Load project locale list. + locale_tree->clear(); + locale_root = locale_tree->create_item(); + ERR_FAIL_NULL(locale_root); + + Vector<String> translations = GLOBAL_GET("internationalization/locale/translations"); + for (const String &E : translations) { + TreeItem *item = locale_tree->create_item(locale_root); + ERR_FAIL_NULL(item); + item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); + item->set_text(0, E); + } + // Load font for preview. font_preview.instantiate(); font_preview->set_data(font_data); @@ -1003,10 +1103,11 @@ void DynamicFontImportSettings::open_settings(const String &p_path) { int gww = get_theme_font(SNAME("font"))->get_string_size("00000").x + 50; glyph_table->set_column_custom_minimum_width(0, gww); - glyph_table->clear(); vars_list->clear(); + glyph_tree->set_selected(glyph_root->get_child(0)); + vars_list_root = vars_list->create_item(); import_settings_data->settings.clear(); @@ -1080,6 +1181,10 @@ void DynamicFontImportSettings::open_settings(const String &p_path) { import_variation_data_custom->selected_glyphs.insert(c); } } + if (preload_configurations.is_empty()) { + _variation_add(); // Add default variation. + } + vars_list->set_selected(vars_list_root->get_child(0)); } else { Variant value = config->get_value("params", key); import_settings_data->defaults[key] = value; @@ -1269,11 +1374,57 @@ DynamicFontImportSettings::DynamicFontImportSettings() { inspector_vars->connect("property_edited", callable_mp(this, &DynamicFontImportSettings::_variation_changed)); page2_side_vb->add_child(inspector_vars); + VBoxContainer *preload_pages_vb = memnew(VBoxContainer); + page2_hb->add_child(preload_pages_vb); + preload_pages = memnew(TabContainer); preload_pages->set_tab_alignment(TabBar::ALIGNMENT_CENTER); preload_pages->set_v_size_flags(Control::SIZE_EXPAND_FILL); preload_pages->set_h_size_flags(Control::SIZE_EXPAND_FILL); - page2_hb->add_child(preload_pages); + preload_pages_vb->add_child(preload_pages); + + HBoxContainer *gl_hb = memnew(HBoxContainer); + preload_pages_vb->add_child(gl_hb); + gl_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL); + + label_glyphs = memnew(Label); + gl_hb->add_child(label_glyphs); + label_glyphs->set_text(TTR("Preloaded glyphs:") + " " + itos(0)); + label_glyphs->set_custom_minimum_size(Size2(50 * EDSCALE, 0)); + + Button *btn_clear = memnew(Button); + gl_hb->add_child(btn_clear); + btn_clear->set_text(TTR("Clear Glyph List")); + btn_clear->connect("pressed", callable_mp(this, &DynamicFontImportSettings::_glyph_clear)); + + VBoxContainer *page2_0_vb = memnew(VBoxContainer); + page2_0_vb->set_name(TTR("Glyphs from the Translations")); + preload_pages->add_child(page2_0_vb); + + page2_0_description = memnew(Label); + page2_0_description->set_text(TTR("Select translations to add all required glyphs to pre-render list:")); + page2_0_description->set_h_size_flags(Control::SIZE_EXPAND_FILL); + page2_0_description->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART); + page2_0_vb->add_child(page2_0_description); + + locale_tree = memnew(Tree); + page2_0_vb->add_child(locale_tree); + locale_tree->set_columns(1); + locale_tree->set_hide_root(true); + locale_tree->set_column_expand(0, true); + locale_tree->connect("item_activated", callable_mp(this, &DynamicFontImportSettings::_locale_edited)); + locale_tree->set_column_custom_minimum_width(0, 120 * EDSCALE); + locale_tree->set_v_size_flags(Control::SIZE_EXPAND_FILL); + locale_root = locale_tree->create_item(); + + HBoxContainer *locale_hb = memnew(HBoxContainer); + page2_0_vb->add_child(locale_hb); + locale_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL); + + btn_fill_locales = memnew(Button); + locale_hb->add_child(btn_fill_locales); + btn_fill_locales->set_text(TTR("Shape all Strings in the Translations and Add Glyphs")); + btn_fill_locales->connect("pressed", callable_mp(this, &DynamicFontImportSettings::_process_locales)); // Page 2.1 layout: Text to select glyphs VBoxContainer *page2_1_vb = memnew(VBoxContainer); @@ -1281,7 +1432,7 @@ DynamicFontImportSettings::DynamicFontImportSettings() { preload_pages->add_child(page2_1_vb); page2_1_description = memnew(Label); - page2_1_description->set_text(TTR("Enter a text to shape and add all required glyphs to pre-render list:")); + page2_1_description->set_text(TTR("Enter a text and select OpenType features to shape and add all required glyphs to pre-render list:")); page2_1_description->set_h_size_flags(Control::SIZE_EXPAND_FILL); page2_1_description->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART); page2_1_vb->add_child(page2_1_description); @@ -1307,21 +1458,11 @@ DynamicFontImportSettings::DynamicFontImportSettings() { page2_1_vb->add_child(text_hb); text_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL); - label_glyphs = memnew(Label); - text_hb->add_child(label_glyphs); - label_glyphs->set_text(TTR("Preloaded glyphs:") + " " + itos(0)); - label_glyphs->set_custom_minimum_size(Size2(50 * EDSCALE, 0)); - - Button *btn_fill = memnew(Button); + btn_fill = memnew(Button); text_hb->add_child(btn_fill); btn_fill->set_text(TTR("Shape Text and Add Glyphs")); btn_fill->connect("pressed", callable_mp(this, &DynamicFontImportSettings::_glyph_text_selected)); - Button *btn_clear = memnew(Button); - text_hb->add_child(btn_clear); - btn_clear->set_text(TTR("Clear Glyph List")); - btn_clear->connect("pressed", callable_mp(this, &DynamicFontImportSettings::_glyph_clear)); - // Page 2.2 layout: Character map VBoxContainer *page2_2_vb = memnew(VBoxContainer); page2_2_vb->set_name(TTR("Glyphs from the Character Map")); diff --git a/editor/import/dynamic_font_import_settings.h b/editor/import/dynamic_font_import_settings.h index a1f763b445..386e9896dc 100644 --- a/editor/import/dynamic_font_import_settings.h +++ b/editor/import/dynamic_font_import_settings.h @@ -118,18 +118,30 @@ class DynamicFontImportSettings : public ConfirmationDialog { TabContainer *preload_pages = nullptr; + Label *label_glyphs = nullptr; + void _glyph_clear(); + void _glyph_update_lbl(); + + // Page 2.0 layout: Translations + Label *page2_0_description = nullptr; + Tree *locale_tree = nullptr; + TreeItem *locale_root = nullptr; + Button *btn_fill_locales = nullptr; + + void _locale_edited(); + void _process_locales(); + // Page 2.1 layout: Text to select glyphs Label *page2_1_description = nullptr; - Label *label_glyphs = nullptr; TextEdit *text_edit = nullptr; EditorInspector *inspector_text = nullptr; + Button *btn_fill = nullptr; List<ResourceImporter::ImportOption> options_text; Ref<DynamicFontImportSettingsData> text_settings_data; void _change_text_opts(); void _glyph_text_selected(); - void _glyph_clear(); // Page 2.2 layout: Character map Label *page2_2_description = nullptr; diff --git a/editor/import/resource_importer_imagefont.cpp b/editor/import/resource_importer_imagefont.cpp index 58c2061051..9d15854707 100644 --- a/editor/import/resource_importer_imagefont.cpp +++ b/editor/import/resource_importer_imagefont.cpp @@ -63,7 +63,8 @@ void ResourceImporterImageFont::get_import_options(const String &p_path, List<Im r_options->push_back(ImportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "character_ranges"), Vector<String>())); r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "columns"), 1)); r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "rows"), 1)); - r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "font_size"), 14)); + r_options->push_back(ImportOption(PropertyInfo(Variant::RECT2I, "image_margin"), Rect2i())); + r_options->push_back(ImportOption(PropertyInfo(Variant::RECT2I, "character_margin"), Rect2i())); r_options->push_back(ImportOption(PropertyInfo(Variant::ARRAY, "fallbacks", PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:%s", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "Font")), Array())); @@ -93,33 +94,39 @@ Error ResourceImporterImageFont::import(const String &p_source_file, const Strin int columns = p_options["columns"]; int rows = p_options["rows"]; - int base_size = p_options["font_size"]; Vector<String> ranges = p_options["character_ranges"]; Array fallbacks = p_options["fallbacks"]; + Rect2i img_margin = p_options["image_margin"]; + Rect2i char_margin = p_options["character_margin"]; + + Ref<Image> img; + img.instantiate(); + Error err = ImageLoader::load_image(p_source_file, img); + ERR_FAIL_COND_V_MSG(err != OK, ERR_FILE_CANT_READ, TTR("Can't load font texture:") + " \"" + p_source_file + "\"."); + + int count = columns * rows; + int chr_cell_width = (img->get_width() - img_margin.position.x - img_margin.size.x) / columns; + int chr_cell_height = (img->get_height() - img_margin.position.y - img_margin.size.y) / rows; + ERR_FAIL_COND_V_MSG(chr_cell_width <= 0 || chr_cell_height <= 0, ERR_FILE_CANT_READ, TTR("Image margin too big.")); + + int chr_width = chr_cell_width - char_margin.position.x - char_margin.size.x; + int chr_height = chr_cell_height - char_margin.position.y - char_margin.size.y; + ERR_FAIL_COND_V_MSG(chr_width <= 0 || chr_height <= 0, ERR_FILE_CANT_READ, TTR("Character margin too bit.")); Ref<FontFile> font; font.instantiate(); font->set_antialiasing(TextServer::FONT_ANTIALIASING_NONE); font->set_generate_mipmaps(false); font->set_multichannel_signed_distance_field(false); - font->set_fixed_size(base_size); + font->set_fixed_size(chr_height); font->set_subpixel_positioning(TextServer::SUBPIXEL_POSITIONING_DISABLED); font->set_force_autohinter(false); font->set_hinting(TextServer::HINTING_NONE); font->set_oversampling(1.0f); font->set_fallbacks(fallbacks); + font->set_texture_image(0, Vector2i(chr_height, 0), 0, img); - Ref<Image> img; - img.instantiate(); - Error err = ImageLoader::load_image(p_source_file, img); - ERR_FAIL_COND_V_MSG(err != OK, ERR_FILE_CANT_READ, TTR("Can't load font texture:") + " \"" + p_source_file + "\"."); - font->set_texture_image(0, Vector2i(base_size, 0), 0, img); - - int count = columns * rows; - int chr_width = img->get_width() / columns; - int chr_height = img->get_height() / rows; int pos = 0; - for (int i = 0; i < ranges.size(); i++) { int32_t start, end; Vector<String> tokens = ranges[i].split("-"); @@ -141,17 +148,17 @@ Error ResourceImporterImageFont::import(const String &p_source_file, const Strin for (int32_t idx = start; idx <= end; idx++) { int x = pos % columns; int y = pos / columns; - font->set_glyph_advance(0, base_size, idx, Vector2(chr_width, 0)); - font->set_glyph_offset(0, Vector2i(base_size, 0), idx, Vector2(0, -0.5 * chr_height)); - font->set_glyph_size(0, Vector2i(base_size, 0), idx, Vector2(chr_width, chr_height)); - font->set_glyph_uv_rect(0, Vector2i(base_size, 0), idx, Rect2(chr_width * x, chr_height * y, chr_width, chr_height)); - font->set_glyph_texture_idx(0, Vector2i(base_size, 0), idx, 0); + font->set_glyph_advance(0, chr_height, idx, Vector2(chr_width, 0)); + font->set_glyph_offset(0, Vector2i(chr_height, 0), idx, Vector2(0, -0.5 * chr_height)); + font->set_glyph_size(0, Vector2i(chr_height, 0), idx, Vector2(chr_width, chr_height)); + font->set_glyph_uv_rect(0, Vector2i(chr_height, 0), idx, Rect2(img_margin.position.x + chr_cell_width * x + char_margin.position.x, img_margin.position.y + chr_cell_height * y + char_margin.position.y, chr_width, chr_height)); + font->set_glyph_texture_idx(0, Vector2i(chr_height, 0), idx, 0); pos++; - ERR_FAIL_COND_V_MSG(pos >= count, ERR_CANT_CREATE, "Too many characters in range."); + ERR_FAIL_COND_V_MSG(pos >= count, ERR_CANT_CREATE, "Too many characters in range, should be " + itos(columns * rows)); } } - font->set_cache_ascent(0, base_size, 0.5 * chr_height); - font->set_cache_descent(0, base_size, 0.5 * chr_height); + font->set_cache_ascent(0, chr_height, 0.5 * chr_height); + font->set_cache_descent(0, chr_height, 0.5 * chr_height); int flg = 0; if ((bool)p_options["compress"]) { diff --git a/editor/import/resource_importer_scene.cpp b/editor/import/resource_importer_scene.cpp index a6a0eef11b..f7a3ce2679 100644 --- a/editor/import/resource_importer_scene.cpp +++ b/editor/import/resource_importer_scene.cpp @@ -402,7 +402,7 @@ void _rescale_importer_mesh(Vector3 p_scale, Ref<ImporterMesh> p_mesh, bool is_s const int fmt_compress_flags = p_mesh->get_surface_format(surf_idx); Array arr = p_mesh->get_surface_arrays(surf_idx); String name = p_mesh->get_surface_name(surf_idx); - Dictionary lods = Dictionary(); + Dictionary lods; Ref<Material> mat = p_mesh->get_surface_material(surf_idx); { Vector<Vector3> vertex_array = arr[ArrayMesh::ARRAY_VERTEX]; diff --git a/editor/import/resource_importer_wav.cpp b/editor/import/resource_importer_wav.cpp index 1dcae2841b..f5a0f0abcf 100644 --- a/editor/import/resource_importer_wav.cpp +++ b/editor/import/resource_importer_wav.cpp @@ -107,7 +107,7 @@ Error ResourceImporterWAV::import(const String &p_source_file, const String &p_s file->get_buffer((uint8_t *)&riff, 4); //RIFF if (riff[0] != 'R' || riff[1] != 'I' || riff[2] != 'F' || riff[3] != 'F') { - ERR_FAIL_V(ERR_FILE_UNRECOGNIZED); + ERR_FAIL_V_MSG(ERR_FILE_UNRECOGNIZED, vformat("Not a WAV file. File should start with 'RIFF', but found '%s', in file of size %d bytes", riff, file->get_length())); } /* GET FILESIZE */ @@ -115,12 +115,12 @@ Error ResourceImporterWAV::import(const String &p_source_file, const String &p_s /* CHECK WAVE */ - char wave[4]; - - file->get_buffer((uint8_t *)&wave, 4); //RIFF + char wave[5]; + wave[4] = 0; + file->get_buffer((uint8_t *)&wave, 4); //WAVE if (wave[0] != 'W' || wave[1] != 'A' || wave[2] != 'V' || wave[3] != 'E') { - ERR_FAIL_V_MSG(ERR_FILE_UNRECOGNIZED, "Not a WAV file (no WAVE RIFF header)."); + ERR_FAIL_V_MSG(ERR_FILE_UNRECOGNIZED, vformat("Not a WAV file. Header should contain 'WAVE', but found '%s', in file of size %d bytes", wave, file->get_length())); } // Let users override potential loop points from the WAV. diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp index 1ebe81c4b0..85739d0d8f 100644 --- a/editor/plugins/animation_player_editor_plugin.cpp +++ b/editor/plugins/animation_player_editor_plugin.cpp @@ -950,7 +950,7 @@ void AnimationPlayerEditor::_update_animation_list_icons() { } void AnimationPlayerEditor::_update_name_dialog_library_dropdown() { - StringName current_library_name = StringName(); + StringName current_library_name; if (animation->has_selectable_items()) { String current_animation_name = animation->get_item_text(animation->get_selected()); Ref<Animation> current_animation = player->get_animation(current_animation_name); diff --git a/editor/plugins/animation_state_machine_editor.cpp b/editor/plugins/animation_state_machine_editor.cpp index c32f9b1775..cc709182a8 100644 --- a/editor/plugins/animation_state_machine_editor.cpp +++ b/editor/plugins/animation_state_machine_editor.cpp @@ -420,7 +420,7 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv if (mm.is_valid()) { state_machine_draw->grab_focus(); - String new_over_node = StringName(); + String new_over_node; int new_over_node_what = -1; if (tool_select->is_pressed()) { for (int i = node_rects.size() - 1; i >= 0; i--) { // Inverse to draw order. diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index baf335f5b4..5584b0299c 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -412,7 +412,7 @@ Point2 CanvasItemEditor::snap_point(Point2 p_target, unsigned int p_modes, unsig // Other nodes sides if ((is_snap_active && snap_other_nodes && (p_modes & SNAP_OTHER_NODES)) || (p_forced_modes & SNAP_OTHER_NODES)) { - Transform2D to_snap_transform = Transform2D(); + Transform2D to_snap_transform; List<const CanvasItem *> exceptions = List<const CanvasItem *>(); for (const CanvasItem *E : p_other_nodes_exceptions) { exceptions.push_back(E); @@ -813,7 +813,7 @@ Vector2 CanvasItemEditor::_position_to_anchor(const Control *p_control, Vector2 Rect2 parent_rect = p_control->get_parent_anchorable_rect(); - Vector2 output = Vector2(); + Vector2 output; if (p_control->is_layout_rtl()) { output.x = (parent_rect.size.x == 0) ? 0.0 : (parent_rect.size.x - p_control->get_transform().xform(position).x - parent_rect.position.x) / parent_rect.size.x; } else { @@ -2268,7 +2268,7 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) { } } - String suffix = String(); + String suffix; if (locked == 1) { suffix = " (" + TTR("Locked") + ")"; } else if (locked == 2) { @@ -2790,7 +2790,7 @@ void CanvasItemEditor::_draw_rulers() { int font_size = get_theme_font_size(SNAME("rulers_size"), SNAME("EditorFonts")); // The rule transform - Transform2D ruler_transform = Transform2D(); + Transform2D ruler_transform; if (grid_snap_active || _is_grid_visible()) { List<CanvasItem *> selection = _get_edited_canvas_items(); if (snap_relative && selection.size() > 0) { @@ -2816,11 +2816,11 @@ void CanvasItemEditor::_draw_rulers() { // Subdivisions int major_subdivision = 2; - Transform2D major_subdivide = Transform2D(); + Transform2D major_subdivide; major_subdivide.scale(Size2(1.0 / major_subdivision, 1.0 / major_subdivision)); int minor_subdivision = 5; - Transform2D minor_subdivide = Transform2D(); + Transform2D minor_subdivide; minor_subdivide.scale(Size2(1.0 / minor_subdivision, 1.0 / minor_subdivision)); // First and last graduations to draw (in the ruler space) @@ -3032,7 +3032,7 @@ void CanvasItemEditor::_draw_ruler_tool() { viewport->draw_string_outline(font, text_pos2, TS->format_number(vformat("%.1f px", length_vector.y)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color); viewport->draw_string(font, text_pos2, TS->format_number(vformat("%.1f px", length_vector.y)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color); - Point2 v_angle_text_pos = Point2(); + Point2 v_angle_text_pos; v_angle_text_pos.x = CLAMP(begin.x - angle_text_width / 2, angle_text_width / 2, viewport->get_rect().size.x - angle_text_width); v_angle_text_pos.y = begin.y < end.y ? MIN(text_pos2.y - 2 * text_height, begin.y - text_height * 0.5) : MAX(text_pos2.y + text_height * 3, begin.y + text_height * 1.5); viewport->draw_string_outline(font, v_angle_text_pos, TS->format_number(vformat(String::utf8("%d°"), vertical_angle)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color); @@ -3043,7 +3043,7 @@ void CanvasItemEditor::_draw_ruler_tool() { viewport->draw_string_outline(font, text_pos2, TS->format_number(vformat("%.1f px", length_vector.x)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color); viewport->draw_string(font, text_pos2, TS->format_number(vformat("%.1f px", length_vector.x)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color); - Point2 h_angle_text_pos = Point2(); + Point2 h_angle_text_pos; h_angle_text_pos.x = CLAMP(end.x - angle_text_width / 2, angle_text_width / 2, viewport->get_rect().size.x - angle_text_width); if (begin.y < end.y) { h_angle_text_pos.y = end.y + text_height * 1.5; diff --git a/editor/plugins/canvas_item_editor_plugin.h b/editor/plugins/canvas_item_editor_plugin.h index 74d4af9a13..cc98aa8c51 100644 --- a/editor/plugins/canvas_item_editor_plugin.h +++ b/editor/plugins/canvas_item_editor_plugin.h @@ -339,12 +339,12 @@ private: Point2 drag_start_origin; DragType drag_type = DRAG_NONE; - Point2 drag_from = Vector2(); - Point2 drag_to = Vector2(); + Point2 drag_from; + Point2 drag_to; Point2 drag_rotation_center; List<CanvasItem *> drag_selection; int dragged_guide_index = -1; - Point2 dragged_guide_pos = Point2(); + Point2 dragged_guide_pos; bool is_hovering_h_guide = false; bool is_hovering_v_guide = false; diff --git a/editor/plugins/control_editor_plugin.cpp b/editor/plugins/control_editor_plugin.cpp index 6af9b85657..00b7845cee 100644 --- a/editor/plugins/control_editor_plugin.cpp +++ b/editor/plugins/control_editor_plugin.cpp @@ -816,7 +816,7 @@ Vector2 ControlEditorToolbar::_position_to_anchor(const Control *p_control, Vect Rect2 parent_rect = p_control->get_parent_anchorable_rect(); - Vector2 output = Vector2(); + Vector2 output; if (p_control->is_layout_rtl()) { output.x = (parent_rect.size.x == 0) ? 0.0 : (parent_rect.size.x - p_control->get_transform().xform(position).x - parent_rect.position.x) / parent_rect.size.x; } else { diff --git a/editor/plugins/editor_debugger_plugin.cpp b/editor/plugins/editor_debugger_plugin.cpp index 4ce3d7cfd5..5dd3038c0e 100644 --- a/editor/plugins/editor_debugger_plugin.cpp +++ b/editor/plugins/editor_debugger_plugin.cpp @@ -32,7 +32,7 @@ #include "editor/debugger/script_editor_debugger.h" -void EditorDebuggerPlugin::_breaked(bool p_really_did, bool p_can_debug, String p_message, bool p_has_stackdump) { +void EditorDebuggerSession::_breaked(bool p_really_did, bool p_can_debug, String p_message, bool p_has_stackdump) { if (p_really_did) { emit_signal(SNAME("breaked"), p_can_debug); } else { @@ -40,22 +40,22 @@ void EditorDebuggerPlugin::_breaked(bool p_really_did, bool p_can_debug, String } } -void EditorDebuggerPlugin::_started() { +void EditorDebuggerSession::_started() { emit_signal(SNAME("started")); } -void EditorDebuggerPlugin::_stopped() { +void EditorDebuggerSession::_stopped() { emit_signal(SNAME("stopped")); } -void EditorDebuggerPlugin::_bind_methods() { - ClassDB::bind_method(D_METHOD("send_message", "message", "data"), &EditorDebuggerPlugin::send_message); - ClassDB::bind_method(D_METHOD("register_message_capture", "name", "callable"), &EditorDebuggerPlugin::register_message_capture); - ClassDB::bind_method(D_METHOD("unregister_message_capture", "name"), &EditorDebuggerPlugin::unregister_message_capture); - ClassDB::bind_method(D_METHOD("has_capture", "name"), &EditorDebuggerPlugin::has_capture); - ClassDB::bind_method(D_METHOD("is_breaked"), &EditorDebuggerPlugin::is_breaked); - ClassDB::bind_method(D_METHOD("is_debuggable"), &EditorDebuggerPlugin::is_debuggable); - ClassDB::bind_method(D_METHOD("is_session_active"), &EditorDebuggerPlugin::is_session_active); +void EditorDebuggerSession::_bind_methods() { + ClassDB::bind_method(D_METHOD("send_message", "message", "data"), &EditorDebuggerSession::send_message, DEFVAL(Array())); + ClassDB::bind_method(D_METHOD("toggle_profiler", "profiler", "enable", "data"), &EditorDebuggerSession::toggle_profiler, DEFVAL(Array())); + ClassDB::bind_method(D_METHOD("is_breaked"), &EditorDebuggerSession::is_breaked); + ClassDB::bind_method(D_METHOD("is_debuggable"), &EditorDebuggerSession::is_debuggable); + ClassDB::bind_method(D_METHOD("is_active"), &EditorDebuggerSession::is_active); + ClassDB::bind_method(D_METHOD("add_session_tab", "control"), &EditorDebuggerSession::add_session_tab); + ClassDB::bind_method(D_METHOD("remove_session_tab", "control"), &EditorDebuggerSession::remove_session_tab); ADD_SIGNAL(MethodInfo("started")); ADD_SIGNAL(MethodInfo("stopped")); @@ -63,62 +63,131 @@ void EditorDebuggerPlugin::_bind_methods() { ADD_SIGNAL(MethodInfo("continued")); } -void EditorDebuggerPlugin::attach_debugger(ScriptEditorDebugger *p_debugger) { - debugger = p_debugger; - if (debugger) { - debugger->connect("started", callable_mp(this, &EditorDebuggerPlugin::_started)); - debugger->connect("stopped", callable_mp(this, &EditorDebuggerPlugin::_stopped)); - debugger->connect("breaked", callable_mp(this, &EditorDebuggerPlugin::_breaked)); - } +void EditorDebuggerSession::add_session_tab(Control *p_tab) { + ERR_FAIL_COND(!p_tab || !debugger); + debugger->add_debugger_tab(p_tab); + tabs.insert(p_tab); } -void EditorDebuggerPlugin::detach_debugger(bool p_call_debugger) { - if (debugger) { - debugger->disconnect("started", callable_mp(this, &EditorDebuggerPlugin::_started)); - debugger->disconnect("stopped", callable_mp(this, &EditorDebuggerPlugin::_stopped)); - debugger->disconnect("breaked", callable_mp(this, &EditorDebuggerPlugin::_breaked)); - if (p_call_debugger && get_script_instance()) { - debugger->remove_debugger_plugin(get_script_instance()->get_script()); - } - debugger = nullptr; - } +void EditorDebuggerSession::remove_session_tab(Control *p_tab) { + ERR_FAIL_COND(!p_tab || !debugger); + debugger->remove_debugger_tab(p_tab); + tabs.erase(p_tab); } -void EditorDebuggerPlugin::send_message(const String &p_message, const Array &p_args) { +void EditorDebuggerSession::send_message(const String &p_message, const Array &p_args) { ERR_FAIL_COND_MSG(!debugger, "Plugin is not attached to debugger"); debugger->send_message(p_message, p_args); } -void EditorDebuggerPlugin::register_message_capture(const StringName &p_name, const Callable &p_callable) { +void EditorDebuggerSession::toggle_profiler(const String &p_profiler, bool p_enable, const Array &p_data) { ERR_FAIL_COND_MSG(!debugger, "Plugin is not attached to debugger"); - debugger->register_message_capture(p_name, p_callable); + debugger->toggle_profiler(p_profiler, p_enable, p_data); } -void EditorDebuggerPlugin::unregister_message_capture(const StringName &p_name) { - ERR_FAIL_COND_MSG(!debugger, "Plugin is not attached to debugger"); - debugger->unregister_message_capture(p_name); -} - -bool EditorDebuggerPlugin::has_capture(const StringName &p_name) { - ERR_FAIL_COND_V_MSG(!debugger, false, "Plugin is not attached to debugger"); - return debugger->has_capture(p_name); -} - -bool EditorDebuggerPlugin::is_breaked() { +bool EditorDebuggerSession::is_breaked() { ERR_FAIL_COND_V_MSG(!debugger, false, "Plugin is not attached to debugger"); return debugger->is_breaked(); } -bool EditorDebuggerPlugin::is_debuggable() { +bool EditorDebuggerSession::is_debuggable() { ERR_FAIL_COND_V_MSG(!debugger, false, "Plugin is not attached to debugger"); return debugger->is_debuggable(); } -bool EditorDebuggerPlugin::is_session_active() { +bool EditorDebuggerSession::is_active() { ERR_FAIL_COND_V_MSG(!debugger, false, "Plugin is not attached to debugger"); return debugger->is_session_active(); } +void EditorDebuggerSession::detach_debugger() { + if (!debugger) { + return; + } + debugger->disconnect("started", callable_mp(this, &EditorDebuggerSession::_started)); + debugger->disconnect("stopped", callable_mp(this, &EditorDebuggerSession::_stopped)); + debugger->disconnect("breaked", callable_mp(this, &EditorDebuggerSession::_breaked)); + debugger->disconnect("tree_exited", callable_mp(this, &EditorDebuggerSession::_debugger_gone_away)); + for (Control *tab : tabs) { + debugger->remove_debugger_tab(tab); + } + tabs.clear(); + debugger = nullptr; +} + +void EditorDebuggerSession::_debugger_gone_away() { + debugger = nullptr; + tabs.clear(); +} + +EditorDebuggerSession::EditorDebuggerSession(ScriptEditorDebugger *p_debugger) { + ERR_FAIL_COND(!p_debugger); + debugger = p_debugger; + debugger->connect("started", callable_mp(this, &EditorDebuggerSession::_started)); + debugger->connect("stopped", callable_mp(this, &EditorDebuggerSession::_stopped)); + debugger->connect("breaked", callable_mp(this, &EditorDebuggerSession::_breaked)); + debugger->connect("tree_exited", callable_mp(this, &EditorDebuggerSession::_debugger_gone_away), CONNECT_ONE_SHOT); +} + +EditorDebuggerSession::~EditorDebuggerSession() { + detach_debugger(); +} + +/// EditorDebuggerPlugin + EditorDebuggerPlugin::~EditorDebuggerPlugin() { - detach_debugger(true); + clear(); +} + +void EditorDebuggerPlugin::clear() { + for (int i = 0; i < sessions.size(); i++) { + sessions[i]->detach_debugger(); + } + sessions.clear(); +} + +void EditorDebuggerPlugin::create_session(ScriptEditorDebugger *p_debugger) { + sessions.push_back(Ref<EditorDebuggerSession>(memnew(EditorDebuggerSession(p_debugger)))); + setup_session(sessions.size() - 1); +} + +void EditorDebuggerPlugin::setup_session(int p_idx) { + GDVIRTUAL_CALL(_setup_session, p_idx); +} + +Ref<EditorDebuggerSession> EditorDebuggerPlugin::get_session(int p_idx) { + ERR_FAIL_INDEX_V(p_idx, sessions.size(), nullptr); + return sessions[p_idx]; +} + +Array EditorDebuggerPlugin::get_sessions() { + Array ret; + for (int i = 0; i < sessions.size(); i++) { + ret.push_back(sessions[i]); + } + return ret; +} + +bool EditorDebuggerPlugin::has_capture(const String &p_message) const { + bool ret = false; + if (GDVIRTUAL_CALL(_has_capture, p_message, ret)) { + return ret; + } + return false; +} + +bool EditorDebuggerPlugin::capture(const String &p_message, const Array &p_data, int p_session_id) { + bool ret = false; + if (GDVIRTUAL_CALL(_capture, p_message, p_data, p_session_id, ret)) { + return ret; + } + return false; +} + +void EditorDebuggerPlugin::_bind_methods() { + GDVIRTUAL_BIND(_setup_session, "session_id"); + GDVIRTUAL_BIND(_has_capture, "capture"); + GDVIRTUAL_BIND(_capture, "message", "data", "session_id"); + ClassDB::bind_method(D_METHOD("get_session", "id"), &EditorDebuggerPlugin::get_session); + ClassDB::bind_method(D_METHOD("get_sessions"), &EditorDebuggerPlugin::get_sessions); } diff --git a/editor/plugins/editor_debugger_plugin.h b/editor/plugins/editor_debugger_plugin.h index b602c36912..46f8f17cc2 100644 --- a/editor/plugins/editor_debugger_plugin.h +++ b/editor/plugins/editor_debugger_plugin.h @@ -35,29 +35,62 @@ class ScriptEditorDebugger; -class EditorDebuggerPlugin : public Control { - GDCLASS(EditorDebuggerPlugin, Control); +class EditorDebuggerSession : public RefCounted { + GDCLASS(EditorDebuggerSession, RefCounted); private: + HashSet<Control *> tabs; + ScriptEditorDebugger *debugger = nullptr; void _breaked(bool p_really_did, bool p_can_debug, String p_message, bool p_has_stackdump); void _started(); void _stopped(); + void _debugger_gone_away(); protected: static void _bind_methods(); public: - void attach_debugger(ScriptEditorDebugger *p_debugger); - void detach_debugger(bool p_call_debugger); - void send_message(const String &p_message, const Array &p_args); - void register_message_capture(const StringName &p_name, const Callable &p_callable); - void unregister_message_capture(const StringName &p_name); - bool has_capture(const StringName &p_name); + void detach_debugger(); + + void add_session_tab(Control *p_tab); + void remove_session_tab(Control *p_tab); + void send_message(const String &p_message, const Array &p_args = Array()); + void toggle_profiler(const String &p_profiler, bool p_enable, const Array &p_data = Array()); bool is_breaked(); bool is_debuggable(); - bool is_session_active(); + bool is_active(); + + EditorDebuggerSession(ScriptEditorDebugger *p_debugger); + ~EditorDebuggerSession(); +}; + +class EditorDebuggerPlugin : public RefCounted { + GDCLASS(EditorDebuggerPlugin, RefCounted); + +private: + List<Ref<EditorDebuggerSession>> sessions; + +protected: + static void _bind_methods(); + +public: + void create_session(ScriptEditorDebugger *p_debugger); + void clear(); + + virtual void setup_session(int p_idx); + virtual bool capture(const String &p_message, const Array &p_data, int p_session); + virtual bool has_capture(const String &p_capture) const; + + Ref<EditorDebuggerSession> get_session(int p_session_id); + Array get_sessions(); + + GDVIRTUAL3R(bool, _capture, const String &, const Array &, int); + GDVIRTUAL1RC(bool, _has_capture, const String &); + GDVIRTUAL1(_setup_session, int); + + EditorDebuggerPlugin() {} ~EditorDebuggerPlugin(); }; diff --git a/editor/plugins/gradient_editor.cpp b/editor/plugins/gradient_editor.cpp index 7039ada10a..f6d5adcd68 100644 --- a/editor/plugins/gradient_editor.cpp +++ b/editor/plugins/gradient_editor.cpp @@ -203,6 +203,7 @@ void GradientEditor::gui_input(const Ref<InputEvent> &p_event) { grabbed = _get_point_from_pos(mb->get_position().x); _show_color_picker(); accept_event(); + return; } // Delete point on right click. diff --git a/editor/plugins/material_editor_plugin.h b/editor/plugins/material_editor_plugin.h index a6321dcf34..076fd5e537 100644 --- a/editor/plugins/material_editor_plugin.h +++ b/editor/plugins/material_editor_plugin.h @@ -46,7 +46,7 @@ class SubViewportContainer; class MaterialEditor : public Control { GDCLASS(MaterialEditor, Control); - Vector2 rot = Vector2(); + Vector2 rot; HBoxContainer *layout_2d = nullptr; ColorRect *rect_instance = nullptr; diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index 7febb50f5c..5929807f98 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -1329,7 +1329,7 @@ void Node3DEditorViewport::_list_select(Ref<InputEventMouseButton> b) { } } - String suffix = String(); + String suffix; if (locked == 1) { suffix = " (" + TTR("Locked") + ")"; } else if (locked == 2) { @@ -3492,7 +3492,7 @@ void Node3DEditorViewport::update_transform_gizmo_view() { } for (int i = 0; i < 3; i++) { - Transform3D axis_angle = Transform3D(); + Transform3D axis_angle; if (xform.basis.get_column(i).normalized().dot(xform.basis.get_column((i + 1) % 3).normalized()) < 1.0) { axis_angle = axis_angle.looking_at(xform.basis.get_column(i).normalized(), xform.basis.get_column((i + 1) % 3).normalized()); } @@ -6803,8 +6803,8 @@ void Node3DEditor::_init_grid() { // Don't draw lines over the origin if it's enabled. if (!(origin_enabled && Math::is_zero_approx(position_a))) { - Vector3 line_bgn = Vector3(); - Vector3 line_end = Vector3(); + Vector3 line_bgn; + Vector3 line_end; line_bgn[a] = position_a; line_end[a] = position_a; line_bgn[b] = bgn_b; @@ -6819,8 +6819,8 @@ void Node3DEditor::_init_grid() { } if (!(origin_enabled && Math::is_zero_approx(position_b))) { - Vector3 line_bgn = Vector3(); - Vector3 line_end = Vector3(); + Vector3 line_bgn; + Vector3 line_end; line_bgn[b] = position_b; line_end[b] = position_b; line_bgn[a] = bgn_a; @@ -6988,8 +6988,8 @@ void Node3DEditor::_snap_selected_nodes_to_floor() { for (Node *E : selection) { Node3D *sp = Object::cast_to<Node3D>(E); if (sp) { - Vector3 from = Vector3(); - Vector3 position_offset = Vector3(); + Vector3 from; + Vector3 position_offset; // Priorities for snapping to floor are CollisionShapes, VisualInstances and then origin HashSet<VisualInstance3D *> vi = _get_child_nodes<VisualInstance3D>(sp); diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h index 5c5034a916..a8d3bfb70c 100644 --- a/editor/plugins/node_3d_editor_plugin.h +++ b/editor/plugins/node_3d_editor_plugin.h @@ -575,7 +575,7 @@ private: bool grid_enabled = false; bool grid_init_draw = false; Camera3D::ProjectionType grid_camera_last_update_perspective = Camera3D::PROJECTION_PERSPECTIVE; - Vector3 grid_camera_last_update_position = Vector3(); + Vector3 grid_camera_last_update_position; 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]; diff --git a/editor/plugins/skeleton_3d_editor_plugin.cpp b/editor/plugins/skeleton_3d_editor_plugin.cpp index 7922a768ec..58c25c1a5d 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.cpp +++ b/editor/plugins/skeleton_3d_editor_plugin.cpp @@ -1288,7 +1288,7 @@ void Skeleton3DGizmoPlugin::set_subgizmo_transform(const EditorNode3DGizmo *p_gi ERR_FAIL_COND(!skeleton); // Prepare for global to local. - Transform3D original_to_local = Transform3D(); + Transform3D original_to_local; int parent_idx = skeleton->get_bone_parent(p_id); if (parent_idx >= 0) { original_to_local = original_to_local * skeleton->get_bone_global_pose(parent_idx); @@ -1296,7 +1296,7 @@ void Skeleton3DGizmoPlugin::set_subgizmo_transform(const EditorNode3DGizmo *p_gi Basis to_local = original_to_local.get_basis().inverse(); // Prepare transform. - Transform3D t = Transform3D(); + Transform3D t; // Basis. t.basis = to_local * p_transform.get_basis(); diff --git a/editor/plugins/sprite_frames_editor_plugin.cpp b/editor/plugins/sprite_frames_editor_plugin.cpp index d3ab5b6f77..cf8cc71db7 100644 --- a/editor/plugins/sprite_frames_editor_plugin.cpp +++ b/editor/plugins/sprite_frames_editor_plugin.cpp @@ -1525,7 +1525,7 @@ SpriteFramesEditor::SpriteFramesEditor() { _zoom_reset(); // Ensure the anim search box is wide enough by default. - // Not by setting its minimum size so it can still be shrinked if desired. + // Not by setting its minimum size so it can still be shrunk if desired. set_split_offset(56 * EDSCALE); } diff --git a/editor/plugins/tiles/tiles_editor_plugin.cpp b/editor/plugins/tiles/tiles_editor_plugin.cpp index 5b54062632..4239e2c981 100644 --- a/editor/plugins/tiles/tiles_editor_plugin.cpp +++ b/editor/plugins/tiles/tiles_editor_plugin.cpp @@ -92,7 +92,7 @@ void TilesEditorPlugin::_thread() { TypedArray<Vector2i> used_cells = tile_map->get_used_cells(0); - Rect2 encompassing_rect = Rect2(); + Rect2 encompassing_rect; encompassing_rect.set_position(tile_map->map_to_local(used_cells[0])); for (int i = 0; i < used_cells.size(); i++) { Vector2i cell = used_cells[i]; diff --git a/editor/plugins/tiles/tiles_editor_plugin.h b/editor/plugins/tiles/tiles_editor_plugin.h index bdada9ec90..fe0d8179bc 100644 --- a/editor/plugins/tiles/tiles_editor_plugin.h +++ b/editor/plugins/tiles/tiles_editor_plugin.h @@ -71,7 +71,7 @@ private: // For synchronization. int atlas_sources_lists_current = 0; float atlas_view_zoom = 1.0; - Vector2 atlas_view_scroll = Vector2(); + Vector2 atlas_view_scroll; void _tile_map_changed(); diff --git a/editor/project_converter_3_to_4.cpp b/editor/project_converter_3_to_4.cpp index 78f3b4de0e..8de2833240 100644 --- a/editor/project_converter_3_to_4.cpp +++ b/editor/project_converter_3_to_4.cpp @@ -366,6 +366,7 @@ static const char *gdscript_function_renames[][2] = { { "get_scancode", "get_keycode" }, // InputEventKey { "get_scancode_string", "get_keycode_string" }, // OS { "get_scancode_with_modifiers", "get_keycode_with_modifiers" }, // InputEventKey + { "get_selected_path", "get_current_directory" }, // EditorInterface { "get_shift", "is_shift_pressed" }, // InputEventWithModifiers { "get_size_override", "get_size_2d_override" }, // SubViewport { "get_slide_count", "get_slide_collision_count" }, // CharacterBody2D, CharacterBody3D diff --git a/editor/script_create_dialog.cpp b/editor/script_create_dialog.cpp index 08c0f0f708..4a3b0e979f 100644 --- a/editor/script_create_dialog.cpp +++ b/editor/script_create_dialog.cpp @@ -853,7 +853,7 @@ ScriptLanguage::ScriptTemplate ScriptCreateDialog::_parse_template(const ScriptL script_template.inherit = p_inherits; String space_indent = " "; // Get meta delimiter - String meta_delimiter = String(); + String meta_delimiter; List<String> comment_delimiters; p_language->get_comment_delimiters(&comment_delimiters); for (const String &script_delimiter : comment_delimiters) { diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp index 420401cb79..0ebdbf090d 100644 --- a/modules/gdscript/editor/gdscript_highlighter.cpp +++ b/modules/gdscript/editor/gdscript_highlighter.cpp @@ -322,7 +322,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l } String word = str.substr(j, to - j); - Color col = Color(); + Color col; if (global_functions.has(word)) { // "assert" and "preload" are reserved, so highlight even if not followed by a bracket. if (word == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::ASSERT) || word == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::PRELOAD)) { diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index b4da9c1224..bd6cef0b6e 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -629,10 +629,6 @@ void GDScript::_update_doc() { } } - for (KeyValue<StringName, Ref<GDScript>> &E : subclasses) { - E.value->_update_doc(); - } - _add_doc(doc); } #endif @@ -674,36 +670,14 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call, PlaceHolderSc base_cache = Ref<GDScript>(); } - if (c->extends_used) { - String ext_path = ""; - if (String(c->extends_path) != "" && String(c->extends_path) != get_path()) { - ext_path = c->extends_path; - if (ext_path.is_relative_path()) { - String base_path = get_path(); - if (base_path.is_empty() || base_path.is_relative_path()) { - ERR_PRINT(("Could not resolve relative path for parent class: " + ext_path).utf8().get_data()); - } else { - ext_path = base_path.get_base_dir().path_join(ext_path); - } - } - } else if (c->extends.size() != 0) { - const StringName &base_class = c->extends[0]; - - if (ScriptServer::is_global_class(base_class)) { - ext_path = ScriptServer::get_global_class_path(base_class); - } - } - - if (!ext_path.is_empty()) { - if (ext_path != get_path()) { - Ref<GDScript> bf = ResourceLoader::load(ext_path); - - if (bf.is_valid()) { - base_cache = bf; - bf->inheriters_cache.insert(get_instance_id()); - } - } else { - ERR_PRINT(("Path extending itself in " + ext_path).utf8().get_data()); + GDScriptParser::DataType base_type = parser.get_tree()->base_type; + if (base_type.kind == GDScriptParser::DataType::CLASS) { + Ref<GDScript> bf = GDScriptCache::get_full_script(base_type.script_path, err, path); + if (err == OK) { + bf = Ref<GDScript>(bf->find_class(base_type.class_type->fqcn)); + if (bf.is_valid()) { + base_cache = bf; + bf->inheriters_cache.insert(get_instance_id()); } } } @@ -825,13 +799,6 @@ void GDScript::update_exports() { #endif } -void GDScript::_set_subclass_path(Ref<GDScript> &p_sc, const String &p_path) { - p_sc->path = p_path; - for (KeyValue<StringName, Ref<GDScript>> &E : p_sc->subclasses) { - _set_subclass_path(E.value, p_path); - } -} - String GDScript::_get_debug_path() const { if (is_built_in() && !get_name().is_empty()) { return get_name() + " (" + get_path() + ")"; @@ -860,7 +827,7 @@ Error GDScript::reload(bool p_keep_state) { basedir = basedir.get_base_dir(); } -// Loading a template, don't parse. + // Loading a template, don't parse. #ifdef TOOLS_ENABLED if (EditorPaths::get_singleton() && basedir.begins_with(EditorPaths::get_singleton()->get_project_script_templates_dir())) { return OK; @@ -913,10 +880,6 @@ Error GDScript::reload(bool p_keep_state) { GDScriptCompiler compiler; err = compiler.compile(&parser, this, p_keep_state); -#ifdef TOOLS_ENABLED - _update_doc(); -#endif - if (err) { if (can_run) { if (EngineDebugger::is_active()) { @@ -937,14 +900,6 @@ Error GDScript::reload(bool p_keep_state) { } #endif - valid = true; - - for (KeyValue<StringName, Ref<GDScript>> &E : subclasses) { - _set_subclass_path(E.value, path); - } - - _init_rpc_methods_properties(); - return OK; } @@ -1051,8 +1006,13 @@ Error GDScript::load_byte_code(const String &p_path) { } void GDScript::set_path(const String &p_path, bool p_take_over) { - Script::set_path(p_path, p_take_over); + if (is_root_script()) { + Script::set_path(p_path, p_take_over); + } this->path = p_path; + for (KeyValue<StringName, Ref<GDScript>> &kv : subclasses) { + kv.value->set_path(p_path, p_take_over); + } } Error GDScript::load_source_code(const String &p_path) { @@ -1127,6 +1087,52 @@ bool GDScript::inherits_script(const Ref<Script> &p_script) const { return false; } +GDScript *GDScript::find_class(const String &p_qualified_name) { + String first = p_qualified_name.get_slice("::", 0); + + GDScript *result = nullptr; + if (first.is_empty() || first == name) { + result = this; + } else if (first == get_root_script()->path) { + result = get_root_script(); + } else if (HashMap<StringName, Ref<GDScript>>::Iterator E = subclasses.find(first)) { + result = E->value.ptr(); + } else if (_owner != nullptr) { + // Check parent scope. + return _owner->find_class(p_qualified_name); + } + + int name_count = p_qualified_name.get_slice_count("::"); + for (int i = 1; result != nullptr && i < name_count; i++) { + String current_name = p_qualified_name.get_slice("::", i); + if (HashMap<StringName, Ref<GDScript>>::Iterator E = result->subclasses.find(current_name)) { + result = E->value.ptr(); + } else { + // Couldn't find inner class. + return nullptr; + } + } + + return result; +} + +bool GDScript::is_subclass(const GDScript *p_script) { + String fqn = p_script->fully_qualified_name; + if (!fqn.is_empty() && fqn != fully_qualified_name && fqn.begins_with(fully_qualified_name)) { + String fqn_rest = fqn.substr(fully_qualified_name.length()); + return find_class(fqn_rest) == p_script; + } + return false; +} + +GDScript *GDScript::get_root_script() { + GDScript *result = this; + while (result->_owner) { + result = result->_owner; + } + return result; +} + bool GDScript::has_script_signal(const StringName &p_signal) const { if (_signals.has(p_signal)) { return true; @@ -1238,25 +1244,11 @@ void GDScript::_init_rpc_methods_properties() { rpc_config = base->rpc_config.duplicate(); } - GDScript *cscript = this; - HashMap<StringName, Ref<GDScript>>::Iterator sub_E = subclasses.begin(); - while (cscript) { - // RPC Methods - for (KeyValue<StringName, GDScriptFunction *> &E : cscript->member_functions) { - Variant config = E.value->get_rpc_config(); - if (config.get_type() != Variant::NIL) { - rpc_config[E.value->get_name()] = config; - } - } - - if (cscript != this) { - ++sub_E; - } - - if (sub_E) { - cscript = sub_E->value.ptr(); - } else { - cscript = nullptr; + // RPC Methods + for (KeyValue<StringName, GDScriptFunction *> &E : member_functions) { + Variant config = E.value->get_rpc_config(); + if (config.get_type() != Variant::NIL) { + rpc_config[E.value->get_name()] = config; } } } diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 72ad890fbc..61600b1258 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -137,7 +137,6 @@ class GDScript : public Script { void _super_implicit_constructor(GDScript *p_script, GDScriptInstance *p_instance, Callable::CallError &r_error); GDScriptInstance *_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_is_ref_counted, Callable::CallError &r_error); - void _set_subclass_path(Ref<GDScript> &p_sc, const String &p_path); String _get_debug_path() const; #ifdef TOOLS_ENABLED @@ -178,6 +177,11 @@ public: bool inherits_script(const Ref<Script> &p_script) const override; + GDScript *find_class(const String &p_qualified_name); + bool is_subclass(const GDScript *p_script); + GDScript *get_root_script(); + bool is_root_script() const { return _owner == nullptr; } + String get_fully_qualified_name() const { return fully_qualified_name; } const HashMap<StringName, Ref<GDScript>> &get_subclasses() const { return subclasses; } const HashMap<StringName, Variant> &get_constants() const { return constants; } const HashSet<StringName> &get_members() const { return members; } @@ -223,7 +227,6 @@ public: virtual Error reload(bool p_keep_state = false) override; virtual void set_path(const String &p_path, bool p_take_over = false) override; - void set_script_path(const String &p_path) { path = p_path; } //because subclasses need a path too... Error load_source_code(const String &p_path); Error load_byte_code(const String &p_path); diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 704dda8045..8da77b9e5b 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -213,16 +213,6 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, return OK; } - if (p_class == parser->head) { - if (p_class->identifier) { - p_class->fqcn = p_class->identifier->name; - } else { - p_class->fqcn = parser->script_path; - } - } else { - p_class->fqcn = p_class->outer->fqcn + "::" + String(p_class->identifier->name); - } - if (p_class->identifier) { StringName class_name = p_class->identifier->name; if (class_exists(class_name)) { diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp index 271296c2f9..03a101b9fc 100644 --- a/modules/gdscript/gdscript_cache.cpp +++ b/modules/gdscript/gdscript_cache.cpp @@ -34,6 +34,7 @@ #include "core/templates/vector.h" #include "gdscript.h" #include "gdscript_analyzer.h" +#include "gdscript_compiler.h" #include "gdscript_parser.h" bool GDScriptParserRef::is_valid() const { @@ -161,7 +162,7 @@ String GDScriptCache::get_source_code(const String &p_path) { return source; } -Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, const String &p_owner) { +Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, Error &r_error, const String &p_owner) { MutexLock lock(singleton->lock); if (!p_owner.is_empty()) { singleton->dependencies[p_owner].insert(p_path); @@ -173,11 +174,16 @@ Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, const Stri return singleton->shallow_gdscript_cache[p_path]; } + Ref<GDScriptParserRef> parser_ref = get_parser(p_path, GDScriptParserRef::PARSED, r_error); + if (r_error != OK) { + return Ref<GDScript>(); + } + Ref<GDScript> script; script.instantiate(); script->set_path(p_path, true); - script->set_script_path(p_path); script->load_source_code(p_path); + GDScriptCompiler::make_scripts(script.ptr(), parser_ref->get_parser()->get_tree(), true); singleton->shallow_gdscript_cache[p_path] = script.ptr(); return script; @@ -200,17 +206,21 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro } if (script.is_null()) { - script = get_shallow_script(p_path); - ERR_FAIL_COND_V(script.is_null(), Ref<GDScript>()); + script = get_shallow_script(p_path, r_error); + if (r_error) { + return script; + } } - r_error = script->load_source_code(p_path); + if (p_update_from_disk) { + r_error = script->load_source_code(p_path); + } if (r_error) { return script; } - r_error = script->reload(); + r_error = script->reload(true); if (r_error) { return script; } @@ -221,9 +231,25 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro return script; } +Ref<GDScript> GDScriptCache::get_cached_script(const String &p_path) { + MutexLock lock(singleton->lock); + + if (singleton->full_gdscript_cache.has(p_path)) { + return singleton->full_gdscript_cache[p_path]; + } + + if (singleton->shallow_gdscript_cache.has(p_path)) { + return singleton->shallow_gdscript_cache[p_path]; + } + + return Ref<GDScript>(); +} + Error GDScriptCache::finish_compiling(const String &p_owner) { + MutexLock lock(singleton->lock); + // Mark this as compiled. - Ref<GDScript> script = get_shallow_script(p_owner); + Ref<GDScript> script = get_cached_script(p_owner); singleton->full_gdscript_cache[p_owner] = script.ptr(); singleton->shallow_gdscript_cache.erase(p_owner); diff --git a/modules/gdscript/gdscript_cache.h b/modules/gdscript/gdscript_cache.h index 3d111ea229..fcd240ba8d 100644 --- a/modules/gdscript/gdscript_cache.h +++ b/modules/gdscript/gdscript_cache.h @@ -87,8 +87,9 @@ class GDScriptCache { public: static Ref<GDScriptParserRef> get_parser(const String &p_path, GDScriptParserRef::Status status, Error &r_error, const String &p_owner = String()); static String get_source_code(const String &p_path); - static Ref<GDScript> get_shallow_script(const String &p_path, const String &p_owner = String()); + static Ref<GDScript> get_shallow_script(const String &p_path, Error &r_error, const String &p_owner = String()); static Ref<GDScript> get_full_script(const String &p_path, Error &r_error, const String &p_owner = String(), bool p_update_from_disk = false); + static Ref<GDScript> get_cached_script(const String &p_path); static Error finish_compiling(const String &p_owner); GDScriptCache(); diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index fd13edc4cf..aa1356d8c0 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -80,7 +80,7 @@ void GDScriptCompiler::_set_error(const String &p_error, const GDScriptParser::N } } -GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner) const { +GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner) { if (!p_datatype.is_set() || !p_datatype.is_hard_type()) { return GDScriptDataType(); } @@ -103,74 +103,43 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D } break; case GDScriptParser::DataType::SCRIPT: { result.kind = GDScriptDataType::SCRIPT; - result.script_type_ref = Ref<Script>(p_datatype.script_type); + result.builtin_type = p_datatype.builtin_type; + result.script_type_ref = p_datatype.script_type; result.script_type = result.script_type_ref.ptr(); - result.native_type = result.script_type->get_instance_base_type(); + result.native_type = p_datatype.native_type; } break; case GDScriptParser::DataType::CLASS: { - // Locate class by constructing the path to it and following that path. - GDScriptParser::ClassNode *class_type = p_datatype.class_type; - if (class_type) { - result.kind = GDScriptDataType::GDSCRIPT; - result.builtin_type = p_datatype.builtin_type; - - String class_name = class_type->fqcn.split("::")[0]; - const bool is_inner_by_path = (!main_script->path.is_empty()) && (class_name == main_script->path); - const bool is_inner_by_name = (!main_script->name.is_empty()) && (class_name == main_script->name); - if (is_inner_by_path || is_inner_by_name) { - // Local class. - List<StringName> names; - while (class_type->outer) { - names.push_back(class_type->identifier->name); - class_type = class_type->outer; - } + result.kind = GDScriptDataType::GDSCRIPT; + result.builtin_type = p_datatype.builtin_type; + result.native_type = p_datatype.native_type; - Ref<GDScript> script = Ref<GDScript>(main_script); - while (names.back()) { - if (!script->subclasses.has(names.back()->get())) { - ERR_PRINT("Parser bug: Cannot locate datatype class."); - result.has_type = false; - return GDScriptDataType(); - } - script = script->subclasses[names.back()->get()]; - names.pop_back(); - } - result.script_type = script.ptr(); - result.native_type = script->get_instance_base_type(); - } else { - // Inner class. - PackedStringArray classes = class_type->fqcn.split("::"); - if (!classes.is_empty()) { - for (GDScript *script : parsed_classes) { - // Checking of inheritance structure of inner class to find a correct script link. - if (script->name == classes[classes.size() - 1]) { - PackedStringArray classes2 = script->fully_qualified_name.split("::"); - bool valid = true; - if (classes.size() != classes2.size()) { - valid = false; - } else { - for (int i = 0; i < classes.size(); i++) { - if (classes[i] != classes2[i]) { - valid = false; - break; - } - } - } - if (!valid) { - continue; - } - result.script_type_ref = Ref<GDScript>(script); - break; - } - } - } - if (result.script_type_ref.is_null()) { - result.script_type_ref = GDScriptCache::get_shallow_script(p_datatype.script_path, main_script->path); - } + String root_name = p_datatype.class_type->fqcn.get_slice("::", 0); + bool is_local_class = !root_name.is_empty() && root_name == main_script->fully_qualified_name; + + Ref<GDScript> script; + if (is_local_class) { + script = Ref<GDScript>(main_script); + } else { + Error err = OK; + script = GDScriptCache::get_shallow_script(p_datatype.script_path, err, p_owner->path); + if (err) { + _set_error(vformat(R"(Could not find script "%s": %s)", p_datatype.script_path, error_names[err]), nullptr); + } + } - result.script_type = result.script_type_ref.ptr(); - result.native_type = p_datatype.native_type; + if (script.is_valid()) { + script = Ref<GDScript>(script->find_class(p_datatype.class_type->fqcn)); + } + + if (script.is_null()) { + _set_error(vformat(R"(Could not find class "%s" in "%s".)", p_datatype.class_type->fqcn, p_datatype.script_path), nullptr); + } else { + // Only hold a strong reference if the owner of the element qualified with this type is not local, to avoid cyclic references (leaks). + // TODO: Might lead to use after free if script_type is a subclass and is used after its parent is freed. + if (!is_local_class) { + result.script_type_ref = script; } + result.script_type = script.ptr(); } } break; case GDScriptParser::DataType::ENUM: @@ -189,13 +158,7 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D } if (p_datatype.has_container_element_type()) { - result.set_container_element_type(_gdtype_from_datatype(p_datatype.get_container_element_type())); - } - - // Only hold strong reference to the script if it's not the owner of the - // element qualified with this type, to avoid cyclic references (leaks). - if (result.script_type && result.script_type == p_owner) { - result.script_type_ref = Ref<Script>(); + result.set_container_element_type(_gdtype_from_datatype(p_datatype.get_container_element_type(), p_owner)); } return result; @@ -367,7 +330,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code // This is so one autoload doesn't try to load another before it's compiled. HashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); if (autoloads.has(identifier) && autoloads[identifier].is_singleton) { - GDScriptCodeGenerator::Address global = codegen.add_temporary(_gdtype_from_datatype(in->get_datatype())); + GDScriptCodeGenerator::Address global = codegen.add_temporary(_gdtype_from_datatype(in->get_datatype(), codegen.script)); int idx = GDScriptLanguage::get_singleton()->get_global_map()[identifier]; gen->write_store_global(global, idx); return global; @@ -434,7 +397,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code Vector<GDScriptCodeGenerator::Address> values; // Create the result temporary first since it's the last to be killed. - GDScriptDataType array_type = _gdtype_from_datatype(an->get_datatype()); + GDScriptDataType array_type = _gdtype_from_datatype(an->get_datatype(), codegen.script); GDScriptCodeGenerator::Address result = codegen.add_temporary(array_type); for (int i = 0; i < an->elements.size(); i++) { @@ -511,7 +474,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code case GDScriptParser::Node::CAST: { const GDScriptParser::CastNode *cn = static_cast<const GDScriptParser::CastNode *>(p_expression); GDScriptParser::DataType og_cast_type = cn->cast_type->get_datatype(); - GDScriptDataType cast_type = _gdtype_from_datatype(og_cast_type); + GDScriptDataType cast_type = _gdtype_from_datatype(og_cast_type, codegen.script); if (og_cast_type.kind == GDScriptParser::DataType::ENUM) { // Enum types are usually treated as dictionaries, but in this case we want to cast to an integer. @@ -534,7 +497,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code } break; case GDScriptParser::Node::CALL: { const GDScriptParser::CallNode *call = static_cast<const GDScriptParser::CallNode *>(p_expression); - GDScriptDataType type = _gdtype_from_datatype(call->get_datatype()); + GDScriptDataType type = _gdtype_from_datatype(call->get_datatype(), codegen.script); GDScriptCodeGenerator::Address result = codegen.add_temporary(type); GDScriptCodeGenerator::Address nil = GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::NIL); @@ -670,7 +633,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code Vector<GDScriptCodeGenerator::Address> args; args.push_back(codegen.add_constant(NodePath(get_node->full_path))); - GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(get_node->get_datatype())); + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(get_node->get_datatype(), codegen.script)); MethodBind *get_node_method = ClassDB::get_method("Node", "get_node"); gen->write_call_ptrcall(result, GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF), get_node_method, args); @@ -686,7 +649,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code case GDScriptParser::Node::AWAIT: { const GDScriptParser::AwaitNode *await = static_cast<const GDScriptParser::AwaitNode *>(p_expression); - GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(p_expression->get_datatype())); + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(p_expression->get_datatype(), codegen.script)); within_await = true; GDScriptCodeGenerator::Address argument = _parse_expression(codegen, r_error, await->to_await); within_await = false; @@ -705,7 +668,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code // Indexing operator. case GDScriptParser::Node::SUBSCRIPT: { const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(p_expression); - GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(subscript->get_datatype())); + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(subscript->get_datatype(), codegen.script)); GDScriptCodeGenerator::Address base = _parse_expression(codegen, r_error, subscript->base); if (r_error) { @@ -735,7 +698,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code // Remove result temp as we don't need it. gen->pop_temporary(); // Faster than indexing self (as if no self. had been used). - return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::MEMBER, MI->value.index, _gdtype_from_datatype(subscript->get_datatype())); + return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::MEMBER, MI->value.index, _gdtype_from_datatype(subscript->get_datatype(), codegen.script)); } } @@ -773,7 +736,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code case GDScriptParser::Node::UNARY_OPERATOR: { const GDScriptParser::UnaryOpNode *unary = static_cast<const GDScriptParser::UnaryOpNode *>(p_expression); - GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(unary->get_datatype())); + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(unary->get_datatype(), codegen.script)); GDScriptCodeGenerator::Address operand = _parse_expression(codegen, r_error, unary->operand); if (r_error) { @@ -791,7 +754,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code case GDScriptParser::Node::BINARY_OPERATOR: { const GDScriptParser::BinaryOpNode *binary = static_cast<const GDScriptParser::BinaryOpNode *>(p_expression); - GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(binary->get_datatype())); + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(binary->get_datatype(), codegen.script)); switch (binary->operation) { case GDScriptParser::BinaryOpNode::OP_LOGIC_AND: { @@ -867,7 +830,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code case GDScriptParser::Node::TERNARY_OPERATOR: { // x IF a ELSE y operator with early out on failure. const GDScriptParser::TernaryOpNode *ternary = static_cast<const GDScriptParser::TernaryOpNode *>(p_expression); - GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(ternary->get_datatype())); + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(ternary->get_datatype(), codegen.script)); gen->write_start_ternary(result); @@ -985,7 +948,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code break; } const GDScriptParser::SubscriptNode *subscript_elem = E->get(); - GDScriptCodeGenerator::Address value = codegen.add_temporary(_gdtype_from_datatype(subscript_elem->get_datatype())); + GDScriptCodeGenerator::Address value = codegen.add_temporary(_gdtype_from_datatype(subscript_elem->get_datatype(), codegen.script)); GDScriptCodeGenerator::Address key; StringName name; @@ -1024,8 +987,8 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code // Perform operator if any. if (assignment->operation != GDScriptParser::AssignmentNode::OP_NONE) { - GDScriptCodeGenerator::Address op_result = codegen.add_temporary(_gdtype_from_datatype(assignment->get_datatype())); - GDScriptCodeGenerator::Address value = codegen.add_temporary(_gdtype_from_datatype(subscript->get_datatype())); + GDScriptCodeGenerator::Address op_result = codegen.add_temporary(_gdtype_from_datatype(assignment->get_datatype(), codegen.script)); + GDScriptCodeGenerator::Address value = codegen.add_temporary(_gdtype_from_datatype(subscript->get_datatype(), codegen.script)); if (subscript->is_attribute) { gen->write_get_named(value, name, prev_base); } else { @@ -1130,8 +1093,8 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code StringName name = static_cast<GDScriptParser::IdentifierNode *>(assignment->assignee)->name; if (has_operation) { - GDScriptCodeGenerator::Address op_result = codegen.add_temporary(_gdtype_from_datatype(assignment->get_datatype())); - GDScriptCodeGenerator::Address member = codegen.add_temporary(_gdtype_from_datatype(assignment->assignee->get_datatype())); + GDScriptCodeGenerator::Address op_result = codegen.add_temporary(_gdtype_from_datatype(assignment->get_datatype(), codegen.script)); + GDScriptCodeGenerator::Address member = codegen.add_temporary(_gdtype_from_datatype(assignment->assignee->get_datatype(), codegen.script)); gen->write_get_member(member, name); gen->write_binary_operator(op_result, assignment->variant_op, member, assigned_value); gen->pop_temporary(); // Pop member temp. @@ -1184,7 +1147,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code bool has_operation = assignment->operation != GDScriptParser::AssignmentNode::OP_NONE; if (has_operation) { // Perform operation. - GDScriptCodeGenerator::Address op_result = codegen.add_temporary(_gdtype_from_datatype(assignment->get_datatype())); + GDScriptCodeGenerator::Address op_result = codegen.add_temporary(_gdtype_from_datatype(assignment->get_datatype(), codegen.script)); GDScriptCodeGenerator::Address og_value = _parse_expression(codegen, r_error, assignment->assignee); gen->write_binary_operator(op_result, assignment->variant_op, og_value, assigned_value); to_assign = op_result; @@ -1196,7 +1159,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code to_assign = assigned_value; } - GDScriptDataType assign_type = _gdtype_from_datatype(assignment->assignee->get_datatype()); + GDScriptDataType assign_type = _gdtype_from_datatype(assignment->assignee->get_datatype(), codegen.script); if (has_setter && !is_in_setter) { // Call setter. @@ -1226,7 +1189,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code } break; case GDScriptParser::Node::LAMBDA: { const GDScriptParser::LambdaNode *lambda = static_cast<const GDScriptParser::LambdaNode *>(p_expression); - GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(lambda->get_datatype())); + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(lambda->get_datatype(), codegen.script)); Vector<GDScriptCodeGenerator::Address> captures; captures.resize(lambda->captures.size()); @@ -1645,7 +1608,7 @@ void GDScriptCompiler::_add_locals_in_block(CodeGen &codegen, const GDScriptPars // Parameters are added directly from function and loop variables are declared explicitly. continue; } - codegen.add_local(p_block->locals[i].name, _gdtype_from_datatype(p_block->locals[i].get_datatype())); + codegen.add_local(p_block->locals[i].name, _gdtype_from_datatype(p_block->locals[i].get_datatype(), codegen.script)); } } @@ -1675,7 +1638,7 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui codegen.start_block(); // Evaluate the match expression. - GDScriptCodeGenerator::Address value = codegen.add_local("@match_value", _gdtype_from_datatype(match->test->get_datatype())); + GDScriptCodeGenerator::Address value = codegen.add_local("@match_value", _gdtype_from_datatype(match->test->get_datatype(), codegen.script)); GDScriptCodeGenerator::Address value_expr = _parse_expression(codegen, err, match->test); if (err) { return err; @@ -1784,9 +1747,9 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui const GDScriptParser::ForNode *for_n = static_cast<const GDScriptParser::ForNode *>(s); codegen.start_block(); - GDScriptCodeGenerator::Address iterator = codegen.add_local(for_n->variable->name, _gdtype_from_datatype(for_n->variable->get_datatype())); + GDScriptCodeGenerator::Address iterator = codegen.add_local(for_n->variable->name, _gdtype_from_datatype(for_n->variable->get_datatype(), codegen.script)); - gen->start_for(iterator.type, _gdtype_from_datatype(for_n->list->get_datatype())); + gen->start_for(iterator.type, _gdtype_from_datatype(for_n->list->get_datatype(), codegen.script)); GDScriptCodeGenerator::Address list = _parse_expression(codegen, err, for_n->list); if (err) { @@ -1897,13 +1860,13 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui const GDScriptParser::VariableNode *lv = static_cast<const GDScriptParser::VariableNode *>(s); // Should be already in stack when the block began. GDScriptCodeGenerator::Address local = codegen.locals[lv->identifier->name]; - GDScriptParser::DataType local_type = lv->get_datatype(); + GDScriptDataType local_type = _gdtype_from_datatype(lv->get_datatype(), codegen.script); if (lv->initializer != nullptr) { // For typed arrays we need to make sure this is already initialized correctly so typed assignment work. - if (local_type.is_hard_type() && local_type.builtin_type == Variant::ARRAY) { + if (local_type.has_type && local_type.builtin_type == Variant::ARRAY) { if (local_type.has_container_element_type()) { - codegen.generator->write_construct_typed_array(local, _gdtype_from_datatype(local_type.get_container_element_type(), codegen.script), Vector<GDScriptCodeGenerator::Address>()); + codegen.generator->write_construct_typed_array(local, local_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>()); } else { codegen.generator->write_construct_array(local, Vector<GDScriptCodeGenerator::Address>()); } @@ -1920,11 +1883,11 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui if (src_address.mode == GDScriptCodeGenerator::Address::TEMPORARY) { codegen.generator->pop_temporary(); } - } else if (lv->get_datatype().is_hard_type()) { + } else if (local_type.has_type) { // Initialize with default for type. if (local_type.has_container_element_type()) { - codegen.generator->write_construct_typed_array(local, _gdtype_from_datatype(local_type.get_container_element_type(), codegen.script), Vector<GDScriptCodeGenerator::Address>()); - } else if (local_type.kind == GDScriptParser::DataType::BUILTIN) { + codegen.generator->write_construct_typed_array(local, local_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>()); + } else if (local_type.kind == GDScriptDataType::BUILTIN) { codegen.generator->write_construct(local, local_type.builtin_type, Vector<GDScriptCodeGenerator::Address>()); } // The `else` branch is for objects, in such case we leave it as `null`. @@ -2033,17 +1996,17 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ continue; } - GDScriptParser::DataType field_type = field->get_datatype(); + GDScriptDataType field_type = _gdtype_from_datatype(field->get_datatype(), codegen.script); - GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, _gdtype_from_datatype(field->get_datatype())); + GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, field_type); if (field->initializer) { // Emit proper line change. codegen.generator->write_newline(field->initializer->start_line); // For typed arrays we need to make sure this is already initialized correctly so typed assignment work. - if (field_type.is_hard_type() && field_type.builtin_type == Variant::ARRAY) { + if (field_type.has_type && field_type.builtin_type == Variant::ARRAY) { if (field_type.has_container_element_type()) { - codegen.generator->write_construct_typed_array(dst_address, _gdtype_from_datatype(field_type.get_container_element_type(), codegen.script), Vector<GDScriptCodeGenerator::Address>()); + codegen.generator->write_construct_typed_array(dst_address, field_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>()); } else { codegen.generator->write_construct_array(dst_address, Vector<GDScriptCodeGenerator::Address>()); } @@ -2062,13 +2025,13 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ if (src_address.mode == GDScriptCodeGenerator::Address::TEMPORARY) { codegen.generator->pop_temporary(); } - } else if (field->get_datatype().is_hard_type()) { + } else if (field_type.has_type) { codegen.generator->write_newline(field->start_line); // Initialize with default for type. if (field_type.has_container_element_type()) { - codegen.generator->write_construct_typed_array(dst_address, _gdtype_from_datatype(field_type.get_container_element_type(), codegen.script), Vector<GDScriptCodeGenerator::Address>()); - } else if (field_type.kind == GDScriptParser::DataType::BUILTIN) { + codegen.generator->write_construct_typed_array(dst_address, field_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>()); + } else if (field_type.kind == GDScriptDataType::BUILTIN) { codegen.generator->write_construct(dst_address, field_type.builtin_type, Vector<GDScriptCodeGenerator::Address>()); } // The `else` branch is for objects, in such case we leave it as `null`. @@ -2195,23 +2158,19 @@ Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptP return err; } -Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { - parsing_classes.insert(p_script); +Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { + if (parsed_classes.has(p_script)) { + return OK; + } - if (p_class->outer && p_class->outer->outer) { - // Owner is not root - if (!parsed_classes.has(p_script->_owner)) { - if (parsing_classes.has(p_script->_owner)) { - _set_error("Cyclic class reference for '" + String(p_class->identifier->name) + "'.", p_class); - return ERR_PARSE_ERROR; - } - Error err = _parse_class_level(p_script->_owner, p_class->outer, p_keep_state); - if (err) { - return err; - } - } + if (parsing_classes.has(p_script)) { + String class_name = p_class->identifier ? String(p_class->identifier->name) : p_class->fqcn; + _set_error(vformat(R"(Cyclic class reference for "%s".)", class_name), p_class); + return ERR_PARSE_ERROR; } + parsing_classes.insert(p_script); + #ifdef TOOLS_ENABLED p_script->doc_functions.clear(); p_script->doc_variables.clear(); @@ -2253,7 +2212,6 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar p_script->implicit_ready = nullptr; p_script->tool = parser->is_tool(); - p_script->name = p_class->identifier ? p_class->identifier->name : ""; if (!p_script->name.is_empty()) { if (ClassDB::class_exists(p_script->name) && ClassDB::is_class_exposed(p_script->name)) { @@ -2262,53 +2220,50 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar } } - Ref<GDScriptNativeClass> native; + GDScriptDataType base_type = _gdtype_from_datatype(p_class->base_type, p_script); - GDScriptDataType base_type = _gdtype_from_datatype(p_class->base_type); // Inheritance switch (base_type.kind) { case GDScriptDataType::NATIVE: { int native_idx = GDScriptLanguage::get_singleton()->get_global_map()[base_type.native_type]; - native = GDScriptLanguage::get_singleton()->get_global_array()[native_idx]; - ERR_FAIL_COND_V(native.is_null(), ERR_BUG); - p_script->native = native; + p_script->native = GDScriptLanguage::get_singleton()->get_global_array()[native_idx]; + ERR_FAIL_COND_V(p_script->native.is_null(), ERR_BUG); } break; case GDScriptDataType::GDSCRIPT: { Ref<GDScript> base = Ref<GDScript>(base_type.script_type); - p_script->base = base; - p_script->_base = base.ptr(); + if (base.is_null()) { + return ERR_COMPILATION_FAILED; + } - if (p_class->base_type.kind == GDScriptParser::DataType::CLASS && p_class->base_type.class_type != nullptr) { - if (p_class->base_type.script_path == main_script->path) { - if (!parsed_classes.has(p_script->_base)) { - if (parsing_classes.has(p_script->_base)) { - String class_name = p_class->identifier ? p_class->identifier->name : "<main>"; - _set_error("Cyclic class reference for '" + class_name + "'.", p_class); - return ERR_PARSE_ERROR; - } - Error err = _parse_class_level(p_script->_base, p_class->base_type.class_type, p_keep_state); - if (err) { - return err; - } - } - } else { - Error err = OK; - base = GDScriptCache::get_full_script(p_class->base_type.script_path, err, main_script->path); - if (err) { - return err; - } - if (base.is_null() || !base->is_valid()) { - return ERR_COMPILATION_FAILED; - } + if (base.ptr() == main_script || main_script->is_subclass(base.ptr())) { + Error err = _populate_class_members(base.ptr(), p_class->base_type.class_type, p_keep_state); + if (err) { + return err; + } + } else if (!base->is_valid()) { + Error err = OK; + Ref<GDScript> base_root = GDScriptCache::get_full_script(base->path, err, p_script->path); + if (err) { + _set_error(vformat(R"(Could not compile base class "%s" from "%s": %s)", base->fully_qualified_name, base->path, error_names[err]), nullptr); + return err; } + if (base_root.is_valid()) { + base = Ref<GDScript>(base_root->find_class(base->fully_qualified_name)); + } + if (base.is_null()) { + _set_error(vformat(R"(Could not find class "%s" in "%s".)", base->fully_qualified_name, base->path), nullptr); + return ERR_COMPILATION_FAILED; + } + ERR_FAIL_COND_V(!base->is_valid(), ERR_BUG); } + p_script->base = base; + p_script->_base = base.ptr(); p_script->member_indices = base->member_indices; - native = base->native; - p_script->native = native; + p_script->native = base->native; } break; default: { - _set_error("Parser bug: invalid inheritance.", p_class); + _set_error("Parser bug: invalid inheritance.", nullptr); return ERR_BUG; } break; } @@ -2478,8 +2433,7 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar parsed_classes.insert(p_script); parsing_classes.erase(p_script); - //parse sub-classes - + // Populate sub-classes. for (int i = 0; i < p_class->members.size(); i++) { const GDScriptParser::ClassNode::Member &member = p_class->members[i]; if (member.type != member.CLASS) { @@ -2491,8 +2445,8 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar GDScript *subclass_ptr = subclass.ptr(); // Subclass might still be parsing, just skip it - if (!parsed_classes.has(subclass_ptr) && !parsing_classes.has(subclass_ptr)) { - Error err = _parse_class_level(subclass_ptr, inner_class, p_keep_state); + if (!parsing_classes.has(subclass_ptr)) { + Error err = _populate_class_members(subclass_ptr, inner_class, p_keep_state); if (err) { return err; } @@ -2509,9 +2463,8 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar return OK; } -Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { - //parse methods - +Error GDScriptCompiler::_compile_class(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { + // Compile member functions, getters, and setters. for (int i = 0; i < p_class->members.size(); i++) { const GDScriptParser::ClassNode::Member &member = p_class->members[i]; if (member.type == member.FUNCTION) { @@ -2615,17 +2568,26 @@ Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptPa StringName name = inner_class->identifier->name; GDScript *subclass = p_script->subclasses[name].ptr(); - Error err = _parse_class_blocks(subclass, inner_class, p_keep_state); + Error err = _compile_class(subclass, inner_class, p_keep_state); if (err) { return err; } } +#ifdef TOOLS_ENABLED + p_script->_update_doc(); +#endif + + p_script->_init_rpc_methods_properties(); + p_script->valid = true; return OK; } -void GDScriptCompiler::_make_scripts(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { +void GDScriptCompiler::make_scripts(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { + p_script->fully_qualified_name = p_class->fqcn; + p_script->name = p_class->identifier ? p_class->identifier->name : ""; + HashMap<StringName, Ref<GDScript>> old_subclasses; if (p_keep_state) { @@ -2642,24 +2604,22 @@ void GDScriptCompiler::_make_scripts(GDScript *p_script, const GDScriptParser::C StringName name = inner_class->identifier->name; Ref<GDScript> subclass; - String fully_qualified_name = p_script->fully_qualified_name + "::" + name; if (old_subclasses.has(name)) { subclass = old_subclasses[name]; } else { - Ref<GDScript> orphan_subclass = GDScriptLanguage::get_singleton()->get_orphan_subclass(fully_qualified_name); - if (orphan_subclass.is_valid()) { - subclass = orphan_subclass; - } else { - subclass.instantiate(); - } + subclass = GDScriptLanguage::get_singleton()->get_orphan_subclass(inner_class->fqcn); + } + + if (subclass.is_null()) { + subclass.instantiate(); } subclass->_owner = p_script; - subclass->fully_qualified_name = fully_qualified_name; + subclass->path = p_script->path; p_script->subclasses.insert(name, subclass); - _make_scripts(subclass.ptr(), inner_class, false); + make_scripts(subclass.ptr(), inner_class, p_keep_state); } } @@ -2673,26 +2633,22 @@ Error GDScriptCompiler::compile(const GDScriptParser *p_parser, GDScript *p_scri source = p_script->get_path(); - // The best fully qualified name for a base level script is its file path - p_script->fully_qualified_name = p_script->path; - // Create scripts for subclasses beforehand so they can be referenced - _make_scripts(p_script, root, p_keep_state); + make_scripts(p_script, root, p_keep_state); - p_script->_owner = nullptr; - Error err = _parse_class_level(p_script, root, p_keep_state); + main_script->_owner = nullptr; + Error err = _populate_class_members(main_script, parser->get_tree(), p_keep_state); if (err) { return err; } - err = _parse_class_blocks(p_script, root, p_keep_state); - + err = _compile_class(main_script, root, p_keep_state); if (err) { return err; } - return GDScriptCache::finish_compiling(p_script->get_path()); + return GDScriptCache::finish_compiling(main_script->get_path()); } String GDScriptCompiler::get_error() const { diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h index e4264ea55b..45ca4fe342 100644 --- a/modules/gdscript/gdscript_compiler.h +++ b/modules/gdscript/gdscript_compiler.h @@ -121,7 +121,7 @@ class GDScriptCompiler { Error _create_binary_operator(CodeGen &codegen, const GDScriptParser::BinaryOpNode *on, Variant::Operator op, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); Error _create_binary_operator(CodeGen &codegen, const GDScriptParser::ExpressionNode *p_left_operand, const GDScriptParser::ExpressionNode *p_right_operand, Variant::Operator op, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); - GDScriptDataType _gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner = nullptr) const; + GDScriptDataType _gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner); GDScriptCodeGenerator::Address _parse_assign_right_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::AssignmentNode *p_assignmentint, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); GDScriptCodeGenerator::Address _parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root = false, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); @@ -130,9 +130,8 @@ class GDScriptCompiler { Error _parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, bool p_add_locals = true); GDScriptFunction *_parse_function(Error &r_error, GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready = false, bool p_for_lambda = false); Error _parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter); - Error _parse_class_level(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); - Error _parse_class_blocks(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); - void _make_scripts(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); + Error _populate_class_members(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); + Error _compile_class(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); int err_line = 0; int err_column = 0; StringName source; @@ -140,6 +139,7 @@ class GDScriptCompiler { bool within_await = false; public: + static void make_scripts(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); Error compile(const GDScriptParser *p_parser, GDScript *p_script, bool p_keep_state = false); String get_error() const; diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index f33d1409bc..68508b20da 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -2765,7 +2765,61 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c const GDScriptParser::SubscriptNode *attr = static_cast<const GDScriptParser::SubscriptNode *>(completion_context.node); if (attr->base) { GDScriptCompletionIdentifier base; - if (!_guess_expression_type(completion_context, attr->base, base)) { + bool found_type = false; + + if (p_owner != nullptr && attr->base->type == GDScriptParser::Node::IDENTIFIER) { + const GDScriptParser::GetNodeNode *get_node = nullptr; + const GDScriptParser::IdentifierNode *identifier_node = static_cast<GDScriptParser::IdentifierNode *>(attr->base); + + switch (identifier_node->source) { + case GDScriptParser::IdentifierNode::Source::MEMBER_VARIABLE: { + if (completion_context.current_class != nullptr) { + const StringName &member_name = identifier_node->name; + const GDScriptParser::ClassNode *current_class = completion_context.current_class; + + if (current_class->has_member(member_name)) { + const GDScriptParser::ClassNode::Member &member = current_class->get_member(member_name); + + if (member.type == GDScriptParser::ClassNode::Member::VARIABLE) { + const GDScriptParser::VariableNode *variable = static_cast<GDScriptParser::VariableNode *>(member.variable); + + if (variable->initializer && variable->initializer->type == GDScriptParser::Node::GET_NODE) { + get_node = static_cast<GDScriptParser::GetNodeNode *>(variable->initializer); + } + } + } + } + } break; + case GDScriptParser::IdentifierNode::Source::LOCAL_VARIABLE: { + if (identifier_node->next != nullptr && identifier_node->next->type == GDScriptParser::ClassNode::Node::GET_NODE) { + get_node = static_cast<GDScriptParser::GetNodeNode *>(identifier_node->next); + } + } break; + default: + break; + } + + if (get_node != nullptr) { + const Object *node = p_owner->call("get_node_or_null", NodePath(get_node->full_path)); + if (node != nullptr) { + found_type = true; + + GDScriptParser::DataType type; + type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + type.kind = GDScriptParser::DataType::NATIVE; + type.native_type = node->get_class_name(); + type.builtin_type = Variant::OBJECT; + + base.type = type; + } + + if (!found_type) { + break; + } + } + } + + if (!found_type && !_guess_expression_type(completion_context, attr->base, base)) { break; } @@ -3051,8 +3105,9 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co r_result.type = ScriptLanguage::LOOKUP_RESULT_SCRIPT_LOCATION; r_result.location = base_type.class_type->get_member(p_symbol).get_line(); r_result.class_path = base_type.script_path; - r_result.script = GDScriptCache::get_shallow_script(r_result.class_path); - return OK; + Error err = OK; + r_result.script = GDScriptCache::get_shallow_script(r_result.class_path, err); + return err; } base_type = base_type.class_type->base_type; } diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 6842a1ff49..71cedb4f38 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -534,6 +534,7 @@ void GDScriptParser::end_statement(const String &p_context) { void GDScriptParser::parse_program() { head = alloc_node<ClassNode>(); + head->fqcn = script_path; current_class = head; // If we happen to parse an annotation before extends or class_name keywords, track it. @@ -646,6 +647,9 @@ GDScriptParser::ClassNode *GDScriptParser::parse_class() { if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier for the class name after "class".)")) { n_class->identifier = parse_identifier(); + if (n_class->outer) { + n_class->fqcn = n_class->outer->fqcn + "::" + n_class->identifier->name; + } } if (match(GDScriptTokenizer::Token::EXTENDS)) { @@ -684,6 +688,7 @@ GDScriptParser::ClassNode *GDScriptParser::parse_class() { void GDScriptParser::parse_class_name() { if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier for the global class name after "class_name".)")) { current_class->identifier = parse_identifier(); + current_class->fqcn = String(current_class->identifier->name); } if (match(GDScriptTokenizer::Token::EXTENDS)) { diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp index 15131afde7..1ccbf9d150 100644 --- a/modules/gdscript/tests/gdscript_test_runner.cpp +++ b/modules/gdscript/tests/gdscript_test_runner.cpp @@ -461,7 +461,6 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) { Ref<GDScript> script; script.instantiate(); script->set_path(source_file); - script->set_script_path(source_file); err = script->load_source_code(source_file); if (err != OK) { enable_stdout(); diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_inner_base.gd b/modules/gdscript/tests/scripts/analyzer/features/external_inner_base.gd new file mode 100644 index 0000000000..3f9bfe189c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/external_inner_base.gd @@ -0,0 +1,4 @@ +extends "inner_base.gd".InnerA.InnerAB + +func test(): + super.test() diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_inner_base.out b/modules/gdscript/tests/scripts/analyzer/features/external_inner_base.out new file mode 100644 index 0000000000..62f1383392 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/external_inner_base.out @@ -0,0 +1,3 @@ +GDTEST_OK +InnerA.InnerAB.test +InnerB.test diff --git a/modules/gdscript/tests/scripts/analyzer/features/inner_base.gd b/modules/gdscript/tests/scripts/analyzer/features/inner_base.gd new file mode 100644 index 0000000000..a825b59255 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/inner_base.gd @@ -0,0 +1,18 @@ +extends InnerA + +func test(): + super.test() + +class InnerA extends InnerAB: + func test(): + print("InnerA.test") + super.test() + + class InnerAB extends InnerB: + func test(): + print("InnerA.InnerAB.test") + super.test() + +class InnerB: + func test(): + print("InnerB.test") diff --git a/modules/gdscript/tests/scripts/analyzer/features/inner_base.out b/modules/gdscript/tests/scripts/analyzer/features/inner_base.out new file mode 100644 index 0000000000..ddd5ffcfd3 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/inner_base.out @@ -0,0 +1,4 @@ +GDTEST_OK +InnerA.test +InnerA.InnerAB.test +InnerB.test diff --git a/modules/lightmapper_rd/register_types.cpp b/modules/lightmapper_rd/register_types.cpp index 0e0330c1a1..ed223e1faa 100644 --- a/modules/lightmapper_rd/register_types.cpp +++ b/modules/lightmapper_rd/register_types.cpp @@ -57,6 +57,7 @@ void initialize_lightmapper_rd_module(ModuleInitializationLevel p_level) { GLOBAL_DEF("rendering/lightmapping/bake_quality/high_quality_probe_ray_count", 512); GLOBAL_DEF("rendering/lightmapping/bake_quality/ultra_quality_probe_ray_count", 2048); GLOBAL_DEF("rendering/lightmapping/bake_performance/max_rays_per_probe_pass", 64); + GLOBAL_DEF("rendering/lightmapping/primitive_meshes/texel_size", 0.2); #ifndef _3D_DISABLED GDREGISTER_CLASS(LightmapperRD); Lightmapper::create_gpu = create_lightmapper_rd; diff --git a/editor/debugger/editor_network_profiler.cpp b/modules/multiplayer/editor/editor_network_profiler.cpp index 8c18eba71d..a7e5b80b66 100644 --- a/editor/debugger/editor_network_profiler.cpp +++ b/modules/multiplayer/editor/editor_network_profiler.cpp @@ -59,7 +59,7 @@ void EditorNetworkProfiler::_update_frame() { TreeItem *root = counters_display->create_item(); - for (const KeyValue<ObjectID, SceneDebugger::RPCNodeInfo> &E : nodes_data) { + for (const KeyValue<ObjectID, RPCNodeInfo> &E : nodes_data) { TreeItem *node = counters_display->create_item(root); for (int j = 0; j < counters_display->get_columns(); ++j) { @@ -92,7 +92,7 @@ void EditorNetworkProfiler::_clear_pressed() { } } -void EditorNetworkProfiler::add_node_frame_data(const SceneDebugger::RPCNodeInfo p_frame) { +void EditorNetworkProfiler::add_node_frame_data(const RPCNodeInfo p_frame) { if (!nodes_data.has(p_frame.node)) { nodes_data.insert(p_frame.node, p_frame); } else { diff --git a/editor/debugger/editor_network_profiler.h b/modules/multiplayer/editor/editor_network_profiler.h index aea7ce3eec..98d12e3c0a 100644 --- a/editor/debugger/editor_network_profiler.h +++ b/modules/multiplayer/editor/editor_network_profiler.h @@ -38,10 +38,14 @@ #include "scene/gui/split_container.h" #include "scene/gui/tree.h" +#include "../multiplayer_debugger.h" + class EditorNetworkProfiler : public VBoxContainer { GDCLASS(EditorNetworkProfiler, VBoxContainer) private: + using RPCNodeInfo = MultiplayerDebugger::RPCNodeInfo; + Button *activate = nullptr; Button *clear_button = nullptr; Tree *counters_display = nullptr; @@ -50,7 +54,7 @@ private: Timer *frame_delay = nullptr; - HashMap<ObjectID, SceneDebugger::RPCNodeInfo> nodes_data; + HashMap<ObjectID, RPCNodeInfo> nodes_data; void _update_frame(); @@ -62,7 +66,7 @@ protected: static void _bind_methods(); public: - void add_node_frame_data(const SceneDebugger::RPCNodeInfo p_frame); + void add_node_frame_data(const RPCNodeInfo p_frame); void set_bandwidth(int p_incoming, int p_outgoing); bool is_profiling(); diff --git a/modules/multiplayer/editor/multiplayer_editor_plugin.cpp b/modules/multiplayer/editor/multiplayer_editor_plugin.cpp new file mode 100644 index 0000000000..00b1537827 --- /dev/null +++ b/modules/multiplayer/editor/multiplayer_editor_plugin.cpp @@ -0,0 +1,140 @@ +/*************************************************************************/ +/* multiplayer_editor_plugin.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 "multiplayer_editor_plugin.h" + +#include "../multiplayer_synchronizer.h" +#include "editor_network_profiler.h" +#include "replication_editor.h" + +#include "editor/editor_node.h" + +bool MultiplayerEditorDebugger::has_capture(const String &p_capture) const { + return p_capture == "multiplayer"; +} + +bool MultiplayerEditorDebugger::capture(const String &p_message, const Array &p_data, int p_session) { + ERR_FAIL_COND_V(!profilers.has(p_session), false); + EditorNetworkProfiler *profiler = profilers[p_session]; + if (p_message == "multiplayer:rpc") { + MultiplayerDebugger::RPCFrame frame; + frame.deserialize(p_data); + for (int i = 0; i < frame.infos.size(); i++) { + profiler->add_node_frame_data(frame.infos[i]); + } + return true; + + } else if (p_message == "multiplayer:bandwidth") { + ERR_FAIL_COND_V(p_data.size() < 2, false); + profiler->set_bandwidth(p_data[0], p_data[1]); + return true; + } + return false; +} + +void MultiplayerEditorDebugger::_profiler_activate(bool p_enable, int p_session_id) { + Ref<EditorDebuggerSession> session = get_session(p_session_id); + ERR_FAIL_COND(session.is_null()); + session->toggle_profiler("multiplayer", p_enable); + session->toggle_profiler("rpc", p_enable); +} + +void MultiplayerEditorDebugger::setup_session(int p_session_id) { + Ref<EditorDebuggerSession> session = get_session(p_session_id); + ERR_FAIL_COND(session.is_null()); + EditorNetworkProfiler *profiler = memnew(EditorNetworkProfiler); + profiler->connect("enable_profiling", callable_mp(this, &MultiplayerEditorDebugger::_profiler_activate).bind(p_session_id)); + profiler->set_name(TTR("Network Profiler")); + session->add_session_tab(profiler); + profilers[p_session_id] = profiler; +} + +MultiplayerEditorPlugin::MultiplayerEditorPlugin() { + repl_editor = memnew(ReplicationEditor); + button = EditorNode::get_singleton()->add_bottom_panel_item(TTR("Replication"), repl_editor); + button->hide(); + repl_editor->get_pin()->connect("pressed", callable_mp(this, &MultiplayerEditorPlugin::_pinned)); + debugger.instantiate(); +} + +MultiplayerEditorPlugin::~MultiplayerEditorPlugin() { +} + +void MultiplayerEditorPlugin::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + get_tree()->connect("node_removed", callable_mp(this, &MultiplayerEditorPlugin::_node_removed)); + add_debugger_plugin(debugger); + } break; + case NOTIFICATION_EXIT_TREE: { + remove_debugger_plugin(debugger); + } + } +} + +void MultiplayerEditorPlugin::_node_removed(Node *p_node) { + if (p_node && p_node == repl_editor->get_current()) { + repl_editor->edit(nullptr); + if (repl_editor->is_visible_in_tree()) { + EditorNode::get_singleton()->hide_bottom_panel(); + } + button->hide(); + repl_editor->get_pin()->set_pressed(false); + } +} + +void MultiplayerEditorPlugin::_pinned() { + if (!repl_editor->get_pin()->is_pressed()) { + if (repl_editor->is_visible_in_tree()) { + EditorNode::get_singleton()->hide_bottom_panel(); + } + button->hide(); + } +} + +void MultiplayerEditorPlugin::edit(Object *p_object) { + repl_editor->edit(Object::cast_to<MultiplayerSynchronizer>(p_object)); +} + +bool MultiplayerEditorPlugin::handles(Object *p_object) const { + return p_object->is_class("MultiplayerSynchronizer"); +} + +void MultiplayerEditorPlugin::make_visible(bool p_visible) { + if (p_visible) { + button->show(); + EditorNode::get_singleton()->make_bottom_panel_item_visible(repl_editor); + } else if (!repl_editor->get_pin()->is_pressed()) { + if (repl_editor->is_visible_in_tree()) { + EditorNode::get_singleton()->hide_bottom_panel(); + } + button->hide(); + } +} diff --git a/modules/multiplayer/editor/multiplayer_editor_plugin.h b/modules/multiplayer/editor/multiplayer_editor_plugin.h new file mode 100644 index 0000000000..6d1514cdb1 --- /dev/null +++ b/modules/multiplayer/editor/multiplayer_editor_plugin.h @@ -0,0 +1,81 @@ +/*************************************************************************/ +/* multiplayer_editor_plugin.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 MULTIPLAYER_EDITOR_PLUGIN_H +#define MULTIPLAYER_EDITOR_PLUGIN_H + +#include "editor/editor_plugin.h" + +#include "editor/plugins/editor_debugger_plugin.h" + +class EditorNetworkProfiler; +class MultiplayerEditorDebugger : public EditorDebuggerPlugin { + GDCLASS(MultiplayerEditorDebugger, EditorDebuggerPlugin); + +private: + HashMap<int, EditorNetworkProfiler *> profilers; + + void _profiler_activate(bool p_enable, int p_session_id); + +public: + virtual bool has_capture(const String &p_capture) const override; + virtual bool capture(const String &p_message, const Array &p_data, int p_index) override; + virtual void setup_session(int p_session_id) override; + + MultiplayerEditorDebugger() {} +}; + +class ReplicationEditor; + +class MultiplayerEditorPlugin : public EditorPlugin { + GDCLASS(MultiplayerEditorPlugin, EditorPlugin); + +private: + Button *button = nullptr; + ReplicationEditor *repl_editor = nullptr; + Ref<MultiplayerEditorDebugger> debugger; + + void _node_removed(Node *p_node); + + void _pinned(); + +protected: + void _notification(int p_what); + +public: + virtual void edit(Object *p_object) override; + virtual bool handles(Object *p_object) const override; + virtual void make_visible(bool p_visible) override; + + MultiplayerEditorPlugin(); + ~MultiplayerEditorPlugin(); +}; + +#endif // MULTIPLAYER_EDITOR_PLUGIN_H diff --git a/modules/multiplayer/editor/replication_editor_plugin.cpp b/modules/multiplayer/editor/replication_editor.cpp index de10420652..ccde9fed22 100644 --- a/modules/multiplayer/editor/replication_editor_plugin.cpp +++ b/modules/multiplayer/editor/replication_editor.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* replication_editor_plugin.cpp */ +/* replication_editor.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,7 +28,9 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "replication_editor_plugin.h" +#include "replication_editor.h" + +#include "../multiplayer_synchronizer.h" #include "editor/editor_node.h" #include "editor/editor_scale.h" @@ -37,7 +39,6 @@ #include "editor/inspector_dock.h" #include "editor/property_selector.h" #include "editor/scene_tree_editor.h" -#include "modules/multiplayer/multiplayer_synchronizer.h" #include "scene/gui/dialogs.h" #include "scene/gui/separator.h" #include "scene/gui/tree.h" @@ -141,7 +142,7 @@ void ReplicationEditor::_add_sync_property(String p_path) { return; } - Ref<EditorUndoRedoManager> undo_redo = EditorNode::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &undo_redo = EditorNode::get_singleton()->get_undo_redo(); undo_redo->create_action(TTR("Add property to synchronizer")); if (config.is_null()) { @@ -201,7 +202,7 @@ ReplicationEditor::ReplicationEditor() { add_pick_button = memnew(Button); add_pick_button->connect("pressed", callable_mp(this, &ReplicationEditor::_pick_new_property)); - add_pick_button->set_text(TTR("Add property to sync..")); + add_pick_button->set_text(TTR("Add property to sync...")); hb->add_child(add_pick_button); VSeparator *vs = memnew(VSeparator); vs->set_custom_minimum_size(Size2(30 * EDSCALE, 0)); @@ -493,62 +494,3 @@ void ReplicationEditor::_add_property(const NodePath &p_property, bool p_spawn, item->set_checked(2, p_sync); item->set_editable(2, true); } - -/// ReplicationEditorPlugin -ReplicationEditorPlugin::ReplicationEditorPlugin() { - repl_editor = memnew(ReplicationEditor); - button = EditorNode::get_singleton()->add_bottom_panel_item(TTR("Replication"), repl_editor); - button->hide(); - repl_editor->get_pin()->connect("pressed", callable_mp(this, &ReplicationEditorPlugin::_pinned)); -} - -ReplicationEditorPlugin::~ReplicationEditorPlugin() { -} - -void ReplicationEditorPlugin::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_TREE: { - get_tree()->connect("node_removed", callable_mp(this, &ReplicationEditorPlugin::_node_removed)); - } break; - } -} - -void ReplicationEditorPlugin::_node_removed(Node *p_node) { - if (p_node && p_node == repl_editor->get_current()) { - repl_editor->edit(nullptr); - if (repl_editor->is_visible_in_tree()) { - EditorNode::get_singleton()->hide_bottom_panel(); - } - button->hide(); - repl_editor->get_pin()->set_pressed(false); - } -} - -void ReplicationEditorPlugin::_pinned() { - if (!repl_editor->get_pin()->is_pressed()) { - if (repl_editor->is_visible_in_tree()) { - EditorNode::get_singleton()->hide_bottom_panel(); - } - button->hide(); - } -} - -void ReplicationEditorPlugin::edit(Object *p_object) { - repl_editor->edit(Object::cast_to<MultiplayerSynchronizer>(p_object)); -} - -bool ReplicationEditorPlugin::handles(Object *p_object) const { - return p_object->is_class("MultiplayerSynchronizer"); -} - -void ReplicationEditorPlugin::make_visible(bool p_visible) { - if (p_visible) { - button->show(); - EditorNode::get_singleton()->make_bottom_panel_item_visible(repl_editor); - } else if (!repl_editor->get_pin()->is_pressed()) { - if (repl_editor->is_visible_in_tree()) { - EditorNode::get_singleton()->hide_bottom_panel(); - } - button->hide(); - } -} diff --git a/modules/multiplayer/editor/replication_editor_plugin.h b/modules/multiplayer/editor/replication_editor.h index 6c40a99293..8a48e8dbe7 100644 --- a/modules/multiplayer/editor/replication_editor_plugin.h +++ b/modules/multiplayer/editor/replication_editor.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* replication_editor_plugin.h */ +/* replication_editor.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,8 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef REPLICATION_EDITOR_PLUGIN_H -#define REPLICATION_EDITOR_PLUGIN_H +#ifndef REPLICATION_EDITOR_H +#define REPLICATION_EDITOR_H #include "editor/editor_plugin.h" #include "modules/multiplayer/scene_replication_config.h" @@ -105,27 +105,4 @@ public: ~ReplicationEditor() {} }; -class ReplicationEditorPlugin : public EditorPlugin { - GDCLASS(ReplicationEditorPlugin, EditorPlugin); - -private: - Button *button = nullptr; - ReplicationEditor *repl_editor = nullptr; - - void _node_removed(Node *p_node); - - void _pinned(); - -protected: - void _notification(int p_what); - -public: - virtual void edit(Object *p_object) override; - virtual bool handles(Object *p_object) const override; - virtual void make_visible(bool p_visible) override; - - ReplicationEditorPlugin(); - ~ReplicationEditorPlugin(); -}; - -#endif // REPLICATION_EDITOR_PLUGIN_H +#endif // REPLICATION_EDITOR_H diff --git a/modules/multiplayer/multiplayer_debugger.cpp b/modules/multiplayer/multiplayer_debugger.cpp new file mode 100644 index 0000000000..3d22af04dc --- /dev/null +++ b/modules/multiplayer/multiplayer_debugger.cpp @@ -0,0 +1,194 @@ +/*************************************************************************/ +/* multiplayer_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 "multiplayer_debugger.h" + +#include "core/debugger/engine_debugger.h" +#include "scene/main/node.h" + +List<Ref<EngineProfiler>> multiplayer_profilers; + +void MultiplayerDebugger::initialize() { + Ref<BandwidthProfiler> bandwidth; + bandwidth.instantiate(); + bandwidth->bind("multiplayer"); + multiplayer_profilers.push_back(bandwidth); + + Ref<RPCProfiler> rpc_profiler; + rpc_profiler.instantiate(); + rpc_profiler->bind("rpc"); + multiplayer_profilers.push_back(rpc_profiler); +} + +void MultiplayerDebugger::deinitialize() { + multiplayer_profilers.clear(); +} + +// BandwidthProfiler + +int MultiplayerDebugger::BandwidthProfiler::bandwidth_usage(const Vector<BandwidthFrame> &p_buffer, int p_pointer) { + ERR_FAIL_COND_V(p_buffer.size() == 0, 0); + int total_bandwidth = 0; + + uint64_t timestamp = OS::get_singleton()->get_ticks_msec(); + uint64_t final_timestamp = timestamp - 1000; + + int i = (p_pointer + p_buffer.size() - 1) % p_buffer.size(); + + while (i != p_pointer && p_buffer[i].packet_size > 0) { + if (p_buffer[i].timestamp < final_timestamp) { + return total_bandwidth; + } + total_bandwidth += p_buffer[i].packet_size; + i = (i + p_buffer.size() - 1) % p_buffer.size(); + } + + ERR_FAIL_COND_V_MSG(i == p_pointer, total_bandwidth, "Reached the end of the bandwidth profiler buffer, values might be inaccurate."); + return total_bandwidth; +} + +void MultiplayerDebugger::BandwidthProfiler::toggle(bool p_enable, const Array &p_opts) { + if (!p_enable) { + bandwidth_in.clear(); + bandwidth_out.clear(); + } else { + bandwidth_in_ptr = 0; + bandwidth_in.resize(16384); // ~128kB + for (int i = 0; i < bandwidth_in.size(); ++i) { + bandwidth_in.write[i].packet_size = -1; + } + bandwidth_out_ptr = 0; + bandwidth_out.resize(16384); // ~128kB + for (int i = 0; i < bandwidth_out.size(); ++i) { + bandwidth_out.write[i].packet_size = -1; + } + } +} + +void MultiplayerDebugger::BandwidthProfiler::add(const Array &p_data) { + 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(); + } +} + +void MultiplayerDebugger::BandwidthProfiler::tick(double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time) { + uint64_t pt = OS::get_singleton()->get_ticks_msec(); + if (pt - last_bandwidth_time > 200) { + last_bandwidth_time = pt; + int incoming_bandwidth = bandwidth_usage(bandwidth_in, bandwidth_in_ptr); + int outgoing_bandwidth = bandwidth_usage(bandwidth_out, bandwidth_out_ptr); + + Array arr; + arr.push_back(incoming_bandwidth); + arr.push_back(outgoing_bandwidth); + EngineDebugger::get_singleton()->send_message("multiplayer:bandwidth", arr); + } +} + +// RPCProfiler + +Array MultiplayerDebugger::RPCFrame::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 MultiplayerDebugger::RPCFrame::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; +} + +void MultiplayerDebugger::RPCProfiler::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; +} + +void MultiplayerDebugger::RPCProfiler::toggle(bool p_enable, const Array &p_opts) { + rpc_node_data.clear(); +} + +void MultiplayerDebugger::RPCProfiler::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 MultiplayerDebugger::RPCProfiler::tick(double p_frame_time, double p_process_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; + RPCFrame 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()); + } +} diff --git a/modules/multiplayer/multiplayer_debugger.h b/modules/multiplayer/multiplayer_debugger.h new file mode 100644 index 0000000000..4efd1da016 --- /dev/null +++ b/modules/multiplayer/multiplayer_debugger.h @@ -0,0 +1,95 @@ +/*************************************************************************/ +/* multiplayer_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 MULTIPLAYER_DEBUGGER_H +#define MULTIPLAYER_DEBUGGER_H + +#include "core/debugger/engine_profiler.h" + +#include "core/os/os.h" + +class MultiplayerDebugger { +public: + struct RPCNodeInfo { + ObjectID node; + String node_path; + int incoming_rpc = 0; + int outgoing_rpc = 0; + }; + + struct RPCFrame { + Vector<RPCNodeInfo> infos; + + Array serialize(); + bool deserialize(const Array &p_arr); + }; + +private: + class BandwidthProfiler : public EngineProfiler { + protected: + struct BandwidthFrame { + uint32_t timestamp; + int packet_size; + }; + + int bandwidth_in_ptr = 0; + Vector<BandwidthFrame> bandwidth_in; + int bandwidth_out_ptr = 0; + Vector<BandwidthFrame> bandwidth_out; + uint64_t last_bandwidth_time = 0; + + int bandwidth_usage(const Vector<BandwidthFrame> &p_buffer, int p_pointer); + + public: + void toggle(bool p_enable, const Array &p_opts); + void add(const Array &p_data); + void tick(double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time); + }; + + class RPCProfiler : public EngineProfiler { + public: + private: + HashMap<ObjectID, RPCNodeInfo> rpc_node_data; + uint64_t last_profile_time = 0; + + void init_node(const ObjectID p_node); + + public: + void toggle(bool p_enable, const Array &p_opts); + void add(const Array &p_data); + void tick(double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time); + }; + +public: + static void initialize(); + static void deinitialize(); +}; + +#endif // MULTIPLAYER_DEBUGGER_H diff --git a/modules/multiplayer/register_types.cpp b/modules/multiplayer/register_types.cpp index a2c524da80..2bf1041029 100644 --- a/modules/multiplayer/register_types.cpp +++ b/modules/multiplayer/register_types.cpp @@ -36,9 +36,10 @@ #include "scene_replication_interface.h" #include "scene_rpc_interface.h" +#include "multiplayer_debugger.h" + #ifdef TOOLS_ENABLED -#include "editor/editor_plugin.h" -#include "editor/replication_editor_plugin.h" +#include "editor/multiplayer_editor_plugin.h" #endif void initialize_multiplayer_module(ModuleInitializationLevel p_level) { @@ -48,13 +49,15 @@ void initialize_multiplayer_module(ModuleInitializationLevel p_level) { GDREGISTER_CLASS(MultiplayerSynchronizer); GDREGISTER_CLASS(SceneMultiplayer); MultiplayerAPI::set_default_interface("SceneMultiplayer"); + MultiplayerDebugger::initialize(); } #ifdef TOOLS_ENABLED if (p_level == MODULE_INITIALIZATION_LEVEL_EDITOR) { - EditorPlugins::add_by_type<ReplicationEditorPlugin>(); + EditorPlugins::add_by_type<MultiplayerEditorPlugin>(); } #endif } void uninitialize_multiplayer_module(ModuleInitializationLevel p_level) { + MultiplayerDebugger::deinitialize(); } diff --git a/modules/navigation/nav_link.h b/modules/navigation/nav_link.h index 8d57f076c0..8f51a63951 100644 --- a/modules/navigation/nav_link.h +++ b/modules/navigation/nav_link.h @@ -37,8 +37,8 @@ class NavLink : public NavBase { NavMap *map = nullptr; bool bidirectional = true; - Vector3 start_location = Vector3(); - Vector3 end_location = Vector3(); + Vector3 start_location; + Vector3 end_location; bool link_dirty = true; diff --git a/modules/text_server_adv/SCsub b/modules/text_server_adv/SCsub index bd208a8429..3c7a89b705 100644 --- a/modules/text_server_adv/SCsub +++ b/modules/text_server_adv/SCsub @@ -39,6 +39,9 @@ thirdparty_obj = [] freetype_enabled = "freetype" in env.module_list msdfgen_enabled = "msdfgen" in env.module_list +if "svg" in env.module_list: + env_text_server_adv.Prepend(CPPPATH=["#thirdparty/thorvg/inc", "#thirdparty/thorvg/src/lib"]) + if env["builtin_harfbuzz"]: env_harfbuzz = env_modules.Clone() env_harfbuzz.disable_warnings() diff --git a/modules/text_server_adv/gdextension_build/SConstruct b/modules/text_server_adv/gdextension_build/SConstruct index 65b41e46ce..4a363fdd7a 100644 --- a/modules/text_server_adv/gdextension_build/SConstruct +++ b/modules/text_server_adv/gdextension_build/SConstruct @@ -23,6 +23,7 @@ opts.Add(BoolVariable("brotli_enabled", "Use Brotli library", True)) opts.Add(BoolVariable("freetype_enabled", "Use FreeType library", True)) opts.Add(BoolVariable("msdfgen_enabled", "Use MSDFgen library (require FreeType)", True)) opts.Add(BoolVariable("graphite_enabled", "Use Graphite library (require FreeType)", True)) +opts.Add(BoolVariable("thorvg_enabled", "Use ThorVG library (require FreeType)", True)) opts.Add(BoolVariable("static_icu_data", "Use built-in ICU data", True)) opts.Add(BoolVariable("verbose", "Enable verbose output for the compilation", False)) @@ -34,6 +35,79 @@ if not env["verbose"]: if env["platform"] == "windows" and not env["use_mingw"]: env.AppendUnique(CCFLAGS=["/utf-8"]) # Force to use Unicode encoding. +# ThorVG +if env["thorvg_enabled"] and env["freetype_enabled"]: + env_tvg = env.Clone() + env_tvg.disable_warnings() + + thirdparty_tvg_dir = "../../../thirdparty/thorvg/" + thirdparty_tvg_sources = [ + "src/lib/sw_engine/tvgSwFill.cpp", + "src/lib/sw_engine/tvgSwImage.cpp", + "src/lib/sw_engine/tvgSwMath.cpp", + "src/lib/sw_engine/tvgSwMemPool.cpp", + "src/lib/sw_engine/tvgSwRaster.cpp", + "src/lib/sw_engine/tvgSwRenderer.cpp", + "src/lib/sw_engine/tvgSwRle.cpp", + "src/lib/sw_engine/tvgSwShape.cpp", + "src/lib/sw_engine/tvgSwStroke.cpp", + "src/lib/tvgAccessor.cpp", + "src/lib/tvgBezier.cpp", + "src/lib/tvgCanvas.cpp", + "src/lib/tvgFill.cpp", + "src/lib/tvgGlCanvas.cpp", + "src/lib/tvgInitializer.cpp", + "src/lib/tvgLinearGradient.cpp", + "src/lib/tvgLoader.cpp", + "src/lib/tvgLzw.cpp", + "src/lib/tvgPaint.cpp", + "src/lib/tvgPicture.cpp", + "src/lib/tvgRadialGradient.cpp", + "src/lib/tvgRender.cpp", + "src/lib/tvgSaver.cpp", + "src/lib/tvgScene.cpp", + "src/lib/tvgShape.cpp", + "src/lib/tvgSwCanvas.cpp", + "src/lib/tvgTaskScheduler.cpp", + "src/loaders/external_png/tvgPngLoader.cpp", + "src/loaders/jpg/tvgJpgd.cpp", + "src/loaders/jpg/tvgJpgLoader.cpp", + "src/loaders/raw/tvgRawLoader.cpp", + "src/loaders/svg/tvgSvgCssStyle.cpp", + "src/loaders/svg/tvgSvgLoader.cpp", + "src/loaders/svg/tvgSvgPath.cpp", + "src/loaders/svg/tvgSvgSceneBuilder.cpp", + "src/loaders/svg/tvgSvgUtil.cpp", + "src/loaders/svg/tvgXmlParser.cpp", + "src/loaders/tvg/tvgTvgBinInterpreter.cpp", + "src/loaders/tvg/tvgTvgLoader.cpp", + "src/savers/tvg/tvgTvgSaver.cpp", + ] + thirdparty_tvg_sources = [thirdparty_tvg_dir + file for file in thirdparty_tvg_sources] + + env_tvg.Append( + CPPPATH=[ + "../../../thirdparty/thorvg/inc", + "../../../thirdparty/thorvg/src/lib", + "../../../thirdparty/thorvg/src/lib/sw_engine", + "../../../thirdparty/thorvg/src/loaders/external_png", + "../../../thirdparty/thorvg/src/loaders/jpg", + "../../../thirdparty/thorvg/src/loaders/raw", + "../../../thirdparty/thorvg/src/loaders/svg", + "../../../thirdparty/thorvg/src/loaders/tvg", + "../../../thirdparty/thorvg/src/savers/tvg", + "../../../thirdparty/libpng", + ] + ) + env.Append(CPPPATH=["../../../thirdparty/thorvg/inc", "../../../thirdparty/thorvg/src/lib"]) + env.Append(CPPDEFINES=["MODULE_SVG_ENABLED"]) + + lib = env_tvg.Library( + f'tvg_builtin{env["suffix"]}{env["LIBSUFFIX"]}', + thirdparty_tvg_sources, + ) + env.Append(LIBS=[lib]) + # MSDFGEN if env["msdfgen_enabled"] and env["freetype_enabled"]: env_msdfgen = env.Clone() diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index 155da3c67b..166325c551 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -41,6 +41,8 @@ using namespace godot; +#define GLOBAL_GET(m_var) ProjectSettings::get_singleton()->get(m_var) + #else // Headers for building as built-in module. @@ -50,7 +52,7 @@ using namespace godot; #include "core/string/print_string.h" #include "core/string/translation.h" -#include "modules/modules_enabled.gen.h" // For freetype, msdfgen. +#include "modules/modules_enabled.gen.h" // For freetype, msdfgen, svg. #endif @@ -69,6 +71,10 @@ using namespace godot; #include "msdfgen.h" #endif +#ifdef MODULE_SVG_ENABLED +#include "thorvg_svg_in_ot.h" +#endif + /*************************************************************************/ /* bmp_font_t HarfBuzz Bitmap font interface */ /*************************************************************************/ @@ -1346,6 +1352,9 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_f memdelete(fd); ERR_FAIL_V_MSG(false, "FreeType: Error initializing library: '" + String(FT_Error_String(error)) + "'."); } +#ifdef MODULE_SVG_ENABLED + FT_Property_Set(ft_library, "ot-svg", "svg-hooks", get_tvg_svg_in_ot_hooks()); +#endif } memset(&fd->stream, 0, sizeof(FT_StreamRec)); @@ -1888,6 +1897,9 @@ int64_t TextServerAdvanced::_font_get_face_count(const RID &p_font_rid) const { if (!ft_library) { error = FT_Init_FreeType(&ft_library); ERR_FAIL_COND_V_MSG(error != 0, false, "FreeType: Error initializing library: '" + String(FT_Error_String(error)) + "'."); +#ifdef MODULE_SVG_ENABLED + FT_Property_Set(ft_library, "ot-svg", "svg-hooks", get_tvg_svg_in_ot_hooks()); +#endif } FT_StreamRec stream; diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h index 33fa1e117e..10fe3c2316 100644 --- a/modules/text_server_adv/text_server_adv.h +++ b/modules/text_server_adv/text_server_adv.h @@ -88,7 +88,7 @@ using namespace godot; #include "core/templates/rid_owner.h" #include "scene/resources/texture.h" -#include "modules/modules_enabled.gen.h" // For freetype, msdfgen. +#include "modules/modules_enabled.gen.h" // For freetype, msdfgen, svg. #endif @@ -117,6 +117,7 @@ using namespace godot; #include FT_ADVANCES_H #include FT_MULTIPLE_MASTERS_H #include FT_BBOX_H +#include FT_MODULE_H #include FT_CONFIG_OPTIONS_H #if !defined(FT_CONFIG_OPTION_USE_BROTLI) && !defined(_MSC_VER) #warning FreeType is configured without Brotli support, built-in fonts will not be available. diff --git a/modules/text_server_adv/thorvg_bounds_iterator.cpp b/modules/text_server_adv/thorvg_bounds_iterator.cpp new file mode 100644 index 0000000000..54a6136134 --- /dev/null +++ b/modules/text_server_adv/thorvg_bounds_iterator.cpp @@ -0,0 +1,70 @@ +/*************************************************************************/ +/* thorvg_bounds_iterator.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. */ +/*************************************************************************/ + +#ifdef GDEXTENSION +// Headers for building as GDExtension plug-in. + +#include <godot_cpp/godot.hpp> + +using namespace godot; + +#else +// Headers for building as built-in module. + +#include "core/typedefs.h" + +#include "modules/modules_enabled.gen.h" // For svg. +#endif + +#ifdef MODULE_SVG_ENABLED + +#include "thorvg_bounds_iterator.h" + +#include <tvgIteratorAccessor.h> +#include <tvgPaint.h> + +// This function uses private ThorVG API to get bounding box of top level children elements. + +void tvg_get_bounds(tvg::Picture *p_picture, float &r_min_x, float &r_min_y, float &r_max_x, float &r_max_y) { + tvg::IteratorAccessor itrAccessor; + if (tvg::Iterator *it = itrAccessor.iterator(p_picture)) { + while (const tvg::Paint *child = it->next()) { + float x = 0, y = 0, w = 0, h = 0; + child->bounds(&x, &y, &w, &h, true); + r_min_x = MIN(x, r_min_x); + r_min_y = MIN(y, r_min_y); + r_max_x = MAX(x + w, r_max_x); + r_max_y = MAX(y + h, r_max_y); + } + delete (it); + } +} + +#endif // MODULE_SVG_ENABLED diff --git a/modules/text_server_adv/thorvg_bounds_iterator.h b/modules/text_server_adv/thorvg_bounds_iterator.h new file mode 100644 index 0000000000..e54e30eaa2 --- /dev/null +++ b/modules/text_server_adv/thorvg_bounds_iterator.h @@ -0,0 +1,58 @@ +/*************************************************************************/ +/* thorvg_bounds_iterator.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 THORVG_BOUNDS_ITERATOR_H +#define THORVG_BOUNDS_ITERATOR_H + +#ifdef GDEXTENSION +// Headers for building as GDExtension plug-in. + +#include <godot_cpp/core/mutex_lock.hpp> +#include <godot_cpp/godot.hpp> + +using namespace godot; + +#else +// Headers for building as built-in module. + +#include "core/typedefs.h" + +#include "modules/modules_enabled.gen.h" // For svg. +#endif + +#ifdef MODULE_SVG_ENABLED + +#include <thorvg.h> + +void tvg_get_bounds(tvg::Picture *p_picture, float &r_min_x, float &r_min_y, float &r_max_x, float &r_max_y); + +#endif // MODULE_SVG_ENABLED + +#endif // THORVG_BOUNDS_ITERATOR_H diff --git a/modules/text_server_adv/thorvg_svg_in_ot.cpp b/modules/text_server_adv/thorvg_svg_in_ot.cpp new file mode 100644 index 0000000000..7863ab67fa --- /dev/null +++ b/modules/text_server_adv/thorvg_svg_in_ot.cpp @@ -0,0 +1,320 @@ +/*************************************************************************/ +/* thorvg_svg_in_ot.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. */ +/*************************************************************************/ + +#ifdef GDEXTENSION +// Headers for building as GDExtension plug-in. + +#include <godot_cpp/classes/xml_parser.hpp> +#include <godot_cpp/core/mutex_lock.hpp> +#include <godot_cpp/godot.hpp> +#include <godot_cpp/templates/vector.hpp> + +using namespace godot; + +#else +// Headers for building as built-in module. + +#include "core/error/error_macros.h" +#include "core/io/xml_parser.h" +#include "core/os/memory.h" +#include "core/os/os.h" +#include "core/string/ustring.h" +#include "core/typedefs.h" +#include "core/variant/variant.h" + +#include "modules/modules_enabled.gen.h" // For svg. +#endif + +#ifdef MODULE_SVG_ENABLED + +#include "thorvg_bounds_iterator.h" +#include "thorvg_svg_in_ot.h" + +#include <freetype/otsvg.h> +#include <ft2build.h> + +#include <math.h> +#include <stdlib.h> + +FT_Error tvg_svg_in_ot_init(FT_Pointer *p_state) { + *p_state = memnew(TVG_State); + + return FT_Err_Ok; +} + +void tvg_svg_in_ot_free(FT_Pointer *p_state) { + TVG_State *state = *reinterpret_cast<TVG_State **>(p_state); + memdelete(state); +} + +FT_Error tvg_svg_in_ot_preset_slot(FT_GlyphSlot p_slot, FT_Bool p_cache, FT_Pointer *p_state) { + TVG_State *state = *reinterpret_cast<TVG_State **>(p_state); + if (!state) { + ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "SVG in OT state not initialized."); + } + MutexLock lock(state->mutex); + + FT_SVG_Document document = (FT_SVG_Document)p_slot->other; + FT_Size_Metrics metrics = document->metrics; + + GL_State &gl_state = state->glyph_map[p_slot->glyph_index]; + if (!gl_state.ready) { + Ref<XMLParser> parser; + parser.instantiate(); +#ifdef GDEXTENSION + PackedByteArray data; + data.resize(document->svg_document_length); + memcpy(data.ptrw(), document->svg_document, document->svg_document_length); + parser->open_buffer(data); +#else + parser->_open_buffer((const uint8_t *)document->svg_document, document->svg_document_length); +#endif + + float aspect = 1.0f; + String xml_body; + while (parser->read() == OK) { + if (parser->has_attribute("id")) { +#ifdef GDEXTENSION + const String &gl_name = parser->get_named_attribute_value("id"); +#else + const String &gl_name = parser->get_attribute_value("id"); +#endif + if (gl_name.begins_with("glyph")) { + int dot_pos = gl_name.find("."); + int64_t gl_idx = gl_name.substr(5, (dot_pos > 0) ? dot_pos - 5 : -1).to_int(); + if (p_slot->glyph_index != gl_idx) { + parser->skip_section(); + continue; + } + } + } + if (parser->get_node_type() == XMLParser::NODE_ELEMENT && parser->get_node_name() == "svg") { + if (parser->has_attribute("viewBox")) { +#ifdef GDEXTENSION + PackedStringArray vb = parser->get_named_attribute_value("viewBox").split(" "); +#else + Vector<String> vb = parser->get_attribute_value("viewBox").split(" "); +#endif + + if (vb.size() == 4) { + aspect = vb[2].to_float() / vb[3].to_float(); + } + } + continue; + } +#ifdef GDEXTENSION + if (parser->get_node_type() == XMLParser::NODE_ELEMENT) { + xml_body = xml_body + "<" + parser->get_node_name(); + for (int i = 0; i < parser->get_attribute_count(); i++) { + xml_body = xml_body + " " + parser->get_attribute_name(i) + "=\"" + parser->get_attribute_value(i) + "\""; + } + xml_body = xml_body + ">"; + } else if (parser->get_node_type() == XMLParser::NODE_TEXT) { + xml_body = xml_body + parser->get_node_data(); + } else if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END) { + xml_body = xml_body + "</" + parser->get_node_name() + ">"; + } +#else + if (parser->get_node_type() == XMLParser::NODE_ELEMENT) { + xml_body += vformat("<%s", parser->get_node_name()); + for (int i = 0; i < parser->get_attribute_count(); i++) { + xml_body += vformat(" %s=\"%s\"", parser->get_attribute_name(i), parser->get_attribute_value(i)); + } + xml_body += ">"; + } else if (parser->get_node_type() == XMLParser::NODE_TEXT) { + xml_body += parser->get_node_data(); + } else if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END) { + xml_body += vformat("</%s>", parser->get_node_name()); + } +#endif + } + String temp_xml = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 0 0\">" + xml_body; + + std::unique_ptr<tvg::Picture> picture = tvg::Picture::gen(); + tvg::Result result = picture->load(temp_xml.utf8().get_data(), temp_xml.utf8().length(), "svg+xml", false); + if (result != tvg::Result::Success) { + ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to load SVG document (bounds detection)."); + } + + float min_x = INFINITY, min_y = INFINITY, max_x = -INFINITY, max_y = -INFINITY; + tvg_get_bounds(picture.get(), min_x, min_y, max_x, max_y); + + float new_h = (max_y - min_y); + float new_w = (max_x - min_x); + + if (new_h * aspect >= new_w) { + new_w = (new_h * aspect); + } else { + new_h = (new_w / aspect); + } + +#ifdef GDEXTENSION + gl_state.xml_code = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"" + rtos(min_x) + " " + rtos(min_y) + " " + rtos(new_w) + " " + rtos(new_h) + "\">" + xml_body; +#else + gl_state.xml_code = vformat("<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"%f %f %f %f\">", min_x, min_y, new_w, new_h) + xml_body; +#endif + + picture = tvg::Picture::gen(); + result = picture->load(gl_state.xml_code.utf8().get_data(), gl_state.xml_code.utf8().length(), "svg+xml", false); + if (result != tvg::Result::Success) { + ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to load SVG document (glyph metrics)."); + } + + float x_svg_to_out, y_svg_to_out; + x_svg_to_out = (float)metrics.x_ppem / new_w; + y_svg_to_out = (float)metrics.y_ppem / new_h; + + gl_state.m.e11 = (double)document->transform.xx / (1 << 16) * x_svg_to_out; + gl_state.m.e12 = -(double)document->transform.xy / (1 << 16) * x_svg_to_out; + gl_state.m.e21 = -(double)document->transform.yx / (1 << 16) * y_svg_to_out; + gl_state.m.e22 = (double)document->transform.yy / (1 << 16) * y_svg_to_out; + gl_state.m.e13 = (double)document->delta.x / 64 * new_w / metrics.x_ppem; + gl_state.m.e23 = -(double)document->delta.y / 64 * new_h / metrics.y_ppem; + gl_state.m.e31 = 0; + gl_state.m.e32 = 0; + gl_state.m.e33 = 1; + + result = picture->transform(gl_state.m); + if (result != tvg::Result::Success) { + ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to apply transform to SVG document."); + } + + result = picture->bounds(&gl_state.x, &gl_state.y, &gl_state.w, &gl_state.h, true); + if (result != tvg::Result::Success) { + ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to get SVG bounds."); + } + + gl_state.bmp_y = -min_y * gl_state.h / new_h; + gl_state.bmp_x = min_x * gl_state.w / new_w; + + gl_state.ready = true; + } + + p_slot->bitmap_left = (FT_Int)gl_state.bmp_x; + p_slot->bitmap_top = (FT_Int)gl_state.bmp_y; + + float tmp = ceil(gl_state.h); + p_slot->bitmap.rows = (unsigned int)tmp; + tmp = ceil(gl_state.w); + p_slot->bitmap.width = (unsigned int)tmp; + p_slot->bitmap.pitch = (int)p_slot->bitmap.width * 4; + p_slot->bitmap.pixel_mode = FT_PIXEL_MODE_BGRA; + + float metrics_width, metrics_height; + float horiBearingX, horiBearingY; + float vertBearingX, vertBearingY; + + metrics_width = (float)gl_state.w; + metrics_height = (float)gl_state.h; + horiBearingX = (float)gl_state.x; + horiBearingY = (float)-gl_state.y; + vertBearingX = p_slot->metrics.horiBearingX / 64.0f - p_slot->metrics.horiAdvance / 64.0f / 2; + vertBearingY = (p_slot->metrics.vertAdvance / 64.0f - p_slot->metrics.height / 64.0f) / 2; + + tmp = roundf(metrics_width * 64); + p_slot->metrics.width = (FT_Pos)tmp; + tmp = roundf(metrics_height * 64); + p_slot->metrics.height = (FT_Pos)tmp; + + p_slot->metrics.horiBearingX = (FT_Pos)(horiBearingX * 64); + p_slot->metrics.horiBearingY = (FT_Pos)(horiBearingY * 64); + p_slot->metrics.vertBearingX = (FT_Pos)(vertBearingX * 64); + p_slot->metrics.vertBearingY = (FT_Pos)(vertBearingY * 64); + + if (p_slot->metrics.vertAdvance == 0) { + p_slot->metrics.vertAdvance = (FT_Pos)(metrics_height * 1.2f * 64); + } + + return FT_Err_Ok; +} + +FT_Error tvg_svg_in_ot_render(FT_GlyphSlot p_slot, FT_Pointer *p_state) { + TVG_State *state = *reinterpret_cast<TVG_State **>(p_state); + if (!state) { + ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "SVG in OT state not initialized."); + } + MutexLock lock(state->mutex); + + if (!state->glyph_map.has(p_slot->glyph_index)) { + ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "SVG glyph not loaded."); + } + + GL_State &gl_state = state->glyph_map[p_slot->glyph_index]; + ERR_FAIL_COND_V_MSG(!gl_state.ready, FT_Err_Invalid_SVG_Document, "SVG glyph not ready."); + + std::unique_ptr<tvg::Picture> picture = tvg::Picture::gen(); + tvg::Result res = picture->load(gl_state.xml_code.utf8().get_data(), gl_state.xml_code.utf8().length(), "svg+xml", false); + if (res != tvg::Result::Success) { + ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to load SVG document (glyph rendering)."); + } + res = picture->transform(gl_state.m); + if (res != tvg::Result::Success) { + ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to apply transform to SVG document."); + } + + std::unique_ptr<tvg::SwCanvas> sw_canvas = tvg::SwCanvas::gen(); + res = sw_canvas->target((uint32_t *)p_slot->bitmap.buffer, (int)p_slot->bitmap.width, (int)p_slot->bitmap.width, (int)p_slot->bitmap.rows, tvg::SwCanvas::ARGB8888_STRAIGHT); + if (res != tvg::Result::Success) { + ERR_FAIL_V_MSG(FT_Err_Invalid_Outline, "Failed to create SVG canvas."); + } + res = sw_canvas->push(std::move(picture)); + if (res != tvg::Result::Success) { + ERR_FAIL_V_MSG(FT_Err_Invalid_Outline, "Failed to set SVG canvas source."); + } + res = sw_canvas->draw(); + if (res != tvg::Result::Success) { + ERR_FAIL_V_MSG(FT_Err_Invalid_Outline, "Failed to draw to SVG canvas."); + } + res = sw_canvas->sync(); + if (res != tvg::Result::Success) { + ERR_FAIL_V_MSG(FT_Err_Invalid_Outline, "Failed to sync SVG canvas."); + } + + state->glyph_map.erase(p_slot->glyph_index); + + p_slot->bitmap.pixel_mode = FT_PIXEL_MODE_BGRA; + p_slot->bitmap.num_grays = 256; + p_slot->format = FT_GLYPH_FORMAT_BITMAP; + + return FT_Err_Ok; +} + +SVG_RendererHooks tvg_svg_in_ot_hooks = { + (SVG_Lib_Init_Func)tvg_svg_in_ot_init, + (SVG_Lib_Free_Func)tvg_svg_in_ot_free, + (SVG_Lib_Render_Func)tvg_svg_in_ot_render, + (SVG_Lib_Preset_Slot_Func)tvg_svg_in_ot_preset_slot, +}; + +SVG_RendererHooks *get_tvg_svg_in_ot_hooks() { + return &tvg_svg_in_ot_hooks; +} + +#endif // MODULE_SVG_ENABLED diff --git a/modules/text_server_adv/thorvg_svg_in_ot.h b/modules/text_server_adv/thorvg_svg_in_ot.h new file mode 100644 index 0000000000..b2816193d9 --- /dev/null +++ b/modules/text_server_adv/thorvg_svg_in_ot.h @@ -0,0 +1,86 @@ +/*************************************************************************/ +/* thorvg_svg_in_ot.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 THORVG_SVG_IN_OT_H +#define THORVG_SVG_IN_OT_H + +#ifdef GDEXTENSION +// Headers for building as GDExtension plug-in. + +#include <godot_cpp/core/mutex_lock.hpp> +#include <godot_cpp/godot.hpp> +#include <godot_cpp/templates/hash_map.hpp> + +using namespace godot; + +#else +// Headers for building as built-in module. + +#include "core/os/mutex.h" +#include "core/templates/hash_map.h" +#include "core/typedefs.h" + +#include "modules/modules_enabled.gen.h" // For svg. +#endif + +#ifdef MODULE_SVG_ENABLED + +#include <freetype/freetype.h> +#include <freetype/otsvg.h> +#include <ft2build.h> +#include <thorvg.h> + +struct GL_State { + bool ready = false; + float bmp_x = 0; + float bmp_y = 0; + float x = 0; + float y = 0; + float w = 0; + float h = 0; + String xml_code; + tvg::Matrix m; +}; + +struct TVG_State { + Mutex mutex; + HashMap<uint32_t, GL_State> glyph_map; +}; + +FT_Error tvg_svg_in_ot_init(FT_Pointer *p_state); +void tvg_svg_in_ot_free(FT_Pointer *p_state); +FT_Error tvg_svg_in_ot_preset_slot(FT_GlyphSlot p_slot, FT_Bool p_cache, FT_Pointer *p_state); +FT_Error tvg_svg_in_ot_render(FT_GlyphSlot p_slot, FT_Pointer *p_state); + +SVG_RendererHooks *get_tvg_svg_in_ot_hooks(); + +#endif // MODULE_SVG_ENABLED + +#endif // THORVG_SVG_IN_OT_H diff --git a/modules/text_server_fb/SCsub b/modules/text_server_fb/SCsub index 429d2e1fdc..f1d57ec4d3 100644 --- a/modules/text_server_fb/SCsub +++ b/modules/text_server_fb/SCsub @@ -8,6 +8,9 @@ msdfgen_enabled = "msdfgen" in env.module_list env_text_server_fb = env_modules.Clone() +if "svg" in env.module_list: + env_text_server_fb.Prepend(CPPPATH=["#thirdparty/thorvg/inc", "#thirdparty/thorvg/src/lib"]) + if env["builtin_msdfgen"] and msdfgen_enabled: env_text_server_fb.Prepend(CPPPATH=["#thirdparty/msdfgen"]) diff --git a/modules/text_server_fb/gdextension_build/SConstruct b/modules/text_server_fb/gdextension_build/SConstruct index 8ed8f61a43..7b4c548a21 100644 --- a/modules/text_server_fb/gdextension_build/SConstruct +++ b/modules/text_server_fb/gdextension_build/SConstruct @@ -22,6 +22,7 @@ opts = Variables([], ARGUMENTS) opts.Add(BoolVariable("brotli_enabled", "Use Brotli library", True)) opts.Add(BoolVariable("freetype_enabled", "Use FreeType library", True)) opts.Add(BoolVariable("msdfgen_enabled", "Use MSDFgen library (require FreeType)", True)) +opts.Add(BoolVariable("thorvg_enabled", "Use ThorVG library (require FreeType)", True)) opts.Add(BoolVariable("verbose", "Enable verbose output for the compilation", False)) opts.Update(env) @@ -29,6 +30,79 @@ opts.Update(env) if not env["verbose"]: methods.no_verbose(sys, env) +# ThorVG +if env["thorvg_enabled"] and env["freetype_enabled"]: + env_tvg = env.Clone() + env_tvg.disable_warnings() + + thirdparty_tvg_dir = "../../../thirdparty/thorvg/" + thirdparty_tvg_sources = [ + "src/lib/sw_engine/tvgSwFill.cpp", + "src/lib/sw_engine/tvgSwImage.cpp", + "src/lib/sw_engine/tvgSwMath.cpp", + "src/lib/sw_engine/tvgSwMemPool.cpp", + "src/lib/sw_engine/tvgSwRaster.cpp", + "src/lib/sw_engine/tvgSwRenderer.cpp", + "src/lib/sw_engine/tvgSwRle.cpp", + "src/lib/sw_engine/tvgSwShape.cpp", + "src/lib/sw_engine/tvgSwStroke.cpp", + "src/lib/tvgAccessor.cpp", + "src/lib/tvgBezier.cpp", + "src/lib/tvgCanvas.cpp", + "src/lib/tvgFill.cpp", + "src/lib/tvgGlCanvas.cpp", + "src/lib/tvgInitializer.cpp", + "src/lib/tvgLinearGradient.cpp", + "src/lib/tvgLoader.cpp", + "src/lib/tvgLzw.cpp", + "src/lib/tvgPaint.cpp", + "src/lib/tvgPicture.cpp", + "src/lib/tvgRadialGradient.cpp", + "src/lib/tvgRender.cpp", + "src/lib/tvgSaver.cpp", + "src/lib/tvgScene.cpp", + "src/lib/tvgShape.cpp", + "src/lib/tvgSwCanvas.cpp", + "src/lib/tvgTaskScheduler.cpp", + "src/loaders/external_png/tvgPngLoader.cpp", + "src/loaders/jpg/tvgJpgd.cpp", + "src/loaders/jpg/tvgJpgLoader.cpp", + "src/loaders/raw/tvgRawLoader.cpp", + "src/loaders/svg/tvgSvgCssStyle.cpp", + "src/loaders/svg/tvgSvgLoader.cpp", + "src/loaders/svg/tvgSvgPath.cpp", + "src/loaders/svg/tvgSvgSceneBuilder.cpp", + "src/loaders/svg/tvgSvgUtil.cpp", + "src/loaders/svg/tvgXmlParser.cpp", + "src/loaders/tvg/tvgTvgBinInterpreter.cpp", + "src/loaders/tvg/tvgTvgLoader.cpp", + "src/savers/tvg/tvgTvgSaver.cpp", + ] + thirdparty_tvg_sources = [thirdparty_tvg_dir + file for file in thirdparty_tvg_sources] + + env_tvg.Append( + CPPPATH=[ + "../../../thirdparty/thorvg/inc", + "../../../thirdparty/thorvg/src/lib", + "../../../thirdparty/thorvg/src/lib/sw_engine", + "../../../thirdparty/thorvg/src/loaders/external_png", + "../../../thirdparty/thorvg/src/loaders/jpg", + "../../../thirdparty/thorvg/src/loaders/raw", + "../../../thirdparty/thorvg/src/loaders/svg", + "../../../thirdparty/thorvg/src/loaders/tvg", + "../../../thirdparty/thorvg/src/savers/tvg", + "../../../thirdparty/libpng", + ] + ) + env.Append(CPPPATH=["../../../thirdparty/thorvg/inc", "../../../thirdparty/thorvg/src/lib"]) + env.Append(CPPDEFINES=["MODULE_SVG_ENABLED"]) + + lib = env_tvg.Library( + f'tvg_builtin{env["suffix"]}{env["LIBSUFFIX"]}', + thirdparty_tvg_sources, + ) + env.Append(LIBS=[lib]) + # MSDFGEN if env["msdfgen_enabled"] and env["freetype_enabled"]: env_msdfgen = env.Clone() diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp index 999870b904..aaef9c9a3d 100644 --- a/modules/text_server_fb/text_server_fb.cpp +++ b/modules/text_server_fb/text_server_fb.cpp @@ -41,6 +41,8 @@ using namespace godot; +#define GLOBAL_GET(m_var) ProjectSettings::get_singleton()->get(m_var) + #else // Headers for building as built-in module. @@ -49,7 +51,7 @@ using namespace godot; #include "core/string/print_string.h" #include "core/string/ucaps.h" -#include "modules/modules_enabled.gen.h" // For freetype, msdfgen. +#include "modules/modules_enabled.gen.h" // For freetype, msdfgen, svg. #endif @@ -62,6 +64,10 @@ using namespace godot; #include "msdfgen.h" #endif +#ifdef MODULE_SVG_ENABLED +#include "thorvg_svg_in_ot.h" +#endif + /*************************************************************************/ #define OT_TAG(c1, c2, c3, c4) ((int32_t)((((uint32_t)(c1)&0xff) << 24) | (((uint32_t)(c2)&0xff) << 16) | (((uint32_t)(c3)&0xff) << 8) | ((uint32_t)(c4)&0xff))) @@ -771,6 +777,9 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontFallback *p_f memdelete(fd); ERR_FAIL_V_MSG(false, "FreeType: Error initializing library: '" + String(FT_Error_String(error)) + "'."); } +#ifdef MODULE_SVG_ENABLED + FT_Property_Set(ft_library, "ot-svg", "svg-hooks", get_tvg_svg_in_ot_hooks()); +#endif } memset(&fd->stream, 0, sizeof(FT_StreamRec)); @@ -992,6 +1001,9 @@ int64_t TextServerFallback::_font_get_face_count(const RID &p_font_rid) const { if (!ft_library) { error = FT_Init_FreeType(&ft_library); ERR_FAIL_COND_V_MSG(error != 0, false, "FreeType: Error initializing library: '" + String(FT_Error_String(error)) + "'."); +#ifdef MODULE_SVG_ENABLED + FT_Property_Set(ft_library, "ot-svg", "svg-hooks", get_tvg_svg_in_ot_hooks()); +#endif } FT_StreamRec stream; diff --git a/modules/text_server_fb/text_server_fb.h b/modules/text_server_fb/text_server_fb.h index 151ae13904..7e0bc99618 100644 --- a/modules/text_server_fb/text_server_fb.h +++ b/modules/text_server_fb/text_server_fb.h @@ -87,7 +87,7 @@ using namespace godot; #include "core/templates/rid_owner.h" #include "scene/resources/texture.h" -#include "modules/modules_enabled.gen.h" // For freetype, msdfgen. +#include "modules/modules_enabled.gen.h" // For freetype, msdfgen, svg. #endif @@ -101,6 +101,7 @@ using namespace godot; #include FT_ADVANCES_H #include FT_MULTIPLE_MASTERS_H #include FT_BBOX_H +#include FT_MODULE_H #include FT_CONFIG_OPTIONS_H #if !defined(FT_CONFIG_OPTION_USE_BROTLI) && !defined(_MSC_VER) #warning FreeType is configured without Brotli support, built-in fonts will not be available. diff --git a/modules/text_server_fb/thorvg_bounds_iterator.cpp b/modules/text_server_fb/thorvg_bounds_iterator.cpp new file mode 100644 index 0000000000..54a6136134 --- /dev/null +++ b/modules/text_server_fb/thorvg_bounds_iterator.cpp @@ -0,0 +1,70 @@ +/*************************************************************************/ +/* thorvg_bounds_iterator.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. */ +/*************************************************************************/ + +#ifdef GDEXTENSION +// Headers for building as GDExtension plug-in. + +#include <godot_cpp/godot.hpp> + +using namespace godot; + +#else +// Headers for building as built-in module. + +#include "core/typedefs.h" + +#include "modules/modules_enabled.gen.h" // For svg. +#endif + +#ifdef MODULE_SVG_ENABLED + +#include "thorvg_bounds_iterator.h" + +#include <tvgIteratorAccessor.h> +#include <tvgPaint.h> + +// This function uses private ThorVG API to get bounding box of top level children elements. + +void tvg_get_bounds(tvg::Picture *p_picture, float &r_min_x, float &r_min_y, float &r_max_x, float &r_max_y) { + tvg::IteratorAccessor itrAccessor; + if (tvg::Iterator *it = itrAccessor.iterator(p_picture)) { + while (const tvg::Paint *child = it->next()) { + float x = 0, y = 0, w = 0, h = 0; + child->bounds(&x, &y, &w, &h, true); + r_min_x = MIN(x, r_min_x); + r_min_y = MIN(y, r_min_y); + r_max_x = MAX(x + w, r_max_x); + r_max_y = MAX(y + h, r_max_y); + } + delete (it); + } +} + +#endif // MODULE_SVG_ENABLED diff --git a/modules/text_server_fb/thorvg_bounds_iterator.h b/modules/text_server_fb/thorvg_bounds_iterator.h new file mode 100644 index 0000000000..e54e30eaa2 --- /dev/null +++ b/modules/text_server_fb/thorvg_bounds_iterator.h @@ -0,0 +1,58 @@ +/*************************************************************************/ +/* thorvg_bounds_iterator.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 THORVG_BOUNDS_ITERATOR_H +#define THORVG_BOUNDS_ITERATOR_H + +#ifdef GDEXTENSION +// Headers for building as GDExtension plug-in. + +#include <godot_cpp/core/mutex_lock.hpp> +#include <godot_cpp/godot.hpp> + +using namespace godot; + +#else +// Headers for building as built-in module. + +#include "core/typedefs.h" + +#include "modules/modules_enabled.gen.h" // For svg. +#endif + +#ifdef MODULE_SVG_ENABLED + +#include <thorvg.h> + +void tvg_get_bounds(tvg::Picture *p_picture, float &r_min_x, float &r_min_y, float &r_max_x, float &r_max_y); + +#endif // MODULE_SVG_ENABLED + +#endif // THORVG_BOUNDS_ITERATOR_H diff --git a/modules/text_server_fb/thorvg_svg_in_ot.cpp b/modules/text_server_fb/thorvg_svg_in_ot.cpp new file mode 100644 index 0000000000..7863ab67fa --- /dev/null +++ b/modules/text_server_fb/thorvg_svg_in_ot.cpp @@ -0,0 +1,320 @@ +/*************************************************************************/ +/* thorvg_svg_in_ot.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. */ +/*************************************************************************/ + +#ifdef GDEXTENSION +// Headers for building as GDExtension plug-in. + +#include <godot_cpp/classes/xml_parser.hpp> +#include <godot_cpp/core/mutex_lock.hpp> +#include <godot_cpp/godot.hpp> +#include <godot_cpp/templates/vector.hpp> + +using namespace godot; + +#else +// Headers for building as built-in module. + +#include "core/error/error_macros.h" +#include "core/io/xml_parser.h" +#include "core/os/memory.h" +#include "core/os/os.h" +#include "core/string/ustring.h" +#include "core/typedefs.h" +#include "core/variant/variant.h" + +#include "modules/modules_enabled.gen.h" // For svg. +#endif + +#ifdef MODULE_SVG_ENABLED + +#include "thorvg_bounds_iterator.h" +#include "thorvg_svg_in_ot.h" + +#include <freetype/otsvg.h> +#include <ft2build.h> + +#include <math.h> +#include <stdlib.h> + +FT_Error tvg_svg_in_ot_init(FT_Pointer *p_state) { + *p_state = memnew(TVG_State); + + return FT_Err_Ok; +} + +void tvg_svg_in_ot_free(FT_Pointer *p_state) { + TVG_State *state = *reinterpret_cast<TVG_State **>(p_state); + memdelete(state); +} + +FT_Error tvg_svg_in_ot_preset_slot(FT_GlyphSlot p_slot, FT_Bool p_cache, FT_Pointer *p_state) { + TVG_State *state = *reinterpret_cast<TVG_State **>(p_state); + if (!state) { + ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "SVG in OT state not initialized."); + } + MutexLock lock(state->mutex); + + FT_SVG_Document document = (FT_SVG_Document)p_slot->other; + FT_Size_Metrics metrics = document->metrics; + + GL_State &gl_state = state->glyph_map[p_slot->glyph_index]; + if (!gl_state.ready) { + Ref<XMLParser> parser; + parser.instantiate(); +#ifdef GDEXTENSION + PackedByteArray data; + data.resize(document->svg_document_length); + memcpy(data.ptrw(), document->svg_document, document->svg_document_length); + parser->open_buffer(data); +#else + parser->_open_buffer((const uint8_t *)document->svg_document, document->svg_document_length); +#endif + + float aspect = 1.0f; + String xml_body; + while (parser->read() == OK) { + if (parser->has_attribute("id")) { +#ifdef GDEXTENSION + const String &gl_name = parser->get_named_attribute_value("id"); +#else + const String &gl_name = parser->get_attribute_value("id"); +#endif + if (gl_name.begins_with("glyph")) { + int dot_pos = gl_name.find("."); + int64_t gl_idx = gl_name.substr(5, (dot_pos > 0) ? dot_pos - 5 : -1).to_int(); + if (p_slot->glyph_index != gl_idx) { + parser->skip_section(); + continue; + } + } + } + if (parser->get_node_type() == XMLParser::NODE_ELEMENT && parser->get_node_name() == "svg") { + if (parser->has_attribute("viewBox")) { +#ifdef GDEXTENSION + PackedStringArray vb = parser->get_named_attribute_value("viewBox").split(" "); +#else + Vector<String> vb = parser->get_attribute_value("viewBox").split(" "); +#endif + + if (vb.size() == 4) { + aspect = vb[2].to_float() / vb[3].to_float(); + } + } + continue; + } +#ifdef GDEXTENSION + if (parser->get_node_type() == XMLParser::NODE_ELEMENT) { + xml_body = xml_body + "<" + parser->get_node_name(); + for (int i = 0; i < parser->get_attribute_count(); i++) { + xml_body = xml_body + " " + parser->get_attribute_name(i) + "=\"" + parser->get_attribute_value(i) + "\""; + } + xml_body = xml_body + ">"; + } else if (parser->get_node_type() == XMLParser::NODE_TEXT) { + xml_body = xml_body + parser->get_node_data(); + } else if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END) { + xml_body = xml_body + "</" + parser->get_node_name() + ">"; + } +#else + if (parser->get_node_type() == XMLParser::NODE_ELEMENT) { + xml_body += vformat("<%s", parser->get_node_name()); + for (int i = 0; i < parser->get_attribute_count(); i++) { + xml_body += vformat(" %s=\"%s\"", parser->get_attribute_name(i), parser->get_attribute_value(i)); + } + xml_body += ">"; + } else if (parser->get_node_type() == XMLParser::NODE_TEXT) { + xml_body += parser->get_node_data(); + } else if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END) { + xml_body += vformat("</%s>", parser->get_node_name()); + } +#endif + } + String temp_xml = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 0 0\">" + xml_body; + + std::unique_ptr<tvg::Picture> picture = tvg::Picture::gen(); + tvg::Result result = picture->load(temp_xml.utf8().get_data(), temp_xml.utf8().length(), "svg+xml", false); + if (result != tvg::Result::Success) { + ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to load SVG document (bounds detection)."); + } + + float min_x = INFINITY, min_y = INFINITY, max_x = -INFINITY, max_y = -INFINITY; + tvg_get_bounds(picture.get(), min_x, min_y, max_x, max_y); + + float new_h = (max_y - min_y); + float new_w = (max_x - min_x); + + if (new_h * aspect >= new_w) { + new_w = (new_h * aspect); + } else { + new_h = (new_w / aspect); + } + +#ifdef GDEXTENSION + gl_state.xml_code = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"" + rtos(min_x) + " " + rtos(min_y) + " " + rtos(new_w) + " " + rtos(new_h) + "\">" + xml_body; +#else + gl_state.xml_code = vformat("<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"%f %f %f %f\">", min_x, min_y, new_w, new_h) + xml_body; +#endif + + picture = tvg::Picture::gen(); + result = picture->load(gl_state.xml_code.utf8().get_data(), gl_state.xml_code.utf8().length(), "svg+xml", false); + if (result != tvg::Result::Success) { + ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to load SVG document (glyph metrics)."); + } + + float x_svg_to_out, y_svg_to_out; + x_svg_to_out = (float)metrics.x_ppem / new_w; + y_svg_to_out = (float)metrics.y_ppem / new_h; + + gl_state.m.e11 = (double)document->transform.xx / (1 << 16) * x_svg_to_out; + gl_state.m.e12 = -(double)document->transform.xy / (1 << 16) * x_svg_to_out; + gl_state.m.e21 = -(double)document->transform.yx / (1 << 16) * y_svg_to_out; + gl_state.m.e22 = (double)document->transform.yy / (1 << 16) * y_svg_to_out; + gl_state.m.e13 = (double)document->delta.x / 64 * new_w / metrics.x_ppem; + gl_state.m.e23 = -(double)document->delta.y / 64 * new_h / metrics.y_ppem; + gl_state.m.e31 = 0; + gl_state.m.e32 = 0; + gl_state.m.e33 = 1; + + result = picture->transform(gl_state.m); + if (result != tvg::Result::Success) { + ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to apply transform to SVG document."); + } + + result = picture->bounds(&gl_state.x, &gl_state.y, &gl_state.w, &gl_state.h, true); + if (result != tvg::Result::Success) { + ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to get SVG bounds."); + } + + gl_state.bmp_y = -min_y * gl_state.h / new_h; + gl_state.bmp_x = min_x * gl_state.w / new_w; + + gl_state.ready = true; + } + + p_slot->bitmap_left = (FT_Int)gl_state.bmp_x; + p_slot->bitmap_top = (FT_Int)gl_state.bmp_y; + + float tmp = ceil(gl_state.h); + p_slot->bitmap.rows = (unsigned int)tmp; + tmp = ceil(gl_state.w); + p_slot->bitmap.width = (unsigned int)tmp; + p_slot->bitmap.pitch = (int)p_slot->bitmap.width * 4; + p_slot->bitmap.pixel_mode = FT_PIXEL_MODE_BGRA; + + float metrics_width, metrics_height; + float horiBearingX, horiBearingY; + float vertBearingX, vertBearingY; + + metrics_width = (float)gl_state.w; + metrics_height = (float)gl_state.h; + horiBearingX = (float)gl_state.x; + horiBearingY = (float)-gl_state.y; + vertBearingX = p_slot->metrics.horiBearingX / 64.0f - p_slot->metrics.horiAdvance / 64.0f / 2; + vertBearingY = (p_slot->metrics.vertAdvance / 64.0f - p_slot->metrics.height / 64.0f) / 2; + + tmp = roundf(metrics_width * 64); + p_slot->metrics.width = (FT_Pos)tmp; + tmp = roundf(metrics_height * 64); + p_slot->metrics.height = (FT_Pos)tmp; + + p_slot->metrics.horiBearingX = (FT_Pos)(horiBearingX * 64); + p_slot->metrics.horiBearingY = (FT_Pos)(horiBearingY * 64); + p_slot->metrics.vertBearingX = (FT_Pos)(vertBearingX * 64); + p_slot->metrics.vertBearingY = (FT_Pos)(vertBearingY * 64); + + if (p_slot->metrics.vertAdvance == 0) { + p_slot->metrics.vertAdvance = (FT_Pos)(metrics_height * 1.2f * 64); + } + + return FT_Err_Ok; +} + +FT_Error tvg_svg_in_ot_render(FT_GlyphSlot p_slot, FT_Pointer *p_state) { + TVG_State *state = *reinterpret_cast<TVG_State **>(p_state); + if (!state) { + ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "SVG in OT state not initialized."); + } + MutexLock lock(state->mutex); + + if (!state->glyph_map.has(p_slot->glyph_index)) { + ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "SVG glyph not loaded."); + } + + GL_State &gl_state = state->glyph_map[p_slot->glyph_index]; + ERR_FAIL_COND_V_MSG(!gl_state.ready, FT_Err_Invalid_SVG_Document, "SVG glyph not ready."); + + std::unique_ptr<tvg::Picture> picture = tvg::Picture::gen(); + tvg::Result res = picture->load(gl_state.xml_code.utf8().get_data(), gl_state.xml_code.utf8().length(), "svg+xml", false); + if (res != tvg::Result::Success) { + ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to load SVG document (glyph rendering)."); + } + res = picture->transform(gl_state.m); + if (res != tvg::Result::Success) { + ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to apply transform to SVG document."); + } + + std::unique_ptr<tvg::SwCanvas> sw_canvas = tvg::SwCanvas::gen(); + res = sw_canvas->target((uint32_t *)p_slot->bitmap.buffer, (int)p_slot->bitmap.width, (int)p_slot->bitmap.width, (int)p_slot->bitmap.rows, tvg::SwCanvas::ARGB8888_STRAIGHT); + if (res != tvg::Result::Success) { + ERR_FAIL_V_MSG(FT_Err_Invalid_Outline, "Failed to create SVG canvas."); + } + res = sw_canvas->push(std::move(picture)); + if (res != tvg::Result::Success) { + ERR_FAIL_V_MSG(FT_Err_Invalid_Outline, "Failed to set SVG canvas source."); + } + res = sw_canvas->draw(); + if (res != tvg::Result::Success) { + ERR_FAIL_V_MSG(FT_Err_Invalid_Outline, "Failed to draw to SVG canvas."); + } + res = sw_canvas->sync(); + if (res != tvg::Result::Success) { + ERR_FAIL_V_MSG(FT_Err_Invalid_Outline, "Failed to sync SVG canvas."); + } + + state->glyph_map.erase(p_slot->glyph_index); + + p_slot->bitmap.pixel_mode = FT_PIXEL_MODE_BGRA; + p_slot->bitmap.num_grays = 256; + p_slot->format = FT_GLYPH_FORMAT_BITMAP; + + return FT_Err_Ok; +} + +SVG_RendererHooks tvg_svg_in_ot_hooks = { + (SVG_Lib_Init_Func)tvg_svg_in_ot_init, + (SVG_Lib_Free_Func)tvg_svg_in_ot_free, + (SVG_Lib_Render_Func)tvg_svg_in_ot_render, + (SVG_Lib_Preset_Slot_Func)tvg_svg_in_ot_preset_slot, +}; + +SVG_RendererHooks *get_tvg_svg_in_ot_hooks() { + return &tvg_svg_in_ot_hooks; +} + +#endif // MODULE_SVG_ENABLED diff --git a/modules/text_server_fb/thorvg_svg_in_ot.h b/modules/text_server_fb/thorvg_svg_in_ot.h new file mode 100644 index 0000000000..b2816193d9 --- /dev/null +++ b/modules/text_server_fb/thorvg_svg_in_ot.h @@ -0,0 +1,86 @@ +/*************************************************************************/ +/* thorvg_svg_in_ot.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 THORVG_SVG_IN_OT_H +#define THORVG_SVG_IN_OT_H + +#ifdef GDEXTENSION +// Headers for building as GDExtension plug-in. + +#include <godot_cpp/core/mutex_lock.hpp> +#include <godot_cpp/godot.hpp> +#include <godot_cpp/templates/hash_map.hpp> + +using namespace godot; + +#else +// Headers for building as built-in module. + +#include "core/os/mutex.h" +#include "core/templates/hash_map.h" +#include "core/typedefs.h" + +#include "modules/modules_enabled.gen.h" // For svg. +#endif + +#ifdef MODULE_SVG_ENABLED + +#include <freetype/freetype.h> +#include <freetype/otsvg.h> +#include <ft2build.h> +#include <thorvg.h> + +struct GL_State { + bool ready = false; + float bmp_x = 0; + float bmp_y = 0; + float x = 0; + float y = 0; + float w = 0; + float h = 0; + String xml_code; + tvg::Matrix m; +}; + +struct TVG_State { + Mutex mutex; + HashMap<uint32_t, GL_State> glyph_map; +}; + +FT_Error tvg_svg_in_ot_init(FT_Pointer *p_state); +void tvg_svg_in_ot_free(FT_Pointer *p_state); +FT_Error tvg_svg_in_ot_preset_slot(FT_GlyphSlot p_slot, FT_Bool p_cache, FT_Pointer *p_state); +FT_Error tvg_svg_in_ot_render(FT_GlyphSlot p_slot, FT_Pointer *p_state); + +SVG_RendererHooks *get_tvg_svg_in_ot_hooks(); + +#endif // MODULE_SVG_ENABLED + +#endif // THORVG_SVG_IN_OT_H diff --git a/modules/webxr/native/library_godot_webxr.js b/modules/webxr/native/library_godot_webxr.js index c476a54c59..714768347c 100644 --- a/modules/webxr/native/library_godot_webxr.js +++ b/modules/webxr/native/library_godot_webxr.js @@ -28,7 +28,7 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ const GodotWebXR = { - $GodotWebXR__deps: ['$Browser', '$GL', '$GodotRuntime'], + $GodotWebXR__deps: ['$Browser', '$GL', '$GodotRuntime', '$runtimeKeepalivePush', '$runtimeKeepalivePop'], $GodotWebXR: { gl: null, @@ -69,7 +69,9 @@ const GodotWebXR = { // gets picked up automatically, however, in the Oculus Browser // on the Quest, we need to pause and resume the main loop. Browser.mainLoop.pause(); + runtimeKeepalivePush(); // eslint-disable-line no-undef window.setTimeout(function () { + runtimeKeepalivePop(); // eslint-disable-line no-undef Browser.mainLoop.resume(); }, 0); }, diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 9919268375..c3fba625c6 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -802,7 +802,7 @@ void EditorExportPlatformAndroid::_get_permissions(const Ref<EditorExportPreset> } void EditorExportPlatformAndroid::_write_tmp_manifest(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, bool p_debug) { - print_verbose("Building temporary manifest.."); + print_verbose("Building temporary manifest..."); String manifest_text = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" @@ -2491,7 +2491,7 @@ void EditorExportPlatformAndroid::_clear_assets_directory() { // Clear the APK assets directory if (da_res->dir_exists(APK_ASSETS_DIRECTORY)) { - print_verbose("Clearing APK assets directory.."); + print_verbose("Clearing APK assets directory..."); Ref<DirAccess> da_assets = DirAccess::open(APK_ASSETS_DIRECTORY); da_assets->erase_contents_recursive(); da_res->remove(APK_ASSETS_DIRECTORY); @@ -2499,7 +2499,7 @@ void EditorExportPlatformAndroid::_clear_assets_directory() { // Clear the AAB assets directory if (da_res->dir_exists(AAB_ASSETS_DIRECTORY)) { - print_verbose("Clearing AAB assets directory.."); + print_verbose("Clearing AAB assets directory..."); Ref<DirAccess> da_assets = DirAccess::open(AAB_ASSETS_DIRECTORY); da_assets->erase_contents_recursive(); da_res->remove(AAB_ASSETS_DIRECTORY); @@ -2615,10 +2615,10 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP } if (use_custom_build) { - print_verbose("Starting custom build.."); + print_verbose("Starting custom build..."); //test that installed build version is alright { - print_verbose("Checking build version.."); + print_verbose("Checking build version..."); Ref<FileAccess> f = FileAccess::open("res://android/.build_version", FileAccess::READ); if (f.is_null()) { add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Trying to build from a custom built template, but no version info for it exists. Please reinstall from the 'Project' menu.")); @@ -2651,7 +2651,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP _clear_assets_directory(); _remove_copied_libs(); if (!apk_expansion) { - print_verbose("Exporting project files.."); + print_verbose("Exporting project files..."); CustomExportData user_data; user_data.assets_directory = assets_directory; user_data.debug = p_debug; @@ -2665,14 +2665,14 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP fa->store_string(JSON::stringify(user_data.libs, "\t")); } } else { - print_verbose("Saving apk expansion file.."); + print_verbose("Saving apk expansion file..."); err = save_apk_expansion_file(p_preset, p_debug, p_path); if (err != OK) { add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Could not write expansion package file!")); return err; } } - print_verbose("Storing command line flags.."); + print_verbose("Storing command line flags..."); store_file_at_path(assets_directory + "/_cl_", command_line_flags); print_verbose("Updating ANDROID_HOME environment to " + sdk_path); @@ -2825,7 +2825,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP return OK; } // This is the start of the Legacy build system - print_verbose("Starting legacy build system.."); + print_verbose("Starting legacy build system..."); if (p_debug) { src_apk = p_preset->get("custom_template/debug"); } else { diff --git a/platform/android/plugin/godot_plugin_jni.cpp b/platform/android/plugin/godot_plugin_jni.cpp index 498977ad49..5a7123b833 100644 --- a/platform/android/plugin/godot_plugin_jni.cpp +++ b/platform/android/plugin/godot_plugin_jni.cpp @@ -135,7 +135,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegis } // Retrieve the current list of gdnative libraries. - Array singletons = Array(); + Array singletons; if (ProjectSettings::get_singleton()->has_setting("gdnative/singletons")) { singletons = GLOBAL_GET("gdnative/singletons"); } diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp index 22c5f063c3..81cadea51f 100644 --- a/platform/linuxbsd/os_linuxbsd.cpp +++ b/platform/linuxbsd/os_linuxbsd.cpp @@ -128,6 +128,8 @@ void OS_LinuxBSD::initialize() { crash_handler.initialize(); OS_Unix::initialize_core(); + + system_dir_desktop_cache = get_system_dir(SYSTEM_DIR_DESKTOP); } void OS_LinuxBSD::initialize_joypads() { @@ -719,6 +721,10 @@ String OS_LinuxBSD::get_cache_path() const { } String OS_LinuxBSD::get_system_dir(SystemDir p_dir, bool p_shared_storage) const { + if (p_dir == SYSTEM_DIR_DESKTOP && !system_dir_desktop_cache.is_empty()) { + return system_dir_desktop_cache; + } + String xdgparam; switch (p_dir) { @@ -727,31 +733,24 @@ String OS_LinuxBSD::get_system_dir(SystemDir p_dir, bool p_shared_storage) const } break; case SYSTEM_DIR_DCIM: { xdgparam = "PICTURES"; - } break; case SYSTEM_DIR_DOCUMENTS: { xdgparam = "DOCUMENTS"; - } break; case SYSTEM_DIR_DOWNLOADS: { xdgparam = "DOWNLOAD"; - } break; case SYSTEM_DIR_MOVIES: { xdgparam = "VIDEOS"; - } break; case SYSTEM_DIR_MUSIC: { xdgparam = "MUSIC"; - } break; case SYSTEM_DIR_PICTURES: { xdgparam = "PICTURES"; - } break; case SYSTEM_DIR_RINGTONES: { xdgparam = "MUSIC"; - } break; } diff --git a/platform/linuxbsd/os_linuxbsd.h b/platform/linuxbsd/os_linuxbsd.h index aea04c1363..aa7af92aa1 100644 --- a/platform/linuxbsd/os_linuxbsd.h +++ b/platform/linuxbsd/os_linuxbsd.h @@ -72,6 +72,8 @@ class OS_LinuxBSD : public OS_Unix { Vector<String> lspci_device_filter(Vector<String> vendor_device_id_mapping, String class_suffix, String check_column, String whitelist) const; Vector<String> lspci_get_device_value(Vector<String> vendor_device_id_mapping, String check_column, String blacklist) const; + String system_dir_desktop_cache; + protected: virtual void initialize() override; virtual void finalize() override; diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h index 618da6b388..8e75b98302 100644 --- a/platform/macos/display_server_macos.h +++ b/platform/macos/display_server_macos.h @@ -106,6 +106,7 @@ public: bool layered_window = false; bool fullscreen = false; + bool exclusive_fullscreen = false; bool on_top = false; bool borderless = false; bool resize_disabled = false; diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm index e8a56c984c..42a984a4eb 100644 --- a/platform/macos/display_server_macos.mm +++ b/platform/macos/display_server_macos.mm @@ -2584,7 +2584,13 @@ void DisplayServerMacOS::window_set_mode(WindowMode p_mode, WindowID p_window) { [wd.window_object setContentMaxSize:NSMakeSize(size.x, size.y)]; } [wd.window_object toggleFullScreen:nil]; + + if (old_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) { + [NSApp setPresentationOptions:NSApplicationPresentationDefault]; + } + wd.fullscreen = false; + wd.exclusive_fullscreen = false; } break; case WINDOW_MODE_MAXIMIZED: { if ([wd.window_object isZoomed]) { @@ -2609,7 +2615,15 @@ void DisplayServerMacOS::window_set_mode(WindowMode p_mode, WindowID p_window) { [wd.window_object setContentMinSize:NSMakeSize(0, 0)]; [wd.window_object setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)]; [wd.window_object toggleFullScreen:nil]; + wd.fullscreen = true; + if (p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) { + const NSUInteger presentationOptions = NSApplicationPresentationHideDock | NSApplicationPresentationHideMenuBar; + [NSApp setPresentationOptions:presentationOptions]; + wd.exclusive_fullscreen = true; + } else { + wd.exclusive_fullscreen = false; + } } break; case WINDOW_MODE_MAXIMIZED: { if (![wd.window_object isZoomed]) { @@ -2626,7 +2640,11 @@ DisplayServer::WindowMode DisplayServerMacOS::window_get_mode(WindowID p_window) const WindowData &wd = windows[p_window]; if (wd.fullscreen) { // If fullscreen, it's not in another mode. - return WINDOW_MODE_FULLSCREEN; + if (wd.exclusive_fullscreen) { + return WINDOW_MODE_EXCLUSIVE_FULLSCREEN; + } else { + return WINDOW_MODE_FULLSCREEN; + } } if ([wd.window_object isZoomed] && !wd.resize_disabled) { return WINDOW_MODE_MAXIMIZED; diff --git a/platform/macos/godot_window_delegate.mm b/platform/macos/godot_window_delegate.mm index 279fd2a359..3bdbc8c5ec 100644 --- a/platform/macos/godot_window_delegate.mm +++ b/platform/macos/godot_window_delegate.mm @@ -147,7 +147,12 @@ } DisplayServerMacOS::WindowData &wd = ds->get_window(window_id); + if (wd.exclusive_fullscreen) { + [NSApp setPresentationOptions:NSApplicationPresentationDefault]; + } + wd.fullscreen = false; + wd.exclusive_fullscreen = false; [(GodotWindow *)wd.window_object setAnimDuration:-1.0f]; diff --git a/platform/windows/console_wrapper_windows.cpp b/platform/windows/console_wrapper_windows.cpp index 749f51e6e4..258176426b 100644 --- a/platform/windows/console_wrapper_windows.cpp +++ b/platform/windows/console_wrapper_windows.cpp @@ -63,7 +63,7 @@ int main(int argc, char *argv[]) { } } - // Enable virtual termial sequences processing. + // Enable virtual terminal sequences processing. HANDLE stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE); DWORD out_mode = ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING; SetConsoleMode(stdout_handle, out_mode); diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index ec6a8d5579..af80a07da9 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -1892,7 +1892,7 @@ void DisplayServerWindows::set_native_icon(const String &p_filename) { pos += sizeof(WORD); f->seek(pos); - icon_dir = (ICONDIR *)memrealloc(icon_dir, 3 * sizeof(WORD) + icon_dir->idCount * sizeof(ICONDIRENTRY)); + icon_dir = (ICONDIR *)memrealloc(icon_dir, sizeof(ICONDIR) - sizeof(ICONDIRENTRY) + icon_dir->idCount * sizeof(ICONDIRENTRY)); f->get_buffer((uint8_t *)&icon_dir->idEntries[0], icon_dir->idCount * sizeof(ICONDIRENTRY)); int small_icon_index = -1; // Select 16x16 with largest color count. diff --git a/scene/2d/navigation_link_2d.h b/scene/2d/navigation_link_2d.h index 2a5092216d..9d36f80dd6 100644 --- a/scene/2d/navigation_link_2d.h +++ b/scene/2d/navigation_link_2d.h @@ -37,11 +37,11 @@ class NavigationLink2D : public Node2D { GDCLASS(NavigationLink2D, Node2D); bool enabled = true; - RID link = RID(); + RID link; bool bidirectional = true; uint32_t navigation_layers = 1; - Vector2 end_location = Vector2(); - Vector2 start_location = Vector2(); + Vector2 end_location; + Vector2 start_location; real_t enter_cost = 0.0; real_t travel_cost = 1.0; diff --git a/scene/2d/skeleton_2d.cpp b/scene/2d/skeleton_2d.cpp index 02d8e20ce8..62787d4488 100644 --- a/scene/2d/skeleton_2d.cpp +++ b/scene/2d/skeleton_2d.cpp @@ -213,7 +213,7 @@ void Bone2D::_notification(int p_what) { } // Undo scaling - Transform2D editor_gizmo_trans = Transform2D(); + Transform2D editor_gizmo_trans; editor_gizmo_trans.set_scale(Vector2(1, 1) / get_global_scale()); RenderingServer::get_singleton()->canvas_item_set_transform(editor_gizmo_rid, editor_gizmo_trans); diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h index 7f3e241871..eaf100631e 100644 --- a/scene/2d/tile_map.h +++ b/scene/2d/tile_map.h @@ -115,7 +115,7 @@ public: class TerrainConstraint { private: const TileMap *tile_map; - Vector2i base_cell_coords = Vector2i(); + Vector2i base_cell_coords; int bit = -1; int terrain = -1; diff --git a/scene/3d/cpu_particles_3d.cpp b/scene/3d/cpu_particles_3d.cpp index 7de685b469..5ac8535bb6 100644 --- a/scene/3d/cpu_particles_3d.cpp +++ b/scene/3d/cpu_particles_3d.cpp @@ -867,7 +867,7 @@ void CPUParticles3D::_particles_process(double p_delta) { real_t ring_random_angle = Math::randf() * Math_TAU; real_t ring_random_radius = Math::randf() * (emission_ring_radius - emission_ring_inner_radius) + emission_ring_inner_radius; Vector3 axis = emission_ring_axis.normalized(); - Vector3 ortho_axis = Vector3(); + Vector3 ortho_axis; if (axis == Vector3(1.0, 0.0, 0.0)) { ortho_axis = Vector3(0.0, 1.0, 0.0).cross(axis); } else { diff --git a/scene/3d/navigation_link_3d.h b/scene/3d/navigation_link_3d.h index 1fc03546fa..46bc6318b8 100644 --- a/scene/3d/navigation_link_3d.h +++ b/scene/3d/navigation_link_3d.h @@ -37,11 +37,11 @@ class NavigationLink3D : public Node3D { GDCLASS(NavigationLink3D, Node3D); bool enabled = true; - RID link = RID(); + RID link; bool bidirectional = true; uint32_t navigation_layers = 1; - Vector3 end_location = Vector3(); - Vector3 start_location = Vector3(); + Vector3 end_location; + Vector3 start_location; real_t enter_cost = 0.0; real_t travel_cost = 1.0; diff --git a/scene/debugger/scene_debugger.cpp b/scene/debugger/scene_debugger.cpp index b4bdda9ecb..df27eecce7 100644 --- a/scene/debugger/scene_debugger.cpp +++ b/scene/debugger/scene_debugger.cpp @@ -39,87 +39,10 @@ #include "scene/main/window.h" #include "scene/resources/packed_scene.h" -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 { - HashMap<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_process_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)); diff --git a/scene/debugger/scene_debugger.h b/scene/debugger/scene_debugger.h index fe35446aae..0428bfcc2e 100644 --- a/scene/debugger/scene_debugger.h +++ b/scene/debugger/scene_debugger.h @@ -42,28 +42,9 @@ 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: diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp index c2b82e01d1..0e7bc5c306 100644 --- a/scene/gui/button.cpp +++ b/scene/gui/button.cpp @@ -231,7 +231,7 @@ void Button::_notification(int p_what) { _icon = icon; } - Rect2 icon_region = Rect2(); + Rect2 icon_region; HorizontalAlignment icon_align_rtl_checked = icon_alignment; HorizontalAlignment align_rtl_checked = alignment; // Swap icon and text alignment sides if right-to-left layout is set. diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index 82f56a56c9..ab74979777 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -558,7 +558,7 @@ void PopupMenu::_draw_items() { check_ofs += theme_cache.h_separation; } - Point2 ofs = Point2(); + Point2 ofs; // Loop through all items and draw each. for (int i = 0; i < items.size(); i++) { diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 7f487175dc..87cc26187a 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -4057,7 +4057,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { Color color = theme_cache.default_color; Color outline_color = theme_cache.font_outline_color; int outline_size = theme_cache.outline_size; - Rect2 dropcap_margins = Rect2(); + Rect2 dropcap_margins; for (int i = 0; i < subtag.size(); i++) { Vector<String> subtag_a = subtag[i].split("="); @@ -5716,11 +5716,11 @@ Ref<RichTextEffect> RichTextLabel::_get_custom_effect_by_code(String p_bbcode_id } Dictionary RichTextLabel::parse_expressions_for_values(Vector<String> p_expressions) { - Dictionary d = Dictionary(); + Dictionary d; for (int i = 0; i < p_expressions.size(); i++) { String expression = p_expressions[i]; - Array a = Array(); + Array a; Vector<String> parts = expression.split("=", true); String key = parts[0]; if (parts.size() != 2) { diff --git a/scene/gui/scroll_bar.h b/scene/gui/scroll_bar.h index 13ca62d7ff..d62acd52af 100644 --- a/scene/gui/scroll_bar.h +++ b/scene/gui/scroll_bar.h @@ -72,7 +72,7 @@ class ScrollBar : public Range { NodePath drag_node_path; bool drag_node_enabled = true; - Vector2 drag_node_speed = Vector2(); + Vector2 drag_node_speed; Vector2 drag_node_accum; Vector2 drag_node_from; Vector2 last_drag_node_accum; diff --git a/scene/gui/texture_button.cpp b/scene/gui/texture_button.cpp index d9ab1c2c55..ad12757921 100644 --- a/scene/gui/texture_button.cpp +++ b/scene/gui/texture_button.cpp @@ -64,7 +64,7 @@ Size2 TextureButton::get_minimum_size() const { bool TextureButton::has_point(const Point2 &p_point) const { if (click_mask.is_valid()) { Point2 point = p_point; - Rect2 rect = Rect2(); + Rect2 rect; Size2 mask_size = click_mask->get_size(); if (!_position_rect.has_area()) { diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index 1c96858da7..2da76883b4 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -4338,6 +4338,12 @@ TreeItem *Tree::get_selected() const { return selected_item; } +void Tree::set_selected(TreeItem *p_item, int p_column) { + ERR_FAIL_INDEX(p_column, columns.size()); + ERR_FAIL_COND(!p_item); + select_single_item(p_item, get_root(), p_column); +} + int Tree::get_selected_column() const { return selected_col; } @@ -4547,6 +4553,7 @@ void Tree::ensure_cursor_is_visible() { return; // Nothing under cursor. } + // Note: Code below similar to Tree::scroll_to_item(), in case of bug fix both. const Size2 area_size = get_size() - theme_cache.panel_style->get_minimum_size(); int y_offset = get_item_offset(selected_item); @@ -4555,7 +4562,10 @@ void Tree::ensure_cursor_is_visible() { y_offset -= tbh; const int cell_h = compute_item_height(selected_item) + theme_cache.vseparation; - const int screen_h = area_size.height - h_scroll->get_combined_minimum_size().height - tbh; + int screen_h = area_size.height - tbh; + if (h_scroll->is_visible()) { + screen_h -= h_scroll->get_combined_minimum_size().height; + } if (cell_h > screen_h) { // Screen size is too small, maybe it was not resized yet. v_scroll->set_value(y_offset); @@ -4706,26 +4716,32 @@ Point2 Tree::get_scroll() const { void Tree::scroll_to_item(TreeItem *p_item, bool p_center_on_item) { ERR_FAIL_NULL(p_item); - if (!is_visible_in_tree() || !p_item->is_visible()) { - return; // Hack to work around crash in get_item_rect() if Tree is not in tree. - } update_scrollbars(); - const real_t tree_height = get_size().y; - const Rect2 item_rect = get_item_rect(p_item); - const real_t item_y = item_rect.position.y; - const real_t item_height = item_rect.size.y + theme_cache.vseparation; + // Note: Code below similar to Tree::ensure_cursor_is_visible(), in case of bug fix both. + const Size2 area_size = get_size() - theme_cache.panel_style->get_minimum_size(); - if (p_center_on_item) { - v_scroll->set_value(item_y - (tree_height - item_height) / 2.0f); - } else { - if (item_y < v_scroll->get_value()) { - v_scroll->set_value(item_y); + int y_offset = get_item_offset(p_item); + if (y_offset != -1) { + const int tbh = _get_title_button_height(); + y_offset -= tbh; + + const int cell_h = compute_item_height(p_item) + theme_cache.vseparation; + int screen_h = area_size.height - tbh; + if (h_scroll->is_visible()) { + screen_h -= h_scroll->get_combined_minimum_size().height; + } + + if (p_center_on_item) { + v_scroll->set_value(y_offset - (screen_h - cell_h) / 2.0f); } else { - const real_t new_position = item_y + item_height - tree_height; - if (new_position > v_scroll->get_value()) { - v_scroll->set_value(new_position); + if (cell_h > screen_h) { // Screen size is too small, maybe it was not resized yet. + v_scroll->set_value(y_offset); + } else if (y_offset + cell_h > v_scroll->get_value() + screen_h) { + v_scroll->set_value(y_offset - screen_h + cell_h); + } else if (y_offset < v_scroll->get_value()) { + v_scroll->set_value(y_offset); } } } @@ -5156,6 +5172,7 @@ void Tree::_bind_methods() { ClassDB::bind_method(D_METHOD("is_root_hidden"), &Tree::is_root_hidden); ClassDB::bind_method(D_METHOD("get_next_selected", "from"), &Tree::get_next_selected); ClassDB::bind_method(D_METHOD("get_selected"), &Tree::get_selected); + ClassDB::bind_method(D_METHOD("set_selected", "item", "column"), &Tree::set_selected); ClassDB::bind_method(D_METHOD("get_selected_column"), &Tree::get_selected_column); ClassDB::bind_method(D_METHOD("get_pressed_button"), &Tree::get_pressed_button); ClassDB::bind_method(D_METHOD("set_select_mode", "mode"), &Tree::set_select_mode); diff --git a/scene/gui/tree.h b/scene/gui/tree.h index f994a5cec1..77a62e1d6a 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -666,6 +666,7 @@ public: bool is_root_hidden() const; TreeItem *get_next_selected(TreeItem *p_item); TreeItem *get_selected() const; + void set_selected(TreeItem *p_item, int p_column = 0); int get_selected_column() const; int get_pressed_button() const; void set_select_mode(SelectMode p_mode); diff --git a/scene/main/multiplayer_api.cpp b/scene/main/multiplayer_api.cpp index 946929aa89..8b4b98c172 100644 --- a/scene/main/multiplayer_api.cpp +++ b/scene/main/multiplayer_api.cpp @@ -39,7 +39,7 @@ #include "core/os/os.h" #endif -StringName MultiplayerAPI::default_interface = StringName(); +StringName MultiplayerAPI::default_interface; void MultiplayerAPI::set_default_interface(const StringName &p_interface) { ERR_FAIL_COND_MSG(!ClassDB::is_parent_class(p_interface, MultiplayerAPI::get_class_static()), vformat("Can't make %s the default multiplayer interface since it does not extend MultiplayerAPI.", p_interface)); diff --git a/scene/main/node.cpp b/scene/main/node.cpp index 304504a82e..5dd0911694 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -1759,11 +1759,11 @@ void Node::add_to_group(const StringName &p_identifier, bool p_persistent) { } void Node::remove_from_group(const StringName &p_identifier) { - ERR_FAIL_COND(!data.grouped.has(p_identifier)); - HashMap<StringName, GroupData>::Iterator E = data.grouped.find(p_identifier); - ERR_FAIL_COND(!E); + if (!E) { + return; + } if (data.tree) { data.tree->remove_from_group(E->key, this); @@ -2581,10 +2581,14 @@ void Node::print_orphan_nodes() { } void Node::queue_free() { + // There are users which instantiate multiple scene trees for their games. + // Use the node's own tree to handle its deletion when relevant. if (is_inside_tree()) { get_tree()->queue_delete(this); } else { - SceneTree::get_singleton()->queue_delete(this); + SceneTree *tree = SceneTree::get_singleton(); + ERR_FAIL_NULL_MSG(tree, "Can't queue free a node when no SceneTree is available."); + tree->queue_delete(this); } } @@ -2652,7 +2656,7 @@ PackedStringArray Node::get_configuration_warnings() const { String Node::get_configuration_warnings_as_string() const { PackedStringArray warnings = get_configuration_warnings(); - String all_warnings = String(); + String all_warnings; for (int i = 0; i < warnings.size(); i++) { if (i > 0) { all_warnings += "\n\n"; diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index 41eefe0f85..81a4e3073b 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -123,6 +123,9 @@ void SceneTree::tree_changed() { void SceneTree::node_added(Node *p_node) { emit_signal(node_added_name, p_node); + if (call_lock > 0) { + call_skip.erase(p_node->get_instance_id()); + } } void SceneTree::node_removed(Node *p_node) { @@ -131,7 +134,7 @@ void SceneTree::node_removed(Node *p_node) { } emit_signal(node_removed_name, p_node); if (call_lock > 0) { - call_skip.insert(p_node); + call_skip.insert(p_node->get_instance_id()); } } @@ -261,7 +264,7 @@ void SceneTree::call_group_flagsp(uint32_t p_call_flags, const StringName &p_gro if (p_call_flags & GROUP_CALL_REVERSE) { for (int i = gr_node_count - 1; i >= 0; i--) { - if (call_lock && call_skip.has(gr_nodes[i])) { + if (call_lock && call_skip.has(gr_nodes[i]->get_instance_id())) { continue; } @@ -275,7 +278,7 @@ void SceneTree::call_group_flagsp(uint32_t p_call_flags, const StringName &p_gro } else { for (int i = 0; i < gr_node_count; i++) { - if (call_lock && call_skip.has(gr_nodes[i])) { + if (call_lock && call_skip.has(gr_nodes[i]->get_instance_id())) { continue; } @@ -314,7 +317,7 @@ void SceneTree::notify_group_flags(uint32_t p_call_flags, const StringName &p_gr if (p_call_flags & GROUP_CALL_REVERSE) { for (int i = gr_node_count - 1; i >= 0; i--) { - if (call_lock && call_skip.has(gr_nodes[i])) { + if (call_lock && call_skip.has(gr_nodes[i]->get_instance_id())) { continue; } @@ -327,7 +330,7 @@ void SceneTree::notify_group_flags(uint32_t p_call_flags, const StringName &p_gr } else { for (int i = 0; i < gr_node_count; i++) { - if (call_lock && call_skip.has(gr_nodes[i])) { + if (call_lock && call_skip.has(gr_nodes[i]->get_instance_id())) { continue; } @@ -365,7 +368,7 @@ void SceneTree::set_group_flags(uint32_t p_call_flags, const StringName &p_group if (p_call_flags & GROUP_CALL_REVERSE) { for (int i = gr_node_count - 1; i >= 0; i--) { - if (call_lock && call_skip.has(gr_nodes[i])) { + if (call_lock && call_skip.has(gr_nodes[i]->get_instance_id())) { continue; } @@ -378,7 +381,7 @@ void SceneTree::set_group_flags(uint32_t p_call_flags, const StringName &p_group } else { for (int i = 0; i < gr_node_count; i++) { - if (call_lock && call_skip.has(gr_nodes[i])) { + if (call_lock && call_skip.has(gr_nodes[i]->get_instance_id())) { continue; } @@ -854,7 +857,7 @@ void SceneTree::_notify_group_pause(const StringName &p_group, int p_notificatio for (int i = 0; i < gr_node_count; i++) { Node *n = gr_nodes[i]; - if (call_lock && call_skip.has(n)) { + if (call_lock && call_skip.has(n->get_instance_id())) { continue; } @@ -904,7 +907,7 @@ void SceneTree::_call_input_pause(const StringName &p_group, CallInputType p_cal } Node *n = gr_nodes[i]; - if (call_lock && call_skip.has(n)) { + if (call_lock && call_skip.has(n->get_instance_id())) { continue; } diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h index a460e40597..d4fcb288ae 100644 --- a/scene/main/scene_tree.h +++ b/scene/main/scene_tree.h @@ -135,7 +135,7 @@ private: // Safety for when a node is deleted while a group is being called. int call_lock = 0; - HashSet<Node *> call_skip; // Skip erased nodes. + HashSet<ObjectID> call_skip; // Skip erased nodes. Store ID instead of pointer to avoid false positives when node is freed and a new node is allocated at the pointed address. List<ObjectID> delete_queue; diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 43a2c9473e..4c2a761138 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -1669,6 +1669,9 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { _gui_cancel_tooltip(); if (over) { + if (!gui.mouse_over) { + _drop_physics_mouseover(); + } _gui_call_notification(over, Control::NOTIFICATION_MOUSE_ENTER); gui.mouse_over = over; } @@ -2660,6 +2663,11 @@ bool Viewport::_sub_windows_forward_input(const Ref<InputEvent> &p_event) { } else { gui.subwindow_resize_mode = _sub_window_get_resize_margin(sw.window, mb->get_position()); if (gui.subwindow_resize_mode != SUB_WINDOW_RESIZE_DISABLED) { + if (gui.subwindow_focused != sw.window) { + // Refocus. + _sub_window_grab_focus(sw.window); + } + gui.subwindow_resize_from_rect = r; gui.subwindow_drag_from = mb->get_position(); gui.subwindow_drag = SUB_WINDOW_DRAG_RESIZE; @@ -3039,8 +3047,6 @@ bool Viewport::gui_is_drag_successful() const { } void Viewport::set_input_as_handled() { - _drop_physics_mouseover(); - if (!handle_input_locally) { ERR_FAIL_COND(!is_inside_tree()); Viewport *vp = this; @@ -4159,7 +4165,7 @@ DisplayServer::WindowID SubViewport::get_window_id() const { } Transform2D SubViewport::_stretch_transform() { - Transform2D transform = Transform2D(); + Transform2D transform; Size2i view_size_2d_override = _get_size_2d_override(); if (size_2d_override_stretch && view_size_2d_override.width > 0 && view_size_2d_override.height > 0) { Size2 scale = Size2(_get_size()) / Size2(view_size_2d_override); @@ -4170,7 +4176,7 @@ Transform2D SubViewport::_stretch_transform() { } Transform2D SubViewport::get_screen_transform() const { - Transform2D container_transform = Transform2D(); + Transform2D container_transform; SubViewportContainer *c = Object::cast_to<SubViewportContainer>(get_parent()); if (c) { if (c->is_stretch_enabled()) { diff --git a/scene/main/window.cpp b/scene/main/window.cpp index ebb11ecee8..aff2c594d9 100644 --- a/scene/main/window.cpp +++ b/scene/main/window.cpp @@ -1633,7 +1633,7 @@ void Window::_validate_property(PropertyInfo &p_property) const { } Transform2D Window::get_screen_transform() const { - Transform2D embedder_transform = Transform2D(); + Transform2D embedder_transform; if (_get_embedder()) { embedder_transform.translate_local(get_position()); embedder_transform = _get_embedder()->get_screen_transform() * embedder_transform; diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp index 1eb78f0160..ef619c9893 100644 --- a/scene/resources/animation.cpp +++ b/scene/resources/animation.cpp @@ -4964,7 +4964,7 @@ void Animation::compress(uint32_t p_page_size, uint32_t p_fps, float p_split_tol if (rollback || best_frame == FRAME_MAX) { // Commit the page if had to rollback or if no track was found - print_animc("\tCommiting page.."); + print_animc("\tCommiting page..."); // The end frame for the page depends entirely on whether its valid or // no more keys were found. diff --git a/scene/resources/bone_map.cpp b/scene/resources/bone_map.cpp index dfaf82f36a..5698e61004 100644 --- a/scene/resources/bone_map.cpp +++ b/scene/resources/bone_map.cpp @@ -93,7 +93,7 @@ void BoneMap::set_skeleton_bone_name(StringName p_profile_bone_name, const Strin } StringName BoneMap::find_profile_bone_name(StringName p_skeleton_bone_name) const { - StringName profile_bone_name = StringName(); + StringName profile_bone_name; HashMap<StringName, StringName>::ConstIterator E = bone_map.begin(); while (E) { if (E->value == p_skeleton_bone_name) { diff --git a/scene/resources/font.cpp b/scene/resources/font.cpp index 185b6f4bdc..84814d939b 100644 --- a/scene/resources/font.cpp +++ b/scene/resources/font.cpp @@ -1396,6 +1396,9 @@ Error FontFile::load_bitmap_font(const String &p_path) { case 1: /* info */ { ERR_FAIL_COND_V_MSG(block_size < 15, ERR_CANT_CREATE, RTR("Invalid BMFont info block size.")); base_size = f->get_16(); + if (base_size <= 0) { + base_size = 16; + } uint8_t flags = f->get_8(); if (flags & (1 << 3)) { st_flags.set_flag(TextServer::FONT_BOLD); @@ -1681,7 +1684,6 @@ Error FontFile::load_bitmap_font(const String &p_path) { if (type == "info") { if (keys.has("size")) { base_size = keys["size"].to_int(); - set_fixed_size(base_size); } if (keys.has("outline")) { outline = keys["outline"].to_int(); @@ -1730,6 +1732,7 @@ Error FontFile::load_bitmap_font(const String &p_path) { encoding = 2; } } + set_fixed_size(base_size); } else if (type == "common") { if (keys.has("lineHeight")) { height = keys["lineHeight"].to_int(); @@ -1788,7 +1791,10 @@ Error FontFile::load_bitmap_font(const String &p_path) { ERR_FAIL_V_MSG(ERR_CANT_CREATE, RTR("Unsupported BMFont texture format.")); } } else { - if ((ch[0] == 0) && (ch[1] == 0) && (ch[2] == 0) && (ch[3] == 0)) { // RGBA8 color, no outline + if ((ch[3] == 0) && (ch[0] == 4) && (ch[1] == 4) && (ch[2] == 4) && img->get_format() == Image::FORMAT_RGBA8) { // might be RGBA8 color, no outline (color part of the image should be sold white, but some apps designed for Godot 3 generate color fonts with this config) + outline = 0; + set_texture_image(0, Vector2i(base_size, 0), page, img); + } else if ((ch[0] == 0) && (ch[1] == 0) && (ch[2] == 0) && (ch[3] == 0)) { // RGBA8 color, no outline outline = 0; ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, RTR("Unsupported BMFont texture format.")); set_texture_image(0, Vector2i(base_size, 0), page, img); diff --git a/scene/resources/importer_mesh.cpp b/scene/resources/importer_mesh.cpp index b728c24e0d..cec5569345 100644 --- a/scene/resources/importer_mesh.cpp +++ b/scene/resources/importer_mesh.cpp @@ -255,7 +255,7 @@ void ImporterMesh::set_surface_material(int p_surface, const Ref<Material> &p_ma } #define VERTEX_SKIN_FUNC(bone_count, vert_idx, read_array, write_array, transform_array, bone_array, weight_array) \ - Vector3 transformed_vert = Vector3(); \ + Vector3 transformed_vert; \ for (unsigned int weight_idx = 0; weight_idx < bone_count; weight_idx++) { \ int bone_idx = bone_array[vert_idx * bone_count + weight_idx]; \ float w = weight_array[vert_idx * bone_count + weight_idx]; \ diff --git a/scene/resources/primitive_meshes.cpp b/scene/resources/primitive_meshes.cpp index eb83a37c7b..4c6d533c72 100644 --- a/scene/resources/primitive_meshes.cpp +++ b/scene/resources/primitive_meshes.cpp @@ -30,6 +30,7 @@ #include "primitive_meshes.h" +#include "core/config/project_settings.h" #include "core/core_string_names.h" #include "scene/resources/theme.h" #include "scene/theme/theme_db.h" @@ -37,6 +38,8 @@ #include "thirdparty/misc/clipper.hpp" #include "thirdparty/misc/polypartition.h" +#define PADDING_REF_SIZE 1024.0 + /** PrimitiveMesh */ @@ -94,6 +97,26 @@ void PrimitiveMesh::_update() const { } } + if (add_uv2) { + // _create_mesh_array should populate our UV2, this is a fallback in case it doesn't. + // As we don't know anything about the geometry we only pad the right and bottom edge + // of our texture. + Vector<Vector2> uv = arr[RS::ARRAY_TEX_UV]; + Vector<Vector2> uv2 = arr[RS::ARRAY_TEX_UV2]; + + if (uv.size() > 0 && uv2.size() == 0) { + Vector2 uv2_scale = get_uv2_scale(); + uv2.resize(uv.size()); + + Vector2 *uv2w = uv2.ptrw(); + for (int i = 0; i < uv.size(); i++) { + uv2w[i] = uv[i] * uv2_scale; + } + } + + arr[RS::ARRAY_TEX_UV2] = uv2; + } + array_len = pc; index_array_len = indices.size(); // in with the new @@ -160,7 +183,12 @@ TypedArray<Array> PrimitiveMesh::surface_get_blend_shape_arrays(int p_surface) c uint32_t PrimitiveMesh::surface_get_format(int p_idx) const { ERR_FAIL_INDEX_V(p_idx, 1, 0); - return RS::ARRAY_FORMAT_VERTEX | RS::ARRAY_FORMAT_NORMAL | RS::ARRAY_FORMAT_TANGENT | RS::ARRAY_FORMAT_TEX_UV | RS::ARRAY_FORMAT_INDEX; + uint32_t mesh_format = RS::ARRAY_FORMAT_VERTEX | RS::ARRAY_FORMAT_NORMAL | RS::ARRAY_FORMAT_TANGENT | RS::ARRAY_FORMAT_TEX_UV | RS::ARRAY_FORMAT_INDEX; + if (add_uv2) { + mesh_format |= RS::ARRAY_FORMAT_TEX_UV2; + } + + return mesh_format; } Mesh::PrimitiveType PrimitiveMesh::surface_get_primitive_type(int p_idx) const { @@ -219,9 +247,17 @@ void PrimitiveMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("set_flip_faces", "flip_faces"), &PrimitiveMesh::set_flip_faces); ClassDB::bind_method(D_METHOD("get_flip_faces"), &PrimitiveMesh::get_flip_faces); + ClassDB::bind_method(D_METHOD("set_add_uv2", "add_uv2"), &PrimitiveMesh::set_add_uv2); + ClassDB::bind_method(D_METHOD("get_add_uv2"), &PrimitiveMesh::get_add_uv2); + + ClassDB::bind_method(D_METHOD("set_uv2_padding", "uv2_padding"), &PrimitiveMesh::set_uv2_padding); + ClassDB::bind_method(D_METHOD("get_uv2_padding"), &PrimitiveMesh::get_uv2_padding); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,ShaderMaterial"), "set_material", "get_material"); ADD_PROPERTY(PropertyInfo(Variant::AABB, "custom_aabb", PROPERTY_HINT_NONE, "suffix:m"), "set_custom_aabb", "get_custom_aabb"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_faces"), "set_flip_faces", "get_flip_faces"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "add_uv2"), "set_add_uv2", "get_add_uv2"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "uv2_padding"), "set_uv2_padding", "get_uv2_padding"); GDVIRTUAL_BIND(_create_mesh_array); } @@ -233,7 +269,7 @@ void PrimitiveMesh::set_material(const Ref<Material> &p_material) { RenderingServer::get_singleton()->mesh_surface_set_material(mesh, 0, material.is_null() ? RID() : material->get_rid()); notify_property_list_changed(); emit_changed(); - }; + } } Ref<Material> PrimitiveMesh::get_material() const { @@ -263,6 +299,42 @@ bool PrimitiveMesh::get_flip_faces() const { return flip_faces; } +void PrimitiveMesh::set_add_uv2(bool p_enable) { + add_uv2 = p_enable; + _update_lightmap_size(); + _request_update(); +} + +void PrimitiveMesh::set_uv2_padding(float p_padding) { + uv2_padding = p_padding; + _update_lightmap_size(); + _request_update(); +} + +Vector2 PrimitiveMesh::get_uv2_scale(Vector2 p_margin_scale) const { + Vector2 uv2_scale; + Vector2 lightmap_size = get_lightmap_size_hint(); + + // Calculate it as a margin, if no lightmap size hint is given we assume "PADDING_REF_SIZE" as our texture size. + uv2_scale.x = p_margin_scale.x * uv2_padding / (lightmap_size.x == 0.0 ? PADDING_REF_SIZE : lightmap_size.x); + uv2_scale.y = p_margin_scale.y * uv2_padding / (lightmap_size.y == 0.0 ? PADDING_REF_SIZE : lightmap_size.y); + + // Inverse it to turn our margin into a scale + uv2_scale = Vector2(1.0, 1.0) - uv2_scale; + + return uv2_scale; +} + +float PrimitiveMesh::get_lightmap_texel_size() const { + float texel_size = GLOBAL_GET("rendering/lightmapping/primitive_meshes/texel_size"); + + if (texel_size <= 0.0) { + texel_size = 0.2; + } + + return texel_size; +} + PrimitiveMesh::PrimitiveMesh() { mesh = RenderingServer::get_singleton()->mesh_create(); } @@ -275,22 +347,52 @@ PrimitiveMesh::~PrimitiveMesh() { CapsuleMesh */ +void CapsuleMesh::_update_lightmap_size() { + if (get_add_uv2()) { + // size must have changed, update lightmap size hint + Size2i _lightmap_size_hint; + float texel_size = get_lightmap_texel_size(); + float padding = get_uv2_padding(); + + float radial_length = radius * Math_PI * 0.5; // circumference of 90 degree bend + float vertical_length = radial_length * 2 + (height - 2.0 * radius); // total vertical length + + _lightmap_size_hint.x = MAX(1.0, 4.0 * radial_length / texel_size) + padding; + _lightmap_size_hint.y = MAX(1.0, vertical_length / texel_size) + padding; + + set_lightmap_size_hint(_lightmap_size_hint); + } +} + void CapsuleMesh::_create_mesh_array(Array &p_arr) const { - create_mesh_array(p_arr, radius, height, radial_segments, rings); + bool _add_uv2 = get_add_uv2(); + float texel_size = get_lightmap_texel_size(); + float _uv2_padding = get_uv2_padding() * texel_size; + + create_mesh_array(p_arr, radius, height, radial_segments, rings, _add_uv2, _uv2_padding); } -void CapsuleMesh::create_mesh_array(Array &p_arr, const float radius, const float height, const int radial_segments, const int rings) { +void CapsuleMesh::create_mesh_array(Array &p_arr, const float radius, const float height, const int radial_segments, const int rings, bool p_add_uv2, const float p_uv2_padding) { int i, j, prevrow, thisrow, point; float x, y, z, u, v, w; float onethird = 1.0 / 3.0; float twothirds = 2.0 / 3.0; + // Only used if we calculate UV2 + float radial_width = 2.0 * radius * Math_PI; + float radial_h = radial_width / (radial_width + p_uv2_padding); + float radial_length = radius * Math_PI * 0.5; // circumference of 90 degree bend + float vertical_length = radial_length * 2 + (height - 2.0 * radius) + p_uv2_padding; // total vertical length + float radial_v = radial_length / vertical_length; // v size of top and bottom section + float height_v = (height - 2.0 * radius) / vertical_length; // v size of height section + // note, this has been aligned with our collision shape but I've left the descriptions as top/middle/bottom Vector<Vector3> points; Vector<Vector3> normals; Vector<float> tangents; Vector<Vector2> uvs; + Vector<Vector2> uv2s; Vector<int> indices; point = 0; @@ -322,6 +424,9 @@ void CapsuleMesh::create_mesh_array(Array &p_arr, const float radius, const floa normals.push_back(p.normalized()); ADD_TANGENT(-z, 0.0, -x, 1.0) uvs.push_back(Vector2(u, v * onethird)); + if (p_add_uv2) { + uv2s.push_back(Vector2(u * radial_h, v * radial_v)); + } point++; if (i > 0 && j > 0) { @@ -332,12 +437,12 @@ void CapsuleMesh::create_mesh_array(Array &p_arr, const float radius, const floa indices.push_back(prevrow + i); indices.push_back(thisrow + i); indices.push_back(thisrow + i - 1); - }; - }; + } + } prevrow = thisrow; thisrow = point; - }; + } /* cylinder */ thisrow = point; @@ -361,6 +466,9 @@ void CapsuleMesh::create_mesh_array(Array &p_arr, const float radius, const floa normals.push_back(Vector3(x, 0.0, -z)); ADD_TANGENT(-z, 0.0, -x, 1.0) uvs.push_back(Vector2(u, onethird + (v * onethird))); + if (p_add_uv2) { + uv2s.push_back(Vector2(u * radial_h, radial_v + (v * height_v))); + } point++; if (i > 0 && j > 0) { @@ -371,12 +479,12 @@ void CapsuleMesh::create_mesh_array(Array &p_arr, const float radius, const floa indices.push_back(prevrow + i); indices.push_back(thisrow + i); indices.push_back(thisrow + i - 1); - }; - }; + } + } prevrow = thisrow; thisrow = point; - }; + } /* bottom hemisphere */ thisrow = point; @@ -390,17 +498,20 @@ void CapsuleMesh::create_mesh_array(Array &p_arr, const float radius, const floa y = radius * cos(0.5 * Math_PI * v); for (i = 0; i <= radial_segments; i++) { - float u2 = i; - u2 /= radial_segments; + u = i; + u /= radial_segments; - x = -sin(u2 * Math_TAU); - z = cos(u2 * Math_TAU); + x = -sin(u * Math_TAU); + z = cos(u * Math_TAU); Vector3 p = Vector3(x * radius * w, y, -z * radius * w); points.push_back(p + Vector3(0.0, -0.5 * height + radius, 0.0)); normals.push_back(p.normalized()); ADD_TANGENT(-z, 0.0, -x, 1.0) - uvs.push_back(Vector2(u2, twothirds + ((v - 1.0) * onethird))); + uvs.push_back(Vector2(u, twothirds + ((v - 1.0) * onethird))); + if (p_add_uv2) { + uv2s.push_back(Vector2(u * radial_h, radial_v + height_v + ((v - 1.0) * radial_v))); + } point++; if (i > 0 && j > 0) { @@ -411,17 +522,20 @@ void CapsuleMesh::create_mesh_array(Array &p_arr, const float radius, const floa indices.push_back(prevrow + i); indices.push_back(thisrow + i); indices.push_back(thisrow + i - 1); - }; - }; + } + } prevrow = thisrow; thisrow = point; - }; + } p_arr[RS::ARRAY_VERTEX] = points; p_arr[RS::ARRAY_NORMAL] = normals; p_arr[RS::ARRAY_TANGENT] = tangents; p_arr[RS::ARRAY_TEX_UV] = uvs; + if (p_add_uv2) { + p_arr[RS::ARRAY_TEX_UV2] = uv2s; + } p_arr[RS::ARRAY_INDEX] = indices; } @@ -450,6 +564,7 @@ void CapsuleMesh::set_radius(const float p_radius) { if (radius > height * 0.5) { height = radius * 2.0; } + _update_lightmap_size(); _request_update(); } @@ -462,6 +577,7 @@ void CapsuleMesh::set_height(const float p_height) { if (radius > height * 0.5) { radius = height * 0.5; } + _update_lightmap_size(); _request_update(); } @@ -493,16 +609,53 @@ CapsuleMesh::CapsuleMesh() {} BoxMesh */ +void BoxMesh::_update_lightmap_size() { + if (get_add_uv2()) { + // size must have changed, update lightmap size hint + Size2i _lightmap_size_hint; + float texel_size = get_lightmap_texel_size(); + float padding = get_uv2_padding(); + + float width = (size.x + size.z) / texel_size; + float length = (size.y + size.y + MAX(size.x, size.z)) / texel_size; + + _lightmap_size_hint.x = MAX(1.0, width) + 2.0 * padding; + _lightmap_size_hint.y = MAX(1.0, length) + 3.0 * padding; + + set_lightmap_size_hint(_lightmap_size_hint); + } +} + void BoxMesh::_create_mesh_array(Array &p_arr) const { - BoxMesh::create_mesh_array(p_arr, size, subdivide_w, subdivide_h, subdivide_d); + // Note about padding, with our box each face of the box faces a different direction so we want a seam + // around every face. We thus add our padding to the right and bottom of each face. + // With 3 faces along the width and 2 along the height of the texture we need to adjust our scale + // accordingly. + bool _add_uv2 = get_add_uv2(); + float texel_size = get_lightmap_texel_size(); + float _uv2_padding = get_uv2_padding() * texel_size; + + BoxMesh::create_mesh_array(p_arr, size, subdivide_w, subdivide_h, subdivide_d, _add_uv2, _uv2_padding); } -void BoxMesh::create_mesh_array(Array &p_arr, Vector3 size, int subdivide_w, int subdivide_h, int subdivide_d) { +void BoxMesh::create_mesh_array(Array &p_arr, Vector3 size, int subdivide_w, int subdivide_h, int subdivide_d, bool p_add_uv2, const float p_uv2_padding) { int i, j, prevrow, thisrow, point; float x, y, z; float onethird = 1.0 / 3.0; float twothirds = 2.0 / 3.0; + // Only used if we calculate UV2 + // TODO this could be improved by changing the order depending on which side is the longest (basically the below works best if size.y is the longest) + float total_h = (size.x + size.z + (2.0 * p_uv2_padding)); + float padding_h = p_uv2_padding / total_h; + float width_h = size.x / total_h; + float depth_h = size.z / total_h; + float total_v = (size.y + size.y + MAX(size.x, size.z) + (3.0 * p_uv2_padding)); + float padding_v = p_uv2_padding / total_v; + float width_v = size.x / total_v; + float height_v = size.y / total_v; + float depth_v = size.z / total_v; + Vector3 start_pos = size * -0.5; // set our bounding box @@ -511,6 +664,7 @@ void BoxMesh::create_mesh_array(Array &p_arr, Vector3 size, int subdivide_w, int Vector<Vector3> normals; Vector<float> tangents; Vector<Vector2> uvs; + Vector<Vector2> uv2s; Vector<int> indices; point = 0; @@ -525,18 +679,24 @@ void BoxMesh::create_mesh_array(Array &p_arr, Vector3 size, int subdivide_w, int thisrow = point; prevrow = 0; for (j = 0; j <= subdivide_h + 1; j++) { + float v = j; + float v2 = v / (subdivide_w + 1.0); + v /= (2.0 * (subdivide_h + 1.0)); + x = start_pos.x; for (i = 0; i <= subdivide_w + 1; i++) { float u = i; - float v = j; + float u2 = u / (subdivide_w + 1.0); u /= (3.0 * (subdivide_w + 1.0)); - v /= (2.0 * (subdivide_h + 1.0)); // front points.push_back(Vector3(x, -y, -start_pos.z)); // double negative on the Z! normals.push_back(Vector3(0.0, 0.0, 1.0)); ADD_TANGENT(1.0, 0.0, 0.0, 1.0); uvs.push_back(Vector2(u, v)); + if (p_add_uv2) { + uv2s.push_back(Vector2(u2 * width_h, v2 * height_v)); + } point++; // back @@ -544,6 +704,9 @@ void BoxMesh::create_mesh_array(Array &p_arr, Vector3 size, int subdivide_w, int normals.push_back(Vector3(0.0, 0.0, -1.0)); ADD_TANGENT(-1.0, 0.0, 0.0, 1.0); uvs.push_back(Vector2(twothirds + u, v)); + if (p_add_uv2) { + uv2s.push_back(Vector2(u2 * width_h, height_v + padding_v + (v2 * height_v))); + } point++; if (i > 0 && j > 0) { @@ -564,33 +727,39 @@ void BoxMesh::create_mesh_array(Array &p_arr, Vector3 size, int subdivide_w, int indices.push_back(prevrow + i2 + 1); indices.push_back(thisrow + i2 + 1); indices.push_back(thisrow + i2 - 1); - }; + } x += size.x / (subdivide_w + 1.0); - }; + } y += size.y / (subdivide_h + 1.0); prevrow = thisrow; thisrow = point; - }; + } // left + right y = start_pos.y; thisrow = point; prevrow = 0; for (j = 0; j <= (subdivide_h + 1); j++) { + float v = j; + float v2 = v / (subdivide_h + 1.0); + v /= (2.0 * (subdivide_h + 1.0)); + z = start_pos.z; for (i = 0; i <= (subdivide_d + 1); i++) { float u = i; - float v = j; + float u2 = u / (subdivide_d + 1.0); u /= (3.0 * (subdivide_d + 1.0)); - v /= (2.0 * (subdivide_h + 1.0)); // right points.push_back(Vector3(-start_pos.x, -y, -z)); normals.push_back(Vector3(1.0, 0.0, 0.0)); ADD_TANGENT(0.0, 0.0, -1.0, 1.0); uvs.push_back(Vector2(onethird + u, v)); + if (p_add_uv2) { + uv2s.push_back(Vector2(width_h + padding_h + (u2 * depth_h), v2 * height_v)); + } point++; // left @@ -598,6 +767,9 @@ void BoxMesh::create_mesh_array(Array &p_arr, Vector3 size, int subdivide_w, int normals.push_back(Vector3(-1.0, 0.0, 0.0)); ADD_TANGENT(0.0, 0.0, 1.0, 1.0); uvs.push_back(Vector2(u, 0.5 + v)); + if (p_add_uv2) { + uv2s.push_back(Vector2(width_h + padding_h + (u2 * depth_h), height_v + padding_v + (v2 * height_v))); + } point++; if (i > 0 && j > 0) { @@ -618,33 +790,39 @@ void BoxMesh::create_mesh_array(Array &p_arr, Vector3 size, int subdivide_w, int indices.push_back(prevrow + i2 + 1); indices.push_back(thisrow + i2 + 1); indices.push_back(thisrow + i2 - 1); - }; + } z += size.z / (subdivide_d + 1.0); - }; + } y += size.y / (subdivide_h + 1.0); prevrow = thisrow; thisrow = point; - }; + } // top + bottom z = start_pos.z; thisrow = point; prevrow = 0; for (j = 0; j <= (subdivide_d + 1); j++) { + float v = j; + float v2 = v / (subdivide_d + 1.0); + v /= (2.0 * (subdivide_d + 1.0)); + x = start_pos.x; for (i = 0; i <= (subdivide_w + 1); i++) { float u = i; - float v = j; + float u2 = u / (subdivide_w + 1.0); u /= (3.0 * (subdivide_w + 1.0)); - v /= (2.0 * (subdivide_d + 1.0)); // top points.push_back(Vector3(-x, -start_pos.y, -z)); normals.push_back(Vector3(0.0, 1.0, 0.0)); ADD_TANGENT(-1.0, 0.0, 0.0, 1.0); uvs.push_back(Vector2(onethird + u, 0.5 + v)); + if (p_add_uv2) { + uv2s.push_back(Vector2(u2 * width_h, ((height_v + padding_v) * 2.0) + (v2 * depth_v))); + } point++; // bottom @@ -652,6 +830,9 @@ void BoxMesh::create_mesh_array(Array &p_arr, Vector3 size, int subdivide_w, int normals.push_back(Vector3(0.0, -1.0, 0.0)); ADD_TANGENT(1.0, 0.0, 0.0, 1.0); uvs.push_back(Vector2(twothirds + u, 0.5 + v)); + if (p_add_uv2) { + uv2s.push_back(Vector2(width_h + padding_h + (u2 * depth_h), ((height_v + padding_v) * 2.0) + (v2 * width_v))); + } point++; if (i > 0 && j > 0) { @@ -672,20 +853,23 @@ void BoxMesh::create_mesh_array(Array &p_arr, Vector3 size, int subdivide_w, int indices.push_back(prevrow + i2 + 1); indices.push_back(thisrow + i2 + 1); indices.push_back(thisrow + i2 - 1); - }; + } x += size.x / (subdivide_w + 1.0); - }; + } z += size.z / (subdivide_d + 1.0); prevrow = thisrow; thisrow = point; - }; + } p_arr[RS::ARRAY_VERTEX] = points; p_arr[RS::ARRAY_NORMAL] = normals; p_arr[RS::ARRAY_TANGENT] = tangents; p_arr[RS::ARRAY_TEX_UV] = uvs; + if (p_add_uv2) { + p_arr[RS::ARRAY_TEX_UV2] = uv2s; + } p_arr[RS::ARRAY_INDEX] = indices; } @@ -708,6 +892,7 @@ void BoxMesh::_bind_methods() { void BoxMesh::set_size(const Vector3 &p_size) { size = p_size; + _update_lightmap_size(); _request_update(); } @@ -748,18 +933,58 @@ BoxMesh::BoxMesh() {} CylinderMesh */ +void CylinderMesh::_update_lightmap_size() { + if (get_add_uv2()) { + // size must have changed, update lightmap size hint + Size2i _lightmap_size_hint; + float texel_size = get_lightmap_texel_size(); + float padding = get_uv2_padding(); + + float top_circumference = top_radius * Math_PI * 2.0; + float bottom_circumference = bottom_radius * Math_PI * 2.0; + + float _width = MAX(top_circumference, bottom_circumference) / texel_size + padding; + _width = MAX(_width, (((top_radius + bottom_radius) / texel_size) + padding) * 2.0); // this is extremely unlikely to be larger, will only happen if padding is larger then our diameter. + _lightmap_size_hint.x = MAX(1.0, _width); + + float _height = ((height + (MAX(top_radius, bottom_radius) * 2.0)) / texel_size) + (2.0 * padding); + + _lightmap_size_hint.y = MAX(1.0, _height); + + set_lightmap_size_hint(_lightmap_size_hint); + } +} + void CylinderMesh::_create_mesh_array(Array &p_arr) const { - create_mesh_array(p_arr, top_radius, bottom_radius, height, radial_segments, rings, cap_top, cap_bottom); + bool _add_uv2 = get_add_uv2(); + float texel_size = get_lightmap_texel_size(); + float _uv2_padding = get_uv2_padding() * texel_size; + + create_mesh_array(p_arr, top_radius, bottom_radius, height, radial_segments, rings, cap_top, cap_bottom, _add_uv2, _uv2_padding); } -void CylinderMesh::create_mesh_array(Array &p_arr, float top_radius, float bottom_radius, float height, int radial_segments, int rings, bool cap_top, bool cap_bottom) { +void CylinderMesh::create_mesh_array(Array &p_arr, float top_radius, float bottom_radius, float height, int radial_segments, int rings, bool cap_top, bool cap_bottom, bool p_add_uv2, const float p_uv2_padding) { int i, j, prevrow, thisrow, point; - float x, y, z, u, v, radius; + float x, y, z, u, v, radius, radius_h; + + // Only used if we calculate UV2 + float top_circumference = top_radius * Math_PI * 2.0; + float bottom_circumference = bottom_radius * Math_PI * 2.0; + float vertical_length = height + MAX(2.0 * top_radius, 2.0 * bottom_radius) + (2.0 * p_uv2_padding); + float height_v = height / vertical_length; + float padding_v = p_uv2_padding / vertical_length; + + float horizonal_length = MAX(MAX(2.0 * (top_radius + bottom_radius + p_uv2_padding), top_circumference + p_uv2_padding), bottom_circumference + p_uv2_padding); + float center_h = 0.5 * (horizonal_length - p_uv2_padding) / horizonal_length; + float top_h = top_circumference / horizonal_length; + float bottom_h = bottom_circumference / horizonal_length; + float padding_h = p_uv2_padding / horizonal_length; Vector<Vector3> points; Vector<Vector3> normals; Vector<float> tangents; Vector<Vector2> uvs; + Vector<Vector2> uv2s; Vector<int> indices; point = 0; @@ -777,6 +1002,7 @@ void CylinderMesh::create_mesh_array(Array &p_arr, float top_radius, float botto v /= (rings + 1); radius = top_radius + ((bottom_radius - top_radius) * v); + radius_h = top_h + ((bottom_h - top_h) * v); y = height * v; y = (height * 0.5) - y; @@ -793,6 +1019,9 @@ void CylinderMesh::create_mesh_array(Array &p_arr, float top_radius, float botto normals.push_back(Vector3(x, side_normal_y, z).normalized()); ADD_TANGENT(z, 0.0, -x, 1.0) uvs.push_back(Vector2(u, v * 0.5)); + if (p_add_uv2) { + uv2s.push_back(Vector2(center_h + (u - 0.5) * radius_h, v * height_v)); + } point++; if (i > 0 && j > 0) { @@ -803,14 +1032,20 @@ void CylinderMesh::create_mesh_array(Array &p_arr, float top_radius, float botto indices.push_back(prevrow + i); indices.push_back(thisrow + i); indices.push_back(thisrow + i - 1); - }; - }; + } + } prevrow = thisrow; thisrow = point; - }; + } - // add top + // Adjust for bottom section, only used if we calculate UV2s. + top_h = top_radius / horizonal_length; + float top_v = top_radius / vertical_length; + bottom_h = bottom_radius / horizonal_length; + float bottom_v = bottom_radius / vertical_length; + + // Add top. if (cap_top && top_radius > 0.0) { y = height * 0.5; @@ -819,6 +1054,9 @@ void CylinderMesh::create_mesh_array(Array &p_arr, float top_radius, float botto normals.push_back(Vector3(0.0, 1.0, 0.0)); ADD_TANGENT(1.0, 0.0, 0.0, 1.0) uvs.push_back(Vector2(0.25, 0.75)); + if (p_add_uv2) { + uv2s.push_back(Vector2(top_h, height_v + padding_v + MAX(top_v, bottom_v))); + } point++; for (i = 0; i <= radial_segments; i++) { @@ -836,17 +1074,20 @@ void CylinderMesh::create_mesh_array(Array &p_arr, float top_radius, float botto normals.push_back(Vector3(0.0, 1.0, 0.0)); ADD_TANGENT(1.0, 0.0, 0.0, 1.0) uvs.push_back(Vector2(u, v)); + if (p_add_uv2) { + uv2s.push_back(Vector2(top_h + (x * top_h), height_v + padding_v + MAX(top_v, bottom_v) + (z * top_v))); + } point++; if (i > 0) { indices.push_back(thisrow); indices.push_back(point - 1); indices.push_back(point - 2); - }; - }; - }; + } + } + } - // add bottom + // Add bottom. if (cap_bottom && bottom_radius > 0.0) { y = height * -0.5; @@ -855,6 +1096,9 @@ void CylinderMesh::create_mesh_array(Array &p_arr, float top_radius, float botto normals.push_back(Vector3(0.0, -1.0, 0.0)); ADD_TANGENT(1.0, 0.0, 0.0, 1.0) uvs.push_back(Vector2(0.75, 0.75)); + if (p_add_uv2) { + uv2s.push_back(Vector2(top_h + top_h + padding_h + bottom_h, height_v + padding_v + MAX(top_v, bottom_v))); + } point++; for (i = 0; i <= radial_segments; i++) { @@ -872,20 +1116,26 @@ void CylinderMesh::create_mesh_array(Array &p_arr, float top_radius, float botto normals.push_back(Vector3(0.0, -1.0, 0.0)); ADD_TANGENT(1.0, 0.0, 0.0, 1.0) uvs.push_back(Vector2(u, v)); + if (p_add_uv2) { + uv2s.push_back(Vector2(top_h + top_h + padding_h + bottom_h + (x * bottom_h), height_v + padding_v + MAX(top_v, bottom_v) - (z * bottom_v))); + } point++; if (i > 0) { indices.push_back(thisrow); indices.push_back(point - 2); indices.push_back(point - 1); - }; - }; - }; + } + } + } p_arr[RS::ARRAY_VERTEX] = points; p_arr[RS::ARRAY_NORMAL] = normals; p_arr[RS::ARRAY_TANGENT] = tangents; p_arr[RS::ARRAY_TEX_UV] = uvs; + if (p_add_uv2) { + p_arr[RS::ARRAY_TEX_UV2] = uv2s; + } p_arr[RS::ARRAY_INDEX] = indices; } @@ -919,6 +1169,7 @@ void CylinderMesh::_bind_methods() { void CylinderMesh::set_top_radius(const float p_radius) { top_radius = p_radius; + _update_lightmap_size(); _request_update(); } @@ -928,6 +1179,7 @@ float CylinderMesh::get_top_radius() const { void CylinderMesh::set_bottom_radius(const float p_radius) { bottom_radius = p_radius; + _update_lightmap_size(); _request_update(); } @@ -937,6 +1189,7 @@ float CylinderMesh::get_bottom_radius() const { void CylinderMesh::set_height(const float p_height) { height = p_height; + _update_lightmap_size(); _request_update(); } @@ -986,10 +1239,26 @@ CylinderMesh::CylinderMesh() {} PlaneMesh */ +void PlaneMesh::_update_lightmap_size() { + if (get_add_uv2()) { + // size must have changed, update lightmap size hint + Size2i _lightmap_size_hint; + float texel_size = get_lightmap_texel_size(); + float padding = get_uv2_padding(); + + _lightmap_size_hint.x = MAX(1.0, (size.x / texel_size) + padding); + _lightmap_size_hint.y = MAX(1.0, (size.y / texel_size) + padding); + + set_lightmap_size_hint(_lightmap_size_hint); + } +} + void PlaneMesh::_create_mesh_array(Array &p_arr) const { int i, j, prevrow, thisrow, point; float x, z; + // Plane mesh can use default UV2 calculation as implemented in Primitive Mesh + Size2 start_pos = size * -0.5; Vector3 normal = Vector3(0.0, 1.0, 0.0); @@ -1043,15 +1312,15 @@ void PlaneMesh::_create_mesh_array(Array &p_arr) const { indices.push_back(prevrow + i); indices.push_back(thisrow + i); indices.push_back(thisrow + i - 1); - }; + } x += size.x / (subdivide_w + 1.0); - }; + } z += size.y / (subdivide_d + 1.0); prevrow = thisrow; thisrow = point; - }; + } p_arr[RS::ARRAY_VERTEX] = points; p_arr[RS::ARRAY_NORMAL] = normals; @@ -1088,6 +1357,7 @@ void PlaneMesh::_bind_methods() { void PlaneMesh::set_size(const Size2 &p_size) { size = p_size; + _update_lightmap_size(); _request_update(); } @@ -1137,12 +1407,49 @@ PlaneMesh::PlaneMesh() {} PrismMesh */ +void PrismMesh::_update_lightmap_size() { + if (get_add_uv2()) { + // size must have changed, update lightmap size hint + Size2i _lightmap_size_hint; + float texel_size = get_lightmap_texel_size(); + float padding = get_uv2_padding(); + + // left_to_right does not effect the surface area of the prism so we ignore that. + // TODO we could combine the two triangles and save some space but we need to re-align the uv1 and adjust the tangent. + + float width = (size.x + size.z) / texel_size; + float length = (size.y + size.y + size.z) / texel_size; + + _lightmap_size_hint.x = MAX(1.0, width) + 2.0 * padding; + _lightmap_size_hint.y = MAX(1.0, length) + 3.0 * padding; + + set_lightmap_size_hint(_lightmap_size_hint); + } +} + void PrismMesh::_create_mesh_array(Array &p_arr) const { int i, j, prevrow, thisrow, point; float x, y, z; float onethird = 1.0 / 3.0; float twothirds = 2.0 / 3.0; + // Only used if we calculate UV2 + bool _add_uv2 = get_add_uv2(); + float texel_size = get_lightmap_texel_size(); + float _uv2_padding = get_uv2_padding() * texel_size; + + float horizontal_total = size.x + size.z + 2.0 * _uv2_padding; + float width_h = size.x / horizontal_total; + float depth_h = size.z / horizontal_total; + float padding_h = _uv2_padding / horizontal_total; + + float vertical_total = (size.y + size.y + size.z) + (3.0 * _uv2_padding); + float height_v = size.y / vertical_total; + float depth_v = size.z / vertical_total; + float padding_v = _uv2_padding / vertical_total; + + // and start building + Vector3 start_pos = size * -0.5; // set our bounding box @@ -1151,6 +1458,7 @@ void PrismMesh::_create_mesh_array(Array &p_arr) const { Vector<Vector3> normals; Vector<float> tangents; Vector<Vector2> uvs; + Vector<Vector2> uv2s; Vector<int> indices; point = 0; @@ -1171,12 +1479,15 @@ void PrismMesh::_create_mesh_array(Array &p_arr) const { float offset_front = (1.0 - scale) * onethird * left_to_right; float offset_back = (1.0 - scale) * onethird * (1.0 - left_to_right); + float v = j; + float v2 = j / (subdivide_h + 1.0); + v /= (2.0 * (subdivide_h + 1.0)); + x = 0.0; for (i = 0; i <= (subdivide_w + 1); i++) { float u = i; - float v = j; + float u2 = i / (subdivide_w + 1.0); u /= (3.0 * (subdivide_w + 1.0)); - v /= (2.0 * (subdivide_h + 1.0)); u *= scale; @@ -1185,6 +1496,9 @@ void PrismMesh::_create_mesh_array(Array &p_arr) const { normals.push_back(Vector3(0.0, 0.0, 1.0)); ADD_TANGENT(1.0, 0.0, 0.0, 1.0); uvs.push_back(Vector2(offset_front + u, v)); + if (_add_uv2) { + uv2s.push_back(Vector2(u2 * scale * width_h, v2 * height_v)); + } point++; /* back */ @@ -1192,6 +1506,9 @@ void PrismMesh::_create_mesh_array(Array &p_arr) const { normals.push_back(Vector3(0.0, 0.0, -1.0)); ADD_TANGENT(-1.0, 0.0, 0.0, 1.0); uvs.push_back(Vector2(twothirds + offset_back + u, v)); + if (_add_uv2) { + uv2s.push_back(Vector2(u2 * scale * width_h, height_v + padding_v + v2 * height_v)); + } point++; if (i > 0 && j == 1) { @@ -1224,15 +1541,15 @@ void PrismMesh::_create_mesh_array(Array &p_arr) const { indices.push_back(prevrow + i2 + 1); indices.push_back(thisrow + i2 + 1); indices.push_back(thisrow + i2 - 1); - }; + } x += scale * size.x / (subdivide_w + 1.0); - }; + } y += size.y / (subdivide_h + 1.0); prevrow = thisrow; thisrow = point; - }; + } /* left + right */ Vector3 normal_left, normal_right; @@ -1246,6 +1563,10 @@ void PrismMesh::_create_mesh_array(Array &p_arr) const { thisrow = point; prevrow = 0; for (j = 0; j <= (subdivide_h + 1); j++) { + float v = j; + float v2 = j / (subdivide_h + 1.0); + v /= (2.0 * (subdivide_h + 1.0)); + float left, right; float scale = (y - start_pos.y) / size.y; @@ -1255,15 +1576,17 @@ void PrismMesh::_create_mesh_array(Array &p_arr) const { z = start_pos.z; for (i = 0; i <= (subdivide_d + 1); i++) { float u = i; - float v = j; + float u2 = u / (subdivide_d + 1.0); u /= (3.0 * (subdivide_d + 1.0)); - v /= (2.0 * (subdivide_h + 1.0)); /* right */ points.push_back(Vector3(right, -y, -z)); normals.push_back(normal_right); ADD_TANGENT(0.0, 0.0, -1.0, 1.0); uvs.push_back(Vector2(onethird + u, v)); + if (_add_uv2) { + uv2s.push_back(Vector2(width_h + padding_h + u2 * depth_h, v2 * height_v)); + } point++; /* left */ @@ -1271,6 +1594,9 @@ void PrismMesh::_create_mesh_array(Array &p_arr) const { normals.push_back(normal_left); ADD_TANGENT(0.0, 0.0, 1.0, 1.0); uvs.push_back(Vector2(u, 0.5 + v)); + if (_add_uv2) { + uv2s.push_back(Vector2(width_h + padding_h + u2 * depth_h, height_v + padding_v + v2 * height_v)); + } point++; if (i > 0 && j > 0) { @@ -1291,33 +1617,39 @@ void PrismMesh::_create_mesh_array(Array &p_arr) const { indices.push_back(prevrow + i2 + 1); indices.push_back(thisrow + i2 + 1); indices.push_back(thisrow + i2 - 1); - }; + } z += size.z / (subdivide_d + 1.0); - }; + } y += size.y / (subdivide_h + 1.0); prevrow = thisrow; thisrow = point; - }; + } /* bottom */ z = start_pos.z; thisrow = point; prevrow = 0; for (j = 0; j <= (subdivide_d + 1); j++) { + float v = j; + float v2 = v / (subdivide_d + 1.0); + v /= (2.0 * (subdivide_d + 1.0)); + x = start_pos.x; for (i = 0; i <= (subdivide_w + 1); i++) { float u = i; - float v = j; + float u2 = u / (subdivide_w + 1.0); u /= (3.0 * (subdivide_w + 1.0)); - v /= (2.0 * (subdivide_d + 1.0)); /* bottom */ points.push_back(Vector3(x, start_pos.y, -z)); normals.push_back(Vector3(0.0, -1.0, 0.0)); ADD_TANGENT(1.0, 0.0, 0.0, 1.0); uvs.push_back(Vector2(twothirds + u, 0.5 + v)); + if (_add_uv2) { + uv2s.push_back(Vector2(u2 * width_h, 2.0 * (height_v + padding_v) + v2 * depth_v)); + } point++; if (i > 0 && j > 0) { @@ -1328,20 +1660,23 @@ void PrismMesh::_create_mesh_array(Array &p_arr) const { indices.push_back(prevrow + i); indices.push_back(thisrow + i); indices.push_back(thisrow + i - 1); - }; + } x += size.x / (subdivide_w + 1.0); - }; + } z += size.z / (subdivide_d + 1.0); prevrow = thisrow; thisrow = point; - }; + } p_arr[RS::ARRAY_VERTEX] = points; p_arr[RS::ARRAY_NORMAL] = normals; p_arr[RS::ARRAY_TANGENT] = tangents; p_arr[RS::ARRAY_TEX_UV] = uvs; + if (_add_uv2) { + p_arr[RS::ARRAY_TEX_UV2] = uv2s; + } p_arr[RS::ARRAY_INDEX] = indices; } @@ -1377,6 +1712,7 @@ float PrismMesh::get_left_to_right() const { void PrismMesh::set_size(const Vector3 &p_size) { size = p_size; + _update_lightmap_size(); _request_update(); } @@ -1417,22 +1753,50 @@ PrismMesh::PrismMesh() {} SphereMesh */ +void SphereMesh::_update_lightmap_size() { + if (get_add_uv2()) { + // size must have changed, update lightmap size hint + Size2i _lightmap_size_hint; + float texel_size = get_lightmap_texel_size(); + float padding = get_uv2_padding(); + + float _width = radius * Math_TAU; + _lightmap_size_hint.x = MAX(1.0, (_width / texel_size) + padding); + float _height = (is_hemisphere ? 1.0 : 0.5) * height * Math_PI; // note, with hemisphere height is our radius, while with a full sphere it is the diameter.. + _lightmap_size_hint.y = MAX(1.0, (_height / texel_size) + padding); + + set_lightmap_size_hint(_lightmap_size_hint); + } +} + void SphereMesh::_create_mesh_array(Array &p_arr) const { - create_mesh_array(p_arr, radius, height, radial_segments, rings, is_hemisphere); + bool _add_uv2 = get_add_uv2(); + float texel_size = get_lightmap_texel_size(); + float _uv2_padding = get_uv2_padding() * texel_size; + + create_mesh_array(p_arr, radius, height, radial_segments, rings, is_hemisphere, _add_uv2, _uv2_padding); } -void SphereMesh::create_mesh_array(Array &p_arr, float radius, float height, int radial_segments, int rings, bool is_hemisphere) { +void SphereMesh::create_mesh_array(Array &p_arr, float radius, float height, int radial_segments, int rings, bool is_hemisphere, bool p_add_uv2, const float p_uv2_padding) { int i, j, prevrow, thisrow, point; float x, y, z; float scale = height * (is_hemisphere ? 1.0 : 0.5); + // Only used if we calculate UV2 + float circumference = radius * Math_TAU; + float horizontal_length = circumference + p_uv2_padding; + float center_h = 0.5 * circumference / horizontal_length; + + float height_v = scale * Math_PI / ((scale * Math_PI) + p_uv2_padding); + // set our bounding box Vector<Vector3> points; Vector<Vector3> normals; Vector<float> tangents; Vector<Vector2> uvs; + Vector<Vector2> uv2s; Vector<int> indices; point = 0; @@ -1467,9 +1831,13 @@ void SphereMesh::create_mesh_array(Array &p_arr, float radius, float height, int points.push_back(p); Vector3 normal = Vector3(x * w * scale, radius * (y / scale), z * w * scale); normals.push_back(normal.normalized()); - }; + } ADD_TANGENT(z, 0.0, -x, 1.0) uvs.push_back(Vector2(u, v)); + if (p_add_uv2) { + float w_h = w * 2.0 * center_h; + uv2s.push_back(Vector2(center_h + ((u - 0.5) * w_h), v * height_v)); + } point++; if (i > 0 && j > 0) { @@ -1480,17 +1848,20 @@ void SphereMesh::create_mesh_array(Array &p_arr, float radius, float height, int indices.push_back(prevrow + i); indices.push_back(thisrow + i); indices.push_back(thisrow + i - 1); - }; - }; + } + } prevrow = thisrow; thisrow = point; - }; + } p_arr[RS::ARRAY_VERTEX] = points; p_arr[RS::ARRAY_NORMAL] = normals; p_arr[RS::ARRAY_TANGENT] = tangents; p_arr[RS::ARRAY_TEX_UV] = uvs; + if (p_add_uv2) { + p_arr[RS::ARRAY_TEX_UV2] = uv2s; + } p_arr[RS::ARRAY_INDEX] = indices; } @@ -1517,6 +1888,7 @@ void SphereMesh::_bind_methods() { void SphereMesh::set_radius(const float p_radius) { radius = p_radius; + _update_lightmap_size(); _request_update(); } @@ -1526,6 +1898,7 @@ float SphereMesh::get_radius() const { void SphereMesh::set_height(const float p_height) { height = p_height; + _update_lightmap_size(); _request_update(); } @@ -1553,6 +1926,7 @@ int SphereMesh::get_rings() const { void SphereMesh::set_is_hemisphere(const bool p_is_hemisphere) { is_hemisphere = p_is_hemisphere; + _update_lightmap_size(); _request_update(); } @@ -1566,6 +1940,31 @@ SphereMesh::SphereMesh() {} TorusMesh */ +void TorusMesh::_update_lightmap_size() { + if (get_add_uv2()) { + // size must have changed, update lightmap size hint + Size2i _lightmap_size_hint; + float texel_size = get_lightmap_texel_size(); + float padding = get_uv2_padding(); + + float min_radius = inner_radius; + float max_radius = outer_radius; + + if (min_radius > max_radius) { + SWAP(min_radius, max_radius); + } + + float radius = (max_radius - min_radius) * 0.5; + + float _width = max_radius * Math_TAU; + _lightmap_size_hint.x = MAX(1.0, (_width / texel_size) + padding); + float _height = radius * Math_TAU; + _lightmap_size_hint.y = MAX(1.0, (_height / texel_size) + padding); + + set_lightmap_size_hint(_lightmap_size_hint); + } +} + void TorusMesh::_create_mesh_array(Array &p_arr) const { // set our bounding box @@ -1573,6 +1972,7 @@ void TorusMesh::_create_mesh_array(Array &p_arr) const { Vector<Vector3> normals; Vector<float> tangents; Vector<Vector2> uvs; + Vector<Vector2> uv2s; Vector<int> indices; #define ADD_TANGENT(m_x, m_y, m_z, m_d) \ @@ -1592,6 +1992,17 @@ void TorusMesh::_create_mesh_array(Array &p_arr) const { float radius = (max_radius - min_radius) * 0.5; + // Only used if we calculate UV2 + bool _add_uv2 = get_add_uv2(); + float texel_size = get_lightmap_texel_size(); + float _uv2_padding = get_uv2_padding() * texel_size; + + float horizontal_total = max_radius * Math_TAU + _uv2_padding; + float max_h = max_radius * Math_TAU / horizontal_total; + float delta_h = (max_radius - min_radius) * Math_TAU / horizontal_total; + + float height_v = radius * Math_TAU / (radius * Math_TAU + _uv2_padding); + for (int i = 0; i <= rings; i++) { int prevrow = (i - 1) * (ring_segments + 1); int thisrow = i * (ring_segments + 1); @@ -1607,10 +2018,17 @@ void TorusMesh::_create_mesh_array(Array &p_arr) const { Vector2 normalj = Vector2(-Math::cos(angj), Math::sin(angj)); Vector2 normalk = normalj * radius + Vector2(min_radius + radius, 0); + float offset_h = 0.5 * (1.0 - normalj.x) * delta_h; + float adj_h = max_h - offset_h; + offset_h *= 0.5; + points.push_back(Vector3(normali.x * normalk.x, normalk.y, normali.y * normalk.x)); normals.push_back(Vector3(normali.x * normalj.x, normalj.y, normali.y * normalj.x)); ADD_TANGENT(-Math::cos(angi), 0.0, Math::sin(angi), 1.0); uvs.push_back(Vector2(inci, incj)); + if (_add_uv2) { + uv2s.push_back(Vector2(offset_h + inci * adj_h, incj * height_v)); + } if (i > 0 && j > 0) { indices.push_back(thisrow + j - 1); @@ -1628,6 +2046,9 @@ void TorusMesh::_create_mesh_array(Array &p_arr) const { p_arr[RS::ARRAY_NORMAL] = normals; p_arr[RS::ARRAY_TANGENT] = tangents; p_arr[RS::ARRAY_TEX_UV] = uvs; + if (_add_uv2) { + p_arr[RS::ARRAY_TEX_UV2] = uv2s; + } p_arr[RS::ARRAY_INDEX] = indices; } @@ -1785,6 +2206,8 @@ Transform3D TubeTrailMesh::get_builtin_bind_pose(int p_index) const { } void TubeTrailMesh::_create_mesh_array(Array &p_arr) const { + // Seeing use case for TubeTrailMesh, no need to do anything more then default UV2 calculation + PackedVector3Array points; PackedVector3Array normals; PackedFloat32Array tangents; @@ -1920,9 +2343,9 @@ void TubeTrailMesh::_create_mesh_array(Array &p_arr) const { indices.push_back(thisrow); indices.push_back(point - 1); indices.push_back(point - 2); - }; - }; - }; + } + } + } float scale_neg = 1.0; if (curve.is_valid() && curve->get_point_count() > 0) { @@ -1983,9 +2406,9 @@ void TubeTrailMesh::_create_mesh_array(Array &p_arr) const { indices.push_back(thisrow); indices.push_back(point - 2); indices.push_back(point - 1); - }; - }; - }; + } + } + } p_arr[RS::ARRAY_VERTEX] = points; p_arr[RS::ARRAY_NORMAL] = normals; @@ -2109,6 +2532,8 @@ Transform3D RibbonTrailMesh::get_builtin_bind_pose(int p_index) const { } void RibbonTrailMesh::_create_mesh_array(Array &p_arr) const { + // Seeing use case of ribbon trail mesh, no need to implement special UV2 calculation + PackedVector3Array points; PackedVector3Array normals; PackedFloat32Array tangents; diff --git a/scene/resources/primitive_meshes.h b/scene/resources/primitive_meshes.h index ee61f0ac55..06f9781b84 100644 --- a/scene/resources/primitive_meshes.h +++ b/scene/resources/primitive_meshes.h @@ -56,6 +56,9 @@ private: Ref<Material> material; bool flip_faces = false; + bool add_uv2 = false; + float uv2_padding = 2.0; + // make sure we do an update after we've finished constructing our object mutable bool pending_request = true; void _update() const; @@ -70,6 +73,10 @@ protected: void _request_update(); GDVIRTUAL0RC(Array, _create_mesh_array) + Vector2 get_uv2_scale(Vector2 p_margin_scale = Vector2(1.0, 1.0)) const; + float get_lightmap_texel_size() const; + virtual void _update_lightmap_size(){}; + public: virtual int get_surface_count() const override; virtual int surface_get_array_len(int p_idx) const override; @@ -98,6 +105,12 @@ public: void set_flip_faces(bool p_enable); bool get_flip_faces() const; + void set_add_uv2(bool p_enable); + bool get_add_uv2() const { return add_uv2; } + + void set_uv2_padding(float p_padding); + float get_uv2_padding() const { return uv2_padding; } + PrimitiveMesh(); ~PrimitiveMesh(); }; @@ -118,8 +131,10 @@ protected: static void _bind_methods(); virtual void _create_mesh_array(Array &p_arr) const override; + virtual void _update_lightmap_size() override; + public: - static void create_mesh_array(Array &p_arr, float radius, float height, int radial_segments = 64, int rings = 8); + static void create_mesh_array(Array &p_arr, float radius, float height, int radial_segments = 64, int rings = 8, bool p_add_uv2 = false, const float p_uv2_padding = 1.0); void set_radius(const float p_radius); float get_radius() const; @@ -152,8 +167,10 @@ protected: static void _bind_methods(); virtual void _create_mesh_array(Array &p_arr) const override; + virtual void _update_lightmap_size() override; + public: - static void create_mesh_array(Array &p_arr, Vector3 size, int subdivide_w = 0, int subdivide_h = 0, int subdivide_d = 0); + static void create_mesh_array(Array &p_arr, Vector3 size, int subdivide_w = 0, int subdivide_h = 0, int subdivide_d = 0, bool p_add_uv2 = false, const float p_uv2_padding = 1.0); void set_size(const Vector3 &p_size); Vector3 get_size() const; @@ -190,8 +207,10 @@ protected: static void _bind_methods(); virtual void _create_mesh_array(Array &p_arr) const override; + virtual void _update_lightmap_size() override; + public: - static void create_mesh_array(Array &p_arr, float top_radius, float bottom_radius, float height, int radial_segments = 64, int rings = 4, bool cap_top = true, bool cap_bottom = true); + static void create_mesh_array(Array &p_arr, float top_radius, float bottom_radius, float height, int radial_segments = 64, int rings = 4, bool cap_top = true, bool cap_bottom = true, bool p_add_uv2 = false, const float p_uv2_padding = 1.0); void set_top_radius(const float p_radius); float get_top_radius() const; @@ -241,6 +260,8 @@ protected: static void _bind_methods(); virtual void _create_mesh_array(Array &p_arr) const override; + virtual void _update_lightmap_size() override; + public: void set_size(const Size2 &p_size); Size2 get_size() const; @@ -292,6 +313,8 @@ protected: static void _bind_methods(); virtual void _create_mesh_array(Array &p_arr) const override; + virtual void _update_lightmap_size() override; + public: void set_left_to_right(const float p_left_to_right); float get_left_to_right() const; @@ -328,8 +351,10 @@ protected: static void _bind_methods(); virtual void _create_mesh_array(Array &p_arr) const override; + virtual void _update_lightmap_size() override; + public: - static void create_mesh_array(Array &p_arr, float radius, float height, int radial_segments = 64, int rings = 32, bool is_hemisphere = false); + static void create_mesh_array(Array &p_arr, float radius, float height, int radial_segments = 64, int rings = 32, bool is_hemisphere = false, bool p_add_uv2 = false, const float p_uv2_padding = 1.0); void set_radius(const float p_radius); float get_radius() const; @@ -365,6 +390,8 @@ protected: static void _bind_methods(); virtual void _create_mesh_array(Array &p_arr) const override; + virtual void _update_lightmap_size() override; + public: void set_inner_radius(const float p_inner_radius); float get_inner_radius() const; diff --git a/scene/resources/skeleton_modification_2d_fabrik.h b/scene/resources/skeleton_modification_2d_fabrik.h index 4a875d039f..0ca6582965 100644 --- a/scene/resources/skeleton_modification_2d_fabrik.h +++ b/scene/resources/skeleton_modification_2d_fabrik.h @@ -68,8 +68,8 @@ private: float chain_tolarance = 0.01; int chain_max_iterations = 10; int chain_iterations = 0; - Transform2D target_global_pose = Transform2D(); - Transform2D origin_global_pose = Transform2D(); + Transform2D target_global_pose; + Transform2D origin_global_pose; void fabrik_joint_update_bone2d_cache(int p_joint_idx); void chain_backwards(); diff --git a/scene/resources/skeleton_modification_3d_fabrik.h b/scene/resources/skeleton_modification_3d_fabrik.h index e2e490d636..3e3aa5e587 100644 --- a/scene/resources/skeleton_modification_3d_fabrik.h +++ b/scene/resources/skeleton_modification_3d_fabrik.h @@ -47,7 +47,7 @@ private: bool auto_calculate_length = true; bool use_tip_node = false; - NodePath tip_node = NodePath(); + NodePath tip_node; ObjectID tip_node_cache; bool use_target_basis = false; @@ -68,8 +68,8 @@ private: void update_joint_tip_cache(int p_joint_idx); int final_joint_idx = 0; - Transform3D target_global_pose = Transform3D(); - Transform3D origin_global_pose = Transform3D(); + Transform3D target_global_pose; + Transform3D origin_global_pose; void chain_backwards(); void chain_forwards(); diff --git a/scene/resources/syntax_highlighter.cpp b/scene/resources/syntax_highlighter.cpp index f1eddd8ffc..cb5cb4ef96 100644 --- a/scene/resources/syntax_highlighter.cpp +++ b/scene/resources/syntax_highlighter.cpp @@ -336,7 +336,7 @@ Dictionary CodeHighlighter::_get_line_syntax_highlighting_impl(int p_line) { } String word = str.substr(j, to - j); - Color col = Color(); + Color col; if (keywords.has(word)) { col = keywords[word]; } else if (member_keywords.has(word)) { diff --git a/scene/resources/tile_set.cpp b/scene/resources/tile_set.cpp index 3caf6484d9..d4ad81614d 100644 --- a/scene/resources/tile_set.cpp +++ b/scene/resources/tile_set.cpp @@ -3789,7 +3789,7 @@ Vector2i TileSetAtlasSource::get_atlas_grid_size() const { Size2i valid_area = txt->get_size() - margins; // Compute the number of valid tiles in the tiles atlas - Size2i grid_size = Size2i(); + Size2i grid_size; if (valid_area.x >= texture_region_size.x && valid_area.y >= texture_region_size.y) { valid_area -= texture_region_size; grid_size = Size2i(1, 1) + valid_area / (texture_region_size + separation); diff --git a/scene/resources/tile_set.h b/scene/resources/tile_set.h index 8f175e99a6..9f465a17e9 100644 --- a/scene/resources/tile_set.h +++ b/scene/resources/tile_set.h @@ -785,7 +785,7 @@ private: bool flip_h = false; bool flip_v = false; bool transpose = false; - Vector2i tex_offset = Vector2i(); + Vector2i tex_offset; Ref<Material> material = Ref<Material>(); Color modulate = Color(1.0, 1.0, 1.0, 1.0); int z_index = 0; diff --git a/servers/navigation/navigation_utilities.h b/servers/navigation/navigation_utilities.h index bedcc16a67..e99c139528 100644 --- a/servers/navigation/navigation_utilities.h +++ b/servers/navigation/navigation_utilities.h @@ -48,8 +48,8 @@ struct PathQueryParameters { PathfindingAlgorithm pathfinding_algorithm = PATHFINDING_ALGORITHM_ASTAR; PathPostProcessing path_postprocessing = PATH_POSTPROCESSING_CORRIDORFUNNEL; RID map; - Vector3 start_position = Vector3(); - Vector3 target_position = Vector3(); + Vector3 start_position; + Vector3 target_position; uint32_t navigation_layers = 1; }; diff --git a/servers/rendering/renderer_rd/effects/ss_effects.h b/servers/rendering/renderer_rd/effects/ss_effects.h index dfaf3881bb..a04dfc4a74 100644 --- a/servers/rendering/renderer_rd/effects/ss_effects.h +++ b/servers/rendering/renderer_rd/effects/ss_effects.h @@ -100,7 +100,7 @@ public: float sharpness = 0.98; float normal_rejection = 1.0; - Size2i full_screen_size = Size2i(); + Size2i full_screen_size; }; void ssil_allocate_buffers(SSILRenderBuffers &p_ssil_buffers, const SSILSettings &p_settings, RID p_linear_depth); @@ -137,7 +137,7 @@ public: float horizon = 0.06; float sharpness = 0.98; - Size2i full_screen_size = Size2i(); + Size2i full_screen_size; }; void ssao_allocate_buffers(SSAORenderBuffers &p_ssao_buffers, const SSAOSettings &p_settings, RID p_linear_depth); diff --git a/servers/rendering/renderer_rd/environment/fog.cpp b/servers/rendering/renderer_rd/environment/fog.cpp index 74082906c4..eece195946 100644 --- a/servers/rendering/renderer_rd/environment/fog.cpp +++ b/servers/rendering/renderer_rd/environment/fog.cpp @@ -727,9 +727,9 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P any_uses_time |= shader_data->uses_time; - Vector3i min = Vector3i(); - Vector3i max = Vector3i(); - Vector3i kernel_size = Vector3i(); + Vector3i min; + Vector3i max; + Vector3i kernel_size; Vector3 position = fog_volume_instance->transform.get_origin(); RS::FogVolumeShape volume_type = RendererRD::Fog::get_singleton()->fog_volume_get_shape(fog_volume); diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp index d6dd56c959..2d1d0e0951 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp @@ -3089,7 +3089,7 @@ RID RenderForwardClustered::_setup_render_pass_uniform_set(RenderListType p_rend RD::Uniform u; u.binding = 19; u.uniform_type = RD::UNIFORM_TYPE_TEXTURE; - RID vfog = RID(); + RID vfog; if (rb_data.is_valid() && rb->has_custom_data(RB_SCOPE_FOG)) { Ref<RendererRD::Fog::VolumetricFog> fog = rb->get_custom_data(RB_SCOPE_FOG); vfog = fog->fog_map; diff --git a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp index d2f66896d7..6c8ce5265e 100644 --- a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp @@ -115,6 +115,9 @@ void SceneShaderForwardClustered::ShaderData::set_code(const String &p_code) { actions.usage_flag_pointers["ALPHA"] = &uses_alpha; actions.usage_flag_pointers["ALPHA_SCISSOR_THRESHOLD"] = &uses_alpha_clip; + // Use alpha clip pipeline for alpha hash/dither. + // This prevents sorting issues inherent to alpha blending and allows such materials to cast shadows. + actions.usage_flag_pointers["ALPHA_HASH_SCALE"] = &uses_alpha_clip; actions.render_mode_flags["depth_prepass_alpha"] = &uses_depth_pre_pass; actions.usage_flag_pointers["SSS_STRENGTH"] = &uses_sss; diff --git a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp index 7ed6e24cf4..ee82fa7c7d 100644 --- a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp +++ b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp @@ -116,6 +116,9 @@ void SceneShaderForwardMobile::ShaderData::set_code(const String &p_code) { actions.usage_flag_pointers["ALPHA"] = &uses_alpha; actions.usage_flag_pointers["ALPHA_SCISSOR_THRESHOLD"] = &uses_alpha_clip; + // Use alpha clip pipeline for alpha hash/dither. + // This prevents sorting issues inherent to alpha blending and allows such materials to cast shadows. + actions.usage_flag_pointers["ALPHA_HASH_SCALE"] = &uses_alpha_clip; actions.render_mode_flags["depth_prepass_alpha"] = &uses_depth_pre_pass; // actions.usage_flag_pointers["SSS_STRENGTH"] = &uses_sss; diff --git a/servers/rendering/renderer_rd/storage_rd/light_storage.cpp b/servers/rendering/renderer_rd/storage_rd/light_storage.cpp index c83473ef07..1dd95969e6 100644 --- a/servers/rendering/renderer_rd/storage_rd/light_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/light_storage.cpp @@ -653,6 +653,14 @@ void LightStorage::update_light_buffers(RenderDataRD *p_render_data, const Paged if (light_data.shadow_opacity > 0.001) { RS::LightDirectionalShadowMode smode = light->directional_shadow_mode; + light_data.soft_shadow_scale = light->param[RS::LIGHT_PARAM_SHADOW_BLUR]; + light_data.softshadow_angle = angular_diameter; + light_data.bake_mode = light->bake_mode; + + if (angular_diameter <= 0.0) { + light_data.soft_shadow_scale *= RendererSceneRenderRD::get_singleton()->directional_shadow_quality_radius_get(); // Only use quality radius for PCF + } + int limit = smode == RS::LIGHT_DIRECTIONAL_SHADOW_ORTHOGONAL ? 0 : (smode == RS::LIGHT_DIRECTIONAL_SHADOW_PARALLEL_2_SPLITS ? 1 : 3); light_data.blend_splits = (smode != RS::LIGHT_DIRECTIONAL_SHADOW_ORTHOGONAL) && light->directional_blend_splits; for (int j = 0; j < 4; j++) { @@ -669,7 +677,7 @@ void LightStorage::update_light_buffers(RenderDataRD *p_render_data, const Paged Projection shadow_mtx = rectm * bias * matrix * modelview; light_data.shadow_split_offsets[j] = split; - float bias_scale = light_instance->shadow_transform[j].bias_scale; + float bias_scale = light_instance->shadow_transform[j].bias_scale * light_data.soft_shadow_scale; light_data.shadow_bias[j] = light->param[RS::LIGHT_PARAM_SHADOW_BIAS] / 100.0 * bias_scale; light_data.shadow_normal_bias[j] = light->param[RS::LIGHT_PARAM_SHADOW_NORMAL_BIAS] * light_instance->shadow_transform[j].shadow_texel_size; light_data.shadow_transmittance_bias[j] = light->param[RS::LIGHT_PARAM_TRANSMITTANCE_BIAS] * bias_scale; @@ -702,14 +710,6 @@ void LightStorage::update_light_buffers(RenderDataRD *p_render_data, const Paged float fade_start = light->param[RS::LIGHT_PARAM_SHADOW_FADE_START]; light_data.fade_from = -light_data.shadow_split_offsets[3] * MIN(fade_start, 0.999); //using 1.0 would break smoothstep light_data.fade_to = -light_data.shadow_split_offsets[3]; - - light_data.soft_shadow_scale = light->param[RS::LIGHT_PARAM_SHADOW_BLUR]; - light_data.softshadow_angle = angular_diameter; - light_data.bake_mode = light->bake_mode; - - if (angular_diameter <= 0.0) { - light_data.soft_shadow_scale *= RendererSceneRenderRD::get_singleton()->directional_shadow_quality_radius_get(); // Only use quality radius for PCF - } } r_directional_light_count++; @@ -978,6 +978,7 @@ void LightStorage::update_light_buffers(RenderDataRD *p_render_data, const Paged light_data.soft_shadow_size = 0.0; light_data.soft_shadow_scale *= RendererSceneRenderRD::get_singleton()->shadows_quality_radius_get(); // Only use quality radius for PCF } + light_data.shadow_bias *= light_data.soft_shadow_scale; } } else { light_data.shadow_opacity = 0.0; diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp index 14c09c1512..797e332af0 100644 --- a/servers/rendering/shader_language.cpp +++ b/servers/rendering/shader_language.cpp @@ -3645,7 +3645,7 @@ Variant ShaderLanguage::constant_value_to_variant(const Vector<ShaderLanguage::C switch (p_type) { case ShaderLanguage::TYPE_BOOL: if (array_size > 0) { - PackedInt32Array array = PackedInt32Array(); + PackedInt32Array array; for (int i = 0; i < array_size; i++) { array.push_back(p_value[i].boolean); } @@ -3658,7 +3658,7 @@ Variant ShaderLanguage::constant_value_to_variant(const Vector<ShaderLanguage::C array_size *= 2; if (array_size > 0) { - PackedInt32Array array = PackedInt32Array(); + PackedInt32Array array; for (int i = 0; i < array_size; i++) { array.push_back(p_value[i].boolean); } @@ -3671,7 +3671,7 @@ Variant ShaderLanguage::constant_value_to_variant(const Vector<ShaderLanguage::C array_size *= 3; if (array_size > 0) { - PackedInt32Array array = PackedInt32Array(); + PackedInt32Array array; for (int i = 0; i < array_size; i++) { array.push_back(p_value[i].boolean); } @@ -3684,7 +3684,7 @@ Variant ShaderLanguage::constant_value_to_variant(const Vector<ShaderLanguage::C array_size *= 4; if (array_size > 0) { - PackedInt32Array array = PackedInt32Array(); + PackedInt32Array array; for (int i = 0; i < array_size; i++) { array.push_back(p_value[i].boolean); } @@ -3695,7 +3695,7 @@ Variant ShaderLanguage::constant_value_to_variant(const Vector<ShaderLanguage::C break; case ShaderLanguage::TYPE_INT: if (array_size > 0) { - PackedInt32Array array = PackedInt32Array(); + PackedInt32Array array; for (int i = 0; i < array_size; i++) { array.push_back(p_value[i].sint); } @@ -3708,7 +3708,7 @@ Variant ShaderLanguage::constant_value_to_variant(const Vector<ShaderLanguage::C if (array_size > 0) { array_size *= 2; - PackedInt32Array array = PackedInt32Array(); + PackedInt32Array array; for (int i = 0; i < array_size; i++) { array.push_back(p_value[i].sint); } @@ -3721,7 +3721,7 @@ Variant ShaderLanguage::constant_value_to_variant(const Vector<ShaderLanguage::C if (array_size > 0) { array_size *= 3; - PackedInt32Array array = PackedInt32Array(); + PackedInt32Array array; for (int i = 0; i < array_size; i++) { array.push_back(p_value[i].sint); } @@ -3734,7 +3734,7 @@ Variant ShaderLanguage::constant_value_to_variant(const Vector<ShaderLanguage::C if (array_size > 0) { array_size *= 4; - PackedInt32Array array = PackedInt32Array(); + PackedInt32Array array; for (int i = 0; i < array_size; i++) { array.push_back(p_value[i].sint); } @@ -3745,7 +3745,7 @@ Variant ShaderLanguage::constant_value_to_variant(const Vector<ShaderLanguage::C break; case ShaderLanguage::TYPE_UINT: if (array_size > 0) { - PackedInt32Array array = PackedInt32Array(); + PackedInt32Array array; for (int i = 0; i < array_size; i++) { array.push_back(p_value[i].uint); } @@ -3758,7 +3758,7 @@ Variant ShaderLanguage::constant_value_to_variant(const Vector<ShaderLanguage::C if (array_size > 0) { array_size *= 2; - PackedInt32Array array = PackedInt32Array(); + PackedInt32Array array; for (int i = 0; i < array_size; i++) { array.push_back(p_value[i].uint); } @@ -3771,7 +3771,7 @@ Variant ShaderLanguage::constant_value_to_variant(const Vector<ShaderLanguage::C if (array_size > 0) { array_size *= 3; - PackedInt32Array array = PackedInt32Array(); + PackedInt32Array array; for (int i = 0; i < array_size; i++) { array.push_back(p_value[i].uint); } @@ -3784,7 +3784,7 @@ Variant ShaderLanguage::constant_value_to_variant(const Vector<ShaderLanguage::C if (array_size > 0) { array_size *= 4; - PackedInt32Array array = PackedInt32Array(); + PackedInt32Array array; for (int i = 0; i < array_size; i++) { array.push_back(p_value[i].uint); } @@ -3795,7 +3795,7 @@ Variant ShaderLanguage::constant_value_to_variant(const Vector<ShaderLanguage::C break; case ShaderLanguage::TYPE_FLOAT: if (array_size > 0) { - PackedFloat32Array array = PackedFloat32Array(); + PackedFloat32Array array; for (int i = 0; i < array_size; i++) { array.push_back(p_value[i].real); } @@ -3808,7 +3808,7 @@ Variant ShaderLanguage::constant_value_to_variant(const Vector<ShaderLanguage::C if (array_size > 0) { array_size *= 2; - PackedVector2Array array = PackedVector2Array(); + PackedVector2Array array; for (int i = 0; i < array_size; i += 2) { array.push_back(Vector2(p_value[i].real, p_value[i + 1].real)); } @@ -3822,13 +3822,13 @@ Variant ShaderLanguage::constant_value_to_variant(const Vector<ShaderLanguage::C array_size *= 3; if (p_hint == ShaderLanguage::ShaderNode::Uniform::HINT_SOURCE_COLOR) { - PackedColorArray array = PackedColorArray(); + PackedColorArray array; for (int i = 0; i < array_size; i += 3) { array.push_back(Color(p_value[i].real, p_value[i + 1].real, p_value[i + 2].real)); } value = Variant(array); } else { - PackedVector3Array array = PackedVector3Array(); + PackedVector3Array array; for (int i = 0; i < array_size; i += 3) { array.push_back(Vector3(p_value[i].real, p_value[i + 1].real, p_value[i + 2].real)); } @@ -3847,13 +3847,13 @@ Variant ShaderLanguage::constant_value_to_variant(const Vector<ShaderLanguage::C array_size *= 4; if (p_hint == ShaderLanguage::ShaderNode::Uniform::HINT_SOURCE_COLOR) { - PackedColorArray array = PackedColorArray(); + PackedColorArray array; for (int i = 0; i < array_size; i += 4) { array.push_back(Color(p_value[i].real, p_value[i + 1].real, p_value[i + 2].real, p_value[i + 3].real)); } value = Variant(array); } else { - PackedFloat32Array array = PackedFloat32Array(); + PackedFloat32Array array; for (int i = 0; i < array_size; i += 4) { array.push_back(p_value[i].real); array.push_back(p_value[i + 1].real); @@ -3874,7 +3874,7 @@ Variant ShaderLanguage::constant_value_to_variant(const Vector<ShaderLanguage::C if (array_size > 0) { array_size *= 4; - PackedFloat32Array array = PackedFloat32Array(); + PackedFloat32Array array; for (int i = 0; i < array_size; i += 4) { array.push_back(p_value[i].real); array.push_back(p_value[i + 1].real); @@ -3890,7 +3890,7 @@ Variant ShaderLanguage::constant_value_to_variant(const Vector<ShaderLanguage::C if (array_size > 0) { array_size *= 9; - PackedFloat32Array array = PackedFloat32Array(); + PackedFloat32Array array; for (int i = 0; i < array_size; i += 9) { for (int j = 0; j < 9; j++) { array.push_back(p_value[i + j].real); @@ -3916,7 +3916,7 @@ Variant ShaderLanguage::constant_value_to_variant(const Vector<ShaderLanguage::C if (array_size > 0) { array_size *= 16; - PackedFloat32Array array = PackedFloat32Array(); + PackedFloat32Array array; for (int i = 0; i < array_size; i += 16) { for (int j = 0; j < 16; j++) { array.push_back(p_value[i + j].real); diff --git a/servers/rendering/storage/environment_storage.h b/servers/rendering/storage/environment_storage.h index 17bde94902..dea9487af6 100644 --- a/servers/rendering/storage/environment_storage.h +++ b/servers/rendering/storage/environment_storage.h @@ -98,7 +98,7 @@ private: float glow_hdr_luminance_cap = 12.0; float glow_hdr_bleed_scale = 2.0; float glow_map_strength = 0.0f; // 1.0f in GLES3 ?? - RID glow_map = RID(); + RID glow_map; // SSR bool ssr_enabled = false; @@ -143,7 +143,7 @@ private: float adjustments_contrast = 1.0f; float adjustments_saturation = 1.0f; bool use_1d_color_correction = false; - RID color_correction = RID(); + RID color_correction; }; mutable RID_Owner<Environment, true> environment_owner; diff --git a/servers/xr/xr_interface.h b/servers/xr/xr_interface.h index 86d328d41c..2960074dd2 100644 --- a/servers/xr/xr_interface.h +++ b/servers/xr/xr_interface.h @@ -138,8 +138,6 @@ public: virtual bool start_passthrough() { return false; } virtual void stop_passthrough() {} - virtual void notification(int p_what){}; - XRInterface(); ~XRInterface(); diff --git a/servers/xr/xr_interface_extension.cpp b/servers/xr/xr_interface_extension.cpp index 89752b3017..2142ecd262 100644 --- a/servers/xr/xr_interface_extension.cpp +++ b/servers/xr/xr_interface_extension.cpp @@ -58,8 +58,6 @@ void XRInterfaceExtension::_bind_methods() { GDVIRTUAL_BIND(_post_draw_viewport, "render_target", "screen_rect"); GDVIRTUAL_BIND(_end_frame); - GDVIRTUAL_BIND(_notification, "what"); - /** input and output **/ GDVIRTUAL_BIND(_get_suggested_tracker_names); @@ -309,10 +307,6 @@ void XRInterfaceExtension::end_frame() { GDVIRTUAL_CALL(_end_frame); } -void XRInterfaceExtension::notification(int p_what) { - GDVIRTUAL_CALL(_notification, p_what); -} - RID XRInterfaceExtension::get_render_target_texture(RID p_render_target) { // In due time this will need to be enhance to return the correct INTERNAL RID for the chosen rendering engine. // So once a GLES driver is implemented we'll return that and the implemented plugin needs to handle this correctly too. diff --git a/servers/xr/xr_interface_extension.h b/servers/xr/xr_interface_extension.h index 2235b57cb3..123626864d 100644 --- a/servers/xr/xr_interface_extension.h +++ b/servers/xr/xr_interface_extension.h @@ -123,7 +123,6 @@ public: virtual bool pre_draw_viewport(RID p_render_target) override; virtual Vector<BlitToScreen> post_draw_viewport(RID p_render_target, const Rect2 &p_screen_rect) override; virtual void end_frame() override; - virtual void notification(int p_what) override; GDVIRTUAL0(_process); GDVIRTUAL0(_pre_render); @@ -131,8 +130,6 @@ public: GDVIRTUAL2(_post_draw_viewport, RID, const Rect2 &); GDVIRTUAL0(_end_frame); - GDVIRTUAL1(_notification, int); - /* access to some internals we need */ RID get_render_target_texture(RID p_render_target); // RID get_render_target_depth(RID p_render_target); |