diff options
Diffstat (limited to 'modules')
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=""static""> - 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(); |
