diff options
29 files changed, 906 insertions, 113 deletions
diff --git a/SConstruct b/SConstruct index b427895520..031f421a63 100644 --- a/SConstruct +++ b/SConstruct @@ -722,9 +722,9 @@ if selected_platform in platform_list: if env.msvc: env.Append(CPPDEFINES=[("_HAS_EXCEPTIONS", 0)]) else: - env.Append(CCFLAGS=["-fno-exceptions"]) + env.Append(CXXFLAGS=["-fno-exceptions"]) elif env.msvc: - env.Append(CCFLAGS=["/EHsc"]) + env.Append(CXXFLAGS=["/EHsc"]) # Configure compiler warnings if env.msvc: # MSVC diff --git a/core/object/make_virtuals.py b/core/object/make_virtuals.py index 0f3cf3916a..79a8df6c8a 100644 --- a/core/object/make_virtuals.py +++ b/core/object/make_virtuals.py @@ -47,8 +47,8 @@ _FORCE_INLINE_ bool _gdvirtual_##m_name##_call($CALLARGS) $CONST { \\ }\\ _FORCE_INLINE_ bool _gdvirtual_##m_name##_overridden() const { \\ ScriptInstance *_script_instance = ((Object*)(this))->get_script_instance();\\ - if (_script_instance) {\\ - return _script_instance->has_method(_gdvirtual_##m_name##_sn);\\ + if (_script_instance && _script_instance->has_method(_gdvirtual_##m_name##_sn)) {\\ + return true;\\ }\\ if (unlikely(_get_extension() && !_gdvirtual_##m_name##_initialized)) {\\ _gdvirtual_##m_name = nullptr;\\ diff --git a/core/string/string_name.cpp b/core/string/string_name.cpp index 4402e44ad4..5a8df07410 100644 --- a/core/string/string_name.cpp +++ b/core/string/string_name.cpp @@ -100,11 +100,9 @@ void StringName::cleanup() { lost_strings++; if (OS::get_singleton()->is_stdout_verbose()) { - if (d->cname) { - print_line("Orphan StringName: " + String(d->cname)); - } else { - print_line("Orphan StringName: " + String(d->name)); - } + String dname = String(d->cname ? d->cname : d->name); + + print_line(vformat("Orphan StringName: %s (static: %d, total: %d)", dname, d->static_count.get(), d->refcount.get())); } } @@ -113,7 +111,7 @@ void StringName::cleanup() { } } if (lost_strings) { - print_verbose("StringName: " + itos(lost_strings) + " unclaimed string names at exit."); + print_verbose(vformat("StringName: %d unclaimed string names at exit.", lost_strings)); } configured = false; } diff --git a/core/variant/variant_parser.cpp b/core/variant/variant_parser.cpp index fea1622222..86e7654090 100644 --- a/core/variant/variant_parser.cpp +++ b/core/variant/variant_parser.cpp @@ -1075,7 +1075,7 @@ Error VariantParser::parse_value(Token &token, Variant &value, Stream *p_stream, return ERR_PARSE_ERROR; } - static HashMap<StringName, Variant::Type> builtin_types; + static HashMap<String, Variant::Type> builtin_types; if (builtin_types.is_empty()) { for (int i = 1; i < Variant::VARIANT_MAX; i++) { builtin_types[Variant::get_type_name((Variant::Type)i)] = (Variant::Type)i; diff --git a/doc/classes/LineEdit.xml b/doc/classes/LineEdit.xml index e2aa13403a..e706e3d6e0 100644 --- a/doc/classes/LineEdit.xml +++ b/doc/classes/LineEdit.xml @@ -269,7 +269,7 @@ If [code]true[/code], every character is replaced with the secret character (see [member secret_character]). </member> <member name="secret_character" type="String" setter="set_secret_character" getter="get_secret_character" default=""•""> - The character to use to mask secret input (defaults to "•"). Only a single character can be used as the secret character. + The character to use to mask secret input. Only a single character can be used as the secret character. If it is longer than one character, only the first one will be used. If it is empty, a space will be used instead. </member> <member name="select_all_on_focus" type="bool" setter="set_select_all_on_focus" getter="is_select_all_on_focus" default="false"> If [code]true[/code], the [LineEdit] will select the whole text when it gains focus. diff --git a/doc/classes/Object.xml b/doc/classes/Object.xml index bb7289cc99..2ffb02096d 100644 --- a/doc/classes/Object.xml +++ b/doc/classes/Object.xml @@ -5,7 +5,7 @@ </brief_description> <description> An advanced [Variant] type. All classes in the engine inherit from Object. Each class may define new properties, methods or signals, which are available to all inheriting classes. For example, a [Sprite2D] instance is able to call [method Node.add_child] because it inherits from [Node]. - You can create new instances, using [code]Object.new()[/code] in GDScript, or [code]new Object[/code] in C#. + You can create new instances, using [code]Object.new()[/code] in GDScript, or [code]new GodotObject[/code] in C#. To delete an Object instance, call [method free]. This is necessary for most classes inheriting Object, because they do not manage memory on their own, and will otherwise cause memory leaks when no longer in use. There are a few classes that perform memory management. For example, [RefCounted] (and by extension [Resource]) deletes itself when no longer referenced, and [Node] deletes its children when freed. Objects can have a [Script] attached to them. Once the [Script] is instantiated, it effectively acts as an extension to the base class, allowing it to define and inherit new properties, methods and signals. Inside a [Script], [method _get_property_list] may be overridden to customize properties in several ways. This allows them to be available to the editor, display as lists of options, sub-divide into groups, save on disk, etc. Scripting languages offer easier ways to customize properties, such as with the [annotation @GDScript.@export] annotation. @@ -159,7 +159,7 @@ <method name="_init" qualifiers="virtual"> <return type="void" /> <description> - Called when the object's script is instantiated, oftentimes after the object is initialized in memory (through [code]Object.new()[/code] in GDScript, or [code]new Object[/code] in C#). It can be also defined to take in parameters. This method is similar to a constructor in most programming languages. + Called when the object's script is instantiated, oftentimes after the object is initialized in memory (through [code]Object.new()[/code] in GDScript, or [code]new GodotObject[/code] in C#). It can be also defined to take in parameters. This method is similar to a constructor in most programming languages. [b]Note:[/b] If [method _init] is defined with [i]required[/i] parameters, the Object with script may only be created directly. If any other means (such as [method PackedScene.instantiate] or [method Node.duplicate]) are used, the script's initialization will fail. </description> </method> @@ -219,6 +219,7 @@ # Storing the value in the fake property. internal_data["fake_property"] = value return true + return false func _get_property_list(): return [ @@ -228,7 +229,7 @@ [csharp] private Godot.Collections.Dictionary _internalData = new Godot.Collections.Dictionary(); - public override void _Set(StringName property, Variant value) + public override bool _Set(StringName property, Variant value) { if (property == "FakeProperty") { diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index dcc9255dc6..fa8810c539 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -5856,6 +5856,14 @@ void EditorNode::add_control_to_dock(DockSlot p_slot, Control *p_control) { } void EditorNode::remove_control_from_dock(Control *p_control) { + // If the dock is floating, close it first. + for (WindowWrapper *wrapper : floating_docks) { + if (p_control == wrapper->get_wrapped_control()) { + wrapper->set_window_enabled(false); + break; + } + } + Control *dock = nullptr; for (int i = 0; i < DOCK_SLOT_MAX; i++) { if (p_control->get_parent() == dock_slot[i]) { 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_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/webrtc/webrtc_peer_connection.cpp b/modules/webrtc/webrtc_peer_connection.cpp index 8bad6fd784..0a50b677c4 100644 --- a/modules/webrtc/webrtc_peer_connection.cpp +++ b/modules/webrtc/webrtc_peer_connection.cpp @@ -40,14 +40,14 @@ StringName WebRTCPeerConnection::default_extension; void WebRTCPeerConnection::set_default_extension(const StringName &p_extension) { ERR_FAIL_COND_MSG(!ClassDB::is_parent_class(p_extension, WebRTCPeerConnectionExtension::get_class_static()), vformat("Can't make %s the default WebRTC extension since it does not extend WebRTCPeerConnectionExtension.", p_extension)); - default_extension = p_extension; + default_extension = StringName(p_extension, true); } WebRTCPeerConnection *WebRTCPeerConnection::create() { #ifdef WEB_ENABLED return memnew(WebRTCPeerConnectionJS); #else - if (default_extension == String()) { + if (default_extension == StringName()) { WARN_PRINT_ONCE("No default WebRTC extension configured."); return memnew(WebRTCPeerConnectionExtension); } diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp index fe81da76d3..1660101598 100644 --- a/platform/linuxbsd/x11/display_server_x11.cpp +++ b/platform/linuxbsd/x11/display_server_x11.cpp @@ -39,6 +39,7 @@ #include "core/math/math_funcs.h" #include "core/string/print_string.h" #include "core/string/ustring.h" +#include "drivers/png/png_driver_common.h" #include "main/main.h" #include "scene/resources/atlas_texture.h" @@ -519,7 +520,7 @@ Bool DisplayServerX11::_predicate_clipboard_selection(Display *display, XEvent * } Bool DisplayServerX11::_predicate_clipboard_incr(Display *display, XEvent *event, XPointer arg) { - if (event->type == PropertyNotify && event->xproperty.state == PropertyNewValue) { + if (event->type == PropertyNotify && event->xproperty.state == PropertyNewValue && event->xproperty.atom == *(Atom *)arg) { return True; } else { return False; @@ -593,7 +594,7 @@ String DisplayServerX11::_clipboard_get_impl(Atom p_source, Window x11_window, A // Non-blocking wait for next event and remove it from the queue. XEvent ev; - while (XCheckIfEvent(x11_display, &ev, _predicate_clipboard_incr, nullptr)) { + while (XCheckIfEvent(x11_display, &ev, _predicate_clipboard_incr, (XPointer)&selection)) { result = XGetWindowProperty(x11_display, x11_window, selection, // selection type 0, LONG_MAX, // offset - len @@ -664,6 +665,74 @@ String DisplayServerX11::_clipboard_get_impl(Atom p_source, Window x11_window, A return ret; } +Atom DisplayServerX11::_clipboard_get_image_target(Atom p_source, Window x11_window) const { + Atom target = XInternAtom(x11_display, "TARGETS", 0); + Atom png = XInternAtom(x11_display, "image/png", 0); + Atom *valid_targets = nullptr; + unsigned long atom_count = 0; + + Window selection_owner = XGetSelectionOwner(x11_display, p_source); + if (selection_owner != None) { + // Block events polling while processing selection events. + MutexLock mutex_lock(events_mutex); + + Atom selection = XA_PRIMARY; + XConvertSelection(x11_display, p_source, target, selection, x11_window, CurrentTime); + + XFlush(x11_display); + + // Blocking wait for predicate to be True and remove the event from the queue. + XEvent event; + XIfEvent(x11_display, &event, _predicate_clipboard_selection, (XPointer)&x11_window); + // Do not get any data, see how much data is there. + Atom type; + int format, result; + unsigned long len, bytes_left, dummy; + XGetWindowProperty(x11_display, x11_window, + selection, // Tricky.. + 0, 0, // offset - len + 0, // Delete 0==FALSE + XA_ATOM, // flag + &type, // return type + &format, // return format + &len, &bytes_left, // data length + (unsigned char **)&valid_targets); + + if (valid_targets) { + XFree(valid_targets); + valid_targets = nullptr; + } + + if (type == XA_ATOM && bytes_left > 0) { + // Data is ready and can be processed all at once. + result = XGetWindowProperty(x11_display, x11_window, + selection, 0, bytes_left / 4, 0, + XA_ATOM, &type, &format, + &len, &dummy, (unsigned char **)&valid_targets); + if (result == Success) { + atom_count = len; + } else { + print_verbose("Failed to get selection data."); + return None; + } + } else { + return None; + } + } else { + return None; + } + for (unsigned long i = 0; i < atom_count; i++) { + Atom atom = valid_targets[i]; + if (atom == png) { + XFree(valid_targets); + return png; + } + } + + XFree(valid_targets); + return None; +} + String DisplayServerX11::_clipboard_get(Atom p_source, Window x11_window) const { String ret; Atom utf8_atom = XInternAtom(x11_display, "UTF8_STRING", True); @@ -702,6 +771,158 @@ String DisplayServerX11::clipboard_get_primary() const { return ret; } +Ref<Image> DisplayServerX11::clipboard_get_image() const { + _THREAD_SAFE_METHOD_ + Atom clipboard = XInternAtom(x11_display, "CLIPBOARD", 0); + Window x11_window = windows[MAIN_WINDOW_ID].x11_window; + Ref<Image> ret; + Atom target = _clipboard_get_image_target(clipboard, x11_window); + if (target == None) { + return ret; + } + + Window selection_owner = XGetSelectionOwner(x11_display, clipboard); + + if (selection_owner != None) { + // Block events polling while processing selection events. + MutexLock mutex_lock(events_mutex); + + // Identifier for the property the other window + // will send the converted data to. + Atom transfer_prop = XA_PRIMARY; + XConvertSelection(x11_display, + clipboard, // source selection + target, // format to convert to + transfer_prop, // output property + x11_window, CurrentTime); + + XFlush(x11_display); + + // Blocking wait for predicate to be True and remove the event from the queue. + XEvent event; + XIfEvent(x11_display, &event, _predicate_clipboard_selection, (XPointer)&x11_window); + + // Do not get any data, see how much data is there. + Atom type; + int format, result; + unsigned long len, bytes_left, dummy; + unsigned char *data; + XGetWindowProperty(x11_display, x11_window, + transfer_prop, // Property data is transferred through + 0, 1, // offset, len (4 so we can get the size if INCR is used) + 0, // Delete 0==FALSE + AnyPropertyType, // flag + &type, // return type + &format, // return format + &len, &bytes_left, // data length + &data); + + if (type == XInternAtom(x11_display, "INCR", 0)) { + ERR_FAIL_COND_V_MSG(len != 1, ret, "Incremental transfer initial value was not length."); + + // Data is going to be received incrementally. + DEBUG_LOG_X11("INCR selection started.\n"); + + LocalVector<uint8_t> incr_data; + uint32_t data_size = 0; + bool success = false; + + // Initial response is the lower bound of the length of the transferred data. + incr_data.resize(*(unsigned long *)data); + XFree(data); + data = nullptr; + + // Delete INCR property to notify the owner. + XDeleteProperty(x11_display, x11_window, transfer_prop); + + // Process events from the queue. + bool done = false; + while (!done) { + if (!_wait_for_events()) { + // Error or timeout, abort. + break; + } + // Non-blocking wait for next event and remove it from the queue. + XEvent ev; + while (XCheckIfEvent(x11_display, &ev, _predicate_clipboard_incr, (XPointer)&transfer_prop)) { + result = XGetWindowProperty(x11_display, x11_window, + transfer_prop, // output property + 0, LONG_MAX, // offset - len + True, // delete property to notify the owner + AnyPropertyType, // flag + &type, // return type + &format, // return format + &len, &bytes_left, // data length + &data); + + DEBUG_LOG_X11("PropertyNotify: len=%lu, format=%i\n", len, format); + + if (result == Success) { + if (data && (len > 0)) { + uint32_t prev_size = incr_data.size(); + // New chunk, resize to be safe and append data. + incr_data.resize(MAX(data_size + len, prev_size)); + memcpy(incr_data.ptr() + data_size, data, len); + data_size += len; + } else if (!(format == 0 && len == 0)) { + // For unclear reasons the first GetWindowProperty always returns a length and format of 0. + // Otherwise, last chunk, process finished. + done = true; + success = true; + } + } else { + print_verbose("Failed to get selection data chunk."); + done = true; + } + + if (data) { + XFree(data); + data = nullptr; + } + + if (done) { + break; + } + } + } + + if (success && (data_size > 0)) { + ret.instantiate(); + PNGDriverCommon::png_to_image(incr_data.ptr(), incr_data.size(), false, ret); + } + } else if (bytes_left > 0) { + if (data) { + XFree(data); + data = nullptr; + } + // Data is ready and can be processed all at once. + result = XGetWindowProperty(x11_display, x11_window, + transfer_prop, 0, bytes_left + 4, 0, + AnyPropertyType, &type, &format, + &len, &dummy, &data); + if (result == Success) { + ret.instantiate(); + PNGDriverCommon::png_to_image((uint8_t *)data, bytes_left, false, ret); + } else { + print_verbose("Failed to get selection data."); + } + + if (data) { + XFree(data); + } + } + } + + return ret; +} + +bool DisplayServerX11::clipboard_has_image() const { + Atom target = _clipboard_get_image_target( + XInternAtom(x11_display, "CLIPBOARD", 0), + windows[MAIN_WINDOW_ID].x11_window); + return target != None; +} + Bool DisplayServerX11::_predicate_clipboard_save_targets(Display *display, XEvent *event, XPointer arg) { if (event->xany.window == *(Window *)arg) { return (event->type == SelectionRequest) || diff --git a/platform/linuxbsd/x11/display_server_x11.h b/platform/linuxbsd/x11/display_server_x11.h index 9706a4aa11..a8d134a6c7 100644 --- a/platform/linuxbsd/x11/display_server_x11.h +++ b/platform/linuxbsd/x11/display_server_x11.h @@ -304,6 +304,7 @@ class DisplayServerX11 : public DisplayServer { String _clipboard_get_impl(Atom p_source, Window x11_window, Atom target) const; String _clipboard_get(Atom p_source, Window x11_window) const; + Atom _clipboard_get_image_target(Atom p_source, Window x11_window) const; void _clipboard_transfer_ownership(Atom p_source, Window x11_window) const; bool do_mouse_warp = false; @@ -408,6 +409,8 @@ public: virtual void clipboard_set(const String &p_text) override; virtual String clipboard_get() const override; + virtual Ref<Image> clipboard_get_image() const override; + virtual bool clipboard_has_image() const override; virtual void clipboard_set_primary(const String &p_text) override; virtual String clipboard_get_primary() const override; diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index 9de86d1877..12ffafadf7 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -1914,15 +1914,12 @@ bool LineEdit::is_secret() const { } void LineEdit::set_secret_character(const String &p_string) { - // An empty string as the secret character would crash the engine. - // It also wouldn't make sense to use multiple characters as the secret character. - ERR_FAIL_COND_MSG(p_string.length() != 1, "Secret character must be exactly one character long (" + itos(p_string.length()) + " characters given)."); - if (secret_character == p_string) { return; } secret_character = p_string; + update_configuration_warnings(); _shape(); queue_redraw(); } @@ -2266,6 +2263,13 @@ void LineEdit::_emit_text_change() { emit_signal(SNAME("text_changed"), text); text_changed_dirty = false; } +PackedStringArray LineEdit::get_configuration_warnings() const { + PackedStringArray warnings = Control::get_configuration_warnings(); + if (secret_character.length() > 1) { + warnings.push_back("Secret Character property supports only one character. Extra characters will be ignored."); + } + return warnings; +} void LineEdit::_shape() { const Ref<Font> &font = theme_cache.font; @@ -2281,7 +2285,14 @@ void LineEdit::_shape() { if (text.length() == 0 && ime_text.length() == 0) { t = placeholder_translated; } else if (pass) { - t = secret_character.repeat(text.length() + ime_text.length()); + // TODO: Integrate with text server to add support for non-latin scripts. + // Allow secret_character as empty strings, act like if a space was used as a secret character. + String secret = " "; + // Allow values longer than 1 character in the property, but trim characters after the first one. + if (!secret_character.is_empty()) { + secret = secret_character.left(1); + } + t = secret.repeat(text.length() + ime_text.length()); } else { if (ime_text.length() > 0) { t = text.substr(0, caret_column) + ime_text + text.substr(caret_column, text.length()); diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h index 4a81f90166..993bc727e4 100644 --- a/scene/gui/line_edit.h +++ b/scene/gui/line_edit.h @@ -385,6 +385,8 @@ public: virtual bool is_text_field() const override; + PackedStringArray get_configuration_warnings() const override; + void show_virtual_keyboard(); LineEdit(const String &p_placeholder = String()); diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp index a857434b2a..b80e258af9 100644 --- a/scene/resources/packed_scene.cpp +++ b/scene/resources/packed_scene.cpp @@ -450,7 +450,7 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { } if (!old_parent_path.is_empty()) { - node->_set_name_nocheck(old_parent_path + "@" + node->get_name()); + node->set_name(old_parent_path + "#" + node->get_name()); } if (n.owner >= 0) { diff --git a/scene/resources/particle_process_material.cpp b/scene/resources/particle_process_material.cpp index ff0a6431bd..0a114d6b35 100644 --- a/scene/resources/particle_process_material.cpp +++ b/scene/resources/particle_process_material.cpp @@ -570,7 +570,7 @@ void ParticleProcessMaterial::_update_shader() { code += " int point = min(emission_texture_point_count - 1, int(rand_from_seed(alt_seed) * float(emission_texture_point_count)));\n"; code += " ivec2 emission_tex_size = textureSize(emission_texture_points, 0);\n"; code += " ivec2 emission_tex_ofs = ivec2(point % emission_tex_size.x, point / emission_tex_size.x);\n"; - code += " parameters.color *= texelFetch(emission_texture_color, emission_tex_ofs, 0);\n"; + code += " params.color *= texelFetch(emission_texture_color, emission_tex_ofs, 0);\n"; } code += "}\n"; 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 efec3b5072..dce97808b1 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp @@ -2137,6 +2137,12 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co RD::get_singleton()->draw_list_end(); } + if (rb_data.is_valid() && using_fsr2) { + // Make sure the upscaled texture is initialized, but not necessarily filled, before running screen copies + // so it properly detect if a dedicated copy texture should be used. + rb->ensure_upscaled(); + } + if (scene_state.used_screen_texture) { RENDER_TIMESTAMP("Copy Screen Texture"); @@ -2200,7 +2206,6 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co if (rb_data.is_valid() && (using_fsr2 || using_taa)) { if (using_fsr2) { - rb->ensure_upscaled(); rb_data->ensure_fsr2(fsr2_effect); RID exposure; diff --git a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp index 07d56eae0c..d10443d6ad 100644 --- a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp @@ -260,15 +260,29 @@ void RendererSceneRenderRD::_render_buffers_copy_screen_texture(const RenderData RD::get_singleton()->draw_command_begin_label("Copy screen texture"); - rb->allocate_blur_textures(); - + StringName texture_name; bool can_use_storage = _render_buffers_can_be_storage(); Size2i size = rb->get_internal_size(); + // When upscaling, the blur texture needs to be at the target size for post-processing to work. We prefer to use a + // dedicated backbuffer copy texture instead if the blur texture is not an option so shader effects work correctly. + Size2i target_size = rb->get_target_size(); + bool internal_size_matches = (size.width == target_size.width) && (size.height == target_size.height); + bool reuse_blur_texture = !rb->has_upscaled_texture() || internal_size_matches; + if (reuse_blur_texture) { + rb->allocate_blur_textures(); + texture_name = RB_TEX_BLUR_0; + } else { + uint32_t usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_CAN_COPY_TO_BIT; + usage_bits |= can_use_storage ? RD::TEXTURE_USAGE_STORAGE_BIT : RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT; + rb->create_texture(RB_SCOPE_BUFFERS, RB_TEX_BACK_COLOR, rb->get_base_data_format(), usage_bits); + texture_name = RB_TEX_BACK_COLOR; + } + for (uint32_t v = 0; v < rb->get_view_count(); v++) { RID texture = rb->get_internal_texture(v); - int mipmaps = int(rb->get_texture_format(RB_SCOPE_BUFFERS, RB_TEX_BLUR_0).mipmaps); - RID dest = rb->get_texture_slice(RB_SCOPE_BUFFERS, RB_TEX_BLUR_0, v, 0); + int mipmaps = int(rb->get_texture_format(RB_SCOPE_BUFFERS, texture_name).mipmaps); + RID dest = rb->get_texture_slice(RB_SCOPE_BUFFERS, texture_name, v, 0); if (can_use_storage) { copy_effects->copy_to_rect(texture, dest, Rect2i(0, 0, size.x, size.y)); @@ -279,8 +293,8 @@ void RendererSceneRenderRD::_render_buffers_copy_screen_texture(const RenderData for (int i = 1; i < mipmaps; i++) { RID source = dest; - dest = rb->get_texture_slice(RB_SCOPE_BUFFERS, RB_TEX_BLUR_0, v, i); - Size2i msize = rb->get_texture_slice_size(RB_SCOPE_BUFFERS, RB_TEX_BLUR_0, i); + dest = rb->get_texture_slice(RB_SCOPE_BUFFERS, texture_name, v, i); + Size2i msize = rb->get_texture_slice_size(RB_SCOPE_BUFFERS, texture_name, i); if (can_use_storage) { copy_effects->make_mipmap(source, dest, msize); diff --git a/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.h b/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.h index 43704119e7..b2946e6bbc 100644 --- a/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.h +++ b/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.h @@ -58,6 +58,7 @@ #define RB_TEX_BLUR_1 SNAME("blur_1") #define RB_TEX_HALF_BLUR SNAME("half_blur") // only for raster! +#define RB_TEX_BACK_COLOR SNAME("back_color") #define RB_TEX_BACK_DEPTH SNAME("back_depth") class RenderSceneBuffersRD : public RenderSceneBuffers { @@ -267,7 +268,16 @@ public: } // back buffer (color) - RID get_back_buffer_texture() const { return has_texture(RB_SCOPE_BUFFERS, RB_TEX_BLUR_0) ? get_texture(RB_SCOPE_BUFFERS, RB_TEX_BLUR_0) : RID(); } // We (re)use our blur texture here. + RID get_back_buffer_texture() const { + // Prefer returning the dedicated backbuffer color texture if it was created. Return the reused blur texture otherwise. + if (has_texture(RB_SCOPE_BUFFERS, RB_TEX_BACK_COLOR)) { + return get_texture(RB_SCOPE_BUFFERS, RB_TEX_BACK_COLOR); + } else if (has_texture(RB_SCOPE_BUFFERS, RB_TEX_BLUR_0)) { + return get_texture(RB_SCOPE_BUFFERS, RB_TEX_BLUR_0); + } else { + return RID(); + } + } // Upscaled. void ensure_upscaled(); |
