diff options
162 files changed, 3880 insertions, 2833 deletions
diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt index e79817a2e7..8288e79602 100644 --- a/COPYRIGHT.txt +++ b/COPYRIGHT.txt @@ -466,7 +466,7 @@ License: Expat Files: ./thirdparty/thorvg/ Comment: ThorVG -Copyright: 2020-2022, Samsung Electronics Co., Ltd. +Copyright: 2020-2023, The ThorVG Project License: Expat Files: ./thirdparty/tinyexr/ diff --git a/core/extension/extension_api_dump.cpp b/core/extension/extension_api_dump.cpp index f11a4bc9a4..58cb51245a 100644 --- a/core/extension/extension_api_dump.cpp +++ b/core/extension/extension_api_dump.cpp @@ -39,6 +39,7 @@ #include "core/version.h" #ifdef TOOLS_ENABLED +#include "editor/editor_help.h" static String get_builtin_or_variant_type_name(const Variant::Type p_type) { if (p_type == Variant::NIL) { @@ -88,7 +89,16 @@ static String get_type_meta_name(const GodotTypeInfo::Metadata metadata) { return argmeta[metadata]; } -Dictionary GDExtensionAPIDump::generate_extension_api() { +static String fix_doc_description(const String &p_bbcode) { + // Based on what EditorHelp does. + + return p_bbcode.dedent() + .replace("\t", "") + .replace("\r", "") + .strip_edges(); +} + +Dictionary GDExtensionAPIDump::generate_extension_api(bool p_include_docs) { Dictionary api_dump; { @@ -460,12 +470,22 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { api_dump["builtin_class_member_offsets"] = core_type_member_offsets; } + if (p_include_docs) { + EditorHelp::generate_doc(false); + } + { // Global enums and constants. Array constants; HashMap<String, List<Pair<String, int64_t>>> enum_list; HashMap<String, bool> enum_is_bitfield; + const DocData::ClassDoc *global_scope_doc = nullptr; + if (p_include_docs) { + global_scope_doc = EditorHelp::get_doc_data()->class_list.getptr("@GlobalScope"); + CRASH_COND_MSG(!global_scope_doc, "Could not find '@GlobalScope' in DocData."); + } + for (int i = 0; i < CoreConstants::get_global_constant_count(); i++) { int64_t value = CoreConstants::get_global_constant_value(i); String enum_name = CoreConstants::get_global_constant_enum(i); @@ -479,6 +499,14 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { d["name"] = name; d["value"] = value; d["is_bitfield"] = bitfield; + if (p_include_docs) { + for (const DocData::ConstantDoc &constant_doc : global_scope_doc->constants) { + if (constant_doc.name == name) { + d["documentation"] = fix_doc_description(constant_doc.description); + break; + } + } + } constants.push_back(d); } } @@ -490,11 +518,25 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { Dictionary d1; d1["name"] = E.key; d1["is_bitfield"] = enum_is_bitfield[E.key]; + if (p_include_docs) { + const DocData::EnumDoc *enum_doc = global_scope_doc->enums.getptr(E.key); + if (enum_doc) { + d1["documentation"] = fix_doc_description(enum_doc->description); + } + } Array values; for (const Pair<String, int64_t> &F : E.value) { Dictionary d2; d2["name"] = F.first; d2["value"] = F.second; + if (p_include_docs) { + for (const DocData::ConstantDoc &constant_doc : global_scope_doc->constants) { + if (constant_doc.name == F.first) { + d2["documentation"] = fix_doc_description(constant_doc.description); + break; + } + } + } values.push_back(d2); } d1["values"] = values; @@ -509,6 +551,12 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { List<StringName> utility_func_names; Variant::get_utility_function_list(&utility_func_names); + const DocData::ClassDoc *global_scope_doc = nullptr; + if (p_include_docs) { + global_scope_doc = EditorHelp::get_doc_data()->class_list.getptr("@GlobalScope"); + CRASH_COND_MSG(!global_scope_doc, "Could not find '@GlobalScope' in DocData."); + } + for (const StringName &name : utility_func_names) { Dictionary func; func["name"] = String(name); @@ -545,6 +593,15 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { func["arguments"] = arguments; } + if (p_include_docs) { + for (const DocData::MethodDoc &method_doc : global_scope_doc->methods) { + if (method_doc.name == name) { + func["documentation"] = fix_doc_description(method_doc.description); + break; + } + } + } + utility_funcs.push_back(func); } @@ -571,6 +628,12 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { d["is_keyed"] = Variant::is_keyed(type); + DocData::ClassDoc *builtin_doc = nullptr; + if (p_include_docs && d["name"] != "Nil") { + builtin_doc = EditorHelp::get_doc_data()->class_list.getptr(d["name"]); + CRASH_COND_MSG(!builtin_doc, vformat("Could not find '%s' in DocData.", d["name"])); + } + { //members Array members; @@ -581,6 +644,14 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { Dictionary d2; d2["name"] = String(member_name); d2["type"] = get_builtin_or_variant_type_name(Variant::get_member_type(type, member_name)); + if (p_include_docs) { + for (const DocData::PropertyDoc &property_doc : builtin_doc->properties) { + if (property_doc.name == member_name) { + d2["documentation"] = fix_doc_description(property_doc.description); + break; + } + } + } members.push_back(d2); } if (members.size()) { @@ -599,6 +670,14 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { Variant constant = Variant::get_constant_value(type, constant_name); d2["type"] = get_builtin_or_variant_type_name(constant.get_type()); d2["value"] = constant.get_construct_string(); + if (p_include_docs) { + for (const DocData::ConstantDoc &constant_doc : builtin_doc->constants) { + if (constant_doc.name == constant_name) { + d2["documentation"] = fix_doc_description(constant_doc.description); + break; + } + } + } constants.push_back(d2); } if (constants.size()) { @@ -624,9 +703,24 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { Dictionary values_dict; values_dict["name"] = String(enumeration); values_dict["value"] = Variant::get_enum_value(type, enum_name, enumeration); + if (p_include_docs) { + for (const DocData::ConstantDoc &constant_doc : builtin_doc->constants) { + if (constant_doc.name == enumeration) { + values_dict["documentation"] = fix_doc_description(constant_doc.description); + break; + } + } + } values.push_back(values_dict); } + if (p_include_docs) { + const DocData::EnumDoc *enum_doc = builtin_doc->enums.getptr(enum_name); + if (enum_doc) { + enum_dict["documentation"] = fix_doc_description(enum_doc->description); + } + } + if (values.size()) { enum_dict["values"] = values; } @@ -646,11 +740,22 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { Variant::Type rt = Variant::get_operator_return_type(Variant::Operator(k), type, Variant::Type(j)); if (rt != Variant::NIL) { Dictionary d2; - d2["name"] = Variant::get_operator_name(Variant::Operator(k)); + String operator_name = Variant::get_operator_name(Variant::Operator(k)); + d2["name"] = operator_name; if (k != Variant::OP_NEGATE && k != Variant::OP_POSITIVE && k != Variant::OP_NOT && k != Variant::OP_BIT_NEGATE) { d2["right_type"] = get_builtin_or_variant_type_name(Variant::Type(j)); } d2["return_type"] = get_builtin_or_variant_type_name(Variant::get_operator_return_type(Variant::Operator(k), type, Variant::Type(j))); + + if (p_include_docs && builtin_doc != nullptr) { + for (const DocData::MethodDoc &operator_doc : builtin_doc->operators) { + if (operator_doc.name == "operator " + operator_name) { + d2["documentation"] = fix_doc_description(operator_doc.description); + break; + } + } + } + operators.push_back(d2); } } @@ -697,6 +802,15 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { d2["arguments"] = arguments; } + if (p_include_docs) { + for (const DocData::MethodDoc &method_doc : builtin_doc->methods) { + if (method_doc.name == method_name) { + d2["documentation"] = fix_doc_description(method_doc.description); + break; + } + } + } + methods.push_back(d2); } if (methods.size()) { @@ -722,6 +836,28 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { if (arguments.size()) { d2["arguments"] = arguments; } + + if (p_include_docs && builtin_doc) { + for (const DocData::MethodDoc &constructor_doc : builtin_doc->constructors) { + if (constructor_doc.arguments.size() != argcount) { + continue; + } + bool constructor_found = true; + for (int k = 0; k < argcount; k++) { + const DocData::ArgumentDoc &argument_doc = constructor_doc.arguments[k]; + const Dictionary &argument_dict = arguments[k]; + const String &argument_string = argument_dict["type"]; + if (argument_doc.type != argument_string) { + constructor_found = false; + break; + } + } + if (constructor_found) { + d2["documentation"] = fix_doc_description(constructor_doc.description); + } + } + } + constructors.push_back(d2); } @@ -734,6 +870,10 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { d["has_destructor"] = Variant::has_destructor(type); } + if (p_include_docs && builtin_doc != nullptr) { + d["documentation"] = fix_doc_description(builtin_doc->description); + } + builtins.push_back(d); } @@ -763,6 +903,12 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { d["inherits"] = String(parent_class); } + DocData::ClassDoc *class_doc = nullptr; + if (p_include_docs) { + class_doc = EditorHelp::get_doc_data()->class_list.getptr(class_name); + CRASH_COND_MSG(!class_doc, vformat("Could not find '%s' in DocData.", class_name)); + } + { ClassDB::APIType api = ClassDB::get_api_type(class_name); static const char *api_type[5] = { "core", "editor", "extension", "editor_extension" }; @@ -784,6 +930,15 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { d2["name"] = String(F); d2["value"] = ClassDB::get_integer_constant(class_name, F); + if (p_include_docs) { + for (const DocData::ConstantDoc &constant_doc : class_doc->constants) { + if (constant_doc.name == F) { + d2["documentation"] = fix_doc_description(constant_doc.description); + break; + } + } + } + constants.push_back(d2); } @@ -808,11 +963,28 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { Dictionary d3; d3["name"] = String(G->get()); d3["value"] = ClassDB::get_integer_constant(class_name, G->get()); + + if (p_include_docs) { + for (const DocData::ConstantDoc &constant_doc : class_doc->constants) { + if (constant_doc.name == G->get()) { + d3["documentation"] = fix_doc_description(constant_doc.description); + break; + } + } + } + values.push_back(d3); } d2["values"] = values; + if (p_include_docs) { + const DocData::EnumDoc *enum_doc = class_doc->enums.getptr(F); + if (enum_doc) { + d2["documentation"] = fix_doc_description(enum_doc->description); + } + } + enums.push_back(d2); } @@ -864,6 +1036,15 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { d2["arguments"] = arguments; } + if (p_include_docs) { + for (const DocData::MethodDoc &method_doc : class_doc->methods) { + if (method_doc.name == method_name) { + d2["documentation"] = fix_doc_description(method_doc.description); + break; + } + } + } + methods.push_back(d2); } else if (F.name.begins_with("_")) { @@ -932,6 +1113,15 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { d2["arguments"] = arguments; } + if (p_include_docs) { + for (const DocData::MethodDoc &method_doc : class_doc->methods) { + if (method_doc.name == method_name) { + d2["documentation"] = fix_doc_description(method_doc.description); + break; + } + } + } + methods.push_back(d2); } } @@ -966,6 +1156,15 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { d2["arguments"] = arguments; } + if (p_include_docs) { + for (const DocData::MethodDoc &signal_doc : class_doc->signals) { + if (signal_doc.name == signal_name) { + d2["documentation"] = fix_doc_description(signal_doc.description); + break; + } + } + } + signals.push_back(d2); } @@ -1005,6 +1204,16 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { if (index != -1) { d2["index"] = index; } + + if (p_include_docs) { + for (const DocData::PropertyDoc &property_doc : class_doc->properties) { + if (property_doc.name == property_name) { + d2["documentation"] = fix_doc_description(property_doc.description); + break; + } + } + } + properties.push_back(d2); } @@ -1013,6 +1222,10 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { } } + if (p_include_docs && class_doc != nullptr) { + d["documentation"] = fix_doc_description(class_doc->description); + } + classes.push_back(d); } @@ -1065,8 +1278,8 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { return api_dump; } -void GDExtensionAPIDump::generate_extension_json_file(const String &p_path) { - Dictionary api = generate_extension_api(); +void GDExtensionAPIDump::generate_extension_json_file(const String &p_path, bool p_include_docs) { + Dictionary api = generate_extension_api(p_include_docs); Ref<JSON> json; json.instantiate(); diff --git a/core/extension/extension_api_dump.h b/core/extension/extension_api_dump.h index 11ea2cf923..204a115f84 100644 --- a/core/extension/extension_api_dump.h +++ b/core/extension/extension_api_dump.h @@ -37,8 +37,8 @@ class GDExtensionAPIDump { public: - static Dictionary generate_extension_api(); - static void generate_extension_json_file(const String &p_path); + static Dictionary generate_extension_api(bool p_include_docs = false); + static void generate_extension_json_file(const String &p_path, bool p_include_docs = false); static Error validate_extension_json_file(const String &p_path); }; #endif diff --git a/core/io/image.cpp b/core/io/image.cpp index 674af6b0a6..15d0182dfc 100644 --- a/core/io/image.cpp +++ b/core/io/image.cpp @@ -1930,8 +1930,7 @@ Error Image::generate_mipmaps(bool p_renormalize) { } Error Image::generate_mipmap_roughness(RoughnessChannel p_roughness_channel, const Ref<Image> &p_normal_map) { - Vector<double> normal_sat_vec; //summed area table - double *normal_sat = nullptr; //summed area table for normal map + LocalVector<double> normal_sat_vec; //summed area table int normal_w = 0, normal_h = 0; ERR_FAIL_COND_V_MSG(p_normal_map.is_null() || p_normal_map->is_empty(), ERR_INVALID_PARAMETER, "Must provide a valid normal map for roughness mipmaps"); @@ -1945,8 +1944,7 @@ Error Image::generate_mipmap_roughness(RoughnessChannel p_roughness_channel, con normal_h = nm->get_height(); normal_sat_vec.resize(normal_w * normal_h * 3); - - normal_sat = normal_sat_vec.ptrw(); + double *normal_sat = normal_sat_vec.ptr(); //create summed area table @@ -2021,24 +2019,26 @@ Error Image::generate_mipmap_roughness(RoughnessChannel p_roughness_channel, con avg[2] += normal_sat[tofs + 2]; } - if (from_y > 0) { + if (from_y > 0 && to_x > 0) { uint32_t tofs = ((from_y - 1) * normal_w + to_x) * 3; avg[0] -= normal_sat[tofs + 0]; avg[1] -= normal_sat[tofs + 1]; avg[2] -= normal_sat[tofs + 2]; } - if (from_x > 0) { + if (from_x > 0 && to_y > 0) { uint32_t tofs = (to_y * normal_w + (from_x - 1)) * 3; avg[0] -= normal_sat[tofs + 0]; avg[1] -= normal_sat[tofs + 1]; avg[2] -= normal_sat[tofs + 2]; } - uint32_t tofs = (to_y * normal_w + to_x) * 3; - avg[0] += normal_sat[tofs + 0]; - avg[1] += normal_sat[tofs + 1]; - avg[2] += normal_sat[tofs + 2]; + if (to_y > 0 && to_x > 0) { + uint32_t tofs = (to_y * normal_w + to_x) * 3; + avg[0] += normal_sat[tofs + 0]; + avg[1] += normal_sat[tofs + 1]; + avg[2] += normal_sat[tofs + 2]; + } double div = double(size_x * size_y); Vector3 vec(avg[0] / div, avg[1] / div, avg[2] / div); diff --git a/core/io/resource.cpp b/core/io/resource.cpp index 68cdeabac7..e0d42a274a 100644 --- a/core/io/resource.cpp +++ b/core/io/resource.cpp @@ -379,8 +379,8 @@ Node *Resource::get_local_scene() const { } void Resource::setup_local_to_scene() { - // Can't use GDVIRTUAL in Resource, so this will have to be done with a signal emit_signal(SNAME("setup_local_to_scene_requested")); + GDVIRTUAL_CALL(_setup_local_to_scene); } void Resource::reset_local_to_scene() { @@ -460,6 +460,7 @@ void Resource::_bind_methods() { get_rid_bind.return_val.type = Variant::RID; ::ClassDB::add_virtual_method(get_class_static(), get_rid_bind, true, Vector<String>(), true); + GDVIRTUAL_BIND(_setup_local_to_scene); } Resource::Resource() : diff --git a/core/io/resource.h b/core/io/resource.h index f848bdba99..a9b1a88f6b 100644 --- a/core/io/resource.h +++ b/core/io/resource.h @@ -33,6 +33,7 @@ #include "core/io/resource_uid.h" #include "core/object/class_db.h" +#include "core/object/gdvirtual.gen.inc" #include "core/object/ref_counted.h" #include "core/templates/safe_refcount.h" #include "core/templates/self_list.h" @@ -81,6 +82,7 @@ protected: void _take_over_path(const String &p_path); virtual void reset_local_to_scene(); + GDVIRTUAL0(_setup_local_to_scene); public: static Node *(*_get_local_scene_func)(); //used by editor diff --git a/core/math/bvh_split.inc b/core/math/bvh_split.inc index 875abedb70..2c85a63575 100644 --- a/core/math/bvh_split.inc +++ b/core/math/bvh_split.inc @@ -20,8 +20,8 @@ void _split_leaf_sort_groups_simple(int &num_a, int &num_b, uint16_t *group_a, u group_b[num_b++] = ind; // remove from a - group_a[0] = group_a[num_a - 1]; num_a--; + group_a[0] = group_a[num_a]; return; } @@ -30,15 +30,15 @@ void _split_leaf_sort_groups_simple(int &num_a, int &num_b, uint16_t *group_a, u int order[POINT::AXIS_COUNT]; - order[0] = size.min_axis_index(); - order[POINT::AXIS_COUNT - 1] = size.max_axis_index(); + order[0] = size.max_axis_index(); // The longest axis. + order[POINT::AXIS_COUNT - 1] = size.min_axis_index(); // The shortest axis. static_assert(POINT::AXIS_COUNT <= 3, "BVH POINT::AXIS_COUNT has unexpected size"); if constexpr (POINT::AXIS_COUNT == 3) { order[1] = 3 - (order[0] + order[2]); } - // simplest case, split on the longest axis + // Simplest case, split on the longest axis. int split_axis = order[0]; for (int a = 0; a < num_a; a++) { uint32_t ind = group_a[a]; @@ -48,8 +48,8 @@ void _split_leaf_sort_groups_simple(int &num_a, int &num_b, uint16_t *group_a, u group_b[num_b++] = ind; // remove from a - group_a[a] = group_a[num_a - 1]; num_a--; + group_a[a] = group_a[num_a]; // do this one again, as it has been replaced a--; @@ -67,7 +67,7 @@ void _split_leaf_sort_groups_simple(int &num_a, int &num_b, uint16_t *group_a, u } num_b = 0; - // now calculate the best split + // Now calculate the best split. for (int axis = 1; axis < POINT::AXIS_COUNT; axis++) { split_axis = order[axis]; int count = 0; @@ -105,8 +105,8 @@ void _split_leaf_sort_groups_simple(int &num_a, int &num_b, uint16_t *group_a, u group_b[num_b++] = ind; // remove from a - group_a[a] = group_a[num_a - 1]; num_a--; + group_a[a] = group_a[num_a]; // do this one again, as it has been replaced a--; @@ -123,8 +123,8 @@ void _split_leaf_sort_groups_simple(int &num_a, int &num_b, uint16_t *group_a, u group_b[num_b++] = ind; // remove from a - group_a[0] = group_a[num_a - 1]; num_a--; + group_a[0] = group_a[num_a]; } // opposite problem! :) if (!num_a) { @@ -134,8 +134,8 @@ void _split_leaf_sort_groups_simple(int &num_a, int &num_b, uint16_t *group_a, u group_a[num_a++] = ind; // remove from b - group_b[0] = group_b[num_b - 1]; num_b--; + group_b[0] = group_b[num_b]; } } diff --git a/core/variant/array.cpp b/core/variant/array.cpp index 4c9ee68ab5..ab0315ae34 100644 --- a/core/variant/array.cpp +++ b/core/variant/array.cpp @@ -137,7 +137,7 @@ bool Array::recursive_equal(const Array &p_array, int recursion_count) const { } recursion_count++; for (int i = 0; i < size; i++) { - if (!a1[i].hash_compare(a2[i], recursion_count)) { + if (!a1[i].hash_compare(a2[i], recursion_count, false)) { return false; } } diff --git a/core/variant/dictionary.cpp b/core/variant/dictionary.cpp index 2dda9fc1f8..141ce25fa6 100644 --- a/core/variant/dictionary.cpp +++ b/core/variant/dictionary.cpp @@ -210,7 +210,7 @@ bool Dictionary::recursive_equal(const Dictionary &p_dictionary, int recursion_c recursion_count++; for (const KeyValue<Variant, Variant> &this_E : _p->variant_map) { HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator>::ConstIterator other_E(p_dictionary._p->variant_map.find(this_E.key)); - if (!other_E || !this_E.value.hash_compare(other_E->value, recursion_count)) { + if (!other_E || !this_E.value.hash_compare(other_E->value, recursion_count, false)) { return false; } } diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp index 8a0289898d..63ea3274ce 100644 --- a/core/variant/variant.cpp +++ b/core/variant/variant.cpp @@ -3235,8 +3235,11 @@ uint32_t Variant::recursive_hash(int recursion_count) const { return 0; } +#define hash_compare_scalar_base(p_lhs, p_rhs, semantic_comparison) \ + (((p_lhs) == (p_rhs)) || (semantic_comparison && Math::is_nan(p_lhs) && Math::is_nan(p_rhs))) + #define hash_compare_scalar(p_lhs, p_rhs) \ - (((p_lhs) == (p_rhs)) || (Math::is_nan(p_lhs) && Math::is_nan(p_rhs))) + (hash_compare_scalar_base(p_lhs, p_rhs, true)) #define hash_compare_vector2(p_lhs, p_rhs) \ (hash_compare_scalar((p_lhs).x, (p_rhs).x) && \ @@ -3282,7 +3285,7 @@ uint32_t Variant::recursive_hash(int recursion_count) const { \ return true -bool Variant::hash_compare(const Variant &p_variant, int recursion_count) const { +bool Variant::hash_compare(const Variant &p_variant, int recursion_count, bool semantic_comparison) const { if (type != p_variant.type) { return false; } @@ -3293,7 +3296,7 @@ bool Variant::hash_compare(const Variant &p_variant, int recursion_count) const } break; case FLOAT: { - return hash_compare_scalar(_data._float, p_variant._data._float); + return hash_compare_scalar_base(_data._float, p_variant._data._float, semantic_comparison); } break; case STRING: { diff --git a/core/variant/variant.h b/core/variant/variant.h index 04c2fe2012..d698f85754 100644 --- a/core/variant/variant.h +++ b/core/variant/variant.h @@ -751,7 +751,8 @@ public: uint32_t hash() const; uint32_t recursive_hash(int recursion_count) const; - bool hash_compare(const Variant &p_variant, int recursion_count = 0) const; + // By default, performs a semantic comparison. Otherwise, numeric/binary comparison (if appropriate). + bool hash_compare(const Variant &p_variant, int recursion_count = 0, bool semantic_comparison = true) const; bool identity_compare(const Variant &p_variant) const; bool booleanize() const; String stringify(int recursion_count = 0) const; diff --git a/doc/classes/Button.xml b/doc/classes/Button.xml index 25c71deb1f..bee0cbcf4a 100644 --- a/doc/classes/Button.xml +++ b/doc/classes/Button.xml @@ -131,20 +131,40 @@ <theme_item name="font_size" data_type="font_size" type="int"> Font size of the [Button]'s text. </theme_item> + <theme_item name="icon" data_type="icon" type="Texture2D"> + </theme_item> <theme_item name="disabled" data_type="style" type="StyleBox"> [StyleBox] used when the [Button] is disabled. </theme_item> + <theme_item name="disabled_mirrored" data_type="style" type="StyleBox"> + [StyleBox] used when the [Button] is disabled (for right-to-left layouts). + </theme_item> <theme_item name="focus" data_type="style" type="StyleBox"> [StyleBox] used when the [Button] is focused. The [code]focus[/code] [StyleBox] is displayed [i]over[/i] the base [StyleBox], so a partially transparent [StyleBox] should be used to ensure the base [StyleBox] remains visible. A [StyleBox] that represents an outline or an underline works well for this purpose. To disable the focus visual effect, assign a [StyleBoxEmpty] resource. Note that disabling the focus visual effect will harm keyboard/controller navigation usability, so this is not recommended for accessibility reasons. </theme_item> <theme_item name="hover" data_type="style" type="StyleBox"> [StyleBox] used when the [Button] is being hovered. </theme_item> + <theme_item name="hover_mirrored" data_type="style" type="StyleBox"> + [StyleBox] used when the [Button] is being hovered (for right-to-left layouts). + </theme_item> + <theme_item name="hover_pressed" data_type="style" type="StyleBox"> + [StyleBox] used when the [Button] is being pressed and hovered at the same time. + </theme_item> + <theme_item name="hover_pressed_mirrored" data_type="style" type="StyleBox"> + [StyleBox] used when the [Button] is being pressed and hovered at the same time (for right-to-left layouts). + </theme_item> <theme_item name="normal" data_type="style" type="StyleBox"> Default [StyleBox] for the [Button]. </theme_item> + <theme_item name="normal_mirrored" data_type="style" type="StyleBox"> + Default [StyleBox] for the [Button] (for right-to-left layouts). + </theme_item> <theme_item name="pressed" data_type="style" type="StyleBox"> [StyleBox] used when the [Button] is being pressed. </theme_item> + <theme_item name="pressed_mirrored" data_type="style" type="StyleBox"> + [StyleBox] used when the [Button] is being pressed (for right-to-left layouts). + </theme_item> </theme_items> </class> diff --git a/doc/classes/CheckBox.xml b/doc/classes/CheckBox.xml index fb733a95b8..5b2c93f7b3 100644 --- a/doc/classes/CheckBox.xml +++ b/doc/classes/CheckBox.xml @@ -15,43 +15,9 @@ <member name="toggle_mode" type="bool" setter="set_toggle_mode" getter="is_toggle_mode" overrides="BaseButton" default="true" /> </members> <theme_items> - <theme_item name="font_color" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 1)"> - The [CheckBox] text's font color. - </theme_item> - <theme_item name="font_disabled_color" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 0.5)"> - The [CheckBox] text's font color when it's disabled. - </theme_item> - <theme_item name="font_focus_color" data_type="color" type="Color" default="Color(0.95, 0.95, 0.95, 1)"> - The [CheckBox] text's font color when it's focused. Only replaces the normal text color of the checkbox. Disabled, hovered, and pressed states take precedence over this color. - </theme_item> - <theme_item name="font_hover_color" data_type="color" type="Color" default="Color(0.95, 0.95, 0.95, 1)"> - The [CheckBox] text's font color when it's hovered. - </theme_item> - <theme_item name="font_hover_pressed_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> - The [CheckBox] text's font color when it's hovered and pressed. - </theme_item> - <theme_item name="font_outline_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> - The tint of text outline of the [CheckBox]. - </theme_item> - <theme_item name="font_pressed_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> - The [CheckBox] text's font color when it's pressed. - </theme_item> <theme_item name="check_v_offset" data_type="constant" type="int" default="0"> The vertical offset used when rendering the check icons (in pixels). </theme_item> - <theme_item name="h_separation" data_type="constant" type="int" default="4"> - The separation between the check icon and the text (in pixels). Negative values will be treated as [code]0[/code] when used. - </theme_item> - <theme_item name="outline_size" data_type="constant" type="int" default="0"> - The size of the text outline. - [b]Note:[/b] If using a font with [member FontFile.multichannel_signed_distance_field] enabled, its [member FontFile.msdf_pixel_range] must be set to at least [i]twice[/i] the value of [theme_item outline_size] for outline rendering to look correct. Otherwise, the outline may appear to be cut off earlier than intended. - </theme_item> - <theme_item name="font" data_type="font" type="Font"> - The [Font] to use for the [CheckBox] text. - </theme_item> - <theme_item name="font_size" data_type="font_size" type="int"> - Font size of the [CheckBox]'s text. - </theme_item> <theme_item name="checked" data_type="icon" type="Texture2D"> The check icon to display when the [CheckBox] is checked. </theme_item> @@ -76,23 +42,5 @@ <theme_item name="unchecked_disabled" data_type="icon" type="Texture2D"> The check icon to display when the [CheckBox] is unchecked and is disabled. </theme_item> - <theme_item name="disabled" data_type="style" type="StyleBox"> - The [StyleBox] to display as a background when the [CheckBox] is disabled. - </theme_item> - <theme_item name="focus" data_type="style" type="StyleBox"> - The [StyleBox] to display as a background when the [CheckBox] is focused. The [code]focus[/code] [StyleBox] is displayed [i]over[/i] the base [StyleBox], so a partially transparent [StyleBox] should be used to ensure the base [StyleBox] remains visible. A [StyleBox] that represents an outline or an underline works well for this purpose. To disable the focus visual effect, assign a [StyleBoxEmpty] resource. Note that disabling the focus visual effect will harm keyboard/controller navigation usability, so this is not recommended for accessibility reasons. - </theme_item> - <theme_item name="hover" data_type="style" type="StyleBox"> - The [StyleBox] to display as a background when the [CheckBox] is hovered. - </theme_item> - <theme_item name="hover_pressed" data_type="style" type="StyleBox"> - The [StyleBox] to display as a background when the [CheckBox] is hovered and pressed. - </theme_item> - <theme_item name="normal" data_type="style" type="StyleBox"> - The [StyleBox] to display as a background. - </theme_item> - <theme_item name="pressed" data_type="style" type="StyleBox"> - The [StyleBox] to display as a background when the [CheckBox] is pressed. - </theme_item> </theme_items> </class> diff --git a/doc/classes/CheckButton.xml b/doc/classes/CheckButton.xml index 0fffe7f621..c2da6cfe3e 100644 --- a/doc/classes/CheckButton.xml +++ b/doc/classes/CheckButton.xml @@ -14,43 +14,9 @@ <member name="toggle_mode" type="bool" setter="set_toggle_mode" getter="is_toggle_mode" overrides="BaseButton" default="true" /> </members> <theme_items> - <theme_item name="font_color" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 1)"> - The [CheckButton] text's font color. - </theme_item> - <theme_item name="font_disabled_color" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 0.5)"> - The [CheckButton] text's font color when it's disabled. - </theme_item> - <theme_item name="font_focus_color" data_type="color" type="Color" default="Color(0.95, 0.95, 0.95, 1)"> - The [CheckButton] text's font color when it's focused. Only replaces the normal text color of the button. Disabled, hovered, and pressed states take precedence over this color. - </theme_item> - <theme_item name="font_hover_color" data_type="color" type="Color" default="Color(0.95, 0.95, 0.95, 1)"> - The [CheckButton] text's font color when it's hovered. - </theme_item> - <theme_item name="font_hover_pressed_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> - The [CheckButton] text's font color when it's hovered and pressed. - </theme_item> - <theme_item name="font_outline_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> - The tint of text outline of the [CheckButton]. - </theme_item> - <theme_item name="font_pressed_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> - The [CheckButton] text's font color when it's pressed. - </theme_item> <theme_item name="check_v_offset" data_type="constant" type="int" default="0"> The vertical offset used when rendering the toggle icons (in pixels). </theme_item> - <theme_item name="h_separation" data_type="constant" type="int" default="4"> - The separation between the toggle icon and the text (in pixels). Negative values will be treated as [code]0[/code] when used. - </theme_item> - <theme_item name="outline_size" data_type="constant" type="int" default="0"> - The size of the text outline. - [b]Note:[/b] If using a font with [member FontFile.multichannel_signed_distance_field] enabled, its [member FontFile.msdf_pixel_range] must be set to at least [i]twice[/i] the value of [theme_item outline_size] for outline rendering to look correct. Otherwise, the outline may appear to be cut off earlier than intended. - </theme_item> - <theme_item name="font" data_type="font" type="Font"> - The [Font] to use for the [CheckButton] text. - </theme_item> - <theme_item name="font_size" data_type="font_size" type="int"> - Font size of the [CheckButton]'s text. - </theme_item> <theme_item name="checked" data_type="icon" type="Texture2D"> The icon to display when the [CheckButton] is checked (for left-to-right layouts). </theme_item> @@ -75,23 +41,5 @@ <theme_item name="unchecked_mirrored" data_type="icon" type="Texture2D"> The icon to display when the [CheckButton] is unchecked (for right-to-left layouts). </theme_item> - <theme_item name="disabled" data_type="style" type="StyleBox"> - The [StyleBox] to display as a background when the [CheckButton] is disabled. - </theme_item> - <theme_item name="focus" data_type="style" type="StyleBox"> - The [StyleBox] to display as a background when the [CheckButton] is focused. The [code]focus[/code] [StyleBox] is displayed [i]over[/i] the base [StyleBox], so a partially transparent [StyleBox] should be used to ensure the base [StyleBox] remains visible. A [StyleBox] that represents an outline or an underline works well for this purpose. To disable the focus visual effect, assign a [StyleBoxEmpty] resource. Note that disabling the focus visual effect will harm keyboard/controller navigation usability, so this is not recommended for accessibility reasons. - </theme_item> - <theme_item name="hover" data_type="style" type="StyleBox"> - The [StyleBox] to display as a background when the [CheckButton] is hovered. - </theme_item> - <theme_item name="hover_pressed" data_type="style" type="StyleBox"> - The [StyleBox] to display as a background when the [CheckButton] is hovered and pressed. - </theme_item> - <theme_item name="normal" data_type="style" type="StyleBox"> - The [StyleBox] to display as a background. - </theme_item> - <theme_item name="pressed" data_type="style" type="StyleBox"> - The [StyleBox] to display as a background when the [CheckButton] is pressed. - </theme_item> </theme_items> </class> diff --git a/doc/classes/CodeEdit.xml b/doc/classes/CodeEdit.xml index 9b4b3aa36e..e5c255706f 100644 --- a/doc/classes/CodeEdit.xml +++ b/doc/classes/CodeEdit.xml @@ -633,9 +633,6 @@ </constant> </constants> <theme_items> - <theme_item name="background_color" data_type="color" type="Color" default="Color(0, 0, 0, 0)"> - Sets the background [Color]. - </theme_item> <theme_item name="bookmark_color" data_type="color" type="Color" default="Color(0.5, 0.64, 1, 0.8)"> [Color] of the bookmark icon for bookmarked lines. </theme_item> @@ -645,12 +642,6 @@ <theme_item name="breakpoint_color" data_type="color" type="Color" default="Color(0.9, 0.29, 0.3, 1)"> [Color] of the breakpoint icon for bookmarked lines. </theme_item> - <theme_item name="caret_background_color" data_type="color" type="Color" default="Color(0, 0, 0, 1)"> - [Color] of the text behind the caret when block caret is enabled. - </theme_item> - <theme_item name="caret_color" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 1)"> - [Color] of the caret. - </theme_item> <theme_item name="code_folding_color" data_type="color" type="Color" default="Color(0.8, 0.8, 0.8, 0.8)"> [Color] for all icons related to line folding. </theme_item> @@ -660,9 +651,6 @@ <theme_item name="completion_existing_color" data_type="color" type="Color" default="Color(0.87, 0.87, 0.87, 0.13)"> Background highlight [Color] for matching text in code completion options. </theme_item> - <theme_item name="completion_font_color" data_type="color" type="Color" default="Color(0.67, 0.67, 0.67, 1)"> - Font [Color] for the code completion popup. - </theme_item> <theme_item name="completion_scroll_color" data_type="color" type="Color" default="Color(1, 1, 1, 0.29)"> [Color] of the scrollbar in the code completion popup. </theme_item> @@ -672,48 +660,18 @@ <theme_item name="completion_selected_color" data_type="color" type="Color" default="Color(0.26, 0.26, 0.27, 1)"> Background highlight [Color] for the current selected option item in the code completion popup. </theme_item> - <theme_item name="current_line_color" data_type="color" type="Color" default="Color(0.25, 0.25, 0.26, 0.8)"> - Background [Color] of the line containing the caret. - </theme_item> <theme_item name="executing_line_color" data_type="color" type="Color" default="Color(0.98, 0.89, 0.27, 1)"> [Color] of the executing icon for executing lines. </theme_item> <theme_item name="folded_code_region_color" data_type="color" type="Color" default="Color(0.68, 0.46, 0.77, 0.2)"> [Color] of background line highlight for folded code region. </theme_item> - <theme_item name="font_color" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 1)"> - Sets the font [Color]. - </theme_item> - <theme_item name="font_outline_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> - The tint of text outline of the [CodeEdit]. - </theme_item> - <theme_item name="font_placeholder_color" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 0.6)"> - Font color for [member TextEdit.placeholder_text]. - </theme_item> - <theme_item name="font_readonly_color" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 0.5)"> - Sets the font [Color] when [member TextEdit.editable] is disabled. - </theme_item> - <theme_item name="font_selected_color" data_type="color" type="Color" default="Color(0, 0, 0, 0)"> - Sets the [Color] of the selected text. If equal to [code]Color(0, 0, 0, 0)[/code], it will be ignored. - </theme_item> <theme_item name="line_length_guideline_color" data_type="color" type="Color" default="Color(0.3, 0.5, 0.8, 0.1)"> [Color] of the main line length guideline, secondary guidelines will have 50% alpha applied. </theme_item> <theme_item name="line_number_color" data_type="color" type="Color" default="Color(0.67, 0.67, 0.67, 0.4)"> Sets the [Color] of line numbers. </theme_item> - <theme_item name="search_result_border_color" data_type="color" type="Color" default="Color(0.3, 0.3, 0.3, 0.4)"> - [Color] of the border around text that matches the search query. - </theme_item> - <theme_item name="search_result_color" data_type="color" type="Color" default="Color(0.3, 0.3, 0.3, 1)"> - [Color] behind the text that matches the search query. - </theme_item> - <theme_item name="selection_color" data_type="color" type="Color" default="Color(0.5, 0.5, 0.5, 1)"> - Sets the highlight [Color] of text selections. - </theme_item> - <theme_item name="word_highlighted_color" data_type="color" type="Color" default="Color(0.8, 0.9, 0.9, 0.15)"> - Sets the highlight [Color] of multiple occurrences. [member TextEdit.highlight_all_occurrences] has to be enabled. - </theme_item> <theme_item name="completion_lines" data_type="constant" type="int" default="7"> Max number of options to display in the code completion popup at any one time. </theme_item> @@ -723,19 +681,6 @@ <theme_item name="completion_scroll_width" data_type="constant" type="int" default="6"> Width of the scrollbar in the code completion popup. </theme_item> - <theme_item name="line_spacing" data_type="constant" type="int" default="4"> - Sets the spacing between the lines. - </theme_item> - <theme_item name="outline_size" data_type="constant" type="int" default="0"> - The size of the text outline. - [b]Note:[/b] If using a font with [member FontFile.multichannel_signed_distance_field] enabled, its [member FontFile.msdf_pixel_range] must be set to at least [i]twice[/i] the value of [theme_item outline_size] for outline rendering to look correct. Otherwise, the outline may appear to be cut off earlier than intended. - </theme_item> - <theme_item name="font" data_type="font" type="Font"> - Sets the default [Font]. - </theme_item> - <theme_item name="font_size" data_type="font_size" type="int"> - Sets default font size. - </theme_item> <theme_item name="bookmark" data_type="icon" type="Texture2D"> Sets a custom [Texture2D] to draw in the bookmark gutter for bookmarked lines. </theme_item> @@ -760,23 +705,8 @@ <theme_item name="folded_eol_icon" data_type="icon" type="Texture2D"> Sets a custom [Texture2D] to draw at the end of a folded line. </theme_item> - <theme_item name="space" data_type="icon" type="Texture2D"> - Sets a custom [Texture2D] for space text characters. - </theme_item> - <theme_item name="tab" data_type="icon" type="Texture2D"> - Sets a custom [Texture2D] for tab text characters. - </theme_item> <theme_item name="completion" data_type="style" type="StyleBox"> [StyleBox] for the code completion popup. </theme_item> - <theme_item name="focus" data_type="style" type="StyleBox"> - Sets the [StyleBox] when in focus. The [code]focus[/code] [StyleBox] is displayed [i]over[/i] the base [StyleBox], so a partially transparent [StyleBox] should be used to ensure the base [StyleBox] remains visible. A [StyleBox] that represents an outline or an underline works well for this purpose. To disable the focus visual effect, assign a [StyleBoxEmpty] resource. Note that disabling the focus visual effect will harm keyboard/controller navigation usability, so this is not recommended for accessibility reasons. - </theme_item> - <theme_item name="normal" data_type="style" type="StyleBox"> - Sets the [StyleBox]. - </theme_item> - <theme_item name="read_only" data_type="style" type="StyleBox"> - Sets the [StyleBox] when [member TextEdit.editable] is disabled. - </theme_item> </theme_items> </class> diff --git a/doc/classes/ColorPicker.xml b/doc/classes/ColorPicker.xml index 2f01e791a2..dfa8d7d840 100644 --- a/doc/classes/ColorPicker.xml +++ b/doc/classes/ColorPicker.xml @@ -142,7 +142,7 @@ </constants> <theme_items> <theme_item name="center_slider_grabbers" data_type="constant" type="int" default="1"> - Overrides the [theme_item HSlider.center_grabber] theme property of the sliders. + Overrides the [theme_item Slider.center_grabber] theme property of the sliders. </theme_item> <theme_item name="h_width" data_type="constant" type="int" default="30"> The width of the hue selection slider. diff --git a/doc/classes/ColorPickerButton.xml b/doc/classes/ColorPickerButton.xml index f6af188998..89cb01407d 100644 --- a/doc/classes/ColorPickerButton.xml +++ b/doc/classes/ColorPickerButton.xml @@ -56,54 +56,8 @@ </signal> </signals> <theme_items> - <theme_item name="font_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> - Default text [Color] of the [ColorPickerButton]. - </theme_item> - <theme_item name="font_disabled_color" data_type="color" type="Color" default="Color(0.9, 0.9, 0.9, 0.3)"> - Text [Color] used when the [ColorPickerButton] is disabled. - </theme_item> - <theme_item name="font_focus_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> - Text [Color] used when the [ColorPickerButton] is focused. Only replaces the normal text color of the button. Disabled, hovered, and pressed states take precedence over this color. - </theme_item> - <theme_item name="font_hover_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> - Text [Color] used when the [ColorPickerButton] is being hovered. - </theme_item> - <theme_item name="font_outline_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> - The tint of text outline of the [ColorPickerButton]. - </theme_item> - <theme_item name="font_pressed_color" data_type="color" type="Color" default="Color(0.8, 0.8, 0.8, 1)"> - Text [Color] used when the [ColorPickerButton] is being pressed. - </theme_item> - <theme_item name="h_separation" data_type="constant" type="int" default="4"> - The horizontal space between [ColorPickerButton]'s icon and text. - </theme_item> - <theme_item name="outline_size" data_type="constant" type="int" default="0"> - The size of the text outline. - [b]Note:[/b] If using a font with [member FontFile.multichannel_signed_distance_field] enabled, its [member FontFile.msdf_pixel_range] must be set to at least [i]twice[/i] the value of [theme_item outline_size] for outline rendering to look correct. Otherwise, the outline may appear to be cut off earlier than intended. - </theme_item> - <theme_item name="font" data_type="font" type="Font"> - [Font] of the [ColorPickerButton]'s text. - </theme_item> - <theme_item name="font_size" data_type="font_size" type="int"> - Font size of the [ColorPickerButton]'s text. - </theme_item> <theme_item name="bg" data_type="icon" type="Texture2D"> The background of the color preview rect on the button. </theme_item> - <theme_item name="disabled" data_type="style" type="StyleBox"> - [StyleBox] used when the [ColorPickerButton] is disabled. - </theme_item> - <theme_item name="focus" data_type="style" type="StyleBox"> - [StyleBox] used when the [ColorPickerButton] is focused. The [code]focus[/code] [StyleBox] is displayed [i]over[/i] the base [StyleBox], so a partially transparent [StyleBox] should be used to ensure the base [StyleBox] remains visible. A [StyleBox] that represents an outline or an underline works well for this purpose. To disable the focus visual effect, assign a [StyleBoxEmpty] resource. Note that disabling the focus visual effect will harm keyboard/controller navigation usability, so this is not recommended for accessibility reasons. - </theme_item> - <theme_item name="hover" data_type="style" type="StyleBox"> - [StyleBox] used when the [ColorPickerButton] is being hovered. - </theme_item> - <theme_item name="normal" data_type="style" type="StyleBox"> - Default [StyleBox] for the [ColorPickerButton]. - </theme_item> - <theme_item name="pressed" data_type="style" type="StyleBox"> - [StyleBox] used when the [ColorPickerButton] is being pressed. - </theme_item> </theme_items> </class> diff --git a/doc/classes/EditorFileDialog.xml b/doc/classes/EditorFileDialog.xml index 42cb420e74..59c0b7b912 100644 --- a/doc/classes/EditorFileDialog.xml +++ b/doc/classes/EditorFileDialog.xml @@ -19,6 +19,14 @@ For example, a [param filter] of [code]"*.tscn, *.scn"[/code] and a [param description] of [code]"Scenes"[/code] results in filter text "Scenes (*.tscn, *.scn)". </description> </method> + <method name="add_side_menu"> + <return type="void" /> + <param index="0" name="menu" type="Control" /> + <param index="1" name="title" type="String" default="""" /> + <description> + Adds the given [param menu] to the side of the file dialog with the given [param title] text on top. Only one side menu is allowed. + </description> + </method> <method name="clear_filters"> <return type="void" /> <description> diff --git a/doc/classes/GraphElement.xml b/doc/classes/GraphElement.xml index 6c3b6b727a..589b553cb6 100644 --- a/doc/classes/GraphElement.xml +++ b/doc/classes/GraphElement.xml @@ -66,4 +66,9 @@ </description> </signal> </signals> + <theme_items> + <theme_item name="resizer" data_type="icon" type="Texture2D"> + The icon used for the resizer, visible when [member resizable] is enabled. + </theme_item> + </theme_items> </class> diff --git a/doc/classes/GraphNode.xml b/doc/classes/GraphNode.xml index 5d52ea17e2..9e1392567a 100644 --- a/doc/classes/GraphNode.xml +++ b/doc/classes/GraphNode.xml @@ -263,9 +263,6 @@ <theme_item name="port" data_type="icon" type="Texture2D"> The icon used for representing ports. </theme_item> - <theme_item name="resizer" data_type="icon" type="Texture2D"> - The icon used for the resizer, visible when [member GraphElement.resizable] is enabled. - </theme_item> <theme_item name="panel" data_type="style" type="StyleBox"> The default background for the slot area of the [GraphNode]. </theme_item> diff --git a/doc/classes/HBoxContainer.xml b/doc/classes/HBoxContainer.xml index 331c1d23ad..f531f3f910 100644 --- a/doc/classes/HBoxContainer.xml +++ b/doc/classes/HBoxContainer.xml @@ -9,9 +9,4 @@ <tutorials> <link title="Using Containers">$DOCS_URL/tutorials/ui/gui_containers.html</link> </tutorials> - <theme_items> - <theme_item name="separation" data_type="constant" type="int" default="4"> - The horizontal space between the [HBoxContainer]'s elements. - </theme_item> - </theme_items> </class> diff --git a/doc/classes/HFlowContainer.xml b/doc/classes/HFlowContainer.xml index 44d1af37cf..36a87376ca 100644 --- a/doc/classes/HFlowContainer.xml +++ b/doc/classes/HFlowContainer.xml @@ -9,12 +9,4 @@ <tutorials> <link title="Using Containers">$DOCS_URL/tutorials/ui/gui_containers.html</link> </tutorials> - <theme_items> - <theme_item name="h_separation" data_type="constant" type="int" default="4"> - The horizontal separation of children nodes. - </theme_item> - <theme_item name="v_separation" data_type="constant" type="int" default="4"> - The vertical separation of children nodes. - </theme_item> - </theme_items> </class> diff --git a/doc/classes/HScrollBar.xml b/doc/classes/HScrollBar.xml index 307d244a40..84ed0c69ad 100644 --- a/doc/classes/HScrollBar.xml +++ b/doc/classes/HScrollBar.xml @@ -8,39 +8,4 @@ </description> <tutorials> </tutorials> - <theme_items> - <theme_item name="decrement" data_type="icon" type="Texture2D"> - Icon used as a button to scroll the [ScrollBar] left. Supports custom step using the [member ScrollBar.custom_step] property. - </theme_item> - <theme_item name="decrement_highlight" data_type="icon" type="Texture2D"> - Displayed when the mouse cursor hovers over the decrement button. - </theme_item> - <theme_item name="decrement_pressed" data_type="icon" type="Texture2D"> - Displayed when the decrement button is being pressed. - </theme_item> - <theme_item name="increment" data_type="icon" type="Texture2D"> - Icon used as a button to scroll the [ScrollBar] right. Supports custom step using the [member ScrollBar.custom_step] property. - </theme_item> - <theme_item name="increment_highlight" data_type="icon" type="Texture2D"> - Displayed when the mouse cursor hovers over the increment button. - </theme_item> - <theme_item name="increment_pressed" data_type="icon" type="Texture2D"> - Displayed when the increment button is being pressed. - </theme_item> - <theme_item name="grabber" data_type="style" type="StyleBox"> - Used as texture for the grabber, the draggable element representing current scroll. - </theme_item> - <theme_item name="grabber_highlight" data_type="style" type="StyleBox"> - Used when the mouse hovers over the grabber. - </theme_item> - <theme_item name="grabber_pressed" data_type="style" type="StyleBox"> - Used when the grabber is being dragged. - </theme_item> - <theme_item name="scroll" data_type="style" type="StyleBox"> - Used as background of this [ScrollBar]. - </theme_item> - <theme_item name="scroll_focus" data_type="style" type="StyleBox"> - Used as background when the [ScrollBar] has the GUI focus. - </theme_item> - </theme_items> </class> diff --git a/doc/classes/HSeparator.xml b/doc/classes/HSeparator.xml index c749eaa10e..ffba571f90 100644 --- a/doc/classes/HSeparator.xml +++ b/doc/classes/HSeparator.xml @@ -8,12 +8,4 @@ </description> <tutorials> </tutorials> - <theme_items> - <theme_item name="separation" data_type="constant" type="int" default="4"> - The height of the area covered by the separator. Effectively works like a minimum height. - </theme_item> - <theme_item name="separator" data_type="style" type="StyleBox"> - The style for the separator line. Works best with [StyleBoxLine]. - </theme_item> - </theme_items> </class> diff --git a/doc/classes/HSlider.xml b/doc/classes/HSlider.xml index 352ab3773e..40ec408f9b 100644 --- a/doc/classes/HSlider.xml +++ b/doc/classes/HSlider.xml @@ -8,33 +8,4 @@ </description> <tutorials> </tutorials> - <theme_items> - <theme_item name="center_grabber" data_type="constant" type="int" default="0"> - Boolean constant. If [code]1[/code], the grabber texture size will be ignored and it will fit within slider's bounds based only on its center position. - </theme_item> - <theme_item name="grabber_offset" data_type="constant" type="int" default="0"> - Vertical offset of the grabber. - </theme_item> - <theme_item name="grabber" data_type="icon" type="Texture2D"> - The texture for the grabber (the draggable element). - </theme_item> - <theme_item name="grabber_disabled" data_type="icon" type="Texture2D"> - The texture for the grabber when it's disabled. - </theme_item> - <theme_item name="grabber_highlight" data_type="icon" type="Texture2D"> - The texture for the grabber when it's focused. - </theme_item> - <theme_item name="tick" data_type="icon" type="Texture2D"> - The texture for the ticks, visible when [member Slider.tick_count] is greater than 0. - </theme_item> - <theme_item name="grabber_area" data_type="style" type="StyleBox"> - The background of the area to the left of the grabber. - </theme_item> - <theme_item name="grabber_area_highlight" data_type="style" type="StyleBox"> - The background of the area to the left of the grabber that displays when it's being hovered or focused. - </theme_item> - <theme_item name="slider" data_type="style" type="StyleBox"> - The background for the whole slider. Determines the height of the [code]grabber_area[/code]. - </theme_item> - </theme_items> </class> diff --git a/doc/classes/HSplitContainer.xml b/doc/classes/HSplitContainer.xml index 7cc6d94af3..1d8dbcb17c 100644 --- a/doc/classes/HSplitContainer.xml +++ b/doc/classes/HSplitContainer.xml @@ -9,18 +9,4 @@ <tutorials> <link title="Using Containers">$DOCS_URL/tutorials/ui/gui_containers.html</link> </tutorials> - <theme_items> - <theme_item name="autohide" data_type="constant" type="int" default="1"> - Boolean value. If 1 ([code]true[/code]), the grabber will hide automatically when it isn't under the cursor. If 0 ([code]false[/code]), it's always visible. - </theme_item> - <theme_item name="minimum_grab_thickness" data_type="constant" type="int" default="6"> - The minimum thickness of the area users can click on to grab the splitting line. If [theme_item separation] or [theme_item grabber]'s thickness are too small, this ensure that the splitting line can still be dragged. - </theme_item> - <theme_item name="separation" data_type="constant" type="int" default="12"> - The space between sides of the container. - </theme_item> - <theme_item name="grabber" data_type="icon" type="Texture2D"> - The icon used for the grabber drawn in the middle area. - </theme_item> - </theme_items> </class> diff --git a/doc/classes/LinkButton.xml b/doc/classes/LinkButton.xml index 010760cae0..4f38b4d336 100644 --- a/doc/classes/LinkButton.xml +++ b/doc/classes/LinkButton.xml @@ -62,12 +62,18 @@ <theme_item name="font_color" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 1)"> Default text [Color] of the [LinkButton]. </theme_item> + <theme_item name="font_disabled_color" data_type="color" type="Color" default="Color(0, 0, 0, 1)"> + Text [Color] used when the [LinkButton] is disabled. + </theme_item> <theme_item name="font_focus_color" data_type="color" type="Color" default="Color(0.95, 0.95, 0.95, 1)"> Text [Color] used when the [LinkButton] is focused. Only replaces the normal text color of the button. Disabled, hovered, and pressed states take precedence over this color. </theme_item> <theme_item name="font_hover_color" data_type="color" type="Color" default="Color(0.95, 0.95, 0.95, 1)"> Text [Color] used when the [LinkButton] is being hovered. </theme_item> + <theme_item name="font_hover_pressed_color" data_type="color" type="Color" default="Color(0, 0, 0, 1)"> + Text [Color] used when the [LinkButton] is being hovered and pressed. + </theme_item> <theme_item name="font_outline_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> The tint of text outline of the [LinkButton]. </theme_item> diff --git a/doc/classes/MenuBar.xml b/doc/classes/MenuBar.xml index 7d1b73278e..07f64d0572 100644 --- a/doc/classes/MenuBar.xml +++ b/doc/classes/MenuBar.xml @@ -154,17 +154,32 @@ <theme_item name="disabled" data_type="style" type="StyleBox"> [StyleBox] used when the menu item is disabled. </theme_item> - <theme_item name="focus" data_type="style" type="StyleBox"> - [StyleBox] used when the menu item is focused. The [code]focus[/code] [StyleBox] is displayed [i]over[/i] the base [StyleBox], so a partially transparent [StyleBox] should be used to ensure the base [StyleBox] remains visible. A [StyleBox] that represents an outline or an underline works well for this purpose. To disable the focus visual effect, assign a [StyleBoxEmpty] resource. Note that disabling the focus visual effect will harm keyboard/controller navigation usability, so this is not recommended for accessibility reasons. + <theme_item name="disabled_mirrored" data_type="style" type="StyleBox"> + [StyleBox] used when the menu item is disabled (for right-to-left layouts). </theme_item> <theme_item name="hover" data_type="style" type="StyleBox"> [StyleBox] used when the menu item is being hovered. </theme_item> + <theme_item name="hover_mirrored" data_type="style" type="StyleBox"> + [StyleBox] used when the menu item is being hovered (for right-to-left layouts). + </theme_item> + <theme_item name="hover_pressed" data_type="style" type="StyleBox"> + [StyleBox] used when the menu item is being pressed and hovered at the same time. + </theme_item> + <theme_item name="hover_pressed_mirrored" data_type="style" type="StyleBox"> + [StyleBox] used when the menu item is being pressed and hovered at the same time (for right-to-left layouts). + </theme_item> <theme_item name="normal" data_type="style" type="StyleBox"> Default [StyleBox] for the menu item. </theme_item> + <theme_item name="normal_mirrored" data_type="style" type="StyleBox"> + Default [StyleBox] for the menu item (for right-to-left layouts). + </theme_item> <theme_item name="pressed" data_type="style" type="StyleBox"> [StyleBox] used when the menu item is being pressed. </theme_item> + <theme_item name="pressed_mirrored" data_type="style" type="StyleBox"> + [StyleBox] used when the menu item is being pressed (for right-to-left layouts). + </theme_item> </theme_items> </class> diff --git a/doc/classes/MenuButton.xml b/doc/classes/MenuButton.xml index e3a707ba72..6aa17c1e16 100644 --- a/doc/classes/MenuButton.xml +++ b/doc/classes/MenuButton.xml @@ -50,52 +50,4 @@ </description> </signal> </signals> - <theme_items> - <theme_item name="font_color" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 1)"> - Default text [Color] of the [MenuButton]. - </theme_item> - <theme_item name="font_disabled_color" data_type="color" type="Color" default="Color(1, 1, 1, 0.3)"> - Text [Color] used when the [MenuButton] is disabled. - </theme_item> - <theme_item name="font_focus_color" data_type="color" type="Color" default="Color(0.95, 0.95, 0.95, 1)"> - Text [Color] used when the [MenuButton] is focused. Only replaces the normal text color of the button. Disabled, hovered, and pressed states take precedence over this color. - </theme_item> - <theme_item name="font_hover_color" data_type="color" type="Color" default="Color(0.95, 0.95, 0.95, 1)"> - Text [Color] used when the [MenuButton] is being hovered. - </theme_item> - <theme_item name="font_outline_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> - The tint of text outline of the [MenuButton]. - </theme_item> - <theme_item name="font_pressed_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> - Text [Color] used when the [MenuButton] is being pressed. - </theme_item> - <theme_item name="h_separation" data_type="constant" type="int" default="4"> - The horizontal space between [MenuButton]'s icon and text. Negative values will be treated as [code]0[/code] when used. - </theme_item> - <theme_item name="outline_size" data_type="constant" type="int" default="0"> - The size of the text outline. - [b]Note:[/b] If using a font with [member FontFile.multichannel_signed_distance_field] enabled, its [member FontFile.msdf_pixel_range] must be set to at least [i]twice[/i] the value of [theme_item outline_size] for outline rendering to look correct. Otherwise, the outline may appear to be cut off earlier than intended. - </theme_item> - <theme_item name="font" data_type="font" type="Font"> - [Font] of the [MenuButton]'s text. - </theme_item> - <theme_item name="font_size" data_type="font_size" type="int"> - Font size of the [MenuButton]'s text. - </theme_item> - <theme_item name="disabled" data_type="style" type="StyleBox"> - [StyleBox] used when the [MenuButton] is disabled. - </theme_item> - <theme_item name="focus" data_type="style" type="StyleBox"> - [StyleBox] used when the [MenuButton] is focused. The [code]focus[/code] [StyleBox] is displayed [i]over[/i] the base [StyleBox], so a partially transparent [StyleBox] should be used to ensure the base [StyleBox] remains visible. A [StyleBox] that represents an outline or an underline works well for this purpose. To disable the focus visual effect, assign a [StyleBoxEmpty] resource. Note that disabling the focus visual effect will harm keyboard/controller navigation usability, so this is not recommended for accessibility reasons. - </theme_item> - <theme_item name="hover" data_type="style" type="StyleBox"> - [StyleBox] used when the [MenuButton] is being hovered. - </theme_item> - <theme_item name="normal" data_type="style" type="StyleBox"> - Default [StyleBox] for the [MenuButton]. - </theme_item> - <theme_item name="pressed" data_type="style" type="StyleBox"> - [StyleBox] used when the [MenuButton] is being pressed. - </theme_item> - </theme_items> </class> diff --git a/doc/classes/OptionButton.xml b/doc/classes/OptionButton.xml index 47a68802aa..4e223c12fa 100644 --- a/doc/classes/OptionButton.xml +++ b/doc/classes/OptionButton.xml @@ -243,75 +243,14 @@ </signal> </signals> <theme_items> - <theme_item name="font_color" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 1)"> - Default text [Color] of the [OptionButton]. - </theme_item> - <theme_item name="font_disabled_color" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 0.5)"> - Text [Color] used when the [OptionButton] is disabled. - </theme_item> - <theme_item name="font_focus_color" data_type="color" type="Color" default="Color(0.95, 0.95, 0.95, 1)"> - Text [Color] used when the [OptionButton] is focused. Only replaces the normal text color of the button. Disabled, hovered, and pressed states take precedence over this color. - </theme_item> - <theme_item name="font_hover_color" data_type="color" type="Color" default="Color(0.95, 0.95, 0.95, 1)"> - Text [Color] used when the [OptionButton] is being hovered. - </theme_item> - <theme_item name="font_hover_pressed_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> - Text [Color] used when the [OptionButton] is being hovered and pressed. - </theme_item> - <theme_item name="font_outline_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> - The tint of text outline of the [OptionButton]. - </theme_item> - <theme_item name="font_pressed_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> - Text [Color] used when the [OptionButton] is being pressed. - </theme_item> <theme_item name="arrow_margin" data_type="constant" type="int" default="4"> The horizontal space between the arrow icon and the right edge of the button. </theme_item> - <theme_item name="h_separation" data_type="constant" type="int" default="4"> - The horizontal space between [OptionButton]'s icon and text. Negative values will be treated as [code]0[/code] when used. - </theme_item> <theme_item name="modulate_arrow" data_type="constant" type="int" default="0"> If different than [code]0[/code], the arrow icon will be modulated to the font color. </theme_item> - <theme_item name="outline_size" data_type="constant" type="int" default="0"> - The size of the text outline. - [b]Note:[/b] If using a font with [member FontFile.multichannel_signed_distance_field] enabled, its [member FontFile.msdf_pixel_range] must be set to at least [i]twice[/i] the value of [theme_item outline_size] for outline rendering to look correct. Otherwise, the outline may appear to be cut off earlier than intended. - </theme_item> - <theme_item name="font" data_type="font" type="Font"> - [Font] of the [OptionButton]'s text. - </theme_item> - <theme_item name="font_size" data_type="font_size" type="int"> - Font size of the [OptionButton]'s text. - </theme_item> <theme_item name="arrow" data_type="icon" type="Texture2D"> The arrow icon to be drawn on the right end of the button. </theme_item> - <theme_item name="disabled" data_type="style" type="StyleBox"> - [StyleBox] used when the [OptionButton] is disabled (for left-to-right layouts). - </theme_item> - <theme_item name="disabled_mirrored" data_type="style" type="StyleBox"> - [StyleBox] used when the [OptionButton] is disabled (for right-to-left layouts). - </theme_item> - <theme_item name="focus" data_type="style" type="StyleBox"> - [StyleBox] used when the [OptionButton] is focused. The [code]focus[/code] [StyleBox] is displayed [i]over[/i] the base [StyleBox], so a partially transparent [StyleBox] should be used to ensure the base [StyleBox] remains visible. A [StyleBox] that represents an outline or an underline works well for this purpose. To disable the focus visual effect, assign a [StyleBoxEmpty] resource. Note that disabling the focus visual effect will harm keyboard/controller navigation usability, so this is not recommended for accessibility reasons. - </theme_item> - <theme_item name="hover" data_type="style" type="StyleBox"> - [StyleBox] used when the [OptionButton] is being hovered (for left-to-right layouts). - </theme_item> - <theme_item name="hover_mirrored" data_type="style" type="StyleBox"> - [StyleBox] used when the [OptionButton] is being hovered (for right-to-left layouts). - </theme_item> - <theme_item name="normal" data_type="style" type="StyleBox"> - Default [StyleBox] for the [OptionButton] (for left-to-right layouts). - </theme_item> - <theme_item name="normal_mirrored" data_type="style" type="StyleBox"> - Default [StyleBox] for the [OptionButton] (for right-to-left layouts). - </theme_item> - <theme_item name="pressed" data_type="style" type="StyleBox"> - [StyleBox] used when the [OptionButton] is being pressed (for left-to-right layouts). - </theme_item> - <theme_item name="pressed_mirrored" data_type="style" type="StyleBox"> - [StyleBox] used when the [OptionButton] is being pressed (for right-to-left layouts). - </theme_item> </theme_items> </class> diff --git a/doc/classes/Popup.xml b/doc/classes/Popup.xml index 29b44a98f2..c435f3d291 100644 --- a/doc/classes/Popup.xml +++ b/doc/classes/Popup.xml @@ -23,4 +23,9 @@ </description> </signal> </signals> + <theme_items> + <theme_item name="panel" data_type="style" type="StyleBox"> + Default [StyleBox] for the [Popup]. + </theme_item> + </theme_items> </class> diff --git a/doc/classes/PopupMenu.xml b/doc/classes/PopupMenu.xml index d1c1304509..6293fcb309 100644 --- a/doc/classes/PopupMenu.xml +++ b/doc/classes/PopupMenu.xml @@ -691,12 +691,6 @@ <theme_item name="labeled_separator_right" data_type="style" type="StyleBox"> [StyleBox] for the right side of labeled separator. See [method add_separator]. </theme_item> - <theme_item name="panel" data_type="style" type="StyleBox"> - Default [StyleBox] of the [PopupMenu] items. - </theme_item> - <theme_item name="panel_disabled" data_type="style" type="StyleBox"> - [StyleBox] used when the [PopupMenu] item is disabled. - </theme_item> <theme_item name="separator" data_type="style" type="StyleBox"> [StyleBox] used for the separators. See [method add_separator]. </theme_item> diff --git a/doc/classes/PopupPanel.xml b/doc/classes/PopupPanel.xml index ad981116de..b86972e8af 100644 --- a/doc/classes/PopupPanel.xml +++ b/doc/classes/PopupPanel.xml @@ -8,9 +8,4 @@ </description> <tutorials> </tutorials> - <theme_items> - <theme_item name="panel" data_type="style" type="StyleBox"> - The background panel style of this [PopupPanel]. - </theme_item> - </theme_items> </class> diff --git a/doc/classes/ProgressBar.xml b/doc/classes/ProgressBar.xml index 595749f4b6..1102f0369a 100644 --- a/doc/classes/ProgressBar.xml +++ b/doc/classes/ProgressBar.xml @@ -37,9 +37,6 @@ <theme_item name="font_outline_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> The tint of text outline of the [ProgressBar]. </theme_item> - <theme_item name="font_shadow_color" data_type="color" type="Color" default="Color(0, 0, 0, 1)"> - The color of the text's shadow. - </theme_item> <theme_item name="outline_size" data_type="constant" type="int" default="0"> The size of the text outline. [b]Note:[/b] If using a font with [member FontFile.multichannel_signed_distance_field] enabled, its [member FontFile.msdf_pixel_range] must be set to at least [i]twice[/i] the value of [theme_item outline_size] for outline rendering to look correct. Otherwise, the outline may appear to be cut off earlier than intended. diff --git a/doc/classes/Resource.xml b/doc/classes/Resource.xml index be04ebd893..75c258253d 100644 --- a/doc/classes/Resource.xml +++ b/doc/classes/Resource.xml @@ -19,6 +19,21 @@ Override this method to return a custom [RID] when [method get_rid] is called. </description> </method> + <method name="_setup_local_to_scene" qualifiers="virtual"> + <return type="void" /> + <description> + Override this method to customize the newly duplicated resource created from [method PackedScene.instantiate], if the original's [member resource_local_to_scene] is set to [code]true[/code]. + [b]Example:[/b] Set a random [code]damage[/code] value to every local resource from an instantiated scene. + [codeblock] + extends Resource + + var damage = 0 + + func _setup_local_to_scene(): + damage = randi_range(10, 40) + [/codeblock] + </description> + </method> <method name="duplicate" qualifiers="const"> <return type="Resource" /> <param index="0" name="subresources" type="bool" default="false" /> @@ -58,8 +73,8 @@ <method name="setup_local_to_scene" is_deprecated="true"> <return type="void" /> <description> - Emits the [signal setup_local_to_scene_requested] signal. If [member resource_local_to_scene] is set to [code]true[/code], this method is called from [method PackedScene.instantiate] by the newly duplicated resource within the scene instance. - For most resources, this method performs no logic of its own. Custom behavior can be defined by connecting [signal setup_local_to_scene_requested] from a script, [b]not[/b] by overriding this method. + Calls [method _setup_local_to_scene]. If [member resource_local_to_scene] is set to [code]true[/code], this method is automatically called from [method PackedScene.instantiate] by the newly duplicated resource within the scene instance. + [i]Deprecated.[/i] This method should only be called internally. Override [method _setup_local_to_scene] instead. </description> </method> <method name="take_over_path"> @@ -90,9 +105,10 @@ [b]Note:[/b] This signal is not emitted automatically for properties of custom resources. If necessary, a setter needs to be created to emit the signal. </description> </signal> - <signal name="setup_local_to_scene_requested"> + <signal name="setup_local_to_scene_requested" is_deprecated="true"> <description> - Emitted by the newly duplicated resource with [member resource_local_to_scene] set to [code]true[/code], when the scene is instantiated. Custom behavior can be defined by connecting this signal. + Emitted by a newly duplicated resource with [member resource_local_to_scene] set to [code]true[/code]. + [i]Deprecated.[/i] This signal is only emitted when the resource is created. Override [method _setup_local_to_scene] instead. </description> </signal> </signals> diff --git a/doc/classes/ScrollBar.xml b/doc/classes/ScrollBar.xml index 0cbb60198b..e8d2753a9a 100644 --- a/doc/classes/ScrollBar.xml +++ b/doc/classes/ScrollBar.xml @@ -21,4 +21,41 @@ </description> </signal> </signals> + <theme_items> + <theme_item name="decrement" data_type="icon" type="Texture2D"> + Icon used as a button to scroll the [ScrollBar] left/up. Supports custom step using the [member ScrollBar.custom_step] property. + </theme_item> + <theme_item name="decrement_highlight" data_type="icon" type="Texture2D"> + Displayed when the mouse cursor hovers over the decrement button. + </theme_item> + <theme_item name="decrement_pressed" data_type="icon" type="Texture2D"> + Displayed when the decrement button is being pressed. + </theme_item> + <theme_item name="increment" data_type="icon" type="Texture2D"> + Icon used as a button to scroll the [ScrollBar] right/down. Supports custom step using the [member ScrollBar.custom_step] property. + </theme_item> + <theme_item name="increment_highlight" data_type="icon" type="Texture2D"> + Displayed when the mouse cursor hovers over the increment button. + </theme_item> + <theme_item name="increment_pressed" data_type="icon" type="Texture2D"> + Displayed when the increment button is being pressed. + </theme_item> + <theme_item name="grabber" data_type="style" type="StyleBox"> + Used as texture for the grabber, the draggable element representing current scroll. + </theme_item> + <theme_item name="grabber_highlight" data_type="style" type="StyleBox"> + Used when the mouse hovers over the grabber. + </theme_item> + <theme_item name="grabber_pressed" data_type="style" type="StyleBox"> + Used when the grabber is being dragged. + </theme_item> + <theme_item name="hscroll" data_type="style" type="StyleBox"> + </theme_item> + <theme_item name="scroll" data_type="style" type="StyleBox"> + Used as background of this [ScrollBar]. + </theme_item> + <theme_item name="scroll_focus" data_type="style" type="StyleBox"> + Used as background when the [ScrollBar] has the GUI focus. + </theme_item> + </theme_items> </class> diff --git a/doc/classes/Separator.xml b/doc/classes/Separator.xml index fe6f6858b7..d0535d450f 100644 --- a/doc/classes/Separator.xml +++ b/doc/classes/Separator.xml @@ -8,4 +8,12 @@ </description> <tutorials> </tutorials> + <theme_items> + <theme_item name="separation" data_type="constant" type="int" default="0"> + The size of the area covered by the separator. Effectively works like a minimum width/height. + </theme_item> + <theme_item name="separator" data_type="style" type="StyleBox"> + The style for the separator line. Works best with [StyleBoxLine] (remember to enable [member StyleBoxLine.vertical] for [VSeparator]). + </theme_item> + </theme_items> </class> diff --git a/doc/classes/Slider.xml b/doc/classes/Slider.xml index b946b6cedb..efb646b7ae 100644 --- a/doc/classes/Slider.xml +++ b/doc/classes/Slider.xml @@ -37,4 +37,33 @@ </description> </signal> </signals> + <theme_items> + <theme_item name="center_grabber" data_type="constant" type="int" default="0"> + Boolean constant. If [code]1[/code], the grabber texture size will be ignored and it will fit within slider's bounds based only on its center position. + </theme_item> + <theme_item name="grabber_offset" data_type="constant" type="int" default="0"> + Vertical/horizontal offset of the grabber. + </theme_item> + <theme_item name="grabber" data_type="icon" type="Texture2D"> + The texture for the grabber (the draggable element). + </theme_item> + <theme_item name="grabber_disabled" data_type="icon" type="Texture2D"> + The texture for the grabber when it's disabled. + </theme_item> + <theme_item name="grabber_highlight" data_type="icon" type="Texture2D"> + The texture for the grabber when it's focused. + </theme_item> + <theme_item name="tick" data_type="icon" type="Texture2D"> + The texture for the ticks, visible when [member Slider.tick_count] is greater than 0. + </theme_item> + <theme_item name="grabber_area" data_type="style" type="StyleBox"> + The background of the area to the left/bottom of the grabber. + </theme_item> + <theme_item name="grabber_area_highlight" data_type="style" type="StyleBox"> + The background of the area to the left/bottom of the grabber that displays when it's being hovered or focused. + </theme_item> + <theme_item name="slider" data_type="style" type="StyleBox"> + The background for the whole slider. Determines the height/width of the [code]grabber_area[/code]. + </theme_item> + </theme_items> </class> diff --git a/doc/classes/SplitContainer.xml b/doc/classes/SplitContainer.xml index 5078685cce..454a542cc8 100644 --- a/doc/classes/SplitContainer.xml +++ b/doc/classes/SplitContainer.xml @@ -61,6 +61,9 @@ <theme_item name="separation" data_type="constant" type="int" default="12"> The space between sides of the container. </theme_item> + <theme_item name="grabber" data_type="icon" type="Texture2D"> + The icon used for the grabber drawn in the middle area. + </theme_item> <theme_item name="h_grabber" data_type="icon" type="Texture2D"> The icon used for the grabber drawn in the middle area when [member vertical] is [code]false[/code]. </theme_item> diff --git a/doc/classes/TabBar.xml b/doc/classes/TabBar.xml index bfcb1a0e69..9b462b9f50 100644 --- a/doc/classes/TabBar.xml +++ b/doc/classes/TabBar.xml @@ -414,6 +414,7 @@ </theme_item> <theme_item name="tab_hovered" data_type="style" type="StyleBox"> The style of the currently hovered tab. Does not apply to the selected tab. + [b]Note:[/b] This style will be drawn with the same width as [theme_item tab_unselected] at minimum. </theme_item> <theme_item name="tab_selected" data_type="style" type="StyleBox"> The style of the currently selected tab. diff --git a/doc/classes/TabContainer.xml b/doc/classes/TabContainer.xml index 07f6dbd8c5..b08e075a23 100644 --- a/doc/classes/TabContainer.xml +++ b/doc/classes/TabContainer.xml @@ -318,6 +318,7 @@ </theme_item> <theme_item name="tab_hovered" data_type="style" type="StyleBox"> The style of the currently hovered tab. + [b]Note:[/b] This style will be drawn with the same width as [theme_item tab_unselected] at minimum. </theme_item> <theme_item name="tab_selected" data_type="style" type="StyleBox"> The style of the currently selected tab. diff --git a/doc/classes/VBoxContainer.xml b/doc/classes/VBoxContainer.xml index 38541859a6..d3ea94c0eb 100644 --- a/doc/classes/VBoxContainer.xml +++ b/doc/classes/VBoxContainer.xml @@ -10,9 +10,4 @@ <link title="Using Containers">$DOCS_URL/tutorials/ui/gui_containers.html</link> <link title="3D Voxel Demo">https://godotengine.org/asset-library/asset/676</link> </tutorials> - <theme_items> - <theme_item name="separation" data_type="constant" type="int" default="4"> - The vertical space between the [VBoxContainer]'s elements. - </theme_item> - </theme_items> </class> diff --git a/doc/classes/VFlowContainer.xml b/doc/classes/VFlowContainer.xml index dcec1a58d9..5c896da729 100644 --- a/doc/classes/VFlowContainer.xml +++ b/doc/classes/VFlowContainer.xml @@ -9,12 +9,4 @@ <tutorials> <link title="Using Containers">$DOCS_URL/tutorials/ui/gui_containers.html</link> </tutorials> - <theme_items> - <theme_item name="h_separation" data_type="constant" type="int" default="4"> - The horizontal separation of children nodes. - </theme_item> - <theme_item name="v_separation" data_type="constant" type="int" default="4"> - The vertical separation of children nodes. - </theme_item> - </theme_items> </class> diff --git a/doc/classes/VScrollBar.xml b/doc/classes/VScrollBar.xml index 7cb1a3d16d..d6a69a6bba 100644 --- a/doc/classes/VScrollBar.xml +++ b/doc/classes/VScrollBar.xml @@ -12,39 +12,4 @@ <member name="size_flags_horizontal" type="int" setter="set_h_size_flags" getter="get_h_size_flags" overrides="Control" enum="Control.SizeFlags" is_bitfield="true" default="0" /> <member name="size_flags_vertical" type="int" setter="set_v_size_flags" getter="get_v_size_flags" overrides="Control" enum="Control.SizeFlags" is_bitfield="true" default="1" /> </members> - <theme_items> - <theme_item name="decrement" data_type="icon" type="Texture2D"> - Icon used as a button to scroll the [ScrollBar] up. Supports custom step using the [member ScrollBar.custom_step] property. - </theme_item> - <theme_item name="decrement_highlight" data_type="icon" type="Texture2D"> - Displayed when the mouse cursor hovers over the decrement button. - </theme_item> - <theme_item name="decrement_pressed" data_type="icon" type="Texture2D"> - Displayed when the decrement button is being pressed. - </theme_item> - <theme_item name="increment" data_type="icon" type="Texture2D"> - Icon used as a button to scroll the [ScrollBar] down. Supports custom step using the [member ScrollBar.custom_step] property. - </theme_item> - <theme_item name="increment_highlight" data_type="icon" type="Texture2D"> - Displayed when the mouse cursor hovers over the increment button. - </theme_item> - <theme_item name="increment_pressed" data_type="icon" type="Texture2D"> - Displayed when the increment button is being pressed. - </theme_item> - <theme_item name="grabber" data_type="style" type="StyleBox"> - Used as texture for the grabber, the draggable element representing current scroll. - </theme_item> - <theme_item name="grabber_highlight" data_type="style" type="StyleBox"> - Used when the mouse hovers over the grabber. - </theme_item> - <theme_item name="grabber_pressed" data_type="style" type="StyleBox"> - Used when the grabber is being dragged. - </theme_item> - <theme_item name="scroll" data_type="style" type="StyleBox"> - Used as background of this [ScrollBar]. - </theme_item> - <theme_item name="scroll_focus" data_type="style" type="StyleBox"> - Used as background when the [ScrollBar] has the GUI focus. - </theme_item> - </theme_items> </class> diff --git a/doc/classes/VSeparator.xml b/doc/classes/VSeparator.xml index ffb4e76df8..57d1c9e704 100644 --- a/doc/classes/VSeparator.xml +++ b/doc/classes/VSeparator.xml @@ -8,12 +8,4 @@ </description> <tutorials> </tutorials> - <theme_items> - <theme_item name="separation" data_type="constant" type="int" default="4"> - The width of the area covered by the separator. Effectively works like a minimum width. - </theme_item> - <theme_item name="separator" data_type="style" type="StyleBox"> - The style for the separator line. Works best with [StyleBoxLine] (remember to enable [member StyleBoxLine.vertical]). - </theme_item> - </theme_items> </class> diff --git a/doc/classes/VSlider.xml b/doc/classes/VSlider.xml index 7475e5ff7e..eea5ba5060 100644 --- a/doc/classes/VSlider.xml +++ b/doc/classes/VSlider.xml @@ -12,33 +12,4 @@ <member name="size_flags_horizontal" type="int" setter="set_h_size_flags" getter="get_h_size_flags" overrides="Control" enum="Control.SizeFlags" is_bitfield="true" default="0" /> <member name="size_flags_vertical" type="int" setter="set_v_size_flags" getter="get_v_size_flags" overrides="Control" enum="Control.SizeFlags" is_bitfield="true" default="1" /> </members> - <theme_items> - <theme_item name="center_grabber" data_type="constant" type="int" default="0"> - Boolean constant. If [code]1[/code], the grabber texture size will be ignored and it will fit within slider's bounds based only on its center position. - </theme_item> - <theme_item name="grabber_offset" data_type="constant" type="int" default="0"> - Horizontal offset of the grabber. - </theme_item> - <theme_item name="grabber" data_type="icon" type="Texture2D"> - The texture for the grabber (the draggable element). - </theme_item> - <theme_item name="grabber_disabled" data_type="icon" type="Texture2D"> - The texture for the grabber when it's disabled. - </theme_item> - <theme_item name="grabber_highlight" data_type="icon" type="Texture2D"> - The texture for the grabber when it's focused. - </theme_item> - <theme_item name="tick" data_type="icon" type="Texture2D"> - The texture for the ticks, visible when [member Slider.tick_count] is greater than 0. - </theme_item> - <theme_item name="grabber_area" data_type="style" type="StyleBox"> - The background of the area below the grabber. - </theme_item> - <theme_item name="grabber_area_highlight" data_type="style" type="StyleBox"> - The background of the area below the grabber that displays when it's being hovered or focused. - </theme_item> - <theme_item name="slider" data_type="style" type="StyleBox"> - The background for the whole slider. Determines the width of the [code]grabber_area[/code]. - </theme_item> - </theme_items> </class> diff --git a/doc/classes/VSplitContainer.xml b/doc/classes/VSplitContainer.xml index 1e363d987c..5932ca7369 100644 --- a/doc/classes/VSplitContainer.xml +++ b/doc/classes/VSplitContainer.xml @@ -9,18 +9,4 @@ <tutorials> <link title="Using Containers">$DOCS_URL/tutorials/ui/gui_containers.html</link> </tutorials> - <theme_items> - <theme_item name="autohide" data_type="constant" type="int" default="1"> - Boolean value. If 1 ([code]true[/code]), the grabber will hide automatically when it isn't under the cursor. If 0 ([code]false[/code]), it's always visible. - </theme_item> - <theme_item name="minimum_grab_thickness" data_type="constant" type="int" default="6"> - The minimum thickness of the area users can click on to grab the splitting line. If [theme_item separation] or [theme_item grabber]'s thickness are too small, this ensure that the splitting line can still be dragged. - </theme_item> - <theme_item name="separation" data_type="constant" type="int" default="12"> - The space between sides of the container. - </theme_item> - <theme_item name="grabber" data_type="icon" type="Texture2D"> - The icon used for the grabber drawn in the middle area. - </theme_item> - </theme_items> </class> diff --git a/editor/doc_tools.cpp b/editor/doc_tools.cpp index e0b344fc19..ef3981fd3f 100644 --- a/editor/doc_tools.cpp +++ b/editor/doc_tools.cpp @@ -624,66 +624,47 @@ void DocTools::generate(bool p_basic_types) { // Theme items. { - List<StringName> l; + List<ThemeDB::ThemeItemBind> theme_items; + ThemeDB::get_singleton()->get_class_own_items(cname, &theme_items); + Ref<Theme> default_theme = ThemeDB::get_singleton()->get_default_theme(); - ThemeDB::get_singleton()->get_default_theme()->get_color_list(cname, &l); - for (const StringName &E : l) { + for (const ThemeDB::ThemeItemBind &theme_item : theme_items) { DocData::ThemeItemDoc tid; - tid.name = E; - tid.type = "Color"; - tid.data_type = "color"; - tid.default_value = DocData::get_default_value_string(ThemeDB::get_singleton()->get_default_theme()->get_color(E, cname)); - c.theme_properties.push_back(tid); - } - - l.clear(); - ThemeDB::get_singleton()->get_default_theme()->get_constant_list(cname, &l); - for (const StringName &E : l) { - DocData::ThemeItemDoc tid; - tid.name = E; - tid.type = "int"; - tid.data_type = "constant"; - tid.default_value = itos(ThemeDB::get_singleton()->get_default_theme()->get_constant(E, cname)); - c.theme_properties.push_back(tid); - } - - l.clear(); - ThemeDB::get_singleton()->get_default_theme()->get_font_list(cname, &l); - for (const StringName &E : l) { - DocData::ThemeItemDoc tid; - tid.name = E; - tid.type = "Font"; - tid.data_type = "font"; - c.theme_properties.push_back(tid); - } - - l.clear(); - ThemeDB::get_singleton()->get_default_theme()->get_font_size_list(cname, &l); - for (const StringName &E : l) { - DocData::ThemeItemDoc tid; - tid.name = E; - tid.type = "int"; - tid.data_type = "font_size"; - c.theme_properties.push_back(tid); - } + tid.name = theme_item.item_name; + + switch (theme_item.data_type) { + case Theme::DATA_TYPE_COLOR: + tid.type = "Color"; + tid.data_type = "color"; + break; + case Theme::DATA_TYPE_CONSTANT: + tid.type = "int"; + tid.data_type = "constant"; + break; + case Theme::DATA_TYPE_FONT: + tid.type = "Font"; + tid.data_type = "font"; + break; + case Theme::DATA_TYPE_FONT_SIZE: + tid.type = "int"; + tid.data_type = "font_size"; + break; + case Theme::DATA_TYPE_ICON: + tid.type = "Texture2D"; + tid.data_type = "icon"; + break; + case Theme::DATA_TYPE_STYLEBOX: + tid.type = "StyleBox"; + tid.data_type = "style"; + break; + case Theme::DATA_TYPE_MAX: + break; // Can't happen, but silences warning. + } - l.clear(); - ThemeDB::get_singleton()->get_default_theme()->get_icon_list(cname, &l); - for (const StringName &E : l) { - DocData::ThemeItemDoc tid; - tid.name = E; - tid.type = "Texture2D"; - tid.data_type = "icon"; - c.theme_properties.push_back(tid); - } + if (theme_item.data_type == Theme::DATA_TYPE_COLOR || theme_item.data_type == Theme::DATA_TYPE_CONSTANT) { + tid.default_value = DocData::get_default_value_string(default_theme->get_theme_item(theme_item.data_type, theme_item.item_name, cname)); + } - l.clear(); - ThemeDB::get_singleton()->get_default_theme()->get_stylebox_list(cname, &l); - for (const StringName &E : l) { - DocData::ThemeItemDoc tid; - tid.name = E; - tid.type = "StyleBox"; - tid.data_type = "style"; c.theme_properties.push_back(tid); } diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp index 5f25dde9d1..8aa58a5d7f 100644 --- a/editor/editor_themes.cpp +++ b/editor/editor_themes.cpp @@ -1130,7 +1130,6 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_stylebox("normal", "MenuBar", style_widget); theme->set_stylebox("hover", "MenuBar", style_widget_hover); theme->set_stylebox("pressed", "MenuBar", style_widget_pressed); - theme->set_stylebox("focus", "MenuBar", style_widget_focus); theme->set_stylebox("disabled", "MenuBar", style_widget_disabled); theme->set_color("font_color", "MenuBar", font_color); @@ -2330,7 +2329,6 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_color("completion_existing_color", "CodeEdit", EDITOR_GET("text_editor/theme/highlighting/completion_existing_color")); theme->set_color("completion_scroll_color", "CodeEdit", EDITOR_GET("text_editor/theme/highlighting/completion_scroll_color")); theme->set_color("completion_scroll_hovered_color", "CodeEdit", EDITOR_GET("text_editor/theme/highlighting/completion_scroll_hovered_color")); - theme->set_color("completion_font_color", "CodeEdit", EDITOR_GET("text_editor/theme/highlighting/completion_font_color")); theme->set_color("font_color", "CodeEdit", EDITOR_GET("text_editor/theme/highlighting/text_color")); theme->set_color("line_number_color", "CodeEdit", EDITOR_GET("text_editor/theme/highlighting/line_number_color")); theme->set_color("caret_color", "CodeEdit", EDITOR_GET("text_editor/theme/highlighting/caret_color")); diff --git a/editor/gui/editor_file_dialog.cpp b/editor/gui/editor_file_dialog.cpp index 8fb0c14e80..14d57cbd10 100644 --- a/editor/gui/editor_file_dialog.cpp +++ b/editor/gui/editor_file_dialog.cpp @@ -1619,6 +1619,7 @@ void EditorFileDialog::_bind_methods() { ClassDB::bind_method(D_METHOD("_thumbnail_result"), &EditorFileDialog::_thumbnail_result); ClassDB::bind_method(D_METHOD("set_disable_overwrite_warning", "disable"), &EditorFileDialog::set_disable_overwrite_warning); ClassDB::bind_method(D_METHOD("is_overwrite_warning_disabled"), &EditorFileDialog::is_overwrite_warning_disabled); + ClassDB::bind_method(D_METHOD("add_side_menu", "menu", "title"), &EditorFileDialog::add_side_menu, DEFVAL("")); ClassDB::bind_method(D_METHOD("invalidate"), &EditorFileDialog::invalidate); @@ -1712,6 +1713,25 @@ bool EditorFileDialog::are_previews_enabled() { return previews_enabled; } +void EditorFileDialog::add_side_menu(Control *p_menu, const String &p_title) { + // HSplitContainer has 3 children at maximum capacity, 1 of them is the SplitContainerDragger. + ERR_FAIL_COND_MSG(body_hsplit->get_child_count() > 2, "EditorFileDialog: Only one side menu can be added."); + // Everything for the side menu goes inside of a VBoxContainer. + VBoxContainer *side_vbox = memnew(VBoxContainer); + side_vbox->set_h_size_flags(Control::SIZE_EXPAND_FILL); + side_vbox->set_stretch_ratio(0.5); + body_hsplit->add_child(side_vbox); + // Add a Label to the VBoxContainer. + if (!p_title.is_empty()) { + Label *title_label = memnew(Label(p_title)); + title_label->set_theme_type_variation("HeaderSmall"); + side_vbox->add_child(title_label); + } + // Add the given menu to the VBoxContainer. + p_menu->set_v_size_flags(Control::SIZE_EXPAND_FILL); + side_vbox->add_child(p_menu); +} + EditorFileDialog::EditorFileDialog() { show_hidden_files = default_show_hidden_files; display_mode = default_display_mode; @@ -1739,6 +1759,7 @@ EditorFileDialog::EditorFileDialog() { } HBoxContainer *pathhb = memnew(HBoxContainer); + vbc->add_child(pathhb); dir_prev = memnew(Button); dir_prev->set_theme_type_variation("FlatButton"); @@ -1826,11 +1847,13 @@ EditorFileDialog::EditorFileDialog() { makedir->connect("pressed", callable_mp(this, &EditorFileDialog::_make_dir)); pathhb->add_child(makedir); - list_hb = memnew(HSplitContainer); + body_hsplit = memnew(HSplitContainer); + body_hsplit->set_v_size_flags(Control::SIZE_EXPAND_FILL); + vbc->add_child(body_hsplit); - vbc->add_child(pathhb); - vbc->add_child(list_hb); - list_hb->set_v_size_flags(Control::SIZE_EXPAND_FILL); + list_hb = memnew(HSplitContainer); + list_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL); + body_hsplit->add_child(list_hb); VSplitContainer *vsc = memnew(VSplitContainer); list_hb->add_child(vsc); diff --git a/editor/gui/editor_file_dialog.h b/editor/gui/editor_file_dialog.h index 923c7080c5..913b48a10b 100644 --- a/editor/gui/editor_file_dialog.h +++ b/editor/gui/editor_file_dialog.h @@ -103,6 +103,7 @@ private: PopupMenu *item_menu = nullptr; TextureRect *preview = nullptr; VBoxContainer *preview_vb = nullptr; + HSplitContainer *body_hsplit = nullptr; HSplitContainer *list_hb = nullptr; HBoxContainer *file_box = nullptr; LineEdit *file = nullptr; @@ -282,6 +283,8 @@ public: void set_previews_enabled(bool p_enabled); bool are_previews_enabled(); + void add_side_menu(Control *p_menu, const String &p_title = ""); + EditorFileDialog(); ~EditorFileDialog(); }; diff --git a/editor/icons/FileTree.svg b/editor/icons/FileTree.svg new file mode 100644 index 0000000000..995715c993 --- /dev/null +++ b/editor/icons/FileTree.svg @@ -0,0 +1 @@ +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m2 2v2h2v5h2v5h8v-2h-6v-3h6v-2h-8v-3h8v-2z" fill="#e0e0e0"/></svg> diff --git a/editor/import/scene_import_settings.cpp b/editor/import/scene_import_settings.cpp index c4e1261a9c..2ef5e89bf7 100644 --- a/editor/import/scene_import_settings.cpp +++ b/editor/import/scene_import_settings.cpp @@ -1556,6 +1556,7 @@ SceneImportSettings::SceneImportSettings() { Ref<StandardMaterial3D> selection_mat; selection_mat.instantiate(); selection_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + selection_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); selection_mat->set_albedo(Color(1, 0.8, 1.0)); Ref<SurfaceTool> st; @@ -1597,6 +1598,7 @@ SceneImportSettings::SceneImportSettings() { { collider_mat.instantiate(); collider_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + collider_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); collider_mat->set_albedo(Color(0.5, 0.5, 1.0)); } diff --git a/editor/plugins/gizmos/lightmap_gi_gizmo_plugin.cpp b/editor/plugins/gizmos/lightmap_gi_gizmo_plugin.cpp index b860f5c0ee..74e6e818c6 100644 --- a/editor/plugins/gizmos/lightmap_gi_gizmo_plugin.cpp +++ b/editor/plugins/gizmos/lightmap_gi_gizmo_plugin.cpp @@ -47,6 +47,7 @@ LightmapGIGizmoPlugin::LightmapGIGizmoPlugin() { mat->set_cull_mode(StandardMaterial3D::CULL_DISABLED); mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, false); + mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); add_material("lightmap_probe_material", mat); diff --git a/editor/plugins/gizmos/marker_3d_gizmo_plugin.cpp b/editor/plugins/gizmos/marker_3d_gizmo_plugin.cpp index fa2c95d8db..39ae020d53 100644 --- a/editor/plugins/gizmos/marker_3d_gizmo_plugin.cpp +++ b/editor/plugins/gizmos/marker_3d_gizmo_plugin.cpp @@ -85,6 +85,7 @@ Marker3DGizmoPlugin::Marker3DGizmoPlugin() { mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); + mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); Array d; diff --git a/editor/plugins/navigation_obstacle_3d_editor_plugin.cpp b/editor/plugins/navigation_obstacle_3d_editor_plugin.cpp index 4f5bc67c1b..9747ef4d48 100644 --- a/editor/plugins/navigation_obstacle_3d_editor_plugin.cpp +++ b/editor/plugins/navigation_obstacle_3d_editor_plugin.cpp @@ -546,6 +546,7 @@ NavigationObstacle3DEditor::NavigationObstacle3DEditor() { line_material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); line_material->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); line_material->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); + line_material->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); line_material->set_albedo(Color(1, 1, 1)); handle_material = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); @@ -554,6 +555,7 @@ NavigationObstacle3DEditor::NavigationObstacle3DEditor() { handle_material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); handle_material->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); handle_material->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); + handle_material->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); Ref<Texture2D> handle = EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("Editor3DHandle"), EditorStringName(EditorIcons)); handle_material->set_point_size(handle->get_width()); handle_material->set_texture(StandardMaterial3D::TEXTURE_ALBEDO, handle); diff --git a/editor/plugins/node_3d_editor_gizmos.cpp b/editor/plugins/node_3d_editor_gizmos.cpp index 0d3000a318..3bd786c04f 100644 --- a/editor/plugins/node_3d_editor_gizmos.cpp +++ b/editor/plugins/node_3d_editor_gizmos.cpp @@ -876,6 +876,7 @@ void EditorNode3DGizmoPlugin::create_material(const String &p_name, const Color material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); material->set_render_priority(StandardMaterial3D::RENDER_PRIORITY_MIN + 1); material->set_cull_mode(StandardMaterial3D::CULL_DISABLED); + material->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); if (p_use_vertex_color) { material->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); @@ -918,6 +919,7 @@ void EditorNode3DGizmoPlugin::create_icon_material(const String &p_name, const R icon->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); icon->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); icon->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); + icon->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); icon->set_cull_mode(StandardMaterial3D::CULL_DISABLED); icon->set_depth_draw_mode(StandardMaterial3D::DEPTH_DRAW_DISABLED); icon->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); @@ -947,6 +949,7 @@ void EditorNode3DGizmoPlugin::create_handle_material(const String &p_name, bool handle_material->set_albedo(Color(1, 1, 1)); handle_material->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); handle_material->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); + handle_material->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); handle_material->set_on_top_of_alpha(); if (p_billboard) { handle_material->set_billboard_mode(StandardMaterial3D::BILLBOARD_ENABLED); diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index 2eae48da97..d94ca9414a 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -5832,6 +5832,7 @@ void Node3DEditor::_generate_selection_boxes() { Ref<StandardMaterial3D> mat = memnew(StandardMaterial3D); mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); const Color selection_box_color = EDITOR_GET("editors/3d/selection_box_color"); mat->set_albedo(selection_box_color); mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); @@ -5840,6 +5841,7 @@ void Node3DEditor::_generate_selection_boxes() { Ref<StandardMaterial3D> mat_xray = memnew(StandardMaterial3D); mat_xray->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + mat_xray->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); mat_xray->set_flag(StandardMaterial3D::FLAG_DISABLE_DEPTH_TEST, true); mat_xray->set_albedo(selection_box_color * Color(1, 1, 1, 0.15)); mat_xray->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); @@ -6484,6 +6486,7 @@ void Node3DEditor::_init_indicators() { indicator_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); indicator_mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); indicator_mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); + indicator_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); indicator_mat->set_transparency(StandardMaterial3D::Transparency::TRANSPARENCY_ALPHA_DEPTH_PRE_PASS); Vector<Color> origin_colors; @@ -6541,7 +6544,7 @@ void Node3DEditor::_init_indicators() { shader_type spatial; -render_mode unshaded; +render_mode unshaded, fog_disabled; uniform bool orthogonal; uniform float grid_size; @@ -6638,6 +6641,7 @@ void fragment() { Ref<StandardMaterial3D> mat = memnew(StandardMaterial3D); mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); mat->set_on_top_of_alpha(); mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); mat->set_albedo(col); @@ -6722,6 +6726,7 @@ void fragment() { Ref<StandardMaterial3D> plane_mat = memnew(StandardMaterial3D); plane_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + plane_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); plane_mat->set_on_top_of_alpha(); plane_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); plane_mat->set_cull_mode(StandardMaterial3D::CULL_DISABLED); @@ -6782,7 +6787,7 @@ void fragment() { shader_type spatial; -render_mode unshaded, depth_test_disabled; +render_mode unshaded, depth_test_disabled, fog_disabled; uniform vec4 albedo; @@ -6790,14 +6795,14 @@ mat3 orthonormalize(mat3 m) { vec3 x = normalize(m[0]); vec3 y = normalize(m[1] - x * dot(x, m[1])); vec3 z = m[2] - x * dot(x, m[2]); - z = normalize(z - y * (dot(y,m[2]))); + z = normalize(z - y * (dot(y, m[2]))); return mat3(x,y,z); } void vertex() { mat3 mv = orthonormalize(mat3(MODELVIEW_MATRIX)); vec3 n = mv * VERTEX; - float orientation = dot(vec3(0, 0, -1), n); + float orientation = dot(vec3(0.0, 0.0, -1.0), n); if (orientation <= 0.005) { VERTEX += NORMAL * 0.02; } @@ -6832,7 +6837,7 @@ void fragment() { shader_type spatial; -render_mode unshaded, depth_test_disabled; +render_mode unshaded, depth_test_disabled, fog_disabled; uniform vec4 albedo; @@ -6840,16 +6845,16 @@ mat3 orthonormalize(mat3 m) { vec3 x = normalize(m[0]); vec3 y = normalize(m[1] - x * dot(x, m[1])); vec3 z = m[2] - x * dot(x, m[2]); - z = normalize(z - y * (dot(y,m[2]))); - return mat3(x,y,z); + z = normalize(z - y * (dot(y, m[2]))); + return mat3(x, y, z); } void vertex() { mat3 mv = orthonormalize(mat3(MODELVIEW_MATRIX)); mv = inverse(mv); - VERTEX += NORMAL*0.008; - vec3 camera_dir_local = mv * vec3(0,0,1); - vec3 camera_up_local = mv * vec3(0,1,0); + VERTEX += NORMAL * 0.008; + vec3 camera_dir_local = mv * vec3(0.0, 0.0, 1.0); + vec3 camera_up_local = mv * vec3(0.0, 1.0, 0.0); mat3 rotation_matrix = mat3(cross(camera_dir_local, camera_up_local), camera_up_local, camera_dir_local); VERTEX = rotation_matrix * VERTEX; } @@ -6944,6 +6949,7 @@ void fragment() { Ref<StandardMaterial3D> plane_mat = memnew(StandardMaterial3D); plane_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + plane_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); plane_mat->set_on_top_of_alpha(); plane_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); plane_mat->set_cull_mode(StandardMaterial3D::CULL_DISABLED); diff --git a/editor/plugins/polygon_3d_editor_plugin.cpp b/editor/plugins/polygon_3d_editor_plugin.cpp index c1b1240708..2ea251c455 100644 --- a/editor/plugins/polygon_3d_editor_plugin.cpp +++ b/editor/plugins/polygon_3d_editor_plugin.cpp @@ -561,6 +561,7 @@ Polygon3DEditor::Polygon3DEditor() { line_material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); line_material->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); line_material->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); + line_material->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); line_material->set_albedo(Color(1, 1, 1)); handle_material = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); @@ -569,6 +570,7 @@ Polygon3DEditor::Polygon3DEditor() { handle_material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); handle_material->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); handle_material->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); + handle_material->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); Ref<Texture2D> handle = EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("Editor3DHandle"), EditorStringName(EditorIcons)); handle_material->set_point_size(handle->get_width()); handle_material->set_texture(StandardMaterial3D::TEXTURE_ALBEDO, handle); diff --git a/editor/plugins/skeleton_3d_editor_plugin.cpp b/editor/plugins/skeleton_3d_editor_plugin.cpp index c5fb8a0d8d..e6bb5532a3 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.cpp +++ b/editor/plugins/skeleton_3d_editor_plugin.cpp @@ -916,24 +916,32 @@ Skeleton3DEditor::Skeleton3DEditor(EditorInspectorPluginSkeleton *e_plugin, Skel // Skeleton 3D gizmo handle shader. shader_type spatial; -render_mode unshaded, shadows_disabled, depth_draw_always; +render_mode unshaded, shadows_disabled, depth_draw_always, fog_disabled; + uniform sampler2D texture_albedo : source_color; -uniform float point_size : hint_range(0,128) = 32; +uniform float point_size : hint_range(0, 128) = 32; + void vertex() { if (!OUTPUT_IS_SRGB) { - COLOR.rgb = mix( pow((COLOR.rgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)), vec3(2.4)), COLOR.rgb* (1.0 / 12.92), lessThan(COLOR.rgb,vec3(0.04045)) ); + COLOR.rgb = mix(pow((COLOR.rgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)), vec3(2.4)), COLOR.rgb * (1.0 / 12.92), lessThan(COLOR.rgb, vec3(0.04045))); } + VERTEX = VERTEX; POSITION = PROJECTION_MATRIX * VIEW_MATRIX * MODEL_MATRIX * vec4(VERTEX.xyz, 1.0); - POSITION.z = mix(POSITION.z, 0, 0.999); + POSITION.z = mix(POSITION.z, 0.0, 0.999); POINT_SIZE = point_size; } + void fragment() { - vec4 albedo_tex = texture(texture_albedo,POINT_COORD); + vec4 albedo_tex = texture(texture_albedo, POINT_COORD); vec3 col = albedo_tex.rgb + COLOR.rgb; - col = vec3(min(col.r,1.0),min(col.g,1.0),min(col.b,1.0)); + col = vec3(min(col.r, 1.0), min(col.g, 1.0), min(col.b, 1.0)); ALBEDO = col; - if (albedo_tex.a < 0.5) { discard; } + + if (albedo_tex.a < 0.5) { + discard; + } + ALPHA = albedo_tex.a; } )"); @@ -1184,6 +1192,7 @@ Skeleton3DGizmoPlugin::Skeleton3DGizmoPlugin() { unselected_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); unselected_mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); unselected_mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); + unselected_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); selected_mat = Ref<ShaderMaterial>(memnew(ShaderMaterial)); selected_sh = Ref<Shader>(memnew(Shader)); diff --git a/main/main.cpp b/main/main.cpp index 57b0c16258..f0a05fcd63 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -225,6 +225,7 @@ static bool print_fps = false; #ifdef TOOLS_ENABLED static bool dump_gdextension_interface = false; static bool dump_extension_api = false; +static bool include_docs_in_extension_api_dump = false; static bool validate_extension_api = false; static String validate_extension_api_file; #endif @@ -521,7 +522,8 @@ void Main::print_help(const char *p_binary) { OS::get_singleton()->print(" --build-solutions Build the scripting solutions (e.g. for C# projects). Implies --editor and requires a valid project to edit.\n"); OS::get_singleton()->print(" --dump-gdextension-interface Generate GDExtension header file 'gdextension_interface.h' in the current folder. This file is the base file required to implement a GDExtension.\n"); OS::get_singleton()->print(" --dump-extension-api Generate JSON dump of the Godot API for GDExtension bindings named 'extension_api.json' in the current folder.\n"); - OS::get_singleton()->print(" --validate-extension-api <path> Validate an extension API file dumped (with the option above) from a previous version of the engine to ensure API compatibility. If incompatibilities or errors are detected, the return code will be non zero.\n"); + OS::get_singleton()->print(" --dump-extension-api-with-docs Generate JSON dump of the Godot API like the previous option, but including documentation.\n"); + OS::get_singleton()->print(" --validate-extension-api <path> Validate an extension API file dumped (with one of the two previous options) from a previous version of the engine to ensure API compatibility. If incompatibilities or errors are detected, the return code will be non zero.\n"); OS::get_singleton()->print(" --benchmark Benchmark the run time and print it to console.\n"); OS::get_singleton()->print(" --benchmark-file <path> Benchmark the run time and save it to a given file in JSON format. The path should be absolute.\n"); #ifdef TESTS_ENABLED @@ -1255,6 +1257,17 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph // run the project instead of a cmdline tool. // Needs full refactoring to fix properly. main_args.push_back(I->get()); + } else if (I->get() == "--dump-extension-api-with-docs") { + // Register as an editor instance to use low-end fallback if relevant. + editor = true; + cmdline_tool = true; + dump_extension_api = true; + include_docs_in_extension_api_dump = true; + print_line("Dumping Extension API including documentation"); + // Hack. Not needed but otherwise we end up detecting that this should + // run the project instead of a cmdline tool. + // Needs full refactoring to fix properly. + main_args.push_back(I->get()); } else if (I->get() == "--validate-extension-api") { // Register as an editor instance to use low-end fallback if relevant. editor = true; @@ -2921,7 +2934,7 @@ bool Main::start() { } if (dump_extension_api) { - GDExtensionAPIDump::generate_extension_json_file("extension_api.json"); + GDExtensionAPIDump::generate_extension_json_file("extension_api.json", include_docs_in_extension_api_dump); } if (dump_gdextension_interface || dump_extension_api) { diff --git a/misc/extension_api_validation/4.1-stable.expected b/misc/extension_api_validation/4.1-stable.expected index 376dfb145c..0fb834bbbf 100644 --- a/misc/extension_api_validation/4.1-stable.expected +++ b/misc/extension_api_validation/4.1-stable.expected @@ -198,3 +198,10 @@ GH-80410 Validate extension JSON: Error: Field 'classes/RichTextLabel/methods/add_image/arguments': size changed value in new API, from 6 to 10. Added optional argument. Compatibility method registered. + + +GH-82403 +-------- +Validate extension JSON: Error: Field 'native_structures/PhysicsServer3DExtensionRayResult': format changed value in new API, from "Vector3 position;Vector3 normal;RID rid;ObjectID collider_id;Object *collider;int shape" to "Vector3 position;Vector3 normal;RID rid;ObjectID collider_id;Object *collider;int shape;int face_index". + +Added/moved face_index field (introduced in GH-71233) to end of struct. Should still be compatible with 4.1. diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 55bb99133a..02b6af1e87 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -248,7 +248,7 @@ Error GDScriptAnalyzer::check_native_member_name_conflict(const StringName &p_me return ERR_PARSE_ERROR; } - if (GDScriptParser::get_builtin_type(p_member_name) != Variant::VARIANT_MAX) { + if (GDScriptParser::get_builtin_type(p_member_name) < Variant::VARIANT_MAX) { push_error(vformat(R"(The member "%s" cannot have the same name as a builtin type.)", p_member_name), p_member_node); return ERR_PARSE_ERROR; } @@ -673,11 +673,6 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type return bad_type; } result.kind = GDScriptParser::DataType::VARIANT; - } else if (first == SNAME("Object")) { - // Object is treated like a native type, not a built-in. - result.kind = GDScriptParser::DataType::NATIVE; - result.builtin_type = Variant::OBJECT; - result.native_type = SNAME("Object"); } else if (GDScriptParser::get_builtin_type(first) < Variant::VARIANT_MAX) { // Built-in types. if (p_type->type_chain.size() > 1) { @@ -1708,7 +1703,7 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * } parent_signature += ") -> "; - const String return_type = parent_return_type.is_hard_type() ? parent_return_type.to_string() : "Variant"; + const String return_type = parent_return_type.to_string_strict(); if (return_type == "null") { parent_signature += "void"; } else { @@ -2899,19 +2894,20 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a if (!p_call->is_super && callee_type == GDScriptParser::Node::IDENTIFIER) { // Call to name directly. StringName function_name = p_call->function_name; - Variant::Type builtin_type = GDScriptParser::get_builtin_type(function_name); + if (function_name == SNAME("Object")) { + push_error(R"*(Invalid constructor "Object()", use "Object.new()" instead.)*", p_call); + p_call->set_datatype(call_type); + return; + } + + Variant::Type builtin_type = GDScriptParser::get_builtin_type(function_name); if (builtin_type < Variant::VARIANT_MAX) { // Is a builtin constructor. call_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; call_type.kind = GDScriptParser::DataType::BUILTIN; call_type.builtin_type = builtin_type; - if (builtin_type == Variant::OBJECT) { - call_type.kind = GDScriptParser::DataType::NATIVE; - call_type.native_type = function_name; // "Object". - } - bool safe_to_fold = true; switch (builtin_type) { // Those are stored by reference so not suited for compile-time construction. @@ -2947,7 +2943,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a switch (err.error) { case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: - push_error(vformat(R"(Invalid argument for %s constructor: argument %d should be "%s" but is "%s".)", Variant::get_type_name(builtin_type), err.argument + 1, + push_error(vformat(R"*(Invalid argument for "%s()" constructor: argument %d should be "%s" but is "%s".)*", Variant::get_type_name(builtin_type), err.argument + 1, Variant::get_type_name(Variant::Type(err.expected)), p_call->arguments[err.argument]->get_datatype().to_string()), p_call->arguments[err.argument]); break; @@ -2963,10 +2959,10 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a push_error(vformat(R"(No constructor of "%s" matches the signature "%s".)", Variant::get_type_name(builtin_type), signature), p_call->callee); } break; case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS: - push_error(vformat(R"(Too many arguments for %s constructor. Received %d but expected %d.)", Variant::get_type_name(builtin_type), p_call->arguments.size(), err.expected), p_call); + push_error(vformat(R"*(Too many arguments for "%s()" constructor. Received %d but expected %d.)*", Variant::get_type_name(builtin_type), p_call->arguments.size(), err.expected), p_call); break; case Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS: - push_error(vformat(R"(Too few arguments for %s constructor. Received %d but expected %d.)", Variant::get_type_name(builtin_type), p_call->arguments.size(), err.expected), p_call); + push_error(vformat(R"*(Too few arguments for "%s()" constructor. Received %d but expected %d.)*", Variant::get_type_name(builtin_type), p_call->arguments.size(), err.expected), p_call); break; case Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL: case Callable::CallError::CALL_ERROR_METHOD_NOT_CONST: @@ -2977,21 +2973,27 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a break; } } else { - // TODO: Check constructors without constants. - // If there's one argument, try to use copy constructor (those aren't explicitly defined). if (p_call->arguments.size() == 1) { GDScriptParser::DataType arg_type = p_call->arguments[0]->get_datatype(); - if (arg_type.is_variant()) { - mark_node_unsafe(p_call->arguments[0]); - } else { + if (arg_type.is_hard_type() && !arg_type.is_variant()) { if (arg_type.kind == GDScriptParser::DataType::BUILTIN && arg_type.builtin_type == builtin_type) { // Okay. p_call->set_datatype(call_type); return; } + } else { +#ifdef DEBUG_ENABLED + mark_node_unsafe(p_call); + // We don't know what type was expected since constructors support overloads. + // TODO: Improve this by checking for matching candidates? + parser->push_warning(p_call->arguments[0], GDScriptWarning::UNSAFE_CALL_ARGUMENT, "1", function_name, "<unknown type>", "Variant"); +#endif + p_call->set_datatype(call_type); + return; } } + List<MethodInfo> constructors; Variant::get_constructor_list(builtin_type, &constructors); bool match = false; @@ -3008,14 +3010,14 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a for (int i = 0; i < p_call->arguments.size(); i++) { GDScriptParser::DataType par_type = type_from_property(info.arguments[i], true); - - if (!is_type_compatible(par_type, p_call->arguments[i]->get_datatype(), true)) { + GDScriptParser::DataType arg_type = p_call->arguments[i]->get_datatype(); + if (!is_type_compatible(par_type, arg_type, true)) { types_match = false; break; #ifdef DEBUG_ENABLED } else { - if (par_type.builtin_type == Variant::INT && p_call->arguments[i]->get_datatype().builtin_type == Variant::FLOAT && builtin_type != Variant::INT) { - parser->push_warning(p_call, GDScriptWarning::NARROWING_CONVERSION, p_call->function_name); + if (par_type.builtin_type == Variant::INT && arg_type.builtin_type == Variant::FLOAT && builtin_type != Variant::INT) { + parser->push_warning(p_call, GDScriptWarning::NARROWING_CONVERSION, function_name); } #endif } @@ -3023,9 +3025,19 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a if (types_match) { for (int i = 0; i < p_call->arguments.size(); i++) { + GDScriptParser::DataType par_type = type_from_property(info.arguments[i], true); if (p_call->arguments[i]->is_constant) { - update_const_expression_builtin_type(p_call->arguments[i], type_from_property(info.arguments[i], true), "pass"); + update_const_expression_builtin_type(p_call->arguments[i], par_type, "pass"); + } +#ifdef DEBUG_ENABLED + if (!(par_type.is_variant() && par_type.is_hard_type())) { + GDScriptParser::DataType arg_type = p_call->arguments[i]->get_datatype(); + if (arg_type.is_variant() || !arg_type.is_hard_type() || !is_type_compatible(arg_type, par_type, true)) { + mark_node_unsafe(p_call); + parser->push_warning(p_call->arguments[i], GDScriptWarning::UNSAFE_CALL_ARGUMENT, itos(i + 1), function_name, par_type.to_string(), arg_type.to_string_strict()); + } } +#endif } match = true; call_type = type_from_property(info.return_val); @@ -3331,8 +3343,8 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a #else push_error(vformat(R"*(Function "%s()" not found in base %s.)*", p_call->function_name, base_name), p_call->is_super ? p_call : p_call->callee); #endif // SUGGEST_GODOT4_RENAMES - } else if (!found && (!p_call->is_super && base_type.is_hard_type() && base_type.kind == GDScriptParser::DataType::NATIVE && base_type.is_meta_type)) { - push_error(vformat(R"*(Static function "%s()" not found in base "%s".)*", p_call->function_name, base_type.native_type), p_call); + } else if (!found && (!p_call->is_super && base_type.is_hard_type() && base_type.is_meta_type)) { + push_error(vformat(R"*(Static function "%s()" not found in base "%s".)*", p_call->function_name, base_type.to_string()), p_call); } } @@ -3820,6 +3832,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident #endif // Not a local, so check members. + if (!found_source) { reduce_identifier_from_base(p_identifier); if (p_identifier->source != GDScriptParser::IdentifierNode::UNDEFINED_SOURCE || p_identifier->get_datatype().is_set()) { @@ -3872,10 +3885,10 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident StringName name = p_identifier->name; p_identifier->source = GDScriptParser::IdentifierNode::UNDEFINED_SOURCE; - // Check globals. We make an exception for Variant::OBJECT because it's the base class for - // non-builtin types so we allow doing e.g. Object.new() + // Not a local or a member, so check globals. + Variant::Type builtin_type = GDScriptParser::get_builtin_type(name); - if (builtin_type != Variant::OBJECT && builtin_type < Variant::VARIANT_MAX) { + if (builtin_type < Variant::VARIANT_MAX) { if (can_be_builtin) { p_identifier->set_datatype(make_builtin_meta_type(builtin_type)); return; @@ -5003,21 +5016,28 @@ void GDScriptAnalyzer::validate_call_arg(const List<GDScriptParser::DataType> &p GDScriptParser::DataType arg_type = p_call->arguments[i]->get_datatype(); if (arg_type.is_variant() || !arg_type.is_hard_type()) { +#ifdef DEBUG_ENABLED // Argument can be anything, so this is unsafe (unless the parameter is a hard variant). if (!(par_type.is_hard_type() && par_type.is_variant())) { mark_node_unsafe(p_call->arguments[i]); + parser->push_warning(p_call->arguments[i], GDScriptWarning::UNSAFE_CALL_ARGUMENT, itos(i + 1), p_call->function_name, par_type.to_string(), arg_type.to_string_strict()); } +#endif } else if (par_type.is_hard_type() && !is_type_compatible(par_type, arg_type, true)) { - // Supertypes are acceptable for dynamic compliance, but it's unsafe. - mark_node_unsafe(p_call); if (!is_type_compatible(arg_type, par_type)) { push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be "%s" but is "%s".)*", p_call->function_name, i + 1, par_type.to_string(), arg_type.to_string()), p_call->arguments[i]); +#ifdef DEBUG_ENABLED + } else { + // Supertypes are acceptable for dynamic compliance, but it's unsafe. + mark_node_unsafe(p_call); + parser->push_warning(p_call->arguments[i], GDScriptWarning::UNSAFE_CALL_ARGUMENT, itos(i + 1), p_call->function_name, par_type.to_string(), arg_type.to_string_strict()); +#endif } #ifdef DEBUG_ENABLED } else if (par_type.kind == GDScriptParser::DataType::BUILTIN && par_type.builtin_type == Variant::INT && arg_type.kind == GDScriptParser::DataType::BUILTIN && arg_type.builtin_type == Variant::FLOAT) { - parser->push_warning(p_call, GDScriptWarning::NARROWING_CONVERSION, p_call->function_name); + parser->push_warning(p_call->arguments[i], GDScriptWarning::NARROWING_CONVERSION, p_call->function_name); #endif } } @@ -5049,7 +5069,7 @@ void GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_identifier String class_path = ScriptServer::get_global_class_path(name).get_file(); parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, vformat(R"(global class defined in "%s")", class_path)); return; - } else if (GDScriptParser::get_builtin_type(name) != Variant::VARIANT_MAX) { + } else if (GDScriptParser::get_builtin_type(name) < Variant::VARIANT_MAX) { parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, "built-in type"); return; } diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 97e02ac716..1278090c57 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -612,11 +612,8 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code arguments.push_back(arg); } - if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && GDScriptParser::get_builtin_type(call->function_name) != Variant::VARIANT_MAX) { - // Construct a built-in type. - Variant::Type vtype = GDScriptParser::get_builtin_type(static_cast<GDScriptParser::IdentifierNode *>(call->callee)->name); - - gen->write_construct(result, vtype, arguments); + if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && GDScriptParser::get_builtin_type(call->function_name) < Variant::VARIANT_MAX) { + gen->write_construct(result, GDScriptParser::get_builtin_type(call->function_name), arguments); } else if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && Variant::has_utility_function(call->function_name)) { // Variant utility function. gen->write_call_utility(result, call->function_name, arguments); diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 0801582dbd..c059d8b2fa 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -52,11 +52,18 @@ #include "editor/editor_settings.h" #endif +// This function is used to determine that a type is "built-in" as opposed to native +// and custom classes. So `Variant::NIL` and `Variant::OBJECT` are excluded: +// `Variant::NIL` - `null` is literal, not a type. +// `Variant::OBJECT` - `Object` should be treated as a class, not as a built-in type. static HashMap<StringName, Variant::Type> builtin_types; Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) { - if (builtin_types.is_empty()) { - for (int i = 1; i < Variant::VARIANT_MAX; i++) { - builtin_types[Variant::get_type_name((Variant::Type)i)] = (Variant::Type)i; + if (unlikely(builtin_types.is_empty())) { + for (int i = 0; i < Variant::VARIANT_MAX; i++) { + Variant::Type type = (Variant::Type)i; + if (type != Variant::NIL && type != Variant::OBJECT) { + builtin_types[Variant::get_type_name(type)] = type; + } } } diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 3bd3696e99..9b50c34ed2 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -149,6 +149,7 @@ public: _FORCE_INLINE_ bool is_hard_type() const { return type_source > INFERRED; } String to_string() const; + _FORCE_INLINE_ String to_string_strict() const { return is_hard_type() ? to_string() : "Variant"; } PropertyInfo to_property_info(const String &p_name) const; _FORCE_INLINE_ void set_container_element_type(const DataType &p_type) { @@ -1530,7 +1531,7 @@ public: bool is_tool() const { return _is_tool; } ClassNode *find_class(const String &p_qualified_name) const; bool has_class(const GDScriptParser::ClassNode *p_class) const; - static Variant::Type get_builtin_type(const StringName &p_type); + static Variant::Type get_builtin_type(const StringName &p_type); // Excluding `Variant::NIL` and `Variant::OBJECT`. CompletionContext get_completion_context() const { return completion_context; } CompletionCall get_completion_call() const { return completion_call; } diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp index a0078f84d6..cabac07ef9 100644 --- a/modules/gdscript/gdscript_warning.cpp +++ b/modules/gdscript/gdscript_warning.cpp @@ -108,7 +108,7 @@ String GDScriptWarning::get_message() const { return vformat(R"(The value is cast to "%s" but has an unknown type.)", symbols[0]); case UNSAFE_CALL_ARGUMENT: CHECK_SYMBOLS(4); - return vformat(R"*(The argument %s of the function "%s()" requires a the subtype "%s" but the supertype "%s" was provided.)*", symbols[0], symbols[1], symbols[2], symbols[3]); + return vformat(R"*(The argument %s of the function "%s()" requires the subtype "%s" but the supertype "%s" was provided.)*", symbols[0], symbols[1], symbols[2], symbols[3]); case UNSAFE_VOID_RETURN: CHECK_SYMBOLS(2); return vformat(R"*(The method "%s()" returns "void" but it's trying to return a call to "%s()" that can't be ensured to also be "void".)*", symbols[0], symbols[1]); diff --git a/modules/gdscript/tests/scripts/analyzer/errors/call_not_existing_static_method.gd b/modules/gdscript/tests/scripts/analyzer/errors/call_not_existing_static_method.gd new file mode 100644 index 0000000000..87d1b9ea18 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/call_not_existing_static_method.gd @@ -0,0 +1,7 @@ +# GH-73283 + +class MyClass: + pass + +func test(): + MyClass.not_existing_method() diff --git a/modules/gdscript/tests/scripts/analyzer/errors/call_not_existing_static_method.out b/modules/gdscript/tests/scripts/analyzer/errors/call_not_existing_static_method.out new file mode 100644 index 0000000000..7340058dd4 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/call_not_existing_static_method.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Static function "not_existing_method()" not found in base "MyClass". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/object_invalid_constructor.gd b/modules/gdscript/tests/scripts/analyzer/errors/object_invalid_constructor.gd new file mode 100644 index 0000000000..1600c3001f --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/object_invalid_constructor.gd @@ -0,0 +1,4 @@ +# GH-73213 + +func test(): + print(Object()) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/object_invalid_constructor.out b/modules/gdscript/tests/scripts/analyzer/errors/object_invalid_constructor.out new file mode 100644 index 0000000000..27668fcd48 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/object_invalid_constructor.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Invalid constructor "Object()", use "Object.new()" instead. diff --git a/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd b/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd index b447180ea8..d0f895d784 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd @@ -23,6 +23,7 @@ func test() -> void: typed = variant() inferred = variant() + @warning_ignore("unsafe_call_argument") # TODO: Hard vs Weak vs Unknown. param_weak(typed) param_typed(typed) param_inferred(typed) diff --git a/modules/gdscript/tests/scripts/analyzer/features/null_initializer.gd b/modules/gdscript/tests/scripts/analyzer/features/null_initializer.gd index 5a413e2015..08e7dc590e 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/null_initializer.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/null_initializer.gd @@ -6,10 +6,12 @@ var prop = null func check_arg(arg = null) -> void: if arg != null: + @warning_ignore("unsafe_call_argument") print(check(arg)) func check_recur() -> void: if recur != null: + @warning_ignore("unsafe_call_argument") print(check(recur)) else: recur = 1 @@ -22,11 +24,13 @@ func test() -> void: if prop == null: set('prop', 1) + @warning_ignore("unsafe_call_argument") print(check(prop)) set('prop', null) var loop = null while loop != 2: if loop != null: + @warning_ignore("unsafe_call_argument") print(check(loop)) loop = 1 if loop == null else 2 diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.gd b/modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.gd index 849df0921e..c1776fe1b4 100644 --- a/modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.gd +++ b/modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.gd @@ -14,4 +14,5 @@ func test(): func do_add_node(): var node = Node.new() node.name = "Node" + @warning_ignore("unsafe_call_argument") add_child(node) diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.gd b/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.gd new file mode 100644 index 0000000000..573060ae0f --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.gd @@ -0,0 +1,37 @@ +func variant_func(x: Variant) -> void: + print(x) + +func int_func(x: int) -> void: + print(x) + +func float_func(x: float) -> void: + print(x) + +# We don't want to execute it because of errors, just analyze. +func no_exec_test(): + var untyped_int = 42 + var untyped_string = "abc" + var variant_int: Variant = 42 + var variant_string: Variant = "abc" + var typed_int: int = 42 + + variant_func(untyped_int) # No warning. + variant_func(untyped_string) # No warning. + variant_func(variant_int) # No warning. + variant_func(variant_string) # No warning. + variant_func(typed_int) # No warning. + + int_func(untyped_int) + int_func(untyped_string) + int_func(variant_int) + int_func(variant_string) + int_func(typed_int) # No warning. + + float_func(untyped_int) + float_func(untyped_string) + float_func(variant_int) + float_func(variant_string) + float_func(typed_int) # No warning. + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.out b/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.out new file mode 100644 index 0000000000..b8fcb67158 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.out @@ -0,0 +1,33 @@ +GDTEST_OK +>> WARNING +>> Line: 24 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "int_func()" requires the subtype "int" but the supertype "Variant" was provided. +>> WARNING +>> Line: 25 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "int_func()" requires the subtype "int" but the supertype "Variant" was provided. +>> WARNING +>> Line: 26 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "int_func()" requires the subtype "int" but the supertype "Variant" was provided. +>> WARNING +>> Line: 27 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "int_func()" requires the subtype "int" but the supertype "Variant" was provided. +>> WARNING +>> Line: 30 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "float_func()" requires the subtype "float" but the supertype "Variant" was provided. +>> WARNING +>> Line: 31 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "float_func()" requires the subtype "float" but the supertype "Variant" was provided. +>> WARNING +>> Line: 32 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "float_func()" requires the subtype "float" but the supertype "Variant" was provided. +>> WARNING +>> Line: 33 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "float_func()" requires the subtype "float" but the supertype "Variant" was provided. diff --git a/modules/gdscript/tests/scripts/parser/features/dollar_and_percent_get_node.gd b/modules/gdscript/tests/scripts/parser/features/dollar_and_percent_get_node.gd index f04f4de08d..19f6e08285 100644 --- a/modules/gdscript/tests/scripts/parser/features/dollar_and_percent_get_node.gd +++ b/modules/gdscript/tests/scripts/parser/features/dollar_and_percent_get_node.gd @@ -3,27 +3,32 @@ extends Node func test(): var child = Node.new() child.name = "Child" + @warning_ignore("unsafe_call_argument") add_child(child) child.owner = self var hey = Node.new() hey.name = "Hey" + @warning_ignore("unsafe_call_argument") child.add_child(hey) hey.owner = self hey.unique_name_in_owner = true var fake_hey = Node.new() fake_hey.name = "Hey" + @warning_ignore("unsafe_call_argument") add_child(fake_hey) fake_hey.owner = self var sub_child = Node.new() sub_child.name = "SubChild" + @warning_ignore("unsafe_call_argument") hey.add_child(sub_child) sub_child.owner = self var howdy = Node.new() howdy.name = "Howdy" + @warning_ignore("unsafe_call_argument") sub_child.add_child(howdy) howdy.owner = self howdy.unique_name_in_owner = true diff --git a/modules/gdscript/tests/scripts/parser/features/dollar_node_paths.gd b/modules/gdscript/tests/scripts/parser/features/dollar_node_paths.gd index 8ba558e91d..3d9404b20b 100644 --- a/modules/gdscript/tests/scripts/parser/features/dollar_node_paths.gd +++ b/modules/gdscript/tests/scripts/parser/features/dollar_node_paths.gd @@ -5,9 +5,11 @@ func test(): # Create the required node structure. var hello = Node.new() hello.name = "Hello" + @warning_ignore("unsafe_call_argument") add_child(hello) var world = Node.new() world.name = "World" + @warning_ignore("unsafe_call_argument") hello.add_child(world) # All the ways of writing node paths below with the `$` operator are valid. diff --git a/modules/gdscript/tests/scripts/parser/features/lambda_ends_with_new_line.gd b/modules/gdscript/tests/scripts/parser/features/lambda_ends_with_new_line.gd index df6001c7e2..f16c768f7f 100644 --- a/modules/gdscript/tests/scripts/parser/features/lambda_ends_with_new_line.gd +++ b/modules/gdscript/tests/scripts/parser/features/lambda_ends_with_new_line.gd @@ -26,6 +26,7 @@ func test(): if true: (v as Callable).call() print() + @warning_ignore("unsafe_call_argument") other(v) print() diff --git a/modules/gdscript/tests/scripts/parser/features/nested_function_calls.gd b/modules/gdscript/tests/scripts/parser/features/nested_function_calls.gd index 59cdc7d6c2..31de73813f 100644 --- a/modules/gdscript/tests/scripts/parser/features/nested_function_calls.gd +++ b/modules/gdscript/tests/scripts/parser/features/nested_function_calls.gd @@ -2,4 +2,5 @@ func foo(x): return x + 1 func test(): + @warning_ignore("unsafe_call_argument") print(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(0))))))))))))))))))))))))) diff --git a/modules/gdscript/tests/scripts/parser/features/super.gd b/modules/gdscript/tests/scripts/parser/features/super.gd index f5ae2a74a7..33accd92a9 100644 --- a/modules/gdscript/tests/scripts/parser/features/super.gd +++ b/modules/gdscript/tests/scripts/parser/features/super.gd @@ -36,6 +36,7 @@ class SayNothing extends Say: print("howdy, see above") func say(name): + @warning_ignore("unsafe_call_argument") super(name + " super'd") print(prefix, " say nothing... or not? ", name) diff --git a/modules/gdscript/tests/scripts/parser/features/unicode_identifiers.gd b/modules/gdscript/tests/scripts/parser/features/unicode_identifiers.gd index 523959a016..20cc0cee2e 100644 --- a/modules/gdscript/tests/scripts/parser/features/unicode_identifiers.gd +++ b/modules/gdscript/tests/scripts/parser/features/unicode_identifiers.gd @@ -29,6 +29,7 @@ func test(): const d = 1.1 _process(d) + @warning_ignore("unsafe_call_argument") print(is_equal_approx(ㄥ, PI + (d * PI))) func _process(Δ: float) -> void: diff --git a/modules/gdscript/tests/scripts/runtime/features/object_constructor.gd b/modules/gdscript/tests/scripts/runtime/features/object_constructor.gd new file mode 100644 index 0000000000..b875efef56 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/object_constructor.gd @@ -0,0 +1,6 @@ +# GH-73213 + +func test(): + var object := Object.new() # Not `Object()`. + print(object.get_class()) + object.free() diff --git a/modules/gdscript/tests/scripts/runtime/features/object_constructor.out b/modules/gdscript/tests/scripts/runtime/features/object_constructor.out new file mode 100644 index 0000000000..3673881576 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/object_constructor.out @@ -0,0 +1,2 @@ +GDTEST_OK +Object diff --git a/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd b/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd index 2f55059334..fd1460a48f 100644 --- a/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd +++ b/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd @@ -12,6 +12,7 @@ func test(): print("end") func test_construct(v, f): + @warning_ignore("unsafe_call_argument") Vector2(v, v) # Built-in type construct. assert(not f) # Test unary operator reading from `nil`. diff --git a/modules/gdscript/tests/scripts/runtime/features/static_variables.gd b/modules/gdscript/tests/scripts/runtime/features/static_variables.gd index 8da8bb7e53..7fa76ca4b0 100644 --- a/modules/gdscript/tests/scripts/runtime/features/static_variables.gd +++ b/modules/gdscript/tests/scripts/runtime/features/static_variables.gd @@ -44,6 +44,7 @@ func test(): @warning_ignore("unsafe_method_access") var path = get_script().get_path().get_base_dir() + @warning_ignore("unsafe_call_argument") var other = load(path + "/static_variables_load.gd") prints("load.perm:", other.perm) diff --git a/modules/gdscript/tests/scripts/runtime/features/stringify.gd b/modules/gdscript/tests/scripts/runtime/features/stringify.gd index fead2df854..1e66d8f34a 100644 --- a/modules/gdscript/tests/scripts/runtime/features/stringify.gd +++ b/modules/gdscript/tests/scripts/runtime/features/stringify.gd @@ -24,7 +24,8 @@ func test(): print(StringName("hello")) print(NodePath("hello/world")) var node := Node.new() - print(RID(node)) + @warning_ignore("unsafe_call_argument") + print(RID(node)) # TODO: Why is the constructor (or implicit cast) not documented? print(node.get_name) print(node.property_list_changed) node.free() diff --git a/modules/gridmap/editor/grid_map_editor_plugin.cpp b/modules/gridmap/editor/grid_map_editor_plugin.cpp index b5a53fa1bf..f7c01ff840 100644 --- a/modules/gridmap/editor/grid_map_editor_plugin.cpp +++ b/modules/gridmap/editor/grid_map_editor_plugin.cpp @@ -1412,6 +1412,7 @@ GridMapEditor::GridMapEditor() { inner_mat.instantiate(); inner_mat->set_albedo(Color(0.7, 0.7, 1.0, 0.2)); inner_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + inner_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); inner_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); d[RS::ARRAY_VERTEX] = triangles; @@ -1424,11 +1425,13 @@ GridMapEditor::GridMapEditor() { outer_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); outer_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); + outer_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); selection_floor_mat.instantiate(); selection_floor_mat->set_albedo(Color(0.80, 0.80, 1.0, 1)); selection_floor_mat->set_on_top_of_alpha(); selection_floor_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + selection_floor_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); d[RS::ARRAY_VERTEX] = lines; RenderingServer::get_singleton()->mesh_add_surface_from_arrays(selection_mesh, RS::PRIMITIVE_LINES, d); @@ -1457,6 +1460,7 @@ GridMapEditor::GridMapEditor() { indicator_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); indicator_mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); indicator_mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); + indicator_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); indicator_mat->set_albedo(Color(0.8, 0.5, 0.1)); } diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildDiagnostic.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildDiagnostic.cs new file mode 100644 index 0000000000..6e0c63dd43 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildDiagnostic.cs @@ -0,0 +1,23 @@ +#nullable enable + +namespace GodotTools.Build +{ + public class BuildDiagnostic + { + public enum DiagnosticType + { + Hidden, + Info, + Warning, + Error, + } + + public DiagnosticType Type { get; set; } + public string? File { get; set; } + public int Line { get; set; } + public int Column { get; set; } + public string? Code { get; set; } + public string Message { get; set; } = ""; + public string? ProjectFile { get; set; } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs index 312c65e364..9bb4fd153b 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs @@ -40,9 +40,6 @@ namespace GodotTools.Build plugin.MakeBottomPanelItemVisible(plugin.MSBuildPanel); } - public static void RestartBuild(BuildOutputView buildOutputView) => throw new NotImplementedException(); - public static void StopBuild(BuildOutputView buildOutputView) => throw new NotImplementedException(); - private static string GetLogFilePath(BuildInfo buildInfo) { return Path.Combine(buildInfo.LogsDirPath, MsBuildLogFileName); diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs index 54f7ed02f5..f9e85c36e5 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs @@ -1,425 +1,150 @@ using Godot; -using System; -using System.Diagnostics.CodeAnalysis; -using GodotTools.Internals; -using File = GodotTools.Utils.File; -using Path = System.IO.Path; +using static GodotTools.Internals.Globals; + +#nullable enable namespace GodotTools.Build { - public partial class BuildOutputView : VBoxContainer, ISerializationListener + public partial class BuildOutputView : HBoxContainer { - [Serializable] - private partial class BuildIssue : RefCounted // TODO Remove RefCounted once we have proper serialization - { - public bool Warning { get; set; } - public string File { get; set; } - public int Line { get; set; } - public int Column { get; set; } - public string Code { get; set; } - public string Message { get; set; } - public string ProjectFile { get; set; } - } - - [Signal] - public delegate void BuildStateChangedEventHandler(); - - public bool HasBuildExited { get; private set; } = false; +#nullable disable + private RichTextLabel _log; - public BuildResult? BuildResult { get; private set; } = null; + private Button _clearButton; + private Button _copyButton; +#nullable enable - public int ErrorCount { get; private set; } = 0; - - public int WarningCount { get; private set; } = 0; - - public bool ErrorsVisible { get; set; } = true; - public bool WarningsVisible { get; set; } = true; - - public Texture2D BuildStateIcon + public void Append(string text) { - get - { - if (!HasBuildExited) - return GetThemeIcon("Stop", "EditorIcons"); - - if (BuildResult == Build.BuildResult.Error) - return GetThemeIcon("Error", "EditorIcons"); - - if (WarningCount > 1) - return GetThemeIcon("Warning", "EditorIcons"); - - return null; - } + _log.AddText(text); } - public bool LogVisible + public void Clear() { - set => _buildLog.Visible = value; + _log.Clear(); } - // TODO Use List once we have proper serialization. - private Godot.Collections.Array<BuildIssue> _issues = new(); - private ItemList _issuesList; - private PopupMenu _issuesListContextMenu; - private TextEdit _buildLog; - private BuildInfo _buildInfo; - - private readonly object _pendingBuildLogTextLock = new object(); - [NotNull] private string _pendingBuildLogText = string.Empty; - - private void LoadIssuesFromFile(string csvFile) + private void CopyRequested() { - using var file = FileAccess.Open(csvFile, FileAccess.ModeFlags.Read); - - if (file == null) - return; + string text = _log.GetSelectedText(); - while (!file.EofReached()) - { - string[] csvColumns = file.GetCsvLine(); + if (string.IsNullOrEmpty(text)) + text = _log.GetParsedText(); - if (csvColumns.Length == 1 && string.IsNullOrEmpty(csvColumns[0])) - return; - - if (csvColumns.Length != 7) - { - GD.PushError($"Expected 7 columns, got {csvColumns.Length}"); - continue; - } - - var issue = new BuildIssue - { - Warning = csvColumns[0] == "warning", - File = csvColumns[1], - Line = int.Parse(csvColumns[2]), - Column = int.Parse(csvColumns[3]), - Code = csvColumns[4], - Message = csvColumns[5], - ProjectFile = csvColumns[6] - }; - - if (issue.Warning) - WarningCount += 1; - else - ErrorCount += 1; - - _issues.Add(issue); - } + if (!string.IsNullOrEmpty(text)) + DisplayServer.ClipboardSet(text); } - private void IssueActivated(long idx) + public override void _Ready() { - if (idx < 0 || idx >= _issuesList.ItemCount) - throw new ArgumentOutOfRangeException(nameof(idx), "Item list index out of range."); - - // Get correct issue idx from issue list - int issueIndex = (int)_issuesList.GetItemMetadata((int)idx); - - if (issueIndex < 0 || issueIndex >= _issues.Count) - throw new InvalidOperationException("Issue index out of range."); - - BuildIssue issue = _issues[issueIndex]; + Name = "Output".TTR(); - if (string.IsNullOrEmpty(issue.ProjectFile) && string.IsNullOrEmpty(issue.File)) - return; - - string projectDir = !string.IsNullOrEmpty(issue.ProjectFile) ? - issue.ProjectFile.GetBaseDir() : - _buildInfo.Solution.GetBaseDir(); - - string file = Path.Combine(projectDir.SimplifyGodotPath(), issue.File.SimplifyGodotPath()); - - if (!File.Exists(file)) - return; - - file = ProjectSettings.LocalizePath(file); - - if (file.StartsWith("res://")) + var vbLeft = new VBoxContainer { - var script = (Script)ResourceLoader.Load(file, typeHint: Internal.CSharpLanguageType); - - // Godot's ScriptEditor.Edit is 0-based but the issue lines are 1-based. - if (script != null && Internal.ScriptEditorEdit(script, issue.Line - 1, issue.Column - 1)) - Internal.EditorNodeShowScriptScreen(); - } - } - - public void UpdateIssuesList() - { - _issuesList.Clear(); + CustomMinimumSize = new Vector2(0, 180 * EditorScale), + SizeFlagsVertical = SizeFlags.ExpandFill, + SizeFlagsHorizontal = SizeFlags.ExpandFill, + }; + AddChild(vbLeft); - using (var warningIcon = GetThemeIcon("Warning", "EditorIcons")) - using (var errorIcon = GetThemeIcon("Error", "EditorIcons")) + // Log - Rich Text Label. + _log = new RichTextLabel { - for (int i = 0; i < _issues.Count; i++) - { - BuildIssue issue = _issues[i]; - - if (!(issue.Warning ? WarningsVisible : ErrorsVisible)) - continue; - - string tooltip = string.Empty; - tooltip += $"Message: {issue.Message}"; - - if (!string.IsNullOrEmpty(issue.Code)) - tooltip += $"\nCode: {issue.Code}"; - - tooltip += $"\nType: {(issue.Warning ? "warning" : "error")}"; - - string text = string.Empty; - - if (!string.IsNullOrEmpty(issue.File)) - { - text += $"{issue.File}({issue.Line},{issue.Column}): "; - - tooltip += $"\nFile: {issue.File}"; - tooltip += $"\nLine: {issue.Line}"; - tooltip += $"\nColumn: {issue.Column}"; - } - - if (!string.IsNullOrEmpty(issue.ProjectFile)) - tooltip += $"\nProject: {issue.ProjectFile}"; - - text += issue.Message; - - int lineBreakIdx = text.IndexOf("\n", StringComparison.Ordinal); - string itemText = lineBreakIdx == -1 ? text : text.Substring(0, lineBreakIdx); - _issuesList.AddItem(itemText, issue.Warning ? warningIcon : errorIcon); - - int index = _issuesList.ItemCount - 1; - _issuesList.SetItemTooltip(index, tooltip); - _issuesList.SetItemMetadata(index, i); - } - } - } - - private void BuildLaunchFailed(BuildInfo buildInfo, string cause) - { - HasBuildExited = true; - BuildResult = Build.BuildResult.Error; - - _issuesList.Clear(); - - var issue = new BuildIssue { Message = cause, Warning = false }; - - ErrorCount += 1; - _issues.Add(issue); - - UpdateIssuesList(); - - EmitSignal(nameof(BuildStateChanged)); - } - - private void BuildStarted(BuildInfo buildInfo) - { - _buildInfo = buildInfo; - HasBuildExited = false; - - _issues.Clear(); - WarningCount = 0; - ErrorCount = 0; - _buildLog.Text = string.Empty; - - UpdateIssuesList(); - - EmitSignal(nameof(BuildStateChanged)); - } - - private void BuildFinished(BuildResult result) - { - HasBuildExited = true; - BuildResult = result; - - LoadIssuesFromFile(Path.Combine(_buildInfo.LogsDirPath, BuildManager.MsBuildIssuesFileName)); + BbcodeEnabled = true, + ScrollFollowing = true, + SelectionEnabled = true, + ContextMenuEnabled = true, + FocusMode = FocusModeEnum.Click, + SizeFlagsVertical = SizeFlags.ExpandFill, + SizeFlagsHorizontal = SizeFlags.ExpandFill, + DeselectOnFocusLossEnabled = false, - UpdateIssuesList(); + }; + vbLeft.AddChild(_log); - EmitSignal(nameof(BuildStateChanged)); - } + var vbRight = new VBoxContainer(); + AddChild(vbRight); - private void UpdateBuildLogText() - { - lock (_pendingBuildLogTextLock) + // Tools grid + var hbTools = new HBoxContainer { - _buildLog.Text += _pendingBuildLogText; - _pendingBuildLogText = string.Empty; - ScrollToLastNonEmptyLogLine(); - } - } - - private void StdOutputReceived(string text) - { - lock (_pendingBuildLogTextLock) - { - if (_pendingBuildLogText.Length == 0) - CallDeferred(nameof(UpdateBuildLogText)); - _pendingBuildLogText += text + "\n"; - } - } + SizeFlagsHorizontal = SizeFlags.ExpandFill, + }; + vbRight.AddChild(hbTools); - private void StdErrorReceived(string text) - { - lock (_pendingBuildLogTextLock) + // Clear. + _clearButton = new Button { - if (_pendingBuildLogText.Length == 0) - CallDeferred(nameof(UpdateBuildLogText)); - _pendingBuildLogText += text + "\n"; - } - } + ThemeTypeVariation = "FlatButton", + FocusMode = FocusModeEnum.None, + Shortcut = EditorDefShortcut("editor/clear_output", "Clear Output".TTR(), (Key)KeyModifierMask.MaskCmdOrCtrl | (Key)KeyModifierMask.MaskShift | Key.K), + }; + _clearButton.Pressed += Clear; + hbTools.AddChild(_clearButton); - private void ScrollToLastNonEmptyLogLine() - { - int line; - for (line = _buildLog.GetLineCount(); line > 0; line--) + // Copy. + _copyButton = new Button { - string lineText = _buildLog.GetLine(line); - - if (!string.IsNullOrEmpty(lineText) || !string.IsNullOrEmpty(lineText?.Trim())) - break; - } - - _buildLog.SetCaretLine(line); - } - - public void RestartBuild() - { - if (!HasBuildExited) - throw new InvalidOperationException("Build already started."); - - BuildManager.RestartBuild(this); - } - - public void StopBuild() - { - if (!HasBuildExited) - throw new InvalidOperationException("Build is not in progress."); + ThemeTypeVariation = "FlatButton", + FocusMode = FocusModeEnum.None, + Shortcut = EditorDefShortcut("editor/copy_output", "Copy Selection".TTR(), (Key)KeyModifierMask.MaskCmdOrCtrl | Key.C), + ShortcutContext = this, + }; + _copyButton.Pressed += CopyRequested; + hbTools.AddChild(_copyButton); - BuildManager.StopBuild(this); + UpdateTheme(); } - private enum IssuesContextMenuOption + public override void _Notification(int what) { - Copy - } + base._Notification(what); - private void IssuesListContextOptionPressed(long id) - { - switch ((IssuesContextMenuOption)id) + if (what == NotificationThemeChanged) { - case IssuesContextMenuOption.Copy: - { - // We don't allow multi-selection but just in case that changes later... - string text = null; - - foreach (int issueIndex in _issuesList.GetSelectedItems()) - { - if (text != null) - text += "\n"; - text += _issuesList.GetItemText(issueIndex); - } - - if (text != null) - DisplayServer.ClipboardSet(text); - break; - } - default: - throw new ArgumentOutOfRangeException(nameof(id), id, "Invalid issue context menu option"); + UpdateTheme(); } } - private void IssuesListClicked(long index, Vector2 atPosition, long mouseButtonIndex) + private void UpdateTheme() { - if (mouseButtonIndex != (long)MouseButton.Right) - { + // Nodes will be null until _Ready is called. + if (_log == null) return; - } - - _ = index; // Unused - - _issuesListContextMenu.Clear(); - _issuesListContextMenu.Size = new Vector2I(1, 1); - - if (_issuesList.IsAnythingSelected()) - { - // Add menu entries for the selected item - _issuesListContextMenu.AddIconItem(GetThemeIcon("ActionCopy", "EditorIcons"), - label: "Copy Error".TTR(), (int)IssuesContextMenuOption.Copy); - } - - if (_issuesListContextMenu.ItemCount > 0) - { - _issuesListContextMenu.Position = (Vector2I)(_issuesList.GlobalPosition + atPosition); - _issuesListContextMenu.Popup(); - } - } - - public override void _Ready() - { - base._Ready(); - SizeFlagsVertical = SizeFlags.ExpandFill; + var normalFont = GetThemeFont("output_source", "EditorFonts"); + if (normalFont != null) + _log.AddThemeFontOverride("normal_font", normalFont); - var hsc = new HSplitContainer - { - SizeFlagsHorizontal = SizeFlags.ExpandFill, - SizeFlagsVertical = SizeFlags.ExpandFill - }; - AddChild(hsc); + var boldFont = GetThemeFont("output_source_bold", "EditorFonts"); + if (boldFont != null) + _log.AddThemeFontOverride("bold_font", boldFont); - _issuesList = new ItemList - { - SizeFlagsVertical = SizeFlags.ExpandFill, - SizeFlagsHorizontal = SizeFlags.ExpandFill // Avoid being squashed by the build log - }; - _issuesList.ItemActivated += IssueActivated; - _issuesList.AllowRmbSelect = true; - _issuesList.ItemClicked += IssuesListClicked; - hsc.AddChild(_issuesList); + var italicsFont = GetThemeFont("output_source_italic", "EditorFonts"); + if (italicsFont != null) + _log.AddThemeFontOverride("italics_font", italicsFont); - _issuesListContextMenu = new PopupMenu(); - _issuesListContextMenu.IdPressed += IssuesListContextOptionPressed; - _issuesList.AddChild(_issuesListContextMenu); + var boldItalicsFont = GetThemeFont("output_source_bold_italic", "EditorFonts"); + if (boldItalicsFont != null) + _log.AddThemeFontOverride("bold_italics_font", boldItalicsFont); - _buildLog = new TextEdit - { - Editable = false, - SizeFlagsVertical = SizeFlags.ExpandFill, - SizeFlagsHorizontal = SizeFlags.ExpandFill // Avoid being squashed by the issues list - }; - hsc.AddChild(_buildLog); + var monoFont = GetThemeFont("output_source_mono", "EditorFonts"); + if (monoFont != null) + _log.AddThemeFontOverride("mono_font", monoFont); - AddBuildEventListeners(); - } - - private void AddBuildEventListeners() - { - BuildManager.BuildLaunchFailed += BuildLaunchFailed; - BuildManager.BuildStarted += BuildStarted; - BuildManager.BuildFinished += BuildFinished; - // StdOutput/Error can be received from different threads, so we need to use CallDeferred - BuildManager.StdOutputReceived += StdOutputReceived; - BuildManager.StdErrorReceived += StdErrorReceived; - } + // Disable padding for highlighted background/foreground to prevent highlights from overlapping on close lines. + // This also better matches terminal output, which does not use any form of padding. + _log.AddThemeConstantOverride("text_highlight_h_padding", 0); + _log.AddThemeConstantOverride("text_highlight_v_padding", 0); - public void OnBeforeSerialize() - { - // In case it didn't update yet. We don't want to have to serialize any pending output. - UpdateBuildLogText(); - - // NOTE: - // Currently, GodotTools is loaded in its own load context. This load context is not reloaded, but the script still are. - // Until that changes, we need workarounds like this one because events keep strong references to disposed objects. - BuildManager.BuildLaunchFailed -= BuildLaunchFailed; - BuildManager.BuildStarted -= BuildStarted; - BuildManager.BuildFinished -= BuildFinished; - // StdOutput/Error can be received from different threads, so we need to use CallDeferred - BuildManager.StdOutputReceived -= StdOutputReceived; - BuildManager.StdErrorReceived -= StdErrorReceived; - } + int font_size = GetThemeFontSize("output_source_size", "EditorFonts"); + _log.AddThemeFontSizeOverride("normal_font_size", font_size); + _log.AddThemeFontSizeOverride("bold_font_size", font_size); + _log.AddThemeFontSizeOverride("italics_font_size", font_size); + _log.AddThemeFontSizeOverride("mono_font_size", font_size); - public void OnAfterDeserialize() - { - AddBuildEventListeners(); // Re-add them + _clearButton.Icon = GetThemeIcon("Clear", "EditorIcons"); + _copyButton.Icon = GetThemeIcon("ActionCopy", "EditorIcons"); } } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsFilter.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsFilter.cs new file mode 100644 index 0000000000..9c165e5767 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsFilter.cs @@ -0,0 +1,40 @@ +using Godot; + +#nullable enable + +namespace GodotTools.Build +{ + public class BuildProblemsFilter + { + public BuildDiagnostic.DiagnosticType Type { get; } + + public Button ToggleButton { get; } + + private int _problemsCount; + + public int ProblemsCount + { + get => _problemsCount; + set + { + _problemsCount = value; + ToggleButton.Text = _problemsCount.ToString(); + } + } + + public bool IsActive => ToggleButton.ButtonPressed; + + public BuildProblemsFilter(BuildDiagnostic.DiagnosticType type) + { + Type = type; + ToggleButton = new Button + { + ToggleMode = true, + ButtonPressed = true, + Text = "0", + FocusMode = Control.FocusModeEnum.None, + ThemeTypeVariation = "EditorLogFilterButton", + }; + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsView.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsView.cs new file mode 100644 index 0000000000..b23b3f42ef --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsView.cs @@ -0,0 +1,694 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using Godot; +using GodotTools.Internals; +using static GodotTools.Internals.Globals; +using FileAccess = Godot.FileAccess; + +#nullable enable + +namespace GodotTools.Build +{ + public partial class BuildProblemsView : HBoxContainer + { +#nullable disable + private Button _clearButton; + private Button _copyButton; + + private Button _toggleLayoutButton; + + private Button _showSearchButton; + private LineEdit _searchBox; +#nullable enable + + private readonly Dictionary<BuildDiagnostic.DiagnosticType, BuildProblemsFilter> _filtersByType = new(); + +#nullable disable + private Tree _problemsTree; + private PopupMenu _problemsContextMenu; +#nullable enable + + public enum ProblemsLayout { List, Tree } + private ProblemsLayout _layout = ProblemsLayout.Tree; + + private readonly List<BuildDiagnostic> _diagnostics = new(); + + public int TotalDiagnosticCount => _diagnostics.Count; + + private readonly Dictionary<BuildDiagnostic.DiagnosticType, int> _problemCountByType = new(); + + public int WarningCount => + GetProblemCountForType(BuildDiagnostic.DiagnosticType.Warning); + + public int ErrorCount => + GetProblemCountForType(BuildDiagnostic.DiagnosticType.Error); + + private int GetProblemCountForType(BuildDiagnostic.DiagnosticType type) + { + if (!_problemCountByType.TryGetValue(type, out int count)) + { + count = _diagnostics.Count(d => d.Type == type); + _problemCountByType[type] = count; + } + + return count; + } + + private static IEnumerable<BuildDiagnostic> ReadDiagnosticsFromFile(string csvFile) + { + using var file = FileAccess.Open(csvFile, FileAccess.ModeFlags.Read); + + if (file == null) + yield break; + + while (!file.EofReached()) + { + string[] csvColumns = file.GetCsvLine(); + + if (csvColumns.Length == 1 && string.IsNullOrEmpty(csvColumns[0])) + yield break; + + if (csvColumns.Length != 7) + { + GD.PushError($"Expected 7 columns, got {csvColumns.Length}"); + continue; + } + + var diagnostic = new BuildDiagnostic + { + Type = csvColumns[0] switch + { + "warning" => BuildDiagnostic.DiagnosticType.Warning, + "error" or _ => BuildDiagnostic.DiagnosticType.Error, + }, + File = csvColumns[1], + Line = int.Parse(csvColumns[2]), + Column = int.Parse(csvColumns[3]), + Code = csvColumns[4], + Message = csvColumns[5], + ProjectFile = csvColumns[6], + }; + + // If there's no ProjectFile but the File is a csproj, then use that. + if (string.IsNullOrEmpty(diagnostic.ProjectFile) && + !string.IsNullOrEmpty(diagnostic.File) && + diagnostic.File.EndsWith(".csproj")) + { + diagnostic.ProjectFile = diagnostic.File; + } + + yield return diagnostic; + } + } + + public void SetDiagnosticsFromFile(string csvFile) + { + var diagnostics = ReadDiagnosticsFromFile(csvFile); + SetDiagnostics(diagnostics); + } + + public void SetDiagnostics(IEnumerable<BuildDiagnostic> diagnostics) + { + _diagnostics.Clear(); + _problemCountByType.Clear(); + + _diagnostics.AddRange(diagnostics); + UpdateProblemsView(); + } + + public void Clear() + { + _problemsTree.Clear(); + _diagnostics.Clear(); + _problemCountByType.Clear(); + + UpdateProblemsView(); + } + + private void CopySelectedProblems() + { + var selectedItem = _problemsTree.GetNextSelected(null); + if (selectedItem == null) + return; + + var selectedIdxs = new List<int>(); + while (selectedItem != null) + { + int selectedIdx = (int)selectedItem.GetMetadata(0); + selectedIdxs.Add(selectedIdx); + + selectedItem = _problemsTree.GetNextSelected(selectedItem); + } + + if (selectedIdxs.Count == 0) + return; + + var selectedDiagnostics = selectedIdxs.Select(i => _diagnostics[i]); + + var sb = new StringBuilder(); + + foreach (var diagnostic in selectedDiagnostics) + { + if (!string.IsNullOrEmpty(diagnostic.Code)) + sb.Append($"{diagnostic.Code}: "); + + sb.AppendLine($"{diagnostic.Message} {diagnostic.File}({diagnostic.Line},{diagnostic.Column})"); + } + + string text = sb.ToString(); + + if (!string.IsNullOrEmpty(text)) + DisplayServer.ClipboardSet(text); + } + + private void ToggleLayout(bool pressed) + { + _layout = pressed ? ProblemsLayout.List : ProblemsLayout.Tree; + + var editorSettings = EditorInterface.Singleton.GetEditorSettings(); + editorSettings.SetSetting(GodotSharpEditor.Settings.ProblemsLayout, Variant.From(_layout)); + + _toggleLayoutButton.Icon = GetToggleLayoutIcon(); + _toggleLayoutButton.TooltipText = GetToggleLayoutTooltipText(); + + UpdateProblemsView(); + } + + private bool GetToggleLayoutPressedState() + { + // If pressed: List layout. + // If not pressed: Tree layout. + return _layout == ProblemsLayout.List; + } + + private Texture2D? GetToggleLayoutIcon() + { + return _layout switch + { + ProblemsLayout.List => GetThemeIcon("FileList", "EditorIcons"), + ProblemsLayout.Tree or _ => GetThemeIcon("FileTree", "EditorIcons"), + }; + } + + private string GetToggleLayoutTooltipText() + { + return _layout switch + { + ProblemsLayout.List => "View as a Tree".TTR(), + ProblemsLayout.Tree or _ => "View as a List".TTR(), + }; + } + + private void ToggleSearchBoxVisibility(bool pressed) + { + _searchBox.Visible = pressed; + if (pressed) + { + _searchBox.GrabFocus(); + } + } + + private void SearchTextChanged(string text) + { + UpdateProblemsView(); + } + + private void ToggleFilter(bool pressed) + { + UpdateProblemsView(); + } + + private void GoToSelectedProblem() + { + var selectedItem = _problemsTree.GetSelected(); + if (selectedItem == null) + throw new InvalidOperationException("Item tree has no selected items."); + + // Get correct diagnostic index from problems tree. + int diagnosticIndex = (int)selectedItem.GetMetadata(0); + + if (diagnosticIndex < 0 || diagnosticIndex >= _diagnostics.Count) + throw new InvalidOperationException("Diagnostic index out of range."); + + var diagnostic = _diagnostics[diagnosticIndex]; + + if (string.IsNullOrEmpty(diagnostic.ProjectFile) && string.IsNullOrEmpty(diagnostic.File)) + return; + + string? projectDir = !string.IsNullOrEmpty(diagnostic.ProjectFile) ? + diagnostic.ProjectFile.GetBaseDir() : + GodotSharpEditor.Instance.MSBuildPanel.LastBuildInfo?.Solution.GetBaseDir(); + if (string.IsNullOrEmpty(projectDir)) + return; + + string file = Path.Combine(projectDir.SimplifyGodotPath(), diagnostic.File.SimplifyGodotPath()); + + if (!File.Exists(file)) + return; + + file = ProjectSettings.LocalizePath(file); + + if (file.StartsWith("res://")) + { + var script = (Script)ResourceLoader.Load(file, typeHint: Internal.CSharpLanguageType); + + // Godot's ScriptEditor.Edit is 0-based but the diagnostic lines are 1-based. + if (script != null && Internal.ScriptEditorEdit(script, diagnostic.Line - 1, diagnostic.Column - 1)) + Internal.EditorNodeShowScriptScreen(); + } + } + + private void ShowProblemContextMenu(Vector2 position, long mouseButtonIndex) + { + if (mouseButtonIndex != (long)MouseButton.Right) + return; + + _problemsContextMenu.Clear(); + _problemsContextMenu.Size = new Vector2I(1, 1); + + var selectedItem = _problemsTree.GetSelected(); + if (selectedItem != null) + { + // Add menu entries for the selected item. + _problemsContextMenu.AddIconItem(GetThemeIcon("ActionCopy", "EditorIcons"), + label: "Copy Error".TTR(), (int)ProblemContextMenuOption.Copy); + } + + if (_problemsContextMenu.ItemCount > 0) + { + _problemsContextMenu.Position = (Vector2I)(_problemsTree.GlobalPosition + position); + _problemsContextMenu.Popup(); + } + } + + private enum ProblemContextMenuOption + { + Copy, + } + + private void ProblemContextOptionPressed(long id) + { + switch ((ProblemContextMenuOption)id) + { + case ProblemContextMenuOption.Copy: + CopySelectedProblems(); + break; + + default: + throw new ArgumentOutOfRangeException(nameof(id), id, "Invalid problem context menu option."); + } + } + + private bool ShouldDisplayDiagnostic(BuildDiagnostic diagnostic) + { + if (!_filtersByType[diagnostic.Type].IsActive) + return false; + + string searchText = _searchBox.Text; + if (!string.IsNullOrEmpty(searchText) && + (!diagnostic.Message.Contains(searchText, StringComparison.OrdinalIgnoreCase) || + !(diagnostic.File?.Contains(searchText, StringComparison.OrdinalIgnoreCase) ?? false))) + { + return false; + } + + return true; + } + + private Color? GetProblemItemColor(BuildDiagnostic diagnostic) + { + return diagnostic.Type switch + { + BuildDiagnostic.DiagnosticType.Warning => GetThemeColor("warning_color", "Editor"), + BuildDiagnostic.DiagnosticType.Error => GetThemeColor("error_color", "Editor"), + _ => null, + }; + } + + public void UpdateProblemsView() + { + switch (_layout) + { + case ProblemsLayout.List: + UpdateProblemsList(); + break; + + case ProblemsLayout.Tree: + default: + UpdateProblemsTree(); + break; + } + + foreach (var (type, filter) in _filtersByType) + { + int count = _diagnostics.Count(d => d.Type == type); + filter.ProblemsCount = count; + } + + if (_diagnostics.Count == 0) + Name = "Problems".TTR(); + else + Name = $"{"Problems".TTR()} ({_diagnostics.Count})"; + } + + private void UpdateProblemsList() + { + _problemsTree.Clear(); + + var root = _problemsTree.CreateItem(); + + for (int i = 0; i < _diagnostics.Count; i++) + { + var diagnostic = _diagnostics[i]; + + if (!ShouldDisplayDiagnostic(diagnostic)) + continue; + + var item = CreateProblemItem(diagnostic, includeFileInText: true); + + var problemItem = _problemsTree.CreateItem(root); + problemItem.SetIcon(0, item.Icon); + problemItem.SetText(0, item.Text); + problemItem.SetTooltipText(0, item.TooltipText); + problemItem.SetMetadata(0, i); + + var color = GetProblemItemColor(diagnostic); + if (color.HasValue) + problemItem.SetCustomColor(0, color.Value); + } + } + + private void UpdateProblemsTree() + { + _problemsTree.Clear(); + + var root = _problemsTree.CreateItem(); + + var groupedDiagnostics = _diagnostics.Select((d, i) => (Diagnostic: d, Index: i)) + .Where(x => ShouldDisplayDiagnostic(x.Diagnostic)) + .GroupBy(x => x.Diagnostic.ProjectFile) + .Select(g => (ProjectFile: g.Key, Diagnostics: g.GroupBy(x => x.Diagnostic.File) + .Select(x => (File: x.Key, Diagnostics: x.ToArray())))) + .ToArray(); + + if (groupedDiagnostics.Length == 0) + return; + + foreach (var (projectFile, projectDiagnostics) in groupedDiagnostics) + { + TreeItem projectItem; + + if (groupedDiagnostics.Length == 1) + { + // Don't create a project item if there's only one project. + projectItem = root; + } + else + { + string projectFilePath = !string.IsNullOrEmpty(projectFile) + ? projectFile + : "Unknown project".TTR(); + projectItem = _problemsTree.CreateItem(root); + projectItem.SetText(0, projectFilePath); + projectItem.SetSelectable(0, false); + } + + foreach (var (file, fileDiagnostics) in projectDiagnostics) + { + if (fileDiagnostics.Length == 0) + continue; + + string? projectDir = Path.GetDirectoryName(projectFile); + string relativeFilePath = !string.IsNullOrEmpty(file) && !string.IsNullOrEmpty(projectDir) + ? Path.GetRelativePath(projectDir, file) + : "Unknown file".TTR(); + + string fileItemText = string.Format("{0} ({1} issues)".TTR(), relativeFilePath, fileDiagnostics.Length); + + var fileItem = _problemsTree.CreateItem(projectItem); + fileItem.SetText(0, fileItemText); + fileItem.SetSelectable(0, false); + + foreach (var (diagnostic, index) in fileDiagnostics) + { + var item = CreateProblemItem(diagnostic); + + var problemItem = _problemsTree.CreateItem(fileItem); + problemItem.SetIcon(0, item.Icon); + problemItem.SetText(0, item.Text); + problemItem.SetTooltipText(0, item.TooltipText); + problemItem.SetMetadata(0, index); + + var color = GetProblemItemColor(diagnostic); + if (color.HasValue) + problemItem.SetCustomColor(0, color.Value); + } + } + } + } + + private class ProblemItem + { + public string? Text { get; set; } + public string? TooltipText { get; set; } + public Texture2D? Icon { get; set; } + } + + private ProblemItem CreateProblemItem(BuildDiagnostic diagnostic, bool includeFileInText = false) + { + var text = new StringBuilder(); + var tooltip = new StringBuilder(); + + ReadOnlySpan<char> shortMessage = diagnostic.Message.AsSpan(); + int lineBreakIdx = shortMessage.IndexOf('\n'); + if (lineBreakIdx != -1) + shortMessage = shortMessage[..lineBreakIdx]; + text.Append(shortMessage); + + tooltip.Append($"Message: {diagnostic.Message}"); + + if (!string.IsNullOrEmpty(diagnostic.Code)) + tooltip.Append($"\nCode: {diagnostic.Code}"); + + string type = diagnostic.Type switch + { + BuildDiagnostic.DiagnosticType.Hidden => "hidden", + BuildDiagnostic.DiagnosticType.Info => "info", + BuildDiagnostic.DiagnosticType.Warning => "warning", + BuildDiagnostic.DiagnosticType.Error => "error", + _ => "unknown", + }; + tooltip.Append($"\nType: {type}"); + + if (!string.IsNullOrEmpty(diagnostic.File)) + { + text.Append(' '); + if (includeFileInText) + { + text.Append(diagnostic.File); + } + + text.Append($"({diagnostic.Line},{diagnostic.Column})"); + + tooltip.Append($"\nFile: {diagnostic.File}"); + tooltip.Append($"\nLine: {diagnostic.Line}"); + tooltip.Append($"\nColumn: {diagnostic.Column}"); + } + + if (!string.IsNullOrEmpty(diagnostic.ProjectFile)) + tooltip.Append($"\nProject: {diagnostic.ProjectFile}"); + + return new ProblemItem() + { + Text = text.ToString(), + TooltipText = tooltip.ToString(), + Icon = diagnostic.Type switch + { + BuildDiagnostic.DiagnosticType.Warning => GetThemeIcon("Warning", "EditorIcons"), + BuildDiagnostic.DiagnosticType.Error => GetThemeIcon("Error", "EditorIcons"), + _ => null, + }, + }; + } + + public override void _Ready() + { + var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); + _layout = editorSettings.GetSetting(GodotSharpEditor.Settings.ProblemsLayout).As<ProblemsLayout>(); + + Name = "Problems".TTR(); + + var vbLeft = new VBoxContainer + { + CustomMinimumSize = new Vector2(0, 180 * EditorScale), + SizeFlagsVertical = SizeFlags.ExpandFill, + SizeFlagsHorizontal = SizeFlags.ExpandFill, + }; + AddChild(vbLeft); + + // Problem Tree. + _problemsTree = new Tree + { + SizeFlagsVertical = SizeFlags.ExpandFill, + SizeFlagsHorizontal = SizeFlags.ExpandFill, + AllowRmbSelect = true, + HideRoot = true, + }; + _problemsTree.ItemActivated += GoToSelectedProblem; + _problemsTree.ItemMouseSelected += ShowProblemContextMenu; + vbLeft.AddChild(_problemsTree); + + // Problem context menu. + _problemsContextMenu = new PopupMenu(); + _problemsContextMenu.IdPressed += ProblemContextOptionPressed; + _problemsTree.AddChild(_problemsContextMenu); + + // Search box. + _searchBox = new LineEdit + { + SizeFlagsHorizontal = SizeFlags.ExpandFill, + PlaceholderText = "Filter Problems".TTR(), + ClearButtonEnabled = true, + }; + _searchBox.TextChanged += SearchTextChanged; + vbLeft.AddChild(_searchBox); + + var vbRight = new VBoxContainer(); + AddChild(vbRight); + + // Tools grid. + var hbTools = new HBoxContainer + { + SizeFlagsHorizontal = SizeFlags.ExpandFill, + }; + vbRight.AddChild(hbTools); + + // Clear. + _clearButton = new Button + { + ThemeTypeVariation = "FlatButton", + FocusMode = FocusModeEnum.None, + Shortcut = EditorDefShortcut("editor/clear_output", "Clear Output".TTR(), (Key)KeyModifierMask.MaskCmdOrCtrl | (Key)KeyModifierMask.MaskShift | Key.K), + ShortcutContext = this, + }; + _clearButton.Pressed += Clear; + hbTools.AddChild(_clearButton); + + // Copy. + _copyButton = new Button + { + ThemeTypeVariation = "FlatButton", + FocusMode = FocusModeEnum.None, + Shortcut = EditorDefShortcut("editor/copy_output", "Copy Selection".TTR(), (Key)KeyModifierMask.MaskCmdOrCtrl | Key.C), + ShortcutContext = this, + }; + _copyButton.Pressed += CopySelectedProblems; + hbTools.AddChild(_copyButton); + + // A second hbox to make a 2x2 grid of buttons. + var hbTools2 = new HBoxContainer + { + SizeFlagsHorizontal = SizeFlags.ShrinkCenter, + }; + vbRight.AddChild(hbTools2); + + // Toggle List/Tree. + _toggleLayoutButton = new Button + { + Flat = true, + FocusMode = FocusModeEnum.None, + TooltipText = GetToggleLayoutTooltipText(), + ToggleMode = true, + ButtonPressed = GetToggleLayoutPressedState(), + }; + // Don't tint the icon even when in "pressed" state. + _toggleLayoutButton.AddThemeColorOverride("icon_pressed_color", Colors.White); + _toggleLayoutButton.Toggled += ToggleLayout; + hbTools2.AddChild(_toggleLayoutButton); + + // Show Search. + _showSearchButton = new Button + { + ThemeTypeVariation = "FlatButton", + FocusMode = FocusModeEnum.None, + ToggleMode = true, + ButtonPressed = true, + Shortcut = EditorDefShortcut("editor/open_search", "Focus Search/Filter Bar".TTR(), (Key)KeyModifierMask.MaskCmdOrCtrl | Key.F), + ShortcutContext = this, + }; + _showSearchButton.Toggled += ToggleSearchBoxVisibility; + hbTools2.AddChild(_showSearchButton); + + // Diagnostic Type Filters. + vbRight.AddChild(new HSeparator()); + + var infoFilter = new BuildProblemsFilter(BuildDiagnostic.DiagnosticType.Info); + infoFilter.ToggleButton.TooltipText = "Toggle visibility of info diagnostics.".TTR(); + infoFilter.ToggleButton.Toggled += ToggleFilter; + vbRight.AddChild(infoFilter.ToggleButton); + _filtersByType[BuildDiagnostic.DiagnosticType.Info] = infoFilter; + + var errorFilter = new BuildProblemsFilter(BuildDiagnostic.DiagnosticType.Error); + errorFilter.ToggleButton.TooltipText = "Toggle visibility of errors.".TTR(); + errorFilter.ToggleButton.Toggled += ToggleFilter; + vbRight.AddChild(errorFilter.ToggleButton); + _filtersByType[BuildDiagnostic.DiagnosticType.Error] = errorFilter; + + var warningFilter = new BuildProblemsFilter(BuildDiagnostic.DiagnosticType.Warning); + warningFilter.ToggleButton.TooltipText = "Toggle visibility of warnings.".TTR(); + warningFilter.ToggleButton.Toggled += ToggleFilter; + vbRight.AddChild(warningFilter.ToggleButton); + _filtersByType[BuildDiagnostic.DiagnosticType.Warning] = warningFilter; + + UpdateTheme(); + + UpdateProblemsView(); + } + + public override void _Notification(int what) + { + base._Notification(what); + + switch ((long)what) + { + case EditorSettings.NotificationEditorSettingsChanged: + var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); + _layout = editorSettings.GetSetting(GodotSharpEditor.Settings.ProblemsLayout).As<ProblemsLayout>(); + _toggleLayoutButton.ButtonPressed = GetToggleLayoutPressedState(); + UpdateProblemsView(); + break; + + case NotificationThemeChanged: + UpdateTheme(); + break; + } + } + + private void UpdateTheme() + { + // Nodes will be null until _Ready is called. + if (_clearButton == null) + return; + + foreach (var (type, filter) in _filtersByType) + { + filter.ToggleButton.Icon = type switch + { + BuildDiagnostic.DiagnosticType.Info => GetThemeIcon("Popup", "EditorIcons"), + BuildDiagnostic.DiagnosticType.Warning => GetThemeIcon("StatusWarning", "EditorIcons"), + BuildDiagnostic.DiagnosticType.Error => GetThemeIcon("StatusError", "EditorIcons"), + _ => null, + }; + } + + _clearButton.Icon = GetThemeIcon("Clear", "EditorIcons"); + _copyButton.Icon = GetThemeIcon("ActionCopy", "EditorIcons"); + _toggleLayoutButton.Icon = GetToggleLayoutIcon(); + _showSearchButton.Icon = GetThemeIcon("Search", "EditorIcons"); + _searchBox.RightIcon = GetThemeIcon("Search", "EditorIcons"); + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs index cc11132a55..bae87dd1dd 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs @@ -5,28 +5,73 @@ using GodotTools.Internals; using static GodotTools.Internals.Globals; using File = GodotTools.Utils.File; +#nullable enable + namespace GodotTools.Build { - public partial class MSBuildPanel : VBoxContainer + public partial class MSBuildPanel : MarginContainer, ISerializationListener { - public BuildOutputView BuildOutputView { get; private set; } + [Signal] + public delegate void BuildStateChangedEventHandler(); + +#nullable disable + private MenuButton _buildMenuButton; + private Button _openLogsFolderButton; + + private BuildProblemsView _problemsView; + private BuildOutputView _outputView; +#nullable enable + + public BuildInfo? LastBuildInfo { get; private set; } + public bool IsBuildingOngoing { get; private set; } + public BuildResult? BuildResult { get; private set; } - private MenuButton _buildMenuBtn; - private Button _errorsBtn; - private Button _warningsBtn; - private Button _viewLogBtn; - private Button _openLogsFolderBtn; + private readonly object _pendingBuildLogTextLock = new object(); + private string _pendingBuildLogText = string.Empty; - private void WarningsToggled(bool pressed) + public Texture2D? GetBuildStateIcon() { - BuildOutputView.WarningsVisible = pressed; - BuildOutputView.UpdateIssuesList(); + if (IsBuildingOngoing) + return GetThemeIcon("Stop", "EditorIcons"); + + if (_problemsView.WarningCount > 0 && _problemsView.ErrorCount > 0) + return GetThemeIcon("ErrorWarning", "EditorIcons"); + + if (_problemsView.WarningCount > 0) + return GetThemeIcon("Warning", "EditorIcons"); + + if (_problemsView.ErrorCount > 0) + return GetThemeIcon("Error", "EditorIcons"); + + return null; } - private void ErrorsToggled(bool pressed) + private enum BuildMenuOptions { - BuildOutputView.ErrorsVisible = pressed; - BuildOutputView.UpdateIssuesList(); + BuildProject, + RebuildProject, + CleanProject, + } + + private void BuildMenuOptionPressed(long id) + { + switch ((BuildMenuOptions)id) + { + case BuildMenuOptions.BuildProject: + BuildProject(); + break; + + case BuildMenuOptions.RebuildProject: + RebuildProject(); + break; + + case BuildMenuOptions.CleanProject: + CleanProject(); + break; + + default: + throw new ArgumentOutOfRangeException(nameof(id), id, "Invalid build menu option"); + } } public void BuildProject() @@ -73,108 +118,136 @@ namespace GodotTools.Build _ = BuildManager.CleanProjectBlocking("Debug"); } - private void ViewLogToggled(bool pressed) => BuildOutputView.LogVisible = pressed; - - private void OpenLogsFolderPressed() => OS.ShellOpen( + private void OpenLogsFolder() => OS.ShellOpen( $"file://{GodotSharpDirs.LogsDirPathFor("Debug")}" ); - private void BuildMenuOptionPressed(long id) + private void BuildLaunchFailed(BuildInfo buildInfo, string cause) { - switch ((BuildMenuOptions)id) + IsBuildingOngoing = false; + BuildResult = Build.BuildResult.Error; + + _problemsView.Clear(); + _outputView.Clear(); + + var diagnostic = new BuildDiagnostic { - case BuildMenuOptions.BuildProject: - BuildProject(); - break; - case BuildMenuOptions.RebuildProject: - RebuildProject(); - break; - case BuildMenuOptions.CleanProject: - CleanProject(); - break; - default: - throw new ArgumentOutOfRangeException(nameof(id), id, "Invalid build menu option"); + Type = BuildDiagnostic.DiagnosticType.Error, + Message = cause, + }; + + _problemsView.SetDiagnostics(new[] { diagnostic }); + + EmitSignal(SignalName.BuildStateChanged); + } + + private void BuildStarted(BuildInfo buildInfo) + { + LastBuildInfo = buildInfo; + IsBuildingOngoing = true; + BuildResult = null; + + _problemsView.Clear(); + _outputView.Clear(); + + _problemsView.UpdateProblemsView(); + + EmitSignal(SignalName.BuildStateChanged); + } + + private void BuildFinished(BuildResult result) + { + IsBuildingOngoing = false; + BuildResult = result; + + string csvFile = Path.Combine(LastBuildInfo!.LogsDirPath, BuildManager.MsBuildIssuesFileName); + _problemsView.SetDiagnosticsFromFile(csvFile); + + _problemsView.UpdateProblemsView(); + + EmitSignal(SignalName.BuildStateChanged); + } + + private void UpdateBuildLogText() + { + lock (_pendingBuildLogTextLock) + { + _outputView.Append(_pendingBuildLogText); + _pendingBuildLogText = string.Empty; } } - private enum BuildMenuOptions + private void StdOutputReceived(string text) { - BuildProject, - RebuildProject, - CleanProject + lock (_pendingBuildLogTextLock) + { + if (_pendingBuildLogText.Length == 0) + CallDeferred(nameof(UpdateBuildLogText)); + _pendingBuildLogText += text + "\n"; + } + } + + private void StdErrorReceived(string text) + { + lock (_pendingBuildLogTextLock) + { + if (_pendingBuildLogText.Length == 0) + CallDeferred(nameof(UpdateBuildLogText)); + _pendingBuildLogText += text + "\n"; + } } public override void _Ready() { base._Ready(); - CustomMinimumSize = new Vector2(0, 228 * EditorScale); - SizeFlagsVertical = SizeFlags.ExpandFill; + var bottomPanelStylebox = EditorInterface.Singleton.GetBaseControl().GetThemeStylebox("BottomPanel", "EditorStyles"); + AddThemeConstantOverride("margin_top", -(int)bottomPanelStylebox.ContentMarginTop); + AddThemeConstantOverride("margin_left", -(int)bottomPanelStylebox.ContentMarginLeft); + AddThemeConstantOverride("margin_right", -(int)bottomPanelStylebox.ContentMarginRight); + + var tabs = new TabContainer(); + AddChild(tabs); - var toolBarHBox = new HBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill }; - AddChild(toolBarHBox); + var tabActions = new HBoxContainer + { + SizeFlagsVertical = SizeFlags.ExpandFill, + SizeFlagsHorizontal = SizeFlags.ExpandFill, + Alignment = BoxContainer.AlignmentMode.End, + }; + tabActions.SetAnchorsAndOffsetsPreset(LayoutPreset.FullRect); + tabs.GetTabBar().AddChild(tabActions); - _buildMenuBtn = new MenuButton { Text = "Build", Icon = GetThemeIcon("BuildCSharp", "EditorIcons") }; - toolBarHBox.AddChild(_buildMenuBtn); + _buildMenuButton = new MenuButton + { + TooltipText = "Build".TTR(), + Flat = true, + }; + tabActions.AddChild(_buildMenuButton); - var buildMenu = _buildMenuBtn.GetPopup(); + var buildMenu = _buildMenuButton.GetPopup(); buildMenu.AddItem("Build Project".TTR(), (int)BuildMenuOptions.BuildProject); buildMenu.AddItem("Rebuild Project".TTR(), (int)BuildMenuOptions.RebuildProject); buildMenu.AddItem("Clean Project".TTR(), (int)BuildMenuOptions.CleanProject); buildMenu.IdPressed += BuildMenuOptionPressed; - _errorsBtn = new Button + _openLogsFolderButton = new Button { - TooltipText = "Show Errors".TTR(), - Icon = GetThemeIcon("StatusError", "EditorIcons"), - ExpandIcon = false, - ToggleMode = true, - ButtonPressed = true, - FocusMode = FocusModeEnum.None + TooltipText = "Show Logs in File Manager".TTR(), + Flat = true, }; - _errorsBtn.Toggled += ErrorsToggled; - toolBarHBox.AddChild(_errorsBtn); + _openLogsFolderButton.Pressed += OpenLogsFolder; + tabActions.AddChild(_openLogsFolderButton); - _warningsBtn = new Button - { - TooltipText = "Show Warnings".TTR(), - Icon = GetThemeIcon("NodeWarning", "EditorIcons"), - ExpandIcon = false, - ToggleMode = true, - ButtonPressed = true, - FocusMode = FocusModeEnum.None - }; - _warningsBtn.Toggled += WarningsToggled; - toolBarHBox.AddChild(_warningsBtn); + _problemsView = new BuildProblemsView(); + tabs.AddChild(_problemsView); - _viewLogBtn = new Button - { - Text = "Show Output".TTR(), - ToggleMode = true, - ButtonPressed = true, - FocusMode = FocusModeEnum.None - }; - _viewLogBtn.Toggled += ViewLogToggled; - toolBarHBox.AddChild(_viewLogBtn); - - // Horizontal spacer, push everything to the right. - toolBarHBox.AddChild(new Control - { - SizeFlagsHorizontal = SizeFlags.ExpandFill, - }); + _outputView = new BuildOutputView(); + tabs.AddChild(_outputView); - _openLogsFolderBtn = new Button - { - Text = "Show Logs in File Manager".TTR(), - Icon = GetThemeIcon("Filesystem", "EditorIcons"), - ExpandIcon = false, - FocusMode = FocusModeEnum.None, - }; - _openLogsFolderBtn.Pressed += OpenLogsFolderPressed; - toolBarHBox.AddChild(_openLogsFolderBtn); + UpdateTheme(); - BuildOutputView = new BuildOutputView(); - AddChild(BuildOutputView); + AddBuildEventListeners(); } public override void _Notification(int what) @@ -183,13 +256,49 @@ namespace GodotTools.Build if (what == NotificationThemeChanged) { - if (_buildMenuBtn != null) - _buildMenuBtn.Icon = GetThemeIcon("BuildCSharp", "EditorIcons"); - if (_errorsBtn != null) - _errorsBtn.Icon = GetThemeIcon("StatusError", "EditorIcons"); - if (_warningsBtn != null) - _warningsBtn.Icon = GetThemeIcon("NodeWarning", "EditorIcons"); + UpdateTheme(); } } + + private void UpdateTheme() + { + // Nodes will be null until _Ready is called. + if (_buildMenuButton == null) + return; + + _buildMenuButton.Icon = GetThemeIcon("BuildCSharp", "EditorIcons"); + _openLogsFolderButton.Icon = GetThemeIcon("Filesystem", "EditorIcons"); + } + + private void AddBuildEventListeners() + { + BuildManager.BuildLaunchFailed += BuildLaunchFailed; + BuildManager.BuildStarted += BuildStarted; + BuildManager.BuildFinished += BuildFinished; + // StdOutput/Error can be received from different threads, so we need to use CallDeferred. + BuildManager.StdOutputReceived += StdOutputReceived; + BuildManager.StdErrorReceived += StdErrorReceived; + } + + public void OnBeforeSerialize() + { + // In case it didn't update yet. We don't want to have to serialize any pending output. + UpdateBuildLogText(); + + // NOTE: + // Currently, GodotTools is loaded in its own load context. This load context is not reloaded, but the script still are. + // Until that changes, we need workarounds like this one because events keep strong references to disposed objects. + BuildManager.BuildLaunchFailed -= BuildLaunchFailed; + BuildManager.BuildStarted -= BuildStarted; + BuildManager.BuildFinished -= BuildFinished; + // StdOutput/Error can be received from different threads, so we need to use CallDeferred + BuildManager.StdOutputReceived -= StdOutputReceived; + BuildManager.StdErrorReceived -= StdErrorReceived; + } + + public void OnAfterDeserialize() + { + AddBuildEventListeners(); // Re-add them. + } } } diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index e186c0302b..48e654c286 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -30,6 +30,7 @@ namespace GodotTools public const string VerbosityLevel = "dotnet/build/verbosity_level"; public const string NoConsoleLogging = "dotnet/build/no_console_logging"; public const string CreateBinaryLog = "dotnet/build/create_binary_log"; + public const string ProblemsLayout = "dotnet/build/problems_layout"; } private EditorSettings _editorSettings; @@ -437,7 +438,7 @@ namespace GodotTools private void BuildStateChanged() { if (_bottomPanelBtn != null) - _bottomPanelBtn.Icon = MSBuildPanel.BuildOutputView.BuildStateIcon; + _bottomPanelBtn.Icon = MSBuildPanel.GetBuildStateIcon(); } public override void _EnablePlugin() @@ -489,8 +490,7 @@ namespace GodotTools editorBaseControl.AddChild(_confirmCreateSlnDialog); MSBuildPanel = new MSBuildPanel(); - MSBuildPanel.Ready += () => - MSBuildPanel.BuildOutputView.BuildStateChanged += BuildStateChanged; + MSBuildPanel.BuildStateChanged += BuildStateChanged; _bottomPanelBtn = AddControlToBottomPanel(MSBuildPanel, "MSBuild".TTR()); AddChild(new HotReloadAssemblyWatcher { Name = "HotReloadAssemblyWatcher" }); @@ -535,6 +535,7 @@ namespace GodotTools EditorDef(Settings.VerbosityLevel, Variant.From(VerbosityLevelId.Normal)); EditorDef(Settings.NoConsoleLogging, false); EditorDef(Settings.CreateBinaryLog, false); + EditorDef(Settings.ProblemsLayout, Variant.From(BuildProblemsView.ProblemsLayout.Tree)); string settingsHintStr = "Disabled"; @@ -593,6 +594,14 @@ namespace GodotTools ["hint_string"] = string.Join(",", verbosityLevels), }); + _editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary + { + ["type"] = (int)Variant.Type.Int, + ["name"] = Settings.ProblemsLayout, + ["hint"] = (int)PropertyHint.Enum, + ["hint_string"] = "View as List,View as Tree", + }); + OnSettingsChanged(); _editorSettings.SettingsChanged += OnSettingsChanged; diff --git a/modules/svg/SCsub b/modules/svg/SCsub index c4d7671fb3..55b8c4f4a0 100644 --- a/modules/svg/SCsub +++ b/modules/svg/SCsub @@ -21,23 +21,22 @@ thirdparty_sources = [ "src/lib/sw_engine/tvgSwShape.cpp", "src/lib/sw_engine/tvgSwStroke.cpp", "src/lib/tvgAccessor.cpp", - "src/lib/tvgBezier.cpp", "src/lib/tvgCanvas.cpp", "src/lib/tvgFill.cpp", "src/lib/tvgGlCanvas.cpp", "src/lib/tvgInitializer.cpp", - "src/lib/tvgLinearGradient.cpp", "src/lib/tvgLoader.cpp", - "src/lib/tvgLzw.cpp", "src/lib/tvgPaint.cpp", "src/lib/tvgPicture.cpp", - "src/lib/tvgRadialGradient.cpp", "src/lib/tvgRender.cpp", "src/lib/tvgSaver.cpp", "src/lib/tvgScene.cpp", "src/lib/tvgShape.cpp", "src/lib/tvgSwCanvas.cpp", "src/lib/tvgTaskScheduler.cpp", + "src/utils/tvgBezier.cpp", + "src/utils/tvgCompressor.cpp", + "src/utils/tvgStr.cpp", "src/loaders/raw/tvgRawLoader.cpp", "src/loaders/svg/tvgSvgCssStyle.cpp", "src/loaders/svg/tvgSvgLoader.cpp", @@ -62,6 +61,7 @@ env_thirdparty.Prepend( thirdparty_dir + "src/lib/sw_engine", thirdparty_dir + "src/loaders/raw", thirdparty_dir + "src/loaders/svg", + thirdparty_dir + "src/utils", ] ) # Also requires libpng headers diff --git a/modules/text_server_adv/SCsub b/modules/text_server_adv/SCsub index 360741363a..1acff68135 100644 --- a/modules/text_server_adv/SCsub +++ b/modules/text_server_adv/SCsub @@ -39,7 +39,9 @@ freetype_enabled = "freetype" in env.module_list 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"]) + env_text_server_adv.Prepend( + CPPPATH=["#thirdparty/thorvg/inc", "#thirdparty/thorvg/src/lib", "#thirdparty/thorvg/src/utils"] + ) # Enable ThorVG static object linking. env_text_server_adv.Append(CPPDEFINES=["TVG_STATIC"]) diff --git a/modules/text_server_adv/gdextension_build/SConstruct b/modules/text_server_adv/gdextension_build/SConstruct index 38fd5f6403..bf29ad3016 100644 --- a/modules/text_server_adv/gdextension_build/SConstruct +++ b/modules/text_server_adv/gdextension_build/SConstruct @@ -52,23 +52,22 @@ if env["thorvg_enabled"] and env["freetype_enabled"]: "src/lib/sw_engine/tvgSwShape.cpp", "src/lib/sw_engine/tvgSwStroke.cpp", "src/lib/tvgAccessor.cpp", - "src/lib/tvgBezier.cpp", "src/lib/tvgCanvas.cpp", "src/lib/tvgFill.cpp", "src/lib/tvgGlCanvas.cpp", "src/lib/tvgInitializer.cpp", - "src/lib/tvgLinearGradient.cpp", "src/lib/tvgLoader.cpp", - "src/lib/tvgLzw.cpp", "src/lib/tvgPaint.cpp", "src/lib/tvgPicture.cpp", - "src/lib/tvgRadialGradient.cpp", "src/lib/tvgRender.cpp", "src/lib/tvgSaver.cpp", "src/lib/tvgScene.cpp", "src/lib/tvgShape.cpp", "src/lib/tvgSwCanvas.cpp", "src/lib/tvgTaskScheduler.cpp", + "src/utils/tvgBezier.cpp", + "src/utils/tvgCompressor.cpp", + "src/utils/tvgStr.cpp", "src/loaders/raw/tvgRawLoader.cpp", "src/loaders/svg/tvgSvgCssStyle.cpp", "src/loaders/svg/tvgSvgLoader.cpp", @@ -86,6 +85,7 @@ if env["thorvg_enabled"] and env["freetype_enabled"]: "../../../thirdparty/thorvg/src/lib/sw_engine", "../../../thirdparty/thorvg/src/loaders/raw", "../../../thirdparty/thorvg/src/loaders/svg", + "../../../thirdparty/thorvg/src/utils", "../../../thirdparty/libpng", ] ) @@ -93,7 +93,13 @@ if env["thorvg_enabled"] and env["freetype_enabled"]: # Enable ThorVG static object linking. env_tvg.Append(CPPDEFINES=["TVG_STATIC"]) - env.Append(CPPPATH=["../../../thirdparty/thorvg/inc", "../../../thirdparty/thorvg/src/lib"]) + env.Append( + CPPPATH=[ + "../../../thirdparty/thorvg/inc", + "../../../thirdparty/thorvg/src/lib", + "../../../thirdparty/thorvg/src/utils", + ] + ) env.Append(CPPDEFINES=["MODULE_SVG_ENABLED"]) lib = env_tvg.Library( diff --git a/modules/text_server_fb/SCsub b/modules/text_server_fb/SCsub index 0da2a54bc2..8705bc430d 100644 --- a/modules/text_server_fb/SCsub +++ b/modules/text_server_fb/SCsub @@ -9,7 +9,9 @@ msdfgen_enabled = "msdfgen" in env.module_list 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"]) + env_text_server_fb.Prepend( + CPPPATH=["#thirdparty/thorvg/inc", "#thirdparty/thorvg/src/lib", "#thirdparty/thorvg/src/utils"] + ) # Enable ThorVG static object linking. env_text_server_fb.Append(CPPDEFINES=["TVG_STATIC"]) diff --git a/modules/text_server_fb/gdextension_build/SConstruct b/modules/text_server_fb/gdextension_build/SConstruct index 20e1afa2e5..40bb2dc1b9 100644 --- a/modules/text_server_fb/gdextension_build/SConstruct +++ b/modules/text_server_fb/gdextension_build/SConstruct @@ -47,23 +47,22 @@ if env["thorvg_enabled"] and env["freetype_enabled"]: "src/lib/sw_engine/tvgSwShape.cpp", "src/lib/sw_engine/tvgSwStroke.cpp", "src/lib/tvgAccessor.cpp", - "src/lib/tvgBezier.cpp", "src/lib/tvgCanvas.cpp", "src/lib/tvgFill.cpp", "src/lib/tvgGlCanvas.cpp", "src/lib/tvgInitializer.cpp", - "src/lib/tvgLinearGradient.cpp", "src/lib/tvgLoader.cpp", - "src/lib/tvgLzw.cpp", "src/lib/tvgPaint.cpp", "src/lib/tvgPicture.cpp", - "src/lib/tvgRadialGradient.cpp", "src/lib/tvgRender.cpp", "src/lib/tvgSaver.cpp", "src/lib/tvgScene.cpp", "src/lib/tvgShape.cpp", "src/lib/tvgSwCanvas.cpp", "src/lib/tvgTaskScheduler.cpp", + "src/utils/tvgBezier.cpp", + "src/utils/tvgCompressor.cpp", + "src/utils/tvgStr.cpp", "src/loaders/raw/tvgRawLoader.cpp", "src/loaders/svg/tvgSvgCssStyle.cpp", "src/loaders/svg/tvgSvgLoader.cpp", @@ -81,6 +80,7 @@ if env["thorvg_enabled"] and env["freetype_enabled"]: "../../../thirdparty/thorvg/src/lib/sw_engine", "../../../thirdparty/thorvg/src/loaders/raw", "../../../thirdparty/thorvg/src/loaders/svg", + "../../../thirdparty/thorvg/src/utils", "../../../thirdparty/libpng", ] ) @@ -88,7 +88,13 @@ if env["thorvg_enabled"] and env["freetype_enabled"]: # Enable ThorVG static object linking. env_tvg.Append(CPPDEFINES=["TVG_STATIC"]) - env.Append(CPPPATH=["../../../thirdparty/thorvg/inc", "../../../thirdparty/thorvg/src/lib"]) + env.Append( + CPPPATH=[ + "../../../thirdparty/thorvg/inc", + "../../../thirdparty/thorvg/src/lib", + "../../../thirdparty/thorvg/src/utils", + ] + ) env.Append(CPPDEFINES=["MODULE_SVG_ENABLED"]) lib = env_tvg.Library( diff --git a/scene/3d/decal.cpp b/scene/3d/decal.cpp index 50a5b2da70..fecf70fe5d 100644 --- a/scene/3d/decal.cpp +++ b/scene/3d/decal.cpp @@ -32,7 +32,7 @@ void Decal::set_size(const Vector3 &p_size) { size = Vector3(MAX(0.001, p_size.x), MAX(0.001, p_size.y), MAX(0.001, p_size.z)); - RS::get_singleton()->decal_set_size(decal, p_size); + RS::get_singleton()->decal_set_size(decal, size); update_gizmos(); } diff --git a/scene/3d/mesh_instance_3d.cpp b/scene/3d/mesh_instance_3d.cpp index 0b0b098f65..78b02d74d5 100644 --- a/scene/3d/mesh_instance_3d.cpp +++ b/scene/3d/mesh_instance_3d.cpp @@ -458,6 +458,7 @@ MeshInstance3D *MeshInstance3D::create_debug_tangents_node() { sm->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); sm->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); sm->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); + sm->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); Ref<ArrayMesh> am; am.instantiate(); diff --git a/scene/3d/ray_cast_3d.cpp b/scene/3d/ray_cast_3d.cpp index 13519ecec7..b821181d29 100644 --- a/scene/3d/ray_cast_3d.cpp +++ b/scene/3d/ray_cast_3d.cpp @@ -463,6 +463,7 @@ void RayCast3D::_update_debug_shape_material(bool p_check_collision) { debug_material = material; material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + material->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); // Use double-sided rendering so that the RayCast can be seen if the camera is inside. material->set_cull_mode(BaseMaterial3D::CULL_DISABLED); material->set_transparency(BaseMaterial3D::TRANSPARENCY_ALPHA); diff --git a/scene/3d/shape_cast_3d.cpp b/scene/3d/shape_cast_3d.cpp index b6401832ed..4d9eeada06 100644 --- a/scene/3d/shape_cast_3d.cpp +++ b/scene/3d/shape_cast_3d.cpp @@ -547,6 +547,7 @@ void ShapeCast3D::_update_debug_shape_material(bool p_check_collision) { debug_material = material; material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + material->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); // Use double-sided rendering so that the RayCast can be seen if the camera is inside. material->set_cull_mode(BaseMaterial3D::CULL_DISABLED); material->set_transparency(BaseMaterial3D::TRANSPARENCY_ALPHA); diff --git a/scene/3d/voxelizer.cpp b/scene/3d/voxelizer.cpp index 42b0748698..7c752545db 100644 --- a/scene/3d/voxelizer.cpp +++ b/scene/3d/voxelizer.cpp @@ -990,6 +990,7 @@ Ref<MultiMesh> Voxelizer::create_debug_multimesh() { fsm->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); fsm->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); fsm->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + fsm->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); fsm->set_albedo(Color(1, 1, 1, 1)); mesh->surface_set_material(0, fsm); diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp index 14eed4c7a2..9a915939c2 100644 --- a/scene/gui/tab_bar.cpp +++ b/scene/gui/tab_bar.cpp @@ -948,7 +948,7 @@ void TabBar::_update_hover() { } } -void TabBar::_update_cache() { +void TabBar::_update_cache(bool p_update_hover) { if (tabs.is_empty()) { buttons_visible = false; return; @@ -1011,7 +1011,9 @@ void TabBar::_update_cache() { buttons_visible = offset > 0 || missing_right; if (tab_alignment == ALIGNMENT_LEFT) { - _update_hover(); + if (p_update_hover) { + _update_hover(); + } return; } @@ -1029,7 +1031,9 @@ void TabBar::_update_cache() { } } - _update_hover(); + if (p_update_hover) { + _update_hover(); + } } void TabBar::_on_mouse_exited() { @@ -1039,7 +1043,7 @@ void TabBar::_on_mouse_exited() { highlight_arrow = -1; dragging_valid_tab = false; - _update_cache(); + _update_cache(false); queue_redraw(); } @@ -1373,7 +1377,8 @@ int TabBar::get_tab_width(int p_idx) const { style = theme_cache.tab_disabled_style; } else if (current == p_idx) { style = theme_cache.tab_selected_style; - } else if (hover == p_idx) { + // Use the unselected style's width if the hovered one is shorter, to avoid an infinite loop when switching tabs with the mouse. + } else if (hover == p_idx && theme_cache.tab_hovered_style->get_minimum_size().width >= theme_cache.tab_unselected_style->get_minimum_size().width) { style = theme_cache.tab_hovered_style; } else { style = theme_cache.tab_unselected_style; diff --git a/scene/gui/tab_bar.h b/scene/gui/tab_bar.h index b79c170a7b..4bce30ea52 100644 --- a/scene/gui/tab_bar.h +++ b/scene/gui/tab_bar.h @@ -148,7 +148,7 @@ private: void _ensure_no_over_offset(); void _update_hover(); - void _update_cache(); + void _update_cache(bool p_update_hover = true); void _on_mouse_exited(); diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index d357e35c1d..ebe4f4c59d 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -789,6 +789,7 @@ Ref<Material> SceneTree::get_debug_paths_material() { _debug_material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); _debug_material->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); _debug_material->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); + _debug_material->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); _debug_material->set_albedo(get_debug_paths_color()); debug_paths_material = _debug_material; @@ -808,6 +809,7 @@ Ref<Material> SceneTree::get_debug_collision_material() { line_material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); line_material->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); line_material->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); + line_material->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); line_material->set_albedo(get_debug_collisions_color()); collision_material = line_material; @@ -829,6 +831,7 @@ Ref<ArrayMesh> SceneTree::get_debug_contact_mesh() { mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); + mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); mat->set_albedo(get_debug_collision_contact_color()); Vector3 diamond[6] = { diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 3a4fe9a059..9472c75273 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -819,6 +819,9 @@ void Viewport::_process_picking() { sorter.sort(res, rc); } for (int i = 0; i < rc; i++) { + if (is_input_handled()) { + break; + } if (res[i].collider_id.is_valid() && res[i].collider) { CollisionObject2D *co = Object::cast_to<CollisionObject2D>(res[i].collider); if (co && co->can_process()) { diff --git a/scene/theme/default_theme.cpp b/scene/theme/default_theme.cpp index b65c372fab..91d7e41cc9 100644 --- a/scene/theme/default_theme.cpp +++ b/scene/theme/default_theme.cpp @@ -191,7 +191,6 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_stylebox("hover", "MenuBar", button_hover); theme->set_stylebox("pressed", "MenuBar", button_pressed); theme->set_stylebox("disabled", "MenuBar", button_disabled); - theme->set_stylebox("focus", "MenuBar", focus); theme->set_font("font", "MenuBar", Ref<Font>()); theme->set_font_size("font_size", "MenuBar", -1); @@ -451,7 +450,6 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_font_size("font_size", "ProgressBar", -1); theme->set_color("font_color", "ProgressBar", control_font_hover_color); - theme->set_color("font_shadow_color", "ProgressBar", Color(0, 0, 0)); theme->set_color("font_outline_color", "ProgressBar", Color(1, 1, 1)); theme->set_constant("outline_size", "ProgressBar", 0); @@ -513,7 +511,6 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("completion_existing_color", "CodeEdit", Color(0.87, 0.87, 0.87, 0.13)); theme->set_color("completion_scroll_color", "CodeEdit", control_font_pressed_color * Color(1, 1, 1, 0.29)); theme->set_color("completion_scroll_hovered_color", "CodeEdit", control_font_pressed_color * Color(1, 1, 1, 0.4)); - theme->set_color("completion_font_color", "CodeEdit", Color(0.67, 0.67, 0.67)); theme->set_color("font_color", "CodeEdit", control_font_color); theme->set_color("font_selected_color", "CodeEdit", Color(0, 0, 0, 0)); theme->set_color("font_readonly_color", "CodeEdit", Color(control_font_color.r, control_font_color.g, control_font_color.b, 0.5f)); @@ -680,11 +677,8 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const Ref<StyleBoxFlat> style_popup_panel = make_flat_stylebox(style_popup_color); style_popup_panel->set_border_width_all(2); style_popup_panel->set_border_color(style_popup_border_color); - Ref<StyleBoxFlat> style_popup_panel_disabled = style_popup_panel->duplicate(); - style_popup_panel_disabled->set_bg_color(style_disabled_color); theme->set_stylebox("panel", "PopupMenu", style_popup_panel); - theme->set_stylebox("panel_disabled", "PopupMenu", style_popup_panel_disabled); theme->set_stylebox("hover", "PopupMenu", make_flat_stylebox(style_popup_hover_color)); theme->set_stylebox("separator", "PopupMenu", separator_horizontal); theme->set_stylebox("labeled_separator_left", "PopupMenu", separator_horizontal); diff --git a/scene/theme/theme_db.cpp b/scene/theme/theme_db.cpp index c0e7636f5a..8dc9d288e2 100644 --- a/scene/theme/theme_db.cpp +++ b/scene/theme/theme_db.cpp @@ -38,7 +38,6 @@ #include "scene/resources/font.h" #include "scene/resources/style_box.h" #include "scene/resources/texture.h" -#include "scene/resources/theme.h" #include "scene/theme/default_theme.h" #include "servers/text_server.h" @@ -328,10 +327,11 @@ ThemeContext *ThemeDB::get_nearest_theme_context(Node *p_for_node) const { // Theme item binding. -void ThemeDB::bind_class_item(const StringName &p_class_name, const StringName &p_prop_name, const StringName &p_item_name, ThemeItemSetter p_setter) { +void ThemeDB::bind_class_item(Theme::DataType p_data_type, const StringName &p_class_name, const StringName &p_prop_name, const StringName &p_item_name, ThemeItemSetter p_setter) { ERR_FAIL_COND_MSG(theme_item_binds[p_class_name].has(p_prop_name), vformat("Failed to bind theme item '%s' in class '%s': already bound", p_prop_name, p_class_name)); ThemeItemBind bind; + bind.data_type = p_data_type; bind.class_name = p_class_name; bind.item_name = p_item_name; bind.setter = p_setter; @@ -339,10 +339,11 @@ void ThemeDB::bind_class_item(const StringName &p_class_name, const StringName & theme_item_binds[p_class_name][p_prop_name] = bind; } -void ThemeDB::bind_class_external_item(const StringName &p_class_name, const StringName &p_prop_name, const StringName &p_item_name, const StringName &p_type_name, ThemeItemSetter p_setter) { +void ThemeDB::bind_class_external_item(Theme::DataType p_data_type, const StringName &p_class_name, const StringName &p_prop_name, const StringName &p_item_name, const StringName &p_type_name, ThemeItemSetter p_setter) { ERR_FAIL_COND_MSG(theme_item_binds[p_class_name].has(p_prop_name), vformat("Failed to bind theme item '%s' in class '%s': already bound", p_prop_name, p_class_name)); ThemeItemBind bind; + bind.data_type = p_data_type; bind.class_name = p_class_name; bind.item_name = p_item_name; bind.type_name = p_type_name; @@ -370,6 +371,33 @@ void ThemeDB::update_class_instance_items(Node *p_instance) { } } +void ThemeDB::get_class_own_items(const StringName &p_class_name, List<ThemeItemBind> *r_list) { + List<StringName> class_hierarchy; + StringName class_name = p_class_name; + while (class_name != StringName()) { + class_hierarchy.push_front(class_name); // Put parent classes in front. + class_name = ClassDB::get_parent_class_nocheck(class_name); + } + + HashSet<StringName> inherited_props; + for (const StringName &theme_type : class_hierarchy) { + HashMap<StringName, HashMap<StringName, ThemeItemBind>>::Iterator E = theme_item_binds.find(theme_type); + if (E) { + for (const KeyValue<StringName, ThemeItemBind> &F : E->value) { + if (inherited_props.has(F.value.item_name)) { + continue; // Skip inherited properties. + } + if (F.value.external || F.value.class_name != p_class_name) { + inherited_props.insert(F.value.item_name); + continue; // Track properties defined in parent classes, and skip them. + } + + r_list->push_back(F.value); + } + } + } +} + // Object methods. void ThemeDB::_bind_methods() { diff --git a/scene/theme/theme_db.h b/scene/theme/theme_db.h index 07325e2d05..e472f4e935 100644 --- a/scene/theme/theme_db.h +++ b/scene/theme/theme_db.h @@ -33,6 +33,7 @@ #include "core/object/class_db.h" #include "core/object/ref_counted.h" +#include "scene/resources/theme.h" #include <functional> @@ -40,31 +41,30 @@ class Font; class Node; class StyleBox; class Texture2D; -class Theme; class ThemeContext; // Macros for binding theme items of this class. This information is used for the documentation, theme // overrides, etc. This is also the basis for theme cache. -#define BIND_THEME_ITEM(m_data_type, m_class, m_prop) \ - ThemeDB::get_singleton()->bind_class_item(get_class_static(), #m_prop, #m_prop, [](Node *p_instance) { \ - m_class *p_cast = Object::cast_to<m_class>(p_instance); \ - p_cast->theme_cache.m_prop = p_cast->get_theme_item(m_data_type, _scs_create(#m_prop)); \ +#define BIND_THEME_ITEM(m_data_type, m_class, m_prop) \ + ThemeDB::get_singleton()->bind_class_item(m_data_type, get_class_static(), #m_prop, #m_prop, [](Node *p_instance) { \ + m_class *p_cast = Object::cast_to<m_class>(p_instance); \ + p_cast->theme_cache.m_prop = p_cast->get_theme_item(m_data_type, _scs_create(#m_prop)); \ }) -#define BIND_THEME_ITEM_CUSTOM(m_data_type, m_class, m_prop, m_item_name) \ - ThemeDB::get_singleton()->bind_class_item(get_class_static(), #m_prop, m_item_name, [](Node *p_instance) { \ - m_class *p_cast = Object::cast_to<m_class>(p_instance); \ - p_cast->theme_cache.m_prop = p_cast->get_theme_item(m_data_type, _scs_create(m_item_name)); \ +#define BIND_THEME_ITEM_CUSTOM(m_data_type, m_class, m_prop, m_item_name) \ + ThemeDB::get_singleton()->bind_class_item(m_data_type, get_class_static(), #m_prop, m_item_name, [](Node *p_instance) { \ + m_class *p_cast = Object::cast_to<m_class>(p_instance); \ + p_cast->theme_cache.m_prop = p_cast->get_theme_item(m_data_type, _scs_create(m_item_name)); \ }) // Macro for binding theme items used by this class, but defined/binded by other classes. This is primarily used for // the theme cache. Can also be used to list such items in documentation. -#define BIND_THEME_ITEM_EXT(m_data_type, m_class, m_prop, m_item_name, m_type_name) \ - ThemeDB::get_singleton()->bind_class_external_item(get_class_static(), #m_prop, m_item_name, m_type_name, [](Node *p_instance) { \ - m_class *p_cast = Object::cast_to<m_class>(p_instance); \ - p_cast->theme_cache.m_prop = p_cast->get_theme_item(m_data_type, _scs_create(m_item_name), _scs_create(m_type_name)); \ +#define BIND_THEME_ITEM_EXT(m_data_type, m_class, m_prop, m_item_name, m_type_name) \ + ThemeDB::get_singleton()->bind_class_external_item(m_data_type, get_class_static(), #m_prop, m_item_name, m_type_name, [](Node *p_instance) { \ + m_class *p_cast = Object::cast_to<m_class>(p_instance); \ + p_cast->theme_cache.m_prop = p_cast->get_theme_item(m_data_type, _scs_create(m_item_name), _scs_create(m_type_name)); \ }) class ThemeDB : public Object { @@ -96,9 +96,11 @@ class ThemeDB : public Object { // Binding of theme items to Node classes. +public: typedef std::function<void(Node *)> ThemeItemSetter; struct ThemeItemBind { + Theme::DataType data_type; StringName class_name; StringName item_name; StringName type_name; @@ -107,6 +109,7 @@ class ThemeDB : public Object { ThemeItemSetter setter; }; +private: HashMap<StringName, HashMap<StringName, ThemeItemBind>> theme_item_binds; protected: @@ -155,10 +158,12 @@ public: // Theme item binding. - void bind_class_item(const StringName &p_class_name, const StringName &p_prop_name, const StringName &p_item_name, ThemeItemSetter p_setter); - void bind_class_external_item(const StringName &p_class_name, const StringName &p_prop_name, const StringName &p_item_name, const StringName &p_type_name, ThemeItemSetter p_setter); + void bind_class_item(Theme::DataType p_data_type, const StringName &p_class_name, const StringName &p_prop_name, const StringName &p_item_name, ThemeItemSetter p_setter); + void bind_class_external_item(Theme::DataType p_data_type, const StringName &p_class_name, const StringName &p_prop_name, const StringName &p_item_name, const StringName &p_type_name, ThemeItemSetter p_setter); void update_class_instance_items(Node *p_instance); + void get_class_own_items(const StringName &p_class_name, List<ThemeItemBind> *r_list); + // Memory management, reference, and initialization. static ThemeDB *get_singleton(); diff --git a/servers/audio/audio_stream.cpp b/servers/audio/audio_stream.cpp index 70496ee65b..455a8b49a1 100644 --- a/servers/audio/audio_stream.cpp +++ b/servers/audio/audio_stream.cpp @@ -730,11 +730,11 @@ void AudioStreamRandomizer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_playback_mode", "mode"), &AudioStreamRandomizer::set_playback_mode); ClassDB::bind_method(D_METHOD("get_playback_mode"), &AudioStreamRandomizer::get_playback_mode); - ADD_ARRAY("streams", "stream_"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "streams_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_streams_count", "get_streams_count"); ADD_PROPERTY(PropertyInfo(Variant::INT, "playback_mode", PROPERTY_HINT_ENUM, "Random (Avoid Repeats),Random,Sequential"), "set_playback_mode", "get_playback_mode"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "random_pitch", PROPERTY_HINT_RANGE, "1,16,0.01"), "set_random_pitch", "get_random_pitch"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "random_volume_offset_db", PROPERTY_HINT_RANGE, "0,40,0.01,suffix:dB"), "set_random_volume_offset_db", "get_random_volume_offset_db"); + ADD_ARRAY("streams", "stream_"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "streams_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_streams_count", "get_streams_count"); BIND_ENUM_CONSTANT(PLAYBACK_RANDOM_NO_REPEATS); BIND_ENUM_CONSTANT(PLAYBACK_RANDOM); diff --git a/servers/navigation_server_3d.cpp b/servers/navigation_server_3d.cpp index 75036b935b..eccacd3ea8 100644 --- a/servers/navigation_server_3d.cpp +++ b/servers/navigation_server_3d.cpp @@ -301,6 +301,7 @@ Ref<StandardMaterial3D> NavigationServer3D::get_debug_navigation_geometry_face_m face_material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); face_material->set_albedo(get_debug_navigation_geometry_face_color()); face_material->set_cull_mode(StandardMaterial3D::CULL_DISABLED); + face_material->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); if (enabled_geometry_face_random_color) { face_material->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); face_material->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); @@ -321,6 +322,7 @@ Ref<StandardMaterial3D> NavigationServer3D::get_debug_navigation_geometry_edge_m Ref<StandardMaterial3D> line_material = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); line_material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); line_material->set_albedo(get_debug_navigation_geometry_edge_color()); + line_material->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); if (enabled_edge_lines_xray) { line_material->set_flag(StandardMaterial3D::FLAG_DISABLE_DEPTH_TEST, true); } @@ -339,6 +341,7 @@ Ref<StandardMaterial3D> NavigationServer3D::get_debug_navigation_geometry_face_d face_disabled_material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); face_disabled_material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); face_disabled_material->set_albedo(get_debug_navigation_geometry_face_disabled_color()); + face_disabled_material->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); debug_navigation_geometry_face_disabled_material = face_disabled_material; @@ -355,6 +358,7 @@ Ref<StandardMaterial3D> NavigationServer3D::get_debug_navigation_geometry_edge_d Ref<StandardMaterial3D> line_disabled_material = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); line_disabled_material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); line_disabled_material->set_albedo(get_debug_navigation_geometry_edge_disabled_color()); + line_disabled_material->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); if (enabled_edge_lines_xray) { line_disabled_material->set_flag(StandardMaterial3D::FLAG_DISABLE_DEPTH_TEST, true); } @@ -374,6 +378,7 @@ Ref<StandardMaterial3D> NavigationServer3D::get_debug_navigation_edge_connection Ref<StandardMaterial3D> edge_connections_material = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); edge_connections_material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); edge_connections_material->set_albedo(get_debug_navigation_edge_connection_color()); + edge_connections_material->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); if (enabled_edge_connections_xray) { edge_connections_material->set_flag(StandardMaterial3D::FLAG_DISABLE_DEPTH_TEST, true); } @@ -392,6 +397,7 @@ Ref<StandardMaterial3D> NavigationServer3D::get_debug_navigation_link_connection Ref<StandardMaterial3D> material = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); material->set_albedo(debug_navigation_link_connection_color); + material->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); if (debug_navigation_enable_link_connections_xray) { material->set_flag(StandardMaterial3D::FLAG_DISABLE_DEPTH_TEST, true); } @@ -409,6 +415,7 @@ Ref<StandardMaterial3D> NavigationServer3D::get_debug_navigation_link_connection Ref<StandardMaterial3D> material = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); material->set_albedo(debug_navigation_link_connection_disabled_color); + material->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); if (debug_navigation_enable_link_connections_xray) { material->set_flag(StandardMaterial3D::FLAG_DISABLE_DEPTH_TEST, true); } @@ -427,6 +434,7 @@ Ref<StandardMaterial3D> NavigationServer3D::get_debug_navigation_agent_path_line material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); material->set_albedo(debug_navigation_agent_path_color); + material->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); if (debug_navigation_enable_agent_paths_xray) { material->set_flag(StandardMaterial3D::FLAG_DISABLE_DEPTH_TEST, true); } @@ -445,6 +453,7 @@ Ref<StandardMaterial3D> NavigationServer3D::get_debug_navigation_agent_path_poin material->set_albedo(debug_navigation_agent_path_color); material->set_flag(StandardMaterial3D::FLAG_USE_POINT_SIZE, true); material->set_point_size(debug_navigation_agent_path_point_size); + material->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); if (debug_navigation_enable_agent_paths_xray) { material->set_flag(StandardMaterial3D::FLAG_DISABLE_DEPTH_TEST, true); } @@ -476,6 +485,7 @@ Ref<StandardMaterial3D> NavigationServer3D::get_debug_navigation_avoidance_obsta Ref<StandardMaterial3D> material = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + material->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); material->set_cull_mode(StandardMaterial3D::CULL_DISABLED); material->set_albedo(debug_navigation_avoidance_obstacles_radius_color); @@ -492,6 +502,7 @@ Ref<StandardMaterial3D> NavigationServer3D::get_debug_navigation_avoidance_stati Ref<StandardMaterial3D> material = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + material->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); material->set_cull_mode(StandardMaterial3D::CULL_DISABLED); material->set_albedo(debug_navigation_avoidance_static_obstacle_pushin_face_color); @@ -508,6 +519,7 @@ Ref<StandardMaterial3D> NavigationServer3D::get_debug_navigation_avoidance_stati Ref<StandardMaterial3D> material = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + material->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); material->set_cull_mode(StandardMaterial3D::CULL_DISABLED); material->set_albedo(debug_navigation_avoidance_static_obstacle_pushout_face_color); @@ -524,6 +536,7 @@ Ref<StandardMaterial3D> NavigationServer3D::get_debug_navigation_avoidance_stati Ref<StandardMaterial3D> material = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + material->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); //material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); //material->set_cull_mode(StandardMaterial3D::CULL_DISABLED); material->set_albedo(debug_navigation_avoidance_static_obstacle_pushin_edge_color); @@ -541,6 +554,7 @@ Ref<StandardMaterial3D> NavigationServer3D::get_debug_navigation_avoidance_stati Ref<StandardMaterial3D> material = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + material->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); ///material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); //material->set_cull_mode(StandardMaterial3D::CULL_DISABLED); material->set_albedo(debug_navigation_avoidance_static_obstacle_pushout_edge_color); diff --git a/servers/physics_server_3d.h b/servers/physics_server_3d.h index d78a59e17f..248b6cd8f8 100644 --- a/servers/physics_server_3d.h +++ b/servers/physics_server_3d.h @@ -150,11 +150,11 @@ public: struct RayResult { Vector3 position; Vector3 normal; - int face_index = -1; RID rid; ObjectID collider_id; Object *collider = nullptr; int shape = 0; + int face_index = -1; }; virtual bool intersect_ray(const RayParameters &p_parameters, RayResult &r_result) = 0; diff --git a/servers/register_server_types.cpp b/servers/register_server_types.cpp index 67e48df9c9..50e14a1f37 100644 --- a/servers/register_server_types.cpp +++ b/servers/register_server_types.cpp @@ -157,7 +157,7 @@ void register_server_types() { GDREGISTER_VIRTUAL_CLASS(PhysicsDirectSpaceState3DExtension) GDREGISTER_VIRTUAL_CLASS(PhysicsServer3DRenderingServerHandler) - GDREGISTER_NATIVE_STRUCT(PhysicsServer3DExtensionRayResult, "Vector3 position;Vector3 normal;RID rid;ObjectID collider_id;Object *collider;int shape"); + GDREGISTER_NATIVE_STRUCT(PhysicsServer3DExtensionRayResult, "Vector3 position;Vector3 normal;RID rid;ObjectID collider_id;Object *collider;int shape;int face_index"); GDREGISTER_NATIVE_STRUCT(PhysicsServer3DExtensionShapeResult, "RID rid;ObjectID collider_id;Object *collider;int shape"); GDREGISTER_NATIVE_STRUCT(PhysicsServer3DExtensionShapeRestInfo, "Vector3 point;Vector3 normal;RID rid;ObjectID collider_id;int shape;Vector3 linear_velocity"); GDREGISTER_NATIVE_STRUCT(PhysicsServer3DExtensionMotionCollision, "Vector3 position;Vector3 normal;Vector3 collider_velocity;Vector3 collider_angular_velocity;real_t depth;int local_shape;ObjectID collider_id;RID collider;int collider_shape"); diff --git a/thirdparty/README.md b/thirdparty/README.md index 9da4905943..15d9ad4e8e 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -815,7 +815,7 @@ instead of `miniz.h` as an external dependency. ## thorvg - Upstream: https://github.com/thorvg/thorvg -- Version: 0.10.0 (b8c605583fd7de73209a93a1238e1ba72cce2e8f, 2023) +- Version: 0.10.7 (026ff4ce7eda10dd0cf80eeaef56fe3a5ed89f93, 2023) - License: MIT Files extracted from upstream source: diff --git a/thirdparty/thorvg/inc/config.h b/thirdparty/thorvg/inc/config.h index 87125418fb..b2efe3def3 100644 --- a/thirdparty/thorvg/inc/config.h +++ b/thirdparty/thorvg/inc/config.h @@ -5,5 +5,5 @@ #define THORVG_SVG_LOADER_SUPPORT -#define THORVG_VERSION_STRING "0.10.0" +#define THORVG_VERSION_STRING "0.10.7" #endif diff --git a/thirdparty/thorvg/inc/thorvg.h b/thirdparty/thorvg/inc/thorvg.h index 897296fa9d..a5efc5ec17 100644 --- a/thirdparty/thorvg/inc/thorvg.h +++ b/thirdparty/thorvg/inc/thorvg.h @@ -1268,7 +1268,7 @@ public: * * @param[in] data A pointer to a memory location where the content of the picture file is stored. * @param[in] size The size in bytes of the memory occupied by the @p data. - * @param[in] mimeType Mimetype or extension of data such as "jpg", "jpeg", "svg", "svg+xml", "png", etc. In case an empty string or an unknown type is provided, the loaders will be tried one by one. + * @param[in] mimeType Mimetype or extension of data such as "jpg", "jpeg", "lottie", "svg", "svg+xml", "png", etc. In case an empty string or an unknown type is provided, the loaders will be tried one by one. * @param[in] copy If @c true the data are copied into the engine local buffer, otherwise they are not. * * @retval Result::Success When succeed. @@ -1278,6 +1278,7 @@ public: * * @warning: It's the user responsibility to release the @p data memory if the @p copy is @c true. * + * @note If you are unsure about the MIME type, you can provide an empty value like @c "", and thorvg will attempt to figure it out. * @since 0.5 */ Result load(const char* data, uint32_t size, const std::string& mimeType, bool copy = false) noexcept; diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwCommon.h b/thirdparty/thorvg/src/lib/sw_engine/tvgSwCommon.h index 3d68b56fb8..4cee0b18e2 100644 --- a/thirdparty/thorvg/src/lib/sw_engine/tvgSwCommon.h +++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwCommon.h @@ -26,6 +26,8 @@ #include "tvgCommon.h" #include "tvgRender.h" +#include <algorithm> + #if 0 #include <sys/time.h> static double timeStamp() @@ -139,10 +141,11 @@ struct SwFill }; struct SwRadial { - float a11, a12, shiftX; - float a21, a22, shiftY; - float detSecDeriv; - float a; + float a11, a12, a13; + float a21, a22, a23; + float fx, fy, fr; + float dx, dy, dr; + float invA, a; }; union { @@ -194,14 +197,14 @@ struct SwStroke struct SwDashStroke { - SwOutline* outline; - float curLen; - int32_t curIdx; - Point ptStart; - Point ptCur; - float* pattern; - uint32_t cnt; - bool curOpGap; + SwOutline* outline = nullptr; + float curLen = 0; + int32_t curIdx = 0; + Point ptStart = {0, 0}; + Point ptCur = {0, 0}; + float* pattern = nullptr; + uint32_t cnt = 0; + bool curOpGap = false; }; struct SwShape @@ -235,6 +238,7 @@ struct SwImage bool scaled = false; //draw scaled image }; +typedef uint8_t(*SwMask)(uint8_t s, uint8_t d, uint8_t a); //src, dst, alpha typedef uint32_t(*SwBlender)(uint32_t s, uint32_t d, uint8_t a); //src, dst, alpha typedef uint32_t(*SwJoin)(uint8_t r, uint8_t g, uint8_t b, uint8_t a); //color channel join typedef uint8_t(*SwAlpha)(uint8_t*); //blending alpha @@ -295,7 +299,7 @@ static inline uint32_t INTERPOLATE(uint32_t s, uint32_t d, uint8_t a) static inline uint8_t INTERPOLATE8(uint8_t s, uint8_t d, uint8_t a) { - return ((s * a + 0xff) >> 8) + ((d * ~a + 0xff) >> 8); + return (((s) * (a) + 0xff) >> 8) + (((d) * ~(a) + 0xff) >> 8); } static inline SwCoord HALF_STROKE(float width) @@ -363,18 +367,18 @@ static inline uint32_t opBlendDifference(uint32_t s, uint32_t d, TVG_UNUSED uint static inline uint32_t opBlendExclusion(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a) { //A + B - 2AB - auto c1 = min(255, C1(s) + C1(d) - min(255, (C1(s) * C1(d)) << 1)); - auto c2 = min(255, C2(s) + C2(d) - min(255, (C2(s) * C2(d)) << 1)); - auto c3 = min(255, C3(s) + C3(d) - min(255, (C3(s) * C3(d)) << 1)); + auto c1 = std::min(255, C1(s) + C1(d) - std::min(255, (C1(s) * C1(d)) << 1)); + auto c2 = std::min(255, C2(s) + C2(d) - std::min(255, (C2(s) * C2(d)) << 1)); + auto c3 = std::min(255, C3(s) + C3(d) - std::min(255, (C3(s) * C3(d)) << 1)); return JOIN(255, c1, c2, c3); } static inline uint32_t opBlendAdd(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a) { // s + d - auto c1 = min(C1(s) + C1(d), 255); - auto c2 = min(C2(s) + C2(d), 255); - auto c3 = min(C3(s) + C3(d), 255); + auto c1 = std::min(C1(s) + C1(d), 255); + auto c2 = std::min(C2(s) + C2(d), 255); + auto c3 = std::min(C3(s) + C3(d), 255); return JOIN(255, c1, c2, c3); } @@ -402,27 +406,27 @@ static inline uint32_t opBlendOverlay(uint32_t s, uint32_t d, TVG_UNUSED uint8_t { // if (2 * d < da) => 2 * s * d, // else => 1 - 2 * (1 - s) * (1 - d) - auto c1 = (C1(d) < 128) ? min(255, 2 * MULTIPLY(C1(s), C1(d))) : (255 - min(255, 2 * MULTIPLY(255 - C1(s), 255 - C1(d)))); - auto c2 = (C2(d) < 128) ? min(255, 2 * MULTIPLY(C2(s), C2(d))) : (255 - min(255, 2 * MULTIPLY(255 - C2(s), 255 - C2(d)))); - auto c3 = (C3(d) < 128) ? min(255, 2 * MULTIPLY(C3(s), C3(d))) : (255 - min(255, 2 * MULTIPLY(255 - C3(s), 255 - C3(d)))); + auto c1 = (C1(d) < 128) ? std::min(255, 2 * MULTIPLY(C1(s), C1(d))) : (255 - std::min(255, 2 * MULTIPLY(255 - C1(s), 255 - C1(d)))); + auto c2 = (C2(d) < 128) ? std::min(255, 2 * MULTIPLY(C2(s), C2(d))) : (255 - std::min(255, 2 * MULTIPLY(255 - C2(s), 255 - C2(d)))); + auto c3 = (C3(d) < 128) ? std::min(255, 2 * MULTIPLY(C3(s), C3(d))) : (255 - std::min(255, 2 * MULTIPLY(255 - C3(s), 255 - C3(d)))); return JOIN(255, c1, c2, c3); } static inline uint32_t opBlendDarken(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a) { // min(s, d) - auto c1 = min(C1(s), C1(d)); - auto c2 = min(C2(s), C2(d)); - auto c3 = min(C3(s), C3(d)); + auto c1 = std::min(C1(s), C1(d)); + auto c2 = std::min(C2(s), C2(d)); + auto c3 = std::min(C3(s), C3(d)); return JOIN(255, c1, c2, c3); } static inline uint32_t opBlendLighten(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a) { // max(s, d) - auto c1 = max(C1(s), C1(d)); - auto c2 = max(C2(s), C2(d)); - auto c3 = max(C3(s), C3(d)); + auto c1 = std::max(C1(s), C1(d)); + auto c2 = std::max(C2(s), C2(d)); + auto c3 = std::max(C3(s), C3(d)); return JOIN(255, c1, c2, c3); } @@ -448,61 +452,21 @@ static inline uint32_t opBlendColorBurn(uint32_t s, uint32_t d, TVG_UNUSED uint8 static inline uint32_t opBlendHardLight(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a) { - auto c1 = (C1(s) < 128) ? min(255, 2 * MULTIPLY(C1(s), C1(d))) : (255 - min(255, 2 * MULTIPLY(255 - C1(s), 255 - C1(d)))); - auto c2 = (C2(s) < 128) ? min(255, 2 * MULTIPLY(C2(s), C2(d))) : (255 - min(255, 2 * MULTIPLY(255 - C2(s), 255 - C2(d)))); - auto c3 = (C3(s) < 128) ? min(255, 2 * MULTIPLY(C3(s), C3(d))) : (255 - min(255, 2 * MULTIPLY(255 - C3(s), 255 - C3(d)))); + auto c1 = (C1(s) < 128) ? std::min(255, 2 * MULTIPLY(C1(s), C1(d))) : (255 - std::min(255, 2 * MULTIPLY(255 - C1(s), 255 - C1(d)))); + auto c2 = (C2(s) < 128) ? std::min(255, 2 * MULTIPLY(C2(s), C2(d))) : (255 - std::min(255, 2 * MULTIPLY(255 - C2(s), 255 - C2(d)))); + auto c3 = (C3(s) < 128) ? std::min(255, 2 * MULTIPLY(C3(s), C3(d))) : (255 - std::min(255, 2 * MULTIPLY(255 - C3(s), 255 - C3(d)))); return JOIN(255, c1, c2, c3); } static inline uint32_t opBlendSoftLight(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a) { //(255 - 2 * s) * (d * d) + (2 * s * b) - auto c1 = min(255, MULTIPLY(255 - min(255, 2 * C1(s)), MULTIPLY(C1(d), C1(d))) + 2 * MULTIPLY(C1(s), C1(d))); - auto c2 = min(255, MULTIPLY(255 - min(255, 2 * C2(s)), MULTIPLY(C2(d), C2(d))) + 2 * MULTIPLY(C2(s), C2(d))); - auto c3 = min(255, MULTIPLY(255 - min(255, 2 * C3(s)), MULTIPLY(C3(d), C3(d))) + 2 * MULTIPLY(C3(s), C3(d))); + auto c1 = std::min(255, MULTIPLY(255 - std::min(255, 2 * C1(s)), MULTIPLY(C1(d), C1(d))) + 2 * MULTIPLY(C1(s), C1(d))); + auto c2 = std::min(255, MULTIPLY(255 - std::min(255, 2 * C2(s)), MULTIPLY(C2(d), C2(d))) + 2 * MULTIPLY(C2(s), C2(d))); + auto c3 = std::min(255, MULTIPLY(255 - std::min(255, 2 * C3(s)), MULTIPLY(C3(d), C3(d))) + 2 * MULTIPLY(C3(s), C3(d))); return JOIN(255, c1, c2, c3); } -static inline uint32_t opMaskAdd(uint32_t s, uint32_t d, uint8_t a) -{ - return opBlendNormal(s, d, a); -} - -static inline uint32_t opMaskSubtract(uint32_t s, uint32_t d, uint8_t a) -{ - return ALPHA_BLEND(d, MULTIPLY(IA(s), a)); -} - -static inline uint32_t opMaskDifference(uint32_t s, uint32_t d, uint8_t a) -{ - auto t = ALPHA_BLEND(s, a); - return ALPHA_BLEND(t, IA(d)) + ALPHA_BLEND(d, IA(t)); -} - -static inline uint32_t opMaskIntersect(uint32_t s, uint32_t d, uint8_t a) -{ - return ALPHA_BLEND(d, MULTIPLY(IA(s), a)); -} - -static inline uint32_t opMaskPreAdd(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a) -{ - return opBlendPreNormal(s, d, a); -} - -static inline uint32_t opMaskPreSubtract(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a) -{ - return ALPHA_BLEND(d, IA(s)); -} - -static inline uint32_t opMaskPreDifference(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a) -{ - return ALPHA_BLEND(s, IA(d)) + ALPHA_BLEND(d, IA(s)); -} - -static inline uint32_t opMaskPreIntersect(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a) -{ - return ALPHA_BLEND(d, MULTIPLY(a, IA(s))); -} int64_t mathMultiply(int64_t a, int64_t b); int64_t mathDivide(int64_t a, int64_t b); @@ -551,13 +515,19 @@ void imageFree(SwImage* image); bool fillGenColorTable(SwFill* fill, const Fill* fdata, const Matrix* transform, SwSurface* surface, uint8_t opacity, bool ctable); void fillReset(SwFill* fill); void fillFree(SwFill* fill); + //OPTIMIZE_ME: Skip the function pointer access +void fillLinear(const SwFill* fill, uint8_t* dst, uint32_t y, uint32_t x, uint32_t len, SwMask maskOp, uint8_t opacity); //composite masking ver. +void fillLinear(const SwFill* fill, uint8_t* dst, uint32_t y, uint32_t x, uint32_t len, uint8_t* cmp, SwMask maskOp, uint8_t opacity); //direct masking ver. void fillLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, uint8_t a); //blending ver. void fillLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, SwBlender op2, uint8_t a); //blending + BlendingMethod(op2) ver. -void fillLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, uint8_t* cmp, SwAlpha alpha, uint8_t csize, uint8_t opacity); //masking ver. +void fillLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, uint8_t* cmp, SwAlpha alpha, uint8_t csize, uint8_t opacity); //matting ver. + +void fillRadial(const SwFill* fill, uint8_t* dst, uint32_t y, uint32_t x, uint32_t len, SwMask op, uint8_t a); //composite masking ver. +void fillRadial(const SwFill* fill, uint8_t* dst, uint32_t y, uint32_t x, uint32_t len, uint8_t* cmp, SwMask op, uint8_t a) ; //direct masking ver. void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, uint8_t a); //blending ver. void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, SwBlender op2, uint8_t a); //blending + BlendingMethod(op2) ver. -void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, uint8_t* cmp, SwAlpha alpha, uint8_t csize, uint8_t opacity); //masking ver. +void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, uint8_t* cmp, SwAlpha alpha, uint8_t csize, uint8_t opacity); //matting ver. SwRleData* rleRender(SwRleData* rle, const SwOutline* outline, const SwBBox& renderRegion, bool antiAlias); SwRleData* rleRender(const SwBBox* bbox); diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwFill.cpp b/thirdparty/thorvg/src/lib/sw_engine/tvgSwFill.cpp index 1c6eb4e428..cede9e6eb7 100644 --- a/thirdparty/thorvg/src/lib/sw_engine/tvgSwFill.cpp +++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwFill.cpp @@ -22,16 +22,46 @@ #include "tvgMath.h" #include "tvgSwCommon.h" - +#include "tvgFill.h" /************************************************************************/ /* Internal Class Implementation */ /************************************************************************/ +#define RADIAL_A_THRESHOLD 0.0005f #define GRADIENT_STOP_SIZE 1024 #define FIXPT_BITS 8 #define FIXPT_SIZE (1<<FIXPT_BITS) +/* + * quadratic equation with the following coefficients (rx and ry defined in the _calculateCoefficients()): + * A = a // fill->radial.a + * B = 2 * (dr * fr + rx * dx + ry * dy) + * C = fr^2 - rx^2 - ry^2 + * Derivatives are computed with respect to dx. + * This procedure aims to optimize and eliminate the need to calculate all values from the beginning + * for consecutive x values with a constant y. The Taylor series expansions are computed as long as + * its terms are non-zero. + */ +static void _calculateCoefficients(const SwFill* fill, uint32_t x, uint32_t y, float& b, float& deltaB, float& det, float& deltaDet, float& deltaDeltaDet) +{ + auto radial = &fill->radial; + + auto rx = (x + 0.5f) * radial->a11 + (y + 0.5f) * radial->a12 + radial->a13 - radial->fx; + auto ry = (x + 0.5f) * radial->a21 + (y + 0.5f) * radial->a22 + radial->a23 - radial->fy; + + b = (radial->dr * radial->fr + rx * radial->dx + ry * radial->dy) * radial->invA; + deltaB = (radial->a11 * radial->dx + radial->a21 * radial->dy) * radial->invA; + + auto rr = rx * rx + ry * ry; + auto deltaRr = 2.0f * (rx * radial->a11 + ry * radial->a21) * radial->invA; + auto deltaDeltaRr = 2.0f * (radial->a11 * radial->a11 + radial->a21 * radial->a21) * radial->invA; + + det = b * b + (rr - radial->fr * radial->fr) * radial->invA; + deltaDet = 2.0f * b * deltaB + deltaB * deltaB + deltaRr + deltaDeltaRr; + deltaDeltaDet = 2.0f * deltaB * deltaB + deltaDeltaRr; +} + static bool _updateColorTable(SwFill* fill, const Fill* fdata, const SwSurface* surface, uint8_t opacity) { @@ -146,46 +176,62 @@ bool _prepareLinear(SwFill* fill, const LinearGradient* linear, const Matrix* tr bool _prepareRadial(SwFill* fill, const RadialGradient* radial, const Matrix* transform) { - float radius, cx, cy; - if (radial->radial(&cx, &cy, &radius) != Result::Success) return false; - if (radius < FLT_EPSILON) return true; + auto cx = P(radial)->cx; + auto cy = P(radial)->cy; + auto r = P(radial)->r; + auto fx = P(radial)->fx; + auto fy = P(radial)->fy; + auto fr = P(radial)->fr; + + if (r < FLT_EPSILON) return true; + + fill->radial.dr = r - fr; + fill->radial.dx = cx - fx; + fill->radial.dy = cy - fy; + fill->radial.fr = fr; + fill->radial.fx = fx; + fill->radial.fy = fy; + fill->radial.a = fill->radial.dr * fill->radial.dr - fill->radial.dx * fill->radial.dx - fill->radial.dy * fill->radial.dy; + + //This condition fulfills the SVG 1.1 std: + //the focal point, if outside the end circle, is moved to be on the end circle + //See: the SVG 2 std requirements: https://www.w3.org/TR/SVG2/pservers.html#RadialGradientNotes + if (fill->radial.a < 0) { + auto dist = sqrtf(fill->radial.dx * fill->radial.dx + fill->radial.dy * fill->radial.dy); + fill->radial.fx = cx + r * (fx - cx) / dist; + fill->radial.fy = cy + r * (fy - cy) / dist; + fill->radial.dx = cx - fill->radial.fx; + fill->radial.dy = cy - fill->radial.fy; + fill->radial.a = fill->radial.dr * fill->radial.dr - fill->radial.dx * fill->radial.dx - fill->radial.dy * fill->radial.dy; + } - float invR = 1.0f / radius; - fill->radial.shiftX = -cx; - fill->radial.shiftY = -cy; - fill->radial.a = radius; + if (fill->radial.a > 0) fill->radial.invA = 1.0f / fill->radial.a; auto gradTransform = radial->transform(); bool isTransformation = !mathIdentity((const Matrix*)(&gradTransform)); - if (isTransformation) { - if (transform) gradTransform = mathMultiply(transform, &gradTransform); - } else if (transform) { - gradTransform = *transform; - isTransformation = true; + if (transform) { + if (isTransformation) gradTransform = mathMultiply(transform, &gradTransform); + else { + gradTransform = *transform; + isTransformation = true; + } } if (isTransformation) { Matrix invTransform; if (!mathInverse(&gradTransform, &invTransform)) return false; - - fill->radial.a11 = invTransform.e11 * invR; - fill->radial.a12 = invTransform.e12 * invR; - fill->radial.shiftX += invTransform.e13; - fill->radial.a21 = invTransform.e21 * invR; - fill->radial.a22 = invTransform.e22 * invR; - fill->radial.shiftY += invTransform.e23; - fill->radial.detSecDeriv = 2.0f * fill->radial.a11 * fill->radial.a11 + 2 * fill->radial.a21 * fill->radial.a21; - - fill->radial.a *= sqrt(pow(invTransform.e11, 2) + pow(invTransform.e21, 2)); + fill->radial.a11 = invTransform.e11; + fill->radial.a12 = invTransform.e12; + fill->radial.a13 = invTransform.e13; + fill->radial.a21 = invTransform.e21; + fill->radial.a22 = invTransform.e22; + fill->radial.a23 = invTransform.e23; } else { - fill->radial.a11 = fill->radial.a22 = invR; - fill->radial.a12 = fill->radial.a21 = 0.0f; - fill->radial.detSecDeriv = 2.0f * invR * invR; + fill->radial.a11 = fill->radial.a22 = 1.0f; + fill->radial.a12 = fill->radial.a13 = 0.0f; + fill->radial.a21 = fill->radial.a23 = 0.0f; } - fill->radial.shiftX *= invR; - fill->radial.shiftY *= invR; - return true; } @@ -233,77 +279,181 @@ static inline uint32_t _pixel(const SwFill* fill, float pos) /* External Class Implementation */ /************************************************************************/ + void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, uint8_t* cmp, SwAlpha alpha, uint8_t csize, uint8_t opacity) { - auto rx = (x + 0.5f) * fill->radial.a11 + (y + 0.5f) * fill->radial.a12 + fill->radial.shiftX; - auto ry = (x + 0.5f) * fill->radial.a21 + (y + 0.5f) * fill->radial.a22 + fill->radial.shiftY; + //edge case + if (fill->radial.a < RADIAL_A_THRESHOLD) { + auto radial = &fill->radial; + auto rx = (x + 0.5f) * radial->a11 + (y + 0.5f) * radial->a12 + radial->a13 - radial->fx; + auto ry = (x + 0.5f) * radial->a21 + (y + 0.5f) * radial->a22 + radial->a23 - radial->fy; + + if (opacity == 255) { + for (uint32_t i = 0 ; i < len ; ++i, ++dst, cmp += csize) { + auto x0 = 0.5f * (rx * rx + ry * ry - radial->fr * radial->fr) / (radial->dr * radial->fr + rx * radial->dx + ry * radial->dy); + *dst = opBlendNormal(_pixel(fill, x0), *dst, alpha(cmp)); + rx += radial->a11; + ry += radial->a21; + } + } else { + for (uint32_t i = 0 ; i < len ; ++i, ++dst, cmp += csize) { + auto x0 = 0.5f * (rx * rx + ry * ry - radial->fr * radial->fr) / (radial->dr * radial->fr + rx * radial->dx + ry * radial->dy); + *dst = opBlendNormal(_pixel(fill, x0), *dst, MULTIPLY(opacity, alpha(cmp))); + rx += radial->a11; + ry += radial->a21; + } + } + } else { + float b, deltaB, det, deltaDet, deltaDeltaDet; + _calculateCoefficients(fill, x, y, b, deltaB, det, deltaDet, deltaDeltaDet); + + if (opacity == 255) { + for (uint32_t i = 0 ; i < len ; ++i, ++dst, cmp += csize) { + *dst = opBlendNormal(_pixel(fill, sqrtf(det) - b), *dst, alpha(cmp)); + det += deltaDet; + deltaDet += deltaDeltaDet; + b += deltaB; + } + } else { + for (uint32_t i = 0 ; i < len ; ++i, ++dst, cmp += csize) { + *dst = opBlendNormal(_pixel(fill, sqrtf(det) - b), *dst, MULTIPLY(opacity, alpha(cmp))); + det += deltaDet; + deltaDet += deltaDeltaDet; + b += deltaB; + } + } + } +} - // detSecondDerivative = d(detFirstDerivative)/dx = d( d(det)/dx )/dx - auto detSecondDerivative = fill->radial.detSecDeriv; - // detFirstDerivative = d(det)/dx - auto detFirstDerivative = 2.0f * (fill->radial.a11 * rx + fill->radial.a21 * ry) + 0.5f * detSecondDerivative; - auto det = rx * rx + ry * ry; - if (opacity == 255) { - for (uint32_t i = 0 ; i < len ; ++i, ++dst, cmp += csize) { - *dst = opBlendNormal(_pixel(fill, sqrtf(det)), *dst, alpha(cmp)); - det += detFirstDerivative; - detFirstDerivative += detSecondDerivative; +void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, uint8_t a) +{ + if (fill->radial.a < RADIAL_A_THRESHOLD) { + auto radial = &fill->radial; + auto rx = (x + 0.5f) * radial->a11 + (y + 0.5f) * radial->a12 + radial->a13 - radial->fx; + auto ry = (x + 0.5f) * radial->a21 + (y + 0.5f) * radial->a22 + radial->a23 - radial->fy; + for (uint32_t i = 0; i < len; ++i, ++dst) { + auto x0 = 0.5f * (rx * rx + ry * ry - radial->fr * radial->fr) / (radial->dr * radial->fr + rx * radial->dx + ry * radial->dy); + *dst = op(_pixel(fill, x0), *dst, a); + rx += radial->a11; + ry += radial->a21; } } else { - for (uint32_t i = 0 ; i < len ; ++i, ++dst, cmp += csize) { - *dst = opBlendNormal(_pixel(fill, sqrtf(det)), *dst, MULTIPLY(opacity, alpha(cmp))); - det += detFirstDerivative; - detFirstDerivative += detSecondDerivative; + float b, deltaB, det, deltaDet, deltaDeltaDet; + _calculateCoefficients(fill, x, y, b, deltaB, det, deltaDet, deltaDeltaDet); + + for (uint32_t i = 0; i < len; ++i, ++dst) { + *dst = op(_pixel(fill, sqrtf(det) - b), *dst, a); + det += deltaDet; + deltaDet += deltaDeltaDet; + b += deltaB; } } } -void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, uint8_t a) +void fillRadial(const SwFill* fill, uint8_t* dst, uint32_t y, uint32_t x, uint32_t len, SwMask maskOp, uint8_t a) { - auto rx = (x + 0.5f) * fill->radial.a11 + (y + 0.5f) * fill->radial.a12 + fill->radial.shiftX; - auto ry = (x + 0.5f) * fill->radial.a21 + (y + 0.5f) * fill->radial.a22 + fill->radial.shiftY; - - // detSecondDerivative = d(detFirstDerivative)/dx = d( d(det)/dx )/dx - auto detSecondDerivative = fill->radial.detSecDeriv; - // detFirstDerivative = d(det)/dx - auto detFirstDerivative = 2.0f * (fill->radial.a11 * rx + fill->radial.a21 * ry) + 0.5f * detSecondDerivative; - auto det = rx * rx + ry * ry; - - for (uint32_t i = 0 ; i < len ; ++i, ++dst) { - *dst = op(_pixel(fill, sqrtf(det)), *dst, a); - det += detFirstDerivative; - detFirstDerivative += detSecondDerivative; + if (fill->radial.a < RADIAL_A_THRESHOLD) { + auto radial = &fill->radial; + auto rx = (x + 0.5f) * radial->a11 + (y + 0.5f) * radial->a12 + radial->a13 - radial->fx; + auto ry = (x + 0.5f) * radial->a21 + (y + 0.5f) * radial->a22 + radial->a23 - radial->fy; + for (uint32_t i = 0 ; i < len ; ++i, ++dst) { + auto x0 = 0.5f * (rx * rx + ry * ry - radial->fr * radial->fr) / (radial->dr * radial->fr + rx * radial->dx + ry * radial->dy); + auto src = MULTIPLY(a, A(_pixel(fill, x0))); + *dst = maskOp(src, *dst, ~src); + rx += radial->a11; + ry += radial->a21; + } + } else { + float b, deltaB, det, deltaDet, deltaDeltaDet; + _calculateCoefficients(fill, x, y, b, deltaB, det, deltaDet, deltaDeltaDet); + + for (uint32_t i = 0 ; i < len ; ++i, ++dst) { + auto src = MULTIPLY(a, A(_pixel(fill, sqrtf(det) - b))); + *dst = maskOp(src, *dst, ~src); + det += deltaDet; + deltaDet += deltaDeltaDet; + b += deltaB; + } } } -void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, SwBlender op2, uint8_t a) +void fillRadial(const SwFill* fill, uint8_t* dst, uint32_t y, uint32_t x, uint32_t len, uint8_t* cmp, SwMask maskOp, uint8_t a) { - auto rx = (x + 0.5f) * fill->radial.a11 + (y + 0.5f) * fill->radial.a12 + fill->radial.shiftX; - auto ry = (x + 0.5f) * fill->radial.a21 + (y + 0.5f) * fill->radial.a22 + fill->radial.shiftY; + if (fill->radial.a < RADIAL_A_THRESHOLD) { + auto radial = &fill->radial; + auto rx = (x + 0.5f) * radial->a11 + (y + 0.5f) * radial->a12 + radial->a13 - radial->fx; + auto ry = (x + 0.5f) * radial->a21 + (y + 0.5f) * radial->a22 + radial->a23 - radial->fy; + for (uint32_t i = 0 ; i < len ; ++i, ++dst, ++cmp) { + auto x0 = 0.5f * (rx * rx + ry * ry - radial->fr * radial->fr) / (radial->dr * radial->fr + rx * radial->dx + ry * radial->dy); + auto src = MULTIPLY(A(A(_pixel(fill, x0))), a); + auto tmp = maskOp(src, *cmp, 0); + *dst = tmp + MULTIPLY(*dst, ~tmp); + rx += radial->a11; + ry += radial->a21; + } + } else { + float b, deltaB, det, deltaDet, deltaDeltaDet; + _calculateCoefficients(fill, x, y, b, deltaB, det, deltaDet, deltaDeltaDet); + + for (uint32_t i = 0 ; i < len ; ++i, ++dst, ++cmp) { + auto src = MULTIPLY(A(_pixel(fill, sqrtf(det))), a); + auto tmp = maskOp(src, *cmp, 0); + *dst = tmp + MULTIPLY(*dst, ~tmp); + deltaDet += deltaDeltaDet; + b += deltaB; + } + } +} - // detSecondDerivative = d(detFirstDerivative)/dx = d( d(det)/dx )/dx - auto detSecondDerivative = fill->radial.detSecDeriv; - // detFirstDerivative = d(det)/dx - auto detFirstDerivative = 2.0f * (fill->radial.a11 * rx + fill->radial.a21 * ry) + 0.5f * detSecondDerivative; - auto det = rx * rx + ry * ry; - if (a == 255) { - for (uint32_t i = 0 ; i < len ; ++i, ++dst) { - auto tmp = op(_pixel(fill, sqrtf(det)), *dst, 255); - *dst = op2(tmp, *dst, 255); - det += detFirstDerivative; - detFirstDerivative += detSecondDerivative; +void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, SwBlender op2, uint8_t a) +{ + if (fill->radial.a < RADIAL_A_THRESHOLD) { + auto radial = &fill->radial; + auto rx = (x + 0.5f) * radial->a11 + (y + 0.5f) * radial->a12 + radial->a13 - radial->fx; + auto ry = (x + 0.5f) * radial->a21 + (y + 0.5f) * radial->a22 + radial->a23 - radial->fy; + + if (a == 255) { + for (uint32_t i = 0; i < len; ++i, ++dst) { + auto x0 = 0.5f * (rx * rx + ry * ry - radial->fr * radial->fr) / (radial->dr * radial->fr + rx * radial->dx + ry * radial->dy); + auto tmp = op(_pixel(fill, x0), *dst, 255); + *dst = op2(tmp, *dst, 255); + rx += radial->a11; + ry += radial->a21; + } + } else { + for (uint32_t i = 0; i < len; ++i, ++dst) { + auto x0 = 0.5f * (rx * rx + ry * ry - radial->fr * radial->fr) / (radial->dr * radial->fr + rx * radial->dx + ry * radial->dy); + auto tmp = op(_pixel(fill, x0), *dst, 255); + auto tmp2 = op2(tmp, *dst, 255); + *dst = INTERPOLATE(tmp2, *dst, a); + rx += radial->a11; + ry += radial->a21; + } } } else { - for (uint32_t i = 0 ; i < len ; ++i, ++dst) { - auto tmp = op(_pixel(fill, sqrtf(det)), *dst, 255); - auto tmp2 = op2(tmp, *dst, 255); - *dst = INTERPOLATE(tmp2, *dst, a); - det += detFirstDerivative; - detFirstDerivative += detSecondDerivative; + float b, deltaB, det, deltaDet, deltaDeltaDet; + _calculateCoefficients(fill, x, y, b, deltaB, det, deltaDet, deltaDeltaDet); + if (a == 255) { + for (uint32_t i = 0 ; i < len ; ++i, ++dst) { + auto tmp = op(_pixel(fill, sqrtf(det) - b), *dst, 255); + *dst = op2(tmp, *dst, 255); + det += deltaDet; + deltaDet += deltaDeltaDet; + b += deltaB; + } + } else { + for (uint32_t i = 0 ; i < len ; ++i, ++dst) { + auto tmp = op(_pixel(fill, sqrtf(det) - b), *dst, 255); + auto tmp2 = op2(tmp, *dst, 255); + *dst = INTERPOLATE(tmp2, *dst, a); + det += deltaDet; + deltaDet += deltaDeltaDet; + b += deltaB; + } } } } @@ -383,6 +533,95 @@ void fillLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint3 } +void fillLinear(const SwFill* fill, uint8_t* dst, uint32_t y, uint32_t x, uint32_t len, SwMask maskOp, uint8_t a) +{ + //Rotation + float rx = x + 0.5f; + float ry = y + 0.5f; + float t = (fill->linear.dx * rx + fill->linear.dy * ry + fill->linear.offset) * (GRADIENT_STOP_SIZE - 1); + float inc = (fill->linear.dx) * (GRADIENT_STOP_SIZE - 1); + + if (mathZero(inc)) { + auto src = MULTIPLY(a, A(_fixedPixel(fill, static_cast<int32_t>(t * FIXPT_SIZE)))); + for (uint32_t i = 0; i < len; ++i, ++dst) { + *dst = maskOp(src, *dst, ~src); + } + return; + } + + auto vMax = static_cast<float>(INT32_MAX >> (FIXPT_BITS + 1)); + auto vMin = -vMax; + auto v = t + (inc * len); + + //we can use fixed point math + if (v < vMax && v > vMin) { + auto t2 = static_cast<int32_t>(t * FIXPT_SIZE); + auto inc2 = static_cast<int32_t>(inc * FIXPT_SIZE); + for (uint32_t j = 0; j < len; ++j, ++dst) { + auto src = MULTIPLY(_fixedPixel(fill, t2), a); + *dst = maskOp(src, *dst, ~src); + t2 += inc2; + } + //we have to fallback to float math + } else { + uint32_t counter = 0; + while (counter++ < len) { + auto src = MULTIPLY(_pixel(fill, t / GRADIENT_STOP_SIZE), a); + *dst = maskOp(src, *dst, ~src); + ++dst; + t += inc; + } + } +} + + +void fillLinear(const SwFill* fill, uint8_t* dst, uint32_t y, uint32_t x, uint32_t len, uint8_t* cmp, SwMask maskOp, uint8_t a) +{ + //Rotation + float rx = x + 0.5f; + float ry = y + 0.5f; + float t = (fill->linear.dx * rx + fill->linear.dy * ry + fill->linear.offset) * (GRADIENT_STOP_SIZE - 1); + float inc = (fill->linear.dx) * (GRADIENT_STOP_SIZE - 1); + + if (mathZero(inc)) { + auto src = A(_fixedPixel(fill, static_cast<int32_t>(t * FIXPT_SIZE))); + src = MULTIPLY(src, a); + for (uint32_t i = 0; i < len; ++i, ++dst, ++cmp) { + auto tmp = maskOp(src, *cmp, 0); + *dst = tmp + MULTIPLY(*dst, ~tmp); + } + return; + } + + auto vMax = static_cast<float>(INT32_MAX >> (FIXPT_BITS + 1)); + auto vMin = -vMax; + auto v = t + (inc * len); + + //we can use fixed point math + if (v < vMax && v > vMin) { + auto t2 = static_cast<int32_t>(t * FIXPT_SIZE); + auto inc2 = static_cast<int32_t>(inc * FIXPT_SIZE); + for (uint32_t j = 0; j < len; ++j, ++dst, ++cmp) { + auto src = MULTIPLY(a, A(_fixedPixel(fill, t2))); + auto tmp = maskOp(src, *cmp, 0); + *dst = tmp + MULTIPLY(*dst, ~tmp); + t2 += inc2; + } + //we have to fallback to float math + } else { + uint32_t counter = 0; + while (counter++ < len) { + auto src = MULTIPLY(A(_pixel(fill, t / GRADIENT_STOP_SIZE)), a); + auto tmp = maskOp(src, *cmp, 0); + *dst = tmp + MULTIPLY(*dst, ~tmp); + ++dst; + ++cmp; + t += inc; + } + } +} + + void fillLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, uint8_t a) { //Rotation diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwImage.cpp b/thirdparty/thorvg/src/lib/sw_engine/tvgSwImage.cpp index 4829a8c81d..fb8581b412 100644 --- a/thirdparty/thorvg/src/lib/sw_engine/tvgSwImage.cpp +++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwImage.cpp @@ -93,7 +93,7 @@ static bool _genOutline(SwImage* image, const RenderMesh* mesh, const Matrix* tr outline->types.push(SW_CURVE_TYPE_POINT); } - outline->pts.push(outline->pts.data[0]); + outline->pts.push(outline->pts[0]); outline->types.push(SW_CURVE_TYPE_POINT); outline->cntrs.push(outline->pts.count - 1); outline->closed.push(true); diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwRaster.cpp b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRaster.cpp index bd32bf0b23..8fd54c2a4f 100644 --- a/thirdparty/thorvg/src/lib/sw_engine/tvgSwRaster.cpp +++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRaster.cpp @@ -39,6 +39,16 @@ constexpr auto DOWN_SCALE_TOLERANCE = 0.5f; struct FillLinear { + void operator()(const SwFill* fill, uint8_t* dst, uint32_t y, uint32_t x, uint32_t len, SwMask op, uint8_t a) + { + fillLinear(fill, dst, y, x, len, op, a); + } + + void operator()(const SwFill* fill, uint8_t* dst, uint32_t y, uint32_t x, uint32_t len, uint8_t* cmp, SwMask op, uint8_t a) + { + fillLinear(fill, dst, y, x, len, cmp, op, a); + } + void operator()(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, uint8_t a) { fillLinear(fill, dst, y, x, len, op, a); @@ -58,6 +68,16 @@ struct FillLinear struct FillRadial { + void operator()(const SwFill* fill, uint8_t* dst, uint32_t y, uint32_t x, uint32_t len, SwMask op, uint8_t a) + { + fillRadial(fill, dst, y, x, len, op, a); + } + + void operator()(const SwFill* fill, uint8_t* dst, uint32_t y, uint32_t x, uint32_t len, uint8_t* cmp, SwMask op, uint8_t a) + { + fillRadial(fill, dst, y, x, len, cmp, op, a); + } + void operator()(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, uint8_t a) { fillRadial(fill, dst, y, x, len, op, a); @@ -75,9 +95,6 @@ struct FillRadial }; -static bool _rasterDirectImage(SwSurface* surface, const SwImage* image, const SwBBox& region, uint8_t opacity = 255); - - static inline uint8_t _alpha(uint8_t* a) { return *a; @@ -148,70 +165,70 @@ static inline bool _matting(const SwSurface* surface) else return false; } - -static inline bool _masking(const SwSurface* surface) +static inline uint8_t _opMaskNone(uint8_t s, TVG_UNUSED uint8_t d, TVG_UNUSED uint8_t a) { - if ((int)surface->compositor->method >= (int)CompositeMethod::AddMask) return true; - else return false; + return s; } - -static inline uint32_t _opMaskAdd(uint32_t s, uint32_t d, uint8_t a) +static inline uint8_t _opMaskAdd(uint8_t s, uint8_t d, uint8_t a) { - return s + ALPHA_BLEND(d, a); + return s + MULTIPLY(d, a); } -static inline uint32_t _opMaskSubtract(TVG_UNUSED uint32_t s, uint32_t d, uint8_t a) +static inline uint8_t _opMaskSubtract(uint8_t s, uint8_t d, TVG_UNUSED uint8_t a) { - return ALPHA_BLEND(d, a); + return MULTIPLY(s, 255 - d); } -static inline uint32_t _opMaskDifference(uint32_t s, uint32_t d, uint8_t a) +static inline uint8_t _opMaskIntersect(uint8_t s, uint8_t d, TVG_UNUSED uint8_t a) { - return ALPHA_BLEND(s, IA(d)) + ALPHA_BLEND(d, a); + return MULTIPLY(s, d); } -static inline uint32_t _opAMaskAdd(uint32_t s, uint32_t d, uint8_t a) +static inline uint8_t _opMaskDifference(uint8_t s, uint8_t d, uint8_t a) { - return INTERPOLATE(s, d, a); + return MULTIPLY(s, 255 - d) + MULTIPLY(d, a); } -static inline uint32_t _opAMaskSubtract(TVG_UNUSED uint32_t s, uint32_t d, uint8_t a) +static inline bool _direct(CompositeMethod method) { - return ALPHA_BLEND(d, IA(ALPHA_BLEND(s, a))); -} - - -static inline uint32_t _opAMaskDifference(uint32_t s, uint32_t d, uint8_t a) -{ - auto t = ALPHA_BLEND(s, a); - return ALPHA_BLEND(t, IA(d)) + ALPHA_BLEND(d, IA(t)); + //subtract & Intersect allows the direct composition + if (method == CompositeMethod::SubtractMask || method == CompositeMethod::IntersectMask) return true; + return false; } -static inline SwBlender _getMaskOp(CompositeMethod method) +static inline SwMask _getMaskOp(CompositeMethod method) { switch (method) { case CompositeMethod::AddMask: return _opMaskAdd; case CompositeMethod::SubtractMask: return _opMaskSubtract; case CompositeMethod::DifferenceMask: return _opMaskDifference; + case CompositeMethod::IntersectMask: return _opMaskIntersect; default: return nullptr; } } -static inline SwBlender _getAMaskOp(CompositeMethod method) +static bool _compositeMaskImage(SwSurface* surface, const SwImage* image, const SwBBox& region) { - switch (method) { - case CompositeMethod::AddMask: return _opAMaskAdd; - case CompositeMethod::SubtractMask: return _opAMaskSubtract; - case CompositeMethod::DifferenceMask: return _opAMaskDifference; - default: return nullptr; + auto dbuffer = &surface->buf8[region.min.y * surface->stride + region.min.x]; + auto sbuffer = image->buf8 + (region.min.y + image->oy) * image->stride + (region.min.x + image->ox); + + for (auto y = region.min.y; y < region.max.y; ++y) { + auto dst = dbuffer; + auto src = sbuffer; + for (auto x = region.min.x; x < region.max.x; x++, dst++, src++) { + *dst = *src + MULTIPLY(*dst, ~*src); + } + dbuffer += surface->stride; + sbuffer += image->stride; } + return true; } @@ -284,78 +301,57 @@ static uint32_t _interpDownScaler(const uint32_t *img, uint32_t stride, uint32_t /* Rect */ /************************************************************************/ -static void _rasterMaskedRectDup(SwSurface* surface, const SwBBox& region, SwBlender opMask, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +static bool _rasterCompositeMaskedRect(SwSurface* surface, const SwBBox& region, SwMask maskOp, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { auto w = static_cast<uint32_t>(region.max.x - region.min.x); auto h = static_cast<uint32_t>(region.max.y - region.min.y); - auto cbuffer = surface->compositor->image.buf32 + (region.min.y * surface->compositor->image.stride + region.min.x); //compositor buffer auto cstride = surface->compositor->image.stride; - auto color = surface->join(r, g, b, a); + auto cbuffer = surface->compositor->image.buf8 + (region.min.y * cstride + region.min.x); //compositor buffer auto ialpha = 255 - a; for (uint32_t y = 0; y < h; ++y) { auto cmp = cbuffer; for (uint32_t x = 0; x < w; ++x, ++cmp) { - *cmp = opMask(color, *cmp, ialpha); + *cmp = maskOp(a, *cmp, ialpha); } cbuffer += cstride; } + return _compositeMaskImage(surface, &surface->compositor->image, surface->compositor->bbox); } -static void _rasterMaskedRectInt(SwSurface* surface, const SwBBox& region, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +static bool _rasterDirectMaskedRect(SwSurface* surface, const SwBBox& region, SwMask maskOp, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { auto w = static_cast<uint32_t>(region.max.x - region.min.x); auto h = static_cast<uint32_t>(region.max.y - region.min.y); - auto cstride = surface->compositor->image.stride; + auto cbuffer = surface->compositor->image.buf8 + (region.min.y * surface->compositor->image.stride + region.min.x); //compositor buffer + auto dbuffer = surface->buf8 + (region.min.y * surface->stride + region.min.x); //destination buffer - for (auto y = surface->compositor->bbox.min.y; y < surface->compositor->bbox.max.y; ++y) { - auto cmp = surface->compositor->image.buf32 + (y * cstride + surface->compositor->bbox.min.x); - if (y == region.min.y) { - for (auto y2 = y; y2 < region.max.y; ++y2) { - auto tmp = cmp; - auto x = surface->compositor->bbox.min.x; - while (x < surface->compositor->bbox.max.x) { - if (x == region.min.x) { - for (uint32_t i = 0; i < w; ++i, ++tmp) { - *tmp = ALPHA_BLEND(*tmp, a); - } - x += w; - } else { - *tmp = 0; - ++tmp; - ++x; - } - } - cmp += cstride; - } - y += (h - 1); - } else { - rasterPixel32(cmp, 0x00000000, 0, w); - cmp += cstride; + for (uint32_t y = 0; y < h; ++y) { + auto cmp = cbuffer; + auto dst = dbuffer; + for (uint32_t x = 0; x < w; ++x, ++cmp, ++dst) { + auto tmp = maskOp(a, *cmp, 0); //not use alpha. + *dst = tmp + MULTIPLY(*dst, ~tmp); } + cbuffer += surface->compositor->image.stride; + dbuffer += surface->stride; } + return true; } static bool _rasterMaskedRect(SwSurface* surface, const SwBBox& region, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { - //32bit channels composition - if (surface->channelSize != sizeof(uint32_t)) return false; - - TVGLOG("SW_ENGINE", "Masked(%d) Rect [Region: %lu %lu %lu %lu]", (int)surface->compositor->method, region.min.x, region.min.y, region.max.x - region.max.y, region.min.y); + //8bit masking channels composition + if (surface->channelSize != sizeof(uint8_t)) return false; - if (surface->compositor->method == CompositeMethod::IntersectMask) { - _rasterMaskedRectInt(surface, region, r, g, b, a); - } else if (auto opMask = _getMaskOp(surface->compositor->method)) { - //Other Masking operations: Add, Subtract, Difference ... - _rasterMaskedRectDup(surface, region, opMask, r, g, b, a); - } else { - return false; - } + TVGLOG("SW_ENGINE", "Masked(%d) Rect [Region: %lu %lu %lu %lu]", (int)surface->compositor->method, region.min.x, region.min.y, region.max.x - region.min.x, region.max.y - region.min.y); - //Masking Composition - return _rasterDirectImage(surface, &surface->compositor->image, surface->compositor->bbox); + auto maskOp = _getMaskOp(surface->compositor->method); + if (_direct(surface->compositor->method)) return _rasterDirectMaskedRect(surface, region, maskOp, r, g, b, a); + else return _rasterCompositeMaskedRect(surface, region, maskOp, r, g, b, a); + return false; } @@ -444,7 +440,7 @@ static bool _rasterSolidRect(SwSurface* surface, const SwBBox& region, uint8_t r //8bits grayscale if (surface->channelSize == sizeof(uint8_t)) { for (uint32_t y = 0; y < h; ++y) { - rasterGrayscale8(surface->buf8, 255, region.min.y * surface->stride + region.min.x, w); + rasterGrayscale8(surface->buf8, 255, (y + region.min.y) * surface->stride + region.min.x, w); } return true; } @@ -471,53 +467,44 @@ static bool _rasterRect(SwSurface* surface, const SwBBox& region, uint8_t r, uin /* Rle */ /************************************************************************/ -static void _rasterMaskedRleDup(SwSurface* surface, SwRleData* rle, SwBlender maskOp, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +static bool _rasterCompositeMaskedRle(SwSurface* surface, SwRleData* rle, SwMask maskOp, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { auto span = rle->spans; - auto cbuffer = surface->compositor->image.buf32; + auto cbuffer = surface->compositor->image.buf8; auto cstride = surface->compositor->image.stride; - auto color = surface->join(r, g, b, a); - uint32_t src; + uint8_t src; for (uint32_t i = 0; i < rle->size; ++i, ++span) { auto cmp = &cbuffer[span->y * cstride + span->x]; - if (span->coverage == 255) src = color; - else src = ALPHA_BLEND(color, span->coverage); - auto ialpha = IA(src); + if (span->coverage == 255) src = a; + else src = MULTIPLY(a, span->coverage); + auto ialpha = 255 - src; for (auto x = 0; x < span->len; ++x, ++cmp) { *cmp = maskOp(src, *cmp, ialpha); } } + return _compositeMaskImage(surface, &surface->compositor->image, surface->compositor->bbox); } -static void _rasterMaskedRleInt(SwSurface* surface, SwRleData* rle, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +static bool _rasterDirectMaskedRle(SwSurface* surface, SwRleData* rle, SwMask maskOp, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { auto span = rle->spans; - auto cbuffer = surface->compositor->image.buf32; + auto cbuffer = surface->compositor->image.buf8; auto cstride = surface->compositor->image.stride; - auto color = surface->join(r, g, b, a); - uint32_t src; - - for (auto y = surface->compositor->bbox.min.y; y < surface->compositor->bbox.max.y; ++y) { - auto cmp = &cbuffer[y * cstride]; - auto x = surface->compositor->bbox.min.x; - while (x < surface->compositor->bbox.max.x) { - if (y == span->y && x == span->x && x + span->len <= surface->compositor->bbox.max.x) { - if (span->coverage == 255) src = color; - else src = ALPHA_BLEND(color, span->coverage); - auto alpha = A(src); - for (uint32_t i = 0; i < span->len; ++i) { - cmp[x + i] = ALPHA_BLEND(cmp[x + i], alpha); - } - x += span->len; - ++span; - } else { - cmp[x] = 0; - ++x; - } + uint8_t src; + + for (uint32_t i = 0; i < rle->size; ++i, ++span) { + auto cmp = &cbuffer[span->y * cstride + span->x]; + auto dst = &surface->buf8[span->y * surface->stride + span->x]; + if (span->coverage == 255) src = a; + else src = MULTIPLY(a, span->coverage); + for (auto x = 0; x < span->len; ++x, ++cmp, ++dst) { + auto tmp = maskOp(src, *cmp, 0); //not use alpha + *dst = tmp + MULTIPLY(*dst, ~tmp); } } + return true; } @@ -525,20 +512,13 @@ static bool _rasterMaskedRle(SwSurface* surface, SwRleData* rle, uint8_t r, uint { TVGLOG("SW_ENGINE", "Masked(%d) Rle", (int)surface->compositor->method); - //32bit channels composition - if (surface->channelSize != sizeof(uint32_t)) return false; - - if (surface->compositor->method == CompositeMethod::IntersectMask) { - _rasterMaskedRleInt(surface, rle, r, g, b, a); - } else if (auto opMask = _getMaskOp(surface->compositor->method)) { - //Other Masking operations: Add, Subtract, Difference ... - _rasterMaskedRleDup(surface, rle, opMask, r, g, b, a); - } else { - return false; - } + //8bit masking channels composition + if (surface->channelSize != sizeof(uint8_t)) return false; - //Masking Composition - return _rasterDirectImage(surface, &surface->compositor->image, surface->compositor->bbox); + auto maskOp = _getMaskOp(surface->compositor->method); + if (_direct(surface->compositor->method)) return _rasterDirectMaskedRle(surface, rle, maskOp, r, g, b, a); + else return _rasterCompositeMaskedRle(surface, rle, maskOp, r, g, b, a); + return false; } @@ -644,7 +624,15 @@ static bool _rasterSolidRle(SwSurface* surface, const SwRleData* rle, uint8_t r, //8bit grayscale } else if (surface->channelSize == sizeof(uint8_t)) { for (uint32_t i = 0; i < rle->size; ++i, ++span) { - rasterGrayscale8(surface->buf8, span->coverage, span->y * surface->stride + span->x, span->len); + if (span->coverage == 255) { + rasterGrayscale8(surface->buf8, span->coverage, span->y * surface->stride + span->x, span->len); + } else { + auto dst = &surface->buf8[span->y * surface->stride + span->x]; + auto ialpha = 255 - span->coverage; + for (uint32_t x = 0; x < span->len; ++x, ++dst) { + *dst = span->coverage + MULTIPLY(*dst, ialpha); + } + } } } return true; @@ -669,28 +657,11 @@ static bool _rasterRle(SwSurface* surface, SwRleData* rle, uint8_t r, uint8_t g, /************************************************************************/ -/* RLE Transformed Image */ -/************************************************************************/ - -static bool _transformedRleImage(SwSurface* surface, const SwImage* image, const Matrix* transform, uint8_t opacity) -{ - auto ret = _rasterTexmapPolygon(surface, image, transform, nullptr, opacity); - - //Masking Composition - if (_compositing(surface) && _masking(surface)) { - return _rasterDirectImage(surface, &surface->compositor->image, surface->compositor->bbox); - } - - return ret; - -} - - -/************************************************************************/ /* RLE Scaled Image */ /************************************************************************/ -static void _rasterScaledMaskedRleImageDup(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, SwBlender maskOp, SwBlender amaskOp, uint8_t opacity) +#if 0 //Enable it when GRAYSCALE image is supported +static bool _rasterCompositeScaledMaskedRleImage(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, SwMask maskOp, uint8_t opacity) { auto scaleMethod = image->scale < DOWN_SCALE_TOLERANCE ? _interpDownScaler : _interpUpScaler; auto sampleSize = _sampleSize(image->scale); @@ -700,82 +671,77 @@ static void _rasterScaledMaskedRleImageDup(SwSurface* surface, const SwImage* im for (uint32_t i = 0; i < image->rle->size; ++i, ++span) { auto sy = span->y * itransform->e22 + itransform->e23; if ((uint32_t)sy >= image->h) continue; - auto cmp = &surface->compositor->image.buf32[span->y * surface->compositor->image.stride + span->x]; + auto cmp = &surface->compositor->image.buf8[span->y * surface->compositor->image.stride + span->x]; auto a = MULTIPLY(span->coverage, opacity); if (a == 255) { for (uint32_t x = static_cast<uint32_t>(span->x); x < static_cast<uint32_t>(span->x) + span->len; ++x, ++cmp) { auto sx = x * itransform->e11 + itransform->e13; if ((uint32_t)sx >= image->w) continue; - auto src = scaleMethod(image->buf32, image->stride, image->w, image->h, sx, sy, sampleSize, sampleSize2); - *cmp = maskOp(src, *cmp, 255); + auto src = scaleMethod(image->buf8, image->stride, image->w, image->h, sx, sy, sampleSize, sampleSize2); + *cmp = maskOp(src, *cmp, ~src); } } else { for (uint32_t x = static_cast<uint32_t>(span->x); x < static_cast<uint32_t>(span->x) + span->len; ++x, ++cmp) { auto sx = x * itransform->e11 + itransform->e13; if ((uint32_t)sx >= image->w) continue; - auto src = scaleMethod(image->buf32, image->stride, image->w, image->h, sx, sy, sampleSize, sampleSize2); - *cmp = amaskOp(src, *cmp, a); + auto src = scaleMethod(image->buf8, image->stride, image->w, image->h, sx, sy, sampleSize, sampleSize2); + auto tmp = MULTIPLY(src, a); + *cmp = maskOp(tmp, *cmp, ~tmp); } } } + return true; } -static void _rasterScaledMaskedRleImageInt(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, uint8_t opacity) +static bool _rasterDirectScaledMaskedRleImage(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, SwMask maskOp, uint8_t opacity) { auto scaleMethod = image->scale < DOWN_SCALE_TOLERANCE ? _interpDownScaler : _interpUpScaler; auto sampleSize = _sampleSize(image->scale); auto sampleSize2 = sampleSize * sampleSize; auto span = image->rle->spans; - auto cbuffer = surface->compositor->image.buf32; - auto cstride = surface->compositor->image.stride; - for (auto y = surface->compositor->bbox.min.y; y < surface->compositor->bbox.max.y; ++y) { - auto cmp = &cbuffer[y * cstride]; - for (auto x = surface->compositor->bbox.min.x; x < surface->compositor->bbox.max.x; ++x) { - if (y == span->y && x == span->x && x + span->len <= surface->compositor->bbox.max.x) { - auto sy = span->y * itransform->e22 + itransform->e23; - if ((uint32_t)sy >= image->h) continue; - auto alpha = MULTIPLY(span->coverage, opacity); - if (alpha == 255) { - for (uint32_t i = 0; i < span->len; ++i) { - auto sx = (x + i) * itransform->e11 + itransform->e13; - if ((uint32_t)sx >= image->w) continue; - auto src = scaleMethod(image->buf32, image->stride, image->w, image->h, sx, sy, sampleSize, sampleSize2); - cmp[x + i] = ALPHA_BLEND(cmp[x + i], A(src)); - } - } else { - for (uint32_t i = 0; i < span->len; ++i) { - auto sx = (x + i) * itransform->e11 + itransform->e13; - if ((uint32_t)sx >= image->w) continue; - auto src = scaleMethod(image->buf32, image->stride, image->w, image->h, sx, sy, sampleSize, sampleSize2); - cmp[x + i] = ALPHA_BLEND(cmp[x + i], A(ALPHA_BLEND(src, alpha))); - } - } - x += span->len - 1; - ++span; - } else { - cmp[x] = 0; + for (uint32_t i = 0; i < image->rle->size; ++i, ++span) { + auto sy = span->y * itransform->e22 + itransform->e23; + if ((uint32_t)sy >= image->h) continue; + auto cmp = &surface->compositor->image.buf8[span->y * surface->compositor->image.stride + span->x]; + auto dst = &surface->buf8[span->y * surface->stride + span->x]; + auto a = MULTIPLY(span->coverage, opacity); + if (a == 255) { + for (uint32_t x = static_cast<uint32_t>(span->x); x < static_cast<uint32_t>(span->x) + span->len; ++x, ++cmp, ++dst) { + auto sx = x * itransform->e11 + itransform->e13; + if ((uint32_t)sx >= image->w) continue; + auto src = scaleMethod(image->buf8, image->stride, image->w, image->h, sx, sy, sampleSize, sampleSize2); + auto tmp = maskOp(src, *cmp, 0); //not use alpha + *dst = tmp + MULTIPLY(*dst, ~tmp); + } + } else { + for (uint32_t x = static_cast<uint32_t>(span->x); x < static_cast<uint32_t>(span->x) + span->len; ++x, ++cmp, ++dst) { + auto sx = x * itransform->e11 + itransform->e13; + if ((uint32_t)sx >= image->w) continue; + auto src = scaleMethod(image->buf8, image->stride, image->w, image->h, sx, sy, sampleSize, sampleSize2); + auto tmp = maskOp(MULTIPLY(src, a), *cmp, 0); //not use alpha + *dst = tmp + MULTIPLY(*dst, ~tmp); } } } + return _compositeMaskImage(surface, &surface->compositor->image, surface->compositor->bbox); } - +#endif static bool _rasterScaledMaskedRleImage(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, uint8_t opacity) { +#if 0 //Enable it when GRAYSCALE image is supported TVGLOG("SW_ENGINE", "Scaled Masked(%d) Rle Image", (int)surface->compositor->method); - if (surface->compositor->method == CompositeMethod::IntersectMask) { - _rasterScaledMaskedRleImageInt(surface, image, itransform, region, opacity); - } else if (auto opMask = _getMaskOp(surface->compositor->method)) { - //Other Masking operations: Add, Subtract, Difference ... - _rasterScaledMaskedRleImageDup(surface, image, itransform, region, opMask, _getAMaskOp(surface->compositor->method), opacity); - } else { - return false; - } - //Masking Composition - return _rasterDirectImage(surface, &surface->compositor->image, surface->compositor->bbox); + //8bit masking channels composition + if (surface->channelSize != sizeof(uint8_t)) return false; + + auto maskOp = _getMaskOp(surface->compositor->method); + if (_direct(surface->compositor->method)) return _rasterDirectScaledMaskedRleImage(surface, image, itransform, region, maskOp, opacity); + else return _rasterCompositeScaledMaskedRleImage(surface, image, itransform, region, maskOp, opacity); +#endif + return false; } @@ -895,6 +861,11 @@ static bool _rasterScaledRleImage(SwSurface* surface, const SwImage* image, cons static bool _scaledRleImage(SwSurface* surface, const SwImage* image, const Matrix* transform, const SwBBox& region, uint8_t opacity) { + if (surface->channelSize == sizeof(uint8_t)) { + TVGERR("SW_ENGINE", "Not supported scaled rle image!"); + return false; + } + Matrix itransform; if (transform) { @@ -917,78 +888,72 @@ static bool _scaledRleImage(SwSurface* surface, const SwImage* image, const Matr /* RLE Direct Image */ /************************************************************************/ -static void _rasterDirectMaskedRleImageDup(SwSurface* surface, const SwImage* image, SwBlender maskOp, SwBlender amaskOp, uint8_t opacity) +#if 0 //Enable it when GRAYSCALE image is supported +static bool _rasterCompositeDirectMaskedRleImage(SwSurface* surface, const SwImage* image, SwMask maskOp, uint8_t opacity) { auto span = image->rle->spans; - auto cbuffer = surface->compositor->image.buf32; + auto cbuffer = surface->compositor->image.buf8; auto ctride = surface->compositor->image.stride; for (uint32_t i = 0; i < image->rle->size; ++i, ++span) { - auto src = image->buf32 + (span->y + image->oy) * image->stride + (span->x + image->ox); + auto src = image->buf8 + (span->y + image->oy) * image->stride + (span->x + image->ox); auto cmp = &cbuffer[span->y * ctride + span->x]; auto alpha = MULTIPLY(span->coverage, opacity); if (alpha == 255) { for (uint32_t x = 0; x < span->len; ++x, ++src, ++cmp) { - *cmp = maskOp(*src, *cmp, IA(*src)); + *cmp = maskOp(*src, *cmp, ~*src); } } else { for (uint32_t x = 0; x < span->len; ++x, ++src, ++cmp) { - *cmp = amaskOp(*src, *cmp, alpha); + auto tmp = MULTIPLY(*src, alpha); + *cmp = maskOp(*src, *cmp, ~tmp); } } } + return _compositeMaskImage(surface, &surface->compositor->image, surface->compositor->bbox); } -static void _rasterDirectMaskedRleImageInt(SwSurface* surface, const SwImage* image, uint8_t opacity) +static bool _rasterDirectDirectMaskedRleImage(SwSurface* surface, const SwImage* image, SwMask maskOp, uint8_t opacity) { auto span = image->rle->spans; - auto cbuffer = surface->compositor->image.buf32; + auto cbuffer = surface->compositor->image.buf8; auto ctride = surface->compositor->image.stride; - for (auto y = surface->compositor->bbox.min.y; y < surface->compositor->bbox.max.y; ++y) { - auto cmp = &cbuffer[y * ctride]; - auto x = surface->compositor->bbox.min.x; - while (x < surface->compositor->bbox.max.x) { - if (y == span->y && x == span->x && x + span->len <= surface->compositor->bbox.max.x) { - auto alpha = MULTIPLY(span->coverage, opacity); - auto src = image->buf32 + (span->y + image->oy) * image->stride + (span->x + image->ox); - if (alpha == 255) { - for (uint32_t i = 0; i < span->len; ++i, ++src) { - cmp[x + i] = ALPHA_BLEND(cmp[x + i], A(*src)); - } - } else { - for (uint32_t i = 0; i < span->len; ++i, ++src) { - auto t = ALPHA_BLEND(*src, alpha); - cmp[x + i] = ALPHA_BLEND(cmp[x + i], A(t)); - } - } - x += span->len; - ++span; - } else { - cmp[x] = 0; - ++x; + for (uint32_t i = 0; i < image->rle->size; ++i, ++span) { + auto src = image->buf8 + (span->y + image->oy) * image->stride + (span->x + image->ox); + auto cmp = &cbuffer[span->y * ctride + span->x]; + auto dst = &surface->buf8[span->y * surface->stride + span->x]; + auto alpha = MULTIPLY(span->coverage, opacity); + if (alpha == 255) { + for (uint32_t x = 0; x < span->len; ++x, ++src, ++cmp, ++dst) { + auto tmp = maskOp(*src, *cmp, 0); //not use alpha + *dst = INTERPOLATE8(tmp, *dst, (255 - tmp)); + } + } else { + for (uint32_t x = 0; x < span->len; ++x, ++src, ++cmp, ++dst) { + auto tmp = maskOp(MULTIPLY(*src, alpha), *cmp, 0); //not use alpha + *dst = INTERPOLATE8(tmp, *dst, (255 - tmp)); } } } + return true; } - +#endif static bool _rasterDirectMaskedRleImage(SwSurface* surface, const SwImage* image, uint8_t opacity) { +#if 0 //Enable it when GRAYSCALE image is supported TVGLOG("SW_ENGINE", "Direct Masked(%d) Rle Image", (int)surface->compositor->method); - if (surface->compositor->method == CompositeMethod::IntersectMask) { - _rasterDirectMaskedRleImageInt(surface, image, opacity); - } else if (auto opMask = _getMaskOp(surface->compositor->method)) { - //Other Masking operations: Add, Subtract, Difference ... - _rasterDirectMaskedRleImageDup(surface, image, opMask, _getAMaskOp(surface->compositor->method), opacity); - } else { - return false; - } + //8bit masking channels composition + if (surface->channelSize != sizeof(uint8_t)) return false; - //Masking Composition - return _rasterDirectImage(surface, &surface->compositor->image, surface->compositor->bbox); + auto maskOp = _getMaskOp(surface->compositor->method); + if (_direct(surface->compositor->method)) _rasterDirectDirectMaskedRleImage(surface, image, maskOp, opacity); + else return _rasterCompositeDirectMaskedRleImage(surface, image, maskOp, opacity); +#endif + return false; } @@ -1076,6 +1041,11 @@ static bool _rasterDirectRleImage(SwSurface* surface, const SwImage* image, uint static bool _directRleImage(SwSurface* surface, const SwImage* image, uint8_t opacity) { + if (surface->channelSize == sizeof(uint8_t)) { + TVGERR("SW_ENGINE", "Not supported grayscale rle image!"); + return false; + } + if (_compositing(surface)) { if (_matting(surface)) return _rasterDirectMattedRleImage(surface, image, opacity); else return _rasterDirectMaskedRleImage(surface, image, opacity); @@ -1089,40 +1059,17 @@ static bool _directRleImage(SwSurface* surface, const SwImage* image, uint8_t op /************************************************************************/ -/* Transformed Image */ -/************************************************************************/ - -static bool _transformedImage(SwSurface* surface, const SwImage* image, const Matrix* transform, const SwBBox& region, uint8_t opacity) -{ - auto ret = _rasterTexmapPolygon(surface, image, transform, ®ion, opacity); - - //Masking Composition - if (_compositing(surface) && _masking(surface)) { - return _rasterDirectImage(surface, &surface->compositor->image, surface->compositor->bbox); - } - - return ret; -} - - -static bool _transformedImageMesh(SwSurface* surface, const SwImage* image, const RenderMesh* mesh, const Matrix* transform, const SwBBox* region, uint8_t opacity) -{ - //TODO: Not completed for all cases. - return _rasterTexmapPolygonMesh(surface, image, mesh, transform, region, opacity); -} - - -/************************************************************************/ /*Scaled Image */ /************************************************************************/ -static void _rasterScaledMaskedImageDup(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, SwBlender maskOp, SwBlender amaskOp, uint8_t opacity) +#if 0 //Enable it when GRAYSCALE image is supported +static bool _rasterCompositeScaledMaskedImage(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, SwMask maskOp, uint8_t opacity) { auto scaleMethod = image->scale < DOWN_SCALE_TOLERANCE ? _interpDownScaler : _interpUpScaler; auto sampleSize = _sampleSize(image->scale); auto sampleSize2 = sampleSize * sampleSize; auto cstride = surface->compositor->image.stride; - auto cbuffer = surface->compositor->image.buf32 + (region.min.y * cstride + region.min.x); + auto cbuffer = surface->compositor->image.buf8 + (region.min.y * cstride + region.min.x); for (auto y = region.min.y; y < region.max.y; ++y) { auto sy = y * itransform->e22 + itransform->e23; @@ -1132,92 +1079,73 @@ static void _rasterScaledMaskedImageDup(SwSurface* surface, const SwImage* image for (auto x = region.min.x; x < region.max.x; ++x, ++cmp) { auto sx = x * itransform->e11 + itransform->e13; if ((uint32_t)sx >= image->w) continue; - auto src = scaleMethod(image->buf32, image->stride, image->w, image->h, sx, sy, sampleSize, sampleSize2); - *cmp = maskOp(src, *cmp, IA(src)); + auto src = scaleMethod(image->buf8, image->stride, image->w, image->h, sx, sy, sampleSize, sampleSize2); + *cmp = maskOp(src, *cmp, ~src); } } else { for (auto x = region.min.x; x < region.max.x; ++x, ++cmp) { auto sx = x * itransform->e11 + itransform->e13; if ((uint32_t)sx >= image->w) continue; - auto src = scaleMethod(image->buf32, image->stride, image->w, image->h, sx, sy, sampleSize, sampleSize2); - *cmp = amaskOp(src, *cmp, opacity); + auto src = scaleMethod(image->buf8, image->stride, image->w, image->h, sx, sy, sampleSize, sampleSize2); + auto tmp = MULTIPLY(src, opacity); + *cmp = maskOp(tmp, *cmp, ~tmp); } } cbuffer += cstride; } + return _compositeMaskImage(surface, &surface->compositor->image, surface->compositor->bbox); } -static void _rasterScaledMaskedImageInt(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, uint8_t opacity) + +static bool _rasterDirectScaledMaskedImage(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, SwMask maskOp, uint8_t opacity) { auto scaleMethod = image->scale < DOWN_SCALE_TOLERANCE ? _interpDownScaler : _interpUpScaler; auto sampleSize = _sampleSize(image->scale); auto sampleSize2 = sampleSize * sampleSize; - auto h = static_cast<uint32_t>(region.max.y - region.min.y); - auto w = static_cast<uint32_t>(region.max.x - region.min.x); auto cstride = surface->compositor->image.stride; - auto cbuffer = surface->compositor->image.buf32 + (surface->compositor->bbox.min.y * cstride + surface->compositor->bbox.min.x); - - for (auto y = surface->compositor->bbox.min.y; y < surface->compositor->bbox.max.y; ++y) { - if (y == region.min.y) { - auto cbuffer2 = cbuffer; - for (auto y2 = y; y2 < region.max.y; ++y2) { - auto sy = y2 * itransform->e22 + itransform->e23; - if ((uint32_t)sy >= image->h) continue; - auto tmp = cbuffer2; - auto x = surface->compositor->bbox.min.x; - while (x < surface->compositor->bbox.max.x) { - if (x == region.min.x) { - if (opacity == 255) { - for (uint32_t i = 0; i < w; ++i, ++tmp) { - auto sx = (x + i) * itransform->e11 + itransform->e13; - if ((uint32_t)sx >= image->w) continue; - auto src = scaleMethod(image->buf32, image->stride, image->w, image->h, sx, sy, sampleSize, sampleSize2); - *tmp = ALPHA_BLEND(*tmp, A(src)); - } - } else { - for (uint32_t i = 0; i < w; ++i, ++tmp) { - auto sx = (x + i) * itransform->e11 + itransform->e13; - if ((uint32_t)sx >= image->w) continue; - auto src = ALPHA_BLEND(scaleMethod(image->buf32, image->stride, image->w, image->h, sx, sy, sampleSize, sampleSize2), opacity); - *tmp = ALPHA_BLEND(*tmp, A(src)); - } - } - x += w; - } else { - *tmp = 0; - ++tmp; - ++x; - } - } - cbuffer2 += cstride; + auto cbuffer = surface->compositor->image.buf8 + (region.min.y * cstride + region.min.x); + auto dbuffer = surface->buf8 + (region.min.y * surface->stride + region.min.x); + + for (auto y = region.min.y; y < region.max.y; ++y) { + auto sy = y * itransform->e22 + itransform->e23; + if ((uint32_t)sy >= image->h) continue; + auto cmp = cbuffer; + auto dst = dbuffer; + if (opacity == 255) { + for (auto x = region.min.x; x < region.max.x; ++x, ++cmp, ++dst) { + auto sx = x * itransform->e11 + itransform->e13; + if ((uint32_t)sx >= image->w) continue; + auto src = scaleMethod(image->buf8, image->stride, image->w, image->h, sx, sy, sampleSize, sampleSize2); + auto tmp = maskOp(src, *cmp, 0); //not use alpha + *dst = tmp + MULTIPLY(*dst, ~tmp); } - y += (h - 1); } else { - auto tmp = cbuffer; - for (auto x = surface->compositor->bbox.min.x; x < surface->compositor->bbox.max.x; ++x, ++tmp) { - *tmp = 0; + for (auto x = region.min.x; x < region.max.x; ++x, ++cmp, ++dst) { + auto sx = x * itransform->e11 + itransform->e13; + if ((uint32_t)sx >= image->w) continue; + auto src = scaleMethod(image->buf8, image->stride, image->w, image->h, sx, sy, sampleSize, sampleSize2); + auto tmp = MULTIPLY(src, opacity); + auto tmp2 = maskOp(tmp, *cmp, 0); //not use alpha + *dst = tmp2 + MULTIPLY(*dst, ~tmp2); } } cbuffer += cstride; + dbuffer += surface->stride; } + return true; } - +#endif static bool _rasterScaledMaskedImage(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, uint8_t opacity) { +#if 0 //Enable it when GRAYSCALE image is supported TVGLOG("SW_ENGINE", "Scaled Masked(%d) Image [Region: %lu %lu %lu %lu]", (int)surface->compositor->method, region.min.x, region.min.y, region.max.x - region.min.x, region.max.y - region.min.y); - if (surface->compositor->method == CompositeMethod::IntersectMask) { - _rasterScaledMaskedImageInt(surface, image, itransform, region, opacity); - } else if (auto opMask = _getMaskOp(surface->compositor->method)) { - //Other Masking operations: Add, Subtract, Difference ... - _rasterScaledMaskedImageDup(surface, image, itransform, region, opMask, _getAMaskOp(surface->compositor->method), opacity); - } else { - return false; - } - - //Masking Composition - return _rasterDirectImage(surface, &surface->compositor->image, surface->compositor->bbox); + auto maskOp = _getMaskOp(surface->compositor->method); + if (_direct(surface->compositor->method)) return _rasterDirectScaledMaskedImage(surface, image, itransform, region, maskOp, opacity); + else return _rasterCompositeScaledMaskedImage(surface, image, itransform, region, maskOp, opacity); +#endif + return false; } @@ -1329,6 +1257,11 @@ static bool _rasterScaledImage(SwSurface* surface, const SwImage* image, const M static bool _scaledImage(SwSurface* surface, const SwImage* image, const Matrix* transform, const SwBBox& region, uint8_t opacity) { + if (surface->channelSize == sizeof(uint8_t)) { + TVGERR("SW_ENGINE", "Not supported grayscale Textmap polygon mesh!"); + return false; + } + Matrix itransform; if (transform) { @@ -1351,125 +1284,135 @@ static bool _scaledImage(SwSurface* surface, const SwImage* image, const Matrix* /* Direct Image */ /************************************************************************/ -static void _rasterDirectMaskedImageDup(SwSurface* surface, const SwImage* image, const SwBBox& region, SwBlender maskOp, SwBlender amaskOp, uint8_t opacity) +#if 0 //Enable it when GRAYSCALE image is supported +static bool _rasterCompositeDirectMaskedImage(SwSurface* surface, const SwImage* image, const SwBBox& region, SwMask maskOp, uint8_t opacity) { auto h = static_cast<uint32_t>(region.max.y - region.min.y); auto w = static_cast<uint32_t>(region.max.x - region.min.x); auto cstride = surface->compositor->image.stride; - auto cbuffer = surface->compositor->image.buf32 + (region.min.y * cstride + region.min.x); //compositor buffer - auto sbuffer = image->buf32 + (region.min.y + image->oy) * image->stride + (region.min.x + image->ox); + auto cbuffer = surface->compositor->image.buf8 + (region.min.y * cstride + region.min.x); //compositor buffer + auto sbuffer = image->buf8 + (region.min.y + image->oy) * image->stride + (region.min.x + image->ox); for (uint32_t y = 0; y < h; ++y) { auto cmp = cbuffer; auto src = sbuffer; if (opacity == 255) { for (uint32_t x = 0; x < w; ++x, ++src, ++cmp) { - *cmp = maskOp(*src, *cmp, IA(*src)); + *cmp = maskOp(*src, *cmp, ~*src); } } else { for (uint32_t x = 0; x < w; ++x, ++src, ++cmp) { - *cmp = amaskOp(*src, *cmp, opacity); + auto tmp = MULTIPLY(*src, opacity); + *cmp = maskOp(tmp, *cmp, ~tmp); } } cbuffer += cstride; sbuffer += image->stride; } + return _compositeMaskImage(surface, &surface->compositor->image, surface->compositor->bbox); } -static void _rasterDirectMaskedImageInt(SwSurface* surface, const SwImage* image, const SwBBox& region, uint8_t opacity) +static bool _rasterDirectDirectMaskedImage(SwSurface* surface, const SwImage* image, const SwBBox& region, SwMask maskOp, uint8_t opacity) { auto h = static_cast<uint32_t>(region.max.y - region.min.y); auto w = static_cast<uint32_t>(region.max.x - region.min.x); auto cstride = surface->compositor->image.stride; - auto cbuffer = surface->compositor->image.buf32 + (surface->compositor->bbox.min.y * cstride + surface->compositor->bbox.min.x); - - for (auto y = surface->compositor->bbox.min.y; y < surface->compositor->bbox.max.y; ++y) { - if (y == region.min.y) { - auto cbuffer2 = cbuffer; - for (auto y2 = y; y2 < region.max.y; ++y2) { - auto tmp = cbuffer2; - auto x = surface->compositor->bbox.min.x; - while (x < surface->compositor->bbox.max.x) { - if (x == region.min.x) { - auto src = &image->buf32[(y2 + image->oy) * image->stride + (x + image->ox)]; - if (opacity == 255) { - for (uint32_t i = 0; i < w; ++i, ++tmp, ++src) { - *tmp = ALPHA_BLEND(*tmp, A(*src)); - } - } else { - for (uint32_t i = 0; i < w; ++i, ++tmp, ++src) { - auto t = ALPHA_BLEND(*src, opacity); - *tmp = ALPHA_BLEND(*tmp, A(t)); - } - } - x += w; - } else { - *tmp = 0; - ++tmp; - ++x; - } - } - cbuffer2 += cstride; + + auto cbuffer = surface->compositor->image.buf32 + (region.min.y * cstride + region.min.x); //compositor buffer + auto dbuffer = surface->buf8 + (region.min.y * surface->stride + region.min.x); //destination buffer + auto sbuffer = image->buf8 + (region.min.y + image->oy) * image->stride + (region.min.x + image->ox); + + for (uint32_t y = 0; y < h; ++y) { + auto cmp = cbuffer; + auto dst = dbuffer; + auto src = sbuffer; + if (opacity == 255) { + for (uint32_t x = 0; x < w; ++x, ++src, ++cmp, ++dst) { + auto tmp = maskOp(*src, *cmp, 0); //not use alpha + *dst = tmp + MULTIPLY(*dst, ~tmp); } - y += (h - 1); } else { - rasterPixel32(cbuffer, 0x00000000, 0, surface->compositor->bbox.max.x - surface->compositor->bbox.min.x); + for (uint32_t x = 0; x < w; ++x, ++src, ++cmp, ++dst) { + auto tmp = maskOp(MULTIPLY(*src, opacity), *cmp, 0); //not use alpha + *dst = tmp + MULTIPLY(*dst, ~tmp); + } } cbuffer += cstride; + dbuffer += surface->stride; + sbuffer += image->stride; } + return true; } - +#endif static bool _rasterDirectMaskedImage(SwSurface* surface, const SwImage* image, const SwBBox& region, uint8_t opacity) { - TVGLOG("SW_ENGINE", "Direct Masked(%d) Image [Region: %lu %lu %lu %lu]", (int)surface->compositor->method, region.min.x, region.min.y, region.max.x - region.min.x, region.max.y - region.min.y); + TVGERR("SW_ENGINE", "Not Supported: Direct Masked(%d) Image [Region: %lu %lu %lu %lu]", (int)surface->compositor->method, region.min.x, region.min.y, region.max.x - region.min.x, region.max.y - region.min.y); - if (surface->compositor->method == CompositeMethod::IntersectMask) { - _rasterDirectMaskedImageInt(surface, image, region, opacity); - } else if (auto opMask = _getMaskOp(surface->compositor->method)) { - //Other Masking operations: Add, Subtract, Difference ... - _rasterDirectMaskedImageDup(surface, image, region, opMask, _getAMaskOp(surface->compositor->method), opacity); - } else { - return false; - } - //Masking Composition - return _rasterDirectImage(surface, &surface->compositor->image, surface->compositor->bbox); +#if 0 //Enable it when GRAYSCALE image is supported + auto maskOp = _getMaskOp(surface->compositor->method); + if (_direct(surface->compositor->method)) return _rasterDirectDirectMaskedImage(surface, image, region, maskOp, opacity); + else return _rasterCompositeDirectMaskedImage(surface, image, region, maskOp, opacity); +#endif + return false; } static bool _rasterDirectMattedImage(SwSurface* surface, const SwImage* image, const SwBBox& region, uint8_t opacity) { - auto buffer = surface->buf32 + (region.min.y * surface->stride) + region.min.x; auto h = static_cast<uint32_t>(region.max.y - region.min.y); auto w = static_cast<uint32_t>(region.max.x - region.min.x); auto csize = surface->compositor->image.channelSize; auto alpha = surface->alpha(surface->compositor->method); - - TVGLOG("SW_ENGINE", "Direct Matted(%d) Image [Region: %lu %lu %u %u]", (int)surface->compositor->method, region.min.x, region.min.y, w, h); - auto sbuffer = image->buf32 + (region.min.y + image->oy) * image->stride + (region.min.x + image->ox); auto cbuffer = surface->compositor->image.buf8 + (region.min.y * surface->compositor->image.stride + region.min.x) * csize; //compositor buffer - for (uint32_t y = 0; y < h; ++y) { - auto dst = buffer; - auto cmp = cbuffer; - auto src = sbuffer; - if (opacity == 255) { - for (uint32_t x = 0; x < w; ++x, ++dst, ++src, cmp += csize) { - auto tmp = ALPHA_BLEND(*src, alpha(cmp)); - *dst = tmp + ALPHA_BLEND(*dst, IA(tmp)); + TVGLOG("SW_ENGINE", "Direct Matted(%d) Image [Region: %lu %lu %u %u]", (int)surface->compositor->method, region.min.x, region.min.y, w, h); + + //32 bits + if (surface->channelSize == sizeof(uint32_t)) { + auto buffer = surface->buf32 + (region.min.y * surface->stride) + region.min.x; + for (uint32_t y = 0; y < h; ++y) { + auto dst = buffer; + auto cmp = cbuffer; + auto src = sbuffer; + if (opacity == 255) { + for (uint32_t x = 0; x < w; ++x, ++dst, ++src, cmp += csize) { + auto tmp = ALPHA_BLEND(*src, alpha(cmp)); + *dst = tmp + ALPHA_BLEND(*dst, IA(tmp)); + } + } else { + for (uint32_t x = 0; x < w; ++x, ++dst, ++src, cmp += csize) { + auto tmp = ALPHA_BLEND(*src, MULTIPLY(opacity, alpha(cmp))); + *dst = tmp + ALPHA_BLEND(*dst, IA(tmp)); + } } - } else { - for (uint32_t x = 0; x < w; ++x, ++dst, ++src, cmp += csize) { - auto tmp = ALPHA_BLEND(*src, MULTIPLY(opacity, alpha(cmp))); - *dst = tmp + ALPHA_BLEND(*dst, IA(tmp)); + buffer += surface->stride; + cbuffer += surface->compositor->image.stride * csize; + sbuffer += image->stride; + } + //8 bits + } else if (surface->channelSize == sizeof(uint8_t)) { + auto buffer = surface->buf8 + (region.min.y * surface->stride) + region.min.x; + for (uint32_t y = 0; y < h; ++y) { + auto dst = buffer; + auto cmp = cbuffer; + auto src = sbuffer; + if (opacity == 255) { + for (uint32_t x = 0; x < w; ++x, ++dst, ++src, cmp += csize) { + *dst = MULTIPLY(A(*src), alpha(cmp)); + } + } else { + for (uint32_t x = 0; x < w; ++x, ++dst, ++src, cmp += csize) { + *dst = MULTIPLY(A(*src), MULTIPLY(opacity, alpha(cmp))); + } } + buffer += surface->stride; + cbuffer += surface->compositor->image.stride * csize; + sbuffer += image->stride; } - buffer += surface->stride; - cbuffer += surface->compositor->image.stride * csize; - sbuffer += image->stride; } return true; } @@ -1477,6 +1420,11 @@ static bool _rasterDirectMattedImage(SwSurface* surface, const SwImage* image, c static bool _rasterDirectBlendingImage(SwSurface* surface, const SwImage* image, const SwBBox& region, uint8_t opacity) { + if (surface->channelSize == sizeof(uint8_t)) { + TVGERR("SW_ENGINE", "Not supported grayscale image!"); + return false; + } + auto dbuffer = &surface->buf32[region.min.y * surface->stride + region.min.x]; auto sbuffer = image->buf32 + (region.min.y + image->oy) * image->stride + (region.min.x + image->ox); @@ -1504,6 +1452,11 @@ static bool _rasterDirectBlendingImage(SwSurface* surface, const SwImage* image, static bool _rasterDirectImage(SwSurface* surface, const SwImage* image, const SwBBox& region, uint8_t opacity) { + if (surface->channelSize == sizeof(uint8_t)) { + TVGERR("SW_ENGINE", "Not supported grayscale image!"); + return false; + } + auto dbuffer = &surface->buf32[region.min.y * surface->stride + region.min.x]; auto sbuffer = image->buf32 + (region.min.y + image->oy) * image->stride + (region.min.x + image->ox); @@ -1549,12 +1502,12 @@ static bool _rasterImage(SwSurface* surface, SwImage* image, const Matrix* trans if (image->rle) { if (image->direct) return _directRleImage(surface, image, opacity); else if (image->scaled) return _scaledRleImage(surface, image, transform, region, opacity); - else return _transformedRleImage(surface, image, transform, opacity); + else return _rasterTexmapPolygon(surface, image, transform, nullptr, opacity); //Whole Image } else { if (image->direct) return _directImage(surface, image, region, opacity); else if (image->scaled) return _scaledImage(surface, image, transform, region, opacity); - else return _transformedImage(surface, image, transform, region, opacity); + else return _rasterTexmapPolygon(surface, image, transform, ®ion, opacity); } } @@ -1564,52 +1517,36 @@ static bool _rasterImage(SwSurface* surface, SwImage* image, const Matrix* trans /************************************************************************/ template<typename fillMethod> -static void _rasterGradientMaskedRectDup(SwSurface* surface, const SwBBox& region, const SwFill* fill, SwBlender maskOp) +static bool _rasterCompositeGradientMaskedRect(SwSurface* surface, const SwBBox& region, const SwFill* fill, SwMask maskOp) { auto h = static_cast<uint32_t>(region.max.y - region.min.y); auto w = static_cast<uint32_t>(region.max.x - region.min.x); auto cstride = surface->compositor->image.stride; - auto cbuffer = surface->compositor->image.buf32 + (region.min.y * cstride + region.min.x); + auto cbuffer = surface->compositor->image.buf8 + (region.min.y * cstride + region.min.x); for (uint32_t y = 0; y < h; ++y) { fillMethod()(fill, cbuffer, region.min.y + y, region.min.x, w, maskOp, 255); cbuffer += surface->stride; } + return _compositeMaskImage(surface, &surface->compositor->image, surface->compositor->bbox); } template<typename fillMethod> -static void _rasterGradientMaskedRectInt(SwSurface* surface, const SwBBox& region, const SwFill* fill) +static bool _rasterDirectGradientMaskedRect(SwSurface* surface, const SwBBox& region, const SwFill* fill, SwMask maskOp) { auto h = static_cast<uint32_t>(region.max.y - region.min.y); auto w = static_cast<uint32_t>(region.max.x - region.min.x); auto cstride = surface->compositor->image.stride; + auto cbuffer = surface->compositor->image.buf8 + (region.min.y * cstride + region.min.x); + auto dbuffer = surface->buf8 + (region.min.y * surface->stride + region.min.x); - for (auto y = surface->compositor->bbox.min.y; y < surface->compositor->bbox.max.y; ++y) { - auto cmp = surface->compositor->image.buf32 + (y * cstride + surface->compositor->bbox.min.x); - if (y == region.min.y) { - for (auto y2 = y; y2 < region.max.y; ++y2) { - auto tmp = cmp; - auto x = surface->compositor->bbox.min.x; - while (x < surface->compositor->bbox.max.x) { - if (x == region.min.x) { - fillMethod()(fill, tmp, y2, x, w, opMaskPreIntersect, 255); - x += w; - tmp += w; - } else { - *tmp = 0; - ++tmp; - ++x; - } - } - cmp += cstride; - } - y += (h - 1); - } else { - rasterPixel32(cmp, 0x00000000, 0, surface->compositor->bbox.max.x -surface->compositor->bbox.min.x); - cmp += cstride; - } + for (uint32_t y = 0; y < h; ++y) { + fillMethod()(fill, dbuffer, region.min.y + y, region.min.x, w, cbuffer, maskOp, 255); + cbuffer += cstride; + dbuffer += surface->stride; } + return true; } @@ -1618,16 +1555,14 @@ static bool _rasterGradientMaskedRect(SwSurface* surface, const SwBBox& region, { auto method = surface->compositor->method; - TVGLOG("SW_ENGINE", "Masked(%d) Gradient [Region: %lu %lu %lu %lu]", (int)surface->compositor->method, region.min.x, region.min.y, region.max.x - region.min.x, region.max.y - region.min.y); + TVGLOG("SW_ENGINE", "Masked(%d) Gradient [Region: %lu %lu %lu %lu]", (int)method, region.min.x, region.min.y, region.max.x - region.min.x, region.max.y - region.min.y); - if (method == CompositeMethod::AddMask) _rasterGradientMaskedRectDup<fillMethod>(surface, region, fill, opMaskPreAdd); - else if (method == CompositeMethod::SubtractMask) _rasterGradientMaskedRectDup<fillMethod>(surface, region, fill, opMaskPreSubtract); - else if (method == CompositeMethod::DifferenceMask) _rasterGradientMaskedRectDup<fillMethod>(surface, region, fill, opMaskPreDifference); - else if (method == CompositeMethod::IntersectMask) _rasterGradientMaskedRectInt<fillMethod>(surface, region, fill); - else return false; + auto maskOp = _getMaskOp(method); - //Masking Composition - return _rasterDirectImage(surface, &surface->compositor->image, surface->compositor->bbox, 255); + if (_direct(method)) return _rasterDirectGradientMaskedRect<fillMethod>(surface, region, fill, maskOp); + else return _rasterCompositeGradientMaskedRect<fillMethod>(surface, region, fill, maskOp); + + return false; } @@ -1719,8 +1654,6 @@ static bool _rasterLinearGradientRect(SwSurface* surface, const SwBBox& region, static bool _rasterRadialGradientRect(SwSurface* surface, const SwBBox& region, const SwFill* fill) { - if (fill->radial.a < FLT_EPSILON) return false; - if (_compositing(surface)) { if (_matting(surface)) return _rasterGradientMattedRect<FillRadial>(surface, region, fill); else return _rasterGradientMaskedRect<FillRadial>(surface, region, fill); @@ -1734,64 +1667,54 @@ static bool _rasterRadialGradientRect(SwSurface* surface, const SwBBox& region, } - /************************************************************************/ /* Rle Gradient */ /************************************************************************/ template<typename fillMethod> -static void _rasterGradientMaskedRleDup(SwSurface* surface, const SwRleData* rle, const SwFill* fill, SwBlender maskOp) +static bool _rasterCompositeGradientMaskedRle(SwSurface* surface, const SwRleData* rle, const SwFill* fill, SwMask maskOp) { auto span = rle->spans; auto cstride = surface->compositor->image.stride; - auto cbuffer = surface->compositor->image.buf32; + auto cbuffer = surface->compositor->image.buf8; for (uint32_t i = 0; i < rle->size; ++i, ++span) { auto cmp = &cbuffer[span->y * cstride + span->x]; fillMethod()(fill, cmp, span->y, span->x, span->len, maskOp, span->coverage); } + return _compositeMaskImage(surface, &surface->compositor->image, surface->compositor->bbox); } template<typename fillMethod> -static void _rasterGradientMaskedRleInt(SwSurface* surface, const SwRleData* rle, const SwFill* fill) +static bool _rasterDirectGradientMaskedRle(SwSurface* surface, const SwRleData* rle, const SwFill* fill, SwMask maskOp) { auto span = rle->spans; auto cstride = surface->compositor->image.stride; - auto cbuffer = surface->compositor->image.buf32; - - for (auto y = surface->compositor->bbox.min.y; y < surface->compositor->bbox.max.y; ++y) { - auto cmp = &cbuffer[y * cstride]; - auto x = surface->compositor->bbox.min.x; - while (x < surface->compositor->bbox.max.x) { - if (y == span->y && x == span->x && x + span->len <= surface->compositor->bbox.max.x) { - fillMethod()(fill, cmp, span->y, span->x, span->len, opMaskIntersect, span->coverage); - x += span->len; - ++span; - } else { - cmp[x] = 0; - ++x; - } - } + auto cbuffer = surface->compositor->image.buf8; + auto dbuffer = surface->buf8; + + for (uint32_t i = 0; i < rle->size; ++i, ++span) { + auto cmp = &cbuffer[span->y * cstride + span->x]; + auto dst = &dbuffer[span->y * surface->stride + span->x]; + fillMethod()(fill, dst, span->y, span->x, span->len, cmp, maskOp, span->coverage); } + return true; } template<typename fillMethod> static bool _rasterGradientMaskedRle(SwSurface* surface, const SwRleData* rle, const SwFill* fill) { - TVGLOG("SW_ENGINE", "Masked(%d) Rle Linear Gradient", (int)surface->compositor->method); - auto method = surface->compositor->method; - if (method == CompositeMethod::AddMask) _rasterGradientMaskedRleDup<fillMethod>(surface, rle, fill, opMaskAdd); - else if (method == CompositeMethod::SubtractMask) _rasterGradientMaskedRleDup<fillMethod>(surface, rle, fill, opMaskSubtract); - else if (method == CompositeMethod::DifferenceMask) _rasterGradientMaskedRleDup<fillMethod>(surface, rle, fill, opMaskDifference); - else if (method == CompositeMethod::IntersectMask) _rasterGradientMaskedRleInt<fillMethod>(surface, rle, fill); - else return false; + TVGLOG("SW_ENGINE", "Masked(%d) Rle Linear Gradient", (int)method); - //Masking Composition - return _rasterDirectImage(surface, &surface->compositor->image, surface->compositor->bbox, 255); + auto maskOp = _getMaskOp(method); + + if (_direct(method)) return _rasterDirectGradientMaskedRle<fillMethod>(surface, rle, fill, maskOp); + else return _rasterCompositeGradientMaskedRle<fillMethod>(surface, rle, fill, maskOp); + return false; } @@ -1832,10 +1755,19 @@ static bool _rasterTranslucentGradientRle(SwSurface* surface, const SwRleData* r { auto span = rle->spans; - for (uint32_t i = 0; i < rle->size; ++i, ++span) { - auto dst = &surface->buf32[span->y * surface->stride + span->x]; - if (span->coverage == 255) fillMethod()(fill, dst, span->y, span->x, span->len, opBlendPreNormal, 255); - else fillMethod()(fill, dst, span->y, span->x, span->len, opBlendNormal, span->coverage); + //32 bits + if (surface->channelSize == sizeof(uint32_t)) { + for (uint32_t i = 0; i < rle->size; ++i, ++span) { + auto dst = &surface->buf32[span->y * surface->stride + span->x]; + if (span->coverage == 255) fillMethod()(fill, dst, span->y, span->x, span->len, opBlendPreNormal, 255); + else fillMethod()(fill, dst, span->y, span->x, span->len, opBlendNormal, span->coverage); + } + //8 bits + } else if (surface->channelSize == sizeof(uint8_t)) { + for (uint32_t i = 0; i < rle->size; ++i, ++span) { + auto dst = &surface->buf8[span->y * surface->stride + span->x]; + fillMethod()(fill, dst, span->y, span->x, span->len, _opMaskAdd, 255); + } } return true; } @@ -1846,11 +1778,22 @@ static bool _rasterSolidGradientRle(SwSurface* surface, const SwRleData* rle, co { auto span = rle->spans; - for (uint32_t i = 0; i < rle->size; ++i, ++span) { - auto dst = &surface->buf32[span->y * surface->stride + span->x]; - if (span->coverage == 255) fillMethod()(fill, dst, span->y, span->x, span->len, opBlendSrcOver, 255); - else fillMethod()(fill, dst, span->y, span->x, span->len, opBlendInterp, span->coverage); + //32 bits + if (surface->channelSize == sizeof(uint32_t)) { + for (uint32_t i = 0; i < rle->size; ++i, ++span) { + auto dst = &surface->buf32[span->y * surface->stride + span->x]; + if (span->coverage == 255) fillMethod()(fill, dst, span->y, span->x, span->len, opBlendSrcOver, 255); + else fillMethod()(fill, dst, span->y, span->x, span->len, opBlendInterp, span->coverage); + } + //8 bits + } else if (surface->channelSize == sizeof(uint8_t)) { + for (uint32_t i = 0; i < rle->size; ++i, ++span) { + auto dst = &surface->buf8[span->y * surface->stride + span->x]; + if (span->coverage == 255) fillMethod()(fill, dst, span->y, span->x, span->len, _opMaskNone, 255); + else fillMethod()(fill, dst, span->y, span->x, span->len, _opMaskAdd, span->coverage); + } } + return true; } @@ -1874,7 +1817,7 @@ static bool _rasterLinearGradientRle(SwSurface* surface, const SwRleData* rle, c static bool _rasterRadialGradientRle(SwSurface* surface, const SwRleData* rle, const SwFill* fill) { - if (!rle || fill->radial.a < FLT_EPSILON) return false; + if (!rle) return false; if (_compositing(surface)) { if (_matting(surface)) return _rasterGradientMattedRle<FillRadial>(surface, rle, fill); @@ -2018,11 +1961,6 @@ void rasterPremultiply(Surface* surface) bool rasterGradientShape(SwSurface* surface, SwShape* shape, unsigned id) { - if (surface->channelSize == sizeof(uint8_t)) { - TVGERR("SW_ENGINE", "Not supported grayscale gradient!"); - return false; - } - if (!shape->fill) return false; if (shape->fastTrack) { @@ -2038,11 +1976,6 @@ bool rasterGradientShape(SwSurface* surface, SwShape* shape, unsigned id) bool rasterGradientStroke(SwSurface* surface, SwShape* shape, unsigned id) { - if (surface->channelSize == sizeof(uint8_t)) { - TVGERR("SW_ENGINE", "Not supported grayscale gradient!"); - return false; - } - if (!shape->stroke || !shape->stroke->fill || !shape->strokeRle) return false; if (id == TVG_CLASS_ID_LINEAR) return _rasterLinearGradientRle(surface, shape->strokeRle, shape->stroke->fill); @@ -2059,7 +1992,6 @@ bool rasterShape(SwSurface* surface, SwShape* shape, uint8_t r, uint8_t g, uint8 g = MULTIPLY(g, a); b = MULTIPLY(b, a); } - if (shape->fastTrack) return _rasterRect(surface, shape->bbox, r, g, b, a); else return _rasterRle(surface, shape->rle, r, g, b, a); } @@ -2079,19 +2011,10 @@ bool rasterStroke(SwSurface* surface, SwShape* shape, uint8_t r, uint8_t g, uint bool rasterImage(SwSurface* surface, SwImage* image, const RenderMesh* mesh, const Matrix* transform, const SwBBox& bbox, uint8_t opacity) { - if (surface->channelSize == sizeof(uint8_t)) { - TVGERR("SW_ENGINE", "Not supported grayscale image!"); - return false; - } - //Verify Boundary if (bbox.max.x < 0 || bbox.max.y < 0 || bbox.min.x >= static_cast<SwCoord>(surface->w) || bbox.min.y >= static_cast<SwCoord>(surface->h)) return false; - //TOOD: switch (image->format) - //TODO: case: _rasterRGBImageMesh() - //TODO: case: _rasterGrayscaleImageMesh() - //TODO: case: _rasterAlphaImageMesh() - if (mesh && mesh->triangleCnt > 0) return _transformedImageMesh(surface, image, mesh, transform, &bbox, opacity); + if (mesh && mesh->triangleCnt > 0) return _rasterTexmapPolygonMesh(surface, image, mesh, transform, &bbox, opacity); else return _rasterImage(surface, image, transform, bbox, opacity); } diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwRasterTexmap.h b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRasterTexmap.h index 4b30c52ea3..698ab37da2 100644 --- a/thirdparty/thorvg/src/lib/sw_engine/tvgSwRasterTexmap.h +++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRasterTexmap.h @@ -70,190 +70,17 @@ static bool _arrange(const SwImage* image, const SwBBox* region, int& yStart, in } -static void _rasterMaskedPolygonImageSegmentInt(SwSurface* surface, const SwImage* image, const SwBBox* region, int yStart, int yEnd, AASpans* aaSpans, uint8_t opacity, uint8_t dirFlag) +static bool _rasterMaskedPolygonImageSegment(SwSurface* surface, const SwImage* image, const SwBBox* region, int yStart, int yEnd, AASpans* aaSpans, uint8_t opacity, uint8_t dirFlag = 0) { - float _dudx = dudx, _dvdx = dvdx; - float _dxdya = dxdya, _dxdyb = dxdyb, _dudya = dudya, _dvdya = dvdya; - float _xa = xa, _xb = xb, _ua = ua, _va = va; - auto sbuf = image->buf32; - int32_t sw = static_cast<int32_t>(image->stride); - int32_t sh = image->h; - int32_t x1, x2, ar, ab, iru, irv, px, ay; - int32_t vv = 0, uu = 0; - int32_t minx = INT32_MAX, maxx = INT32_MIN; - float dx, u, v, iptr; - auto cbuffer = surface->compositor->image.buf32; - SwSpan* span = nullptr; //used only when rle based. - - if (!_arrange(image, region, yStart, yEnd)) return; - - //Clear out of the Polygon vertical ranges - auto size = surface->compositor->bbox.max.x - surface->compositor->bbox.min.x; - if (dirFlag == 1) { //left top case. - for(int y = surface->compositor->bbox.min.y; y < yStart; ++y) { - rasterPixel32(surface->compositor->image.buf32 + y * surface->compositor->image.stride, 0, surface->compositor->bbox.min.x, size); - } - } - if (dirFlag == 4) { //right bottom case. - for(int y = yEnd; y < surface->compositor->bbox.max.y; ++y) { - rasterPixel32(surface->compositor->image.buf32 + y * surface->compositor->image.stride, 0, surface->compositor->bbox.min.x, size); - } - } - - //Loop through all lines in the segment - uint32_t spanIdx = 0; - - if (region) { - minx = region->min.x; - maxx = region->max.x; - } else { - span = image->rle->spans; - while (span->y < yStart) { - ++span; - ++spanIdx; - } - } - - for (int32_t y = yStart; y < yEnd; ++y) { - auto cmp = &cbuffer[y * surface->compositor->image.stride]; - x1 = (int32_t)_xa; - x2 = (int32_t)_xb; - - if (!region) { - minx = INT32_MAX; - maxx = INT32_MIN; - //one single row, could be consisted of multiple spans. - while (span->y == y && spanIdx < image->rle->size) { - if (minx > span->x) minx = span->x; - if (maxx < span->x + span->len) maxx = span->x + span->len; - ++span; - ++spanIdx; - } - } - - if (x1 < minx) x1 = minx; - if (x2 > maxx) x2 = maxx; - - //Anti-Aliasing frames - //FIXME: this aa must be applied before masking op - ay = y - aaSpans->yStart; - if (aaSpans->lines[ay].x[0] > x1) aaSpans->lines[ay].x[0] = x1; - if (aaSpans->lines[ay].x[1] < x2) aaSpans->lines[ay].x[1] = x2; - - //Range allowed - if ((x2 - x1) >= 1 && (x1 < maxx) && (x2 > minx)) { - for (int32_t x = surface->compositor->bbox.min.x; x < surface->compositor->bbox.max.x; ++x) { - //Range allowed - if (x >= x1 && x < x2) { - //Perform subtexel pre-stepping on UV - dx = 1 - (_xa - x1); - u = _ua + dx * _dudx; - v = _va + dx * _dvdx; - if ((uint32_t)v >= image->h) { - cmp[x] = 0; - } else { - if (opacity == 255) { - uu = (int) u; - if (uu >= sw) continue; - vv = (int) v; - if (vv >= sh) continue; - - ar = (int)(255 * (1 - modff(u, &iptr))); - ab = (int)(255 * (1 - modff(v, &iptr))); - iru = uu + 1; - irv = vv + 1; - - px = *(sbuf + (vv * sw) + uu); - - /* horizontal interpolate */ - if (iru < sw) { - /* right pixel */ - int px2 = *(sbuf + (vv * sw) + iru); - px = INTERPOLATE(px, px2, ar); - } - /* vertical interpolate */ - if (irv < sh) { - /* bottom pixel */ - int px2 = *(sbuf + (irv * sw) + uu); - - /* horizontal interpolate */ - if (iru < sw) { - /* bottom right pixel */ - int px3 = *(sbuf + (irv * sw) + iru); - px2 = INTERPOLATE(px2, px3, ar); - } - px = INTERPOLATE(px, px2, ab); - } - cmp[x] = ALPHA_BLEND(cmp[x], A(px)); - - //Step UV horizontally - u += _dudx; - v += _dvdx; - } else { - uu = (int) u; - if (uu >= sw) continue; - vv = (int) v; - if (vv >= sh) continue; - - ar = (int)(255 * (1 - modff(u, &iptr))); - ab = (int)(255 * (1 - modff(v, &iptr))); - iru = uu + 1; - irv = vv + 1; - - px = *(sbuf + (vv * sw) + uu); - - /* horizontal interpolate */ - if (iru < sw) { - /* right pixel */ - int px2 = *(sbuf + (vv * sw) + iru); - px = INTERPOLATE(px, px2, ar); - } - /* vertical interpolate */ - if (irv < sh) { - /* bottom pixel */ - int px2 = *(sbuf + (irv * sw) + uu); - - /* horizontal interpolate */ - if (iru < sw) { - /* bottom right pixel */ - int px3 = *(sbuf + (irv * sw) + iru); - px2 = INTERPOLATE(px2, px3, ar); - } - px = INTERPOLATE(px, px2, ab); - } - cmp[x] = ALPHA_BLEND(cmp[x], MULTIPLY(A(px), opacity)); - - //Step UV horizontally - u += _dudx; - v += _dvdx; - } - } - } else { - //Clear out of polygon horizontal range - if (x < x1 && (dirFlag == 1 || dirFlag == 2)) cmp[x] = 0; - else if (x >= x2 && (dirFlag == 3 || dirFlag == 4)) cmp[x] = 0; - } - } - } - //Step along both edges - _xa += _dxdya; - _xb += _dxdyb; - _ua += _dudya; - _va += _dvdya; - } - xa = _xa; - xb = _xb; - ua = _ua; - va = _va; -} - + return false; -static void _rasterMaskedPolygonImageSegmentDup(SwSurface* surface, const SwImage* image, const SwBBox* region, SwBlender maskOp, SwBlender amaskOp, int yStart, int yEnd, AASpans* aaSpans, uint8_t opacity) -{ +#if 0 //Enable it when GRAYSCALE image is supported + auto maskOp = _getMaskOp(surface->compositor->method); + auto direct = _direct(surface->compositor->method); float _dudx = dudx, _dvdx = dvdx; float _dxdya = dxdya, _dxdyb = dxdyb, _dudya = dudya, _dvdya = dvdya; float _xa = xa, _xb = xb, _ua = ua, _va = va; - auto sbuf = image->buf32; + auto sbuf = image->buf8; int32_t sw = static_cast<int32_t>(image->stride); int32_t sh = image->h; int32_t x1, x2, x, y, ar, ab, iru, irv, px, ay; @@ -262,7 +89,7 @@ static void _rasterMaskedPolygonImageSegmentDup(SwSurface* surface, const SwImag float dx, u, v, iptr; SwSpan* span = nullptr; //used only when rle based. - if (!_arrange(image, region, yStart, yEnd)) return; + if (!_arrange(image, region, yStart, yEnd)) return false; //Loop through all lines in the segment uint32_t spanIdx = 0; @@ -313,7 +140,8 @@ static void _rasterMaskedPolygonImageSegmentDup(SwSurface* surface, const SwImag x = x1; - auto cmp = &surface->compositor->image.buf32[y * surface->compositor->image.stride + x1]; + auto cmp = &surface->compositor->image.buf8[y * surface->compositor->image.stride + x1]; + auto dst = &surface->buf8[y * surface->stride + x1]; if (opacity == 255) { //Draw horizontal line @@ -349,7 +177,13 @@ static void _rasterMaskedPolygonImageSegmentDup(SwSurface* surface, const SwImag } px = INTERPOLATE(px, px2, ab); } - *cmp = maskOp(px, *cmp, IA(px)); + if (direct) { + auto tmp = maskOp(px, *cmp, 0); //not use alpha + *dst = tmp + MULTIPLY(*dst, ~tmp); + ++dst; + } else { + *cmp = maskOp(px, *cmp, ~px); + } ++cmp; //Step UV horizontally @@ -392,7 +226,15 @@ static void _rasterMaskedPolygonImageSegmentDup(SwSurface* surface, const SwImag } px = INTERPOLATE(px, px2, ab); } - *cmp = amaskOp(px, *cmp, opacity); + + if (direct) { + auto tmp = maskOp(MULTIPLY(px, opacity), *cmp, 0); + *dst = tmp + MULTIPLY(*dst, ~tmp); + ++dst; + } else { + auto tmp = MULTIPLY(px, opacity); + *cmp = maskOp(tmp, *cmp, ~px); + } ++cmp; //Step UV horizontally @@ -418,17 +260,9 @@ static void _rasterMaskedPolygonImageSegmentDup(SwSurface* surface, const SwImag xb = _xb; ua = _ua; va = _va; -} - -static void _rasterMaskedPolygonImageSegment(SwSurface* surface, const SwImage* image, const SwBBox* region, int yStart, int yEnd, AASpans* aaSpans, uint8_t opacity, uint8_t dirFlag = 0) -{ - if (surface->compositor->method == CompositeMethod::IntersectMask) { - _rasterMaskedPolygonImageSegmentInt(surface, image, region, yStart, yEnd, aaSpans, opacity, dirFlag); - } else if (auto opMask = _getMaskOp(surface->compositor->method)) { - //Other Masking operations: Add, Subtract, Difference ... - _rasterMaskedPolygonImageSegmentDup(surface, image, region, opMask, _getAMaskOp(surface->compositor->method), yStart, yEnd, aaSpans, opacity); - } + return true; +#endif } @@ -1256,6 +1090,11 @@ static bool _apply(SwSurface* surface, AASpans* aaSpans) */ static bool _rasterTexmapPolygon(SwSurface* surface, const SwImage* image, const Matrix* transform, const SwBBox* region, uint8_t opacity) { + if (surface->channelSize == sizeof(uint8_t)) { + TVGERR("SW_ENGINE", "Not supported grayscale Textmap polygon!"); + return false; + } + //Exceptions: No dedicated drawing area? if ((!image->rle && !region) || (image->rle && image->rle->size == 0)) return false; @@ -1294,6 +1133,11 @@ static bool _rasterTexmapPolygon(SwSurface* surface, const SwImage* image, const _rasterPolygonImage(surface, image, region, polygon, aaSpans, opacity); +#if 0 + if (_compositing(surface) && _masking(surface) && !_direct(surface->compositor->method)) { + _compositeMaskImage(surface, &surface->compositor->image, surface->compositor->bbox); + } +#endif return _apply(surface, aaSpans); } @@ -1313,6 +1157,11 @@ static bool _rasterTexmapPolygon(SwSurface* surface, const SwImage* image, const */ static bool _rasterTexmapPolygonMesh(SwSurface* surface, const SwImage* image, const RenderMesh* mesh, const Matrix* transform, const SwBBox* region, uint8_t opacity) { + if (surface->channelSize == sizeof(uint8_t)) { + TVGERR("SW_ENGINE", "Not supported grayscale Textmap polygon mesh!"); + return false; + } + //Exceptions: No dedicated drawing area? if ((!image->rle && !region) || (image->rle && image->rle->size == 0)) return false; @@ -1342,15 +1191,17 @@ static bool _rasterTexmapPolygonMesh(SwSurface* surface, const SwImage* image, c } // Get AA spans and step polygons again to draw - auto aaSpans = _AASpans(ys, ye, image, region); - if (aaSpans) { + if (auto aaSpans = _AASpans(ys, ye, image, region)) { for (uint32_t i = 0; i < mesh->triangleCnt; i++) { _rasterPolygonImage(surface, image, region, transformedTris[i], aaSpans, opacity); } - // Apply to surface (note: frees the AA spans) +#if 0 + if (_compositing(surface) && _masking(surface) && !_direct(surface->compositor->method)) { + _compositeMaskImage(surface, &surface->compositor->image, surface->compositor->bbox); + } +#endif _apply(surface, aaSpans); } free(transformedTris); - return true; } diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwRenderer.cpp b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRenderer.cpp index 1115a41d64..091b72fa97 100644 --- a/thirdparty/thorvg/src/lib/sw_engine/tvgSwRenderer.cpp +++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRenderer.cpp @@ -78,6 +78,30 @@ struct SwShapeTask : SwTask bool cmpStroking = false; bool clipper = false; + /* We assume that if the stroke width is greater than 2, + the shape's outline beneath the stroke could be adequately covered by the stroke drawing. + Therefore, antialiasing is disabled under this condition. + Additionally, the stroke style should not be dashed. */ + bool antialiasing(float strokeWidth) + { + return strokeWidth < 2.0f || rshape->stroke->dashCnt > 0 || rshape->stroke->strokeFirst; + } + + float validStrokeWidth() + { + if (!rshape->stroke) return 0.0f; + + auto width = rshape->stroke->width; + if (mathZero(width)) return 0.0f; + + if (!rshape->stroke->fill && (MULTIPLY(rshape->stroke->color[3], opacity) == 0)) return 0.0f; + if (mathZero(rshape->stroke->trim.begin - rshape->stroke->trim.end)) return 0.0f; + + if (transform) return (width * sqrt(transform->e11 * transform->e11 + transform->e12 * transform->e12)); + else return width; + } + + bool clip(SwRleData* target) override { if (shape.fastTrack) rleClipRect(target, &bbox); @@ -99,16 +123,10 @@ struct SwShapeTask : SwTask { if (opacity == 0 && !clipper) return; //Invisible - uint8_t strokeAlpha = 0; - auto visibleStroke = false; + auto strokeWidth = validStrokeWidth(); bool visibleFill = false; auto clipRegion = bbox; - if (HALF_STROKE(rshape->strokeWidth()) > 0) { - rshape->strokeColor(nullptr, nullptr, nullptr, &strokeAlpha); - visibleStroke = rshape->strokeFill() || (MULTIPLY(strokeAlpha, opacity) > 0); - } - //This checks also for the case, if the invisible shape turned to visible by alpha. auto prepareShape = false; if (!shapePrepared(&shape) && (flags & RenderUpdateFlag::Color)) prepareShape = true; @@ -119,22 +137,15 @@ struct SwShapeTask : SwTask rshape->fillColor(nullptr, nullptr, nullptr, &alpha); alpha = MULTIPLY(alpha, opacity); visibleFill = (alpha > 0 || rshape->fill); - if (visibleFill || visibleStroke || clipper) { + if (visibleFill || clipper) { shapeReset(&shape); if (!shapePrepare(&shape, rshape, transform, clipRegion, bbox, mpool, tid, clips.count > 0 ? true : false)) goto err; } } - //Fill if (flags & (RenderUpdateFlag::Gradient | RenderUpdateFlag::Transform | RenderUpdateFlag::Color)) { if (visibleFill || clipper) { - /* We assume that if stroke width is bigger than 2, - shape outline below stroke could be full covered by stroke drawing. - Thus it turns off antialising in that condition. - Also, it shouldn't be dash style. */ - auto antiAlias = strokeAlpha < 255 || rshape->strokeWidth() <= 2 || rshape->strokeDash(nullptr) > 0 || (rshape->stroke && rshape->stroke->strokeFirst); - - if (!shapeGenRle(&shape, rshape, antiAlias)) goto err; + if (!shapeGenRle(&shape, rshape, antialiasing(strokeWidth))) goto err; } if (auto fill = rshape->fill) { auto ctable = (flags & RenderUpdateFlag::Gradient) ? true : false; @@ -144,10 +155,9 @@ struct SwShapeTask : SwTask shapeDelFill(&shape); } } - //Stroke if (flags & (RenderUpdateFlag::Stroke | RenderUpdateFlag::Transform)) { - if (visibleStroke) { + if (strokeWidth > 0.0f) { shapeResetStroke(&shape, rshape, transform); if (!shapeGenStrokeRle(&shape, rshape, transform, clipRegion, bbox, mpool, tid)) goto err; @@ -641,8 +651,6 @@ Compositor* SwRenderer::target(const RenderRegion& region, ColorSpace cs) if (x + w > sw) w = (sw - x); if (y + h > sh) h = (sh - y); - TVGLOG("SW_ENGINE", "Using intermediate composition [Region: %d %d %d %d]", x, y, w, h); - cmp->compositor->recoverSfc = surface; cmp->compositor->recoverCmp = surface->compositor; cmp->compositor->valid = false; diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwRle.cpp b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRle.cpp index fe84983a76..a4a7fabdee 100644 --- a/thirdparty/thorvg/src/lib/sw_engine/tvgSwRle.cpp +++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRle.cpp @@ -716,11 +716,11 @@ static void _decomposeOutline(RleWorker& rw) for (auto cntr = outline->cntrs.data; cntr < outline->cntrs.end(); ++cntr) { auto last = *cntr; auto limit = outline->pts.data + last; - auto start = UPSCALE(outline->pts.data[first]); + auto start = UPSCALE(outline->pts[first]); auto pt = outline->pts.data + first; auto types = outline->types.data + first; - _moveTo(rw, UPSCALE(outline->pts.data[first])); + _moveTo(rw, UPSCALE(outline->pts[first])); while (pt < limit) { ++pt; diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwShape.cpp b/thirdparty/thorvg/src/lib/sw_engine/tvgSwShape.cpp index 14dd68b906..651eaee452 100644 --- a/thirdparty/thorvg/src/lib/sw_engine/tvgSwShape.cpp +++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwShape.cpp @@ -21,9 +21,8 @@ */ #include "tvgSwCommon.h" +#include "tvgMath.h" #include "tvgBezier.h" -#include <float.h> -#include <math.h> /************************************************************************/ /* Internal Class Implementation */ @@ -108,7 +107,7 @@ static void _outlineClose(SwOutline& outline) if (outline.pts.count == i) return; //Close the path - outline.pts.push(outline.pts.data[i]); + outline.pts.push(outline.pts[i]); outline.types.push(SW_CURVE_TYPE_POINT); outline.closed.push(true); } @@ -127,14 +126,18 @@ static void _dashLineTo(SwDashStroke& dash, const Point* to, const Matrix* trans } } else { while (len > dash.curLen) { - len -= dash.curLen; Line left, right; - _lineSplitAt(cur, dash.curLen, left, right);; - dash.curIdx = (dash.curIdx + 1) % dash.cnt; - if (!dash.curOpGap) { - _outlineMoveTo(*dash.outline, &left.pt1, transform); - _outlineLineTo(*dash.outline, &left.pt2, transform); + if (dash.curLen > 0) { + len -= dash.curLen; + _lineSplitAt(cur, dash.curLen, left, right); + if (!dash.curOpGap) { + _outlineMoveTo(*dash.outline, &left.pt1, transform); + _outlineLineTo(*dash.outline, &left.pt2, transform); + } + } else { + right = cur; } + dash.curIdx = (dash.curIdx + 1) % dash.cnt; dash.curLen = dash.pattern[dash.curIdx]; dash.curOpGap = !dash.curOpGap; cur = right; @@ -169,16 +172,22 @@ static void _dashCubicTo(SwDashStroke& dash, const Point* ctrl1, const Point* ct _outlineCubicTo(*dash.outline, ctrl1, ctrl2, to, transform); } } else { + bool begin = true; //starting with move_to while (len > dash.curLen) { Bezier left, right; - len -= dash.curLen; - bezSplitAt(cur, dash.curLen, left, right); - if (!dash.curOpGap) { - // leftovers from a previous command don't require moveTo - if (dash.pattern[dash.curIdx] - dash.curLen < FLT_EPSILON) { - _outlineMoveTo(*dash.outline, &left.start, transform); + if (dash.curLen > 0) { + len -= dash.curLen; + bezSplitAt(cur, dash.curLen, left, right); + if (!dash.curOpGap) { + // leftovers from a previous command don't require moveTo + if (begin || dash.pattern[dash.curIdx] - dash.curLen < FLT_EPSILON) { + _outlineMoveTo(*dash.outline, &left.start, transform); + begin = false; + } + _outlineCubicTo(*dash.outline, &left.ctrl1, &left.ctrl2, &left.end, transform); } - _outlineCubicTo(*dash.outline, &left.ctrl1, &left.ctrl2, &left.end, transform); + } else { + right = cur; } dash.curIdx = (dash.curIdx + 1) % dash.cnt; dash.curLen = dash.pattern[dash.curIdx]; @@ -203,11 +212,10 @@ static void _dashCubicTo(SwDashStroke& dash, const Point* ctrl1, const Point* ct } -static SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix* transform) +static SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix* transform, float length) { const PathCommand* cmds = rshape->path.cmds.data; auto cmdCnt = rshape->path.cmds.count; - const Point* pts = rshape->path.pts.data; auto ptsCnt = rshape->path.pts.count; @@ -215,86 +223,161 @@ static SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix* trans if (cmdCnt == 0 || ptsCnt == 0) return nullptr; SwDashStroke dash; - dash.curIdx = 0; - dash.curLen = 0; - dash.ptStart = {0, 0}; - dash.ptCur = {0, 0}; - dash.curOpGap = false; + auto offset = 0.0f; + auto trimmed = false; + + dash.cnt = rshape->strokeDash((const float**)&dash.pattern, &offset); + + //dash by trimming. + if (length > 0.0f && dash.cnt == 0) { + auto begin = length * rshape->stroke->trim.begin; + auto end = length * rshape->stroke->trim.end; + + //TODO: mix trimming + dash style + + //default + if (end > begin) { + if (begin > 0) dash.cnt += 4; + else dash.cnt += 2; + //looping + } else dash.cnt += 3; + + dash.pattern = (float*)malloc(sizeof(float) * dash.cnt); + + if (dash.cnt == 2) { + dash.pattern[0] = end - begin; + dash.pattern[1] = length - (end - begin); + } else if (dash.cnt == 3) { + dash.pattern[0] = end; + dash.pattern[1] = (begin - end); + dash.pattern[2] = length - begin; + } else { + dash.pattern[0] = 0; //zero dash to start with a space. + dash.pattern[1] = begin; + dash.pattern[2] = end - begin; + dash.pattern[3] = length - (end - begin); + } + + trimmed = true; + //just a dasy style. + } else { + + if (dash.cnt == 0) return nullptr; + } - const float* pattern; - dash.cnt = rshape->strokeDash(&pattern); - if (dash.cnt == 0) return nullptr; + //offset? + auto patternLength = 0.0f; + uint32_t offIdx = 0; + if (!mathZero(offset)) { + for (size_t i = 0; i < dash.cnt; ++i) patternLength += dash.pattern[i]; + bool isOdd = dash.cnt % 2; + if (isOdd) patternLength *= 2; + + offset = fmod(offset, patternLength); + if (offset < 0) offset += patternLength; + + for (size_t i = 0; i < dash.cnt * (1 + (size_t)isOdd); ++i, ++offIdx) { + auto curPattern = dash.pattern[i % dash.cnt]; + if (offset < curPattern) break; + offset -= curPattern; + } + } //OPTMIZE ME: Use mempool??? - dash.pattern = const_cast<float*>(pattern); dash.outline = static_cast<SwOutline*>(calloc(1, sizeof(SwOutline))); //smart reservation - auto outlinePtsCnt = 0; - auto outlineCntrsCnt = 0; + auto closeCnt = 0; + auto moveCnt = 0; + + for (auto cmd = rshape->path.cmds.data; cmd < rshape->path.cmds.end(); ++cmd) { + if (*cmd == PathCommand::Close) ++closeCnt; + else if (*cmd == PathCommand::MoveTo) ++moveCnt; + } - for (uint32_t i = 0; i < cmdCnt; ++i) { - switch (*(cmds + i)) { + //No idea exact count.... Reserve Approximitely 20x... + //OPTIMIZE: we can directly copy the path points when the close is occupied with a point. + dash.outline->pts.grow(20 * (closeCnt + ptsCnt + 1)); + dash.outline->types.grow(20 * (closeCnt + ptsCnt + 1)); + dash.outline->cntrs.grow(20 * (moveCnt + 1)); + + while (cmdCnt-- > 0) { + switch (*cmds) { case PathCommand::Close: { - ++outlinePtsCnt; + _dashLineTo(dash, &dash.ptStart, transform); break; } case PathCommand::MoveTo: { - ++outlineCntrsCnt; - ++outlinePtsCnt; + //reset the dash + dash.curIdx = offIdx % dash.cnt; + dash.curLen = dash.pattern[dash.curIdx] - offset; + dash.curOpGap = offIdx % 2; + dash.ptStart = dash.ptCur = *pts; + ++pts; break; } case PathCommand::LineTo: { - ++outlinePtsCnt; + _dashLineTo(dash, pts, transform); + ++pts; break; } case PathCommand::CubicTo: { - outlinePtsCnt += 3; + _dashCubicTo(dash, pts, pts + 1, pts + 2, transform); + pts += 3; break; } } + ++cmds; } - ++outlinePtsCnt; //for close - ++outlineCntrsCnt; //for end + _outlineEnd(*dash.outline); - //No idea exact count.... Reserve Approximitely 20x... - dash.outline->pts.grow(20 * outlinePtsCnt); - dash.outline->types.grow(20 * outlinePtsCnt); - dash.outline->cntrs.grow(20 * outlineCntrsCnt); + if (trimmed) free(dash.pattern); + + return dash.outline; +} + + +static float _outlineLength(const RenderShape* rshape) +{ + const PathCommand* cmds = rshape->path.cmds.data; + auto cmdCnt = rshape->path.cmds.count; + const Point* pts = rshape->path.pts.data; + auto ptsCnt = rshape->path.pts.count; + //No actual shape data + if (cmdCnt == 0 || ptsCnt == 0) return 0.0f; + + const Point* close = nullptr; + auto length = 0.0f; + + //Compute the whole length while (cmdCnt-- > 0) { switch (*cmds) { case PathCommand::Close: { - _dashLineTo(dash, &dash.ptStart, transform); + length += mathLength(pts - 1, close); + ++pts; break; } case PathCommand::MoveTo: { - //reset the dash - dash.curIdx = 0; - dash.curLen = *dash.pattern; - dash.curOpGap = false; - dash.ptStart = dash.ptCur = *pts; + close = pts; ++pts; break; } case PathCommand::LineTo: { - _dashLineTo(dash, pts, transform); + length += mathLength(pts - 1, pts); ++pts; break; } case PathCommand::CubicTo: { - _dashCubicTo(dash, pts, pts + 1, pts + 2, transform); + length += bezLength({*(pts - 1), *pts, *(pts + 1), *(pts + 2)}); pts += 3; break; } } ++cmds; } - - _outlineEnd(*dash.outline); - - return dash.outline; + return length; } @@ -321,7 +404,6 @@ static bool _genOutline(SwShape* shape, const RenderShape* rshape, const Matrix* { const PathCommand* cmds = rshape->path.cmds.data; auto cmdCnt = rshape->path.cmds.count; - const Point* pts = rshape->path.pts.data; auto ptsCnt = rshape->path.pts.count; @@ -329,47 +411,21 @@ static bool _genOutline(SwShape* shape, const RenderShape* rshape, const Matrix* if (cmdCnt == 0 || ptsCnt == 0) return false; //smart reservation - auto outlinePtsCnt = 0; - auto outlineCntrsCnt = 0; + auto moveCnt = 0; auto closeCnt = 0; - for (uint32_t i = 0; i < cmdCnt; ++i) { - switch (*(cmds + i)) { - case PathCommand::Close: { - ++outlinePtsCnt; - ++closeCnt; - break; - } - case PathCommand::MoveTo: { - ++outlineCntrsCnt; - ++outlinePtsCnt; - break; - } - case PathCommand::LineTo: { - ++outlinePtsCnt; - break; - } - case PathCommand::CubicTo: { - outlinePtsCnt += 3; - break; - } - } + for (auto cmd = rshape->path.cmds.data; cmd < rshape->path.cmds.end(); ++cmd) { + if (*cmd == PathCommand::Close) ++closeCnt; + else if (*cmd == PathCommand::MoveTo) ++moveCnt; } - if (static_cast<uint32_t>(outlinePtsCnt - closeCnt) > ptsCnt) { - TVGERR("SW_ENGINE", "Wrong a pair of the commands & points - required(%d), current(%d)", outlinePtsCnt - closeCnt, ptsCnt); - return false; - } - - ++outlinePtsCnt; //for close - ++outlineCntrsCnt; //for end - shape->outline = mpoolReqOutline(mpool, tid); auto outline = shape->outline; - outline->pts.grow(outlinePtsCnt); - outline->types.grow(outlinePtsCnt); - outline->cntrs.grow(outlineCntrsCnt); + //OPTIMIZE: we can directly copy the path points when the close is occupied with a point. + outline->pts.grow(ptsCnt + closeCnt + 1); + outline->types.grow(ptsCnt + closeCnt + 1); + outline->cntrs.grow(moveCnt + 1); //Dash outlines are always opened. //Only normal outlines use this information, it sholud be same to their contour counts. @@ -514,12 +570,14 @@ bool shapeGenStrokeRle(SwShape* shape, const RenderShape* rshape, const Matrix* bool freeOutline = false; bool ret = true; - //Dash Style Stroke - if (rshape->strokeDash(nullptr) > 0) { - shapeOutline = _genDashOutline(rshape, transform); + auto length = rshape->strokeTrim() ? _outlineLength(rshape) : 0.0f; + + //Dash style (+trimming) + if (rshape->stroke->dashCnt > 0 || length > 0) { + shapeOutline = _genDashOutline(rshape, transform, length); if (!shapeOutline) return false; freeOutline = true; - //Normal Style stroke + //Normal style } else { if (!shape->outline) { if (!_genOutline(shape, rshape, transform, mpool, tid, false)) return false; diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwStroke.cpp b/thirdparty/thorvg/src/lib/sw_engine/tvgSwStroke.cpp index 38626cb428..f46a4a5a1d 100644 --- a/thirdparty/thorvg/src/lib/sw_engine/tvgSwStroke.cpp +++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwStroke.cpp @@ -373,10 +373,6 @@ void _firstSubPath(SwStroke& stroke, SwFixed startAngle, SwFixed lineLength) static void _lineTo(SwStroke& stroke, const SwPoint& to) { auto delta = to - stroke.center; - - //a zero-length lineto is a no-op; avoid creating a spurious corner - if (delta.zero()) return; - //compute length of line auto angle = mathAtan(delta); @@ -428,12 +424,6 @@ static void _lineTo(SwStroke& stroke, const SwPoint& to) static void _cubicTo(SwStroke& stroke, const SwPoint& ctrl1, const SwPoint& ctrl2, const SwPoint& to) { - //if all control points are coincident, this is a no-op; avoid creating a spurious corner - if ((stroke.center - ctrl1).small() && (ctrl1 - ctrl2).small() && (ctrl2 - to).small()) { - stroke.center = to; - return; - } - SwPoint bezStack[37]; //TODO: static? auto limit = bezStack + 32; auto arc = bezStack; @@ -852,7 +842,7 @@ bool strokeParseOutline(SwStroke* stroke, const SwOutline& outline) continue; } - auto start = outline.pts.data[first]; + auto start = outline.pts[first]; auto pt = outline.pts.data + first; auto types = outline.types.data + first; auto type = types[0]; diff --git a/thirdparty/thorvg/src/lib/tvgAnimation.cpp b/thirdparty/thorvg/src/lib/tvgAnimation.cpp index f671daa727..4e8c8568d4 100644 --- a/thirdparty/thorvg/src/lib/tvgAnimation.cpp +++ b/thirdparty/thorvg/src/lib/tvgAnimation.cpp @@ -23,6 +23,7 @@ //#include "tvgAnimationImpl.h" #include "tvgCommon.h" #include "tvgFrameModule.h" +#include "tvgPaint.h" #include "tvgPictureImpl.h" /************************************************************************/ @@ -31,8 +32,20 @@ struct Animation::Impl { - //TODO: Memory Safety - Picture picture; + Picture* picture = nullptr; + + Impl() + { + picture = Picture::gen().release(); + static_cast<Paint*>(picture)->pImpl->ref(); + } + + ~Impl() + { + if (static_cast<Paint*>(picture)->pImpl->unref() == 0) { + delete(picture); + } + } }; /************************************************************************/ @@ -41,19 +54,18 @@ struct Animation::Impl Animation::~Animation() { - + delete(pImpl); } Animation::Animation() : pImpl(new Impl) { - pImpl->picture.pImpl->animated = true; } Result Animation::frame(uint32_t no) noexcept { - auto loader = pImpl->picture.pImpl->loader.get(); + auto loader = pImpl->picture->pImpl->loader.get(); if (!loader) return Result::InsufficientCondition; if (!loader->animatable()) return Result::NonSupport; @@ -65,13 +77,13 @@ Result Animation::frame(uint32_t no) noexcept Picture* Animation::picture() const noexcept { - return &pImpl->picture; + return pImpl->picture; } uint32_t Animation::curFrame() const noexcept { - auto loader = pImpl->picture.pImpl->loader.get(); + auto loader = pImpl->picture->pImpl->loader.get(); if (!loader) return 0; if (!loader->animatable()) return 0; @@ -82,7 +94,7 @@ uint32_t Animation::curFrame() const noexcept uint32_t Animation::totalFrame() const noexcept { - auto loader = pImpl->picture.pImpl->loader.get(); + auto loader = pImpl->picture->pImpl->loader.get(); if (!loader) return 0; if (!loader->animatable()) return 0; @@ -93,7 +105,7 @@ uint32_t Animation::totalFrame() const noexcept float Animation::duration() const noexcept { - auto loader = pImpl->picture.pImpl->loader.get(); + auto loader = pImpl->picture->pImpl->loader.get(); if (!loader) return 0; if (!loader->animatable()) return 0; diff --git a/thirdparty/thorvg/src/lib/tvgCanvasImpl.h b/thirdparty/thorvg/src/lib/tvgCanvasImpl.h index 9a3018e7a6..fe934ec352 100644 --- a/thirdparty/thorvg/src/lib/tvgCanvasImpl.h +++ b/thirdparty/thorvg/src/lib/tvgCanvasImpl.h @@ -65,8 +65,9 @@ struct Canvas::Impl //Free paints for (auto paint : paints) { - paint->pImpl->dispose(*renderer); - if (free) delete(paint); + if (paint->pImpl->dispose(*renderer)) { + if (free && paint->pImpl->unref() == 0) delete(paint); + } } paints.clear(); diff --git a/thirdparty/thorvg/src/lib/tvgCommon.h b/thirdparty/thorvg/src/lib/tvgCommon.h index 23011ffed5..f36b4b9b30 100644 --- a/thirdparty/thorvg/src/lib/tvgCommon.h +++ b/thirdparty/thorvg/src/lib/tvgCommon.h @@ -76,10 +76,14 @@ using Size = Point; #define TVGERR(tag, fmt, ...) fprintf(stderr, "%s[E]%s %s" tag "%s (%s %d): %s" fmt "\n", ErrorBgColor, ResetColors, ErrorColor, GreyColor, __FILE__, __LINE__, ResetColors, ##__VA_ARGS__) #define TVGLOG(tag, fmt, ...) fprintf(stdout, "%s[L]%s %s" tag "%s (%s %d): %s" fmt "\n", LogBgColor, ResetColors, LogColor, GreyColor, __FILE__, __LINE__, ResetColors, ##__VA_ARGS__) #else - #define TVGERR(...) - #define TVGLOG(...) + #define TVGERR(...) do {} while(0) + #define TVGLOG(...) do {} while(0) #endif uint16_t THORVG_VERSION_NUMBER(); + +#define P(A) ((A)->pImpl) //Access to pimpl. +#define PP(A) (((Paint*)(A))->pImpl) //Access to pimpl. + #endif //_TVG_COMMON_H_ diff --git a/thirdparty/thorvg/src/lib/tvgFill.cpp b/thirdparty/thorvg/src/lib/tvgFill.cpp index cc7e1ccaed..9215882c8d 100644 --- a/thirdparty/thorvg/src/lib/tvgFill.cpp +++ b/thirdparty/thorvg/src/lib/tvgFill.cpp @@ -26,6 +26,50 @@ /* Internal Class Implementation */ /************************************************************************/ +Fill* RadialGradient::Impl::duplicate() +{ + auto ret = RadialGradient::gen(); + if (!ret) return nullptr; + + ret->pImpl->cx = cx; + ret->pImpl->cy = cy; + ret->pImpl->r = r; + ret->pImpl->fx = fx; + ret->pImpl->fy = fy; + ret->pImpl->fr = fr; + + return ret.release(); +} + + +Result RadialGradient::Impl::radial(float cx, float cy, float r, float fx, float fy, float fr) +{ + if (r < 0 || fr < 0) return Result::InvalidArguments; + + this->cx = cx; + this->cy = cy; + this->r = r; + this->fx = fx; + this->fy = fy; + this->fr = fr; + + return Result::Success; +}; + + +Fill* LinearGradient::Impl::duplicate() +{ + auto ret = LinearGradient::gen(); + if (!ret) return nullptr; + + ret->pImpl->x1 = x1; + ret->pImpl->y1 = y1; + ret->pImpl->x2 = x2; + ret->pImpl->y2 = y2; + + return ret.release(); +}; + /************************************************************************/ /* External Class Implementation */ @@ -110,7 +154,97 @@ Fill* Fill::duplicate() const noexcept return pImpl->duplicate(); } + uint32_t Fill::identifier() const noexcept { return pImpl->id; } + + +RadialGradient::RadialGradient():pImpl(new Impl()) +{ + Fill::pImpl->id = TVG_CLASS_ID_RADIAL; + Fill::pImpl->method(new FillDup<RadialGradient::Impl>(pImpl)); +} + + +RadialGradient::~RadialGradient() +{ + delete(pImpl); +} + + +Result RadialGradient::radial(float cx, float cy, float r) noexcept +{ + return pImpl->radial(cx, cy, r, cx, cy, 0.0f); +} + + +Result RadialGradient::radial(float* cx, float* cy, float* r) const noexcept +{ + if (cx) *cx = pImpl->cx; + if (cy) *cy = pImpl->cy; + if (r) *r = pImpl->r; + + return Result::Success; +} + + +unique_ptr<RadialGradient> RadialGradient::gen() noexcept +{ + return unique_ptr<RadialGradient>(new RadialGradient); +} + + +uint32_t RadialGradient::identifier() noexcept +{ + return TVG_CLASS_ID_RADIAL; +} + + +LinearGradient::LinearGradient():pImpl(new Impl()) +{ + Fill::pImpl->id = TVG_CLASS_ID_LINEAR; + Fill::pImpl->method(new FillDup<LinearGradient::Impl>(pImpl)); +} + + +LinearGradient::~LinearGradient() +{ + delete(pImpl); +} + + +Result LinearGradient::linear(float x1, float y1, float x2, float y2) noexcept +{ + pImpl->x1 = x1; + pImpl->y1 = y1; + pImpl->x2 = x2; + pImpl->y2 = y2; + + return Result::Success; +} + + +Result LinearGradient::linear(float* x1, float* y1, float* x2, float* y2) const noexcept +{ + if (x1) *x1 = pImpl->x1; + if (x2) *x2 = pImpl->x2; + if (y1) *y1 = pImpl->y1; + if (y2) *y2 = pImpl->y2; + + return Result::Success; +} + + +unique_ptr<LinearGradient> LinearGradient::gen() noexcept +{ + return unique_ptr<LinearGradient>(new LinearGradient); +} + + +uint32_t LinearGradient::identifier() noexcept +{ + return TVG_CLASS_ID_LINEAR; +} + diff --git a/thirdparty/thorvg/src/lib/tvgFill.h b/thirdparty/thorvg/src/lib/tvgFill.h index 20603b7333..ff3dd48c49 100644 --- a/thirdparty/thorvg/src/lib/tvgFill.h +++ b/thirdparty/thorvg/src/lib/tvgFill.h @@ -86,4 +86,27 @@ struct Fill::Impl } }; + +struct RadialGradient::Impl +{ + float cx = 0.0f, cy = 0.0f; + float fx = 0.0f, fy = 0.0f; + float r = 0.0f, fr = 0.0f; + + Fill* duplicate(); + Result radial(float cx, float cy, float r, float fx, float fy, float fr); +}; + + +struct LinearGradient::Impl +{ + float x1 = 0.0f; + float y1 = 0.0f; + float x2 = 0.0f; + float y2 = 0.0f; + + Fill* duplicate(); +}; + + #endif //_TVG_FILL_H_ diff --git a/thirdparty/thorvg/src/lib/tvgLinearGradient.cpp b/thirdparty/thorvg/src/lib/tvgLinearGradient.cpp deleted file mode 100644 index 3e040c08f1..0000000000 --- a/thirdparty/thorvg/src/lib/tvgLinearGradient.cpp +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (c) 2020 - 2023 the ThorVG project. All rights reserved. - - * 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 <float.h> -#include <math.h> -#include "tvgFill.h" - -/************************************************************************/ -/* Internal Class Implementation */ -/************************************************************************/ - -struct LinearGradient::Impl -{ - float x1 = 0; - float y1 = 0; - float x2 = 0; - float y2 = 0; - - Fill* duplicate() - { - auto ret = LinearGradient::gen(); - if (!ret) return nullptr; - - ret->pImpl->x1 = x1; - ret->pImpl->y1 = y1; - ret->pImpl->x2 = x2; - ret->pImpl->y2 = y2; - - return ret.release(); - } -}; - -/************************************************************************/ -/* External Class Implementation */ -/************************************************************************/ - -LinearGradient::LinearGradient():pImpl(new Impl()) -{ - Fill::pImpl->id = TVG_CLASS_ID_LINEAR; - Fill::pImpl->method(new FillDup<LinearGradient::Impl>(pImpl)); -} - - -LinearGradient::~LinearGradient() -{ - delete(pImpl); -} - - -Result LinearGradient::linear(float x1, float y1, float x2, float y2) noexcept -{ - pImpl->x1 = x1; - pImpl->y1 = y1; - pImpl->x2 = x2; - pImpl->y2 = y2; - - return Result::Success; -} - - -Result LinearGradient::linear(float* x1, float* y1, float* x2, float* y2) const noexcept -{ - if (x1) *x1 = pImpl->x1; - if (x2) *x2 = pImpl->x2; - if (y1) *y1 = pImpl->y1; - if (y2) *y2 = pImpl->y2; - - return Result::Success; -} - - -unique_ptr<LinearGradient> LinearGradient::gen() noexcept -{ - return unique_ptr<LinearGradient>(new LinearGradient); -} - - -uint32_t LinearGradient::identifier() noexcept -{ - return TVG_CLASS_ID_LINEAR; -} diff --git a/thirdparty/thorvg/src/lib/tvgLoader.cpp b/thirdparty/thorvg/src/lib/tvgLoader.cpp index 16b604c89e..8ed0d5752e 100644 --- a/thirdparty/thorvg/src/lib/tvgLoader.cpp +++ b/thirdparty/thorvg/src/lib/tvgLoader.cpp @@ -164,7 +164,7 @@ static LoadModule* _findByType(const string& mimeType) if (mimeType == "tvg") type = FileType::Tvg; else if (mimeType == "svg" || mimeType == "svg+xml") type = FileType::Svg; - else if (mimeType == "lottie" || mimeType == "json") type = FileType::Lottie; + else if (mimeType == "lottie") type = FileType::Lottie; else if (mimeType == "raw") type = FileType::Raw; else if (mimeType == "png") type = FileType::Png; else if (mimeType == "jpg" || mimeType == "jpeg") type = FileType::Jpg; @@ -214,22 +214,24 @@ shared_ptr<LoadModule> LoaderMgr::loader(const string& path, bool* invalid) shared_ptr<LoadModule> LoaderMgr::loader(const char* data, uint32_t size, const string& mimeType, bool copy) { - //Try first with the given MimeType - if (auto loader = _findByType(mimeType)) { - if (loader->open(data, size, copy)) { - return shared_ptr<LoadModule>(loader); - } else { - TVGLOG("LOADER", "Given mimetype \"%s\" seems incorrect or not supported. Will try again with other types.", mimeType.c_str()); - delete(loader); - } - } - - //Abnormal MimeType. Try with the candidates in the order - for (int i = 0; i < static_cast<int>(FileType::Unknown); i++) { - auto loader = _find(static_cast<FileType>(i)); - if (loader) { - if (loader->open(data, size, copy)) return shared_ptr<LoadModule>(loader); - else delete(loader); + //Try with the given MimeType + if (!mimeType.empty()) { + if (auto loader = _findByType(mimeType)) { + if (loader->open(data, size, copy)) { + return shared_ptr<LoadModule>(loader); + } else { + TVGLOG("LOADER", "Given mimetype \"%s\" seems incorrect or not supported. Will try again with other types.", mimeType.c_str()); + delete(loader); + } + } + //Unkown MimeType. Try with the candidates in the order + } else { + for (int i = 0; i < static_cast<int>(FileType::Unknown); i++) { + auto loader = _find(static_cast<FileType>(i)); + if (loader) { + if (loader->open(data, size, copy)) return shared_ptr<LoadModule>(loader); + else delete(loader); + } } } return nullptr; diff --git a/thirdparty/thorvg/src/lib/tvgPaint.cpp b/thirdparty/thorvg/src/lib/tvgPaint.cpp index 57da269cd7..bac5e434a5 100644 --- a/thirdparty/thorvg/src/lib/tvgPaint.cpp +++ b/thirdparty/thorvg/src/lib/tvgPaint.cpp @@ -166,7 +166,7 @@ bool Paint::Impl::render(RenderMethod& renderer) Create a composition image. */ if (compData && compData->method != CompositeMethod::ClipPath && !(compData->target->pImpl->ctxFlag & ContextFlag::FastTrack)) { auto region = smethod->bounds(renderer); - if (MASK_OPERATION(compData->method)) region.add(compData->target->pImpl->smethod->bounds(renderer)); + if (MASK_REGION_MERGING(compData->method)) region.add(compData->target->pImpl->smethod->bounds(renderer)); if (region.w == 0 || region.h == 0) return true; cmp = renderer.target(region, COMPOSITE_TO_COLORSPACE(renderer, compData->method)); if (renderer.beginComposite(cmp, CompositeMethod::None, 255)) { @@ -206,23 +206,20 @@ RenderData Paint::Impl::update(RenderMethod& renderer, const RenderTransform* pT auto method = compData->method; target->pImpl->ctxFlag &= ~ContextFlag::FastTrack; //reset - /* If transform has no rotation factors && ClipPath / AlphaMasking is a simple rectangle, - we can avoid regular ClipPath / AlphaMasking sequence but use viewport for performance */ + /* If the transformation has no rotational factors and the ClipPath/Alpha(InvAlpha)Masking involves a simple rectangle, + we can optimize by using the viewport instead of the regular ClipPath/AlphaMasking sequence for improved performance. */ auto tryFastTrack = false; if (target->identifier() == TVG_CLASS_ID_SHAPE) { if (method == CompositeMethod::ClipPath) tryFastTrack = true; - //OPTIMIZE HERE: Actually, this condition AlphaMask is useless. We can skip it? - else if (method == CompositeMethod::AlphaMask) { + else { auto shape = static_cast<Shape*>(target); uint8_t a; shape->fillColor(nullptr, nullptr, nullptr, &a); - if (a == 255 && shape->opacity() == 255 && !shape->fill()) tryFastTrack = true; - //OPTIMIZE HERE: Actually, this condition InvAlphaMask is useless. We can skip it? - } else if (method == CompositeMethod::InvAlphaMask) { - auto shape = static_cast<Shape*>(target); - uint8_t a; - shape->fillColor(nullptr, nullptr, nullptr, &a); - if ((a == 0 || shape->opacity() == 0) && !shape->fill()) tryFastTrack = true; + //no gradient fill & no compositions of the composition target. + if (!shape->fill() && !(PP(shape)->compData)) { + if (method == CompositeMethod::AlphaMask && a == 255 && PP(shape)->opacity == 255) tryFastTrack = true; + else if (method == CompositeMethod::InvAlphaMask && (a == 0 || PP(shape)->opacity == 0)) tryFastTrack = true; + } } if (tryFastTrack) { RenderRegion viewport2; @@ -263,12 +260,12 @@ RenderData Paint::Impl::update(RenderMethod& renderer, const RenderTransform* pT } -bool Paint::Impl::bounds(float* x, float* y, float* w, float* h, bool transformed) +bool Paint::Impl::bounds(float* x, float* y, float* w, float* h, bool transformed, bool stroking) { Matrix* m = nullptr; //Case: No transformed, quick return! - if (!transformed || !(m = this->transform())) return smethod->bounds(x, y, w, h); + if (!transformed || !(m = this->transform())) return smethod->bounds(x, y, w, h, stroking); //Case: Transformed auto tx = 0.0f; @@ -276,7 +273,7 @@ bool Paint::Impl::bounds(float* x, float* y, float* w, float* h, bool transforme auto tw = 0.0f; auto th = 0.0f; - auto ret = smethod->bounds(&tx, &ty, &tw, &th); + auto ret = smethod->bounds(&tx, &ty, &tw, &th, stroking); //Get vertices Point pt[4] = {{tx, ty}, {tx + tw, ty}, {tx + tw, ty + th}, {tx, ty + th}}; @@ -365,7 +362,7 @@ TVG_DEPRECATED Result Paint::bounds(float* x, float* y, float* w, float* h) cons Result Paint::bounds(float* x, float* y, float* w, float* h, bool transform) const noexcept { - if (pImpl->bounds(x, y, w, h, transform)) return Result::Success; + if (pImpl->bounds(x, y, w, h, transform, true)) return Result::Success; return Result::InsufficientCondition; } diff --git a/thirdparty/thorvg/src/lib/tvgPaint.h b/thirdparty/thorvg/src/lib/tvgPaint.h index c00238a070..c020a7dffd 100644 --- a/thirdparty/thorvg/src/lib/tvgPaint.h +++ b/thirdparty/thorvg/src/lib/tvgPaint.h @@ -42,10 +42,10 @@ namespace tvg { virtual ~StrategyMethod() {} - virtual bool dispose(RenderMethod& renderer) = 0; + virtual bool dispose(RenderMethod& renderer) = 0; //return true if the deletion is allowed. virtual void* update(RenderMethod& renderer, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper) = 0; //Return engine data if it has. virtual bool render(RenderMethod& renderer) = 0; - virtual bool bounds(float* x, float* y, float* w, float* h) = 0; + virtual bool bounds(float* x, float* y, float* w, float* h, bool stroking) = 0; virtual RenderRegion bounds(RenderMethod& renderer) const = 0; virtual Paint* duplicate() = 0; virtual Iterator* iterator() = 0; @@ -68,6 +68,7 @@ namespace tvg uint8_t ctxFlag = ContextFlag::Invalid; uint8_t id; uint8_t opacity = 255; + uint8_t refCnt = 1; ~Impl() { @@ -79,6 +80,18 @@ namespace tvg delete(rTransform); } + uint8_t ref() + { + if (refCnt == 255) TVGERR("RENDERER", "Corrupted reference count!"); + return (++refCnt); + } + + uint8_t unref() + { + if (refCnt == 0) TVGERR("RENDERER", "Corrupted reference count!"); + return (--refCnt); + } + void method(StrategyMethod* method) { smethod = method; @@ -147,7 +160,7 @@ namespace tvg bool rotate(float degree); bool scale(float factor); bool translate(float x, float y); - bool bounds(float* x, float* y, float* w, float* h, bool transformed); + bool bounds(float* x, float* y, float* w, float* h, bool transformed, bool stroking); RenderData update(RenderMethod& renderer, const RenderTransform* pTransform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper = false); bool render(RenderMethod& renderer); Paint* duplicate(); @@ -162,9 +175,9 @@ namespace tvg PaintMethod(T* _inst) : inst(_inst) {} ~PaintMethod() {} - bool bounds(float* x, float* y, float* w, float* h) override + bool bounds(float* x, float* y, float* w, float* h, bool stroking) override { - return inst->bounds(x, y, w, h); + return inst->bounds(x, y, w, h, stroking); } RenderRegion bounds(RenderMethod& renderer) const override diff --git a/thirdparty/thorvg/src/lib/tvgPictureImpl.h b/thirdparty/thorvg/src/lib/tvgPictureImpl.h index d445c72c10..f29b8a1ec3 100644 --- a/thirdparty/thorvg/src/lib/tvgPictureImpl.h +++ b/thirdparty/thorvg/src/lib/tvgPictureImpl.h @@ -70,7 +70,6 @@ struct Picture::Impl Picture* picture = nullptr; bool resizing = false; bool needComp = false; //need composition - bool animated = false; //picture is belonged to Animation Impl(Picture* p) : picture(p) { @@ -84,12 +83,10 @@ struct Picture::Impl bool dispose(RenderMethod& renderer) { - bool ret = true; - if (paint) ret = paint->pImpl->dispose(renderer); - else if (surface) ret = renderer.dispose(rd); + if (paint) paint->pImpl->dispose(renderer); + else if (surface) renderer.dispose(rd); rd = nullptr; - - return ret; + return true; } RenderUpdateFlag load() @@ -191,7 +188,7 @@ struct Picture::Impl return true; } - bool bounds(float* x, float* y, float* w, float* h) + bool bounds(float* x, float* y, float* w, float* h, bool stroking) { if (rm.triangleCnt > 0) { auto triangles = rm.triangles; diff --git a/thirdparty/thorvg/src/lib/tvgRadialGradient.cpp b/thirdparty/thorvg/src/lib/tvgRadialGradient.cpp deleted file mode 100644 index a85f60e5d0..0000000000 --- a/thirdparty/thorvg/src/lib/tvgRadialGradient.cpp +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) 2020 - 2023 the ThorVG project. All rights reserved. - - * 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 <float.h> -#include "tvgFill.h" - -/************************************************************************/ -/* Internal Class Implementation */ -/************************************************************************/ - -struct RadialGradient::Impl -{ - float cx = 0; - float cy = 0; - float radius = 0; - - Fill* duplicate() - { - auto ret = RadialGradient::gen(); - if (!ret) return nullptr; - - ret->pImpl->cx = cx; - ret->pImpl->cy = cy; - ret->pImpl->radius = radius; - - return ret.release(); - } -}; - - -/************************************************************************/ -/* External Class Implementation */ -/************************************************************************/ - -RadialGradient::RadialGradient():pImpl(new Impl()) -{ - Fill::pImpl->id = TVG_CLASS_ID_RADIAL; - Fill::pImpl->method(new FillDup<RadialGradient::Impl>(pImpl)); -} - - -RadialGradient::~RadialGradient() -{ - delete(pImpl); -} - - -Result RadialGradient::radial(float cx, float cy, float radius) noexcept -{ - if (radius < 0) return Result::InvalidArguments; - - pImpl->cx = cx; - pImpl->cy = cy; - pImpl->radius = radius; - - return Result::Success; -} - - -Result RadialGradient::radial(float* cx, float* cy, float* radius) const noexcept -{ - if (cx) *cx = pImpl->cx; - if (cy) *cy = pImpl->cy; - if (radius) *radius = pImpl->radius; - - return Result::Success; -} - - -unique_ptr<RadialGradient> RadialGradient::gen() noexcept -{ - return unique_ptr<RadialGradient>(new RadialGradient); -} - - -uint32_t RadialGradient::identifier() noexcept -{ - return TVG_CLASS_ID_RADIAL; -} diff --git a/thirdparty/thorvg/src/lib/tvgRender.h b/thirdparty/thorvg/src/lib/tvgRender.h index 9d7bafb613..1231089ff5 100644 --- a/thirdparty/thorvg/src/lib/tvgRender.h +++ b/thirdparty/thorvg/src/lib/tvgRender.h @@ -137,11 +137,17 @@ struct RenderStroke Fill *fill = nullptr; float* dashPattern = nullptr; uint32_t dashCnt = 0; + float dashOffset = 0.0f; StrokeCap cap = StrokeCap::Square; StrokeJoin join = StrokeJoin::Bevel; float miterlimit = 4.0f; bool strokeFirst = false; + struct { + float begin = 0.0f; + float end = 1.0f; + } trim; + ~RenderStroke() { free(dashPattern); @@ -182,6 +188,14 @@ struct RenderShape return stroke->width; } + bool strokeTrim() const + { + if (!stroke) return false; + if (stroke->trim.begin == 0.0f && stroke->trim.end == 1.0f) return false; + if (stroke->trim.begin == 1.0f && stroke->trim.end == 0.0f) return false; + return true; + } + bool strokeColor(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) const { if (!stroke) return false; @@ -200,10 +214,11 @@ struct RenderShape return stroke->fill; } - uint32_t strokeDash(const float** dashPattern) const + uint32_t strokeDash(const float** dashPattern, float* offset) const { if (!stroke) return 0; if (dashPattern) *dashPattern = stroke->dashPattern; + if (offset) *offset = stroke->dashOffset; return stroke->dashCnt; } @@ -253,21 +268,22 @@ public: virtual bool endComposite(Compositor* cmp) = 0; }; -static inline bool MASK_OPERATION(CompositeMethod method) +static inline bool MASK_REGION_MERGING(CompositeMethod method) { switch(method) { case CompositeMethod::AlphaMask: case CompositeMethod::InvAlphaMask: case CompositeMethod::LumaMask: case CompositeMethod::InvLumaMask: - return false; - case CompositeMethod::AddMask: case CompositeMethod::SubtractMask: case CompositeMethod::IntersectMask: + return false; + //these might expand the rendering region + case CompositeMethod::AddMask: case CompositeMethod::DifferenceMask: return true; default: - TVGERR("COMMON", "Unsupported Composite Size! = %d", (int)method); + TVGERR("RENDERER", "Unsupported Composite Method! = %d", (int)method); return false; } } @@ -284,7 +300,7 @@ static inline uint8_t CHANNEL_SIZE(ColorSpace cs) return sizeof(uint8_t); case ColorSpace::Unsupported: default: - TVGERR("SW_ENGINE", "Unsupported Channel Size! = %d", (int)cs); + TVGERR("RENDERER", "Unsupported Channel Size! = %d", (int)cs); return 0; } } @@ -294,16 +310,17 @@ static inline ColorSpace COMPOSITE_TO_COLORSPACE(RenderMethod& renderer, Composi switch(method) { case CompositeMethod::AlphaMask: case CompositeMethod::InvAlphaMask: - return ColorSpace::Grayscale8; - case CompositeMethod::LumaMask: - case CompositeMethod::InvLumaMask: case CompositeMethod::AddMask: + case CompositeMethod::DifferenceMask: case CompositeMethod::SubtractMask: case CompositeMethod::IntersectMask: - case CompositeMethod::DifferenceMask: + return ColorSpace::Grayscale8; + //TODO: Optimize Luma/InvLuma colorspace to Grayscale8 + case CompositeMethod::LumaMask: + case CompositeMethod::InvLumaMask: return renderer.colorSpace(); default: - TVGERR("COMMON", "Unsupported Composite Size! = %d", (int)method); + TVGERR("RENDERER", "Unsupported Composite Size! = %d", (int)method); return ColorSpace::Unsupported; } } diff --git a/thirdparty/thorvg/src/lib/tvgSceneImpl.h b/thirdparty/thorvg/src/lib/tvgSceneImpl.h index 8fa38bcc85..90b1775610 100644 --- a/thirdparty/thorvg/src/lib/tvgSceneImpl.h +++ b/thirdparty/thorvg/src/lib/tvgSceneImpl.h @@ -75,7 +75,7 @@ struct Scene::Impl ~Impl() { for (auto paint : paints) { - delete(paint); + if (paint->pImpl->unref() == 0) delete(paint); } } @@ -85,11 +85,11 @@ struct Scene::Impl paint->pImpl->dispose(renderer); } - auto ret = renderer.dispose(rd); + renderer.dispose(rd); this->renderer = nullptr; this->rd = nullptr; - return ret; + return true; } bool needComposition(uint8_t opacity) @@ -181,7 +181,7 @@ struct Scene::Impl return {x1, y1, (x2 - x1), (y2 - y1)}; } - bool bounds(float* px, float* py, float* pw, float* ph) + bool bounds(float* px, float* py, float* pw, float* ph, bool stroking) { if (paints.empty()) return false; @@ -196,7 +196,7 @@ struct Scene::Impl auto w = 0.0f; auto h = 0.0f; - if (paint->bounds(&x, &y, &w, &h, true) != tvg::Result::Success) continue; + if (!P(paint)->bounds(&x, &y, &w, &h, true, stroking)) continue; //Merge regions if (x < x1) x1 = x; @@ -231,7 +231,7 @@ struct Scene::Impl auto dispose = renderer ? true : false; for (auto paint : paints) { - if (dispose) paint->pImpl->dispose(*renderer); + if (dispose) free &= paint->pImpl->dispose(*renderer); if (free) delete(paint); } paints.clear(); diff --git a/thirdparty/thorvg/src/lib/tvgShape.cpp b/thirdparty/thorvg/src/lib/tvgShape.cpp index f32feb2133..336ac71d81 100644 --- a/thirdparty/thorvg/src/lib/tvgShape.cpp +++ b/thirdparty/thorvg/src/lib/tvgShape.cpp @@ -150,13 +150,13 @@ Result Shape::appendArc(float cx, float cy, float radius, float startAngle, floa //just circle if (sweep >= 360.0f || sweep <= -360.0f) return appendCircle(cx, cy, radius, radius); - startAngle = (startAngle * M_PI) / 180.0f; - sweep = sweep * M_PI / 180.0f; + startAngle = (startAngle * MATH_PI) / 180.0f; + sweep = sweep * MATH_PI / 180.0f; - auto nCurves = ceil(fabsf(sweep / float(M_PI_2))); + auto nCurves = ceil(fabsf(sweep / MATH_PI2)); auto sweepSign = (sweep < 0 ? -1 : 1); - auto fract = fmodf(sweep, float(M_PI_2)); - fract = (mathZero(fract)) ? float(M_PI_2) * sweepSign : fract; + auto fract = fmodf(sweep, MATH_PI2); + fract = (mathZero(fract)) ? MATH_PI2 * sweepSign : fract; //Start from here Point start = {radius * cosf(startAngle), radius * sinf(startAngle)}; @@ -342,22 +342,13 @@ const Fill* Shape::strokeFill() const noexcept Result Shape::stroke(const float* dashPattern, uint32_t cnt) noexcept { - if ((cnt == 1) || (!dashPattern && cnt > 0) || (dashPattern && cnt == 0)) { - return Result::InvalidArguments; - } - - for (uint32_t i = 0; i < cnt; i++) - if (dashPattern[i] < FLT_EPSILON) return Result::InvalidArguments; - - if (!pImpl->strokeDash(dashPattern, cnt)) return Result::FailedAllocation; - - return Result::Success; + return pImpl->strokeDash(dashPattern, cnt, 0); } uint32_t Shape::strokeDash(const float** dashPattern) const noexcept { - return pImpl->rs.strokeDash(dashPattern); + return pImpl->rs.strokeDash(dashPattern, nullptr); } diff --git a/thirdparty/thorvg/src/lib/tvgShapeImpl.h b/thirdparty/thorvg/src/lib/tvgShapeImpl.h index b05f85bf1c..bb266866d0 100644 --- a/thirdparty/thorvg/src/lib/tvgShapeImpl.h +++ b/thirdparty/thorvg/src/lib/tvgShapeImpl.h @@ -46,9 +46,9 @@ struct Shape::Impl bool dispose(RenderMethod& renderer) { - auto ret = renderer.dispose(rd); + renderer.dispose(rd); rd = nullptr; - return ret; + return true; } bool render(RenderMethod& renderer) @@ -70,7 +70,7 @@ struct Shape::Impl if (opacity == 0) return false; //Shape composition is only necessary when stroking & fill are valid. - if (!rs.stroke || rs.stroke->width < FLT_EPSILON || rs.stroke->color[3] == 0) return false; + if (!rs.stroke || rs.stroke->width < FLT_EPSILON || (!rs.stroke->fill && rs.stroke->color[3] == 0)) return false; if (!rs.fill && rs.color[3] == 0) return false; //translucent fill & stroke @@ -104,7 +104,7 @@ struct Shape::Impl return renderer.region(rd); } - bool bounds(float* x, float* y, float* w, float* h) + bool bounds(float* x, float* y, float* w, float* h, bool stroking) { //Path bounding size if (rs.path.pts.count > 0 ) { @@ -126,7 +126,7 @@ struct Shape::Impl } //Stroke feathering - if (rs.stroke) { + if (stroking && rs.stroke) { if (x) *x -= rs.stroke->width * 0.5f; if (y) *y -= rs.stroke->width * 0.5f; if (w) *w += rs.stroke->width; @@ -199,8 +199,6 @@ struct Shape::Impl bool strokeWidth(float width) { - //TODO: Size Exception? - if (!rs.stroke) rs.stroke = new RenderStroke(); rs.stroke->width = width; flag |= RenderUpdateFlag::Stroke; @@ -208,6 +206,22 @@ struct Shape::Impl return true; } + bool strokeTrim(float begin, float end) + { + if (!rs.stroke) { + if (begin == 0.0f && end == 1.0f) return true; + rs.stroke = new RenderStroke(); + } + + if (mathEqual(rs.stroke->trim.begin, begin) && mathEqual(rs.stroke->trim.end, end)) return true; + + rs.stroke->trim.begin = begin; + rs.stroke->trim.end = end; + flag |= RenderUpdateFlag::Stroke; + + return true; + } + bool strokeCap(StrokeCap cap) { if (!rs.stroke) rs.stroke = new RenderStroke(); @@ -269,8 +283,16 @@ struct Shape::Impl return Result::Success; } - bool strokeDash(const float* pattern, uint32_t cnt) + Result strokeDash(const float* pattern, uint32_t cnt, float offset) { + if ((cnt == 1) || (!pattern && cnt > 0) || (pattern && cnt == 0)) { + return Result::InvalidArguments; + } + + for (uint32_t i = 0; i < cnt; i++) { + if (pattern[i] < FLT_EPSILON) return Result::InvalidArguments; + } + //Reset dash if (!pattern && cnt == 0) { free(rs.stroke->dashPattern); @@ -283,16 +305,17 @@ struct Shape::Impl } if (!rs.stroke->dashPattern) { rs.stroke->dashPattern = static_cast<float*>(malloc(sizeof(float) * cnt)); - if (!rs.stroke->dashPattern) return false; + if (!rs.stroke->dashPattern) return Result::FailedAllocation; } for (uint32_t i = 0; i < cnt; ++i) { rs.stroke->dashPattern[i] = pattern[i]; } } rs.stroke->dashCnt = cnt; + rs.stroke->dashOffset = offset; flag |= RenderUpdateFlag::Stroke; - return true; + return Result::Success; } bool strokeFirst() @@ -336,24 +359,17 @@ struct Shape::Impl //Stroke if (rs.stroke) { dup->rs.stroke = new RenderStroke(); - dup->rs.stroke->width = rs.stroke->width; - dup->rs.stroke->dashCnt = rs.stroke->dashCnt; - dup->rs.stroke->cap = rs.stroke->cap; - dup->rs.stroke->join = rs.stroke->join; - dup->rs.stroke->strokeFirst = rs.stroke->strokeFirst; + *dup->rs.stroke = *rs.stroke; memcpy(dup->rs.stroke->color, rs.stroke->color, sizeof(rs.stroke->color)); - if (rs.stroke->dashCnt > 0) { dup->rs.stroke->dashPattern = static_cast<float*>(malloc(sizeof(float) * rs.stroke->dashCnt)); memcpy(dup->rs.stroke->dashPattern, rs.stroke->dashPattern, sizeof(float) * rs.stroke->dashCnt); } - - dup->flag |= RenderUpdateFlag::Stroke; - if (rs.stroke->fill) { dup->rs.stroke->fill = rs.stroke->fill->duplicate(); dup->flag |= RenderUpdateFlag::GradientStroke; } + dup->flag |= RenderUpdateFlag::Stroke; } //Fill diff --git a/thirdparty/thorvg/src/lib/tvgTaskScheduler.cpp b/thirdparty/thorvg/src/lib/tvgTaskScheduler.cpp index 019468083d..e3887c60fc 100644 --- a/thirdparty/thorvg/src/lib/tvgTaskScheduler.cpp +++ b/thirdparty/thorvg/src/lib/tvgTaskScheduler.cpp @@ -100,6 +100,9 @@ struct TaskQueue { }; +static thread_local bool _async = true; //toggle async tasking for each thread on/off + + struct TaskSchedulerImpl { uint32_t threadCnt; @@ -109,6 +112,8 @@ struct TaskSchedulerImpl TaskSchedulerImpl(unsigned threadCnt) : threadCnt(threadCnt), taskQueues(threadCnt) { + threads.reserve(threadCnt); + for (unsigned i = 0; i < threadCnt; ++i) { threads.emplace_back([&, i] { run(i); }); } @@ -142,7 +147,7 @@ struct TaskSchedulerImpl void request(Task* task) { //Async - if (threadCnt > 0) { + if (threadCnt > 0 && _async) { task->prepare(); auto i = idx++; for (unsigned n = 0; n < threadCnt; ++n) { @@ -190,3 +195,9 @@ unsigned TaskScheduler::threads() if (inst) return inst->threadCnt; return 0; } + + +void TaskScheduler::async(bool on) +{ + _async = on; +} diff --git a/thirdparty/thorvg/src/lib/tvgTaskScheduler.h b/thirdparty/thorvg/src/lib/tvgTaskScheduler.h index 7ada963b77..2dad80f5d0 100644 --- a/thirdparty/thorvg/src/lib/tvgTaskScheduler.h +++ b/thirdparty/thorvg/src/lib/tvgTaskScheduler.h @@ -38,6 +38,7 @@ struct TaskScheduler static void init(unsigned threads); static void term(); static void request(Task* task); + static void async(bool on); }; struct Task diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgCssStyle.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgCssStyle.cpp index 6fdb641204..c3c477a263 100644 --- a/thirdparty/thorvg/src/loaders/svg/tvgSvgCssStyle.cpp +++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgCssStyle.cpp @@ -123,7 +123,7 @@ static void _copyStyle(SvgStyleProperty* to, const SvgStyleProperty* from) to->stroke.dash.array.clear(); to->stroke.dash.array.reserve(from->stroke.dash.array.count); for (uint32_t i = 0; i < from->stroke.dash.array.count; ++i) { - to->stroke.dash.array.push(from->stroke.dash.array.data[i]); + to->stroke.dash.array.push(from->stroke.dash.array[i]); } to->stroke.flags = (to->stroke.flags | SvgStrokeFlags::Dash); to->flags = (to->flags | SvgStyleFlags::StrokeDashArray); @@ -236,7 +236,7 @@ void cssUpdateStyle(SvgNode* doc, SvgNode* style) void cssApplyStyleToPostponeds(Array<SvgNodeIdPair>& postponeds, SvgNode* style) { for (uint32_t i = 0; i < postponeds.count; ++i) { - auto nodeIdPair = postponeds.data[i]; + auto nodeIdPair = postponeds[i]; //css styling: tag.name has higher priority than .name if (auto cssNode = cssFindStyleNode(style, nodeIdPair.id, nodeIdPair.node->type)) { diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp index 998c98e83f..9825fd8cc4 100644 --- a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp +++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp @@ -59,7 +59,7 @@ #include "tvgXmlParser.h" #include "tvgSvgLoader.h" #include "tvgSvgSceneBuilder.h" -#include "tvgSvgUtil.h" +#include "tvgStr.h" #include "tvgSvgCssStyle.h" #include "tvgMath.h" @@ -110,7 +110,7 @@ static bool _parseNumber(const char** content, float* number) { char* end = nullptr; - *number = svgUtilStrtof(*content, &end); + *number = strToFloat(*content, &end); //If the start of string is not number if ((*content) == end) return false; //Skip comma if any @@ -166,7 +166,7 @@ static void _parseAspectRatio(const char** content, AspectRatioAlign* align, Asp */ static float _toFloat(const SvgParser* svgParse, const char* str, SvgParserLengthType type) { - float parsedValue = svgUtilStrtof(str, nullptr); + float parsedValue = strToFloat(str, nullptr); if (strstr(str, "cm")) parsedValue *= PX_PER_CM; else if (strstr(str, "mm")) parsedValue *= PX_PER_MM; @@ -194,7 +194,7 @@ static float _gradientToFloat(const SvgParser* svgParse, const char* str, bool& { char* end = nullptr; - float parsedValue = svgUtilStrtof(str, &end); + float parsedValue = strToFloat(str, &end); isPercentage = false; if (strstr(str, "%")) { @@ -217,7 +217,7 @@ static float _toOffset(const char* str) char* end = nullptr; auto strEnd = str + strlen(str); - float parsedValue = svgUtilStrtof(str, &end); + float parsedValue = strToFloat(str, &end); end = _skipSpace(end, nullptr); auto ptr = strstr(str, "%"); @@ -234,7 +234,7 @@ static float _toOffset(const char* str) static int _toOpacity(const char* str) { char* end = nullptr; - float opacity = svgUtilStrtof(str, &end); + float opacity = strToFloat(str, &end); if (end) { if (end[0] == '%' && end[1] == '\0') return lrint(opacity * 2.55f); @@ -362,7 +362,7 @@ static void _parseDashArray(SvgLoaderData* loader, const char *str, SvgDash* das while (*str) { str = _skipComma(str); - float parsedValue = svgUtilStrtof(str, &end); + float parsedValue = strToFloat(str, &end); if (str == end) break; if (parsedValue <= 0.0f) break; if (*end == '%') { @@ -375,7 +375,7 @@ static void _parseDashArray(SvgLoaderData* loader, const char *str, SvgDash* das str = end; } //If dash array size is 1, it means that dash and gap size are the same. - if ((*dash).array.count == 1) (*dash).array.push((*dash).array.data[0]); + if ((*dash).array.count == 1) (*dash).array.push((*dash).array[0]); } @@ -393,7 +393,7 @@ static char* _idFromUrl(const char* url) int i = 0; while (url[i] > ' ' && url[i] != ')' && url[i] != '\'') ++i; - return svgUtilStrndup(url, i); + return strDuplicate(url, i); } @@ -401,7 +401,7 @@ static unsigned char _parseColor(const char* value, char** end) { float r; - r = svgUtilStrtof(value, end); + r = strToFloat(value, end); *end = _skipSpace(*end, nullptr); if (**end == '%') { r = 255 * r / 100; @@ -643,7 +643,7 @@ static char* _parseNumbersArray(char* str, float* points, int* ptCount, int len) str = _skipSpace(str, nullptr); while ((count < len) && (isdigit(*str) || *str == '-' || *str == '+' || *str == '.')) { - points[count++] = svgUtilStrtof(str, &end); + points[count++] = strToFloat(str, &end); str = end; str = _skipSpace(str, nullptr); if (*str == ',') ++str; @@ -893,7 +893,7 @@ static bool _attrParseSvgNode(void* data, const char* key, const char* value) } else if (!strcmp(key, "style")) { return simpleXmlParseW3CAttribute(value, strlen(value), _parseStyleAttr, loader); #ifdef THORVG_LOG_ENABLED - } else if ((!strcmp(key, "x") || !strcmp(key, "y")) && fabsf(svgUtilStrtof(value, nullptr)) > FLT_EPSILON) { + } else if ((!strcmp(key, "x") || !strcmp(key, "y")) && fabsf(strToFloat(value, nullptr)) > FLT_EPSILON) { TVGLOG("SVG", "Unsupported attributes used [Elements type: Svg][Attribute: %s][Value: %s]", key, value); #endif } else { @@ -956,6 +956,12 @@ static void _handleStrokeDashArrayAttr(SvgLoaderData* loader, SvgNode* node, con _parseDashArray(loader, value, &node->style->stroke.dash); } +static void _handleStrokeDashOffsetAttr(SvgLoaderData* loader, SvgNode* node, const char* value) +{ + node->style->stroke.flags = (node->style->stroke.flags | SvgStrokeFlags::DashOffset); + node->style->stroke.dash.offset = _toFloat(loader->svgParse, value, SvgParserLengthType::Horizontal); +} + static void _handleStrokeWidthAttr(SvgLoaderData* loader, SvgNode* node, const char* value) { node->style->stroke.flags = (node->style->stroke.flags | SvgStrokeFlags::Width); @@ -979,7 +985,7 @@ static void _handleStrokeLineJoinAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* static void _handleStrokeMiterlimitAttr(SvgLoaderData* loader, SvgNode* node, const char* value) { char* end = nullptr; - const float miterlimit = svgUtilStrtof(value, &end); + const float miterlimit = strToFloat(value, &end); // https://www.w3.org/TR/SVG2/painting.html#LineJoin // - A negative value for stroke-miterlimit must be treated as an illegal value. @@ -1112,6 +1118,7 @@ static constexpr struct STYLE_DEF(stroke-linecap, StrokeLineCap, SvgStyleFlags::StrokeLineCap), STYLE_DEF(stroke-opacity, StrokeOpacity, SvgStyleFlags::StrokeOpacity), STYLE_DEF(stroke-dasharray, StrokeDashArray, SvgStyleFlags::StrokeDashArray), + STYLE_DEF(stroke-dashoffset, StrokeDashOffset, SvgStyleFlags::StrokeDashOffset), STYLE_DEF(transform, Transform, SvgStyleFlags::Transform), STYLE_DEF(clip-path, ClipPath, SvgStyleFlags::ClipPath), STYLE_DEF(mask, Mask, SvgStyleFlags::Mask), @@ -1141,7 +1148,7 @@ static bool _parseStyleAttr(void* data, const char* key, const char* value, bool while (size > 0 && isspace(value[size - 1])) { size--; } - value = svgUtilStrndup(value, size); + value = strDuplicate(value, size); importance = true; } if (style) { @@ -2097,6 +2104,12 @@ static void _handleRadialFyAttr(SvgLoaderData* loader, SvgRadialGradient* radial } +static void _handleRadialFrAttr(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value) +{ + radial->fr = _gradientToFloat(loader->svgParse, value, radial->isFrPercentage); +} + + static void _handleRadialRAttr(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value) { radial->r = _gradientToFloat(loader->svgParse, value, radial->isRPercentage); @@ -2127,6 +2140,13 @@ static void _recalcRadialFyAttr(SvgLoaderData* loader, SvgRadialGradient* radial } +static void _recalcRadialFrAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace) +{ + // scaling factor based on the Units paragraph from : https://www.w3.org/TR/2015/WD-SVG2-20150915/coords.html + if (userSpace && !radial->isFrPercentage) radial->fr = radial->fr / (sqrtf(powf(loader->svgParse->global.h, 2) + powf(loader->svgParse->global.w, 2)) / sqrtf(2.0)); +} + + static void _recalcRadialRAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace) { // scaling factor based on the Units paragraph from : https://www.w3.org/TR/2015/WD-SVG2-20150915/coords.html @@ -2170,6 +2190,15 @@ static void _recalcInheritedRadialFyAttr(SvgLoaderData* loader, SvgRadialGradien } +static void _recalcInheritedRadialFrAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace) +{ + if (!radial->isFrPercentage) { + if (userSpace) radial->fr /= sqrtf(powf(loader->svgParse->global.h, 2) + powf(loader->svgParse->global.w, 2)) / sqrtf(2.0); + else radial->fr *= sqrtf(powf(loader->svgParse->global.h, 2) + powf(loader->svgParse->global.w, 2)) / sqrtf(2.0); + } +} + + static void _recalcInheritedRadialRAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace) { if (!radial->isRPercentage) { @@ -2211,6 +2240,14 @@ static void _inheritRadialFyAttr(SvgStyleGradient* to, SvgStyleGradient* from) } +static void _inheritRadialFrAttr(SvgStyleGradient* to, SvgStyleGradient* from) +{ + to->radial->fr = from->radial->fr; + to->radial->isFrPercentage = from->radial->isFrPercentage; + to->flags = (to->flags | SvgGradientFlags::Fr); +} + + static void _inheritRadialRAttr(SvgStyleGradient* to, SvgStyleGradient* from) { to->radial->r = from->radial->r; @@ -2244,7 +2281,8 @@ static constexpr struct RADIAL_DEF(cy, Cy, SvgGradientFlags::Cy), RADIAL_DEF(fx, Fx, SvgGradientFlags::Fx), RADIAL_DEF(fy, Fy, SvgGradientFlags::Fy), - RADIAL_DEF(r, R, SvgGradientFlags::R) + RADIAL_DEF(r, R, SvgGradientFlags::R), + RADIAL_DEF(fr, Fr, SvgGradientFlags::Fr) }; @@ -2312,6 +2350,7 @@ static SvgStyleGradient* _createRadialGradient(SvgLoaderData* loader, const char grad->radial->isFxPercentage = true; grad->radial->isFyPercentage = true; grad->radial->isRPercentage = true; + grad->radial->isFrPercentage = true; loader->svgParse->gradient.parsedFx = false; loader->svgParse->gradient.parsedFy = false; @@ -2619,7 +2658,7 @@ static GradientFactoryMethod _findGradientFactory(const char* name) static void _cloneGradStops(Array<Fill::ColorStop>& dst, const Array<Fill::ColorStop>& src) { for (uint32_t i = 0; i < src.count; ++i) { - dst.push(src.data[i]); + dst.push(src[i]); } } @@ -2773,10 +2812,13 @@ static void _styleInherit(SvgStyleProperty* child, const SvgStyleProperty* paren child->stroke.dash.array.clear(); child->stroke.dash.array.reserve(parent->stroke.dash.array.count); for (uint32_t i = 0; i < parent->stroke.dash.array.count; ++i) { - child->stroke.dash.array.push(parent->stroke.dash.array.data[i]); + child->stroke.dash.array.push(parent->stroke.dash.array[i]); } } } + if (!(child->stroke.flags & SvgStrokeFlags::DashOffset)) { + child->stroke.dash.offset = parent->stroke.dash.offset; + } if (!(child->stroke.flags & SvgStrokeFlags::Cap)) { child->stroke.cap = parent->stroke.cap; } @@ -2839,17 +2881,19 @@ static void _styleCopy(SvgStyleProperty* to, const SvgStyleProperty* from) to->stroke.dash.array.clear(); to->stroke.dash.array.reserve(from->stroke.dash.array.count); for (uint32_t i = 0; i < from->stroke.dash.array.count; ++i) { - to->stroke.dash.array.push(from->stroke.dash.array.data[i]); + to->stroke.dash.array.push(from->stroke.dash.array[i]); } } } + if (from->stroke.flags & SvgStrokeFlags::DashOffset) { + to->stroke.dash.offset = from->stroke.dash.offset; + } if (from->stroke.flags & SvgStrokeFlags::Cap) { to->stroke.cap = from->stroke.cap; } if (from->stroke.flags & SvgStrokeFlags::Join) { to->stroke.join = from->stroke.join; } - if (from->stroke.flags & SvgStrokeFlags::Miterlimit) { to->stroke.miterlimit = from->stroke.miterlimit; } @@ -2983,7 +3027,7 @@ static void _cloneNode(SvgNode* from, SvgNode* parent, int depth) static void _clonePostponedNodes(Array<SvgNodeIdPair>* cloneNodes, SvgNode* doc) { for (uint32_t i = 0; i < cloneNodes->count; ++i) { - auto nodeIdPair = cloneNodes->data[i]; + auto nodeIdPair = (*cloneNodes)[i]; auto defs = _getDefsNode(nodeIdPair.node); auto nodeFrom = _findNodeById(defs, nodeIdPair.id); if (!nodeFrom) nodeFrom = _findNodeById(doc, nodeIdPair.id); @@ -3064,7 +3108,7 @@ static void _svgLoaderParserXmlOpen(SvgLoaderData* loader, const char* content, loader->doc = node; } else { if (!strcmp(tagName, "svg")) return; //Already loaded <svg>(SvgNodeType::Doc) tag - if (loader->stack.count > 0) parent = loader->stack.data[loader->stack.count - 1]; + if (loader->stack.count > 0) parent = loader->stack.last(); else parent = loader->doc; if (!strcmp(tagName, "style")) { // TODO: For now only the first style node is saved. After the css id selector @@ -3085,7 +3129,7 @@ static void _svgLoaderParserXmlOpen(SvgLoaderData* loader, const char* content, loader->stack.push(node); } } else if ((method = _findGraphicsFactory(tagName))) { - if (loader->stack.count > 0) parent = loader->stack.data[loader->stack.count - 1]; + if (loader->stack.count > 0) parent = loader->stack.last(); else parent = loader->doc; node = method(loader, parent, attrs, attrsLength, simpleXmlParseAttributes); } else if ((gradientMethod = _findGradientFactory(tagName))) { diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoaderCommon.h b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoaderCommon.h index 961438ff42..1809c7749c 100644 --- a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoaderCommon.h +++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoaderCommon.h @@ -100,7 +100,8 @@ enum class SvgStrokeFlags Cap = 0x20, Join = 0x40, Dash = 0x80, - Miterlimit = 0x100 + Miterlimit = 0x100, + DashOffset = 0x200 }; constexpr bool operator &(SvgStrokeFlags a, SvgStrokeFlags b) @@ -139,7 +140,8 @@ enum class SvgStyleFlags MaskType = 0x4000, Display = 0x8000, PaintOrder = 0x10000, - StrokeMiterlimit = 0x20000 + StrokeMiterlimit = 0x20000, + StrokeDashOffset = 0x40000, }; constexpr bool operator &(SvgStyleFlags a, SvgStyleFlags b) @@ -182,7 +184,8 @@ enum class SvgGradientFlags Cy = 0x80, R = 0x100, Fx = 0x200, - Fy = 0x400 + Fy = 0x400, + Fr = 0x800 }; constexpr bool operator &(SvgGradientFlags a, SvgGradientFlags b) @@ -390,11 +393,13 @@ struct SvgRadialGradient float fx; float fy; float r; + float fr; bool isCxPercentage; bool isCyPercentage; bool isFxPercentage; bool isFyPercentage; bool isRPercentage; + bool isFrPercentage; }; struct SvgComposite @@ -423,6 +428,7 @@ struct SvgPaint struct SvgDash { Array<float> array; + float offset; }; struct SvgStyleGradient @@ -469,7 +475,6 @@ struct SvgStyleStroke StrokeJoin join; float miterlimit; SvgDash dash; - int dashCount; }; struct SvgStyleProperty @@ -561,18 +566,4 @@ struct Box float x, y, w, h; }; -/* - * https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/strtof-strtod-l-wcstod-wcstod-l?view=vs-2017 - * - * src should be one of the following form : - * - * [whitespace] [sign] {digits [radix digits] | radix digits} [{e | E} [sign] digits] - * [whitespace] [sign] {INF | INFINITY} - * [whitespace] [sign] NAN [sequence] - * - * No hexadecimal form supported - * no sequence supported after NAN - */ -float customStrtof(const char *nptr, char **endptr); - #endif diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp index e044931b51..79a9c0771d 100644 --- a/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp +++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp @@ -55,7 +55,7 @@ #include <ctype.h> #include "tvgSvgLoaderCommon.h" #include "tvgSvgPath.h" -#include "tvgSvgUtil.h" +#include "tvgStr.h" /************************************************************************/ /* Internal Class Implementation */ @@ -74,7 +74,7 @@ static char* _skipComma(const char* content) static bool _parseNumber(char** content, float* number) { char* end = NULL; - *number = svgUtilStrtof(*content, &end); + *number = strToFloat(*content, &end); //If the start of string is not number if ((*content) == end) return false; //Skip comma if any @@ -382,7 +382,7 @@ static bool _processCommand(Array<PathCommand>* cmds, Array<Point>* pts, char cm case 's': case 'S': { Point p[3], ctrl; - if ((cmds->count > 1) && (cmds->data[cmds->count - 1] == PathCommand::CubicTo) && + if ((cmds->count > 1) && (cmds->last() == PathCommand::CubicTo) && !(*isQuadratic)) { ctrl.x = 2 * cur->x - curCtl->x; ctrl.y = 2 * cur->y - curCtl->y; @@ -423,7 +423,7 @@ static bool _processCommand(Array<PathCommand>* cmds, Array<Point>* pts, char cm case 't': case 'T': { Point p[3], ctrl; - if ((cmds->count > 1) && (cmds->data[cmds->count - 1] == PathCommand::CubicTo) && + if ((cmds->count > 1) && (cmds->last() == PathCommand::CubicTo) && *isQuadratic) { ctrl.x = 2 * cur->x - curCtl->x; ctrl.y = 2 * cur->y - curCtl->y; diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp index e5beef8093..71712442e8 100644 --- a/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp +++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp @@ -52,6 +52,11 @@ #include "tvgMath.h" /* to include math.h before cstring */ #include <cstring> #include <string> +#include "tvgShapeImpl.h" +#include "tvgCompressor.h" +#include "tvgPaint.h" +#include "tvgFill.h" +#include "tvgStr.h" #include "tvgSvgLoaderCommon.h" #include "tvgSvgSceneBuilder.h" #include "tvgSvgPath.h" @@ -62,6 +67,7 @@ /************************************************************************/ static bool _appendShape(SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath); +static bool _appendClipShape(SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath, const Matrix* transform); static unique_ptr<Scene> _sceneBuildHelper(const SvgNode* node, const Box& vBox, const string& svgPath, bool mask, int depth, bool* isMaskWhite = nullptr); @@ -138,7 +144,7 @@ static unique_ptr<LinearGradient> _applyLinearGradientProperty(SvgStyleGradient* if (!stops) return fillGrad; auto prevOffset = 0.0f; for (uint32_t i = 0; i < g->stops.count; ++i) { - auto colorStop = &g->stops.data[i]; + auto colorStop = &g->stops[i]; //Use premultiplied color stops[i].r = colorStop->r; stops[i].g = colorStop->g; @@ -175,6 +181,7 @@ static unique_ptr<RadialGradient> _applyRadialGradientProperty(SvgStyleGradient* g->radial->r = g->radial->r * sqrtf(powf(vBox.w, 2.0f) + powf(vBox.h, 2.0f)) / sqrtf(2.0f); g->radial->fx = g->radial->fx * vBox.w; g->radial->fy = g->radial->fy * vBox.h; + g->radial->fr = g->radial->fr * sqrtf(powf(vBox.w, 2.0f) + powf(vBox.h, 2.0f)) / sqrtf(2.0f); } else { Matrix m = {vBox.w, 0, vBox.x, 0, vBox.h, vBox.y, 0, 0, 1}; if (isTransform) _transformMultiply(&m, &finalTransform); @@ -186,11 +193,7 @@ static unique_ptr<RadialGradient> _applyRadialGradientProperty(SvgStyleGradient* if (isTransform) fillGrad->transform(finalTransform); - //TODO: Tvg is not support to focal - //if (g->radial->fx != 0 && g->radial->fy != 0) { - // fillGrad->radial(g->radial->fx, g->radial->fy, g->radial->r); - //} - fillGrad->radial(g->radial->cx, g->radial->cy, g->radial->r); + P(fillGrad)->radial(g->radial->cx, g->radial->cy, g->radial->r, g->radial->fx, g->radial->fy, g->radial->fr); fillGrad->spread(g->spread); //Update the stops @@ -200,7 +203,7 @@ static unique_ptr<RadialGradient> _applyRadialGradientProperty(SvgStyleGradient* if (!stops) return fillGrad; auto prevOffset = 0.0f; for (uint32_t i = 0; i < g->stops.count; ++i) { - auto colorStop = &g->stops.data[i]; + auto colorStop = &g->stops[i]; //Use premultiplied color stops[i].r = colorStop->r; stops[i].g = colorStop->g; @@ -219,20 +222,50 @@ static unique_ptr<RadialGradient> _applyRadialGradientProperty(SvgStyleGradient* } -static bool _appendChildShape(SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath) +//The SVG standard allows only for 'use' nodes that point directly to a basic shape. +static bool _appendClipUseNode(SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath) { - auto valid = false; + if (node->child.count != 1) return false; + auto child = *(node->child.data); - if (_appendShape(node, shape, vBox, svgPath)) valid = true; + Matrix finalTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1}; + if (node->transform) finalTransform = *node->transform; + if (node->node.use.x != 0.0f || node->node.use.y != 0.0f) { + Matrix m = {1, 0, node->node.use.x, 0, 1, node->node.use.y, 0, 0, 1}; + finalTransform = mathMultiply(&finalTransform, &m); + } + if (child->transform) finalTransform = mathMultiply(child->transform, &finalTransform); - if (node->child.count > 0) { - auto child = node->child.data; - for (uint32_t i = 0; i < node->child.count; ++i, ++child) { - if (_appendChildShape(*child, shape, vBox, svgPath)) valid = true; - } + return _appendClipShape(child, shape, vBox, svgPath, mathIdentity((const Matrix*)(&finalTransform)) ? nullptr : &finalTransform); +} + + +static bool _appendClipChild(SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath, bool clip) +{ + if (node->type == SvgNodeType::Use) { + return _appendClipUseNode(node, shape, vBox, svgPath); } + return _appendClipShape(node, shape, vBox, svgPath, nullptr); +} - return valid; + +static Matrix _compositionTransform(Paint* paint, const SvgNode* node, const SvgNode* compNode, SvgNodeType type) +{ + Matrix m = {1, 0, 0, 0, 1, 0, 0, 0, 1}; + //The initial mask transformation ignored according to the SVG standard. + if (node->transform && type != SvgNodeType::Mask) { + m = *node->transform; + } + if (compNode->transform) { + m = mathMultiply(&m, compNode->transform); + } + if (!compNode->node.clip.userSpace) { + float x, y, w, h; + P(paint)->bounds(&x, &y, &w, &h, false, false); + Matrix mBBox = {w, 0, x, 0, h, y, 0, 0, 1}; + m = mathMultiply(&m, &mBBox); + } + return m; } @@ -251,19 +284,18 @@ static void _applyComposition(Paint* paint, const SvgNode* node, const Box& vBox auto comp = Shape::gen(); auto child = compNode->child.data; - auto valid = false; //Composite only when valid shapes are existed + auto valid = false; //Composite only when valid shapes exist for (uint32_t i = 0; i < compNode->child.count; ++i, ++child) { - if (_appendChildShape(*child, comp.get(), vBox, svgPath)) valid = true; + if (_appendClipChild(*child, comp.get(), vBox, svgPath, compNode->child.count > 1)) valid = true; } - if (node->transform) { - auto m = comp->transform(); - m = mathMultiply(node->transform, &m); - comp->transform(m); - } + if (valid) { + Matrix finalTransform = _compositionTransform(paint, node, compNode, SvgNodeType::ClipPath); + comp->transform(finalTransform); - if (valid) paint->composite(std::move(comp), CompositeMethod::ClipPath); + paint->composite(std::move(comp), CompositeMethod::ClipPath); + } node->style->clipPath.applying = false; } @@ -280,9 +312,9 @@ static void _applyComposition(Paint* paint, const SvgNode* node, const Box& vBox node->style->mask.applying = true; bool isMaskWhite = true; - auto comp = _sceneBuildHelper(compNode, vBox, svgPath, true, 0, &isMaskWhite); - if (comp) { - if (node->transform) comp->transform(*node->transform); + if (auto comp = _sceneBuildHelper(compNode, vBox, svgPath, true, 0, &isMaskWhite)) { + Matrix finalTransform = _compositionTransform(paint, node, compNode, SvgNodeType::Mask); + comp->transform(finalTransform); if (compNode->node.mask.type == SvgMaskType::Luminance && !isMaskWhite) { paint->composite(std::move(comp), CompositeMethod::LumaMask); @@ -297,11 +329,12 @@ static void _applyComposition(Paint* paint, const SvgNode* node, const Box& vBox } -static void _applyProperty(SvgNode* node, Shape* vg, const Box& vBox, const string& svgPath) +static void _applyProperty(SvgNode* node, Shape* vg, const Box& vBox, const string& svgPath, bool clip) { SvgStyleProperty* style = node->style; - if (node->transform) vg->transform(*node->transform); + //Clip transformation is applied directly to the path in the _appendClipShape function + if (node->transform && !clip) vg->transform(*node->transform); if (node->type == SvgNodeType::Doc || !node->display) return; //If fill property is nullptr then do nothing @@ -344,7 +377,7 @@ static void _applyProperty(SvgNode* node, Shape* vg, const Box& vBox, const stri vg->stroke(style->stroke.join); vg->strokeMiterlimit(style->stroke.miterlimit); if (style->stroke.dash.array.count > 0) { - vg->stroke(style->stroke.dash.array.data, style->stroke.dash.array.count); + P(vg)->strokeDash(style->stroke.dash.array.data, style->stroke.dash.array.count, style->stroke.dash.offset); } //If stroke property is nullptr then do nothing @@ -383,14 +416,13 @@ static unique_ptr<Shape> _shapeBuildHelper(SvgNode* node, const Box& vBox, const } -static bool _appendShape(SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath) +static bool _recognizeShape(SvgNode* node, Shape* shape) { - Array<PathCommand> cmds; - Array<Point> pts; - switch (node->type) { case SvgNodeType::Path: { if (node->node.path.path) { + Array<PathCommand> cmds; + Array<Point> pts; if (svgPathToTvgPath(node->node.path.path, cmds, pts)) { shape->appendPath(cmds.data, cmds.count, pts.data, pts.count); } @@ -437,8 +469,41 @@ static bool _appendShape(SvgNode* node, Shape* shape, const Box& vBox, const str return false; } } + return true; +} + - _applyProperty(node, shape, vBox, svgPath); +static bool _appendShape(SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath) +{ + if (!_recognizeShape(node, shape)) return false; + + _applyProperty(node, shape, vBox, svgPath, false); + return true; +} + + +static bool _appendClipShape(SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath, const Matrix* transform) +{ + //The 'transform' matrix has higher priority than the node->transform, since it already contains it + auto m = transform ? transform : (node->transform ? node->transform : nullptr); + + uint32_t currentPtsCnt = 0; + if (m) { + const Point *tmp = nullptr; + currentPtsCnt = shape->pathCoords(&tmp); + } + + if (!_recognizeShape(node, shape)) return false; + + if (m) { + const Point *pts = nullptr; + auto ptsCnt = shape->pathCoords(&pts); + + auto p = const_cast<Point*>(pts) + currentPtsCnt; + while (currentPtsCnt++ < ptsCnt) mathMultiply(p++, m); + } + + _applyProperty(node, shape, vBox, svgPath, true); return true; } @@ -514,12 +579,15 @@ static bool _isValidImageMimeTypeAndEncoding(const char** href, const char** mim return false; } +#include "tvgTaskScheduler.h" static unique_ptr<Picture> _imageBuildHelper(SvgNode* node, const Box& vBox, const string& svgPath) { if (!node->node.image.href) return nullptr; auto picture = Picture::gen(); + TaskScheduler::async(false); //force to load a picture on the same thread + const char* href = node->node.image.href; if (!strncmp(href, "data:", sizeof("data:") - 1)) { href += sizeof("data:") - 1; @@ -527,11 +595,22 @@ static unique_ptr<Picture> _imageBuildHelper(SvgNode* node, const Box& vBox, con imageMimeTypeEncoding encoding; if (!_isValidImageMimeTypeAndEncoding(&href, &mimetype, &encoding)) return nullptr; //not allowed mime type or encoding if (encoding == imageMimeTypeEncoding::base64) { - string decoded = svgUtilBase64Decode(href); - if (picture->load(decoded.c_str(), decoded.size(), mimetype, true) != Result::Success) return nullptr; + char* decoded = nullptr; + auto size = b64Decode(href, strlen(href), &decoded); + //OPTIMIZE: Skip data copy. + if (picture->load(decoded, size, mimetype, true) != Result::Success) { + free(decoded); + TaskScheduler::async(true); + return nullptr; + } + free(decoded); } else { string decoded = svgUtilURLDecode(href); - if (picture->load(decoded.c_str(), decoded.size(), mimetype, true) != Result::Success) return nullptr; + //OPTIMIZE: Skip data copy. + if (picture->load(decoded.c_str(), decoded.size(), mimetype, true) != Result::Success) { + TaskScheduler::async(true); + return nullptr; + } } } else { if (!strncmp(href, "file://", sizeof("file://") - 1)) href += sizeof("file://") - 1; @@ -540,6 +619,7 @@ static unique_ptr<Picture> _imageBuildHelper(SvgNode* node, const Box& vBox, con const char *dot = strrchr(href, '.'); if (dot && !strcmp(dot, ".svg")) { TVGLOG("SVG", "Embedded svg file is disabled."); + TaskScheduler::async(true); return nullptr; } string imagePath = href; @@ -547,9 +627,14 @@ static unique_ptr<Picture> _imageBuildHelper(SvgNode* node, const Box& vBox, con auto last = svgPath.find_last_of("/"); imagePath = svgPath.substr(0, (last == string::npos ? 0 : last + 1)) + imagePath; } - if (picture->load(imagePath) != Result::Success) return nullptr; + if (picture->load(imagePath) != Result::Success) { + TaskScheduler::async(true); + return nullptr; + } } + TaskScheduler::async(true); + float w, h; Matrix m = {1, 0, 0, 0, 1, 0, 0, 0, 1}; if (picture->size(&w, &h) == Result::Success && w > 0 && h > 0) { diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.cpp index 2aaeb2b25d..763a357f99 100644 --- a/thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.cpp +++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.cpp @@ -21,19 +21,12 @@ */ #include <cstring> -#include <math.h> -#include <memory.h> #include "tvgSvgUtil.h" /************************************************************************/ /* Internal Class Implementation */ /************************************************************************/ -static inline bool _floatExact(float a, float b) -{ - return memcmp(&a, &b, sizeof(float)) == 0; -} - static uint8_t _hexCharToDec(const char c) { if (c >= 'a') return c - 'a' + 10; @@ -41,181 +34,11 @@ static uint8_t _hexCharToDec(const char c) else return c - '0'; } -static uint8_t _base64Value(const char chr) -{ - if (chr >= 'A' && chr <= 'Z') return chr - 'A'; - else if (chr >= 'a' && chr <= 'z') return chr - 'a' + ('Z' - 'A') + 1; - else if (chr >= '0' && chr <= '9') return chr - '0' + ('Z' - 'A') + ('z' - 'a') + 2; - else if (chr == '+' || chr == '-') return 62; - else return 63; -} - /************************************************************************/ /* External Class Implementation */ /************************************************************************/ - -/* - * https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/strtof-strtof-l-wcstof-wcstof-l?view=msvc-160 - * - * src should be one of the following form : - * - * [whitespace] [sign] {digits [radix digits] | radix digits} [{e | E} [sign] digits] - * [whitespace] [sign] {INF | INFINITY} - * [whitespace] [sign] NAN [sequence] - * - * No hexadecimal form supported - * no sequence supported after NAN - */ -float svgUtilStrtof(const char *nPtr, char **endPtr) -{ - if (endPtr) *endPtr = (char*)(nPtr); - if (!nPtr) return 0.0f; - - auto a = nPtr; - auto iter = nPtr; - auto val = 0.0f; - unsigned long long integerPart = 0; - int minus = 1; - - //ignore leading whitespaces - while (isspace(*iter)) iter++; - - //signed or not - if (*iter == '-') { - minus = -1; - iter++; - } else if (*iter == '+') { - iter++; - } - - if (tolower(*iter) == 'i') { - if ((tolower(*(iter + 1)) == 'n') && (tolower(*(iter + 2)) == 'f')) iter += 3; - else goto error; - - if (tolower(*(iter)) == 'i') { - if ((tolower(*(iter + 1)) == 'n') && (tolower(*(iter + 2)) == 'i') && (tolower(*(iter + 3)) == 't') && (tolower(*(iter + 4)) == 'y')) iter += 5; - else goto error; - } - if (endPtr) *endPtr = (char *)(iter); - return (minus == -1) ? -INFINITY : INFINITY; - } - - if (tolower(*iter) == 'n') { - if ((tolower(*(iter + 1)) == 'a') && (tolower(*(iter + 2)) == 'n')) iter += 3; - else goto error; - - if (endPtr) *endPtr = (char *)(iter); - return (minus == -1) ? -NAN : NAN; - } - - //Optional: integer part before dot - if (isdigit(*iter)) { - for (; isdigit(*iter); iter++) { - integerPart = integerPart * 10ULL + (unsigned long long)(*iter - '0'); - } - a = iter; - } else if (*iter != '.') { - goto success; - } - - val = static_cast<float>(integerPart); - - //Optional: decimal part after dot - if (*iter == '.') { - unsigned long long decimalPart = 0; - unsigned long long pow10 = 1; - int count = 0; - - iter++; - - if (isdigit(*iter)) { - for (; isdigit(*iter); iter++, count++) { - if (count < 19) { - decimalPart = decimalPart * 10ULL + + static_cast<unsigned long long>(*iter - '0'); - pow10 *= 10ULL; - } - } - } else if (isspace(*iter)) { //skip if there is a space after the dot. - a = iter; - goto success; - } - - val += static_cast<float>(decimalPart) / static_cast<float>(pow10); - a = iter; - } - - //Optional: exponent - if (*iter == 'e' || *iter == 'E') { - ++iter; - - //Exception: svg may have 'em' unit for fonts. ex) 5em, 10.5em - if ((*iter == 'm') || (*iter == 'M')) { - //TODO: We don't support font em unit now, but has to multiply val * font size later... - a = iter + 1; - goto success; - } - - //signed or not - int minus_e = 1; - - if (*iter == '-') { - minus_e = -1; - ++iter; - } else if (*iter == '+') { - iter++; - } - - unsigned int exponentPart = 0; - - if (isdigit(*iter)) { - while (*iter == '0') iter++; - for (; isdigit(*iter); iter++) { - exponentPart = exponentPart * 10U + static_cast<unsigned int>(*iter - '0'); - } - } else if (!isdigit(*(a - 1))) { - a = nPtr; - goto success; - } else if (*iter == 0) { - goto success; - } - - //if ((_floatExact(val, 2.2250738585072011f)) && ((minus_e * static_cast<int>(exponentPart)) <= -308)) { - if ((_floatExact(val, 1.175494351f)) && ((minus_e * static_cast<int>(exponentPart)) <= -38)) { - //val *= 1.0e-308f; - val *= 1.0e-38f; - a = iter; - goto success; - } - - a = iter; - auto scale = 1.0f; - - while (exponentPart >= 8U) { - scale *= 1E8; - exponentPart -= 8U; - } - while (exponentPart > 0U) { - scale *= 10.0f; - exponentPart--; - } - val = (minus_e == -1) ? (val / scale) : (val * scale); - } else if ((iter > nPtr) && !isdigit(*(iter - 1))) { - a = nPtr; - goto success; - } - -success: - if (endPtr) *endPtr = (char*)(a); - return minus * val; - -error: - if (endPtr) *endPtr = (char*)(nPtr); - return 0.0f; -} - - string svgUtilURLDecode(const char *src) { if (!src) return nullptr; @@ -242,49 +65,3 @@ string svgUtilURLDecode(const char *src) } return decoded; } - - -string svgUtilBase64Decode(const char *src) -{ - if (!src) return nullptr; - - auto length = strlen(src); - if (length == 0) return nullptr; - - string decoded; - decoded.reserve(3*(1+(length >> 2))); - - while (*src && *(src+1)) { - if (*src <= 0x20) { - ++src; - continue; - } - - auto value1 = _base64Value(src[0]); - auto value2 = _base64Value(src[1]); - decoded += (value1 << 2) + ((value2 & 0x30) >> 4); - - if (!src[2] || src[2] == '=' || src[2] == '.') break; - auto value3 = _base64Value(src[2]); - decoded += ((value2 & 0x0f) << 4) + ((value3 & 0x3c) >> 2); - - if (!src[3] || src[3] == '=' || src[3] == '.') break; - auto value4 = _base64Value(src[3]); - decoded += ((value3 & 0x03) << 6) + value4; - src += 4; - } - return decoded; -} - - -char* svgUtilStrndup(const char* str, size_t n) -{ - auto len = strlen(str); - if (len < n) n = len; - - auto ret = (char*)malloc(n + 1); - if (!ret) return nullptr; - ret[n] = '\0'; - - return (char*)memcpy(ret, str, n); -} diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.h b/thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.h index 48be4649bc..e914eadc65 100644 --- a/thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.h +++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.h @@ -25,11 +25,6 @@ #include "tvgCommon.h" -float svgUtilStrtof(const char *nPtr, char **endPtr); - string svgUtilURLDecode(const char *src); -string svgUtilBase64Decode(const char *src); - -char* svgUtilStrndup(const char* str, size_t n); #endif //_TVG_SVG_UTIL_H_ diff --git a/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp b/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp index faacfcd223..dbc3b17b70 100644 --- a/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp +++ b/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp @@ -33,7 +33,7 @@ #endif #include "tvgXmlParser.h" -#include "tvgSvgUtil.h" +#include "tvgStr.h" /************************************************************************/ /* Internal Class Implementation */ @@ -557,10 +557,10 @@ const char* simpleXmlParseCSSAttribute(const char* buf, unsigned bufLength, char } if (p == itr) *tag = strdup("all"); - else *tag = svgUtilStrndup(itr, p - itr); + else *tag = strDuplicate(itr, p - itr); if (p == itrEnd) *name = nullptr; - else *name = svgUtilStrndup(p + 1, itrEnd - p - 1); + else *name = strDuplicate(p + 1, itrEnd - p - 1); return (nextElement ? nextElement + 1 : nullptr); } diff --git a/thirdparty/thorvg/src/lib/tvgArray.h b/thirdparty/thorvg/src/utils/tvgArray.h index 0e8aef3071..919da7e108 100644 --- a/thirdparty/thorvg/src/lib/tvgArray.h +++ b/thirdparty/thorvg/src/utils/tvgArray.h @@ -73,11 +73,36 @@ struct Array return reserve(count + size); } - T* end() const + const T& operator[](size_t idx) const + { + return data[idx]; + } + + T& operator[](size_t idx) + { + return data[idx]; + } + + T* end() + { + return data + count; + } + + const T* end() const { return data + count; } + const T& last() const + { + return data[count - 1]; + } + + const T& first() const + { + return data[0]; + } + T& last() { return data[count - 1]; diff --git a/thirdparty/thorvg/src/lib/tvgBezier.cpp b/thirdparty/thorvg/src/utils/tvgBezier.cpp index 87511a06c7..f9daf07b84 100644 --- a/thirdparty/thorvg/src/lib/tvgBezier.cpp +++ b/thirdparty/thorvg/src/utils/tvgBezier.cpp @@ -116,7 +116,7 @@ float bezAt(const Bezier& bz, float at, float length) //just in case to prevent an infinite loop if (at <= 0) return 0.0f; - if (at >= length) return length; + if (at >= length) return 1.0f; while (true) { auto right = bz; diff --git a/thirdparty/thorvg/src/lib/tvgBezier.h b/thirdparty/thorvg/src/utils/tvgBezier.h index 539a63bdd3..539a63bdd3 100644 --- a/thirdparty/thorvg/src/lib/tvgBezier.h +++ b/thirdparty/thorvg/src/utils/tvgBezier.h diff --git a/thirdparty/thorvg/src/lib/tvgLzw.cpp b/thirdparty/thorvg/src/utils/tvgCompressor.cpp index 52f4ed6716..e38940f3d0 100644 --- a/thirdparty/thorvg/src/lib/tvgLzw.cpp +++ b/thirdparty/thorvg/src/utils/tvgCompressor.cpp @@ -55,17 +55,20 @@ */ #include "config.h" -#if defined(THORVG_TVG_SAVER_SUPPORT) || defined(THORVG_TVG_LOADER_SUPPORT) -/************************************************************************/ -/* Internal Class Implementation */ -/************************************************************************/ #include <string> #include <memory.h> -#include "tvgLzw.h" +#include "tvgCompressor.h" + +namespace tvg { + + +/************************************************************************/ +/* LZW Implementation */ +/************************************************************************/ + -namespace { //LZW Dictionary helper: constexpr int Nil = -1; constexpr int MaxDictBits = 12; @@ -334,15 +337,8 @@ static bool outputSequence(const Dictionary& dict, int code, uint8_t*& output, i } return true; } -} -/************************************************************************/ -/* External Class Implementation */ -/************************************************************************/ - -namespace tvg { - uint8_t* lzwDecode(const uint8_t* compressed, uint32_t compressedSizeBytes, uint32_t compressedSizeBits, uint32_t uncompressedSizeBytes) { int code = Nil; @@ -423,6 +419,57 @@ uint8_t* lzwEncode(const uint8_t* uncompressed, uint32_t uncompressedSizeBytes, return bitStream.release(); } + +/************************************************************************/ +/* B64 Implementation */ +/************************************************************************/ + + +size_t b64Decode(const char* encoded, const size_t len, char** decoded) +{ + static constexpr const char B64_INDEX[256] = + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 62, 63, 62, 62, 63, 52, 53, 54, 55, 56, 57, + 58, 59, 60, 61, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 0, 0, 0, 0, 63, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, + 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 + }; + + + if (!decoded || !encoded || len == 0) return 0; + + auto reserved = 3 * (1 + (len >> 2)) + 1; + auto output = static_cast<char*>(malloc(reserved * sizeof(char))); + if (!output) return 0; + output[reserved - 1] = '\0'; + + size_t idx = 0; + + while (*encoded && *(encoded + 1)) { + if (*encoded <= 0x20) { + ++encoded; + continue; + } + + auto value1 = B64_INDEX[(size_t)encoded[0]]; + auto value2 = B64_INDEX[(size_t)encoded[1]]; + output[idx++] = (value1 << 2) + ((value2 & 0x30) >> 4); + + if (!encoded[2] || encoded[2] == '=' || encoded[2] == '.') break; + auto value3 = B64_INDEX[(size_t)encoded[2]]; + output[idx++] = ((value2 & 0x0f) << 4) + ((value3 & 0x3c) >> 2); + + if (!encoded[3] || encoded[3] == '=' || encoded[3] == '.') break; + auto value4 = B64_INDEX[(size_t)encoded[3]]; + output[idx++] = ((value3 & 0x03) << 6) + value4; + encoded += 4; + } + *decoded = output; + return reserved; } -#endif + +} diff --git a/thirdparty/thorvg/src/lib/tvgLzw.h b/thirdparty/thorvg/src/utils/tvgCompressor.h index bd4e783fbf..05d23f4293 100644 --- a/thirdparty/thorvg/src/lib/tvgLzw.h +++ b/thirdparty/thorvg/src/utils/tvgCompressor.h @@ -20,8 +20,8 @@ * SOFTWARE. */ -#ifndef _TVG_LZW_H_ -#define _TVG_LZW_H_ +#ifndef _TVG_COMPRESSOR_H_ +#define _TVG_COMPRESSOR_H_ #include <cstdint> @@ -29,6 +29,7 @@ namespace tvg { uint8_t* lzwEncode(const uint8_t* uncompressed, uint32_t uncompressedSizeBytes, uint32_t* compressedSizeBytes, uint32_t* compressedSizeBits); uint8_t* lzwDecode(const uint8_t* compressed, uint32_t compressedSizeBytes, uint32_t compressedSizeBits, uint32_t uncompressedSizeBytes); + size_t b64Decode(const char* encoded, const size_t len, char** decoded); } -#endif //_TVG_LZW_H +#endif //_TVG_COMPRESSOR_H_ diff --git a/thirdparty/thorvg/src/lib/tvgMath.h b/thirdparty/thorvg/src/utils/tvgMath.h index afe1849825..897ff46427 100644 --- a/thirdparty/thorvg/src/lib/tvgMath.h +++ b/thirdparty/thorvg/src/utils/tvgMath.h @@ -29,6 +29,8 @@ #include <math.h> #include "tvgCommon.h" +#define MATH_PI 3.14159265358979323846f +#define MATH_PI2 1.57079632679489661923f #define mathMin(x, y) (((x) < (y)) ? (x) : (y)) #define mathMax(x, y) (((x) > (y)) ? (x) : (y)) @@ -45,6 +47,7 @@ static inline bool mathEqual(float a, float b) return (fabsf(a - b) < FLT_EPSILON); } + static inline bool mathEqual(const Matrix& a, const Matrix& b) { if (!mathEqual(a.e11, b.e11) || !mathEqual(a.e12, b.e12) || !mathEqual(a.e13, b.e13) || @@ -55,6 +58,7 @@ static inline bool mathEqual(const Matrix& a, const Matrix& b) return true; } + static inline bool mathRightAngle(const Matrix* m) { auto radian = fabsf(atan2f(m->e21, m->e11)); @@ -118,6 +122,15 @@ static inline void mathIdentity(Matrix* m) } +static inline void mathTransform(Matrix* transform, Point* coord) +{ + auto x = coord->x; + auto y = coord->y; + coord->x = x * transform->e11 + y * transform->e12 + transform->e13; + coord->y = x * transform->e21 + y * transform->e22 + transform->e23; +} + + static inline void mathScale(Matrix* m, float sx, float sy) { m->e11 *= sx; @@ -125,6 +138,19 @@ static inline void mathScale(Matrix* m, float sx, float sy) } +static inline void mathScaleR(Matrix* m, float x, float y) +{ + if (x != 1.0f) { + m->e11 *= x; + m->e21 *= x; + } + if (y != 1.0f) { + m->e22 *= y; + m->e12 *= y; + } +} + + static inline void mathTranslate(Matrix* m, float x, float y) { m->e13 += x; @@ -174,6 +200,32 @@ static inline Matrix mathMultiply(const Matrix* lhs, const Matrix* rhs) } +static inline void mathTranslateR(Matrix* m, float x, float y) +{ + if (x == 0.0f && y == 0.0f) return; + m->e13 += (x * m->e11 + y * m->e12); + m->e23 += (x * m->e21 + y * m->e22); +} + + +static inline void mathLog(Matrix* m) +{ + TVGLOG("MATH", "Matrix: [%f %f %f] [%f %f %f] [%f %f %f]", m->e11, m->e12, m->e13, m->e21, m->e22, m->e23, m->e31, m->e32, m->e33); +} + + +static inline float mathLength(const Point* a, const Point* b) +{ + auto x = b->x - a->x; + auto y = b->y - a->y; + + if (x < 0) x = -x; + if (y < 0) y = -y; + + return (x > y) ? (x + 0.375f * y) : (y + 0.375f * x); +} + + static inline Point operator-(const Point& lhs, const Point& rhs) { return {lhs.x - rhs.x, lhs.y - rhs.y}; diff --git a/thirdparty/thorvg/src/utils/tvgStr.cpp b/thirdparty/thorvg/src/utils/tvgStr.cpp new file mode 100644 index 0000000000..eeed4fc404 --- /dev/null +++ b/thirdparty/thorvg/src/utils/tvgStr.cpp @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2020 - 2023 the ThorVG project. All rights reserved. + + * 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 "config.h" +#include <cstring> +#include <memory.h> +#include "tvgMath.h" +#include "tvgStr.h" + + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +static inline bool _floatExact(float a, float b) +{ + return memcmp(&a, &b, sizeof(float)) == 0; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +namespace tvg { + +/* + * https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/strtof-strtof-l-wcstof-wcstof-l?view=msvc-160 + * + * src should be one of the following form : + * + * [whitespace] [sign] {digits [radix digits] | radix digits} [{e | E} [sign] digits] + * [whitespace] [sign] {INF | INFINITY} + * [whitespace] [sign] NAN [sequence] + * + * No hexadecimal form supported + * no sequence supported after NAN + */ +float strToFloat(const char *nPtr, char **endPtr) +{ + if (endPtr) *endPtr = (char *) (nPtr); + if (!nPtr) return 0.0f; + + auto a = nPtr; + auto iter = nPtr; + auto val = 0.0f; + unsigned long long integerPart = 0; + int minus = 1; + + //ignore leading whitespaces + while (isspace(*iter)) iter++; + + //signed or not + if (*iter == '-') { + minus = -1; + iter++; + } else if (*iter == '+') { + iter++; + } + + if (tolower(*iter) == 'i') { + if ((tolower(*(iter + 1)) == 'n') && (tolower(*(iter + 2)) == 'f')) iter += 3; + else goto error; + + if (tolower(*(iter)) == 'i') { + if ((tolower(*(iter + 1)) == 'n') && (tolower(*(iter + 2)) == 'i') && (tolower(*(iter + 3)) == 't') && + (tolower(*(iter + 4)) == 'y')) + iter += 5; + else goto error; + } + if (endPtr) *endPtr = (char *) (iter); + return (minus == -1) ? -INFINITY : INFINITY; + } + + if (tolower(*iter) == 'n') { + if ((tolower(*(iter + 1)) == 'a') && (tolower(*(iter + 2)) == 'n')) iter += 3; + else goto error; + + if (endPtr) *endPtr = (char *) (iter); + return (minus == -1) ? -NAN : NAN; + } + + //Optional: integer part before dot + if (isdigit(*iter)) { + for (; isdigit(*iter); iter++) { + integerPart = integerPart * 10ULL + (unsigned long long) (*iter - '0'); + } + a = iter; + } else if (*iter != '.') { + goto success; + } + + val = static_cast<float>(integerPart); + + //Optional: decimal part after dot + if (*iter == '.') { + unsigned long long decimalPart = 0; + unsigned long long pow10 = 1; + int count = 0; + + iter++; + + if (isdigit(*iter)) { + for (; isdigit(*iter); iter++, count++) { + if (count < 19) { + decimalPart = decimalPart * 10ULL + +static_cast<unsigned long long>(*iter - '0'); + pow10 *= 10ULL; + } + } + } else if (isspace(*iter)) { //skip if there is a space after the dot. + a = iter; + goto success; + } + + val += static_cast<float>(decimalPart) / static_cast<float>(pow10); + a = iter; + } + + //Optional: exponent + if (*iter == 'e' || *iter == 'E') { + ++iter; + + //Exception: svg may have 'em' unit for fonts. ex) 5em, 10.5em + if ((*iter == 'm') || (*iter == 'M')) { + //TODO: We don't support font em unit now, but has to multiply val * font size later... + a = iter + 1; + goto success; + } + + //signed or not + int minus_e = 1; + + if (*iter == '-') { + minus_e = -1; + ++iter; + } else if (*iter == '+') { + iter++; + } + + unsigned int exponentPart = 0; + + if (isdigit(*iter)) { + while (*iter == '0') iter++; + for (; isdigit(*iter); iter++) { + exponentPart = exponentPart * 10U + static_cast<unsigned int>(*iter - '0'); + } + } else if (!isdigit(*(a - 1))) { + a = nPtr; + goto success; + } else if (*iter == 0) { + goto success; + } + + //if ((_floatExact(val, 2.2250738585072011f)) && ((minus_e * static_cast<int>(exponentPart)) <= -308)) { + if ((_floatExact(val, 1.175494351f)) && ((minus_e * static_cast<int>(exponentPart)) <= -38)) { + //val *= 1.0e-308f; + val *= 1.0e-38f; + a = iter; + goto success; + } + + a = iter; + auto scale = 1.0f; + + while (exponentPart >= 8U) { + scale *= 1E8; + exponentPart -= 8U; + } + while (exponentPart > 0U) { + scale *= 10.0f; + exponentPart--; + } + val = (minus_e == -1) ? (val / scale) : (val * scale); + } else if ((iter > nPtr) && !isdigit(*(iter - 1))) { + a = nPtr; + goto success; + } + +success: + if (endPtr) *endPtr = (char *)(a); + return minus * val; + +error: + if (endPtr) *endPtr = (char *)(nPtr); + return 0.0f; +} + + +int str2int(const char* str, size_t n) +{ + int ret = 0; + for(size_t i = 0; i < n; ++i) { + ret = ret * 10 + (str[i] - '0'); + } + return ret; +} + +char* strDuplicate(const char *str, size_t n) +{ + auto len = strlen(str); + if (len < n) n = len; + + auto ret = (char *) malloc(n + 1); + if (!ret) return nullptr; + ret[n] = '\0'; + + return (char *) memcpy(ret, str, n); +} + +char* strDirname(const char* path) +{ + const char *ptr = strrchr(path, '/'); +#ifdef _WIN32 + if (ptr) ptr = strrchr(ptr + 1, '\\'); +#endif + int len = int(ptr + 1 - path); // +1 to include '/' + return strDuplicate(path, len); +} + +} diff --git a/thirdparty/thorvg/src/utils/tvgStr.h b/thirdparty/thorvg/src/utils/tvgStr.h new file mode 100644 index 0000000000..448cc69336 --- /dev/null +++ b/thirdparty/thorvg/src/utils/tvgStr.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020 - 2023 the ThorVG project. All rights reserved. + + * 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 _TVG_STR_H_ +#define _TVG_STR_H_ + +#include <cstddef> + +namespace tvg +{ + +float strToFloat(const char *nPtr, char **endPtr); //convert to float +int str2int(const char* str, size_t n); //convert to integer +char* strDuplicate(const char *str, size_t n); //copy the string +char* strDirname(const char* path); //return the full directory name + +} +#endif //_TVG_STR_H_ diff --git a/thirdparty/thorvg/update-thorvg.sh b/thirdparty/thorvg/update-thorvg.sh index f57824872b..804d3b76db 100755 --- a/thirdparty/thorvg/update-thorvg.sh +++ b/thirdparty/thorvg/update-thorvg.sh @@ -1,6 +1,6 @@ #!/bin/bash -e -VERSION=0.10.0 +VERSION=0.10.7 rm -rf AUTHORS LICENSE inc/ src/ *.zip *.tar.gz tmp/ @@ -32,7 +32,7 @@ cat << EOF > ../inc/config.h EOF mkdir ../src -cp -rv src/lib ../src/ +cp -rv src/lib src/utils ../src/ # Only sw_engine is enabled. rm -rfv ../src/lib/gl_engine |