summaryrefslogtreecommitdiffstats
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/csg/csg_shape.cpp4
-rw-r--r--modules/csg/doc_classes/CSGMesh3D.xml3
-rw-r--r--modules/dds/image_loader_dds.cpp1
-rw-r--r--modules/denoise/SCsub9
-rw-r--r--modules/gdscript/editor/gdscript_docgen.cpp193
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp58
-rw-r--r--modules/gdscript/gdscript_byte_codegen.cpp25
-rw-r--r--modules/gdscript/gdscript_byte_codegen.h5
-rw-r--r--modules/gdscript/gdscript_codegen.h4
-rw-r--r--modules/gdscript/gdscript_compiler.cpp4
-rw-r--r--modules/gdscript/gdscript_lambda_callable.cpp4
-rw-r--r--modules/gdscript/gdscript_lambda_callable.h1
-rw-r--r--modules/gdscript/gdscript_parser.cpp17
-rw-r--r--modules/gdscript/gdscript_parser.h4
-rw-r--r--modules/gdscript/gdscript_rpc_callable.cpp4
-rw-r--r--modules/gdscript/gdscript_rpc_callable.h1
-rw-r--r--modules/gdscript/gdscript_tokenizer.cpp18
-rw-r--r--modules/gdscript/gdscript_warning.cpp9
-rw-r--r--modules/gdscript/gdscript_warning.h2
-rw-r--r--modules/gdscript/icons/GDScript.svg2
-rw-r--r--modules/gdscript/icons/GDScriptInternal.svg2
-rw-r--r--modules/gdscript/language_server/gdscript_language_server.cpp1
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_in_static_function.gd9
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_in_static_function.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/inner_class_access_from_inside.gd21
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/inner_class_access_from_inside.out5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_equal_to_inferred.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_equal_to_inferred.out5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_supertype_of_inferred.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_supertype_of_inferred.out5
-rw-r--r--modules/gdscript/tests/scripts/parser/.editorconfig2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/mixed_indentation_on_blank_lines.gd20
-rw-r--r--modules/gdscript/tests/scripts/parser/features/mixed_indentation_on_blank_lines.out4
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/for_loop_iterator_type_not_match_specified.gd4
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/for_loop_iterator_type_not_match_specified.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd34
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out25
-rw-r--r--modules/glslang/register_types.cpp30
-rw-r--r--modules/gltf/doc_classes/GLTFPhysicsBody.xml5
-rw-r--r--modules/gltf/extensions/gltf_document_extension_texture_ktx.cpp66
-rw-r--r--modules/gltf/extensions/gltf_document_extension_texture_ktx.h47
-rw-r--r--modules/gltf/extensions/physics/gltf_physics_body.cpp30
-rw-r--r--modules/gltf/extensions/physics/gltf_physics_body.h8
-rw-r--r--modules/gltf/register_types.cpp19
-rw-r--r--modules/gridmap/doc_classes/GridMap.xml5
-rw-r--r--modules/gridmap/editor/grid_map_editor_plugin.cpp111
-rw-r--r--modules/gridmap/editor/grid_map_editor_plugin.h3
-rw-r--r--modules/gridmap/grid_map.cpp2
-rw-r--r--modules/ktx/SCsub58
-rw-r--r--modules/ktx/config.py7
-rw-r--r--modules/ktx/register_types.cpp53
-rw-r--r--modules/ktx/register_types.h39
-rw-r--r--modules/ktx/texture_loader_ktx.cpp562
-rw-r--r--modules/ktx/texture_loader_ktx.h48
-rw-r--r--modules/mono/editor/bindings_generator.cpp81
-rw-r--r--modules/mono/editor/bindings_generator.h8
-rw-r--r--modules/mono/godotsharp_dirs.cpp4
-rw-r--r--modules/mono/icons/BuildCSharp.svg2
-rw-r--r--modules/navigation/godot_navigation_server.cpp34
-rw-r--r--modules/navigation/godot_navigation_server.h5
-rw-r--r--modules/navigation/nav_mesh_generator_3d.cpp125
-rw-r--r--modules/navigation/nav_mesh_generator_3d.h32
-rw-r--r--modules/noise/doc_classes/FastNoiseLite.xml4
-rw-r--r--modules/noise/doc_classes/Noise.xml2
-rw-r--r--modules/noise/doc_classes/NoiseTexture2D.xml4
-rw-r--r--modules/noise/doc_classes/NoiseTexture3D.xml4
-rw-r--r--modules/noise/fastnoise_lite.h2
-rw-r--r--modules/openxr/SCsub5
-rw-r--r--modules/openxr/extensions/openxr_extension_wrapper_extension.cpp4
-rw-r--r--modules/openxr/openxr_api.cpp33
-rw-r--r--modules/openxr/openxr_api.h2
-rw-r--r--modules/openxr/openxr_interface.cpp4
-rw-r--r--modules/svg/SCsub13
-rw-r--r--modules/svg/image_loader_svg.cpp2
-rw-r--r--modules/text_server_adv/SCsub2
-rw-r--r--modules/text_server_adv/gdextension_build/SConstruct14
-rw-r--r--modules/text_server_adv/text_server_adv.cpp114
-rw-r--r--modules/text_server_adv/text_server_adv.h6
-rw-r--r--modules/text_server_adv/thorvg_svg_in_ot.cpp17
-rw-r--r--modules/text_server_adv/thorvg_svg_in_ot.h14
-rw-r--r--modules/text_server_fb/SCsub2
-rw-r--r--modules/text_server_fb/gdextension_build/SConstruct14
-rw-r--r--modules/text_server_fb/text_server_fb.cpp20
-rw-r--r--modules/text_server_fb/text_server_fb.h2
-rw-r--r--modules/text_server_fb/thorvg_svg_in_ot.cpp2
-rw-r--r--modules/vorbis/doc_classes/ResourceImporterOggVorbis.xml2
-rw-r--r--modules/zip/doc_classes/ZIPReader.xml9
-rw-r--r--modules/zip/zip_reader.cpp16
-rw-r--r--modules/zip/zip_reader.h1
91 files changed, 1882 insertions, 343 deletions
diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp
index 4c217dac28..7cafccfdcb 100644
--- a/modules/csg/csg_shape.cpp
+++ b/modules/csg/csg_shape.cpp
@@ -1933,7 +1933,7 @@ CSGBrush *CSGPolygon3D::_build_brush() {
case PATH_ROTATION_PATH:
break;
case PATH_ROTATION_PATH_FOLLOW:
- current_up = curve->sample_baked_up_vector(0);
+ current_up = curve->sample_baked_up_vector(0, true);
break;
}
@@ -2020,7 +2020,7 @@ CSGBrush *CSGPolygon3D::_build_brush() {
case PATH_ROTATION_PATH:
break;
case PATH_ROTATION_PATH_FOLLOW:
- current_up = curve->sample_baked_up_vector(current_offset);
+ current_up = curve->sample_baked_up_vector(current_offset, true);
break;
}
diff --git a/modules/csg/doc_classes/CSGMesh3D.xml b/modules/csg/doc_classes/CSGMesh3D.xml
index 9a0f121e19..96d89ff486 100644
--- a/modules/csg/doc_classes/CSGMesh3D.xml
+++ b/modules/csg/doc_classes/CSGMesh3D.xml
@@ -16,7 +16,8 @@
</member>
<member name="mesh" type="Mesh" setter="set_mesh" getter="get_mesh">
The [Mesh] resource to use as a CSG shape.
- [b]Note:[/b] When using an [ArrayMesh], avoid meshes with vertex normals unless a flat shader is required. By default, CSGMesh will ignore the mesh's vertex normals and use a smooth shader calculated using the faces' normals. If a flat shader is required, ensure that all faces' vertex normals are parallel.
+ [b]Note:[/b] When using an [ArrayMesh], all vertex attributes except [constant Mesh.ARRAY_VERTEX], [constant Mesh.ARRAY_NORMAL] and [constant Mesh.ARRAY_TEX_UV] are left unused. Only [constant Mesh.ARRAY_VERTEX] and [constant Mesh.ARRAY_TEX_UV] will be passed to the GPU.
+ [constant Mesh.ARRAY_NORMAL] is only used to determine which faces require the use of flat shading. By default, CSGMesh will ignore the mesh's vertex normals, recalculate them for each vertex and use a smooth shader. If a flat shader is required for a face, ensure that all vertex normals of the face are approximately equal.
</member>
</members>
</class>
diff --git a/modules/dds/image_loader_dds.cpp b/modules/dds/image_loader_dds.cpp
index 42c8120595..13ca1e6bff 100644
--- a/modules/dds/image_loader_dds.cpp
+++ b/modules/dds/image_loader_dds.cpp
@@ -66,7 +66,6 @@ enum DDSFormat {
DDS_BGR5A1,
DDS_BGR565,
DDS_BGR10A2,
- DDS_INDEXED,
DDS_LUMINANCE,
DDS_LUMINANCE_ALPHA,
DDS_MAX
diff --git a/modules/denoise/SCsub b/modules/denoise/SCsub
index 779ce165d2..967a511e1e 100644
--- a/modules/denoise/SCsub
+++ b/modules/denoise/SCsub
@@ -109,6 +109,15 @@ env_oidn.AppendUnique(CPPDEFINES=["NDEBUG"]) # No assert() even in debug builds
env_thirdparty = env_oidn.Clone()
env_thirdparty.disable_warnings()
+
+if env["disable_exceptions"]:
+ # OIDN hard-requires exceptions, so we re-enable them here.
+ if env.msvc and ("_HAS_EXCEPTIONS", 0) in env_thirdparty["CPPDEFINES"]:
+ env_thirdparty["CPPDEFINES"].remove(("_HAS_EXCEPTIONS", 0))
+ env_thirdparty.AppendUnique(CCFLAGS=["/EHsc"])
+ elif not env.msvc and "-fno-exceptions" in env_thirdparty["CCFLAGS"]:
+ env_thirdparty["CCFLAGS"].remove("-fno-exceptions")
+
env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources)
env.modules_sources += thirdparty_obj
diff --git a/modules/gdscript/editor/gdscript_docgen.cpp b/modules/gdscript/editor/gdscript_docgen.cpp
index 26f326838c..1aecfc6de1 100644
--- a/modules/gdscript/editor/gdscript_docgen.cpp
+++ b/modules/gdscript/editor/gdscript_docgen.cpp
@@ -36,19 +36,19 @@ using GDP = GDScriptParser;
using GDType = GDP::DataType;
static String _get_script_path(const String &p_path) {
- return vformat(R"("%s")", p_path.get_slice("://", 1));
+ return p_path.trim_prefix("res://").quote();
}
static String _get_class_name(const GDP::ClassNode &p_class) {
const GDP::ClassNode *curr_class = &p_class;
- if (!curr_class->identifier) { // All inner classes have a identifier, so this is the outer class
+ if (!curr_class->identifier) { // All inner classes have an identifier, so this is the outer class.
return _get_script_path(curr_class->fqcn);
}
String full_name = curr_class->identifier->name;
while (curr_class->outer) {
curr_class = curr_class->outer;
- if (!curr_class->identifier) { // All inner classes have a identifier, so this is the outer class
+ if (!curr_class->identifier) { // All inner classes have an identifier, so this is the outer class.
return vformat("%s.%s", _get_script_path(curr_class->fqcn), full_name);
}
full_name = vformat("%s.%s", curr_class->identifier->name, full_name);
@@ -56,20 +56,71 @@ static String _get_class_name(const GDP::ClassNode &p_class) {
return full_name;
}
-static PropertyInfo _property_info_from_datatype(const GDType &p_type) {
- PropertyInfo pi;
- pi.type = p_type.builtin_type;
- if (p_type.kind == GDType::CLASS) {
- pi.class_name = _get_class_name(*p_type.class_type);
- } else if (p_type.kind == GDType::ENUM && p_type.enum_type != StringName()) {
- pi.type = Variant::INT; // Only int types are recognized as enums by the EditorHelp
- pi.usage |= PROPERTY_USAGE_CLASS_IS_ENUM;
- // Replace :: from enum's use of fully qualified class names with regular .
- pi.class_name = String(p_type.native_type).replace("::", ".");
- } else if (p_type.kind == GDType::NATIVE) {
- pi.class_name = p_type.native_type;
+static void _doctype_from_gdtype(const GDType &p_gdtype, String &r_type, String &r_enum, bool p_is_return = false) {
+ if (!p_gdtype.is_hard_type()) {
+ r_type = "Variant";
+ return;
+ }
+ switch (p_gdtype.kind) {
+ case GDType::BUILTIN:
+ if (p_gdtype.builtin_type == Variant::NIL) {
+ r_type = p_is_return ? "void" : "null";
+ return;
+ }
+ if (p_gdtype.builtin_type == Variant::ARRAY && p_gdtype.has_container_element_type()) {
+ _doctype_from_gdtype(p_gdtype.get_container_element_type(), r_type, r_enum);
+ if (!r_enum.is_empty()) {
+ r_type = "int[]";
+ r_enum += "[]";
+ return;
+ }
+ if (!r_type.is_empty() && r_type != "Variant") {
+ r_type += "[]";
+ return;
+ }
+ }
+ r_type = Variant::get_type_name(p_gdtype.builtin_type);
+ return;
+ case GDType::NATIVE:
+ r_type = p_gdtype.native_type;
+ return;
+ case GDType::SCRIPT:
+ if (p_gdtype.script_type.is_valid()) {
+ if (p_gdtype.script_type->get_global_name() != StringName()) {
+ r_type = _get_script_path(p_gdtype.script_type->get_global_name());
+ return;
+ }
+ if (!p_gdtype.script_type->get_path().is_empty()) {
+ r_type = _get_script_path(p_gdtype.script_type->get_path());
+ return;
+ }
+ }
+ if (!p_gdtype.script_path.is_empty()) {
+ r_type = _get_script_path(p_gdtype.script_path);
+ return;
+ }
+ r_type = "Object";
+ return;
+ case GDType::CLASS:
+ r_type = _get_class_name(*p_gdtype.class_type);
+ return;
+ case GDType::ENUM:
+ r_type = "int";
+ r_enum = String(p_gdtype.native_type).replace("::", ".");
+ if (r_enum.begins_with("res://")) {
+ r_enum = r_enum.trim_prefix("res://");
+ int dot_pos = r_enum.rfind(".");
+ if (dot_pos >= 0) {
+ r_enum = r_enum.left(dot_pos).quote() + r_enum.substr(dot_pos);
+ }
+ }
+ return;
+ case GDType::VARIANT:
+ case GDType::RESOLVING:
+ case GDType::UNRESOLVED:
+ r_type = "Variant";
+ return;
}
- return pi;
}
void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_class) {
@@ -120,8 +171,8 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c
p_script->member_lines[class_name] = inner_class->start_line;
- // Recursively generate inner class docs
- // Needs inner GDScripts to exist: previously generated in GDScriptCompiler::make_scripts()
+ // Recursively generate inner class docs.
+ // Needs inner GDScripts to exist: previously generated in GDScriptCompiler::make_scripts().
GDScriptDocGen::generate_docs(*p_script->subclasses[class_name], inner_class);
} break;
@@ -144,22 +195,33 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c
p_script->member_lines[func_name] = m_func->start_line;
- MethodInfo mi;
- mi.name = func_name;
+ DocData::MethodDoc method_doc;
+ method_doc.name = func_name;
+ method_doc.description = m_func->doc_data.description;
+ method_doc.is_deprecated = m_func->doc_data.is_deprecated;
+ method_doc.is_experimental = m_func->doc_data.is_experimental;
+ method_doc.qualifiers = m_func->is_static ? "static" : "";
if (m_func->return_type) {
- mi.return_val = _property_info_from_datatype(m_func->return_type->get_datatype());
+ _doctype_from_gdtype(m_func->return_type->get_datatype(), method_doc.return_type, method_doc.return_enum, true);
+ } else {
+ method_doc.return_type = "Variant";
}
+
for (const GDScriptParser::ParameterNode *p : m_func->parameters) {
- PropertyInfo pi = _property_info_from_datatype(p->get_datatype());
- pi.name = p->identifier->name;
- mi.arguments.push_back(pi);
+ DocData::ArgumentDoc arg_doc;
+ arg_doc.name = p->identifier->name;
+ _doctype_from_gdtype(p->get_datatype(), arg_doc.type, arg_doc.enumeration);
+ if (p->initializer != nullptr) {
+ if (p->initializer->is_constant) {
+ arg_doc.default_value = p->initializer->reduced_value.get_construct_string().replace("\n", "\\n");
+ } else {
+ arg_doc.default_value = "<unknown>";
+ }
+ }
+ method_doc.arguments.push_back(arg_doc);
}
- DocData::MethodDoc method_doc;
- DocData::method_doc_from_methodinfo(method_doc, mi, m_func->doc_data.description);
- method_doc.is_deprecated = m_func->doc_data.is_deprecated;
- method_doc.is_experimental = m_func->doc_data.is_experimental;
doc.methods.push_back(method_doc);
} break;
@@ -169,18 +231,19 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c
p_script->member_lines[signal_name] = m_signal->start_line;
- MethodInfo mi;
- mi.name = signal_name;
- for (const GDScriptParser::ParameterNode *p : m_signal->parameters) {
- PropertyInfo pi = _property_info_from_datatype(p->get_datatype());
- pi.name = p->identifier->name;
- mi.arguments.push_back(pi);
- }
-
DocData::MethodDoc signal_doc;
- DocData::signal_doc_from_methodinfo(signal_doc, mi, m_signal->doc_data.description);
+ signal_doc.name = signal_name;
+ signal_doc.description = m_signal->doc_data.description;
signal_doc.is_deprecated = m_signal->doc_data.is_deprecated;
signal_doc.is_experimental = m_signal->doc_data.is_experimental;
+
+ for (const GDScriptParser::ParameterNode *p : m_signal->parameters) {
+ DocData::ArgumentDoc arg_doc;
+ arg_doc.name = p->identifier->name;
+ _doctype_from_gdtype(p->get_datatype(), arg_doc.type, arg_doc.enumeration);
+ signal_doc.arguments.push_back(arg_doc);
+ }
+
doc.signals.push_back(signal_doc);
} break;
@@ -191,50 +254,41 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c
p_script->member_lines[var_name] = m_var->start_line;
DocData::PropertyDoc prop_doc;
-
prop_doc.name = var_name;
prop_doc.description = m_var->doc_data.description;
prop_doc.is_deprecated = m_var->doc_data.is_deprecated;
prop_doc.is_experimental = m_var->doc_data.is_experimental;
+ _doctype_from_gdtype(m_var->get_datatype(), prop_doc.type, prop_doc.enumeration);
- GDType dt = m_var->get_datatype();
- switch (dt.kind) {
- case GDType::CLASS:
- prop_doc.type = _get_class_name(*dt.class_type);
+ switch (m_var->property) {
+ case GDP::VariableNode::PROP_NONE:
break;
- case GDType::VARIANT:
- prop_doc.type = "Variant";
+ case GDP::VariableNode::PROP_INLINE:
+ if (m_var->setter != nullptr) {
+ prop_doc.setter = m_var->setter->identifier->name;
+ }
+ if (m_var->getter != nullptr) {
+ prop_doc.getter = m_var->getter->identifier->name;
+ }
break;
- case GDType::ENUM:
- prop_doc.type = Variant::get_type_name(dt.builtin_type);
- // Replace :: from enum's use of fully qualified class names with regular .
- prop_doc.enumeration = String(dt.native_type).replace("::", ".");
- break;
- case GDType::NATIVE:;
- prop_doc.type = dt.native_type;
- break;
- case GDType::BUILTIN:
- prop_doc.type = Variant::get_type_name(dt.builtin_type);
- break;
- default:
- // SCRIPT: can be preload()'d and perhaps used as types directly?
- // RESOLVING & UNRESOLVED should never happen since docgen requires analyzing w/o errors
+ case GDP::VariableNode::PROP_SETGET:
+ if (m_var->setter_pointer != nullptr) {
+ prop_doc.setter = m_var->setter_pointer->name;
+ }
+ if (m_var->getter_pointer != nullptr) {
+ prop_doc.getter = m_var->getter_pointer->name;
+ }
break;
}
- if (m_var->property == GDP::VariableNode::PROP_SETGET) {
- if (m_var->setter_pointer != nullptr) {
- prop_doc.setter = m_var->setter_pointer->name;
- }
- if (m_var->getter_pointer != nullptr) {
- prop_doc.getter = m_var->getter_pointer->name;
+ if (m_var->initializer) {
+ if (m_var->initializer->is_constant) {
+ prop_doc.default_value = m_var->initializer->reduced_value.get_construct_string().replace("\n", "\\n");
+ } else {
+ prop_doc.default_value = "<unknown>";
}
}
- if (m_var->initializer && m_var->initializer->is_constant) {
- prop_doc.default_value = m_var->initializer->reduced_value.get_construct_string().replace("\n", "");
- }
-
prop_doc.overridden = false;
doc.properties.push_back(prop_doc);
@@ -280,8 +334,7 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c
const_doc.is_experimental = m_enum_val.doc_data.is_experimental;
doc.constants.push_back(const_doc);
} break;
- case GDP::ClassNode::Member::GROUP:
- case GDP::ClassNode::Member::UNDEFINED:
+
default:
break;
}
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index cb04913620..214b484b12 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -2001,13 +2001,16 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) {
}
GDScriptParser::DataType variable_type;
+ String list_visible_type = "<unresolved type>";
if (list_resolved) {
variable_type.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED;
variable_type.kind = GDScriptParser::DataType::BUILTIN;
variable_type.builtin_type = Variant::INT;
+ list_visible_type = "Array[int]"; // NOTE: `range()` has `Array` return type.
} else if (p_for->list) {
resolve_node(p_for->list, false);
GDScriptParser::DataType list_type = p_for->list->get_datatype();
+ list_visible_type = list_type.to_string();
if (!list_type.is_hard_type()) {
mark_node_unsafe(p_for->list);
}
@@ -2051,8 +2054,37 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) {
push_error(vformat(R"(Unable to iterate on value of type "%s".)", list_type.to_string()), p_for->list);
}
}
+
if (p_for->variable) {
- p_for->variable->set_datatype(variable_type);
+ if (p_for->datatype_specifier) {
+ GDScriptParser::DataType specified_type = type_from_metatype(resolve_datatype(p_for->datatype_specifier));
+ if (!specified_type.is_variant()) {
+ if (variable_type.is_variant() || !variable_type.is_hard_type()) {
+ mark_node_unsafe(p_for->variable);
+ p_for->use_conversion_assign = true;
+ } else if (!is_type_compatible(specified_type, variable_type, true, p_for->variable)) {
+ if (is_type_compatible(variable_type, specified_type)) {
+ mark_node_unsafe(p_for->variable);
+ p_for->use_conversion_assign = true;
+ } else {
+ push_error(vformat(R"(Unable to iterate on value of type "%s" with variable of type "%s".)", list_visible_type, specified_type.to_string()), p_for->datatype_specifier);
+ }
+ } else if (!is_type_compatible(specified_type, variable_type)) {
+ p_for->use_conversion_assign = true;
+#ifdef DEBUG_ENABLED
+ } else {
+ parser->push_warning(p_for->datatype_specifier, GDScriptWarning::REDUNDANT_FOR_VARIABLE_TYPE, p_for->variable->name, variable_type.to_string(), specified_type.to_string());
+#endif
+ }
+#ifdef DEBUG_ENABLED
+ } else {
+ parser->push_warning(p_for->datatype_specifier, GDScriptWarning::REDUNDANT_FOR_VARIABLE_TYPE, p_for->variable->name, variable_type.to_string(), specified_type.to_string());
+#endif
+ }
+ p_for->variable->set_datatype(specified_type);
+ } else {
+ p_for->variable->set_datatype(variable_type);
+ }
}
resolve_suite(p_for->loop);
@@ -3301,17 +3333,26 @@ void GDScriptAnalyzer::reduce_dictionary(GDScriptParser::DictionaryNode *p_dicti
void GDScriptAnalyzer::reduce_get_node(GDScriptParser::GetNodeNode *p_get_node) {
GDScriptParser::DataType result;
- result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
- result.kind = GDScriptParser::DataType::NATIVE;
- result.native_type = SNAME("Node");
- result.builtin_type = Variant::OBJECT;
+ result.kind = GDScriptParser::DataType::VARIANT;
- if (!ClassDB::is_parent_class(parser->current_class->base_type.native_type, result.native_type)) {
- push_error(R"*(Cannot use shorthand "get_node()" notation ("$") on a class that isn't a node.)*", p_get_node);
+ if (!ClassDB::is_parent_class(parser->current_class->base_type.native_type, SNAME("Node"))) {
+ push_error(vformat(R"*(Cannot use shorthand "get_node()" notation ("%c") on a class that isn't a node.)*", p_get_node->use_dollar ? '$' : '%'), p_get_node);
+ p_get_node->set_datatype(result);
+ return;
+ }
+
+ if (static_context) {
+ push_error(vformat(R"*(Cannot use shorthand "get_node()" notation ("%c") in a static function.)*", p_get_node->use_dollar ? '$' : '%'), p_get_node);
+ p_get_node->set_datatype(result);
+ return;
}
mark_lambda_use_self();
+ result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ result.kind = GDScriptParser::DataType::NATIVE;
+ result.builtin_type = Variant::OBJECT;
+ result.native_type = SNAME("Node");
p_get_node->set_datatype(result);
}
@@ -3469,6 +3510,9 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
for (GDScriptParser::ClassNode *script_class : script_classes) {
if (p_base == nullptr && script_class->identifier && script_class->identifier->name == name) {
reduce_identifier_from_base_set_class(p_identifier, script_class->get_datatype());
+ if (script_class->outer != nullptr) {
+ p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CLASS;
+ }
return;
}
diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp
index 6057a00f9b..af7862efc5 100644
--- a/modules/gdscript/gdscript_byte_codegen.cpp
+++ b/modules/gdscript/gdscript_byte_codegen.cpp
@@ -1494,19 +1494,16 @@ void GDScriptByteCodeGenerator::start_for(const GDScriptDataType &p_iterator_typ
for_container_variables.push_back(container);
}
-void GDScriptByteCodeGenerator::write_for_assignment(const Address &p_variable, const Address &p_list) {
+void GDScriptByteCodeGenerator::write_for_assignment(const Address &p_list) {
const Address &container = for_container_variables.back()->get();
// Assign container.
append_opcode(GDScriptFunction::OPCODE_ASSIGN);
append(container);
append(p_list);
-
- for_iterator_variables.push_back(p_variable);
}
-void GDScriptByteCodeGenerator::write_for() {
- const Address &iterator = for_iterator_variables.back()->get();
+void GDScriptByteCodeGenerator::write_for(const Address &p_variable, bool p_use_conversion) {
const Address &counter = for_counter_variables.back()->get();
const Address &container = for_container_variables.back()->get();
@@ -1599,11 +1596,16 @@ void GDScriptByteCodeGenerator::write_for() {
}
}
+ Address temp;
+ if (p_use_conversion) {
+ temp = Address(Address::LOCAL_VARIABLE, add_local("@iterator_temp", GDScriptDataType()));
+ }
+
// Begin loop.
append_opcode(begin_opcode);
append(counter);
append(container);
- append(iterator);
+ append(p_use_conversion ? temp : p_variable);
for_jmp_addrs.push_back(opcodes.size());
append(0); // End of loop address, will be patched.
append_opcode(GDScriptFunction::OPCODE_JUMP);
@@ -1615,9 +1617,17 @@ void GDScriptByteCodeGenerator::write_for() {
append_opcode(iterate_opcode);
append(counter);
append(container);
- append(iterator);
+ append(p_use_conversion ? temp : p_variable);
for_jmp_addrs.push_back(opcodes.size());
append(0); // Jump destination, will be patched.
+
+ if (p_use_conversion) {
+ write_assign_with_conversion(p_variable, temp);
+ const GDScriptDataType &type = p_variable.type;
+ if (type.kind != GDScriptDataType::BUILTIN || type.builtin_type == Variant::ARRAY || type.builtin_type == Variant::DICTIONARY) {
+ write_assign_false(temp); // Can contain RefCounted, so clear it.
+ }
+ }
}
void GDScriptByteCodeGenerator::write_endfor() {
@@ -1639,7 +1649,6 @@ void GDScriptByteCodeGenerator::write_endfor() {
current_breaks_to_patch.pop_back();
// Pop state.
- for_iterator_variables.pop_back();
for_counter_variables.pop_back();
for_container_variables.pop_back();
}
diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h
index bbcd252b13..671dea5d6d 100644
--- a/modules/gdscript/gdscript_byte_codegen.h
+++ b/modules/gdscript/gdscript_byte_codegen.h
@@ -143,7 +143,6 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
// Lists since these can be nested.
List<int> if_jmp_addrs;
List<int> for_jmp_addrs;
- List<Address> for_iterator_variables;
List<Address> for_counter_variables;
List<Address> for_container_variables;
List<int> while_jmp_addrs;
@@ -536,8 +535,8 @@ public:
virtual void write_jump_if_shared(const Address &p_value) override;
virtual void write_end_jump_if_shared() override;
virtual void start_for(const GDScriptDataType &p_iterator_type, const GDScriptDataType &p_list_type) override;
- virtual void write_for_assignment(const Address &p_variable, const Address &p_list) override;
- virtual void write_for() override;
+ virtual void write_for_assignment(const Address &p_list) override;
+ virtual void write_for(const Address &p_variable, bool p_use_conversion) override;
virtual void write_endfor() override;
virtual void start_while_condition() override;
virtual void write_while(const Address &p_condition) override;
diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h
index 9810f5395a..cf17353dec 100644
--- a/modules/gdscript/gdscript_codegen.h
+++ b/modules/gdscript/gdscript_codegen.h
@@ -145,8 +145,8 @@ public:
virtual void write_jump_if_shared(const Address &p_value) = 0;
virtual void write_end_jump_if_shared() = 0;
virtual void start_for(const GDScriptDataType &p_iterator_type, const GDScriptDataType &p_list_type) = 0;
- virtual void write_for_assignment(const Address &p_variable, const Address &p_list) = 0;
- virtual void write_for() = 0;
+ virtual void write_for_assignment(const Address &p_list) = 0;
+ virtual void write_for(const Address &p_variable, bool p_use_conversion) = 0;
virtual void write_endfor() = 0;
virtual void start_while_condition() = 0; // Used to allow a jump to the expression evaluation.
virtual void write_while(const Address &p_condition) = 0;
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index 3366fa2eec..f964db231a 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -1953,13 +1953,13 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
return err;
}
- gen->write_for_assignment(iterator, list);
+ gen->write_for_assignment(list);
if (list.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
codegen.generator->pop_temporary();
}
- gen->write_for();
+ gen->write_for(iterator, for_n->use_conversion_assign);
err = _parse_block(codegen, for_n->loop);
if (err) {
diff --git a/modules/gdscript/gdscript_lambda_callable.cpp b/modules/gdscript/gdscript_lambda_callable.cpp
index 9e14e43a58..3b89f077bd 100644
--- a/modules/gdscript/gdscript_lambda_callable.cpp
+++ b/modules/gdscript/gdscript_lambda_callable.cpp
@@ -67,6 +67,10 @@ ObjectID GDScriptLambdaCallable::get_object() const {
return script->get_instance_id();
}
+StringName GDScriptLambdaCallable::get_method() const {
+ return function->get_name();
+}
+
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();
diff --git a/modules/gdscript/gdscript_lambda_callable.h b/modules/gdscript/gdscript_lambda_callable.h
index 33bdf6dfc1..1c7a18fb9d 100644
--- a/modules/gdscript/gdscript_lambda_callable.h
+++ b/modules/gdscript/gdscript_lambda_callable.h
@@ -56,6 +56,7 @@ public:
CompareEqualFunc get_compare_equal_func() const override;
CompareLessFunc get_compare_less_func() const override;
ObjectID get_object() const override;
+ StringName get_method() const override;
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);
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index debc85ebbf..1dde67d2d1 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -1850,7 +1850,18 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() {
n_for->variable = parse_identifier();
}
- consume(GDScriptTokenizer::Token::IN, R"(Expected "in" after "for" variable name.)");
+ if (match(GDScriptTokenizer::Token::COLON)) {
+ n_for->datatype_specifier = parse_type();
+ if (n_for->datatype_specifier == nullptr) {
+ push_error(R"(Expected type specifier after ":".)");
+ }
+ }
+
+ if (n_for->datatype_specifier == nullptr) {
+ consume(GDScriptTokenizer::Token::IN, R"(Expected "in" or ":" after "for" variable name.)");
+ } else {
+ consume(GDScriptTokenizer::Token::IN, R"(Expected "in" after "for" variable type specifier.)");
+ }
n_for->list = parse_expression(false);
@@ -2446,7 +2457,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_binary_operator(Expression
complete_extents(operation);
if (operation->right_operand == nullptr) {
- push_error(vformat(R"(Expected expression after "%s" operator.")", op.get_name()));
+ push_error(vformat(R"(Expected expression after "%s" operator.)", op.get_name()));
}
// TODO: Also for unary, ternary, and assignment.
@@ -3035,10 +3046,8 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p
if (previous.type == GDScriptTokenizer::Token::DOLLAR) {
// Detect initial slash, which will be handled in the loop if it matches.
match(GDScriptTokenizer::Token::SLASH);
-#ifdef DEBUG_ENABLED
} else {
get_node->use_dollar = false;
-#endif
}
int context_argument = 0;
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index 20f5dcf06d..652faaebc3 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -814,6 +814,8 @@ public:
struct ForNode : public Node {
IdentifierNode *variable = nullptr;
+ TypeNode *datatype_specifier = nullptr;
+ bool use_conversion_assign = false;
ExpressionNode *list = nullptr;
SuiteNode *loop = nullptr;
@@ -848,9 +850,7 @@ public:
struct GetNodeNode : public ExpressionNode {
String full_path;
-#ifdef DEBUG_ENABLED
bool use_dollar = true;
-#endif
GetNodeNode() {
type = GET_NODE;
diff --git a/modules/gdscript/gdscript_rpc_callable.cpp b/modules/gdscript/gdscript_rpc_callable.cpp
index a4dd8a8d3c..265e624b6c 100644
--- a/modules/gdscript/gdscript_rpc_callable.cpp
+++ b/modules/gdscript/gdscript_rpc_callable.cpp
@@ -63,6 +63,10 @@ ObjectID GDScriptRPCCallable::get_object() const {
return object->get_instance_id();
}
+StringName GDScriptRPCCallable::get_method() const {
+ return method;
+}
+
void GDScriptRPCCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const {
r_return_value = object->callp(method, p_arguments, p_argcount, r_call_error);
}
diff --git a/modules/gdscript/gdscript_rpc_callable.h b/modules/gdscript/gdscript_rpc_callable.h
index c1007b18b0..66052157be 100644
--- a/modules/gdscript/gdscript_rpc_callable.h
+++ b/modules/gdscript/gdscript_rpc_callable.h
@@ -51,6 +51,7 @@ public:
CompareEqualFunc get_compare_equal_func() const override;
CompareLessFunc get_compare_less_func() const override;
ObjectID get_object() const override;
+ StringName get_method() const override;
void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override;
Error rpc(int p_peer_id, const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) const override;
diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp
index 4f374b63b0..42b983ef45 100644
--- a/modules/gdscript/gdscript_tokenizer.cpp
+++ b/modules/gdscript/gdscript_tokenizer.cpp
@@ -1162,15 +1162,6 @@ void GDScriptTokenizer::check_indent() {
_advance();
}
- if (mixed && !(line_continuation || multiline_mode)) {
- Token error = make_error("Mixed use of tabs and spaces for indentation.");
- error.start_line = line;
- error.start_column = 1;
- error.leftmost_column = 1;
- error.rightmost_column = column;
- push_error(error);
- }
-
if (_is_at_end()) {
// Reached the end with an empty line, so just dedent as much as needed.
pending_indents -= indent_level();
@@ -1214,6 +1205,15 @@ void GDScriptTokenizer::check_indent() {
continue;
}
+ if (mixed && !line_continuation && !multiline_mode) {
+ Token error = make_error("Mixed use of tabs and spaces for indentation.");
+ error.start_line = line;
+ error.start_column = 1;
+ error.leftmost_column = 1;
+ error.rightmost_column = column;
+ push_error(error);
+ }
+
if (line_continuation || multiline_mode) {
// We cleared up all the whitespace at the beginning of the line.
// But if this is a continuation or multiline mode and we don't want any indentation change.
diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp
index 24aa793c47..4fec445995 100644
--- a/modules/gdscript/gdscript_warning.cpp
+++ b/modules/gdscript/gdscript_warning.cpp
@@ -113,6 +113,14 @@ String GDScriptWarning::get_message() const {
return R"(The "@static_unload" annotation is redundant because the file does not have a class with static variables.)";
case REDUNDANT_AWAIT:
return R"("await" keyword not needed in this case, because the expression isn't a coroutine nor a signal.)";
+ case REDUNDANT_FOR_VARIABLE_TYPE:
+ CHECK_SYMBOLS(3);
+ if (symbols[1] == symbols[2]) {
+ return vformat(R"(The for loop iterator "%s" already has inferred type "%s", the specified type is redundant.)", symbols[0], symbols[1]);
+ } else {
+ return vformat(R"(The for loop iterator "%s" has inferred type "%s" but its supertype "%s" is specified.)", symbols[0], symbols[1], symbols[2]);
+ }
+ break;
case ASSERT_ALWAYS_TRUE:
return "Assert statement is redundant because the expression is always true.";
case ASSERT_ALWAYS_FALSE:
@@ -209,6 +217,7 @@ String GDScriptWarning::get_name_from_code(Code p_code) {
"STATIC_CALLED_ON_INSTANCE",
"REDUNDANT_STATIC_UNLOAD",
"REDUNDANT_AWAIT",
+ "REDUNDANT_FOR_VARIABLE_TYPE",
"ASSERT_ALWAYS_TRUE",
"ASSERT_ALWAYS_FALSE",
"INTEGER_DIVISION",
diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h
index 8444d46a88..73e12eb20e 100644
--- a/modules/gdscript/gdscript_warning.h
+++ b/modules/gdscript/gdscript_warning.h
@@ -73,6 +73,7 @@ public:
STATIC_CALLED_ON_INSTANCE, // A static method was called on an instance of a class instead of on the class itself.
REDUNDANT_STATIC_UNLOAD, // The `@static_unload` annotation is used but the class does not have static data.
REDUNDANT_AWAIT, // await is used but expression is synchronous (not a signal nor a coroutine).
+ REDUNDANT_FOR_VARIABLE_TYPE, // The for variable type specifier is a supertype of the inferred type.
ASSERT_ALWAYS_TRUE, // Expression for assert argument is always true.
ASSERT_ALWAYS_FALSE, // Expression for assert argument is always false.
INTEGER_DIVISION, // Integer divide by integer, decimal part is discarded.
@@ -120,6 +121,7 @@ public:
WARN, // STATIC_CALLED_ON_INSTANCE
WARN, // REDUNDANT_STATIC_UNLOAD
WARN, // REDUNDANT_AWAIT
+ WARN, // REDUNDANT_FOR_VARIABLE_TYPE
WARN, // ASSERT_ALWAYS_TRUE
WARN, // ASSERT_ALWAYS_FALSE
WARN, // INTEGER_DIVISION
diff --git a/modules/gdscript/icons/GDScript.svg b/modules/gdscript/icons/GDScript.svg
index aa59125ea9..2671c007f3 100644
--- a/modules/gdscript/icons/GDScript.svg
+++ b/modules/gdscript/icons/GDScript.svg
@@ -1 +1 @@
-<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m7 1-.56445 2.2578a5 5 0 0 0 -.68945.2793l-1.9883-1.1934-1.4141 1.4141 1.1953 1.9941a5 5 0 0 0 -.28516.68555l-2.2539.5625v2l2.2578.56445a5 5 0 0 0 .2793.6875l-1.1934 1.9902 1.4141 1.4141 1.9941-1.1953a5 5 0 0 0 .68555.28516l.5625 2.2539h2l.56445-2.2578a5 5 0 0 0 .6875-.2793l1.9902 1.1934 1.4141-1.4141-1.1953-1.9941a5 5 0 0 0 .28516-.68555l2.2539-.5625v-2l-2.2578-.56445a5 5 0 0 0 -.2793-.6875l1.1934-1.9902-1.4141-1.4141-1.9941 1.1953a5 5 0 0 0 -.68555-.28516l-.5625-2.2539h-2zm1 5a2 2 0 0 1 2 2 2 2 0 0 1 -2 2 2 2 0 0 1 -2-2 2 2 0 0 1 2-2z" fill="#e0e0e0"/></svg>
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m7 1-.565 2.258a5 5 0 0 0-.689.28L3.758 2.343 2.344 3.758l1.195 1.994a5 5 0 0 0-.285.685L1 7v2l2.258.564a5 5 0 0 0 .279.688l-1.193 1.99 1.414 1.414 1.994-1.195a5 5 0 0 0 .685.285L7 15h2l.564-2.258a5 5 0 0 0 .688-.28l1.99 1.194 1.414-1.414-1.195-1.994a5 5 0 0 0 .285-.685L15 9V7l-2.258-.564a5 5 0 0 0-.28-.688l1.194-1.99-1.414-1.414-1.994 1.195a5 5 0 0 0-.686-.285L9 1H7zm1 5a2 2 0 0 1 0 4 2 2 0 0 1 0-4z" fill="#e0e0e0"/></svg>
diff --git a/modules/gdscript/icons/GDScriptInternal.svg b/modules/gdscript/icons/GDScriptInternal.svg
index fcabaafbd0..81a59a7387 100644
--- a/modules/gdscript/icons/GDScriptInternal.svg
+++ b/modules/gdscript/icons/GDScriptInternal.svg
@@ -1 +1 @@
-<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m7 1-.56445 2.2578c-.2364329.0758517-.4668872.16921-.68945.2793l-1.9883-1.1934-1.4141 1.4141 1.1953 1.9941c-.1119126.2211335-.2072287.4502818-.28516.68555l-2.2539.5625v2l2.2578.56445c.075942.2357685.1692993.465568.2793.6875l-1.1934 1.9902 1.4141 1.4141 1.9941-1.1953c.2211335.111913.4502818.207229.68555.28516l.5625 2.2539h2l.56445-2.2578c.2357685-.07594.465568-.169299.6875-.2793l1.9902 1.1934 1.4141-1.4141-1.1953-1.9941c.111913-.221133.207229-.4502818.28516-.68555l2.2539-.5625v-2l-2.2578-.56445c-.075942-.2357685-.169299-.4655679-.2793-.6875l1.1934-1.9902-1.4141-1.4141-1.9941 1.1953c-.221133-.1119126-.4502818-.2072287-.68555-.28516l-.5625-2.2539zm1 5c1.1045695 0 2 .8954305 2 2s-.8954305 2-2 2-2-.8954305-2-2 .8954305-2 2-2z" fill="none" stroke="#e0e0e0"/></svg>
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m7 1-.565 2.258a5 5 0 0 0-.689.28L3.758 2.343 2.344 3.758l1.195 1.994a5 5 0 0 0-.285.685L1 7v2l2.258.564a5 5 0 0 0 .279.688l-1.193 1.99 1.414 1.414 1.994-1.195a5 5 0 0 0 .685.285L7 15h2l.564-2.258a5 5 0 0 0 .688-.28l1.99 1.194 1.414-1.414-1.195-1.994a5 5 0 0 0 .285-.685L15 9V7l-2.258-.564a5 5 0 0 0-.28-.688l1.194-1.99-1.414-1.414-1.994 1.195a5 5 0 0 0-.686-.285L9 1H7zm1 5a2 2 0 0 1 0 4 2 2 0 0 1 0-4z" fill="none" stroke="#e0e0e0"/></svg>
diff --git a/modules/gdscript/language_server/gdscript_language_server.cpp b/modules/gdscript/language_server/gdscript_language_server.cpp
index 062be0fe20..8c44483288 100644
--- a/modules/gdscript/language_server/gdscript_language_server.cpp
+++ b/modules/gdscript/language_server/gdscript_language_server.cpp
@@ -73,6 +73,7 @@ void GDScriptLanguageServer::_notification(int p_what) {
}
void GDScriptLanguageServer::thread_main(void *p_userdata) {
+ set_current_thread_safe_for_nodes(true);
GDScriptLanguageServer *self = static_cast<GDScriptLanguageServer *>(p_userdata);
while (self->thread_running) {
// Poll 20 times per second
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type.gd
new file mode 100644
index 0000000000..7e3b6e3c39
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type.gd
@@ -0,0 +1,4 @@
+func test():
+ var a: Array[Resource] = []
+ for node: Node in a:
+ print(node)
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type.out b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type.out
new file mode 100644
index 0000000000..8f8a368f9a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Unable to iterate on value of type "Array[Resource]" with variable of type "Node".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_in_static_function.gd b/modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_in_static_function.gd
new file mode 100644
index 0000000000..caeea46977
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_in_static_function.gd
@@ -0,0 +1,9 @@
+# GH-75645
+
+extends Node
+
+static func static_func():
+ var a = $Node
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_in_static_function.out b/modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_in_static_function.out
new file mode 100644
index 0000000000..1910b3e66b
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_in_static_function.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot use shorthand "get_node()" notation ("$") in a static function.
diff --git a/modules/gdscript/tests/scripts/analyzer/features/inner_class_access_from_inside.gd b/modules/gdscript/tests/scripts/analyzer/features/inner_class_access_from_inside.gd
new file mode 100644
index 0000000000..51c589b8e0
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/inner_class_access_from_inside.gd
@@ -0,0 +1,21 @@
+# GH-80508
+
+class A:
+ func a():
+ return A.new()
+ func b():
+ return B.new()
+
+class B:
+ func a():
+ return A.new()
+ func b():
+ return B.new()
+
+func test():
+ var a := A.new()
+ var b := B.new()
+ print(a.a() is A)
+ print(a.b() is B)
+ print(b.a() is A)
+ print(b.b() is B)
diff --git a/modules/gdscript/tests/scripts/analyzer/features/inner_class_access_from_inside.out b/modules/gdscript/tests/scripts/analyzer/features/inner_class_access_from_inside.out
new file mode 100644
index 0000000000..f9783e4362
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/inner_class_access_from_inside.out
@@ -0,0 +1,5 @@
+GDTEST_OK
+true
+true
+true
+true
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_equal_to_inferred.gd b/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_equal_to_inferred.gd
new file mode 100644
index 0000000000..1b32491e48
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_equal_to_inferred.gd
@@ -0,0 +1,4 @@
+func test():
+ var a: Array[Node] = []
+ for node: Node in a:
+ print(node)
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_equal_to_inferred.out b/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_equal_to_inferred.out
new file mode 100644
index 0000000000..3b3fbd9bd1
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_equal_to_inferred.out
@@ -0,0 +1,5 @@
+GDTEST_OK
+>> WARNING
+>> Line: 3
+>> REDUNDANT_FOR_VARIABLE_TYPE
+>> The for loop iterator "node" already has inferred type "Node", the specified type is redundant.
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_supertype_of_inferred.gd b/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_supertype_of_inferred.gd
new file mode 100644
index 0000000000..fcbc13c53d
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_supertype_of_inferred.gd
@@ -0,0 +1,4 @@
+func test():
+ var a: Array[Node2D] = []
+ for node: Node in a:
+ print(node)
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_supertype_of_inferred.out b/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_supertype_of_inferred.out
new file mode 100644
index 0000000000..36d4a161d3
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_supertype_of_inferred.out
@@ -0,0 +1,5 @@
+GDTEST_OK
+>> WARNING
+>> Line: 3
+>> REDUNDANT_FOR_VARIABLE_TYPE
+>> The for loop iterator "node" has inferred type "Node2D" but its supertype "Node" is specified.
diff --git a/modules/gdscript/tests/scripts/parser/.editorconfig b/modules/gdscript/tests/scripts/parser/.editorconfig
new file mode 100644
index 0000000000..fa43b3ad78
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/.editorconfig
@@ -0,0 +1,2 @@
+[*.{gd,out}]
+trim_trailing_whitespace = false
diff --git a/modules/gdscript/tests/scripts/parser/features/mixed_indentation_on_blank_lines.gd b/modules/gdscript/tests/scripts/parser/features/mixed_indentation_on_blank_lines.gd
new file mode 100644
index 0000000000..7ee2708999
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/mixed_indentation_on_blank_lines.gd
@@ -0,0 +1,20 @@
+# Empty line:
+
+
+# Comment line:
+ # Comment.
+
+func test():
+ print(1)
+
+ if true:
+
+ # Empty line:
+
+
+ print(2)
+
+ # Comment line:
+ # Comment.
+
+ print(3)
diff --git a/modules/gdscript/tests/scripts/parser/features/mixed_indentation_on_blank_lines.out b/modules/gdscript/tests/scripts/parser/features/mixed_indentation_on_blank_lines.out
new file mode 100644
index 0000000000..c40e402ba3
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/mixed_indentation_on_blank_lines.out
@@ -0,0 +1,4 @@
+GDTEST_OK
+1
+2
+3
diff --git a/modules/gdscript/tests/scripts/runtime/errors/for_loop_iterator_type_not_match_specified.gd b/modules/gdscript/tests/scripts/runtime/errors/for_loop_iterator_type_not_match_specified.gd
new file mode 100644
index 0000000000..cdc278ecf5
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/for_loop_iterator_type_not_match_specified.gd
@@ -0,0 +1,4 @@
+func test():
+ var a: Array = [Resource.new()]
+ for node: Node in a:
+ print(node)
diff --git a/modules/gdscript/tests/scripts/runtime/errors/for_loop_iterator_type_not_match_specified.out b/modules/gdscript/tests/scripts/runtime/errors/for_loop_iterator_type_not_match_specified.out
new file mode 100644
index 0000000000..b7ef07afb2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/for_loop_iterator_type_not_match_specified.out
@@ -0,0 +1,6 @@
+GDTEST_RUNTIME_ERROR
+>> SCRIPT ERROR
+>> on function: test()
+>> runtime/errors/for_loop_iterator_type_not_match_specified.gd
+>> 3
+>> Trying to assign value of type 'Resource' to a variable of type 'Node'.
diff --git a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd
new file mode 100644
index 0000000000..58b4df5a79
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd
@@ -0,0 +1,34 @@
+func test():
+ print("Test range.")
+ for e: float in range(2, 5):
+ var elem := e
+ prints(var_to_str(e), var_to_str(elem))
+
+ print("Test int.")
+ for e: float in 3:
+ var elem := e
+ prints(var_to_str(e), var_to_str(elem))
+
+ print("Test untyped int array.")
+ var a1 := [10, 20, 30]
+ for e: float in a1:
+ var elem := e
+ prints(var_to_str(e), var_to_str(elem))
+
+ print("Test typed int array.")
+ var a2: Array[int] = [10, 20, 30]
+ for e: float in a2:
+ var elem := e
+ prints(var_to_str(e), var_to_str(elem))
+
+ print("Test String-keys dictionary.")
+ var d1 := {a = 1, b = 2, c = 3}
+ for k: StringName in d1:
+ var key := k
+ prints(var_to_str(k), var_to_str(key))
+
+ print("Test RefCounted-keys dictionary.")
+ var d2 := {RefCounted.new(): 1, Resource.new(): 2, ConfigFile.new(): 3}
+ for k: RefCounted in d2:
+ var key := k
+ prints(k.get_class(), key.get_class())
diff --git a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out
new file mode 100644
index 0000000000..f67f7d89d5
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out
@@ -0,0 +1,25 @@
+GDTEST_OK
+Test range.
+2.0 2.0
+3.0 3.0
+4.0 4.0
+Test int.
+0.0 0.0
+1.0 1.0
+2.0 2.0
+Test untyped int array.
+10.0 10.0
+20.0 20.0
+30.0 30.0
+Test typed int array.
+10.0 10.0
+20.0 20.0
+30.0 30.0
+Test String-keys dictionary.
+&"a" &"a"
+&"b" &"b"
+&"c" &"c"
+Test RefCounted-keys dictionary.
+RefCounted RefCounted
+Resource Resource
+ConfigFile ConfigFile
diff --git a/modules/glslang/register_types.cpp b/modules/glslang/register_types.cpp
index 622910761d..2b070c24b8 100644
--- a/modules/glslang/register_types.cpp
+++ b/modules/glslang/register_types.cpp
@@ -32,6 +32,7 @@
#include "glslang_resource_limits.h"
+#include "core/config/engine.h"
#include "servers/rendering/rendering_device.h"
#include <glslang/Include/Types.h>
@@ -56,7 +57,6 @@ static Vector<uint8_t> _compile_shader_glsl(RenderingDevice::ShaderStage p_stage
glslang::EShTargetClientVersion ClientVersion = glslang::EShTargetVulkan_1_2;
glslang::EShTargetLanguageVersion TargetVersion = glslang::EShTargetSpv_1_5;
- glslang::TShader::ForbidIncluder includer;
if (capabilities->device_family == RenderingDevice::DeviceFamily::DEVICE_VULKAN) {
if (capabilities->version_major == 1 && capabilities->version_minor == 0) {
@@ -127,23 +127,10 @@ static Vector<uint8_t> _compile_shader_glsl(RenderingDevice::ShaderStage p_stage
}
EShMessages messages = (EShMessages)(EShMsgSpvRules | EShMsgVulkanRules);
- const int DefaultVersion = 100;
- std::string pre_processed_code;
-
- //preprocess
- if (!shader.preprocess(&DefaultTBuiltInResource, DefaultVersion, ENoProfile, false, false, messages, &pre_processed_code, includer)) {
- if (r_error) {
- (*r_error) = "Failed pre-process:\n";
- (*r_error) += shader.getInfoLog();
- (*r_error) += "\n";
- (*r_error) += shader.getInfoDebugLog();
- }
-
- return ret;
+ if (Engine::get_singleton()->is_generate_spirv_debug_info_enabled()) {
+ messages = (EShMessages)(messages | EShMsgDebugInfo);
}
- //set back..
- cs_strings = pre_processed_code.c_str();
- shader.setStrings(&cs_strings, 1);
+ const int DefaultVersion = 100;
//parse
if (!shader.parse(&DefaultTBuiltInResource, DefaultVersion, false, messages)) {
@@ -174,6 +161,13 @@ static Vector<uint8_t> _compile_shader_glsl(RenderingDevice::ShaderStage p_stage
std::vector<uint32_t> SpirV;
spv::SpvBuildLogger logger;
glslang::SpvOptions spvOptions;
+
+ if (Engine::get_singleton()->is_generate_spirv_debug_info_enabled()) {
+ spvOptions.generateDebugInfo = true;
+ spvOptions.emitNonSemanticShaderDebugInfo = true;
+ spvOptions.emitNonSemanticShaderDebugSource = true;
+ }
+
glslang::GlslangToSpv(*program.getIntermediate(stages[p_stage]), SpirV, &logger, &spvOptions);
ret.resize(SpirV.size() * sizeof(uint32_t));
@@ -188,7 +182,7 @@ static Vector<uint8_t> _compile_shader_glsl(RenderingDevice::ShaderStage p_stage
static String _get_cache_key_function_glsl(const RenderingDevice *p_render_device) {
const RD::Capabilities *capabilities = p_render_device->get_device_capabilities();
String version;
- version = "SpirVGen=" + itos(glslang::GetSpirvGeneratorVersion()) + ", major=" + itos(capabilities->version_major) + ", minor=" + itos(capabilities->version_minor) + " , subgroup_size=" + itos(p_render_device->limit_get(RD::LIMIT_SUBGROUP_SIZE)) + " , subgroup_ops=" + itos(p_render_device->limit_get(RD::LIMIT_SUBGROUP_OPERATIONS)) + " , subgroup_in_shaders=" + itos(p_render_device->limit_get(RD::LIMIT_SUBGROUP_IN_SHADERS));
+ version = "SpirVGen=" + itos(glslang::GetSpirvGeneratorVersion()) + ", major=" + itos(capabilities->version_major) + ", minor=" + itos(capabilities->version_minor) + " , subgroup_size=" + itos(p_render_device->limit_get(RD::LIMIT_SUBGROUP_SIZE)) + " , subgroup_ops=" + itos(p_render_device->limit_get(RD::LIMIT_SUBGROUP_OPERATIONS)) + " , subgroup_in_shaders=" + itos(p_render_device->limit_get(RD::LIMIT_SUBGROUP_IN_SHADERS)) + " , debug=" + itos(Engine::get_singleton()->is_generate_spirv_debug_info_enabled());
return version;
}
diff --git a/modules/gltf/doc_classes/GLTFPhysicsBody.xml b/modules/gltf/doc_classes/GLTFPhysicsBody.xml
index 3aab1c5183..d364069193 100644
--- a/modules/gltf/doc_classes/GLTFPhysicsBody.xml
+++ b/modules/gltf/doc_classes/GLTFPhysicsBody.xml
@@ -42,7 +42,10 @@
The angular velocity of the physics body, in radians per second. This is only used when the body type is "rigid" or "vehicle".
</member>
<member name="body_type" type="String" setter="set_body_type" getter="get_body_type" default="&quot;static&quot;">
- The type of the body. Valid values are "static", "kinematic", "character", "rigid", "vehicle", and "trigger".
+ The type of the body. When importing, this controls what type of [CollisionObject3D] node Godot should generate. Valid values are "static", "kinematic", "character", "rigid", "vehicle", and "trigger".
+ </member>
+ <member name="center_of_mass" type="Vector3" setter="set_center_of_mass" getter="get_center_of_mass" default="Vector3(0, 0, 0)">
+ The center of mass of the body, in meters. This is in local space relative to the body. By default, the center of the mass is the body's origin.
</member>
<member name="inertia_tensor" type="Basis" setter="set_inertia_tensor" getter="get_inertia_tensor" default="Basis(0, 0, 0, 0, 0, 0, 0, 0, 0)">
The inertia tensor of the physics body, in kilogram meter squared (kg⋅m²). This is only used when the body type is "rigid" or "vehicle".
diff --git a/modules/gltf/extensions/gltf_document_extension_texture_ktx.cpp b/modules/gltf/extensions/gltf_document_extension_texture_ktx.cpp
new file mode 100644
index 0000000000..ca61a24201
--- /dev/null
+++ b/modules/gltf/extensions/gltf_document_extension_texture_ktx.cpp
@@ -0,0 +1,66 @@
+/**************************************************************************/
+/* gltf_document_extension_texture_ktx.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "gltf_document_extension_texture_ktx.h"
+
+// Import process.
+Error GLTFDocumentExtensionTextureKTX::import_preflight(Ref<GLTFState> p_state, Vector<String> p_extensions) {
+ if (!p_extensions.has("KHR_texture_basisu")) {
+ return ERR_SKIP;
+ }
+ return OK;
+}
+
+Vector<String> GLTFDocumentExtensionTextureKTX::get_supported_extensions() {
+ Vector<String> ret;
+ ret.push_back("KHR_texture_basisu");
+ return ret;
+}
+
+Error GLTFDocumentExtensionTextureKTX::parse_image_data(Ref<GLTFState> p_state, const PackedByteArray &p_image_data, const String &p_mime_type, Ref<Image> r_image) {
+ if (p_mime_type == "image/ktx2") {
+ return r_image->load_ktx_from_buffer(p_image_data);
+ }
+ return OK;
+}
+
+Error GLTFDocumentExtensionTextureKTX::parse_texture_json(Ref<GLTFState> p_state, const Dictionary &p_texture_json, Ref<GLTFTexture> r_gltf_texture) {
+ if (!p_texture_json.has("extensions")) {
+ return OK;
+ }
+ const Dictionary &extensions = p_texture_json["extensions"];
+ if (!extensions.has("KHR_texture_basisu")) {
+ return OK;
+ }
+ const Dictionary &texture_ktx = extensions["KHR_texture_basisu"];
+ ERR_FAIL_COND_V(!texture_ktx.has("source"), ERR_PARSE_ERROR);
+ r_gltf_texture->set_src_image(texture_ktx["source"]);
+ return OK;
+}
diff --git a/modules/gltf/extensions/gltf_document_extension_texture_ktx.h b/modules/gltf/extensions/gltf_document_extension_texture_ktx.h
new file mode 100644
index 0000000000..e4cb38a044
--- /dev/null
+++ b/modules/gltf/extensions/gltf_document_extension_texture_ktx.h
@@ -0,0 +1,47 @@
+/**************************************************************************/
+/* gltf_document_extension_texture_ktx.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef GLTF_DOCUMENT_EXTENSION_TEXTURE_KTX_H
+#define GLTF_DOCUMENT_EXTENSION_TEXTURE_KTX_H
+
+#include "gltf_document_extension.h"
+
+class GLTFDocumentExtensionTextureKTX : public GLTFDocumentExtension {
+ GDCLASS(GLTFDocumentExtensionTextureKTX, GLTFDocumentExtension);
+
+public:
+ // Import process.
+ Error import_preflight(Ref<GLTFState> p_state, Vector<String> p_extensions) override;
+ Vector<String> get_supported_extensions() override;
+ Error parse_image_data(Ref<GLTFState> p_state, const PackedByteArray &p_image_data, const String &p_mime_type, Ref<Image> r_image) override;
+ Error parse_texture_json(Ref<GLTFState> p_state, const Dictionary &p_texture_json, Ref<GLTFTexture> r_gltf_texture) override;
+};
+
+#endif // GLTF_DOCUMENT_EXTENSION_TEXTURE_KTX_H
diff --git a/modules/gltf/extensions/physics/gltf_physics_body.cpp b/modules/gltf/extensions/physics/gltf_physics_body.cpp
index 3b0fad064a..49a6edd2e3 100644
--- a/modules/gltf/extensions/physics/gltf_physics_body.cpp
+++ b/modules/gltf/extensions/physics/gltf_physics_body.cpp
@@ -48,6 +48,8 @@ void GLTFPhysicsBody::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_linear_velocity", "linear_velocity"), &GLTFPhysicsBody::set_linear_velocity);
ClassDB::bind_method(D_METHOD("get_angular_velocity"), &GLTFPhysicsBody::get_angular_velocity);
ClassDB::bind_method(D_METHOD("set_angular_velocity", "angular_velocity"), &GLTFPhysicsBody::set_angular_velocity);
+ ClassDB::bind_method(D_METHOD("get_center_of_mass"), &GLTFPhysicsBody::get_center_of_mass);
+ ClassDB::bind_method(D_METHOD("set_center_of_mass", "center_of_mass"), &GLTFPhysicsBody::set_center_of_mass);
ClassDB::bind_method(D_METHOD("get_inertia_tensor"), &GLTFPhysicsBody::get_inertia_tensor);
ClassDB::bind_method(D_METHOD("set_inertia_tensor", "inertia_tensor"), &GLTFPhysicsBody::set_inertia_tensor);
@@ -55,6 +57,7 @@ void GLTFPhysicsBody::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass"), "set_mass", "get_mass");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "linear_velocity"), "set_linear_velocity", "get_linear_velocity");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "angular_velocity"), "set_angular_velocity", "get_angular_velocity");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "center_of_mass"), "set_center_of_mass", "get_center_of_mass");
ADD_PROPERTY(PropertyInfo(Variant::BASIS, "inertia_tensor"), "set_inertia_tensor", "get_inertia_tensor");
}
@@ -90,6 +93,14 @@ void GLTFPhysicsBody::set_angular_velocity(Vector3 p_angular_velocity) {
angular_velocity = p_angular_velocity;
}
+Vector3 GLTFPhysicsBody::get_center_of_mass() const {
+ return center_of_mass;
+}
+
+void GLTFPhysicsBody::set_center_of_mass(const Vector3 &p_center_of_mass) {
+ center_of_mass = p_center_of_mass;
+}
+
Basis GLTFPhysicsBody::get_inertia_tensor() const {
return inertia_tensor;
}
@@ -111,6 +122,7 @@ Ref<GLTFPhysicsBody> GLTFPhysicsBody::from_node(const CollisionObject3D *p_body_
physics_body->mass = body->get_mass();
physics_body->linear_velocity = body->get_linear_velocity();
physics_body->angular_velocity = body->get_angular_velocity();
+ physics_body->center_of_mass = body->get_center_of_mass();
Vector3 inertia_diagonal = body->get_inertia();
physics_body->inertia_tensor = Basis(inertia_diagonal.x, 0, 0, 0, inertia_diagonal.y, 0, 0, 0, inertia_diagonal.z);
if (body->get_center_of_mass() != Vector3()) {
@@ -145,6 +157,7 @@ CollisionObject3D *GLTFPhysicsBody::to_node() const {
body->set_angular_velocity(angular_velocity);
body->set_inertia(inertia_tensor.get_main_diagonal());
body->set_center_of_mass_mode(RigidBody3D::CENTER_OF_MASS_MODE_CUSTOM);
+ body->set_center_of_mass(center_of_mass);
return body;
}
if (body_type == "rigid") {
@@ -154,6 +167,7 @@ CollisionObject3D *GLTFPhysicsBody::to_node() const {
body->set_angular_velocity(angular_velocity);
body->set_inertia(inertia_tensor.get_main_diagonal());
body->set_center_of_mass_mode(RigidBody3D::CENTER_OF_MASS_MODE_CUSTOM);
+ body->set_center_of_mass(center_of_mass);
return body;
}
if (body_type == "static") {
@@ -193,6 +207,14 @@ Ref<GLTFPhysicsBody> GLTFPhysicsBody::from_dictionary(const Dictionary p_diction
ERR_PRINT("Error parsing GLTF physics body: The angular velocity vector must have exactly 3 numbers.");
}
}
+ if (p_dictionary.has("centerOfMass")) {
+ const Array &arr = p_dictionary["centerOfMass"];
+ if (arr.size() == 3) {
+ physics_body->set_center_of_mass(Vector3(arr[0], arr[1], arr[2]));
+ } else {
+ ERR_PRINT("Error parsing GLTF physics body: The center of mass vector must have exactly 3 numbers.");
+ }
+ }
if (p_dictionary.has("inertiaTensor")) {
const Array &arr = p_dictionary["inertiaTensor"];
if (arr.size() == 9) {
@@ -230,6 +252,14 @@ Dictionary GLTFPhysicsBody::to_dictionary() const {
velocity_array[2] = angular_velocity.z;
d["angularVelocity"] = velocity_array;
}
+ if (center_of_mass != Vector3()) {
+ Array center_of_mass_array;
+ center_of_mass_array.resize(3);
+ center_of_mass_array[0] = center_of_mass.x;
+ center_of_mass_array[1] = center_of_mass.y;
+ center_of_mass_array[2] = center_of_mass.z;
+ d["centerOfMass"] = center_of_mass_array;
+ }
if (inertia_tensor != Basis(0, 0, 0, 0, 0, 0, 0, 0, 0)) {
Array inertia_array;
inertia_array.resize(9);
diff --git a/modules/gltf/extensions/physics/gltf_physics_body.h b/modules/gltf/extensions/physics/gltf_physics_body.h
index 5fedb4f111..391b4b873f 100644
--- a/modules/gltf/extensions/physics/gltf_physics_body.h
+++ b/modules/gltf/extensions/physics/gltf_physics_body.h
@@ -45,8 +45,9 @@ protected:
private:
String body_type = "static";
real_t mass = 1.0;
- Vector3 linear_velocity = Vector3();
- Vector3 angular_velocity = Vector3();
+ Vector3 linear_velocity;
+ Vector3 angular_velocity;
+ Vector3 center_of_mass;
Basis inertia_tensor = Basis(0, 0, 0, 0, 0, 0, 0, 0, 0);
public:
@@ -62,6 +63,9 @@ public:
Vector3 get_angular_velocity() const;
void set_angular_velocity(Vector3 p_angular_velocity);
+ Vector3 get_center_of_mass() const;
+ void set_center_of_mass(const Vector3 &p_center_of_mass);
+
Basis get_inertia_tensor() const;
void set_inertia_tensor(Basis p_inertia_tensor);
diff --git a/modules/gltf/register_types.cpp b/modules/gltf/register_types.cpp
index 1788ffac3a..fecea45fc9 100644
--- a/modules/gltf/register_types.cpp
+++ b/modules/gltf/register_types.cpp
@@ -31,6 +31,7 @@
#include "register_types.h"
#include "extensions/gltf_document_extension_convert_importer_mesh.h"
+#include "extensions/gltf_document_extension_texture_ktx.h"
#include "extensions/gltf_document_extension_texture_webp.h"
#include "extensions/gltf_spec_gloss.h"
#include "extensions/physics/gltf_document_extension_physics.h"
@@ -55,18 +56,7 @@ static void _editor_init() {
// Blend to glTF importer.
bool blend_enabled = GLOBAL_GET("filesystem/import/blender/enabled");
- // Defined here because EditorSettings doesn't exist in `register_gltf_types` yet.
- EDITOR_DEF_RST("filesystem/import/blender/rpc_port", 6011);
- EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT,
- "filesystem/import/blender/rpc_port", PROPERTY_HINT_RANGE, "0,65535,1"));
-
- EDITOR_DEF_RST("filesystem/import/blender/rpc_server_uptime", 5);
- EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::FLOAT,
- "filesystem/import/blender/rpc_server_uptime", PROPERTY_HINT_RANGE, "0,300,1,or_greater,suffix:s"));
-
- String blender3_path = EDITOR_DEF_RST("filesystem/import/blender/blender3_path", "");
- EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING,
- "filesystem/import/blender/blender3_path", PROPERTY_HINT_GLOBAL_DIR));
+ String blender3_path = EDITOR_GET("filesystem/import/blender/blender3_path");
if (blend_enabled) {
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
if (blender3_path.is_empty()) {
@@ -89,10 +79,6 @@ static void _editor_init() {
// FBX to glTF importer.
bool fbx_enabled = GLOBAL_GET("filesystem/import/fbx/enabled");
- // Defined here because EditorSettings doesn't exist in `register_gltf_types` yet.
- String fbx2gltf_path = EDITOR_DEF_RST("filesystem/import/fbx/fbx2gltf_path", "");
- EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING,
- "filesystem/import/fbx/fbx2gltf_path", PROPERTY_HINT_GLOBAL_FILE));
if (fbx_enabled) {
Ref<EditorSceneFormatImporterFBX> importer;
importer.instantiate();
@@ -133,6 +119,7 @@ void initialize_gltf_module(ModuleInitializationLevel p_level) {
GDREGISTER_CLASS(GLTFTextureSampler);
// Register GLTFDocumentExtension classes with GLTFDocument.
GLTF_REGISTER_DOCUMENT_EXTENSION(GLTFDocumentExtensionPhysics);
+ GLTF_REGISTER_DOCUMENT_EXTENSION(GLTFDocumentExtensionTextureKTX);
GLTF_REGISTER_DOCUMENT_EXTENSION(GLTFDocumentExtensionTextureWebP);
bool is_editor = ::Engine::get_singleton()->is_editor_hint();
if (!is_editor) {
diff --git a/modules/gridmap/doc_classes/GridMap.xml b/modules/gridmap/doc_classes/GridMap.xml
index f9c3ca476a..3094a7bf80 100644
--- a/modules/gridmap/doc_classes/GridMap.xml
+++ b/modules/gridmap/doc_classes/GridMap.xml
@@ -228,6 +228,11 @@
Emitted when [member cell_size] changes.
</description>
</signal>
+ <signal name="changed">
+ <description>
+ Emitted when the [MeshLibrary] of this GridMap changes.
+ </description>
+ </signal>
</signals>
<constants>
<constant name="INVALID_CELL_ITEM" value="-1">
diff --git a/modules/gridmap/editor/grid_map_editor_plugin.cpp b/modules/gridmap/editor/grid_map_editor_plugin.cpp
index a300f7d2e2..0c9f8fb3e0 100644
--- a/modules/gridmap/editor/grid_map_editor_plugin.cpp
+++ b/modules/gridmap/editor/grid_map_editor_plugin.cpp
@@ -32,6 +32,7 @@
#ifdef TOOLS_ENABLED
+#include "core/core_string_names.h"
#include "core/input/input.h"
#include "core/os/keyboard.h"
#include "editor/editor_node.h"
@@ -341,7 +342,6 @@ bool GridMapEditor::do_input_action(Camera3D *p_camera, const Point2 &p_point, b
if (selected_palette < 0 && input_action != INPUT_PICK && input_action != INPUT_SELECT && input_action != INPUT_PASTE) {
return false;
}
- Ref<MeshLibrary> mesh_library = node->get_mesh_library();
if (mesh_library.is_null()) {
return false;
}
@@ -743,6 +743,17 @@ EditorPlugin::AfterGUIInput GridMapEditor::forward_spatial_input_event(Camera3D
}
}
+ // Consume input to avoid conflicts with other plugins.
+ if (k.is_valid() && k->is_pressed() && !k->is_echo()) {
+ for (int i = 0; i < options->get_popup()->get_item_count(); ++i) {
+ const Ref<Shortcut> &shortcut = options->get_popup()->get_item_shortcut(i);
+ if (shortcut.is_valid() && shortcut->matches_event(p_event)) {
+ _menu_option(options->get_popup()->get_item_id(i));
+ return EditorPlugin::AFTER_GUI_INPUT_STOP;
+ }
+ }
+ }
+
if (k->is_shift_pressed() && selection.active && input_action != INPUT_PASTE) {
if (k->get_keycode() == (Key)options->get_popup()->get_item_accelerator(options->get_popup()->get_item_index(MENU_OPTION_PREV_LEVEL))) {
selection.click[edit_axis]--;
@@ -855,10 +866,7 @@ void GridMapEditor::update_palette() {
mesh_library_palette->set_fixed_icon_size(Size2(min_size, min_size));
mesh_library_palette->set_max_text_lines(2);
- Ref<MeshLibrary> mesh_library = node->get_mesh_library();
-
if (mesh_library.is_null()) {
- last_mesh_library = nullptr;
search_box->set_text("");
search_box->set_editable(false);
info_message->show();
@@ -911,13 +919,39 @@ void GridMapEditor::update_palette() {
item++;
}
+}
+
+void GridMapEditor::_update_mesh_library() {
+ ERR_FAIL_NULL(node);
+
+ Ref<MeshLibrary> new_mesh_library = node->get_mesh_library();
+ if (new_mesh_library != mesh_library) {
+ if (mesh_library.is_valid()) {
+ mesh_library->disconnect_changed(callable_mp(this, &GridMapEditor::update_palette));
+ }
+ mesh_library = new_mesh_library;
+ } else {
+ return;
+ }
- last_mesh_library = *mesh_library;
+ if (mesh_library.is_valid()) {
+ mesh_library->connect_changed(callable_mp(this, &GridMapEditor::update_palette));
+ }
+
+ update_palette();
+ // Update the cursor and grid in case the library is changed or removed.
+ _update_cursor_instance();
+ update_grid();
}
void GridMapEditor::edit(GridMap *p_gridmap) {
- 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));
+ if (node) {
+ node->disconnect(SNAME("cell_size_changed"), callable_mp(this, &GridMapEditor::_draw_grids));
+ node->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &GridMapEditor::_update_mesh_library));
+ if (mesh_library.is_valid()) {
+ mesh_library->disconnect_changed(callable_mp(this, &GridMapEditor::update_palette));
+ mesh_library = Ref<MeshLibrary>();
+ }
}
node = p_gridmap;
@@ -950,7 +984,9 @@ void GridMapEditor::edit(GridMap *p_gridmap) {
_draw_grids(node->get_cell_size());
update_grid();
- node->connect("cell_size_changed", callable_mp(this, &GridMapEditor::_draw_grids));
+ node->connect(SNAME("cell_size_changed"), callable_mp(this, &GridMapEditor::_draw_grids));
+ node->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &GridMapEditor::_update_mesh_library));
+ _update_mesh_library();
}
void GridMapEditor::update_grid() {
@@ -1081,13 +1117,6 @@ void GridMapEditor::_notification(int p_what) {
}
grid_xform = xf;
}
- Ref<MeshLibrary> cgmt = node->get_mesh_library();
- if (cgmt.operator->() != last_mesh_library) {
- update_palette();
- // Update the cursor and grid in case the library is changed or removed.
- _update_cursor_instance();
- update_grid();
- }
} break;
case NOTIFICATION_THEME_CHANGED: {
@@ -1154,6 +1183,24 @@ void GridMapEditor::_bind_methods() {
}
GridMapEditor::GridMapEditor() {
+ ED_SHORTCUT("grid_map/previous_floor", TTR("Previous Floor"), Key::Q, true);
+ ED_SHORTCUT("grid_map/next_floor", TTR("Next Floor"), Key::E, true);
+ ED_SHORTCUT("grid_map/edit_x_axis", TTR("Edit X Axis"), Key::Z, true);
+ ED_SHORTCUT("grid_map/edit_y_axis", TTR("Edit Y Axis"), Key::X, true);
+ ED_SHORTCUT("grid_map/edit_z_axis", TTR("Edit Z Axis"), Key::C, true);
+ ED_SHORTCUT("grid_map/cursor_rotate_x", TTR("Cursor Rotate X"), Key::A, true);
+ ED_SHORTCUT("grid_map/cursor_rotate_y", TTR("Cursor Rotate Y"), Key::S, true);
+ ED_SHORTCUT("grid_map/cursor_rotate_z", TTR("Cursor Rotate Z"), Key::D, true);
+ ED_SHORTCUT("grid_map/cursor_back_rotate_x", TTR("Cursor Back Rotate X"), KeyModifierMask::SHIFT + Key::A, true);
+ ED_SHORTCUT("grid_map/cursor_back_rotate_y", TTR("Cursor Back Rotate Y"), KeyModifierMask::SHIFT + Key::S, true);
+ ED_SHORTCUT("grid_map/cursor_back_rotate_z", TTR("Cursor Back Rotate Z"), KeyModifierMask::SHIFT + Key::D, true);
+ ED_SHORTCUT("grid_map/cursor_clear_rotation", TTR("Cursor Clear Rotation"), Key::W, true);
+ ED_SHORTCUT("grid_map/paste_selects", TTR("Paste Selects"));
+ ED_SHORTCUT("grid_map/duplicate_selection", TTR("Duplicate Selection"), KeyModifierMask::CTRL + Key::C);
+ ED_SHORTCUT("grid_map/cut_selection", TTR("Cut Selection"), KeyModifierMask::CTRL + Key::X);
+ ED_SHORTCUT("grid_map/clear_selection", TTR("Clear Selection"), Key::KEY_DELETE);
+ ED_SHORTCUT("grid_map/fill_selection", TTR("Fill Selection"), KeyModifierMask::CTRL + Key::F);
+
int mw = EDITOR_DEF("editors/grid_map/palette_min_width", 230);
Control *ec = memnew(Control);
ec->set_custom_minimum_size(Size2(mw, 0) * EDSCALE);
@@ -1186,29 +1233,29 @@ GridMapEditor::GridMapEditor() {
spatial_editor_hb->hide();
options->set_text(TTR("Grid Map"));
- options->get_popup()->add_item(TTR("Previous Floor"), MENU_OPTION_PREV_LEVEL, Key::Q);
- options->get_popup()->add_item(TTR("Next Floor"), MENU_OPTION_NEXT_LEVEL, Key::E);
+ options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/previous_floor"), MENU_OPTION_PREV_LEVEL);
+ options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/next_floor"), MENU_OPTION_NEXT_LEVEL);
options->get_popup()->add_separator();
- options->get_popup()->add_radio_check_item(TTR("Edit X Axis"), MENU_OPTION_X_AXIS, Key::Z);
- options->get_popup()->add_radio_check_item(TTR("Edit Y Axis"), MENU_OPTION_Y_AXIS, Key::X);
- options->get_popup()->add_radio_check_item(TTR("Edit Z Axis"), MENU_OPTION_Z_AXIS, Key::C);
+ options->get_popup()->add_radio_check_shortcut(ED_GET_SHORTCUT("grid_map/edit_x_axis"), MENU_OPTION_X_AXIS);
+ options->get_popup()->add_radio_check_shortcut(ED_GET_SHORTCUT("grid_map/edit_y_axis"), MENU_OPTION_Y_AXIS);
+ options->get_popup()->add_radio_check_shortcut(ED_GET_SHORTCUT("grid_map/edit_z_axis"), MENU_OPTION_Z_AXIS);
options->get_popup()->set_item_checked(options->get_popup()->get_item_index(MENU_OPTION_Y_AXIS), true);
options->get_popup()->add_separator();
- options->get_popup()->add_item(TTR("Cursor Rotate X"), MENU_OPTION_CURSOR_ROTATE_X, Key::A);
- options->get_popup()->add_item(TTR("Cursor Rotate Y"), MENU_OPTION_CURSOR_ROTATE_Y, Key::S);
- options->get_popup()->add_item(TTR("Cursor Rotate Z"), MENU_OPTION_CURSOR_ROTATE_Z, Key::D);
- options->get_popup()->add_item(TTR("Cursor Back Rotate X"), MENU_OPTION_CURSOR_BACK_ROTATE_X, KeyModifierMask::SHIFT + Key::A);
- options->get_popup()->add_item(TTR("Cursor Back Rotate Y"), MENU_OPTION_CURSOR_BACK_ROTATE_Y, KeyModifierMask::SHIFT + Key::S);
- options->get_popup()->add_item(TTR("Cursor Back Rotate Z"), MENU_OPTION_CURSOR_BACK_ROTATE_Z, KeyModifierMask::SHIFT + Key::D);
- options->get_popup()->add_item(TTR("Cursor Clear Rotation"), MENU_OPTION_CURSOR_CLEAR_ROTATION, Key::W);
+ options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_rotate_x"), MENU_OPTION_CURSOR_ROTATE_X);
+ options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_rotate_y"), MENU_OPTION_CURSOR_ROTATE_Y);
+ options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_rotate_z"), MENU_OPTION_CURSOR_ROTATE_Z);
+ options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_back_rotate_x"), MENU_OPTION_CURSOR_BACK_ROTATE_X);
+ options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_back_rotate_y"), MENU_OPTION_CURSOR_BACK_ROTATE_Y);
+ options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_back_rotate_z"), MENU_OPTION_CURSOR_BACK_ROTATE_Z);
+ options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_clear_rotation"), MENU_OPTION_CURSOR_CLEAR_ROTATION);
options->get_popup()->add_separator();
// TRANSLATORS: This is a toggle to select after pasting the new content.
- options->get_popup()->add_check_item(TTR("Paste Selects"), MENU_OPTION_PASTE_SELECTS);
+ options->get_popup()->add_check_shortcut(ED_GET_SHORTCUT("grid_map/paste_selects"), MENU_OPTION_PASTE_SELECTS);
options->get_popup()->add_separator();
- options->get_popup()->add_item(TTR("Duplicate Selection"), MENU_OPTION_SELECTION_DUPLICATE, KeyModifierMask::CTRL + Key::C);
- options->get_popup()->add_item(TTR("Cut Selection"), MENU_OPTION_SELECTION_CUT, KeyModifierMask::CTRL + Key::X);
- options->get_popup()->add_item(TTR("Clear Selection"), MENU_OPTION_SELECTION_CLEAR, Key::KEY_DELETE);
- options->get_popup()->add_item(TTR("Fill Selection"), MENU_OPTION_SELECTION_FILL, KeyModifierMask::CTRL + Key::F);
+ options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/duplicate_selection"), MENU_OPTION_SELECTION_DUPLICATE);
+ options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cut_selection"), MENU_OPTION_SELECTION_CUT);
+ options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/clear_selection"), MENU_OPTION_SELECTION_CLEAR);
+ options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/fill_selection"), MENU_OPTION_SELECTION_FILL);
options->get_popup()->add_separator();
options->get_popup()->add_item(TTR("Settings..."), MENU_OPTION_GRIDMAP_SETTINGS);
diff --git a/modules/gridmap/editor/grid_map_editor_plugin.h b/modules/gridmap/editor/grid_map_editor_plugin.h
index fd9daa7c29..924e21aef5 100644
--- a/modules/gridmap/editor/grid_map_editor_plugin.h
+++ b/modules/gridmap/editor/grid_map_editor_plugin.h
@@ -92,7 +92,7 @@ class GridMapEditor : public VBoxContainer {
List<SetItem> set_items;
GridMap *node = nullptr;
- MeshLibrary *last_mesh_library = nullptr;
+ Ref<MeshLibrary> mesh_library = nullptr;
Transform3D grid_xform;
Transform3D edit_grid_xform;
@@ -191,6 +191,7 @@ class GridMapEditor : public VBoxContainer {
void _configure();
void _menu_option(int);
void update_palette();
+ void _update_mesh_library();
void _set_display_mode(int p_mode);
void _item_selected_cbk(int idx);
void _update_cursor_transform();
diff --git a/modules/gridmap/grid_map.cpp b/modules/gridmap/grid_map.cpp
index f1e2218434..34c53b5de5 100644
--- a/modules/gridmap/grid_map.cpp
+++ b/modules/gridmap/grid_map.cpp
@@ -30,6 +30,7 @@
#include "grid_map.h"
+#include "core/core_string_names.h"
#include "core/io/marshalls.h"
#include "core/object/message_queue.h"
#include "scene/3d/light_3d.h"
@@ -1122,6 +1123,7 @@ void GridMap::_bind_methods() {
BIND_CONSTANT(INVALID_CELL_ITEM);
ADD_SIGNAL(MethodInfo("cell_size_changed", PropertyInfo(Variant::VECTOR3, "cell_size")));
+ ADD_SIGNAL(MethodInfo(CoreStringNames::get_singleton()->changed));
}
void GridMap::set_cell_scale(float p_scale) {
diff --git a/modules/ktx/SCsub b/modules/ktx/SCsub
new file mode 100644
index 0000000000..b160f7a287
--- /dev/null
+++ b/modules/ktx/SCsub
@@ -0,0 +1,58 @@
+#!/usr/bin/env python
+
+Import("env")
+Import("env_modules")
+
+env_ktx = env_modules.Clone()
+
+# libktx thirdparty source files
+
+thirdparty_obj = []
+
+thirdparty_dir = "#thirdparty/libktx/"
+thirdparty_sources = [
+ "lib/basis_transcode.cpp",
+ "lib/checkheader.c",
+ "lib/filestream.c",
+ "lib/hashlist.c",
+ "lib/memstream.c",
+ "lib/swap.c",
+ "lib/texture.c",
+ "lib/texture1.c",
+ "lib/texture2.c",
+ "lib/dfdutils/createdfd.c",
+ "lib/dfdutils/colourspaces.c",
+ "lib/dfdutils/interpretdfd.c",
+ "lib/dfdutils/printdfd.c",
+ "lib/dfdutils/queries.c",
+ "lib/dfdutils/vk2dfd.c",
+]
+thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources]
+
+env_ktx.Prepend(CPPPATH=[thirdparty_dir + "include"])
+env_ktx.Prepend(CPPPATH=[thirdparty_dir + "utils"])
+env_ktx.Prepend(CPPPATH=[thirdparty_dir + "lib"])
+env_ktx.Prepend(CPPPATH=[thirdparty_dir + "other_include"])
+env_ktx.Prepend(CPPPATH=["#thirdparty/basis_universal"])
+
+if env["vulkan"]:
+ env_ktx.Prepend(CPPPATH=["#thirdparty/vulkan/include"])
+else:
+ # Falls back on bundled `vkformat_enum.h`.
+ env_ktx.Append(CPPDEFINES=["LIBKTX"])
+
+env_ktx.Append(CPPDEFINES=[("KHRONOS_STATIC", 1)])
+
+env_thirdparty = env_ktx.Clone()
+env_thirdparty.disable_warnings()
+env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources)
+env.modules_sources += thirdparty_obj
+
+# Godot source files
+module_obj = []
+
+env_ktx.add_source_files(module_obj, "*.cpp")
+env.modules_sources += module_obj
+
+# Needed to force rebuilding the module files when the thirdparty library is updated.
+env.Depends(module_obj, thirdparty_obj)
diff --git a/modules/ktx/config.py b/modules/ktx/config.py
new file mode 100644
index 0000000000..4c8391ea2a
--- /dev/null
+++ b/modules/ktx/config.py
@@ -0,0 +1,7 @@
+def can_build(env, platform):
+ env.module_add_dependencies("ktx", ["basis_universal"])
+ return True
+
+
+def configure(env):
+ pass
diff --git a/modules/ktx/register_types.cpp b/modules/ktx/register_types.cpp
new file mode 100644
index 0000000000..1d48e05a90
--- /dev/null
+++ b/modules/ktx/register_types.cpp
@@ -0,0 +1,53 @@
+/**************************************************************************/
+/* register_types.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "register_types.h"
+
+#include "texture_loader_ktx.h"
+
+static Ref<ResourceFormatKTX> resource_loader_ktx;
+
+void initialize_ktx_module(ModuleInitializationLevel p_level) {
+ if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
+ return;
+ }
+
+ resource_loader_ktx.instantiate();
+ ResourceLoader::add_resource_format_loader(resource_loader_ktx);
+}
+
+void uninitialize_ktx_module(ModuleInitializationLevel p_level) {
+ if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
+ return;
+ }
+
+ ResourceLoader::remove_resource_format_loader(resource_loader_ktx);
+ resource_loader_ktx.unref();
+}
diff --git a/modules/ktx/register_types.h b/modules/ktx/register_types.h
new file mode 100644
index 0000000000..a50dc48b40
--- /dev/null
+++ b/modules/ktx/register_types.h
@@ -0,0 +1,39 @@
+/**************************************************************************/
+/* register_types.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef KTX_REGISTER_TYPES_H
+#define KTX_REGISTER_TYPES_H
+
+#include "modules/register_module_types.h"
+
+void initialize_ktx_module(ModuleInitializationLevel p_level);
+void uninitialize_ktx_module(ModuleInitializationLevel p_level);
+
+#endif // KTX_REGISTER_TYPES_H
diff --git a/modules/ktx/texture_loader_ktx.cpp b/modules/ktx/texture_loader_ktx.cpp
new file mode 100644
index 0000000000..155ed56bd0
--- /dev/null
+++ b/modules/ktx/texture_loader_ktx.cpp
@@ -0,0 +1,562 @@
+/**************************************************************************/
+/* texture_loader_ktx.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "texture_loader_ktx.h"
+
+#include "core/io/file_access.h"
+#include "core/io/file_access_memory.h"
+#include "scene/resources/image_texture.h"
+
+#include <ktx.h>
+#include <vk_format.h>
+
+KTX_error_code ktx_read(ktxStream *stream, void *dst, const ktx_size_t count) {
+ Ref<FileAccess> *f = reinterpret_cast<Ref<FileAccess> *>(stream->data.custom_ptr.address);
+ (*f)->get_buffer(reinterpret_cast<uint8_t *>(dst), count);
+ return KTX_SUCCESS;
+}
+
+KTX_error_code ktx_skip(ktxStream *stream, const ktx_size_t count) {
+ Ref<FileAccess> *f = reinterpret_cast<Ref<FileAccess> *>(stream->data.custom_ptr.address);
+ for (ktx_size_t i = 0; i < count; ++i) {
+ (*f)->get_8();
+ }
+ return KTX_SUCCESS;
+}
+
+KTX_error_code ktx_write(ktxStream *stream, const void *src, const ktx_size_t size, const ktx_size_t count) {
+ Ref<FileAccess> *f = reinterpret_cast<Ref<FileAccess> *>(stream->data.custom_ptr.address);
+ (*f)->store_buffer(reinterpret_cast<const uint8_t *>(src), size * count);
+ return KTX_SUCCESS;
+}
+
+KTX_error_code ktx_getpos(ktxStream *stream, ktx_off_t *const offset) {
+ Ref<FileAccess> *f = reinterpret_cast<Ref<FileAccess> *>(stream->data.custom_ptr.address);
+ *offset = (*f)->get_position();
+ return KTX_SUCCESS;
+}
+
+KTX_error_code ktx_setpos(ktxStream *stream, const ktx_off_t offset) {
+ Ref<FileAccess> *f = reinterpret_cast<Ref<FileAccess> *>(stream->data.custom_ptr.address);
+ (*f)->seek(offset);
+ return KTX_SUCCESS;
+}
+
+KTX_error_code ktx_getsize(ktxStream *stream, ktx_size_t *const size) {
+ Ref<FileAccess> *f = reinterpret_cast<Ref<FileAccess> *>(stream->data.custom_ptr.address);
+ *size = (*f)->get_length();
+ return KTX_SUCCESS;
+}
+
+void ktx_destruct(ktxStream *stream) {
+ (void)stream;
+}
+
+static Ref<Image> load_from_file_access(Ref<FileAccess> f, Error *r_error) {
+ ktxStream ktx_stream;
+ ktx_stream.read = ktx_read;
+ ktx_stream.skip = ktx_skip;
+ ktx_stream.write = ktx_write;
+ ktx_stream.getpos = ktx_getpos;
+ ktx_stream.setpos = ktx_setpos;
+ ktx_stream.getsize = ktx_getsize;
+ ktx_stream.destruct = ktx_destruct;
+ ktx_stream.type = eStreamTypeCustom;
+ ktx_stream.data.custom_ptr.address = &f;
+ ktx_stream.data.custom_ptr.allocatorAddress = NULL;
+ ktx_stream.data.custom_ptr.size = 0;
+ ktx_stream.readpos = 0;
+ ktx_stream.closeOnDestruct = false;
+ ktxTexture *ktx_texture;
+ KTX_error_code result = ktxTexture_CreateFromStream(&ktx_stream,
+ KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT,
+ &ktx_texture);
+ if (result != KTX_SUCCESS) {
+ ERR_FAIL_V_MSG(Ref<Resource>(), "Invalid or unsupported KTX texture file.");
+ }
+
+ if (ktx_texture->numDimensions != 2) {
+ ktxTexture_Destroy(ktx_texture);
+ ERR_FAIL_V_MSG(Ref<Resource>(), "Unsupported non-2D KTX texture file.");
+ }
+
+ if (ktx_texture->isCubemap || ktx_texture->numFaces != 1) {
+ ktxTexture_Destroy(ktx_texture);
+ ERR_FAIL_V_MSG(Ref<Resource>(), "Unsupported cube map KTX texture file.");
+ }
+
+ if (ktx_texture->isArray) {
+ ktxTexture_Destroy(ktx_texture);
+ ERR_FAIL_V_MSG(Ref<Resource>(), "Unsupported array KTX texture file.");
+ }
+
+ uint32_t width = ktx_texture->baseWidth;
+ uint32_t height = ktx_texture->baseHeight;
+ uint32_t mipmaps = ktx_texture->numLevels;
+ Image::Format format;
+ bool srgb = false;
+
+ switch (ktx_texture->classId) {
+ case ktxTexture1_c:
+ switch (((ktxTexture1 *)ktx_texture)->glInternalformat) {
+ case GL_LUMINANCE:
+ format = Image::FORMAT_L8;
+ break;
+ case GL_LUMINANCE_ALPHA:
+ format = Image::FORMAT_LA8;
+ break;
+ case GL_SRGB8:
+ format = Image::FORMAT_RGB8;
+ srgb = true;
+ break;
+ case GL_SRGB8_ALPHA8:
+ format = Image::FORMAT_RGBA8;
+ srgb = true;
+ break;
+ case GL_R8:
+ case GL_R8UI:
+ format = Image::FORMAT_R8;
+ break;
+ case GL_RG8:
+ format = Image::FORMAT_RG8;
+ break;
+ case GL_RGB8:
+ format = Image::FORMAT_RGB8;
+ break;
+ case GL_RGBA8:
+ format = Image::FORMAT_RGBA8;
+ break;
+ case GL_RGBA4:
+ format = Image::FORMAT_RGBA4444;
+ break;
+ case GL_RGB565:
+ format = Image::FORMAT_RGB565;
+ break;
+ case GL_R32F:
+ format = Image::FORMAT_RF;
+ break;
+ case GL_RG32F:
+ format = Image::FORMAT_RGF;
+ break;
+ case GL_RGB32F:
+ format = Image::FORMAT_RGBF;
+ break;
+ case GL_RGBA32F:
+ format = Image::FORMAT_RGBAF;
+ break;
+ case GL_R16F:
+ format = Image::FORMAT_RH;
+ break;
+ case GL_RG16F:
+ format = Image::FORMAT_RGH;
+ break;
+ case GL_RGB16F:
+ format = Image::FORMAT_RGBH;
+ break;
+ case GL_RGBA16F:
+ format = Image::FORMAT_RGBAH;
+ break;
+ case GL_RGB9_E5:
+ format = Image::FORMAT_RGBE9995;
+ break;
+ case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
+ case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
+ format = Image::FORMAT_DXT1;
+ break;
+ case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
+ format = Image::FORMAT_DXT3;
+ break;
+ case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
+ format = Image::FORMAT_DXT5;
+ break;
+ case GL_COMPRESSED_RED_RGTC1:
+ format = Image::FORMAT_RGTC_R;
+ break;
+ case GL_COMPRESSED_RG_RGTC2:
+ format = Image::FORMAT_RGTC_RG;
+ break;
+ case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT:
+ format = Image::FORMAT_BPTC_RGBFU;
+ break;
+ case GL_COMPRESSED_RGBA_BPTC_UNORM:
+ format = Image::FORMAT_BPTC_RGBA;
+ break;
+#if 0 // TODO: ETC compression is bogus.
+ case GL_ETC1_RGB8_OES:
+ format = Image::FORMAT_ETC;
+ break;
+ case GL_COMPRESSED_R11_EAC:
+ format = Image::FORMAT_ETC2_R11;
+ break;
+ case GL_COMPRESSED_SIGNED_R11_EAC:
+ format = Image::FORMAT_ETC2_R11S;
+ break;
+ case GL_COMPRESSED_RG11_EAC:
+ format = Image::FORMAT_ETC2_RG11;
+ break;
+ case GL_COMPRESSED_SIGNED_RG11_EAC:
+ format = Image::FORMAT_ETC2_RG11S;
+ break;
+ case GL_COMPRESSED_RGB8_ETC2:
+ format = Image::FORMAT_ETC2_RGB8;
+ break;
+ case GL_COMPRESSED_RGBA8_ETC2_EAC:
+ format = Image::FORMAT_ETC2_RGBA8;
+ break;
+ case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
+ format = Image::FORMAT_ETC2_RGB8A1;
+ break;
+#endif
+ case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR:
+ format = Image::FORMAT_ASTC_4x4;
+ break;
+ case GL_COMPRESSED_RGBA_ASTC_4x4_KHR:
+ format = Image::FORMAT_ASTC_4x4_HDR;
+ break;
+ case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR:
+ format = Image::FORMAT_ASTC_8x8;
+ break;
+ case GL_COMPRESSED_RGBA_ASTC_8x8_KHR:
+ format = Image::FORMAT_ASTC_8x8_HDR;
+ break;
+ default:
+ ktxTexture_Destroy(ktx_texture);
+ ERR_FAIL_V_MSG(Ref<Resource>(), "Unsupported format " + itos(((ktxTexture1 *)ktx_texture)->glInternalformat) + " of KTX1 texture file.");
+ }
+ break;
+ case ktxTexture2_c: {
+ ktxTexture2 *ktx_texture2 = reinterpret_cast<ktxTexture2 *>(ktx_texture);
+ if (ktx_texture2->vkFormat == VK_FORMAT_UNDEFINED) {
+ if (!ktxTexture2_NeedsTranscoding(ktx_texture2)) {
+ ktxTexture_Destroy(ktx_texture);
+ ERR_FAIL_V_MSG(Ref<Resource>(), "Invalid VK_FORMAT_UNDEFINED of KTX2 texture file.");
+ }
+ ktx_transcode_fmt_e ktxfmt;
+ switch (ktxTexture2_GetNumComponents(ktx_texture2)) {
+ case 1: {
+ if (ktxTexture2_GetOETF_e(ktx_texture2) == KHR_DF_TRANSFER_SRGB) { // TODO srgb native support
+ ktxfmt = KTX_TTF_RGBA32;
+ } else if (RS::get_singleton()->has_os_feature("rgtc")) {
+ ktxfmt = KTX_TTF_BC4_R;
+ } else {
+ ktxfmt = KTX_TTF_RGBA32;
+ }
+ break;
+ }
+ case 2: {
+ if (ktxTexture2_GetOETF_e(ktx_texture2) == KHR_DF_TRANSFER_SRGB) { // TODO srgb native support
+ ktxfmt = KTX_TTF_RGBA32;
+ } else if (RS::get_singleton()->has_os_feature("rgtc")) {
+ ktxfmt = KTX_TTF_BC5_RG;
+ } else {
+ ktxfmt = KTX_TTF_RGBA32;
+ }
+ break;
+ }
+ case 3: {
+ if (ktxTexture2_GetOETF_e(ktx_texture2) == KHR_DF_TRANSFER_SRGB) { // TODO: srgb native support
+ ktxfmt = KTX_TTF_RGBA32;
+ } else if (RS::get_singleton()->has_os_feature("bptc")) {
+ ktxfmt = KTX_TTF_BC7_RGBA;
+ } else if (RS::get_singleton()->has_os_feature("s3tc")) {
+ ktxfmt = KTX_TTF_BC1_RGB;
+ } else if (RS::get_singleton()->has_os_feature("etc")) {
+ ktxfmt = KTX_TTF_ETC1_RGB;
+ } else {
+ ktxfmt = KTX_TTF_RGBA32;
+ }
+ break;
+ }
+ case 4: {
+ if (ktxTexture2_GetOETF_e(ktx_texture2) == KHR_DF_TRANSFER_SRGB) { // TODO srgb native support
+ ktxfmt = KTX_TTF_RGBA32;
+ } else if (RS::get_singleton()->has_os_feature("astc")) {
+ ktxfmt = KTX_TTF_ASTC_4x4_RGBA;
+ } else if (RS::get_singleton()->has_os_feature("bptc")) {
+ ktxfmt = KTX_TTF_BC7_RGBA;
+ } else if (RS::get_singleton()->has_os_feature("s3tc")) {
+ ktxfmt = KTX_TTF_BC3_RGBA;
+ } else if (RS::get_singleton()->has_os_feature("etc2")) {
+ ktxfmt = KTX_TTF_ETC2_RGBA;
+ } else {
+ ktxfmt = KTX_TTF_RGBA32;
+ }
+ break;
+ }
+ default: {
+ ktxTexture_Destroy(ktx_texture);
+ ERR_FAIL_V_MSG(Ref<Resource>(), "Invalid components of KTX2 texture file.");
+ }
+ }
+ result = ktxTexture2_TranscodeBasis(ktx_texture2, ktxfmt, 0);
+ if (result != KTX_SUCCESS) {
+ ktxTexture_Destroy(ktx_texture);
+ ERR_FAIL_V_MSG(Ref<Resource>(), "Failed to transcode KTX2 texture file.");
+ }
+ }
+ switch (ktx_texture2->vkFormat) {
+ case VK_FORMAT_R8_UNORM:
+ format = Image::FORMAT_L8;
+ break;
+ case VK_FORMAT_R8G8_UNORM:
+ format = Image::FORMAT_LA8;
+ break;
+ case VK_FORMAT_R8G8B8_SRGB:
+ format = Image::FORMAT_RGB8;
+ srgb = true;
+ break;
+ case VK_FORMAT_R8G8B8A8_SRGB:
+ format = Image::FORMAT_RGBA8;
+ srgb = true;
+ break;
+ case VK_FORMAT_R8_UINT:
+ format = Image::FORMAT_R8;
+ break;
+ case VK_FORMAT_R8G8_UINT:
+ format = Image::FORMAT_RG8;
+ break;
+ case VK_FORMAT_R8G8B8_UINT:
+ format = Image::FORMAT_RGB8;
+ break;
+ case VK_FORMAT_R8G8B8A8_UINT:
+ format = Image::FORMAT_RGBA8;
+ break;
+ case VK_FORMAT_R4G4B4A4_UNORM_PACK16:
+ format = Image::FORMAT_RGBA4444;
+ break;
+ case VK_FORMAT_R5G6B5_UNORM_PACK16:
+ format = Image::FORMAT_RGB565;
+ break;
+ case VK_FORMAT_R32_SFLOAT:
+ format = Image::FORMAT_RF;
+ break;
+ case VK_FORMAT_R32G32_SFLOAT:
+ format = Image::FORMAT_RGF;
+ break;
+ case VK_FORMAT_R32G32B32_SFLOAT:
+ format = Image::FORMAT_RGBF;
+ break;
+ case VK_FORMAT_R32G32B32A32_SFLOAT:
+ format = Image::FORMAT_RGBAF;
+ break;
+ case VK_FORMAT_R16_SFLOAT:
+ format = Image::FORMAT_RH;
+ break;
+ case VK_FORMAT_R16G16_SFLOAT:
+ format = Image::FORMAT_RGH;
+ break;
+ case VK_FORMAT_R16G16B16_SFLOAT:
+ format = Image::FORMAT_RGBH;
+ break;
+ case VK_FORMAT_R16G16B16A16_SFLOAT:
+ format = Image::FORMAT_RGBAH;
+ break;
+ case VK_FORMAT_E5B9G9R9_UFLOAT_PACK32:
+ format = Image::FORMAT_RGBE9995;
+ break;
+ case VK_FORMAT_BC1_RGB_UNORM_BLOCK:
+ case VK_FORMAT_BC1_RGBA_UNORM_BLOCK:
+ format = Image::FORMAT_DXT1;
+ break;
+ case VK_FORMAT_BC2_UNORM_BLOCK:
+ format = Image::FORMAT_DXT3;
+ break;
+ case VK_FORMAT_BC3_UNORM_BLOCK:
+ format = Image::FORMAT_DXT5;
+ break;
+ case VK_FORMAT_BC4_UNORM_BLOCK:
+ format = Image::FORMAT_RGTC_R;
+ break;
+ case VK_FORMAT_BC5_UNORM_BLOCK:
+ format = Image::FORMAT_RGTC_RG;
+ break;
+ case VK_FORMAT_BC6H_UFLOAT_BLOCK:
+ format = Image::FORMAT_BPTC_RGBFU;
+ break;
+ case VK_FORMAT_BC6H_SFLOAT_BLOCK:
+ format = Image::FORMAT_BPTC_RGBF;
+ break;
+ case VK_FORMAT_BC7_UNORM_BLOCK:
+ format = Image::FORMAT_BPTC_RGBA;
+ break;
+#if 0 // TODO: ETC compression is bogus.
+ case VK_FORMAT_EAC_R11_UNORM_BLOCK:
+ format = Image::FORMAT_ETC2_R11;
+ break;
+ case VK_FORMAT_EAC_R11_SNORM_BLOCK:
+ format = Image::FORMAT_ETC2_R11S;
+ break;
+ case VK_FORMAT_EAC_R11G11_UNORM_BLOCK:
+ format = Image::FORMAT_ETC2_RG11;
+ break;
+ case VK_FORMAT_EAC_R11G11_SNORM_BLOCK:
+ format = Image::FORMAT_ETC2_RG11S;
+ break;
+ case VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK:
+ format = Image::FORMAT_ETC2_RGB8;
+ break;
+ case VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK:
+ format = Image::FORMAT_ETC2_RGBA8;
+ break;
+ case VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK:
+ format = Image::FORMAT_ETC2_RGB8A1;
+ break;
+#endif
+ case VK_FORMAT_ASTC_4x4_SRGB_BLOCK:
+ format = Image::FORMAT_ASTC_4x4;
+ break;
+ case VK_FORMAT_ASTC_4x4_UNORM_BLOCK:
+ format = Image::FORMAT_ASTC_4x4_HDR;
+ break;
+ case VK_FORMAT_ASTC_8x8_SRGB_BLOCK:
+ format = Image::FORMAT_ASTC_8x8;
+ break;
+ case VK_FORMAT_ASTC_8x8_UNORM_BLOCK:
+ format = Image::FORMAT_ASTC_8x8_HDR;
+ break;
+ default:
+ ktxTexture_Destroy(ktx_texture);
+ ERR_FAIL_V_MSG(Ref<Resource>(), "Unsupported format " + itos(((ktxTexture2 *)ktx_texture)->vkFormat) + " of KTX2 texture file.");
+ break;
+ }
+ break;
+ }
+ default:
+ ktxTexture_Destroy(ktx_texture);
+ ERR_FAIL_V_MSG(Ref<Resource>(), "Unsupported version KTX texture file.");
+ break;
+ }
+
+ Vector<uint8_t> src_data;
+
+ // KTX use 4-bytes padding, don't use mipmaps if padding is effective
+ // TODO: unpad dynamically
+ int pixel_size = Image::get_format_pixel_size(format);
+ int pixel_rshift = Image::get_format_pixel_rshift(format);
+ int block = Image::get_format_block_size(format);
+ int minw, minh;
+ Image::get_format_min_pixel_size(format, minw, minh);
+ int w = width;
+ int h = height;
+ for (uint32_t i = 0; i < mipmaps; ++i) {
+ ktx_size_t mip_size = ktxTexture_GetImageSize(ktx_texture, i);
+ size_t bw = w % block != 0 ? w + (block - w % block) : w;
+ size_t bh = h % block != 0 ? h + (block - h % block) : h;
+ size_t s = bw * bh;
+ s *= pixel_size;
+ s >>= pixel_rshift;
+ if (mip_size != static_cast<ktx_size_t>(s)) {
+ if (!i) {
+ ktxTexture_Destroy(ktx_texture);
+ ERR_FAIL_V_MSG(Ref<Resource>(), "Unsupported padded KTX texture file.");
+ }
+ mipmaps = 1;
+ break;
+ }
+ w = MAX(minw, w >> 1);
+ h = MAX(minh, h >> 1);
+ }
+
+ for (uint32_t i = 0; i < mipmaps; ++i) {
+ ktx_size_t mip_size = ktxTexture_GetImageSize(ktx_texture, i);
+ ktx_size_t offset;
+ if (ktxTexture_GetImageOffset(ktx_texture, i, 0, 0, &offset) != KTX_SUCCESS) {
+ ktxTexture_Destroy(ktx_texture);
+ ERR_FAIL_V_MSG(Ref<Resource>(), "Invalid KTX texture file.");
+ }
+ int prev_size = src_data.size();
+ src_data.resize(prev_size + mip_size);
+ memcpy(src_data.ptrw() + prev_size, ktxTexture_GetData(ktx_texture) + offset, mip_size);
+ }
+
+ Ref<Image> img = memnew(Image(width, height, mipmaps - 1, format, src_data));
+ if (srgb) {
+ img->srgb_to_linear();
+ }
+
+ if (r_error) {
+ *r_error = OK;
+ }
+
+ ktxTexture_Destroy(ktx_texture);
+ return img;
+}
+
+Ref<Resource> ResourceFormatKTX::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) {
+ if (r_error) {
+ *r_error = ERR_CANT_OPEN;
+ }
+
+ Error err;
+ Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ, &err);
+ if (f.is_null()) {
+ return Ref<Resource>();
+ }
+
+ Ref<FileAccess> fref(f);
+ if (r_error) {
+ *r_error = ERR_FILE_CORRUPT;
+ }
+
+ ERR_FAIL_COND_V_MSG(err != OK, Ref<Resource>(), "Unable to open KTX texture file '" + p_path + "'.");
+ Ref<Image> img = load_from_file_access(f, r_error);
+ Ref<ImageTexture> texture = ImageTexture::create_from_image(img);
+ return texture;
+}
+
+static Ref<Image> _ktx_mem_loader_func(const uint8_t *p_ktx, int p_size) {
+ Ref<FileAccessMemory> f;
+ f.instantiate();
+ f->open_custom(p_ktx, p_size);
+ Error err;
+ Ref<Image> img = load_from_file_access(f, &err);
+ ERR_FAIL_COND_V(err, Ref<Image>());
+ return img;
+}
+
+void ResourceFormatKTX::get_recognized_extensions(List<String> *p_extensions) const {
+ p_extensions->push_back("ktx");
+ p_extensions->push_back("ktx2");
+}
+
+bool ResourceFormatKTX::handles_type(const String &p_type) const {
+ return ClassDB::is_parent_class(p_type, "Texture2D");
+}
+
+String ResourceFormatKTX::get_resource_type(const String &p_path) const {
+ if (p_path.get_extension().to_lower() == "ktx" || p_path.get_extension().to_lower() == "ktx2") {
+ return "ImageTexture";
+ }
+ return "";
+}
+
+ResourceFormatKTX::ResourceFormatKTX() {
+ Image::_ktx_mem_loader_func = _ktx_mem_loader_func;
+}
diff --git a/modules/ktx/texture_loader_ktx.h b/modules/ktx/texture_loader_ktx.h
new file mode 100644
index 0000000000..0ea676be6b
--- /dev/null
+++ b/modules/ktx/texture_loader_ktx.h
@@ -0,0 +1,48 @@
+/**************************************************************************/
+/* texture_loader_ktx.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef TEXTURE_LOADER_KTX_H
+#define TEXTURE_LOADER_KTX_H
+
+#include "core/io/resource_loader.h"
+#include "scene/resources/texture.h"
+
+class ResourceFormatKTX : public ResourceFormatLoader {
+public:
+ virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE);
+ virtual void get_recognized_extensions(List<String> *p_extensions) const;
+ virtual bool handles_type(const String &p_type) const;
+ virtual String get_resource_type(const String &p_path) const;
+
+ virtual ~ResourceFormatKTX() {}
+ ResourceFormatKTX();
+};
+
+#endif // TEXTURE_LOADER_KTX_H
diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp
index 342c94cbd9..006aca6c73 100644
--- a/modules/mono/editor/bindings_generator.cpp
+++ b/modules/mono/editor/bindings_generator.cpp
@@ -148,7 +148,7 @@ static String fix_doc_description(const String &p_bbcode) {
.strip_edges();
}
-String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterface *p_itype) {
+String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterface *p_itype, bool p_is_signal) {
// Based on the version in EditorHelp
if (p_bbcode.is_empty()) {
@@ -305,11 +305,11 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf
_append_xml_enum(xml_output, target_itype, target_cname, link_target, link_target_parts);
} else if (link_tag == "constant") {
_append_xml_constant(xml_output, target_itype, target_cname, link_target, link_target_parts);
+ } else if (link_tag == "param") {
+ _append_xml_param(xml_output, link_target, p_is_signal);
} else if (link_tag == "theme_item") {
// We do not declare theme_items in any way in C#, so there is nothing to reference
_append_xml_undeclared(xml_output, link_target);
- } else if (link_tag == "param") {
- _append_xml_undeclared(xml_output, snake_to_camel_case(link_target, false));
}
pos = brk_end + 1;
@@ -684,7 +684,7 @@ void BindingsGenerator::_append_xml_constant(StringBuilder &p_xml_output, const
p_xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".");
p_xml_output.append(p_target_itype->proxy_name);
p_xml_output.append(".");
- p_xml_output.append(target_ienum->cname);
+ p_xml_output.append(target_ienum->proxy_name);
p_xml_output.append(".");
p_xml_output.append(target_iconst->proxy_name);
p_xml_output.append("\"/>");
@@ -725,7 +725,7 @@ void BindingsGenerator::_append_xml_constant_in_global_scope(StringBuilder &p_xm
if (target_iconst) {
p_xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".");
- p_xml_output.append(target_ienum->cname);
+ p_xml_output.append(target_ienum->proxy_name);
p_xml_output.append(".");
p_xml_output.append(target_iconst->proxy_name);
p_xml_output.append("\"/>");
@@ -736,6 +736,21 @@ void BindingsGenerator::_append_xml_constant_in_global_scope(StringBuilder &p_xm
}
}
+void BindingsGenerator::_append_xml_param(StringBuilder &p_xml_output, const String &p_link_target, bool p_is_signal) {
+ const String link_target = snake_to_camel_case(p_link_target);
+
+ if (!p_is_signal) {
+ p_xml_output.append("<paramref name=\"");
+ p_xml_output.append(link_target);
+ p_xml_output.append("\"/>");
+ } else {
+ // Documentation in C# is added to an event, not the delegate itself;
+ // as such, we treat these parameters as codeblocks instead.
+ // See: https://github.com/godotengine/godot/pull/65529
+ _append_xml_undeclared(p_xml_output, link_target);
+ }
+}
+
void BindingsGenerator::_append_xml_undeclared(StringBuilder &p_xml_output, const String &p_link_target) {
p_xml_output.append("<c>");
p_xml_output.append(p_link_target);
@@ -981,7 +996,7 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) {
for (const EnumInterface &ienum : global_enums) {
CRASH_COND(ienum.constants.is_empty());
- String enum_proxy_name = ienum.cname.operator String();
+ String enum_proxy_name = ienum.proxy_name;
bool enum_in_static_class = false;
@@ -995,7 +1010,7 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) {
_log("Declaring global enum '%s' inside struct '%s'\n", enum_proxy_name.utf8().get_data(), enum_class_name.utf8().get_data());
p_output.append("\npublic partial struct ");
- p_output.append(pascal_to_pascal_case(enum_class_name));
+ p_output.append(enum_class_name);
p_output.append("\n" OPEN_BLOCK);
}
@@ -1004,7 +1019,7 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) {
}
p_output.append("\npublic enum ");
- p_output.append(pascal_to_pascal_case(enum_proxy_name));
+ p_output.append(enum_proxy_name);
p_output.append(" : long");
p_output.append("\n" OPEN_BLOCK);
@@ -1469,7 +1484,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
}
output.append(MEMBER_BEGIN "public enum ");
- output.append(pascal_to_pascal_case(ienum.cname.operator String()));
+ output.append(ienum.proxy_name);
output.append(" : long");
output.append(MEMBER_BEGIN OPEN_BLOCK);
@@ -2330,31 +2345,31 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf
// Generate signal
{
- p_output.append(MEMBER_BEGIN "/// <summary>\n");
- p_output.append(INDENT1 "/// ");
- p_output.append("Represents the method that handles the ");
- p_output.append("<see cref=\"" BINDINGS_NAMESPACE "." + p_itype.proxy_name + "." + p_isignal.proxy_name + "\"/>");
- p_output.append(" event of a ");
- p_output.append("<see cref=\"" BINDINGS_NAMESPACE "." + p_itype.proxy_name + "\"/>");
- p_output.append(" class.\n");
- p_output.append(INDENT1 "/// </summary>");
-
- if (p_isignal.is_deprecated) {
- if (p_isignal.deprecation_message.is_empty()) {
- WARN_PRINT("An empty deprecation message is discouraged. Signal: '" + p_isignal.proxy_name + "'.");
- }
-
- p_output.append(MEMBER_BEGIN "[Obsolete(\"");
- p_output.append(p_isignal.deprecation_message);
- p_output.append("\")]");
- }
-
bool is_parameterless = p_isignal.arguments.size() == 0;
// Delegate name is [SignalName]EventHandler
String delegate_name = is_parameterless ? "Action" : p_isignal.proxy_name + "EventHandler";
if (!is_parameterless) {
+ p_output.append(MEMBER_BEGIN "/// <summary>\n");
+ p_output.append(INDENT1 "/// ");
+ p_output.append("Represents the method that handles the ");
+ p_output.append("<see cref=\"" BINDINGS_NAMESPACE "." + p_itype.proxy_name + "." + p_isignal.proxy_name + "\"/>");
+ p_output.append(" event of a ");
+ p_output.append("<see cref=\"" BINDINGS_NAMESPACE "." + p_itype.proxy_name + "\"/>");
+ p_output.append(" class.\n");
+ p_output.append(INDENT1 "/// </summary>");
+
+ if (p_isignal.is_deprecated) {
+ if (p_isignal.deprecation_message.is_empty()) {
+ WARN_PRINT("An empty deprecation message is discouraged. Signal: '" + p_isignal.proxy_name + "'.");
+ }
+
+ p_output.append(MEMBER_BEGIN "[Obsolete(\"");
+ p_output.append(p_isignal.deprecation_message);
+ p_output.append("\")]");
+ }
+
// Generate delegate
p_output.append(MEMBER_BEGIN "public delegate void ");
p_output.append(delegate_name);
@@ -2390,7 +2405,7 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf
}
if (p_isignal.method_doc && p_isignal.method_doc->description.size()) {
- String xml_summary = bbcode_to_xml(fix_doc_description(p_isignal.method_doc->description), &p_itype);
+ String xml_summary = bbcode_to_xml(fix_doc_description(p_isignal.method_doc->description), &p_itype, true);
Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();
if (summary_lines.size()) {
@@ -3316,8 +3331,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
enum_proxy_name += "Enum";
enum_proxy_cname = StringName(enum_proxy_name);
}
- EnumInterface ienum(enum_proxy_cname);
- ienum.is_flags = E.value.is_bitfield;
+ EnumInterface ienum(enum_proxy_cname, enum_proxy_name, E.value.is_bitfield);
const List<StringName> &enum_constants = E.value.constants;
for (const StringName &constant_cname : enum_constants) {
String constant_name = constant_cname.operator String();
@@ -3950,8 +3964,7 @@ void BindingsGenerator::_populate_global_constants() {
iconstant.const_doc = const_doc;
if (enum_name != StringName()) {
- EnumInterface ienum(enum_name);
- ienum.is_flags = CoreConstants::is_global_constant_bitfield(i);
+ EnumInterface ienum(enum_name, pascal_to_pascal_case(enum_name.operator String()), CoreConstants::is_global_constant_bitfield(i));
List<EnumInterface>::Element *enum_match = global_enums.find(ienum);
if (enum_match) {
enum_match->get().constants.push_back(iconstant);
@@ -3969,7 +3982,7 @@ void BindingsGenerator::_populate_global_constants() {
enum_itype.is_enum = true;
enum_itype.name = ienum.cname.operator String();
enum_itype.cname = ienum.cname;
- enum_itype.proxy_name = pascal_to_pascal_case(enum_itype.name);
+ enum_itype.proxy_name = ienum.proxy_name;
TypeInterface::postsetup_enum_type(enum_itype);
enum_types.insert(enum_itype.cname, enum_itype);
diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h
index d4c7a59e74..6118576bb6 100644
--- a/modules/mono/editor/bindings_generator.h
+++ b/modules/mono/editor/bindings_generator.h
@@ -60,6 +60,7 @@ class BindingsGenerator {
struct EnumInterface {
StringName cname;
+ String proxy_name;
List<ConstantInterface> constants;
bool is_flags = false;
@@ -69,8 +70,10 @@ class BindingsGenerator {
EnumInterface() {}
- EnumInterface(const StringName &p_cname) {
+ EnumInterface(const StringName &p_cname, const String &p_proxy_name, bool p_is_flags) {
cname = p_cname;
+ proxy_name = p_proxy_name;
+ is_flags = p_is_flags;
}
};
@@ -751,7 +754,7 @@ class BindingsGenerator {
return p_type->name;
}
- String bbcode_to_xml(const String &p_bbcode, const TypeInterface *p_itype);
+ String bbcode_to_xml(const String &p_bbcode, const TypeInterface *p_itype, bool p_is_signal = false);
void _append_xml_method(StringBuilder &p_xml_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts);
void _append_xml_member(StringBuilder &p_xml_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts);
@@ -759,6 +762,7 @@ class BindingsGenerator {
void _append_xml_enum(StringBuilder &p_xml_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts);
void _append_xml_constant(StringBuilder &p_xml_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts);
void _append_xml_constant_in_global_scope(StringBuilder &p_xml_output, const String &p_target_cname, const String &p_link_target);
+ void _append_xml_param(StringBuilder &p_xml_output, const String &p_link_target, bool p_is_signal);
void _append_xml_undeclared(StringBuilder &p_xml_output, const String &p_link_target);
int _determine_enum_prefix(const EnumInterface &p_ienum);
diff --git a/modules/mono/godotsharp_dirs.cpp b/modules/mono/godotsharp_dirs.cpp
index 00ef4ccdde..c84ecf4ceb 100644
--- a/modules/mono/godotsharp_dirs.cpp
+++ b/modules/mono/godotsharp_dirs.cpp
@@ -33,10 +33,6 @@
#include "mono_gd/gd_mono.h"
#include "utils/path_utils.h"
-#ifdef ANDROID_ENABLED
-#include "mono_gd/support/android_support.h"
-#endif
-
#include "core/config/project_settings.h"
#include "core/io/dir_access.h"
#include "core/os/os.h"
diff --git a/modules/mono/icons/BuildCSharp.svg b/modules/mono/icons/BuildCSharp.svg
index 9d0102c35d..d4ba5bc293 100644
--- a/modules/mono/icons/BuildCSharp.svg
+++ b/modules/mono/icons/BuildCSharp.svg
@@ -1 +1 @@
-<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M 9.6060193,0.78346667 C 8.6741914,0.96303367 7.6708299,1.5334576 6.9028943,1.9768256 l -2.1523438,1.244141 0.082031,0.138672 -0.3105469,-0.00781 -2.5839844,1.492188 1.9101563,3.308593 2.5820312,-1.490234 0.1425781,-0.255859 4.1875002,7.2539054 0.5,0.867187 c 0.415803,0.720194 1.331398,0.964165 2.050782,0.548829 0.719286,-0.415279 0.963839,-1.33001 0.548828,-2.048829 l -2,-3.4648424 -2.8808602,-4.990235 3.7070322,-2.101562 -0.265626,-0.439453 C 11.697382,0.83561667 10.650124,0.58226267 9.6060193,0.78346667 Z" fill="#e0e0e0"/></svg>
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m2.256 4.85 1.7 2.945 2.598-1.5 4.5 7.794a1 1-30 0 0 2.598-1.5l-4.65-8.054 3.464-2-.25-.433s-1.25-2.165-3.848-.665l-3.464 2-.31.063z" fill="#e0e0e0"/></svg>
diff --git a/modules/navigation/godot_navigation_server.cpp b/modules/navigation/godot_navigation_server.cpp
index 8f56bf0f1d..9162fcf171 100644
--- a/modules/navigation/godot_navigation_server.cpp
+++ b/modules/navigation/godot_navigation_server.cpp
@@ -930,7 +930,7 @@ COMMAND_2(obstacle_set_avoidance_layers, RID, p_obstacle, uint32_t, p_layers) {
obstacle->set_avoidance_layers(p_layers);
}
-void GodotNavigationServer::parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback) {
+void GodotNavigationServer::parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, Node *p_root_node, const Callable &p_callback) {
#ifndef _3D_DISABLED
ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "The SceneTree can only be parsed on the main thread. Call this function from the main thread or use call_deferred().");
ERR_FAIL_COND_MSG(!p_navigation_mesh.is_valid(), "Invalid navigation mesh.");
@@ -942,26 +942,26 @@ void GodotNavigationServer::parse_source_geometry_data(const Ref<NavigationMesh>
#endif // _3D_DISABLED
}
-void GodotNavigationServer::bake_from_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback) {
+void GodotNavigationServer::bake_from_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback) {
#ifndef _3D_DISABLED
ERR_FAIL_COND_MSG(!p_navigation_mesh.is_valid(), "Invalid navigation mesh.");
ERR_FAIL_COND_MSG(!p_source_geometry_data.is_valid(), "Invalid NavigationMeshSourceGeometryData3D.");
- if (!p_source_geometry_data->has_data()) {
- p_navigation_mesh->clear();
- if (p_callback.is_valid()) {
- Callable::CallError ce;
- Variant result;
- p_callback.callp(nullptr, 0, result, ce);
- }
- return;
- }
-
ERR_FAIL_NULL(NavMeshGenerator3D::get_singleton());
NavMeshGenerator3D::get_singleton()->bake_from_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_callback);
#endif // _3D_DISABLED
}
+void GodotNavigationServer::bake_from_source_geometry_data_async(const Ref<NavigationMesh> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback) {
+#ifndef _3D_DISABLED
+ ERR_FAIL_COND_MSG(!p_navigation_mesh.is_valid(), "Invalid navigation mesh.");
+ ERR_FAIL_COND_MSG(!p_source_geometry_data.is_valid(), "Invalid NavigationMeshSourceGeometryData3D.");
+
+ ERR_FAIL_NULL(NavMeshGenerator3D::get_singleton());
+ NavMeshGenerator3D::get_singleton()->bake_from_source_geometry_data_async(p_navigation_mesh, p_source_geometry_data, p_callback);
+#endif // _3D_DISABLED
+}
+
COMMAND_1(free, RID, p_object) {
if (map_owner.owns(p_object)) {
NavMap *map = map_owner.get_or_null(p_object);
@@ -1093,6 +1093,16 @@ void GodotNavigationServer::process(real_t p_delta_time) {
return;
}
+#ifndef _3D_DISABLED
+ // Sync finished navmesh bakes before doing NavMap updates.
+ if (navmesh_generator_3d) {
+ navmesh_generator_3d->sync();
+ // Finished bakes emit callbacks and users might have reacted to those.
+ // Flush queue again so users do not have to wait for the next sync.
+ flush_queries();
+ }
+#endif // _3D_DISABLED
+
int _new_pm_region_count = 0;
int _new_pm_agent_count = 0;
int _new_pm_link_count = 0;
diff --git a/modules/navigation/godot_navigation_server.h b/modules/navigation/godot_navigation_server.h
index 8b91ca36bd..40893bada6 100644
--- a/modules/navigation/godot_navigation_server.h
+++ b/modules/navigation/godot_navigation_server.h
@@ -228,8 +228,9 @@ public:
virtual void obstacle_set_vertices(RID p_obstacle, const Vector<Vector3> &p_vertices) override;
COMMAND_2(obstacle_set_avoidance_layers, RID, p_obstacle, uint32_t, p_layers);
- virtual void parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable()) override;
- virtual void bake_from_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback = Callable()) override;
+ virtual void parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable()) override;
+ virtual void bake_from_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback = Callable()) override;
+ virtual void bake_from_source_geometry_data_async(const Ref<NavigationMesh> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback = Callable()) override;
COMMAND_1(free, RID, p_object);
diff --git a/modules/navigation/nav_mesh_generator_3d.cpp b/modules/navigation/nav_mesh_generator_3d.cpp
index 0132e38ceb..7c27417e5f 100644
--- a/modules/navigation/nav_mesh_generator_3d.cpp
+++ b/modules/navigation/nav_mesh_generator_3d.cpp
@@ -32,6 +32,7 @@
#include "nav_mesh_generator_3d.h"
+#include "core/config/project_settings.h"
#include "core/math/convex_hull.h"
#include "core/os/thread.h"
#include "scene/3d/mesh_instance_3d.h"
@@ -63,7 +64,12 @@
NavMeshGenerator3D *NavMeshGenerator3D::singleton = nullptr;
Mutex NavMeshGenerator3D::baking_navmesh_mutex;
+Mutex NavMeshGenerator3D::generator_task_mutex;
+bool NavMeshGenerator3D::use_threads = true;
+bool NavMeshGenerator3D::baking_use_multiple_threads = true;
+bool NavMeshGenerator3D::baking_use_high_priority_threads = true;
HashSet<Ref<NavigationMesh>> NavMeshGenerator3D::baking_navmeshes;
+HashMap<WorkerThreadPool::TaskID, NavMeshGenerator3D::NavMeshGeneratorTask3D *> NavMeshGenerator3D::generator_tasks;
NavMeshGenerator3D *NavMeshGenerator3D::get_singleton() {
return singleton;
@@ -72,15 +78,67 @@ NavMeshGenerator3D *NavMeshGenerator3D::get_singleton() {
NavMeshGenerator3D::NavMeshGenerator3D() {
ERR_FAIL_COND(singleton != nullptr);
singleton = this;
+
+ baking_use_multiple_threads = GLOBAL_GET("navigation/baking/thread_model/baking_use_multiple_threads");
+ baking_use_high_priority_threads = GLOBAL_GET("navigation/baking/thread_model/baking_use_high_priority_threads");
+
+ // Using threads might cause problems on certain exports or with the Editor on certain devices.
+ // This is the main switch to turn threaded navmesh baking off should the need arise.
+ use_threads = baking_use_multiple_threads && !Engine::get_singleton()->is_editor_hint();
}
NavMeshGenerator3D::~NavMeshGenerator3D() {
cleanup();
}
+void NavMeshGenerator3D::sync() {
+ if (generator_tasks.size() == 0) {
+ return;
+ }
+
+ baking_navmesh_mutex.lock();
+ generator_task_mutex.lock();
+
+ LocalVector<WorkerThreadPool::TaskID> finished_task_ids;
+
+ for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask3D *> &E : generator_tasks) {
+ if (WorkerThreadPool::get_singleton()->is_task_completed(E.key)) {
+ WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key);
+ finished_task_ids.push_back(E.key);
+
+ NavMeshGeneratorTask3D *generator_task = E.value;
+ DEV_ASSERT(generator_task->status == NavMeshGeneratorTask3D::TaskStatus::BAKING_FINISHED);
+
+ baking_navmeshes.erase(generator_task->navigation_mesh);
+ if (generator_task->callback.is_valid()) {
+ generator_emit_callback(generator_task->callback);
+ }
+ memdelete(generator_task);
+ }
+ }
+
+ for (WorkerThreadPool::TaskID finished_task_id : finished_task_ids) {
+ generator_tasks.erase(finished_task_id);
+ }
+
+ generator_task_mutex.unlock();
+ baking_navmesh_mutex.unlock();
+}
+
void NavMeshGenerator3D::cleanup() {
baking_navmesh_mutex.lock();
+ generator_task_mutex.lock();
+
baking_navmeshes.clear();
+
+ for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask3D *> &E : generator_tasks) {
+ WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key);
+ NavMeshGeneratorTask3D *generator_task = E.value;
+ memdelete(generator_task);
+ }
+ generator_tasks.clear();
+
+ generator_task_mutex.unlock();
baking_navmesh_mutex.unlock();
}
@@ -88,7 +146,7 @@ void NavMeshGenerator3D::finish() {
cleanup();
}
-void NavMeshGenerator3D::parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback) {
+void NavMeshGenerator3D::parse_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback) {
ERR_FAIL_COND(!Thread::is_main_thread());
ERR_FAIL_COND(!p_navigation_mesh.is_valid());
ERR_FAIL_COND(p_root_node == nullptr);
@@ -102,19 +160,25 @@ void NavMeshGenerator3D::parse_source_geometry_data(const Ref<NavigationMesh> &p
}
}
-void NavMeshGenerator3D::bake_from_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback) {
+void NavMeshGenerator3D::bake_from_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, const Callable &p_callback) {
ERR_FAIL_COND(!p_navigation_mesh.is_valid());
ERR_FAIL_COND(!p_source_geometry_data.is_valid());
- ERR_FAIL_COND(!p_source_geometry_data->has_data());
+
+ if (!p_source_geometry_data->has_data()) {
+ p_navigation_mesh->clear();
+ if (p_callback.is_valid()) {
+ generator_emit_callback(p_callback);
+ }
+ return;
+ }
baking_navmesh_mutex.lock();
if (baking_navmeshes.has(p_navigation_mesh)) {
baking_navmesh_mutex.unlock();
ERR_FAIL_MSG("NavigationMesh is already baking. Wait for current bake to finish.");
- } else {
- baking_navmeshes.insert(p_navigation_mesh);
- baking_navmesh_mutex.unlock();
}
+ baking_navmeshes.insert(p_navigation_mesh);
+ baking_navmesh_mutex.unlock();
generator_bake_from_source_geometry_data(p_navigation_mesh, p_source_geometry_data);
@@ -127,6 +191,51 @@ void NavMeshGenerator3D::bake_from_source_geometry_data(Ref<NavigationMesh> p_na
}
}
+void NavMeshGenerator3D::bake_from_source_geometry_data_async(Ref<NavigationMesh> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, const Callable &p_callback) {
+ ERR_FAIL_COND(!p_navigation_mesh.is_valid());
+ ERR_FAIL_COND(!p_source_geometry_data.is_valid());
+
+ if (!p_source_geometry_data->has_data()) {
+ p_navigation_mesh->clear();
+ if (p_callback.is_valid()) {
+ generator_emit_callback(p_callback);
+ }
+ return;
+ }
+
+ if (!use_threads) {
+ bake_from_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_callback);
+ return;
+ }
+
+ baking_navmesh_mutex.lock();
+ if (baking_navmeshes.has(p_navigation_mesh)) {
+ baking_navmesh_mutex.unlock();
+ ERR_FAIL_MSG("NavigationMesh is already baking. Wait for current bake to finish.");
+ return;
+ }
+ baking_navmeshes.insert(p_navigation_mesh);
+ baking_navmesh_mutex.unlock();
+
+ generator_task_mutex.lock();
+ NavMeshGeneratorTask3D *generator_task = memnew(NavMeshGeneratorTask3D);
+ generator_task->navigation_mesh = p_navigation_mesh;
+ generator_task->source_geometry_data = p_source_geometry_data;
+ generator_task->callback = p_callback;
+ generator_task->status = NavMeshGeneratorTask3D::TaskStatus::BAKING_STARTED;
+ generator_task->thread_task_id = WorkerThreadPool::get_singleton()->add_native_task(&NavMeshGenerator3D::generator_thread_bake, generator_task, NavMeshGenerator3D::baking_use_high_priority_threads, SNAME("NavMeshGeneratorBake3D"));
+ generator_tasks.insert(generator_task->thread_task_id, generator_task);
+ generator_task_mutex.unlock();
+}
+
+void NavMeshGenerator3D::generator_thread_bake(void *p_arg) {
+ NavMeshGeneratorTask3D *generator_task = static_cast<NavMeshGeneratorTask3D *>(p_arg);
+
+ generator_bake_from_source_geometry_data(generator_task->navigation_mesh, generator_task->source_geometry_data);
+
+ generator_task->status = NavMeshGeneratorTask3D::TaskStatus::BAKING_FINISHED;
+}
+
void NavMeshGenerator3D::generator_parse_geometry_node(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node, bool p_recurse_children) {
generator_parse_meshinstance3d_node(p_navigation_mesh, p_source_geometry_data, p_node);
generator_parse_multimeshinstance3d_node(p_navigation_mesh, p_source_geometry_data, p_node);
@@ -504,8 +613,8 @@ void NavMeshGenerator3D::generator_bake_from_source_geometry_data(Ref<Navigation
return;
}
- const Vector<float> vertices = p_source_geometry_data->get_vertices();
- const Vector<int> indices = p_source_geometry_data->get_indices();
+ const Vector<float> &vertices = p_source_geometry_data->get_vertices();
+ const Vector<int> &indices = p_source_geometry_data->get_indices();
if (vertices.size() < 3 || indices.size() < 3) {
return;
diff --git a/modules/navigation/nav_mesh_generator_3d.h b/modules/navigation/nav_mesh_generator_3d.h
index dc844c2595..4220927641 100644
--- a/modules/navigation/nav_mesh_generator_3d.h
+++ b/modules/navigation/nav_mesh_generator_3d.h
@@ -34,6 +34,7 @@
#ifndef _3D_DISABLED
#include "core/object/class_db.h"
+#include "core/object/worker_thread_pool.h"
#include "modules/modules_enabled.gen.h" // For csg, gridmap.
class Node;
@@ -44,6 +45,31 @@ class NavMeshGenerator3D : public Object {
static NavMeshGenerator3D *singleton;
static Mutex baking_navmesh_mutex;
+ static Mutex generator_task_mutex;
+
+ static bool use_threads;
+ static bool baking_use_multiple_threads;
+ static bool baking_use_high_priority_threads;
+
+ struct NavMeshGeneratorTask3D {
+ enum TaskStatus {
+ BAKING_STARTED,
+ BAKING_FINISHED,
+ BAKING_FAILED,
+ CALLBACK_DISPATCHED,
+ CALLBACK_FAILED,
+ };
+
+ Ref<NavigationMesh> navigation_mesh;
+ Ref<NavigationMeshSourceGeometryData3D> source_geometry_data;
+ Callable callback;
+ WorkerThreadPool::TaskID thread_task_id = WorkerThreadPool::INVALID_TASK_ID;
+ NavMeshGeneratorTask3D::TaskStatus status = NavMeshGeneratorTask3D::TaskStatus::BAKING_STARTED;
+ };
+
+ static HashMap<WorkerThreadPool::TaskID, NavMeshGeneratorTask3D *> generator_tasks;
+
+ static void generator_thread_bake(void *p_arg);
static HashSet<Ref<NavigationMesh>> baking_navmeshes;
@@ -66,11 +92,13 @@ class NavMeshGenerator3D : public Object {
public:
static NavMeshGenerator3D *get_singleton();
+ static void sync();
static void cleanup();
static void finish();
- static void parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable());
- static void bake_from_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback = Callable());
+ static void parse_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable());
+ static void bake_from_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, const Callable &p_callback = Callable());
+ static void bake_from_source_geometry_data_async(Ref<NavigationMesh> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, const Callable &p_callback = Callable());
NavMeshGenerator3D();
~NavMeshGenerator3D();
diff --git a/modules/noise/doc_classes/FastNoiseLite.xml b/modules/noise/doc_classes/FastNoiseLite.xml
index 4c6cdfbf12..f2a6c60376 100644
--- a/modules/noise/doc_classes/FastNoiseLite.xml
+++ b/modules/noise/doc_classes/FastNoiseLite.xml
@@ -5,7 +5,7 @@
</brief_description>
<description>
This class generates noise using the FastNoiseLite library, which is a collection of several noise algorithms including Cellular, Perlin, Value, and more.
- Most generated noise values are in the range of [code][-1,1][/code], however not always. Some of the cellular noise algorithms return results above [code]1[/code].
+ Most generated noise values are in the range of [code][-1, 1][/code], but not always. Some of the cellular noise algorithms return results above [code]1[/code].
</description>
<tutorials>
</tutorials>
@@ -13,7 +13,7 @@
<member name="cellular_distance_function" type="int" setter="set_cellular_distance_function" getter="get_cellular_distance_function" enum="FastNoiseLite.CellularDistanceFunction" default="0">
Determines how the distance to the nearest/second-nearest point is computed. See [enum CellularDistanceFunction] for options.
</member>
- <member name="cellular_jitter" type="float" setter="set_cellular_jitter" getter="get_cellular_jitter" default="0.45">
+ <member name="cellular_jitter" type="float" setter="set_cellular_jitter" getter="get_cellular_jitter" default="1.0">
Maximum distance a point can move off of its grid position. Set to [code]0[/code] for an even grid.
</member>
<member name="cellular_return_type" type="int" setter="set_cellular_return_type" getter="get_cellular_return_type" enum="FastNoiseLite.CellularReturnType" default="1">
diff --git a/modules/noise/doc_classes/Noise.xml b/modules/noise/doc_classes/Noise.xml
index dd232af1cc..7d74c84f93 100644
--- a/modules/noise/doc_classes/Noise.xml
+++ b/modules/noise/doc_classes/Noise.xml
@@ -5,7 +5,7 @@
</brief_description>
<description>
This class defines the interface for noise generation libraries to inherit from.
- A default get_seamless_noise() implementation is provided for libraries that do not provide seamless noise. This function requests a larger image from get_image(), reverses the quadrants of the image, then uses the strips of extra width to blend over the seams.
+ A default [method get_seamless_image] implementation is provided for libraries that do not provide seamless noise. This function requests a larger image from the [method get_image] method, reverses the quadrants of the image, then uses the strips of extra width to blend over the seams.
Inheriting noise classes can optionally override this function to provide a more optimal algorithm.
</description>
<tutorials>
diff --git a/modules/noise/doc_classes/NoiseTexture2D.xml b/modules/noise/doc_classes/NoiseTexture2D.xml
index e25af794d4..3f4afc5141 100644
--- a/modules/noise/doc_classes/NoiseTexture2D.xml
+++ b/modules/noise/doc_classes/NoiseTexture2D.xml
@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="NoiseTexture2D" inherits="Texture2D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
- A texture filled with noise generated by a [Noise] object.
+ A 2D texture filled with noise generated by a [Noise] object.
</brief_description>
<description>
- Uses [FastNoiseLite] or other libraries to fill the texture data of your desired size. [NoiseTexture2D] can also generate normal map textures.
+ Uses the [FastNoiseLite] library or other noise generators to fill the texture data of your desired size. [NoiseTexture2D] can also generate normal map textures.
The class uses [Thread]s to generate the texture data internally, so [method Texture2D.get_image] may return [code]null[/code] if the generation process has not completed yet. In that case, you need to wait for the texture to be generated before accessing the image and the generated byte data:
[codeblock]
var texture = NoiseTexture2D.new()
diff --git a/modules/noise/doc_classes/NoiseTexture3D.xml b/modules/noise/doc_classes/NoiseTexture3D.xml
index 0ada6942ad..e8e205bc68 100644
--- a/modules/noise/doc_classes/NoiseTexture3D.xml
+++ b/modules/noise/doc_classes/NoiseTexture3D.xml
@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="NoiseTexture3D" inherits="Texture3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
- A texture filled with noise generated by a [Noise] object.
+ A 3D texture filled with noise generated by a [Noise] object.
</brief_description>
<description>
- Uses [FastNoiseLite] or other libraries to fill the texture data of your desired size.
+ Uses the [FastNoiseLite] library or other noise generators to fill the texture data of your desired size.
The class uses [Thread]s to generate the texture data internally, so [method Texture3D.get_data] may return [code]null[/code] if the generation process has not completed yet. In that case, you need to wait for the texture to be generated before accessing the image:
[codeblock]
var texture = NoiseTexture3D.new()
diff --git a/modules/noise/fastnoise_lite.h b/modules/noise/fastnoise_lite.h
index c63f7d7d29..06a2b39abe 100644
--- a/modules/noise/fastnoise_lite.h
+++ b/modules/noise/fastnoise_lite.h
@@ -116,7 +116,7 @@ private:
// Cellular specific.
CellularDistanceFunction cellular_distance_function = DISTANCE_EUCLIDEAN;
CellularReturnType cellular_return_type = RETURN_DISTANCE;
- real_t cellular_jitter = 0.45;
+ real_t cellular_jitter = 1.0;
// Domain warp specific.
bool domain_warp_enabled = false;
diff --git a/modules/openxr/SCsub b/modules/openxr/SCsub
index f49dc390de..1bd10f1009 100644
--- a/modules/openxr/SCsub
+++ b/modules/openxr/SCsub
@@ -52,10 +52,11 @@ if env["builtin_openxr"]:
env_thirdparty = env_openxr.Clone()
env_thirdparty.disable_warnings()
+
env_thirdparty.AppendUnique(CPPDEFINES=["DISABLE_STD_FILESYSTEM"])
+ if env["disable_exceptions"]:
+ env_thirdparty.AppendUnique(CPPDEFINES=["XRLOADER_DISABLE_EXCEPTION_HANDLING", ("JSON_USE_EXCEPTION", 0)])
- if "-fno-exceptions" in env_thirdparty["CXXFLAGS"]:
- env_thirdparty["CXXFLAGS"].remove("-fno-exceptions")
env_thirdparty.Append(CPPPATH=[thirdparty_dir + "/src/loader"])
# add in external jsoncpp dependency
diff --git a/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp b/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp
index 81ba9c56b8..4829f713d2 100644
--- a/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp
+++ b/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp
@@ -126,7 +126,7 @@ void OpenXRExtensionWrapperExtension::on_before_instance_created() {
}
void OpenXRExtensionWrapperExtension::on_instance_created(const XrInstance p_instance) {
- uint64_t instance = reinterpret_cast<uint64_t>(p_instance);
+ uint64_t instance = (uint64_t)p_instance;
GDVIRTUAL_CALL(_on_instance_created, instance);
}
@@ -135,7 +135,7 @@ void OpenXRExtensionWrapperExtension::on_instance_destroyed() {
}
void OpenXRExtensionWrapperExtension::on_session_created(const XrSession p_session) {
- uint64_t session = reinterpret_cast<uint64_t>(p_session);
+ uint64_t session = (uint64_t)p_session;
GDVIRTUAL_CALL(_on_session_created, session);
}
diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp
index 9885190cb1..2bd5376b21 100644
--- a/modules/openxr/openxr_api.cpp
+++ b/modules/openxr/openxr_api.cpp
@@ -2274,33 +2274,42 @@ String OpenXRAPI::action_set_get_name(RID p_action_set) {
return action_set->name;
}
-bool OpenXRAPI::action_set_attach(RID p_action_set) {
- ActionSet *action_set = action_set_owner.get_or_null(p_action_set);
- ERR_FAIL_NULL_V(action_set, false);
+bool OpenXRAPI::attach_action_sets(const Vector<RID> &p_action_sets) {
+ ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false);
- if (action_set->is_attached) {
- // already attached
- return true;
- }
+ Vector<XrActionSet> action_handles;
+ action_handles.resize(p_action_sets.size());
+ for (int i = 0; i < p_action_sets.size(); i++) {
+ ActionSet *action_set = action_set_owner.get_or_null(p_action_sets[i]);
+ ERR_FAIL_NULL_V(action_set, false);
- ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false);
+ if (action_set->is_attached) {
+ return false;
+ }
+
+ action_handles.set(i, action_set->handle);
+ }
// So according to the docs, once we attach our action set to our session it becomes read only..
// https://www.khronos.org/registry/OpenXR/specs/1.0/man/html/xrAttachSessionActionSets.html
XrSessionActionSetsAttachInfo attach_info = {
XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO, // type
nullptr, // next
- 1, // countActionSets,
- &action_set->handle // actionSets
+ (uint32_t)p_action_sets.size(), // countActionSets,
+ action_handles.ptr() // actionSets
};
XrResult result = xrAttachSessionActionSets(session, &attach_info);
if (XR_FAILED(result)) {
- print_line("OpenXR: failed to attach action set! [", get_error_string(result), "]");
+ print_line("OpenXR: failed to attach action sets! [", get_error_string(result), "]");
return false;
}
- action_set->is_attached = true;
+ for (int i = 0; i < p_action_sets.size(); i++) {
+ ActionSet *action_set = action_set_owner.get_or_null(p_action_sets[i]);
+ ERR_FAIL_NULL_V(action_set, false);
+ action_set->is_attached = true;
+ }
/* For debugging:
print_verbose("Attached set " + action_set->name);
diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h
index 9374cb7afa..2498cd1eb4 100644
--- a/modules/openxr/openxr_api.h
+++ b/modules/openxr/openxr_api.h
@@ -380,7 +380,7 @@ public:
RID action_set_create(const String p_name, const String p_localized_name, const int p_priority);
String action_set_get_name(RID p_action_set);
- bool action_set_attach(RID p_action_set);
+ bool attach_action_sets(const Vector<RID> &p_action_sets);
void action_set_free(RID p_action_set);
RID action_create(RID p_action_set, const String p_name, const String p_localized_name, OpenXRAction::ActionType p_action_type, const Vector<RID> &p_trackers);
diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp
index 933148da87..cc2b4fa11b 100644
--- a/modules/openxr/openxr_interface.cpp
+++ b/modules/openxr/openxr_interface.cpp
@@ -555,9 +555,11 @@ bool OpenXRInterface::initialize() {
xr_server->add_tracker(head);
// attach action sets
+ Vector<RID> loaded_action_sets;
for (int i = 0; i < action_sets.size(); i++) {
- openxr_api->action_set_attach(action_sets[i]->action_set_rid);
+ loaded_action_sets.append(action_sets[i]->action_set_rid);
}
+ openxr_api->attach_action_sets(loaded_action_sets);
// make this our primary interface
xr_server->set_primary_interface(this);
diff --git a/modules/svg/SCsub b/modules/svg/SCsub
index ae3e1bdedb..c4d7671fb3 100644
--- a/modules/svg/SCsub
+++ b/modules/svg/SCsub
@@ -38,9 +38,6 @@ thirdparty_sources = [
"src/lib/tvgShape.cpp",
"src/lib/tvgSwCanvas.cpp",
"src/lib/tvgTaskScheduler.cpp",
- "src/loaders/external_png/tvgPngLoader.cpp",
- "src/loaders/jpg/tvgJpgd.cpp",
- "src/loaders/jpg/tvgJpgLoader.cpp",
"src/loaders/raw/tvgRawLoader.cpp",
"src/loaders/svg/tvgSvgCssStyle.cpp",
"src/loaders/svg/tvgSvgLoader.cpp",
@@ -48,27 +45,23 @@ thirdparty_sources = [
"src/loaders/svg/tvgSvgSceneBuilder.cpp",
"src/loaders/svg/tvgSvgUtil.cpp",
"src/loaders/svg/tvgXmlParser.cpp",
- "src/loaders/tvg/tvgTvgBinInterpreter.cpp",
- "src/loaders/tvg/tvgTvgLoader.cpp",
- "src/savers/tvg/tvgTvgSaver.cpp",
]
thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources]
env_svg.Prepend(CPPPATH=[thirdparty_dir + "inc"])
+# Enable ThorVG static object linking.
+env_svg.Append(CPPDEFINES=["TVG_STATIC"])
+
env_thirdparty = env_svg.Clone()
env_thirdparty.disable_warnings()
env_thirdparty.Prepend(
CPPPATH=[
thirdparty_dir + "src/lib",
thirdparty_dir + "src/lib/sw_engine",
- thirdparty_dir + "src/loaders/external_png",
- thirdparty_dir + "src/loaders/jpg",
thirdparty_dir + "src/loaders/raw",
thirdparty_dir + "src/loaders/svg",
- thirdparty_dir + "src/loaders/tvg",
- thirdparty_dir + "src/savers/tvg",
]
)
# Also requires libpng headers
diff --git a/modules/svg/image_loader_svg.cpp b/modules/svg/image_loader_svg.cpp
index 7639155914..a542bbc234 100644
--- a/modules/svg/image_loader_svg.cpp
+++ b/modules/svg/image_loader_svg.cpp
@@ -107,7 +107,7 @@ Error ImageLoaderSVG::create_image_from_utf8_buffer(Ref<Image> p_image, const ui
// Note: memalloc here, be sure to memfree before any return.
uint32_t *buffer = (uint32_t *)memalloc(sizeof(uint32_t) * width * height);
- tvg::Result res = sw_canvas->target(buffer, width, width, height, tvg::SwCanvas::ARGB8888_STRAIGHT);
+ tvg::Result res = sw_canvas->target(buffer, width, width, height, tvg::SwCanvas::ARGB8888S);
if (res != tvg::Result::Success) {
memfree(buffer);
ERR_FAIL_V_MSG(FAILED, "ImageLoaderSVG: Couldn't set target on ThorVG canvas.");
diff --git a/modules/text_server_adv/SCsub b/modules/text_server_adv/SCsub
index bf828cf77a..360741363a 100644
--- a/modules/text_server_adv/SCsub
+++ b/modules/text_server_adv/SCsub
@@ -40,6 +40,8 @@ msdfgen_enabled = "msdfgen" in env.module_list
if "svg" in env.module_list:
env_text_server_adv.Prepend(CPPPATH=["#thirdparty/thorvg/inc", "#thirdparty/thorvg/src/lib"])
+ # Enable ThorVG static object linking.
+ env_text_server_adv.Append(CPPDEFINES=["TVG_STATIC"])
if env["builtin_harfbuzz"]:
env_harfbuzz = env_modules.Clone()
diff --git a/modules/text_server_adv/gdextension_build/SConstruct b/modules/text_server_adv/gdextension_build/SConstruct
index 4b12a4e41d..38fd5f6403 100644
--- a/modules/text_server_adv/gdextension_build/SConstruct
+++ b/modules/text_server_adv/gdextension_build/SConstruct
@@ -69,9 +69,6 @@ if env["thorvg_enabled"] and env["freetype_enabled"]:
"src/lib/tvgShape.cpp",
"src/lib/tvgSwCanvas.cpp",
"src/lib/tvgTaskScheduler.cpp",
- "src/loaders/external_png/tvgPngLoader.cpp",
- "src/loaders/jpg/tvgJpgd.cpp",
- "src/loaders/jpg/tvgJpgLoader.cpp",
"src/loaders/raw/tvgRawLoader.cpp",
"src/loaders/svg/tvgSvgCssStyle.cpp",
"src/loaders/svg/tvgSvgLoader.cpp",
@@ -79,9 +76,6 @@ if env["thorvg_enabled"] and env["freetype_enabled"]:
"src/loaders/svg/tvgSvgSceneBuilder.cpp",
"src/loaders/svg/tvgSvgUtil.cpp",
"src/loaders/svg/tvgXmlParser.cpp",
- "src/loaders/tvg/tvgTvgBinInterpreter.cpp",
- "src/loaders/tvg/tvgTvgLoader.cpp",
- "src/savers/tvg/tvgTvgSaver.cpp",
]
thirdparty_tvg_sources = [thirdparty_tvg_dir + file for file in thirdparty_tvg_sources]
@@ -90,15 +84,15 @@ if env["thorvg_enabled"] and env["freetype_enabled"]:
"../../../thirdparty/thorvg/inc",
"../../../thirdparty/thorvg/src/lib",
"../../../thirdparty/thorvg/src/lib/sw_engine",
- "../../../thirdparty/thorvg/src/loaders/external_png",
- "../../../thirdparty/thorvg/src/loaders/jpg",
"../../../thirdparty/thorvg/src/loaders/raw",
"../../../thirdparty/thorvg/src/loaders/svg",
- "../../../thirdparty/thorvg/src/loaders/tvg",
- "../../../thirdparty/thorvg/src/savers/tvg",
"../../../thirdparty/libpng",
]
)
+
+ # Enable ThorVG static object linking.
+ env_tvg.Append(CPPDEFINES=["TVG_STATIC"])
+
env.Append(CPPPATH=["../../../thirdparty/thorvg/inc", "../../../thirdparty/thorvg/src/lib"])
env.Append(CPPDEFINES=["MODULE_SVG_ENABLED"])
diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp
index 043a33ab35..804a16423d 100644
--- a/modules/text_server_adv/text_server_adv.cpp
+++ b/modules/text_server_adv/text_server_adv.cpp
@@ -2113,12 +2113,10 @@ Dictionary TextServerAdvanced::_font_get_ot_name_strings(const RID &p_font_rid)
name = vformat("unknown_%d", names[i].name_id);
} break;
}
+ String text;
unsigned int text_size = hb_ot_name_get_utf32(hb_face, names[i].name_id, names[i].language, nullptr, nullptr) + 1;
- // @todo After godot-cpp#1141 is fixed, use text.resize() and write directly to text.wptr() instead of using a temporary buffer.
- char32_t *buffer = memnew_arr(char32_t, text_size);
- hb_ot_name_get_utf32(hb_face, names[i].name_id, names[i].language, &text_size, (uint32_t *)buffer);
- String text(buffer);
- memdelete_arr(buffer);
+ text.resize(text_size);
+ hb_ot_name_get_utf32(hb_face, names[i].name_id, names[i].language, &text_size, (uint32_t *)text.ptrw());
if (!text.is_empty()) {
Dictionary &id_string = names_for_lang[String(hb_language_to_string(names[i].language))];
id_string[name] = text;
@@ -3782,6 +3780,7 @@ void TextServerAdvanced::invalidate(TextServerAdvanced::ShapedTextDataAdvanced *
p_shaped->script_iter = nullptr;
}
p_shaped->break_ops_valid = false;
+ p_shaped->chars_valid = false;
p_shaped->js_ops_valid = false;
}
}
@@ -4835,6 +4834,75 @@ int64_t TextServerAdvanced::_shaped_text_get_ellipsis_glyph_count(const RID &p_s
return sd->overrun_trim_data.ellipsis_glyph_buf.size();
}
+void TextServerAdvanced::_update_chars(ShapedTextDataAdvanced *p_sd) const {
+ if (!p_sd->chars_valid) {
+ p_sd->chars.clear();
+
+ const UChar *data = p_sd->utf16.get_data();
+ UErrorCode err = U_ZERO_ERROR;
+ int prev = -1;
+ int i = 0;
+
+ Vector<ShapedTextDataAdvanced::Span> &spans = p_sd->spans;
+ if (p_sd->parent != RID()) {
+ ShapedTextDataAdvanced *parent_sd = shaped_owner.get_or_null(p_sd->parent);
+ ERR_FAIL_COND(!parent_sd->valid);
+ spans = parent_sd->spans;
+ }
+
+ while (i < spans.size()) {
+ if (spans[i].start > p_sd->end) {
+ break;
+ }
+ if (spans[i].end < p_sd->start) {
+ i++;
+ continue;
+ }
+
+ int r_start = MAX(0, spans[i].start - p_sd->start);
+ String language = spans[i].language;
+ while (i + 1 < spans.size() && language == spans[i + 1].language) {
+ i++;
+ }
+ int r_end = MIN(spans[i].end - p_sd->start, p_sd->text.length());
+ UBreakIterator *bi = ubrk_open(UBRK_CHARACTER, (language.is_empty()) ? TranslationServer::get_singleton()->get_tool_locale().ascii().get_data() : language.ascii().get_data(), data + _convert_pos_inv(p_sd, r_start), _convert_pos_inv(p_sd, r_end - r_start), &err);
+ if (U_SUCCESS(err)) {
+ while (ubrk_next(bi) != UBRK_DONE) {
+ int pos = _convert_pos(p_sd, ubrk_current(bi)) + r_start + p_sd->start;
+ if (prev != pos) {
+ p_sd->chars.push_back(pos);
+ }
+ prev = pos;
+ }
+ ubrk_close(bi);
+ } else {
+ for (int j = r_start; j < r_end; j++) {
+ if (prev != j) {
+ p_sd->chars.push_back(j + 1 + p_sd->start);
+ }
+ prev = j;
+ }
+ }
+ i++;
+ }
+ p_sd->chars_valid = true;
+ }
+}
+
+PackedInt32Array TextServerAdvanced::_shaped_text_get_character_breaks(const RID &p_shaped) const {
+ ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
+ ERR_FAIL_COND_V(!sd, PackedInt32Array());
+
+ MutexLock lock(sd->mutex);
+ if (!sd->valid) {
+ const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
+ }
+
+ _update_chars(sd);
+
+ return sd->chars;
+}
+
bool TextServerAdvanced::_shaped_text_update_breaks(const RID &p_shaped) {
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, false);
@@ -5338,7 +5406,17 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star
// Try system fallback.
RID fdef = p_fonts[0];
if (_font_is_allow_system_fallback(fdef)) {
- String text = p_sd->text.substr(p_start, 1);
+ _update_chars(p_sd);
+
+ int64_t next = p_end;
+ for (const int32_t &E : p_sd->chars) {
+ if (E > p_start) {
+ next = E;
+ break;
+ }
+ }
+ String text = p_sd->text.substr(p_start, next - p_start);
+
String font_name = _font_get_name(fdef);
BitField<FontStyle> font_style = _font_get_style(fdef);
int font_weight = _font_get_weight(fdef);
@@ -6602,6 +6680,30 @@ PackedInt32Array TextServerAdvanced::_string_get_word_breaks(const String &p_str
return ret;
}
+PackedInt32Array TextServerAdvanced::_string_get_character_breaks(const String &p_string, const String &p_language) const {
+ const String lang = (p_language.is_empty()) ? TranslationServer::get_singleton()->get_tool_locale() : p_language;
+ // Convert to UTF-16.
+ Char16String utf16 = p_string.utf16();
+
+ PackedInt32Array ret;
+
+ UErrorCode err = U_ZERO_ERROR;
+ UBreakIterator *bi = ubrk_open(UBRK_CHARACTER, lang.ascii().get_data(), (const UChar *)utf16.get_data(), utf16.length(), &err);
+ if (U_SUCCESS(err)) {
+ while (ubrk_next(bi) != UBRK_DONE) {
+ int pos = _convert_pos(p_string, utf16, ubrk_current(bi));
+ ret.push_back(pos);
+ }
+ ubrk_close(bi);
+ } else {
+ for (int i = 0; i <= p_string.size(); i++) {
+ ret.push_back(i);
+ }
+ }
+
+ return ret;
+}
+
bool TextServerAdvanced::_is_valid_identifier(const String &p_string) const {
#ifndef ICU_STATIC_DATA
if (!icu_data_loaded) {
diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h
index 44700e045b..f27fa1dee9 100644
--- a/modules/text_server_adv/text_server_adv.h
+++ b/modules/text_server_adv/text_server_adv.h
@@ -509,9 +509,11 @@ class TextServerAdvanced : public TextServerExtension {
HashMap<int, bool> jstops;
HashMap<int, bool> breaks;
+ PackedInt32Array chars;
int break_inserts = 0;
bool break_ops_valid = false;
bool js_ops_valid = false;
+ bool chars_valid = false;
~ShapedTextDataAdvanced() {
for (int i = 0; i < bidi_iter.size(); i++) {
@@ -609,6 +611,7 @@ class TextServerAdvanced : public TextServerExtension {
mutable HashMap<SystemFontKey, SystemFontCache, SystemFontKeyHasher> system_fonts;
mutable HashMap<String, PackedByteArray> system_font_data;
+ void _update_chars(ShapedTextDataAdvanced *p_sd) const;
void _realign(ShapedTextDataAdvanced *p_sd) const;
int64_t _convert_pos(const String &p_utf32, const Char16String &p_utf16, int64_t p_pos) const;
int64_t _convert_pos(const ShapedTextDataAdvanced *p_sd, int64_t p_pos) const;
@@ -920,11 +923,14 @@ public:
MODBIND1RC(double, shaped_text_get_underline_position, const RID &);
MODBIND1RC(double, shaped_text_get_underline_thickness, const RID &);
+ MODBIND1RC(PackedInt32Array, shaped_text_get_character_breaks, const RID &);
+
MODBIND2RC(String, format_number, const String &, const String &);
MODBIND2RC(String, parse_number, const String &, const String &);
MODBIND1RC(String, percent_sign, const String &);
MODBIND3RC(PackedInt32Array, string_get_word_breaks, const String &, const String &, int64_t);
+ MODBIND2RC(PackedInt32Array, string_get_character_breaks, const String &, const String &);
MODBIND2RC(int64_t, is_confusable, const String &, const PackedStringArray &);
MODBIND1RC(bool, spoof_check, const String &);
diff --git a/modules/text_server_adv/thorvg_svg_in_ot.cpp b/modules/text_server_adv/thorvg_svg_in_ot.cpp
index 2f48f1564c..97952a1d96 100644
--- a/modules/text_server_adv/thorvg_svg_in_ot.cpp
+++ b/modules/text_server_adv/thorvg_svg_in_ot.cpp
@@ -155,21 +155,10 @@ FT_Error tvg_svg_in_ot_preset_slot(FT_GlyphSlot p_slot, FT_Bool p_cache, FT_Poin
}
String xml_code_str = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"" + rtos(min_x) + " " + rtos(min_y) + " " + rtos(new_w) + " " + rtos(new_h) + "\">" + xml_body;
-#ifndef GDEXTENSION
gl_state.xml_code = xml_code_str.utf8();
-#else
- CharString xml_code = xml_code_str.utf8();
- gl_state.xml_code_length = xml_code.length();
- gl_state.xml_code = memnew_arr(char, gl_state.xml_code_length);
- memcpy(gl_state.xml_code, xml_code.get_data(), gl_state.xml_code_length);
-#endif
picture = tvg::Picture::gen();
-#ifndef GDEXTENSION
result = picture->load(gl_state.xml_code.get_data(), gl_state.xml_code.length(), "svg+xml", false);
-#else
- result = picture->load(gl_state.xml_code, gl_state.xml_code_length, "svg+xml", false);
-#endif
if (result != tvg::Result::Success) {
ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to load SVG document (glyph metrics).");
}
@@ -257,11 +246,7 @@ FT_Error tvg_svg_in_ot_render(FT_GlyphSlot p_slot, FT_Pointer *p_state) {
ERR_FAIL_COND_V_MSG(!gl_state.ready, FT_Err_Invalid_SVG_Document, "SVG glyph not ready.");
std::unique_ptr<tvg::Picture> picture = tvg::Picture::gen();
-#ifndef GDEXTENSION
tvg::Result res = picture->load(gl_state.xml_code.get_data(), gl_state.xml_code.length(), "svg+xml", false);
-#else
- tvg::Result res = picture->load(gl_state.xml_code, gl_state.xml_code_length, "svg+xml", false);
-#endif
if (res != tvg::Result::Success) {
ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to load SVG document (glyph rendering).");
}
@@ -271,7 +256,7 @@ FT_Error tvg_svg_in_ot_render(FT_GlyphSlot p_slot, FT_Pointer *p_state) {
}
std::unique_ptr<tvg::SwCanvas> sw_canvas = tvg::SwCanvas::gen();
- res = sw_canvas->target((uint32_t *)p_slot->bitmap.buffer, (int)p_slot->bitmap.width, (int)p_slot->bitmap.width, (int)p_slot->bitmap.rows, tvg::SwCanvas::ARGB8888_STRAIGHT);
+ res = sw_canvas->target((uint32_t *)p_slot->bitmap.buffer, (int)p_slot->bitmap.width, (int)p_slot->bitmap.width, (int)p_slot->bitmap.rows, tvg::SwCanvas::ARGB8888S);
if (res != tvg::Result::Success) {
ERR_FAIL_V_MSG(FT_Err_Invalid_Outline, "Failed to create SVG canvas.");
}
diff --git a/modules/text_server_adv/thorvg_svg_in_ot.h b/modules/text_server_adv/thorvg_svg_in_ot.h
index 5e79c8e444..034fffb5e6 100644
--- a/modules/text_server_adv/thorvg_svg_in_ot.h
+++ b/modules/text_server_adv/thorvg_svg_in_ot.h
@@ -66,22 +66,8 @@ struct GL_State {
float y = 0;
float w = 0;
float h = 0;
-#ifndef GDEXTENSION
CharString xml_code;
-#else
- // @todo After godot-cpp#1142 is fixed, use CharString just like when compiled as a Godot module.
- char *xml_code = nullptr;
- int xml_code_length = 0;
-#endif
tvg::Matrix m;
-
-#ifdef GDEXTENSION
- ~GL_State() {
- if (xml_code) {
- memdelete_arr(xml_code);
- }
- }
-#endif
};
struct TVG_State {
diff --git a/modules/text_server_fb/SCsub b/modules/text_server_fb/SCsub
index 582e622147..0da2a54bc2 100644
--- a/modules/text_server_fb/SCsub
+++ b/modules/text_server_fb/SCsub
@@ -10,6 +10,8 @@ env_text_server_fb = env_modules.Clone()
if "svg" in env.module_list:
env_text_server_fb.Prepend(CPPPATH=["#thirdparty/thorvg/inc", "#thirdparty/thorvg/src/lib"])
+ # Enable ThorVG static object linking.
+ env_text_server_fb.Append(CPPDEFINES=["TVG_STATIC"])
if env["builtin_msdfgen"] and msdfgen_enabled:
# Treat msdfgen headers as system headers to avoid raising warnings. Not supported on MSVC.
diff --git a/modules/text_server_fb/gdextension_build/SConstruct b/modules/text_server_fb/gdextension_build/SConstruct
index fcf8976019..20e1afa2e5 100644
--- a/modules/text_server_fb/gdextension_build/SConstruct
+++ b/modules/text_server_fb/gdextension_build/SConstruct
@@ -64,9 +64,6 @@ if env["thorvg_enabled"] and env["freetype_enabled"]:
"src/lib/tvgShape.cpp",
"src/lib/tvgSwCanvas.cpp",
"src/lib/tvgTaskScheduler.cpp",
- "src/loaders/external_png/tvgPngLoader.cpp",
- "src/loaders/jpg/tvgJpgd.cpp",
- "src/loaders/jpg/tvgJpgLoader.cpp",
"src/loaders/raw/tvgRawLoader.cpp",
"src/loaders/svg/tvgSvgCssStyle.cpp",
"src/loaders/svg/tvgSvgLoader.cpp",
@@ -74,9 +71,6 @@ if env["thorvg_enabled"] and env["freetype_enabled"]:
"src/loaders/svg/tvgSvgSceneBuilder.cpp",
"src/loaders/svg/tvgSvgUtil.cpp",
"src/loaders/svg/tvgXmlParser.cpp",
- "src/loaders/tvg/tvgTvgBinInterpreter.cpp",
- "src/loaders/tvg/tvgTvgLoader.cpp",
- "src/savers/tvg/tvgTvgSaver.cpp",
]
thirdparty_tvg_sources = [thirdparty_tvg_dir + file for file in thirdparty_tvg_sources]
@@ -85,15 +79,15 @@ if env["thorvg_enabled"] and env["freetype_enabled"]:
"../../../thirdparty/thorvg/inc",
"../../../thirdparty/thorvg/src/lib",
"../../../thirdparty/thorvg/src/lib/sw_engine",
- "../../../thirdparty/thorvg/src/loaders/external_png",
- "../../../thirdparty/thorvg/src/loaders/jpg",
"../../../thirdparty/thorvg/src/loaders/raw",
"../../../thirdparty/thorvg/src/loaders/svg",
- "../../../thirdparty/thorvg/src/loaders/tvg",
- "../../../thirdparty/thorvg/src/savers/tvg",
"../../../thirdparty/libpng",
]
)
+
+ # Enable ThorVG static object linking.
+ env_tvg.Append(CPPDEFINES=["TVG_STATIC"])
+
env.Append(CPPPATH=["../../../thirdparty/thorvg/inc", "../../../thirdparty/thorvg/src/lib"])
env.Append(CPPDEFINES=["MODULE_SVG_ENABLED"])
diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp
index 70da5e2782..4976c70b3b 100644
--- a/modules/text_server_fb/text_server_fb.cpp
+++ b/modules/text_server_fb/text_server_fb.cpp
@@ -4079,6 +4079,26 @@ double TextServerFallback::_shaped_text_get_underline_thickness(const RID &p_sha
return sd->uthk;
}
+PackedInt32Array TextServerFallback::_shaped_text_get_character_breaks(const RID &p_shaped) const {
+ const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
+ ERR_FAIL_COND_V(!sd, PackedInt32Array());
+
+ MutexLock lock(sd->mutex);
+ if (!sd->valid) {
+ const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
+ }
+
+ PackedInt32Array ret;
+ int size = sd->end - sd->start;
+ if (size > 0) {
+ ret.resize(size);
+ for (int i = 0; i < size; i++) {
+ ret.write[i] = i + 1 + sd->start;
+ }
+ }
+ return ret;
+}
+
String TextServerFallback::_string_to_upper(const String &p_string, const String &p_language) const {
return p_string.to_upper();
}
diff --git a/modules/text_server_fb/text_server_fb.h b/modules/text_server_fb/text_server_fb.h
index 54311caaf9..457573ecb3 100644
--- a/modules/text_server_fb/text_server_fb.h
+++ b/modules/text_server_fb/text_server_fb.h
@@ -788,6 +788,8 @@ public:
MODBIND1RC(double, shaped_text_get_underline_position, const RID &);
MODBIND1RC(double, shaped_text_get_underline_thickness, const RID &);
+ MODBIND1RC(PackedInt32Array, shaped_text_get_character_breaks, const RID &);
+
MODBIND3RC(PackedInt32Array, string_get_word_breaks, const String &, const String &, int64_t);
MODBIND2RC(String, string_to_upper, const String &, const String &);
diff --git a/modules/text_server_fb/thorvg_svg_in_ot.cpp b/modules/text_server_fb/thorvg_svg_in_ot.cpp
index 773b103c01..785d6dbe4d 100644
--- a/modules/text_server_fb/thorvg_svg_in_ot.cpp
+++ b/modules/text_server_fb/thorvg_svg_in_ot.cpp
@@ -256,7 +256,7 @@ FT_Error tvg_svg_in_ot_render(FT_GlyphSlot p_slot, FT_Pointer *p_state) {
}
std::unique_ptr<tvg::SwCanvas> sw_canvas = tvg::SwCanvas::gen();
- res = sw_canvas->target((uint32_t *)p_slot->bitmap.buffer, (int)p_slot->bitmap.width, (int)p_slot->bitmap.width, (int)p_slot->bitmap.rows, tvg::SwCanvas::ARGB8888_STRAIGHT);
+ res = sw_canvas->target((uint32_t *)p_slot->bitmap.buffer, (int)p_slot->bitmap.width, (int)p_slot->bitmap.width, (int)p_slot->bitmap.rows, tvg::SwCanvas::ARGB8888S);
if (res != tvg::Result::Success) {
ERR_FAIL_V_MSG(FT_Err_Invalid_Outline, "Failed to create SVG canvas.");
}
diff --git a/modules/vorbis/doc_classes/ResourceImporterOggVorbis.xml b/modules/vorbis/doc_classes/ResourceImporterOggVorbis.xml
index c2dcb832e0..dd6c181eae 100644
--- a/modules/vorbis/doc_classes/ResourceImporterOggVorbis.xml
+++ b/modules/vorbis/doc_classes/ResourceImporterOggVorbis.xml
@@ -9,7 +9,7 @@
Ogg Vorbis requires more CPU to decode than [ResourceImporterWAV]. If you need to play a lot of simultaneous sounds, it's recommended to use WAV for those sounds instead, especially if targeting low-end devices.
</description>
<tutorials>
- <link title="Importing audio samples">https://docs.godotengine.org/en/latest/tutorials/assets_pipeline/importing_audio_samples.html</link>
+ <link title="Importing audio samples">$DOCS_URL/tutorials/assets_pipeline/importing_audio_samples.html</link>
</tutorials>
<methods>
<method name="load_from_buffer" qualifiers="static">
diff --git a/modules/zip/doc_classes/ZIPReader.xml b/modules/zip/doc_classes/ZIPReader.xml
index 0fa2672044..bf596806b2 100644
--- a/modules/zip/doc_classes/ZIPReader.xml
+++ b/modules/zip/doc_classes/ZIPReader.xml
@@ -25,6 +25,15 @@
Closes the underlying resources used by this instance.
</description>
</method>
+ <method name="file_exists">
+ <return type="bool" />
+ <param index="0" name="path" type="String" />
+ <param index="1" name="case_sensitive" type="bool" default="true" />
+ <description>
+ Returns [code]true[/code] if the file exists in the loaded zip archive.
+ Must be called after [method open].
+ </description>
+ </method>
<method name="get_files">
<return type="PackedStringArray" />
<description>
diff --git a/modules/zip/zip_reader.cpp b/modules/zip/zip_reader.cpp
index 2684875e1c..5752b829ef 100644
--- a/modules/zip/zip_reader.cpp
+++ b/modules/zip/zip_reader.cpp
@@ -118,6 +118,21 @@ PackedByteArray ZIPReader::read_file(String p_path, bool p_case_sensitive) {
return data;
}
+bool ZIPReader::file_exists(String p_path, bool p_case_sensitive) {
+ ERR_FAIL_COND_V_MSG(fa.is_null(), false, "ZIPReader must be opened before use.");
+
+ int cs = p_case_sensitive ? 1 : 2;
+ if (unzLocateFile(uzf, p_path.utf8().get_data(), cs) != UNZ_OK) {
+ return false;
+ }
+ if (unzOpenCurrentFile(uzf) != UNZ_OK) {
+ return false;
+ }
+
+ unzCloseCurrentFile(uzf);
+ return true;
+}
+
ZIPReader::ZIPReader() {}
ZIPReader::~ZIPReader() {
@@ -131,4 +146,5 @@ void ZIPReader::_bind_methods() {
ClassDB::bind_method(D_METHOD("close"), &ZIPReader::close);
ClassDB::bind_method(D_METHOD("get_files"), &ZIPReader::get_files);
ClassDB::bind_method(D_METHOD("read_file", "path", "case_sensitive"), &ZIPReader::read_file, DEFVAL(Variant(true)));
+ ClassDB::bind_method(D_METHOD("file_exists", "path", "case_sensitive"), &ZIPReader::file_exists, DEFVAL(Variant(true)));
}
diff --git a/modules/zip/zip_reader.h b/modules/zip/zip_reader.h
index c074197eb4..0f78352e3f 100644
--- a/modules/zip/zip_reader.h
+++ b/modules/zip/zip_reader.h
@@ -51,6 +51,7 @@ public:
PackedStringArray get_files();
PackedByteArray read_file(String p_path, bool p_case_sensitive);
+ bool file_exists(String p_path, bool p_case_sensitive);
ZIPReader();
~ZIPReader();