diff options
author | Yuri Sizov <11782833+YuriSizov@users.noreply.github.com> | 2023-03-27 20:14:47 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-03-27 20:14:47 +0200 |
commit | 19501f8eb19481b029f67ecf78e711d42f2fc431 (patch) | |
tree | 933ea3320b35bce6ba65ab1e1d1aa8ad662c90d4 /modules | |
parent | cacf49999e3fb37281d66cc591ca8bebc5712d4d (diff) | |
parent | 843f5adbc523ad2511322b4f09b5ce5a3fb9e225 (diff) |
Merge pull request #75397 from YuriSizov/4.0-cherrypicks
Cherry-picks for the 4.0 branch (future 4.0.2) - 1st batch
Diffstat (limited to 'modules')
23 files changed, 307 insertions, 169 deletions
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index effc0aab96..13d223b0d9 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -18,12 +18,13 @@ <param index="2" name="b8" type="int" /> <param index="3" name="a8" type="int" default="255" /> <description> - Returns a [Color] constructed from red ([param r8]), green ([param g8]), blue ([param b8]), and optionally alpha ([param a8]) integer channels, each divided by [code]255.0[/code] for their final value. + Returns a [Color] constructed from red ([param r8]), green ([param g8]), blue ([param b8]), and optionally alpha ([param a8]) integer channels, each divided by [code]255.0[/code] for their final value. Using [method Color8] instead of the standard [Color] constructor is useful when you need to match exact color values in an [Image]. [codeblock] var red = Color8(255, 0, 0) # Same as Color(1, 0, 0). var dark_blue = Color8(0, 0, 51) # Same as Color(0, 0, 0.2). var my_color = Color8(306, 255, 0, 102) # Same as Color(1.2, 1, 0, 0.4). [/codeblock] + [b]Note:[/b] Due to the lower precision of [method Color8] compared to the standard [Color] constructor, a color created with [method Color8] will generally not be equal to the same color created with the standard [Color] constructor. Use [method Color.is_equal_approx] for comparisons to avoid issues with floating-point precision error. </description> </method> <method name="assert"> diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp index 5883ec863d..bba11363d5 100644 --- a/modules/gdscript/editor/gdscript_highlighter.cpp +++ b/modules/gdscript/editor/gdscript_highlighter.cpp @@ -294,17 +294,27 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l !(str[j] == '_' && (prev_is_digit || str[j - 1] == 'b' || str[j - 1] == 'x' || str[j - 1] == '.')) && !(str[j] == 'e' && (prev_is_digit || str[j - 1] == '_')) && !(str[j] == '.' && (prev_is_digit || (!prev_is_binary_op && (j > 0 && (str[j - 1] == '-' || str[j - 1] == '+' || str[j - 1] == '~'))))) && - !((str[j] == '-' || str[j] == '+' || str[j] == '~') && !prev_is_binary_op && str[j - 1] != 'e')) { - /* This condition continues Number highlighting in special cases. - 1st row: '+' or '-' after scientific notation; + !((str[j] == '-' || str[j] == '+' || str[j] == '~') && !is_binary_op && !prev_is_binary_op && str[j - 1] != 'e')) { + /* This condition continues number highlighting in special cases. + 1st row: '+' or '-' after scientific notation (like 3e-4); 2nd row: '_' as a numeric separator; 3rd row: Scientific notation 'e' and floating points; 4th row: Floating points inside the number, or leading if after a unary mathematical operator; - 5th row: Multiple unary mathematical operators */ + 5th row: Multiple unary mathematical operators (like ~-7)*/ in_number = false; } - } else if (!is_binary_op && (str[j] == '-' || str[j] == '+' || str[j] == '~' || (str[j] == '.' && str[j + 1] != '.' && (j == 0 || (j > 0 && str[j - 1] != '.'))))) { + } else if (str[j] == '.' && !is_binary_op && is_digit(str[j + 1]) && (j == 0 || (j > 0 && str[j - 1] != '.'))) { + // Start number highlighting from leading decimal points (like .42) in_number = true; + } else if ((str[j] == '-' || str[j] == '+' || str[j] == '~') && !is_binary_op) { + // Only start number highlighting on unary operators if a digit follows them. + int non_op = j + 1; + while (str[non_op] == '-' || str[non_op] == '+' || str[non_op] == '~') { + non_op++; + } + if (is_digit(str[non_op]) || (str[non_op] == '.' && non_op < line_length && is_digit(str[non_op + 1]))) { + in_number = true; + } } if (!in_word && is_unicode_identifier_start(str[j]) && !in_number) { diff --git a/modules/gridmap/editor/grid_map_editor_plugin.cpp b/modules/gridmap/editor/grid_map_editor_plugin.cpp index 214d4d8ec2..541a95ab6f 100644 --- a/modules/gridmap/editor/grid_map_editor_plugin.cpp +++ b/modules/gridmap/editor/grid_map_editor_plugin.cpp @@ -913,7 +913,7 @@ void GridMapEditor::update_palette() { } void GridMapEditor::edit(GridMap *p_gridmap) { - if (node) { + if (node && node->is_connected("cell_size_changed", callable_mp(this, &GridMapEditor::_draw_grids))) { node->disconnect("cell_size_changed", callable_mp(this, &GridMapEditor::_draw_grids)); } diff --git a/modules/gridmap/grid_map.cpp b/modules/gridmap/grid_map.cpp index 3c0bd56e86..db8c645558 100644 --- a/modules/gridmap/grid_map.cpp +++ b/modules/gridmap/grid_map.cpp @@ -557,10 +557,14 @@ bool GridMap::_octant_update(const OctantKey &p_key) { } //erase navigation - for (const KeyValue<IndexKey, Octant::NavigationCell> &E : g.navigation_cell_ids) { - NavigationServer3D::get_singleton()->free(E.value.region); + for (KeyValue<IndexKey, Octant::NavigationCell> &E : g.navigation_cell_ids) { + if (E.value.region.is_valid()) { + NavigationServer3D::get_singleton()->free(E.value.region); + E.value.region = RID(); + } if (E.value.navigation_mesh_debug_instance.is_valid()) { RS::get_singleton()->free(E.value.navigation_mesh_debug_instance); + E.value.navigation_mesh_debug_instance = RID(); } } g.navigation_cell_ids.clear(); diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 1dbf6b3471..a77b1d83ad 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -334,8 +334,8 @@ bool CSharpLanguage::is_using_templates() { } Ref<Script> CSharpLanguage::make_template(const String &p_template, const String &p_class_name, const String &p_base_class_name) const { - Ref<CSharpScript> script; - script.instantiate(); + Ref<CSharpScript> scr; + scr.instantiate(); String class_name_no_spaces = p_class_name.replace(" ", "_"); String base_class_name = get_base_class_name(p_base_class_name, class_name_no_spaces); @@ -344,8 +344,8 @@ Ref<Script> CSharpLanguage::make_template(const String &p_template, const String .replace("_BASE_", base_class_name) .replace("_CLASS_", class_name_no_spaces) .replace("_TS_", _get_indentation()); - script->set_source_code(processed_template); - return script; + scr->set_source_code(processed_template); + return scr; } Vector<ScriptLanguage::ScriptTemplate> CSharpLanguage::get_built_in_templates(StringName p_object) { @@ -780,28 +780,28 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { // As scripts are going to be reloaded, must proceed without locking here - for (Ref<CSharpScript> &script : scripts) { + for (Ref<CSharpScript> &scr : scripts) { // If someone removes a script from a node, deletes the script, builds, adds a script to the // same node, then builds again, the script might have no path and also no script_class. In // that case, we can't (and don't need to) reload it. - if (script->get_path().is_empty() && !script->valid) { + if (scr->get_path().is_empty() && !scr->valid) { continue; } - to_reload.push_back(script); + to_reload.push_back(scr); // Script::instances are deleted during managed object disposal, which happens on domain finalize. // Only placeholders are kept. Therefore we need to keep a copy before that happens. - for (Object *obj : script->instances) { - script->pending_reload_instances.insert(obj->get_instance_id()); + for (Object *obj : scr->instances) { + scr->pending_reload_instances.insert(obj->get_instance_id()); // Since this script instance wasn't a placeholder, add it to the list of placeholders // that will have to be eventually replaced with a script instance in case it turns into one. // This list is not cleared after the reload and the collected instances only leave // the list if the script is instantiated or if it was a tool script but becomes a // non-tool script in a rebuild. - script->pending_replace_placeholders.insert(obj->get_instance_id()); + scr->pending_replace_placeholders.insert(obj->get_instance_id()); RefCounted *rc = Object::cast_to<RefCounted>(obj); if (rc) { @@ -810,9 +810,9 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { } #ifdef TOOLS_ENABLED - for (PlaceHolderScriptInstance *script_instance : script->placeholders) { - Object *obj = script_instance->get_owner(); - script->pending_reload_instances.insert(obj->get_instance_id()); + for (PlaceHolderScriptInstance *instance : scr->placeholders) { + Object *obj = instance->get_owner(); + scr->pending_reload_instances.insert(obj->get_instance_id()); RefCounted *rc = Object::cast_to<RefCounted>(obj); if (rc) { @@ -822,9 +822,9 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { #endif // Save state and remove script from instances - RBMap<ObjectID, CSharpScript::StateBackup> &owners_map = script->pending_reload_state; + RBMap<ObjectID, CSharpScript::StateBackup> &owners_map = scr->pending_reload_state; - for (Object *obj : script->instances) { + for (Object *obj : scr->instances) { ERR_CONTINUE(!obj->get_script_instance()); CSharpInstance *csi = static_cast<CSharpInstance *>(obj->get_script_instance()); @@ -849,14 +849,14 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { } // After the state of all instances is saved, clear scripts and script instances - for (Ref<CSharpScript> &script : scripts) { - while (script->instances.begin()) { - Object *obj = *script->instances.begin(); + for (Ref<CSharpScript> &scr : scripts) { + while (scr->instances.begin()) { + Object *obj = *scr->instances.begin(); obj->set_script(Ref<RefCounted>()); // Remove script and existing script instances (placeholder are not removed before domain reload) } - script->was_tool_before_reload = script->tool; - script->_clear(); + scr->was_tool_before_reload = scr->tool; + scr->_clear(); } // Do domain reload @@ -901,44 +901,44 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { List<Ref<CSharpScript>> to_reload_state; - for (Ref<CSharpScript> &script : to_reload) { + for (Ref<CSharpScript> &scr : to_reload) { #ifdef TOOLS_ENABLED - script->exports_invalidated = true; + scr->exports_invalidated = true; #endif - if (!script->get_path().is_empty()) { - script->reload(p_soft_reload); + if (!scr->get_path().is_empty()) { + scr->reload(p_soft_reload); - if (!script->valid) { - script->pending_reload_instances.clear(); - script->pending_reload_state.clear(); + if (!scr->valid) { + scr->pending_reload_instances.clear(); + scr->pending_reload_state.clear(); continue; } } else { - bool success = GDMonoCache::managed_callbacks.ScriptManagerBridge_TryReloadRegisteredScriptWithClass(script.ptr()); + bool success = GDMonoCache::managed_callbacks.ScriptManagerBridge_TryReloadRegisteredScriptWithClass(scr.ptr()); if (!success) { // Couldn't reload - script->pending_reload_instances.clear(); - script->pending_reload_state.clear(); + scr->pending_reload_instances.clear(); + scr->pending_reload_state.clear(); continue; } } - StringName native_name = script->get_instance_base_type(); + StringName native_name = scr->get_instance_base_type(); { - for (const ObjectID &obj_id : script->pending_reload_instances) { + for (const ObjectID &obj_id : scr->pending_reload_instances) { Object *obj = ObjectDB::get_instance(obj_id); if (!obj) { - script->pending_reload_state.erase(obj_id); + scr->pending_reload_state.erase(obj_id); continue; } if (!ClassDB::is_parent_class(obj->get_class_name(), native_name)) { // No longer inherits the same compatible type, can't reload - script->pending_reload_state.erase(obj_id); + scr->pending_reload_state.erase(obj_id); continue; } @@ -946,11 +946,11 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { // Check if the script must be instantiated or kept as a placeholder // when the script may not be a tool (see #65266) - bool replace_placeholder = script->pending_replace_placeholders.has(obj->get_instance_id()); - if (!script->is_tool() && script->was_tool_before_reload) { + bool replace_placeholder = scr->pending_replace_placeholders.has(obj->get_instance_id()); + if (!scr->is_tool() && scr->was_tool_before_reload) { // The script was a tool before the rebuild so the removal was intentional. replace_placeholder = false; - script->pending_replace_placeholders.erase(obj->get_instance_id()); + scr->pending_replace_placeholders.erase(obj->get_instance_id()); } #ifdef TOOLS_ENABLED @@ -959,20 +959,20 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { // Non-placeholder script instances are removed in godot_icall_Object_Disposed. CRASH_COND(!si->is_placeholder()); - if (replace_placeholder || script->is_tool() || ScriptServer::is_scripting_enabled()) { + if (replace_placeholder || scr->is_tool() || ScriptServer::is_scripting_enabled()) { // Replace placeholder with a script instance. - CSharpScript::StateBackup &state_backup = script->pending_reload_state[obj_id]; + CSharpScript::StateBackup &state_backup = scr->pending_reload_state[obj_id]; // Backup placeholder script instance state before replacing it with a script instance. si->get_property_state(state_backup.properties); - ScriptInstance *script_instance = script->instance_create(obj); + ScriptInstance *instance = scr->instance_create(obj); - if (script_instance) { - script->placeholders.erase(static_cast<PlaceHolderScriptInstance *>(si)); - script->pending_replace_placeholders.erase(obj->get_instance_id()); - obj->set_script_instance(script_instance); + if (instance) { + scr->placeholders.erase(static_cast<PlaceHolderScriptInstance *>(si)); + scr->pending_replace_placeholders.erase(obj->get_instance_id()); + obj->set_script_instance(instance); } } @@ -983,18 +983,18 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { #endif // Re-create the script instance. - if (replace_placeholder || script->is_tool() || ScriptServer::is_scripting_enabled()) { + if (replace_placeholder || scr->is_tool() || ScriptServer::is_scripting_enabled()) { // Create script instance or replace placeholder with a script instance. - ScriptInstance *script_instance = script->instance_create(obj); + ScriptInstance *instance = scr->instance_create(obj); - if (script_instance) { - script->pending_replace_placeholders.erase(obj->get_instance_id()); - obj->set_script_instance(script_instance); + if (instance) { + scr->pending_replace_placeholders.erase(obj->get_instance_id()); + obj->set_script_instance(instance); continue; } } // The script instance could not be instantiated or wasn't in the list of placeholders to replace. - obj->set_script(script); + obj->set_script(scr); #if DEBUG_ENABLED // If we reached here, the instantiated script must be a placeholder. CRASH_COND(!obj->get_script_instance()->is_placeholder()); @@ -1002,21 +1002,21 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { } } - to_reload_state.push_back(script); + to_reload_state.push_back(scr); } - for (Ref<CSharpScript> &script : to_reload_state) { - for (const ObjectID &obj_id : script->pending_reload_instances) { + for (Ref<CSharpScript> &scr : to_reload_state) { + for (const ObjectID &obj_id : scr->pending_reload_instances) { Object *obj = ObjectDB::get_instance(obj_id); if (!obj) { - script->pending_reload_state.erase(obj_id); + scr->pending_reload_state.erase(obj_id); continue; } ERR_CONTINUE(!obj->get_script_instance()); - CSharpScript::StateBackup &state_backup = script->pending_reload_state[obj_id]; + CSharpScript::StateBackup &state_backup = scr->pending_reload_state[obj_id]; CSharpInstance *csi = CAST_CSHARP_INSTANCE(obj->get_script_instance()); @@ -1033,8 +1033,8 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { } } - script->pending_reload_instances.clear(); - script->pending_reload_state.clear(); + scr->pending_reload_instances.clear(); + scr->pending_reload_state.clear(); } // Deserialize managed callables @@ -2144,8 +2144,8 @@ bool CSharpScript::_update_exports(PlaceHolderScriptInstance *p_instance_to_upda _update_exports_values(values, propnames); if (changed) { - for (PlaceHolderScriptInstance *script_instance : placeholders) { - script_instance->update(propnames, values); + for (PlaceHolderScriptInstance *instance : placeholders) { + instance->update(propnames, values); } } else { p_instance_to_update->update(propnames, values); @@ -2711,28 +2711,28 @@ Ref<Resource> ResourceFormatLoaderCSharpScript::load(const String &p_path, const // TODO ignore anything inside bin/ and obj/ in tools builds? - Ref<CSharpScript> script; + Ref<CSharpScript> scr; if (GDMonoCache::godot_api_cache_updated) { - GDMonoCache::managed_callbacks.ScriptManagerBridge_GetOrCreateScriptBridgeForPath(&p_path, &script); + GDMonoCache::managed_callbacks.ScriptManagerBridge_GetOrCreateScriptBridgeForPath(&p_path, &scr); } else { - script = Ref<CSharpScript>(memnew(CSharpScript)); + scr = Ref<CSharpScript>(memnew(CSharpScript)); } #if defined(DEBUG_ENABLED) || defined(TOOLS_ENABLED) - Error err = script->load_source_code(p_path); + Error err = scr->load_source_code(p_path); ERR_FAIL_COND_V_MSG(err != OK, Ref<Resource>(), "Cannot load C# script file '" + p_path + "'."); #endif - script->set_path(p_original_path); + scr->set_path(p_original_path); - script->reload(); + scr->reload(); if (r_error) { *r_error = OK; } - return script; + return scr; } void ResourceFormatLoaderCSharpScript::get_recognized_extensions(List<String> *p_extensions) const { diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props index 0d0889c491..894053c5de 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props @@ -14,6 +14,7 @@ <GodotProjectDir Condition=" '$(GodotProjectDir)' == '' ">$(MSBuildProjectDirectory)</GodotProjectDir> <GodotProjectDir>$([MSBuild]::EnsureTrailingSlash('$(GodotProjectDir)'))</GodotProjectDir> + <GodotProjectDirBase64 Condition=" $([MSBuild]::VersionGreaterThanOrEquals($(MSBuildAssemblyVersion), '17.3')) ">$([MSBuild]::ConvertToBase64('$(GodotProjectDir)'))</GodotProjectDirBase64> <!-- Custom output paths for Godot projects. In brief, 'bin\' and 'obj\' are moved to '$(GodotProjectDir)\.godot\mono\temp\'. --> <BaseOutputPath>$(GodotProjectDir).godot\mono\temp\bin\</BaseOutputPath> @@ -29,6 +30,13 @@ <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> </PropertyGroup> + <Target Condition=" $([MSBuild]::VersionLessThan($(MSBuildAssemblyVersion), '17.3')) " Name="GodotProjectDir" BeforeTargets="Build"> + <Error Text="Cannot build from path containing '%23', move your project or update dotnet to the latest version." Condition="$(GodotProjectDir.Contains('%23'))" /> <!-- # --> + <Error Text="Cannot build from path containing '%3B', move your project or update dotnet to the latest version." Condition="$(GodotProjectDir.Contains('%3B'))" /> <!-- ; --> + <Error Text="Cannot build from path containing newlines, move your project or update dotnet to the latest version." Condition="$(GodotProjectDir.Contains('%0A'))" /> <!-- \n --> + <Error Text="Cannot build from path containing newlines, move your project or update dotnet to the latest version." Condition="$(GodotProjectDir.Contains('%0D'))" /> <!-- \r --> + </Target> + <Import Sdk="Microsoft.NET.Sdk" Project="Sdk.props" Condition=" '$(GodotSdkImportsMicrosoftNetSdk)' == 'true' " /> <PropertyGroup> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj index 8e78e0385d..3f569ebac3 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj @@ -7,6 +7,7 @@ <PropertyGroup> <!-- $(GodotProjectDir) would normally be defined by the Godot.NET.Sdk --> <GodotProjectDir>$(MSBuildProjectDirectory)</GodotProjectDir> + <GodotProjectDirBase64 Condition=" $([MSBuild]::VersionGreaterThanOrEquals($(MSBuildAssemblyVersion), '17.3')) ">$([MSBuild]::ConvertToBase64('$(GodotProjectDir)'))</GodotProjectDirBase64> <!-- For compiling GetGodotPropertyDefaultValues. --> <DefineConstants>$(DefineConstants);TOOLS</DefineConstants> </PropertyGroup> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props index 7881ed0a8c..2a8ae7f958 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props @@ -2,6 +2,7 @@ <ItemGroup> <!-- $(GodotProjectDir) is defined by Godot.NET.Sdk --> <CompilerVisibleProperty Include="GodotProjectDir" /> + <CompilerVisibleProperty Include="GodotProjectDirBase64" /> <CompilerVisibleProperty Include="GodotSourceGenerators" /> <CompilerVisibleProperty Include="IsGodotToolsProject" /> </ItemGroup> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs index eae7e41da8..d14e3c3781 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs @@ -22,10 +22,17 @@ namespace Godot.SourceGenerators // NOTE: NotNullWhen diagnostics don't work on projects targeting .NET Standard 2.0 // ReSharper disable once ReplaceWithStringIsNullOrEmpty - if (!context.TryGetGlobalAnalyzerProperty("GodotProjectDir", out string? godotProjectDir) - || godotProjectDir!.Length == 0) + if (!context.TryGetGlobalAnalyzerProperty("GodotProjectDirBase64", out string? godotProjectDir) || godotProjectDir!.Length == 0) { - throw new InvalidOperationException("Property 'GodotProjectDir' is null or empty."); + if (!context.TryGetGlobalAnalyzerProperty("GodotProjectDir", out godotProjectDir) || godotProjectDir!.Length == 0) + { + throw new InvalidOperationException("Property 'GodotProjectDir' is null or empty."); + } + } + else + { + // Workaround for https://github.com/dotnet/roslyn/issues/51692 + godotProjectDir = Encoding.UTF8.GetString(Convert.FromBase64String(godotProjectDir)); } Dictionary<INamedTypeSymbol, IEnumerable<ClassDeclarationSyntax>> godotClasses = context diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 83101c1443..6690a3badb 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -523,7 +523,10 @@ void BindingsGenerator::_append_xml_method(StringBuilder &p_xml_output, const Ty p_xml_output.append(target_imethod->proxy_name); p_xml_output.append("\"/>"); } else { - ERR_PRINT("Cannot resolve method reference in documentation: '" + p_link_target + "'."); + if (!p_target_itype->is_intentionally_ignored(p_link_target)) { + ERR_PRINT("Cannot resolve method reference in documentation: '" + p_link_target + "'."); + } + _append_xml_undeclared(p_xml_output, p_link_target); } } @@ -563,7 +566,10 @@ void BindingsGenerator::_append_xml_member(StringBuilder &p_xml_output, const Ty p_xml_output.append(target_iprop->proxy_name); p_xml_output.append("\"/>"); } else { - ERR_PRINT("Cannot resolve member reference in documentation: '" + p_link_target + "'."); + if (!p_target_itype->is_intentionally_ignored(p_link_target)) { + ERR_PRINT("Cannot resolve member reference in documentation: '" + p_link_target + "'."); + } + _append_xml_undeclared(p_xml_output, p_link_target); } } @@ -591,7 +597,10 @@ void BindingsGenerator::_append_xml_signal(StringBuilder &p_xml_output, const Ty p_xml_output.append(target_isignal->proxy_name); p_xml_output.append("\"/>"); } else { - ERR_PRINT("Cannot resolve signal reference in documentation: '" + p_link_target + "'."); + if (!p_target_itype->is_intentionally_ignored(p_link_target)) { + ERR_PRINT("Cannot resolve signal reference in documentation: '" + p_link_target + "'."); + } + _append_xml_undeclared(p_xml_output, p_link_target); } } @@ -613,7 +622,10 @@ void BindingsGenerator::_append_xml_enum(StringBuilder &p_xml_output, const Type p_xml_output.append(target_enum_itype.proxy_name); // Includes nesting class if any p_xml_output.append("\"/>"); } else { - ERR_PRINT("Cannot resolve enum reference in documentation: '" + p_link_target + "'."); + if (!p_target_itype->is_intentionally_ignored(p_link_target)) { + ERR_PRINT("Cannot resolve enum reference in documentation: '" + p_link_target + "'."); + } + _append_xml_undeclared(p_xml_output, p_link_target); } } @@ -673,7 +685,10 @@ void BindingsGenerator::_append_xml_constant(StringBuilder &p_xml_output, const // Also search in @GlobalScope as a last resort if no class was specified _append_xml_constant_in_global_scope(p_xml_output, p_target_cname, p_link_target); } else { - ERR_PRINT("Cannot resolve constant reference in documentation: '" + p_link_target + "'."); + if (!p_target_itype->is_intentionally_ignored(p_link_target)) { + ERR_PRINT("Cannot resolve constant reference in documentation: '" + p_link_target + "'."); + } + _append_xml_undeclared(p_xml_output, p_link_target); } } @@ -2936,6 +2951,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() { if (method_has_ptr_parameter(method_info)) { // Pointers are not supported. + itype.ignored_members.insert(method_info.name); continue; } diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h index 5c266ed31f..eac281ddb4 100644 --- a/modules/mono/editor/bindings_generator.h +++ b/modules/mono/editor/bindings_generator.h @@ -408,6 +408,7 @@ class BindingsGenerator { List<PropertyInterface> properties; List<MethodInterface> methods; List<SignalInterface> signals_; + HashSet<String> ignored_members; bool has_virtual_methods = false; @@ -471,6 +472,10 @@ class BindingsGenerator { return nullptr; } + bool is_intentionally_ignored(const String &p_name) const { + return ignored_members.has(p_name); + } + private: static DocData::ClassDoc *_get_type_doc(TypeInterface &itype) { String doc_name = itype.name.begins_with("_") ? itype.name.substr(1) : itype.name; diff --git a/modules/mono/godotsharp_dirs.cpp b/modules/mono/godotsharp_dirs.cpp index d08dedcfcb..bff7d04b55 100644 --- a/modules/mono/godotsharp_dirs.cpp +++ b/modules/mono/godotsharp_dirs.cpp @@ -66,23 +66,25 @@ String _get_mono_user_dir() { if (EditorPaths::get_singleton()) { return EditorPaths::get_singleton()->get_data_dir().path_join("mono"); } else { - String settings_path; + String settings_path = OS::get_singleton()->get_data_path().path_join(OS::get_singleton()->get_godot_dir_name()); // Self-contained mode if a `._sc_` or `_sc_` file is present in executable dir. String exe_dir = OS::get_singleton()->get_executable_path().get_base_dir(); - - // On macOS, look outside .app bundle, since .app bundle is read-only. - if (OS::get_singleton()->has_feature("macos") && exe_dir.ends_with("MacOS") && exe_dir.path_join("..").simplify_path().ends_with("Contents")) { - exe_dir = exe_dir.path_join("../../..").simplify_path(); - } - Ref<DirAccess> d = DirAccess::create_for_path(exe_dir); - if (d->file_exists("._sc_") || d->file_exists("_sc_")) { // contain yourself settings_path = exe_dir.path_join("editor_data"); - } else { - settings_path = OS::get_singleton()->get_data_path().path_join(OS::get_singleton()->get_godot_dir_name()); + } + + // On macOS, look outside .app bundle, since .app bundle is read-only. + // Note: This will not work if Gatekeeper path randomization is active. + if (OS::get_singleton()->has_feature("macos") && exe_dir.ends_with("MacOS") && exe_dir.path_join("..").simplify_path().ends_with("Contents")) { + exe_dir = exe_dir.path_join("../../..").simplify_path(); + d = DirAccess::create_for_path(exe_dir); + if (d->file_exists("._sc_") || d->file_exists("_sc_")) { + // contain yourself + settings_path = exe_dir.path_join("editor_data"); + } } return settings_path.path_join("mono"); diff --git a/modules/navigation/nav_map.cpp b/modules/navigation/nav_map.cpp index b1674c8fc5..4dcdbd6446 100644 --- a/modules/navigation/nav_map.cpp +++ b/modules/navigation/nav_map.cpp @@ -894,9 +894,9 @@ void NavMap::sync() { if (agents_dirty) { // cannot use LocalVector here as RVO library expects std::vector to build KdTree std::vector<RVO::Agent *> raw_agents; - raw_agents.reserve(agents.size()); - for (NavAgent *agent : agents) { - raw_agents.push_back(agent->get_agent()); + raw_agents.reserve(controlled_agents.size()); + for (NavAgent *controlled_agent : controlled_agents) { + raw_agents.push_back(controlled_agent->get_agent()); } rvo.buildAgentTree(raw_agents); } diff --git a/modules/openxr/action_map/openxr_action_map.cpp b/modules/openxr/action_map/openxr_action_map.cpp index 669c395b3e..d2f6be2233 100644 --- a/modules/openxr/action_map/openxr_action_map.cpp +++ b/modules/openxr/action_map/openxr_action_map.cpp @@ -178,6 +178,7 @@ void OpenXRActionMap::create_default_action_sets() { Ref<OpenXRAction> grip = action_set->add_new_action("grip", "Grip", OpenXRAction::OPENXR_ACTION_FLOAT, "/user/hand/left,/user/hand/right"); Ref<OpenXRAction> grip_click = action_set->add_new_action("grip_click", "Grip click", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); Ref<OpenXRAction> grip_touch = action_set->add_new_action("grip_touch", "Grip touching", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> grip_force = action_set->add_new_action("grip_force", "Grip force", OpenXRAction::OPENXR_ACTION_FLOAT, "/user/hand/left,/user/hand/right"); Ref<OpenXRAction> primary = action_set->add_new_action("primary", "Primary joystick/thumbstick/trackpad", OpenXRAction::OPENXR_ACTION_VECTOR2, "/user/hand/left,/user/hand/right"); Ref<OpenXRAction> primary_click = action_set->add_new_action("primary_click", "Primary joystick/thumbstick/trackpad click", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); Ref<OpenXRAction> primary_touch = action_set->add_new_action("primary_touch", "Primary joystick/thumbstick/trackpad touching", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); @@ -349,6 +350,7 @@ void OpenXRActionMap::create_default_action_sets() { profile->add_new_binding(trigger_touch, "/user/hand/left/input/trigger/touch,/user/hand/right/input/trigger/touch"); profile->add_new_binding(grip, "/user/hand/left/input/squeeze/value,/user/hand/right/input/squeeze/value"); profile->add_new_binding(grip_click, "/user/hand/left/input/squeeze/value,/user/hand/right/input/squeeze/value"); // this should do a float to bool conversion + profile->add_new_binding(grip_force, "/user/hand/left/input/squeeze/force,/user/hand/right/input/squeeze/force"); // grip force seems to be unique to the Valve Index // primary on our index controller is our thumbstick profile->add_new_binding(primary, "/user/hand/left/input/thumbstick,/user/hand/right/input/thumbstick"); profile->add_new_binding(primary_click, "/user/hand/left/input/thumbstick/click,/user/hand/right/input/thumbstick/click"); diff --git a/modules/openxr/action_map/openxr_interaction_profile_meta_data.cpp b/modules/openxr/action_map/openxr_interaction_profile_meta_data.cpp index 1118b53d65..70879c6b6b 100644 --- a/modules/openxr/action_map/openxr_interaction_profile_meta_data.cpp +++ b/modules/openxr/action_map/openxr_interaction_profile_meta_data.cpp @@ -376,7 +376,9 @@ void OpenXRInteractionProfileMetaData::_register_core_metadata() { register_io_path("/interaction_profiles/valve/index_controller", "Trigger touch", "/user/hand/right", "/user/hand/right/input/trigger/touch", "", OpenXRAction::OPENXR_ACTION_BOOL); register_io_path("/interaction_profiles/valve/index_controller", "Squeeze", "/user/hand/left", "/user/hand/left/input/squeeze/value", "", OpenXRAction::OPENXR_ACTION_FLOAT); + register_io_path("/interaction_profiles/valve/index_controller", "Squeeze force", "/user/hand/left", "/user/hand/left/input/squeeze/force", "", OpenXRAction::OPENXR_ACTION_FLOAT); register_io_path("/interaction_profiles/valve/index_controller", "Squeeze", "/user/hand/right", "/user/hand/right/input/squeeze/value", "", OpenXRAction::OPENXR_ACTION_FLOAT); + register_io_path("/interaction_profiles/valve/index_controller", "Squeeze force", "/user/hand/right", "/user/hand/right/input/squeeze/force", "", OpenXRAction::OPENXR_ACTION_FLOAT); register_io_path("/interaction_profiles/valve/index_controller", "Thumbstick", "/user/hand/left", "/user/hand/left/input/thumbstick", "", OpenXRAction::OPENXR_ACTION_VECTOR2); register_io_path("/interaction_profiles/valve/index_controller", "Thumbstick click", "/user/hand/left", "/user/hand/left/input/thumbstick/click", "", OpenXRAction::OPENXR_ACTION_BOOL); diff --git a/modules/openxr/extensions/openxr_extension_wrapper.h b/modules/openxr/extensions/openxr_extension_wrapper.h index 84279635b5..2c855c3cde 100644 --- a/modules/openxr/extensions/openxr_extension_wrapper.h +++ b/modules/openxr/extensions/openxr_extension_wrapper.h @@ -80,7 +80,9 @@ public: // this happens right before physics process and normal processing is run. // This is when controller data is queried and made available to game logic. virtual void on_process() {} - virtual void on_pre_render() {} // `on_pre_render` is called right before we start rendering our XR viewport. + virtual void on_pre_render() {} // `on_pre_render` is called right before we start rendering our XR viewports. + virtual void on_pre_draw_viewport(RID p_render_target) {} // `on_pre_draw_viewport` is called right before we start rendering this viewport + virtual void on_post_draw_viewport(RID p_render_target) {} // `on_port_draw_viewport` is called right after we start rendering this viewport (note that on Vulkan draw commands may only be queued) virtual void on_state_idle() {} // `on_state_idle` is called when the OpenXR session state is changed to idle. virtual void on_state_ready() {} // `on_state_ready` is called when the OpenXR session state is changed to ready, this means OpenXR is ready to setup our session. diff --git a/modules/openxr/extensions/openxr_opengl_extension.cpp b/modules/openxr/extensions/openxr_opengl_extension.cpp index 0d201161f1..20ccfe3906 100644 --- a/modules/openxr/extensions/openxr_opengl_extension.cpp +++ b/modules/openxr/extensions/openxr_opengl_extension.cpp @@ -37,6 +37,28 @@ #include "servers/rendering/rendering_server_globals.h" #include "servers/rendering_server.h" +// OpenXR requires us to submit sRGB textures so that it recognises the content +// as being in sRGB color space. We do fall back on "normal" textures but this +// will likely result in incorrect colors as OpenXR will double the sRGB conversion. +// All major XR runtimes support sRGB textures. + +// In OpenGL output of the fragment shader is assumed to be in the color space of +// the developers choice, however a linear to sRGB HW conversion can be enabled +// through enabling GL_FRAMEBUFFER_SRGB if an sRGB color attachment is used. +// This is a global setting. +// See: https://www.khronos.org/opengl/wiki/Framebuffer + +// In OpenGLES output of the fragment shader is assumed to be in linear color space +// and will be converted by default to sRGB if an sRGB color attachment is used. +// The extension GL_EXT_sRGB_write_control was introduced to enable turning this +// feature off. +// See: https://registry.khronos.org/OpenGL/extensions/EXT/EXT_sRGB_write_control.txt + +// On OpenGLES this is not defined in our standard headers.. +#ifndef GL_FRAMEBUFFER_SRGB +#define GL_FRAMEBUFFER_SRGB 0x8DB9 +#endif + HashMap<String, bool *> OpenXROpenGLExtension::get_requested_extensions() { HashMap<String, bool *> request_extensions; @@ -157,8 +179,8 @@ void *OpenXROpenGLExtension::set_session_create_and_get_next_pointer(void *p_nex } void OpenXROpenGLExtension::get_usable_swapchain_formats(Vector<int64_t> &p_usable_swap_chains) { - p_usable_swap_chains.push_back(GL_RGBA8); p_usable_swap_chains.push_back(GL_SRGB8_ALPHA8); + p_usable_swap_chains.push_back(GL_RGBA8); } void OpenXROpenGLExtension::get_usable_depth_formats(Vector<int64_t> &p_usable_depth_formats) { @@ -168,6 +190,23 @@ void OpenXROpenGLExtension::get_usable_depth_formats(Vector<int64_t> &p_usable_d p_usable_depth_formats.push_back(GL_DEPTH_COMPONENT24); } +void OpenXROpenGLExtension::on_pre_draw_viewport(RID p_render_target) { + if (srgb_ext_is_available) { + hw_linear_to_srgb_is_enabled = glIsEnabled(GL_FRAMEBUFFER_SRGB); + if (hw_linear_to_srgb_is_enabled) { + // Disable this. + glDisable(GL_FRAMEBUFFER_SRGB); + } + } +} + +void OpenXROpenGLExtension::on_post_draw_viewport(RID p_render_target) { + if (srgb_ext_is_available && hw_linear_to_srgb_is_enabled) { + // Re-enable this. + glEnable(GL_FRAMEBUFFER_SRGB); + } +} + bool OpenXROpenGLExtension::get_swapchain_image_data(XrSwapchain p_swapchain, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, void **r_swapchain_graphics_data) { GLES3::TextureStorage *texture_storage = GLES3::TextureStorage::get_singleton(); ERR_FAIL_NULL_V(texture_storage, false); diff --git a/modules/openxr/extensions/openxr_opengl_extension.h b/modules/openxr/extensions/openxr_opengl_extension.h index 03a640cbfb..29a9f35c51 100644 --- a/modules/openxr/extensions/openxr_opengl_extension.h +++ b/modules/openxr/extensions/openxr_opengl_extension.h @@ -79,6 +79,9 @@ public: virtual void on_instance_created(const XrInstance p_instance) override; virtual void *set_session_create_and_get_next_pointer(void *p_next_pointer) override; + virtual void on_pre_draw_viewport(RID p_render_target) override; + virtual void on_post_draw_viewport(RID p_render_target) override; + virtual void get_usable_swapchain_formats(Vector<int64_t> &p_usable_swap_chains) override; virtual void get_usable_depth_formats(Vector<int64_t> &p_usable_swap_chains) override; virtual String get_swapchain_format_name(int64_t p_swapchain_format) const override; @@ -103,6 +106,9 @@ private: Vector<RID> texture_rids; }; + bool srgb_ext_is_available = true; + bool hw_linear_to_srgb_is_enabled = false; + bool check_graphics_api_support(XrVersion p_desired_version); #ifdef ANDROID_ENABLED diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp index ddb3114b59..af59fe7dde 100644 --- a/modules/openxr/openxr_api.cpp +++ b/modules/openxr/openxr_api.cpp @@ -1815,6 +1815,10 @@ bool OpenXRAPI::pre_draw_viewport(RID p_render_target) { } } + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_pre_draw_viewport(p_render_target); + } + return true; } @@ -1839,7 +1843,9 @@ void OpenXRAPI::post_draw_viewport(RID p_render_target) { return; } - // Nothing to do here at this point in time... + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_post_draw_viewport(p_render_target); + } }; void OpenXRAPI::end_frame() { diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index 3463cb5d9d..3cf6288fef 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -386,6 +386,8 @@ int64_t TextServerAdvanced::_get_features() const { void TextServerAdvanced::_free_rid(const RID &p_rid) { _THREAD_SAFE_METHOD_ if (font_owner.owns(p_rid)) { + MutexLock ftlock(ft_mutex); + FontAdvanced *fd = font_owner.get_or_null(p_rid); { MutexLock lock(fd->mutex); @@ -1321,45 +1323,48 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_f // Init dynamic font. #ifdef MODULE_FREETYPE_ENABLED int error = 0; - if (!ft_library) { - error = FT_Init_FreeType(&ft_library); - if (error != 0) { - memdelete(fd); - ERR_FAIL_V_MSG(false, "FreeType: Error initializing library: '" + String(FT_Error_String(error)) + "'."); - } + { + MutexLock ftlock(ft_mutex); + if (!ft_library) { + error = FT_Init_FreeType(&ft_library); + if (error != 0) { + memdelete(fd); + ERR_FAIL_V_MSG(false, "FreeType: Error initializing library: '" + String(FT_Error_String(error)) + "'."); + } #ifdef MODULE_SVG_ENABLED - FT_Property_Set(ft_library, "ot-svg", "svg-hooks", get_tvg_svg_in_ot_hooks()); + FT_Property_Set(ft_library, "ot-svg", "svg-hooks", get_tvg_svg_in_ot_hooks()); #endif - } - - memset(&fd->stream, 0, sizeof(FT_StreamRec)); - fd->stream.base = (unsigned char *)p_font_data->data_ptr; - fd->stream.size = p_font_data->data_size; - fd->stream.pos = 0; - - FT_Open_Args fargs; - memset(&fargs, 0, sizeof(FT_Open_Args)); - fargs.memory_base = (unsigned char *)p_font_data->data_ptr; - fargs.memory_size = p_font_data->data_size; - fargs.flags = FT_OPEN_MEMORY; - fargs.stream = &fd->stream; + } - int max_index = 0; - FT_Face tmp_face = nullptr; - error = FT_Open_Face(ft_library, &fargs, -1, &tmp_face); - if (tmp_face && error == 0) { - max_index = tmp_face->num_faces - 1; - } - if (tmp_face) { - FT_Done_Face(tmp_face); - } + memset(&fd->stream, 0, sizeof(FT_StreamRec)); + fd->stream.base = (unsigned char *)p_font_data->data_ptr; + fd->stream.size = p_font_data->data_size; + fd->stream.pos = 0; + + FT_Open_Args fargs; + memset(&fargs, 0, sizeof(FT_Open_Args)); + fargs.memory_base = (unsigned char *)p_font_data->data_ptr; + fargs.memory_size = p_font_data->data_size; + fargs.flags = FT_OPEN_MEMORY; + fargs.stream = &fd->stream; + + int max_index = 0; + FT_Face tmp_face = nullptr; + error = FT_Open_Face(ft_library, &fargs, -1, &tmp_face); + if (tmp_face && error == 0) { + max_index = tmp_face->num_faces - 1; + } + if (tmp_face) { + FT_Done_Face(tmp_face); + } - error = FT_Open_Face(ft_library, &fargs, CLAMP(p_font_data->face_index, 0, max_index), &fd->face); - if (error) { - FT_Done_Face(fd->face); - fd->face = nullptr; - memdelete(fd); - ERR_FAIL_V_MSG(false, "FreeType: Error loading font: '" + String(FT_Error_String(error)) + "'."); + error = FT_Open_Face(ft_library, &fargs, CLAMP(p_font_data->face_index, 0, max_index), &fd->face); + if (error) { + FT_Done_Face(fd->face); + fd->face = nullptr; + memdelete(fd); + ERR_FAIL_V_MSG(false, "FreeType: Error loading font: '" + String(FT_Error_String(error)) + "'."); + } } if (p_font_data->msdf) { @@ -1788,6 +1793,8 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_f } _FORCE_INLINE_ void TextServerAdvanced::_font_clear_cache(FontAdvanced *p_font_data) { + MutexLock ftlock(ft_mutex); + for (const KeyValue<Vector2i, FontForSizeAdvanced *> &E : p_font_data->cache) { memdelete(E.value); } @@ -1894,6 +1901,8 @@ int64_t TextServerAdvanced::_font_get_face_count(const RID &p_font_rid) const { fargs.flags = FT_OPEN_MEMORY; fargs.stream = &stream; + MutexLock ftlock(ft_mutex); + FT_Face tmp_face = nullptr; error = FT_Open_Face(ft_library, &fargs, -1, &tmp_face); if (error == 0) { @@ -2285,6 +2294,7 @@ void TextServerAdvanced::_font_clear_size_cache(const RID &p_font_rid) { ERR_FAIL_COND(!fd); MutexLock lock(fd->mutex); + MutexLock ftlock(ft_mutex); for (const KeyValue<Vector2i, FontForSizeAdvanced *> &E : fd->cache) { memdelete(E.value); } @@ -2296,6 +2306,7 @@ void TextServerAdvanced::_font_remove_size_cache(const RID &p_font_rid, const Ve ERR_FAIL_COND(!fd); MutexLock lock(fd->mutex); + MutexLock ftlock(ft_mutex); if (fd->cache.has(p_size)) { memdelete(fd->cache[p_size]); fd->cache.erase(p_size); diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h index f092fa8cca..ce08cf7694 100644 --- a/modules/text_server_adv/text_server_adv.h +++ b/modules/text_server_adv/text_server_adv.h @@ -616,6 +616,8 @@ class TextServerAdvanced : public TextServerExtension { _FORCE_INLINE_ void _add_featuers(const Dictionary &p_source, Vector<hb_feature_t> &r_ftrs); + Mutex ft_mutex; + // HarfBuzz bitmap font interface. static hb_font_funcs_t *funcs; diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp index 15124ad488..8687726287 100644 --- a/modules/text_server_fb/text_server_fb.cpp +++ b/modules/text_server_fb/text_server_fb.cpp @@ -113,6 +113,8 @@ int64_t TextServerFallback::_get_features() const { void TextServerFallback::_free_rid(const RID &p_rid) { _THREAD_SAFE_METHOD_ if (font_owner.owns(p_rid)) { + MutexLock ftlock(ft_mutex); + FontFallback *fd = font_owner.get_or_null(p_rid); { MutexLock lock(fd->mutex); @@ -760,45 +762,48 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontFallback *p_f // Init dynamic font. #ifdef MODULE_FREETYPE_ENABLED int error = 0; - if (!ft_library) { - error = FT_Init_FreeType(&ft_library); - if (error != 0) { - memdelete(fd); - ERR_FAIL_V_MSG(false, "FreeType: Error initializing library: '" + String(FT_Error_String(error)) + "'."); - } + { + MutexLock ftlock(ft_mutex); + if (!ft_library) { + error = FT_Init_FreeType(&ft_library); + if (error != 0) { + memdelete(fd); + ERR_FAIL_V_MSG(false, "FreeType: Error initializing library: '" + String(FT_Error_String(error)) + "'."); + } #ifdef MODULE_SVG_ENABLED - FT_Property_Set(ft_library, "ot-svg", "svg-hooks", get_tvg_svg_in_ot_hooks()); + FT_Property_Set(ft_library, "ot-svg", "svg-hooks", get_tvg_svg_in_ot_hooks()); #endif - } - - memset(&fd->stream, 0, sizeof(FT_StreamRec)); - fd->stream.base = (unsigned char *)p_font_data->data_ptr; - fd->stream.size = p_font_data->data_size; - fd->stream.pos = 0; - - FT_Open_Args fargs; - memset(&fargs, 0, sizeof(FT_Open_Args)); - fargs.memory_base = (unsigned char *)p_font_data->data_ptr; - fargs.memory_size = p_font_data->data_size; - fargs.flags = FT_OPEN_MEMORY; - fargs.stream = &fd->stream; + } - int max_index = 0; - FT_Face tmp_face = nullptr; - error = FT_Open_Face(ft_library, &fargs, -1, &tmp_face); - if (tmp_face && error == 0) { - max_index = tmp_face->num_faces - 1; - } - if (tmp_face) { - FT_Done_Face(tmp_face); - } + memset(&fd->stream, 0, sizeof(FT_StreamRec)); + fd->stream.base = (unsigned char *)p_font_data->data_ptr; + fd->stream.size = p_font_data->data_size; + fd->stream.pos = 0; + + FT_Open_Args fargs; + memset(&fargs, 0, sizeof(FT_Open_Args)); + fargs.memory_base = (unsigned char *)p_font_data->data_ptr; + fargs.memory_size = p_font_data->data_size; + fargs.flags = FT_OPEN_MEMORY; + fargs.stream = &fd->stream; + + int max_index = 0; + FT_Face tmp_face = nullptr; + error = FT_Open_Face(ft_library, &fargs, -1, &tmp_face); + if (tmp_face && error == 0) { + max_index = tmp_face->num_faces - 1; + } + if (tmp_face) { + FT_Done_Face(tmp_face); + } - error = FT_Open_Face(ft_library, &fargs, CLAMP(p_font_data->face_index, 0, max_index), &fd->face); - if (error) { - FT_Done_Face(fd->face); - fd->face = nullptr; - memdelete(fd); - ERR_FAIL_V_MSG(false, "FreeType: Error loading font: '" + String(FT_Error_String(error)) + "'."); + error = FT_Open_Face(ft_library, &fargs, CLAMP(p_font_data->face_index, 0, max_index), &fd->face); + if (error) { + FT_Done_Face(fd->face); + fd->face = nullptr; + memdelete(fd); + ERR_FAIL_V_MSG(false, "FreeType: Error loading font: '" + String(FT_Error_String(error)) + "'."); + } } if (p_font_data->msdf) { @@ -909,6 +914,8 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontFallback *p_f } _FORCE_INLINE_ void TextServerFallback::_font_clear_cache(FontFallback *p_font_data) { + MutexLock ftlock(ft_mutex); + for (const KeyValue<Vector2i, FontForSizeFallback *> &E : p_font_data->cache) { memdelete(E.value); } @@ -1012,6 +1019,8 @@ int64_t TextServerFallback::_font_get_face_count(const RID &p_font_rid) const { fargs.flags = FT_OPEN_MEMORY; fargs.stream = &stream; + MutexLock ftlock(ft_mutex); + FT_Face tmp_face = nullptr; error = FT_Open_Face(ft_library, &fargs, -1, &tmp_face); if (error == 0) { @@ -1393,6 +1402,7 @@ void TextServerFallback::_font_clear_size_cache(const RID &p_font_rid) { ERR_FAIL_COND(!fd); MutexLock lock(fd->mutex); + MutexLock ftlock(ft_mutex); for (const KeyValue<Vector2i, FontForSizeFallback *> &E : fd->cache) { memdelete(E.value); } @@ -1404,6 +1414,7 @@ void TextServerFallback::_font_remove_size_cache(const RID &p_font_rid, const Ve ERR_FAIL_COND(!fd); MutexLock lock(fd->mutex); + MutexLock ftlock(ft_mutex); if (fd->cache.has(p_size)) { memdelete(fd->cache[p_size]); fd->cache.erase(p_size); diff --git a/modules/text_server_fb/text_server_fb.h b/modules/text_server_fb/text_server_fb.h index 12ed21ee95..d9e471154d 100644 --- a/modules/text_server_fb/text_server_fb.h +++ b/modules/text_server_fb/text_server_fb.h @@ -531,6 +531,8 @@ class TextServerFallback : public TextServerExtension { void _realign(ShapedTextDataFallback *p_sd) const; + Mutex ft_mutex; + protected: static void _bind_methods(){}; |