summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--SConstruct7
-rw-r--r--core/io/resource_format_binary.cpp6
-rw-r--r--core/variant/dictionary.cpp9
-rw-r--r--core/variant/dictionary.h1
-rw-r--r--core/variant/variant.cpp2
-rw-r--r--core/variant/variant_call.cpp1
-rw-r--r--core/variant/variant_op.h32
-rw-r--r--doc/classes/Dictionary.xml8
-rw-r--r--doc/classes/GraphEdit.xml2
-rw-r--r--doc/classes/Resource.xml3
-rw-r--r--doc/classes/ResourceLoader.xml4
-rw-r--r--doc/classes/Sprite2D.xml6
-rw-r--r--doc/classes/Sprite3D.xml6
-rw-r--r--editor/animation_track_editor.cpp26
-rw-r--r--editor/animation_track_editor.h2
-rw-r--r--editor/import/resource_importer_texture.cpp4
-rw-r--r--modules/etcpak/SCsub1
-rw-r--r--modules/etcpak/image_compress_etcpak.cpp13
-rw-r--r--modules/etcpak/image_compress_etcpak.h2
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml2
-rw-r--r--modules/gdscript/gdscript.cpp41
-rw-r--r--modules/gdscript/gdscript.h2
-rw-r--r--modules/minimp3/register_types.cpp5
-rw-r--r--modules/openxr/openxr_api.cpp22
-rw-r--r--modules/openxr/openxr_api.h6
-rw-r--r--modules/openxr/openxr_interface.cpp36
-rw-r--r--modules/openxr/openxr_interface.h1
-rw-r--r--modules/squish/image_decompress_squish.cpp46
-rw-r--r--modules/vorbis/register_types.cpp5
-rw-r--r--platform/web/display_server_web.cpp153
-rw-r--r--platform/web/display_server_web.h28
-rw-r--r--platform/web/godot_js.h5
-rw-r--r--platform/web/js/libs/library_godot_input.js135
-rw-r--r--scene/2d/area_2d.cpp8
-rw-r--r--scene/2d/area_2d.h3
-rw-r--r--scene/2d/collision_object_2d.cpp8
-rw-r--r--scene/2d/collision_object_2d.h2
-rw-r--r--scene/2d/sprite_2d.cpp17
-rw-r--r--scene/3d/area_3d.cpp10
-rw-r--r--scene/3d/area_3d.h2
-rw-r--r--scene/3d/collision_object_3d.cpp7
-rw-r--r--scene/3d/collision_object_3d.h2
-rw-r--r--scene/3d/sprite_3d.cpp21
-rw-r--r--scene/animation/animation_tree.cpp23
-rw-r--r--scene/resources/resource_format_text.cpp4
-rw-r--r--servers/physics_2d/godot_area_2d.h4
-rw-r--r--servers/physics_2d/godot_body_2d.cpp3
-rw-r--r--servers/physics_2d/godot_collision_object_2d.cpp11
-rw-r--r--servers/physics_3d/godot_area_3d.h4
-rw-r--r--servers/physics_3d/godot_body_3d.cpp3
-rw-r--r--servers/physics_3d/godot_collision_object_3d.cpp11
-rw-r--r--servers/rendering/renderer_rd/effects/copy_effects.cpp34
-rw-r--r--servers/rendering/renderer_rd/effects/copy_effects.h1
-rw-r--r--servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp268
-rw-r--r--servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h10
-rw-r--r--thirdparty/etcpak/ProcessRgtc.cpp106
-rw-r--r--thirdparty/etcpak/ProcessRgtc.hpp14
57 files changed, 893 insertions, 305 deletions
diff --git a/SConstruct b/SConstruct
index 7ae90272f3..da2f89fbeb 100644
--- a/SConstruct
+++ b/SConstruct
@@ -212,6 +212,7 @@ opts.Add("extra_suffix", "Custom extra suffix added to the base filename of all
opts.Add("object_prefix", "Custom prefix added to the base filename of all generated object files", "")
opts.Add(BoolVariable("vsproj", "Generate a Visual Studio solution", False))
opts.Add("vsproj_name", "Name of the Visual Studio solution", "godot")
+opts.Add("import_env_vars", "A comma-separated list of environment variables to copy from the outer environment.", "")
opts.Add(BoolVariable("disable_3d", "Disable 3D nodes for a smaller executable", False))
opts.Add(BoolVariable("disable_advanced_gui", "Disable advanced GUI nodes and behaviors", False))
opts.Add("build_profile", "Path to a file containing a feature build profile", "")
@@ -270,6 +271,12 @@ opts.Add("LINKFLAGS", "Custom flags for the linker")
# in following code (especially platform and custom_modules).
opts.Update(env_base)
+# Copy custom environment variables if set.
+if env_base["import_env_vars"]:
+ for env_var in str(env_base["import_env_vars"]).split(","):
+ if env_var in os.environ:
+ env_base["ENV"][env_var] = os.environ[env_var]
+
# Platform selection: validate input, and add options.
selected_platform = ""
diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp
index 2a33f723dc..20c494516b 100644
--- a/core/io/resource_format_binary.cpp
+++ b/core/io/resource_format_binary.cpp
@@ -1454,8 +1454,10 @@ Error ResourceFormatLoaderBinary::rename_dependencies(const String &p_path, cons
fw.unref();
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
- da->remove(p_path);
- da->rename(p_path + ".depren", p_path);
+ if (da->exists(p_path + ".depren")) {
+ da->remove(p_path);
+ da->rename(p_path + ".depren", p_path);
+ }
return OK;
}
diff --git a/core/variant/dictionary.cpp b/core/variant/dictionary.cpp
index 141ce25fa6..8b61a8993a 100644
--- a/core/variant/dictionary.cpp
+++ b/core/variant/dictionary.cpp
@@ -150,6 +150,15 @@ Variant Dictionary::get(const Variant &p_key, const Variant &p_default) const {
return *result;
}
+Variant Dictionary::get_or_add(const Variant &p_key, const Variant &p_default) {
+ const Variant *result = getptr(p_key);
+ if (!result) {
+ operator[](p_key) = p_default;
+ return p_default;
+ }
+ return *result;
+}
+
int Dictionary::size() const {
return _p->variant_map.size();
}
diff --git a/core/variant/dictionary.h b/core/variant/dictionary.h
index 8935d35ed9..f94a0da80a 100644
--- a/core/variant/dictionary.h
+++ b/core/variant/dictionary.h
@@ -58,6 +58,7 @@ public:
Variant get_valid(const Variant &p_key) const;
Variant get(const Variant &p_key, const Variant &p_default) const;
+ Variant get_or_add(const Variant &p_key, const Variant &p_default);
int size() const;
bool is_empty() const;
diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp
index 4c0212075b..c352d800f9 100644
--- a/core/variant/variant.cpp
+++ b/core/variant/variant.cpp
@@ -931,7 +931,7 @@ bool Variant::is_zero() const {
return *reinterpret_cast<const ::RID *>(_data._mem) == ::RID();
}
case OBJECT: {
- return _get_obj().obj == nullptr;
+ return get_validated_object() == nullptr;
}
case CALLABLE: {
return reinterpret_cast<const Callable *>(_data._mem)->is_null();
diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp
index f041d2c95e..7121b4da96 100644
--- a/core/variant/variant_call.cpp
+++ b/core/variant/variant_call.cpp
@@ -2195,6 +2195,7 @@ static void _register_variant_builtin_methods() {
bind_method(Dictionary, values, sarray(), varray());
bind_method(Dictionary, duplicate, sarray("deep"), varray(false));
bind_method(Dictionary, get, sarray("key", "default"), varray(Variant()));
+ bind_method(Dictionary, get_or_add, sarray("key", "default"), varray(Variant()));
bind_method(Dictionary, make_read_only, sarray(), varray());
bind_method(Dictionary, is_read_only, sarray(), varray());
diff --git a/core/variant/variant_op.h b/core/variant/variant_op.h
index 9e6367ab6d..17ad126891 100644
--- a/core/variant/variant_op.h
+++ b/core/variant/variant_op.h
@@ -549,14 +549,14 @@ public:
class OperatorEvaluatorEqualObject {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
- const Object *a = p_left.get_validated_object();
- const Object *b = p_right.get_validated_object();
+ const ObjectID &a = VariantInternal::get_object_id(&p_left);
+ const ObjectID &b = VariantInternal::get_object_id(&p_right);
*r_ret = a == b;
r_valid = true;
}
static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) {
- const Object *a = left->get_validated_object();
- const Object *b = right->get_validated_object();
+ const ObjectID &a = VariantInternal::get_object_id(left);
+ const ObjectID &b = VariantInternal::get_object_id(right);
*VariantGetInternalPtr<bool>::get_ptr(r_ret) = a == b;
}
static void ptr_evaluate(const void *left, const void *right, void *r_ret) {
@@ -568,12 +568,12 @@ public:
class OperatorEvaluatorEqualObjectNil {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
- const Object *a = p_left.get_validated_object();
+ const Object *a = p_left.operator Object *();
*r_ret = a == nullptr;
r_valid = true;
}
static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) {
- const Object *a = left->get_validated_object();
+ const Object *a = left->operator Object *();
*VariantGetInternalPtr<bool>::get_ptr(r_ret) = a == nullptr;
}
static void ptr_evaluate(const void *left, const void *right, void *r_ret) {
@@ -585,12 +585,12 @@ public:
class OperatorEvaluatorEqualNilObject {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
- const Object *b = p_right.get_validated_object();
+ const Object *b = p_right.operator Object *();
*r_ret = nullptr == b;
r_valid = true;
}
static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) {
- const Object *b = right->get_validated_object();
+ const Object *b = right->operator Object *();
*VariantGetInternalPtr<bool>::get_ptr(r_ret) = nullptr == b;
}
static void ptr_evaluate(const void *left, const void *right, void *r_ret) {
@@ -620,14 +620,14 @@ public:
class OperatorEvaluatorNotEqualObject {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
- Object *a = p_left.get_validated_object();
- Object *b = p_right.get_validated_object();
+ const ObjectID &a = VariantInternal::get_object_id(&p_left);
+ const ObjectID &b = VariantInternal::get_object_id(&p_right);
*r_ret = a != b;
r_valid = true;
}
static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) {
- Object *a = left->get_validated_object();
- Object *b = right->get_validated_object();
+ const ObjectID &a = VariantInternal::get_object_id(left);
+ const ObjectID &b = VariantInternal::get_object_id(right);
*VariantGetInternalPtr<bool>::get_ptr(r_ret) = a != b;
}
static void ptr_evaluate(const void *left, const void *right, void *r_ret) {
@@ -639,12 +639,12 @@ public:
class OperatorEvaluatorNotEqualObjectNil {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
- Object *a = p_left.get_validated_object();
+ Object *a = p_left.operator Object *();
*r_ret = a != nullptr;
r_valid = true;
}
static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) {
- Object *a = left->get_validated_object();
+ Object *a = left->operator Object *();
*VariantGetInternalPtr<bool>::get_ptr(r_ret) = a != nullptr;
}
static void ptr_evaluate(const void *left, const void *right, void *r_ret) {
@@ -656,12 +656,12 @@ public:
class OperatorEvaluatorNotEqualNilObject {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
- Object *b = p_right.get_validated_object();
+ Object *b = p_right.operator Object *();
*r_ret = nullptr != b;
r_valid = true;
}
static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) {
- Object *b = right->get_validated_object();
+ Object *b = right->operator Object *();
*VariantGetInternalPtr<bool>::get_ptr(r_ret) = nullptr != b;
}
static void ptr_evaluate(const void *left, const void *right, void *r_ret) {
diff --git a/doc/classes/Dictionary.xml b/doc/classes/Dictionary.xml
index 955d80fcb7..7a5e51e4ef 100644
--- a/doc/classes/Dictionary.xml
+++ b/doc/classes/Dictionary.xml
@@ -194,6 +194,14 @@
Returns the corresponding value for the given [param key] in the dictionary. If the [param key] does not exist, returns [param default], or [code]null[/code] if the parameter is omitted.
</description>
</method>
+ <method name="get_or_add">
+ <return type="Variant" />
+ <param index="0" name="key" type="Variant" />
+ <param index="1" name="default" type="Variant" default="null" />
+ <description>
+ Gets a value and ensures the key is set. If the [param key] exists in the dictionary, this behaves like [method get]. Otherwise, the [param default] value is inserted into the dictionary and returned.
+ </description>
+ </method>
<method name="has" qualifiers="const">
<return type="bool" />
<param index="0" name="key" type="Variant" />
diff --git a/doc/classes/GraphEdit.xml b/doc/classes/GraphEdit.xml
index 410efd6389..9765b10d22 100644
--- a/doc/classes/GraphEdit.xml
+++ b/doc/classes/GraphEdit.xml
@@ -154,7 +154,7 @@
<method name="get_connection_list" qualifiers="const">
<return type="Dictionary[]" />
<description>
- Returns an Array containing the list of connections. A connection consists in a structure of the form [code]{ from_port: 0, from: "GraphNode name 0", to_port: 1, to: "GraphNode name 1" }[/code].
+ Returns an Array containing the list of connections. A connection consists in a structure of the form [code]{ from_port: 0, from_node: "GraphNode name 0", to_port: 1, to_node: "GraphNode name 1" }[/code].
</description>
</method>
<method name="get_menu_hbox">
diff --git a/doc/classes/Resource.xml b/doc/classes/Resource.xml
index c8146bb48f..49db3f81b4 100644
--- a/doc/classes/Resource.xml
+++ b/doc/classes/Resource.xml
@@ -4,8 +4,9 @@
Base class for serializable objects.
</brief_description>
<description>
- Resource is the base class for all Godot-specific resource types, serving primarily as data containers. Since they inherit from [RefCounted], resources are reference-counted and freed when no longer in use. They can also be nested within other resources, and saved on disk. Once loaded from disk, further attempts to load a resource by [member resource_path] returns the same reference. [PackedScene], one of the most common [Object]s in a Godot project, is also a resource, uniquely capable of storing and instantiating the [Node]s it contains as many times as desired.
+ Resource is the base class for all Godot-specific resource types, serving primarily as data containers. Since they inherit from [RefCounted], resources are reference-counted and freed when no longer in use. They can also be nested within other resources, and saved on disk. [PackedScene], one of the most common [Object]s in a Godot project, is also a resource, uniquely capable of storing and instantiating the [Node]s it contains as many times as desired.
In GDScript, resources can loaded from disk by their [member resource_path] using [method @GDScript.load] or [method @GDScript.preload].
+ The engine keeps a global cache of all loaded resources, referenced by paths (see [method ResourceLoader.has_cached]). A resource will be cached when loaded for the first time and removed from cache once all references are released. When a resource is cached, subsequent loads using its path will return the cached reference.
[b]Note:[/b] In C#, resources will not be freed instantly after they are no longer in use. Instead, garbage collection will run periodically and will free resources that are no longer in use. This means that unused resources will linger on for a while before being removed.
</description>
<tutorials>
diff --git a/doc/classes/ResourceLoader.xml b/doc/classes/ResourceLoader.xml
index 267594edcb..54e95d8c30 100644
--- a/doc/classes/ResourceLoader.xml
+++ b/doc/classes/ResourceLoader.xml
@@ -28,6 +28,7 @@
<description>
Returns whether a recognized resource exists for the given [param path].
An optional [param type_hint] can be used to further specify the [Resource] type that should be handled by the [ResourceFormatLoader]. Anything that inherits from [Resource] can be used as a type hint, for example [Image].
+ [b]Note:[/b] If you use [method Resource.take_over_path], this method will return [code]true[/code] for the taken path even if the resource wasn't saved (i.e. exists only in resource cache).
</description>
</method>
<method name="get_dependencies">
@@ -137,10 +138,13 @@
The resource was loaded successfully and can be accessed via [method load_threaded_get].
</constant>
<constant name="CACHE_MODE_IGNORE" value="0" enum="CacheMode">
+ The resource is always loaded from disk, even if a cache entry exists for its path, and the newly loaded copy will not be cached. Instances loaded with this mode will exist independently.
</constant>
<constant name="CACHE_MODE_REUSE" value="1" enum="CacheMode">
+ If a resource is cached, returns the cached reference. Otherwise it's loaded from disk.
</constant>
<constant name="CACHE_MODE_REPLACE" value="2" enum="CacheMode">
+ The resource is always loaded from disk, even if a cache entry exists for its path. The cached entry will be replaced by the newly loaded copy.
</constant>
</constants>
</class>
diff --git a/doc/classes/Sprite2D.xml b/doc/classes/Sprite2D.xml
index 8eb9e26a83..f8622d8f38 100644
--- a/doc/classes/Sprite2D.xml
+++ b/doc/classes/Sprite2D.xml
@@ -60,13 +60,13 @@
If [code]true[/code], texture is flipped vertically.
</member>
<member name="frame" type="int" setter="set_frame" getter="get_frame" default="0">
- Current frame to display from sprite sheet. [member hframes] or [member vframes] must be greater than 1.
+ Current frame to display from sprite sheet. [member hframes] or [member vframes] must be greater than 1. This property is automatically adjusted when [member hframes] or [member vframes] are changed to keep pointing to the same visual frame (same column and row). If that's impossible, this value is reset to [code]0[/code].
</member>
<member name="frame_coords" type="Vector2i" setter="set_frame_coords" getter="get_frame_coords" default="Vector2i(0, 0)">
Coordinates of the frame to display from sprite sheet. This is as an alias for the [member frame] property. [member hframes] or [member vframes] must be greater than 1.
</member>
<member name="hframes" type="int" setter="set_hframes" getter="get_hframes" default="1">
- The number of columns in the sprite sheet.
+ The number of columns in the sprite sheet. When this property is changed, [member frame] is adjusted so that the same visual frame is maintained (same row and column). If that's impossible, [member frame] is reset to [code]0[/code].
</member>
<member name="offset" type="Vector2" setter="set_offset" getter="get_offset" default="Vector2(0, 0)">
The texture's drawing offset.
@@ -84,7 +84,7 @@
[Texture2D] object to draw.
</member>
<member name="vframes" type="int" setter="set_vframes" getter="get_vframes" default="1">
- The number of rows in the sprite sheet.
+ The number of rows in the sprite sheet. When this property is changed, [member frame] is adjusted so that the same visual frame is maintained (same row and column). If that's impossible, [member frame] is reset to [code]0[/code].
</member>
</members>
<signals>
diff --git a/doc/classes/Sprite3D.xml b/doc/classes/Sprite3D.xml
index 9733c5f48a..4b4421aeba 100644
--- a/doc/classes/Sprite3D.xml
+++ b/doc/classes/Sprite3D.xml
@@ -10,13 +10,13 @@
</tutorials>
<members>
<member name="frame" type="int" setter="set_frame" getter="get_frame" default="0">
- Current frame to display from sprite sheet. [member hframes] or [member vframes] must be greater than 1.
+ Current frame to display from sprite sheet. [member hframes] or [member vframes] must be greater than 1. This property is automatically adjusted when [member hframes] or [member vframes] are changed to keep pointing to the same visual frame (same column and row). If that's impossible, this value is reset to [code]0[/code].
</member>
<member name="frame_coords" type="Vector2i" setter="set_frame_coords" getter="get_frame_coords" default="Vector2i(0, 0)">
Coordinates of the frame to display from sprite sheet. This is as an alias for the [member frame] property. [member hframes] or [member vframes] must be greater than 1.
</member>
<member name="hframes" type="int" setter="set_hframes" getter="get_hframes" default="1">
- The number of columns in the sprite sheet.
+ The number of columns in the sprite sheet. When this property is changed, [member frame] is adjusted so that the same visual frame is maintained (same row and column). If that's impossible, [member frame] is reset to [code]0[/code].
</member>
<member name="region_enabled" type="bool" setter="set_region_enabled" getter="is_region_enabled" default="false">
If [code]true[/code], the sprite will use [member region_rect] and display only the specified part of its texture.
@@ -28,7 +28,7 @@
[Texture2D] object to draw. If [member GeometryInstance3D.material_override] is used, this will be overridden. The size information is still used.
</member>
<member name="vframes" type="int" setter="set_vframes" getter="get_vframes" default="1">
- The number of rows in the sprite sheet.
+ The number of rows in the sprite sheet. When this property is changed, [member frame] is adjusted so that the same visual frame is maintained (same row and column). If that's impossible, [member frame] is reset to [code]0[/code].
</member>
</members>
<signals>
diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp
index faffbb631f..8268cf10ae 100644
--- a/editor/animation_track_editor.cpp
+++ b/editor/animation_track_editor.cpp
@@ -306,10 +306,14 @@ bool AnimationTrackKeyEdit::_set(const StringName &p_name, const Variant &p_valu
setting = true;
undo_redo->create_action(TTR("Animation Change Keyframe Value"), UndoRedo::MERGE_ENDS);
- int prev = animation->bezier_track_get_key_handle_mode(track, key);
- undo_redo->add_do_method(this, "_bezier_track_set_key_handle_mode", animation.ptr(), track, key, value);
- undo_redo->add_undo_method(this, "_bezier_track_set_key_handle_mode", animation.ptr(), track, key, prev);
+ int prev_mode = animation->bezier_track_get_key_handle_mode(track, key);
+ Vector2 prev_in_handle = animation->bezier_track_get_key_in_handle(track, key);
+ Vector2 prev_out_handle = animation->bezier_track_get_key_out_handle(track, key);
+ undo_redo->add_do_method(editor, "_bezier_track_set_key_handle_mode", animation.ptr(), track, key, value);
undo_redo->add_do_method(this, "_update_obj", animation);
+ undo_redo->add_undo_method(editor, "_bezier_track_set_key_handle_mode", animation.ptr(), track, key, prev_mode);
+ undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_in_handle", track, key, prev_in_handle);
+ undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", track, key, prev_out_handle);
undo_redo->add_undo_method(this, "_update_obj", animation);
undo_redo->commit_action();
@@ -857,8 +861,8 @@ bool AnimationMultiTrackKeyEdit::_set(const StringName &p_name, const Variant &p
undo_redo->create_action(TTR("Animation Multi Change Keyframe Value"), UndoRedo::MERGE_ENDS);
}
Vector2 prev = animation->bezier_track_get_key_in_handle(track, key);
- undo_redo->add_do_method(this, "_bezier_track_set_key_in_handle", track, key, value);
- undo_redo->add_undo_method(this, "_bezier_track_set_key_in_handle", track, key, prev);
+ undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_in_handle", track, key, value);
+ undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_in_handle", track, key, prev);
update_obj = true;
} else if (name == "out_handle") {
const Variant &value = p_value;
@@ -878,9 +882,13 @@ bool AnimationMultiTrackKeyEdit::_set(const StringName &p_name, const Variant &p
setting = true;
undo_redo->create_action(TTR("Animation Multi Change Keyframe Value"), UndoRedo::MERGE_ENDS);
}
- int prev = animation->bezier_track_get_key_handle_mode(track, key);
- undo_redo->add_do_method(this, "_bezier_track_set_key_handle_mode", animation.ptr(), track, key, value);
- undo_redo->add_undo_method(this, "_bezier_track_set_key_handle_mode", animation.ptr(), track, key, prev);
+ int prev_mode = animation->bezier_track_get_key_handle_mode(track, key);
+ Vector2 prev_in_handle = animation->bezier_track_get_key_in_handle(track, key);
+ Vector2 prev_out_handle = animation->bezier_track_get_key_out_handle(track, key);
+ undo_redo->add_do_method(editor, "_bezier_track_set_key_handle_mode", animation.ptr(), track, key, value);
+ undo_redo->add_undo_method(editor, "_bezier_track_set_key_handle_mode", animation.ptr(), track, key, prev_mode);
+ undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_in_handle", track, key, prev_in_handle);
+ undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", track, key, prev_out_handle);
update_obj = true;
}
} break;
@@ -5175,6 +5183,7 @@ void AnimationTrackEditor::_update_key_edit() {
key_edit->animation_read_only = read_only;
key_edit->track = selection.front()->key().track;
key_edit->use_fps = timeline->is_using_fps();
+ key_edit->editor = this;
int key_id = selection.front()->key().key;
if (key_id >= animation->track_get_key_count(key_edit->track)) {
@@ -5194,6 +5203,7 @@ void AnimationTrackEditor::_update_key_edit() {
multi_key_edit = memnew(AnimationMultiTrackKeyEdit);
multi_key_edit->animation = animation;
multi_key_edit->animation_read_only = read_only;
+ multi_key_edit->editor = this;
RBMap<int, List<float>> key_ofs_map;
RBMap<int, NodePath> base_map;
diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h
index eb2cb2a4e4..4b9849b26c 100644
--- a/editor/animation_track_editor.h
+++ b/editor/animation_track_editor.h
@@ -68,6 +68,7 @@ public:
PropertyInfo hint;
NodePath base;
bool use_fps = false;
+ AnimationTrackEditor *editor = nullptr;
bool _hide_script_from_inspector() { return true; }
bool _hide_metadata_from_inspector() { return true; }
@@ -105,6 +106,7 @@ public:
Node *root_path = nullptr;
bool use_fps = false;
+ AnimationTrackEditor *editor = nullptr;
bool _hide_script_from_inspector() { return true; }
bool _hide_metadata_from_inspector() { return true; }
diff --git a/editor/import/resource_importer_texture.cpp b/editor/import/resource_importer_texture.cpp
index 9b98023359..7f27be99f7 100644
--- a/editor/import/resource_importer_texture.cpp
+++ b/editor/import/resource_importer_texture.cpp
@@ -745,6 +745,10 @@ bool ResourceImporterTexture::are_import_settings_valid(const String &p_path) co
if (meta.has("has_editor_variant")) {
String imported_path = ResourceFormatImporter::get_singleton()->get_internal_resource_path(p_path);
+ if (!FileAccess::exists(imported_path)) {
+ return false;
+ }
+
String editor_meta_path = imported_path.replace(".editor.ctex", ".editor.meta");
Dictionary editor_meta = _load_editor_meta(editor_meta_path);
diff --git a/modules/etcpak/SCsub b/modules/etcpak/SCsub
index 2d3b69be75..3a4bff8e87 100644
--- a/modules/etcpak/SCsub
+++ b/modules/etcpak/SCsub
@@ -13,6 +13,7 @@ thirdparty_dir = "#thirdparty/etcpak/"
thirdparty_sources = [
"Dither.cpp",
"ProcessDxtc.cpp",
+ "ProcessRgtc.cpp",
"ProcessRGB.cpp",
"Tables.cpp",
]
diff --git a/modules/etcpak/image_compress_etcpak.cpp b/modules/etcpak/image_compress_etcpak.cpp
index 14cce2686c..f528b92cf2 100644
--- a/modules/etcpak/image_compress_etcpak.cpp
+++ b/modules/etcpak/image_compress_etcpak.cpp
@@ -35,6 +35,7 @@
#include <ProcessDxtc.hpp>
#include <ProcessRGB.hpp>
+#include <ProcessRgtc.hpp>
EtcpakType _determine_etc_type(Image::UsedChannels p_channels) {
switch (p_channels) {
@@ -62,9 +63,9 @@ EtcpakType _determine_dxt_type(Image::UsedChannels p_channels) {
case Image::USED_CHANNELS_LA:
return EtcpakType::ETCPAK_TYPE_DXT5;
case Image::USED_CHANNELS_R:
- return EtcpakType::ETCPAK_TYPE_DXT5;
+ return EtcpakType::ETCPAK_TYPE_RGTC_R;
case Image::USED_CHANNELS_RG:
- return EtcpakType::ETCPAK_TYPE_DXT5_RA_AS_RG;
+ return EtcpakType::ETCPAK_TYPE_RGTC_RG;
case Image::USED_CHANNELS_RGB:
return EtcpakType::ETCPAK_TYPE_DXT1;
case Image::USED_CHANNELS_RGBA:
@@ -127,6 +128,10 @@ void _compress_etcpak(EtcpakType p_compresstype, Image *r_img) {
r_img->convert_rg_to_ra_rgba8();
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT5) {
target_format = Image::FORMAT_DXT5;
+ } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_RGTC_R) {
+ target_format = Image::FORMAT_RGTC_R;
+ } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_RGTC_RG) {
+ target_format = Image::FORMAT_RGTC_RG;
} else {
ERR_FAIL_MSG("Invalid or unsupported etcpak compression format, not ETC or DXT.");
}
@@ -229,6 +234,10 @@ void _compress_etcpak(EtcpakType p_compresstype, Image *r_img) {
CompressDxt1Dither(src_mip_read, dest_mip_write, blocks, mip_w);
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT5 || p_compresstype == EtcpakType::ETCPAK_TYPE_DXT5_RA_AS_RG) {
CompressDxt5(src_mip_read, dest_mip_write, blocks, mip_w);
+ } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_RGTC_RG) {
+ CompressRgtcRG(src_mip_read, dest_mip_write, blocks, mip_w);
+ } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_RGTC_R) {
+ CompressRgtcR(src_mip_read, dest_mip_write, blocks, mip_w);
} else {
ERR_FAIL_MSG("etcpak: Invalid or unsupported compression format.");
}
diff --git a/modules/etcpak/image_compress_etcpak.h b/modules/etcpak/image_compress_etcpak.h
index ff267631a6..ff8bb635b4 100644
--- a/modules/etcpak/image_compress_etcpak.h
+++ b/modules/etcpak/image_compress_etcpak.h
@@ -41,6 +41,8 @@ enum class EtcpakType {
ETCPAK_TYPE_DXT1,
ETCPAK_TYPE_DXT5,
ETCPAK_TYPE_DXT5_RA_AS_RG,
+ ETCPAK_TYPE_RGTC_R,
+ ETCPAK_TYPE_RGTC_RG,
};
void _compress_etc1(Image *r_img);
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml
index 3da6bcf10c..fcb7a11a14 100644
--- a/modules/gdscript/doc_classes/@GDScript.xml
+++ b/modules/gdscript/doc_classes/@GDScript.xml
@@ -162,7 +162,7 @@
<return type="Resource" />
<param index="0" name="path" type="String" />
<description>
- Returns a [Resource] from the filesystem located at the absolute [param path]. Unless it's already referenced elsewhere (such as in another script or in the scene), the resource is loaded from disk on function call, which might cause a slight delay, especially when loading large scenes. To avoid unnecessary delays when loading something multiple times, either store the resource in a variable or use [method preload].
+ Returns a [Resource] from the filesystem located at the absolute [param path]. Unless it's already referenced elsewhere (such as in another script or in the scene), the resource is loaded from disk on function call, which might cause a slight delay, especially when loading large scenes. To avoid unnecessary delays when loading something multiple times, either store the resource in a variable or use [method preload]. This method is equivalent of using [method ResourceLoader.load] with [constant ResourceLoader.CACHE_MODE_REUSE].
[b]Note:[/b] Resource paths can be obtained by right-clicking on a resource in the FileSystem dock and choosing "Copy Path", or by dragging the file from the FileSystem dock into the current script.
[codeblock]
# Load a scene called "main" located in the root of the project directory and cache it in a variable.
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index 07ab99e98b..52235b1854 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -1155,8 +1155,8 @@ RBSet<GDScript *> GDScript::get_dependencies() {
return dependencies;
}
-RBSet<GDScript *> GDScript::get_inverted_dependencies() {
- RBSet<GDScript *> inverted_dependencies;
+HashMap<GDScript *, RBSet<GDScript *>> GDScript::get_all_dependencies() {
+ HashMap<GDScript *, RBSet<GDScript *>> all_dependencies;
List<GDScript *> scripts;
{
@@ -1170,51 +1170,42 @@ RBSet<GDScript *> GDScript::get_inverted_dependencies() {
}
for (GDScript *scr : scripts) {
- if (scr == nullptr || scr == this || scr->destructing) {
+ if (scr == nullptr || scr->destructing) {
continue;
}
-
- RBSet<GDScript *> scr_dependencies = scr->get_dependencies();
- if (scr_dependencies.has(this)) {
- inverted_dependencies.insert(scr);
- }
+ all_dependencies.insert(scr, scr->get_dependencies());
}
- return inverted_dependencies;
+ return all_dependencies;
}
RBSet<GDScript *> GDScript::get_must_clear_dependencies() {
RBSet<GDScript *> dependencies = get_dependencies();
RBSet<GDScript *> must_clear_dependencies;
- HashMap<GDScript *, RBSet<GDScript *>> inverted_dependencies;
-
- for (GDScript *E : dependencies) {
- inverted_dependencies.insert(E, E->get_inverted_dependencies());
- }
+ HashMap<GDScript *, RBSet<GDScript *>> all_dependencies = get_all_dependencies();
RBSet<GDScript *> cant_clear;
- for (KeyValue<GDScript *, RBSet<GDScript *>> &E : inverted_dependencies) {
+ for (KeyValue<GDScript *, RBSet<GDScript *>> &E : all_dependencies) {
+ if (dependencies.has(E.key)) {
+ continue;
+ }
for (GDScript *F : E.value) {
- if (!dependencies.has(F)) {
- cant_clear.insert(E.key);
- for (GDScript *G : E.key->get_dependencies()) {
- cant_clear.insert(G);
- }
- break;
+ if (dependencies.has(F)) {
+ cant_clear.insert(F);
}
}
}
- for (KeyValue<GDScript *, RBSet<GDScript *>> &E : inverted_dependencies) {
- if (cant_clear.has(E.key) || ScriptServer::is_global_class(E.key->get_fully_qualified_name())) {
+ for (GDScript *E : dependencies) {
+ if (cant_clear.has(E) || ScriptServer::is_global_class(E->get_fully_qualified_name())) {
continue;
}
- must_clear_dependencies.insert(E.key);
+ must_clear_dependencies.insert(E);
}
cant_clear.clear();
dependencies.clear();
- inverted_dependencies.clear();
+ all_dependencies.clear();
return must_clear_dependencies;
}
diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h
index aba4d7e721..5165ec1b06 100644
--- a/modules/gdscript/gdscript.h
+++ b/modules/gdscript/gdscript.h
@@ -254,7 +254,7 @@ public:
const Ref<GDScriptNativeClass> &get_native() const { return native; }
RBSet<GDScript *> get_dependencies();
- RBSet<GDScript *> get_inverted_dependencies();
+ HashMap<GDScript *, RBSet<GDScript *>> get_all_dependencies();
RBSet<GDScript *> get_must_clear_dependencies();
virtual bool has_script_signal(const StringName &p_signal) const override;
diff --git a/modules/minimp3/register_types.cpp b/modules/minimp3/register_types.cpp
index 627d093bc1..c85f0b3389 100644
--- a/modules/minimp3/register_types.cpp
+++ b/modules/minimp3/register_types.cpp
@@ -52,8 +52,13 @@ void initialize_minimp3_module(ModuleInitializationLevel p_level) {
ResourceFormatImporter::get_singleton()->add_importer(mp3_import);
}
+ ClassDB::APIType prev_api = ClassDB::get_current_api();
+ ClassDB::set_current_api(ClassDB::API_EDITOR);
+
// Required to document import options in the class reference.
GDREGISTER_CLASS(ResourceImporterMP3);
+
+ ClassDB::set_current_api(prev_api);
#endif
GDREGISTER_CLASS(AudioStreamMP3);
diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp
index 3c606de670..da6fd2e9b2 100644
--- a/modules/openxr/openxr_api.cpp
+++ b/modules/openxr/openxr_api.cpp
@@ -87,7 +87,7 @@ String OpenXRAPI::get_default_action_map_resource_name() {
return name;
}
-String OpenXRAPI::get_error_string(XrResult result) {
+String OpenXRAPI::get_error_string(XrResult result) const {
if (XR_SUCCEEDED(result)) {
return String("Succeeded");
}
@@ -1261,6 +1261,7 @@ bool OpenXRAPI::resolve_instance_openxr_symbols() {
OPENXR_API_INIT_XR_FUNC_V(xrGetActionStateFloat);
OPENXR_API_INIT_XR_FUNC_V(xrGetActionStateVector2f);
OPENXR_API_INIT_XR_FUNC_V(xrGetCurrentInteractionProfile);
+ OPENXR_API_INIT_XR_FUNC_V(xrGetReferenceSpaceBoundsRect);
OPENXR_API_INIT_XR_FUNC_V(xrGetSystem);
OPENXR_API_INIT_XR_FUNC_V(xrGetSystemProperties);
OPENXR_API_INIT_XR_FUNC_V(xrLocateViews);
@@ -2072,6 +2073,25 @@ void OpenXRAPI::set_foveation_dynamic(bool p_foveation_dynamic) {
}
}
+Size2 OpenXRAPI::get_play_space_bounds() const {
+ Size2 ret;
+
+ ERR_FAIL_COND_V(session == XR_NULL_HANDLE, Size2());
+
+ XrExtent2Df extents;
+
+ XrResult result = xrGetReferenceSpaceBoundsRect(session, reference_space, &extents);
+ if (XR_FAILED(result)) {
+ print_line("OpenXR: failed to get play space bounds! [", get_error_string(result), "]");
+ return ret;
+ }
+
+ ret.width = extents.width;
+ ret.height = extents.height;
+
+ return ret;
+}
+
OpenXRAPI::OpenXRAPI() {
// OpenXRAPI is only constructed if OpenXR is enabled.
singleton = this;
diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h
index 64769b244c..efa32b7544 100644
--- a/modules/openxr/openxr_api.h
+++ b/modules/openxr/openxr_api.h
@@ -199,6 +199,7 @@ private:
EXT_PROTO_XRRESULT_FUNC3(xrGetActionStateVector2f, (XrSession), session, (const XrActionStateGetInfo *), getInfo, (XrActionStateVector2f *), state)
EXT_PROTO_XRRESULT_FUNC3(xrGetCurrentInteractionProfile, (XrSession), session, (XrPath), topLevelUserPath, (XrInteractionProfileState *), interactionProfile)
EXT_PROTO_XRRESULT_FUNC2(xrGetInstanceProperties, (XrInstance), instance, (XrInstanceProperties *), instanceProperties)
+ EXT_PROTO_XRRESULT_FUNC3(xrGetReferenceSpaceBoundsRect, (XrSession), session, (XrReferenceSpaceType), referenceSpaceType, (XrExtent2Df *), bounds)
EXT_PROTO_XRRESULT_FUNC3(xrGetSystem, (XrInstance), instance, (const XrSystemGetInfo *), getInfo, (XrSystemId *), systemId)
EXT_PROTO_XRRESULT_FUNC3(xrGetSystemProperties, (XrInstance), instance, (XrSystemId), systemId, (XrSystemProperties *), properties)
EXT_PROTO_XRRESULT_FUNC4(xrLocateSpace, (XrSpace), space, (XrSpace), baseSpace, (XrTime), time, (XrSpaceLocation *), location)
@@ -317,7 +318,7 @@ public:
XrResult try_get_instance_proc_addr(const char *p_name, PFN_xrVoidFunction *p_addr);
XrResult get_instance_proc_addr(const char *p_name, PFN_xrVoidFunction *p_addr);
- String get_error_string(XrResult result);
+ String get_error_string(XrResult result) const;
String get_swapchain_format_name(int64_t p_swapchain_format) const;
void set_xr_interface(OpenXRInterface *p_xr_interface);
@@ -380,6 +381,9 @@ public:
bool get_foveation_dynamic() const;
void set_foveation_dynamic(bool p_foveation_dynamic);
+ // Play space.
+ Size2 get_play_space_bounds() const;
+
// action map
String get_default_action_map_resource_name();
diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp
index 8ce76a5fad..66f8192c9e 100644
--- a/modules/openxr/openxr_interface.cpp
+++ b/modules/openxr/openxr_interface.cpp
@@ -690,6 +690,42 @@ bool OpenXRInterface::set_play_area_mode(XRInterface::PlayAreaMode p_mode) {
return false;
}
+PackedVector3Array OpenXRInterface::get_play_area() const {
+ XRServer *xr_server = XRServer::get_singleton();
+ ERR_FAIL_NULL_V(xr_server, PackedVector3Array());
+ PackedVector3Array arr;
+
+ Vector3 sides[4] = {
+ Vector3(-0.5f, 0.0f, -0.5f),
+ Vector3(0.5f, 0.0f, -0.5f),
+ Vector3(0.5f, 0.0f, 0.5f),
+ Vector3(-0.5f, 0.0f, 0.5f),
+ };
+
+ if (openxr_api != nullptr && openxr_api->is_initialized()) {
+ Size2 extents = openxr_api->get_play_space_bounds();
+ if (extents.width != 0.0 && extents.height != 0.0) {
+ Transform3D reference_frame = xr_server->get_reference_frame();
+
+ for (int i = 0; i < 4; i++) {
+ Vector3 coord = sides[i];
+
+ // Scale it up.
+ coord.x *= extents.width;
+ coord.z *= extents.height;
+
+ // Now apply our reference.
+ Vector3 out = reference_frame.xform(coord);
+ arr.push_back(out);
+ }
+ } else {
+ WARN_PRINT_ONCE("OpenXR: No extents available.");
+ }
+ }
+
+ return arr;
+}
+
float OpenXRInterface::get_display_refresh_rate() const {
if (openxr_api == nullptr) {
return 0.0;
diff --git a/modules/openxr/openxr_interface.h b/modules/openxr/openxr_interface.h
index 51ef4ea228..489d0845ba 100644
--- a/modules/openxr/openxr_interface.h
+++ b/modules/openxr/openxr_interface.h
@@ -125,6 +125,7 @@ public:
virtual bool supports_play_area_mode(XRInterface::PlayAreaMode p_mode) override;
virtual XRInterface::PlayAreaMode get_play_area_mode() const override;
virtual bool set_play_area_mode(XRInterface::PlayAreaMode p_mode) override;
+ virtual PackedVector3Array get_play_area() const override;
float get_display_refresh_rate() const;
void set_display_refresh_rate(float p_refresh_rate);
diff --git a/modules/squish/image_decompress_squish.cpp b/modules/squish/image_decompress_squish.cpp
index 5b35a2643a..fba76621d6 100644
--- a/modules/squish/image_decompress_squish.cpp
+++ b/modules/squish/image_decompress_squish.cpp
@@ -36,7 +36,9 @@ void image_decompress_squish(Image *p_image) {
int w = p_image->get_width();
int h = p_image->get_height();
+ Image::Format source_format = p_image->get_format();
Image::Format target_format = Image::FORMAT_RGBA8;
+
Vector<uint8_t> data;
int target_size = Image::get_image_data_size(w, h, target_format, p_image->has_mipmaps());
int mm_count = p_image->get_mipmap_count();
@@ -45,33 +47,49 @@ void image_decompress_squish(Image *p_image) {
const uint8_t *rb = p_image->get_data().ptr();
uint8_t *wb = data.ptrw();
- int squish_flags = Image::FORMAT_MAX;
- if (p_image->get_format() == Image::FORMAT_DXT1) {
- squish_flags = squish::kDxt1;
- } else if (p_image->get_format() == Image::FORMAT_DXT3) {
- squish_flags = squish::kDxt3;
- } else if (p_image->get_format() == Image::FORMAT_DXT5 || p_image->get_format() == Image::FORMAT_DXT5_RA_AS_RG) {
- squish_flags = squish::kDxt5;
- } else if (p_image->get_format() == Image::FORMAT_RGTC_R) {
- squish_flags = squish::kBc4;
- } else if (p_image->get_format() == Image::FORMAT_RGTC_RG) {
- squish_flags = squish::kBc5;
- } else {
- ERR_FAIL_MSG("Squish: Can't decompress unknown format: " + itos(p_image->get_format()) + ".");
+ int squish_flags = 0;
+
+ switch (source_format) {
+ case Image::FORMAT_DXT1:
+ squish_flags = squish::kDxt1;
+ break;
+
+ case Image::FORMAT_DXT3:
+ squish_flags = squish::kDxt3;
+ break;
+
+ case Image::FORMAT_DXT5:
+ case Image::FORMAT_DXT5_RA_AS_RG:
+ squish_flags = squish::kDxt5;
+ break;
+
+ case Image::FORMAT_RGTC_R:
+ squish_flags = squish::kBc4;
+ break;
+
+ case Image::FORMAT_RGTC_RG:
+ squish_flags = squish::kBc5;
+ break;
+
+ default:
+ ERR_FAIL_MSG("Squish: Can't decompress unknown format: " + itos(p_image->get_format()) + ".");
+ break;
}
for (int i = 0; i <= mm_count; i++) {
int src_ofs = 0, mipmap_size = 0, mipmap_w = 0, mipmap_h = 0;
p_image->get_mipmap_offset_size_and_dimensions(i, src_ofs, mipmap_size, mipmap_w, mipmap_h);
+
int dst_ofs = Image::get_image_mipmap_offset(p_image->get_width(), p_image->get_height(), target_format, i);
squish::DecompressImage(&wb[dst_ofs], w, h, &rb[src_ofs], squish_flags);
+
w >>= 1;
h >>= 1;
}
p_image->set_data(p_image->get_width(), p_image->get_height(), p_image->has_mipmaps(), target_format, data);
- if (p_image->get_format() == Image::FORMAT_DXT5_RA_AS_RG) {
+ if (source_format == Image::FORMAT_DXT5_RA_AS_RG) {
p_image->convert_ra_rgba8_to_rg();
}
}
diff --git a/modules/vorbis/register_types.cpp b/modules/vorbis/register_types.cpp
index 26af912999..def34220ea 100644
--- a/modules/vorbis/register_types.cpp
+++ b/modules/vorbis/register_types.cpp
@@ -48,8 +48,13 @@ void initialize_vorbis_module(ModuleInitializationLevel p_level) {
ResourceFormatImporter::get_singleton()->add_importer(ogg_vorbis_importer);
}
+ ClassDB::APIType prev_api = ClassDB::get_current_api();
+ ClassDB::set_current_api(ClassDB::API_EDITOR);
+
// Required to document import options in the class reference.
GDREGISTER_CLASS(ResourceImporterOggVorbis);
+
+ ClassDB::set_current_api(prev_api);
#endif
GDREGISTER_CLASS(AudioStreamOggVorbis);
diff --git a/platform/web/display_server_web.cpp b/platform/web/display_server_web.cpp
index 022e044185..b4a190d47e 100644
--- a/platform/web/display_server_web.cpp
+++ b/platform/web/display_server_web.cpp
@@ -174,6 +174,11 @@ void DisplayServerWeb::_key_callback(const String &p_key_event_code, const Strin
// Resume audio context after input in case autoplay was denied.
OS_Web::get_singleton()->resume_audio();
+ DisplayServerWeb *ds = get_singleton();
+ if (ds->ime_started) {
+ return;
+ }
+
char32_t c = 0x00;
String unicode = p_key_event_key;
if (unicode.length() == 1) {
@@ -183,17 +188,21 @@ void DisplayServerWeb::_key_callback(const String &p_key_event_code, const Strin
Key keycode = dom_code2godot_scancode(p_key_event_code.utf8().get_data(), p_key_event_key.utf8().get_data(), false);
Key scancode = dom_code2godot_scancode(p_key_event_code.utf8().get_data(), p_key_event_key.utf8().get_data(), true);
- Ref<InputEventKey> ev;
- ev.instantiate();
- ev->set_echo(p_repeat);
- ev->set_keycode(fix_keycode(c, keycode));
- ev->set_physical_keycode(scancode);
- ev->set_key_label(fix_key_label(c, keycode));
- ev->set_unicode(fix_unicode(c));
- ev->set_pressed(p_pressed);
- dom2godot_mod(ev, p_modifiers, fix_keycode(c, keycode));
+ DisplayServerWeb::KeyEvent ke;
- Input::get_singleton()->parse_input_event(ev);
+ ke.pressed = p_pressed;
+ ke.echo = p_repeat;
+ ke.raw = true;
+ ke.keycode = fix_keycode(c, keycode);
+ ke.physical_keycode = scancode;
+ ke.key_label = fix_key_label(c, keycode);
+ ke.unicode = fix_unicode(c);
+ ke.mod = p_modifiers;
+
+ if (ds->key_event_pos >= ds->key_event_buffer.size()) {
+ ds->key_event_buffer.resize(1 + ds->key_event_pos);
+ }
+ ds->key_event_buffer.write[ds->key_event_pos++] = ke;
// Make sure to flush all events so we can call restricted APIs inside the event.
Input::get_singleton()->flush_buffered_events();
@@ -619,7 +628,7 @@ int DisplayServerWeb::mouse_wheel_callback(double p_delta_x, double p_delta_y) {
}
int DisplayServerWeb::_mouse_wheel_callback(double p_delta_x, double p_delta_y) {
- if (!godot_js_display_canvas_is_focused()) {
+ if (!godot_js_display_canvas_is_focused() && !godot_js_is_ime_focused()) {
if (get_singleton()->cursor_inside_canvas) {
godot_js_display_canvas_focus();
} else {
@@ -726,7 +735,7 @@ bool DisplayServerWeb::is_touchscreen_available() const {
// Virtual Keyboard
void DisplayServerWeb::vk_input_text_callback(const char *p_text, int p_cursor) {
- String text = p_text;
+ String text = String::utf8(p_text);
#ifdef PROXY_TO_PTHREAD_ENABLED
if (!Thread::is_main_thread()) {
@@ -809,6 +818,100 @@ void DisplayServerWeb::_gamepad_callback(int p_index, int p_connected, const Str
}
}
+// IME.
+void DisplayServerWeb::ime_callback(int p_type, const char *p_text) {
+ String text = String::utf8(p_text);
+
+#ifdef PROXY_TO_PTHREAD_ENABLED
+ if (!Thread::is_main_thread()) {
+ callable_mp_static(DisplayServerWeb::_ime_callback).bind(p_type, text).call_deferred();
+ return;
+ }
+#endif
+
+ _ime_callback(p_type, text);
+}
+
+void DisplayServerWeb::_ime_callback(int p_type, const String &p_text) {
+ DisplayServerWeb *ds = get_singleton();
+ // Resume audio context after input in case autoplay was denied.
+ OS_Web::get_singleton()->resume_audio();
+
+ switch (p_type) {
+ case 0: {
+ // IME start.
+ ds->ime_text = String();
+ ds->ime_selection = Vector2i();
+ for (int i = ds->key_event_pos - 1; i >= 0; i--) {
+ // Delete last raw keydown event from query.
+ if (ds->key_event_buffer[i].pressed && ds->key_event_buffer[i].raw) {
+ ds->key_event_buffer.remove_at(i);
+ ds->key_event_pos--;
+ break;
+ }
+ }
+ OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE);
+ ds->ime_started = true;
+ } break;
+ case 1: {
+ // IME update.
+ if (ds->ime_active && ds->ime_started) {
+ ds->ime_text = p_text;
+ ds->ime_selection = Vector2i(ds->ime_text.length(), ds->ime_text.length());
+ OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE);
+ }
+ } break;
+ case 2: {
+ // IME commit.
+ if (ds->ime_active && ds->ime_started) {
+ ds->ime_started = false;
+
+ ds->ime_text = String();
+ ds->ime_selection = Vector2i();
+ OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE);
+
+ String text = p_text;
+ for (int i = 0; i < text.length(); i++) {
+ DisplayServerWeb::KeyEvent ke;
+
+ ke.pressed = true;
+ ke.echo = false;
+ ke.raw = false;
+ ke.keycode = Key::NONE;
+ ke.physical_keycode = Key::NONE;
+ ke.key_label = Key::NONE;
+ ke.unicode = text[i];
+ ke.mod = 0;
+
+ if (ds->key_event_pos >= ds->key_event_buffer.size()) {
+ ds->key_event_buffer.resize(1 + ds->key_event_pos);
+ }
+ ds->key_event_buffer.write[ds->key_event_pos++] = ke;
+ }
+ }
+ } break;
+ default:
+ break;
+ }
+}
+
+void DisplayServerWeb::window_set_ime_active(const bool p_active, WindowID p_window) {
+ ime_active = p_active;
+ godot_js_set_ime_active(p_active);
+}
+
+void DisplayServerWeb::window_set_ime_position(const Point2i &p_pos, WindowID p_window) {
+ godot_js_set_ime_position(p_pos.x, p_pos.y);
+}
+
+Point2i DisplayServerWeb::ime_get_selection() const {
+ return ime_selection;
+}
+
+String DisplayServerWeb::ime_get_text() const {
+ return ime_text;
+}
+
void DisplayServerWeb::process_joypads() {
Input *input = Input::get_singleton();
int32_t pads = godot_js_input_gamepad_sample_count();
@@ -893,6 +996,9 @@ void DisplayServerWeb::_send_window_event_callback(int p_notification) {
if (p_notification == DisplayServer::WINDOW_EVENT_MOUSE_ENTER || p_notification == DisplayServer::WINDOW_EVENT_MOUSE_EXIT) {
ds->cursor_inside_canvas = p_notification == DisplayServer::WINDOW_EVENT_MOUSE_ENTER;
}
+ if (godot_js_is_ime_focused() && (p_notification == DisplayServer::WINDOW_EVENT_FOCUS_IN || p_notification == DisplayServer::WINDOW_EVENT_FOCUS_OUT)) {
+ return;
+ }
if (!ds->window_event_callback.is_null()) {
Variant event = int(p_notification);
ds->window_event_callback.call(event);
@@ -1003,6 +1109,7 @@ DisplayServerWeb::DisplayServerWeb(const String &p_rendering_driver, WindowMode
godot_js_input_paste_cb(&DisplayServerWeb::update_clipboard_callback);
godot_js_input_drop_files_cb(&DisplayServerWeb::drop_files_js_callback);
godot_js_input_gamepad_cb(&DisplayServerWeb::gamepad_callback);
+ godot_js_set_ime_cb(&DisplayServerWeb::ime_callback, &DisplayServerWeb::key_callback, key_event.code, key_event.key);
// JS Display interface (js/libs/library_godot_display.js)
godot_js_display_fullscreen_cb(&DisplayServerWeb::fullscreen_change_callback);
@@ -1030,7 +1137,6 @@ bool DisplayServerWeb::has_feature(Feature p_feature) const {
switch (p_feature) {
//case FEATURE_GLOBAL_MENU:
//case FEATURE_HIDPI:
- //case FEATURE_IME:
case FEATURE_ICON:
case FEATURE_CLIPBOARD:
case FEATURE_CURSOR_SHAPE:
@@ -1044,6 +1150,9 @@ bool DisplayServerWeb::has_feature(Feature p_feature) const {
//case FEATURE_WINDOW_TRANSPARENCY:
//case FEATURE_KEEP_SCREEN_ON:
//case FEATURE_ORIENTATION:
+ case FEATURE_IME:
+ // IME does not work with experimental VK support.
+ return godot_js_display_vk_available() == 0;
case FEATURE_VIRTUAL_KEYBOARD:
return godot_js_display_vk_available() != 0;
case FEATURE_TEXT_TO_SPEECH:
@@ -1263,6 +1372,24 @@ void DisplayServerWeb::process_events() {
Input::get_singleton()->flush_buffered_events();
if (godot_js_input_gamepad_sample() == OK) {
process_joypads();
+ for (int i = 0; i < key_event_pos; i++) {
+ const DisplayServerWeb::KeyEvent &ke = key_event_buffer[i];
+
+ Ref<InputEventKey> ev;
+ ev.instantiate();
+ ev->set_pressed(ke.pressed);
+ ev->set_echo(ke.echo);
+ ev->set_keycode(ke.keycode);
+ ev->set_physical_keycode(ke.physical_keycode);
+ ev->set_key_label(ke.key_label);
+ ev->set_unicode(ke.unicode);
+ if (ke.raw) {
+ dom2godot_mod(ev, ke.mod, ke.keycode);
+ }
+
+ Input::get_singleton()->parse_input_event(ev);
+ }
+ key_event_pos = 0;
}
}
diff --git a/platform/web/display_server_web.h b/platform/web/display_server_web.h
index 51c6ab3c0a..140aef952b 100644
--- a/platform/web/display_server_web.h
+++ b/platform/web/display_server_web.h
@@ -82,6 +82,25 @@ private:
uint64_t last_click_ms = 0;
MouseButton last_click_button_index = MouseButton::NONE;
+ bool ime_active = false;
+ bool ime_started = false;
+ String ime_text;
+ Vector2i ime_selection;
+
+ struct KeyEvent {
+ bool pressed = false;
+ bool echo = false;
+ bool raw = false;
+ Key keycode = Key::NONE;
+ Key physical_keycode = Key::NONE;
+ Key key_label = Key::NONE;
+ uint32_t unicode = 0;
+ int mod = 0;
+ };
+
+ Vector<KeyEvent> key_event_buffer;
+ int key_event_pos = 0;
+
bool swap_cancel_ok = false;
bool tts = false;
@@ -108,6 +127,8 @@ private:
static void _gamepad_callback(int p_index, int p_connected, const String &p_id, const String &p_guid);
WASM_EXPORT static void js_utterance_callback(int p_event, int p_id, int p_pos);
static void _js_utterance_callback(int p_event, int p_id, int p_pos);
+ WASM_EXPORT static void ime_callback(int p_type, const char *p_text);
+ static void _ime_callback(int p_type, const String &p_text);
WASM_EXPORT static void request_quit_callback();
static void _request_quit_callback();
WASM_EXPORT static void window_blur_callback();
@@ -162,6 +183,13 @@ public:
virtual MouseMode mouse_get_mode() const override;
virtual Point2i mouse_get_position() const override;
+ // ime
+ virtual void window_set_ime_active(const bool p_active, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual void window_set_ime_position(const Point2i &p_pos, WindowID p_window = MAIN_WINDOW_ID) override;
+
+ virtual Point2i ime_get_selection() const override;
+ virtual String ime_get_text() const override;
+
// touch
virtual bool is_touchscreen_available() const override;
diff --git a/platform/web/godot_js.h b/platform/web/godot_js.h
index 031e67e486..a3d2632f17 100644
--- a/platform/web/godot_js.h
+++ b/platform/web/godot_js.h
@@ -64,6 +64,11 @@ extern void godot_js_input_touch_cb(void (*p_callback)(int p_type, int p_count),
extern void godot_js_input_key_cb(void (*p_callback)(int p_type, int p_repeat, int p_modifiers), char r_code[32], char r_key[32]);
extern void godot_js_input_vibrate_handheld(int p_duration_ms);
+extern void godot_js_set_ime_active(int p_active);
+extern void godot_js_set_ime_position(int p_x, int p_y);
+extern void godot_js_set_ime_cb(void (*p_input)(int p_type, const char *p_text), void (*p_callback)(int p_type, int p_repeat, int p_modifiers), char r_code[32], char r_key[32]);
+extern int godot_js_is_ime_focused();
+
// Input gamepad
extern void godot_js_input_gamepad_cb(void (*p_on_change)(int p_index, int p_connected, const char *p_id, const char *p_guid));
extern int godot_js_input_gamepad_sample();
diff --git a/platform/web/js/libs/library_godot_input.js b/platform/web/js/libs/library_godot_input.js
index eaff40f89c..1292c468f5 100644
--- a/platform/web/js/libs/library_godot_input.js
+++ b/platform/web/js/libs/library_godot_input.js
@@ -29,6 +29,110 @@
/**************************************************************************/
/*
+ * IME API helper.
+ */
+
+const GodotIME = {
+ $GodotIME__deps: ['$GodotRuntime', '$GodotEventListeners'],
+ $GodotIME__postset: 'GodotOS.atexit(function(resolve, reject) { GodotIME.clear(); resolve(); });',
+ $GodotIME: {
+ ime: null,
+ active: false,
+
+ getModifiers: function (evt) {
+ return (evt.shiftKey + 0) + ((evt.altKey + 0) << 1) + ((evt.ctrlKey + 0) << 2) + ((evt.metaKey + 0) << 3);
+ },
+
+ ime_active: function (active) {
+ function focus_timer() {
+ GodotIME.active = true;
+ GodotIME.ime.focus();
+ }
+
+ if (GodotIME.ime) {
+ if (active) {
+ GodotIME.ime.style.display = 'block';
+ setInterval(focus_timer, 100);
+ } else {
+ GodotIME.ime.style.display = 'none';
+ GodotConfig.canvas.focus();
+ GodotIME.active = false;
+ }
+ }
+ },
+
+ ime_position: function (x, y) {
+ if (GodotIME.ime) {
+ GodotIME.ime.style.left = `${x}px`;
+ GodotIME.ime.style.top = `${y}px`;
+ }
+ },
+
+ init: function (ime_cb, key_cb, code, key) {
+ function key_event_cb(pressed, evt) {
+ const modifiers = GodotIME.getModifiers(evt);
+ GodotRuntime.stringToHeap(evt.code, code, 32);
+ GodotRuntime.stringToHeap(evt.key, key, 32);
+ key_cb(pressed, evt.repeat, modifiers);
+ evt.preventDefault();
+ }
+ function ime_event_cb(event) {
+ if (GodotIME.ime) {
+ if (event.type === 'compositionstart') {
+ ime_cb(0, null);
+ GodotIME.ime.innerHTML = '';
+ } else if (event.type === 'compositionupdate') {
+ const ptr = GodotRuntime.allocString(event.data);
+ ime_cb(1, ptr);
+ GodotRuntime.free(ptr);
+ } else if (event.type === 'compositionend') {
+ const ptr = GodotRuntime.allocString(event.data);
+ ime_cb(2, ptr);
+ GodotRuntime.free(ptr);
+ GodotIME.ime.innerHTML = '';
+ }
+ }
+ }
+
+ const ime = document.createElement('div');
+ ime.className = 'ime';
+ ime.style.background = 'none';
+ ime.style.opacity = 0.0;
+ ime.style.position = 'fixed';
+ ime.style.left = '0px';
+ ime.style.top = '0px';
+ ime.style.width = '2px';
+ ime.style.height = '2px';
+ ime.style.display = 'none';
+ ime.contentEditable = 'true';
+
+ GodotEventListeners.add(ime, 'compositionstart', ime_event_cb, false);
+ GodotEventListeners.add(ime, 'compositionupdate', ime_event_cb, false);
+ GodotEventListeners.add(ime, 'compositionend', ime_event_cb, false);
+ GodotEventListeners.add(ime, 'keydown', key_event_cb.bind(null, 1), false);
+ GodotEventListeners.add(ime, 'keyup', key_event_cb.bind(null, 0), false);
+
+ ime.onblur = function () {
+ this.style.display = 'none';
+ GodotConfig.canvas.focus();
+ GodotIME.active = false;
+ };
+
+ GodotConfig.canvas.parentElement.appendChild(ime);
+ GodotIME.ime = ime;
+ },
+
+ clear: function () {
+ if (GodotIME.ime) {
+ GodotIME.ime.remove();
+ GodotIME.ime = null;
+ }
+ },
+ },
+};
+mergeInto(LibraryManager.library, GodotIME);
+
+/*
* Gamepad API helper.
*/
const GodotInputGamepads = {
@@ -338,7 +442,7 @@ mergeInto(LibraryManager.library, GodotInputDragDrop);
* Godot exposed input functions.
*/
const GodotInput = {
- $GodotInput__deps: ['$GodotRuntime', '$GodotConfig', '$GodotEventListeners', '$GodotInputGamepads', '$GodotInputDragDrop'],
+ $GodotInput__deps: ['$GodotRuntime', '$GodotConfig', '$GodotEventListeners', '$GodotInputGamepads', '$GodotInputDragDrop', '$GodotIME'],
$GodotInput: {
getModifiers: function (evt) {
return (evt.shiftKey + 0) + ((evt.altKey + 0) << 1) + ((evt.ctrlKey + 0) << 2) + ((evt.metaKey + 0) << 3);
@@ -462,6 +566,35 @@ const GodotInput = {
},
/*
+ * IME API
+ */
+ godot_js_set_ime_active__proxy: 'sync',
+ godot_js_set_ime_active__sig: 'vi',
+ godot_js_set_ime_active: function (p_active) {
+ GodotIME.ime_active(p_active);
+ },
+
+ godot_js_set_ime_position__proxy: 'sync',
+ godot_js_set_ime_position__sig: 'vii',
+ godot_js_set_ime_position: function (p_x, p_y) {
+ GodotIME.ime_position(p_x, p_y);
+ },
+
+ godot_js_set_ime_cb__proxy: 'sync',
+ godot_js_set_ime_cb__sig: 'viiii',
+ godot_js_set_ime_cb: function (p_ime_cb, p_key_cb, code, key) {
+ const ime_cb = GodotRuntime.get_func(p_ime_cb);
+ const key_cb = GodotRuntime.get_func(p_key_cb);
+ GodotIME.init(ime_cb, key_cb, code, key);
+ },
+
+ godot_js_is_ime_focused__proxy: 'sync',
+ godot_js_is_ime_focused__sig: 'i',
+ godot_js_is_ime_focused: function () {
+ return GodotIME.active;
+ },
+
+ /*
* Gamepad API
*/
godot_js_input_gamepad_cb__proxy: 'sync',
diff --git a/scene/2d/area_2d.cpp b/scene/2d/area_2d.cpp
index 3aa2a71a2c..2c4bf08f34 100644
--- a/scene/2d/area_2d.cpp
+++ b/scene/2d/area_2d.cpp
@@ -384,11 +384,9 @@ void Area2D::_clear_monitoring() {
}
}
-void Area2D::_notification(int p_what) {
- switch (p_what) {
- case NOTIFICATION_EXIT_TREE: {
- _clear_monitoring();
- } break;
+void Area2D::_space_changed(const RID &p_new_space) {
+ if (p_new_space.is_null()) {
+ _clear_monitoring();
}
}
diff --git a/scene/2d/area_2d.h b/scene/2d/area_2d.h
index 421c29f758..9d6e04b706 100644
--- a/scene/2d/area_2d.h
+++ b/scene/2d/area_2d.h
@@ -133,10 +133,11 @@ private:
StringName audio_bus;
protected:
- void _notification(int p_what);
static void _bind_methods();
void _validate_property(PropertyInfo &p_property) const;
+ virtual void _space_changed(const RID &p_new_space) override;
+
public:
void set_gravity_space_override_mode(SpaceOverride p_mode);
SpaceOverride get_gravity_space_override_mode() const;
diff --git a/scene/2d/collision_object_2d.cpp b/scene/2d/collision_object_2d.cpp
index ab42c52913..2fbe4eb409 100644
--- a/scene/2d/collision_object_2d.cpp
+++ b/scene/2d/collision_object_2d.cpp
@@ -59,6 +59,7 @@ void CollisionObject2D::_notification(int p_what) {
} else {
PhysicsServer2D::get_singleton()->body_set_space(rid, space);
}
+ _space_changed(space);
}
_update_pickable();
@@ -102,6 +103,7 @@ void CollisionObject2D::_notification(int p_what) {
} else {
PhysicsServer2D::get_singleton()->body_set_space(rid, RID());
}
+ _space_changed(RID());
}
}
@@ -125,6 +127,7 @@ void CollisionObject2D::_notification(int p_what) {
} else {
PhysicsServer2D::get_singleton()->body_set_space(rid, space);
}
+ _space_changed(space);
} break;
case NOTIFICATION_DISABLED: {
@@ -246,6 +249,7 @@ void CollisionObject2D::_apply_disabled() {
} else {
PhysicsServer2D::get_singleton()->body_set_space(rid, RID());
}
+ _space_changed(RID());
}
}
} break;
@@ -272,6 +276,7 @@ void CollisionObject2D::_apply_enabled() {
} else {
PhysicsServer2D::get_singleton()->body_set_space(rid, space);
}
+ _space_changed(space);
}
} break;
@@ -569,6 +574,9 @@ void CollisionObject2D::set_body_mode(PhysicsServer2D::BodyMode p_mode) {
PhysicsServer2D::get_singleton()->body_set_mode(rid, p_mode);
}
+void CollisionObject2D::_space_changed(const RID &p_new_space) {
+}
+
void CollisionObject2D::_update_pickable() {
if (!is_inside_tree()) {
return;
diff --git a/scene/2d/collision_object_2d.h b/scene/2d/collision_object_2d.h
index 88429b145d..780793f289 100644
--- a/scene/2d/collision_object_2d.h
+++ b/scene/2d/collision_object_2d.h
@@ -109,6 +109,8 @@ protected:
void set_body_mode(PhysicsServer2D::BodyMode p_mode);
+ virtual void _space_changed(const RID &p_new_space);
+
GDVIRTUAL3(_input_event, Viewport *, Ref<InputEvent>, int)
GDVIRTUAL0(_mouse_enter)
GDVIRTUAL0(_mouse_exit)
diff --git a/scene/2d/sprite_2d.cpp b/scene/2d/sprite_2d.cpp
index 7e6b43559c..6d0a2968d7 100644
--- a/scene/2d/sprite_2d.cpp
+++ b/scene/2d/sprite_2d.cpp
@@ -261,6 +261,9 @@ Vector2i Sprite2D::get_frame_coords() const {
void Sprite2D::set_vframes(int p_amount) {
ERR_FAIL_COND_MSG(p_amount < 1, "Amount of vframes cannot be smaller than 1.");
vframes = p_amount;
+ if (frame >= vframes * hframes) {
+ frame = 0;
+ }
queue_redraw();
item_rect_changed();
notify_property_list_changed();
@@ -272,7 +275,21 @@ int Sprite2D::get_vframes() const {
void Sprite2D::set_hframes(int p_amount) {
ERR_FAIL_COND_MSG(p_amount < 1, "Amount of hframes cannot be smaller than 1.");
+ if (vframes > 1) {
+ // Adjust the frame to fit new sheet dimensions.
+ int original_column = frame % hframes;
+ if (original_column >= p_amount) {
+ // Frame's column was dropped, reset.
+ frame = 0;
+ } else {
+ int original_row = frame / hframes;
+ frame = original_row * p_amount + original_column;
+ }
+ }
hframes = p_amount;
+ if (frame >= vframes * hframes) {
+ frame = 0;
+ }
queue_redraw();
item_rect_changed();
notify_property_list_changed();
diff --git a/scene/3d/area_3d.cpp b/scene/3d/area_3d.cpp
index beb6892435..585bce09fb 100644
--- a/scene/3d/area_3d.cpp
+++ b/scene/3d/area_3d.cpp
@@ -347,12 +347,14 @@ void Area3D::_clear_monitoring() {
}
}
+void Area3D::_space_changed(const RID &p_new_space) {
+ if (p_new_space.is_null()) {
+ _clear_monitoring();
+ }
+}
+
void Area3D::_notification(int p_what) {
switch (p_what) {
- case NOTIFICATION_EXIT_TREE: {
- _clear_monitoring();
- } break;
-
case NOTIFICATION_ENTER_TREE: {
_initialize_wind();
} break;
diff --git a/scene/3d/area_3d.h b/scene/3d/area_3d.h
index 86602d3192..05c558e8f0 100644
--- a/scene/3d/area_3d.h
+++ b/scene/3d/area_3d.h
@@ -149,6 +149,8 @@ protected:
static void _bind_methods();
void _validate_property(PropertyInfo &p_property) const;
+ virtual void _space_changed(const RID &p_new_space) override;
+
public:
void set_gravity_space_override_mode(SpaceOverride p_mode);
SpaceOverride get_gravity_space_override_mode() const;
diff --git a/scene/3d/collision_object_3d.cpp b/scene/3d/collision_object_3d.cpp
index bfe594adc2..4562ecfb5f 100644
--- a/scene/3d/collision_object_3d.cpp
+++ b/scene/3d/collision_object_3d.cpp
@@ -78,6 +78,7 @@ void CollisionObject3D::_notification(int p_what) {
} else {
PhysicsServer3D::get_singleton()->body_set_space(rid, space);
}
+ _space_changed(space);
}
_update_pickable();
@@ -117,6 +118,7 @@ void CollisionObject3D::_notification(int p_what) {
} else {
PhysicsServer3D::get_singleton()->body_set_space(rid, RID());
}
+ _space_changed(RID());
}
}
@@ -244,6 +246,7 @@ void CollisionObject3D::_apply_disabled() {
} else {
PhysicsServer3D::get_singleton()->body_set_space(rid, RID());
}
+ _space_changed(RID());
}
}
} break;
@@ -270,6 +273,7 @@ void CollisionObject3D::_apply_enabled() {
} else {
PhysicsServer3D::get_singleton()->body_set_space(rid, space);
}
+ _space_changed(space);
}
} break;
@@ -320,6 +324,9 @@ void CollisionObject3D::set_body_mode(PhysicsServer3D::BodyMode p_mode) {
PhysicsServer3D::get_singleton()->body_set_mode(rid, p_mode);
}
+void CollisionObject3D::_space_changed(const RID &p_new_space) {
+}
+
void CollisionObject3D::set_only_update_transform_changes(bool p_enable) {
only_update_transform_changes = p_enable;
}
diff --git a/scene/3d/collision_object_3d.h b/scene/3d/collision_object_3d.h
index ebcbb39e0d..b51423f021 100644
--- a/scene/3d/collision_object_3d.h
+++ b/scene/3d/collision_object_3d.h
@@ -116,6 +116,8 @@ protected:
void set_body_mode(PhysicsServer3D::BodyMode p_mode);
+ virtual void _space_changed(const RID &p_new_space);
+
void set_only_update_transform_changes(bool p_enable);
bool is_only_update_transform_changes_enabled() const;
diff --git a/scene/3d/sprite_3d.cpp b/scene/3d/sprite_3d.cpp
index 920cf22b83..ffd328c37e 100644
--- a/scene/3d/sprite_3d.cpp
+++ b/scene/3d/sprite_3d.cpp
@@ -804,8 +804,11 @@ Vector2i Sprite3D::get_frame_coords() const {
}
void Sprite3D::set_vframes(int p_amount) {
- ERR_FAIL_COND(p_amount < 1);
+ ERR_FAIL_COND_MSG(p_amount < 1, "Amount of vframes cannot be smaller than 1.");
vframes = p_amount;
+ if (frame >= vframes * hframes) {
+ frame = 0;
+ }
_queue_redraw();
notify_property_list_changed();
}
@@ -815,8 +818,22 @@ int Sprite3D::get_vframes() const {
}
void Sprite3D::set_hframes(int p_amount) {
- ERR_FAIL_COND(p_amount < 1);
+ ERR_FAIL_COND_MSG(p_amount < 1, "Amount of hframes cannot be smaller than 1.");
+ if (vframes > 1) {
+ // Adjust the frame to fit new sheet dimensions.
+ int original_column = frame % hframes;
+ if (original_column >= p_amount) {
+ // Frame's column was dropped, reset.
+ frame = 0;
+ } else {
+ int original_row = frame / hframes;
+ frame = original_row * p_amount + original_column;
+ }
+ }
hframes = p_amount;
+ if (frame >= vframes * hframes) {
+ frame = 0;
+ }
_queue_redraw();
notify_property_list_changed();
}
diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp
index e24816122c..da0687dd70 100644
--- a/scene/animation/animation_tree.cpp
+++ b/scene/animation/animation_tree.cpp
@@ -33,6 +33,7 @@
#include "animation_blend_tree.h"
#include "core/config/engine.h"
+#include "scene/animation/animation_player.h"
#include "scene/scene_string_names.h"
void AnimationNode::get_parameter_list(List<PropertyInfo> *r_list) const {
@@ -764,15 +765,16 @@ void AnimationTree::_setup_animation_player() {
return;
}
- AnimationMixer *mixer = Object::cast_to<AnimationMixer>(get_node_or_null(animation_player));
- if (mixer) {
- if (!mixer->is_connected(SNAME("caches_cleared"), callable_mp(this, &AnimationTree::_setup_animation_player))) {
- mixer->connect(SNAME("caches_cleared"), callable_mp(this, &AnimationTree::_setup_animation_player), CONNECT_DEFERRED);
+ // Using AnimationPlayer here is for compatibility. Changing to AnimationMixer needs extra work like error handling.
+ AnimationPlayer *player = Object::cast_to<AnimationPlayer>(get_node_or_null(animation_player));
+ if (player) {
+ if (!player->is_connected(SNAME("caches_cleared"), callable_mp(this, &AnimationTree::_setup_animation_player))) {
+ player->connect(SNAME("caches_cleared"), callable_mp(this, &AnimationTree::_setup_animation_player), CONNECT_DEFERRED);
}
- if (!mixer->is_connected(SNAME("animation_list_changed"), callable_mp(this, &AnimationTree::_setup_animation_player))) {
- mixer->connect(SNAME("animation_list_changed"), callable_mp(this, &AnimationTree::_setup_animation_player), CONNECT_DEFERRED);
+ if (!player->is_connected(SNAME("animation_list_changed"), callable_mp(this, &AnimationTree::_setup_animation_player))) {
+ player->connect(SNAME("animation_list_changed"), callable_mp(this, &AnimationTree::_setup_animation_player), CONNECT_DEFERRED);
}
- Node *root = mixer->get_node_or_null(mixer->get_root_node());
+ Node *root = player->get_node_or_null(player->get_root_node());
if (root) {
set_root_node(get_path_to(root, true));
}
@@ -780,9 +782,9 @@ void AnimationTree::_setup_animation_player() {
remove_animation_library(animation_libraries[0].name);
}
List<StringName> list;
- mixer->get_animation_library_list(&list);
+ player->get_animation_library_list(&list);
for (int i = 0; i < list.size(); i++) {
- Ref<AnimationLibrary> lib = mixer->get_animation_library(list[i]);
+ Ref<AnimationLibrary> lib = player->get_animation_library(list[i]);
if (lib.is_valid()) {
add_animation_library(list[i], lib);
}
@@ -799,6 +801,9 @@ void AnimationTree::_validate_property(PropertyInfo &p_property) const {
if (p_property.name == "root_node" || p_property.name.begins_with("libraries")) {
p_property.usage |= PROPERTY_USAGE_READ_ONLY;
}
+ if (p_property.name.begins_with("libraries")) {
+ p_property.usage &= ~PROPERTY_USAGE_STORAGE;
+ }
}
}
diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp
index 3dc920c4be..037cd32f10 100644
--- a/scene/resources/resource_format_text.cpp
+++ b/scene/resources/resource_format_text.cpp
@@ -1790,8 +1790,8 @@ Error ResourceFormatLoaderText::rename_dependencies(const String &p_path, const
err = loader.rename_dependencies(f, p_path, p_map);
}
- if (err == OK) {
- Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
+ Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
+ if (err == OK && da->file_exists(p_path + ".depren")) {
da->remove(p_path);
da->rename(p_path + ".depren", p_path);
}
diff --git a/servers/physics_2d/godot_area_2d.h b/servers/physics_2d/godot_area_2d.h
index 234e4eb9a9..d14ddb6a2e 100644
--- a/servers/physics_2d/godot_area_2d.h
+++ b/servers/physics_2d/godot_area_2d.h
@@ -167,7 +167,7 @@ void GodotArea2D::add_body_to_query(GodotBody2D *p_body, uint32_t p_body_shape,
void GodotArea2D::remove_body_from_query(GodotBody2D *p_body, uint32_t p_body_shape, uint32_t p_area_shape) {
BodyKey bk(p_body, p_body_shape, p_area_shape);
monitored_bodies[bk].dec();
- if (!monitor_query_list.in_list()) {
+ if (get_space() && !monitor_query_list.in_list()) {
_queue_monitor_update();
}
}
@@ -183,7 +183,7 @@ void GodotArea2D::add_area_to_query(GodotArea2D *p_area, uint32_t p_area_shape,
void GodotArea2D::remove_area_from_query(GodotArea2D *p_area, uint32_t p_area_shape, uint32_t p_self_shape) {
BodyKey bk(p_area, p_area_shape, p_self_shape);
monitored_areas[bk].dec();
- if (!monitor_query_list.in_list()) {
+ if (get_space() && !monitor_query_list.in_list()) {
_queue_monitor_update();
}
}
diff --git a/servers/physics_2d/godot_body_2d.cpp b/servers/physics_2d/godot_body_2d.cpp
index 12c3e1e5b4..01996dc43c 100644
--- a/servers/physics_2d/godot_body_2d.cpp
+++ b/servers/physics_2d/godot_body_2d.cpp
@@ -407,7 +407,8 @@ void GodotBody2D::set_space(GodotSpace2D *p_space) {
if (get_space()) {
_mass_properties_changed();
- if (active) {
+
+ if (active && !active_list.in_list()) {
get_space()->body_add_to_active_list(&active_list);
}
}
diff --git a/servers/physics_2d/godot_collision_object_2d.cpp b/servers/physics_2d/godot_collision_object_2d.cpp
index 4fb53a2d89..9851cac140 100644
--- a/servers/physics_2d/godot_collision_object_2d.cpp
+++ b/servers/physics_2d/godot_collision_object_2d.cpp
@@ -212,20 +212,21 @@ void GodotCollisionObject2D::_update_shapes_with_motion(const Vector2 &p_motion)
}
void GodotCollisionObject2D::_set_space(GodotSpace2D *p_space) {
- if (space) {
- space->remove_object(this);
+ GodotSpace2D *old_space = space;
+ space = p_space;
+
+ if (old_space) {
+ old_space->remove_object(this);
for (int i = 0; i < shapes.size(); i++) {
Shape &s = shapes.write[i];
if (s.bpid) {
- space->get_broadphase()->remove(s.bpid);
+ old_space->get_broadphase()->remove(s.bpid);
s.bpid = 0;
}
}
}
- space = p_space;
-
if (space) {
space->add_object(this);
_update_shapes();
diff --git a/servers/physics_3d/godot_area_3d.h b/servers/physics_3d/godot_area_3d.h
index f05d0f9019..c3c9e494a4 100644
--- a/servers/physics_3d/godot_area_3d.h
+++ b/servers/physics_3d/godot_area_3d.h
@@ -204,7 +204,7 @@ void GodotArea3D::add_body_to_query(GodotBody3D *p_body, uint32_t p_body_shape,
void GodotArea3D::remove_body_from_query(GodotBody3D *p_body, uint32_t p_body_shape, uint32_t p_area_shape) {
BodyKey bk(p_body, p_body_shape, p_area_shape);
monitored_bodies[bk].dec();
- if (!monitor_query_list.in_list()) {
+ if (get_space() && !monitor_query_list.in_list()) {
_queue_monitor_update();
}
}
@@ -220,7 +220,7 @@ void GodotArea3D::add_area_to_query(GodotArea3D *p_area, uint32_t p_area_shape,
void GodotArea3D::remove_area_from_query(GodotArea3D *p_area, uint32_t p_area_shape, uint32_t p_self_shape) {
BodyKey bk(p_area, p_area_shape, p_self_shape);
monitored_areas[bk].dec();
- if (!monitor_query_list.in_list()) {
+ if (get_space() && !monitor_query_list.in_list()) {
_queue_monitor_update();
}
}
diff --git a/servers/physics_3d/godot_body_3d.cpp b/servers/physics_3d/godot_body_3d.cpp
index e102d0f3c9..407957b904 100644
--- a/servers/physics_3d/godot_body_3d.cpp
+++ b/servers/physics_3d/godot_body_3d.cpp
@@ -454,7 +454,8 @@ void GodotBody3D::set_space(GodotSpace3D *p_space) {
if (get_space()) {
_mass_properties_changed();
- if (active) {
+
+ if (active && !active_list.in_list()) {
get_space()->body_add_to_active_list(&active_list);
}
}
diff --git a/servers/physics_3d/godot_collision_object_3d.cpp b/servers/physics_3d/godot_collision_object_3d.cpp
index 0d7fcb67f6..283614a43d 100644
--- a/servers/physics_3d/godot_collision_object_3d.cpp
+++ b/servers/physics_3d/godot_collision_object_3d.cpp
@@ -210,20 +210,21 @@ void GodotCollisionObject3D::_update_shapes_with_motion(const Vector3 &p_motion)
}
void GodotCollisionObject3D::_set_space(GodotSpace3D *p_space) {
- if (space) {
- space->remove_object(this);
+ GodotSpace3D *old_space = space;
+ space = p_space;
+
+ if (old_space) {
+ old_space->remove_object(this);
for (int i = 0; i < shapes.size(); i++) {
Shape &s = shapes.write[i];
if (s.bpid) {
- space->get_broadphase()->remove(s.bpid);
+ old_space->get_broadphase()->remove(s.bpid);
s.bpid = 0;
}
}
}
- space = p_space;
-
if (space) {
space->add_object(this);
_update_shapes();
diff --git a/servers/rendering/renderer_rd/effects/copy_effects.cpp b/servers/rendering/renderer_rd/effects/copy_effects.cpp
index c9f0008998..92bd3a043a 100644
--- a/servers/rendering/renderer_rd/effects/copy_effects.cpp
+++ b/servers/rendering/renderer_rd/effects/copy_effects.cpp
@@ -593,6 +593,40 @@ void CopyEffects::copy_to_fb_rect(RID p_source_rd_texture, RID p_dest_framebuffe
RD::get_singleton()->draw_list_end();
}
+void CopyEffects::copy_to_drawlist(RD::DrawListID p_draw_list, RD::FramebufferFormatID p_fb_format, RID p_source_rd_texture, bool p_linear) {
+ UniformSetCacheRD *uniform_set_cache = UniformSetCacheRD::get_singleton();
+ ERR_FAIL_NULL(uniform_set_cache);
+ MaterialStorage *material_storage = MaterialStorage::get_singleton();
+ ERR_FAIL_NULL(material_storage);
+
+ memset(&copy_to_fb.push_constant, 0, sizeof(CopyToFbPushConstant));
+ copy_to_fb.push_constant.luminance_multiplier = 1.0;
+
+ if (p_linear) {
+ // Used for copying to a linear buffer. In the mobile renderer we divide the contents of the linear buffer
+ // to allow for a wider effective range.
+ copy_to_fb.push_constant.flags |= COPY_TO_FB_FLAG_LINEAR;
+ copy_to_fb.push_constant.luminance_multiplier = prefer_raster_effects ? 2.0 : 1.0;
+ }
+
+ // setup our uniforms
+ RID default_sampler = material_storage->sampler_rd_get_default(RS::CANVAS_ITEM_TEXTURE_FILTER_LINEAR, RS::CANVAS_ITEM_TEXTURE_REPEAT_DISABLED);
+
+ RD::Uniform u_source_rd_texture(RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE, 0, Vector<RID>({ default_sampler, p_source_rd_texture }));
+
+ // Multiview not supported here!
+ CopyToFBMode mode = COPY_TO_FB_COPY;
+
+ RID shader = copy_to_fb.shader.version_get_shader(copy_to_fb.shader_version, mode);
+ ERR_FAIL_COND(shader.is_null());
+
+ RD::get_singleton()->draw_list_bind_render_pipeline(p_draw_list, copy_to_fb.pipelines[mode].get_render_pipeline(RD::INVALID_ID, p_fb_format));
+ RD::get_singleton()->draw_list_bind_uniform_set(p_draw_list, uniform_set_cache->get_cache(shader, 0, u_source_rd_texture), 0);
+ RD::get_singleton()->draw_list_bind_index_array(p_draw_list, material_storage->get_quad_index_array());
+ RD::get_singleton()->draw_list_set_push_constant(p_draw_list, &copy_to_fb.push_constant, sizeof(CopyToFbPushConstant));
+ RD::get_singleton()->draw_list_draw(p_draw_list, true);
+}
+
void CopyEffects::copy_raster(RID p_source_texture, RID p_dest_framebuffer) {
ERR_FAIL_COND_MSG(!prefer_raster_effects, "Can't use the raster version of the copy with the clustered renderer.");
diff --git a/servers/rendering/renderer_rd/effects/copy_effects.h b/servers/rendering/renderer_rd/effects/copy_effects.h
index 0fa4fe1518..f9dcc42421 100644
--- a/servers/rendering/renderer_rd/effects/copy_effects.h
+++ b/servers/rendering/renderer_rd/effects/copy_effects.h
@@ -330,6 +330,7 @@ public:
void copy_depth_to_rect_and_linearize(RID p_source_rd_texture, RID p_dest_texture, const Rect2i &p_rect, bool p_flip_y, float p_z_near, float p_z_far);
void copy_to_fb_rect(RID p_source_rd_texture, RID p_dest_framebuffer, const Rect2i &p_rect, bool p_flip_y = false, bool p_force_luminance = false, bool p_alpha_to_zero = false, bool p_srgb = false, RID p_secondary = RID(), bool p_multiview = false, bool alpha_to_one = false, bool p_linear = false);
void copy_to_atlas_fb(RID p_source_rd_texture, RID p_dest_framebuffer, const Rect2 &p_uv_rect, RD::DrawListID p_draw_list, bool p_flip_y = false, bool p_panorama = false);
+ void copy_to_drawlist(RD::DrawListID p_draw_list, RD::FramebufferFormatID p_fb_format, RID p_source_rd_texture, bool p_linear = false);
void copy_raster(RID p_source_texture, RID p_dest_framebuffer);
void gaussian_blur(RID p_source_rd_texture, RID p_texture, const Rect2i &p_region, const Size2i &p_size, bool p_8bit_dst = false);
diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
index e052cb6ebc..b7d7105daa 100644
--- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
+++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
@@ -192,72 +192,43 @@ RID RenderForwardMobile::RenderBufferDataForwardMobile::get_color_fbs(Framebuffe
// Now define our subpasses
Vector<RD::FramebufferPass> passes;
- // Define our base pass, we'll be re-using this
- RD::FramebufferPass pass;
- pass.color_attachments.push_back(0);
- pass.depth_attachment = 1;
- if (vrs_texture.is_valid()) {
- pass.vrs_attachment = 2;
- }
-
switch (p_config_type) {
- case FB_CONFIG_ONE_PASS: {
- // just one pass
- if (use_msaa) {
- // Add resolve
- pass.resolve_attachments.push_back(color_buffer_id);
+ case FB_CONFIG_RENDER_PASS: {
+ RD::FramebufferPass pass;
+ pass.color_attachments.push_back(0);
+ pass.depth_attachment = 1;
+ if (vrs_texture.is_valid()) {
+ pass.vrs_attachment = 2;
}
- passes.push_back(pass);
- return FramebufferCacheRD::get_singleton()->get_cache_multipass(textures, passes, view_count);
- } break;
- case FB_CONFIG_TWO_SUBPASSES: {
- // - opaque pass
- passes.push_back(pass);
-
- // - add sky pass
if (use_msaa) {
- // add resolve
+ // Add resolve
pass.resolve_attachments.push_back(color_buffer_id);
}
passes.push_back(pass);
return FramebufferCacheRD::get_singleton()->get_cache_multipass(textures, passes, view_count);
} break;
- case FB_CONFIG_THREE_SUBPASSES: {
- // - opaque pass
- passes.push_back(pass);
- // - add sky pass
- passes.push_back(pass);
-
- // - add alpha pass
- if (use_msaa) {
- // add resolve
- pass.resolve_attachments.push_back(color_buffer_id);
- }
- passes.push_back(pass);
-
- return FramebufferCacheRD::get_singleton()->get_cache_multipass(textures, passes, view_count);
- } break;
- case FB_CONFIG_FOUR_SUBPASSES: {
+ case FB_CONFIG_RENDER_AND_POST_PASS: {
Size2i target_size = render_buffers->get_target_size();
Size2i internal_size = render_buffers->get_internal_size();
// can't do our blit pass if resolutions don't match, this should already have been checked.
ERR_FAIL_COND_V(target_size != internal_size, RID());
- // - opaque pass
- passes.push_back(pass);
-
- // - add sky pass
- passes.push_back(pass);
+ RD::FramebufferPass pass;
+ pass.color_attachments.push_back(0);
+ pass.depth_attachment = 1;
+ if (vrs_texture.is_valid()) {
+ pass.vrs_attachment = 2;
+ }
- // - add alpha pass
if (use_msaa) {
// add resolve
pass.resolve_attachments.push_back(color_buffer_id);
}
+
passes.push_back(pass);
// - add blit to 2D pass
@@ -739,8 +710,8 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color
Size2i screen_size;
RID framebuffer;
bool reverse_cull = p_render_data->scene_data->cam_transform.basis.determinant() < 0;
- bool using_subpass_transparent = true;
- bool using_subpass_post_process = true;
+ bool merge_transparent_pass = true; // If true: we can do our transparent pass in the same pass as our opaque pass.
+ bool using_subpass_post_process = true; // If true: we can do our post processing in a subpass
RendererRD::MaterialStorage::Samplers samplers;
bool using_shadows = true;
@@ -786,7 +757,7 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color
}
reverse_cull = true;
- using_subpass_transparent = true; // we ignore our screen/depth texture here
+ merge_transparent_pass = true; // we ignore our screen/depth texture here
using_subpass_post_process = false; // not applicable at all for reflection probes.
samplers = RendererRD::MaterialStorage::get_singleton()->samplers_rd_get_default();
} else if (rb_data.is_valid()) {
@@ -803,19 +774,16 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color
if (scene_state.used_screen_texture || scene_state.used_depth_texture) {
// can't use our last two subpasses because we're reading from screen texture or depth texture
- using_subpass_transparent = false;
+ merge_transparent_pass = false;
using_subpass_post_process = false;
}
if (using_subpass_post_process) {
- // all as subpasses
- framebuffer = rb_data->get_color_fbs(RenderBufferDataForwardMobile::FB_CONFIG_FOUR_SUBPASSES);
- } else if (using_subpass_transparent) {
- // our tonemap pass is separate
- framebuffer = rb_data->get_color_fbs(RenderBufferDataForwardMobile::FB_CONFIG_THREE_SUBPASSES);
+ // We can do all in one go.
+ framebuffer = rb_data->get_color_fbs(RenderBufferDataForwardMobile::FB_CONFIG_RENDER_AND_POST_PASS);
} else {
- // only opaque and sky as subpasses
- framebuffer = rb_data->get_color_fbs(RenderBufferDataForwardMobile::FB_CONFIG_TWO_SUBPASSES);
+ // We separate things out.
+ framebuffer = rb_data->get_color_fbs(RenderBufferDataForwardMobile::FB_CONFIG_RENDER_PASS);
}
samplers = rb->get_samplers();
} else {
@@ -844,6 +812,7 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color
Color clear_color = p_default_bg_color;
bool keep_color = false;
+ bool copy_canvas = false;
if (get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_OVERDRAW) {
clear_color = Color(0, 0, 0, 1); //in overdraw mode, BG should always be black
@@ -882,12 +851,8 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color
} break;
case RS::ENV_BG_CANVAS: {
if (rb_data.is_valid()) {
- RID dest_framebuffer = rb_data->get_color_fbs(RenderBufferDataForwardMobile::FB_CONFIG_ONE_PASS);
- RID texture = RendererRD::TextureStorage::get_singleton()->render_target_get_rd_texture(rb->get_render_target());
- bool convert_to_linear = !RendererRD::TextureStorage::get_singleton()->render_target_is_using_hdr(rb->get_render_target());
- copy_effects->copy_to_fb_rect(texture, dest_framebuffer, Rect2i(), false, false, false, false, RID(), false, false, convert_to_linear);
+ copy_canvas = true;
}
- keep_color = true;
} break;
case RS::ENV_BG_KEEP: {
keep_color = true;
@@ -955,6 +920,7 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color
spec_constant_base_flags |= 1 << SPEC_CONSTANT_DISABLE_FOG;
}
}
+
{
if (rb_data.is_valid()) {
RD::get_singleton()->draw_command_begin_label("Render 3D Pass");
@@ -963,8 +929,7 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color
}
// opaque pass
-
- RD::get_singleton()->draw_command_begin_label("Render Opaque Subpass");
+ RD::get_singleton()->draw_command_begin_label("Render Opaque");
p_render_data->scene_data->directional_light_count = p_render_data->directional_light_count;
@@ -973,9 +938,9 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color
_setup_environment(p_render_data, is_reflection_probe, screen_size, !is_reflection_probe, p_default_bg_color, p_render_data->render_buffers.is_valid());
- if (using_subpass_transparent && using_subpass_post_process) {
+ if (merge_transparent_pass && using_subpass_post_process) {
RENDER_TIMESTAMP("Render Opaque + Transparent + Tonemap");
- } else if (using_subpass_transparent) {
+ } else if (merge_transparent_pass) {
RENDER_TIMESTAMP("Render Opaque + Transparent");
} else {
RENDER_TIMESTAMP("Render Opaque");
@@ -983,110 +948,74 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color
RID rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_OPAQUE, p_render_data, radiance_texture, samplers, true);
- bool can_continue_color = !using_subpass_transparent && !scene_state.used_screen_texture;
- bool can_continue_depth = !using_subpass_transparent && !scene_state.used_depth_texture;
-
+ // Set clear colors.
+ Vector<Color> c;
{
- // regular forward for now
- Vector<Color> c;
- {
- Color cc = clear_color.srgb_to_linear() * inverse_luminance_multiplier;
- if (rb_data.is_valid()) {
- cc.a = 0; // For transparent viewport backgrounds.
+ Color cc = clear_color.srgb_to_linear() * inverse_luminance_multiplier;
+ if (rb_data.is_valid()) {
+ cc.a = 0; // For transparent viewport backgrounds.
+ }
+ c.push_back(cc); // Our render buffer.
+ if (rb_data.is_valid()) {
+ if (p_render_data->render_buffers->get_msaa_3d() != RS::VIEWPORT_MSAA_DISABLED) {
+ c.push_back(clear_color.srgb_to_linear() * inverse_luminance_multiplier); // Our resolve buffer.
}
- c.push_back(cc); // Our render buffer.
- if (rb_data.is_valid()) {
- if (p_render_data->render_buffers->get_msaa_3d() != RS::VIEWPORT_MSAA_DISABLED) {
- c.push_back(clear_color.srgb_to_linear() * inverse_luminance_multiplier); // Our resolve buffer.
- }
- if (using_subpass_post_process) {
- c.push_back(Color()); // Our 2D buffer we're copying into.
- }
+ if (using_subpass_post_process) {
+ c.push_back(Color()); // Our 2D buffer we're copying into.
}
}
+ }
- RD::FramebufferFormatID fb_format = RD::get_singleton()->framebuffer_get_format(framebuffer);
- RenderListParameters render_list_params(render_list[RENDER_LIST_OPAQUE].elements.ptr(), render_list[RENDER_LIST_OPAQUE].element_info.ptr(), render_list[RENDER_LIST_OPAQUE].elements.size(), reverse_cull, PASS_MODE_COLOR, rp_uniform_set, spec_constant_base_flags, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, p_render_data->scene_data->view_count);
- render_list_params.framebuffer_format = fb_format;
- if ((uint32_t)render_list_params.element_count > render_list_thread_threshold && false) {
- // secondary command buffers need more testing at this time
- //multi threaded
- thread_draw_lists.resize(WorkerThreadPool::get_singleton()->get_thread_count());
- RD::get_singleton()->draw_list_begin_split(framebuffer, thread_draw_lists.size(), thread_draw_lists.ptr(), keep_color ? RD::INITIAL_ACTION_KEEP : RD::INITIAL_ACTION_CLEAR, can_continue_color ? RD::FINAL_ACTION_CONTINUE : RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_CLEAR, can_continue_depth ? RD::FINAL_ACTION_CONTINUE : RD::FINAL_ACTION_READ, c, 1.0, 0);
-
- WorkerThreadPool::GroupID group_task = WorkerThreadPool::get_singleton()->add_template_group_task(this, &RenderForwardMobile::_render_list_thread_function, &render_list_params, thread_draw_lists.size(), -1, true, SNAME("ForwardMobileRenderList"));
- WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_task);
+ RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(framebuffer, keep_color ? RD::INITIAL_ACTION_KEEP : RD::INITIAL_ACTION_CLEAR, merge_transparent_pass ? RD::FINAL_ACTION_READ : RD::FINAL_ACTION_CONTINUE, RD::INITIAL_ACTION_CLEAR, merge_transparent_pass ? RD::FINAL_ACTION_READ : RD::FINAL_ACTION_CONTINUE, c, 1.0, 0);
+ RD::FramebufferFormatID fb_format = RD::get_singleton()->framebuffer_get_format(framebuffer);
+ if (copy_canvas) {
+ if (p_render_data->scene_data->view_count > 1) {
+ WARN_PRINT_ONCE("Canvas background is not supported in multiview!");
} else {
- //single threaded
- RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(framebuffer, keep_color ? RD::INITIAL_ACTION_KEEP : RD::INITIAL_ACTION_CLEAR, can_continue_color ? RD::FINAL_ACTION_CONTINUE : RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_CLEAR, can_continue_depth ? RD::FINAL_ACTION_CONTINUE : RD::FINAL_ACTION_READ, c, 1.0, 0);
- _render_list(draw_list, fb_format, &render_list_params, 0, render_list_params.element_count);
+ RID texture = RendererRD::TextureStorage::get_singleton()->render_target_get_rd_texture(rb->get_render_target());
+ bool convert_to_linear = !RendererRD::TextureStorage::get_singleton()->render_target_is_using_hdr(rb->get_render_target());
+
+ copy_effects->copy_to_drawlist(draw_list, fb_format, texture, convert_to_linear);
}
}
- RD::get_singleton()->draw_command_end_label(); //Render Opaque Subpass
+ if (render_list[RENDER_LIST_OPAQUE].elements.size() > 0) {
+ RenderListParameters render_list_params(render_list[RENDER_LIST_OPAQUE].elements.ptr(), render_list[RENDER_LIST_OPAQUE].element_info.ptr(), render_list[RENDER_LIST_OPAQUE].elements.size(), reverse_cull, PASS_MODE_COLOR, rp_uniform_set, spec_constant_base_flags, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, p_render_data->scene_data->view_count);
+ render_list_params.framebuffer_format = fb_format;
+ render_list_params.subpass = RD::get_singleton()->draw_list_get_current_pass(); // Should now always be 0.
+
+ _render_list(draw_list, fb_format, &render_list_params, 0, render_list_params.element_count);
+ }
+
+ RD::get_singleton()->draw_command_end_label(); //Render Opaque
if (draw_sky || draw_sky_fog_only) {
- RD::get_singleton()->draw_command_begin_label("Draw Sky Subpass");
+ RD::get_singleton()->draw_command_begin_label("Draw Sky");
// Note, sky.setup should have been called up above and setup stuff we need.
- RD::DrawListID draw_list = RD::get_singleton()->draw_list_switch_to_next_pass();
-
sky.draw_sky(draw_list, rb, p_render_data->environment, framebuffer, time, sky_energy_multiplier);
- RD::get_singleton()->draw_command_end_label(); // Draw Sky Subpass
-
- // note, if MSAA is used in 2-subpass approach we should get an automatic resolve here
- } else {
- // switch to subpass but we do nothing here so basically we skip (though this should trigger resolve with 2-subpass MSAA).
- RD::get_singleton()->draw_list_switch_to_next_pass();
- }
-
- if (!using_subpass_transparent) {
- // We're done with our subpasses so end our container pass
- RD::get_singleton()->draw_list_end(RD::BARRIER_MASK_ALL_BARRIERS);
-
- RD::get_singleton()->draw_command_end_label(); // Render 3D Pass / Render Reflection Probe Pass
- }
-
- if (scene_state.used_screen_texture) {
- // Copy screen texture to backbuffer so we can read from it
- _render_buffers_copy_screen_texture(p_render_data);
- }
-
- if (scene_state.used_depth_texture) {
- // Copy depth texture to backbuffer so we can read from it
- _render_buffers_copy_depth_texture(p_render_data);
+ RD::get_singleton()->draw_command_end_label(); // Draw Sky
}
- // transparent pass
+ if (merge_transparent_pass) {
+ if (render_list[RENDER_LIST_ALPHA].element_info.size() > 0) {
+ // transparent pass
- RD::get_singleton()->draw_command_begin_label("Render Transparent Subpass");
+ RD::get_singleton()->draw_command_begin_label("Render Transparent");
- rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_ALPHA, p_render_data, radiance_texture, samplers, true);
+ rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_ALPHA, p_render_data, radiance_texture, samplers, true);
- if (using_subpass_transparent) {
- RD::FramebufferFormatID fb_format = RD::get_singleton()->framebuffer_get_format(framebuffer);
- RenderListParameters render_list_params(render_list[RENDER_LIST_ALPHA].elements.ptr(), render_list[RENDER_LIST_ALPHA].element_info.ptr(), render_list[RENDER_LIST_ALPHA].elements.size(), reverse_cull, PASS_MODE_COLOR_TRANSPARENT, rp_uniform_set, spec_constant_base_flags, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, p_render_data->scene_data->view_count);
- render_list_params.framebuffer_format = fb_format;
- if ((uint32_t)render_list_params.element_count > render_list_thread_threshold && false) {
- // secondary command buffers need more testing at this time
- //multi threaded
- thread_draw_lists.resize(WorkerThreadPool::get_singleton()->get_thread_count());
- RD::get_singleton()->draw_list_switch_to_next_pass_split(thread_draw_lists.size(), thread_draw_lists.ptr());
- render_list_params.subpass = RD::get_singleton()->draw_list_get_current_pass();
- WorkerThreadPool::GroupID group_task = WorkerThreadPool::get_singleton()->add_template_group_task(this, &RenderForwardMobile::_render_list_thread_function, &render_list_params, thread_draw_lists.size(), -1, true, SNAME("ForwardMobileRenderSubpass"));
- WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_task);
+ RenderListParameters render_list_params(render_list[RENDER_LIST_ALPHA].elements.ptr(), render_list[RENDER_LIST_ALPHA].element_info.ptr(), render_list[RENDER_LIST_ALPHA].elements.size(), reverse_cull, PASS_MODE_COLOR_TRANSPARENT, rp_uniform_set, spec_constant_base_flags, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, p_render_data->scene_data->view_count);
+ render_list_params.framebuffer_format = fb_format;
+ render_list_params.subpass = RD::get_singleton()->draw_list_get_current_pass(); // Should now always be 0.
- } else {
- //single threaded
- RD::DrawListID draw_list = RD::get_singleton()->draw_list_switch_to_next_pass();
- render_list_params.subpass = RD::get_singleton()->draw_list_get_current_pass();
_render_list(draw_list, fb_format, &render_list_params, 0, render_list_params.element_count);
- }
- RD::get_singleton()->draw_command_end_label(); // Render Transparent Subpass
+ RD::get_singleton()->draw_command_end_label(); // Render Transparent Subpass
+ }
// note if we are using MSAA we should get an automatic resolve through our subpass configuration.
@@ -1099,35 +1028,46 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color
RD::get_singleton()->draw_list_end(RD::BARRIER_MASK_ALL_BARRIERS);
} else {
- RENDER_TIMESTAMP("Render Transparent");
+ // We're done with our subpasses so end our container pass
+ // note, if MSAA is used we should get an automatic resolve here
- if (rb_data.is_valid()) {
- framebuffer = rb_data->get_color_fbs(RenderBufferDataForwardMobile::FB_CONFIG_ONE_PASS);
+ RD::get_singleton()->draw_list_end(RD::BARRIER_MASK_ALL_BARRIERS);
+
+ RD::get_singleton()->draw_command_end_label(); // Render 3D Pass / Render Reflection Probe Pass
+
+ if (scene_state.used_screen_texture) {
+ // Copy screen texture to backbuffer so we can read from it
+ _render_buffers_copy_screen_texture(p_render_data);
+ }
+
+ if (scene_state.used_depth_texture) {
+ // Copy depth texture to backbuffer so we can read from it
+ _render_buffers_copy_depth_texture(p_render_data);
}
- // this may be needed if we re-introduced steps that change info, not sure which do so in the previous implementation
- // _setup_environment(p_render_data, is_reflection_probe, screen_size, !is_reflection_probe, p_default_bg_color, false);
+ if (render_list[RENDER_LIST_ALPHA].element_info.size() > 0) {
+ RD::get_singleton()->draw_command_begin_label("Render Transparent Pass");
+ RENDER_TIMESTAMP("Render Transparent");
- RD::FramebufferFormatID fb_format = RD::get_singleton()->framebuffer_get_format(framebuffer);
- RenderListParameters render_list_params(render_list[RENDER_LIST_ALPHA].elements.ptr(), render_list[RENDER_LIST_ALPHA].element_info.ptr(), render_list[RENDER_LIST_ALPHA].elements.size(), reverse_cull, PASS_MODE_COLOR, rp_uniform_set, spec_constant_base_flags, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, p_render_data->scene_data->view_count);
- render_list_params.framebuffer_format = fb_format;
- if ((uint32_t)render_list_params.element_count > render_list_thread_threshold && false) {
- // secondary command buffers need more testing at this time
- //multi threaded
- thread_draw_lists.resize(WorkerThreadPool::get_singleton()->get_thread_count());
- RD::get_singleton()->draw_list_begin_split(framebuffer, thread_draw_lists.size(), thread_draw_lists.ptr(), can_continue_color ? RD::INITIAL_ACTION_CONTINUE : RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_READ, can_continue_depth ? RD::INITIAL_ACTION_CONTINUE : RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_READ);
- WorkerThreadPool::GroupID group_task = WorkerThreadPool::get_singleton()->add_template_group_task(this, &RenderForwardMobile::_render_list_thread_function, &render_list_params, thread_draw_lists.size(), -1, true, SNAME("ForwardMobileRenderSubpass"));
- WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_task);
+ rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_ALPHA, p_render_data, radiance_texture, samplers, true);
- RD::get_singleton()->draw_list_end(RD::BARRIER_MASK_ALL_BARRIERS);
- } else {
- //single threaded
- RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(framebuffer, can_continue_color ? RD::INITIAL_ACTION_CONTINUE : RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_READ, can_continue_depth ? RD::INITIAL_ACTION_CONTINUE : RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_READ);
+ if (rb_data.is_valid()) {
+ framebuffer = rb_data->get_color_fbs(RenderBufferDataForwardMobile::FB_CONFIG_RENDER_PASS);
+ }
+
+ // this may be needed if we re-introduced steps that change info, not sure which do so in the previous implementation
+ //_setup_environment(p_render_data, is_reflection_probe, screen_size, !is_reflection_probe, p_default_bg_color, false);
+
+ RenderListParameters render_list_params(render_list[RENDER_LIST_ALPHA].elements.ptr(), render_list[RENDER_LIST_ALPHA].element_info.ptr(), render_list[RENDER_LIST_ALPHA].elements.size(), reverse_cull, PASS_MODE_COLOR, rp_uniform_set, spec_constant_base_flags, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, p_render_data->scene_data->view_count);
+ render_list_params.framebuffer_format = fb_format;
+ render_list_params.subpass = RD::get_singleton()->draw_list_get_current_pass(); // Should now always be 0.
+
+ draw_list = RD::get_singleton()->draw_list_begin(framebuffer, RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_READ);
_render_list(draw_list, fb_format, &render_list_params, 0, render_list_params.element_count);
RD::get_singleton()->draw_list_end(RD::BARRIER_MASK_ALL_BARRIERS);
- }
- RD::get_singleton()->draw_command_end_label(); // Render Transparent Subpass
+ RD::get_singleton()->draw_command_end_label(); // Render Transparent Pass
+ }
}
}
diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h
index c393e87bd0..3496495245 100644
--- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h
+++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h
@@ -106,15 +106,9 @@ private:
GDCLASS(RenderBufferDataForwardMobile, RenderBufferCustomDataRD);
public:
- // We can have:
- // - 4 subpasses combining the full render cycle
- // - 3 subpasses + 1 normal pass for tonemapping/glow/dof/etc (using fb for 2D buffer)
- // - 2 subpasses + 1 normal pass for transparent + 1 normal pass for tonemapping/glow/dof/etc (using fb for 2D buffer)
enum FramebufferConfigType {
- FB_CONFIG_ONE_PASS, // Single pass frame buffer for alpha pass
- FB_CONFIG_TWO_SUBPASSES, // Opaque + Sky sub pass
- FB_CONFIG_THREE_SUBPASSES, // Opaque + Sky + Alpha sub pass
- FB_CONFIG_FOUR_SUBPASSES, // Opaque + Sky + Alpha sub pass + Tonemap pass
+ FB_CONFIG_RENDER_PASS, // Single pass framebuffer for normal rendering.
+ FB_CONFIG_RENDER_AND_POST_PASS, // Two subpasses, one for normal rendering, one for post processing.
FB_CONFIG_MAX
};
diff --git a/thirdparty/etcpak/ProcessRgtc.cpp b/thirdparty/etcpak/ProcessRgtc.cpp
new file mode 100644
index 0000000000..3a283b743b
--- /dev/null
+++ b/thirdparty/etcpak/ProcessRgtc.cpp
@@ -0,0 +1,106 @@
+// -- GODOT start --
+
+#include "ForceInline.hpp"
+#include "ProcessRgtc.hpp"
+
+#include <assert.h>
+#include <string.h>
+
+static const uint8_t AlphaIndexTable[8] = { 1, 7, 6, 5, 4, 3, 2, 0 };
+
+static etcpak_force_inline uint64_t ProcessAlpha( const uint8_t* src )
+{
+ uint8_t solid8 = *src;
+ uint16_t solid16 = uint16_t( solid8 ) | ( uint16_t( solid8 ) << 8 );
+ uint32_t solid32 = uint32_t( solid16 ) | ( uint32_t( solid16 ) << 16 );
+ uint64_t solid64 = uint64_t( solid32 ) | ( uint64_t( solid32 ) << 32 );
+ if( memcmp( src, &solid64, 8 ) == 0 && memcmp( src+8, &solid64, 8 ) == 0 )
+ {
+ return solid8;
+ }
+
+ uint8_t min = src[0];
+ uint8_t max = min;
+ for( int i=1; i<16; i++ )
+ {
+ const auto v = src[i];
+ if( v > max ) max = v;
+ else if( v < min ) min = v;
+ }
+
+ uint32_t range = ( 8 << 13 ) / ( 1 + max - min );
+ uint64_t data = 0;
+ for( int i=0; i<16; i++ )
+ {
+ uint8_t a = src[i] - min;
+ uint64_t idx = AlphaIndexTable[( a * range ) >> 13];
+ data |= idx << (i*3);
+ }
+
+ return max | ( min << 8 ) | ( data << 16 );
+}
+
+void CompressRgtcR(const uint32_t *src, uint64_t *dst, uint32_t blocks, size_t width)
+{
+ int i = 0;
+ auto ptr = dst;
+ do
+ {
+ uint32_t rgba[4 * 4];
+ uint8_t r[4 * 4];
+
+ auto tmp = (char *)rgba;
+ memcpy(tmp, src + width * 0, 4 * 4);
+ memcpy(tmp + 4 * 4, src + width * 1, 4 * 4);
+ memcpy(tmp + 8 * 4, src + width * 2, 4 * 4);
+ memcpy(tmp + 12 * 4, src + width * 3, 4 * 4);
+ src += 4;
+ if (++i == width / 4)
+ {
+ src += width * 3;
+ i = 0;
+ }
+
+ for (int i = 0; i < 16; i++)
+ {
+ r[i] = rgba[i] & 0x000000FF;
+ }
+ *ptr++ = ProcessAlpha(r);
+ }
+ while (--blocks);
+}
+
+void CompressRgtcRG(const uint32_t *src, uint64_t *dst, uint32_t blocks, size_t width)
+{
+ int i = 0;
+ auto ptr = dst;
+ do
+ {
+ uint32_t rgba[4 * 4];
+ uint8_t r[4 * 4];
+ uint8_t g[4 * 4];
+
+ auto tmp = (char *)rgba;
+ memcpy(tmp, src + width * 0, 4 * 4);
+ memcpy(tmp + 4 * 4, src + width * 1, 4 * 4);
+ memcpy(tmp + 8 * 4, src + width * 2, 4 * 4);
+ memcpy(tmp + 12 * 4, src + width * 3, 4 * 4);
+ src += 4;
+ if (++i == width / 4)
+ {
+ src += width * 3;
+ i = 0;
+ }
+
+ for (int i = 0; i < 16; i++)
+ {
+ r[i] = rgba[i] & 0x000000FF;
+ g[i] = (rgba[i] & 0x0000FF00) >> 8;
+ }
+ *ptr++ = ProcessAlpha(r);
+ *ptr++ = ProcessAlpha(g);
+ }
+ while (--blocks);
+}
+
+// -- GODOT end --
diff --git a/thirdparty/etcpak/ProcessRgtc.hpp b/thirdparty/etcpak/ProcessRgtc.hpp
new file mode 100644
index 0000000000..0905b7d9ad
--- /dev/null
+++ b/thirdparty/etcpak/ProcessRgtc.hpp
@@ -0,0 +1,14 @@
+// -- GODOT start --
+
+#ifndef __PROCESSRGTC_HPP__
+#define __PROCESSRGTC_HPP__
+
+#include <stddef.h>
+#include <stdint.h>
+
+void CompressRgtcR(const uint32_t *src, uint64_t *dst, uint32_t blocks, size_t width);
+void CompressRgtcRG(const uint32_t *src, uint64_t *dst, uint32_t blocks, size_t width);
+
+#endif
+
+// -- GODOT end --