diff options
Diffstat (limited to 'modules')
30 files changed, 806 insertions, 156 deletions
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 58aa7a08d4..6245cc85a0 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -1386,6 +1386,36 @@ String GDScript::debug_get_script_name(const Ref<Script> &p_script) { } #endif +thread_local GDScript::UpdatableFuncPtr GDScript::func_ptrs_to_update_thread_local; + +GDScript::UpdatableFuncPtrElement GDScript::_add_func_ptr_to_update(GDScriptFunction **p_func_ptr_ptr) { + UpdatableFuncPtrElement result = {}; + + { + MutexLock lock(func_ptrs_to_update_thread_local.mutex); + result.element = func_ptrs_to_update_thread_local.ptrs.push_back(p_func_ptr_ptr); + result.mutex = &func_ptrs_to_update_thread_local.mutex; + + if (likely(func_ptrs_to_update_thread_local.initialized)) { + return result; + } + + func_ptrs_to_update_thread_local.initialized = true; + } + + MutexLock lock(func_ptrs_to_update_mutex); + func_ptrs_to_update.push_back(&func_ptrs_to_update_thread_local); + + return result; +} + +void GDScript::_remove_func_ptr_to_update(const UpdatableFuncPtrElement p_func_ptr_element) { + ERR_FAIL_NULL(p_func_ptr_element.element); + ERR_FAIL_NULL(p_func_ptr_element.mutex); + MutexLock lock(*p_func_ptr_element.mutex); + p_func_ptr_element.element->erase(); +} + void GDScript::clear(ClearData *p_clear_data) { if (clearing) { return; @@ -1403,6 +1433,16 @@ void GDScript::clear(ClearData *p_clear_data) { is_root = true; } + { + MutexLock outer_lock(func_ptrs_to_update_mutex); + for (UpdatableFuncPtr *updatable : func_ptrs_to_update) { + MutexLock inner_lock(updatable->mutex); + for (GDScriptFunction **func_ptr_ptr : updatable->ptrs) { + *func_ptr_ptr = nullptr; + } + } + } + RBSet<GDScript *> must_clear_dependencies = get_must_clear_dependencies(); for (GDScript *E : must_clear_dependencies) { clear_data->scripts.insert(E); diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index d335ec85ee..04b0a1d786 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -86,6 +86,8 @@ class GDScript : public Script { friend class GDScriptAnalyzer; friend class GDScriptCompiler; friend class GDScriptDocGen; + friend class GDScriptLambdaCallable; + friend class GDScriptLambdaSelfCallable; friend class GDScriptLanguage; friend struct GDScriptUtilityFunctionsDefinitions; @@ -108,6 +110,30 @@ class GDScript : public Script { HashMap<StringName, MethodInfo> _signals; Dictionary rpc_config; + struct LambdaInfo { + int capture_count; + bool use_self; + }; + + HashMap<GDScriptFunction *, LambdaInfo> lambda_info; + + // List is used here because a ptr to elements are stored, so the memory locations need to be stable + struct UpdatableFuncPtr { + List<GDScriptFunction **> ptrs; + Mutex mutex; + bool initialized = false; + }; + struct UpdatableFuncPtrElement { + List<GDScriptFunction **>::Element *element = nullptr; + Mutex *mutex = nullptr; + }; + static thread_local UpdatableFuncPtr func_ptrs_to_update_thread_local; + List<UpdatableFuncPtr *> func_ptrs_to_update; + Mutex func_ptrs_to_update_mutex; + + UpdatableFuncPtrElement _add_func_ptr_to_update(GDScriptFunction **p_func_ptr_ptr); + static void _remove_func_ptr_to_update(const UpdatableFuncPtrElement p_func_ptr_element); + #ifdef TOOLS_ENABLED // For static data storage during hot-reloading. HashMap<StringName, MemberInfo> old_static_variables_indices; diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index f3f95ba50b..2439e5760c 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -4343,7 +4343,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri } } else if (base_type.kind != GDScriptParser::DataType::BUILTIN && !index_type.is_variant()) { if (index_type.builtin_type != Variant::STRING && index_type.builtin_type != Variant::STRING_NAME) { - push_error(vformat(R"(Only String or StringName can be used as index for type "%s", but received a "%s".)", base_type.to_string(), index_type.to_string()), p_subscript->index); + push_error(vformat(R"(Only "String" or "StringName" can be used as index for type "%s", but received "%s".)", base_type.to_string(), index_type.to_string()), p_subscript->index); } } diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp index 18609d0b80..26f01ec218 100644 --- a/modules/gdscript/gdscript_cache.cpp +++ b/modules/gdscript/gdscript_cache.cpp @@ -287,7 +287,8 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro if (script.is_null()) { script = get_shallow_script(p_path, r_error); - if (r_error) { + // Only exit early if script failed to load, otherwise let reload report errors. + if (script.is_null()) { return script; } } diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index bf648abc9e..7980f020b8 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -1371,6 +1371,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code return GDScriptCodeGenerator::Address(); } + main_script->lambda_info.insert(function, { lambda->captures.size(), lambda->use_self }); gen->write_lambda(result, function, captures, lambda->use_self); for (int i = 0; i < captures.size(); i++) { @@ -2631,6 +2632,7 @@ Error GDScriptCompiler::_prepare_compilation(GDScript *p_script, const GDScriptP p_script->implicit_ready = nullptr; p_script->static_initializer = nullptr; p_script->rpc_config.clear(); + p_script->lambda_info.clear(); p_script->clearing = false; @@ -3040,6 +3042,128 @@ void GDScriptCompiler::make_scripts(GDScript *p_script, const GDScriptParser::Cl } } +GDScriptCompiler::FunctionLambdaInfo GDScriptCompiler::_get_function_replacement_info(GDScriptFunction *p_func, int p_index, int p_depth, GDScriptFunction *p_parent_func) { + FunctionLambdaInfo info; + info.function = p_func; + info.parent = p_parent_func; + info.script = p_parent_func; + info.name = p_func->get_name(); + info.line = p_func->_initial_line; + info.index = p_index; + info.depth = p_depth; + info.capture_count = 0; + info.use_self = false; + info.arg_count = p_func->_argument_count; + info.default_arg_count = p_func->_default_arg_count; + info.sublambdas = _get_function_lambda_replacement_info(p_func, p_depth, p_parent_func); + + GDScript::LambdaInfo *extra_info = main_script->lambda_info.getptr(p_func); + if (extra_info != nullptr) { + info.capture_count = extra_info->capture_count; + info.use_self = extra_info->use_self; + } + + return info; +} + +Vector<GDScriptCompiler::FunctionLambdaInfo> GDScriptCompiler::_get_function_lambda_replacement_info(GDScriptFunction *p_func, int p_depth, GDScriptFunction *p_parent_func) { + Vector<FunctionLambdaInfo> result; + // Only scrape the lambdas inside p_func. + for (int i = 0; i < p_func->lambdas.size(); ++i) { + result.push_back(_get_function_replacement_info(p_func->lambdas[i], i, p_depth + 1, p_func)); + } + return result; +} + +GDScriptCompiler::ScriptLambdaInfo GDScriptCompiler::_get_script_lambda_replacement_info(GDScript *p_script) { + ScriptLambdaInfo info; + + if (p_script->implicit_initializer) { + info.implicit_initializer_info = _get_function_lambda_replacement_info(p_script->implicit_initializer); + } + if (p_script->implicit_ready) { + info.implicit_ready_info = _get_function_lambda_replacement_info(p_script->implicit_ready); + } + if (p_script->static_initializer) { + info.static_initializer_info = _get_function_lambda_replacement_info(p_script->static_initializer); + } + + for (const KeyValue<StringName, GDScriptFunction *> &E : p_script->member_functions) { + info.member_function_infos.insert(E.key, _get_function_lambda_replacement_info(E.value)); + } + + for (const KeyValue<StringName, Ref<GDScript>> &KV : p_script->get_subclasses()) { + info.subclass_info.insert(KV.key, _get_script_lambda_replacement_info(KV.value.ptr())); + } + + return info; +} + +bool GDScriptCompiler::_do_function_infos_match(const FunctionLambdaInfo &p_old_info, const FunctionLambdaInfo *p_new_info) { + if (p_new_info == nullptr) { + return false; + } + + if (p_new_info->capture_count != p_old_info.capture_count || p_new_info->use_self != p_old_info.use_self) { + return false; + } + + int old_required_arg_count = p_old_info.arg_count - p_old_info.default_arg_count; + int new_required_arg_count = p_new_info->arg_count - p_new_info->default_arg_count; + if (new_required_arg_count > old_required_arg_count || p_new_info->arg_count < old_required_arg_count) { + return false; + } + + return true; +} + +void GDScriptCompiler::_get_function_ptr_replacements(HashMap<GDScriptFunction *, GDScriptFunction *> &r_replacements, const FunctionLambdaInfo &p_old_info, const FunctionLambdaInfo *p_new_info) { + ERR_FAIL_COND(r_replacements.has(p_old_info.function)); + if (!_do_function_infos_match(p_old_info, p_new_info)) { + p_new_info = nullptr; + } + + r_replacements.insert(p_old_info.function, p_new_info != nullptr ? p_new_info->function : nullptr); + _get_function_ptr_replacements(r_replacements, p_old_info.sublambdas, p_new_info != nullptr ? &p_new_info->sublambdas : nullptr); +} + +void GDScriptCompiler::_get_function_ptr_replacements(HashMap<GDScriptFunction *, GDScriptFunction *> &r_replacements, const Vector<FunctionLambdaInfo> &p_old_infos, const Vector<FunctionLambdaInfo> *p_new_infos) { + for (int i = 0; i < p_old_infos.size(); ++i) { + const FunctionLambdaInfo &old_info = p_old_infos[i]; + const FunctionLambdaInfo *new_info = nullptr; + if (p_new_infos != nullptr && p_new_infos->size() == p_old_infos.size()) { + // For now only attempt if the size is the same. + new_info = &p_new_infos->get(i); + } + _get_function_ptr_replacements(r_replacements, old_info, new_info); + } +} + +void GDScriptCompiler::_get_function_ptr_replacements(HashMap<GDScriptFunction *, GDScriptFunction *> &r_replacements, const ScriptLambdaInfo &p_old_info, const ScriptLambdaInfo *p_new_info) { + _get_function_ptr_replacements(r_replacements, p_old_info.implicit_initializer_info, p_new_info != nullptr ? &p_new_info->implicit_initializer_info : nullptr); + _get_function_ptr_replacements(r_replacements, p_old_info.implicit_ready_info, p_new_info != nullptr ? &p_new_info->implicit_ready_info : nullptr); + _get_function_ptr_replacements(r_replacements, p_old_info.static_initializer_info, p_new_info != nullptr ? &p_new_info->static_initializer_info : nullptr); + + for (const KeyValue<StringName, Vector<FunctionLambdaInfo>> &old_kv : p_old_info.member_function_infos) { + _get_function_ptr_replacements(r_replacements, old_kv.value, p_new_info != nullptr ? p_new_info->member_function_infos.getptr(old_kv.key) : nullptr); + } + for (int i = 0; i < p_old_info.other_function_infos.size(); ++i) { + const FunctionLambdaInfo &old_other_info = p_old_info.other_function_infos[i]; + const FunctionLambdaInfo *new_other_info = nullptr; + if (p_new_info != nullptr && p_new_info->other_function_infos.size() == p_old_info.other_function_infos.size()) { + // For now only attempt if the size is the same. + new_other_info = &p_new_info->other_function_infos[i]; + } + // Needs to be called on all old lambdas, even if there's no replacement. + _get_function_ptr_replacements(r_replacements, old_other_info, new_other_info); + } + for (const KeyValue<StringName, ScriptLambdaInfo> &old_kv : p_old_info.subclass_info) { + const ScriptLambdaInfo &old_subinfo = old_kv.value; + const ScriptLambdaInfo *new_subinfo = p_new_info != nullptr ? p_new_info->subclass_info.getptr(old_kv.key) : nullptr; + _get_function_ptr_replacements(r_replacements, old_subinfo, new_subinfo); + } +} + Error GDScriptCompiler::compile(const GDScriptParser *p_parser, GDScript *p_script, bool p_keep_state) { err_line = -1; err_column = -1; @@ -3050,6 +3174,8 @@ Error GDScriptCompiler::compile(const GDScriptParser *p_parser, GDScript *p_scri source = p_script->get_path(); + ScriptLambdaInfo old_lambda_info = _get_script_lambda_replacement_info(p_script); + // Create scripts for subclasses beforehand so they can be referenced make_scripts(p_script, root, p_keep_state); @@ -3065,6 +3191,27 @@ Error GDScriptCompiler::compile(const GDScriptParser *p_parser, GDScript *p_scri return err; } + ScriptLambdaInfo new_lambda_info = _get_script_lambda_replacement_info(p_script); + + HashMap<GDScriptFunction *, GDScriptFunction *> func_ptr_replacements; + _get_function_ptr_replacements(func_ptr_replacements, old_lambda_info, &new_lambda_info); + + { + MutexLock outer_lock(main_script->func_ptrs_to_update_mutex); + for (GDScript::UpdatableFuncPtr *updatable : main_script->func_ptrs_to_update) { + MutexLock inner_lock(updatable->mutex); + for (GDScriptFunction **func_ptr_ptr : updatable->ptrs) { + GDScriptFunction **replacement = func_ptr_replacements.getptr(*func_ptr_ptr); + if (replacement != nullptr) { + *func_ptr_ptr = *replacement; + } else { + // Probably a lambda from another reload, ignore. + *func_ptr_ptr = nullptr; + } + } + } + } + if (has_static_data && !root->annotated_static_unload) { GDScriptCache::add_static_script(p_script); } diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h index 099bd00a2e..fd6b22f527 100644 --- a/modules/gdscript/gdscript_compiler.h +++ b/modules/gdscript/gdscript_compiler.h @@ -44,6 +44,34 @@ class GDScriptCompiler { HashSet<GDScript *> parsing_classes; GDScript *main_script = nullptr; + struct FunctionLambdaInfo { + GDScriptFunction *function; + GDScriptFunction *parent; + Ref<GDScript> script; + StringName name; + int line; + int index; + int depth; + //uint64_t code_hash; + //int code_size; + int capture_count; + int use_self; + int arg_count; + int default_arg_count; + //Vector<GDScriptDataType> argument_types; + //GDScriptDataType return_type; + Vector<FunctionLambdaInfo> sublambdas; + }; + + struct ScriptLambdaInfo { + Vector<FunctionLambdaInfo> implicit_initializer_info; + Vector<FunctionLambdaInfo> implicit_ready_info; + Vector<FunctionLambdaInfo> static_initializer_info; + HashMap<StringName, Vector<FunctionLambdaInfo>> member_function_infos; + Vector<FunctionLambdaInfo> other_function_infos; + HashMap<StringName, ScriptLambdaInfo> subclass_info; + }; + struct CodeGen { GDScript *script = nullptr; const GDScriptParser::ClassNode *class_node = nullptr; @@ -137,6 +165,13 @@ class GDScriptCompiler { Error _parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter); Error _prepare_compilation(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); + FunctionLambdaInfo _get_function_replacement_info(GDScriptFunction *p_func, int p_index = -1, int p_depth = 0, GDScriptFunction *p_parent_func = nullptr); + Vector<FunctionLambdaInfo> _get_function_lambda_replacement_info(GDScriptFunction *p_func, int p_depth = 0, GDScriptFunction *p_parent_func = nullptr); + ScriptLambdaInfo _get_script_lambda_replacement_info(GDScript *p_script); + bool _do_function_infos_match(const FunctionLambdaInfo &p_old_info, const FunctionLambdaInfo *p_new_info); + void _get_function_ptr_replacements(HashMap<GDScriptFunction *, GDScriptFunction *> &r_replacements, const FunctionLambdaInfo &p_old_info, const FunctionLambdaInfo *p_new_info); + void _get_function_ptr_replacements(HashMap<GDScriptFunction *, GDScriptFunction *> &r_replacements, const Vector<FunctionLambdaInfo> &p_old_infos, const Vector<FunctionLambdaInfo> *p_new_infos); + void _get_function_ptr_replacements(HashMap<GDScriptFunction *, GDScriptFunction *> &r_replacements, const ScriptLambdaInfo &p_old_info, const ScriptLambdaInfo *p_new_info); int err_line = 0; int err_column = 0; StringName source; diff --git a/modules/gdscript/gdscript_lambda_callable.cpp b/modules/gdscript/gdscript_lambda_callable.cpp index 9d0fce0928..547f5607d3 100644 --- a/modules/gdscript/gdscript_lambda_callable.cpp +++ b/modules/gdscript/gdscript_lambda_callable.cpp @@ -44,11 +44,18 @@ bool GDScriptLambdaCallable::compare_less(const CallableCustom *p_a, const Calla return p_a < p_b; } +bool GDScriptLambdaCallable::is_valid() const { + return CallableCustom::is_valid() && function != nullptr; +} + uint32_t GDScriptLambdaCallable::hash() const { return h; } String GDScriptLambdaCallable::get_as_text() const { + if (function == nullptr) { + return "<invalid lambda>"; + } if (function->get_name() != StringName()) { return function->get_name().operator String() + "(lambda)"; } @@ -74,6 +81,12 @@ StringName GDScriptLambdaCallable::get_method() const { void GDScriptLambdaCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const { int captures_amount = captures.size(); + if (function == nullptr) { + r_return_value = Variant(); + r_call_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; + return; + } + if (captures_amount > 0) { Vector<const Variant *> args; args.resize(p_argcount + captures_amount); @@ -127,11 +140,19 @@ void GDScriptLambdaCallable::call(const Variant **p_arguments, int p_argcount, V } GDScriptLambdaCallable::GDScriptLambdaCallable(Ref<GDScript> p_script, GDScriptFunction *p_function, const Vector<Variant> &p_captures) { + ERR_FAIL_NULL(p_script.ptr()); + ERR_FAIL_NULL(p_function); script = p_script; function = p_function; captures = p_captures; h = (uint32_t)hash_murmur3_one_64((uint64_t)this); + + updatable_func_ptr_element = p_script->_add_func_ptr_to_update(&function); +} + +GDScriptLambdaCallable::~GDScriptLambdaCallable() { + GDScript::_remove_func_ptr_to_update(updatable_func_ptr_element); } bool GDScriptLambdaSelfCallable::compare_equal(const CallableCustom *p_a, const CallableCustom *p_b) { @@ -144,11 +165,18 @@ bool GDScriptLambdaSelfCallable::compare_less(const CallableCustom *p_a, const C return p_a < p_b; } +bool GDScriptLambdaSelfCallable::is_valid() const { + return CallableCustom::is_valid() && function != nullptr; +} + uint32_t GDScriptLambdaSelfCallable::hash() const { return h; } String GDScriptLambdaSelfCallable::get_as_text() const { + if (function == nullptr) { + return "<invalid lambda>"; + } if (function->get_name() != StringName()) { return function->get_name().operator String() + "(lambda)"; } @@ -178,6 +206,12 @@ void GDScriptLambdaSelfCallable::call(const Variant **p_arguments, int p_argcoun int captures_amount = captures.size(); + if (function == nullptr) { + r_return_value = Variant(); + r_call_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; + return; + } + if (captures_amount > 0) { Vector<const Variant *> args; args.resize(p_argcount + captures_amount); @@ -231,18 +265,36 @@ void GDScriptLambdaSelfCallable::call(const Variant **p_arguments, int p_argcoun } GDScriptLambdaSelfCallable::GDScriptLambdaSelfCallable(Ref<RefCounted> p_self, GDScriptFunction *p_function, const Vector<Variant> &p_captures) { + ERR_FAIL_NULL(p_self.ptr()); + ERR_FAIL_NULL(p_function); reference = p_self; object = p_self.ptr(); function = p_function; captures = p_captures; h = (uint32_t)hash_murmur3_one_64((uint64_t)this); + + GDScript *gds = p_function->get_script(); + if (gds != nullptr) { + updatable_func_ptr_element = gds->_add_func_ptr_to_update(&function); + } } GDScriptLambdaSelfCallable::GDScriptLambdaSelfCallable(Object *p_self, GDScriptFunction *p_function, const Vector<Variant> &p_captures) { + ERR_FAIL_NULL(p_self); + ERR_FAIL_NULL(p_function); object = p_self; function = p_function; captures = p_captures; h = (uint32_t)hash_murmur3_one_64((uint64_t)this); + + GDScript *gds = p_function->get_script(); + if (gds != nullptr) { + updatable_func_ptr_element = gds->_add_func_ptr_to_update(&function); + } +} + +GDScriptLambdaSelfCallable::~GDScriptLambdaSelfCallable() { + GDScript::_remove_func_ptr_to_update(updatable_func_ptr_element); } diff --git a/modules/gdscript/gdscript_lambda_callable.h b/modules/gdscript/gdscript_lambda_callable.h index 1c7a18fb9d..ee7d547544 100644 --- a/modules/gdscript/gdscript_lambda_callable.h +++ b/modules/gdscript/gdscript_lambda_callable.h @@ -31,12 +31,13 @@ #ifndef GDSCRIPT_LAMBDA_CALLABLE_H #define GDSCRIPT_LAMBDA_CALLABLE_H +#include "gdscript.h" + #include "core/object/ref_counted.h" #include "core/templates/vector.h" #include "core/variant/callable.h" #include "core/variant/variant.h" -class GDScript; class GDScriptFunction; class GDScriptInstance; @@ -44,6 +45,7 @@ class GDScriptLambdaCallable : public CallableCustom { GDScriptFunction *function = nullptr; Ref<GDScript> script; uint32_t h; + GDScript::UpdatableFuncPtrElement updatable_func_ptr_element; Vector<Variant> captures; @@ -51,6 +53,7 @@ class GDScriptLambdaCallable : public CallableCustom { static bool compare_less(const CallableCustom *p_a, const CallableCustom *p_b); public: + bool is_valid() const override; uint32_t hash() const override; String get_as_text() const override; CompareEqualFunc get_compare_equal_func() const override; @@ -60,7 +63,7 @@ public: void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override; GDScriptLambdaCallable(Ref<GDScript> p_script, GDScriptFunction *p_function, const Vector<Variant> &p_captures); - virtual ~GDScriptLambdaCallable() = default; + virtual ~GDScriptLambdaCallable(); }; // Lambda callable that references a particular object, so it can use `self` in the body. @@ -69,6 +72,7 @@ class GDScriptLambdaSelfCallable : public CallableCustom { Ref<RefCounted> reference; // For objects that are RefCounted, keep a reference. Object *object = nullptr; // For non RefCounted objects, use a direct pointer. uint32_t h; + GDScript::UpdatableFuncPtrElement updatable_func_ptr_element; Vector<Variant> captures; @@ -76,6 +80,7 @@ class GDScriptLambdaSelfCallable : public CallableCustom { static bool compare_less(const CallableCustom *p_a, const CallableCustom *p_b); public: + bool is_valid() const override; uint32_t hash() const override; String get_as_text() const override; CompareEqualFunc get_compare_equal_func() const override; @@ -85,7 +90,7 @@ public: GDScriptLambdaSelfCallable(Ref<RefCounted> p_self, GDScriptFunction *p_function, const Vector<Variant> &p_captures); GDScriptLambdaSelfCallable(Object *p_self, GDScriptFunction *p_function, const Vector<Variant> &p_captures); - virtual ~GDScriptLambdaSelfCallable() = default; + virtual ~GDScriptLambdaSelfCallable(); }; #endif // GDSCRIPT_LAMBDA_CALLABLE_H diff --git a/modules/gdscript/gdscript_rpc_callable.cpp b/modules/gdscript/gdscript_rpc_callable.cpp index a6d2388a91..df014d3cfe 100644 --- a/modules/gdscript/gdscript_rpc_callable.cpp +++ b/modules/gdscript/gdscript_rpc_callable.cpp @@ -73,6 +73,7 @@ void GDScriptRPCCallable::call(const Variant **p_arguments, int p_argcount, Vari } GDScriptRPCCallable::GDScriptRPCCallable(Object *p_object, const StringName &p_method) { + ERR_FAIL_NULL(p_object); object = p_object; method = p_method; h = method.hash(); diff --git a/modules/lightmapper_rd/lightmapper_rd.cpp b/modules/lightmapper_rd/lightmapper_rd.cpp index feb9a2274e..fe919953c1 100644 --- a/modules/lightmapper_rd/lightmapper_rd.cpp +++ b/modules/lightmapper_rd/lightmapper_rd.cpp @@ -124,7 +124,7 @@ void LightmapperRD::add_probe(const Vector3 &p_position) { probe_positions.push_back(probe); } -void LightmapperRD::_plot_triangle_into_triangle_index_list(int p_size, const Vector3i &p_ofs, const AABB &p_bounds, const Vector3 p_points[3], uint32_t p_triangle_index, LocalVector<TriangleSort> &triangles, uint32_t p_grid_size) { +void LightmapperRD::_plot_triangle_into_triangle_index_list(int p_size, const Vector3i &p_ofs, const AABB &p_bounds, const Vector3 p_points[3], uint32_t p_triangle_index, LocalVector<TriangleSort> &p_triangles_sort, uint32_t p_grid_size) { int half_size = p_size / 2; for (int i = 0; i < 8; i++) { @@ -159,13 +159,69 @@ void LightmapperRD::_plot_triangle_into_triangle_index_list(int p_size, const Ve TriangleSort ts; ts.cell_index = n.x + (n.y * p_grid_size) + (n.z * p_grid_size * p_grid_size); ts.triangle_index = p_triangle_index; - triangles.push_back(ts); + ts.triangle_aabb.position = p_points[0]; + ts.triangle_aabb.size = Vector3(); + ts.triangle_aabb.expand_to(p_points[1]); + ts.triangle_aabb.expand_to(p_points[2]); + p_triangles_sort.push_back(ts); } else { - _plot_triangle_into_triangle_index_list(half_size, n, aabb, p_points, p_triangle_index, triangles, p_grid_size); + _plot_triangle_into_triangle_index_list(half_size, n, aabb, p_points, p_triangle_index, p_triangles_sort, p_grid_size); } } } +void LightmapperRD::_sort_triangle_clusters(uint32_t p_cluster_size, uint32_t p_cluster_index, uint32_t p_index_start, uint32_t p_count, LocalVector<TriangleSort> &p_triangle_sort, LocalVector<ClusterAABB> &p_cluster_aabb) { + if (p_count == 0) { + return; + } + + // Compute AABB for all triangles in the range. + SortArray<TriangleSort, TriangleSortAxis<0>> triangle_sorter_x; + SortArray<TriangleSort, TriangleSortAxis<1>> triangle_sorter_y; + SortArray<TriangleSort, TriangleSortAxis<2>> triangle_sorter_z; + AABB cluster_aabb = p_triangle_sort[p_index_start].triangle_aabb; + for (uint32_t i = 1; i < p_count; i++) { + cluster_aabb.merge_with(p_triangle_sort[p_index_start + i].triangle_aabb); + } + + if (p_count > p_cluster_size) { + int longest_axis_index = cluster_aabb.get_longest_axis_index(); + switch (longest_axis_index) { + case 0: + triangle_sorter_x.sort(&p_triangle_sort[p_index_start], p_count); + break; + case 1: + triangle_sorter_y.sort(&p_triangle_sort[p_index_start], p_count); + break; + case 2: + triangle_sorter_z.sort(&p_triangle_sort[p_index_start], p_count); + break; + default: + DEV_ASSERT(false && "Invalid axis returned by AABB."); + break; + } + + uint32_t left_cluster_count = next_power_of_2(p_count / 2); + left_cluster_count = MAX(left_cluster_count, p_cluster_size); + left_cluster_count = MIN(left_cluster_count, p_count); + _sort_triangle_clusters(p_cluster_size, p_cluster_index, p_index_start, left_cluster_count, p_triangle_sort, p_cluster_aabb); + + if (left_cluster_count < p_count) { + uint32_t cluster_index_right = p_cluster_index + (left_cluster_count / p_cluster_size); + _sort_triangle_clusters(p_cluster_size, cluster_index_right, p_index_start + left_cluster_count, p_count - left_cluster_count, p_triangle_sort, p_cluster_aabb); + } + } else { + ClusterAABB &aabb = p_cluster_aabb[p_cluster_index]; + Vector3 aabb_end = cluster_aabb.get_end(); + aabb.min_bounds[0] = cluster_aabb.position.x; + aabb.min_bounds[1] = cluster_aabb.position.y; + aabb.min_bounds[2] = cluster_aabb.position.z; + aabb.max_bounds[0] = aabb_end.x; + aabb.max_bounds[1] = aabb_end.y; + aabb.max_bounds[2] = aabb_end.z; + } +} + Lightmapper::BakeError LightmapperRD::_blit_meshes_into_atlas(int p_max_texture_size, Vector<Ref<Image>> &albedo_images, Vector<Ref<Image>> &emission_images, AABB &bounds, Size2i &atlas_size, int &atlas_slices, BakeStepFunc p_step_function, void *p_bake_userdata) { Vector<Size2i> sizes; @@ -281,7 +337,7 @@ Lightmapper::BakeError LightmapperRD::_blit_meshes_into_atlas(int p_max_texture_ return BAKE_OK; } -void LightmapperRD::_create_acceleration_structures(RenderingDevice *rd, Size2i atlas_size, int atlas_slices, AABB &bounds, int grid_size, Vector<Probe> &p_probe_positions, GenerateProbes p_generate_probes, Vector<int> &slice_triangle_count, Vector<int> &slice_seam_count, RID &vertex_buffer, RID &triangle_buffer, RID &lights_buffer, RID &triangle_cell_indices_buffer, RID &probe_positions_buffer, RID &grid_texture, RID &seams_buffer, BakeStepFunc p_step_function, void *p_bake_userdata) { +void LightmapperRD::_create_acceleration_structures(RenderingDevice *rd, Size2i atlas_size, int atlas_slices, AABB &bounds, int grid_size, uint32_t p_cluster_size, Vector<Probe> &p_probe_positions, GenerateProbes p_generate_probes, Vector<int> &slice_triangle_count, Vector<int> &slice_seam_count, RID &vertex_buffer, RID &triangle_buffer, RID &lights_buffer, RID &r_triangle_indices_buffer, RID &r_cluster_indices_buffer, RID &r_cluster_aabbs_buffer, RID &probe_positions_buffer, RID &grid_texture, RID &seams_buffer, BakeStepFunc p_step_function, void *p_bake_userdata) { HashMap<Vertex, uint32_t, VertexHash> vertex_map; //fill triangles array and vertex array @@ -433,31 +489,70 @@ void LightmapperRD::_create_acceleration_structures(RenderingDevice *rd, Size2i //sort it triangle_sort.sort(); + LocalVector<uint32_t> cluster_indices; + LocalVector<ClusterAABB> cluster_aabbs; Vector<uint32_t> triangle_indices; triangle_indices.resize(triangle_sort.size()); Vector<uint32_t> grid_indices; grid_indices.resize(grid_size * grid_size * grid_size * 2); memset(grid_indices.ptrw(), 0, grid_indices.size() * sizeof(uint32_t)); - Vector<bool> solid; - solid.resize(grid_size * grid_size * grid_size); - memset(solid.ptrw(), 0, solid.size() * sizeof(bool)); { - uint32_t *tiw = triangle_indices.ptrw(); + // Fill grid with cell indices. uint32_t last_cell = 0xFFFFFFFF; uint32_t *giw = grid_indices.ptrw(); - bool *solidw = solid.ptrw(); + uint32_t cluster_count = 0; + uint32_t solid_cell_count = 0; for (uint32_t i = 0; i < triangle_sort.size(); i++) { uint32_t cell = triangle_sort[i].cell_index; if (cell != last_cell) { - //cell changed, update pointer to indices - giw[cell * 2 + 1] = i; - solidw[cell] = true; + giw[cell * 2 + 1] = solid_cell_count; + solid_cell_count++; } - tiw[i] = triangle_sort[i].triangle_index; - giw[cell * 2]++; //update counter + + if ((giw[cell * 2] % p_cluster_size) == 0) { + // Add an extra cluster every time the triangle counter reaches a multiple of the cluster size. + cluster_count++; + } + + giw[cell * 2]++; last_cell = cell; } + + // Build fixed-size triangle clusters for all the cells to speed up the traversal. A cell can hold multiple clusters that each contain a fixed + // amount of triangles and an AABB. The tracer will check against the AABBs first to know whether it needs to visit the cell's triangles. + // + // The building algorithm will divide the triangles recursively contained inside each cell, sorting by the longest axis of the AABB on each step. + // + // - If the amount of triangles is less or equal to the cluster size, the AABB will be stored and the algorithm stops. + // + // - The division by two is increased to the next power of two of half the amount of triangles (with cluster size as the minimum value) to + // ensure the first half always fills the cluster. + + cluster_indices.resize(solid_cell_count * 2); + cluster_aabbs.resize(cluster_count); + + uint32_t i = 0; + uint32_t cluster_index = 0; + uint32_t solid_cell_index = 0; + uint32_t *tiw = triangle_indices.ptrw(); + while (i < triangle_sort.size()) { + cluster_indices[solid_cell_index * 2] = cluster_index; + cluster_indices[solid_cell_index * 2 + 1] = i; + + uint32_t cell = triangle_sort[i].cell_index; + uint32_t triangle_count = giw[cell * 2]; + uint32_t cell_cluster_count = (triangle_count + p_cluster_size - 1) / p_cluster_size; + _sort_triangle_clusters(p_cluster_size, cluster_index, i, triangle_count, triangle_sort, cluster_aabbs); + + for (uint32_t j = 0; j < triangle_count; j++) { + tiw[i + j] = triangle_sort[i + j].triangle_index; + } + + i += triangle_count; + cluster_index += cell_cluster_count; + solid_cell_index++; + } } #if 0 for (int i = 0; i < grid_size; i++) { @@ -507,7 +602,13 @@ void LightmapperRD::_create_acceleration_structures(RenderingDevice *rd, Size2i triangle_buffer = rd->storage_buffer_create(tb.size(), tb); Vector<uint8_t> tib = triangle_indices.to_byte_array(); - triangle_cell_indices_buffer = rd->storage_buffer_create(tib.size(), tib); + r_triangle_indices_buffer = rd->storage_buffer_create(tib.size(), tib); + + Vector<uint8_t> cib = cluster_indices.to_byte_array(); + r_cluster_indices_buffer = rd->storage_buffer_create(cib.size(), cib); + + Vector<uint8_t> cab = cluster_aabbs.to_byte_array(); + r_cluster_aabbs_buffer = rd->storage_buffer_create(cab.size(), cab); Vector<uint8_t> lb = lights.to_byte_array(); if (lb.size() == 0) { @@ -1020,24 +1121,29 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d RID vertex_buffer; RID triangle_buffer; RID lights_buffer; - RID triangle_cell_indices_buffer; + RID triangle_indices_buffer; + RID cluster_indices_buffer; + RID cluster_aabbs_buffer; RID grid_texture; RID seams_buffer; RID probe_positions_buffer; Vector<int> slice_seam_count; -#define FREE_BUFFERS \ - rd->free(bake_parameters_buffer); \ - rd->free(vertex_buffer); \ - rd->free(triangle_buffer); \ - rd->free(lights_buffer); \ - rd->free(triangle_cell_indices_buffer); \ - rd->free(grid_texture); \ - rd->free(seams_buffer); \ +#define FREE_BUFFERS \ + rd->free(bake_parameters_buffer); \ + rd->free(vertex_buffer); \ + rd->free(triangle_buffer); \ + rd->free(lights_buffer); \ + rd->free(triangle_indices_buffer); \ + rd->free(cluster_indices_buffer); \ + rd->free(cluster_aabbs_buffer); \ + rd->free(grid_texture); \ + rd->free(seams_buffer); \ rd->free(probe_positions_buffer); - _create_acceleration_structures(rd, atlas_size, atlas_slices, bounds, grid_size, probe_positions, p_generate_probes, slice_triangle_count, slice_seam_count, vertex_buffer, triangle_buffer, lights_buffer, triangle_cell_indices_buffer, probe_positions_buffer, grid_texture, seams_buffer, p_step_function, p_bake_userdata); + const uint32_t cluster_size = 16; + _create_acceleration_structures(rd, atlas_size, atlas_slices, bounds, grid_size, cluster_size, probe_positions, p_generate_probes, slice_triangle_count, slice_seam_count, vertex_buffer, triangle_buffer, lights_buffer, triangle_indices_buffer, cluster_indices_buffer, cluster_aabbs_buffer, probe_positions_buffer, grid_texture, seams_buffer, p_step_function, p_bake_userdata); // Create global bake parameters buffer. BakeParameters bake_parameters; @@ -1133,7 +1239,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d RD::Uniform u; u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; u.binding = 3; - u.append_id(triangle_cell_indices_buffer); + u.append_id(triangle_indices_buffer); base_uniforms.push_back(u); } { @@ -1185,6 +1291,20 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d u.append_id(sampler); base_uniforms.push_back(u); } + { + RD::Uniform u; + u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; + u.binding = 11; + u.append_id(cluster_indices_buffer); + base_uniforms.push_back(u); + } + { + RD::Uniform u; + u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; + u.binding = 12; + u.append_id(cluster_aabbs_buffer); + base_uniforms.push_back(u); + } } RID raster_base_uniform = rd->uniform_set_create(base_uniforms, rasterize_shader, 0); @@ -1230,6 +1350,8 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d Ref<RDShaderFile> compute_shader; String defines = ""; + defines += "\n#define CLUSTER_SIZE " + uitos(cluster_size) + "\n"; + if (p_bake_sh) { defines += "\n#define USE_SH_LIGHTMAPS\n"; } diff --git a/modules/lightmapper_rd/lightmapper_rd.h b/modules/lightmapper_rd/lightmapper_rd.h index 8c1c4deba6..5414048ddc 100644 --- a/modules/lightmapper_rd/lightmapper_rd.h +++ b/modules/lightmapper_rd/lightmapper_rd.h @@ -192,6 +192,13 @@ class LightmapperRD : public Lightmapper { } }; + struct ClusterAABB { + float min_bounds[3]; + float pad0 = 0.0f; + float max_bounds[3]; + float pad1 = 0.0f; + }; + Vector<MeshInstance> mesh_instances; Vector<Light> lights; @@ -199,12 +206,22 @@ class LightmapperRD : public Lightmapper { struct TriangleSort { uint32_t cell_index = 0; uint32_t triangle_index = 0; + AABB triangle_aabb; + bool operator<(const TriangleSort &p_triangle_sort) const { return cell_index < p_triangle_sort.cell_index; //sorting by triangle index in this case makes no sense } }; + template <int T> + struct TriangleSortAxis { + bool operator()(const TriangleSort &p_a, const TriangleSort &p_b) const { + return p_a.triangle_aabb.get_center()[T] < p_b.triangle_aabb.get_center()[T]; + } + }; + void _plot_triangle_into_triangle_index_list(int p_size, const Vector3i &p_ofs, const AABB &p_bounds, const Vector3 p_points[3], uint32_t p_triangle_index, LocalVector<TriangleSort> &triangles, uint32_t p_grid_size); + void _sort_triangle_clusters(uint32_t p_cluster_size, uint32_t p_cluster_index, uint32_t p_index_start, uint32_t p_count, LocalVector<TriangleSort> &p_triangle_sort, LocalVector<ClusterAABB> &p_cluster_aabb); struct RasterPushConstant { float atlas_size[2] = {}; @@ -250,7 +267,7 @@ class LightmapperRD : public Lightmapper { }; BakeError _blit_meshes_into_atlas(int p_max_texture_size, Vector<Ref<Image>> &albedo_images, Vector<Ref<Image>> &emission_images, AABB &bounds, Size2i &atlas_size, int &atlas_slices, BakeStepFunc p_step_function, void *p_bake_userdata); - void _create_acceleration_structures(RenderingDevice *rd, Size2i atlas_size, int atlas_slices, AABB &bounds, int grid_size, Vector<Probe> &probe_positions, GenerateProbes p_generate_probes, Vector<int> &slice_triangle_count, Vector<int> &slice_seam_count, RID &vertex_buffer, RID &triangle_buffer, RID &lights_buffer, RID &triangle_cell_indices_buffer, RID &probe_positions_buffer, RID &grid_texture, RID &seams_buffer, BakeStepFunc p_step_function, void *p_bake_userdata); + void _create_acceleration_structures(RenderingDevice *rd, Size2i atlas_size, int atlas_slices, AABB &bounds, int grid_size, uint32_t p_cluster_size, Vector<Probe> &probe_positions, GenerateProbes p_generate_probes, Vector<int> &slice_triangle_count, Vector<int> &slice_seam_count, RID &vertex_buffer, RID &triangle_buffer, RID &lights_buffer, RID &r_triangle_indices_buffer, RID &r_cluster_indices_buffer, RID &r_cluster_aabbs_buffer, RID &probe_positions_buffer, RID &grid_texture, RID &seams_buffer, BakeStepFunc p_step_function, void *p_bake_userdata); void _raster_geometry(RenderingDevice *rd, Size2i atlas_size, int atlas_slices, int grid_size, AABB bounds, float p_bias, Vector<int> slice_triangle_count, RID position_tex, RID unocclude_tex, RID normal_tex, RID raster_depth_buffer, RID rasterize_shader, RID raster_base_uniform); BakeError _dilate(RenderingDevice *rd, Ref<RDShaderFile> &compute_shader, RID &compute_base_uniform_set, PushConstant &push_constant, RID &source_light_tex, RID &dest_light_tex, const Size2i &atlas_size, int atlas_slices); diff --git a/modules/lightmapper_rd/lm_common_inc.glsl b/modules/lightmapper_rd/lm_common_inc.glsl index c91f06d0f3..98d11b9e69 100644 --- a/modules/lightmapper_rd/lm_common_inc.glsl +++ b/modules/lightmapper_rd/lm_common_inc.glsl @@ -42,15 +42,22 @@ struct Triangle { uint pad1; }; +struct ClusterAABB { + vec3 min_bounds; + uint pad0; + vec3 max_bounds; + uint pad1; +}; + layout(set = 0, binding = 2, std430) restrict readonly buffer Triangles { Triangle data[]; } triangles; -layout(set = 0, binding = 3, std430) restrict readonly buffer GridIndices { +layout(set = 0, binding = 3, std430) restrict readonly buffer TriangleIndices { uint data[]; } -grid_indices; +triangle_indices; #define LIGHT_TYPE_DIRECTIONAL 0 #define LIGHT_TYPE_OMNI 1 @@ -104,6 +111,16 @@ layout(set = 0, binding = 9) uniform texture2DArray emission_tex; layout(set = 0, binding = 10) uniform sampler linear_sampler; +layout(set = 0, binding = 11, std430) restrict readonly buffer ClusterIndices { + uint data[]; +} +cluster_indices; + +layout(set = 0, binding = 12, std430) restrict readonly buffer ClusterAABBs { + ClusterAABB data[]; +} +cluster_aabbs; + // Fragment action constants const uint FA_NONE = 0; const uint FA_SMOOTHEN_POSITION = 1; diff --git a/modules/lightmapper_rd/lm_compute.glsl b/modules/lightmapper_rd/lm_compute.glsl index 572e6d55d8..a2a480043a 100644 --- a/modules/lightmapper_rd/lm_compute.glsl +++ b/modules/lightmapper_rd/lm_compute.glsl @@ -119,6 +119,17 @@ const uint RAY_FRONT = 1; const uint RAY_BACK = 2; const uint RAY_ANY = 3; +bool ray_box_test(vec3 p_from, vec3 p_inv_dir, vec3 p_box_min, vec3 p_box_max) { + vec3 t0 = (p_box_min - p_from) * p_inv_dir; + vec3 t1 = (p_box_max - p_from) * p_inv_dir; + vec3 tmin = min(t0, t1), tmax = max(t0, t1); + return max(tmin.x, max(tmin.y, tmin.z)) <= min(tmax.x, min(tmax.y, tmax.z)); +} + +#if CLUSTER_SIZE > 32 +#define CLUSTER_TRIANGLE_ITERATION +#endif + uint trace_ray(vec3 p_from, vec3 p_to, bool p_any_hit, out float r_distance, out vec3 r_normal, out uint r_triangle, out vec3 r_barycentric) { // World coordinates. vec3 rel = p_to - p_from; @@ -142,60 +153,106 @@ uint trace_ray(vec3 p_from, vec3 p_to, bool p_any_hit, out float r_distance, out uint iters = 0; while (all(greaterThanEqual(icell, ivec3(0))) && all(lessThan(icell, ivec3(bake_params.grid_size))) && (iters < 1000)) { uvec2 cell_data = texelFetch(usampler3D(grid, linear_sampler), icell, 0).xy; - if (cell_data.x > 0) { //triangles here + uint triangle_count = cell_data.x; + if (triangle_count > 0) { uint hit = RAY_MISS; float best_distance = 1e20; - - for (uint i = 0; i < cell_data.x; i++) { - uint tidx = grid_indices.data[cell_data.y + i]; - - // Ray-Box test. - Triangle triangle = triangles.data[tidx]; - vec3 t0 = (triangle.min_bounds - p_from) * inv_dir; - vec3 t1 = (triangle.max_bounds - p_from) * inv_dir; - vec3 tmin = min(t0, t1), tmax = max(t0, t1); - - if (max(tmin.x, max(tmin.y, tmin.z)) > min(tmax.x, min(tmax.y, tmax.z))) { - continue; // Ray-Box test failed. - } - - // Prepare triangle vertices. - vec3 vtx0 = vertices.data[triangle.indices.x].position; - vec3 vtx1 = vertices.data[triangle.indices.y].position; - vec3 vtx2 = vertices.data[triangle.indices.z].position; - vec3 normal = -normalize(cross((vtx0 - vtx1), (vtx0 - vtx2))); - bool backface = dot(normal, dir) >= 0.0; - float distance; - vec3 barycentric; - if (ray_hits_triangle(p_from, dir, rel_len, vtx0, vtx1, vtx2, distance, barycentric)) { - if (p_any_hit) { - // Return early if any hit was requested. - return RAY_ANY; + uint cluster_start = cluster_indices.data[cell_data.y * 2]; + uint cell_triangle_start = cluster_indices.data[cell_data.y * 2 + 1]; + uint cluster_count = (triangle_count + CLUSTER_SIZE - 1) / CLUSTER_SIZE; + uint cluster_base_index = 0; + while (cluster_base_index < cluster_count) { + // To minimize divergence, all Ray-AABB tests on the clusters contained in the cell are performed + // before checking against the triangles. We do this 32 clusters at a time and store the intersected + // clusters on each bit of the 32-bit integer. + uint cluster_test_count = min(32, cluster_count - cluster_base_index); + uint cluster_hits = 0; + for (uint i = 0; i < cluster_test_count; i++) { + uint cluster_index = cluster_start + cluster_base_index + i; + ClusterAABB cluster_aabb = cluster_aabbs.data[cluster_index]; + if (ray_box_test(p_from, inv_dir, cluster_aabb.min_bounds, cluster_aabb.max_bounds)) { + cluster_hits |= (1 << i); } + } - vec3 position = p_from + dir * distance; - vec3 hit_cell = (position - bake_params.to_cell_offset) * bake_params.to_cell_size; - if (icell != ivec3(hit_cell)) { - // It's possible for the ray to hit a triangle in a position outside the bounds of the cell - // if it's large enough to cover multiple ones. The hit must be ignored if this is the case. - continue; - } + // Check the triangles in any of the clusters that were intersected by toggling off the bits in the + // 32-bit integer counter until no bits are left. + while (cluster_hits > 0) { + uint cluster_index = findLSB(cluster_hits); + cluster_hits &= ~(1 << cluster_index); + cluster_index += cluster_base_index; + + // Do the same divergence execution trick with triangles as well. + uint triangle_base_index = 0; +#ifdef CLUSTER_TRIANGLE_ITERATION + while (triangle_base_index < triangle_count) +#endif + { + uint triangle_start_index = cell_triangle_start + cluster_index * CLUSTER_SIZE + triangle_base_index; + uint triangle_test_count = min(CLUSTER_SIZE, triangle_count - triangle_base_index); + uint triangle_hits = 0; + for (uint i = 0; i < triangle_test_count; i++) { + uint triangle_index = triangle_indices.data[triangle_start_index + i]; + if (ray_box_test(p_from, inv_dir, triangles.data[triangle_index].min_bounds, triangles.data[triangle_index].max_bounds)) { + triangle_hits |= (1 << i); + } + } - if (!backface) { - // The case of meshes having both a front and back face in the same plane is more common than expected. - // If this is a front-face, bias it closer to the ray origin, so it always wins over the back-face. - distance = max(bake_params.bias, distance - bake_params.bias); - } + while (triangle_hits > 0) { + uint cluster_triangle_index = findLSB(triangle_hits); + triangle_hits &= ~(1 << cluster_triangle_index); + cluster_triangle_index += triangle_start_index; + + uint triangle_index = triangle_indices.data[cluster_triangle_index]; + Triangle triangle = triangles.data[triangle_index]; + + // Gather the triangle vertex positions. + vec3 vtx0 = vertices.data[triangle.indices.x].position; + vec3 vtx1 = vertices.data[triangle.indices.y].position; + vec3 vtx2 = vertices.data[triangle.indices.z].position; + vec3 normal = -normalize(cross((vtx0 - vtx1), (vtx0 - vtx2))); + bool backface = dot(normal, dir) >= 0.0; + float distance; + vec3 barycentric; + if (ray_hits_triangle(p_from, dir, rel_len, vtx0, vtx1, vtx2, distance, barycentric)) { + if (p_any_hit) { + // Return early if any hit was requested. + return RAY_ANY; + } + + vec3 position = p_from + dir * distance; + vec3 hit_cell = (position - bake_params.to_cell_offset) * bake_params.to_cell_size; + if (icell != ivec3(hit_cell)) { + // It's possible for the ray to hit a triangle in a position outside the bounds of the cell + // if it's large enough to cover multiple ones. The hit must be ignored if this is the case. + continue; + } + + if (!backface) { + // The case of meshes having both a front and back face in the same plane is more common than + // expected, so if this is a front-face, bias it closer to the ray origin, so it always wins + // over the back-face. + distance = max(bake_params.bias, distance - bake_params.bias); + } + + if (distance < best_distance) { + hit = backface ? RAY_BACK : RAY_FRONT; + best_distance = distance; + r_distance = distance; + r_normal = normal; + r_triangle = triangle_index; + r_barycentric = barycentric; + } + } + } - if (distance < best_distance) { - hit = backface ? RAY_BACK : RAY_FRONT; - best_distance = distance; - r_distance = distance; - r_normal = normal; - r_triangle = tidx; - r_barycentric = barycentric; +#ifdef CLUSTER_TRIANGLE_ITERATION + triangle_base_index += CLUSTER_SIZE; +#endif } } + + cluster_base_index += 32; } if (hit != RAY_MISS) { diff --git a/modules/mono/build_scripts/build_assemblies.py b/modules/mono/build_scripts/build_assemblies.py index 580f51c973..90a517cc40 100755 --- a/modules/mono/build_scripts/build_assemblies.py +++ b/modules/mono/build_scripts/build_assemblies.py @@ -316,9 +316,35 @@ def generate_sdk_package_versions(): f.write(props) f.close() + # Also write the versioned docs URL to a constant for the Source Generators. + + constants = """namespace Godot.SourceGenerators +{{ + partial class Common + {{ + public const string VersionDocsUrl = "https://docs.godotengine.org/en/{docs_branch}"; + }} +}} +""".format( + **version_info + ) + + generators_dir = os.path.join( + dirname(script_path), + "editor", + "Godot.NET.Sdk", + "Godot.SourceGenerators", + "Generated", + ) + os.makedirs(generators_dir, exist_ok=True) + + with open(os.path.join(generators_dir, "Common.Constants.cs"), "w") as f: + f.write(constants) + f.close() + def build_all(msbuild_tool, module_dir, output_dir, godot_platform, dev_debug, push_nupkgs_local, precision): - # Generate SdkPackageVersions.props + # Generate SdkPackageVersions.props and VersionDocsUrl constant generate_sdk_package_versions() # Godot API diff --git a/modules/mono/editor/Godot.NET.Sdk/.gitignore b/modules/mono/editor/Godot.NET.Sdk/.gitignore new file mode 100644 index 0000000000..55ec4bcc64 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/.gitignore @@ -0,0 +1,2 @@ +# Generated sources directories +Godot.SourceGenerators/Generated diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/NestedClass.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/NestedClass.cs new file mode 100644 index 0000000000..3021f57115 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/NestedClass.cs @@ -0,0 +1,22 @@ +using System; + +namespace Godot.SourceGenerators.Sample; + +public partial class NestedClass : GodotObject +{ + public partial class NestedClass2 : GodotObject + { + public partial class NestedClass3 : GodotObject + { + [Signal] + public delegate void MySignalEventHandler(string str, int num); + + [Export] private String field_String = "foo"; + [Export] private String property_String { get; set; } = "foo"; + + private void Method() + { + } + } + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs index 72614dd7e0..df35091596 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs @@ -5,8 +5,10 @@ using Microsoft.CodeAnalysis.Diagnostics; namespace Godot.SourceGenerators { - public static class Common + public static partial class Common { + private static readonly string _helpLinkFormat = $"{VersionDocsUrl}/tutorials/scripting/c_sharp/diagnostics/{{0}}.html"; + public static void ReportNonPartialGodotScriptClass( GeneratorExecutionContext context, ClassDeclarationSyntax cds, INamedTypeSymbol symbol @@ -14,9 +16,9 @@ namespace Godot.SourceGenerators { string message = "Missing partial modifier on declaration of type '" + - $"{symbol.FullQualifiedNameOmitGlobal()}' which is a subclass of '{GodotClasses.GodotObject}'"; + $"{symbol.FullQualifiedNameOmitGlobal()}' that derives from '{GodotClasses.GodotObject}'"; - string description = $"{message}. Subclasses of '{GodotClasses.GodotObject}' " + + string description = $"{message}. Classes that derive from '{GodotClasses.GodotObject}' " + "must be declared with the partial modifier."; context.ReportDiagnostic(Diagnostic.Create( @@ -26,7 +28,8 @@ namespace Godot.SourceGenerators category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - description), + description, + helpLinkUri: string.Format(_helpLinkFormat, "GD0001")), cds.GetLocation(), cds.SyntaxTree.FilePath)); } @@ -46,9 +49,9 @@ namespace Godot.SourceGenerators string message = $"Missing partial modifier on declaration of type '{fullQualifiedName}', " + - $"which contains one or more subclasses of '{GodotClasses.GodotObject}'"; + $"which contains nested classes that derive from '{GodotClasses.GodotObject}'"; - string description = $"{message}. Subclasses of '{GodotClasses.GodotObject}' and their " + + string description = $"{message}. Classes that derive from '{GodotClasses.GodotObject}' and their " + "containing types must be declared with the partial modifier."; context.ReportDiagnostic(Diagnostic.Create( @@ -58,7 +61,8 @@ namespace Godot.SourceGenerators category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - description), + description, + helpLinkUri: string.Format(_helpLinkFormat, "GD0002")), outerTypeDeclSyntax.GetLocation(), outerTypeDeclSyntax.SyntaxTree.FilePath)); } @@ -85,7 +89,8 @@ namespace Godot.SourceGenerators category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - description), + description, + helpLinkUri: string.Format(_helpLinkFormat, "GD0101")), location, location?.SourceTree?.FilePath)); } @@ -111,7 +116,8 @@ namespace Godot.SourceGenerators category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - description), + description, + helpLinkUri: string.Format(_helpLinkFormat, "GD0102")), location, location?.SourceTree?.FilePath)); } @@ -139,7 +145,8 @@ namespace Godot.SourceGenerators category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - description), + description, + helpLinkUri: string.Format(_helpLinkFormat, "GD1003")), location, location?.SourceTree?.FilePath)); } @@ -163,7 +170,8 @@ namespace Godot.SourceGenerators category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - description), + description, + helpLinkUri: string.Format(_helpLinkFormat, "GD0104")), location, location?.SourceTree?.FilePath)); } @@ -189,7 +197,8 @@ namespace Godot.SourceGenerators category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - description), + description, + helpLinkUri: string.Format(_helpLinkFormat, "GD0105")), location, location?.SourceTree?.FilePath)); } @@ -215,7 +224,8 @@ namespace Godot.SourceGenerators category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - description), + description, + helpLinkUri: string.Format(_helpLinkFormat, "GD0106")), location, location?.SourceTree?.FilePath)); } @@ -240,7 +250,8 @@ namespace Godot.SourceGenerators category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - description), + description, + helpLinkUri: string.Format(_helpLinkFormat, "GD0201")), location, location?.SourceTree?.FilePath)); } @@ -264,7 +275,8 @@ namespace Godot.SourceGenerators category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - description), + description, + helpLinkUri: string.Format(_helpLinkFormat, "GD0202")), location, location?.SourceTree?.FilePath)); } @@ -288,7 +300,8 @@ namespace Godot.SourceGenerators category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - description), + description, + helpLinkUri: string.Format(_helpLinkFormat, "GD0203")), location, location?.SourceTree?.FilePath)); } @@ -300,7 +313,8 @@ namespace Godot.SourceGenerators category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - "The generic type argument must be a Variant compatible type. Use a Variant compatible type as the generic type argument."); + "The generic type argument must be a Variant compatible type. Use a Variant compatible type as the generic type argument.", + helpLinkUri: string.Format(_helpLinkFormat, "GD0301")); public static void ReportGenericTypeArgumentMustBeVariant( SyntaxNodeAnalysisContext context, @@ -319,7 +333,8 @@ namespace Godot.SourceGenerators category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - description), + description, + helpLinkUri: string.Format(_helpLinkFormat, "GD0301")), typeArgumentSyntax.GetLocation(), typeArgumentSyntax.SyntaxTree.FilePath)); } @@ -331,7 +346,8 @@ namespace Godot.SourceGenerators category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - "The generic type argument must be a Variant type. Use a Variant type as the generic type argument."); + "The generic type argument must be a Variant type. Use a Variant type as the generic type argument.", + helpLinkUri: string.Format(_helpLinkFormat, "GD0302")); public static void ReportGenericTypeParameterMustBeVariantAnnotated( SyntaxNodeAnalysisContext context, @@ -349,7 +365,8 @@ namespace Godot.SourceGenerators category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - description), + description, + helpLinkUri: string.Format(_helpLinkFormat, "GD0302")), typeArgumentSyntax.GetLocation(), typeArgumentSyntax.SyntaxTree.FilePath)); } @@ -361,7 +378,8 @@ namespace Godot.SourceGenerators category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - "The generic type argument must be a Variant type. Use a Variant type as the generic type argument."); + "The generic type argument must be a Variant type. Use a Variant type as the generic type argument.", + helpLinkUri: string.Format(_helpLinkFormat, "GD0303")); public static void ReportTypeArgumentParentSymbolUnhandled( SyntaxNodeAnalysisContext context, @@ -380,7 +398,8 @@ namespace Godot.SourceGenerators category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - description), + description, + helpLinkUri: string.Format(_helpLinkFormat, "GD0303")), typeArgumentSyntax.GetLocation(), typeArgumentSyntax.SyntaxTree.FilePath)); } @@ -388,11 +407,12 @@ namespace Godot.SourceGenerators public static readonly DiagnosticDescriptor GlobalClassMustDeriveFromGodotObjectRule = new DiagnosticDescriptor(id: "GD0401", title: "The class must derive from GodotObject or a derived class", - messageFormat: "The class '{0}' must derive from GodotObject or a derived class.", + messageFormat: "The class '{0}' must derive from GodotObject or a derived class", category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - "The class must derive from GodotObject or a derived class. Change the base class or remove the '[GlobalClass]' attribute."); + "The class must derive from GodotObject or a derived class. Change the base class or remove the '[GlobalClass]' attribute.", + helpLinkUri: string.Format(_helpLinkFormat, "GD0401")); public static void ReportGlobalClassMustDeriveFromGodotObject( SyntaxNodeAnalysisContext context, @@ -410,7 +430,8 @@ namespace Godot.SourceGenerators category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - description), + description, + helpLinkUri: string.Format(_helpLinkFormat, "GD0401")), classSyntax.GetLocation(), classSyntax.SyntaxTree.FilePath)); } @@ -422,7 +443,8 @@ namespace Godot.SourceGenerators category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - "The class must be a non-generic type. Remove the generic arguments or the '[GlobalClass]' attribute."); + "The class must be a non-generic type. Remove the generic arguments or the '[GlobalClass]' attribute.", + helpLinkUri: string.Format(_helpLinkFormat, "GD0401")); public static void ReportGlobalClassMustNotBeGeneric( SyntaxNodeAnalysisContext context, @@ -440,7 +462,8 @@ namespace Godot.SourceGenerators category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - description), + description, + helpLinkUri: string.Format(_helpLinkFormat, "GD0402")), classSyntax.GetLocation(), classSyntax.SyntaxTree.FilePath)); } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs index 7b643914bb..7232e4d7d7 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs @@ -105,16 +105,20 @@ namespace Godot.SourceGenerators if (isInnerClass) { var containingType = symbol.ContainingType; + AppendPartialContainingTypeDeclarations(containingType); - while (containingType != null) + void AppendPartialContainingTypeDeclarations(INamedTypeSymbol? containingType) { + if (containingType == null) + return; + + AppendPartialContainingTypeDeclarations(containingType.ContainingType); + source.Append("partial "); source.Append(containingType.GetDeclarationKeyword()); source.Append(" "); source.Append(containingType.NameWithTypeParameters()); source.Append("\n{\n"); - - containingType = containingType.ContainingType; } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs index 219ab7aa44..de44ada6de 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs @@ -91,16 +91,20 @@ namespace Godot.SourceGenerators if (isInnerClass) { var containingType = symbol.ContainingType; + AppendPartialContainingTypeDeclarations(containingType); - while (containingType != null) + void AppendPartialContainingTypeDeclarations(INamedTypeSymbol? containingType) { + if (containingType == null) + return; + + AppendPartialContainingTypeDeclarations(containingType.ContainingType); + source.Append("partial "); source.Append(containingType.GetDeclarationKeyword()); source.Append(" "); source.Append(containingType.NameWithTypeParameters()); source.Append("\n{\n"); - - containingType = containingType.ContainingType; } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs index 4df16d05f0..fc0bfbf084 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs @@ -88,16 +88,20 @@ namespace Godot.SourceGenerators if (isInnerClass) { var containingType = symbol.ContainingType; + AppendPartialContainingTypeDeclarations(containingType); - while (containingType != null) + void AppendPartialContainingTypeDeclarations(INamedTypeSymbol? containingType) { + if (containingType == null) + return; + + AppendPartialContainingTypeDeclarations(containingType.ContainingType); + source.Append("partial "); source.Append(containingType.GetDeclarationKeyword()); source.Append(" "); source.Append(containingType.NameWithTypeParameters()); source.Append("\n{\n"); - - containingType = containingType.ContainingType; } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs index 9de99414b6..0bc58c2b47 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs @@ -91,16 +91,20 @@ namespace Godot.SourceGenerators if (isInnerClass) { var containingType = symbol.ContainingType; + AppendPartialContainingTypeDeclarations(containingType); - while (containingType != null) + void AppendPartialContainingTypeDeclarations(INamedTypeSymbol? containingType) { + if (containingType == null) + return; + + AppendPartialContainingTypeDeclarations(containingType.ContainingType); + source.Append("partial "); source.Append(containingType.GetDeclarationKeyword()); source.Append(" "); source.Append(containingType.NameWithTypeParameters()); source.Append("\n{\n"); - - containingType = containingType.ContainingType; } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs index 8f2774d5ae..5409d1a961 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs @@ -100,16 +100,20 @@ namespace Godot.SourceGenerators if (isInnerClass) { var containingType = symbol.ContainingType; + AppendPartialContainingTypeDeclarations(containingType); - while (containingType != null) + void AppendPartialContainingTypeDeclarations(INamedTypeSymbol? containingType) { + if (containingType == null) + return; + + AppendPartialContainingTypeDeclarations(containingType.ContainingType); + source.Append("partial "); source.Append(containingType.GetDeclarationKeyword()); source.Append(" "); source.Append(containingType.NameWithTypeParameters()); source.Append("\n{\n"); - - containingType = containingType.ContainingType; } } diff --git a/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/UnmanagedCallbacksGenerator.cs b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/UnmanagedCallbacksGenerator.cs index 6b000cc89b..750e11777d 100644 --- a/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/UnmanagedCallbacksGenerator.cs +++ b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/UnmanagedCallbacksGenerator.cs @@ -128,16 +128,20 @@ using Godot.NativeInterop; if (isInnerClass) { var containingType = symbol.ContainingType; + AppendPartialContainingTypeDeclarations(containingType); - while (containingType != null) + void AppendPartialContainingTypeDeclarations(INamedTypeSymbol? containingType) { + if (containingType == null) + return; + + AppendPartialContainingTypeDeclarations(containingType.ContainingType); + source.Append("partial "); source.Append(containingType.GetDeclarationKeyword()); source.Append(" "); source.Append(containingType.NameWithTypeParameters()); source.Append("\n{\n"); - - containingType = containingType.ContainingType; } } @@ -303,16 +307,20 @@ using Godot.NativeInterop; if (isInnerClass) { var containingType = symbol.ContainingType; + AppendPartialContainingTypeDeclarations(containingType); - while (containingType != null) + void AppendPartialContainingTypeDeclarations(INamedTypeSymbol? containingType) { + if (containingType == null) + return; + + AppendPartialContainingTypeDeclarations(containingType.ContainingType); + source.Append("partial "); source.Append(containingType.GetDeclarationKeyword()); source.Append(" "); source.Append(containingType.NameWithTypeParameters()); source.Append("\n{\n"); - - containingType = containingType.ContainingType; } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs index a7712db737..b4f7b82f60 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs @@ -239,7 +239,7 @@ namespace Godot /// <summary> /// Returns the basis's rotation in the form of Euler angles. - /// The Euler order depends on the [param order] parameter, + /// The Euler order depends on the <paramref name="order"/> parameter, /// by default it uses the YXZ convention: when decomposing, /// first Z, then X, and Y last. The returned vector contains /// the rotation angles in the format (X angle, Y angle, Z angle). @@ -1037,10 +1037,11 @@ namespace Godot } /// <summary> - /// Returns a Vector3 transformed (multiplied) by the transposed basis matrix. - /// - /// Note: This results in a multiplication by the inverse of the - /// basis matrix only if it represents a rotation-reflection. + /// Returns a Vector3 transformed (multiplied) by the inverse basis matrix, + /// under the assumption that the transformation basis is orthonormal (i.e. rotation/reflection + /// is fine, scaling/skew is not). + /// <c>vector * basis</c> is equivalent to <c>basis.Transposed() * vector</c>. See <see cref="Transposed"/>. + /// For transforming by inverse of a non-orthonormal basis (e.g. with scaling) <c>basis.Inverse() * vector</c> can be used instead. See <see cref="Inverse"/>. /// </summary> /// <param name="vector">A Vector3 to inversely transform.</param> /// <param name="basis">The basis matrix transformation to apply.</param> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs index a80d202ef2..155e90ff24 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs @@ -905,7 +905,8 @@ namespace Godot } /// <summary> - /// Returns a Vector4 transformed (multiplied) by the inverse projection. + /// Returns a Vector4 transformed (multiplied) by the transpose of the projection. + /// For transforming by inverse of a projection <c>projection.Inverse() * vector</c> can be used instead. See <see cref="Inverse"/>. /// </summary> /// <param name="proj">The projection to apply.</param> /// <param name="vector">A Vector4 to transform.</param> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs index 39e1b7e4b8..3d45913586 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs @@ -644,6 +644,7 @@ namespace Godot /// <summary> /// Returns a Vector3 rotated (multiplied) by the inverse quaternion. + /// <c>vector * quaternion</c> is equivalent to <c>quaternion.Inverse() * vector</c>. See <see cref="Inverse"/>. /// </summary> /// <param name="vector">A Vector3 to inversely rotate.</param> /// <param name="quaternion">The quaternion to rotate by.</param> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs index 0e3e54a0c2..386f587464 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs @@ -126,7 +126,7 @@ namespace Godot /// <summary> /// Returns the inverse of the transform, under the assumption that - /// the transformation is composed of rotation, scaling, and translation. + /// the basis is invertible (must have non-zero determinant). /// </summary> /// <seealso cref="Inverse"/> /// <returns>The inverse transformation matrix.</returns> @@ -180,11 +180,12 @@ namespace Godot } /// <summary> - /// Returns a vector transformed (multiplied) by the inverse basis matrix. + /// Returns a vector transformed (multiplied) by the inverse basis matrix, + /// under the assumption that the basis is orthonormal (i.e. rotation/reflection + /// is fine, scaling/skew is not). /// This method does not account for translation (the <see cref="Origin"/> vector). - /// - /// Note: This results in a multiplication by the inverse of the - /// basis matrix only if it represents a rotation-reflection. + /// <c>transform.BasisXformInv(vector)</c> is equivalent to <c>transform.Inverse().BasisXform(vector)</c>. See <see cref="Inverse"/>. + /// For non-orthonormal transforms (e.g. with scaling) <c>transform.AffineInverse().BasisXform(vector)</c> can be used instead. See <see cref="AffineInverse"/>. /// </summary> /// <seealso cref="BasisXform(Vector2)"/> /// <param name="v">A vector to inversely transform.</param> @@ -213,8 +214,9 @@ namespace Godot /// <summary> /// Returns the inverse of the transform, under the assumption that - /// the transformation is composed of rotation and translation - /// (no scaling, use <see cref="AffineInverse"/> for transforms with scaling). + /// the transformation basis is orthonormal (i.e. rotation/reflection + /// is fine, scaling/skew is not). Use <see cref="AffineInverse"/> for + /// non-orthonormal transforms (e.g. with scaling). /// </summary> /// <returns>The inverse matrix.</returns> public readonly Transform2D Inverse() @@ -480,7 +482,11 @@ namespace Godot } /// <summary> - /// Returns a Vector2 transformed (multiplied) by the inverse transformation matrix. + /// Returns a Vector2 transformed (multiplied) by the inverse transformation matrix, + /// under the assumption that the transformation basis is orthonormal (i.e. rotation/reflection + /// is fine, scaling/skew is not). + /// <c>vector * transform</c> is equivalent to <c>transform.Inverse() * vector</c>. See <see cref="Inverse"/>. + /// For transforming by inverse of an affine transformation (e.g. with scaling) <c>transform.AffineInverse() * vector</c> can be used instead. See <see cref="AffineInverse"/>. /// </summary> /// <param name="vector">A Vector2 to inversely transform.</param> /// <param name="transform">The transformation to apply.</param> @@ -507,7 +513,11 @@ namespace Godot } /// <summary> - /// Returns a Rect2 transformed (multiplied) by the inverse transformation matrix. + /// Returns a Rect2 transformed (multiplied) by the inverse transformation matrix, + /// under the assumption that the transformation basis is orthonormal (i.e. rotation/reflection + /// is fine, scaling/skew is not). + /// <c>rect * transform</c> is equivalent to <c>transform.Inverse() * rect</c>. See <see cref="Inverse"/>. + /// For transforming by inverse of an affine transformation (e.g. with scaling) <c>transform.AffineInverse() * rect</c> can be used instead. See <see cref="AffineInverse"/>. /// </summary> /// <param name="rect">A Rect2 to inversely transform.</param> /// <param name="transform">The transformation to apply.</param> @@ -541,7 +551,11 @@ namespace Godot } /// <summary> - /// Returns a copy of the given Vector2[] transformed (multiplied) by the inverse transformation matrix. + /// Returns a copy of the given Vector2[] transformed (multiplied) by the inverse transformation matrix, + /// under the assumption that the transformation basis is orthonormal (i.e. rotation/reflection + /// is fine, scaling/skew is not). + /// <c>array * transform</c> is equivalent to <c>transform.Inverse() * array</c>. See <see cref="Inverse"/>. + /// For transforming by inverse of an affine transformation (e.g. with scaling) <c>transform.AffineInverse() * array</c> can be used instead. See <see cref="AffineInverse"/>. /// </summary> /// <param name="array">A Vector2[] to inversely transform.</param> /// <param name="transform">The transformation to apply.</param> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs index 7b27071df1..2d09259dcb 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs @@ -105,7 +105,7 @@ namespace Godot /// <summary> /// Returns the inverse of the transform, under the assumption that - /// the transformation is composed of rotation, scaling, and translation. + /// the basis is invertible (must have non-zero determinant). /// </summary> /// <seealso cref="Inverse"/> /// <returns>The inverse transformation matrix.</returns> @@ -144,8 +144,9 @@ namespace Godot /// <summary> /// Returns the inverse of the transform, under the assumption that - /// the transformation is composed of rotation and translation - /// (no scaling, use <see cref="AffineInverse"/> for transforms with scaling). + /// the transformation basis is orthonormal (i.e. rotation/reflection + /// is fine, scaling/skew is not). Use <see cref="AffineInverse"/> for + /// non-orthonormal transforms (e.g. with scaling). /// </summary> /// <returns>The inverse matrix.</returns> public readonly Transform3D Inverse() @@ -426,10 +427,11 @@ namespace Godot } /// <summary> - /// Returns a Vector3 transformed (multiplied) by the transposed transformation matrix. - /// - /// Note: This results in a multiplication by the inverse of the - /// transformation matrix only if it represents a rotation-reflection. + /// Returns a Vector3 transformed (multiplied) by the inverse transformation matrix, + /// under the assumption that the transformation basis is orthonormal (i.e. rotation/reflection + /// is fine, scaling/skew is not). + /// <c>vector * transform</c> is equivalent to <c>transform.Inverse() * vector</c>. See <see cref="Inverse"/>. + /// For transforming by inverse of an affine transformation (e.g. with scaling) <c>transform.AffineInverse() * vector</c> can be used instead. See <see cref="AffineInverse"/>. /// </summary> /// <param name="vector">A Vector3 to inversely transform.</param> /// <param name="transform">The transformation to apply.</param> @@ -482,7 +484,11 @@ namespace Godot } /// <summary> - /// Returns an AABB transformed (multiplied) by the inverse transformation matrix. + /// Returns an AABB transformed (multiplied) by the inverse transformation matrix, + /// under the assumption that the transformation basis is orthonormal (i.e. rotation/reflection + /// is fine, scaling/skew is not). + /// <c>aabb * transform</c> is equivalent to <c>transform.Inverse() * aabb</c>. See <see cref="Inverse"/>. + /// For transforming by inverse of an affine transformation (e.g. with scaling) <c>transform.AffineInverse() * aabb</c> can be used instead. See <see cref="AffineInverse"/>. /// </summary> /// <param name="aabb">An AABB to inversely transform.</param> /// <param name="transform">The transformation to apply.</param> @@ -523,6 +529,7 @@ namespace Godot /// <summary> /// Returns a Plane transformed (multiplied) by the inverse transformation matrix. + /// <c>plane * transform</c> is equivalent to <c>transform.AffineInverse() * plane</c>. See <see cref="AffineInverse"/>. /// </summary> /// <param name="plane">A Plane to inversely transform.</param> /// <param name="transform">The transformation to apply.</param> @@ -568,7 +575,11 @@ namespace Godot } /// <summary> - /// Returns a copy of the given Vector3[] transformed (multiplied) by the inverse transformation matrix. + /// Returns a copy of the given Vector3[] transformed (multiplied) by the inverse transformation matrix, + /// under the assumption that the transformation basis is orthonormal (i.e. rotation/reflection + /// is fine, scaling/skew is not). + /// <c>array * transform</c> is equivalent to <c>transform.Inverse() * array</c>. See <see cref="Inverse"/>. + /// For transforming by inverse of an affine transformation (e.g. with scaling) <c>transform.AffineInverse() * array</c> can be used instead. See <see cref="AffineInverse"/>. /// </summary> /// <param name="array">A Vector3[] to inversely transform.</param> /// <param name="transform">The transformation to apply.</param> diff --git a/modules/navigation/nav_region.cpp b/modules/navigation/nav_region.cpp index 3675aae518..09697c7be0 100644 --- a/modules/navigation/nav_region.cpp +++ b/modules/navigation/nav_region.cpp @@ -125,11 +125,11 @@ void NavRegion::update_polygons() { #ifdef DEBUG_ENABLED if (!Math::is_equal_approx(double(map->get_cell_size()), double(mesh->get_cell_size()))) { - ERR_PRINT_ONCE(vformat("Navigation map synchronization error. Attempted to update a navigation region with a navigation mesh that uses a `cell_size` of %s while assigned to a navigation map set to a `cell_size` of %s. The cell size for navigation maps can be changed by using the NavigationServer map_set_cell_size() function. The cell size for default navigation maps can also be changed in the ProjectSettings.", double(map->get_cell_size()), double(mesh->get_cell_size()))); + ERR_PRINT_ONCE(vformat("Navigation map synchronization error. Attempted to update a navigation region with a navigation mesh that uses a `cell_size` of %s while assigned to a navigation map set to a `cell_size` of %s. The cell size for navigation maps can be changed by using the NavigationServer map_set_cell_size() function. The cell size for default navigation maps can also be changed in the ProjectSettings.", double(mesh->get_cell_size()), double(map->get_cell_size()))); } if (!Math::is_equal_approx(double(map->get_cell_height()), double(mesh->get_cell_height()))) { - ERR_PRINT_ONCE(vformat("Navigation map synchronization error. Attempted to update a navigation region with a navigation mesh that uses a `cell_height` of %s while assigned to a navigation map set to a `cell_height` of %s. The cell height for navigation maps can be changed by using the NavigationServer map_set_cell_height() function. The cell height for default navigation maps can also be changed in the ProjectSettings.", double(map->get_cell_height()), double(mesh->get_cell_height()))); + ERR_PRINT_ONCE(vformat("Navigation map synchronization error. Attempted to update a navigation region with a navigation mesh that uses a `cell_height` of %s while assigned to a navigation map set to a `cell_height` of %s. The cell height for navigation maps can be changed by using the NavigationServer map_set_cell_height() function. The cell height for default navigation maps can also be changed in the ProjectSettings.", double(mesh->get_cell_height()), double(map->get_cell_height()))); } if (map && Math::rad_to_deg(map->get_up().angle_to(transform.basis.get_column(1))) >= 90.0f) { diff --git a/modules/openxr/SCsub b/modules/openxr/SCsub index 73a3723ea4..2ccfe46062 100644 --- a/modules/openxr/SCsub +++ b/modules/openxr/SCsub @@ -71,6 +71,7 @@ if env["builtin_openxr"]: # Build the engine using object files khrloader_obj = [] env_thirdparty.add_source_files(khrloader_obj, thirdparty_dir + "/src/xr_generated_dispatch_table.c") + env_thirdparty.add_source_files(khrloader_obj, thirdparty_dir + "/src/xr_generated_dispatch_table_core.c") env_thirdparty.add_source_files(khrloader_obj, thirdparty_dir + "/src/common/filesystem_utils.cpp") env_thirdparty.add_source_files(khrloader_obj, thirdparty_dir + "/src/common/object_info.cpp") |