summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--SConstruct4
-rw-r--r--core/object/make_virtuals.py4
-rw-r--r--core/string/string_name.cpp10
-rw-r--r--core/variant/variant_parser.cpp2
-rw-r--r--doc/classes/LineEdit.xml2
-rw-r--r--doc/classes/Object.xml7
-rw-r--r--editor/editor_node.cpp8
-rw-r--r--modules/gdscript/gdscript.cpp40
-rw-r--r--modules/gdscript/gdscript.h26
-rw-r--r--modules/gdscript/gdscript_cache.cpp3
-rw-r--r--modules/gdscript/gdscript_compiler.cpp147
-rw-r--r--modules/gdscript/gdscript_compiler.h35
-rw-r--r--modules/gdscript/gdscript_lambda_callable.cpp52
-rw-r--r--modules/gdscript/gdscript_lambda_callable.h11
-rw-r--r--modules/gdscript/gdscript_rpc_callable.cpp1
-rw-r--r--modules/lightmapper_rd/lightmapper_rd.cpp174
-rw-r--r--modules/lightmapper_rd/lightmapper_rd.h19
-rw-r--r--modules/lightmapper_rd/lm_common_inc.glsl21
-rw-r--r--modules/lightmapper_rd/lm_compute.glsl149
-rw-r--r--modules/webrtc/webrtc_peer_connection.cpp4
-rw-r--r--platform/linuxbsd/x11/display_server_x11.cpp225
-rw-r--r--platform/linuxbsd/x11/display_server_x11.h3
-rw-r--r--scene/gui/line_edit.cpp21
-rw-r--r--scene/gui/line_edit.h2
-rw-r--r--scene/resources/packed_scene.cpp2
-rw-r--r--scene/resources/particle_process_material.cpp2
-rw-r--r--servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp7
-rw-r--r--servers/rendering/renderer_rd/renderer_scene_render_rd.cpp26
-rw-r--r--servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.h12
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="&quot;•&quot;">
- 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();