summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/config/project_settings.cpp4
-rw-r--r--core/extension/extension_api_dump.cpp24
-rw-r--r--core/io/json.cpp11
-rw-r--r--core/io/marshalls.cpp4
-rw-r--r--core/io/resource_format_binary.cpp9
-rw-r--r--core/io/resource_loader.cpp5
-rw-r--r--core/object/object.cpp16
-rw-r--r--core/object/script_language.cpp12
-rw-r--r--core/object/script_language_extension.h36
-rw-r--r--core/variant/array.cpp16
-rw-r--r--core/variant/array.h64
-rw-r--r--core/variant/variant.h52
-rw-r--r--core/variant/variant_call.cpp4
-rw-r--r--core/variant/variant_parser.cpp103
-rw-r--r--core/variant/variant_parser.h1
-rw-r--r--doc/classes/Area3D.xml3
-rw-r--r--doc/classes/CameraServer.xml2
-rw-r--r--doc/classes/CharacterBody2D.xml6
-rw-r--r--doc/classes/CharacterBody3D.xml3
-rw-r--r--doc/classes/MeshInstance3D.xml6
-rw-r--r--doc/classes/Node.xml3
-rw-r--r--doc/classes/PhysicsServer3D.xml2
-rw-r--r--doc/classes/ProjectSettings.xml2
-rw-r--r--doc/classes/SkinReference.xml9
-rw-r--r--doc/classes/SoftBody3D.xml1
-rw-r--r--doc/classes/Vector2.xml8
-rw-r--r--doc/classes/Vector3.xml8
-rw-r--r--drivers/gles3/effects/cubemap_filter.cpp209
-rw-r--r--drivers/gles3/effects/cubemap_filter.h70
-rw-r--r--drivers/gles3/rasterizer_gles3.cpp2
-rw-r--r--drivers/gles3/rasterizer_gles3.h2
-rw-r--r--drivers/gles3/rasterizer_scene_gles3.cpp319
-rw-r--r--drivers/gles3/rasterizer_scene_gles3.h12
-rw-r--r--drivers/gles3/shaders/SCsub1
-rw-r--r--drivers/gles3/shaders/effects/cubemap_filter.glsl (renamed from drivers/gles3/shaders/cubemap_filter.glsl)0
-rw-r--r--drivers/gles3/shaders/scene.glsl158
-rw-r--r--drivers/gles3/storage/light_storage.cpp493
-rw-r--r--drivers/gles3/storage/light_storage.h100
-rw-r--r--drivers/gles3/storage/material_storage.h2
-rw-r--r--drivers/gles3/storage/render_scene_buffers_gles3.cpp7
-rw-r--r--drivers/gles3/storage/render_scene_buffers_gles3.h1
-rw-r--r--drivers/gles3/storage/utilities.cpp14
-rw-r--r--editor/debugger/editor_file_server.cpp2
-rw-r--r--editor/editor_build_profile.cpp8
-rw-r--r--editor/editor_data.cpp3
-rw-r--r--editor/editor_file_system.h1
-rw-r--r--editor/editor_help.cpp4
-rw-r--r--editor/editor_layouts_dialog.cpp8
-rw-r--r--editor/editor_node.cpp17
-rw-r--r--editor/export/editor_export_platform.cpp71
-rw-r--r--editor/export/editor_export_platform.h5
-rw-r--r--editor/export/editor_export_platform_pc.cpp2
-rw-r--r--editor/filesystem_dock.cpp14
-rw-r--r--editor/filesystem_dock.h1
-rw-r--r--editor/gui/scene_tree_editor.h1
-rw-r--r--editor/plugins/path_2d_editor_plugin.cpp13
-rw-r--r--editor/scene_tree_dock.cpp58
-rw-r--r--editor/scene_tree_dock.h8
-rw-r--r--main/main.cpp16
-rw-r--r--misc/extension_api_validation/4.2-stable.expected7
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp4
-rw-r--r--modules/gdscript/gdscript_editor.cpp44
-rw-r--r--modules/gdscript/gdscript_warning.cpp2
-rw-r--r--modules/gdscript/gdscript_warning.h2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_array.gd3
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_array.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_object.gd3
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_object.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/cast_object_to_int.gd3
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/cast_object_to_int.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/unsafe_cast.gd24
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/unsafe_cast.out33
-rw-r--r--modules/gdscript/tests/scripts/completion/builtin_enum/builtin_enum_autocomplete.cfg9
-rw-r--r--modules/gdscript/tests/scripts/completion/builtin_enum/builtin_enum_autocomplete.gd4
-rw-r--r--modules/gdscript/tests/scripts/completion/builtin_enum/builtin_enum_values_autocompletion.cfg5
-rw-r--r--modules/gdscript/tests/scripts/completion/builtin_enum/builtin_enum_values_autocompletion.gd4
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/cast_freed_object.gd4
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/cast_freed_object.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/cast_int_to_array.gd4
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/cast_int_to_array.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/cast_int_to_object.gd4
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/cast_int_to_object.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/cast_object_to_int.gd4
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/cast_object_to_int.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/type_casting.gd24
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/type_casting.out10
-rw-r--r--modules/mono/csharp_script.cpp109
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs2
-rw-r--r--modules/mono/mono_gd/gd_mono.h2
-rw-r--r--modules/openxr/extensions/openxr_composition_layer_extension.cpp34
-rw-r--r--modules/openxr/extensions/platform/openxr_vulkan_extension.cpp4
-rw-r--r--modules/openxr/openxr_api.cpp527
-rw-r--r--modules/openxr/openxr_api.h33
-rw-r--r--platform/android/export/export_plugin.cpp16
-rw-r--r--platform/ios/export/export_plugin.cpp7
-rw-r--r--platform/linuxbsd/export/export_plugin.cpp19
-rw-r--r--platform/macos/export/export_plugin.cpp8
-rw-r--r--platform/web/export/export_plugin.cpp2
-rw-r--r--platform/windows/export/export_plugin.cpp9
-rw-r--r--scene/3d/mesh_instance_3d.cpp5
-rw-r--r--scene/3d/mesh_instance_3d.h2
-rw-r--r--scene/3d/reflection_probe.cpp11
-rw-r--r--scene/3d/reflection_probe.h2
-rw-r--r--scene/gui/rich_text_label.cpp12
-rw-r--r--scene/main/node.cpp51
-rw-r--r--scene/main/node.h7
-rw-r--r--scene/resources/resource_format_text.cpp18
-rw-r--r--scu_builders.py7
-rw-r--r--servers/rendering/dummy/storage/light_storage.h1
-rw-r--r--servers/rendering/renderer_rd/storage_rd/light_storage.h23
-rw-r--r--servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.cpp3
-rw-r--r--servers/rendering/renderer_scene_cull.cpp14
-rw-r--r--servers/rendering/shader_language.cpp6
-rw-r--r--servers/rendering/storage/light_storage.h1
-rw-r--r--tests/core/io/test_ip.h (renamed from scene/main/node.compat.inc)26
-rw-r--r--tests/core/templates/test_oa_hash_map.h227
-rw-r--r--tests/core/variant/test_array.h48
-rw-r--r--tests/scene/test_camera_2d.h318
-rw-r--r--tests/test_main.cpp3
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(&quot;&quot;)">
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"