diff options
119 files changed, 2974 insertions, 846 deletions
diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index 352486bd09..75eea2ef50 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -1330,8 +1330,8 @@ void ProjectSettings::load_scene_groups_cache() { for (const String &E : scene_paths) { Array scene_groups = cf->get_value(E, "groups", Array()); HashSet<StringName> cache; - for (int i = 0; i < scene_groups.size(); ++i) { - cache.insert(scene_groups[i]); + for (const Variant &scene_group : scene_groups) { + cache.insert(scene_group); } add_scene_groups_cache(E, cache); } diff --git a/core/extension/extension_api_dump.cpp b/core/extension/extension_api_dump.cpp index 543dabfb16..69be2d2a8f 100644 --- a/core/extension/extension_api_dump.cpp +++ b/core/extension/extension_api_dump.cpp @@ -1313,9 +1313,7 @@ static bool compare_value(const String &p_path, const String &p_field, const Var } else if (p_old_value.get_type() == Variant::DICTIONARY && p_new_value.get_type() == Variant::DICTIONARY) { Dictionary old_dict = p_old_value; Dictionary new_dict = p_new_value; - Array old_keys = old_dict.keys(); - for (int i = 0; i < old_keys.size(); i++) { - Variant key = old_keys[i]; + for (const Variant &key : old_dict.keys()) { if (!new_dict.has(key)) { failed = true; print_error(vformat("Validate extension JSON: Error: Field '%s': %s was removed.", p_path, key)); @@ -1328,9 +1326,7 @@ static bool compare_value(const String &p_path, const String &p_field, const Var failed = true; } } - Array new_keys = old_dict.keys(); - for (int i = 0; i < new_keys.size(); i++) { - Variant key = new_keys[i]; + for (const Variant &key : old_dict.keys()) { if (!old_dict.has(key)) { failed = true; print_error(vformat("Validate extension JSON: Error: Field '%s': %s was added with value %s.", p_path, key, new_dict[key])); @@ -1356,8 +1352,8 @@ static bool compare_dict_array(const Dictionary &p_old_api, const Dictionary &p_ Array new_api = p_new_api[p_base_array]; HashMap<String, Dictionary> new_api_assoc; - for (int i = 0; i < new_api.size(); i++) { - Dictionary elem = new_api[i]; + for (const Variant &var : new_api) { + Dictionary elem = var; ERR_FAIL_COND_V_MSG(!elem.has(p_name_field), false, vformat("Validate extension JSON: Element of base_array '%s' is missing field '%s'. This is a bug.", base_array, p_name_field)); String name = elem[p_name_field]; if (p_compare_operators && elem.has("right_type")) { @@ -1367,8 +1363,8 @@ static bool compare_dict_array(const Dictionary &p_old_api, const Dictionary &p_ } Array old_api = p_old_api[p_base_array]; - for (int i = 0; i < old_api.size(); i++) { - Dictionary old_elem = old_api[i]; + for (const Variant &var : old_api) { + Dictionary old_elem = var; if (!old_elem.has(p_name_field)) { failed = true; print_error(vformat("Validate extension JSON: JSON file: element of base array '%s' is missing the field: '%s'.", base_array, p_name_field)); @@ -1508,16 +1504,16 @@ static bool compare_sub_dict_array(HashSet<String> &r_removed_classes_registered Array new_api = p_new_api[p_outer]; HashMap<String, Dictionary> new_api_assoc; - for (int i = 0; i < new_api.size(); i++) { - Dictionary elem = new_api[i]; + for (const Variant &var : new_api) { + Dictionary elem = var; ERR_FAIL_COND_V_MSG(!elem.has(p_outer_name), false, vformat("Validate extension JSON: Element of base_array '%s' is missing field '%s'. This is a bug.", p_outer, p_outer_name)); new_api_assoc.insert(elem[p_outer_name], elem); } Array old_api = p_old_api[p_outer]; - for (int i = 0; i < old_api.size(); i++) { - Dictionary old_elem = old_api[i]; + for (const Variant &var : old_api) { + Dictionary old_elem = var; if (!old_elem.has(p_outer_name)) { failed = true; print_error(vformat("Validate extension JSON: JSON file: element of base array '%s' is missing the field: '%s'.", p_outer, p_outer_name)); diff --git a/core/io/json.cpp b/core/io/json.cpp index 496400a5ea..5a1fe45f70 100644 --- a/core/io/json.cpp +++ b/core/io/json.cpp @@ -86,7 +86,7 @@ String JSON::_stringify(const Variant &p_var, const String &p_indent, int p_cur_ case Variant::PACKED_STRING_ARRAY: case Variant::ARRAY: { Array a = p_var; - if (a.size() == 0) { + if (a.is_empty()) { return "[]"; } String s = "["; @@ -95,12 +95,15 @@ String JSON::_stringify(const Variant &p_var, const String &p_indent, int p_cur_ ERR_FAIL_COND_V_MSG(p_markers.has(a.id()), "\"[...]\"", "Converting circular structure to JSON."); p_markers.insert(a.id()); - for (int i = 0; i < a.size(); i++) { - if (i > 0) { + bool first = true; + for (const Variant &var : a) { + if (first) { + first = false; + } else { s += ","; s += end_statement; } - s += _make_indent(p_indent, p_cur_indent + 1) + _stringify(a[i], p_indent, p_cur_indent + 1, p_sort_keys, p_markers); + s += _make_indent(p_indent, p_cur_indent + 1) + _stringify(var, p_indent, p_cur_indent + 1, p_sort_keys, p_markers); } s += end_statement + _make_indent(p_indent, p_cur_indent) + "]"; p_markers.erase(a.id()); diff --git a/core/io/marshalls.cpp b/core/io/marshalls.cpp index f0bbf00a1d..4487b8e472 100644 --- a/core/io/marshalls.cpp +++ b/core/io/marshalls.cpp @@ -1729,9 +1729,9 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo } r_len += 4; - for (int i = 0; i < array.size(); i++) { + for (const Variant &var : array) { int len; - Error err = encode_variant(array.get(i), buf, len, p_full_objects, p_depth + 1); + Error err = encode_variant(var, buf, len, p_full_objects, p_depth + 1); ERR_FAIL_COND_V(err, err); ERR_FAIL_COND_V(len % 4, ERR_BUG); if (buf) { diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp index 17cffb878e..d0a8200546 100644 --- a/core/io/resource_format_binary.cpp +++ b/core/io/resource_format_binary.cpp @@ -1844,8 +1844,8 @@ void ResourceFormatSaverBinaryInstance::write_variant(Ref<FileAccess> f, const V f->store_32(VARIANT_ARRAY); Array a = p_property; f->store_32(uint32_t(a.size())); - for (int i = 0; i < a.size(); i++) { - write_variant(f, a[i], resource_map, external_resources, string_map); + for (const Variant &var : a) { + write_variant(f, var, resource_map, external_resources, string_map); } } break; @@ -2016,9 +2016,8 @@ void ResourceFormatSaverBinaryInstance::_find_resources(const Variant &p_variant case Variant::ARRAY: { Array varray = p_variant; - int len = varray.size(); - for (int i = 0; i < len; i++) { - const Variant &v = varray.get(i); + _find_resources(varray.get_typed_script()); + for (const Variant &v : varray) { _find_resources(v); } diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp index 191abee315..eea6357084 100644 --- a/core/io/resource_loader.cpp +++ b/core/io/resource_loader.cpp @@ -1035,8 +1035,9 @@ void ResourceLoader::load_translation_remaps() { Array langs = remaps[E]; Vector<String> lang_remaps; lang_remaps.resize(langs.size()); - for (int i = 0; i < langs.size(); i++) { - lang_remaps.write[i] = langs[i]; + String *lang_remaps_ptrw = lang_remaps.ptrw(); + for (const Variant &lang : langs) { + *lang_remaps_ptrw++ = lang; } translation_remaps[String(E)] = lang_remaps; diff --git a/core/object/object.cpp b/core/object/object.cpp index 8b6fd587e0..06f6e8e9e6 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -142,16 +142,16 @@ MethodInfo MethodInfo::from_dict(const Dictionary &p_dict) { args = p_dict["args"]; } - for (int i = 0; i < args.size(); i++) { - Dictionary d = args[i]; + for (const Variant &arg : args) { + Dictionary d = arg; mi.arguments.push_back(PropertyInfo::from_dict(d)); } Array defargs; if (p_dict.has("default_args")) { defargs = p_dict["default_args"]; } - for (int i = 0; i < defargs.size(); i++) { - mi.default_arguments.push_back(defargs[i]); + for (const Variant &defarg : defargs) { + mi.default_arguments.push_back(defarg); } if (p_dict.has("return")) { @@ -1233,8 +1233,8 @@ void Object::_add_user_signal(const String &p_name, const Array &p_args) { MethodInfo mi; mi.name = p_name; - for (int i = 0; i < p_args.size(); i++) { - Dictionary d = p_args[i]; + for (const Variant &arg : p_args) { + Dictionary d = arg; PropertyInfo param; if (d.has("name")) { @@ -1585,8 +1585,8 @@ void Object::_clear_internal_resource_paths(const Variant &p_var) { } break; case Variant::ARRAY: { Array a = p_var; - for (int i = 0; i < a.size(); i++) { - _clear_internal_resource_paths(a[i]); + for (const Variant &var : a) { + _clear_internal_resource_paths(var); } } break; diff --git a/core/object/script_language.cpp b/core/object/script_language.cpp index 73da0ba2af..660e13e819 100644 --- a/core/object/script_language.cpp +++ b/core/object/script_language.cpp @@ -251,8 +251,8 @@ void ScriptServer::init_languages() { if (ProjectSettings::get_singleton()->has_setting("_global_script_classes")) { Array script_classes = GLOBAL_GET("_global_script_classes"); - for (int i = 0; i < script_classes.size(); i++) { - Dictionary c = script_classes[i]; + for (const Variant &script_class : script_classes) { + Dictionary c = script_class; if (!c.has("class") || !c.has("language") || !c.has("path") || !c.has("base")) { continue; } @@ -263,8 +263,8 @@ void ScriptServer::init_languages() { #endif Array script_classes = ProjectSettings::get_singleton()->get_global_class_list(); - for (int i = 0; i < script_classes.size(); i++) { - Dictionary c = script_classes[i]; + for (const Variant &script_class : script_classes) { + Dictionary c = script_class; if (!c.has("class") || !c.has("language") || !c.has("path") || !c.has("base")) { continue; } @@ -463,8 +463,8 @@ void ScriptServer::save_global_classes() { Dictionary class_icons; Array script_classes = ProjectSettings::get_singleton()->get_global_class_list(); - for (int i = 0; i < script_classes.size(); i++) { - Dictionary d = script_classes[i]; + for (const Variant &script_class : script_classes) { + Dictionary d = script_class; if (!d.has("name") || !d.has("icon")) { continue; } diff --git a/core/object/script_language_extension.h b/core/object/script_language_extension.h index 1db322526d..cc6b729ae8 100644 --- a/core/object/script_language_extension.h +++ b/core/object/script_language_extension.h @@ -319,8 +319,8 @@ public: } if (r_errors != nullptr && ret.has("errors")) { Array errors = ret["errors"]; - for (int i = 0; i < errors.size(); i++) { - Dictionary err = errors[i]; + for (const Variant &error : errors) { + Dictionary err = error; ERR_CONTINUE(!err.has("line")); ERR_CONTINUE(!err.has("column")); ERR_CONTINUE(!err.has("message")); @@ -339,8 +339,8 @@ public: if (r_warnings != nullptr && ret.has("warnings")) { ERR_FAIL_COND_V(!ret.has("warnings"), false); Array warnings = ret["warnings"]; - for (int i = 0; i < warnings.size(); i++) { - Dictionary warn = warnings[i]; + for (const Variant &warning : warnings) { + Dictionary warn = warning; ERR_CONTINUE(!warn.has("start_line")); ERR_CONTINUE(!warn.has("end_line")); ERR_CONTINUE(!warn.has("leftmost_column")); @@ -402,8 +402,8 @@ public: if (r_options != nullptr && ret.has("options")) { Array options = ret["options"]; - for (int i = 0; i < options.size(); i++) { - Dictionary op = options[i]; + for (const Variant &var : options) { + Dictionary op = var; CodeCompletionOption option; ERR_CONTINUE(!op.has("kind")); option.kind = CodeCompletionKind(int(op["kind"])); @@ -502,8 +502,8 @@ public: } if (p_values != nullptr && ret.has("values")) { Array values = ret["values"]; - for (int i = 0; i < values.size(); i++) { - p_values->push_back(values[i]); + for (const Variant &value : values) { + p_values->push_back(value); } } } @@ -522,8 +522,8 @@ public: } if (p_values != nullptr && ret.has("values")) { Array values = ret["values"]; - for (int i = 0; i < values.size(); i++) { - p_values->push_back(values[i]); + for (const Variant &value : values) { + p_values->push_back(value); } } } @@ -549,8 +549,8 @@ public: } if (p_values != nullptr && ret.has("values")) { Array values = ret["values"]; - for (int i = 0; i < values.size(); i++) { - p_values->push_back(values[i]); + for (const Variant &value : values) { + p_values->push_back(value); } } } @@ -562,9 +562,9 @@ public: TypedArray<Dictionary> ret; GDVIRTUAL_REQUIRED_CALL(_debug_get_current_stack_info, ret); Vector<StackInfo> sret; - for (int i = 0; i < ret.size(); i++) { + for (const Variant &var : ret) { StackInfo si; - Dictionary d = ret[i]; + Dictionary d = var; ERR_CONTINUE(!d.has("file")); ERR_CONTINUE(!d.has("func")); ERR_CONTINUE(!d.has("line")); @@ -595,8 +595,8 @@ public: virtual void get_public_functions(List<MethodInfo> *p_functions) const override { TypedArray<Dictionary> ret; GDVIRTUAL_REQUIRED_CALL(_get_public_functions, ret); - for (int i = 0; i < ret.size(); i++) { - MethodInfo mi = MethodInfo::from_dict(ret[i]); + for (const Variant &var : ret) { + MethodInfo mi = MethodInfo::from_dict(var); p_functions->push_back(mi); } } @@ -615,8 +615,8 @@ public: virtual void get_public_annotations(List<MethodInfo> *p_annotations) const override { TypedArray<Dictionary> ret; GDVIRTUAL_REQUIRED_CALL(_get_public_annotations, ret); - for (int i = 0; i < ret.size(); i++) { - MethodInfo mi = MethodInfo::from_dict(ret[i]); + for (const Variant &var : ret) { + MethodInfo mi = MethodInfo::from_dict(var); p_annotations->push_back(mi); } } diff --git a/core/variant/array.cpp b/core/variant/array.cpp index 5d6fbb8bed..3685515db5 100644 --- a/core/variant/array.cpp +++ b/core/variant/array.cpp @@ -81,6 +81,22 @@ void Array::_unref() const { _p = nullptr; } +Array::Iterator Array::begin() { + return Iterator(_p->array.ptrw(), _p->read_only); +} + +Array::Iterator Array::end() { + return Iterator(_p->array.ptrw() + _p->array.size(), _p->read_only); +} + +Array::ConstIterator Array::begin() const { + return ConstIterator(_p->array.ptr(), _p->read_only); +} + +Array::ConstIterator Array::end() const { + return ConstIterator(_p->array.ptr() + _p->array.size(), _p->read_only); +} + Variant &Array::operator[](int p_idx) { if (unlikely(_p->read_only)) { *_p->read_only = _p->array[p_idx]; diff --git a/core/variant/array.h b/core/variant/array.h index 8b1f8c0678..d45a6887e2 100644 --- a/core/variant/array.h +++ b/core/variant/array.h @@ -46,6 +46,70 @@ class Array { void _unref() const; public: + struct ConstIterator { + _FORCE_INLINE_ const Variant &operator*() const; + _FORCE_INLINE_ const Variant *operator->() const; + + _FORCE_INLINE_ ConstIterator &operator++(); + _FORCE_INLINE_ ConstIterator &operator--(); + + _FORCE_INLINE_ bool operator==(const ConstIterator &p_other) const { return element_ptr == p_other.element_ptr; } + _FORCE_INLINE_ bool operator!=(const ConstIterator &p_other) const { return element_ptr == p_other.element_ptr; } + + _FORCE_INLINE_ ConstIterator(const Variant *p_element_ptr, Variant *p_read_only = nullptr) : + element_ptr(p_element_ptr), read_only(p_read_only) {} + _FORCE_INLINE_ ConstIterator() {} + _FORCE_INLINE_ ConstIterator(const ConstIterator &p_other) : + element_ptr(p_other.element_ptr), read_only(p_other.read_only) {} + + _FORCE_INLINE_ ConstIterator &operator=(const ConstIterator &p_other) { + element_ptr = p_other.element_ptr; + read_only = p_other.read_only; + return *this; + } + + private: + const Variant *element_ptr = nullptr; + Variant *read_only = nullptr; + }; + + struct Iterator { + _FORCE_INLINE_ Variant &operator*() const; + _FORCE_INLINE_ Variant *operator->() const; + + _FORCE_INLINE_ Iterator &operator++(); + _FORCE_INLINE_ Iterator &operator--(); + + _FORCE_INLINE_ bool operator==(const Iterator &p_other) const { return element_ptr == p_other.element_ptr; } + _FORCE_INLINE_ bool operator!=(const Iterator &p_other) const { return element_ptr != p_other.element_ptr; } + + _FORCE_INLINE_ Iterator(Variant *p_element_ptr, Variant *p_read_only = nullptr) : + element_ptr(p_element_ptr), read_only(p_read_only) {} + _FORCE_INLINE_ Iterator() {} + _FORCE_INLINE_ Iterator(const Iterator &p_other) : + element_ptr(p_other.element_ptr), read_only(p_other.read_only) {} + + _FORCE_INLINE_ Iterator &operator=(const Iterator &p_other) { + element_ptr = p_other.element_ptr; + read_only = p_other.read_only; + return *this; + } + + operator ConstIterator() const { + return ConstIterator(element_ptr, read_only); + } + + private: + Variant *element_ptr = nullptr; + Variant *read_only = nullptr; + }; + + Iterator begin(); + Iterator end(); + + ConstIterator begin() const; + ConstIterator end() const; + void _ref(const Array &p_from) const; Variant &operator[](int p_idx); diff --git a/core/variant/variant.h b/core/variant/variant.h index fc4030ac5a..10f8dc3c7f 100644 --- a/core/variant/variant.h +++ b/core/variant/variant.h @@ -865,4 +865,56 @@ Callable Callable::bind(VarArgs... p_args) const { return bindp(sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args)); } +Variant &Array::Iterator::operator*() const { + if (unlikely(read_only)) { + *read_only = *element_ptr; + return *read_only; + } + return *element_ptr; +} + +Variant *Array::Iterator::operator->() const { + if (unlikely(read_only)) { + *read_only = *element_ptr; + return read_only; + } + return element_ptr; +} + +Array::Iterator &Array::Iterator::operator++() { + element_ptr++; + return *this; +} + +Array::Iterator &Array::Iterator::operator--() { + element_ptr--; + return *this; +} + +const Variant &Array::ConstIterator::operator*() const { + if (unlikely(read_only)) { + *read_only = *element_ptr; + return *read_only; + } + return *element_ptr; +} + +const Variant *Array::ConstIterator::operator->() const { + if (unlikely(read_only)) { + *read_only = *element_ptr; + return read_only; + } + return element_ptr; +} + +Array::ConstIterator &Array::ConstIterator::operator++() { + element_ptr++; + return *this; +} + +Array::ConstIterator &Array::ConstIterator::operator--() { + element_ptr--; + return *this; +} + #endif // VARIANT_H diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp index ba7c44e405..d0d940c47d 100644 --- a/core/variant/variant_call.cpp +++ b/core/variant/variant_call.cpp @@ -1799,7 +1799,7 @@ static void _register_variant_builtin_methods() { bind_method(Vector2, dot, sarray("with"), varray()); bind_method(Vector2, slide, sarray("n"), varray()); bind_method(Vector2, bounce, sarray("n"), varray()); - bind_method(Vector2, reflect, sarray("n"), varray()); + bind_method(Vector2, reflect, sarray("line"), varray()); bind_method(Vector2, cross, sarray("with"), varray()); bind_method(Vector2, abs, sarray(), varray()); bind_method(Vector2, sign, sarray(), varray()); @@ -1896,7 +1896,7 @@ static void _register_variant_builtin_methods() { bind_method(Vector3, project, sarray("b"), varray()); bind_method(Vector3, slide, sarray("n"), varray()); bind_method(Vector3, bounce, sarray("n"), varray()); - bind_method(Vector3, reflect, sarray("n"), varray()); + bind_method(Vector3, reflect, sarray("direction"), varray()); bind_method(Vector3, sign, sarray(), varray()); bind_method(Vector3, octahedron_encode, sarray(), varray()); bind_static_method(Vector3, octahedron_decode, sarray("uv"), varray()); diff --git a/core/variant/variant_parser.cpp b/core/variant/variant_parser.cpp index fa91758fff..dcb94b16b1 100644 --- a/core/variant/variant_parser.cpp +++ b/core/variant/variant_parser.cpp @@ -30,6 +30,7 @@ #include "variant_parser.h" +#include "core/crypto/crypto_core.h" #include "core/input/input_event.h" #include "core/io/resource_loader.h" #include "core/object/script_language.h" @@ -595,6 +596,82 @@ Error VariantParser::_parse_construct(Stream *p_stream, Vector<T> &r_construct, return OK; } +Error VariantParser::_parse_byte_array(Stream *p_stream, Vector<uint8_t> &r_construct, int &line, String &r_err_str) { + Token token; + get_token(p_stream, token, line, r_err_str); + if (token.type != TK_PARENTHESIS_OPEN) { + r_err_str = "Expected '(' in constructor"; + return ERR_PARSE_ERROR; + } + + get_token(p_stream, token, line, r_err_str); + if (token.type == TK_STRING) { + // Base64 encoded array. + String base64_encoded_string = token.value; + int strlen = base64_encoded_string.length(); + CharString cstr = base64_encoded_string.ascii(); + + size_t arr_len = 0; + r_construct.resize(strlen / 4 * 3 + 1); + uint8_t *w = r_construct.ptrw(); + Error err = CryptoCore::b64_decode(&w[0], r_construct.size(), &arr_len, (unsigned char *)cstr.get_data(), strlen); + if (err) { + r_err_str = "Invalid base64-encoded string"; + return ERR_PARSE_ERROR; + } + r_construct.resize(arr_len); + + get_token(p_stream, token, line, r_err_str); + if (token.type != TK_PARENTHESIS_CLOSE) { + r_err_str = "Expected ')' in constructor"; + return ERR_PARSE_ERROR; + } + + } else if (token.type == TK_NUMBER || token.type == TK_IDENTIFIER) { + // Individual elements. + while (true) { + if (token.type != TK_NUMBER) { + bool valid = false; + if (token.type == TK_IDENTIFIER) { + double real = stor_fix(token.value); + if (real != -1) { + token.type = TK_NUMBER; + token.value = real; + valid = true; + } + } + if (!valid) { + r_err_str = "Expected number in constructor"; + return ERR_PARSE_ERROR; + } + } + + r_construct.push_back(token.value); + + get_token(p_stream, token, line, r_err_str); + + if (token.type == TK_COMMA) { + //do none + } else if (token.type == TK_PARENTHESIS_CLOSE) { + break; + } else { + r_err_str = "Expected ',' or ')' in constructor"; + return ERR_PARSE_ERROR; + } + + get_token(p_stream, token, line, r_err_str); + } + } else if (token.type == TK_PARENTHESIS_CLOSE) { + // Empty array. + return OK; + } else { + r_err_str = "Expected base64 string, or list of numbers in constructor"; + return ERR_PARSE_ERROR; + } + + return OK; +} + Error VariantParser::parse_value(Token &token, Variant &value, Stream *p_stream, int &line, String &r_err_str, ResourceParser *p_res_parser) { if (token.type == TK_CURLY_BRACKET_OPEN) { Dictionary d; @@ -1148,7 +1225,7 @@ Error VariantParser::parse_value(Token &token, Variant &value, Stream *p_stream, value = array; } else if (id == "PackedByteArray" || id == "PoolByteArray" || id == "ByteArray") { Vector<uint8_t> args; - Error err = _parse_construct<uint8_t>(p_stream, args, line, r_err_str); + Error err = _parse_byte_array(p_stream, args, line, r_err_str); if (err) { return err; } @@ -2012,12 +2089,14 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str p_recursion_count++; p_store_string_func(p_store_string_ud, "["); - int len = array.size(); - for (int i = 0; i < len; i++) { - if (i > 0) { + bool first = true; + for (const Variant &var : array) { + if (first) { + first = false; + } else { p_store_string_func(p_store_string_ud, ", "); } - write(array[i], p_store_string_func, p_store_string_ud, p_encode_res_func, p_encode_res_ud, p_recursion_count); + write(var, p_store_string_func, p_store_string_ud, p_encode_res_func, p_encode_res_ud, p_recursion_count); } p_store_string_func(p_store_string_ud, "]"); @@ -2031,17 +2110,11 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str case Variant::PACKED_BYTE_ARRAY: { p_store_string_func(p_store_string_ud, "PackedByteArray("); Vector<uint8_t> data = p_variant; - int len = data.size(); - const uint8_t *ptr = data.ptr(); - - for (int i = 0; i < len; i++) { - if (i > 0) { - p_store_string_func(p_store_string_ud, ", "); - } - - p_store_string_func(p_store_string_ud, itos(ptr[i])); + if (data.size() > 0) { + p_store_string_func(p_store_string_ud, "\""); + p_store_string_func(p_store_string_ud, CryptoCore::b64_encode_str(data.ptr(), data.size())); + p_store_string_func(p_store_string_ud, "\""); } - p_store_string_func(p_store_string_ud, ")"); } break; case Variant::PACKED_INT32_ARRAY: { diff --git a/core/variant/variant_parser.h b/core/variant/variant_parser.h index 18448100e0..2f8974849f 100644 --- a/core/variant/variant_parser.h +++ b/core/variant/variant_parser.h @@ -141,6 +141,7 @@ private: template <typename T> static Error _parse_construct(Stream *p_stream, Vector<T> &r_construct, int &line, String &r_err_str); + static Error _parse_byte_array(Stream *p_stream, Vector<uint8_t> &r_construct, int &line, String &r_err_str); static Error _parse_enginecfg(Stream *p_stream, Vector<String> &strings, int &line, String &r_err_str); static Error _parse_dictionary(Dictionary &object, Stream *p_stream, int &line, String &r_err_str, ResourceParser *p_res_parser = nullptr); static Error _parse_array(Array &array, Stream *p_stream, int &line, String &r_err_str, ResourceParser *p_res_parser = nullptr); diff --git a/doc/classes/Area3D.xml b/doc/classes/Area3D.xml index 4ee16d499d..8eedd3cdf2 100644 --- a/doc/classes/Area3D.xml +++ b/doc/classes/Area3D.xml @@ -124,12 +124,15 @@ </member> <member name="wind_attenuation_factor" type="float" setter="set_wind_attenuation_factor" getter="get_wind_attenuation_factor" default="0.0"> The exponential rate at which wind force decreases with distance from its origin. + [b]Note:[/b] This wind force only applies to [SoftBody3D] nodes. Other physics bodies are currently not affected by wind. </member> <member name="wind_force_magnitude" type="float" setter="set_wind_force_magnitude" getter="get_wind_force_magnitude" default="0.0"> The magnitude of area-specific wind force. + [b]Note:[/b] This wind force only applies to [SoftBody3D] nodes. Other physics bodies are currently not affected by wind. </member> <member name="wind_source_path" type="NodePath" setter="set_wind_source_path" getter="get_wind_source_path" default="NodePath("")"> The [Node3D] which is used to specify the direction and origin of an area-specific wind force. The direction is opposite to the z-axis of the [Node3D]'s local transform, and its origin is the origin of the [Node3D]'s local transform. + [b]Note:[/b] This wind force only applies to [SoftBody3D] nodes. Other physics bodies are currently not affected by wind. </member> </members> <signals> diff --git a/doc/classes/CameraServer.xml b/doc/classes/CameraServer.xml index 983bd6cd91..020b5d887b 100644 --- a/doc/classes/CameraServer.xml +++ b/doc/classes/CameraServer.xml @@ -6,7 +6,7 @@ <description> The [CameraServer] keeps track of different cameras accessible in Godot. These are external cameras such as webcams or the cameras on your phone. It is notably used to provide AR modules with a video feed from the camera. - [b]Note:[/b] This class is currently only implemented on macOS and iOS. On other platforms, no [CameraFeed]s will be available. + [b]Note:[/b] This class is currently only implemented on macOS and iOS. To get a [CameraFeed] on iOS, the camera plugin from [url=https://github.com/godotengine/godot-ios-plugins]godot-ios-plugins[/url] is required. On other platforms, no [CameraFeed]s will be available. </description> <tutorials> </tutorials> diff --git a/doc/classes/CharacterBody2D.xml b/doc/classes/CharacterBody2D.xml index df409cb3c4..ede4d63cfc 100644 --- a/doc/classes/CharacterBody2D.xml +++ b/doc/classes/CharacterBody2D.xml @@ -30,7 +30,8 @@ <method name="get_floor_normal" qualifiers="const"> <return type="Vector2" /> <description> - Returns the surface normal of the floor at the last collision point. Only valid after calling [method move_and_slide] and when [method is_on_floor] returns [code]true[/code]. + Returns the collision normal of the floor at the last collision point. Only valid after calling [method move_and_slide] and when [method is_on_floor] returns [code]true[/code]. + [b]Warning:[/b] The collision normal is not always the same as the surface normal. </description> </method> <method name="get_last_motion" qualifiers="const"> @@ -94,7 +95,8 @@ <method name="get_wall_normal" qualifiers="const"> <return type="Vector2" /> <description> - Returns the surface normal of the wall at the last collision point. Only valid after calling [method move_and_slide] and when [method is_on_wall] returns [code]true[/code]. + Returns the collision normal of the wall at the last collision point. Only valid after calling [method move_and_slide] and when [method is_on_wall] returns [code]true[/code]. + [b]Warning:[/b] The collision normal is not always the same as the surface normal. </description> </method> <method name="is_on_ceiling" qualifiers="const"> diff --git a/doc/classes/CharacterBody3D.xml b/doc/classes/CharacterBody3D.xml index 498149b9c0..474adfc6ff 100644 --- a/doc/classes/CharacterBody3D.xml +++ b/doc/classes/CharacterBody3D.xml @@ -87,7 +87,8 @@ <method name="get_wall_normal" qualifiers="const"> <return type="Vector3" /> <description> - Returns the surface normal of the wall at the last collision point. Only valid after calling [method move_and_slide] and when [method is_on_wall] returns [code]true[/code]. + Returns the collision normal of the wall at the last collision point. Only valid after calling [method move_and_slide] and when [method is_on_wall] returns [code]true[/code]. + [b]Warning:[/b] The collision normal is not always the same as the surface normal. </description> </method> <method name="is_on_ceiling" qualifiers="const"> diff --git a/doc/classes/MeshInstance3D.xml b/doc/classes/MeshInstance3D.xml index f56b03bdc4..e5187cf7a1 100644 --- a/doc/classes/MeshInstance3D.xml +++ b/doc/classes/MeshInstance3D.xml @@ -70,6 +70,12 @@ Returns the value of the blend shape at the given [param blend_shape_idx]. Returns [code]0.0[/code] and produces an error if [member mesh] is [code]null[/code] or doesn't have a blend shape at that index. </description> </method> + <method name="get_skin_reference" qualifiers="const"> + <return type="SkinReference" /> + <description> + Returns the internal [SkinReference] containing the skeleton's [RID] attached to this RID. See also [method Resource.get_rid], [method SkinReference.get_skeleton], and [method RenderingServer.instance_attach_skeleton]. + </description> + </method> <method name="get_surface_override_material" qualifiers="const"> <return type="Material" /> <param index="0" name="surface" type="int" /> diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml index d9138b059c..aea4082dbe 100644 --- a/doc/classes/Node.xml +++ b/doc/classes/Node.xml @@ -795,9 +795,8 @@ <return type="void" /> <param index="0" name="node" type="Node" /> <param index="1" name="keep_groups" type="bool" default="false" /> - <param index="2" name="keep_children" type="bool" default="true" /> <description> - Replaces this node by the given [param node]. If [param keep_children] is [code]true[/code] all children of this node are moved to [param node]. + Replaces this node by the given [param node]. All children of this node are moved to [param node]. If [param keep_groups] is [code]true[/code], the [param node] is added to the same groups that the replaced node is in (see [method add_to_group]). [b]Warning:[/b] The replaced node is removed from the tree, but it is [b]not[/b] deleted. To prevent memory leaks, store a reference to the node in a variable, or use [method Object.free]. </description> diff --git a/doc/classes/PhysicsServer3D.xml b/doc/classes/PhysicsServer3D.xml index 7dcb185834..4a4a1ad025 100644 --- a/doc/classes/PhysicsServer3D.xml +++ b/doc/classes/PhysicsServer3D.xml @@ -1628,7 +1628,7 @@ Constant to set/get the priority (order of processing) of an area. </constant> <constant name="AREA_PARAM_WIND_FORCE_MAGNITUDE" value="10" enum="AreaParameter"> - Constant to set/get the magnitude of area-specific wind force. + Constant to set/get the magnitude of area-specific wind force. This wind force only applies to [SoftBody3D] nodes. Other physics bodies are currently not affected by wind. </constant> <constant name="AREA_PARAM_WIND_SOURCE" value="11" enum="AreaParameter"> Constant to set/get the 3D vector that specifies the origin from which an area-specific wind blows. diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 3f4a9575be..a5aeee5bc4 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -574,7 +574,7 @@ When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when using an expression whose type may not be compatible with the function parameter expected. </member> <member name="debug/gdscript/warnings/unsafe_cast" type="int" setter="" getter="" default="0"> - When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when performing an unsafe cast. + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a [Variant] value is cast to a non-Variant. </member> <member name="debug/gdscript/warnings/unsafe_method_access" type="int" setter="" getter="" default="0"> When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when calling a method whose presence is not guaranteed at compile-time in the class. diff --git a/doc/classes/SkinReference.xml b/doc/classes/SkinReference.xml index 466dbe2500..cb0c44cefa 100644 --- a/doc/classes/SkinReference.xml +++ b/doc/classes/SkinReference.xml @@ -1,8 +1,14 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="SkinReference" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> + A reference-counted holder object for a skeleton RID used in the [RenderingServer]. </brief_description> <description> + An internal object containing a mapping from a [Skin] used within the context of a particular [MeshInstance3D] to refer to the skeleton's [RID] in the RenderingServer. + See also [method MeshInstance3D.get_skin_reference] and [method RenderingServer.instance_attach_skeleton]. + Note that despite the similar naming, the skeleton RID used in the [RenderingServer] does not have a direct one-to-one correspondence to a [Skeleton3D] node. + In particular, a [Skeleton3D] node with no [MeshInstance3D] children may be unknown to the [RenderingServer]. + On the other hand, a [Skeleton3D] with multiple [MeshInstance3D] nodes which each have different [member MeshInstance3D.skin] objects may have multiple SkinReference instances (and hence, multiple skeleton [RID]s). </description> <tutorials> </tutorials> @@ -10,11 +16,14 @@ <method name="get_skeleton" qualifiers="const"> <return type="RID" /> <description> + Returns the [RID] owned by this SkinReference, as returned by [method RenderingServer.skeleton_create]. </description> </method> <method name="get_skin" qualifiers="const"> <return type="Skin" /> <description> + Returns the [Skin] connected to this SkinReference. In the case of [MeshInstance3D] with no [member MeshInstance3D.skin] assigned, this will reference an internal default [Skin] owned by that [MeshInstance3D]. + Note that a single [Skin] may have more than one [SkinReference] in the case that it is shared by meshes across multiple [Skeleton3D] nodes. </description> </method> </methods> diff --git a/doc/classes/SoftBody3D.xml b/doc/classes/SoftBody3D.xml index a4d80a7c3e..195196b78c 100644 --- a/doc/classes/SoftBody3D.xml +++ b/doc/classes/SoftBody3D.xml @@ -5,6 +5,7 @@ </brief_description> <description> A deformable 3D physics mesh. Used to create elastic or deformable objects such as cloth, rubber, or other flexible materials. + Additionally, [SoftBody3D] is subject to wind forces defined in [Area3D] (see [member Area3D.wind_source_path], [member Area3D.wind_force_magnitude], and [member Area3D.wind_attenuation_factor]). [b]Note:[/b] There are many known bugs in [SoftBody3D]. Therefore, it's not recommended to use them for things that can affect gameplay (such as trampolines). </description> <tutorials> diff --git a/doc/classes/Vector2.xml b/doc/classes/Vector2.xml index 0b39743a72..7b166a4fb0 100644 --- a/doc/classes/Vector2.xml +++ b/doc/classes/Vector2.xml @@ -110,7 +110,8 @@ <return type="Vector2" /> <param index="0" name="n" type="Vector2" /> <description> - Returns a new vector "bounced off" from a plane defined by the given normal. + Returns the vector "bounced off" from a line defined by the given normal [param n] perpendicular to the line. + [b]Note:[/b] [method bounce] performs the operation that most engines and frameworks call [code skip-lint]reflect()[/code]. </description> </method> <method name="ceil" qualifiers="const"> @@ -321,9 +322,10 @@ </method> <method name="reflect" qualifiers="const"> <return type="Vector2" /> - <param index="0" name="n" type="Vector2" /> + <param index="0" name="line" type="Vector2" /> <description> - Returns the result of reflecting the vector from a line defined by the given direction vector [param n]. + Returns the result of reflecting the vector from a line defined by the given direction vector [param line]. + [b]Note:[/b] [method reflect] differs from what other engines and frameworks call [code skip-lint]reflect()[/code]. In other engines, [code skip-lint]reflect()[/code] takes a normal direction which is a direction perpendicular to the line. In Godot, you specify the direction of the line directly. See also [method bounce] which does what most engines call [code skip-lint]reflect()[/code]. </description> </method> <method name="rotated" qualifiers="const"> diff --git a/doc/classes/Vector3.xml b/doc/classes/Vector3.xml index af1383fe22..031d91af78 100644 --- a/doc/classes/Vector3.xml +++ b/doc/classes/Vector3.xml @@ -86,7 +86,8 @@ <return type="Vector3" /> <param index="0" name="n" type="Vector3" /> <description> - Returns the vector "bounced off" from a plane defined by the given normal. + Returns the vector "bounced off" from a plane defined by the given normal [param n]. + [b]Note:[/b] [method bounce] performs the operation that most engines and frameworks call [code skip-lint]reflect()[/code]. </description> </method> <method name="ceil" qualifiers="const"> @@ -306,9 +307,10 @@ </method> <method name="reflect" qualifiers="const"> <return type="Vector3" /> - <param index="0" name="n" type="Vector3" /> + <param index="0" name="direction" type="Vector3" /> <description> - Returns the result of reflecting the vector from a plane defined by the given normal [param n]. + Returns the result of reflecting the vector from a plane defined by the given direction vector [param direction]. + [b]Note:[/b] [method reflect] differs from what other engines and frameworks call [code skip-lint]reflect()[/code]. In other engines, [code skip-lint]reflect()[/code] takes a normal direction which is a direction perpendicular to the plane. In Godot, you specify a direction parallel to the plane. See also [method bounce] which does what most engines call [code skip-lint]reflect()[/code]. </description> </method> <method name="rotated" qualifiers="const"> diff --git a/drivers/gles3/effects/cubemap_filter.cpp b/drivers/gles3/effects/cubemap_filter.cpp new file mode 100644 index 0000000000..b88e655492 --- /dev/null +++ b/drivers/gles3/effects/cubemap_filter.cpp @@ -0,0 +1,209 @@ +/**************************************************************************/ +/* cubemap_filter.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifdef GLES3_ENABLED + +#include "cubemap_filter.h" + +#include "../storage/texture_storage.h" +#include "core/config/project_settings.h" + +using namespace GLES3; + +CubemapFilter *CubemapFilter::singleton = nullptr; + +CubemapFilter::CubemapFilter() { + singleton = this; + ggx_samples = GLOBAL_GET("rendering/reflections/sky_reflections/ggx_samples"); + + { + String defines; + defines += "\n#define MAX_SAMPLE_COUNT " + itos(ggx_samples) + "\n"; + cubemap_filter.shader.initialize(defines); + cubemap_filter.shader_version = cubemap_filter.shader.version_create(); + } + + { // Screen Triangle. + glGenBuffers(1, &screen_triangle); + glBindBuffer(GL_ARRAY_BUFFER, screen_triangle); + + const float qv[6] = { + -1.0f, + -1.0f, + 3.0f, + -1.0f, + -1.0f, + 3.0f, + }; + + glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6, qv, GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); //unbind + + glGenVertexArrays(1, &screen_triangle_array); + glBindVertexArray(screen_triangle_array); + glBindBuffer(GL_ARRAY_BUFFER, screen_triangle); + glVertexAttribPointer(RS::ARRAY_VERTEX, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, nullptr); + glEnableVertexAttribArray(RS::ARRAY_VERTEX); + glBindVertexArray(0); + glBindBuffer(GL_ARRAY_BUFFER, 0); //unbind + } +} + +CubemapFilter::~CubemapFilter() { + glDeleteBuffers(1, &screen_triangle); + glDeleteVertexArrays(1, &screen_triangle_array); + + cubemap_filter.shader.version_free(cubemap_filter.shader_version); + singleton = nullptr; +} + +// Helper functions for IBL filtering + +Vector3 importance_sample_GGX(Vector2 xi, float roughness4) { + // Compute distribution direction + float phi = 2.0 * Math_PI * xi.x; + float cos_theta = sqrt((1.0 - xi.y) / (1.0 + (roughness4 - 1.0) * xi.y)); + float sin_theta = sqrt(1.0 - cos_theta * cos_theta); + + // Convert to spherical direction + Vector3 half_vector; + half_vector.x = sin_theta * cos(phi); + half_vector.y = sin_theta * sin(phi); + half_vector.z = cos_theta; + + return half_vector; +} + +float distribution_GGX(float NdotH, float roughness4) { + float NdotH2 = NdotH * NdotH; + float denom = (NdotH2 * (roughness4 - 1.0) + 1.0); + denom = Math_PI * denom * denom; + + return roughness4 / denom; +} + +float radical_inverse_vdC(uint32_t bits) { + bits = (bits << 16) | (bits >> 16); + bits = ((bits & 0x55555555) << 1) | ((bits & 0xAAAAAAAA) >> 1); + bits = ((bits & 0x33333333) << 2) | ((bits & 0xCCCCCCCC) >> 2); + bits = ((bits & 0x0F0F0F0F) << 4) | ((bits & 0xF0F0F0F0) >> 4); + bits = ((bits & 0x00FF00FF) << 8) | ((bits & 0xFF00FF00) >> 8); + + return float(bits) * 2.3283064365386963e-10; +} + +Vector2 hammersley(uint32_t i, uint32_t N) { + return Vector2(float(i) / float(N), radical_inverse_vdC(i)); +} + +void CubemapFilter::filter_radiance(GLuint p_source_cubemap, GLuint p_dest_cubemap, GLuint p_dest_framebuffer, int p_source_size, int p_mipmap_count, int p_layer) { + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_CUBE_MAP, p_source_cubemap); + glBindFramebuffer(GL_FRAMEBUFFER, p_dest_framebuffer); + + CubemapFilterShaderGLES3::ShaderVariant mode = CubemapFilterShaderGLES3::MODE_DEFAULT; + + if (p_layer == 0) { + glGenerateMipmap(GL_TEXTURE_CUBE_MAP); + // Copy over base layer without filtering. + mode = CubemapFilterShaderGLES3::MODE_COPY; + } + + int size = p_source_size >> p_layer; + glViewport(0, 0, size, size); + glBindVertexArray(screen_triangle_array); + + bool success = cubemap_filter.shader.version_bind_shader(cubemap_filter.shader_version, mode); + if (!success) { + return; + } + + if (p_layer > 0) { + const uint32_t sample_counts[4] = { 1, ggx_samples / 4, ggx_samples / 2, ggx_samples }; + uint32_t sample_count = sample_counts[MIN(3, p_layer)]; + + float roughness = float(p_layer) / (p_mipmap_count); + float roughness4 = roughness * roughness; + roughness4 *= roughness4; + + float solid_angle_texel = 4.0 * Math_PI / float(6 * size * size); + + LocalVector<float> sample_directions; + sample_directions.resize(4 * sample_count); + + uint32_t index = 0; + float weight = 0.0; + for (uint32_t i = 0; i < sample_count; i++) { + Vector2 xi = hammersley(i, sample_count); + Vector3 dir = importance_sample_GGX(xi, roughness4); + Vector3 light_vec = (2.0 * dir.z * dir - Vector3(0.0, 0.0, 1.0)); + + if (light_vec.z < 0.0) { + continue; + } + + sample_directions[index * 4] = light_vec.x; + sample_directions[index * 4 + 1] = light_vec.y; + sample_directions[index * 4 + 2] = light_vec.z; + + float D = distribution_GGX(dir.z, roughness4); + float pdf = D * dir.z / (4.0 * dir.z) + 0.0001; + + float solid_angle_sample = 1.0 / (float(sample_count) * pdf + 0.0001); + + float mip_level = MAX(0.5 * log2(solid_angle_sample / solid_angle_texel) + float(MAX(1, p_layer - 3)), 1.0); + + sample_directions[index * 4 + 3] = mip_level; + weight += light_vec.z; + index++; + } + + glUniform4fv(cubemap_filter.shader.version_get_uniform(CubemapFilterShaderGLES3::SAMPLE_DIRECTIONS_MIP, cubemap_filter.shader_version, mode), sample_count, sample_directions.ptr()); + cubemap_filter.shader.version_set_uniform(CubemapFilterShaderGLES3::WEIGHT, weight, cubemap_filter.shader_version, mode); + cubemap_filter.shader.version_set_uniform(CubemapFilterShaderGLES3::SAMPLE_COUNT, index, cubemap_filter.shader_version, mode); + } + + for (int i = 0; i < 6; i++) { + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, p_dest_cubemap, p_layer); +#ifdef DEBUG_ENABLED + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) { + WARN_PRINT("Could not bind sky radiance face: " + itos(i) + ", status: " + GLES3::TextureStorage::get_singleton()->get_framebuffer_error(status)); + } +#endif + cubemap_filter.shader.version_set_uniform(CubemapFilterShaderGLES3::FACE_ID, i, cubemap_filter.shader_version, mode); + + glDrawArrays(GL_TRIANGLES, 0, 3); + } + glBindVertexArray(0); + glBindFramebuffer(GL_FRAMEBUFFER, GLES3::TextureStorage::system_fbo); +} + +#endif // GLES3_ENABLED diff --git a/drivers/gles3/effects/cubemap_filter.h b/drivers/gles3/effects/cubemap_filter.h new file mode 100644 index 0000000000..eaaa6f4075 --- /dev/null +++ b/drivers/gles3/effects/cubemap_filter.h @@ -0,0 +1,70 @@ +/**************************************************************************/ +/* cubemap_filter.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef CUBEMAP_FILTER_GLES3_H +#define CUBEMAP_FILTER_GLES3_H + +#ifdef GLES3_ENABLED + +#include "drivers/gles3/shaders/effects/cubemap_filter.glsl.gen.h" + +namespace GLES3 { + +class CubemapFilter { +private: + struct CMF { + CubemapFilterShaderGLES3 shader; + RID shader_version; + } cubemap_filter; + + static CubemapFilter *singleton; + + // Use for full-screen effects. Slightly more efficient than screen_quad as this eliminates pixel overdraw along the diagonal. + GLuint screen_triangle = 0; + GLuint screen_triangle_array = 0; + + uint32_t ggx_samples = 128; + +public: + static CubemapFilter *get_singleton() { + return singleton; + } + + CubemapFilter(); + ~CubemapFilter(); + + void filter_radiance(GLuint p_source_cubemap, GLuint p_dest_cubemap, GLuint p_dest_framebuffer, int p_source_size, int p_mipmap_count, int p_layer); +}; + +} //namespace GLES3 + +#endif // GLES3_ENABLED + +#endif // CUBEMAP_FILTER_GLES3_H diff --git a/drivers/gles3/rasterizer_gles3.cpp b/drivers/gles3/rasterizer_gles3.cpp index cee4f93b3d..767a394ce5 100644 --- a/drivers/gles3/rasterizer_gles3.cpp +++ b/drivers/gles3/rasterizer_gles3.cpp @@ -208,6 +208,7 @@ void RasterizerGLES3::finalize() { memdelete(fog); memdelete(post_effects); memdelete(glow); + memdelete(cubemap_filter); memdelete(copy_effects); memdelete(light_storage); memdelete(particles_storage); @@ -354,6 +355,7 @@ RasterizerGLES3::RasterizerGLES3() { particles_storage = memnew(GLES3::ParticlesStorage); light_storage = memnew(GLES3::LightStorage); copy_effects = memnew(GLES3::CopyEffects); + cubemap_filter = memnew(GLES3::CubemapFilter); glow = memnew(GLES3::Glow); post_effects = memnew(GLES3::PostEffects); gi = memnew(GLES3::GI); diff --git a/drivers/gles3/rasterizer_gles3.h b/drivers/gles3/rasterizer_gles3.h index 8d52dc2365..09d3c7bd52 100644 --- a/drivers/gles3/rasterizer_gles3.h +++ b/drivers/gles3/rasterizer_gles3.h @@ -34,6 +34,7 @@ #ifdef GLES3_ENABLED #include "effects/copy_effects.h" +#include "effects/cubemap_filter.h" #include "effects/glow.h" #include "effects/post_effects.h" #include "environment/fog.h" @@ -70,6 +71,7 @@ protected: GLES3::GI *gi = nullptr; GLES3::Fog *fog = nullptr; GLES3::CopyEffects *copy_effects = nullptr; + GLES3::CubemapFilter *cubemap_filter = nullptr; GLES3::Glow *glow = nullptr; GLES3::PostEffects *post_effects = nullptr; RasterizerCanvasGLES3 *canvas = nullptr; diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp index b8cc3928eb..ecb563214c 100644 --- a/drivers/gles3/rasterizer_scene_gles3.cpp +++ b/drivers/gles3/rasterizer_scene_gles3.cpp @@ -65,7 +65,7 @@ RenderGeometryInstance *RasterizerSceneGLES3::geometry_instance_create(RID p_bas } uint32_t RasterizerSceneGLES3::geometry_instance_get_pair_mask() { - return (1 << RS::INSTANCE_LIGHT); + return ((1 << RS::INSTANCE_LIGHT) | (1 << RS::INSTANCE_REFLECTION_PROBE)); } void RasterizerSceneGLES3::GeometryInstanceGLES3::pair_light_instances(const RID *p_light_instances, uint32_t p_light_instance_count) { @@ -97,6 +97,14 @@ void RasterizerSceneGLES3::GeometryInstanceGLES3::pair_light_instances(const RID } } +void RasterizerSceneGLES3::GeometryInstanceGLES3::pair_reflection_probe_instances(const RID *p_reflection_probe_instances, uint32_t p_reflection_probe_instance_count) { + paired_reflection_probes.clear(); + + for (uint32_t i = 0; i < p_reflection_probe_instance_count; i++) { + paired_reflection_probes.push_back(p_reflection_probe_instances[i]); + } +} + void RasterizerSceneGLES3::geometry_instance_free(RenderGeometryInstance *p_geometry_instance) { GeometryInstanceGLES3 *ginstance = static_cast<GeometryInstanceGLES3 *>(p_geometry_instance); ERR_FAIL_NULL(ginstance); @@ -854,6 +862,7 @@ void RasterizerSceneGLES3::_draw_sky(RID p_env, const Projection &p_projection, } void RasterizerSceneGLES3::_update_sky_radiance(RID p_env, const Projection &p_projection, const Transform3D &p_transform, float p_sky_energy_multiplier) { + GLES3::CubemapFilter *cubemap_filter = GLES3::CubemapFilter::get_singleton(); GLES3::MaterialStorage *material_storage = GLES3::MaterialStorage::get_singleton(); ERR_FAIL_COND(p_env.is_null()); @@ -970,10 +979,10 @@ void RasterizerSceneGLES3::_update_sky_radiance(RID p_env, const Projection &p_p if (update_single_frame) { for (int i = 0; i < max_processing_layer; i++) { - _filter_sky_radiance(sky, i); + cubemap_filter->filter_radiance(sky->raw_radiance, sky->radiance, sky->radiance_framebuffer, sky->radiance_size, sky->mipmap_count, i); } } else { - _filter_sky_radiance(sky, 0); //Just copy over the first mipmap + cubemap_filter->filter_radiance(sky->raw_radiance, sky->radiance, sky->radiance_framebuffer, sky->radiance_size, sky->mipmap_count, 0); // Just copy over the first mipmap. } sky->processing_layer = 1; sky->baked_exposure = p_sky_energy_multiplier; @@ -984,135 +993,11 @@ void RasterizerSceneGLES3::_update_sky_radiance(RID p_env, const Projection &p_p scene_state.set_gl_cull_mode(GLES3::SceneShaderData::CULL_DISABLED); scene_state.enable_gl_blend(false); - _filter_sky_radiance(sky, sky->processing_layer); + cubemap_filter->filter_radiance(sky->raw_radiance, sky->radiance, sky->radiance_framebuffer, sky->radiance_size, sky->mipmap_count, sky->processing_layer); sky->processing_layer++; } } -} - -// Helper functions for IBL filtering - -Vector3 importance_sample_GGX(Vector2 xi, float roughness4) { - // Compute distribution direction - float phi = 2.0 * Math_PI * xi.x; - float cos_theta = sqrt((1.0 - xi.y) / (1.0 + (roughness4 - 1.0) * xi.y)); - float sin_theta = sqrt(1.0 - cos_theta * cos_theta); - - // Convert to spherical direction - Vector3 half_vector; - half_vector.x = sin_theta * cos(phi); - half_vector.y = sin_theta * sin(phi); - half_vector.z = cos_theta; - - return half_vector; -} - -float distribution_GGX(float NdotH, float roughness4) { - float NdotH2 = NdotH * NdotH; - float denom = (NdotH2 * (roughness4 - 1.0) + 1.0); - denom = Math_PI * denom * denom; - - return roughness4 / denom; -} - -float radical_inverse_vdC(uint32_t bits) { - bits = (bits << 16) | (bits >> 16); - bits = ((bits & 0x55555555) << 1) | ((bits & 0xAAAAAAAA) >> 1); - bits = ((bits & 0x33333333) << 2) | ((bits & 0xCCCCCCCC) >> 2); - bits = ((bits & 0x0F0F0F0F) << 4) | ((bits & 0xF0F0F0F0) >> 4); - bits = ((bits & 0x00FF00FF) << 8) | ((bits & 0xFF00FF00) >> 8); - - return float(bits) * 2.3283064365386963e-10; -} - -Vector2 hammersley(uint32_t i, uint32_t N) { - return Vector2(float(i) / float(N), radical_inverse_vdC(i)); -} - -void RasterizerSceneGLES3::_filter_sky_radiance(Sky *p_sky, int p_base_layer) { - GLES3::MaterialStorage *material_storage = GLES3::MaterialStorage::get_singleton(); - - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_CUBE_MAP, p_sky->raw_radiance); - glBindFramebuffer(GL_FRAMEBUFFER, p_sky->radiance_framebuffer); - - CubemapFilterShaderGLES3::ShaderVariant mode = CubemapFilterShaderGLES3::MODE_DEFAULT; - - if (p_base_layer == 0) { - glGenerateMipmap(GL_TEXTURE_CUBE_MAP); - // Copy over base layer without filtering. - mode = CubemapFilterShaderGLES3::MODE_COPY; - } - - int size = p_sky->radiance_size >> p_base_layer; - glViewport(0, 0, size, size); - glBindVertexArray(sky_globals.screen_triangle_array); - - bool success = material_storage->shaders.cubemap_filter_shader.version_bind_shader(scene_globals.cubemap_filter_shader_version, mode); - if (!success) { - return; - } - - if (p_base_layer > 0) { - const uint32_t sample_counts[4] = { 1, sky_globals.ggx_samples / 4, sky_globals.ggx_samples / 2, sky_globals.ggx_samples }; - uint32_t sample_count = sample_counts[MIN(3, p_base_layer)]; - - float roughness = float(p_base_layer) / (p_sky->mipmap_count); - float roughness4 = roughness * roughness; - roughness4 *= roughness4; - - float solid_angle_texel = 4.0 * Math_PI / float(6 * size * size); - - LocalVector<float> sample_directions; - sample_directions.resize(4 * sample_count); - - uint32_t index = 0; - float weight = 0.0; - for (uint32_t i = 0; i < sample_count; i++) { - Vector2 xi = hammersley(i, sample_count); - Vector3 dir = importance_sample_GGX(xi, roughness4); - Vector3 light_vec = (2.0 * dir.z * dir - Vector3(0.0, 0.0, 1.0)); - - if (light_vec.z < 0.0) { - continue; - } - - sample_directions[index * 4] = light_vec.x; - sample_directions[index * 4 + 1] = light_vec.y; - sample_directions[index * 4 + 2] = light_vec.z; - - float D = distribution_GGX(dir.z, roughness4); - float pdf = D * dir.z / (4.0 * dir.z) + 0.0001; - - float solid_angle_sample = 1.0 / (float(sample_count) * pdf + 0.0001); - - float mip_level = MAX(0.5 * log2(solid_angle_sample / solid_angle_texel) + float(MAX(1, p_base_layer - 3)), 1.0); - - sample_directions[index * 4 + 3] = mip_level; - weight += light_vec.z; - index++; - } - - glUniform4fv(material_storage->shaders.cubemap_filter_shader.version_get_uniform(CubemapFilterShaderGLES3::SAMPLE_DIRECTIONS_MIP, scene_globals.cubemap_filter_shader_version, mode), sample_count, sample_directions.ptr()); - material_storage->shaders.cubemap_filter_shader.version_set_uniform(CubemapFilterShaderGLES3::WEIGHT, weight, scene_globals.cubemap_filter_shader_version, mode); - material_storage->shaders.cubemap_filter_shader.version_set_uniform(CubemapFilterShaderGLES3::SAMPLE_COUNT, index, scene_globals.cubemap_filter_shader_version, mode); - } - - for (int i = 0; i < 6; i++) { - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, p_sky->radiance, p_base_layer); -#ifdef DEBUG_ENABLED - GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - if (status != GL_FRAMEBUFFER_COMPLETE) { - WARN_PRINT("Could not bind sky radiance face: " + itos(i) + ", status: " + GLES3::TextureStorage::get_singleton()->get_framebuffer_error(status)); - } -#endif - material_storage->shaders.cubemap_filter_shader.version_set_uniform(CubemapFilterShaderGLES3::FACE_ID, i, scene_globals.cubemap_filter_shader_version, mode); - - glDrawArrays(GL_TRIANGLES, 0, 3); - } - glBindVertexArray(0); - glViewport(0, 0, p_sky->screen_size.x, p_sky->screen_size.y); - glBindFramebuffer(GL_FRAMEBUFFER, GLES3::TextureStorage::system_fbo); + glViewport(0, 0, sky->screen_size.x, sky->screen_size.y); } Ref<Image> RasterizerSceneGLES3::sky_bake_panorama(RID p_sky, float p_energy, bool p_bake_irradiance, const Size2i &p_size) { @@ -1334,6 +1219,7 @@ _FORCE_INLINE_ static uint32_t _indices_to_primitives(RS::PrimitiveType p_primit } void RasterizerSceneGLES3::_fill_render_list(RenderListType p_render_list, const RenderDataGLES3 *p_render_data, PassMode p_pass_mode, bool p_append) { GLES3::MeshStorage *mesh_storage = GLES3::MeshStorage::get_singleton(); + GLES3::LightStorage *light_storage = GLES3::LightStorage::get_singleton(); if (p_render_list == RENDER_LIST_OPAQUE) { scene_state.used_screen_texture = false; @@ -1392,22 +1278,24 @@ void RasterizerSceneGLES3::_fill_render_list(RenderListType p_render_list, const inst->light_passes.clear(); inst->spot_light_gl_cache.clear(); inst->omni_light_gl_cache.clear(); + inst->reflection_probes_local_transform_cache.clear(); + inst->reflection_probe_rid_cache.clear(); uint64_t current_frame = RSG::rasterizer->get_frame_number(); if (inst->paired_omni_light_count) { for (uint32_t j = 0; j < inst->paired_omni_light_count; j++) { RID light_instance = inst->paired_omni_lights[j]; - if (GLES3::LightStorage::get_singleton()->light_instance_get_render_pass(light_instance) != current_frame) { + if (light_storage->light_instance_get_render_pass(light_instance) != current_frame) { continue; } - RID light = GLES3::LightStorage::get_singleton()->light_instance_get_base_light(light_instance); - int32_t shadow_id = GLES3::LightStorage::get_singleton()->light_instance_get_shadow_id(light_instance); + RID light = light_storage->light_instance_get_base_light(light_instance); + int32_t shadow_id = light_storage->light_instance_get_shadow_id(light_instance); - if (GLES3::LightStorage::get_singleton()->light_has_shadow(light) && shadow_id >= 0) { + if (light_storage->light_has_shadow(light) && shadow_id >= 0) { // Skip static lights when a lightmap is used. - if (!inst->lightmap_instance.is_valid() || GLES3::LightStorage::get_singleton()->light_get_bake_mode(light) != RenderingServer::LIGHT_BAKE_STATIC) { + if (!inst->lightmap_instance.is_valid() || light_storage->light_get_bake_mode(light) != RenderingServer::LIGHT_BAKE_STATIC) { GeometryInstanceGLES3::LightPass pass; - pass.light_id = GLES3::LightStorage::get_singleton()->light_instance_get_gl_id(light_instance); + pass.light_id = light_storage->light_instance_get_gl_id(light_instance); pass.shadow_id = shadow_id; pass.light_instance_rid = light_instance; pass.is_omni = true; @@ -1415,7 +1303,7 @@ void RasterizerSceneGLES3::_fill_render_list(RenderListType p_render_list, const } } else { // Lights without shadow can all go in base pass. - inst->omni_light_gl_cache.push_back((uint32_t)GLES3::LightStorage::get_singleton()->light_instance_get_gl_id(light_instance)); + inst->omni_light_gl_cache.push_back((uint32_t)light_storage->light_instance_get_gl_id(light_instance)); } } } @@ -1423,24 +1311,42 @@ void RasterizerSceneGLES3::_fill_render_list(RenderListType p_render_list, const if (inst->paired_spot_light_count) { for (uint32_t j = 0; j < inst->paired_spot_light_count; j++) { RID light_instance = inst->paired_spot_lights[j]; - if (GLES3::LightStorage::get_singleton()->light_instance_get_render_pass(light_instance) != current_frame) { + if (light_storage->light_instance_get_render_pass(light_instance) != current_frame) { continue; } - RID light = GLES3::LightStorage::get_singleton()->light_instance_get_base_light(light_instance); - int32_t shadow_id = GLES3::LightStorage::get_singleton()->light_instance_get_shadow_id(light_instance); + RID light = light_storage->light_instance_get_base_light(light_instance); + int32_t shadow_id = light_storage->light_instance_get_shadow_id(light_instance); - if (GLES3::LightStorage::get_singleton()->light_has_shadow(light) && shadow_id >= 0) { + if (light_storage->light_has_shadow(light) && shadow_id >= 0) { // Skip static lights when a lightmap is used. - if (!inst->lightmap_instance.is_valid() || GLES3::LightStorage::get_singleton()->light_get_bake_mode(light) != RenderingServer::LIGHT_BAKE_STATIC) { + if (!inst->lightmap_instance.is_valid() || light_storage->light_get_bake_mode(light) != RenderingServer::LIGHT_BAKE_STATIC) { GeometryInstanceGLES3::LightPass pass; - pass.light_id = GLES3::LightStorage::get_singleton()->light_instance_get_gl_id(light_instance); + pass.light_id = light_storage->light_instance_get_gl_id(light_instance); pass.shadow_id = shadow_id; pass.light_instance_rid = light_instance; inst->light_passes.push_back(pass); } } else { // Lights without shadow can all go in base pass. - inst->spot_light_gl_cache.push_back((uint32_t)GLES3::LightStorage::get_singleton()->light_instance_get_gl_id(light_instance)); + inst->spot_light_gl_cache.push_back((uint32_t)light_storage->light_instance_get_gl_id(light_instance)); + } + } + } + + if (p_render_data->reflection_probe.is_null() && inst->paired_reflection_probes.size() > 0) { + // Do not include if we're rendering reflection probes. + // We only support two probes for now and we handle them first come, first serve. + // This should be improved one day, at minimum the list should be sorted by priority. + + for (uint32_t pi = 0; pi < inst->paired_reflection_probes.size(); pi++) { + RID probe_instance = inst->paired_reflection_probes[pi]; + RID atlas = light_storage->reflection_probe_instance_get_atlas(probe_instance); + RID probe = light_storage->reflection_probe_instance_get_probe(probe_instance); + uint32_t reflection_mask = light_storage->reflection_probe_get_reflection_mask(probe); + if (atlas.is_valid() && (inst->layer_mask & reflection_mask)) { + Transform3D local_matrix = p_render_data->inv_cam_transform * light_storage->reflection_probe_instance_get_transform(probe_instance); + inst->reflection_probes_local_transform_cache.push_back(local_matrix.affine_inverse()); + inst->reflection_probe_rid_cache.push_back(probe_instance); } } } @@ -2321,20 +2227,21 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_ RENDER_TIMESTAMP("Setup 3D Scene"); bool apply_color_adjustments_in_post = false; + bool is_reflection_probe = p_reflection_probe.is_valid(); - Ref<RenderSceneBuffersGLES3> rb; - if (p_render_buffers.is_valid()) { - rb = p_render_buffers; - ERR_FAIL_COND(rb.is_null()); + Ref<RenderSceneBuffersGLES3> rb = p_render_buffers; + ERR_FAIL_COND(rb.is_null()); - if (rb->get_scaling_3d_mode() != RS::VIEWPORT_SCALING_3D_MODE_OFF) { - // If we're scaling, we apply tonemapping etc. in post, so disable it during rendering - apply_color_adjustments_in_post = true; - } + if (rb->get_scaling_3d_mode() != RS::VIEWPORT_SCALING_3D_MODE_OFF) { + // If we're scaling, we apply tonemapping etc. in post, so disable it during rendering + apply_color_adjustments_in_post = true; } - GLES3::RenderTarget *rt = texture_storage->get_render_target(rb->render_target); - ERR_FAIL_NULL(rt); + GLES3::RenderTarget *rt = nullptr; // No render target for reflection probe + if (!is_reflection_probe) { + rt = texture_storage->get_render_target(rb->render_target); + ERR_FAIL_NULL(rt); + } bool glow_enabled = false; if (p_environment.is_valid() && rb.is_valid()) { @@ -2351,7 +2258,7 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_ RenderDataGLES3 render_data; { render_data.render_buffers = rb; - render_data.transparent_bg = rb.is_valid() ? rt->is_transparent : false; + render_data.transparent_bg = rt ? rt->is_transparent : false; // Our first camera is used by default render_data.cam_transform = p_camera_data->main_transform; render_data.inv_cam_transform = render_data.cam_transform.affine_inverse(); @@ -2381,7 +2288,7 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_ // this should be the same for all cameras.. render_data.lod_distance_multiplier = p_camera_data->main_projection.get_lod_multiplier(); - if (rt->color_type == GL_UNSIGNED_INT_2_10_10_10_REV && glow_enabled) { + if (rt != nullptr && rt->color_type == GL_UNSIGNED_INT_2_10_10_10_REV && glow_enabled) { // As our output is in sRGB and we're using 10bit color space, we can fake a little HDR to do glow... render_data.luminance_multiplier = 0.25; } else { @@ -2415,7 +2322,7 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_ glBindBufferBase(GL_UNIFORM_BUFFER, SCENE_GLOBALS_UNIFORM_LOCATION, global_buffer); Color clear_color; - if (p_render_buffers.is_valid()) { + if (!is_reflection_probe && rb->render_target.is_valid()) { clear_color = texture_storage->render_target_get_clear_request_color(rb->render_target); } else { clear_color = texture_storage->get_default_clear_color(); @@ -2448,9 +2355,9 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_ scene_state.ubo.emissive_exposure_normalization = -1.0; // Use default exposure normalization. - bool flip_y = !render_data.reflection_probe.is_valid(); + bool flip_y = !is_reflection_probe; - if (rt->overridden.color.is_valid()) { + if (rt && rt->overridden.color.is_valid()) { // If we've overridden the render target's color texture, then don't render upside down. // We're probably rendering directly to an XR device. flip_y = false; @@ -2462,7 +2369,7 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_ _render_shadows(&render_data, screen_size); _setup_lights(&render_data, true, render_data.directional_light_count, render_data.omni_light_count, render_data.spot_light_count, render_data.directional_shadow_count); - _setup_environment(&render_data, render_data.reflection_probe.is_valid(), screen_size, flip_y, clear_color, false); + _setup_environment(&render_data, is_reflection_probe, screen_size, flip_y, clear_color, false); _fill_render_list(RENDER_LIST_OPAQUE, &render_data, PASS_MODE_COLOR); render_list[RENDER_LIST_OPAQUE].sort_by_key(); @@ -2522,7 +2429,7 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_ if (draw_sky || draw_sky_fog_only || environment_get_reflection_source(render_data.environment) == RS::ENV_REFLECTION_SOURCE_SKY || environment_get_ambient_source(render_data.environment) == RS::ENV_AMBIENT_SOURCE_SKY) { RENDER_TIMESTAMP("Setup Sky"); Projection projection = render_data.cam_projection; - if (render_data.reflection_probe.is_valid()) { + if (is_reflection_probe) { Projection correction; correction.set_depth_correction(true, true, false); projection = correction * render_data.cam_projection; @@ -2543,7 +2450,12 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_ } } - GLuint fbo = rb->get_render_fbo(); + GLuint fbo = 0; + if (is_reflection_probe) { + fbo = GLES3::LightStorage::get_singleton()->reflection_probe_instance_get_framebuffer(render_data.reflection_probe, render_data.reflection_probe_pass); + } else { + fbo = rb->get_render_fbo(); + } glBindFramebuffer(GL_FRAMEBUFFER, fbo); glViewport(0, 0, rb->internal_size.x, rb->internal_size.y); @@ -2664,10 +2576,17 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_ scene_state.enable_gl_blend(false); scene_state.set_gl_cull_mode(GLES3::SceneShaderData::CULL_BACK); - _draw_sky(render_data.environment, render_data.cam_projection, render_data.cam_transform, sky_energy_multiplier, render_data.luminance_multiplier, p_camera_data->view_count > 1, flip_y, apply_color_adjustments_in_post); + Projection projection = render_data.cam_projection; + if (is_reflection_probe) { + Projection correction; + correction.columns[1][1] = -1.0; + projection = correction * render_data.cam_projection; + } + + _draw_sky(render_data.environment, projection, render_data.cam_transform, sky_energy_multiplier, render_data.luminance_multiplier, p_camera_data->view_count > 1, flip_y, apply_color_adjustments_in_post); } - if (scene_state.used_screen_texture || scene_state.used_depth_texture) { + if (rt && (scene_state.used_screen_texture || scene_state.used_depth_texture)) { Size2i size; GLuint backbuffer_fbo = 0; GLuint backbuffer = 0; @@ -2725,7 +2644,7 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_ glFrontFace(GL_CCW); } - if (rb.is_valid()) { + if (!is_reflection_probe && rb.is_valid()) { _render_buffers_debug_draw(rb, p_shadow_atlas, fbo); } @@ -2733,9 +2652,11 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_ scene_state.reset_gl_state(); glUseProgram(0); - _render_post_processing(&render_data); + if (!is_reflection_probe) { + _render_post_processing(&render_data); - texture_storage->render_target_disable_clear_request(rb->render_target); + texture_storage->render_target_disable_clear_request(rb->render_target); + } glActiveTexture(GL_TEXTURE0); } @@ -3203,6 +3124,14 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params, spec_constants |= SceneShaderGLES3::DISABLE_LIGHT_DIRECTIONAL; } + if (inst->reflection_probe_rid_cache.size() == 0) { + // We don't have any probes. + spec_constants |= SceneShaderGLES3::DISABLE_REFLECTION_PROBE; + } else if (inst->reflection_probe_rid_cache.size() > 1) { + // We have a second probe. + spec_constants |= SceneShaderGLES3::SECOND_REFLECTION_PROBE; + } + if (inst->lightmap_instance.is_valid()) { spec_constants |= SceneShaderGLES3::USE_LIGHTMAP; @@ -3224,6 +3153,7 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params, spec_constants |= SceneShaderGLES3::DISABLE_LIGHT_SPOT; spec_constants |= SceneShaderGLES3::DISABLE_LIGHT_DIRECTIONAL; spec_constants |= SceneShaderGLES3::DISABLE_LIGHTMAP; + spec_constants |= SceneShaderGLES3::DISABLE_REFLECTION_PROBE; } if (uses_additive_lighting) { @@ -3383,6 +3313,52 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params, } } + // Pass in reflection probe data + if constexpr (p_pass_mode == PASS_MODE_COLOR || p_pass_mode == PASS_MODE_COLOR_TRANSPARENT) { + if (pass == 0 && inst->reflection_probe_rid_cache.size() > 0) { + GLES3::Config *config = GLES3::Config::get_singleton(); + GLES3::LightStorage *light_storage = GLES3::LightStorage::get_singleton(); + + // Setup first probe. + { + RID probe_rid = light_storage->reflection_probe_instance_get_probe(inst->reflection_probe_rid_cache[0]); + GLES3::ReflectionProbe *probe = light_storage->get_reflection_probe(probe_rid); + + material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::REFPROBE1_USE_BOX_PROJECT, probe->box_projection, shader->version, instance_variant, spec_constants); + material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::REFPROBE1_BOX_EXTENTS, probe->size * 0.5, shader->version, instance_variant, spec_constants); + material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::REFPROBE1_BOX_OFFSET, probe->origin_offset, shader->version, instance_variant, spec_constants); + material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::REFPROBE1_EXTERIOR, !probe->interior, shader->version, instance_variant, spec_constants); + material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::REFPROBE1_INTENSITY, probe->intensity, shader->version, instance_variant, spec_constants); + material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::REFPROBE1_AMBIENT_MODE, int(probe->ambient_mode), shader->version, instance_variant, spec_constants); + material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::REFPROBE1_AMBIENT_COLOR, probe->ambient_color * probe->ambient_color_energy, shader->version, instance_variant, spec_constants); + material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::REFPROBE1_LOCAL_MATRIX, inst->reflection_probes_local_transform_cache[0], shader->version, instance_variant, spec_constants); + + glActiveTexture(GL_TEXTURE0 + config->max_texture_image_units - 7); + glBindTexture(GL_TEXTURE_CUBE_MAP, light_storage->reflection_probe_instance_get_texture(inst->reflection_probe_rid_cache[0])); + } + + if (inst->reflection_probe_rid_cache.size() > 1) { + // Setup second probe. + RID probe_rid = light_storage->reflection_probe_instance_get_probe(inst->reflection_probe_rid_cache[1]); + GLES3::ReflectionProbe *probe = light_storage->get_reflection_probe(probe_rid); + + material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::REFPROBE2_USE_BOX_PROJECT, probe->box_projection, shader->version, instance_variant, spec_constants); + material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::REFPROBE2_BOX_EXTENTS, probe->size * 0.5, shader->version, instance_variant, spec_constants); + material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::REFPROBE2_BOX_OFFSET, probe->origin_offset, shader->version, instance_variant, spec_constants); + material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::REFPROBE2_EXTERIOR, !probe->interior, shader->version, instance_variant, spec_constants); + material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::REFPROBE2_INTENSITY, probe->intensity, shader->version, instance_variant, spec_constants); + material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::REFPROBE2_AMBIENT_MODE, int(probe->ambient_mode), shader->version, instance_variant, spec_constants); + material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::REFPROBE2_AMBIENT_COLOR, probe->ambient_color * probe->ambient_color_energy, shader->version, instance_variant, spec_constants); + material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::REFPROBE2_LOCAL_MATRIX, inst->reflection_probes_local_transform_cache[1], shader->version, instance_variant, spec_constants); + + glActiveTexture(GL_TEXTURE0 + config->max_texture_image_units - 8); + glBindTexture(GL_TEXTURE_CUBE_MAP, light_storage->reflection_probe_instance_get_texture(inst->reflection_probe_rid_cache[1])); + + spec_constants |= SceneShaderGLES3::SECOND_REFLECTION_PROBE; + } + } + } + material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::WORLD_TRANSFORM, world_transform, shader->version, instance_variant, spec_constants); { GLES3::Mesh::Surface *s = reinterpret_cast<GLES3::Mesh::Surface *>(surf->surface); @@ -4074,6 +4050,7 @@ RasterizerSceneGLES3::RasterizerSceneGLES3() { global_defines += "\n#define MAX_LIGHT_DATA_STRUCTS " + itos(config->max_renderable_lights) + "\n"; global_defines += "\n#define MAX_DIRECTIONAL_LIGHT_DATA_STRUCTS " + itos(MAX_DIRECTIONAL_LIGHTS) + "\n"; global_defines += "\n#define MAX_FORWARD_LIGHTS " + itos(config->max_lights_per_object) + "u\n"; + global_defines += "\n#define MAX_ROUGHNESS_LOD " + itos(sky_globals.roughness_layers - 1) + ".0\n"; material_storage->shaders.scene_shader.initialize(global_defines); scene_globals.shader_default_version = material_storage->shaders.scene_shader.version_create(); material_storage->shaders.scene_shader.version_bind_shader(scene_globals.shader_default_version, SceneShaderGLES3::MODE_COLOR); @@ -4129,7 +4106,6 @@ void fragment() { { // Initialize Sky stuff sky_globals.roughness_layers = GLOBAL_GET("rendering/reflections/sky_reflections/roughness_layers"); - sky_globals.ggx_samples = GLOBAL_GET("rendering/reflections/sky_reflections/ggx_samples"); String global_defines; global_defines += "#define MAX_GLOBAL_SHADER_UNIFORMS 256\n"; // TODO: this is arbitrary for now @@ -4139,13 +4115,6 @@ void fragment() { } { - String global_defines; - global_defines += "\n#define MAX_SAMPLE_COUNT " + itos(sky_globals.ggx_samples) + "\n"; - material_storage->shaders.cubemap_filter_shader.initialize(global_defines); - scene_globals.cubemap_filter_shader_version = material_storage->shaders.cubemap_filter_shader.version_create(); - } - - { sky_globals.default_shader = material_storage->shader_allocate(); material_storage->shader_initialize(sky_globals.default_shader); @@ -4234,7 +4203,6 @@ RasterizerSceneGLES3::~RasterizerSceneGLES3() { // Scene Shader GLES3::MaterialStorage::get_singleton()->shaders.scene_shader.version_free(scene_globals.shader_default_version); - GLES3::MaterialStorage::get_singleton()->shaders.cubemap_filter_shader.version_free(scene_globals.cubemap_filter_shader_version); RSG::material_storage->material_free(scene_globals.default_material); RSG::material_storage->shader_free(scene_globals.default_shader); @@ -4250,7 +4218,6 @@ RasterizerSceneGLES3::~RasterizerSceneGLES3() { RSG::material_storage->shader_free(sky_globals.fog_shader); GLES3::Utilities::get_singleton()->buffer_free_data(sky_globals.screen_triangle); glDeleteVertexArrays(1, &sky_globals.screen_triangle_array); - glDeleteTextures(1, &sky_globals.radical_inverse_vdc_cache_tex); GLES3::Utilities::get_singleton()->buffer_free_data(sky_globals.directional_light_buffer); memdelete_arr(sky_globals.directional_lights); memdelete_arr(sky_globals.last_frame_directional_lights); diff --git a/drivers/gles3/rasterizer_scene_gles3.h b/drivers/gles3/rasterizer_scene_gles3.h index 71cd152520..cc479bd4e9 100644 --- a/drivers/gles3/rasterizer_scene_gles3.h +++ b/drivers/gles3/rasterizer_scene_gles3.h @@ -37,7 +37,7 @@ #include "core/templates/paged_allocator.h" #include "core/templates/rid_owner.h" #include "core/templates/self_list.h" -#include "drivers/gles3/shaders/cubemap_filter.glsl.gen.h" +#include "drivers/gles3/shaders/effects/cubemap_filter.glsl.gen.h" #include "drivers/gles3/shaders/sky.glsl.gen.h" #include "scene/resources/mesh.h" #include "servers/rendering/renderer_compositor.h" @@ -157,7 +157,6 @@ private: RID shader_default_version; RID default_material; RID default_shader; - RID cubemap_filter_shader_version; RID overdraw_material; RID overdraw_shader; } scene_globals; @@ -314,6 +313,10 @@ private: LocalVector<uint32_t> omni_light_gl_cache; LocalVector<uint32_t> spot_light_gl_cache; + LocalVector<RID> paired_reflection_probes; + LocalVector<RID> reflection_probe_rid_cache; + LocalVector<Transform3D> reflection_probes_local_transform_cache; + RID lightmap_instance; Rect2 lightmap_uv_scale; uint32_t lightmap_slice_index; @@ -331,7 +334,7 @@ private: virtual void set_lightmap_capture(const Color *p_sh9) override; virtual void pair_light_instances(const RID *p_light_instances, uint32_t p_light_instance_count) override; - virtual void pair_reflection_probe_instances(const RID *p_reflection_probe_instances, uint32_t p_reflection_probe_instance_count) override {} + virtual void pair_reflection_probe_instances(const RID *p_reflection_probe_instances, uint32_t p_reflection_probe_instance_count) override; virtual void pair_decal_instances(const RID *p_decal_instances, uint32_t p_decal_instance_count) override {} virtual void pair_voxel_gi_instances(const RID *p_voxel_gi_instances, uint32_t p_voxel_gi_instance_count) override {} @@ -686,10 +689,8 @@ protected: RID fog_shader; GLuint screen_triangle = 0; GLuint screen_triangle_array = 0; - GLuint radical_inverse_vdc_cache_tex = 0; uint32_t max_directional_lights = 4; uint32_t roughness_layers = 8; - uint32_t ggx_samples = 128; } sky_globals; struct Sky { @@ -733,7 +734,6 @@ protected: void _invalidate_sky(Sky *p_sky); void _update_dirty_skys(); void _update_sky_radiance(RID p_env, const Projection &p_projection, const Transform3D &p_transform, float p_sky_energy_multiplier); - void _filter_sky_radiance(Sky *p_sky, int p_base_layer); void _draw_sky(RID p_env, const Projection &p_projection, const Transform3D &p_transform, float p_sky_energy_multiplier, float p_luminance_multiplier, bool p_use_multiview, bool p_flip_y, bool p_apply_color_adjustments_in_post); void _free_sky_data(Sky *p_sky); diff --git a/drivers/gles3/shaders/SCsub b/drivers/gles3/shaders/SCsub index 0292b5d519..e70912cb4d 100644 --- a/drivers/gles3/shaders/SCsub +++ b/drivers/gles3/shaders/SCsub @@ -18,7 +18,6 @@ if "GLES3_GLSL" in env["BUILDERS"]: env.GLES3_GLSL("canvas.glsl") env.GLES3_GLSL("scene.glsl") env.GLES3_GLSL("sky.glsl") - env.GLES3_GLSL("cubemap_filter.glsl") env.GLES3_GLSL("canvas_occlusion.glsl") env.GLES3_GLSL("canvas_sdf.glsl") env.GLES3_GLSL("particles.glsl") diff --git a/drivers/gles3/shaders/cubemap_filter.glsl b/drivers/gles3/shaders/effects/cubemap_filter.glsl index 6fcb23204d..6fcb23204d 100644 --- a/drivers/gles3/shaders/cubemap_filter.glsl +++ b/drivers/gles3/shaders/effects/cubemap_filter.glsl diff --git a/drivers/gles3/shaders/scene.glsl b/drivers/gles3/shaders/scene.glsl index 8bf844991d..36bbca8728 100644 --- a/drivers/gles3/shaders/scene.glsl +++ b/drivers/gles3/shaders/scene.glsl @@ -12,6 +12,7 @@ DISABLE_LIGHTMAP = false DISABLE_LIGHT_DIRECTIONAL = false DISABLE_LIGHT_OMNI = false DISABLE_LIGHT_SPOT = false +DISABLE_REFLECTION_PROBE = true DISABLE_FOG = false USE_DEPTH_FOG = false USE_RADIANCE_MAP = true @@ -34,6 +35,7 @@ APPLY_TONEMAPPING = true ADDITIVE_OMNI = false ADDITIVE_SPOT = false RENDER_MATERIAL = false +SECOND_REFLECTION_PROBE = false #[vertex] @@ -568,8 +570,11 @@ void main() { 1-color correction // In tonemap_inc.glsl 2-radiance 3-shadow +4-lightmap textures 5-screen 6-depth +7-reflection probe 1 +8-reflection probe 2 */ @@ -626,7 +631,39 @@ in highp vec4 shadow_coord4; uniform samplerCube radiance_map; // texunit:-2 -#endif +#endif // USE_RADIANCE_MAP + +#ifndef DISABLE_REFLECTION_PROBE + +#define REFLECTION_PROBE_MAX_LOD 8.0 + +uniform bool refprobe1_use_box_project; +uniform highp vec3 refprobe1_box_extents; +uniform vec3 refprobe1_box_offset; +uniform highp mat4 refprobe1_local_matrix; +uniform bool refprobe1_exterior; +uniform float refprobe1_intensity; +uniform int refprobe1_ambient_mode; +uniform vec4 refprobe1_ambient_color; + +uniform samplerCube refprobe1_texture; // texunit:-7 + +#ifdef SECOND_REFLECTION_PROBE + +uniform bool refprobe2_use_box_project; +uniform highp vec3 refprobe2_box_extents; +uniform vec3 refprobe2_box_offset; +uniform highp mat4 refprobe2_local_matrix; +uniform bool refprobe2_exterior; +uniform float refprobe2_intensity; +uniform int refprobe2_ambient_mode; +uniform vec4 refprobe2_ambient_color; + +uniform samplerCube refprobe2_texture; // texunit:-8 + +#endif // SECOND_REFLECTION_PROBE + +#endif // DISABLE_REFLECTION_PROBE layout(std140) uniform GlobalShaderUniformData { //ubo:1 vec4 global_shader_uniforms[MAX_GLOBAL_SHADER_UNIFORMS]; @@ -1289,6 +1326,90 @@ vec4 fog_process(vec3 vertex) { return vec4(fog_color, fog_amount); } +#ifndef DISABLE_REFLECTION_PROBE + +#define REFLECTION_AMBIENT_DISABLED 0 +#define REFLECTION_AMBIENT_ENVIRONMENT 1 +#define REFLECTION_AMBIENT_COLOR 2 + +void reflection_process(samplerCube reflection_map, + vec3 normal, vec3 vertex, + mat4 local_matrix, + bool use_box_project, vec3 box_extents, vec3 box_offset, + bool exterior, float intensity, int ref_ambient_mode, vec4 ref_ambient_color, + float roughness, vec3 ambient, vec3 skybox, + inout highp vec4 reflection_accum, inout highp vec4 ambient_accum) { + vec4 reflection; + + vec3 local_pos = (local_matrix * vec4(vertex, 1.0)).xyz; + + if (any(greaterThan(abs(local_pos), box_extents))) { //out of the reflection box + return; + } + + vec3 inner_pos = abs(local_pos / box_extents); + float blend = max(inner_pos.x, max(inner_pos.y, inner_pos.z)); + blend = mix(length(inner_pos), blend, blend); + blend *= blend; + blend = max(0.0, 1.0 - blend); + + //reflect and make local + vec3 ref_normal = normalize(reflect(vertex, normal)); + ref_normal = (local_matrix * vec4(ref_normal, 0.0)).xyz; + + if (use_box_project) { //box project + + vec3 nrdir = normalize(ref_normal); + vec3 rbmax = (box_extents - local_pos) / nrdir; + vec3 rbmin = (-box_extents - local_pos) / nrdir; + + vec3 rbminmax = mix(rbmin, rbmax, vec3(greaterThan(nrdir, vec3(0.0, 0.0, 0.0)))); + + float fa = min(min(rbminmax.x, rbminmax.y), rbminmax.z); + vec3 posonbox = local_pos + nrdir * fa; + ref_normal = posonbox - box_offset.xyz; + } + + reflection.rgb = srgb_to_linear(textureLod(reflection_map, ref_normal, roughness * MAX_ROUGHNESS_LOD).rgb); + + if (exterior) { + reflection.rgb = mix(skybox, reflection.rgb, blend); + } + reflection.rgb *= intensity; + reflection.a = blend; + reflection.rgb *= blend; + + reflection_accum += reflection; + +#ifndef USE_LIGHTMAP + if (ref_ambient_mode == REFLECTION_AMBIENT_ENVIRONMENT) { + vec4 ambient_out; + vec3 amb_normal = (local_matrix * vec4(normal, 0.0)).xyz; + + ambient_out.rgb = srgb_to_linear(textureLod(reflection_map, amb_normal, MAX_ROUGHNESS_LOD).rgb); + if (exterior) { + ambient_out.rgb = mix(ambient, ambient_out.rgb, blend); + } + + ambient_out.a = blend; + ambient_out.rgb *= blend; + ambient_accum += ambient_out; + } else if (ref_ambient_mode == REFLECTION_AMBIENT_COLOR) { + vec4 ambient_out; + ambient_out.rgb = ref_ambient_color.rgb; + if (exterior) { + ambient_out.rgb = mix(ambient, ambient_out.rgb, blend); + } + + ambient_out.a = blend; + ambient_out.rgb *= blend; + ambient_accum += ambient_out; + } +#endif // USE_LIGHTMAP +} + +#endif // DISABLE_REFLECTION_PROBE + #endif // !MODE_RENDER_DEPTH void main() { @@ -1489,9 +1610,33 @@ void main() { specular_light *= horizon * horizon; specular_light *= scene_data.ambient_light_color_energy.a; } -#endif +#endif // USE_RADIANCE_MAP // Calculate Reflection probes +#ifndef DISABLE_REFLECTION_PROBE + vec4 ambient_accum = vec4(0.0); + { + vec4 reflection_accum = vec4(0.0); + + reflection_process(refprobe1_texture, normal, vertex_interp, refprobe1_local_matrix, + refprobe1_use_box_project, refprobe1_box_extents, refprobe1_box_offset, + refprobe1_exterior, refprobe1_intensity, refprobe1_ambient_mode, refprobe1_ambient_color, + roughness, ambient_light, specular_light, reflection_accum, ambient_accum); + +#ifdef SECOND_REFLECTION_PROBE + + reflection_process(refprobe2_texture, normal, vertex_interp, refprobe2_local_matrix, + refprobe2_use_box_project, refprobe2_box_extents, refprobe2_box_offset, + refprobe2_exterior, refprobe2_intensity, refprobe2_ambient_mode, refprobe2_ambient_color, + roughness, ambient_light, specular_light, reflection_accum, ambient_accum); + +#endif // SECOND_REFLECTION_PROBE + + if (reflection_accum.a > 0.0) { + specular_light = reflection_accum.rgb / reflection_accum.a; + } + } +#endif // DISABLE_REFLECTION_PROBE #if defined(CUSTOM_RADIANCE_USED) specular_light = mix(specular_light, custom_radiance.rgb, custom_radiance.a); @@ -1501,6 +1646,7 @@ void main() { //lightmap overrides everything if (scene_data.use_ambient_light) { ambient_light = scene_data.ambient_light_color_energy.rgb; + #ifdef USE_RADIANCE_MAP if (scene_data.use_ambient_cubemap) { vec3 ambient_dir = scene_data.radiance_inverse_xform * normal; @@ -1508,7 +1654,13 @@ void main() { cubemap_ambient = srgb_to_linear(cubemap_ambient); ambient_light = mix(ambient_light, cubemap_ambient * scene_data.ambient_light_color_energy.a, scene_data.ambient_color_sky_mix); } -#endif +#endif // USE_RADIANCE_MAP + +#ifndef DISABLE_REFLECTION_PROBE + if (ambient_accum.a > 0.0) { + ambient_light = mix(ambient_light, (ambient_accum.rgb / ambient_accum.a) * scene_data.ambient_light_color_energy.a, scene_data.ambient_color_sky_mix); + } +#endif // DISABLE_REFLECTION_PROBE } #endif // USE_LIGHTMAP diff --git a/drivers/gles3/storage/light_storage.cpp b/drivers/gles3/storage/light_storage.cpp index eebba6b00d..f9547502f4 100644 --- a/drivers/gles3/storage/light_storage.cpp +++ b/drivers/gles3/storage/light_storage.cpp @@ -423,149 +423,604 @@ void LightStorage::light_instance_mark_visible(RID p_light_instance) { /* PROBE API */ RID LightStorage::reflection_probe_allocate() { - return RID(); + return reflection_probe_owner.allocate_rid(); } void LightStorage::reflection_probe_initialize(RID p_rid) { + ReflectionProbe probe; + + reflection_probe_owner.initialize_rid(p_rid, probe); } void LightStorage::reflection_probe_free(RID p_rid) { + ReflectionProbe *reflection_probe = reflection_probe_owner.get_or_null(p_rid); + reflection_probe->dependency.deleted_notify(p_rid); + + reflection_probe_owner.free(p_rid); } void LightStorage::reflection_probe_set_update_mode(RID p_probe, RS::ReflectionProbeUpdateMode p_mode) { + ReflectionProbe *reflection_probe = reflection_probe_owner.get_or_null(p_probe); + ERR_FAIL_NULL(reflection_probe); + + reflection_probe->update_mode = p_mode; + reflection_probe->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_REFLECTION_PROBE); } void LightStorage::reflection_probe_set_intensity(RID p_probe, float p_intensity) { + ReflectionProbe *reflection_probe = reflection_probe_owner.get_or_null(p_probe); + ERR_FAIL_NULL(reflection_probe); + + reflection_probe->intensity = p_intensity; } void LightStorage::reflection_probe_set_ambient_mode(RID p_probe, RS::ReflectionProbeAmbientMode p_mode) { + ReflectionProbe *reflection_probe = reflection_probe_owner.get_or_null(p_probe); + ERR_FAIL_NULL(reflection_probe); + + reflection_probe->ambient_mode = p_mode; } void LightStorage::reflection_probe_set_ambient_color(RID p_probe, const Color &p_color) { + ReflectionProbe *reflection_probe = reflection_probe_owner.get_or_null(p_probe); + ERR_FAIL_NULL(reflection_probe); + + reflection_probe->ambient_color = p_color; } void LightStorage::reflection_probe_set_ambient_energy(RID p_probe, float p_energy) { + ReflectionProbe *reflection_probe = reflection_probe_owner.get_or_null(p_probe); + ERR_FAIL_NULL(reflection_probe); + + reflection_probe->ambient_color_energy = p_energy; } void LightStorage::reflection_probe_set_max_distance(RID p_probe, float p_distance) { + ReflectionProbe *reflection_probe = reflection_probe_owner.get_or_null(p_probe); + ERR_FAIL_NULL(reflection_probe); + + reflection_probe->max_distance = p_distance; + reflection_probe->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_REFLECTION_PROBE); } void LightStorage::reflection_probe_set_size(RID p_probe, const Vector3 &p_size) { + ReflectionProbe *reflection_probe = reflection_probe_owner.get_or_null(p_probe); + ERR_FAIL_NULL(reflection_probe); + + reflection_probe->size = p_size; + reflection_probe->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_REFLECTION_PROBE); } void LightStorage::reflection_probe_set_origin_offset(RID p_probe, const Vector3 &p_offset) { + ReflectionProbe *reflection_probe = reflection_probe_owner.get_or_null(p_probe); + ERR_FAIL_NULL(reflection_probe); + + reflection_probe->origin_offset = p_offset; + reflection_probe->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_REFLECTION_PROBE); } void LightStorage::reflection_probe_set_as_interior(RID p_probe, bool p_enable) { + ReflectionProbe *reflection_probe = reflection_probe_owner.get_or_null(p_probe); + ERR_FAIL_NULL(reflection_probe); + + reflection_probe->interior = p_enable; + reflection_probe->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_REFLECTION_PROBE); } void LightStorage::reflection_probe_set_enable_box_projection(RID p_probe, bool p_enable) { + ReflectionProbe *reflection_probe = reflection_probe_owner.get_or_null(p_probe); + ERR_FAIL_NULL(reflection_probe); + + reflection_probe->box_projection = p_enable; } void LightStorage::reflection_probe_set_enable_shadows(RID p_probe, bool p_enable) { + ReflectionProbe *reflection_probe = reflection_probe_owner.get_or_null(p_probe); + ERR_FAIL_NULL(reflection_probe); + + reflection_probe->enable_shadows = p_enable; + reflection_probe->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_REFLECTION_PROBE); } void LightStorage::reflection_probe_set_cull_mask(RID p_probe, uint32_t p_layers) { + ReflectionProbe *reflection_probe = reflection_probe_owner.get_or_null(p_probe); + ERR_FAIL_NULL(reflection_probe); + + reflection_probe->cull_mask = p_layers; + reflection_probe->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_REFLECTION_PROBE); } void LightStorage::reflection_probe_set_reflection_mask(RID p_probe, uint32_t p_layers) { + ReflectionProbe *reflection_probe = reflection_probe_owner.get_or_null(p_probe); + ERR_FAIL_NULL(reflection_probe); + + reflection_probe->reflection_mask = p_layers; + reflection_probe->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_REFLECTION_PROBE); } void LightStorage::reflection_probe_set_resolution(RID p_probe, int p_resolution) { + ReflectionProbe *reflection_probe = reflection_probe_owner.get_or_null(p_probe); + ERR_FAIL_NULL(reflection_probe); + + reflection_probe->resolution = p_resolution; } AABB LightStorage::reflection_probe_get_aabb(RID p_probe) const { - return AABB(); + const ReflectionProbe *reflection_probe = reflection_probe_owner.get_or_null(p_probe); + ERR_FAIL_NULL_V(reflection_probe, AABB()); + + AABB aabb; + aabb.position = -reflection_probe->size / 2; + aabb.size = reflection_probe->size; + + return aabb; } RS::ReflectionProbeUpdateMode LightStorage::reflection_probe_get_update_mode(RID p_probe) const { - return RenderingServer::REFLECTION_PROBE_UPDATE_ONCE; + const ReflectionProbe *reflection_probe = reflection_probe_owner.get_or_null(p_probe); + ERR_FAIL_NULL_V(reflection_probe, RenderingServer::REFLECTION_PROBE_UPDATE_ONCE); + + return reflection_probe->update_mode; } uint32_t LightStorage::reflection_probe_get_cull_mask(RID p_probe) const { - return 0; + const ReflectionProbe *reflection_probe = reflection_probe_owner.get_or_null(p_probe); + ERR_FAIL_NULL_V(reflection_probe, 0); + + return reflection_probe->cull_mask; } uint32_t LightStorage::reflection_probe_get_reflection_mask(RID p_probe) const { - return 0; + const ReflectionProbe *reflection_probe = reflection_probe_owner.get_or_null(p_probe); + ERR_FAIL_NULL_V(reflection_probe, 0); + + return reflection_probe->reflection_mask; } Vector3 LightStorage::reflection_probe_get_size(RID p_probe) const { - return Vector3(); + const ReflectionProbe *reflection_probe = reflection_probe_owner.get_or_null(p_probe); + ERR_FAIL_NULL_V(reflection_probe, Vector3()); + + return reflection_probe->size; } Vector3 LightStorage::reflection_probe_get_origin_offset(RID p_probe) const { - return Vector3(); + const ReflectionProbe *reflection_probe = reflection_probe_owner.get_or_null(p_probe); + ERR_FAIL_NULL_V(reflection_probe, Vector3()); + + return reflection_probe->origin_offset; } float LightStorage::reflection_probe_get_origin_max_distance(RID p_probe) const { - return 0.0; + const ReflectionProbe *reflection_probe = reflection_probe_owner.get_or_null(p_probe); + ERR_FAIL_NULL_V(reflection_probe, 0.0); + + return reflection_probe->max_distance; } bool LightStorage::reflection_probe_renders_shadows(RID p_probe) const { - return false; + const ReflectionProbe *reflection_probe = reflection_probe_owner.get_or_null(p_probe); + ERR_FAIL_NULL_V(reflection_probe, false); + + return reflection_probe->enable_shadows; } void LightStorage::reflection_probe_set_mesh_lod_threshold(RID p_probe, float p_ratio) { + ReflectionProbe *reflection_probe = reflection_probe_owner.get_or_null(p_probe); + ERR_FAIL_NULL(reflection_probe); + + reflection_probe->mesh_lod_threshold = p_ratio; + reflection_probe->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_REFLECTION_PROBE); } float LightStorage::reflection_probe_get_mesh_lod_threshold(RID p_probe) const { - return 0.0; + const ReflectionProbe *reflection_probe = reflection_probe_owner.get_or_null(p_probe); + ERR_FAIL_NULL_V(reflection_probe, 0.0); + + return reflection_probe->mesh_lod_threshold; +} + +Dependency *LightStorage::reflection_probe_get_dependency(RID p_probe) const { + ReflectionProbe *reflection_probe = reflection_probe_owner.get_or_null(p_probe); + ERR_FAIL_NULL_V(reflection_probe, nullptr); + + return &reflection_probe->dependency; } /* REFLECTION ATLAS */ RID LightStorage::reflection_atlas_create() { - return RID(); + ReflectionAtlas ra; + ra.count = GLOBAL_GET("rendering/reflections/reflection_atlas/reflection_count"); + ra.size = GLOBAL_GET("rendering/reflections/reflection_atlas/reflection_size"); + + return reflection_atlas_owner.make_rid(ra); } void LightStorage::reflection_atlas_free(RID p_ref_atlas) { + reflection_atlas_set_size(p_ref_atlas, 0, 0); + + reflection_atlas_owner.free(p_ref_atlas); } int LightStorage::reflection_atlas_get_size(RID p_ref_atlas) const { - return 0; + ReflectionAtlas *ra = reflection_atlas_owner.get_or_null(p_ref_atlas); + ERR_FAIL_NULL_V(ra, 0); + + return ra->size; } void LightStorage::reflection_atlas_set_size(RID p_ref_atlas, int p_reflection_size, int p_reflection_count) { + ReflectionAtlas *ra = reflection_atlas_owner.get_or_null(p_ref_atlas); + ERR_FAIL_NULL(ra); + + if (ra->size == p_reflection_size && ra->count == p_reflection_count) { + return; //no changes + } + + ra->size = p_reflection_size; + ra->count = p_reflection_count; + + if (ra->depth != 0) { + //clear and invalidate everything + for (int i = 0; i < ra->reflections.size(); i++) { + for (int j = 0; j < 7; j++) { + if (ra->reflections[i].fbos[j] != 0) { + glDeleteFramebuffers(1, &ra->reflections[i].fbos[j]); + ra->reflections.write[i].fbos[j] = 0; + } + } + + GLES3::Utilities::get_singleton()->texture_free_data(ra->reflections[i].color); + ra->reflections.write[i].color = 0; + + GLES3::Utilities::get_singleton()->texture_free_data(ra->reflections[i].radiance); + ra->reflections.write[i].radiance = 0; + + if (ra->reflections[i].owner.is_null()) { + continue; + } + reflection_probe_release_atlas_index(ra->reflections[i].owner); + //rp->atlasindex clear + } + + ra->reflections.clear(); + + GLES3::Utilities::get_singleton()->texture_free_data(ra->depth); + ra->depth = 0; + } + + if (ra->render_buffers.is_valid()) { + ra->render_buffers->free_render_buffer_data(); + } } /* REFLECTION PROBE INSTANCE */ RID LightStorage::reflection_probe_instance_create(RID p_probe) { - return RID(); + ReflectionProbeInstance rpi; + rpi.probe = p_probe; + + return reflection_probe_instance_owner.make_rid(rpi); } void LightStorage::reflection_probe_instance_free(RID p_instance) { + reflection_probe_release_atlas_index(p_instance); + reflection_probe_instance_owner.free(p_instance); } void LightStorage::reflection_probe_instance_set_transform(RID p_instance, const Transform3D &p_transform) { + ReflectionProbeInstance *rpi = reflection_probe_instance_owner.get_or_null(p_instance); + ERR_FAIL_NULL(rpi); + + rpi->transform = p_transform; + rpi->dirty = true; } bool LightStorage::reflection_probe_has_atlas_index(RID p_instance) { - return false; + ReflectionProbeInstance *rpi = reflection_probe_instance_owner.get_or_null(p_instance); + ERR_FAIL_NULL_V(rpi, false); + + if (rpi->atlas.is_null()) { + return false; + } + + return rpi->atlas_index >= 0; } void LightStorage::reflection_probe_release_atlas_index(RID p_instance) { + ReflectionProbeInstance *rpi = reflection_probe_instance_owner.get_or_null(p_instance); + ERR_FAIL_NULL(rpi); + + if (rpi->atlas.is_null()) { + return; //nothing to release + } + ReflectionAtlas *atlas = reflection_atlas_owner.get_or_null(rpi->atlas); + ERR_FAIL_NULL(atlas); + + ERR_FAIL_INDEX(rpi->atlas_index, atlas->reflections.size()); + atlas->reflections.write[rpi->atlas_index].owner = RID(); + + if (rpi->rendering) { + // We were cancelled mid rendering, trigger refresh. + rpi->rendering = false; + rpi->dirty = true; + rpi->processing_layer = 0; + } + + rpi->atlas_index = -1; + rpi->atlas = RID(); } bool LightStorage::reflection_probe_instance_needs_redraw(RID p_instance) { - return false; + ReflectionProbeInstance *rpi = reflection_probe_instance_owner.get_or_null(p_instance); + ERR_FAIL_NULL_V(rpi, false); + + if (rpi->rendering) { + return false; + } + + if (rpi->dirty) { + return true; + } + + if (reflection_probe_get_update_mode(rpi->probe) == RS::REFLECTION_PROBE_UPDATE_ALWAYS) { + return true; + } + + return rpi->atlas_index == -1; } bool LightStorage::reflection_probe_instance_has_reflection(RID p_instance) { - return false; + ReflectionProbeInstance *rpi = reflection_probe_instance_owner.get_or_null(p_instance); + ERR_FAIL_NULL_V(rpi, false); + + return rpi->atlas.is_valid(); } bool LightStorage::reflection_probe_instance_begin_render(RID p_instance, RID p_reflection_atlas) { - return false; + TextureStorage *texture_storage = TextureStorage::get_singleton(); + ReflectionAtlas *atlas = reflection_atlas_owner.get_or_null(p_reflection_atlas); + + ERR_FAIL_NULL_V(atlas, false); + + ReflectionProbeInstance *rpi = reflection_probe_instance_owner.get_or_null(p_instance); + ERR_FAIL_NULL_V(rpi, false); + + if (atlas->render_buffers.is_null()) { + atlas->render_buffers.instantiate(); + atlas->render_buffers->configure_for_probe(Size2i(atlas->size, atlas->size)); + } + + // First we check if our atlas is initialized. + + // Not making an exception for update_mode = REFLECTION_PROBE_UPDATE_ALWAYS, we are using + // the same render techniques regardless of realtime or update once (for now). + + if (atlas->depth == 0) { + // We need to create our textures + atlas->mipmap_count = Image::get_image_required_mipmaps(atlas->size, atlas->size, Image::FORMAT_RGBAH) - 1; + atlas->mipmap_count = MIN(atlas->mipmap_count, 8); // No more than 8 please.. + + glActiveTexture(GL_TEXTURE0); + + { + // We create one set of 6 layers for depth, we can reuse this when rendering. + glGenTextures(1, &atlas->depth); + glBindTexture(GL_TEXTURE_2D_ARRAY, atlas->depth); + + glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_DEPTH_COMPONENT24, atlas->size, atlas->size, 6, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, nullptr); + + GLES3::Utilities::get_singleton()->texture_allocated_data(atlas->depth, atlas->size * atlas->size * 6 * 3, "Reflection probe atlas (depth)"); + } + + // Make room for our atlas entries + atlas->reflections.resize(atlas->count); + + for (int i = 0; i < atlas->count; i++) { + // Create a cube map for this atlas entry + GLuint color = 0; + glGenTextures(1, &color); + glBindTexture(GL_TEXTURE_CUBE_MAP, color); + atlas->reflections.write[i].color = color; + +#ifdef GL_API_ENABLED + if (RasterizerGLES3::is_gles_over_gl()) { + for (int s = 0; s < 6; s++) { + glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + s, 0, GL_RGB10_A2, atlas->size, atlas->size, 0, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV, nullptr); + } + glGenerateMipmap(GL_TEXTURE_CUBE_MAP); + } +#endif +#ifdef GLES_API_ENABLED + if (!RasterizerGLES3::is_gles_over_gl()) { + glTexStorage2D(GL_TEXTURE_CUBE_MAP, atlas->mipmap_count, GL_RGB10_A2, atlas->size, atlas->size); + } +#endif // GLES_API_ENABLED + + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_BASE_LEVEL, 0); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_LEVEL, atlas->mipmap_count - 1); + + // Setup sizes and calculate how much memory we're using. + int mipmap_size = atlas->size; + uint32_t data_size = 0; + for (int m = 0; m < atlas->mipmap_count; m++) { + atlas->mipmap_size[m] = mipmap_size; + data_size += mipmap_size * mipmap_size * 6 * 4; + mipmap_size = MAX(mipmap_size >> 1, 1); + } + + GLES3::Utilities::get_singleton()->texture_allocated_data(color, data_size, String("Reflection probe atlas (") + String::num_int64(i) + String(", color)")); + + // Create a radiance map for this atlas entry + GLuint radiance = 0; + glGenTextures(1, &radiance); + glBindTexture(GL_TEXTURE_CUBE_MAP, radiance); + atlas->reflections.write[i].radiance = radiance; + +#ifdef GL_API_ENABLED + if (RasterizerGLES3::is_gles_over_gl()) { + for (int s = 0; s < 6; s++) { + glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + s, 0, GL_RGB10_A2, atlas->size, atlas->size, 0, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV, nullptr); + } + glGenerateMipmap(GL_TEXTURE_CUBE_MAP); + } +#endif +#ifdef GLES_API_ENABLED + if (!RasterizerGLES3::is_gles_over_gl()) { + glTexStorage2D(GL_TEXTURE_CUBE_MAP, atlas->mipmap_count, GL_RGB10_A2, atlas->size, atlas->size); + } +#endif // GLES_API_ENABLED + + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_BASE_LEVEL, 0); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_LEVEL, atlas->mipmap_count - 1); + + // Same data size as our color buffer + GLES3::Utilities::get_singleton()->texture_allocated_data(radiance, data_size, String("Reflection probe atlas (") + String::num_int64(i) + String(", radiance)")); + + // Create our framebuffers so we can draw to all sides + for (int side = 0; side < 6; side++) { + GLuint fbo = 0; + glGenFramebuffers(1, &fbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + + // We use glFramebufferTexture2D for the color buffer as glFramebufferTextureLayer doesn't always work with cubemaps. + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + side, color, 0); + glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, atlas->depth, 0, side); + + // Validate framebuffer + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) { + WARN_PRINT("Could not create reflections framebuffer, status: " + texture_storage->get_framebuffer_error(status)); + } + + atlas->reflections.write[i].fbos[side] = fbo; + } + + // Create an extra framebuffer for building our radiance + { + GLuint fbo = 0; + glGenFramebuffers(1, &fbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + + atlas->reflections.write[i].fbos[6] = fbo; + } + } + + glBindFramebuffer(GL_FRAMEBUFFER, GLES3::TextureStorage::system_fbo); + glBindTexture(GL_TEXTURE_CUBE_MAP, 0); + glBindTexture(GL_TEXTURE_2D_ARRAY, 0); + } + + // Then we find a free slot for our reflection probe + + if (rpi->atlas_index == -1) { + for (int i = 0; i < atlas->reflections.size(); i++) { + if (atlas->reflections[i].owner.is_null()) { + rpi->atlas_index = i; + break; + } + } + //find the one used last + if (rpi->atlas_index == -1) { + //everything is in use, find the one least used via LRU + uint64_t pass_min = 0; + + for (int i = 0; i < atlas->reflections.size(); i++) { + ReflectionProbeInstance *rpi2 = reflection_probe_instance_owner.get_or_null(atlas->reflections[i].owner); + if (rpi2->last_pass < pass_min) { + pass_min = rpi2->last_pass; + rpi->atlas_index = i; + } + } + } + } + + if (rpi->atlas_index != -1) { // should we fail if this is still -1 ? + atlas->reflections.write[rpi->atlas_index].owner = p_instance; + } + + rpi->atlas = p_reflection_atlas; + rpi->rendering = true; + rpi->dirty = false; + rpi->processing_layer = 0; + + return true; } Ref<RenderSceneBuffers> LightStorage::reflection_probe_atlas_get_render_buffers(RID p_reflection_atlas) { - return Ref<RenderSceneBuffers>(); + ReflectionAtlas *atlas = reflection_atlas_owner.get_or_null(p_reflection_atlas); + ERR_FAIL_NULL_V(atlas, Ref<RenderSceneBuffersGLES3>()); + + return atlas->render_buffers; } bool LightStorage::reflection_probe_instance_postprocess_step(RID p_instance) { - return true; + GLES3::CubemapFilter *cubemap_filter = GLES3::CubemapFilter::get_singleton(); + ReflectionProbeInstance *rpi = reflection_probe_instance_owner.get_or_null(p_instance); + ERR_FAIL_NULL_V(rpi, false); + ERR_FAIL_COND_V(!rpi->rendering, false); + ERR_FAIL_COND_V(rpi->atlas.is_null(), false); + + ReflectionAtlas *atlas = reflection_atlas_owner.get_or_null(rpi->atlas); + if (!atlas || rpi->atlas_index == -1) { + //does not belong to an atlas anymore, cancel (was removed from atlas or atlas changed while rendering) + rpi->rendering = false; + rpi->processing_layer = 0; + return false; + } + + if (LightStorage::get_singleton()->reflection_probe_get_update_mode(rpi->probe) == RS::REFLECTION_PROBE_UPDATE_ALWAYS) { + // Using real time reflections, all roughness is done in one step + for (int m = 0; m < atlas->mipmap_count; m++) { + const GLES3::ReflectionAtlas::Reflection &reflection = atlas->reflections[rpi->atlas_index]; + cubemap_filter->filter_radiance(reflection.color, reflection.radiance, reflection.fbos[6], atlas->size, atlas->mipmap_count, m); + } + + rpi->rendering = false; + rpi->processing_layer = 0; + return true; + } else { + const GLES3::ReflectionAtlas::Reflection &reflection = atlas->reflections[rpi->atlas_index]; + cubemap_filter->filter_radiance(reflection.color, reflection.radiance, reflection.fbos[6], atlas->size, atlas->mipmap_count, rpi->processing_layer); + + rpi->processing_layer++; + if (rpi->processing_layer == atlas->mipmap_count) { + rpi->rendering = false; + rpi->processing_layer = 0; + return true; + } + } + + return false; +} + +GLuint LightStorage::reflection_probe_instance_get_texture(RID p_instance) { + ReflectionProbeInstance *rpi = reflection_probe_instance_owner.get_or_null(p_instance); + ERR_FAIL_NULL_V(rpi, 0); + + ReflectionAtlas *atlas = reflection_atlas_owner.get_or_null(rpi->atlas); + ERR_FAIL_NULL_V(atlas, 0); + + return atlas->reflections[rpi->atlas_index].radiance; +} + +GLuint LightStorage::reflection_probe_instance_get_framebuffer(RID p_instance, int p_index) { + ReflectionProbeInstance *rpi = reflection_probe_instance_owner.get_or_null(p_instance); + ERR_FAIL_NULL_V(rpi, 0); + ERR_FAIL_INDEX_V(p_index, 6, 0); + + ReflectionAtlas *atlas = reflection_atlas_owner.get_or_null(rpi->atlas); + ERR_FAIL_NULL_V(atlas, 0); + return atlas->reflections[rpi->atlas_index].fbos[p_index]; } /* LIGHTMAP CAPTURE */ diff --git a/drivers/gles3/storage/light_storage.h b/drivers/gles3/storage/light_storage.h index 51c5c48106..b6e64c9492 100644 --- a/drivers/gles3/storage/light_storage.h +++ b/drivers/gles3/storage/light_storage.h @@ -34,6 +34,7 @@ #ifdef GLES3_ENABLED #include "platform_gl.h" +#include "render_scene_buffers_gles3.h" #include "core/templates/local_vector.h" #include "core/templates/rid_owner.h" @@ -126,12 +127,51 @@ struct ReflectionProbe { bool box_projection = false; bool enable_shadows = false; uint32_t cull_mask = (1 << 20) - 1; + uint32_t reflection_mask = (1 << 20) - 1; float mesh_lod_threshold = 0.01; float baked_exposure = 1.0; Dependency dependency; }; +/* REFLECTION ATLAS */ + +struct ReflectionAtlas { + int count = 0; + int size = 0; + + int mipmap_count = 1; // number of mips, including original + int mipmap_size[8]; + GLuint depth = 0; + + struct Reflection { + RID owner; + GLuint color = 0; + GLuint radiance = 0; + GLuint fbos[7]; + }; + Vector<Reflection> reflections; + + Ref<RenderSceneBuffersGLES3> render_buffers; // Further render buffers used. +}; + +/* REFLECTION PROBE INSTANCE */ + +struct ReflectionProbeInstance { + RID probe; + int atlas_index = -1; + RID atlas; + + bool dirty = true; + bool rendering = false; + int processing_layer = 0; + + uint64_t last_pass = 0; + uint32_t cull_mask = 0; + + Transform3D transform; +}; + /* LIGHTMAP */ struct Lightmap { @@ -181,6 +221,13 @@ private: /* REFLECTION PROBE */ mutable RID_Owner<ReflectionProbe, true> reflection_probe_owner; + /* REFLECTION ATLAS */ + mutable RID_Owner<ReflectionAtlas> reflection_atlas_owner; + + /* REFLECTION PROBE INSTANCE */ + + mutable RID_Owner<ReflectionProbeInstance> reflection_probe_instance_owner; + /* LIGHTMAP */ Vector<RID> lightmap_textures; @@ -394,6 +441,29 @@ public: virtual void light_instance_set_shadow_transform(RID p_light_instance, const Projection &p_projection, const Transform3D &p_transform, float p_far, float p_split, int p_pass, float p_shadow_texel_size, float p_bias_scale = 1.0, float p_range_begin = 0, const Vector2 &p_uv_scale = Vector2()) override; virtual void light_instance_mark_visible(RID p_light_instance) override; + virtual bool light_instance_is_shadow_visible_at_position(RID p_light_instance, const Vector3 &p_position) const override { + const LightInstance *light_instance = light_instance_owner.get_or_null(p_light_instance); + ERR_FAIL_NULL_V(light_instance, false); + const Light *light = light_owner.get_or_null(light_instance->light); + ERR_FAIL_NULL_V(light, false); + + if (!light->shadow) { + return false; + } + + if (!light->distance_fade) { + return true; + } + + real_t distance = p_position.distance_to(light_instance->transform.origin); + + if (distance > light->distance_fade_shadow + light->distance_fade_length) { + return false; + } + + return true; + } + _FORCE_INLINE_ RID light_instance_get_base_light(RID p_light_instance) { LightInstance *li = light_instance_owner.get_or_null(p_light_instance); return li->light; @@ -559,6 +629,9 @@ public: /* PROBE API */ + ReflectionProbe *get_reflection_probe(RID p_rid) { return reflection_probe_owner.get_or_null(p_rid); }; + bool owns_reflection_probe(RID p_rid) { return reflection_probe_owner.owns(p_rid); }; + virtual RID reflection_probe_allocate() override; virtual void reflection_probe_initialize(RID p_rid) override; virtual void reflection_probe_free(RID p_rid) override; @@ -589,8 +662,12 @@ public: virtual float reflection_probe_get_origin_max_distance(RID p_probe) const override; virtual bool reflection_probe_renders_shadows(RID p_probe) const override; + Dependency *reflection_probe_get_dependency(RID p_probe) const; + /* REFLECTION ATLAS */ + bool owns_reflection_atlas(RID p_rid) { return reflection_atlas_owner.owns(p_rid); } + virtual RID reflection_atlas_create() override; virtual void reflection_atlas_free(RID p_ref_atlas) override; virtual int reflection_atlas_get_size(RID p_ref_atlas) const override; @@ -598,6 +675,8 @@ public: /* REFLECTION PROBE INSTANCE */ + bool owns_reflection_probe_instance(RID p_rid) { return reflection_probe_instance_owner.owns(p_rid); } + virtual RID reflection_probe_instance_create(RID p_probe) override; virtual void reflection_probe_instance_free(RID p_instance) override; virtual void reflection_probe_instance_set_transform(RID p_instance, const Transform3D &p_transform) override; @@ -609,6 +688,27 @@ public: virtual Ref<RenderSceneBuffers> reflection_probe_atlas_get_render_buffers(RID p_reflection_atlas) override; virtual bool reflection_probe_instance_postprocess_step(RID p_instance) override; + _FORCE_INLINE_ RID reflection_probe_instance_get_probe(RID p_instance) { + ReflectionProbeInstance *rpi = reflection_probe_instance_owner.get_or_null(p_instance); + ERR_FAIL_NULL_V(rpi, RID()); + + return rpi->probe; + } + _FORCE_INLINE_ RID reflection_probe_instance_get_atlas(RID p_instance) { + ReflectionProbeInstance *rpi = reflection_probe_instance_owner.get_or_null(p_instance); + ERR_FAIL_NULL_V(rpi, RID()); + + return rpi->atlas; + } + Transform3D reflection_probe_instance_get_transform(RID p_instance) { + ReflectionProbeInstance *rpi = reflection_probe_instance_owner.get_or_null(p_instance); + ERR_FAIL_NULL_V(rpi, Transform3D()); + + return rpi->transform; + } + GLuint reflection_probe_instance_get_texture(RID p_instance); + GLuint reflection_probe_instance_get_framebuffer(RID p_instance, int p_index); + /* LIGHTMAP CAPTURE */ Lightmap *get_lightmap(RID p_rid) { return lightmap_owner.get_or_null(p_rid); }; diff --git a/drivers/gles3/storage/material_storage.h b/drivers/gles3/storage/material_storage.h index 59f5682362..02aecf33d6 100644 --- a/drivers/gles3/storage/material_storage.h +++ b/drivers/gles3/storage/material_storage.h @@ -43,7 +43,6 @@ #include "servers/rendering/storage/utilities.h" #include "drivers/gles3/shaders/canvas.glsl.gen.h" -#include "drivers/gles3/shaders/cubemap_filter.glsl.gen.h" #include "drivers/gles3/shaders/particles.glsl.gen.h" #include "drivers/gles3/shaders/scene.glsl.gen.h" #include "drivers/gles3/shaders/sky.glsl.gen.h" @@ -543,7 +542,6 @@ public: CanvasShaderGLES3 canvas_shader; SkyShaderGLES3 sky_shader; SceneShaderGLES3 scene_shader; - CubemapFilterShaderGLES3 cubemap_filter_shader; ParticlesShaderGLES3 particles_process_shader; ShaderCompiler compiler_canvas; diff --git a/drivers/gles3/storage/render_scene_buffers_gles3.cpp b/drivers/gles3/storage/render_scene_buffers_gles3.cpp index de0a64f5fe..6803c92dc9 100644 --- a/drivers/gles3/storage/render_scene_buffers_gles3.cpp +++ b/drivers/gles3/storage/render_scene_buffers_gles3.cpp @@ -405,6 +405,13 @@ void RenderSceneBuffersGLES3::_check_render_buffers() { } } +void RenderSceneBuffersGLES3::configure_for_probe(Size2i p_size) { + internal_size = p_size; + target_size = p_size; + scaling_3d_mode = RS::VIEWPORT_SCALING_3D_MODE_OFF; + view_count = 1; +} + void RenderSceneBuffersGLES3::_clear_msaa3d_buffers() { for (const FBDEF &cached_fbo : msaa3d.cached_fbos) { GLuint fbo = cached_fbo.fbo; diff --git a/drivers/gles3/storage/render_scene_buffers_gles3.h b/drivers/gles3/storage/render_scene_buffers_gles3.h index 8d03d3438d..85a02c860d 100644 --- a/drivers/gles3/storage/render_scene_buffers_gles3.h +++ b/drivers/gles3/storage/render_scene_buffers_gles3.h @@ -101,6 +101,7 @@ public: RenderSceneBuffersGLES3(); virtual ~RenderSceneBuffersGLES3(); virtual void configure(const RenderSceneBuffersConfiguration *p_config) override; + void configure_for_probe(Size2i p_size); virtual void set_fsr_sharpness(float p_fsr_sharpness) override{}; virtual void set_texture_mipmap_bias(float p_texture_mipmap_bias) override{}; diff --git a/drivers/gles3/storage/utilities.cpp b/drivers/gles3/storage/utilities.cpp index c4fbe098cd..7e2e3dfa2b 100644 --- a/drivers/gles3/storage/utilities.cpp +++ b/drivers/gles3/storage/utilities.cpp @@ -158,6 +158,8 @@ RS::InstanceType Utilities::get_base_type(RID p_rid) const { return RS::INSTANCE_LIGHTMAP; } else if (GLES3::ParticlesStorage::get_singleton()->owns_particles(p_rid)) { return RS::INSTANCE_PARTICLES; + } else if (GLES3::LightStorage::get_singleton()->owns_reflection_probe(p_rid)) { + return RS::INSTANCE_REFLECTION_PROBE; } else if (GLES3::ParticlesStorage::get_singleton()->owns_particles_collision(p_rid)) { return RS::INSTANCE_PARTICLES_COLLISION; } else if (owns_visibility_notifier(p_rid)) { @@ -197,6 +199,15 @@ bool Utilities::free(RID p_rid) { } else if (GLES3::LightStorage::get_singleton()->owns_lightmap(p_rid)) { GLES3::LightStorage::get_singleton()->lightmap_free(p_rid); return true; + } else if (GLES3::LightStorage::get_singleton()->owns_reflection_probe(p_rid)) { + GLES3::LightStorage::get_singleton()->reflection_probe_free(p_rid); + return true; + } else if (GLES3::LightStorage::get_singleton()->owns_reflection_atlas(p_rid)) { + GLES3::LightStorage::get_singleton()->reflection_atlas_free(p_rid); + return true; + } else if (GLES3::LightStorage::get_singleton()->owns_reflection_probe_instance(p_rid)) { + GLES3::LightStorage::get_singleton()->reflection_probe_instance_free(p_rid); + return true; } else if (GLES3::ParticlesStorage::get_singleton()->owns_particles(p_rid)) { GLES3::ParticlesStorage::get_singleton()->particles_free(p_rid); return true; @@ -229,6 +240,9 @@ void Utilities::base_update_dependency(RID p_base, DependencyTracker *p_instance if (multimesh->mesh.is_valid()) { base_update_dependency(multimesh->mesh, p_instance); } + } else if (LightStorage::get_singleton()->owns_reflection_probe(p_base)) { + Dependency *dependency = LightStorage::get_singleton()->reflection_probe_get_dependency(p_base); + p_instance->update_dependency(dependency); } else if (LightStorage::get_singleton()->owns_light(p_base)) { Light *l = LightStorage::get_singleton()->get_light(p_base); p_instance->update_dependency(&l->dependency); diff --git a/editor/debugger/editor_file_server.cpp b/editor/debugger/editor_file_server.cpp index 428b5f4c26..e84eb14636 100644 --- a/editor/debugger/editor_file_server.cpp +++ b/editor/debugger/editor_file_server.cpp @@ -201,7 +201,7 @@ void EditorFileServer::poll() { // Scan files to send. _scan_files_changed(EditorFileSystem::get_singleton()->get_filesystem(), tags, files_to_send, cached_files); // Add forced export files - Vector<String> forced_export = EditorExportPlatform::get_main_pack_forced_export_files(); + Vector<String> forced_export = EditorExportPlatform::get_forced_export_files(); for (int i = 0; i < forced_export.size(); i++) { _add_custom_file(forced_export[i], files_to_send, cached_files); } diff --git a/editor/editor_build_profile.cpp b/editor/editor_build_profile.cpp index 67bc55fdd6..c6ecaba230 100644 --- a/editor/editor_build_profile.cpp +++ b/editor/editor_build_profile.cpp @@ -861,7 +861,7 @@ EditorBuildProfileManager::EditorBuildProfileManager() { class_list->connect("item_edited", callable_mp(this, &EditorBuildProfileManager::_class_list_item_edited), CONNECT_DEFERRED); class_list->connect("item_collapsed", callable_mp(this, &EditorBuildProfileManager::_class_list_item_collapsed)); // It will be displayed once the user creates or chooses a profile. - main_vbc->add_margin_child(TTR("Configure Engine Build Profile:"), class_list, true); + main_vbc->add_margin_child(TTR("Configure Engine Compilation Profile:"), class_list, true); description_bit = memnew(EditorHelpBit); description_bit->set_custom_minimum_size(Size2(0, 80) * EDSCALE); @@ -875,7 +875,7 @@ EditorBuildProfileManager::EditorBuildProfileManager() { import_profile = memnew(EditorFileDialog); add_child(import_profile); import_profile->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE); - import_profile->add_filter("*.build", TTR("Engine Build Profile")); + import_profile->add_filter("*.build", TTR("Engine Compilation Profile")); import_profile->connect("files_selected", callable_mp(this, &EditorBuildProfileManager::_import_profile)); import_profile->set_title(TTR("Load Profile")); import_profile->set_access(EditorFileDialog::ACCESS_FILESYSTEM); @@ -883,7 +883,7 @@ EditorBuildProfileManager::EditorBuildProfileManager() { export_profile = memnew(EditorFileDialog); add_child(export_profile); export_profile->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE); - export_profile->add_filter("*.build", TTR("Engine Build Profile")); + export_profile->add_filter("*.build", TTR("Engine Compilation Profile")); export_profile->connect("file_selected", callable_mp(this, &EditorBuildProfileManager::_export_profile)); export_profile->set_title(TTR("Export Profile")); export_profile->set_access(EditorFileDialog::ACCESS_FILESYSTEM); @@ -892,7 +892,7 @@ EditorBuildProfileManager::EditorBuildProfileManager() { main_vbc->add_margin_child(TTR("Forced Classes on Detect:"), force_detect_classes); force_detect_classes->connect("text_changed", callable_mp(this, &EditorBuildProfileManager::_force_detect_classes_changed)); - set_title(TTR("Edit Build Configuration Profile")); + set_title(TTR("Edit Compilation Configuration Profile")); singleton = this; } diff --git a/editor/editor_data.cpp b/editor/editor_data.cpp index 72225fd454..bdc6504417 100644 --- a/editor/editor_data.cpp +++ b/editor/editor_data.cpp @@ -722,8 +722,7 @@ bool EditorData::check_and_update_scene(int p_idx) { new_scene->set_scene_file_path(edited_scene[p_idx].root->get_scene_file_path()); Node *old_root = edited_scene[p_idx].root; - edited_scene.write[p_idx].root = new_scene; - old_root->replace_by(new_scene, false, false); + EditorNode::get_singleton()->set_edited_scene(new_scene); memdelete(old_root); edited_scene.write[p_idx].selection = new_selection; diff --git a/editor/editor_file_system.h b/editor/editor_file_system.h index e45d6f8be3..782d3eee38 100644 --- a/editor/editor_file_system.h +++ b/editor/editor_file_system.h @@ -304,6 +304,7 @@ public: EditorFileSystemDirectory *get_filesystem(); bool is_scanning() const; bool is_importing() const { return importing; } + bool doing_first_scan() const { return first_scan; } float get_scanning_progress() const; void scan(); void scan_changes(); diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index cd498ce089..b3fc87e2c8 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -3562,8 +3562,11 @@ void EditorHelpHighlighter::reset_cache() { } EditorHelpHighlighter::EditorHelpHighlighter() { + const Color text_color = EDITOR_GET("text_editor/theme/highlighting/text_color"); + #ifdef MODULE_GDSCRIPT_ENABLED TextEdit *gdscript_text_edit = memnew(TextEdit); + gdscript_text_edit->add_theme_color_override("font_color", text_color); Ref<GDScript> gdscript; gdscript.instantiate(); @@ -3580,6 +3583,7 @@ EditorHelpHighlighter::EditorHelpHighlighter() { #ifdef MODULE_MONO_ENABLED TextEdit *csharp_text_edit = memnew(TextEdit); + csharp_text_edit->add_theme_color_override("font_color", text_color); // See GH-89610. //Ref<CSharpScript> csharp; diff --git a/editor/editor_layouts_dialog.cpp b/editor/editor_layouts_dialog.cpp index db6fa2c035..52047c569d 100644 --- a/editor/editor_layouts_dialog.cpp +++ b/editor/editor_layouts_dialog.cpp @@ -31,8 +31,6 @@ #include "editor_layouts_dialog.h" #include "core/io/config_file.h" -#include "core/object/class_db.h" -#include "core/os/keyboard.h" #include "editor/editor_settings.h" #include "editor/themes/editor_scale.h" #include "scene/gui/item_list.h" @@ -60,7 +58,7 @@ void EditorLayoutsDialog::_update_ok_disable_state() { if (layout_names->is_anything_selected()) { get_ok_button()->set_disabled(false); } else { - get_ok_button()->set_disabled(!name->is_visible() || name->get_text().is_empty()); + get_ok_button()->set_disabled(!name->is_visible() || name->get_text().strip_edges().is_empty()); } } @@ -80,8 +78,8 @@ void EditorLayoutsDialog::ok_pressed() { for (int i = 0; i < selected_items.size(); ++i) { emit_signal(SNAME("name_confirmed"), layout_names->get_item_text(selected_items[i])); } - } else if (name->is_visible() && !name->get_text().is_empty()) { - emit_signal(SNAME("name_confirmed"), name->get_text()); + } else if (name->is_visible() && !name->get_text().strip_edges().is_empty()) { + emit_signal(SNAME("name_confirmed"), name->get_text().strip_edges()); } } diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 0e6c814b44..4fb1a86ce2 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -2136,10 +2136,6 @@ void EditorNode::_dialog_action(String p_file) { } break; case SETTINGS_LAYOUT_DELETE: { - if (p_file.is_empty()) { - return; - } - Ref<ConfigFile> config; config.instantiate(); Error err = config->load(EditorSettings::get_singleton()->get_editor_layouts_config()); @@ -2896,9 +2892,6 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { } } } break; - case TOOLS_BUILD_PROFILE_MANAGER: { - build_profile_manager->popup_centered_clamped(Size2(700, 800) * EDSCALE, 0.8); - } break; case RUN_USER_DATA_FOLDER: { // Ensure_user_data_dir() to prevent the edge case: "Open User Data Folder" won't work after the project was renamed in ProjectSettingsEditor unless the project is saved. OS::get_singleton()->ensure_user_data_dir(); @@ -3203,6 +3196,9 @@ void EditorNode::_tool_menu_option(int p_idx) { case TOOLS_ORPHAN_RESOURCES: { orphan_resources->show(); } break; + case TOOLS_BUILD_PROFILE_MANAGER: { + build_profile_manager->popup_centered_clamped(Size2(700, 800) * EDSCALE, 0.8); + } break; case TOOLS_SURFACE_UPGRADE: { surface_upgrade_dialog->popup_on_demand(); } break; @@ -3674,9 +3670,7 @@ void EditorNode::set_edited_scene(Node *p_scene) { if (old_edited_scene_root->get_parent() == scene_root) { scene_root->remove_child(old_edited_scene_root); } - if (old_edited_scene_root->is_connected("replacing_by", callable_mp(this, &EditorNode::set_edited_scene))) { - old_edited_scene_root->disconnect(SNAME("replacing_by"), callable_mp(this, &EditorNode::set_edited_scene)); - } + old_edited_scene_root->disconnect(SNAME("replacing_by"), callable_mp(this, &EditorNode::set_edited_scene)); } get_editor_data().set_edited_scene_root(p_scene); @@ -6783,13 +6777,12 @@ EditorNode::EditorNode() { #endif project_menu->add_separator(); - project_menu->add_item(TTR("Customize Engine Build Configuration..."), TOOLS_BUILD_PROFILE_MANAGER); - project_menu->add_separator(); tool_menu = memnew(PopupMenu); tool_menu->connect("index_pressed", callable_mp(this, &EditorNode::_tool_menu_option)); project_menu->add_submenu_node_item(TTR("Tools"), tool_menu); tool_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/orphan_resource_explorer", TTR("Orphan Resource Explorer...")), TOOLS_ORPHAN_RESOURCES); + tool_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/engine_compilation_configuration_editor", TTR("Engine Compilation Configuration Editor...")), TOOLS_BUILD_PROFILE_MANAGER); tool_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/upgrade_mesh_surfaces", TTR("Upgrade Mesh Surfaces...")), TOOLS_SURFACE_UPGRADE); project_menu->add_separator(); diff --git a/editor/export/editor_export_platform.cpp b/editor/export/editor_export_platform.cpp index d2a187bcfd..33fdd7418e 100644 --- a/editor/export/editor_export_platform.cpp +++ b/editor/export/editor_export_platform.cpp @@ -864,19 +864,23 @@ String EditorExportPlatform::_get_script_encryption_key(const Ref<EditorExportPr return p_preset->get_script_encryption_key().to_lower(); } -Vector<String> EditorExportPlatform::get_main_pack_forced_export_files() { - // First, get files required by any PCK. - Vector<String> files = get_forced_export_files(); +Vector<String> EditorExportPlatform::get_forced_export_files() { + Vector<String> files; + + files.push_back(ProjectSettings::get_singleton()->get_global_class_list_path()); String icon = GLOBAL_GET("application/config/icon"); + String splash = GLOBAL_GET("application/boot_splash/image"); if (!icon.is_empty() && FileAccess::exists(icon)) { files.push_back(icon); } - - String splash = GLOBAL_GET("application/boot_splash/image"); - if (!splash.is_empty() && icon != splash && FileAccess::exists(splash)) { + if (!splash.is_empty() && FileAccess::exists(splash) && icon != splash) { files.push_back(splash); } + String resource_cache_file = ResourceUID::get_cache_file(); + if (FileAccess::exists(resource_cache_file)) { + files.push_back(resource_cache_file); + } String extension_list_config_file = GDExtension::get_extension_list_config_file(); if (FileAccess::exists(extension_list_config_file)) { @@ -909,19 +913,7 @@ Vector<String> EditorExportPlatform::get_main_pack_forced_export_files() { return files; } -Vector<String> EditorExportPlatform::get_forced_export_files() { - Vector<String> files; - files.push_back(ProjectSettings::get_singleton()->get_global_class_list_path()); - - String resource_cache_file = ResourceUID::get_cache_file(); - if (FileAccess::exists(resource_cache_file)) { - files.push_back(resource_cache_file); - } - - return files; -} - -Error EditorExportPlatform::export_project_files(bool p_main_pack, const Ref<EditorExportPreset> &p_preset, bool p_debug, EditorExportSaveFunction p_func, void *p_udata, EditorExportSaveSharedObject p_so_func) { +Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &p_preset, bool p_debug, EditorExportSaveFunction p_func, void *p_udata, EditorExportSaveSharedObject p_so_func) { //figure out paths of files that will be exported HashSet<String> paths; Vector<String> path_remaps; @@ -968,11 +960,9 @@ Error EditorExportPlatform::export_project_files(bool p_main_pack, const Ref<Edi } } - if (p_main_pack) { - // Add native icons to non-resource include list. - _edit_filter_list(paths, String("*.icns"), false); - _edit_filter_list(paths, String("*.ico"), false); - } + //add native icons to non-resource include list + _edit_filter_list(paths, String("*.icns"), false); + _edit_filter_list(paths, String("*.ico"), false); _edit_filter_list(paths, p_preset->get_include_filter(), false); _edit_filter_list(paths, p_preset->get_exclude_filter(), true); @@ -1404,12 +1394,7 @@ Error EditorExportPlatform::export_project_files(bool p_main_pack, const Ref<Edi } } - Vector<String> forced_export; - if (p_main_pack) { - forced_export = get_main_pack_forced_export_files(); - } else { - forced_export = get_forced_export_files(); - } + Vector<String> forced_export = get_forced_export_files(); for (int i = 0; i < forced_export.size(); i++) { Vector<uint8_t> array = FileAccess::get_file_as_bytes(forced_export[i]); err = p_func(p_udata, forced_export[i], array, idx, total, enc_in_filters, enc_ex_filters, key); @@ -1418,19 +1403,13 @@ Error EditorExportPlatform::export_project_files(bool p_main_pack, const Ref<Edi } } - if (p_main_pack) { - String config_file = "project.binary"; - String engine_cfb = EditorPaths::get_singleton()->get_cache_dir().path_join("tmp" + config_file); - ProjectSettings::get_singleton()->save_custom(engine_cfb, custom_map, custom_list); - Vector<uint8_t> data = FileAccess::get_file_as_bytes(engine_cfb); - DirAccess::remove_file_or_error(engine_cfb); - err = p_func(p_udata, "res://" + config_file, data, idx, total, enc_in_filters, enc_ex_filters, key); - if (err != OK) { - return err; - } - } + String config_file = "project.binary"; + String engine_cfb = EditorPaths::get_singleton()->get_cache_dir().path_join("tmp" + config_file); + ProjectSettings::get_singleton()->save_custom(engine_cfb, custom_map, custom_list); + Vector<uint8_t> data = FileAccess::get_file_as_bytes(engine_cfb); + DirAccess::remove_file_or_error(engine_cfb); - return OK; + return p_func(p_udata, "res://" + config_file, data, idx, total, enc_in_filters, enc_ex_filters, key); } Error EditorExportPlatform::_add_shared_object(void *p_userdata, const SharedObject &p_so) { @@ -1559,7 +1538,7 @@ void EditorExportPlatform::zip_folder_recursive(zipFile &p_zip, const String &p_ da->list_dir_end(); } -Error EditorExportPlatform::save_pack(bool p_main_pack, const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files, bool p_embed, int64_t *r_embedded_start, int64_t *r_embedded_size) { +Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files, bool p_embed, int64_t *r_embedded_start, int64_t *r_embedded_size) { EditorProgress ep("savepack", TTR("Packing"), 102, true); // Create the temporary export directory if it doesn't exist. @@ -1578,7 +1557,7 @@ Error EditorExportPlatform::save_pack(bool p_main_pack, const Ref<EditorExportPr pd.f = ftmp; pd.so_files = p_so_files; - Error err = export_project_files(p_main_pack, p_preset, p_debug, _save_pack_file, &pd, _add_shared_object); + Error err = export_project_files(p_preset, p_debug, _save_pack_file, &pd, _add_shared_object); // Close temp file. pd.f.unref(); @@ -1796,7 +1775,7 @@ Error EditorExportPlatform::save_zip(const Ref<EditorExportPreset> &p_preset, bo zd.ep = &ep; zd.zip = zip; - Error err = export_project_files(false, p_preset, p_debug, _save_zip_file, &zd); + Error err = export_project_files(p_preset, p_debug, _save_zip_file, &zd); if (err != OK && err != ERR_SKIP) { add_message(EXPORT_MESSAGE_ERROR, TTR("Save ZIP"), TTR("Failed to export project files.")); } @@ -1808,7 +1787,7 @@ Error EditorExportPlatform::save_zip(const Ref<EditorExportPreset> &p_preset, bo Error EditorExportPlatform::export_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); - return save_pack(false, p_preset, p_debug, p_path); + return save_pack(p_preset, p_debug, p_path); } Error EditorExportPlatform::export_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { diff --git a/editor/export/editor_export_platform.h b/editor/export/editor_export_platform.h index 4c4e64b1a6..26e1f86c8b 100644 --- a/editor/export/editor_export_platform.h +++ b/editor/export/editor_export_platform.h @@ -203,7 +203,6 @@ public: return worst_type; } - static Vector<String> get_main_pack_forced_export_files(); static Vector<String> get_forced_export_files(); virtual bool fill_log_messages(RichTextLabel *p_log, Error p_err); @@ -217,9 +216,9 @@ public: virtual String get_name() const = 0; virtual Ref<Texture2D> get_logo() const = 0; - Error export_project_files(bool p_main_pack, const Ref<EditorExportPreset> &p_preset, bool p_debug, EditorExportSaveFunction p_func, void *p_udata, EditorExportSaveSharedObject p_so_func = nullptr); + Error export_project_files(const Ref<EditorExportPreset> &p_preset, bool p_debug, EditorExportSaveFunction p_func, void *p_udata, EditorExportSaveSharedObject p_so_func = nullptr); - Error save_pack(bool p_main_pack, const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files = nullptr, bool p_embed = false, int64_t *r_embedded_start = nullptr, int64_t *r_embedded_size = nullptr); + Error save_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files = nullptr, bool p_embed = false, int64_t *r_embedded_start = nullptr, int64_t *r_embedded_size = nullptr); Error save_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path); virtual bool poll_export() { return false; } diff --git a/editor/export/editor_export_platform_pc.cpp b/editor/export/editor_export_platform_pc.cpp index f583c0ec02..cdaf18b346 100644 --- a/editor/export/editor_export_platform_pc.cpp +++ b/editor/export/editor_export_platform_pc.cpp @@ -194,7 +194,7 @@ Error EditorExportPlatformPC::export_project_data(const Ref<EditorExportPreset> int64_t embedded_pos; int64_t embedded_size; - Error err = save_pack(true, p_preset, p_debug, pck_path, &so_files, p_preset->get("binary_format/embed_pck"), &embedded_pos, &embedded_size); + Error err = save_pack(p_preset, p_debug, pck_path, &so_files, p_preset->get("binary_format/embed_pck"), &embedded_pos, &embedded_size); if (err == OK && p_preset->get("binary_format/embed_pck")) { if (embedded_size >= 0x100000000 && String(p_preset->get("binary_format/architecture")).contains("32")) { add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding"), TTR("On 32-bit exports the embedded PCK cannot be bigger than 4 GiB.")); diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index 2087c8cee6..3b6ce8d396 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -2531,6 +2531,14 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected } } break; + case FILE_COPY_ABSOLUTE_PATH: { + if (!p_selected.is_empty()) { + const String &fpath = p_selected[0]; + const String absolute_path = ProjectSettings::get_singleton()->globalize_path(fpath); + DisplayServer::get_singleton()->clipboard_set(absolute_path); + } + } break; + case FILE_COPY_UID: { if (!p_selected.is_empty()) { ResourceUID::ID uid = ResourceLoader::get_resource_uid(p_selected[0]); @@ -3193,6 +3201,7 @@ void FileSystemDock::_file_and_folders_fill_popup(PopupMenu *p_popup, const Vect if (p_paths.size() == 1) { p_popup->add_icon_shortcut(get_editor_theme_icon(SNAME("ActionCopy")), ED_GET_SHORTCUT("filesystem_dock/copy_path"), FILE_COPY_PATH); + p_popup->add_shortcut(ED_GET_SHORTCUT("filesystem_dock/copy_absolute_path"), FILE_COPY_ABSOLUTE_PATH); if (ResourceLoader::get_resource_uid(p_paths[0]) != ResourceUID::INVALID_ID) { p_popup->add_icon_shortcut(get_editor_theme_icon(SNAME("Instance")), ED_GET_SHORTCUT("filesystem_dock/copy_uid"), FILE_COPY_UID); } @@ -3481,6 +3490,8 @@ void FileSystemDock::_tree_gui_input(Ref<InputEvent> p_event) { _tree_rmb_option(FILE_DUPLICATE); } else if (ED_IS_SHORTCUT("filesystem_dock/copy_path", p_event)) { _tree_rmb_option(FILE_COPY_PATH); + } else if (ED_IS_SHORTCUT("filesystem_dock/copy_absolute_path", p_event)) { + _tree_rmb_option(FILE_COPY_ABSOLUTE_PATH); } else if (ED_IS_SHORTCUT("filesystem_dock/copy_uid", p_event)) { _tree_rmb_option(FILE_COPY_UID); } else if (ED_IS_SHORTCUT("filesystem_dock/delete", p_event)) { @@ -3553,6 +3564,8 @@ void FileSystemDock::_file_list_gui_input(Ref<InputEvent> p_event) { _file_list_rmb_option(FILE_DUPLICATE); } else if (ED_IS_SHORTCUT("filesystem_dock/copy_path", p_event)) { _file_list_rmb_option(FILE_COPY_PATH); + } else if (ED_IS_SHORTCUT("filesystem_dock/copy_absolute_path", p_event)) { + _file_list_rmb_option(FILE_COPY_ABSOLUTE_PATH); } else if (ED_IS_SHORTCUT("filesystem_dock/delete", p_event)) { _file_list_rmb_option(FILE_REMOVE); } else if (ED_IS_SHORTCUT("filesystem_dock/rename", p_event)) { @@ -3856,6 +3869,7 @@ FileSystemDock::FileSystemDock() { // `KeyModifierMask::CMD_OR_CTRL | Key::C` conflicts with other editor shortcuts. ED_SHORTCUT("filesystem_dock/copy_path", TTR("Copy Path"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::C); + ED_SHORTCUT("filesystem_dock/copy_absolute_path", TTR("Copy Absolute Path")); ED_SHORTCUT("filesystem_dock/copy_uid", TTR("Copy UID")); ED_SHORTCUT("filesystem_dock/duplicate", TTR("Duplicate..."), KeyModifierMask::CMD_OR_CTRL | Key::D); ED_SHORTCUT("filesystem_dock/delete", TTR("Delete"), Key::KEY_DELETE); diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h index b950075928..c9fe3a8baf 100644 --- a/editor/filesystem_dock.h +++ b/editor/filesystem_dock.h @@ -129,6 +129,7 @@ private: FILE_OPEN_EXTERNAL, FILE_OPEN_IN_TERMINAL, FILE_COPY_PATH, + FILE_COPY_ABSOLUTE_PATH, FILE_COPY_UID, FOLDER_EXPAND_ALL, FOLDER_COLLAPSE_ALL, diff --git a/editor/gui/scene_tree_editor.h b/editor/gui/scene_tree_editor.h index b4d9644f16..9ae1e99a27 100644 --- a/editor/gui/scene_tree_editor.h +++ b/editor/gui/scene_tree_editor.h @@ -160,6 +160,7 @@ public: void set_marked(const HashSet<Node *> &p_marked, bool p_selectable = true, bool p_children_selectable = true); void set_marked(Node *p_marked, bool p_selectable = true, bool p_children_selectable = true); + bool has_marked() const { return !marked.is_empty(); } void set_selected(Node *p_node, bool p_emit_selected = true); Node *get_selected(); void set_can_rename(bool p_can_rename) { can_rename = p_can_rename; } diff --git a/editor/plugins/path_2d_editor_plugin.cpp b/editor/plugins/path_2d_editor_plugin.cpp index cc9114db39..4fff3bfb8a 100644 --- a/editor/plugins/path_2d_editor_plugin.cpp +++ b/editor/plugins/path_2d_editor_plugin.cpp @@ -225,17 +225,20 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) { break; case ACTION_MOVING_POINT: - case ACTION_MOVING_NEW_POINT: { if (original_mouse_pos != gpoint) { - if (action == ACTION_MOVING_POINT) { - undo_redo->create_action(TTR("Move Point in Curve")); - undo_redo->add_undo_method(curve.ptr(), "set_point_position", action_point, moving_from); - } + undo_redo->create_action(TTR("Move Point in Curve")); + undo_redo->add_undo_method(curve.ptr(), "set_point_position", action_point, moving_from); undo_redo->add_do_method(curve.ptr(), "set_point_position", action_point, cpoint); undo_redo->add_do_method(canvas_item_editor, "update_viewport"); undo_redo->add_undo_method(canvas_item_editor, "update_viewport"); undo_redo->commit_action(false); } + break; + case ACTION_MOVING_NEW_POINT: { + undo_redo->add_do_method(curve.ptr(), "set_point_position", action_point, cpoint); + undo_redo->add_undo_method(canvas_item_editor, "update_viewport"); + undo_redo->add_do_method(canvas_item_editor, "update_viewport"); + undo_redo->commit_action(false); } break; case ACTION_MOVING_IN: { diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 26ddec6603..757b9e72ea 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -76,6 +76,43 @@ void SceneTreeDock::_quick_open() { instantiate_scenes(quick_open->get_selected_files(), scene_tree->get_selected()); } +void SceneTreeDock::_inspect_hovered_node() { + scene_tree->set_selected(node_hovered_now); + scene_tree->set_marked(node_hovered_now); + Tree *tree = scene_tree->get_scene_tree(); + TreeItem *item = tree->get_item_at_position(tree->get_local_mouse_position()); + if (item) { + item->set_as_cursor(0); + } + InspectorDock::get_inspector_singleton()->edit(node_hovered_now); + InspectorDock::get_inspector_singleton()->propagate_notification(NOTIFICATION_DRAG_BEGIN); // Enable inspector drag preview after it updated. + InspectorDock::get_singleton()->update(node_hovered_now); + EditorNode::get_singleton()->hide_unused_editors(); +} + +void SceneTreeDock::_handle_hover_to_inspect() { + Tree *tree = scene_tree->get_scene_tree(); + TreeItem *item = tree->get_item_at_position(tree->get_local_mouse_position()); + + if (item) { + const NodePath &np = item->get_metadata(0); + node_hovered_now = get_node_or_null(np); + if (node_hovered_previously != node_hovered_now) { + inspect_hovered_node_delay->start(); + } + node_hovered_previously = node_hovered_now; + } else { + _reset_hovering_timer(); + } +} + +void SceneTreeDock::_reset_hovering_timer() { + if (!inspect_hovered_node_delay->is_stopped()) { + inspect_hovered_node_delay->stop(); + } + node_hovered_previously = nullptr; +} + void SceneTreeDock::input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); @@ -92,6 +129,17 @@ void SceneTreeDock::input(const Ref<InputEvent> &p_event) { _push_item(pending_click_select); pending_click_select = nullptr; } + + if (mb->is_released()) { + if (scene_tree->has_marked()) { + scene_tree->set_marked(nullptr); + } + _reset_hovering_timer(); + } + } + + if (tree_clicked && get_viewport()->gui_is_dragging()) { + _handle_hover_to_inspect(); } } @@ -1539,6 +1587,10 @@ void SceneTreeDock::_notification(int p_what) { } } } break; + + case NOTIFICATION_DRAG_END: { + _reset_hovering_timer(); + } break; } } @@ -4300,6 +4352,7 @@ SceneTreeDock::SceneTreeDock(Node *p_scene_root, EditorSelection *p_editor_selec scene_tree->connect("files_dropped", callable_mp(this, &SceneTreeDock::_files_dropped)); scene_tree->connect("script_dropped", callable_mp(this, &SceneTreeDock::_script_dropped)); scene_tree->connect("nodes_dragged", callable_mp(this, &SceneTreeDock::_nodes_drag_begin)); + scene_tree->connect("mouse_exited", callable_mp(this, &SceneTreeDock::_reset_hovering_timer)); scene_tree->get_scene_tree()->connect("gui_input", callable_mp(this, &SceneTreeDock::_scene_tree_gui_input)); scene_tree->get_scene_tree()->connect("item_icon_double_clicked", callable_mp(this, &SceneTreeDock::_focus_node)); @@ -4309,6 +4362,11 @@ SceneTreeDock::SceneTreeDock(Node *p_scene_root, EditorSelection *p_editor_selec scene_tree->set_as_scene_tree_dock(); scene_tree->set_editor_selection(editor_selection); + inspect_hovered_node_delay = memnew(Timer); + inspect_hovered_node_delay->connect("timeout", callable_mp(this, &SceneTreeDock::_inspect_hovered_node)); + inspect_hovered_node_delay->set_one_shot(true); + add_child(inspect_hovered_node_delay); + create_dialog = memnew(CreateDialog); create_dialog->set_base_type("Node"); add_child(create_dialog); diff --git a/editor/scene_tree_dock.h b/editor/scene_tree_dock.h index 4c1eb5715a..86e9ff8a47 100644 --- a/editor/scene_tree_dock.h +++ b/editor/scene_tree_dock.h @@ -234,6 +234,14 @@ class SceneTreeDock : public VBoxContainer { void _node_prerenamed(Node *p_node, const String &p_new_name); void _nodes_drag_begin(); + + void _handle_hover_to_inspect(); + void _inspect_hovered_node(); + void _reset_hovering_timer(); + Timer *inspect_hovered_node_delay = nullptr; + Node *node_hovered_now = nullptr; + Node *node_hovered_previously = nullptr; + virtual void input(const Ref<InputEvent> &p_event) override; virtual void shortcut_input(const Ref<InputEvent> &p_event) override; void _scene_tree_gui_input(Ref<InputEvent> p_event); diff --git a/main/main.cpp b/main/main.cpp index 6a132a37ae..357033b6d8 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -92,6 +92,7 @@ #include "editor/debugger/editor_debugger_node.h" #include "editor/doc_data_class_path.gen.h" #include "editor/doc_tools.h" +#include "editor/editor_file_system.h" #include "editor/editor_help.h" #include "editor/editor_node.h" #include "editor/editor_paths.h" @@ -184,6 +185,7 @@ static OS::ProcessID editor_pid = 0; static bool found_project = false; static bool auto_build_solutions = false; static String debug_server_uri; +static bool wait_for_import = false; #ifndef DISABLE_DEPRECATED static int converter_max_kb_file = 4 * 1024; // 4MB static int converter_max_line_length = 100000; @@ -617,6 +619,7 @@ void Main::print_help(const char *p_binary) { print_help_option("--main-loop <main_loop_name>", "Run a MainLoop specified by its global class name.\n"); print_help_option("--check-only", "Only parse for errors and quit (use with --script).\n"); #ifdef TOOLS_ENABLED + print_help_option("--import", "Starts the editor, waits for any resources to be imported, and then quits.\n", CLI_OPTION_AVAILABILITY_EDITOR); print_help_option("--export-release <preset> <path>", "Export the project in release mode using the given preset and output path. The preset name should match one defined in \"export_presets.cfg\".\n", CLI_OPTION_AVAILABILITY_EDITOR); print_help_option("", "<path> should be absolute or relative to the project directory, and include the filename for the binary (e.g. \"builds/game.exe\").\n"); print_help_option("", "The target directory must exist.\n"); @@ -1420,12 +1423,17 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph OS::get_singleton()->print("Missing file to load argument after --validate-extension-api, aborting."); goto error; } - + } else if (I->get() == "--import") { + editor = true; + cmdline_tool = true; + wait_for_import = true; + quit_after = 1; } else if (I->get() == "--export-release" || I->get() == "--export-debug" || I->get() == "--export-pack") { // Export project // Actually handling is done in start(). editor = true; cmdline_tool = true; + wait_for_import = true; main_args.push_back(I->get()); #ifndef DISABLE_DEPRECATED } else if (I->get() == "--export") { // For users used to 3.x syntax. @@ -4084,6 +4092,12 @@ bool Main::iteration() { exit = true; } +#ifdef TOOLS_ENABLED + if (wait_for_import && EditorFileSystem::get_singleton()->doing_first_scan()) { + exit = false; + } +#endif + if (fixed_fps != -1) { return exit; } diff --git a/misc/extension_api_validation/4.2-stable.expected b/misc/extension_api_validation/4.2-stable.expected index bb10b422dc..9cd732813e 100644 --- a/misc/extension_api_validation/4.2-stable.expected +++ b/misc/extension_api_validation/4.2-stable.expected @@ -241,13 +241,6 @@ Validate extension JSON: Error: Field 'classes/AcceptDialog/methods/remove_butto Changed argument type to the more specific one actually expected by the method. Compatibility method registered. -GH-89992 --------- -Validate extension JSON: Error: Field 'classes/Node/methods/replace_by/arguments': size changed value in new API, from 2 to 3. - -Added optional argument to prevent children to be reparented during replace_by. Compatibility method registered. - - GH-88047 -------- Validate extension JSON: Error: Field 'classes/AStar2D/methods/get_id_path/arguments': size changed value in new API, from 2 to 3. diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index a51b5f90f9..f8cfd29b9b 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -3457,9 +3457,7 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) { if (op_type.is_variant() || !op_type.is_hard_type()) { mark_node_unsafe(p_cast); #ifdef DEBUG_ENABLED - if (op_type.is_variant() && !op_type.is_hard_type()) { - parser->push_warning(p_cast, GDScriptWarning::UNSAFE_CAST, cast_type.to_string()); - } + parser->push_warning(p_cast, GDScriptWarning::UNSAFE_CAST, cast_type.to_string()); #endif } else { bool valid = false; diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 854c944e14..eaef8a961c 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -651,6 +651,21 @@ static int _get_enum_constant_location(const StringName &p_class, const StringNa return depth | ScriptLanguage::LOCATION_PARENT_MASK; } +static int _get_enum_location(const StringName &p_class, const StringName &p_enum) { + if (!ClassDB::has_enum(p_class, p_enum)) { + return ScriptLanguage::LOCATION_OTHER; + } + + int depth = 0; + StringName class_test = p_class; + while (class_test && !ClassDB::has_enum(class_test, p_enum, true)) { + class_test = ClassDB::get_parent_class(class_test); + depth++; + } + + return depth | ScriptLanguage::LOCATION_PARENT_MASK; +} + // END LOCATION METHODS static String _trim_parent_class(const String &p_class, const String &p_base_class) { @@ -1202,13 +1217,15 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base return; } + List<StringName> enums; + ClassDB::get_enum_list(type, &enums); + for (const StringName &E : enums) { + int location = p_recursion_depth + _get_enum_location(type, E); + ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_ENUM, location); + r_result.insert(option.display, option); + } + if (p_types_only) { - List<StringName> enums; - ClassDB::get_enum_list(type, &enums); - for (const StringName &E : enums) { - ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_ENUM); - r_result.insert(option.display, option); - } return; } @@ -1268,7 +1285,20 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base } return; } break; - case GDScriptParser::DataType::ENUM: + case GDScriptParser::DataType::ENUM: { + String type_str = base_type.native_type; + StringName type = type_str.get_slicec('.', 0); + StringName type_enum = base_type.enum_type; + + List<StringName> enum_values; + ClassDB::get_enum_constants(type, type_enum, &enum_values); + for (const StringName &E : enum_values) { + int location = p_recursion_depth + _get_enum_constant_location(type, E); + ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT, location); + r_result.insert(option.display, option); + } + } + [[fallthrough]]; case GDScriptParser::DataType::BUILTIN: { if (p_types_only) { return; diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp index 8549525ced..708966a0a8 100644 --- a/modules/gdscript/gdscript_warning.cpp +++ b/modules/gdscript/gdscript_warning.cpp @@ -96,7 +96,7 @@ String GDScriptWarning::get_message() const { return vformat(R"*(The method "%s()" is not present on the inferred type "%s" (but may be present on a subtype).)*", symbols[0], symbols[1]); case UNSAFE_CAST: CHECK_SYMBOLS(1); - return vformat(R"(The value is cast to "%s" but has an unknown type.)", symbols[0]); + return vformat(R"(Casting "Variant" to "%s" is unsafe.)", symbols[0]); case UNSAFE_CALL_ARGUMENT: CHECK_SYMBOLS(5); return vformat(R"*(The argument %s of the %s "%s()" requires the subtype "%s" but the supertype "%s" was provided.)*", symbols[0], symbols[1], symbols[2], symbols[3], symbols[4]); diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h index 69cc8c179f..93c232a0f8 100644 --- a/modules/gdscript/gdscript_warning.h +++ b/modules/gdscript/gdscript_warning.h @@ -65,7 +65,7 @@ public: INFERRED_DECLARATION, // Variable/constant/parameter has an implicitly inferred static type. UNSAFE_PROPERTY_ACCESS, // Property not found in the detected type (but can be in subtypes). UNSAFE_METHOD_ACCESS, // Function not found in the detected type (but can be in subtypes). - UNSAFE_CAST, // Cast used in an unknown type. + UNSAFE_CAST, // Casting a `Variant` value to non-`Variant`. UNSAFE_CALL_ARGUMENT, // Function call argument is of a supertype of the required type. UNSAFE_VOID_RETURN, // Function returns void but returned a call to a function that can't be type checked. RETURN_VALUE_DISCARDED, // Function call returns something but the value isn't used. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_array.gd b/modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_array.gd new file mode 100644 index 0000000000..b53e814eea --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_array.gd @@ -0,0 +1,3 @@ +func test(): + var integer := 1 + print(integer as Array) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_array.out b/modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_array.out new file mode 100644 index 0000000000..e3e82c2b7e --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_array.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Invalid cast. Cannot convert from "int" to "Array". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_object.gd b/modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_object.gd new file mode 100644 index 0000000000..323e367f8e --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_object.gd @@ -0,0 +1,3 @@ +func test(): + var integer := 1 + print(integer as Node) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_object.out b/modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_object.out new file mode 100644 index 0000000000..7de40418bf --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_object.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Invalid cast. Cannot convert from "int" to "Node". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cast_object_to_int.gd b/modules/gdscript/tests/scripts/analyzer/errors/cast_object_to_int.gd new file mode 100644 index 0000000000..f6cd5e217e --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cast_object_to_int.gd @@ -0,0 +1,3 @@ +func test(): + var object := RefCounted.new() + print(object as int) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cast_object_to_int.out b/modules/gdscript/tests/scripts/analyzer/errors/cast_object_to_int.out new file mode 100644 index 0000000000..8af0847577 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cast_object_to_int.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Invalid cast. Cannot convert from "RefCounted" to "int". diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_cast.gd b/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_cast.gd new file mode 100644 index 0000000000..1a6d10f8f7 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_cast.gd @@ -0,0 +1,24 @@ +# We don't want to execute it because of errors, just analyze. +func no_exec_test(): + var weak_int = 1 + print(weak_int as Variant) # No warning. + print(weak_int as int) + print(weak_int as Node) + + var weak_node = Node.new() + print(weak_node as Variant) # No warning. + print(weak_node as int) + print(weak_node as Node) + + var weak_variant = null + print(weak_variant as Variant) # No warning. + print(weak_variant as int) + print(weak_variant as Node) + + var hard_variant: Variant = null + print(hard_variant as Variant) # No warning. + print(hard_variant as int) + print(hard_variant as Node) + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_cast.out b/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_cast.out new file mode 100644 index 0000000000..c1e683d942 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_cast.out @@ -0,0 +1,33 @@ +GDTEST_OK +>> WARNING +>> Line: 5 +>> UNSAFE_CAST +>> Casting "Variant" to "int" is unsafe. +>> WARNING +>> Line: 6 +>> UNSAFE_CAST +>> Casting "Variant" to "Node" is unsafe. +>> WARNING +>> Line: 10 +>> UNSAFE_CAST +>> Casting "Variant" to "int" is unsafe. +>> WARNING +>> Line: 11 +>> UNSAFE_CAST +>> Casting "Variant" to "Node" is unsafe. +>> WARNING +>> Line: 15 +>> UNSAFE_CAST +>> Casting "Variant" to "int" is unsafe. +>> WARNING +>> Line: 16 +>> UNSAFE_CAST +>> Casting "Variant" to "Node" is unsafe. +>> WARNING +>> Line: 20 +>> UNSAFE_CAST +>> Casting "Variant" to "int" is unsafe. +>> WARNING +>> Line: 21 +>> UNSAFE_CAST +>> Casting "Variant" to "Node" is unsafe. diff --git a/modules/gdscript/tests/scripts/completion/builtin_enum/builtin_enum_autocomplete.cfg b/modules/gdscript/tests/scripts/completion/builtin_enum/builtin_enum_autocomplete.cfg new file mode 100644 index 0000000000..7c7b465f26 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/builtin_enum/builtin_enum_autocomplete.cfg @@ -0,0 +1,9 @@ +[output] +include=[ + {"display": "DrawMode", + "location": 256}, + {"display": "Anchor", + "location": 257}, + {"display": "TextureRepeat", + "location": 258}, +] diff --git a/modules/gdscript/tests/scripts/completion/builtin_enum/builtin_enum_autocomplete.gd b/modules/gdscript/tests/scripts/completion/builtin_enum/builtin_enum_autocomplete.gd new file mode 100644 index 0000000000..83f4b17a86 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/builtin_enum/builtin_enum_autocomplete.gd @@ -0,0 +1,4 @@ +extends Control + +func _ready(): + var t = BaseButton.➡ diff --git a/modules/gdscript/tests/scripts/completion/builtin_enum/builtin_enum_values_autocompletion.cfg b/modules/gdscript/tests/scripts/completion/builtin_enum/builtin_enum_values_autocompletion.cfg new file mode 100644 index 0000000000..7ccfa550e2 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/builtin_enum/builtin_enum_values_autocompletion.cfg @@ -0,0 +1,5 @@ +[output] +include=[ + {"display": "HEURISTIC_MAX"}, + {"display": "HEURISTIC_OCTILE"}, +] diff --git a/modules/gdscript/tests/scripts/completion/builtin_enum/builtin_enum_values_autocompletion.gd b/modules/gdscript/tests/scripts/completion/builtin_enum/builtin_enum_values_autocompletion.gd new file mode 100644 index 0000000000..99e38be6b9 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/builtin_enum/builtin_enum_values_autocompletion.gd @@ -0,0 +1,4 @@ +extends Control + +func _ready(): + AStarGrid2D.Heuristic.➡ diff --git a/modules/gdscript/tests/scripts/runtime/errors/cast_freed_object.gd b/modules/gdscript/tests/scripts/runtime/errors/cast_freed_object.gd new file mode 100644 index 0000000000..6b766f4d3d --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/cast_freed_object.gd @@ -0,0 +1,4 @@ +func test(): + var node := Node.new() + node.free() + print(node as Node2D) diff --git a/modules/gdscript/tests/scripts/runtime/errors/cast_freed_object.out b/modules/gdscript/tests/scripts/runtime/errors/cast_freed_object.out new file mode 100644 index 0000000000..90d81dd9a1 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/cast_freed_object.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/cast_freed_object.gd +>> 4 +>> Trying to cast a freed object. diff --git a/modules/gdscript/tests/scripts/runtime/errors/cast_int_to_array.gd b/modules/gdscript/tests/scripts/runtime/errors/cast_int_to_array.gd new file mode 100644 index 0000000000..00817c588f --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/cast_int_to_array.gd @@ -0,0 +1,4 @@ +func test(): + var integer: Variant = 1 + @warning_ignore("unsafe_cast") + print(integer as Array) diff --git a/modules/gdscript/tests/scripts/runtime/errors/cast_int_to_array.out b/modules/gdscript/tests/scripts/runtime/errors/cast_int_to_array.out new file mode 100644 index 0000000000..545d7a4906 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/cast_int_to_array.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/cast_int_to_array.gd +>> 4 +>> Invalid cast: could not convert value to 'Array'. diff --git a/modules/gdscript/tests/scripts/runtime/errors/cast_int_to_object.gd b/modules/gdscript/tests/scripts/runtime/errors/cast_int_to_object.gd new file mode 100644 index 0000000000..44673a4513 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/cast_int_to_object.gd @@ -0,0 +1,4 @@ +func test(): + var integer: Variant = 1 + @warning_ignore("unsafe_cast") + print(integer as Node) diff --git a/modules/gdscript/tests/scripts/runtime/errors/cast_int_to_object.out b/modules/gdscript/tests/scripts/runtime/errors/cast_int_to_object.out new file mode 100644 index 0000000000..7c39b46396 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/cast_int_to_object.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/cast_int_to_object.gd +>> 4 +>> Invalid cast: can't convert a non-object value to an object type. diff --git a/modules/gdscript/tests/scripts/runtime/errors/cast_object_to_int.gd b/modules/gdscript/tests/scripts/runtime/errors/cast_object_to_int.gd new file mode 100644 index 0000000000..830d0c0c4a --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/cast_object_to_int.gd @@ -0,0 +1,4 @@ +func test(): + var object: Variant = RefCounted.new() + @warning_ignore("unsafe_cast") + print(object as int) diff --git a/modules/gdscript/tests/scripts/runtime/errors/cast_object_to_int.out b/modules/gdscript/tests/scripts/runtime/errors/cast_object_to_int.out new file mode 100644 index 0000000000..f922199fb3 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/cast_object_to_int.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/cast_object_to_int.gd +>> 4 +>> Invalid cast: could not convert value to 'int'. diff --git a/modules/gdscript/tests/scripts/runtime/features/type_casting.gd b/modules/gdscript/tests/scripts/runtime/features/type_casting.gd new file mode 100644 index 0000000000..c63ea16c32 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/type_casting.gd @@ -0,0 +1,24 @@ +func print_value(value: Variant) -> void: + if value is Object: + @warning_ignore("unsafe_method_access") + print("<%s>" % value.get_class()) + else: + print(var_to_str(value)) + +func test(): + var int_value := 1 + print_value(int_value as Variant) + print_value(int_value as int) + print_value(int_value as float) + + var node_value := Node.new() + print_value(node_value as Variant) + print_value(node_value as Object) + print_value(node_value as Node) + print_value(node_value as Node2D) + node_value.free() + + var null_value = null + print_value(null_value as Variant) + @warning_ignore("unsafe_cast") + print_value(null_value as Node) diff --git a/modules/gdscript/tests/scripts/runtime/features/type_casting.out b/modules/gdscript/tests/scripts/runtime/features/type_casting.out new file mode 100644 index 0000000000..7da5a4c0a4 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/type_casting.out @@ -0,0 +1,10 @@ +GDTEST_OK +1 +1 +1.0 +<Node> +<Node> +<Node> +null +null +null diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 0dd1dc7c12..a3464ccfc2 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -410,115 +410,6 @@ ScriptLanguage::ScriptNameCasing CSharpLanguage::preferred_file_name_casing() co } #ifdef TOOLS_ENABLED -struct VariantCsName { - Variant::Type variant_type; - const String cs_type; -}; - -static String variant_type_to_managed_name(const String &p_var_type_name) { - if (p_var_type_name.is_empty()) { - return "Variant"; - } - - if (ClassDB::class_exists(p_var_type_name)) { - return pascal_to_pascal_case(p_var_type_name); - } - - if (p_var_type_name == Variant::get_type_name(Variant::OBJECT)) { - return "GodotObject"; - } - - if (p_var_type_name == Variant::get_type_name(Variant::INT)) { - return "long"; - } - - if (p_var_type_name == Variant::get_type_name(Variant::FLOAT)) { - return "double"; - } - - if (p_var_type_name == Variant::get_type_name(Variant::STRING)) { - return "string"; // I prefer this one >:[ - } - - if (p_var_type_name == Variant::get_type_name(Variant::DICTIONARY)) { - return "Collections.Dictionary"; - } - - if (p_var_type_name.begins_with(Variant::get_type_name(Variant::ARRAY) + "[")) { - String element_type = p_var_type_name.trim_prefix(Variant::get_type_name(Variant::ARRAY) + "[").trim_suffix("]"); - return "Collections.Array<" + variant_type_to_managed_name(element_type) + ">"; - } - - if (p_var_type_name == Variant::get_type_name(Variant::ARRAY)) { - return "Collections.Array"; - } - - if (p_var_type_name == Variant::get_type_name(Variant::PACKED_BYTE_ARRAY)) { - return "byte[]"; - } - if (p_var_type_name == Variant::get_type_name(Variant::PACKED_INT32_ARRAY)) { - return "int[]"; - } - if (p_var_type_name == Variant::get_type_name(Variant::PACKED_INT64_ARRAY)) { - return "long[]"; - } - if (p_var_type_name == Variant::get_type_name(Variant::PACKED_FLOAT32_ARRAY)) { - return "float[]"; - } - if (p_var_type_name == Variant::get_type_name(Variant::PACKED_FLOAT64_ARRAY)) { - return "double[]"; - } - if (p_var_type_name == Variant::get_type_name(Variant::PACKED_STRING_ARRAY)) { - return "string[]"; - } - if (p_var_type_name == Variant::get_type_name(Variant::PACKED_VECTOR2_ARRAY)) { - return "Vector2[]"; - } - if (p_var_type_name == Variant::get_type_name(Variant::PACKED_VECTOR3_ARRAY)) { - return "Vector3[]"; - } - if (p_var_type_name == Variant::get_type_name(Variant::PACKED_COLOR_ARRAY)) { - return "Color[]"; - } - - if (p_var_type_name == Variant::get_type_name(Variant::SIGNAL)) { - return "Signal"; - } - - const VariantCsName var_types[] = { - { Variant::BOOL, "bool" }, - { Variant::INT, "long" }, - { Variant::VECTOR2, "Vector2" }, - { Variant::VECTOR2I, "Vector2I" }, - { Variant::RECT2, "Rect2" }, - { Variant::RECT2I, "Rect2I" }, - { Variant::VECTOR3, "Vector3" }, - { Variant::VECTOR3I, "Vector3I" }, - { Variant::TRANSFORM2D, "Transform2D" }, - { Variant::VECTOR4, "Vector4" }, - { Variant::VECTOR4I, "Vector4I" }, - { Variant::PLANE, "Plane" }, - { Variant::QUATERNION, "Quaternion" }, - { Variant::AABB, "Aabb" }, - { Variant::BASIS, "Basis" }, - { Variant::TRANSFORM3D, "Transform3D" }, - { Variant::PROJECTION, "Projection" }, - { Variant::COLOR, "Color" }, - { Variant::STRING_NAME, "StringName" }, - { Variant::NODE_PATH, "NodePath" }, - { Variant::RID, "Rid" }, - { Variant::CALLABLE, "Callable" }, - }; - - for (unsigned int i = 0; i < sizeof(var_types) / sizeof(VariantCsName); i++) { - if (p_var_type_name == Variant::get_type_name(var_types[i].variant_type)) { - return var_types[i].cs_type; - } - } - - return "Variant"; -} - String CSharpLanguage::make_function(const String &, const String &p_name, const PackedStringArray &p_args) const { // The make_function() API does not work for C# scripts. // It will always append the generated function at the very end of the script. In C#, it will break compilation by diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs index 57611d5f4c..d3720dcb72 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs @@ -209,7 +209,7 @@ namespace GodotTools.Export List<string> outputPaths = new(); - bool embedBuildResults = (bool)GetOption("dotnet/embed_build_outputs") || platform == OS.Platforms.Android; + bool embedBuildResults = ((bool)GetOption("dotnet/embed_build_outputs") || platform == OS.Platforms.Android) && platform != OS.Platforms.MacOS; foreach (PublishConfig config in targets) { diff --git a/modules/mono/mono_gd/gd_mono.h b/modules/mono/mono_gd/gd_mono.h index 1b46a619ca..0cb087db57 100644 --- a/modules/mono/mono_gd/gd_mono.h +++ b/modules/mono/mono_gd/gd_mono.h @@ -77,7 +77,9 @@ class GDMono { void _try_load_project_assembly(); #endif +#ifdef DEBUG_METHODS_ENABLED uint64_t api_core_hash = 0; +#endif #ifdef TOOLS_ENABLED uint64_t api_editor_hash = 0; #endif diff --git a/modules/openxr/extensions/openxr_composition_layer_extension.cpp b/modules/openxr/extensions/openxr_composition_layer_extension.cpp index cb117d7bb7..1fba8e5f8b 100644 --- a/modules/openxr/extensions/openxr_composition_layer_extension.cpp +++ b/modules/openxr/extensions/openxr_composition_layer_extension.cpp @@ -196,20 +196,20 @@ XrCompositionLayerBaseHeader *OpenXRViewportCompositionLayerProvider::get_compos return nullptr; } - if (swapchain_info.swapchain == XR_NULL_HANDLE) { + if (swapchain_info.get_swapchain() == XR_NULL_HANDLE) { // Don't have a swapchain to display? Ignore our layer. return nullptr; } - if (swapchain_info.image_acquired) { - openxr_api->release_image(swapchain_info); + if (swapchain_info.is_image_acquired()) { + swapchain_info.release(); } // Update the layer struct for the swapchain. switch (composition_layer->type) { case XR_TYPE_COMPOSITION_LAYER_QUAD: { XrCompositionLayerQuad *quad_layer = (XrCompositionLayerQuad *)composition_layer; - quad_layer->subImage.swapchain = swapchain_info.swapchain; + quad_layer->subImage.swapchain = swapchain_info.get_swapchain(); quad_layer->subImage.imageArrayIndex = 0; quad_layer->subImage.imageRect.offset.x = 0; quad_layer->subImage.imageRect.offset.y = 0; @@ -219,7 +219,7 @@ XrCompositionLayerBaseHeader *OpenXRViewportCompositionLayerProvider::get_compos case XR_TYPE_COMPOSITION_LAYER_CYLINDER_KHR: { XrCompositionLayerCylinderKHR *cylinder_layer = (XrCompositionLayerCylinderKHR *)composition_layer; - cylinder_layer->subImage.swapchain = swapchain_info.swapchain; + cylinder_layer->subImage.swapchain = swapchain_info.get_swapchain(); cylinder_layer->subImage.imageArrayIndex = 0; cylinder_layer->subImage.imageRect.offset.x = 0; cylinder_layer->subImage.imageRect.offset.y = 0; @@ -229,7 +229,7 @@ XrCompositionLayerBaseHeader *OpenXRViewportCompositionLayerProvider::get_compos case XR_TYPE_COMPOSITION_LAYER_EQUIRECT2_KHR: { XrCompositionLayerEquirect2KHR *equirect_layer = (XrCompositionLayerEquirect2KHR *)composition_layer; - equirect_layer->subImage.swapchain = swapchain_info.swapchain; + equirect_layer->subImage.swapchain = swapchain_info.get_swapchain(); equirect_layer->subImage.imageArrayIndex = 0; equirect_layer->subImage.imageRect.offset.x = 0; equirect_layer->subImage.imageRect.offset.y = 0; @@ -269,14 +269,16 @@ bool OpenXRViewportCompositionLayerProvider::update_and_acquire_swapchain(bool p } // See if our current swapchain is outdated. - if (swapchain_info.swapchain != XR_NULL_HANDLE) { + if (swapchain_info.get_swapchain() != XR_NULL_HANDLE) { // If this swap chain, or the previous one, were static, then we can't reuse it. if (swapchain_size == viewport_size && !p_static_image && !static_image) { // We're all good! Just acquire it. - return openxr_api->acquire_image(swapchain_info); + // We can ignore should_render here, return will be false. + XrBool32 should_render = true; + return swapchain_info.acquire(should_render); } - openxr_api->free_swapchain(swapchain_info); + swapchain_info.queue_free(); } // Create our new swap chain @@ -287,13 +289,15 @@ bool OpenXRViewportCompositionLayerProvider::update_and_acquire_swapchain(bool p if (p_static_image) { create_flags |= XR_SWAPCHAIN_CREATE_STATIC_IMAGE_BIT; } - if (!openxr_api->create_swapchain(create_flags, XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT, swapchain_format, viewport_size.width, viewport_size.height, sample_count, array_size, swapchain_info.swapchain, &swapchain_info.swapchain_graphics_data)) { + if (!swapchain_info.create(create_flags, XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT, swapchain_format, viewport_size.width, viewport_size.height, sample_count, array_size)) { swapchain_size = Size2i(); return false; } - // Acquire our image so we can start rendering into it - bool ret = openxr_api->acquire_image(swapchain_info); + // Acquire our image so we can start rendering into it, + // we can ignore should_render here, ret will be false. + XrBool32 should_render = true; + bool ret = swapchain_info.acquire(should_render); swapchain_size = viewport_size; static_image = p_static_image; @@ -301,8 +305,8 @@ bool OpenXRViewportCompositionLayerProvider::update_and_acquire_swapchain(bool p } void OpenXRViewportCompositionLayerProvider::free_swapchain() { - if (swapchain_info.swapchain != XR_NULL_HANDLE) { - openxr_api->free_swapchain(swapchain_info); + if (swapchain_info.get_swapchain() != XR_NULL_HANDLE) { + swapchain_info.queue_free(); } swapchain_size = Size2i(); @@ -314,5 +318,5 @@ RID OpenXRViewportCompositionLayerProvider::get_current_swapchain_texture() { return RID(); } - return openxr_api->get_image(swapchain_info); + return swapchain_info.get_image(); } diff --git a/modules/openxr/extensions/platform/openxr_vulkan_extension.cpp b/modules/openxr/extensions/platform/openxr_vulkan_extension.cpp index f5e7fc192c..da613f8435 100644 --- a/modules/openxr/extensions/platform/openxr_vulkan_extension.cpp +++ b/modules/openxr/extensions/platform/openxr_vulkan_extension.cpp @@ -229,6 +229,10 @@ void OpenXRVulkanExtension::get_usable_swapchain_formats(Vector<int64_t> &p_usab } void OpenXRVulkanExtension::get_usable_depth_formats(Vector<int64_t> &p_usable_swap_chains) { + // Note, it is very likely we do NOT support any of depth formats where we can combine our stencil support (e.g. _S8_UINT). + // Right now this isn't a problem but once stencil support becomes an issue, we need to check for this in the rendering engine + // and create a separate buffer for the stencil. + p_usable_swap_chains.push_back(VK_FORMAT_D24_UNORM_S8_UINT); p_usable_swap_chains.push_back(VK_FORMAT_D32_SFLOAT_S8_UINT); p_usable_swap_chains.push_back(VK_FORMAT_D32_SFLOAT); diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp index 91a4839a06..1fe402341b 100644 --- a/modules/openxr/openxr_api.cpp +++ b/modules/openxr/openxr_api.cpp @@ -63,6 +63,198 @@ #define OPENXR_LOADER_NAME "libopenxr_loader.so" #endif +//////////////////////////////////// +// OpenXRAPI::OpenXRSwapChainInfo + +Vector<OpenXRAPI::OpenXRSwapChainInfo> OpenXRAPI::OpenXRSwapChainInfo::free_queue; + +bool OpenXRAPI::OpenXRSwapChainInfo::create(XrSwapchainCreateFlags p_create_flags, XrSwapchainUsageFlags p_usage_flags, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size) { + OpenXRAPI *openxr_api = OpenXRAPI::get_singleton(); + ERR_FAIL_NULL_V(openxr_api, false); + + XrSession xr_session = openxr_api->get_session(); + ERR_FAIL_COND_V(xr_session == XR_NULL_HANDLE, false); + + OpenXRGraphicsExtensionWrapper *xr_graphics_extension = openxr_api->get_graphics_extension(); + ERR_FAIL_NULL_V(xr_graphics_extension, false); + + // We already have a swapchain? + ERR_FAIL_COND_V(swapchain != XR_NULL_HANDLE, false); + + XrResult result; + + void *next_pointer = nullptr; + for (OpenXRExtensionWrapper *wrapper : openxr_api->get_registered_extension_wrappers()) { + void *np = wrapper->set_swapchain_create_info_and_get_next_pointer(next_pointer); + if (np != nullptr) { + next_pointer = np; + } + } + + XrSwapchainCreateInfo swapchain_create_info = { + XR_TYPE_SWAPCHAIN_CREATE_INFO, // type + next_pointer, // next + p_create_flags, // createFlags + p_usage_flags, // usageFlags + p_swapchain_format, // format + p_sample_count, // sampleCount + p_width, // width + p_height, // height + 1, // faceCount + p_array_size, // arraySize + 1 // mipCount + }; + + XrSwapchain new_swapchain; + result = openxr_api->xrCreateSwapchain(xr_session, &swapchain_create_info, &new_swapchain); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to get swapchain [", openxr_api->get_error_string(result), "]"); + return false; + } + + if (!xr_graphics_extension->get_swapchain_image_data(new_swapchain, p_swapchain_format, p_width, p_height, p_sample_count, p_array_size, &swapchain_graphics_data)) { + openxr_api->xrDestroySwapchain(new_swapchain); + return false; + } + + swapchain = new_swapchain; + + return true; +} + +void OpenXRAPI::OpenXRSwapChainInfo::queue_free() { + if (image_acquired) { + release(); + } + + if (swapchain != XR_NULL_HANDLE) { + free_queue.push_back(*this); + + swapchain_graphics_data = nullptr; + swapchain = XR_NULL_HANDLE; + } +} + +void OpenXRAPI::OpenXRSwapChainInfo::free_queued() { + for (OpenXRAPI::OpenXRSwapChainInfo &swapchain_info : free_queue) { + swapchain_info.free(); + } + free_queue.clear(); +} + +void OpenXRAPI::OpenXRSwapChainInfo::free() { + OpenXRAPI *openxr_api = OpenXRAPI::get_singleton(); + ERR_FAIL_NULL(openxr_api); + + if (image_acquired) { + release(); + } + + if (openxr_api->get_graphics_extension() && swapchain_graphics_data != nullptr) { + openxr_api->get_graphics_extension()->cleanup_swapchain_graphics_data(&swapchain_graphics_data); + } + + if (swapchain != XR_NULL_HANDLE) { + openxr_api->xrDestroySwapchain(swapchain); + swapchain = XR_NULL_HANDLE; + } +} + +bool OpenXRAPI::OpenXRSwapChainInfo::acquire(XrBool32 &p_should_render) { + ERR_FAIL_COND_V(image_acquired, true); // This was not released when it should be, error out and reuse... + + OpenXRAPI *openxr_api = OpenXRAPI::get_singleton(); + ERR_FAIL_NULL_V(openxr_api, false); + + XrResult result; + + if (!skip_acquire_swapchain) { + XrSwapchainImageAcquireInfo swapchain_image_acquire_info = { + XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO, // type + nullptr // next + }; + + result = openxr_api->xrAcquireSwapchainImage(swapchain, &swapchain_image_acquire_info, &image_index); + if (!XR_UNQUALIFIED_SUCCESS(result)) { + // Make sure end_frame knows we need to submit an empty frame + p_should_render = false; + + if (XR_FAILED(result)) { + // Unexpected failure, log this! + print_line("OpenXR: failed to acquire swapchain image [", openxr_api->get_error_string(result), "]"); + return false; + } else { + // In this scenario we silently fail, the XR runtime is simply not ready yet to acquire the swapchain. + return false; + } + } + } + + XrSwapchainImageWaitInfo swapchain_image_wait_info = { + XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO, // type + nullptr, // next + 17000000 // timeout in nanoseconds + }; + + result = openxr_api->xrWaitSwapchainImage(swapchain, &swapchain_image_wait_info); + if (!XR_UNQUALIFIED_SUCCESS(result)) { + // Make sure end_frame knows we need to submit an empty frame + p_should_render = false; + + if (XR_FAILED(result)) { + // Unexpected failure, log this! + print_line("OpenXR: failed to wait for swapchain image [", openxr_api->get_error_string(result), "]"); + return false; + } else { + // Make sure to skip trying to acquire the swapchain image in the next frame + skip_acquire_swapchain = true; + return false; + } + } else { + skip_acquire_swapchain = false; + } + + image_acquired = true; + return true; +} + +bool OpenXRAPI::OpenXRSwapChainInfo::release() { + if (!image_acquired) { + // Already released or never acquired. + return true; + } + + image_acquired = false; // Regardless if we succeed or not, consider this released. + + OpenXRAPI *openxr_api = OpenXRAPI::get_singleton(); + ERR_FAIL_NULL_V(openxr_api, false); + + XrSwapchainImageReleaseInfo swapchain_image_release_info = { + XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO, // type + nullptr // next + }; + XrResult result = openxr_api->xrReleaseSwapchainImage(swapchain, &swapchain_image_release_info); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to release swapchain image! [", openxr_api->get_error_string(result), "]"); + return false; + } + + return true; +} + +RID OpenXRAPI::OpenXRSwapChainInfo::get_image() { + OpenXRAPI *openxr_api = OpenXRAPI::get_singleton(); + + if (image_acquired && openxr_api && openxr_api->get_graphics_extension()) { + return OpenXRAPI::get_singleton()->get_graphics_extension()->get_texture(swapchain_graphics_data, image_index); + } else { + return RID(); + } +} + +//////////////////////////////////// +// OpenXRAPI + OpenXRAPI *OpenXRAPI::singleton = nullptr; Vector<OpenXRExtensionWrapper *> OpenXRAPI::registered_extension_wrappers; @@ -568,6 +760,21 @@ bool OpenXRAPI::load_supported_view_configuration_views(XrViewConfigurationType print_verbose(String(" - recommended render sample count: ") + itos(view_configuration_views[i].recommendedSwapchainSampleCount)); } + // Allocate buffers we'll be populating with view information. + views = (XrView *)memalloc(sizeof(XrView) * view_count); + ERR_FAIL_NULL_V_MSG(views, false, "OpenXR Couldn't allocate memory for views"); + memset(views, 0, sizeof(XrView) * view_count); + + projection_views = (XrCompositionLayerProjectionView *)memalloc(sizeof(XrCompositionLayerProjectionView) * view_count); + ERR_FAIL_NULL_V_MSG(projection_views, false, "OpenXR Couldn't allocate memory for projection views"); + memset(projection_views, 0, sizeof(XrCompositionLayerProjectionView) * view_count); + + if (submit_depth_buffer && OpenXRCompositionLayerDepthExtension::get_singleton()->is_available()) { + depth_views = (XrCompositionLayerDepthInfoKHR *)memalloc(sizeof(XrCompositionLayerDepthInfoKHR) * view_count); + ERR_FAIL_NULL_V_MSG(depth_views, false, "OpenXR Couldn't allocate memory for depth views"); + memset(depth_views, 0, sizeof(XrCompositionLayerDepthInfoKHR) * view_count); + } + return true; } @@ -878,31 +1085,10 @@ bool OpenXRAPI::is_swapchain_format_supported(int64_t p_swapchain_format) { return false; } -bool OpenXRAPI::create_swapchains() { +bool OpenXRAPI::obtain_swapchain_formats() { ERR_FAIL_NULL_V(graphics_extension, false); ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); - /* - TODO: We need to improve on this, for now we're taking our old approach of creating our main swapchains and substituting - those for the ones Godot normally creates. - This however means we can only use swapchains for our main XR view. - - It would have been nicer if we could override the swapchain creation in Godot with ours but we have a timing issue here. - We can't create XR swapchains until after our XR session is fully instantiated, yet Godot creates its swapchain much earlier. - - Also Godot only creates a swapchain for the main output. - OpenXR will require us to create swapchains as the render target for additional viewports if we want to use the layer system - to optimize text rendering and background rendering as OpenXR may choose to reuse the results for reprojection while we're - already rendering the next frame. - - Finally an area we need to expand upon is that Foveated rendering is only enabled for the swap chain we create, - as we render 3D content into internal buffers that are copied into the swapchain, we do now have (basic) VRS support - */ - - Size2 recommended_size = get_recommended_target_size(); - uint32_t sample_count = 1; - - // We start with our color swapchain... { // Build a vector with swapchain formats we want to use, from best fit to worst Vector<int64_t> usable_swapchain_formats; @@ -923,23 +1109,9 @@ bool OpenXRAPI::create_swapchains() { } else { print_verbose(String("Using color swap chain format:") + get_swapchain_format_name(color_swapchain_format)); } - - if (!create_swapchain(0, XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT, color_swapchain_format, recommended_size.width, recommended_size.height, sample_count, view_count, swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain, &swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain_graphics_data)) { - return false; - } } - views = (XrView *)memalloc(sizeof(XrView) * view_count); - ERR_FAIL_NULL_V_MSG(views, false, "OpenXR Couldn't allocate memory for views"); - - projection_views = (XrCompositionLayerProjectionView *)memalloc(sizeof(XrCompositionLayerProjectionView) * view_count); - ERR_FAIL_NULL_V_MSG(projection_views, false, "OpenXR Couldn't allocate memory for projection views"); - - // We create our depth swapchain if: - // - we've enabled submitting depth buffer - // - we support our depth layer extension - // - we have our spacewarp extension (not yet implemented) - if (submit_depth_buffer && OpenXRCompositionLayerDepthExtension::get_singleton()->is_available()) { + { // Build a vector with swapchain formats we want to use, from best fit to worst Vector<int64_t> usable_swapchain_formats; depth_swapchain_format = 0; @@ -954,18 +1126,51 @@ bool OpenXRAPI::create_swapchains() { } if (depth_swapchain_format == 0) { - print_line("Couldn't find usable depth swap chain format, depth buffer will not be submitted."); + WARN_PRINT_ONCE("Couldn't find usable depth swap chain format, depth buffer will not be submitted if requested."); } else { print_verbose(String("Using depth swap chain format:") + get_swapchain_format_name(depth_swapchain_format)); + } + } - // Note, if VK_FORMAT_D32_SFLOAT is used here but we're using the forward+ renderer, we should probably output a warning. + return true; +} - if (!create_swapchain(0, XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, depth_swapchain_format, recommended_size.width, recommended_size.height, sample_count, view_count, swapchains[OPENXR_SWAPCHAIN_DEPTH].swapchain, &swapchains[OPENXR_SWAPCHAIN_DEPTH].swapchain_graphics_data)) { - return false; - } +bool OpenXRAPI::create_main_swapchains(Size2i p_size) { + ERR_FAIL_NULL_V(graphics_extension, false); + ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); - depth_views = (XrCompositionLayerDepthInfoKHR *)memalloc(sizeof(XrCompositionLayerDepthInfoKHR) * view_count); - ERR_FAIL_NULL_V_MSG(depth_views, false, "OpenXR Couldn't allocate memory for depth views"); + /* + TODO: We need to improve on this, for now we're taking our old approach of creating our main swapchains and substituting + those for the ones Godot normally creates. + This however means we can only use swapchains for our main XR view. + + It would have been nicer if we could override the swapchain creation in Godot with ours but we have a timing issue here. + We can't create XR swapchains until after our XR session is fully instantiated, yet Godot creates its swapchain much earlier. + + We only creates a swapchain for the main output here. + Additional swapchains may be created through our composition layer extension. + + Finally an area we need to expand upon is that Foveated rendering is only enabled for the swap chain we create, + as we render 3D content into internal buffers that are copied into the swapchain, we do now have (basic) VRS support + */ + + main_swapchain_size = p_size; + uint32_t sample_count = 1; + + // We start with our color swapchain... + if (color_swapchain_format != 0) { + if (!main_swapchains[OPENXR_SWAPCHAIN_COLOR].create(0, XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT, color_swapchain_format, main_swapchain_size.width, main_swapchain_size.height, sample_count, view_count)) { + return false; + } + } + + // We create our depth swapchain if: + // - we've enabled submitting depth buffer + // - we support our depth layer extension + // - we have our spacewarp extension (not yet implemented) + if (depth_swapchain_format != 0 && submit_depth_buffer && OpenXRCompositionLayerDepthExtension::get_singleton()->is_available()) { + if (!main_swapchains[OPENXR_SWAPCHAIN_DEPTH].create(0, XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, depth_swapchain_format, main_swapchain_size.width, main_swapchain_size.height, sample_count, view_count)) { + return false; } } @@ -981,28 +1186,30 @@ bool OpenXRAPI::create_swapchains() { projection_views[i].type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW; projection_views[i].next = nullptr; - projection_views[i].subImage.swapchain = swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain; + projection_views[i].subImage.swapchain = main_swapchains[OPENXR_SWAPCHAIN_COLOR].get_swapchain(); projection_views[i].subImage.imageArrayIndex = i; projection_views[i].subImage.imageRect.offset.x = 0; projection_views[i].subImage.imageRect.offset.y = 0; - projection_views[i].subImage.imageRect.extent.width = recommended_size.width; - projection_views[i].subImage.imageRect.extent.height = recommended_size.height; + projection_views[i].subImage.imageRect.extent.width = main_swapchain_size.width; + projection_views[i].subImage.imageRect.extent.height = main_swapchain_size.height; if (submit_depth_buffer && OpenXRCompositionLayerDepthExtension::get_singleton()->is_available() && depth_views) { projection_views[i].next = &depth_views[i]; depth_views[i].type = XR_TYPE_COMPOSITION_LAYER_DEPTH_INFO_KHR; depth_views[i].next = nullptr; - depth_views[i].subImage.swapchain = swapchains[OPENXR_SWAPCHAIN_DEPTH].swapchain; + depth_views[i].subImage.swapchain = main_swapchains[OPENXR_SWAPCHAIN_DEPTH].get_swapchain(); depth_views[i].subImage.imageArrayIndex = i; depth_views[i].subImage.imageRect.offset.x = 0; depth_views[i].subImage.imageRect.offset.y = 0; - depth_views[i].subImage.imageRect.extent.width = recommended_size.width; - depth_views[i].subImage.imageRect.extent.height = recommended_size.height; + depth_views[i].subImage.imageRect.extent.width = main_swapchain_size.width; + depth_views[i].subImage.imageRect.extent.height = main_swapchain_size.height; + // OpenXR spec says that: minDepth < maxDepth. depth_views[i].minDepth = 0.0; depth_views[i].maxDepth = 1.0; - depth_views[i].nearZ = 0.01; // Near and far Z will be set to the correct values in fill_projection_matrix - depth_views[i].farZ = 100.0; + // But we can reverse near and far for reverse-Z. + depth_views[i].nearZ = 100.0; // Near and far Z will be set to the correct values in fill_projection_matrix + depth_views[i].farZ = 0.01; } }; @@ -1029,9 +1236,8 @@ void OpenXRAPI::destroy_session() { depth_views = nullptr; } - for (int i = 0; i < OPENXR_SWAPCHAIN_MAX; i++) { - free_swapchain(swapchains[i]); - } + free_main_swapchains(); + OpenXRSwapChainInfo::free_queued(); if (supported_swapchain_formats != nullptr) { memfree(supported_swapchain_formats); @@ -1064,51 +1270,6 @@ void OpenXRAPI::destroy_session() { } } -bool OpenXRAPI::create_swapchain(XrSwapchainCreateFlags p_create_flags, XrSwapchainUsageFlags p_usage_flags, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, XrSwapchain &r_swapchain, void **r_swapchain_graphics_data) { - ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); - ERR_FAIL_NULL_V(graphics_extension, false); - - XrResult result; - - void *next_pointer = nullptr; - for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { - void *np = wrapper->set_swapchain_create_info_and_get_next_pointer(next_pointer); - if (np != nullptr) { - next_pointer = np; - } - } - - XrSwapchainCreateInfo swapchain_create_info = { - XR_TYPE_SWAPCHAIN_CREATE_INFO, // type - next_pointer, // next - p_create_flags, // createFlags - p_usage_flags, // usageFlags - p_swapchain_format, // format - p_sample_count, // sampleCount - p_width, // width - p_height, // height - 1, // faceCount - p_array_size, // arraySize - 1 // mipCount - }; - - XrSwapchain new_swapchain; - result = xrCreateSwapchain(session, &swapchain_create_info, &new_swapchain); - if (XR_FAILED(result)) { - print_line("OpenXR: Failed to get swapchain [", get_error_string(result), "]"); - return false; - } - - if (!graphics_extension->get_swapchain_image_data(new_swapchain, p_swapchain_format, p_width, p_height, p_sample_count, p_array_size, r_swapchain_graphics_data)) { - xrDestroySwapchain(new_swapchain); - return false; - } - - r_swapchain = new_swapchain; - - return true; -} - bool OpenXRAPI::on_state_idle() { print_verbose("On state idle"); @@ -1135,17 +1296,6 @@ bool OpenXRAPI::on_state_ready() { return false; } - // This is when we create our swapchain, this can be a "long" time after Godot finishes, we can deal with this for now - // but once we want to provide Viewports for additional layers where OpenXR requires us to create further swapchains, - // we'll be creating those viewport WAY before we reach this point. - // We may need to implement a wait in our init in main.cpp polling our events until the session is ready. - // That will be very very ugly - // The other possibility is to create a separate OpenXRViewport type specifically for this goal as part of our OpenXR module - - if (!create_swapchains()) { - return false; - } - // we're running running = true; @@ -1157,8 +1307,6 @@ bool OpenXRAPI::on_state_ready() { xr_interface->on_state_ready(); } - // TODO Tell android - return true; } @@ -1492,6 +1640,11 @@ bool OpenXRAPI::initialize_session() { return false; } + if (!obtain_swapchain_formats()) { + destroy_session(); + return false; + } + return true; } @@ -1651,8 +1804,9 @@ bool OpenXRAPI::get_view_projection(uint32_t p_view, double p_z_near, double p_z // if we're using depth views, make sure we update our near and far there... if (depth_views != nullptr) { for (uint32_t i = 0; i < view_count; i++) { - depth_views[i].nearZ = p_z_near; - depth_views[i].farZ = p_z_far; + // As we are using reverse-Z these need to be flipped. + depth_views[i].nearZ = p_z_far; + depth_views[i].farZ = p_z_near; } } @@ -1798,103 +1952,10 @@ bool OpenXRAPI::process() { return true; } -void OpenXRAPI::free_swapchain(OpenXRSwapChainInfo &p_swapchain) { - if (p_swapchain.image_acquired) { - release_image(p_swapchain); - } - - if (graphics_extension && p_swapchain.swapchain_graphics_data != nullptr) { - graphics_extension->cleanup_swapchain_graphics_data(&p_swapchain.swapchain_graphics_data); - } - - if (p_swapchain.swapchain != XR_NULL_HANDLE) { - xrDestroySwapchain(p_swapchain.swapchain); - p_swapchain.swapchain = XR_NULL_HANDLE; - } -} - -bool OpenXRAPI::acquire_image(OpenXRSwapChainInfo &p_swapchain) { - ERR_FAIL_COND_V(p_swapchain.image_acquired, true); // This was not released when it should be, error out and reuse... - - XrResult result; - - if (!p_swapchain.skip_acquire_swapchain) { - XrSwapchainImageAcquireInfo swapchain_image_acquire_info = { - XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO, // type - nullptr // next - }; - - result = xrAcquireSwapchainImage(p_swapchain.swapchain, &swapchain_image_acquire_info, &p_swapchain.image_index); - if (!XR_UNQUALIFIED_SUCCESS(result)) { - // Make sure end_frame knows we need to submit an empty frame - frame_state.shouldRender = false; - - if (XR_FAILED(result)) { - // Unexpected failure, log this! - print_line("OpenXR: failed to acquire swapchain image [", get_error_string(result), "]"); - return false; - } else { - // In this scenario we silently fail, the XR runtime is simply not ready yet to acquire the swapchain. - return false; - } - } - } - - XrSwapchainImageWaitInfo swapchain_image_wait_info = { - XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO, // type - nullptr, // next - 17000000 // timeout in nanoseconds - }; - - result = xrWaitSwapchainImage(p_swapchain.swapchain, &swapchain_image_wait_info); - if (!XR_UNQUALIFIED_SUCCESS(result)) { - // Make sure end_frame knows we need to submit an empty frame - frame_state.shouldRender = false; - - if (XR_FAILED(result)) { - // Unexpected failure, log this! - print_line("OpenXR: failed to wait for swapchain image [", get_error_string(result), "]"); - return false; - } else { - // Make sure to skip trying to acquire the swapchain image in the next frame - p_swapchain.skip_acquire_swapchain = true; - return false; - } - } else { - p_swapchain.skip_acquire_swapchain = false; - } - - p_swapchain.image_acquired = true; - return true; -} - -RID OpenXRAPI::get_image(OpenXRSwapChainInfo &p_swapchain) { - if (p_swapchain.image_acquired) { - return graphics_extension->get_texture(p_swapchain.swapchain_graphics_data, p_swapchain.image_index); - } else { - return RID(); - } -} - -bool OpenXRAPI::release_image(OpenXRSwapChainInfo &p_swapchain) { - if (!p_swapchain.image_acquired) { - // Already released or never acquired. - return true; - } - - p_swapchain.image_acquired = false; // Regardless if we succeed or not, consider this released. - - XrSwapchainImageReleaseInfo swapchain_image_release_info = { - XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO, // type - nullptr // next - }; - XrResult result = xrReleaseSwapchainImage(p_swapchain.swapchain, &swapchain_image_release_info); - if (XR_FAILED(result)) { - print_line("OpenXR: failed to release swapchain image! [", get_error_string(result), "]"); - return false; +void OpenXRAPI::free_main_swapchains() { + for (int i = 0; i < OPENXR_SWAPCHAIN_MAX; i++) { + main_swapchains[i].queue_free(); } - - return true; } void OpenXRAPI::pre_render() { @@ -1904,6 +1965,18 @@ void OpenXRAPI::pre_render() { return; } + // Process any swapchains that were queued to be freed + OpenXRSwapChainInfo::free_queued(); + + Size2i swapchain_size = get_recommended_target_size(); + if (swapchain_size != main_swapchain_size) { + // Out with the old. + free_main_swapchains(); + + // In with the new. + create_main_swapchains(swapchain_size); + } + // Waitframe does 2 important things in our process: // 1) It provides us with predictive timing, telling us when OpenXR expects to display the frame we're about to commit // 2) It will use the previous timing to pause our thread so that rendering starts as close to displaying as possible @@ -1996,9 +2069,15 @@ void OpenXRAPI::pre_render() { print_line("OpenXR: failed to being frame [", get_error_string(result), "]"); return; } + + // Reset this, we haven't found a viewport for output yet + has_xr_viewport = false; } bool OpenXRAPI::pre_draw_viewport(RID p_render_target) { + // We found an XR viewport! + has_xr_viewport = true; + if (!can_render()) { return false; } @@ -2007,8 +2086,8 @@ bool OpenXRAPI::pre_draw_viewport(RID p_render_target) { // Acquire our images for (int i = 0; i < OPENXR_SWAPCHAIN_MAX; i++) { - if (!swapchains[i].image_acquired && swapchains[i].swapchain != XR_NULL_HANDLE) { - if (!acquire_image(swapchains[i])) { + if (!main_swapchains[i].is_image_acquired() && main_swapchains[i].get_swapchain() != XR_NULL_HANDLE) { + if (!main_swapchains[i].acquire(frame_state.shouldRender)) { return false; } } @@ -2022,17 +2101,17 @@ bool OpenXRAPI::pre_draw_viewport(RID p_render_target) { } XrSwapchain OpenXRAPI::get_color_swapchain() { - return swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain; + return main_swapchains[OPENXR_SWAPCHAIN_COLOR].get_swapchain(); } RID OpenXRAPI::get_color_texture() { - return get_image(swapchains[OPENXR_SWAPCHAIN_COLOR]); + return main_swapchains[OPENXR_SWAPCHAIN_COLOR].get_image(); } RID OpenXRAPI::get_depth_texture() { // Note, image will not be acquired if we didn't have a suitable swap chain format. if (submit_depth_buffer) { - return get_image(swapchains[OPENXR_SWAPCHAIN_DEPTH]); + return main_swapchains[OPENXR_SWAPCHAIN_DEPTH].get_image(); } else { return RID(); } @@ -2057,15 +2136,19 @@ void OpenXRAPI::end_frame() { return; } - if (frame_state.shouldRender && view_pose_valid && !swapchains[OPENXR_SWAPCHAIN_COLOR].image_acquired) { - print_line("OpenXR: No viewport was marked with use_xr, there is no rendered output!"); + if (frame_state.shouldRender && view_pose_valid) { + if (!has_xr_viewport) { + print_line("OpenXR: No viewport was marked with use_xr, there is no rendered output!"); + } else if (!main_swapchains[OPENXR_SWAPCHAIN_COLOR].is_image_acquired()) { + print_line("OpenXR: No swapchain could be acquired to render to!"); + } } // must have: // - shouldRender set to true // - a valid view pose for projection_views[eye].pose to submit layer // - an image to render - if (!frame_state.shouldRender || !view_pose_valid || !swapchains[OPENXR_SWAPCHAIN_COLOR].image_acquired) { + if (!frame_state.shouldRender || !view_pose_valid || !main_swapchains[OPENXR_SWAPCHAIN_COLOR].is_image_acquired()) { // submit 0 layers when we shouldn't render XrFrameEndInfo frame_end_info = { XR_TYPE_FRAME_END_INFO, // type @@ -2087,8 +2170,8 @@ void OpenXRAPI::end_frame() { // release our swapchain image if we acquired it for (int i = 0; i < OPENXR_SWAPCHAIN_MAX; i++) { - if (swapchains[i].image_acquired) { - release_image(swapchains[i]); + if (main_swapchains[i].is_image_acquired()) { + main_swapchains[i].release(); } } @@ -2332,7 +2415,7 @@ OpenXRAPI::OpenXRAPI() { submit_depth_buffer = GLOBAL_GET("xr/openxr/submit_depth_buffer"); } - // reset a few things that can't be done in our class definition + // Reset a few things that can't be done in our class definition. frame_state.predictedDisplayTime = 0; frame_state.predictedDisplayPeriod = 0; } diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h index 9eb51eee7a..e835366200 100644 --- a/modules/openxr/openxr_api.h +++ b/modules/openxr/openxr_api.h @@ -58,12 +58,28 @@ class OpenXRInterface; class OpenXRAPI { public: - struct OpenXRSwapChainInfo { + class OpenXRSwapChainInfo { + private: XrSwapchain swapchain = XR_NULL_HANDLE; void *swapchain_graphics_data = nullptr; uint32_t image_index = 0; bool image_acquired = false; bool skip_acquire_swapchain = false; + + static Vector<OpenXRSwapChainInfo> free_queue; + + public: + _FORCE_INLINE_ XrSwapchain get_swapchain() const { return swapchain; } + _FORCE_INLINE_ bool is_image_acquired() const { return image_acquired; } + + bool create(XrSwapchainCreateFlags p_create_flags, XrSwapchainUsageFlags p_usage_flags, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size); + void queue_free(); + static void free_queued(); + void free(); + + bool acquire(XrBool32 &p_should_render); + bool release(); + RID get_image(); }; private: @@ -148,12 +164,14 @@ private: int64_t color_swapchain_format = 0; int64_t depth_swapchain_format = 0; - OpenXRSwapChainInfo swapchains[OPENXR_SWAPCHAIN_MAX]; + Size2i main_swapchain_size = { 0, 0 }; + OpenXRSwapChainInfo main_swapchains[OPENXR_SWAPCHAIN_MAX]; XrSpace play_space = XR_NULL_HANDLE; XrSpace view_space = XR_NULL_HANDLE; bool view_pose_valid = false; XRPose::TrackingConfidence head_pose_confidence = XRPose::XR_TRACKING_CONFIDENCE_NONE; + bool has_xr_viewport = false; bool emulating_local_floor = false; bool should_reset_emulated_floor_height = false; @@ -241,7 +259,9 @@ private: bool setup_view_space(); bool load_supported_swapchain_formats(); bool is_swapchain_format_supported(int64_t p_swapchain_format); - bool create_swapchains(); + bool obtain_swapchain_formats(); + bool create_main_swapchains(Size2i p_size); + void free_main_swapchains(); void destroy_session(); // action map @@ -312,6 +332,7 @@ public: XrInstance get_instance() const { return instance; }; XrSystemId get_system_id() const { return system_id; }; XrSession get_session() const { return session; }; + OpenXRGraphicsExtensionWrapper *get_graphics_extension() const { return graphics_extension; }; String get_runtime_name() const { return runtime_name; }; String get_runtime_version() const { return runtime_version; }; @@ -406,11 +427,7 @@ public: // swapchains int64_t get_color_swapchain_format() const { return color_swapchain_format; } - bool create_swapchain(XrSwapchainCreateFlags p_create_flags, XrSwapchainUsageFlags p_usage_flags, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, XrSwapchain &r_swapchain, void **r_swapchain_graphics_data); - void free_swapchain(OpenXRSwapChainInfo &p_swapchain); - bool acquire_image(OpenXRSwapChainInfo &p_swapchain); - RID get_image(OpenXRSwapChainInfo &p_swapchain); - bool release_image(OpenXRSwapChainInfo &p_swapchain); + int64_t get_depth_swapchain_format() const { return depth_swapchain_format; } // action map String get_default_action_map_resource_name(); diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 0a766614f2..b19760026a 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -1939,6 +1939,12 @@ bool EditorExportPlatformAndroid::get_export_option_visibility(const EditorExpor // The APK templates are ignored if Gradle build is enabled. return advanced_options_enabled && !bool(p_preset->get("gradle_build/use_gradle_build")); } + + // Hide .NET embedding option (always enabled). + if (p_option == "dotnet/embed_build_outputs") { + return false; + } + return true; } @@ -2652,7 +2658,7 @@ String EditorExportPlatformAndroid::get_apk_expansion_fullpath(const Ref<EditorE Error EditorExportPlatformAndroid::save_apk_expansion_file(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path) { String fullpath = get_apk_expansion_fullpath(p_preset, p_path); - Error err = save_pack(false, p_preset, p_debug, fullpath); + Error err = save_pack(p_preset, p_debug, fullpath); return err; } @@ -3096,9 +3102,9 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP user_data.libs_directory = gradle_build_directory.path_join("libs"); user_data.debug = p_debug; if (p_flags & DEBUG_FLAG_DUMB_CLIENT) { - err = export_project_files(true, p_preset, p_debug, ignore_apk_file, &user_data, copy_gradle_so); + err = export_project_files(p_preset, p_debug, ignore_apk_file, &user_data, copy_gradle_so); } else { - err = export_project_files(true, p_preset, p_debug, rename_and_store_file_in_gradle_project, &user_data, copy_gradle_so); + err = export_project_files(p_preset, p_debug, rename_and_store_file_in_gradle_project, &user_data, copy_gradle_so); } if (err != OK) { add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Could not export project files to gradle project.")); @@ -3483,7 +3489,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP APKExportData ed; ed.ep = &ep; ed.apk = unaligned_apk; - err = export_project_files(true, p_preset, p_debug, ignore_apk_file, &ed, save_apk_so); + err = export_project_files(p_preset, p_debug, ignore_apk_file, &ed, save_apk_so); } else { if (apk_expansion) { err = save_apk_expansion_file(p_preset, p_debug, p_path); @@ -3495,7 +3501,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP APKExportData ed; ed.ep = &ep; ed.apk = unaligned_apk; - err = export_project_files(true, p_preset, p_debug, save_apk_file, &ed, save_apk_so); + err = export_project_files(p_preset, p_debug, save_apk_file, &ed, save_apk_so); } } diff --git a/platform/ios/export/export_plugin.cpp b/platform/ios/export/export_plugin.cpp index dff4f844c7..33389129b7 100644 --- a/platform/ios/export/export_plugin.cpp +++ b/platform/ios/export/export_plugin.cpp @@ -135,6 +135,11 @@ void EditorExportPlatformIOS::_notification(int p_what) { } bool EditorExportPlatformIOS::get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const { + // Hide unsupported .NET embedding option. + if (p_option == "dotnet/embed_build_outputs") { + return false; + } + return true; } @@ -1657,7 +1662,7 @@ Error EditorExportPlatformIOS::_export_project_helper(const Ref<EditorExportPres } String pack_path = binary_dir + ".pck"; Vector<SharedObject> libraries; - Error err = save_pack(true, p_preset, p_debug, pack_path, &libraries); + Error err = save_pack(p_preset, p_debug, pack_path, &libraries); if (err) { // Message is supplied by the subroutine method. return err; diff --git a/platform/linuxbsd/export/export_plugin.cpp b/platform/linuxbsd/export/export_plugin.cpp index 773b124c6a..936adddda3 100644 --- a/platform/linuxbsd/export/export_plugin.cpp +++ b/platform/linuxbsd/export/export_plugin.cpp @@ -146,12 +146,19 @@ List<String> EditorExportPlatformLinuxBSD::get_binary_extensions(const Ref<Edito } bool EditorExportPlatformLinuxBSD::get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const { - if (p_preset) { - // Hide SSH options. - bool ssh = p_preset->get("ssh_remote_deploy/enabled"); - if (!ssh && p_option != "ssh_remote_deploy/enabled" && p_option.begins_with("ssh_remote_deploy/")) { - return false; - } + if (p_preset == nullptr) { + return true; + } + + bool advanced_options_enabled = p_preset->are_advanced_options_enabled(); + + // Hide SSH options. + bool ssh = p_preset->get("ssh_remote_deploy/enabled"); + if (!ssh && p_option != "ssh_remote_deploy/enabled" && p_option.begins_with("ssh_remote_deploy/")) { + return false; + } + if (p_option == "dotnet/embed_build_outputs") { + return advanced_options_enabled; } return true; } diff --git a/platform/macos/export/export_plugin.cpp b/platform/macos/export/export_plugin.cpp index 05ae4a74c9..d75def9b50 100644 --- a/platform/macos/export/export_plugin.cpp +++ b/platform/macos/export/export_plugin.cpp @@ -333,6 +333,12 @@ bool EditorExportPlatformMacOS::get_export_option_visibility(const EditorExportP return false; } } + + // Hide unsupported .NET embedding option. + if (p_option == "dotnet/embed_build_outputs") { + return false; + } + return true; } @@ -1768,7 +1774,7 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p String pack_path = tmp_app_path_name + "/Contents/Resources/" + pkg_name + ".pck"; Vector<SharedObject> shared_objects; - err = save_pack(true, p_preset, p_debug, pack_path, &shared_objects); + err = save_pack(p_preset, p_debug, pack_path, &shared_objects); bool lib_validation = p_preset->get("codesign/entitlements/disable_library_validation"); if (!shared_objects.is_empty() && sign_enabled && ad_hoc && !lib_validation) { diff --git a/platform/web/export/export_plugin.cpp b/platform/web/export/export_plugin.cpp index ec7f599507..41c969b5f4 100644 --- a/platform/web/export/export_plugin.cpp +++ b/platform/web/export/export_plugin.cpp @@ -479,7 +479,7 @@ Error EditorExportPlatformWeb::export_project(const Ref<EditorExportPreset> &p_p // Export pck and shared objects Vector<SharedObject> shared_objects; String pck_path = base_path + ".pck"; - Error error = save_pack(true, p_preset, p_debug, pck_path, &shared_objects); + Error error = save_pack(p_preset, p_debug, pck_path, &shared_objects); if (error != OK) { add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not write file: \"%s\"."), pck_path)); return error; diff --git a/platform/windows/export/export_plugin.cpp b/platform/windows/export/export_plugin.cpp index fe2a930cc8..a3c86611a4 100644 --- a/platform/windows/export/export_plugin.cpp +++ b/platform/windows/export/export_plugin.cpp @@ -367,11 +367,17 @@ String EditorExportPlatformWindows::get_export_option_warning(const EditorExport } bool EditorExportPlatformWindows::get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const { + if (p_preset == nullptr) { + return true; + } + // This option is not supported by "osslsigncode", used on non-Windows host. if (!OS::get_singleton()->has_feature("windows") && p_option == "codesign/identity_type") { return false; } + bool advanced_options_enabled = p_preset->are_advanced_options_enabled(); + // Hide codesign. bool codesign = p_preset->get("codesign/enable"); if (!codesign && p_option != "codesign/enable" && p_option.begins_with("codesign/")) { @@ -390,6 +396,9 @@ bool EditorExportPlatformWindows::get_export_option_visibility(const EditorExpor return false; } + if (p_option == "dotnet/embed_build_outputs") { + return advanced_options_enabled; + } return true; } diff --git a/scene/3d/mesh_instance_3d.cpp b/scene/3d/mesh_instance_3d.cpp index 8635240655..616fb18d53 100644 --- a/scene/3d/mesh_instance_3d.cpp +++ b/scene/3d/mesh_instance_3d.cpp @@ -204,6 +204,10 @@ Ref<Skin> MeshInstance3D::get_skin() const { return skin; } +Ref<SkinReference> MeshInstance3D::get_skin_reference() const { + return skin_ref; +} + void MeshInstance3D::set_skeleton_path(const NodePath &p_skeleton) { skeleton_path = p_skeleton; if (!is_inside_tree()) { @@ -518,6 +522,7 @@ void MeshInstance3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_skeleton_path"), &MeshInstance3D::get_skeleton_path); ClassDB::bind_method(D_METHOD("set_skin", "skin"), &MeshInstance3D::set_skin); ClassDB::bind_method(D_METHOD("get_skin"), &MeshInstance3D::get_skin); + ClassDB::bind_method(D_METHOD("get_skin_reference"), &MeshInstance3D::get_skin_reference); ClassDB::bind_method(D_METHOD("get_surface_override_material_count"), &MeshInstance3D::get_surface_override_material_count); ClassDB::bind_method(D_METHOD("set_surface_override_material", "surface", "material"), &MeshInstance3D::set_surface_override_material); diff --git a/scene/3d/mesh_instance_3d.h b/scene/3d/mesh_instance_3d.h index add6bfe15e..d6ae1291d3 100644 --- a/scene/3d/mesh_instance_3d.h +++ b/scene/3d/mesh_instance_3d.h @@ -75,6 +75,8 @@ public: void set_skeleton_path(const NodePath &p_skeleton); NodePath get_skeleton_path(); + Ref<SkinReference> get_skin_reference() const; + int get_blend_shape_count() const; int find_blend_shape_by_name(const StringName &p_name); float get_blend_shape_value(int p_blend_shape) const; diff --git a/scene/3d/reflection_probe.cpp b/scene/3d/reflection_probe.cpp index b4dd6d09be..b6ec55286d 100644 --- a/scene/3d/reflection_probe.cpp +++ b/scene/3d/reflection_probe.cpp @@ -190,17 +190,6 @@ AABB ReflectionProbe::get_aabb() const { return aabb; } -PackedStringArray ReflectionProbe::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); - - if (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") { - warnings.push_back(RTR("ReflectionProbes are not supported when using the GL Compatibility backend yet. Support will be added in a future release.")); - return warnings; - } - - return warnings; -} - void ReflectionProbe::_validate_property(PropertyInfo &p_property) const { if (p_property.name == "ambient_color" || p_property.name == "ambient_color_energy") { if (ambient_mode != AMBIENT_COLOR) { diff --git a/scene/3d/reflection_probe.h b/scene/3d/reflection_probe.h index 425fbb5bc2..7221294228 100644 --- a/scene/3d/reflection_probe.h +++ b/scene/3d/reflection_probe.h @@ -122,8 +122,6 @@ public: virtual AABB get_aabb() const override; - virtual PackedStringArray get_configuration_warnings() const override; - ReflectionProbe(); ~ReflectionProbe(); }; diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 1baf71dd07..f8bbedde09 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -835,10 +835,13 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o // Draw dropcap. int dc_lines = l.text_buf->get_dropcap_lines(); float h_off = l.text_buf->get_dropcap_size().x; - if (l.dc_ol_size > 0) { - l.text_buf->draw_dropcap_outline(ci, p_ofs + ((rtl) ? Vector2() : Vector2(l.offset.x, 0)), l.dc_ol_size, l.dc_ol_color); + bool skip_dc = (trim_chars && l.char_offset > visible_characters) || (trim_glyphs_ltr && (r_processed_glyphs >= visible_glyphs)) || (trim_glyphs_rtl && (r_processed_glyphs < total_glyphs - visible_glyphs)); + if (!skip_dc) { + if (l.dc_ol_size > 0) { + l.text_buf->draw_dropcap_outline(ci, p_ofs + ((rtl) ? Vector2() : Vector2(l.offset.x, 0)), l.dc_ol_size, l.dc_ol_color); + } + l.text_buf->draw_dropcap(ci, p_ofs + ((rtl) ? Vector2() : Vector2(l.offset.x, 0)), l.dc_color); } - l.text_buf->draw_dropcap(ci, p_ofs + ((rtl) ? Vector2() : Vector2(l.offset.x, 0)), l.dc_color); int line_count = 0; Size2 ctrl_size = get_size(); @@ -894,7 +897,8 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } break; } - if (!prefix.is_empty() && line == 0) { + bool skip_prefix = (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING && l.char_offset == visible_characters) || (trim_chars && l.char_offset > visible_characters) || (trim_glyphs_ltr && (r_processed_glyphs >= visible_glyphs)) || (trim_glyphs_rtl && (r_processed_glyphs < total_glyphs - visible_glyphs)); + if (!prefix.is_empty() && line == 0 && !skip_prefix) { Ref<Font> font = theme_cache.normal_font; int font_size = theme_cache.normal_font_size; diff --git a/scene/main/node.cpp b/scene/main/node.cpp index beb2583b61..5c5049759f 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -29,7 +29,6 @@ /**************************************************************************/ #include "node.h" -#include "node.compat.inc" #include "core/config/project_settings.h" #include "core/core_string_names.h" @@ -3006,7 +3005,7 @@ static void find_owned_by(Node *p_by, Node *p_node, List<Node *> *p_owned) { } } -void Node::replace_by(Node *p_node, bool p_keep_groups, bool p_keep_children) { +void Node::replace_by(Node *p_node, bool p_keep_groups) { ERR_THREAD_GUARD ERR_FAIL_NULL(p_node); ERR_FAIL_COND(p_node->data.parent); @@ -3027,13 +3026,13 @@ void Node::replace_by(Node *p_node, bool p_keep_groups, bool p_keep_children) { _replace_connections_target(p_node); if (data.owner) { - if (p_keep_children) { - for (int i = 0; i < get_child_count(); i++) { - find_owned_by(data.owner, get_child(i), &owned_by_owner); - } + for (int i = 0; i < get_child_count(); i++) { + find_owned_by(data.owner, get_child(i), &owned_by_owner); } + _clean_up_owner(); } + Node *parent = data.parent; int index_in_parent = get_index(false); @@ -3045,33 +3044,31 @@ void Node::replace_by(Node *p_node, bool p_keep_groups, bool p_keep_children) { emit_signal(SNAME("replacing_by"), p_node); - if (p_keep_children) { - while (get_child_count()) { - Node *child = get_child(0); - remove_child(child); - if (!child->is_owned_by_parent()) { - // add the custom children to the p_node - Node *child_owner = child->get_owner() == this ? p_node : child->get_owner(); - child->set_owner(nullptr); - p_node->add_child(child); - child->set_owner(child_owner); - } + while (get_child_count()) { + Node *child = get_child(0); + remove_child(child); + if (!child->is_owned_by_parent()) { + // add the custom children to the p_node + Node *child_owner = child->get_owner() == this ? p_node : child->get_owner(); + child->set_owner(nullptr); + p_node->add_child(child); + child->set_owner(child_owner); } + } - for (Node *E : owned) { - if (E->data.owner != p_node) { - E->set_owner(p_node); - } + p_node->set_owner(owner); + for (Node *E : owned) { + if (E->data.owner != p_node) { + E->set_owner(p_node); } + } - for (Node *E : owned_by_owner) { - if (E->data.owner != owner) { - E->set_owner(owner); - } + for (Node *E : owned_by_owner) { + if (E->data.owner != owner) { + E->set_owner(owner); } } - p_node->set_owner(owner); p_node->set_scene_file_path(get_scene_file_path()); } @@ -3598,7 +3595,7 @@ void Node::_bind_methods() { ClassDB::bind_method(D_METHOD("create_tween"), &Node::create_tween); ClassDB::bind_method(D_METHOD("duplicate", "flags"), &Node::duplicate, DEFVAL(DUPLICATE_USE_INSTANTIATION | DUPLICATE_SIGNALS | DUPLICATE_GROUPS | DUPLICATE_SCRIPTS)); - ClassDB::bind_method(D_METHOD("replace_by", "node", "keep_groups", "keep_children"), &Node::replace_by, DEFVAL(false), DEFVAL(true)); + ClassDB::bind_method(D_METHOD("replace_by", "node", "keep_groups"), &Node::replace_by, DEFVAL(false)); ClassDB::bind_method(D_METHOD("set_scene_instance_load_placeholder", "load_placeholder"), &Node::set_scene_instance_load_placeholder); ClassDB::bind_method(D_METHOD("get_scene_instance_load_placeholder"), &Node::get_scene_instance_load_placeholder); diff --git a/scene/main/node.h b/scene/main/node.h index 99def10338..f49eeec9cd 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -310,11 +310,6 @@ private: Variant _call_thread_safe_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error); protected: -#ifndef DISABLE_DEPRECATED - void _replace_by_bind_compat_89992(Node *p_node, bool p_keep_data = false); - static void _bind_compatibility_methods(); -#endif // DISABLE_DEPRECATED - void _block() { data.blocked++; } void _unblock() { data.blocked--; } @@ -634,7 +629,7 @@ public: return binds; } - void replace_by(Node *p_node, bool p_keep_groups = false, bool p_keep_children = true); + void replace_by(Node *p_node, bool p_keep_data = false); void set_process_mode(ProcessMode p_mode); ProcessMode get_process_mode() const; diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp index 6d0796f1b9..07868e7e49 100644 --- a/scene/resources/resource_format_text.cpp +++ b/scene/resources/resource_format_text.cpp @@ -39,7 +39,8 @@ // Version 2: changed names for Basis, AABB, Vectors, etc. // Version 3: new string ID for ext/subresources, breaks forward compat. -#define FORMAT_VERSION 3 +// Version 4: PackedByteArray is now stored as base64 encoded. +#define FORMAT_VERSION 4 #define BINARY_FORMAT_VERSION 4 @@ -273,8 +274,8 @@ Ref<PackedScene> ResourceLoaderText::_parse_node_tag(VariantParser::ResourcePars if (next_tag.fields.has("groups")) { Array groups = next_tag.fields["groups"]; - for (int i = 0; i < groups.size(); i++) { - packed_scene->get_state()->add_node_group(node_id, packed_scene->get_state()->add_name(groups[i])); + for (const Variant &group : groups) { + packed_scene->get_state()->add_node_group(node_id, packed_scene->get_state()->add_name(group)); } } @@ -352,8 +353,8 @@ Ref<PackedScene> ResourceLoaderText::_parse_node_tag(VariantParser::ResourcePars } Vector<int> bind_ints; - for (int i = 0; i < binds.size(); i++) { - bind_ints.push_back(packed_scene->get_state()->add_value(binds[i])); + for (const Variant &bind : binds) { + bind_ints.push_back(packed_scene->get_state()->add_value(bind)); } packed_scene->get_state()->add_connection( @@ -1951,10 +1952,9 @@ void ResourceFormatSaverTextInstance::_find_resources(const Variant &p_variant, } break; case Variant::ARRAY: { Array varray = p_variant; - int len = varray.size(); - for (int i = 0; i < len; i++) { - const Variant &v = varray.get(i); - _find_resources(v); + _find_resources(varray.get_typed_script()); + for (const Variant &var : varray) { + _find_resources(var); } } break; diff --git a/scu_builders.py b/scu_builders.py index b180cbc864..e6adf6543c 100644 --- a/scu_builders.py +++ b/scu_builders.py @@ -23,8 +23,9 @@ def clear_out_stale_files(output_folder, extension, fresh_files): return for file in glob.glob(output_folder + "/*." + extension): + file = Path(file) if not file in fresh_files: - # print("removed stale file: " + file) + # print("removed stale file: " + str(file)) os.remove(file) @@ -97,7 +98,7 @@ def write_output_file(file_count, include_list, start_line, end_line, output_fol elif _verbose: print("SCU: Generation not needed for: " + short_filename) - return output_filename + return output_path def write_exception_output_file(file_count, exception_string, output_folder, output_filename_prefix, extension): @@ -124,7 +125,7 @@ def write_exception_output_file(file_count, exception_string, output_folder, out elif _verbose: print("SCU: Generation not needed for: " + short_filename) - return output_filename + return output_path def find_section_name(sub_folder): diff --git a/servers/rendering/dummy/storage/light_storage.h b/servers/rendering/dummy/storage/light_storage.h index a76305cdaa..0a9602b603 100644 --- a/servers/rendering/dummy/storage/light_storage.h +++ b/servers/rendering/dummy/storage/light_storage.h @@ -91,6 +91,7 @@ public: void light_instance_set_aabb(RID p_light_instance, const AABB &p_aabb) override {} void light_instance_set_shadow_transform(RID p_light_instance, const Projection &p_projection, const Transform3D &p_transform, float p_far, float p_split, int p_pass, float p_shadow_texel_size, float p_bias_scale = 1.0, float p_range_begin = 0, const Vector2 &p_uv_scale = Vector2()) override {} void light_instance_mark_visible(RID p_light_instance) override {} + virtual bool light_instance_is_shadow_visible_at_position(RID p_light_instance, const Vector3 &p_position) const override { return false; } /* PROBE API */ virtual RID reflection_probe_allocate() override { return RID(); } diff --git a/servers/rendering/renderer_rd/storage_rd/light_storage.h b/servers/rendering/renderer_rd/storage_rd/light_storage.h index b3d6bf5254..f152cc5dae 100644 --- a/servers/rendering/renderer_rd/storage_rd/light_storage.h +++ b/servers/rendering/renderer_rd/storage_rd/light_storage.h @@ -590,6 +590,29 @@ public: virtual void light_instance_set_shadow_transform(RID p_light_instance, const Projection &p_projection, const Transform3D &p_transform, float p_far, float p_split, int p_pass, float p_shadow_texel_size, float p_bias_scale = 1.0, float p_range_begin = 0, const Vector2 &p_uv_scale = Vector2()) override; virtual void light_instance_mark_visible(RID p_light_instance) override; + virtual bool light_instance_is_shadow_visible_at_position(RID p_light_instance, const Vector3 &p_position) const override { + const LightInstance *light_instance = light_instance_owner.get_or_null(p_light_instance); + ERR_FAIL_NULL_V(light_instance, false); + const Light *light = light_owner.get_or_null(light_instance->light); + ERR_FAIL_NULL_V(light, false); + + if (!light->shadow) { + return false; + } + + if (!light->distance_fade) { + return true; + } + + real_t distance = p_position.distance_to(light_instance->transform.origin); + + if (distance > light->distance_fade_shadow + light->distance_fade_length) { + return false; + } + + return true; + } + _FORCE_INLINE_ RID light_instance_get_base_light(RID p_light_instance) { LightInstance *li = light_instance_owner.get_or_null(p_light_instance); return li->light; diff --git a/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.cpp b/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.cpp index c5d74d395f..b7934cb3de 100644 --- a/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.cpp +++ b/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.cpp @@ -130,9 +130,10 @@ void RenderSceneBuffersRD::cleanup() { named_textures.clear(); // Clear weight_buffer / blur textures. - for (const WeightBuffers &weight_buffer : weight_buffers) { + for (WeightBuffers &weight_buffer : weight_buffers) { if (weight_buffer.weight.is_valid()) { RD::get_singleton()->free(weight_buffer.weight); + weight_buffer.weight = RID(); } } } diff --git a/servers/rendering/renderer_scene_cull.cpp b/servers/rendering/renderer_scene_cull.cpp index b33de9d6f4..96c0479ac3 100644 --- a/servers/rendering/renderer_scene_cull.cpp +++ b/servers/rendering/renderer_scene_cull.cpp @@ -1029,7 +1029,6 @@ inline bool is_geometry_instance(RenderingServer::InstanceType p_type) { void RendererSceneCull::instance_set_custom_aabb(RID p_instance, AABB p_aabb) { Instance *instance = instance_owner.get_or_null(p_instance); ERR_FAIL_NULL(instance); - ERR_FAIL_COND(!is_geometry_instance(instance->base_type)); if (p_aabb != AABB()) { // Set custom AABB @@ -3029,6 +3028,7 @@ void RendererSceneCull::_render_scene(const RendererSceneRender::CameraData *p_c light_culler->prepare_camera(p_camera_data->main_transform, p_camera_data->main_projection); Scenario *scenario = scenario_owner.get_or_null(p_scenario); + Vector3 camera_position = p_camera_data->main_transform.origin; ERR_FAIL_COND(p_render_buffers.is_null()); @@ -3038,7 +3038,7 @@ void RendererSceneCull::_render_scene(const RendererSceneRender::CameraData *p_c if (p_reflection_probe.is_null()) { //no rendering code here, this is only to set up what needs to be done, request regions, etc. - scene_render->sdfgi_update(p_render_buffers, p_environment, p_camera_data->main_transform.origin); //update conditions for SDFGI (whether its used or not) + scene_render->sdfgi_update(p_render_buffers, p_environment, camera_position); //update conditions for SDFGI (whether its used or not) } RENDER_TIMESTAMP("Update Visibility Dependencies"); @@ -3051,7 +3051,7 @@ void RendererSceneCull::_render_scene(const RendererSceneRender::CameraData *p_c VisibilityCullData visibility_cull_data; visibility_cull_data.scenario = scenario; visibility_cull_data.viewport_mask = scenario->viewport_visibility_masks[p_viewport]; - visibility_cull_data.camera_position = p_camera_data->main_transform.origin; + visibility_cull_data.camera_position = camera_position; for (int i = scenario->instance_visibility.get_bin_count() - 1; i > 0; i--) { // We skip bin 0 visibility_cull_data.cull_offset = scenario->instance_visibility.get_bin_start(i); @@ -3220,16 +3220,20 @@ void RendererSceneCull::_render_scene(const RendererSceneRender::CameraData *p_c } } - // Positional Shadowss + // Positional Shadows for (uint32_t i = 0; i < (uint32_t)scene_cull_result.lights.size(); i++) { Instance *ins = scene_cull_result.lights[i]; - if (!p_shadow_atlas.is_valid() || !RSG::light_storage->light_has_shadow(ins->base)) { + if (!p_shadow_atlas.is_valid()) { continue; } InstanceLightData *light = static_cast<InstanceLightData *>(ins->base_data); + if (!RSG::light_storage->light_instance_is_shadow_visible_at_position(light->instance, camera_position)) { + continue; + } + float coverage = 0.f; { //compute coverage diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp index fef1a205d6..8fbad346a4 100644 --- a/servers/rendering/shader_language.cpp +++ b/servers/rendering/shader_language.cpp @@ -8361,7 +8361,7 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f } } #endif // DEBUG_ENABLED - if (String(shader_type_identifier) != "spatial") { + if (shader_type_identifier != StringName() && String(shader_type_identifier) != "spatial") { _set_error(vformat(RTR("Uniform instances are not yet implemented for '%s' shaders."), shader_type_identifier)); return ERR_PARSE_ERROR; } @@ -8848,7 +8848,7 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f _set_error(RTR("'hint_normal_roughness_texture' is only available when using the Forward+ backend.")); return ERR_PARSE_ERROR; } - if (String(shader_type_identifier) != "spatial") { + if (shader_type_identifier != StringName() && String(shader_type_identifier) != "spatial") { _set_error(vformat(RTR("'hint_normal_roughness_texture' is not supported in '%s' shaders."), shader_type_identifier)); return ERR_PARSE_ERROR; } @@ -8857,7 +8857,7 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f new_hint = ShaderNode::Uniform::HINT_DEPTH_TEXTURE; --texture_uniforms; --texture_binding; - if (String(shader_type_identifier) != "spatial") { + if (shader_type_identifier != StringName() && String(shader_type_identifier) != "spatial") { _set_error(vformat(RTR("'hint_depth_texture' is not supported in '%s' shaders."), shader_type_identifier)); return ERR_PARSE_ERROR; } diff --git a/servers/rendering/storage/light_storage.h b/servers/rendering/storage/light_storage.h index d439598f3d..6a0adfa596 100644 --- a/servers/rendering/storage/light_storage.h +++ b/servers/rendering/storage/light_storage.h @@ -98,6 +98,7 @@ public: virtual bool light_instances_can_render_shadow_cube() const { return true; } + virtual bool light_instance_is_shadow_visible_at_position(RID p_light, const Vector3 &p_position) const = 0; /* PROBE API */ diff --git a/scene/main/node.compat.inc b/tests/core/io/test_ip.h index 69ece1a40d..7b5583faa0 100644 --- a/scene/main/node.compat.inc +++ b/tests/core/io/test_ip.h @@ -1,5 +1,5 @@ /**************************************************************************/ -/* node.compat.inc */ +/* test_ip.h */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,14 +28,24 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#ifndef DISABLE_DEPRECATED +#ifndef TEST_IP_H +#define TEST_IP_H -void Node::_replace_by_bind_compat_89992(Node *p_node, bool p_keep_data) { - replace_by(p_node, p_keep_data, true); -} +#include "core/io/ip.h" + +#include "tests/test_macros.h" -void Node::_bind_compatibility_methods() { - ClassDB::bind_compatibility_method(D_METHOD("replace_by", "node", "keep_groups"), &Node::_replace_by_bind_compat_89992, DEFVAL(false)); +namespace TestIP { + +TEST_CASE("[IP] resolve_hostname") { + for (int x = 0; x < 1000; x++) { + IPAddress IPV4 = IP::get_singleton()->resolve_hostname("localhost", IP::TYPE_IPV4); + CHECK("127.0.0.1" == String(IPV4)); + IPAddress IPV6 = IP::get_singleton()->resolve_hostname("localhost", IP::TYPE_IPV6); + CHECK("0:0:0:0:0:0:0:1" == String(IPV6)); + } } -#endif +} // namespace TestIP + +#endif // TEST_IP_H diff --git a/tests/core/templates/test_oa_hash_map.h b/tests/core/templates/test_oa_hash_map.h new file mode 100644 index 0000000000..6e80b52054 --- /dev/null +++ b/tests/core/templates/test_oa_hash_map.h @@ -0,0 +1,227 @@ +/**************************************************************************/ +/* test_oa_hash_map.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef TEST_OA_HASH_MAP_H +#define TEST_OA_HASH_MAP_H + +#include "core/templates/oa_hash_map.h" +#include "scene/resources/texture.h" + +#include "tests/test_macros.h" + +namespace TestOAHashMap { + +TEST_CASE("[OAHashMap] Insert element") { + OAHashMap<int, int> map; + map.insert(42, 84); + int data = 0; + bool lookup_res = map.lookup(42, data); + int value = *map.lookup_ptr(42); + CHECK(lookup_res); + CHECK(value == 84); + CHECK(data == 84); +} + +TEST_CASE("[OAHashMap] Set element") { + OAHashMap<int, int> map; + map.set(42, 84); + int data = 0; + bool lookup_res = map.lookup(42, data); + int value = *map.lookup_ptr(42); + CHECK(lookup_res); + CHECK(value == 84); + CHECK(data == 84); +} + +TEST_CASE("[OAHashMap] Overwrite element") { + OAHashMap<int, int> map; + map.set(42, 84); + map.set(42, 1234); + int result = *map.lookup_ptr(42); + CHECK(result == 1234); +} + +TEST_CASE("[OAHashMap] Remove element") { + OAHashMap<int, int> map; + map.insert(42, 84); + map.remove(42); + CHECK(!map.has(42)); +} + +TEST_CASE("[OAHashMap] Get Num_Elements") { + OAHashMap<int, int> map; + map.set(42, 84); + map.set(123, 84); + map.set(123, 84); + map.set(0, 84); + map.set(123485, 84); + + CHECK(map.get_num_elements() == 4); +} + +TEST_CASE("[OAHashMap] Iteration") { + OAHashMap<int, int> map; + map.insert(42, 84); + map.insert(123, 12385); + map.insert(0, 12934); + map.insert(123485, 1238888); + map.set(123, 111111); + + Vector<Pair<int, int>> expected; + expected.push_back(Pair<int, int>(42, 84)); + expected.push_back(Pair<int, int>(123, 111111)); + expected.push_back(Pair<int, int>(0, 12934)); + expected.push_back(Pair<int, int>(123485, 1238888)); + + int idx = 0; + for (OAHashMap<int, int>::Iterator it = map.iter(); it.valid; it = map.next_iter(it)) { + int64_t result = expected.find(Pair<int, int>(*it.key, *it.value)); + CHECK(result >= 0); + idx++; + } +} + +TEST_CASE("[OAHashMap] Insert, iterate, remove many strings") { + uint64_t pre_mem = Memory::get_mem_usage(); + { + const int elem_max = 40; + OAHashMap<String, int> map; + for (int i = 0; i < elem_max; i++) { + map.insert(itos(i), i); + } + + Vector<String> elems_still_valid; + + for (int i = 0; i < elem_max; i++) { + if ((i % 5) == 0) { + map.remove(itos(i)); + } else { + elems_still_valid.push_back(itos(i)); + } + } + + CHECK(elems_still_valid.size() == map.get_num_elements()); + + for (int i = 0; i < elems_still_valid.size(); i++) { + CHECK(map.has(elems_still_valid[i])); + } + } + + CHECK(Memory::get_mem_usage() == pre_mem); +} + +TEST_CASE("[OAHashMap] Clear") { + OAHashMap<int, int> map; + map.insert(42, 84); + map.insert(0, 1234); + map.clear(); + CHECK(!map.has(42)); + CHECK(!map.has(0)); + CHECK(map.is_empty()); +} + +TEST_CASE("[OAHashMap] Copy constructor") { + uint64_t pre_mem = Memory::get_mem_usage(); + { + OAHashMap<int, int> map0; + const uint32_t count = 5; + for (uint32_t i = 0; i < count; i++) { + map0.insert(i, i); + } + OAHashMap<int, int> map1(map0); + CHECK(map0.get_num_elements() == map1.get_num_elements()); + CHECK(map0.get_capacity() == map1.get_capacity()); + CHECK(*map0.lookup_ptr(0) == *map1.lookup_ptr(0)); + } + CHECK(Memory::get_mem_usage() == pre_mem); +} + +TEST_CASE("[OAHashMap] Operator =") { + uint64_t pre_mem = Memory::get_mem_usage(); + { + OAHashMap<int, int> map0; + OAHashMap<int, int> map1; + const uint32_t count = 5; + map1.insert(1234, 1234); + for (uint32_t i = 0; i < count; i++) { + map0.insert(i, i); + } + map1 = map0; + CHECK(map0.get_num_elements() == map1.get_num_elements()); + CHECK(map0.get_capacity() == map1.get_capacity()); + CHECK(*map0.lookup_ptr(0) == *map1.lookup_ptr(0)); + } + CHECK(Memory::get_mem_usage() == pre_mem); +} + +TEST_CASE("[OAHashMap] Non-trivial types") { + uint64_t pre_mem = Memory::get_mem_usage(); + { + OAHashMap<String, Ref<Texture2D>> map1; + const uint32_t count = 10; + for (uint32_t i = 0; i < count; i++) { + String string = "qwerty"; + string += itos(i); + Ref<Texture2D> ref_texture_2d; + + map1.set(string, ref_texture_2d); + Ref<Texture2D> map_vec = *map1.lookup_ptr(string); + CHECK(map_vec == ref_texture_2d); + } + OAHashMap<String, Ref<Texture2D>> map1copy(map1); + CHECK(map1copy.has(String("qwerty0"))); + map1copy = map1; + CHECK(map1copy.has(String("qwerty2"))); + + OAHashMap<int64_t, Vector4 *> map2; + + for (uint32_t i = 0; i < count; i++) { + Vector4 *vec = memnew(Vector4); + vec->x = 10; + vec->y = 12; + vec->z = 151; + vec->w = -13; + map2.set(i, vec); + Vector4 *p = nullptr; + map2.lookup(i, p); + CHECK(*p == *vec); + } + + OAHashMap<int64_t, Vector4 *> map3(map2); + for (OAHashMap<int64_t, Vector4 *>::Iterator it = map2.iter(); it.valid; it = map2.next_iter(it)) { + memdelete(*(it.value)); + } + } + CHECK(Memory::get_mem_usage() == pre_mem); +} + +} // namespace TestOAHashMap + +#endif // TEST_OA_HASH_MAP_H diff --git a/tests/core/variant/test_array.h b/tests/core/variant/test_array.h index ea61ae2a02..287345e831 100644 --- a/tests/core/variant/test_array.h +++ b/tests/core/variant/test_array.h @@ -545,6 +545,54 @@ TEST_CASE("[Array] Recursive self comparison") { a2.clear(); } +TEST_CASE("[Array] Iteration") { + Array a1 = build_array(1, 2, 3); + Array a2 = build_array(1, 2, 3); + + int idx = 0; + for (Variant &E : a1) { + CHECK_EQ(int(a2[idx]), int(E)); + idx++; + } + + idx = 0; + + for (const Variant &E : (const Array &)a1) { + CHECK_EQ(int(a2[idx]), int(E)); + idx++; + } + + a1.clear(); +} + +TEST_CASE("[Array] Iteration and modification") { + Array a1 = build_array(1, 2, 3); + Array a2 = build_array(2, 3, 4); + Array a3 = build_array(1, 2, 3); + Array a4 = build_array(1, 2, 3); + a3.make_read_only(); + + int idx = 0; + for (Variant &E : a1) { + E = a2[idx]; + idx++; + } + + CHECK_EQ(a1, a2); + + // Ensure read-only is respected. + idx = 0; + for (Variant &E : a3) { + E = a2[idx]; + } + + CHECK_EQ(a3, a4); + + a1.clear(); + a2.clear(); + a4.clear(); +} + } // namespace TestArray #endif // TEST_ARRAY_H diff --git a/tests/scene/test_camera_2d.h b/tests/scene/test_camera_2d.h new file mode 100644 index 0000000000..f03a4aed53 --- /dev/null +++ b/tests/scene/test_camera_2d.h @@ -0,0 +1,318 @@ +/**************************************************************************/ +/* test_camera_2d.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef TEST_CAMERA_2D_H +#define TEST_CAMERA_2D_H + +#include "scene/2d/camera_2d.h" +#include "scene/main/viewport.h" +#include "scene/main/window.h" +#include "tests/test_macros.h" + +namespace TestCamera2D { + +TEST_CASE("[SceneTree][Camera2D] Getters and setters") { + Camera2D *test_camera = memnew(Camera2D); + + SUBCASE("AnchorMode") { + test_camera->set_anchor_mode(Camera2D::AnchorMode::ANCHOR_MODE_FIXED_TOP_LEFT); + CHECK(test_camera->get_anchor_mode() == Camera2D::AnchorMode::ANCHOR_MODE_FIXED_TOP_LEFT); + test_camera->set_anchor_mode(Camera2D::AnchorMode::ANCHOR_MODE_DRAG_CENTER); + CHECK(test_camera->get_anchor_mode() == Camera2D::AnchorMode::ANCHOR_MODE_DRAG_CENTER); + } + + SUBCASE("ProcessCallback") { + test_camera->set_process_callback(Camera2D::Camera2DProcessCallback::CAMERA2D_PROCESS_PHYSICS); + CHECK(test_camera->get_process_callback() == Camera2D::Camera2DProcessCallback::CAMERA2D_PROCESS_PHYSICS); + test_camera->set_process_callback(Camera2D::Camera2DProcessCallback::CAMERA2D_PROCESS_IDLE); + CHECK(test_camera->get_process_callback() == Camera2D::Camera2DProcessCallback::CAMERA2D_PROCESS_IDLE); + } + + SUBCASE("Drag") { + constexpr float drag_left_margin = 0.8f; + constexpr float drag_top_margin = 0.8f; + constexpr float drag_right_margin = 0.8f; + constexpr float drag_bottom_margin = 0.8f; + constexpr float drag_horizontal_offset1 = 0.5f; + constexpr float drag_horizontal_offset2 = -0.5f; + constexpr float drag_vertical_offset1 = 0.5f; + constexpr float drag_vertical_offset2 = -0.5f; + test_camera->set_drag_margin(SIDE_LEFT, drag_left_margin); + CHECK(test_camera->get_drag_margin(SIDE_LEFT) == drag_left_margin); + test_camera->set_drag_margin(SIDE_TOP, drag_top_margin); + CHECK(test_camera->get_drag_margin(SIDE_TOP) == drag_top_margin); + test_camera->set_drag_margin(SIDE_RIGHT, drag_right_margin); + CHECK(test_camera->get_drag_margin(SIDE_RIGHT) == drag_right_margin); + test_camera->set_drag_margin(SIDE_BOTTOM, drag_bottom_margin); + CHECK(test_camera->get_drag_margin(SIDE_BOTTOM) == drag_bottom_margin); + test_camera->set_drag_horizontal_enabled(true); + CHECK(test_camera->is_drag_horizontal_enabled()); + test_camera->set_drag_horizontal_enabled(false); + CHECK_FALSE(test_camera->is_drag_horizontal_enabled()); + test_camera->set_drag_horizontal_offset(drag_horizontal_offset1); + CHECK(test_camera->get_drag_horizontal_offset() == drag_horizontal_offset1); + test_camera->set_drag_horizontal_offset(drag_horizontal_offset2); + CHECK(test_camera->get_drag_horizontal_offset() == drag_horizontal_offset2); + test_camera->set_drag_vertical_enabled(true); + CHECK(test_camera->is_drag_vertical_enabled()); + test_camera->set_drag_vertical_enabled(false); + CHECK_FALSE(test_camera->is_drag_vertical_enabled()); + test_camera->set_drag_vertical_offset(drag_vertical_offset1); + CHECK(test_camera->get_drag_vertical_offset() == drag_vertical_offset1); + test_camera->set_drag_vertical_offset(drag_vertical_offset2); + CHECK(test_camera->get_drag_vertical_offset() == drag_vertical_offset2); + } + + SUBCASE("Drawing") { + test_camera->set_margin_drawing_enabled(true); + CHECK(test_camera->is_margin_drawing_enabled()); + test_camera->set_margin_drawing_enabled(false); + CHECK_FALSE(test_camera->is_margin_drawing_enabled()); + test_camera->set_limit_drawing_enabled(true); + CHECK(test_camera->is_limit_drawing_enabled()); + test_camera->set_limit_drawing_enabled(false); + CHECK_FALSE(test_camera->is_limit_drawing_enabled()); + test_camera->set_screen_drawing_enabled(true); + CHECK(test_camera->is_screen_drawing_enabled()); + test_camera->set_screen_drawing_enabled(false); + CHECK_FALSE(test_camera->is_screen_drawing_enabled()); + } + + SUBCASE("Enabled") { + test_camera->set_enabled(true); + CHECK(test_camera->is_enabled()); + test_camera->set_enabled(false); + CHECK_FALSE(test_camera->is_enabled()); + } + + SUBCASE("Rotation") { + constexpr float rotation_smoothing_speed = 20.0f; + test_camera->set_ignore_rotation(true); + CHECK(test_camera->is_ignoring_rotation()); + test_camera->set_ignore_rotation(false); + CHECK_FALSE(test_camera->is_ignoring_rotation()); + test_camera->set_rotation_smoothing_enabled(true); + CHECK(test_camera->is_rotation_smoothing_enabled()); + test_camera->set_rotation_smoothing_speed(rotation_smoothing_speed); + CHECK(test_camera->get_rotation_smoothing_speed() == rotation_smoothing_speed); + } + + SUBCASE("Zoom") { + const Vector2 zoom = Vector2(4, 4); + test_camera->set_zoom(zoom); + CHECK(test_camera->get_zoom() == zoom); + } + + SUBCASE("Offset") { + const Vector2 offset = Vector2(100, 100); + test_camera->set_offset(offset); + CHECK(test_camera->get_offset() == offset); + } + + SUBCASE("Limit") { + constexpr int limit_left = 100; + constexpr int limit_top = 100; + constexpr int limit_right = 100; + constexpr int limit_bottom = 100; + test_camera->set_limit_smoothing_enabled(true); + CHECK(test_camera->is_limit_smoothing_enabled()); + test_camera->set_limit_smoothing_enabled(false); + CHECK_FALSE(test_camera->is_limit_smoothing_enabled()); + test_camera->set_limit(SIDE_LEFT, limit_left); + CHECK(test_camera->get_limit(SIDE_LEFT) == limit_left); + test_camera->set_limit(SIDE_TOP, limit_top); + CHECK(test_camera->get_limit(SIDE_TOP) == limit_top); + test_camera->set_limit(SIDE_RIGHT, limit_right); + CHECK(test_camera->get_limit(SIDE_RIGHT) == limit_right); + test_camera->set_limit(SIDE_BOTTOM, limit_bottom); + CHECK(test_camera->get_limit(SIDE_BOTTOM) == limit_bottom); + } + + SUBCASE("Position") { + constexpr float smoothing_speed = 20.0f; + test_camera->set_position_smoothing_enabled(true); + CHECK(test_camera->is_position_smoothing_enabled()); + test_camera->set_position_smoothing_speed(smoothing_speed); + CHECK(test_camera->get_position_smoothing_speed() == smoothing_speed); + } + + memdelete(test_camera); +} + +TEST_CASE("[SceneTree][Camera2D] Camera positioning") { + SubViewport *mock_viewport = memnew(SubViewport); + Camera2D *test_camera = memnew(Camera2D); + + mock_viewport->set_size(Vector2(400, 200)); + SceneTree::get_singleton()->get_root()->add_child(mock_viewport); + mock_viewport->add_child(test_camera); + + SUBCASE("Anchor mode") { + test_camera->set_anchor_mode(Camera2D::ANCHOR_MODE_DRAG_CENTER); + CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(0, 0))); + CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(0, 0))); + + test_camera->set_anchor_mode(Camera2D::ANCHOR_MODE_FIXED_TOP_LEFT); + CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(200, 100))); + CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(0, 0))); + } + + SUBCASE("Offset") { + test_camera->set_offset(Vector2(100, 100)); + CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(100, 100))); + CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(0, 0))); + + test_camera->set_offset(Vector2(-100, 300)); + CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(-100, 300))); + CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(0, 0))); + + test_camera->set_offset(Vector2(0, 0)); + CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(0, 0))); + CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(0, 0))); + } + + SUBCASE("Limits") { + test_camera->set_limit(SIDE_LEFT, 100); + test_camera->set_limit(SIDE_TOP, 50); + + CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(300, 150))); + CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(0, 0))); + + test_camera->set_limit(SIDE_LEFT, 0); + test_camera->set_limit(SIDE_TOP, 0); + + CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(200, 100))); + CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(0, 0))); + } + + SUBCASE("Drag") { + CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(0, 0))); + + // horizontal + test_camera->set_drag_horizontal_enabled(true); + test_camera->set_drag_margin(SIDE_RIGHT, 0.5); + + test_camera->set_position(Vector2(100, 100)); + test_camera->force_update_scroll(); + CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(0, 100))); + CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(0, 100))); + test_camera->set_position(Vector2(101, 101)); + test_camera->force_update_scroll(); + CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(1, 101))); + CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(1, 101))); + + // test align + test_camera->set_position(Vector2(0, 0)); + test_camera->align(); + CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(0, 0))); + CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(0, 0))); + + // vertical + test_camera->set_drag_vertical_enabled(true); + test_camera->set_drag_horizontal_enabled(false); + test_camera->set_drag_margin(SIDE_TOP, 0.3); + + test_camera->set_position(Vector2(200, -20)); + test_camera->force_update_scroll(); + CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(200, 0))); + CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(200, 0))); + test_camera->set_position(Vector2(250, -55)); + test_camera->force_update_scroll(); + CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(250, -25))); + CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(250, -25))); + } + + memdelete(test_camera); + memdelete(mock_viewport); +} + +TEST_CASE("[SceneTree][Camera2D] Transforms") { + SubViewport *mock_viewport = memnew(SubViewport); + Camera2D *test_camera = memnew(Camera2D); + + mock_viewport->set_size(Vector2(400, 200)); + SceneTree::get_singleton()->get_root()->add_child(mock_viewport); + mock_viewport->add_child(test_camera); + + SUBCASE("Default camera") { + Transform2D xform = mock_viewport->get_canvas_transform(); + // x,y are basis vectors, origin = screen center + Transform2D test_xform = Transform2D(Vector2(1, 0), Vector2(0, 1), Vector2(200, 100)); + CHECK(xform.is_equal_approx(test_xform)); + } + + SUBCASE("Zoom") { + test_camera->set_zoom(Vector2(0.5, 2)); + Transform2D xform = mock_viewport->get_canvas_transform(); + Transform2D test_xform = Transform2D(Vector2(0.5, 0), Vector2(0, 2), Vector2(200, 100)); + CHECK(xform.is_equal_approx(test_xform)); + + test_camera->set_zoom(Vector2(10, 10)); + xform = mock_viewport->get_canvas_transform(); + test_xform = Transform2D(Vector2(10, 0), Vector2(0, 10), Vector2(200, 100)); + CHECK(xform.is_equal_approx(test_xform)); + + test_camera->set_zoom(Vector2(1, 1)); + xform = mock_viewport->get_canvas_transform(); + test_xform = Transform2D(Vector2(1, 0), Vector2(0, 1), Vector2(200, 100)); + CHECK(xform.is_equal_approx(test_xform)); + } + + SUBCASE("Rotation") { + test_camera->set_rotation(Math_PI / 2); + Transform2D xform = mock_viewport->get_canvas_transform(); + Transform2D test_xform = Transform2D(Vector2(1, 0), Vector2(0, 1), Vector2(200, 100)); + CHECK(xform.is_equal_approx(test_xform)); + + test_camera->set_ignore_rotation(false); + xform = mock_viewport->get_canvas_transform(); + test_xform = Transform2D(Vector2(0, -1), Vector2(1, 0), Vector2(200, 100)); + CHECK(xform.is_equal_approx(test_xform)); + + test_camera->set_rotation(-1 * Math_PI); + test_camera->force_update_scroll(); + xform = mock_viewport->get_canvas_transform(); + test_xform = Transform2D(Vector2(-1, 0), Vector2(0, -1), Vector2(200, 100)); + CHECK(xform.is_equal_approx(test_xform)); + + test_camera->set_rotation(0); + test_camera->force_update_scroll(); + xform = mock_viewport->get_canvas_transform(); + test_xform = Transform2D(Vector2(1, 0), Vector2(0, 1), Vector2(200, 100)); + CHECK(xform.is_equal_approx(test_xform)); + } + + memdelete(test_camera); + memdelete(mock_viewport); +} + +} // namespace TestCamera2D + +#endif // TEST_CAMERA_2D_H diff --git a/tests/test_main.cpp b/tests/test_main.cpp index 24eb84127b..bb6837c965 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -44,6 +44,7 @@ #include "tests/core/io/test_file_access.h" #include "tests/core/io/test_http_client.h" #include "tests/core/io/test_image.h" +#include "tests/core/io/test_ip.h" #include "tests/core/io/test_json.h" #include "tests/core/io/test_marshalls.h" #include "tests/core/io/test_pck_packer.h" @@ -85,6 +86,7 @@ #include "tests/core/templates/test_list.h" #include "tests/core/templates/test_local_vector.h" #include "tests/core/templates/test_lru.h" +#include "tests/core/templates/test_oa_hash_map.h" #include "tests/core/templates/test_paged_array.h" #include "tests/core/templates/test_rid.h" #include "tests/core/templates/test_vector.h" @@ -101,6 +103,7 @@ #include "tests/scene/test_arraymesh.h" #include "tests/scene/test_audio_stream_wav.h" #include "tests/scene/test_bit_map.h" +#include "tests/scene/test_camera_2d.h" #include "tests/scene/test_code_edit.h" #include "tests/scene/test_color_picker.h" #include "tests/scene/test_control.h" |
