summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/classes/Animation.xml15
-rw-r--r--doc/classes/AnimationPlayer.xml4
-rw-r--r--doc/classes/AnimationTree.xml4
-rw-r--r--doc/classes/AudioStreamPlayer.xml6
-rw-r--r--doc/classes/AudioStreamPlayer2D.xml6
-rw-r--r--doc/classes/AudioStreamPlayer3D.xml6
-rw-r--r--editor/animation_track_editor.cpp50
-rw-r--r--editor/animation_track_editor.h5
-rw-r--r--editor/debugger/script_editor_debugger.cpp4
-rw-r--r--editor/icons/UseBlendDisable.svg1
-rw-r--r--editor/icons/UseBlendEnable.svg1
-rw-r--r--editor/plugins/text_shader_editor.cpp5
-rw-r--r--editor/project_converter_3_to_4.cpp4
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp68
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd32
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/hard_variants.out5
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs20
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs30
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs15
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs15
-rw-r--r--platform/android/export/export_plugin.cpp5
-rw-r--r--platform/android/export/gradle_export_util.cpp11
-rw-r--r--platform/android/java/app/AndroidManifest.xml9
-rw-r--r--scene/2d/audio_stream_player_2d.cpp17
-rw-r--r--scene/2d/audio_stream_player_2d.h2
-rw-r--r--scene/2d/gpu_particles_2d.cpp2
-rw-r--r--scene/3d/audio_stream_player_3d.cpp24
-rw-r--r--scene/3d/audio_stream_player_3d.h2
-rw-r--r--scene/animation/animation_player.cpp234
-rw-r--r--scene/animation/animation_player.h29
-rw-r--r--scene/animation/animation_tree.cpp293
-rw-r--r--scene/animation/animation_tree.h28
-rw-r--r--scene/audio/audio_stream_player.cpp5
-rw-r--r--scene/audio/audio_stream_player.h1
-rw-r--r--scene/resources/animation.cpp35
-rw-r--r--scene/resources/animation.h3
-rw-r--r--scene/resources/shader.cpp13
-rw-r--r--scene/resources/shader.h2
-rw-r--r--scene/resources/shader_include.cpp12
-rw-r--r--scene/resources/shader_include.h3
-rw-r--r--servers/rendering/shader_preprocessor.cpp5
41 files changed, 717 insertions, 314 deletions
diff --git a/doc/classes/Animation.xml b/doc/classes/Animation.xml
index c0626dcfe4..74ee13a3d2 100644
--- a/doc/classes/Animation.xml
+++ b/doc/classes/Animation.xml
@@ -104,6 +104,13 @@
[param stream] is the [AudioStream] resource to play. [param start_offset] is the number of seconds cut off at the beginning of the audio stream, while [param end_offset] is at the ending.
</description>
</method>
+ <method name="audio_track_is_use_blend" qualifiers="const">
+ <return type="bool" />
+ <param index="0" name="track_idx" type="int" />
+ <description>
+ Returns [code]true[/code] if the track at [code]idx[/code] will be blended with other animations.
+ </description>
+ </method>
<method name="audio_track_set_key_end_offset">
<return type="void" />
<param index="0" name="track_idx" type="int" />
@@ -131,6 +138,14 @@
Sets the stream of the key identified by [param key_idx] to value [param stream]. The [param track_idx] must be the index of an Audio Track.
</description>
</method>
+ <method name="audio_track_set_use_blend">
+ <return type="void" />
+ <param index="0" name="track_idx" type="int" />
+ <param index="1" name="enable" type="bool" />
+ <description>
+ Sets whether the track will be blended with other animations. If [code]true[/code], the audio playback volume changes depending on the blend value.
+ </description>
+ </method>
<method name="bezier_track_get_key_in_handle" qualifiers="const">
<return type="Vector2" />
<param index="0" name="track_idx" type="int" />
diff --git a/doc/classes/AnimationPlayer.xml b/doc/classes/AnimationPlayer.xml
index 8982eeedb2..25e4a4549d 100644
--- a/doc/classes/AnimationPlayer.xml
+++ b/doc/classes/AnimationPlayer.xml
@@ -232,6 +232,10 @@
<member name="assigned_animation" type="String" setter="set_assigned_animation" getter="get_assigned_animation">
If playing, the the current animation's key, otherwise, the animation last played. When set, this changes the animation, but will not play it unless already playing. See also [member current_animation].
</member>
+ <member name="audio_max_polyphony" type="int" setter="set_audio_max_polyphony" getter="get_audio_max_polyphony" default="32">
+ The number of possible simultaneous sounds for each of the assigned AudioStreamPlayers.
+ For example, if this value is [code]32[/code] and the animation has two audio tracks, the two [AudioStreamPlayer]s assigned can play simultaneously up to [code]32[/code] voices each.
+ </member>
<member name="autoplay" type="String" setter="set_autoplay" getter="get_autoplay" default="&quot;&quot;">
The key of the animation to play when the scene loads.
</member>
diff --git a/doc/classes/AnimationTree.xml b/doc/classes/AnimationTree.xml
index 3a3e8bb1fa..86562c340d 100644
--- a/doc/classes/AnimationTree.xml
+++ b/doc/classes/AnimationTree.xml
@@ -110,6 +110,10 @@
<member name="anim_player" type="NodePath" setter="set_animation_player" getter="get_animation_player" default="NodePath(&quot;&quot;)">
The path to the [AnimationPlayer] used for animating.
</member>
+ <member name="audio_max_polyphony" type="int" setter="set_audio_max_polyphony" getter="get_audio_max_polyphony" default="32">
+ The number of possible simultaneous sounds for each of the assigned AudioStreamPlayers.
+ For example, if this value is [code]32[/code] and the animation has two audio tracks, the two [AudioStreamPlayer]s assigned can play simultaneously up to [code]32[/code] voices each.
+ </member>
<member name="process_callback" type="int" setter="set_process_callback" getter="get_process_callback" enum="AnimationTree.AnimationProcessCallback" default="1">
The process mode of this [AnimationTree]. See [enum AnimationProcessCallback] for available modes.
</member>
diff --git a/doc/classes/AudioStreamPlayer.xml b/doc/classes/AudioStreamPlayer.xml
index 06e183f4e2..9b3a4f7bba 100644
--- a/doc/classes/AudioStreamPlayer.xml
+++ b/doc/classes/AudioStreamPlayer.xml
@@ -28,6 +28,12 @@
Returns the [AudioStreamPlayback] object associated with this [AudioStreamPlayer].
</description>
</method>
+ <method name="has_stream_playback">
+ <return type="bool" />
+ <description>
+ Returns whether the [AudioStreamPlayer] can return the [AudioStreamPlayback] object or not.
+ </description>
+ </method>
<method name="play">
<return type="void" />
<param index="0" name="from_position" type="float" default="0.0" />
diff --git a/doc/classes/AudioStreamPlayer2D.xml b/doc/classes/AudioStreamPlayer2D.xml
index 81755b580f..18869e87cb 100644
--- a/doc/classes/AudioStreamPlayer2D.xml
+++ b/doc/classes/AudioStreamPlayer2D.xml
@@ -25,6 +25,12 @@
Returns the [AudioStreamPlayback] object associated with this [AudioStreamPlayer2D].
</description>
</method>
+ <method name="has_stream_playback">
+ <return type="bool" />
+ <description>
+ Returns whether the [AudioStreamPlayer] can return the [AudioStreamPlayback] object or not.
+ </description>
+ </method>
<method name="play">
<return type="void" />
<param index="0" name="from_position" type="float" default="0.0" />
diff --git a/doc/classes/AudioStreamPlayer3D.xml b/doc/classes/AudioStreamPlayer3D.xml
index f711210ca1..d5a06dcad6 100644
--- a/doc/classes/AudioStreamPlayer3D.xml
+++ b/doc/classes/AudioStreamPlayer3D.xml
@@ -25,6 +25,12 @@
Returns the [AudioStreamPlayback] object associated with this [AudioStreamPlayer3D].
</description>
</method>
+ <method name="has_stream_playback">
+ <return type="bool" />
+ <description>
+ Returns whether the [AudioStreamPlayer] can return the [AudioStreamPlayback] object or not.
+ </description>
+ </method>
<method name="play">
<return type="void" />
<param index="0" name="from_position" type="float" default="0.0" />
diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp
index 338a22c070..8dd087451c 100644
--- a/editor/animation_track_editor.cpp
+++ b/editor/animation_track_editor.cpp
@@ -1954,6 +1954,10 @@ void AnimationTrackEdit::_notification(int p_what) {
get_theme_icon(SNAME("TrackDiscrete"), SNAME("EditorIcons")),
get_theme_icon(SNAME("TrackCapture"), SNAME("EditorIcons"))
};
+ Ref<Texture2D> blend_icon[2] = {
+ get_theme_icon(SNAME("UseBlendEnable"), SNAME("EditorIcons")),
+ get_theme_icon(SNAME("UseBlendDisable"), SNAME("EditorIcons")),
+ };
int ofs = get_size().width - timeline->get_buttons_width();
@@ -1982,6 +1986,11 @@ void AnimationTrackEdit::_notification(int p_what) {
if (!animation->track_is_compressed(track) && animation->track_get_type(track) == Animation::TYPE_VALUE) {
draw_texture(update_icon, update_mode_rect.position);
}
+ if (animation->track_get_type(track) == Animation::TYPE_AUDIO) {
+ Ref<Texture2D> use_blend_icon = blend_icon[animation->audio_track_is_use_blend(track) ? 0 : 1];
+ Vector2 use_blend_icon_pos = update_mode_rect.position + (update_mode_rect.size - use_blend_icon->get_size()) / 2;
+ draw_texture(use_blend_icon, use_blend_icon_pos);
+ }
// Make it easier to click.
update_mode_rect.position.y = 0;
update_mode_rect.size.y = get_size().height;
@@ -1990,13 +1999,12 @@ void AnimationTrackEdit::_notification(int p_what) {
update_mode_rect.size.x += hsep / 2;
if (!read_only) {
- if (animation->track_get_type(track) == Animation::TYPE_VALUE) {
+ if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_AUDIO) {
draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2));
update_mode_rect.size.x += down_icon->get_width();
} else if (animation->track_get_type(track) == Animation::TYPE_BEZIER) {
Ref<Texture2D> bezier_icon = get_theme_icon(SNAME("EditBezier"), SNAME("EditorIcons"));
update_mode_rect.size.x += down_icon->get_width();
-
update_mode_rect = Rect2();
} else {
update_mode_rect = Rect2();
@@ -2439,7 +2447,11 @@ String AnimationTrackEdit::get_tooltip(const Point2 &p_pos) const {
}
if (update_mode_rect.has_point(p_pos)) {
- return TTR("Update Mode (How this property is set)");
+ if (animation->track_get_type(track) == Animation::TYPE_AUDIO) {
+ return TTR("Use Blend");
+ } else {
+ return TTR("Update Mode (How this property is set)");
+ }
}
if (interp_mode_rect.has_point(p_pos)) {
@@ -2641,9 +2653,14 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
menu->connect("id_pressed", callable_mp(this, &AnimationTrackEdit::_menu_selected));
}
menu->clear();
- menu->add_icon_item(get_theme_icon(SNAME("TrackContinuous"), SNAME("EditorIcons")), TTR("Continuous"), MENU_CALL_MODE_CONTINUOUS);
- menu->add_icon_item(get_theme_icon(SNAME("TrackDiscrete"), SNAME("EditorIcons")), TTR("Discrete"), MENU_CALL_MODE_DISCRETE);
- menu->add_icon_item(get_theme_icon(SNAME("TrackCapture"), SNAME("EditorIcons")), TTR("Capture"), MENU_CALL_MODE_CAPTURE);
+ if (animation->track_get_type(track) == Animation::TYPE_AUDIO) {
+ menu->add_icon_item(get_theme_icon(SNAME("UseBlendEnable"), SNAME("EditorIcons")), TTR("Use Blend"), MENU_USE_BLEND_ENABLED);
+ menu->add_icon_item(get_theme_icon(SNAME("UseBlendDisable"), SNAME("EditorIcons")), TTR("Don't Use Blend"), MENU_USE_BLEND_DISABLED);
+ } else {
+ menu->add_icon_item(get_theme_icon(SNAME("TrackContinuous"), SNAME("EditorIcons")), TTR("Continuous"), MENU_CALL_MODE_CONTINUOUS);
+ menu->add_icon_item(get_theme_icon(SNAME("TrackDiscrete"), SNAME("EditorIcons")), TTR("Discrete"), MENU_CALL_MODE_DISCRETE);
+ menu->add_icon_item(get_theme_icon(SNAME("TrackCapture"), SNAME("EditorIcons")), TTR("Capture"), MENU_CALL_MODE_CAPTURE);
+ }
menu->reset_size();
Vector2 popup_pos = get_screen_position() + update_mode_rect.position + Vector2(0, update_mode_rect.size.height);
@@ -2662,7 +2679,7 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
menu->add_icon_item(get_theme_icon(SNAME("InterpRaw"), SNAME("EditorIcons")), TTR("Nearest"), MENU_INTERPOLATION_NEAREST);
menu->add_icon_item(get_theme_icon(SNAME("InterpLinear"), SNAME("EditorIcons")), TTR("Linear"), MENU_INTERPOLATION_LINEAR);
menu->add_icon_item(get_theme_icon(SNAME("InterpCubic"), SNAME("EditorIcons")), TTR("Cubic"), MENU_INTERPOLATION_CUBIC);
- // Check is angle property.
+ // Check whether it is angle property.
AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
if (ape) {
AnimationPlayer *ap = ape->get_player();
@@ -3055,6 +3072,16 @@ void AnimationTrackEdit::_menu_selected(int p_index) {
emit_signal(SNAME("delete_request"));
} break;
+ case MENU_USE_BLEND_ENABLED:
+ case MENU_USE_BLEND_DISABLED: {
+ bool use_blend = p_index == MENU_USE_BLEND_ENABLED;
+ EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
+ undo_redo->create_action(TTR("Change Animation Use Blend"));
+ undo_redo->add_do_method(animation.ptr(), "audio_track_set_use_blend", track, use_blend);
+ undo_redo->add_undo_method(animation.ptr(), "audio_track_set_use_blend", track, animation->audio_track_is_use_blend(track));
+ undo_redo->commit_action();
+ queue_redraw();
+ } break;
}
}
@@ -3488,6 +3515,9 @@ void AnimationTrackEditor::_animation_track_remove_request(int p_track, Ref<Anim
if (p_from_animation->track_get_type(idx) == Animation::TYPE_VALUE) {
undo_redo->add_undo_method(p_from_animation.ptr(), "value_track_set_update_mode", idx, p_from_animation->value_track_get_update_mode(idx));
}
+ if (animation->track_get_type(idx) == Animation::TYPE_AUDIO) {
+ undo_redo->add_undo_method(animation.ptr(), "audio_track_set_use_blend", idx, animation->audio_track_is_use_blend(idx));
+ }
undo_redo->commit_action();
}
@@ -5618,6 +5648,9 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) {
if (tc.track_type == Animation::TYPE_VALUE) {
tc.update_mode = animation->value_track_get_update_mode(idx);
}
+ if (tc.track_type == Animation::TYPE_AUDIO) {
+ tc.use_blend = animation->audio_track_is_use_blend(idx);
+ }
tc.loop_wrap = animation->track_get_interpolation_loop_wrap(idx);
tc.enabled = animation->track_is_enabled(idx);
for (int i = 0; i < animation->track_get_key_count(idx); i++) {
@@ -5662,6 +5695,9 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) {
if (track_clipboard[i].track_type == Animation::TYPE_VALUE) {
undo_redo->add_do_method(animation.ptr(), "value_track_set_update_mode", base_track, track_clipboard[i].update_mode);
}
+ if (track_clipboard[i].track_type == Animation::TYPE_AUDIO) {
+ undo_redo->add_do_method(animation.ptr(), "audio_track_set_use_blend", base_track, track_clipboard[i].use_blend);
+ }
for (int j = 0; j < track_clipboard[i].keys.size(); j++) {
undo_redo->add_do_method(animation.ptr(), "track_insert_key", base_track, track_clipboard[i].keys[j].time, track_clipboard[i].keys[j].value, track_clipboard[i].keys[j].transition);
diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h
index 8506d9b80d..2a59bda2a4 100644
--- a/editor/animation_track_editor.h
+++ b/editor/animation_track_editor.h
@@ -220,7 +220,9 @@ class AnimationTrackEdit : public Control {
MENU_KEY_INSERT,
MENU_KEY_DUPLICATE,
MENU_KEY_ADD_RESET,
- MENU_KEY_DELETE
+ MENU_KEY_DELETE,
+ MENU_USE_BLEND_ENABLED,
+ MENU_USE_BLEND_DISABLED,
};
AnimationTimelineEdit *timeline = nullptr;
@@ -566,6 +568,7 @@ class AnimationTrackEditor : public VBoxContainer {
Animation::LoopMode loop_mode = Animation::LOOP_PINGPONG;
bool loop_wrap = false;
bool enabled = false;
+ bool use_blend = false;
struct Key {
float time = 0;
diff --git a/editor/debugger/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp
index 945a3ef932..32952a367d 100644
--- a/editor/debugger/script_editor_debugger.cpp
+++ b/editor/debugger/script_editor_debugger.cpp
@@ -548,7 +548,7 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da
cpp_cond->set_text(0, "<" + vformat(TTR("%s Error"), source_language_name) + ">");
cpp_cond->set_text(1, oe.error);
cpp_cond->set_text_alignment(0, HORIZONTAL_ALIGNMENT_LEFT);
- tooltip += vformat(TTR("%s Error:"), source_language_name) + "\n";
+ tooltip += vformat(TTR("%s Error:"), source_language_name) + " " + oe.error + "\n";
if (source_is_project_file) {
cpp_cond->set_metadata(0, source_meta);
}
@@ -570,7 +570,7 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da
cpp_source->set_text(0, "<" + vformat(TTR("%s Source"), source_language_name) + ">");
cpp_source->set_text(1, source_txt);
cpp_source->set_text_alignment(0, HORIZONTAL_ALIGNMENT_LEFT);
- tooltip += vformat(TTR("%s Source:"), source_language_name) + source_txt + "\n";
+ tooltip += vformat(TTR("%s Source:"), source_language_name) + " " + source_txt + "\n";
// Set metadata to highlight error line in scripts.
if (source_is_project_file) {
diff --git a/editor/icons/UseBlendDisable.svg b/editor/icons/UseBlendDisable.svg
new file mode 100644
index 0000000000..449dc2d0c8
--- /dev/null
+++ b/editor/icons/UseBlendDisable.svg
@@ -0,0 +1 @@
+<svg enable-background="new -595.5 420.5 16 16" height="16" viewBox="-595.5 420.5 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m-591 421.5h7v14h-7z" fill="#a3e595" opacity=".5"/><g fill="none" stroke="#a3e595" stroke-linecap="square" stroke-width="2"><path d="m-585 434.5v-12"/><path d="m-590 422.5v12"/><path d="m-581.5 422.5h-12"/></g></svg>
diff --git a/editor/icons/UseBlendEnable.svg b/editor/icons/UseBlendEnable.svg
new file mode 100644
index 0000000000..5567b2d8c6
--- /dev/null
+++ b/editor/icons/UseBlendEnable.svg
@@ -0,0 +1 @@
+<svg enable-background="new -595.5 420.5 16 16" height="16" viewBox="-595.5 420.5 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m-587.5 423.244c-3.995 2.354-7 6.775-7 11.256v1h14v-1c0-4.48-3.005-8.901-7-11.256z" fill="#95c6e8" opacity=".5"/><g fill="none" stroke="#95c6e8" stroke-linecap="square" stroke-width="2"><path d="m-581.5 422.5c-6 0-12 6-12 12"/><path d="m-581.5 434.5c0-6-6-12-12-12"/></g></svg>
diff --git a/editor/plugins/text_shader_editor.cpp b/editor/plugins/text_shader_editor.cpp
index 1b29999796..ffd9564816 100644
--- a/editor/plugins/text_shader_editor.cpp
+++ b/editor/plugins/text_shader_editor.cpp
@@ -383,11 +383,12 @@ void ShaderTextEditor::_code_complete_script(const String &p_code, List<ScriptLa
List<ScriptLanguage::CodeCompletionOption> pp_defines;
ShaderPreprocessor preprocessor;
String code;
- complete_from_path = (shader.is_valid() ? shader->get_path() : shader_inc->get_path()).get_base_dir();
+ String resource_path = (shader.is_valid() ? shader->get_path() : shader_inc->get_path());
+ complete_from_path = resource_path.get_base_dir();
if (!complete_from_path.ends_with("/")) {
complete_from_path += "/";
}
- preprocessor.preprocess(p_code, "", code, nullptr, nullptr, nullptr, nullptr, &pp_options, &pp_defines, _complete_include_paths);
+ preprocessor.preprocess(p_code, resource_path, code, nullptr, nullptr, nullptr, nullptr, &pp_options, &pp_defines, _complete_include_paths);
complete_from_path = String();
if (pp_options.size()) {
for (const ScriptLanguage::CodeCompletionOption &E : pp_options) {
diff --git a/editor/project_converter_3_to_4.cpp b/editor/project_converter_3_to_4.cpp
index f38c8ee39d..dfbb8e728b 100644
--- a/editor/project_converter_3_to_4.cpp
+++ b/editor/project_converter_3_to_4.cpp
@@ -1367,6 +1367,10 @@ static const char *project_settings_renames[][2] = {
{ "rendering/vram_compression/import_etc2", "rendering/textures/vram_compression/import_etc2" },
{ "rendering/vram_compression/import_pvrtc", "rendering/textures/vram_compression/import_pvrtc" },
{ "rendering/vram_compression/import_s3tc", "rendering/textures/vram_compression/import_s3tc" },
+ { "window/size/width", "window/size/viewport_width" },
+ { "window/size/height", "window/size/viewport_height" },
+ { "window/size/test_width", "window/size/window_width_override" },
+ { "window/size/test_height", "window/size/window_height_override" },
{ nullptr, nullptr },
};
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index f6385dd132..8c3521c530 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -1999,7 +1999,7 @@ void GDScriptAnalyzer::resolve_return(GDScriptParser::ReturnNode *p_return) {
#ifdef DEBUG_ENABLED
} else if (expected_type.builtin_type == Variant::INT && result.builtin_type == Variant::FLOAT) {
parser->push_warning(p_return, GDScriptWarning::NARROWING_CONVERSION);
- } else if (result.is_variant()) {
+ } else if (result.is_variant() && !expected_type.is_variant()) {
mark_node_unsafe(p_return);
#endif
}
@@ -2375,43 +2375,41 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o
GDScriptParser::DataType result;
- if (left_type.is_variant() || right_type.is_variant()) {
- // Cannot infer type because one operand can be anything.
- result.kind = GDScriptParser::DataType::VARIANT;
- mark_node_unsafe(p_binary_op);
- } else {
- if (p_binary_op->variant_op < Variant::OP_MAX) {
- bool valid = false;
- result = get_operation_type(p_binary_op->variant_op, left_type, right_type, valid, p_binary_op);
-
- if (!valid) {
- push_error(vformat(R"(Invalid operands "%s" and "%s" for "%s" operator.)", left_type.to_string(), right_type.to_string(), Variant::get_operator_name(p_binary_op->variant_op)), p_binary_op);
- }
- } else {
- if (p_binary_op->operation == GDScriptParser::BinaryOpNode::OP_TYPE_TEST) {
- GDScriptParser::DataType test_type = right_type;
- test_type.is_meta_type = false;
-
- if (!is_type_compatible(test_type, left_type, false)) {
- // Test reverse as well to consider for subtypes.
- if (!is_type_compatible(left_type, test_type, false)) {
- if (left_type.is_hard_type()) {
- push_error(vformat(R"(Expression is of type "%s" so it can't be of type "%s".)", left_type.to_string(), test_type.to_string()), p_binary_op->left_operand);
- } else {
- // TODO: Warning.
- mark_node_unsafe(p_binary_op);
- }
- }
- }
+ if (p_binary_op->operation == GDScriptParser::BinaryOpNode::OP_TYPE_TEST) {
+ GDScriptParser::DataType test_type = right_type;
+ test_type.is_meta_type = false;
- // "is" operator is always a boolean anyway.
- result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
- result.kind = GDScriptParser::DataType::BUILTIN;
- result.builtin_type = Variant::BOOL;
+ if (!is_type_compatible(test_type, left_type, false) && !is_type_compatible(left_type, test_type, false)) {
+ if (left_type.is_hard_type()) {
+ push_error(vformat(R"(Expression is of type "%s" so it can't be of type "%s".)", left_type.to_string(), test_type.to_string()), p_binary_op->left_operand);
} else {
- ERR_PRINT("Parser bug: unknown binary operation.");
+ // TODO: Warning.
+ mark_node_unsafe(p_binary_op);
}
}
+
+ // "is" operator is always a boolean anyway.
+ result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ result.kind = GDScriptParser::DataType::BUILTIN;
+ result.builtin_type = Variant::BOOL;
+ } else if ((p_binary_op->variant_op == Variant::OP_EQUAL || p_binary_op->variant_op == Variant::OP_NOT_EQUAL) &&
+ ((left_type.kind == GDScriptParser::DataType::BUILTIN && left_type.builtin_type == Variant::NIL) || (right_type.kind == GDScriptParser::DataType::BUILTIN && right_type.builtin_type == Variant::NIL))) {
+ // "==" and "!=" operators always return a boolean when comparing to null.
+ result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ result.kind = GDScriptParser::DataType::BUILTIN;
+ result.builtin_type = Variant::BOOL;
+ } else if (left_type.is_variant() || right_type.is_variant()) {
+ // Cannot infer type because one operand can be anything.
+ result.kind = GDScriptParser::DataType::VARIANT;
+ mark_node_unsafe(p_binary_op);
+ } else if (p_binary_op->variant_op < Variant::OP_MAX) {
+ bool valid = false;
+ result = get_operation_type(p_binary_op->variant_op, left_type, right_type, valid, p_binary_op);
+ if (!valid) {
+ push_error(vformat(R"(Invalid operands "%s" and "%s" for "%s" operator.)", left_type.to_string(), right_type.to_string(), Variant::get_operator_name(p_binary_op->variant_op)), p_binary_op);
+ }
+ } else {
+ ERR_PRINT("Parser bug: unknown binary operation.");
}
p_binary_op->set_datatype(result);
@@ -4242,7 +4240,7 @@ bool GDScriptAnalyzer::validate_call_arg(const List<GDScriptParser::DataType> &p
GDScriptParser::DataType par_type = p_par_types[i];
GDScriptParser::DataType arg_type = p_call->arguments[i]->get_datatype();
- if (arg_type.is_variant()) {
+ if (arg_type.is_variant() && !(par_type.is_hard_type() && par_type.is_variant())) {
// Argument can be anything, so this is unsafe.
mark_node_unsafe(p_call->arguments[i]);
} else if (par_type.is_hard_type() && !is_type_compatible(par_type, arg_type, true)) {
diff --git a/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd b/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd
new file mode 100644
index 0000000000..211115d9cb
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd
@@ -0,0 +1,32 @@
+func variant() -> Variant: return null
+
+var member_weak = variant()
+var member_typed: Variant = variant()
+var member_inferred := variant()
+
+func param_weak(param = variant()) -> void: print(param)
+func param_typed(param: Variant = variant()) -> void: print(param)
+func param_inferred(param := variant()) -> void: print(param)
+
+func return_untyped(): return variant()
+func return_typed() -> Variant: return variant()
+
+@warning_ignore(unused_variable)
+func test() -> void:
+ var weak = variant()
+ var typed: Variant = variant()
+ var inferred := variant()
+
+ weak = variant()
+ typed = variant()
+ inferred = variant()
+
+ param_weak(typed)
+ param_typed(typed)
+ param_inferred(typed)
+
+ if typed == null: pass
+ if typed != null: pass
+ if typed is Node: pass
+
+ print('ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/features/hard_variants.out b/modules/gdscript/tests/scripts/analyzer/features/hard_variants.out
new file mode 100644
index 0000000000..08491efa07
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/hard_variants.out
@@ -0,0 +1,5 @@
+GDTEST_OK
+<null>
+<null>
+<null>
+ok
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs
index 0e92f4331d..af83cc24bf 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs
@@ -50,6 +50,15 @@ namespace Godot
}
/// <summary>
+ /// The volume of this <see cref="Aabb"/>.
+ /// See also <see cref="HasVolume"/>.
+ /// </summary>
+ public readonly real_t Volume
+ {
+ get { return _size.X * _size.Y * _size.Z; }
+ }
+
+ /// <summary>
/// Returns an <see cref="Aabb"/> with equivalent position and size, modified so that
/// the most-negative corner is the origin and the size is positive.
/// </summary>
@@ -312,15 +321,6 @@ namespace Godot
}
/// <summary>
- /// Returns the volume of the <see cref="Aabb"/>.
- /// </summary>
- /// <returns>The volume.</returns>
- public readonly real_t GetVolume()
- {
- return _size.X * _size.Y * _size.Z;
- }
-
- /// <summary>
/// Returns a copy of the <see cref="Aabb"/> grown a given amount of units towards all the sides.
/// </summary>
/// <param name="by">The amount to grow by.</param>
@@ -383,7 +383,7 @@ namespace Godot
/// Returns <see langword="true"/> if the <see cref="Aabb"/> has
/// area, and <see langword="false"/> if the <see cref="Aabb"/>
/// is linear, empty, or has a negative <see cref="Size"/>.
- /// See also <see cref="GetVolume"/>.
+ /// See also <see cref="Volume"/>.
/// </summary>
/// <returns>
/// A <see langword="bool"/> for whether or not the <see cref="Aabb"/> has volume.
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
index 859b47c276..ec2728140e 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
@@ -243,9 +243,33 @@ namespace Godot.Bridge
if (wrapperType == null)
{
- wrapperType = AppDomain.CurrentDomain.GetAssemblies()
- .FirstOrDefault(a => a.GetName().Name == "GodotSharpEditor")?
- .GetType("Godot." + nativeTypeNameStr);
+ wrapperType = GetTypeByGodotClassAttr(typeof(GodotObject).Assembly, nativeTypeNameStr);
+ }
+
+ if (wrapperType == null)
+ {
+ var editorAssembly = AppDomain.CurrentDomain.GetAssemblies()
+ .FirstOrDefault(a => a.GetName().Name == "GodotSharpEditor");
+ wrapperType = editorAssembly?.GetType("Godot." + nativeTypeNameStr);
+
+ if (wrapperType == null)
+ {
+ wrapperType = GetTypeByGodotClassAttr(editorAssembly, nativeTypeNameStr);
+ }
+ }
+
+ static Type? GetTypeByGodotClassAttr(Assembly assembly, string nativeTypeNameStr)
+ {
+ var types = assembly.GetTypes();
+ foreach (var type in types)
+ {
+ var attr = type.GetCustomAttribute<GodotClassNameAttribute>();
+ if (attr?.Name == nativeTypeNameStr)
+ {
+ return type;
+ }
+ }
+ return null;
}
static bool IsStatic(Type type) => type.IsAbstract && type.IsSealed;
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs
index be4004a0f9..69444f8035 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs
@@ -51,11 +51,11 @@ namespace Godot
/// <summary>
/// The area of this <see cref="Rect2"/>.
+ /// See also <see cref="HasArea"/>.
/// </summary>
- /// <value>Equivalent to <see cref="GetArea()"/>.</value>
public readonly real_t Area
{
- get { return GetArea(); }
+ get { return _size.X * _size.Y; }
}
/// <summary>
@@ -161,15 +161,6 @@ namespace Godot
}
/// <summary>
- /// Returns the area of the <see cref="Rect2"/>.
- /// </summary>
- /// <returns>The area.</returns>
- public readonly real_t GetArea()
- {
- return _size.X * _size.Y;
- }
-
- /// <summary>
/// Returns the center of the <see cref="Rect2"/>, which is equal
/// to <see cref="Position"/> + (<see cref="Size"/> / 2).
/// </summary>
@@ -247,7 +238,7 @@ namespace Godot
/// Returns <see langword="true"/> if the <see cref="Rect2"/> has
/// area, and <see langword="false"/> if the <see cref="Rect2"/>
/// is linear, empty, or has a negative <see cref="Size"/>.
- /// See also <see cref="GetArea"/>.
+ /// See also <see cref="Area"/>.
/// </summary>
/// <returns>
/// A <see langword="bool"/> for whether or not the <see cref="Rect2"/> has area.
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs
index 5b06101db5..2099d0abca 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs
@@ -51,11 +51,11 @@ namespace Godot
/// <summary>
/// The area of this <see cref="Rect2I"/>.
+ /// See also <see cref="HasArea"/>.
/// </summary>
- /// <value>Equivalent to <see cref="GetArea()"/>.</value>
public readonly int Area
{
- get { return GetArea(); }
+ get { return _size.X * _size.Y; }
}
/// <summary>
@@ -151,15 +151,6 @@ namespace Godot
}
/// <summary>
- /// Returns the area of the <see cref="Rect2I"/>.
- /// </summary>
- /// <returns>The area.</returns>
- public readonly int GetArea()
- {
- return _size.X * _size.Y;
- }
-
- /// <summary>
/// Returns the center of the <see cref="Rect2I"/>, which is equal
/// to <see cref="Position"/> + (<see cref="Size"/> / 2).
/// If <see cref="Size"/> is an odd number, the returned center
@@ -239,7 +230,7 @@ namespace Godot
/// Returns <see langword="true"/> if the <see cref="Rect2I"/> has
/// area, and <see langword="false"/> if the <see cref="Rect2I"/>
/// is linear, empty, or has a negative <see cref="Size"/>.
- /// See also <see cref="GetArea"/>.
+ /// See also <see cref="Area"/>.
/// </summary>
/// <returns>
/// A <see langword="bool"/> for whether or not the <see cref="Rect2I"/> has area.
diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp
index 587caf81bf..a23f7d1d02 100644
--- a/platform/android/export/export_plugin.cpp
+++ b/platform/android/export/export_plugin.cpp
@@ -1076,11 +1076,6 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p
Vector<int> feature_versions;
if (xr_mode_index == XR_MODE_OPENXR) {
- // Set degrees of freedom
- feature_names.push_back("android.hardware.vr.headtracking");
- feature_required_list.push_back(true);
- feature_versions.push_back(1);
-
// Check for hand tracking
if (hand_tracking_index > XR_HAND_TRACKING_NONE) {
feature_names.push_back("oculus.software.handtracking");
diff --git a/platform/android/export/gradle_export_util.cpp b/platform/android/export/gradle_export_util.cpp
index e450f3edb3..4fdcca68e9 100644
--- a/platform/android/export/gradle_export_util.cpp
+++ b/platform/android/export/gradle_export_util.cpp
@@ -259,8 +259,6 @@ String _get_xr_features_tag(const Ref<EditorExportPreset> &p_preset) {
int xr_mode_index = (int)(p_preset->get("xr_features/xr_mode"));
bool uses_xr = xr_mode_index == XR_MODE_OPENXR;
if (uses_xr) {
- manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"android.hardware.vr.headtracking\" android:required=\"true\" android:version=\"1\" />\n";
-
int hand_tracking_index = p_preset->get("xr_features/hand_tracking"); // 0: none, 1: optional, 2: required
if (hand_tracking_index == XR_HAND_TRACKING_OPTIONAL) {
manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"oculus.software.handtracking\" android:required=\"false\" />\n";
@@ -279,8 +277,6 @@ String _get_xr_features_tag(const Ref<EditorExportPreset> &p_preset) {
}
String _get_activity_tag(const Ref<EditorExportPreset> &p_preset) {
- int xr_mode_index = (int)(p_preset->get("xr_features/xr_mode"));
- bool uses_xr = xr_mode_index == XR_MODE_OPENXR;
String orientation = _get_android_orientation_label(DisplayServer::ScreenOrientation(int(GLOBAL_GET("display/window/handheld/orientation"))));
String manifest_activity_text = vformat(
" <activity android:name=\"com.godot.game.GodotApp\" "
@@ -291,11 +287,6 @@ String _get_activity_tag(const Ref<EditorExportPreset> &p_preset) {
bool_to_string(p_preset->get("package/exclude_from_recents")),
orientation,
bool_to_string(bool(GLOBAL_GET("display/window/size/resizable"))));
- if (uses_xr) {
- manifest_activity_text += " <meta-data tools:node=\"replace\" android:name=\"com.oculus.vr.focusaware\" android:value=\"true\" />\n";
- } else {
- manifest_activity_text += " <meta-data tools:node=\"remove\" android:name=\"com.oculus.vr.focusaware\" />\n";
- }
manifest_activity_text += " </activity>\n";
return manifest_activity_text;
}
@@ -335,8 +326,6 @@ String _get_application_tag(const Ref<EditorExportPreset> &p_preset, bool p_has_
hand_tracking_frequency);
manifest_application_text += " <meta-data tools:node=\"replace\" android:name=\"com.oculus.handtracking.version\" android:value=\"V2.0\" />\n";
}
- } else {
- manifest_application_text += " <meta-data tools:node=\"remove\" android:name=\"com.oculus.supportedDevices\" />\n";
}
manifest_application_text += _get_activity_tag(p_preset);
manifest_application_text += " </application>\n";
diff --git a/platform/android/java/app/AndroidManifest.xml b/platform/android/java/app/AndroidManifest.xml
index 8c8608cbbb..1969f9c814 100644
--- a/platform/android/java/app/AndroidManifest.xml
+++ b/platform/android/java/app/AndroidManifest.xml
@@ -48,12 +48,6 @@
android:name="xr_hand_tracking_version_name"
android:value="xr_hand_tracking_version_value"/>
- <!-- Supported Meta devices -->
- <!-- This is removed by the exporter if the xr mode is not VR. -->
- <meta-data
- android:name="com.oculus.supportedDevices"
- android:value="all" />
-
<activity
android:name=".GodotApp"
android:label="@string/godot_project_name_string"
@@ -66,9 +60,6 @@
android:resizeableActivity="false"
tools:ignore="UnusedAttribute" >
- <!-- Focus awareness metadata is removed at export time if the xr mode is not VR. -->
- <meta-data android:name="com.oculus.vr.focusaware" android:value="true" />
-
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
diff --git a/scene/2d/audio_stream_player_2d.cpp b/scene/2d/audio_stream_player_2d.cpp
index 7b681eac7a..8c23d33402 100644
--- a/scene/2d/audio_stream_player_2d.cpp
+++ b/scene/2d/audio_stream_player_2d.cpp
@@ -72,12 +72,10 @@ void AudioStreamPlayer2D::_notification(int p_what) {
_update_panning();
}
- if (setplay.get() >= 0 && stream.is_valid()) {
+ if (setplayback.is_valid() && setplay.get() >= 0) {
active.set();
- Ref<AudioStreamPlayback> new_playback = stream->instantiate_playback();
- ERR_FAIL_COND_MSG(new_playback.is_null(), "Failed to instantiate playback.");
- AudioServer::get_singleton()->start_playback_stream(new_playback, _get_actual_bus(), volume_vector, setplay.get(), pitch_scale);
- stream_playbacks.push_back(new_playback);
+ AudioServer::get_singleton()->start_playback_stream(setplayback, _get_actual_bus(), volume_vector, setplay.get(), pitch_scale);
+ setplayback.unref();
setplay.set(-1);
}
@@ -255,7 +253,11 @@ void AudioStreamPlayer2D::play(float p_from_pos) {
if (stream->is_monophonic() && is_playing()) {
stop();
}
+ Ref<AudioStreamPlayback> stream_playback = stream->instantiate_playback();
+ ERR_FAIL_COND_MSG(stream_playback.is_null(), "Failed to instantiate playback.");
+ stream_playbacks.push_back(stream_playback);
+ setplayback = stream_playback;
setplay.set(p_from_pos);
active.set();
set_physics_process_internal(true);
@@ -390,6 +392,10 @@ bool AudioStreamPlayer2D::get_stream_paused() const {
return false;
}
+bool AudioStreamPlayer2D::has_stream_playback() {
+ return !stream_playbacks.is_empty();
+}
+
Ref<AudioStreamPlayback> AudioStreamPlayer2D::get_stream_playback() {
ERR_FAIL_COND_V_MSG(stream_playbacks.is_empty(), Ref<AudioStreamPlayback>(), "Player is inactive. Call play() before requesting get_stream_playback().");
return stream_playbacks[stream_playbacks.size() - 1];
@@ -458,6 +464,7 @@ void AudioStreamPlayer2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_panning_strength", "panning_strength"), &AudioStreamPlayer2D::set_panning_strength);
ClassDB::bind_method(D_METHOD("get_panning_strength"), &AudioStreamPlayer2D::get_panning_strength);
+ ClassDB::bind_method(D_METHOD("has_stream_playback"), &AudioStreamPlayer2D::has_stream_playback);
ClassDB::bind_method(D_METHOD("get_stream_playback"), &AudioStreamPlayer2D::get_stream_playback);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream");
diff --git a/scene/2d/audio_stream_player_2d.h b/scene/2d/audio_stream_player_2d.h
index a5fd584513..79a026fed2 100644
--- a/scene/2d/audio_stream_player_2d.h
+++ b/scene/2d/audio_stream_player_2d.h
@@ -56,6 +56,7 @@ private:
SafeFlag active{ false };
SafeNumeric<float> setplay{ -1.0 };
+ Ref<AudioStreamPlayback> setplayback;
Vector<AudioFrame> volume_vector;
@@ -129,6 +130,7 @@ public:
void set_panning_strength(float p_panning_strength);
float get_panning_strength() const;
+ bool has_stream_playback();
Ref<AudioStreamPlayback> get_stream_playback();
AudioStreamPlayer2D();
diff --git a/scene/2d/gpu_particles_2d.cpp b/scene/2d/gpu_particles_2d.cpp
index 9a3b7c8687..00d13c59b9 100644
--- a/scene/2d/gpu_particles_2d.cpp
+++ b/scene/2d/gpu_particles_2d.cpp
@@ -638,7 +638,7 @@ void GPUParticles2D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "interpolate"), "set_interpolate", "get_interpolate");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fract_delta"), "set_fractional_delta", "get_fractional_delta");
ADD_GROUP("Collision", "collision_");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision_base_size", PROPERTY_HINT_RANGE, "0,128,0.01,or_greater,suffix:px"), "set_collision_base_size", "get_collision_base_size");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision_base_size", PROPERTY_HINT_RANGE, "0,128,0.01,or_greater"), "set_collision_base_size", "get_collision_base_size");
ADD_GROUP("Drawing", "");
ADD_PROPERTY(PropertyInfo(Variant::RECT2, "visibility_rect", PROPERTY_HINT_NONE, "suffix:px"), "set_visibility_rect", "get_visibility_rect");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "local_coords"), "set_use_local_coordinates", "get_use_local_coordinates");
diff --git a/scene/3d/audio_stream_player_3d.cpp b/scene/3d/audio_stream_player_3d.cpp
index 7ed18d2d41..436b936586 100644
--- a/scene/3d/audio_stream_player_3d.cpp
+++ b/scene/3d/audio_stream_player_3d.cpp
@@ -284,14 +284,12 @@ void AudioStreamPlayer3D::_notification(int p_what) {
volume_vector = _update_panning();
}
- if (setplay.get() >= 0 && stream.is_valid()) {
+ if (setplayback.is_valid() && setplay.get() >= 0) {
active.set();
- Ref<AudioStreamPlayback> new_playback = stream->instantiate_playback();
- ERR_FAIL_COND_MSG(new_playback.is_null(), "Failed to instantiate playback.");
HashMap<StringName, Vector<AudioFrame>> bus_map;
bus_map[_get_actual_bus()] = volume_vector;
- AudioServer::get_singleton()->start_playback_stream(new_playback, bus_map, setplay.get(), actual_pitch_scale, linear_attenuation, attenuation_filter_cutoff_hz);
- stream_playbacks.push_back(new_playback);
+ AudioServer::get_singleton()->start_playback_stream(setplayback, bus_map, setplay.get(), actual_pitch_scale, linear_attenuation, attenuation_filter_cutoff_hz);
+ setplayback.unref();
setplay.set(-1);
}
@@ -580,14 +578,21 @@ void AudioStreamPlayer3D::play(float p_from_pos) {
if (stream->is_monophonic() && is_playing()) {
stop();
}
+ Ref<AudioStreamPlayback> stream_playback = stream->instantiate_playback();
+ ERR_FAIL_COND_MSG(stream_playback.is_null(), "Failed to instantiate playback.");
+
+ stream_playbacks.push_back(stream_playback);
+ setplayback = stream_playback;
setplay.set(p_from_pos);
active.set();
set_physics_process_internal(true);
}
void AudioStreamPlayer3D::seek(float p_seconds) {
- stop();
- play(p_seconds);
+ if (is_playing()) {
+ stop();
+ play(p_seconds);
+ }
}
void AudioStreamPlayer3D::stop() {
@@ -783,6 +788,10 @@ bool AudioStreamPlayer3D::get_stream_paused() const {
return false;
}
+bool AudioStreamPlayer3D::has_stream_playback() {
+ return !stream_playbacks.is_empty();
+}
+
Ref<AudioStreamPlayback> AudioStreamPlayer3D::get_stream_playback() {
ERR_FAIL_COND_V_MSG(stream_playbacks.is_empty(), Ref<AudioStreamPlayback>(), "Player is inactive. Call play() before requesting get_stream_playback().");
return stream_playbacks[stream_playbacks.size() - 1];
@@ -875,6 +884,7 @@ void AudioStreamPlayer3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_panning_strength", "panning_strength"), &AudioStreamPlayer3D::set_panning_strength);
ClassDB::bind_method(D_METHOD("get_panning_strength"), &AudioStreamPlayer3D::get_panning_strength);
+ ClassDB::bind_method(D_METHOD("has_stream_playback"), &AudioStreamPlayer3D::has_stream_playback);
ClassDB::bind_method(D_METHOD("get_stream_playback"), &AudioStreamPlayer3D::get_stream_playback);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream");
diff --git a/scene/3d/audio_stream_player_3d.h b/scene/3d/audio_stream_player_3d.h
index 67cec91896..bba8f10761 100644
--- a/scene/3d/audio_stream_player_3d.h
+++ b/scene/3d/audio_stream_player_3d.h
@@ -69,6 +69,7 @@ private:
SafeFlag active{ false };
SafeNumeric<float> setplay{ -1.0 };
+ Ref<AudioStreamPlayback> setplayback;
AttenuationModel attenuation_model = ATTENUATION_INVERSE_DISTANCE;
float volume_db = 0.0;
@@ -188,6 +189,7 @@ public:
void set_panning_strength(float p_panning_strength);
float get_panning_strength() const;
+ bool has_stream_playback();
Ref<AudioStreamPlayback> get_stream_playback();
AudioStreamPlayer3D();
diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp
index 63e0fb6935..1b7615befa 100644
--- a/scene/animation/animation_player.cpp
+++ b/scene/animation/animation_player.cpp
@@ -431,6 +431,17 @@ void AnimationPlayer::_ensure_node_caches(AnimationData *p_anim, Node *p_root_ov
}
}
+ if (a->track_get_type(i) == Animation::TYPE_AUDIO) {
+ if (!node_cache->audio_anim.has(a->track_get_path(i).get_concatenated_names())) {
+ TrackNodeCache::AudioAnim aa;
+ aa.object = (Object *)child;
+ aa.audio_stream.instantiate();
+ aa.audio_stream->set_polyphony(audio_max_polyphony);
+
+ node_cache->audio_anim[a->track_get_path(i).get_concatenated_names()] = aa;
+ }
+ }
+
node_cache->last_setup_pass = setup_pass;
}
}
@@ -820,52 +831,40 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
if (!nc->node || is_stopping) {
continue;
}
-
- if (p_seeked) {
#ifdef TOOLS_ENABLED
- if (!can_call) {
- continue; // To avoid spamming the preview in editor.
- }
+ if (p_seeked && !can_call) {
+ continue; // To avoid spamming the preview in editor.
+ }
#endif // TOOLS_ENABLED
- int idx = a->track_find_key(i, p_time);
- if (idx < 0) {
- continue;
- }
-
- Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx);
- if (!stream.is_valid()) {
- nc->node->call(SNAME("stop"));
- nc->audio_playing = false;
- playing_caches.erase(nc);
- } else {
- float start_ofs = a->audio_track_get_key_start_offset(i, idx);
- start_ofs += p_time - a->track_get_key_time(i, idx);
- float end_ofs = a->audio_track_get_key_end_offset(i, idx);
- float len = stream->get_length();
-
- if (start_ofs > len - end_ofs) {
- nc->node->call(SNAME("stop"));
- nc->audio_playing = false;
- playing_caches.erase(nc);
- continue;
- }
-
- nc->node->call(SNAME("set_stream"), stream);
- nc->node->call(SNAME("play"), start_ofs);
+ HashMap<StringName, TrackNodeCache::AudioAnim>::Iterator E = nc->audio_anim.find(a->track_get_path(i).get_concatenated_names());
+ ERR_CONTINUE(!E); //should it continue, or create a new one?
- nc->audio_playing = true;
- playing_caches.insert(nc);
- if (len && end_ofs > 0) { //force an end at a time
- nc->audio_len = len - start_ofs - end_ofs;
- } else {
- nc->audio_len = 0;
- }
+ TrackNodeCache::AudioAnim *aa = &E->value;
+ Node *asp = Object::cast_to<Node>(aa->object);
+ if (!asp) {
+ continue;
+ }
+ aa->length = a->get_length();
+ aa->time = p_time;
+ aa->loop = a->get_loop_mode() != Animation::LOOP_NONE;
+ aa->backward = backward;
+ if (aa->accum_pass != accum_pass) {
+ ERR_CONTINUE(cache_update_audio_size >= NODE_CACHE_UPDATE_MAX);
+ cache_update_audio[cache_update_audio_size++] = aa;
+ aa->accum_pass = accum_pass;
+ }
- nc->audio_start = p_time;
+ HashMap<int, TrackNodeCache::PlayingAudioStreamInfo> &map = aa->playing_streams;
+ // Find stream.
+ int idx = -1;
+ if (p_seeked) {
+ idx = a->track_find_key(i, p_time);
+ // Discard previous stream when seeking.
+ if (map.has(idx)) {
+ aa->audio_stream_playback->stop_stream(map[idx].index);
+ map.erase(idx);
}
-
} else {
- //find stuff to play
List<int> to_play;
if (p_started) {
int first_key = a->track_find_key(i, p_prev_time, Animation::FIND_MODE_EXACT);
@@ -875,55 +874,47 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
}
a->track_get_key_indices_in_range(i, p_time, p_delta, &to_play, p_looped_flag);
if (to_play.size()) {
- int idx = to_play.back()->get();
-
- Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx);
- if (!stream.is_valid()) {
- nc->node->call(SNAME("stop"));
- nc->audio_playing = false;
- playing_caches.erase(nc);
- } else {
- float start_ofs = a->audio_track_get_key_start_offset(i, idx);
- float end_ofs = a->audio_track_get_key_end_offset(i, idx);
- float len = stream->get_length();
-
- nc->node->call(SNAME("set_stream"), stream);
- nc->node->call(SNAME("play"), start_ofs);
-
- nc->audio_playing = true;
- playing_caches.insert(nc);
- if (len && end_ofs > 0) { //force an end at a time
- nc->audio_len = len - start_ofs - end_ofs;
- } else {
- nc->audio_len = 0;
- }
-
- nc->audio_start = p_time;
- }
- } else if (nc->audio_playing) {
- bool loop = a->get_loop_mode() != Animation::LOOP_NONE;
-
- bool stop = false;
-
- if (!loop) {
- if ((p_time < nc->audio_start && !backward) || (p_time > nc->audio_start && backward)) {
- stop = true;
- }
- } else if (nc->audio_len > 0) {
- float len = nc->audio_start > p_time ? (a->get_length() - nc->audio_start) + p_time : p_time - nc->audio_start;
+ idx = to_play.back()->get();
+ }
+ }
+ if (idx < 0) {
+ continue;
+ }
- if (len > nc->audio_len) {
- stop = true;
- }
+ // Play stream.
+ Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx);
+ if (stream.is_valid()) {
+ double start_ofs = a->audio_track_get_key_start_offset(i, idx);
+ double end_ofs = a->audio_track_get_key_end_offset(i, idx);
+ double len = stream->get_length();
+
+ if (aa->object->call(SNAME("get_stream")) != aa->audio_stream) {
+ aa->object->call(SNAME("set_stream"), aa->audio_stream);
+ aa->audio_stream_playback.unref();
+ if (!playing_audio_stream_players.has(asp)) {
+ playing_audio_stream_players.push_back(asp);
}
+ }
+ if (!aa->object->call(SNAME("is_playing"))) {
+ aa->object->call(SNAME("play"));
+ }
+ if (!aa->object->call(SNAME("has_stream_playback"))) {
+ aa->audio_stream_playback.unref();
+ continue;
+ }
+ if (aa->audio_stream_playback.is_null()) {
+ aa->audio_stream_playback = aa->object->call(SNAME("get_stream_playback"));
+ }
- if (stop) {
- //time to stop
- nc->node->call(SNAME("stop"));
- nc->audio_playing = false;
- playing_caches.erase(nc);
- }
+ TrackNodeCache::PlayingAudioStreamInfo pasi;
+ pasi.index = aa->audio_stream_playback->play_stream(stream, start_ofs);
+ pasi.start = p_time;
+ if (len && end_ofs > 0) { // Force an end at a time.
+ pasi.len = len - start_ofs - end_ofs;
+ } else {
+ pasi.len = 0;
}
+ map[idx] = pasi;
}
} break;
@@ -1223,6 +1214,53 @@ void AnimationPlayer::_animation_update_transforms() {
ERR_CONTINUE(ba->accum_pass != accum_pass);
ba->object->set_indexed(ba->bezier_property, ba->bezier_accum);
}
+
+ for (int i = 0; i < cache_update_audio_size; i++) {
+ TrackNodeCache::AudioAnim *aa = cache_update_audio[i];
+
+ ERR_CONTINUE(aa->accum_pass != accum_pass);
+
+ // Audio ending process.
+ LocalVector<int> erase_list;
+ for (const KeyValue<int, TrackNodeCache::PlayingAudioStreamInfo> &K : aa->playing_streams) {
+ TrackNodeCache::PlayingAudioStreamInfo pasi = K.value;
+
+ bool stop = false;
+ if (!aa->audio_stream_playback->is_stream_playing(pasi.index)) {
+ stop = true;
+ }
+ if (!aa->loop) {
+ if (!aa->backward) {
+ if (aa->time < pasi.start) {
+ stop = true;
+ }
+ } else if (aa->backward) {
+ if (aa->time > pasi.start) {
+ stop = true;
+ }
+ }
+ }
+ if (pasi.len > 0) {
+ double len = 0.0;
+ if (!aa->backward) {
+ len = pasi.start > aa->time ? (aa->length - pasi.start) + aa->time : aa->time - pasi.start;
+ } else {
+ len = pasi.start < aa->time ? (aa->length - aa->time) + pasi.start : pasi.start - aa->time;
+ }
+ if (len > pasi.len) {
+ stop = true;
+ }
+ }
+ if (stop) {
+ // Time to stop.
+ aa->audio_stream_playback->stop_stream(pasi.index);
+ erase_list.push_back(K.key);
+ }
+ }
+ for (uint32_t erase_idx = 0; erase_idx < erase_list.size(); erase_idx++) {
+ aa->playing_streams.erase(erase_list[erase_idx]);
+ }
+ }
}
void AnimationPlayer::_animation_process(double p_delta) {
@@ -1238,6 +1276,7 @@ void AnimationPlayer::_animation_process(double p_delta) {
cache_update_size = 0;
cache_update_prop_size = 0;
cache_update_bezier_size = 0;
+ cache_update_audio_size = 0;
AnimationData *prev_from = playback.current.from;
_animation_process2(p_delta, started);
@@ -1675,6 +1714,7 @@ void AnimationPlayer::play(const StringName &p_name, double p_custom_blend, floa
}
if (get_current_animation() != p_name) {
+ _clear_audio_streams();
_stop_playing_caches(false);
}
@@ -1856,6 +1896,7 @@ void AnimationPlayer::_node_removed(Node *p_node) {
}
void AnimationPlayer::clear_caches() {
+ _clear_audio_streams();
_stop_playing_caches(true);
node_cache_map.clear();
@@ -1867,10 +1908,19 @@ void AnimationPlayer::clear_caches() {
cache_update_size = 0;
cache_update_prop_size = 0;
cache_update_bezier_size = 0;
+ cache_update_audio_size = 0;
emit_signal(SNAME("caches_cleared"));
}
+void AnimationPlayer::_clear_audio_streams() {
+ for (int i = 0; i < playing_audio_stream_players.size(); i++) {
+ playing_audio_stream_players[i]->call(SNAME("stop"));
+ playing_audio_stream_players[i]->call(SNAME("set_stream"), Ref<AudioStream>());
+ }
+ playing_audio_stream_players.clear();
+}
+
void AnimationPlayer::set_active(bool p_active) {
if (active == p_active) {
return;
@@ -1950,6 +2000,15 @@ AnimationPlayer::AnimationMethodCallMode AnimationPlayer::get_method_call_mode()
return method_call_mode;
}
+void AnimationPlayer::set_audio_max_polyphony(int p_audio_max_polyphony) {
+ ERR_FAIL_COND(p_audio_max_polyphony < 0 || p_audio_max_polyphony > 128);
+ audio_max_polyphony = p_audio_max_polyphony;
+}
+
+int AnimationPlayer::get_audio_max_polyphony() const {
+ return audio_max_polyphony;
+}
+
void AnimationPlayer::set_movie_quit_on_finish_enabled(bool p_enabled) {
movie_quit_on_finish = p_enabled;
}
@@ -1978,6 +2037,7 @@ void AnimationPlayer::_set_process(bool p_process, bool p_force) {
}
void AnimationPlayer::_stop_internal(bool p_reset, bool p_keep_state) {
+ _clear_audio_streams();
_stop_playing_caches(p_reset);
Playback &c = playback;
c.blend.clear();
@@ -2198,6 +2258,9 @@ void AnimationPlayer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_method_call_mode", "mode"), &AnimationPlayer::set_method_call_mode);
ClassDB::bind_method(D_METHOD("get_method_call_mode"), &AnimationPlayer::get_method_call_mode);
+ ClassDB::bind_method(D_METHOD("set_audio_max_polyphony", "max_polyphony"), &AnimationPlayer::set_audio_max_polyphony);
+ ClassDB::bind_method(D_METHOD("get_audio_max_polyphony"), &AnimationPlayer::get_audio_max_polyphony);
+
ClassDB::bind_method(D_METHOD("set_movie_quit_on_finish_enabled", "enabled"), &AnimationPlayer::set_movie_quit_on_finish_enabled);
ClassDB::bind_method(D_METHOD("is_movie_quit_on_finish_enabled"), &AnimationPlayer::is_movie_quit_on_finish_enabled);
@@ -2223,6 +2286,7 @@ void AnimationPlayer::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playback_active", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_active", "is_active");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed_scale", PROPERTY_HINT_RANGE, "-64,64,0.01"), "set_speed_scale", "get_speed_scale");
ADD_PROPERTY(PropertyInfo(Variant::INT, "method_call_mode", PROPERTY_HINT_ENUM, "Deferred,Immediate"), "set_method_call_mode", "get_method_call_mode");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "audio_max_polyphony", PROPERTY_HINT_RANGE, "1,127,1"), "set_audio_max_polyphony", "get_audio_max_polyphony");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "movie_quit_on_finish"), "set_movie_quit_on_finish_enabled", "is_movie_quit_on_finish_enabled");
diff --git a/scene/animation/animation_player.h b/scene/animation/animation_player.h
index 2901c43dcf..25de278f97 100644
--- a/scene/animation/animation_player.h
+++ b/scene/animation/animation_player.h
@@ -37,6 +37,7 @@
#include "scene/3d/skeleton_3d.h"
#include "scene/resources/animation.h"
#include "scene/resources/animation_library.h"
+#include "scene/resources/audio_stream_polyphonic.h"
#ifdef TOOLS_ENABLED
class AnimatedValuesBackup : public RefCounted {
@@ -147,6 +148,26 @@ private:
HashMap<StringName, BezierAnim> bezier_anim;
+ struct PlayingAudioStreamInfo {
+ int64_t index = -1;
+ double start = 0.0;
+ double len = 0.0;
+ };
+
+ struct AudioAnim {
+ Ref<AudioStreamPolyphonic> audio_stream;
+ Ref<AudioStreamPlaybackPolyphonic> audio_stream_playback;
+ HashMap<int, PlayingAudioStreamInfo> playing_streams;
+ Object *object = nullptr;
+ uint64_t accum_pass = 0;
+ double length = 0.0;
+ double time = 0.0;
+ bool loop = false;
+ bool backward = false;
+ };
+
+ HashMap<StringName, AudioAnim> audio_anim;
+
uint32_t last_setup_pass = 0;
TrackNodeCache() {}
};
@@ -187,7 +208,10 @@ private:
int cache_update_prop_size = 0;
TrackNodeCache::BezierAnim *cache_update_bezier[NODE_CACHE_UPDATE_MAX];
int cache_update_bezier_size = 0;
+ TrackNodeCache::AudioAnim *cache_update_audio[NODE_CACHE_UPDATE_MAX];
+ int cache_update_audio_size = 0;
HashSet<TrackNodeCache *> playing_caches;
+ Vector<Node *> playing_audio_stream_players;
uint64_t accum_pass = 1;
float speed_scale = 1.0;
@@ -263,6 +287,7 @@ private:
bool reset_on_save = true;
AnimationProcessCallback process_callback = ANIMATION_PROCESS_IDLE;
AnimationMethodCallMode method_call_mode = ANIMATION_METHOD_CALL_DEFERRED;
+ int audio_max_polyphony = 32;
bool movie_quit_on_finish = false;
bool processing = false;
bool active = true;
@@ -278,6 +303,7 @@ private:
void _animation_process(double p_delta);
void _node_removed(Node *p_node);
+ void _clear_audio_streams();
void _stop_playing_caches(bool p_reset);
// bind helpers
@@ -377,6 +403,9 @@ public:
void set_method_call_mode(AnimationMethodCallMode p_mode);
AnimationMethodCallMode get_method_call_mode() const;
+ void set_audio_max_polyphony(int p_audio_max_polyphony);
+ int get_audio_max_polyphony() const;
+
void set_movie_quit_on_finish_enabled(bool p_enabled);
bool is_movie_quit_on_finish_enabled() const;
diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp
index 05b6c72cbd..f630660049 100644
--- a/scene/animation/animation_tree.cpp
+++ b/scene/animation/animation_tree.cpp
@@ -486,13 +486,7 @@ void AnimationTree::set_active(bool p_active) {
}
if (!active && is_inside_tree()) {
- for (const TrackCache *E : playing_caches) {
- if (ObjectDB::get_instance(E->object_id)) {
- E->object->call(SNAME("stop"));
- }
- }
-
- playing_caches.clear();
+ _clear_caches();
}
}
@@ -531,6 +525,7 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) {
if (!player->has_node(player->get_root())) {
ERR_PRINT("AnimationTree: AnimationPlayer root is invalid.");
set_active(false);
+ _clear_caches();
return false;
}
Node *parent = player->get_node(player->get_root());
@@ -763,6 +758,8 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) {
track_audio->object = child;
track_audio->object_id = track_audio->object->get_instance_id();
+ track_audio->audio_stream.instantiate();
+ track_audio->audio_stream->set_polyphony(audio_max_polyphony);
track = track_audio;
@@ -860,14 +857,32 @@ void AnimationTree::_animation_player_changed() {
}
void AnimationTree::_clear_caches() {
+ _clear_audio_streams();
+ _clear_playing_caches();
for (KeyValue<NodePath, TrackCache *> &K : track_cache) {
memdelete(K.value);
}
- playing_caches.clear();
track_cache.clear();
cache_valid = false;
}
+void AnimationTree::_clear_audio_streams() {
+ for (int i = 0; i < playing_audio_stream_players.size(); i++) {
+ playing_audio_stream_players[i]->call(SNAME("stop"));
+ playing_audio_stream_players[i]->call(SNAME("set_stream"), Ref<AudioStream>());
+ }
+ playing_audio_stream_players.clear();
+}
+
+void AnimationTree::_clear_playing_caches() {
+ for (const TrackCache *E : playing_caches) {
+ if (ObjectDB::get_instance(E->object_id)) {
+ E->object->call(SNAME("stop"));
+ }
+ }
+ playing_caches.clear();
+}
+
static void _call_object(Object *p_object, const StringName &p_method, const Vector<Variant> &p_params, bool p_deferred) {
// Separate function to use alloca() more efficiently
const Variant **argptrs = (const Variant **)alloca(sizeof(const Variant **) * p_params.size());
@@ -1007,6 +1022,13 @@ void AnimationTree::_process_graph(double p_delta) {
TrackCacheBezier *t = static_cast<TrackCacheBezier *>(track);
t->value = t->init_value;
} break;
+ case Animation::TYPE_AUDIO: {
+ TrackCacheAudio *t = static_cast<TrackCacheAudio *>(track);
+ for (KeyValue<ObjectID, PlayingAudioTrackInfo> &L : t->playing_streams) {
+ PlayingAudioTrackInfo &track_info = L.value;
+ track_info.volume = 0.0;
+ }
+ } break;
default: {
} break;
}
@@ -1026,8 +1048,8 @@ void AnimationTree::_process_graph(double p_delta) {
bool seeked = as.seeked;
Animation::LoopedFlag looped_flag = as.looped_flag;
bool is_external_seeking = as.is_external_seeking;
+ bool backward = signbit(delta); // This flag is used by the root motion calculates or detecting the end of audio stream.
#ifndef _3D_DISABLED
- bool backward = signbit(delta); // This flag is required only for the root motion since it calculates the difference between the previous and current frames.
bool calc_root = !seeked || is_external_seeking;
#endif // _3D_DISABLED
@@ -1046,9 +1068,6 @@ void AnimationTree::_process_graph(double p_delta) {
int blend_idx = state.track_map[path];
ERR_CONTINUE(blend_idx < 0 || blend_idx >= state.track_count);
real_t blend = (*as.track_blends)[blend_idx] * weight;
- if (Math::is_zero_approx(blend)) {
- continue; // Nothing to blend.
- }
Animation::TrackType ttype = a->track_get_type(i);
if (ttype != Animation::TYPE_POSITION_3D && ttype != Animation::TYPE_ROTATION_3D && ttype != Animation::TYPE_SCALE_3D && track->type != ttype) {
@@ -1060,6 +1079,9 @@ void AnimationTree::_process_graph(double p_delta) {
switch (ttype) {
case Animation::TYPE_POSITION_3D: {
#ifndef _3D_DISABLED
+ if (Math::is_zero_approx(blend)) {
+ continue; // Nothing to blend.
+ }
TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track);
if (track->root_motion && calc_root) {
double prev_time = time - delta;
@@ -1151,6 +1173,9 @@ void AnimationTree::_process_graph(double p_delta) {
} break;
case Animation::TYPE_ROTATION_3D: {
#ifndef _3D_DISABLED
+ if (Math::is_zero_approx(blend)) {
+ continue; // Nothing to blend.
+ }
TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track);
if (track->root_motion && calc_root) {
double prev_time = time - delta;
@@ -1241,6 +1266,9 @@ void AnimationTree::_process_graph(double p_delta) {
} break;
case Animation::TYPE_SCALE_3D: {
#ifndef _3D_DISABLED
+ if (Math::is_zero_approx(blend)) {
+ continue; // Nothing to blend.
+ }
TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track);
if (track->root_motion && calc_root) {
double prev_time = time - delta;
@@ -1332,6 +1360,9 @@ void AnimationTree::_process_graph(double p_delta) {
} break;
case Animation::TYPE_BLEND_SHAPE: {
#ifndef _3D_DISABLED
+ if (Math::is_zero_approx(blend)) {
+ continue; // Nothing to blend.
+ }
TrackCacheBlendShape *t = static_cast<TrackCacheBlendShape *>(track);
float value;
@@ -1348,6 +1379,9 @@ void AnimationTree::_process_graph(double p_delta) {
#endif // _3D_DISABLED
} break;
case Animation::TYPE_VALUE: {
+ if (Math::is_zero_approx(blend)) {
+ continue; // Nothing to blend.
+ }
TrackCacheValue *t = static_cast<TrackCacheValue *>(track);
Animation::UpdateMode update_mode = a->value_track_get_update_mode(i);
@@ -1414,6 +1448,9 @@ void AnimationTree::_process_graph(double p_delta) {
continue;
}
#endif // TOOLS_ENABLED
+ if (Math::is_zero_approx(blend)) {
+ continue; // Nothing to blend.
+ }
TrackCacheMethod *t = static_cast<TrackCacheMethod *>(track);
if (seeked) {
@@ -1435,6 +1472,9 @@ void AnimationTree::_process_graph(double p_delta) {
}
} break;
case Animation::TYPE_BEZIER: {
+ if (Math::is_zero_approx(blend)) {
+ continue; // Nothing to blend.
+ }
TrackCacheBezier *t = static_cast<TrackCacheBezier *>(track);
real_t bezier = a->bezier_track_interpolate(i, time);
@@ -1445,110 +1485,87 @@ void AnimationTree::_process_graph(double p_delta) {
case Animation::TYPE_AUDIO: {
TrackCacheAudio *t = static_cast<TrackCacheAudio *>(track);
- if (seeked) {
- int idx = a->track_find_key(i, time, is_external_seeking ? Animation::FIND_MODE_NEAREST : Animation::FIND_MODE_EXACT);
- if (idx < 0) {
- continue;
- }
-
- Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx);
- if (!stream.is_valid()) {
- t->object->call(SNAME("stop"));
- t->playing = false;
- playing_caches.erase(t);
- } else {
- double start_ofs = a->audio_track_get_key_start_offset(i, idx);
- start_ofs += time - a->track_get_key_time(i, idx);
- double end_ofs = a->audio_track_get_key_end_offset(i, idx);
- double len = stream->get_length();
-
- if (start_ofs > len - end_ofs) {
- t->object->call(SNAME("stop"));
- t->playing = false;
- playing_caches.erase(t);
- continue;
- }
-
- t->object->call(SNAME("set_stream"), stream);
- t->object->call(SNAME("play"), start_ofs);
-
- t->playing = true;
- playing_caches.insert(t);
- if (len && end_ofs > 0) { //force an end at a time
- t->len = len - start_ofs - end_ofs;
- } else {
- t->len = 0;
- }
+ Node *asp = Object::cast_to<Node>(t->object);
+ if (!asp) {
+ t->playing_streams.clear();
+ continue;
+ }
- t->start = time;
+ ObjectID oid = a->get_instance_id();
+ if (!t->playing_streams.has(oid)) {
+ t->playing_streams[oid] = PlayingAudioTrackInfo();
+ }
+ // The end of audio should be observed even if the blend value is 0, build up the information and store to the cache for that.
+ PlayingAudioTrackInfo &track_info = t->playing_streams[oid];
+ track_info.length = a->get_length();
+ track_info.time = time;
+ track_info.volume += blend;
+ track_info.loop = a->get_loop_mode() != Animation::LOOP_NONE;
+ track_info.backward = backward;
+ track_info.use_blend = a->audio_track_is_use_blend(i);
+
+ HashMap<int, PlayingAudioStreamInfo> &map = track_info.stream_info;
+ // Find stream.
+ int idx = -1;
+ if (seeked) {
+ idx = a->track_find_key(i, time, is_external_seeking ? Animation::FIND_MODE_NEAREST : Animation::FIND_MODE_EXACT);
+ // Discard previous stream when seeking.
+ if (map.has(idx)) {
+ t->audio_stream_playback->stop_stream(map[idx].index);
+ map.erase(idx);
}
-
} else {
- //find stuff to play
List<int> to_play;
a->track_get_key_indices_in_range(i, time, delta, &to_play, looped_flag);
if (to_play.size()) {
- int idx = to_play.back()->get();
-
- Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx);
- if (!stream.is_valid()) {
- t->object->call(SNAME("stop"));
- t->playing = false;
- playing_caches.erase(t);
- } else {
- double start_ofs = a->audio_track_get_key_start_offset(i, idx);
- double end_ofs = a->audio_track_get_key_end_offset(i, idx);
- double len = stream->get_length();
-
- t->object->call(SNAME("set_stream"), stream);
- t->object->call(SNAME("play"), start_ofs);
-
- t->playing = true;
- playing_caches.insert(t);
- if (len && end_ofs > 0) { //force an end at a time
- t->len = len - start_ofs - end_ofs;
- } else {
- t->len = 0;
- }
+ idx = to_play.back()->get();
+ }
+ }
+ if (idx < 0) {
+ continue;
+ }
- t->start = time;
- }
- } else if (t->playing) {
- bool loop = a->get_loop_mode() != Animation::LOOP_NONE;
-
- bool stop = false;
-
- if (!loop) {
- if (delta > 0) {
- if (time < t->start) {
- stop = true;
- }
- } else if (delta < 0) {
- if (time > t->start) {
- stop = true;
- }
- }
- } else if (t->len > 0) {
- double len = t->start > time ? (a->get_length() - t->start) + time : time - t->start;
-
- if (len > t->len) {
- stop = true;
- }
+ // Play stream.
+ Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx);
+ if (stream.is_valid()) {
+ double start_ofs = a->audio_track_get_key_start_offset(i, idx);
+ double end_ofs = a->audio_track_get_key_end_offset(i, idx);
+ double len = stream->get_length();
+
+ if (t->object->call(SNAME("get_stream")) != t->audio_stream) {
+ t->object->call(SNAME("set_stream"), t->audio_stream);
+ t->audio_stream_playback.unref();
+ if (!playing_audio_stream_players.has(asp)) {
+ playing_audio_stream_players.push_back(asp);
}
+ }
+ if (!t->object->call(SNAME("is_playing"))) {
+ t->object->call(SNAME("play"));
+ }
+ if (!t->object->call(SNAME("has_stream_playback"))) {
+ t->audio_stream_playback.unref();
+ continue;
+ }
+ if (t->audio_stream_playback.is_null()) {
+ t->audio_stream_playback = t->object->call(SNAME("get_stream_playback"));
+ }
- if (stop) {
- //time to stop
- t->object->call(SNAME("stop"));
- t->playing = false;
- playing_caches.erase(t);
- }
+ PlayingAudioStreamInfo pasi;
+ pasi.index = t->audio_stream_playback->play_stream(stream, start_ofs);
+ pasi.start = time;
+ if (len && end_ofs > 0) { // Force an end at a time.
+ pasi.len = len - start_ofs - end_ofs;
+ } else {
+ pasi.len = 0;
}
+ map[idx] = pasi;
}
- real_t db = Math::linear_to_db(MAX(blend, 0.00001));
- t->object->call(SNAME("set_volume_db"), db);
} break;
case Animation::TYPE_ANIMATION: {
+ if (Math::is_zero_approx(blend)) {
+ continue; // Nothing to blend.
+ }
TrackCacheAnimation *t = static_cast<TrackCacheAnimation *>(track);
AnimationPlayer *player2 = Object::cast_to<AnimationPlayer>(t->object);
@@ -1694,6 +1711,64 @@ void AnimationTree::_process_graph(double p_delta) {
t->object->set_indexed(t->subpath, t->value);
} break;
+ case Animation::TYPE_AUDIO: {
+ TrackCacheAudio *t = static_cast<TrackCacheAudio *>(track);
+
+ // Audio ending process.
+ LocalVector<ObjectID> erase_maps;
+ for (KeyValue<ObjectID, PlayingAudioTrackInfo> &L : t->playing_streams) {
+ PlayingAudioTrackInfo &track_info = L.value;
+ float db = Math::linear_to_db(track_info.use_blend ? track_info.volume : 1.0);
+ LocalVector<int> erase_streams;
+ HashMap<int, PlayingAudioStreamInfo> &map = track_info.stream_info;
+ for (const KeyValue<int, PlayingAudioStreamInfo> &M : map) {
+ PlayingAudioStreamInfo pasi = M.value;
+
+ bool stop = false;
+ if (!t->audio_stream_playback->is_stream_playing(pasi.index)) {
+ stop = true;
+ }
+ if (!track_info.loop) {
+ if (!track_info.backward) {
+ if (track_info.time < pasi.start) {
+ stop = true;
+ }
+ } else if (track_info.backward) {
+ if (track_info.time > pasi.start) {
+ stop = true;
+ }
+ }
+ }
+ if (pasi.len > 0) {
+ double len = 0.0;
+ if (!track_info.backward) {
+ len = pasi.start > track_info.time ? (track_info.length - pasi.start) + track_info.time : track_info.time - pasi.start;
+ } else {
+ len = pasi.start < track_info.time ? (track_info.length - track_info.time) + pasi.start : pasi.start - track_info.time;
+ }
+ if (len > pasi.len) {
+ stop = true;
+ }
+ }
+ if (stop) {
+ // Time to stop.
+ t->audio_stream_playback->stop_stream(pasi.index);
+ erase_streams.push_back(M.key);
+ } else {
+ t->audio_stream_playback->set_stream_volume(pasi.index, db);
+ }
+ }
+ for (uint32_t erase_idx = 0; erase_idx < erase_streams.size(); erase_idx++) {
+ map.erase(erase_streams[erase_idx]);
+ }
+ if (map.size() == 0) {
+ erase_maps.push_back(L.key);
+ }
+ }
+ for (uint32_t erase_idx = 0; erase_idx < erase_maps.size(); erase_idx++) {
+ t->playing_streams.erase(erase_maps[erase_idx]);
+ }
+ } break;
default: {
} //the rest don't matter
}
@@ -1819,6 +1894,15 @@ NodePath AnimationTree::get_advance_expression_base_node() const {
return advance_expression_base_node;
}
+void AnimationTree::set_audio_max_polyphony(int p_audio_max_polyphony) {
+ ERR_FAIL_COND(p_audio_max_polyphony < 0 || p_audio_max_polyphony > 128);
+ audio_max_polyphony = p_audio_max_polyphony;
+}
+
+int AnimationTree::get_audio_max_polyphony() const {
+ return audio_max_polyphony;
+}
+
bool AnimationTree::is_state_invalid() const {
return !state.valid;
}
@@ -2034,6 +2118,9 @@ void AnimationTree::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_root_motion_track", "path"), &AnimationTree::set_root_motion_track);
ClassDB::bind_method(D_METHOD("get_root_motion_track"), &AnimationTree::get_root_motion_track);
+ ClassDB::bind_method(D_METHOD("set_audio_max_polyphony", "max_polyphony"), &AnimationTree::set_audio_max_polyphony);
+ ClassDB::bind_method(D_METHOD("get_audio_max_polyphony"), &AnimationTree::get_audio_max_polyphony);
+
ClassDB::bind_method(D_METHOD("get_root_motion_position"), &AnimationTree::get_root_motion_position);
ClassDB::bind_method(D_METHOD("get_root_motion_rotation"), &AnimationTree::get_root_motion_rotation);
ClassDB::bind_method(D_METHOD("get_root_motion_scale"), &AnimationTree::get_root_motion_scale);
@@ -2052,6 +2139,8 @@ void AnimationTree::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "active"), "set_active", "is_active");
ADD_PROPERTY(PropertyInfo(Variant::INT, "process_callback", PROPERTY_HINT_ENUM, "Physics,Idle,Manual"), "set_process_callback", "get_process_callback");
+ ADD_GROUP("Audio", "audio_");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "audio_max_polyphony", PROPERTY_HINT_RANGE, "1,127,1"), "set_audio_max_polyphony", "get_audio_max_polyphony");
ADD_GROUP("Root Motion", "root_motion_");
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "root_motion_track"), "set_root_motion_track", "get_root_motion_track");
diff --git a/scene/animation/animation_tree.h b/scene/animation/animation_tree.h
index ab538feb58..394a3a2237 100644
--- a/scene/animation/animation_tree.h
+++ b/scene/animation/animation_tree.h
@@ -35,6 +35,7 @@
#include "scene/3d/node_3d.h"
#include "scene/3d/skeleton_3d.h"
#include "scene/resources/animation.h"
+#include "scene/resources/audio_stream_polyphonic.h"
class AnimationNodeBlendTree;
class AnimationNodeStartState;
@@ -252,10 +253,26 @@ private:
}
};
- struct TrackCacheAudio : public TrackCache {
- bool playing = false;
+ struct PlayingAudioStreamInfo {
+ int64_t index = -1;
double start = 0.0;
double len = 0.0;
+ };
+
+ struct PlayingAudioTrackInfo {
+ HashMap<int, PlayingAudioStreamInfo> stream_info;
+ double length = 0.0;
+ double time = 0.0;
+ real_t volume = 0.0;
+ bool loop = false;
+ bool backward = false;
+ bool use_blend = false;
+ };
+
+ struct TrackCacheAudio : public TrackCache {
+ Ref<AudioStreamPolyphonic> audio_stream;
+ Ref<AudioStreamPlaybackPolyphonic> audio_stream_playback;
+ HashMap<ObjectID, PlayingAudioTrackInfo> playing_streams; // Animation resource RID & AudioTrack key index: PlayingAudioStreamInfo.
TrackCacheAudio() {
type = Animation::TYPE_AUDIO;
@@ -272,6 +289,7 @@ private:
HashMap<NodePath, TrackCache *> track_cache;
HashSet<TrackCache *> playing_caches;
+ Vector<Node *> playing_audio_stream_players;
Ref<AnimationNode> root;
NodePath advance_expression_base_node = NodePath(String("."));
@@ -279,6 +297,7 @@ private:
AnimationProcessCallback process_callback = ANIMATION_PROCESS_IDLE;
bool active = false;
NodePath animation_player;
+ int audio_max_polyphony = 32;
AnimationNode::State state;
bool cache_valid = false;
@@ -287,6 +306,8 @@ private:
void _setup_animation_player();
void _animation_player_changed();
void _clear_caches();
+ void _clear_playing_caches();
+ void _clear_audio_streams();
bool _update_caches(AnimationPlayer *player);
void _process_graph(double p_delta);
@@ -348,6 +369,9 @@ public:
void set_advance_expression_base_node(const NodePath &p_advance_expression_base_node);
NodePath get_advance_expression_base_node() const;
+ void set_audio_max_polyphony(int p_audio_max_polyphony);
+ int get_audio_max_polyphony() const;
+
PackedStringArray get_configuration_warnings() const override;
bool is_state_invalid() const;
diff --git a/scene/audio/audio_stream_player.cpp b/scene/audio/audio_stream_player.cpp
index d40fc10441..7533a56b59 100644
--- a/scene/audio/audio_stream_player.cpp
+++ b/scene/audio/audio_stream_player.cpp
@@ -307,6 +307,10 @@ void AudioStreamPlayer::_bus_layout_changed() {
notify_property_list_changed();
}
+bool AudioStreamPlayer::has_stream_playback() {
+ return !stream_playbacks.is_empty();
+}
+
Ref<AudioStreamPlayback> AudioStreamPlayer::get_stream_playback() {
ERR_FAIL_COND_V_MSG(stream_playbacks.is_empty(), Ref<AudioStreamPlayback>(), "Player is inactive. Call play() before requesting get_stream_playback().");
return stream_playbacks[stream_playbacks.size() - 1];
@@ -347,6 +351,7 @@ void AudioStreamPlayer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_max_polyphony", "max_polyphony"), &AudioStreamPlayer::set_max_polyphony);
ClassDB::bind_method(D_METHOD("get_max_polyphony"), &AudioStreamPlayer::get_max_polyphony);
+ ClassDB::bind_method(D_METHOD("has_stream_playback"), &AudioStreamPlayer::has_stream_playback);
ClassDB::bind_method(D_METHOD("get_stream_playback"), &AudioStreamPlayer::get_stream_playback);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream");
diff --git a/scene/audio/audio_stream_player.h b/scene/audio/audio_stream_player.h
index 5368391073..d1f6fca2ee 100644
--- a/scene/audio/audio_stream_player.h
+++ b/scene/audio/audio_stream_player.h
@@ -107,6 +107,7 @@ public:
void set_stream_paused(bool p_pause);
bool get_stream_paused() const;
+ bool has_stream_playback();
Ref<AudioStreamPlayback> get_stream_playback();
AudioStreamPlayer();
diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp
index cb3c865f05..bfbc92a8d4 100644
--- a/scene/resources/animation.cpp
+++ b/scene/resources/animation.cpp
@@ -127,6 +127,10 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) {
}
}
return true;
+ } else if (what == "use_blend") {
+ if (track_get_type(track) == TYPE_AUDIO) {
+ audio_track_set_use_blend(track, p_value);
+ }
} else if (what == "interp") {
track_set_interpolation_type(track, InterpolationType(p_value.operator int()));
} else if (what == "loop_wrap") {
@@ -528,7 +532,10 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const {
}
return true;
-
+ } else if (what == "use_blend") {
+ if (track_get_type(track) == TYPE_AUDIO) {
+ r_ret = audio_track_is_use_blend(track);
+ }
} else if (what == "interp") {
r_ret = track_get_interpolation_type(track);
} else if (what == "loop_wrap") {
@@ -834,6 +841,9 @@ void Animation::_get_property_list(List<PropertyInfo> *p_list) const {
p_list->push_back(PropertyInfo(Variant::BOOL, "tracks/" + itos(i) + "/loop_wrap", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
p_list->push_back(PropertyInfo(Variant::ARRAY, "tracks/" + itos(i) + "/keys", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
}
+ if (track_get_type(i) == TYPE_AUDIO) {
+ p_list->push_back(PropertyInfo(Variant::BOOL, "tracks/" + itos(i) + "/use_blend", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
+ }
}
}
@@ -3581,6 +3591,27 @@ real_t Animation::audio_track_get_key_end_offset(int p_track, int p_key) const {
return at->values[p_key].value.end_offset;
}
+void Animation::audio_track_set_use_blend(int p_track, bool p_enable) {
+ ERR_FAIL_INDEX(p_track, tracks.size());
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND(t->type != TYPE_AUDIO);
+
+ AudioTrack *at = static_cast<AudioTrack *>(t);
+
+ at->use_blend = p_enable;
+ emit_changed();
+}
+
+bool Animation::audio_track_is_use_blend(int p_track) const {
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), false);
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND_V(t->type != TYPE_AUDIO, false);
+
+ AudioTrack *at = static_cast<AudioTrack *>(t);
+
+ return at->use_blend;
+}
+
//
int Animation::animation_track_insert_key(int p_track, double p_time, const StringName &p_animation) {
@@ -3813,6 +3844,8 @@ void Animation::_bind_methods() {
ClassDB::bind_method(D_METHOD("audio_track_get_key_stream", "track_idx", "key_idx"), &Animation::audio_track_get_key_stream);
ClassDB::bind_method(D_METHOD("audio_track_get_key_start_offset", "track_idx", "key_idx"), &Animation::audio_track_get_key_start_offset);
ClassDB::bind_method(D_METHOD("audio_track_get_key_end_offset", "track_idx", "key_idx"), &Animation::audio_track_get_key_end_offset);
+ ClassDB::bind_method(D_METHOD("audio_track_set_use_blend", "track_idx", "enable"), &Animation::audio_track_set_use_blend);
+ ClassDB::bind_method(D_METHOD("audio_track_is_use_blend", "track_idx"), &Animation::audio_track_is_use_blend);
ClassDB::bind_method(D_METHOD("animation_track_insert_key", "track_idx", "time", "animation"), &Animation::animation_track_insert_key);
ClassDB::bind_method(D_METHOD("animation_track_set_key_animation", "track_idx", "key_idx", "animation"), &Animation::animation_track_set_key_animation);
diff --git a/scene/resources/animation.h b/scene/resources/animation.h
index 00a0761e0a..2c2ddb7095 100644
--- a/scene/resources/animation.h
+++ b/scene/resources/animation.h
@@ -213,6 +213,7 @@ private:
struct AudioTrack : public Track {
Vector<TKey<AudioKey>> values;
+ bool use_blend = true;
AudioTrack() {
type = TYPE_AUDIO;
@@ -447,6 +448,8 @@ public:
Ref<Resource> audio_track_get_key_stream(int p_track, int p_key) const;
real_t audio_track_get_key_start_offset(int p_track, int p_key) const;
real_t audio_track_get_key_end_offset(int p_track, int p_key) const;
+ void audio_track_set_use_blend(int p_track, bool p_enable);
+ bool audio_track_is_use_blend(int p_track) const;
int animation_track_insert_key(int p_track, double p_time, const StringName &p_animation);
void animation_track_set_key_animation(int p_track, int p_key, const StringName &p_animation);
diff --git a/scene/resources/shader.cpp b/scene/resources/shader.cpp
index a09dbd50cf..fd2be9ba22 100644
--- a/scene/resources/shader.cpp
+++ b/scene/resources/shader.cpp
@@ -55,6 +55,12 @@ void Shader::set_path(const String &p_path, bool p_take_over) {
RS::get_singleton()->shader_set_path_hint(shader, p_path);
}
+void Shader::set_include_path(const String &p_path) {
+ // Used only if the shader does not have a resource path set,
+ // for example during loading stage or when created by code.
+ include_path = p_path;
+}
+
void Shader::set_code(const String &p_code) {
for (Ref<ShaderInclude> E : include_dependencies) {
E->disconnect(SNAME("changed"), callable_mp(this, &Shader::_dependency_changed));
@@ -80,11 +86,15 @@ void Shader::set_code(const String &p_code) {
HashSet<Ref<ShaderInclude>> new_include_dependencies;
{
+ String path = get_path();
+ if (path.is_empty()) {
+ path = include_path;
+ }
// Preprocessor must run here and not in the server because:
// 1) Need to keep track of include dependencies at resource level
// 2) Server does not do interaction with Resource filetypes, this is a scene level feature.
ShaderPreprocessor preprocessor;
- preprocessor.preprocess(p_code, "", pp_code, nullptr, nullptr, nullptr, &new_include_dependencies);
+ preprocessor.preprocess(p_code, path, pp_code, nullptr, nullptr, nullptr, &new_include_dependencies);
}
// This ensures previous include resources are not freed and then re-loaded during parse (which would make compiling slower)
@@ -231,6 +241,7 @@ Ref<Resource> ResourceFormatLoaderShader::load(const String &p_path, const Strin
String str;
str.parse_utf8((const char *)buffer.ptr(), buffer.size());
+ shader->set_include_path(p_path);
shader->set_code(str);
if (r_error) {
diff --git a/scene/resources/shader.h b/scene/resources/shader.h
index 55608b6c11..ca889940ef 100644
--- a/scene/resources/shader.h
+++ b/scene/resources/shader.h
@@ -56,6 +56,7 @@ private:
Mode mode = MODE_SPATIAL;
HashSet<Ref<ShaderInclude>> include_dependencies;
String code;
+ String include_path;
HashMap<StringName, HashMap<int, Ref<Texture2D>>> default_textures;
@@ -72,6 +73,7 @@ public:
virtual Mode get_mode() const;
virtual void set_path(const String &p_path, bool p_take_over = false) override;
+ void set_include_path(const String &p_path);
void set_code(const String &p_code);
String get_code() const;
diff --git a/scene/resources/shader_include.cpp b/scene/resources/shader_include.cpp
index cd5e9861f7..68137cbec0 100644
--- a/scene/resources/shader_include.cpp
+++ b/scene/resources/shader_include.cpp
@@ -45,9 +45,14 @@ void ShaderInclude::set_code(const String &p_code) {
}
{
+ String path = get_path();
+ if (path.is_empty()) {
+ path = include_path;
+ }
+
String pp_code;
ShaderPreprocessor preprocessor;
- preprocessor.preprocess(p_code, "", pp_code, nullptr, nullptr, nullptr, &new_dependencies);
+ preprocessor.preprocess(p_code, path, pp_code, nullptr, nullptr, nullptr, &new_dependencies);
}
// This ensures previous include resources are not freed and then re-loaded during parse (which would make compiling slower)
@@ -64,6 +69,10 @@ String ShaderInclude::get_code() const {
return code;
}
+void ShaderInclude::set_include_path(const String &p_path) {
+ include_path = p_path;
+}
+
void ShaderInclude::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_code", "code"), &ShaderInclude::set_code);
ClassDB::bind_method(D_METHOD("get_code"), &ShaderInclude::get_code);
@@ -86,6 +95,7 @@ Ref<Resource> ResourceFormatLoaderShaderInclude::load(const String &p_path, cons
String str;
str.parse_utf8((const char *)buffer.ptr(), buffer.size());
+ shader_inc->set_include_path(p_path);
shader_inc->set_code(str);
if (r_error) {
diff --git a/scene/resources/shader_include.h b/scene/resources/shader_include.h
index 04f4f5cf84..a8949b327e 100644
--- a/scene/resources/shader_include.h
+++ b/scene/resources/shader_include.h
@@ -42,6 +42,7 @@ class ShaderInclude : public Resource {
private:
String code;
+ String include_path;
HashSet<Ref<ShaderInclude>> dependencies;
void _dependency_changed();
@@ -51,6 +52,8 @@ protected:
public:
void set_code(const String &p_text);
String get_code() const;
+
+ void set_include_path(const String &p_path);
};
class ResourceFormatLoaderShaderInclude : public ResourceFormatLoader {
diff --git a/servers/rendering/shader_preprocessor.cpp b/servers/rendering/shader_preprocessor.cpp
index 816d937e9f..b45a7c0db3 100644
--- a/servers/rendering/shader_preprocessor.cpp
+++ b/servers/rendering/shader_preprocessor.cpp
@@ -674,6 +674,11 @@ void ShaderPreprocessor::process_include(Tokenizer *p_tokenizer) {
return;
}
+ path = path.simplify_path();
+ if (path.is_relative_path()) {
+ path = state->current_filename.get_base_dir().path_join(path);
+ }
+
Ref<Resource> res = ResourceLoader::load(path);
if (res.is_null()) {
set_error(RTR("Shader include load failed. Does the shader include exist? Is there a cyclic dependency?"), line);