summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/classes/AudioStreamRandomizer.xml4
-rw-r--r--doc/classes/DisplayServer.xml4
-rw-r--r--doc/classes/InputEventWithModifiers.xml4
-rw-r--r--doc/classes/NavigationServer3D.xml9
-rw-r--r--doc/classes/OS.xml2
-rw-r--r--doc/classes/OptionButton.xml7
-rw-r--r--doc/classes/PopupMenu.xml4
-rw-r--r--doc/classes/ProjectSettings.xml6
-rw-r--r--drivers/gles3/rasterizer_canvas_gles3.cpp20
-rw-r--r--drivers/gles3/rasterizer_canvas_gles3.h2
-rw-r--r--editor/connections_dialog.cpp134
-rw-r--r--editor/editor_node.cpp21
-rw-r--r--editor/export/editor_export.cpp17
-rw-r--r--editor/export/editor_export_platform.h2
-rw-r--r--editor/export/project_export.cpp72
-rw-r--r--editor/export/project_export.h20
-rw-r--r--editor/plugins/gizmos/collision_shape_3d_gizmo_plugin.cpp2
-rw-r--r--editor/plugins/script_editor_plugin.cpp5
-rw-r--r--editor/plugins/script_editor_plugin.h3
-rw-r--r--editor/plugins/skeleton_3d_editor_plugin.cpp13
-rw-r--r--editor/plugins/tiles/tile_map_editor.cpp33
-rw-r--r--editor/plugins/tiles/tile_map_editor.h2
-rw-r--r--misc/extension_api_validation/4.0-stable.expected8
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp21
-rw-r--r--modules/gdscript/gdscript_parser.cpp2
-rw-r--r--modules/gdscript/gdscript_parser.h2
-rw-r--r--modules/gdscript/gdscript_tokenizer.cpp18
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_in_static_function.gd9
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_in_static_function.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/.editorconfig2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/mixed_indentation_on_blank_lines.gd20
-rw-r--r--modules/gdscript/tests/scripts/parser/features/mixed_indentation_on_blank_lines.out4
-rw-r--r--modules/gridmap/editor/grid_map_editor_plugin.cpp63
-rw-r--r--modules/navigation/godot_navigation_server.cpp34
-rw-r--r--modules/navigation/godot_navigation_server.h5
-rw-r--r--modules/navigation/nav_mesh_generator_3d.cpp125
-rw-r--r--modules/navigation/nav_mesh_generator_3d.h32
-rw-r--r--platform/android/export/export_plugin.cpp5
-rw-r--r--platform/ios/export/export_plugin.cpp5
-rw-r--r--platform/linuxbsd/freedesktop_portal_desktop.cpp350
-rw-r--r--platform/linuxbsd/freedesktop_portal_desktop.h30
-rw-r--r--platform/linuxbsd/joypad_linux.cpp24
-rw-r--r--platform/linuxbsd/os_linuxbsd.cpp21
-rw-r--r--platform/linuxbsd/os_linuxbsd.h2
-rw-r--r--platform/linuxbsd/x11/display_server_x11.cpp40
-rw-r--r--platform/linuxbsd/x11/display_server_x11.h2
-rw-r--r--platform/macos/export/export_plugin.cpp9
-rw-r--r--platform/web/SCsub3
-rw-r--r--platform/web/detect.py2
-rw-r--r--platform/web/export/export_plugin.cpp5
-rw-r--r--scene/3d/navigation_region_3d.cpp53
-rw-r--r--scene/3d/navigation_region_3d.h2
-rw-r--r--scene/animation/animation_blend_tree.cpp2
-rw-r--r--scene/gui/item_list.cpp25
-rw-r--r--scene/gui/menu_bar.cpp2
-rw-r--r--scene/gui/menu_button.cpp2
-rw-r--r--scene/gui/option_button.cpp22
-rw-r--r--scene/gui/option_button.h4
-rw-r--r--scene/gui/popup_menu.compat.inc46
-rw-r--r--scene/gui/popup_menu.cpp28
-rw-r--r--scene/gui/popup_menu.h11
-rw-r--r--servers/audio/audio_stream.h4
-rw-r--r--servers/navigation_server_3d.cpp4
-rw-r--r--servers/navigation_server_3d.h5
-rw-r--r--servers/navigation_server_3d_dummy.h5
-rw-r--r--servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp5
-rw-r--r--servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp4
-rw-r--r--servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp4
-rw-r--r--servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl86
-rw-r--r--servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp124
-rw-r--r--servers/rendering/renderer_rd/storage_rd/mesh_storage.h45
71 files changed, 1348 insertions, 366 deletions
diff --git a/doc/classes/AudioStreamRandomizer.xml b/doc/classes/AudioStreamRandomizer.xml
index 384c316000..12514fe03d 100644
--- a/doc/classes/AudioStreamRandomizer.xml
+++ b/doc/classes/AudioStreamRandomizer.xml
@@ -68,10 +68,10 @@
<member name="playback_mode" type="int" setter="set_playback_mode" getter="get_playback_mode" enum="AudioStreamRandomizer.PlaybackMode" default="0">
Controls how this AudioStreamRandomizer picks which AudioStream to play next.
</member>
- <member name="random_pitch" type="float" setter="set_random_pitch" getter="get_random_pitch" default="1.1">
+ <member name="random_pitch" type="float" setter="set_random_pitch" getter="get_random_pitch" default="1.0">
The intensity of random pitch variation. A value of 1 means no variation.
</member>
- <member name="random_volume_offset_db" type="float" setter="set_random_volume_offset_db" getter="get_random_volume_offset_db" default="5.0">
+ <member name="random_volume_offset_db" type="float" setter="set_random_volume_offset_db" getter="get_random_volume_offset_db" default="0.0">
The intensity of random volume variation. A value of 0 means no variation.
</member>
<member name="streams_count" type="int" setter="set_streams_count" getter="get_streams_count" default="0">
diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml
index 465855cc92..c2f81ba109 100644
--- a/doc/classes/DisplayServer.xml
+++ b/doc/classes/DisplayServer.xml
@@ -121,7 +121,9 @@
Displays OS native dialog for selecting files or directories in the file system.
Callbacks have the following arguments: [code]bool status, PackedStringArray selected_paths[/code].
[b]Note:[/b] This method is implemented if the display server has the [code]FEATURE_NATIVE_DIALOG[/code] feature.
- [b]Note:[/b] This method is implemented on Windows and macOS.
+ [b]Note:[/b] This method is implemented on Linux, Windows and macOS.
+ [b]Note:[/b] [param current_directory] might be ignored.
+ [b]Note:[/b] On Linux, [param show_hidden] is ignored.
[b]Note:[/b] On macOS, native file dialogs have no title.
[b]Note:[/b] On macOS, sandboxed apps will save security-scoped bookmarks to retain access to the opened folders across multiple sessions. Use [method OS.get_granted_permissions] to get a list of saved bookmarks.
</description>
diff --git a/doc/classes/InputEventWithModifiers.xml b/doc/classes/InputEventWithModifiers.xml
index 8bb3850dc2..5c018b0c56 100644
--- a/doc/classes/InputEventWithModifiers.xml
+++ b/doc/classes/InputEventWithModifiers.xml
@@ -19,7 +19,7 @@
<method name="is_command_or_control_pressed" qualifiers="const">
<return type="bool" />
<description>
- On macOS, returns [code]true[/code] if [kbd]Meta[/kbd] ([kbd]Command[/kbd]) is pressed.
+ On macOS, returns [code]true[/code] if [kbd]Meta[/kbd] ([kbd]Cmd[/kbd]) is pressed.
On other platforms, returns [code]true[/code] if [kbd]Ctrl[/kbd] is pressed.
</description>
</method>
@@ -29,7 +29,7 @@
State of the [kbd]Alt[/kbd] modifier.
</member>
<member name="command_or_control_autoremap" type="bool" setter="set_command_or_control_autoremap" getter="is_command_or_control_autoremap" default="false">
- Automatically use [kbd]Meta[/kbd] ([kbd]Command[/kbd]) on macOS and [kbd]Ctrl[/kbd] on other platforms. If [code]true[/code], [member ctrl_pressed] and [member meta_pressed] cannot be set.
+ Automatically use [kbd]Meta[/kbd] ([kbd]Cmd[/kbd]) on macOS and [kbd]Ctrl[/kbd] on other platforms. If [code]true[/code], [member ctrl_pressed] and [member meta_pressed] cannot be set.
</member>
<member name="ctrl_pressed" type="bool" setter="set_ctrl_pressed" getter="is_ctrl_pressed" default="false">
State of the [kbd]Ctrl[/kbd] modifier.
diff --git a/doc/classes/NavigationServer3D.xml b/doc/classes/NavigationServer3D.xml
index 86c605b8a3..b56b86f435 100644
--- a/doc/classes/NavigationServer3D.xml
+++ b/doc/classes/NavigationServer3D.xml
@@ -216,6 +216,15 @@
Bakes the provided [param navigation_mesh] with the data from the provided [param source_geometry_data]. After the process is finished the optional [param callback] will be called.
</description>
</method>
+ <method name="bake_from_source_geometry_data_async">
+ <return type="void" />
+ <param index="0" name="navigation_mesh" type="NavigationMesh" />
+ <param index="1" name="source_geometry_data" type="NavigationMeshSourceGeometryData3D" />
+ <param index="2" name="callback" type="Callable" default="Callable()" />
+ <description>
+ Bakes the provided [param navigation_mesh] with the data from the provided [param source_geometry_data] as an async task running on a background thread. After the process is finished the optional [param callback] will be called.
+ </description>
+ </method>
<method name="free_rid">
<return type="void" />
<param index="0" name="rid" type="RID" />
diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml
index 03169d390a..d4a6b57c58 100644
--- a/doc/classes/OS.xml
+++ b/doc/classes/OS.xml
@@ -538,7 +538,7 @@
<return type="bool" />
<description>
Returns [code]true[/code] if application is running in the sandbox.
- [b]Note:[/b] This method is implemented on macOS.
+ [b]Note:[/b] This method is implemented on macOS and Linux.
</description>
</method>
<method name="is_stdout_verbose" qualifiers="const">
diff --git a/doc/classes/OptionButton.xml b/doc/classes/OptionButton.xml
index 7737754fc6..d2302fc27f 100644
--- a/doc/classes/OptionButton.xml
+++ b/doc/classes/OptionButton.xml
@@ -148,6 +148,13 @@
Passing [code]-1[/code] as the index deselects any currently selected item.
</description>
</method>
+ <method name="set_disable_shortcuts">
+ <return type="void" />
+ <param index="0" name="disabled" type="bool" />
+ <description>
+ If [code]true[/code], shortcuts are disabled and cannot be used to trigger the button.
+ </description>
+ </method>
<method name="set_item_disabled">
<return type="void" />
<param index="0" name="idx" type="int" />
diff --git a/doc/classes/PopupMenu.xml b/doc/classes/PopupMenu.xml
index daf3163842..b72e65e63a 100644
--- a/doc/classes/PopupMenu.xml
+++ b/doc/classes/PopupMenu.xml
@@ -96,9 +96,11 @@
<param index="1" name="shortcut" type="Shortcut" />
<param index="2" name="id" type="int" default="-1" />
<param index="3" name="global" type="bool" default="false" />
+ <param index="4" name="allow_echo" type="bool" default="false" />
<description>
Adds a new item and assigns the specified [Shortcut] and icon [param texture] to it. Sets the label of the checkbox to the [Shortcut]'s name.
An [param id] can optionally be provided. If no [param id] is provided, one will be created from the index.
+ If [param allow_echo] is [code]true[/code], the shortcut can be activated with echo events.
</description>
</method>
<method name="add_item">
@@ -161,9 +163,11 @@
<param index="0" name="shortcut" type="Shortcut" />
<param index="1" name="id" type="int" default="-1" />
<param index="2" name="global" type="bool" default="false" />
+ <param index="3" name="allow_echo" type="bool" default="false" />
<description>
Adds a [Shortcut].
An [param id] can optionally be provided. If no [param id] is provided, one will be created from the index.
+ If [param allow_echo] is [code]true[/code], the shortcut can be activated with echo events.
</description>
</method>
<method name="add_submenu_item">
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index 1be69052e4..4407176fd2 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -1995,6 +1995,12 @@
<member name="navigation/avoidance/thread_model/avoidance_use_multiple_threads" type="bool" setter="" getter="" default="true">
If enabled the avoidance calculations use multiple threads.
</member>
+ <member name="navigation/baking/thread_model/baking_use_high_priority_threads" type="bool" setter="" getter="" default="true">
+ If enabled and async navmesh baking uses multiple threads the threads run with high priority.
+ </member>
+ <member name="navigation/baking/thread_model/baking_use_multiple_threads" type="bool" setter="" getter="" default="true">
+ If enabled the async navmesh baking uses multiple threads.
+ </member>
<member name="network/limits/debugger/max_chars_per_second" type="int" setter="" getter="" default="32768">
Maximum number of characters allowed to send as output from the debugger. Over this value, content is dropped. This helps not to stall the debugger connection.
</member>
diff --git a/drivers/gles3/rasterizer_canvas_gles3.cpp b/drivers/gles3/rasterizer_canvas_gles3.cpp
index 86405fa3f0..9f8d9164ce 100644
--- a/drivers/gles3/rasterizer_canvas_gles3.cpp
+++ b/drivers/gles3/rasterizer_canvas_gles3.cpp
@@ -2048,16 +2048,26 @@ void RasterizerCanvasGLES3::occluder_polygon_set_cull_mode(RID p_occluder, RS::C
void RasterizerCanvasGLES3::set_shadow_texture_size(int p_size) {
GLES3::Config *config = GLES3::Config::get_singleton();
p_size = nearest_power_of_2_templated(p_size);
- if (p_size == state.shadow_texture_size) {
- return;
- }
if (p_size > config->max_texture_size) {
p_size = config->max_texture_size;
WARN_PRINT("Attempting to set CanvasItem shadow atlas size to " + itos(p_size) + " which is beyond limit of " + itos(config->max_texture_size) + "supported by hardware.");
}
+ if (p_size == state.shadow_texture_size) {
+ return;
+ }
state.shadow_texture_size = p_size;
+
+ if (state.shadow_fb != 0) {
+ glDeleteFramebuffers(1, &state.shadow_fb);
+ GLES3::Utilities::get_singleton()->texture_free_data(state.shadow_texture);
+ glDeleteRenderbuffers(1, &state.shadow_depth_buffer);
+ state.shadow_fb = 0;
+ state.shadow_texture = 0;
+ state.shadow_depth_buffer = 0;
+ }
+ _update_shadow_atlas();
}
bool RasterizerCanvasGLES3::free(RID p_rid) {
@@ -2442,6 +2452,7 @@ RendererCanvasRender::PolygonID RasterizerCanvasGLES3::request_polygon(const Vec
return id;
}
+
void RasterizerCanvasGLES3::free_polygon(PolygonID p_polygon) {
PolygonBuffers *pb_ptr = polygon_buffers.polygons.getptr(p_polygon);
ERR_FAIL_COND(!pb_ptr);
@@ -2521,6 +2532,8 @@ RasterizerCanvasGLES3::RasterizerCanvasGLES3() {
GLES3::MaterialStorage *material_storage = GLES3::MaterialStorage::get_singleton();
GLES3::Config *config = GLES3::Config::get_singleton();
+ glVertexAttrib4f(RS::ARRAY_COLOR, 1.0, 1.0, 1.0, 1.0);
+
polygon_buffers.last_id = 1;
// quad buffer
{
@@ -2700,6 +2713,7 @@ RasterizerCanvasGLES3::RasterizerCanvasGLES3() {
GLES3::MaterialStorage::get_singleton()->shaders.canvas_shader.initialize(global_defines, 1);
data.canvas_shader_default_version = GLES3::MaterialStorage::get_singleton()->shaders.canvas_shader.version_create();
+ state.shadow_texture_size = GLOBAL_GET("rendering/2d/shadow_atlas/size");
shadow_render.shader.initialize();
shadow_render.shader_version = shadow_render.shader.version_create();
diff --git a/drivers/gles3/rasterizer_canvas_gles3.h b/drivers/gles3/rasterizer_canvas_gles3.h
index 2eac5f57e1..c1b3e20e33 100644
--- a/drivers/gles3/rasterizer_canvas_gles3.h
+++ b/drivers/gles3/rasterizer_canvas_gles3.h
@@ -192,7 +192,7 @@ public:
GLuint index_buffer = 0;
int count = 0;
bool color_disabled = false;
- Color color;
+ Color color = Color(1.0, 1.0, 1.0, 1.0);
};
struct {
diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp
index 3965dcd198..d4aebfc7a6 100644
--- a/editor/connections_dialog.cpp
+++ b/editor/connections_dialog.cpp
@@ -31,6 +31,7 @@
#include "connections_dialog.h"
#include "core/config/project_settings.h"
+#include "core/templates/hash_set.h"
#include "editor/doc_tools.h"
#include "editor/editor_help.h"
#include "editor/editor_inspector.h"
@@ -1239,60 +1240,102 @@ void ConnectionsDock::update_tree() {
}
TreeItem *root = tree->create_item();
+ DocTools *doc_data = EditorHelp::get_doc_data();
+ EditorData &editor_data = EditorNode::get_editor_data();
+ StringName native_base = selected_node->get_class();
+ Ref<Script> script_base = selected_node->get_script();
+
+ while (native_base != StringName()) {
+ String class_name;
+ String doc_class_name;
+ Ref<Texture2D> class_icon;
+ List<MethodInfo> class_signals;
+ const DocData::ClassDoc *class_doc = nullptr;
+
+ if (script_base.is_valid()) {
+ // Try script global name.
+ class_name = script_base->get_global_name();
+ if (!class_name.is_empty()) {
+ HashMap<String, DocData::ClassDoc>::ConstIterator F = doc_data->class_list.find(class_name);
+ if (F) {
+ class_doc = &F->value;
+ doc_class_name = class_name;
+ }
+ }
- List<MethodInfo> node_signals;
-
- selected_node->get_signal_list(&node_signals);
+ // Try script path.
+ if (class_name.is_empty()) {
+ class_name = script_base->get_path().get_file();
+ }
+ if (!class_doc) {
+ doc_class_name = script_base->get_path().trim_prefix("res://").quote();
+ HashMap<String, DocData::ClassDoc>::ConstIterator F = doc_data->class_list.find(doc_class_name);
+ if (F) {
+ class_doc = &F->value;
+ }
+ }
- bool did_script = false;
- StringName base = selected_node->get_class();
+ class_icon = editor_data.get_script_icon(script_base);
+ if (class_icon.is_null() && has_theme_icon(native_base, SNAME("EditorIcons"))) {
+ class_icon = get_theme_icon(native_base, SNAME("EditorIcons"));
+ }
- while (base) {
- List<MethodInfo> node_signals2;
- Ref<Texture2D> icon;
- String name;
+ script_base->get_script_signal_list(&class_signals);
- if (!did_script) {
- // Get script signals (including signals from any base scripts).
- Ref<Script> scr = selected_node->get_script();
- if (scr.is_valid()) {
- scr->get_script_signal_list(&node_signals2);
- if (scr->get_path().is_resource_file()) {
- name = scr->get_path().get_file();
- } else {
- name = scr->get_class();
+ // TODO: Core: Add optional parameter to ignore base classes (no_inheritance like in ClassDB).
+ Ref<Script> base = script_base->get_base_script();
+ if (base.is_valid()) {
+ List<MethodInfo> base_signals;
+ base->get_script_signal_list(&base_signals);
+ HashSet<String> base_signal_names;
+ for (List<MethodInfo>::Element *F = base_signals.front(); F; F = F->next()) {
+ base_signal_names.insert(F->get().name);
}
-
- if (has_theme_icon(scr->get_class(), SNAME("EditorIcons"))) {
- icon = get_theme_icon(scr->get_class(), SNAME("EditorIcons"));
+ for (List<MethodInfo>::Element *F = class_signals.front(); F; F = F->next()) {
+ if (base_signal_names.has(F->get().name)) {
+ class_signals.erase(F);
+ }
}
}
+
+ script_base = base;
} else {
- ClassDB::get_signal_list(base, &node_signals2, true);
- if (has_theme_icon(base, SNAME("EditorIcons"))) {
- icon = get_theme_icon(base, SNAME("EditorIcons"));
+ class_name = native_base;
+
+ if (has_theme_icon(native_base, SNAME("EditorIcons"))) {
+ class_icon = get_theme_icon(native_base, SNAME("EditorIcons"));
}
- name = base;
+
+ ClassDB::get_signal_list(native_base, &class_signals, true);
+
+ HashMap<String, DocData::ClassDoc>::ConstIterator F = doc_data->class_list.find(class_name);
+ if (F) {
+ class_doc = &F->value;
+ doc_class_name = class_name;
+ }
+
+ native_base = ClassDB::get_parent_class(native_base);
}
- if (icon.is_null()) {
- icon = get_theme_icon(SNAME("Object"), SNAME("EditorIcons"));
+ if (class_icon.is_null()) {
+ class_icon = get_theme_icon(SNAME("Object"), SNAME("EditorIcons"));
}
TreeItem *section_item = nullptr;
// Create subsections.
- if (node_signals2.size()) {
+ if (!class_signals.is_empty()) {
+ class_signals.sort();
+
section_item = tree->create_item(root);
- section_item->set_text(0, name);
- section_item->set_icon(0, icon);
+ section_item->set_text(0, class_name);
+ section_item->set_icon(0, class_icon);
section_item->set_selectable(0, false);
section_item->set_editable(0, false);
section_item->set_custom_bg_color(0, get_theme_color(SNAME("prop_subsection"), SNAME("Editor")));
- node_signals2.sort();
}
- for (MethodInfo &mi : node_signals2) {
+ for (MethodInfo &mi : class_signals) {
const StringName signal_name = mi.name;
if (!search_box->get_text().is_subsequence_ofn(signal_name)) {
continue;
@@ -1320,9 +1363,9 @@ void ConnectionsDock::update_tree() {
String descr;
bool found = false;
- HashMap<StringName, HashMap<StringName, String>>::Iterator G = descr_cache.find(base);
+ HashMap<StringName, HashMap<StringName, String>>::ConstIterator G = descr_cache.find(doc_class_name);
if (G) {
- HashMap<StringName, String>::Iterator F = G->value.find(signal_name);
+ HashMap<StringName, String>::ConstIterator F = G->value.find(signal_name);
if (F) {
found = true;
descr = F->value;
@@ -1330,22 +1373,15 @@ void ConnectionsDock::update_tree() {
}
if (!found) {
- DocTools *dd = EditorHelp::get_doc_data();
- HashMap<String, DocData::ClassDoc>::Iterator F = dd->class_list.find(base);
- while (F && descr.is_empty()) {
- for (int i = 0; i < F->value.signals.size(); i++) {
- if (F->value.signals[i].name == signal_name.operator String()) {
- descr = DTR(F->value.signals[i].description);
+ if (class_doc) {
+ for (int i = 0; i < class_doc->signals.size(); i++) {
+ if (class_doc->signals[i].name == signal_name.operator String()) {
+ descr = DTR(class_doc->signals[i].description);
break;
}
}
- if (!F->value.inherits.is_empty()) {
- F = dd->class_list.find(F->value.inherits);
- } else {
- break;
- }
}
- descr_cache[base][signal_name] = descr;
+ descr_cache[doc_class_name][signal_name] = descr;
}
// "::" separators used in make_custom_tooltip for formatting.
@@ -1400,12 +1436,6 @@ void ConnectionsDock::update_tree() {
}
}
}
-
- if (!did_script) {
- did_script = true;
- } else {
- base = ClassDB::get_parent_class(base);
- }
}
connect_button->set_text(TTR("Connect..."));
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index f207418f71..c6f5a6082b 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -2357,7 +2357,7 @@ void EditorNode::_edit_current(bool p_skip_foreign) {
SceneTreeDock::get_singleton()->set_selected(current_node);
InspectorDock::get_singleton()->update(current_node);
if (!inspector_only && !skip_main_plugin) {
- skip_main_plugin = stay_in_script_editor_on_node_selected && ScriptEditor::get_singleton()->is_visible_in_tree();
+ skip_main_plugin = stay_in_script_editor_on_node_selected && !ScriptEditor::get_singleton()->is_editor_floating() && ScriptEditor::get_singleton()->is_visible_in_tree();
}
} else {
NodeDock::get_singleton()->set_node(nullptr);
@@ -5681,12 +5681,13 @@ void EditorNode::_scene_tab_exit() {
}
void EditorNode::_scene_tab_input(const Ref<InputEvent> &p_input) {
+ int tab_id = scene_tabs->get_hovered_tab();
Ref<InputEventMouseButton> mb = p_input;
if (mb.is_valid()) {
- if (scene_tabs->get_hovered_tab() >= 0) {
+ if (tab_id >= 0) {
if (mb->get_button_index() == MouseButton::MIDDLE && mb->is_pressed()) {
- _scene_tab_closed(scene_tabs->get_hovered_tab());
+ _scene_tab_closed(tab_id);
}
} else if (mb->get_button_index() == MouseButton::LEFT && mb->is_double_click()) {
int tab_buttons = 0;
@@ -5705,12 +5706,12 @@ void EditorNode::_scene_tab_input(const Ref<InputEvent> &p_input) {
scene_tabs_context_menu->reset_size();
scene_tabs_context_menu->add_shortcut(ED_GET_SHORTCUT("editor/new_scene"), FILE_NEW_SCENE);
- if (scene_tabs->get_hovered_tab() >= 0) {
+ if (tab_id >= 0) {
scene_tabs_context_menu->add_shortcut(ED_GET_SHORTCUT("editor/save_scene"), FILE_SAVE_SCENE);
scene_tabs_context_menu->add_shortcut(ED_GET_SHORTCUT("editor/save_scene_as"), FILE_SAVE_AS_SCENE);
}
scene_tabs_context_menu->add_shortcut(ED_GET_SHORTCUT("editor/save_all_scenes"), FILE_SAVE_ALL_SCENES);
- if (scene_tabs->get_hovered_tab() >= 0) {
+ if (tab_id >= 0) {
scene_tabs_context_menu->add_separator();
scene_tabs_context_menu->add_item(TTR("Show in FileSystem"), FILE_SHOW_IN_FILESYSTEM);
scene_tabs_context_menu->add_item(TTR("Play This Scene"), FILE_RUN_SCENE);
@@ -5724,7 +5725,13 @@ void EditorNode::_scene_tab_input(const Ref<InputEvent> &p_input) {
scene_tabs_context_menu->set_item_disabled(scene_tabs_context_menu->get_item_index(FILE_OPEN_PREV), true);
}
scene_tabs_context_menu->add_item(TTR("Close Other Tabs"), FILE_CLOSE_OTHERS);
+ if (editor_data.get_edited_scene_count() <= 1) {
+ scene_tabs_context_menu->set_item_disabled(file_menu->get_item_index(FILE_CLOSE_OTHERS), true);
+ }
scene_tabs_context_menu->add_item(TTR("Close Tabs to the Right"), FILE_CLOSE_RIGHT);
+ if (editor_data.get_edited_scene_count() == tab_id + 1) {
+ scene_tabs_context_menu->set_item_disabled(file_menu->get_item_index(FILE_CLOSE_RIGHT), true);
+ }
scene_tabs_context_menu->add_item(TTR("Close All Tabs"), FILE_CLOSE_ALL);
}
scene_tabs_context_menu->set_position(scene_tabs->get_screen_position() + mb->get_position());
@@ -7472,8 +7479,8 @@ EditorNode::EditorNode() {
export_as_menu->connect("index_pressed", callable_mp(this, &EditorNode::_export_as_menu_option));
file_menu->add_separator();
- file_menu->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO, true);
- file_menu->add_shortcut(ED_GET_SHORTCUT("ui_redo"), EDIT_REDO, true);
+ file_menu->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO, true, true);
+ file_menu->add_shortcut(ED_GET_SHORTCUT("ui_redo"), EDIT_REDO, true, true);
file_menu->add_separator();
file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/reload_saved_scene", TTR("Reload Saved Scene")), EDIT_RELOAD_SAVED_SCENE);
diff --git a/editor/export/editor_export.cpp b/editor/export/editor_export.cpp
index 6c4fb480d7..670fd0a06d 100644
--- a/editor/export/editor_export.cpp
+++ b/editor/export/editor_export.cpp
@@ -32,7 +32,6 @@
#include "core/config/project_settings.h"
#include "core/io/config_file.h"
-#include "editor/import/resource_importer_texture_settings.h"
EditorExport *EditorExport::singleton = nullptr;
@@ -138,22 +137,6 @@ void EditorExport::add_export_preset(const Ref<EditorExportPreset> &p_preset, in
}
}
-String EditorExportPlatform::test_etc2() const {
- if (!ResourceImporterTextureSettings::should_import_etc2_astc()) {
- return TTR("Target platform requires 'ETC2/ASTC' texture compression. Enable 'Import ETC2 ASTC' in Project Settings.") + "\n";
- }
-
- return String();
-}
-
-String EditorExportPlatform::test_bc() const {
- if (!ResourceImporterTextureSettings::should_import_s3tc_bptc()) {
- return TTR("Target platform requires 'S3TC/BPTC' texture compression. Enable 'Import S3TC BPTC' in Project Settings.") + "\n";
- }
-
- return String();
-}
-
int EditorExport::get_export_preset_count() const {
return export_presets.size();
}
diff --git a/editor/export/editor_export_platform.h b/editor/export/editor_export_platform.h
index 763836e3ec..5f5702026c 100644
--- a/editor/export/editor_export_platform.h
+++ b/editor/export/editor_export_platform.h
@@ -237,8 +237,6 @@ public:
virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) { return OK; }
virtual Ref<Texture2D> get_run_icon() const { return get_logo(); }
- String test_etc2() const;
- String test_bc() const;
bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const;
virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const = 0;
virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const = 0;
diff --git a/editor/export/project_export.cpp b/editor/export/project_export.cpp
index 7c7762e0fd..61f875713f 100644
--- a/editor/export/project_export.cpp
+++ b/editor/export/project_export.cpp
@@ -39,6 +39,7 @@
#include "editor/editor_settings.h"
#include "editor/export/editor_export.h"
#include "editor/gui/editor_file_dialog.h"
+#include "editor/import/resource_importer_texture_settings.h"
#include "scene/gui/check_box.h"
#include "scene/gui/check_button.h"
#include "scene/gui/item_list.h"
@@ -51,6 +52,44 @@
#include "scene/gui/texture_rect.h"
#include "scene/gui/tree.h"
+void ProjectExportTextureFormatError::_on_fix_texture_format_pressed() {
+ ProjectSettings::get_singleton()->set_setting(setting_identifier, true);
+ ProjectSettings::get_singleton()->save();
+ EditorFileSystem::get_singleton()->scan_changes();
+ emit_signal("texture_format_enabled");
+}
+
+void ProjectExportTextureFormatError::_bind_methods() {
+ ADD_SIGNAL(MethodInfo("texture_format_enabled"));
+}
+
+void ProjectExportTextureFormatError::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE:
+ case NOTIFICATION_THEME_CHANGED: {
+ texture_format_error_label->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), SNAME("Editor")));
+ } break;
+ }
+}
+
+void ProjectExportTextureFormatError::show_for_texture_format(const String &p_friendly_name, const String &p_setting_identifier) {
+ texture_format_error_label->set_text(vformat(TTR("Target platform requires '%s' texture compression. Enable 'Import %s' to fix."), p_friendly_name, p_friendly_name.replace("/", " ")));
+ setting_identifier = p_setting_identifier;
+ show();
+}
+
+ProjectExportTextureFormatError::ProjectExportTextureFormatError() {
+ // Set up the label.
+ texture_format_error_label = memnew(Label);
+ add_child(texture_format_error_label);
+ // Set up the fix button.
+ fix_texture_format_button = memnew(LinkButton);
+ fix_texture_format_button->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
+ fix_texture_format_button->set_text(TTR("Fix Import"));
+ add_child(fix_texture_format_button);
+ fix_texture_format_button->connect("pressed", callable_mp(this, &ProjectExportTextureFormatError::_on_fix_texture_format_pressed));
+}
+
void ProjectExportDialog::_theme_changed() {
duplicate_preset->set_icon(presets->get_theme_icon(SNAME("Duplicate"), SNAME("EditorIcons")));
delete_preset->set_icon(presets->get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")));
@@ -300,6 +339,14 @@ void ProjectExportDialog::_edit_preset(int p_index) {
_update_export_all();
child_controls_changed();
+ if ((feature_set.has("s3tc") || feature_set.has("bptc")) && !ResourceImporterTextureSettings::should_import_s3tc_bptc()) {
+ export_texture_format_error->show_for_texture_format("S3TC/BPTC", "rendering/textures/vram_compression/import_s3tc_bptc");
+ } else if ((feature_set.has("etc2") || feature_set.has("astc")) && !ResourceImporterTextureSettings::should_import_etc2_astc()) {
+ export_texture_format_error->show_for_texture_format("ETC2/ASTC", "rendering/textures/vram_compression/import_etc2_astc");
+ } else {
+ export_texture_format_error->hide();
+ }
+
String enc_in_filters_str = current->get_enc_in_filter();
String enc_ex_filters_str = current->get_enc_ex_filter();
if (!updating_enc_filters) {
@@ -343,29 +390,29 @@ void ProjectExportDialog::_update_feature_list() {
Ref<EditorExportPreset> current = get_current_preset();
ERR_FAIL_COND(current.is_null());
- RBSet<String> fset;
- List<String> features;
+ List<String> features_list;
- current->get_platform()->get_platform_features(&features);
- current->get_platform()->get_preset_features(current, &features);
+ current->get_platform()->get_platform_features(&features_list);
+ current->get_platform()->get_preset_features(current, &features_list);
String custom = current->get_custom_features();
Vector<String> custom_list = custom.split(",");
for (int i = 0; i < custom_list.size(); i++) {
String f = custom_list[i].strip_edges();
if (!f.is_empty()) {
- features.push_back(f);
+ features_list.push_back(f);
}
}
- for (const String &E : features) {
- fset.insert(E);
+ feature_set.clear();
+ for (const String &E : features_list) {
+ feature_set.insert(E);
}
custom_feature_display->clear();
String text;
bool first = true;
- for (const String &E : fset) {
+ for (const String &E : feature_set) {
if (!first) {
text += ", ";
} else {
@@ -1356,6 +1403,13 @@ ProjectExportDialog::ProjectExportDialog() {
add_child(export_pck_zip);
export_pck_zip->connect("file_selected", callable_mp(this, &ProjectExportDialog::_export_pck_zip_selected));
+ // Export warnings and errors bottom section.
+
+ export_texture_format_error = memnew(ProjectExportTextureFormatError);
+ main_vb->add_child(export_texture_format_error);
+ export_texture_format_error->hide();
+ export_texture_format_error->connect("texture_format_enabled", callable_mp(this, &ProjectExportDialog::_update_current_preset));
+
export_error = memnew(Label);
main_vb->add_child(export_error);
export_error->hide();
@@ -1390,6 +1444,8 @@ ProjectExportDialog::ProjectExportDialog() {
export_templates_error->add_child(download_templates);
download_templates->connect("pressed", callable_mp(this, &ProjectExportDialog::_open_export_template_manager));
+ // Export project file dialog.
+
export_project = memnew(EditorFileDialog);
export_project->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
add_child(export_project);
diff --git a/editor/export/project_export.h b/editor/export/project_export.h
index e53e393432..4f76167e22 100644
--- a/editor/export/project_export.h
+++ b/editor/export/project_export.h
@@ -41,6 +41,7 @@ class EditorFileSystemDirectory;
class EditorInspector;
class EditorPropertyPath;
class ItemList;
+class LinkButton;
class MenuButton;
class OptionButton;
class PopupMenu;
@@ -49,6 +50,23 @@ class TabContainer;
class Tree;
class TreeItem;
+class ProjectExportTextureFormatError : public HBoxContainer {
+ GDCLASS(ProjectExportTextureFormatError, HBoxContainer);
+
+ Label *texture_format_error_label = nullptr;
+ LinkButton *fix_texture_format_button = nullptr;
+ String setting_identifier;
+ void _on_fix_texture_format_pressed();
+
+protected:
+ static void _bind_methods();
+ void _notification(int p_what);
+
+public:
+ void show_for_texture_format(const String &p_friendly_name, const String &p_setting_identifier);
+ ProjectExportTextureFormatError();
+};
+
class ProjectExportDialog : public ConfirmationDialog {
GDCLASS(ProjectExportDialog, ConfirmationDialog);
@@ -86,12 +104,14 @@ private:
Button *export_all_button = nullptr;
AcceptDialog *export_all_dialog = nullptr;
+ RBSet<String> feature_set;
LineEdit *custom_features = nullptr;
RichTextLabel *custom_feature_display = nullptr;
LineEdit *script_key = nullptr;
Label *script_key_error = nullptr;
+ ProjectExportTextureFormatError *export_texture_format_error = nullptr;
Label *export_error = nullptr;
Label *export_warning = nullptr;
HBoxContainer *export_templates_error = nullptr;
diff --git a/editor/plugins/gizmos/collision_shape_3d_gizmo_plugin.cpp b/editor/plugins/gizmos/collision_shape_3d_gizmo_plugin.cpp
index b967cb7ccd..3becc9c9fd 100644
--- a/editor/plugins/gizmos/collision_shape_3d_gizmo_plugin.cpp
+++ b/editor/plugins/gizmos/collision_shape_3d_gizmo_plugin.cpp
@@ -301,7 +301,7 @@ void CollisionShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
ur->create_action(TTR("Change Box Shape Size"));
ur->add_do_method(ss.ptr(), "set_size", ss->get_size());
- ur->add_do_method(cs, "set_position", cs->get_global_position());
+ ur->add_do_method(cs, "set_global_position", cs->get_global_position());
ur->add_undo_method(ss.ptr(), "set_size", p_restore);
ur->add_undo_method(cs, "set_global_position", initial_transform.get_origin());
ur->commit_action();
diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp
index 67df349e48..eb0e20a12e 100644
--- a/editor/plugins/script_editor_plugin.cpp
+++ b/editor/plugins/script_editor_plugin.cpp
@@ -1791,6 +1791,10 @@ void ScriptEditor::ensure_select_current() {
_update_selected_editor_menu();
}
+bool ScriptEditor::is_editor_floating() {
+ return is_floating;
+}
+
void ScriptEditor::_find_scripts(Node *p_base, Node *p_current, HashSet<Ref<Script>> &used) {
if (p_current != p_base && p_current->get_owner() != p_base) {
return;
@@ -3747,6 +3751,7 @@ void ScriptEditor::_on_find_in_files_modified_files(PackedStringArray paths) {
void ScriptEditor::_window_changed(bool p_visible) {
make_floating->set_visible(!p_visible);
+ is_floating = p_visible;
}
void ScriptEditor::_filter_scripts_text_changed(const String &p_newtext) {
diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h
index 198aaa6c4e..a33c877d2d 100644
--- a/editor/plugins/script_editor_plugin.h
+++ b/editor/plugins/script_editor_plugin.h
@@ -274,6 +274,7 @@ class ScriptEditor : public PanelContainer {
Button *help_search = nullptr;
Button *site_search = nullptr;
Button *make_floating = nullptr;
+ bool is_floating = false;
EditorHelpSearch *help_search_dialog = nullptr;
ItemList *script_list = nullptr;
@@ -507,6 +508,8 @@ public:
void ensure_select_current();
+ bool is_editor_floating();
+
_FORCE_INLINE_ bool edit(const Ref<Resource> &p_resource, bool p_grab_focus = true) { return edit(p_resource, -1, 0, p_grab_focus); }
bool edit(const Ref<Resource> &p_resource, int p_line, int p_col, bool p_grab_focus = true);
diff --git a/editor/plugins/skeleton_3d_editor_plugin.cpp b/editor/plugins/skeleton_3d_editor_plugin.cpp
index 4e5a3eb095..0a3de70e2a 100644
--- a/editor/plugins/skeleton_3d_editor_plugin.cpp
+++ b/editor/plugins/skeleton_3d_editor_plugin.cpp
@@ -52,10 +52,8 @@
#include "scene/scene_string_names.h"
void BoneTransformEditor::create_editors() {
- const Color section_color = get_theme_color(SNAME("prop_subsection"), SNAME("Editor"));
-
section = memnew(EditorInspectorSection);
- section->setup("trf_properties", label, this, section_color, true);
+ section->setup("trf_properties", label, this, Color(0.0f, 0.0f, 0.0f), true);
section->unfold();
add_child(section);
@@ -94,7 +92,7 @@ void BoneTransformEditor::create_editors() {
// Transform/Matrix section.
rest_section = memnew(EditorInspectorSection);
- rest_section->setup("trf_properties_transform", "Rest", this, section_color, true);
+ rest_section->setup("trf_properties_transform", "Rest", this, Color(0.0f, 0.0f, 0.0f), true);
section->get_vbox()->add_child(rest_section);
// Transform/Matrix property.
@@ -107,8 +105,10 @@ void BoneTransformEditor::create_editors() {
void BoneTransformEditor::_notification(int p_what) {
switch (p_what) {
- case NOTIFICATION_ENTER_TREE: {
- create_editors();
+ case NOTIFICATION_THEME_CHANGED: {
+ const Color section_color = get_theme_color(SNAME("prop_subsection"), SNAME("Editor"));
+ section->set_bg_color(section_color);
+ rest_section->set_bg_color(section_color);
} break;
}
}
@@ -128,6 +128,7 @@ void BoneTransformEditor::_value_changed(const String &p_property, Variant p_val
BoneTransformEditor::BoneTransformEditor(Skeleton3D *p_skeleton) :
skeleton(p_skeleton) {
+ create_editors();
}
void BoneTransformEditor::set_keyable(const bool p_keyable) {
diff --git a/editor/plugins/tiles/tile_map_editor.cpp b/editor/plugins/tiles/tile_map_editor.cpp
index 8b58a45ea5..5815c85718 100644
--- a/editor/plugins/tiles/tile_map_editor.cpp
+++ b/editor/plugins/tiles/tile_map_editor.cpp
@@ -562,6 +562,16 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p
return true;
}
+ Ref<InputEventKey> k = p_event;
+ if (k.is_valid() && k->is_pressed() && !k->is_echo()) {
+ for (BaseButton *b : viewport_shortcut_buttons) {
+ if (b->get_shortcut().is_valid() && b->get_shortcut()->matches_event(p_event)) {
+ b->set_pressed(b->get_button_group().is_valid() || !b->is_pressed());
+ return true;
+ }
+ }
+ }
+
Ref<InputEventMouseMotion> mm = p_event;
if (mm.is_valid()) {
has_mouse = true;
@@ -2075,6 +2085,7 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() {
select_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/selection_tool", TTR("Selection"), Key::S));
select_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTilesPlugin::_update_toolbar));
tilemap_tiles_tools_buttons->add_child(select_tool_button);
+ viewport_shortcut_buttons.push_back(select_tool_button);
paint_tool_button = memnew(Button);
paint_tool_button->set_flat(true);
@@ -2084,6 +2095,7 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() {
paint_tool_button->set_tooltip_text(TTR("Shift: Draw line.") + "\n" + TTR("Shift+Ctrl: Draw rectangle."));
paint_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTilesPlugin::_update_toolbar));
tilemap_tiles_tools_buttons->add_child(paint_tool_button);
+ viewport_shortcut_buttons.push_back(paint_tool_button);
line_tool_button = memnew(Button);
line_tool_button->set_flat(true);
@@ -2093,6 +2105,7 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() {
line_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/line_tool", TTR("Line", "Tool"), Key::L));
line_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTilesPlugin::_update_toolbar));
tilemap_tiles_tools_buttons->add_child(line_tool_button);
+ viewport_shortcut_buttons.push_back(line_tool_button);
rect_tool_button = memnew(Button);
rect_tool_button->set_flat(true);
@@ -2101,6 +2114,7 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() {
rect_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/rect_tool", TTR("Rect"), Key::R));
rect_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTilesPlugin::_update_toolbar));
tilemap_tiles_tools_buttons->add_child(rect_tool_button);
+ viewport_shortcut_buttons.push_back(rect_tool_button);
bucket_tool_button = memnew(Button);
bucket_tool_button->set_flat(true);
@@ -2110,6 +2124,7 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() {
bucket_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTilesPlugin::_update_toolbar));
tilemap_tiles_tools_buttons->add_child(bucket_tool_button);
toolbar->add_child(tilemap_tiles_tools_buttons);
+ viewport_shortcut_buttons.push_back(bucket_tool_button);
// -- TileMap tool settings --
tools_settings = memnew(HBoxContainer);
@@ -2126,6 +2141,7 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() {
picker_button->set_tooltip_text(TTR("Alternatively hold Ctrl with other tools to pick tile."));
picker_button->connect("pressed", callable_mp(CanvasItemEditor::get_singleton(), &CanvasItemEditor::update_viewport));
tools_settings->add_child(picker_button);
+ viewport_shortcut_buttons.push_back(picker_button);
// Erase button.
erase_button = memnew(Button);
@@ -2135,6 +2151,7 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() {
erase_button->set_tooltip_text(TTR("Alternatively use RMB to erase tiles."));
erase_button->connect("pressed", callable_mp(CanvasItemEditor::get_singleton(), &CanvasItemEditor::update_viewport));
tools_settings->add_child(erase_button);
+ viewport_shortcut_buttons.push_back(erase_button);
// Separator 2.
tools_settings_vsep_2 = memnew(VSeparator);
@@ -2860,6 +2877,16 @@ bool TileMapEditorTerrainsPlugin::forward_canvas_gui_input(const Ref<InputEvent>
_update_selection();
+ Ref<InputEventKey> k = p_event;
+ if (k.is_valid() && k->is_pressed() && !k->is_echo()) {
+ for (BaseButton *b : viewport_shortcut_buttons) {
+ if (b->get_shortcut().is_valid() && b->get_shortcut()->matches_event(p_event)) {
+ b->set_pressed(b->get_button_group().is_valid() || !b->is_pressed());
+ return true;
+ }
+ }
+ }
+
Ref<InputEventMouseMotion> mm = p_event;
if (mm.is_valid()) {
has_mouse = true;
@@ -3388,6 +3415,7 @@ TileMapEditorTerrainsPlugin::TileMapEditorTerrainsPlugin() {
paint_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/paint_tool", TTR("Paint"), Key::D));
paint_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTerrainsPlugin::_update_toolbar));
tilemap_tiles_tools_buttons->add_child(paint_tool_button);
+ viewport_shortcut_buttons.push_back(paint_tool_button);
line_tool_button = memnew(Button);
line_tool_button->set_flat(true);
@@ -3396,6 +3424,7 @@ TileMapEditorTerrainsPlugin::TileMapEditorTerrainsPlugin() {
line_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/line_tool", TTR("Line"), Key::L));
line_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTerrainsPlugin::_update_toolbar));
tilemap_tiles_tools_buttons->add_child(line_tool_button);
+ viewport_shortcut_buttons.push_back(line_tool_button);
rect_tool_button = memnew(Button);
rect_tool_button->set_flat(true);
@@ -3404,6 +3433,7 @@ TileMapEditorTerrainsPlugin::TileMapEditorTerrainsPlugin() {
rect_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/rect_tool", TTR("Rect"), Key::R));
rect_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTerrainsPlugin::_update_toolbar));
tilemap_tiles_tools_buttons->add_child(rect_tool_button);
+ viewport_shortcut_buttons.push_back(rect_tool_button);
bucket_tool_button = memnew(Button);
bucket_tool_button->set_flat(true);
@@ -3412,6 +3442,7 @@ TileMapEditorTerrainsPlugin::TileMapEditorTerrainsPlugin() {
bucket_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/bucket_tool", TTR("Bucket"), Key::B));
bucket_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTerrainsPlugin::_update_toolbar));
tilemap_tiles_tools_buttons->add_child(bucket_tool_button);
+ viewport_shortcut_buttons.push_back(bucket_tool_button);
toolbar->add_child(tilemap_tiles_tools_buttons);
@@ -3429,6 +3460,7 @@ TileMapEditorTerrainsPlugin::TileMapEditorTerrainsPlugin() {
picker_button->set_shortcut(ED_SHORTCUT("tiles_editor/picker", TTR("Picker"), Key::P));
picker_button->connect("pressed", callable_mp(CanvasItemEditor::get_singleton(), &CanvasItemEditor::update_viewport));
tools_settings->add_child(picker_button);
+ viewport_shortcut_buttons.push_back(picker_button);
// Erase button.
erase_button = memnew(Button);
@@ -3437,6 +3469,7 @@ TileMapEditorTerrainsPlugin::TileMapEditorTerrainsPlugin() {
erase_button->set_shortcut(ED_SHORTCUT("tiles_editor/eraser", TTR("Eraser"), Key::E));
erase_button->connect("pressed", callable_mp(CanvasItemEditor::get_singleton(), &CanvasItemEditor::update_viewport));
tools_settings->add_child(erase_button);
+ viewport_shortcut_buttons.push_back(erase_button);
// Separator 2.
tools_settings_vsep_2 = memnew(VSeparator);
diff --git a/editor/plugins/tiles/tile_map_editor.h b/editor/plugins/tiles/tile_map_editor.h
index eb8c3937a0..ab5787b78f 100644
--- a/editor/plugins/tiles/tile_map_editor.h
+++ b/editor/plugins/tiles/tile_map_editor.h
@@ -203,6 +203,7 @@ private:
// General
void _update_theme();
+ List<BaseButton *> viewport_shortcut_buttons;
// Update callback
virtual void tile_set_changed() override;
@@ -293,6 +294,7 @@ private:
// Cache.
LocalVector<LocalVector<RBSet<TileSet::TerrainsPattern>>> per_terrain_terrains_patterns;
+ List<BaseButton *> viewport_shortcut_buttons;
// Update functions.
void _update_terrains_cache();
diff --git a/misc/extension_api_validation/4.0-stable.expected b/misc/extension_api_validation/4.0-stable.expected
index 823af03c8e..5f982cbff6 100644
--- a/misc/extension_api_validation/4.0-stable.expected
+++ b/misc/extension_api_validation/4.0-stable.expected
@@ -443,3 +443,11 @@ Validate extension JSON: API was removed: classes/SystemFont/properties/fallback
The property was moved to their common base class Font.
The setters and getters were already in Font, so this shouldn't affect compatibility.
+
+
+GH-36493
+--------
+Validate extension JSON: Error: Field 'classes/PopupMenu/methods/add_icon_shortcut/arguments': size changed value in new API, from 4 to 5.
+Validate extension JSON: Error: Field 'classes/PopupMenu/methods/add_shortcut/arguments': size changed value in new API, from 3 to 4.
+
+Added optional argument. Compatibility methods registered.
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index ad4528747b..9f9accf507 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -3301,17 +3301,26 @@ void GDScriptAnalyzer::reduce_dictionary(GDScriptParser::DictionaryNode *p_dicti
void GDScriptAnalyzer::reduce_get_node(GDScriptParser::GetNodeNode *p_get_node) {
GDScriptParser::DataType result;
- result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
- result.kind = GDScriptParser::DataType::NATIVE;
- result.native_type = SNAME("Node");
- result.builtin_type = Variant::OBJECT;
+ result.kind = GDScriptParser::DataType::VARIANT;
+
+ if (!ClassDB::is_parent_class(parser->current_class->base_type.native_type, SNAME("Node"))) {
+ push_error(vformat(R"*(Cannot use shorthand "get_node()" notation ("%c") on a class that isn't a node.)*", p_get_node->use_dollar ? '$' : '%'), p_get_node);
+ p_get_node->set_datatype(result);
+ return;
+ }
- if (!ClassDB::is_parent_class(parser->current_class->base_type.native_type, result.native_type)) {
- push_error(R"*(Cannot use shorthand "get_node()" notation ("$") on a class that isn't a node.)*", p_get_node);
+ if (static_context) {
+ push_error(vformat(R"*(Cannot use shorthand "get_node()" notation ("%c") in a static function.)*", p_get_node->use_dollar ? '$' : '%'), p_get_node);
+ p_get_node->set_datatype(result);
+ return;
}
mark_lambda_use_self();
+ result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ result.kind = GDScriptParser::DataType::NATIVE;
+ result.builtin_type = Variant::OBJECT;
+ result.native_type = SNAME("Node");
p_get_node->set_datatype(result);
}
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 9e1e8f0c75..b76ceea11f 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -3035,10 +3035,8 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p
if (previous.type == GDScriptTokenizer::Token::DOLLAR) {
// Detect initial slash, which will be handled in the loop if it matches.
match(GDScriptTokenizer::Token::SLASH);
-#ifdef DEBUG_ENABLED
} else {
get_node->use_dollar = false;
-#endif
}
int context_argument = 0;
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index 20f5dcf06d..71660d8f60 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -848,9 +848,7 @@ public:
struct GetNodeNode : public ExpressionNode {
String full_path;
-#ifdef DEBUG_ENABLED
bool use_dollar = true;
-#endif
GetNodeNode() {
type = GET_NODE;
diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp
index 4f374b63b0..42b983ef45 100644
--- a/modules/gdscript/gdscript_tokenizer.cpp
+++ b/modules/gdscript/gdscript_tokenizer.cpp
@@ -1162,15 +1162,6 @@ void GDScriptTokenizer::check_indent() {
_advance();
}
- if (mixed && !(line_continuation || multiline_mode)) {
- Token error = make_error("Mixed use of tabs and spaces for indentation.");
- error.start_line = line;
- error.start_column = 1;
- error.leftmost_column = 1;
- error.rightmost_column = column;
- push_error(error);
- }
-
if (_is_at_end()) {
// Reached the end with an empty line, so just dedent as much as needed.
pending_indents -= indent_level();
@@ -1214,6 +1205,15 @@ void GDScriptTokenizer::check_indent() {
continue;
}
+ if (mixed && !line_continuation && !multiline_mode) {
+ Token error = make_error("Mixed use of tabs and spaces for indentation.");
+ error.start_line = line;
+ error.start_column = 1;
+ error.leftmost_column = 1;
+ error.rightmost_column = column;
+ push_error(error);
+ }
+
if (line_continuation || multiline_mode) {
// We cleared up all the whitespace at the beginning of the line.
// But if this is a continuation or multiline mode and we don't want any indentation change.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_in_static_function.gd b/modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_in_static_function.gd
new file mode 100644
index 0000000000..caeea46977
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_in_static_function.gd
@@ -0,0 +1,9 @@
+# GH-75645
+
+extends Node
+
+static func static_func():
+ var a = $Node
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_in_static_function.out b/modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_in_static_function.out
new file mode 100644
index 0000000000..1910b3e66b
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_in_static_function.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot use shorthand "get_node()" notation ("$") in a static function.
diff --git a/modules/gdscript/tests/scripts/parser/.editorconfig b/modules/gdscript/tests/scripts/parser/.editorconfig
new file mode 100644
index 0000000000..fa43b3ad78
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/.editorconfig
@@ -0,0 +1,2 @@
+[*.{gd,out}]
+trim_trailing_whitespace = false
diff --git a/modules/gdscript/tests/scripts/parser/features/mixed_indentation_on_blank_lines.gd b/modules/gdscript/tests/scripts/parser/features/mixed_indentation_on_blank_lines.gd
new file mode 100644
index 0000000000..7ee2708999
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/mixed_indentation_on_blank_lines.gd
@@ -0,0 +1,20 @@
+# Empty line:
+
+
+# Comment line:
+ # Comment.
+
+func test():
+ print(1)
+
+ if true:
+
+ # Empty line:
+
+
+ print(2)
+
+ # Comment line:
+ # Comment.
+
+ print(3)
diff --git a/modules/gdscript/tests/scripts/parser/features/mixed_indentation_on_blank_lines.out b/modules/gdscript/tests/scripts/parser/features/mixed_indentation_on_blank_lines.out
new file mode 100644
index 0000000000..c40e402ba3
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/mixed_indentation_on_blank_lines.out
@@ -0,0 +1,4 @@
+GDTEST_OK
+1
+2
+3
diff --git a/modules/gridmap/editor/grid_map_editor_plugin.cpp b/modules/gridmap/editor/grid_map_editor_plugin.cpp
index a300f7d2e2..d7ecf4ef1a 100644
--- a/modules/gridmap/editor/grid_map_editor_plugin.cpp
+++ b/modules/gridmap/editor/grid_map_editor_plugin.cpp
@@ -743,6 +743,17 @@ EditorPlugin::AfterGUIInput GridMapEditor::forward_spatial_input_event(Camera3D
}
}
+ // Consume input to avoid conflicts with other plugins.
+ if (k.is_valid() && k->is_pressed() && !k->is_echo()) {
+ for (int i = 0; i < options->get_popup()->get_item_count(); ++i) {
+ const Ref<Shortcut> &shortcut = options->get_popup()->get_item_shortcut(i);
+ if (shortcut.is_valid() && shortcut->matches_event(p_event)) {
+ _menu_option(options->get_popup()->get_item_id(i));
+ return EditorPlugin::AFTER_GUI_INPUT_STOP;
+ }
+ }
+ }
+
if (k->is_shift_pressed() && selection.active && input_action != INPUT_PASTE) {
if (k->get_keycode() == (Key)options->get_popup()->get_item_accelerator(options->get_popup()->get_item_index(MENU_OPTION_PREV_LEVEL))) {
selection.click[edit_axis]--;
@@ -1154,6 +1165,24 @@ void GridMapEditor::_bind_methods() {
}
GridMapEditor::GridMapEditor() {
+ ED_SHORTCUT("grid_map/previous_floor", TTR("Previous Floor"), Key::Q, true);
+ ED_SHORTCUT("grid_map/next_floor", TTR("Next Floor"), Key::E, true);
+ ED_SHORTCUT("grid_map/edit_x_axis", TTR("Edit X Axis"), Key::Z, true);
+ ED_SHORTCUT("grid_map/edit_y_axis", TTR("Edit Y Axis"), Key::X, true);
+ ED_SHORTCUT("grid_map/edit_z_axis", TTR("Edit Z Axis"), Key::C, true);
+ ED_SHORTCUT("grid_map/cursor_rotate_x", TTR("Cursor Rotate X"), Key::A, true);
+ ED_SHORTCUT("grid_map/cursor_rotate_y", TTR("Cursor Rotate Y"), Key::S, true);
+ ED_SHORTCUT("grid_map/cursor_rotate_z", TTR("Cursor Rotate Z"), Key::D, true);
+ ED_SHORTCUT("grid_map/cursor_back_rotate_x", TTR("Cursor Back Rotate X"), KeyModifierMask::SHIFT + Key::A, true);
+ ED_SHORTCUT("grid_map/cursor_back_rotate_y", TTR("Cursor Back Rotate Y"), KeyModifierMask::SHIFT + Key::S, true);
+ ED_SHORTCUT("grid_map/cursor_back_rotate_z", TTR("Cursor Back Rotate Z"), KeyModifierMask::SHIFT + Key::D, true);
+ ED_SHORTCUT("grid_map/cursor_clear_rotation", TTR("Cursor Clear Rotation"), Key::W, true);
+ ED_SHORTCUT("grid_map/paste_selects", TTR("Paste Selects"));
+ ED_SHORTCUT("grid_map/duplicate_selection", TTR("Duplicate Selection"), KeyModifierMask::CTRL + Key::C, true);
+ ED_SHORTCUT("grid_map/cut_selection", TTR("Cut Selection"), KeyModifierMask::CTRL + Key::X, true);
+ ED_SHORTCUT("grid_map/clear_selection", TTR("Clear Selection"), Key::KEY_DELETE);
+ ED_SHORTCUT("grid_map/fill_selection", TTR("Fill Selection"), KeyModifierMask::CTRL + Key::F);
+
int mw = EDITOR_DEF("editors/grid_map/palette_min_width", 230);
Control *ec = memnew(Control);
ec->set_custom_minimum_size(Size2(mw, 0) * EDSCALE);
@@ -1186,29 +1215,29 @@ GridMapEditor::GridMapEditor() {
spatial_editor_hb->hide();
options->set_text(TTR("Grid Map"));
- options->get_popup()->add_item(TTR("Previous Floor"), MENU_OPTION_PREV_LEVEL, Key::Q);
- options->get_popup()->add_item(TTR("Next Floor"), MENU_OPTION_NEXT_LEVEL, Key::E);
+ options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/previous_floor"), MENU_OPTION_PREV_LEVEL);
+ options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/next_floor"), MENU_OPTION_NEXT_LEVEL);
options->get_popup()->add_separator();
- options->get_popup()->add_radio_check_item(TTR("Edit X Axis"), MENU_OPTION_X_AXIS, Key::Z);
- options->get_popup()->add_radio_check_item(TTR("Edit Y Axis"), MENU_OPTION_Y_AXIS, Key::X);
- options->get_popup()->add_radio_check_item(TTR("Edit Z Axis"), MENU_OPTION_Z_AXIS, Key::C);
+ options->get_popup()->add_radio_check_shortcut(ED_GET_SHORTCUT("grid_map/edit_x_axis"), MENU_OPTION_X_AXIS);
+ options->get_popup()->add_radio_check_shortcut(ED_GET_SHORTCUT("grid_map/edit_y_axis"), MENU_OPTION_Y_AXIS);
+ options->get_popup()->add_radio_check_shortcut(ED_GET_SHORTCUT("grid_map/edit_z_axis"), MENU_OPTION_Z_AXIS);
options->get_popup()->set_item_checked(options->get_popup()->get_item_index(MENU_OPTION_Y_AXIS), true);
options->get_popup()->add_separator();
- options->get_popup()->add_item(TTR("Cursor Rotate X"), MENU_OPTION_CURSOR_ROTATE_X, Key::A);
- options->get_popup()->add_item(TTR("Cursor Rotate Y"), MENU_OPTION_CURSOR_ROTATE_Y, Key::S);
- options->get_popup()->add_item(TTR("Cursor Rotate Z"), MENU_OPTION_CURSOR_ROTATE_Z, Key::D);
- options->get_popup()->add_item(TTR("Cursor Back Rotate X"), MENU_OPTION_CURSOR_BACK_ROTATE_X, KeyModifierMask::SHIFT + Key::A);
- options->get_popup()->add_item(TTR("Cursor Back Rotate Y"), MENU_OPTION_CURSOR_BACK_ROTATE_Y, KeyModifierMask::SHIFT + Key::S);
- options->get_popup()->add_item(TTR("Cursor Back Rotate Z"), MENU_OPTION_CURSOR_BACK_ROTATE_Z, KeyModifierMask::SHIFT + Key::D);
- options->get_popup()->add_item(TTR("Cursor Clear Rotation"), MENU_OPTION_CURSOR_CLEAR_ROTATION, Key::W);
+ options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_rotate_x"), MENU_OPTION_CURSOR_ROTATE_X);
+ options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_rotate_y"), MENU_OPTION_CURSOR_ROTATE_Y);
+ options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_rotate_z"), MENU_OPTION_CURSOR_ROTATE_Z);
+ options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_back_rotate_x"), MENU_OPTION_CURSOR_BACK_ROTATE_X);
+ options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_back_rotate_y"), MENU_OPTION_CURSOR_BACK_ROTATE_Y);
+ options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_back_rotate_z"), MENU_OPTION_CURSOR_BACK_ROTATE_Z);
+ options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_clear_rotation"), MENU_OPTION_CURSOR_CLEAR_ROTATION);
options->get_popup()->add_separator();
// TRANSLATORS: This is a toggle to select after pasting the new content.
- options->get_popup()->add_check_item(TTR("Paste Selects"), MENU_OPTION_PASTE_SELECTS);
+ options->get_popup()->add_check_shortcut(ED_GET_SHORTCUT("grid_map/paste_selects"), MENU_OPTION_PASTE_SELECTS);
options->get_popup()->add_separator();
- options->get_popup()->add_item(TTR("Duplicate Selection"), MENU_OPTION_SELECTION_DUPLICATE, KeyModifierMask::CTRL + Key::C);
- options->get_popup()->add_item(TTR("Cut Selection"), MENU_OPTION_SELECTION_CUT, KeyModifierMask::CTRL + Key::X);
- options->get_popup()->add_item(TTR("Clear Selection"), MENU_OPTION_SELECTION_CLEAR, Key::KEY_DELETE);
- options->get_popup()->add_item(TTR("Fill Selection"), MENU_OPTION_SELECTION_FILL, KeyModifierMask::CTRL + Key::F);
+ options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/duplicate_selection"), MENU_OPTION_SELECTION_DUPLICATE);
+ options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cut_selection"), MENU_OPTION_SELECTION_CUT);
+ options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/clear_selection"), MENU_OPTION_SELECTION_CLEAR);
+ options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/fill_selection"), MENU_OPTION_SELECTION_FILL);
options->get_popup()->add_separator();
options->get_popup()->add_item(TTR("Settings..."), MENU_OPTION_GRIDMAP_SETTINGS);
diff --git a/modules/navigation/godot_navigation_server.cpp b/modules/navigation/godot_navigation_server.cpp
index 8f56bf0f1d..9162fcf171 100644
--- a/modules/navigation/godot_navigation_server.cpp
+++ b/modules/navigation/godot_navigation_server.cpp
@@ -930,7 +930,7 @@ COMMAND_2(obstacle_set_avoidance_layers, RID, p_obstacle, uint32_t, p_layers) {
obstacle->set_avoidance_layers(p_layers);
}
-void GodotNavigationServer::parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback) {
+void GodotNavigationServer::parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, Node *p_root_node, const Callable &p_callback) {
#ifndef _3D_DISABLED
ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "The SceneTree can only be parsed on the main thread. Call this function from the main thread or use call_deferred().");
ERR_FAIL_COND_MSG(!p_navigation_mesh.is_valid(), "Invalid navigation mesh.");
@@ -942,26 +942,26 @@ void GodotNavigationServer::parse_source_geometry_data(const Ref<NavigationMesh>
#endif // _3D_DISABLED
}
-void GodotNavigationServer::bake_from_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback) {
+void GodotNavigationServer::bake_from_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback) {
#ifndef _3D_DISABLED
ERR_FAIL_COND_MSG(!p_navigation_mesh.is_valid(), "Invalid navigation mesh.");
ERR_FAIL_COND_MSG(!p_source_geometry_data.is_valid(), "Invalid NavigationMeshSourceGeometryData3D.");
- if (!p_source_geometry_data->has_data()) {
- p_navigation_mesh->clear();
- if (p_callback.is_valid()) {
- Callable::CallError ce;
- Variant result;
- p_callback.callp(nullptr, 0, result, ce);
- }
- return;
- }
-
ERR_FAIL_NULL(NavMeshGenerator3D::get_singleton());
NavMeshGenerator3D::get_singleton()->bake_from_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_callback);
#endif // _3D_DISABLED
}
+void GodotNavigationServer::bake_from_source_geometry_data_async(const Ref<NavigationMesh> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback) {
+#ifndef _3D_DISABLED
+ ERR_FAIL_COND_MSG(!p_navigation_mesh.is_valid(), "Invalid navigation mesh.");
+ ERR_FAIL_COND_MSG(!p_source_geometry_data.is_valid(), "Invalid NavigationMeshSourceGeometryData3D.");
+
+ ERR_FAIL_NULL(NavMeshGenerator3D::get_singleton());
+ NavMeshGenerator3D::get_singleton()->bake_from_source_geometry_data_async(p_navigation_mesh, p_source_geometry_data, p_callback);
+#endif // _3D_DISABLED
+}
+
COMMAND_1(free, RID, p_object) {
if (map_owner.owns(p_object)) {
NavMap *map = map_owner.get_or_null(p_object);
@@ -1093,6 +1093,16 @@ void GodotNavigationServer::process(real_t p_delta_time) {
return;
}
+#ifndef _3D_DISABLED
+ // Sync finished navmesh bakes before doing NavMap updates.
+ if (navmesh_generator_3d) {
+ navmesh_generator_3d->sync();
+ // Finished bakes emit callbacks and users might have reacted to those.
+ // Flush queue again so users do not have to wait for the next sync.
+ flush_queries();
+ }
+#endif // _3D_DISABLED
+
int _new_pm_region_count = 0;
int _new_pm_agent_count = 0;
int _new_pm_link_count = 0;
diff --git a/modules/navigation/godot_navigation_server.h b/modules/navigation/godot_navigation_server.h
index 8b91ca36bd..40893bada6 100644
--- a/modules/navigation/godot_navigation_server.h
+++ b/modules/navigation/godot_navigation_server.h
@@ -228,8 +228,9 @@ public:
virtual void obstacle_set_vertices(RID p_obstacle, const Vector<Vector3> &p_vertices) override;
COMMAND_2(obstacle_set_avoidance_layers, RID, p_obstacle, uint32_t, p_layers);
- virtual void parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable()) override;
- virtual void bake_from_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback = Callable()) override;
+ virtual void parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable()) override;
+ virtual void bake_from_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback = Callable()) override;
+ virtual void bake_from_source_geometry_data_async(const Ref<NavigationMesh> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback = Callable()) override;
COMMAND_1(free, RID, p_object);
diff --git a/modules/navigation/nav_mesh_generator_3d.cpp b/modules/navigation/nav_mesh_generator_3d.cpp
index 0132e38ceb..7c27417e5f 100644
--- a/modules/navigation/nav_mesh_generator_3d.cpp
+++ b/modules/navigation/nav_mesh_generator_3d.cpp
@@ -32,6 +32,7 @@
#include "nav_mesh_generator_3d.h"
+#include "core/config/project_settings.h"
#include "core/math/convex_hull.h"
#include "core/os/thread.h"
#include "scene/3d/mesh_instance_3d.h"
@@ -63,7 +64,12 @@
NavMeshGenerator3D *NavMeshGenerator3D::singleton = nullptr;
Mutex NavMeshGenerator3D::baking_navmesh_mutex;
+Mutex NavMeshGenerator3D::generator_task_mutex;
+bool NavMeshGenerator3D::use_threads = true;
+bool NavMeshGenerator3D::baking_use_multiple_threads = true;
+bool NavMeshGenerator3D::baking_use_high_priority_threads = true;
HashSet<Ref<NavigationMesh>> NavMeshGenerator3D::baking_navmeshes;
+HashMap<WorkerThreadPool::TaskID, NavMeshGenerator3D::NavMeshGeneratorTask3D *> NavMeshGenerator3D::generator_tasks;
NavMeshGenerator3D *NavMeshGenerator3D::get_singleton() {
return singleton;
@@ -72,15 +78,67 @@ NavMeshGenerator3D *NavMeshGenerator3D::get_singleton() {
NavMeshGenerator3D::NavMeshGenerator3D() {
ERR_FAIL_COND(singleton != nullptr);
singleton = this;
+
+ baking_use_multiple_threads = GLOBAL_GET("navigation/baking/thread_model/baking_use_multiple_threads");
+ baking_use_high_priority_threads = GLOBAL_GET("navigation/baking/thread_model/baking_use_high_priority_threads");
+
+ // Using threads might cause problems on certain exports or with the Editor on certain devices.
+ // This is the main switch to turn threaded navmesh baking off should the need arise.
+ use_threads = baking_use_multiple_threads && !Engine::get_singleton()->is_editor_hint();
}
NavMeshGenerator3D::~NavMeshGenerator3D() {
cleanup();
}
+void NavMeshGenerator3D::sync() {
+ if (generator_tasks.size() == 0) {
+ return;
+ }
+
+ baking_navmesh_mutex.lock();
+ generator_task_mutex.lock();
+
+ LocalVector<WorkerThreadPool::TaskID> finished_task_ids;
+
+ for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask3D *> &E : generator_tasks) {
+ if (WorkerThreadPool::get_singleton()->is_task_completed(E.key)) {
+ WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key);
+ finished_task_ids.push_back(E.key);
+
+ NavMeshGeneratorTask3D *generator_task = E.value;
+ DEV_ASSERT(generator_task->status == NavMeshGeneratorTask3D::TaskStatus::BAKING_FINISHED);
+
+ baking_navmeshes.erase(generator_task->navigation_mesh);
+ if (generator_task->callback.is_valid()) {
+ generator_emit_callback(generator_task->callback);
+ }
+ memdelete(generator_task);
+ }
+ }
+
+ for (WorkerThreadPool::TaskID finished_task_id : finished_task_ids) {
+ generator_tasks.erase(finished_task_id);
+ }
+
+ generator_task_mutex.unlock();
+ baking_navmesh_mutex.unlock();
+}
+
void NavMeshGenerator3D::cleanup() {
baking_navmesh_mutex.lock();
+ generator_task_mutex.lock();
+
baking_navmeshes.clear();
+
+ for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask3D *> &E : generator_tasks) {
+ WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key);
+ NavMeshGeneratorTask3D *generator_task = E.value;
+ memdelete(generator_task);
+ }
+ generator_tasks.clear();
+
+ generator_task_mutex.unlock();
baking_navmesh_mutex.unlock();
}
@@ -88,7 +146,7 @@ void NavMeshGenerator3D::finish() {
cleanup();
}
-void NavMeshGenerator3D::parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback) {
+void NavMeshGenerator3D::parse_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback) {
ERR_FAIL_COND(!Thread::is_main_thread());
ERR_FAIL_COND(!p_navigation_mesh.is_valid());
ERR_FAIL_COND(p_root_node == nullptr);
@@ -102,19 +160,25 @@ void NavMeshGenerator3D::parse_source_geometry_data(const Ref<NavigationMesh> &p
}
}
-void NavMeshGenerator3D::bake_from_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback) {
+void NavMeshGenerator3D::bake_from_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, const Callable &p_callback) {
ERR_FAIL_COND(!p_navigation_mesh.is_valid());
ERR_FAIL_COND(!p_source_geometry_data.is_valid());
- ERR_FAIL_COND(!p_source_geometry_data->has_data());
+
+ if (!p_source_geometry_data->has_data()) {
+ p_navigation_mesh->clear();
+ if (p_callback.is_valid()) {
+ generator_emit_callback(p_callback);
+ }
+ return;
+ }
baking_navmesh_mutex.lock();
if (baking_navmeshes.has(p_navigation_mesh)) {
baking_navmesh_mutex.unlock();
ERR_FAIL_MSG("NavigationMesh is already baking. Wait for current bake to finish.");
- } else {
- baking_navmeshes.insert(p_navigation_mesh);
- baking_navmesh_mutex.unlock();
}
+ baking_navmeshes.insert(p_navigation_mesh);
+ baking_navmesh_mutex.unlock();
generator_bake_from_source_geometry_data(p_navigation_mesh, p_source_geometry_data);
@@ -127,6 +191,51 @@ void NavMeshGenerator3D::bake_from_source_geometry_data(Ref<NavigationMesh> p_na
}
}
+void NavMeshGenerator3D::bake_from_source_geometry_data_async(Ref<NavigationMesh> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, const Callable &p_callback) {
+ ERR_FAIL_COND(!p_navigation_mesh.is_valid());
+ ERR_FAIL_COND(!p_source_geometry_data.is_valid());
+
+ if (!p_source_geometry_data->has_data()) {
+ p_navigation_mesh->clear();
+ if (p_callback.is_valid()) {
+ generator_emit_callback(p_callback);
+ }
+ return;
+ }
+
+ if (!use_threads) {
+ bake_from_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_callback);
+ return;
+ }
+
+ baking_navmesh_mutex.lock();
+ if (baking_navmeshes.has(p_navigation_mesh)) {
+ baking_navmesh_mutex.unlock();
+ ERR_FAIL_MSG("NavigationMesh is already baking. Wait for current bake to finish.");
+ return;
+ }
+ baking_navmeshes.insert(p_navigation_mesh);
+ baking_navmesh_mutex.unlock();
+
+ generator_task_mutex.lock();
+ NavMeshGeneratorTask3D *generator_task = memnew(NavMeshGeneratorTask3D);
+ generator_task->navigation_mesh = p_navigation_mesh;
+ generator_task->source_geometry_data = p_source_geometry_data;
+ generator_task->callback = p_callback;
+ generator_task->status = NavMeshGeneratorTask3D::TaskStatus::BAKING_STARTED;
+ generator_task->thread_task_id = WorkerThreadPool::get_singleton()->add_native_task(&NavMeshGenerator3D::generator_thread_bake, generator_task, NavMeshGenerator3D::baking_use_high_priority_threads, SNAME("NavMeshGeneratorBake3D"));
+ generator_tasks.insert(generator_task->thread_task_id, generator_task);
+ generator_task_mutex.unlock();
+}
+
+void NavMeshGenerator3D::generator_thread_bake(void *p_arg) {
+ NavMeshGeneratorTask3D *generator_task = static_cast<NavMeshGeneratorTask3D *>(p_arg);
+
+ generator_bake_from_source_geometry_data(generator_task->navigation_mesh, generator_task->source_geometry_data);
+
+ generator_task->status = NavMeshGeneratorTask3D::TaskStatus::BAKING_FINISHED;
+}
+
void NavMeshGenerator3D::generator_parse_geometry_node(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node, bool p_recurse_children) {
generator_parse_meshinstance3d_node(p_navigation_mesh, p_source_geometry_data, p_node);
generator_parse_multimeshinstance3d_node(p_navigation_mesh, p_source_geometry_data, p_node);
@@ -504,8 +613,8 @@ void NavMeshGenerator3D::generator_bake_from_source_geometry_data(Ref<Navigation
return;
}
- const Vector<float> vertices = p_source_geometry_data->get_vertices();
- const Vector<int> indices = p_source_geometry_data->get_indices();
+ const Vector<float> &vertices = p_source_geometry_data->get_vertices();
+ const Vector<int> &indices = p_source_geometry_data->get_indices();
if (vertices.size() < 3 || indices.size() < 3) {
return;
diff --git a/modules/navigation/nav_mesh_generator_3d.h b/modules/navigation/nav_mesh_generator_3d.h
index dc844c2595..4220927641 100644
--- a/modules/navigation/nav_mesh_generator_3d.h
+++ b/modules/navigation/nav_mesh_generator_3d.h
@@ -34,6 +34,7 @@
#ifndef _3D_DISABLED
#include "core/object/class_db.h"
+#include "core/object/worker_thread_pool.h"
#include "modules/modules_enabled.gen.h" // For csg, gridmap.
class Node;
@@ -44,6 +45,31 @@ class NavMeshGenerator3D : public Object {
static NavMeshGenerator3D *singleton;
static Mutex baking_navmesh_mutex;
+ static Mutex generator_task_mutex;
+
+ static bool use_threads;
+ static bool baking_use_multiple_threads;
+ static bool baking_use_high_priority_threads;
+
+ struct NavMeshGeneratorTask3D {
+ enum TaskStatus {
+ BAKING_STARTED,
+ BAKING_FINISHED,
+ BAKING_FAILED,
+ CALLBACK_DISPATCHED,
+ CALLBACK_FAILED,
+ };
+
+ Ref<NavigationMesh> navigation_mesh;
+ Ref<NavigationMeshSourceGeometryData3D> source_geometry_data;
+ Callable callback;
+ WorkerThreadPool::TaskID thread_task_id = WorkerThreadPool::INVALID_TASK_ID;
+ NavMeshGeneratorTask3D::TaskStatus status = NavMeshGeneratorTask3D::TaskStatus::BAKING_STARTED;
+ };
+
+ static HashMap<WorkerThreadPool::TaskID, NavMeshGeneratorTask3D *> generator_tasks;
+
+ static void generator_thread_bake(void *p_arg);
static HashSet<Ref<NavigationMesh>> baking_navmeshes;
@@ -66,11 +92,13 @@ class NavMeshGenerator3D : public Object {
public:
static NavMeshGenerator3D *get_singleton();
+ static void sync();
static void cleanup();
static void finish();
- static void parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable());
- static void bake_from_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback = Callable());
+ static void parse_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable());
+ static void bake_from_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, const Callable &p_callback = Callable());
+ static void bake_from_source_geometry_data_async(Ref<NavigationMesh> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, const Callable &p_callback = Callable());
NavMeshGenerator3D();
~NavMeshGenerator3D();
diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp
index 20aaed1e3e..21de46f4fc 100644
--- a/platform/android/export/export_plugin.cpp
+++ b/platform/android/export/export_plugin.cpp
@@ -47,6 +47,7 @@
#include "editor/editor_paths.h"
#include "editor/editor_scale.h"
#include "editor/editor_settings.h"
+#include "editor/import/resource_importer_texture_settings.h"
#include "main/splash.gen.h"
#include "scene/resources/image_texture.h"
@@ -2384,10 +2385,8 @@ bool EditorExportPlatformAndroid::has_valid_project_configuration(const Ref<Edit
}
}
- String etc_error = test_etc2();
- if (!etc_error.is_empty()) {
+ if (!ResourceImporterTextureSettings::should_import_etc2_astc()) {
valid = false;
- err += etc_error;
}
String min_sdk_str = p_preset->get("gradle_build/min_sdk");
diff --git a/platform/ios/export/export_plugin.cpp b/platform/ios/export/export_plugin.cpp
index 3d63b7bb55..b6320fb22b 100644
--- a/platform/ios/export/export_plugin.cpp
+++ b/platform/ios/export/export_plugin.cpp
@@ -39,6 +39,7 @@
#include "editor/editor_paths.h"
#include "editor/editor_scale.h"
#include "editor/export/editor_export.h"
+#include "editor/import/resource_importer_texture_settings.h"
#include "editor/plugins/script_editor_plugin.h"
#include "modules/modules_enabled.gen.h" // For mono and svg.
@@ -1984,10 +1985,8 @@ bool EditorExportPlatformIOS::has_valid_project_configuration(const Ref<EditorEx
}
}
- const String etc_error = test_etc2();
- if (!etc_error.is_empty()) {
+ if (!ResourceImporterTextureSettings::should_import_etc2_astc()) {
valid = false;
- err += etc_error;
}
if (!err.is_empty()) {
diff --git a/platform/linuxbsd/freedesktop_portal_desktop.cpp b/platform/linuxbsd/freedesktop_portal_desktop.cpp
index c3cb0f411d..91c14e0e91 100644
--- a/platform/linuxbsd/freedesktop_portal_desktop.cpp
+++ b/platform/linuxbsd/freedesktop_portal_desktop.cpp
@@ -32,6 +32,7 @@
#ifdef DBUS_ENABLED
+#include "core/crypto/crypto_core.h"
#include "core/error/error_macros.h"
#include "core/os/os.h"
#include "core/string/ustring.h"
@@ -43,12 +44,15 @@
#include <dbus/dbus.h>
#endif
+#include <unistd.h>
+
#define BUS_OBJECT_NAME "org.freedesktop.portal.Desktop"
#define BUS_OBJECT_PATH "/org/freedesktop/portal/desktop"
#define BUS_INTERFACE_SETTINGS "org.freedesktop.portal.Settings"
+#define BUS_INTERFACE_FILE_CHOOSER "org.freedesktop.portal.FileChooser"
-static bool try_parse_variant(DBusMessage *p_reply_message, int p_type, void *r_value) {
+bool FreeDesktopPortalDesktop::try_parse_variant(DBusMessage *p_reply_message, int p_type, void *r_value) {
DBusMessageIter iter[3];
dbus_message_iter_init(p_reply_message, &iter[0]);
@@ -80,11 +84,11 @@ bool FreeDesktopPortalDesktop::read_setting(const char *p_namespace, const char
DBusConnection *bus = dbus_bus_get(DBUS_BUS_SESSION, &error);
if (dbus_error_is_set(&error)) {
- dbus_error_free(&error);
- unsupported = true;
if (OS::get_singleton()->is_stdout_verbose()) {
- ERR_PRINT(String() + "Error opening D-Bus connection: " + error.message);
+ ERR_PRINT(vformat("Error opening D-Bus connection: %s", error.message));
}
+ dbus_error_free(&error);
+ unsupported = true;
return false;
}
@@ -100,11 +104,11 @@ bool FreeDesktopPortalDesktop::read_setting(const char *p_namespace, const char
DBusMessage *reply = dbus_connection_send_with_reply_and_block(bus, message, 50, &error);
dbus_message_unref(message);
if (dbus_error_is_set(&error)) {
- dbus_error_free(&error);
- dbus_connection_unref(bus);
if (OS::get_singleton()->is_stdout_verbose()) {
- ERR_PRINT(String() + "Error on D-Bus communication: " + error.message);
+ ERR_PRINT(vformat("Error on D-Bus communication: %s", error.message));
}
+ dbus_error_free(&error);
+ dbus_connection_unref(bus);
return false;
}
@@ -126,6 +130,317 @@ uint32_t FreeDesktopPortalDesktop::get_appearance_color_scheme() {
return value;
}
+static const char *cs_empty = "";
+
+void FreeDesktopPortalDesktop::append_dbus_string(DBusMessageIter *p_iter, const String &p_string) {
+ CharString cs = p_string.utf8();
+ const char *cs_ptr = cs.ptr();
+ if (cs_ptr) {
+ dbus_message_iter_append_basic(p_iter, DBUS_TYPE_STRING, &cs_ptr);
+ } else {
+ dbus_message_iter_append_basic(p_iter, DBUS_TYPE_STRING, &cs_empty);
+ }
+}
+
+void FreeDesktopPortalDesktop::append_dbus_dict_filters(DBusMessageIter *p_iter, const Vector<String> &p_filters) {
+ DBusMessageIter dict_iter;
+ DBusMessageIter var_iter;
+ DBusMessageIter arr_iter;
+ const char *filters_key = "filters";
+
+ dbus_message_iter_open_container(p_iter, DBUS_TYPE_DICT_ENTRY, nullptr, &dict_iter);
+ dbus_message_iter_append_basic(&dict_iter, DBUS_TYPE_STRING, &filters_key);
+ dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_VARIANT, "a(sa(us))", &var_iter);
+ dbus_message_iter_open_container(&var_iter, DBUS_TYPE_ARRAY, "(sa(us))", &arr_iter);
+ for (int i = 0; i < p_filters.size(); i++) {
+ Vector<String> tokens = p_filters[i].split(";");
+ if (tokens.size() == 2) {
+ DBusMessageIter struct_iter;
+ DBusMessageIter array_iter;
+ DBusMessageIter array_struct_iter;
+ dbus_message_iter_open_container(&arr_iter, DBUS_TYPE_STRUCT, nullptr, &struct_iter);
+ append_dbus_string(&struct_iter, tokens[0]);
+
+ dbus_message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "(us)", &array_iter);
+ dbus_message_iter_open_container(&array_iter, DBUS_TYPE_STRUCT, nullptr, &array_struct_iter);
+ {
+ const unsigned nil = 0;
+ dbus_message_iter_append_basic(&array_struct_iter, DBUS_TYPE_UINT32, &nil);
+ }
+ append_dbus_string(&array_struct_iter, tokens[1]);
+ dbus_message_iter_close_container(&array_iter, &array_struct_iter);
+ dbus_message_iter_close_container(&struct_iter, &array_iter);
+ dbus_message_iter_close_container(&arr_iter, &struct_iter);
+ }
+ }
+ dbus_message_iter_close_container(&var_iter, &arr_iter);
+ dbus_message_iter_close_container(&dict_iter, &var_iter);
+ dbus_message_iter_close_container(p_iter, &dict_iter);
+}
+
+void FreeDesktopPortalDesktop::append_dbus_dict_string(DBusMessageIter *p_iter, const String &p_key, const String &p_value, bool p_as_byte_array) {
+ DBusMessageIter dict_iter;
+ DBusMessageIter var_iter;
+ dbus_message_iter_open_container(p_iter, DBUS_TYPE_DICT_ENTRY, nullptr, &dict_iter);
+ append_dbus_string(&dict_iter, p_key);
+
+ if (p_as_byte_array) {
+ DBusMessageIter arr_iter;
+ dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_VARIANT, "ay", &var_iter);
+ dbus_message_iter_open_container(&var_iter, DBUS_TYPE_ARRAY, "y", &arr_iter);
+ CharString cs = p_value.utf8();
+ const char *cs_ptr = cs.get_data();
+ do {
+ dbus_message_iter_append_basic(&arr_iter, DBUS_TYPE_BYTE, cs_ptr);
+ } while (*cs_ptr++);
+ dbus_message_iter_close_container(&var_iter, &arr_iter);
+ } else {
+ dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_VARIANT, "s", &var_iter);
+ append_dbus_string(&var_iter, p_value);
+ }
+
+ dbus_message_iter_close_container(&dict_iter, &var_iter);
+ dbus_message_iter_close_container(p_iter, &dict_iter);
+}
+
+void FreeDesktopPortalDesktop::append_dbus_dict_bool(DBusMessageIter *p_iter, const String &p_key, bool p_value) {
+ DBusMessageIter dict_iter;
+ DBusMessageIter var_iter;
+ dbus_message_iter_open_container(p_iter, DBUS_TYPE_DICT_ENTRY, nullptr, &dict_iter);
+ append_dbus_string(&dict_iter, p_key);
+
+ dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_VARIANT, "b", &var_iter);
+ {
+ int val = p_value;
+ dbus_message_iter_append_basic(&var_iter, DBUS_TYPE_BOOLEAN, &val);
+ }
+
+ dbus_message_iter_close_container(&dict_iter, &var_iter);
+ dbus_message_iter_close_container(p_iter, &dict_iter);
+}
+
+bool FreeDesktopPortalDesktop::file_chooser_parse_response(DBusMessageIter *p_iter, bool &r_cancel, Vector<String> &r_urls) {
+ ERR_FAIL_COND_V(dbus_message_iter_get_arg_type(p_iter) != DBUS_TYPE_UINT32, false);
+
+ dbus_uint32_t resp_code;
+ dbus_message_iter_get_basic(p_iter, &resp_code);
+ if (resp_code != 0) {
+ r_cancel = true;
+ } else {
+ r_cancel = false;
+ ERR_FAIL_COND_V(!dbus_message_iter_next(p_iter), false);
+ ERR_FAIL_COND_V(dbus_message_iter_get_arg_type(p_iter) != DBUS_TYPE_ARRAY, false);
+
+ DBusMessageIter dict_iter;
+ dbus_message_iter_recurse(p_iter, &dict_iter);
+ while (dbus_message_iter_get_arg_type(&dict_iter) == DBUS_TYPE_DICT_ENTRY) {
+ DBusMessageIter iter;
+ dbus_message_iter_recurse(&dict_iter, &iter);
+ if (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRING) {
+ const char *key;
+ dbus_message_iter_get_basic(&iter, &key);
+ dbus_message_iter_next(&iter);
+
+ DBusMessageIter var_iter;
+ dbus_message_iter_recurse(&iter, &var_iter);
+ if (strcmp(key, "uris") == 0) {
+ if (dbus_message_iter_get_arg_type(&var_iter) == DBUS_TYPE_ARRAY) {
+ DBusMessageIter uri_iter;
+ dbus_message_iter_recurse(&var_iter, &uri_iter);
+ while (dbus_message_iter_get_arg_type(&uri_iter) == DBUS_TYPE_STRING) {
+ const char *value;
+ dbus_message_iter_get_basic(&uri_iter, &value);
+ r_urls.push_back(String::utf8(value).trim_prefix("file://").uri_decode());
+ if (!dbus_message_iter_next(&uri_iter)) {
+ break;
+ }
+ }
+ }
+ }
+ }
+ if (!dbus_message_iter_next(&dict_iter)) {
+ break;
+ }
+ }
+ }
+ return true;
+}
+
+Error FreeDesktopPortalDesktop::file_dialog_show(const String &p_xid, const String &p_title, const String &p_current_directory, const String &p_filename, DisplayServer::FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) {
+ if (unsupported) {
+ return FAILED;
+ }
+
+ DBusError err;
+ dbus_error_init(&err);
+
+ // Open connection and add signal handler.
+ FileDialogData fd;
+ fd.callback = p_callback;
+
+ CryptoCore::RandomGenerator rng;
+ ERR_FAIL_COND_V_MSG(rng.init(), FAILED, "Failed to initialize random number generator.");
+ uint8_t uuid[64];
+ Error rng_err = rng.get_random_bytes(uuid, 64);
+ ERR_FAIL_COND_V_MSG(rng_err, rng_err, "Failed to generate unique token.");
+
+ fd.connection = dbus_bus_get(DBUS_BUS_SESSION, &err);
+ if (dbus_error_is_set(&err)) {
+ ERR_PRINT(vformat("Failed to open DBus connection: %s", err.message));
+ dbus_error_free(&err);
+ unsupported = true;
+ return FAILED;
+ }
+
+ String dbus_unique_name = String::utf8(dbus_bus_get_unique_name(fd.connection));
+ String token = String::hex_encode_buffer(uuid, 64);
+ String path = vformat("/org/freedesktop/portal/desktop/request/%s/%s", dbus_unique_name.replace(".", "_").replace(":", ""), token);
+
+ fd.path = vformat("type='signal',sender='org.freedesktop.portal.Desktop',path='%s',interface='org.freedesktop.portal.Request',member='Response',destination='%s'", path, dbus_unique_name);
+ dbus_bus_add_match(fd.connection, fd.path.utf8().get_data(), &err);
+ if (dbus_error_is_set(&err)) {
+ ERR_PRINT(vformat("Failed to add DBus match: %s", err.message));
+ dbus_error_free(&err);
+ dbus_connection_unref(fd.connection);
+ return FAILED;
+ }
+
+ // Generate FileChooser message.
+ const char *method = nullptr;
+ switch (p_mode) {
+ case DisplayServer::FILE_DIALOG_MODE_SAVE_FILE: {
+ method = "SaveFile";
+ } break;
+ case DisplayServer::FILE_DIALOG_MODE_OPEN_ANY:
+ case DisplayServer::FILE_DIALOG_MODE_OPEN_FILE:
+ case DisplayServer::FILE_DIALOG_MODE_OPEN_DIR:
+ case DisplayServer::FILE_DIALOG_MODE_OPEN_FILES: {
+ method = "OpenFile";
+ } break;
+ }
+
+ DBusMessage *message = dbus_message_new_method_call(BUS_OBJECT_NAME, BUS_OBJECT_PATH, BUS_INTERFACE_FILE_CHOOSER, method);
+ {
+ DBusMessageIter iter;
+ dbus_message_iter_init_append(message, &iter);
+
+ append_dbus_string(&iter, p_xid);
+ append_dbus_string(&iter, p_title);
+
+ DBusMessageIter arr_iter;
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &arr_iter);
+
+ append_dbus_dict_string(&arr_iter, "handle_token", token);
+ append_dbus_dict_bool(&arr_iter, "multiple", p_mode == DisplayServer::FILE_DIALOG_MODE_OPEN_FILES);
+ append_dbus_dict_bool(&arr_iter, "directory", p_mode == DisplayServer::FILE_DIALOG_MODE_OPEN_DIR);
+ append_dbus_dict_filters(&arr_iter, p_filters);
+ append_dbus_dict_string(&arr_iter, "current_folder", p_current_directory, true);
+ if (p_mode == DisplayServer::FILE_DIALOG_MODE_SAVE_FILE) {
+ append_dbus_dict_string(&arr_iter, "current_name", p_filename);
+ }
+
+ dbus_message_iter_close_container(&iter, &arr_iter);
+ }
+
+ DBusMessage *reply = dbus_connection_send_with_reply_and_block(fd.connection, message, DBUS_TIMEOUT_INFINITE, &err);
+ dbus_message_unref(message);
+
+ if (!reply || dbus_error_is_set(&err)) {
+ ERR_PRINT(vformat("Failed to send DBus message: %s", err.message));
+ dbus_error_free(&err);
+ dbus_bus_remove_match(fd.connection, fd.path.utf8().get_data(), &err);
+ dbus_connection_unref(fd.connection);
+ return FAILED;
+ }
+
+ // Update signal path.
+ {
+ DBusMessageIter iter;
+ if (dbus_message_iter_init(reply, &iter)) {
+ if (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_OBJECT_PATH) {
+ const char *new_path = nullptr;
+ dbus_message_iter_get_basic(&iter, &new_path);
+ if (String::utf8(new_path) != path) {
+ dbus_bus_remove_match(fd.connection, fd.path.utf8().get_data(), &err);
+ if (dbus_error_is_set(&err)) {
+ ERR_PRINT(vformat("Failed to remove DBus match: %s", err.message));
+ dbus_error_free(&err);
+ dbus_connection_unref(fd.connection);
+ return FAILED;
+ }
+ fd.path = String::utf8(new_path);
+ dbus_bus_add_match(fd.connection, fd.path.utf8().get_data(), &err);
+ if (dbus_error_is_set(&err)) {
+ ERR_PRINT(vformat("Failed to add DBus match: %s", err.message));
+ dbus_error_free(&err);
+ dbus_connection_unref(fd.connection);
+ return FAILED;
+ }
+ }
+ }
+ }
+ }
+ dbus_message_unref(reply);
+
+ MutexLock lock(file_dialog_mutex);
+ file_dialogs.push_back(fd);
+
+ return OK;
+}
+
+void FreeDesktopPortalDesktop::_thread_file_dialog_monitor(void *p_ud) {
+ FreeDesktopPortalDesktop *portal = (FreeDesktopPortalDesktop *)p_ud;
+
+ while (!portal->file_dialog_thread_abort.is_set()) {
+ {
+ MutexLock lock(portal->file_dialog_mutex);
+ for (int i = portal->file_dialogs.size() - 1; i >= 0; i--) {
+ bool remove = false;
+ {
+ FreeDesktopPortalDesktop::FileDialogData &fd = portal->file_dialogs.write[i];
+ if (fd.connection) {
+ while (true) {
+ DBusMessage *msg = dbus_connection_pop_message(fd.connection);
+ if (!msg) {
+ break;
+ } else if (dbus_message_is_signal(msg, "org.freedesktop.portal.Request", "Response")) {
+ DBusMessageIter iter;
+ if (dbus_message_iter_init(msg, &iter)) {
+ bool cancel = false;
+ Vector<String> uris;
+ file_chooser_parse_response(&iter, cancel, uris);
+
+ if (fd.callback.is_valid()) {
+ Variant v_status = !cancel;
+ Variant v_files = uris;
+ Variant *v_args[2] = { &v_status, &v_files };
+ fd.callback.call_deferredp((const Variant **)&v_args, 2);
+ }
+ }
+ dbus_message_unref(msg);
+
+ DBusError err;
+ dbus_error_init(&err);
+ dbus_bus_remove_match(fd.connection, fd.path.utf8().get_data(), &err);
+ dbus_error_free(&err);
+ dbus_connection_unref(fd.connection);
+ remove = true;
+ break;
+ }
+ dbus_message_unref(msg);
+ }
+ dbus_connection_read_write(fd.connection, 0);
+ }
+ }
+ if (remove) {
+ portal->file_dialogs.remove_at(i);
+ }
+ }
+ }
+ usleep(50000);
+ }
+}
+
FreeDesktopPortalDesktop::FreeDesktopPortalDesktop() {
#ifdef SOWRAP_ENABLED
#ifdef DEBUG_ENABLED
@@ -153,6 +468,27 @@ FreeDesktopPortalDesktop::FreeDesktopPortalDesktop() {
print_verbose("PortalDesktop: Unsupported DBus library version!");
unsupported = true;
}
+
+ if (!unsupported) {
+ file_dialog_thread_abort.clear();
+ file_dialog_thread.start(FreeDesktopPortalDesktop::_thread_file_dialog_monitor, this);
+ }
+}
+
+FreeDesktopPortalDesktop::~FreeDesktopPortalDesktop() {
+ file_dialog_thread_abort.set();
+ if (file_dialog_thread.is_started()) {
+ file_dialog_thread.wait_to_finish();
+ }
+ for (FreeDesktopPortalDesktop::FileDialogData &fd : file_dialogs) {
+ if (fd.connection) {
+ DBusError err;
+ dbus_error_init(&err);
+ dbus_bus_remove_match(fd.connection, fd.path.utf8().get_data(), &err);
+ dbus_error_free(&err);
+ dbus_connection_unref(fd.connection);
+ }
+ }
}
#endif // DBUS_ENABLED
diff --git a/platform/linuxbsd/freedesktop_portal_desktop.h b/platform/linuxbsd/freedesktop_portal_desktop.h
index 1520ab3a1f..a9b83b3844 100644
--- a/platform/linuxbsd/freedesktop_portal_desktop.h
+++ b/platform/linuxbsd/freedesktop_portal_desktop.h
@@ -33,20 +33,48 @@
#ifdef DBUS_ENABLED
-#include <stdint.h>
+#include "core/os/thread.h"
+#include "servers/display_server.h"
+
+struct DBusMessage;
+struct DBusConnection;
+struct DBusMessageIter;
class FreeDesktopPortalDesktop {
private:
bool unsupported = false;
+ static bool try_parse_variant(DBusMessage *p_reply_message, int p_type, void *r_value);
// Read a setting from org.freekdesktop.portal.Settings
bool read_setting(const char *p_namespace, const char *p_key, int p_type, void *r_value);
+ static void append_dbus_string(DBusMessageIter *p_iter, const String &p_string);
+ static void append_dbus_dict_filters(DBusMessageIter *p_iter, const Vector<String> &p_filters);
+ static void append_dbus_dict_string(DBusMessageIter *p_iter, const String &p_key, const String &p_value, bool p_as_byte_array = false);
+ static void append_dbus_dict_bool(DBusMessageIter *p_iter, const String &p_key, bool p_value);
+ static bool file_chooser_parse_response(DBusMessageIter *p_iter, bool &r_cancel, Vector<String> &r_urls);
+
+ struct FileDialogData {
+ DBusConnection *connection = nullptr;
+ Callable callback;
+ String path;
+ };
+
+ Mutex file_dialog_mutex;
+ Vector<FileDialogData> file_dialogs;
+ Thread file_dialog_thread;
+ SafeFlag file_dialog_thread_abort;
+
+ static void _thread_file_dialog_monitor(void *p_ud);
+
public:
FreeDesktopPortalDesktop();
+ ~FreeDesktopPortalDesktop();
bool is_supported() { return !unsupported; }
+ Error file_dialog_show(const String &p_xid, const String &p_title, const String &p_current_directory, const String &p_filename, DisplayServer::FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback);
+
// Retrieve the system's preferred color scheme.
// 0: No preference or unknown.
// 1: Prefer dark appearance.
diff --git a/platform/linuxbsd/joypad_linux.cpp b/platform/linuxbsd/joypad_linux.cpp
index 5d636240b7..827c567785 100644
--- a/platform/linuxbsd/joypad_linux.cpp
+++ b/platform/linuxbsd/joypad_linux.cpp
@@ -82,31 +82,9 @@ void JoypadLinux::Joypad::reset() {
events.clear();
}
-#ifdef UDEV_ENABLED
-// This function is derived from SDL:
-// https://github.com/libsdl-org/SDL/blob/main/src/core/linux/SDL_sandbox.c#L28-L45
-static bool detect_sandbox() {
- if (access("/.flatpak-info", F_OK) == 0) {
- return true;
- }
-
- // For Snap, we check multiple variables because they might be set for
- // unrelated reasons. This is the same thing WebKitGTK does.
- if (OS::get_singleton()->has_environment("SNAP") && OS::get_singleton()->has_environment("SNAP_NAME") && OS::get_singleton()->has_environment("SNAP_REVISION")) {
- return true;
- }
-
- if (access("/run/host/container-manager", F_OK) == 0) {
- return true;
- }
-
- return false;
-}
-#endif // UDEV_ENABLED
-
JoypadLinux::JoypadLinux(Input *in) {
#ifdef UDEV_ENABLED
- if (detect_sandbox()) {
+ if (OS::get_singleton()->is_sandboxed()) {
// Linux binaries in sandboxes / containers need special handling because
// libudev doesn't work there. So we need to fallback to manual parsing
// of /dev/input in such case.
diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp
index 14d02a73c8..d22d398a67 100644
--- a/platform/linuxbsd/os_linuxbsd.cpp
+++ b/platform/linuxbsd/os_linuxbsd.cpp
@@ -164,6 +164,27 @@ String OS_LinuxBSD::get_processor_name() const {
ERR_FAIL_V_MSG("", String("Couldn't get the CPU model name from `/proc/cpuinfo`. Returning an empty string."));
}
+bool OS_LinuxBSD::is_sandboxed() const {
+ // This function is derived from SDL:
+ // https://github.com/libsdl-org/SDL/blob/main/src/core/linux/SDL_sandbox.c#L28-L45
+
+ if (access("/.flatpak-info", F_OK) == 0) {
+ return true;
+ }
+
+ // For Snap, we check multiple variables because they might be set for
+ // unrelated reasons. This is the same thing WebKitGTK does.
+ if (has_environment("SNAP") && has_environment("SNAP_NAME") && has_environment("SNAP_REVISION")) {
+ return true;
+ }
+
+ if (access("/run/host/container-manager", F_OK) == 0) {
+ return true;
+ }
+
+ return false;
+}
+
void OS_LinuxBSD::finalize() {
if (main_loop) {
memdelete(main_loop);
diff --git a/platform/linuxbsd/os_linuxbsd.h b/platform/linuxbsd/os_linuxbsd.h
index 007b90b82b..6917ea5ae7 100644
--- a/platform/linuxbsd/os_linuxbsd.h
+++ b/platform/linuxbsd/os_linuxbsd.h
@@ -123,6 +123,8 @@ public:
virtual String get_unique_id() const override;
virtual String get_processor_name() const override;
+ virtual bool is_sandboxed() const override;
+
virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override;
virtual bool _check_internal_feature_support(const String &p_feature) override;
diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp
index f0864f5134..f38a9dd278 100644
--- a/platform/linuxbsd/x11/display_server_x11.cpp
+++ b/platform/linuxbsd/x11/display_server_x11.cpp
@@ -122,6 +122,9 @@ bool DisplayServerX11::has_feature(Feature p_feature) const {
case FEATURE_WINDOW_TRANSPARENCY:
//case FEATURE_HIDPI:
case FEATURE_ICON:
+#ifdef DBUS_ENABLED
+ case FEATURE_NATIVE_DIALOG:
+#endif
//case FEATURE_NATIVE_ICON:
case FEATURE_SWAP_BUFFERS:
#ifdef DBUS_ENABLED
@@ -360,6 +363,17 @@ bool DisplayServerX11::is_dark_mode() const {
}
}
+Error DisplayServerX11::file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) {
+ WindowID window_id = _get_focused_window_or_popup();
+
+ if (!windows.has(window_id)) {
+ window_id = MAIN_WINDOW_ID;
+ }
+
+ String xid = vformat("x11:%x", (uint64_t)windows[window_id].x11_window);
+ return portal_desktop->file_dialog_show(xid, p_title, p_current_directory, p_filename, p_mode, p_filters, p_callback);
+}
+
#endif
void DisplayServerX11::mouse_set_mode(MouseMode p_mode) {
@@ -2112,9 +2126,10 @@ bool DisplayServerX11::_window_maximize_check(WindowID p_window, const char *p_a
bool DisplayServerX11::_window_minimize_check(WindowID p_window) const {
const WindowData &wd = windows[p_window];
- // Using ICCCM -- Inter-Client Communication Conventions Manual
- Atom property = XInternAtom(x11_display, "WM_STATE", True);
- if (property == None) {
+ // Using EWMH instead of ICCCM, might work better for Wayland users.
+ Atom property = XInternAtom(x11_display, "_NET_WM_STATE", True);
+ Atom hidden = XInternAtom(x11_display, "_NET_WM_STATE_HIDDEN", True);
+ if (property == None || hidden == None) {
return false;
}
@@ -2122,7 +2137,7 @@ bool DisplayServerX11::_window_minimize_check(WindowID p_window) const {
int format;
unsigned long len;
unsigned long remaining;
- unsigned char *data = nullptr;
+ Atom *atoms = nullptr;
int result = XGetWindowProperty(
x11_display,
@@ -2131,20 +2146,21 @@ bool DisplayServerX11::_window_minimize_check(WindowID p_window) const {
0,
32,
False,
- AnyPropertyType,
+ XA_ATOM,
&type,
&format,
&len,
&remaining,
- &data);
+ (unsigned char **)&atoms);
- if (result == Success && data) {
- long *state = (long *)data;
- if (state[0] == WM_IconicState) {
- XFree(data);
- return true;
+ if (result == Success && atoms) {
+ for (unsigned int i = 0; i < len; i++) {
+ if (atoms[i] == hidden) {
+ XFree(atoms);
+ return true;
+ }
}
- XFree(data);
+ XFree(atoms);
}
return false;
diff --git a/platform/linuxbsd/x11/display_server_x11.h b/platform/linuxbsd/x11/display_server_x11.h
index cce44a92de..71beddce76 100644
--- a/platform/linuxbsd/x11/display_server_x11.h
+++ b/platform/linuxbsd/x11/display_server_x11.h
@@ -393,6 +393,8 @@ public:
#if defined(DBUS_ENABLED)
virtual bool is_dark_mode_supported() const override;
virtual bool is_dark_mode() const override;
+
+ virtual Error file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) override;
#endif
virtual void mouse_set_mode(MouseMode p_mode) override;
diff --git a/platform/macos/export/export_plugin.cpp b/platform/macos/export/export_plugin.cpp
index e788ff1eec..6586fe7f82 100644
--- a/platform/macos/export/export_plugin.cpp
+++ b/platform/macos/export/export_plugin.cpp
@@ -41,6 +41,7 @@
#include "editor/editor_node.h"
#include "editor/editor_paths.h"
#include "editor/editor_scale.h"
+#include "editor/import/resource_importer_texture_settings.h"
#include "scene/resources/image_texture.h"
#include "modules/modules_enabled.gen.h" // For svg and regex.
@@ -2124,16 +2125,12 @@ bool EditorExportPlatformMacOS::has_valid_export_configuration(const Ref<EditorE
// Check the texture formats, which vary depending on the target architecture.
String architecture = p_preset->get("binary_format/architecture");
if (architecture == "universal" || architecture == "x86_64") {
- const String bc_error = test_bc();
- if (!bc_error.is_empty()) {
+ if (!ResourceImporterTextureSettings::should_import_s3tc_bptc()) {
valid = false;
- err += bc_error;
}
} else if (architecture == "arm64") {
- const String etc_error = test_etc2();
- if (!etc_error.is_empty()) {
+ if (!ResourceImporterTextureSettings::should_import_etc2_astc()) {
valid = false;
- err += etc_error;
}
} else {
ERR_PRINT("Invalid architecture");
diff --git a/platform/web/SCsub b/platform/web/SCsub
index e44e59bfb9..1af0642554 100644
--- a/platform/web/SCsub
+++ b/platform/web/SCsub
@@ -70,6 +70,9 @@ if env["dlink_enabled"]:
sys_env.Append(LINKFLAGS=["-s", "WARN_ON_UNDEFINED_SYMBOLS=0"])
# Force exporting the standard library (printf, malloc, etc.)
sys_env["ENV"]["EMCC_FORCE_STDLIBS"] = "libc,libc++,libc++abi"
+ sys_env["CCFLAGS"].remove("-fvisibility=hidden")
+ sys_env["LINKFLAGS"].remove("-fvisibility=hidden")
+
# The main emscripten runtime, with exported standard libraries.
sys = sys_env.Program(build_targets, ["web_runtime.cpp"])
diff --git a/platform/web/detect.py b/platform/web/detect.py
index dc8f4e0dd9..4015c8ff16 100644
--- a/platform/web/detect.py
+++ b/platform/web/detect.py
@@ -208,6 +208,8 @@ def configure(env: "Environment"):
env.Append(CCFLAGS=["-s", "SIDE_MODULE=2"])
env.Append(LINKFLAGS=["-s", "SIDE_MODULE=2"])
+ env.Append(CCFLAGS=["-fvisibility=hidden"])
+ env.Append(LINKFLAGS=["-fvisibility=hidden"])
env.extra_suffix = ".dlink" + env.extra_suffix
# Reduce code size by generating less support code (e.g. skip NodeJS support).
diff --git a/platform/web/export/export_plugin.cpp b/platform/web/export/export_plugin.cpp
index 38e2714d9f..993abd2cee 100644
--- a/platform/web/export/export_plugin.cpp
+++ b/platform/web/export/export_plugin.cpp
@@ -37,6 +37,7 @@
#include "editor/editor_scale.h"
#include "editor/editor_settings.h"
#include "editor/export/editor_export.h"
+#include "editor/import/resource_importer_texture_settings.h"
#include "scene/resources/image_texture.h"
#include "modules/modules_enabled.gen.h" // For mono and svg.
@@ -406,10 +407,8 @@ bool EditorExportPlatformWeb::has_valid_project_configuration(const Ref<EditorEx
// Validate the project configuration.
if (p_preset->get("vram_texture_compression/for_mobile")) {
- String etc_error = test_etc2();
- if (!etc_error.is_empty()) {
+ if (!ResourceImporterTextureSettings::should_import_etc2_astc()) {
valid = false;
- err += etc_error;
}
}
diff --git a/scene/3d/navigation_region_3d.cpp b/scene/3d/navigation_region_3d.cpp
index 367a1d445f..e03640f6cb 100644
--- a/scene/3d/navigation_region_3d.cpp
+++ b/scene/3d/navigation_region_3d.cpp
@@ -236,62 +236,29 @@ RID NavigationRegion3D::get_navigation_map() const {
return RID();
}
-struct BakeThreadsArgs {
- NavigationRegion3D *nav_region = nullptr;
- Ref<NavigationMeshSourceGeometryData3D> source_geometry_data;
-};
-
-void _bake_navigation_mesh(void *p_user_data) {
- BakeThreadsArgs *args = static_cast<BakeThreadsArgs *>(p_user_data);
-
- if (args->nav_region->get_navigation_mesh().is_valid()) {
- Ref<NavigationMesh> nav_mesh = args->nav_region->get_navigation_mesh();
- Ref<NavigationMeshSourceGeometryData3D> source_geometry_data = args->source_geometry_data;
-
- NavigationServer3D::get_singleton()->bake_from_source_geometry_data(nav_mesh, source_geometry_data);
- if (!Thread::is_main_thread()) {
- args->nav_region->call_deferred(SNAME("_bake_finished"), nav_mesh);
- } else {
- args->nav_region->_bake_finished(nav_mesh);
- }
- memdelete(args);
- } else {
- ERR_PRINT("Can't bake the navigation mesh if the `NavigationMesh` resource doesn't exist");
- if (!Thread::is_main_thread()) {
- args->nav_region->call_deferred(SNAME("_bake_finished"), Ref<NavigationMesh>());
- } else {
- args->nav_region->_bake_finished(Ref<NavigationMesh>());
- }
- memdelete(args);
- }
-}
-
void NavigationRegion3D::bake_navigation_mesh(bool p_on_thread) {
ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "The SceneTree can only be parsed on the main thread. Call this function from the main thread or use call_deferred().");
ERR_FAIL_COND_MSG(!navigation_mesh.is_valid(), "Baking the navigation mesh requires a valid `NavigationMesh` resource.");
- ERR_FAIL_COND_MSG(bake_thread.is_started(), "Unable to start another bake request. The navigation mesh bake thread is already baking a navigation mesh.");
Ref<NavigationMeshSourceGeometryData3D> source_geometry_data;
source_geometry_data.instantiate();
NavigationServer3D::get_singleton()->parse_source_geometry_data(navigation_mesh, source_geometry_data, this);
- BakeThreadsArgs *args = memnew(BakeThreadsArgs);
- args->nav_region = this;
- args->source_geometry_data = source_geometry_data;
-
if (p_on_thread) {
- bake_thread.start(_bake_navigation_mesh, args);
+ NavigationServer3D::get_singleton()->bake_from_source_geometry_data_async(navigation_mesh, source_geometry_data, callable_mp(this, &NavigationRegion3D::_bake_finished).bind(navigation_mesh));
} else {
- _bake_navigation_mesh(args);
+ NavigationServer3D::get_singleton()->bake_from_source_geometry_data(navigation_mesh, source_geometry_data, callable_mp(this, &NavigationRegion3D::_bake_finished).bind(navigation_mesh));
}
}
-void NavigationRegion3D::_bake_finished(Ref<NavigationMesh> p_nav_mesh) {
- set_navigation_mesh(p_nav_mesh);
- if (bake_thread.is_started()) {
- bake_thread.wait_to_finish();
+void NavigationRegion3D::_bake_finished(Ref<NavigationMesh> p_navigation_mesh) {
+ if (!Thread::is_main_thread()) {
+ call_deferred(SNAME("_bake_finished"), p_navigation_mesh);
+ return;
}
+
+ set_navigation_mesh(p_navigation_mesh);
emit_signal(SNAME("bake_finished"));
}
@@ -452,10 +419,6 @@ NavigationRegion3D::NavigationRegion3D() {
}
NavigationRegion3D::~NavigationRegion3D() {
- if (bake_thread.is_started()) {
- bake_thread.wait_to_finish();
- }
-
if (navigation_mesh.is_valid()) {
navigation_mesh->disconnect_changed(callable_mp(this, &NavigationRegion3D::_navigation_mesh_changed));
}
diff --git a/scene/3d/navigation_region_3d.h b/scene/3d/navigation_region_3d.h
index e41d07f4cf..02fe5524b2 100644
--- a/scene/3d/navigation_region_3d.h
+++ b/scene/3d/navigation_region_3d.h
@@ -49,8 +49,6 @@ class NavigationRegion3D : public Node3D {
Transform3D current_global_transform;
- Thread bake_thread;
-
void _navigation_mesh_changed();
#ifdef DEBUG_ENABLED
diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp
index 715d8a5bc1..8da1ef8e1d 100644
--- a/scene/animation/animation_blend_tree.cpp
+++ b/scene/animation/animation_blend_tree.cpp
@@ -150,7 +150,7 @@ double AnimationNodeAnimation::_process(double p_time, bool p_seek, bool p_is_ex
// Emit start & finish signal. Internally, the detections are the same for backward.
// We should use call_deferred since the track keys are still being prosessed.
- if (state->tree) {
+ if (state->tree && !p_test_only) {
// AnimationTree uses seek to 0 "internally" to process the first key of the animation, which is used as the start detection.
if (p_seek && !p_is_external_seeking && cur_time == 0) {
state->tree->call_deferred(SNAME("emit_signal"), "animation_started", animation);
diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp
index b273f709f2..cff07c6e1c 100644
--- a/scene/gui/item_list.cpp
+++ b/scene/gui/item_list.cpp
@@ -675,11 +675,18 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
if (closest != -1 && (mb->get_button_index() == MouseButton::LEFT || (allow_rmb_select && mb->get_button_index() == MouseButton::RIGHT))) {
int i = closest;
+ if (items[i].disabled) {
+ // Don't emit any signal or do any action with clicked item when disabled.
+ return;
+ }
+
if (select_mode == SELECT_MULTI && items[i].selected && mb->is_command_or_control_pressed()) {
deselect(i);
emit_signal(SNAME("multi_selected"), i, false);
} else if (select_mode == SELECT_MULTI && mb->is_shift_pressed() && current >= 0 && current < items.size() && current != i) {
+ // Range selection.
+
int from = current;
int to = i;
if (i < current) {
@@ -687,6 +694,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
}
for (int j = from; j <= to; j++) {
if (!CAN_SELECT(j)) {
+ // Item is not selectable during a range selection, so skip it.
continue;
}
bool selected = !items[j].selected;
@@ -698,12 +706,17 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
emit_signal(SNAME("item_clicked"), i, get_local_mouse_position(), mb->get_button_index());
} else {
- if (!mb->is_double_click() && !mb->is_command_or_control_pressed() && select_mode == SELECT_MULTI && items[i].selectable && !items[i].disabled && items[i].selected && mb->get_button_index() == MouseButton::LEFT) {
+ if (!mb->is_double_click() &&
+ !mb->is_command_or_control_pressed() &&
+ select_mode == SELECT_MULTI &&
+ items[i].selectable &&
+ items[i].selected &&
+ mb->get_button_index() == MouseButton::LEFT) {
defer_select_single = i;
return;
}
- if (!items[i].selected || allow_reselect) {
+ if (items[i].selectable && (!items[i].selected || allow_reselect)) {
select(i, select_mode == SELECT_SINGLE || !mb->is_command_or_control_pressed());
if (select_mode == SELECT_SINGLE) {
@@ -722,7 +735,9 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
return;
} else if (closest != -1) {
- emit_signal(SNAME("item_clicked"), closest, get_local_mouse_position(), mb->get_button_index());
+ if (!items[closest].disabled) {
+ emit_signal(SNAME("item_clicked"), closest, get_local_mouse_position(), mb->get_button_index());
+ }
} else {
// Since closest is null, more likely we clicked on empty space, so send signal to interested controls. Allows, for example, implement items deselecting.
emit_signal(SNAME("empty_clicked"), get_local_mouse_position(), mb->get_button_index());
@@ -886,7 +901,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
search_string = "";
} else if (p_event->is_action("ui_select", true) && select_mode == SELECT_MULTI) {
if (current >= 0 && current < items.size()) {
- if (items[current].selectable && !items[current].disabled && !items[current].selected) {
+ if (CAN_SELECT(current) && !items[current].selected) {
select(current, false);
emit_signal(SNAME("multi_selected"), current, true);
} else if (items[current].selected) {
@@ -897,7 +912,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
} else if (p_event->is_action("ui_accept", true)) {
search_string = ""; //any mousepress cancels
- if (current >= 0 && current < items.size()) {
+ if (current >= 0 && current < items.size() && !items[current].disabled) {
emit_signal(SNAME("item_activated"), current);
}
} else {
diff --git a/scene/gui/menu_bar.cpp b/scene/gui/menu_bar.cpp
index 85068ac862..0dd258d92c 100644
--- a/scene/gui/menu_bar.cpp
+++ b/scene/gui/menu_bar.cpp
@@ -152,7 +152,7 @@ void MenuBar::shortcut_input(const Ref<InputEvent> &p_event) {
return;
}
- if (p_event->is_pressed() && !p_event->is_echo() && (Object::cast_to<InputEventKey>(p_event.ptr()) || Object::cast_to<InputEventJoypadButton>(p_event.ptr()) || Object::cast_to<InputEventAction>(*p_event) || Object::cast_to<InputEventShortcut>(*p_event))) {
+ if (p_event->is_pressed() && (Object::cast_to<InputEventKey>(p_event.ptr()) || Object::cast_to<InputEventJoypadButton>(p_event.ptr()) || Object::cast_to<InputEventAction>(*p_event) || Object::cast_to<InputEventShortcut>(*p_event))) {
if (!get_parent() || !is_visible_in_tree()) {
return;
}
diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp
index 4e80d7a2d6..868383b141 100644
--- a/scene/gui/menu_button.cpp
+++ b/scene/gui/menu_button.cpp
@@ -40,7 +40,7 @@ void MenuButton::shortcut_input(const Ref<InputEvent> &p_event) {
return;
}
- if (p_event->is_pressed() && !p_event->is_echo() && !is_disabled() && is_visible_in_tree() && popup->activate_item_by_event(p_event, false)) {
+ if (p_event->is_pressed() && !is_disabled() && is_visible_in_tree() && popup->activate_item_by_event(p_event, false)) {
accept_event();
return;
}
diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp
index 2b8c85c823..a260385a46 100644
--- a/scene/gui/option_button.cpp
+++ b/scene/gui/option_button.cpp
@@ -30,10 +30,26 @@
#include "option_button.h"
+#include "core/os/keyboard.h"
#include "core/string/print_string.h"
static const int NONE_SELECTED = -1;
+void OptionButton::shortcut_input(const Ref<InputEvent> &p_event) {
+ ERR_FAIL_COND(p_event.is_null());
+
+ if (disable_shortcuts) {
+ return;
+ }
+
+ if (p_event->is_pressed() && !p_event->is_echo() && !is_disabled() && is_visible_in_tree() && popup->activate_item_by_event(p_event, false)) {
+ accept_event();
+ return;
+ }
+
+ Button::shortcut_input(p_event);
+}
+
Size2 OptionButton::get_minimum_size() const {
Size2 minsize;
if (fit_to_longest_item) {
@@ -574,6 +590,7 @@ void OptionButton::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_fit_to_longest_item"), &OptionButton::is_fit_to_longest_item);
ClassDB::bind_method(D_METHOD("set_allow_reselect", "allow"), &OptionButton::set_allow_reselect);
ClassDB::bind_method(D_METHOD("get_allow_reselect"), &OptionButton::get_allow_reselect);
+ ClassDB::bind_method(D_METHOD("set_disable_shortcuts", "disabled"), &OptionButton::set_disable_shortcuts);
// "selected" property must come after "item_count", otherwise GH-10213 occurs.
ADD_ARRAY_COUNT("Items", "item_count", "set_item_count", "get_item_count", "popup/item_");
@@ -584,9 +601,14 @@ void OptionButton::_bind_methods() {
ADD_SIGNAL(MethodInfo("item_focused", PropertyInfo(Variant::INT, "index")));
}
+void OptionButton::set_disable_shortcuts(bool p_disabled) {
+ disable_shortcuts = p_disabled;
+}
+
OptionButton::OptionButton(const String &p_text) :
Button(p_text) {
set_toggle_mode(true);
+ set_process_shortcut_input(true);
set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT);
set_action_mode(ACTION_MODE_BUTTON_PRESS);
diff --git a/scene/gui/option_button.h b/scene/gui/option_button.h
index 7dcb3319c6..e29f14ad54 100644
--- a/scene/gui/option_button.h
+++ b/scene/gui/option_button.h
@@ -37,6 +37,7 @@
class OptionButton : public Button {
GDCLASS(OptionButton, Button);
+ bool disable_shortcuts = false;
PopupMenu *popup = nullptr;
int current = -1;
bool fit_to_longest_item = true;
@@ -79,6 +80,7 @@ protected:
void _get_property_list(List<PropertyInfo> *p_list) const;
void _validate_property(PropertyInfo &p_property) const;
static void _bind_methods();
+ virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
public:
// ATTENTION: This is used by the POT generator's scene parser. If the number of properties returned by `_get_items()` ever changes,
@@ -129,6 +131,8 @@ public:
PopupMenu *get_popup() const;
void show_popup();
+ void set_disable_shortcuts(bool p_disabled);
+
OptionButton(const String &p_text = String());
~OptionButton();
};
diff --git a/scene/gui/popup_menu.compat.inc b/scene/gui/popup_menu.compat.inc
new file mode 100644
index 0000000000..ef74a17228
--- /dev/null
+++ b/scene/gui/popup_menu.compat.inc
@@ -0,0 +1,46 @@
+/**************************************************************************/
+/* popup_menu.compat.inc */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef DISABLE_DEPRECATED
+
+void PopupMenu::_add_shortcut_bind_compat_36493(const Ref<Shortcut> &p_shortcut, int p_id, bool p_global) {
+ return add_shortcut(p_shortcut, p_id, p_global, false);
+}
+
+void PopupMenu::_add_icon_shortcut_bind_compat_36493(const Ref<Texture2D> &p_icon, const Ref<Shortcut> &p_shortcut, int p_id, bool p_global) {
+ return add_icon_shortcut(p_icon, p_shortcut, p_id, p_global, false);
+}
+
+void PopupMenu::_bind_compatibility_methods() {
+ ClassDB::bind_compatibility_method(D_METHOD("add_shortcut", "shortcut", "id", "global"), &PopupMenu::_add_shortcut_bind_compat_36493, DEFVAL(-1), DEFVAL(false));
+ ClassDB::bind_compatibility_method(D_METHOD("add_icon_shortcut", "texture", "shortcut", "id", "global"), &PopupMenu::_add_icon_shortcut_bind_compat_36493, DEFVAL(-1), DEFVAL(false));
+}
+
+#endif
diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp
index 40db8deaac..4bba33f18e 100644
--- a/scene/gui/popup_menu.cpp
+++ b/scene/gui/popup_menu.cpp
@@ -29,6 +29,7 @@
/**************************************************************************/
#include "popup_menu.h"
+#include "popup_menu.compat.inc"
#include "core/config/project_settings.h"
#include "core/input/input.h"
@@ -1164,18 +1165,19 @@ void PopupMenu::add_multistate_item(const String &p_label, int p_max_states, int
_menu_changed();
}
-#define ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global) \
+#define ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global, p_allow_echo) \
ERR_FAIL_COND_MSG(p_shortcut.is_null(), "Cannot add item with invalid Shortcut."); \
_ref_shortcut(p_shortcut); \
item.text = p_shortcut->get_name(); \
item.xl_text = atr(item.text); \
item.id = p_id == -1 ? items.size() : p_id; \
item.shortcut = p_shortcut; \
- item.shortcut_is_global = p_global;
+ item.shortcut_is_global = p_global; \
+ item.allow_echo = p_allow_echo;
-void PopupMenu::add_shortcut(const Ref<Shortcut> &p_shortcut, int p_id, bool p_global) {
+void PopupMenu::add_shortcut(const Ref<Shortcut> &p_shortcut, int p_id, bool p_global, bool p_allow_echo) {
Item item;
- ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global);
+ ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global, p_allow_echo);
items.push_back(item);
_shape_item(items.size() - 1);
@@ -1185,9 +1187,9 @@ void PopupMenu::add_shortcut(const Ref<Shortcut> &p_shortcut, int p_id, bool p_g
_menu_changed();
}
-void PopupMenu::add_icon_shortcut(const Ref<Texture2D> &p_icon, const Ref<Shortcut> &p_shortcut, int p_id, bool p_global) {
+void PopupMenu::add_icon_shortcut(const Ref<Texture2D> &p_icon, const Ref<Shortcut> &p_shortcut, int p_id, bool p_global, bool p_allow_echo) {
Item item;
- ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global);
+ ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global, p_allow_echo);
item.icon = p_icon;
items.push_back(item);
@@ -1200,7 +1202,7 @@ void PopupMenu::add_icon_shortcut(const Ref<Texture2D> &p_icon, const Ref<Shortc
void PopupMenu::add_check_shortcut(const Ref<Shortcut> &p_shortcut, int p_id, bool p_global) {
Item item;
- ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global);
+ ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global, false); // Echo for check shortcuts doesn't make sense.
item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
items.push_back(item);
@@ -1213,7 +1215,7 @@ void PopupMenu::add_check_shortcut(const Ref<Shortcut> &p_shortcut, int p_id, bo
void PopupMenu::add_icon_check_shortcut(const Ref<Texture2D> &p_icon, const Ref<Shortcut> &p_shortcut, int p_id, bool p_global) {
Item item;
- ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global);
+ ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global, false);
item.icon = p_icon;
item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
items.push_back(item);
@@ -1227,7 +1229,7 @@ void PopupMenu::add_icon_check_shortcut(const Ref<Texture2D> &p_icon, const Ref<
void PopupMenu::add_radio_check_shortcut(const Ref<Shortcut> &p_shortcut, int p_id, bool p_global) {
Item item;
- ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global);
+ ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global, false);
item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
items.push_back(item);
@@ -1240,7 +1242,7 @@ void PopupMenu::add_radio_check_shortcut(const Ref<Shortcut> &p_shortcut, int p_
void PopupMenu::add_icon_radio_check_shortcut(const Ref<Texture2D> &p_icon, const Ref<Shortcut> &p_shortcut, int p_id, bool p_global) {
Item item;
- ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global);
+ ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global, false);
item.icon = p_icon;
item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
items.push_back(item);
@@ -1838,7 +1840,7 @@ bool PopupMenu::activate_item_by_event(const Ref<InputEvent> &p_event, bool p_fo
}
for (int i = 0; i < items.size(); i++) {
- if (is_item_disabled(i) || items[i].shortcut_is_disabled) {
+ if (is_item_disabled(i) || items[i].shortcut_is_disabled || (!items[i].allow_echo && p_event->is_echo())) {
continue;
}
@@ -2213,8 +2215,8 @@ void PopupMenu::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_multistate_item", "label", "max_states", "default_state", "id", "accel"), &PopupMenu::add_multistate_item, DEFVAL(0), DEFVAL(-1), DEFVAL(0));
- ClassDB::bind_method(D_METHOD("add_shortcut", "shortcut", "id", "global"), &PopupMenu::add_shortcut, DEFVAL(-1), DEFVAL(false));
- ClassDB::bind_method(D_METHOD("add_icon_shortcut", "texture", "shortcut", "id", "global"), &PopupMenu::add_icon_shortcut, DEFVAL(-1), DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("add_shortcut", "shortcut", "id", "global", "allow_echo"), &PopupMenu::add_shortcut, DEFVAL(-1), DEFVAL(false), DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("add_icon_shortcut", "texture", "shortcut", "id", "global", "allow_echo"), &PopupMenu::add_icon_shortcut, DEFVAL(-1), DEFVAL(false), DEFVAL(false));
ClassDB::bind_method(D_METHOD("add_check_shortcut", "shortcut", "id", "global"), &PopupMenu::add_check_shortcut, DEFVAL(-1), DEFVAL(false));
ClassDB::bind_method(D_METHOD("add_icon_check_shortcut", "texture", "shortcut", "id", "global"), &PopupMenu::add_icon_check_shortcut, DEFVAL(-1), DEFVAL(false));
ClassDB::bind_method(D_METHOD("add_radio_check_shortcut", "shortcut", "id", "global"), &PopupMenu::add_radio_check_shortcut, DEFVAL(-1), DEFVAL(false));
diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h
index 5ad9cd4303..ef754315f0 100644
--- a/scene/gui/popup_menu.h
+++ b/scene/gui/popup_menu.h
@@ -74,6 +74,7 @@ class PopupMenu : public Popup {
Ref<Shortcut> shortcut;
bool shortcut_is_global = false;
bool shortcut_is_disabled = false;
+ bool allow_echo = false;
// Returns (0,0) if icon is null.
Size2 get_icon_size() const {
@@ -199,6 +200,12 @@ protected:
void _get_property_list(List<PropertyInfo> *p_list) const;
static void _bind_methods();
+#ifndef DISABLE_DEPRECATED
+ void _add_shortcut_bind_compat_36493(const Ref<Shortcut> &p_shortcut, int p_id = -1, bool p_global = false);
+ void _add_icon_shortcut_bind_compat_36493(const Ref<Texture2D> &p_icon, const Ref<Shortcut> &p_shortcut, int p_id = -1, bool p_global = false);
+ static void _bind_compatibility_methods();
+#endif
+
public:
// ATTENTION: This is used by the POT generator's scene parser. If the number of properties returned by `_get_items()` ever changes,
// this value should be updated to reflect the new size.
@@ -215,8 +222,8 @@ public:
void add_multistate_item(const String &p_label, int p_max_states, int p_default_state = 0, int p_id = -1, Key p_accel = Key::NONE);
- void add_shortcut(const Ref<Shortcut> &p_shortcut, int p_id = -1, bool p_global = false);
- void add_icon_shortcut(const Ref<Texture2D> &p_icon, const Ref<Shortcut> &p_shortcut, int p_id = -1, bool p_global = false);
+ void add_shortcut(const Ref<Shortcut> &p_shortcut, int p_id = -1, bool p_global = false, bool p_allow_echo = false);
+ void add_icon_shortcut(const Ref<Texture2D> &p_icon, const Ref<Shortcut> &p_shortcut, int p_id = -1, bool p_global = false, bool p_allow_echo = false);
void add_check_shortcut(const Ref<Shortcut> &p_shortcut, int p_id = -1, bool p_global = false);
void add_icon_check_shortcut(const Ref<Texture2D> &p_icon, const Ref<Shortcut> &p_shortcut, int p_id = -1, bool p_global = false);
void add_radio_check_shortcut(const Ref<Shortcut> &p_shortcut, int p_id = -1, bool p_global = false);
diff --git a/servers/audio/audio_stream.h b/servers/audio/audio_stream.h
index 02a7651631..983ca8892d 100644
--- a/servers/audio/audio_stream.h
+++ b/servers/audio/audio_stream.h
@@ -223,8 +223,8 @@ private:
HashSet<AudioStreamPlaybackRandomizer *> playbacks;
Vector<PoolEntry> audio_stream_pool;
- float random_pitch_scale = 1.1f;
- float random_volume_offset_db = 5.0f;
+ float random_pitch_scale = 1.0f;
+ float random_volume_offset_db = 0.0f;
Ref<AudioStreamPlayback> instance_playback_random();
Ref<AudioStreamPlayback> instance_playback_no_repeats();
diff --git a/servers/navigation_server_3d.cpp b/servers/navigation_server_3d.cpp
index 04facdb8d9..75036b935b 100644
--- a/servers/navigation_server_3d.cpp
+++ b/servers/navigation_server_3d.cpp
@@ -155,6 +155,7 @@ void NavigationServer3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("parse_source_geometry_data", "navigation_mesh", "source_geometry_data", "root_node", "callback"), &NavigationServer3D::parse_source_geometry_data, DEFVAL(Callable()));
ClassDB::bind_method(D_METHOD("bake_from_source_geometry_data", "navigation_mesh", "source_geometry_data", "callback"), &NavigationServer3D::bake_from_source_geometry_data, DEFVAL(Callable()));
+ ClassDB::bind_method(D_METHOD("bake_from_source_geometry_data_async", "navigation_mesh", "source_geometry_data", "callback"), &NavigationServer3D::bake_from_source_geometry_data_async, DEFVAL(Callable()));
ClassDB::bind_method(D_METHOD("free_rid", "rid"), &NavigationServer3D::free);
@@ -204,6 +205,9 @@ NavigationServer3D::NavigationServer3D() {
GLOBAL_DEF("navigation/avoidance/thread_model/avoidance_use_multiple_threads", true);
GLOBAL_DEF("navigation/avoidance/thread_model/avoidance_use_high_priority_threads", true);
+ GLOBAL_DEF("navigation/baking/thread_model/baking_use_multiple_threads", true);
+ GLOBAL_DEF("navigation/baking/thread_model/baking_use_high_priority_threads", true);
+
#ifdef DEBUG_ENABLED
debug_navigation_edge_connection_color = GLOBAL_DEF("debug/shapes/navigation/edge_connection_color", Color(1.0, 0.0, 1.0, 1.0));
debug_navigation_geometry_edge_color = GLOBAL_DEF("debug/shapes/navigation/geometry_edge_color", Color(0.5, 1.0, 1.0, 1.0));
diff --git a/servers/navigation_server_3d.h b/servers/navigation_server_3d.h
index 4bf25f7a33..39f147357a 100644
--- a/servers/navigation_server_3d.h
+++ b/servers/navigation_server_3d.h
@@ -309,8 +309,9 @@ public:
virtual NavigationUtilities::PathQueryResult _query_path(const NavigationUtilities::PathQueryParameters &p_parameters) const = 0;
- virtual void parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable()) = 0;
- virtual void bake_from_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback = Callable()) = 0;
+ virtual void parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable()) = 0;
+ virtual void bake_from_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback = Callable()) = 0;
+ virtual void bake_from_source_geometry_data_async(const Ref<NavigationMesh> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback = Callable()) = 0;
NavigationServer3D();
~NavigationServer3D() override;
diff --git a/servers/navigation_server_3d_dummy.h b/servers/navigation_server_3d_dummy.h
index a5c9fc57f2..b1ec214bb0 100644
--- a/servers/navigation_server_3d_dummy.h
+++ b/servers/navigation_server_3d_dummy.h
@@ -145,8 +145,9 @@ public:
void obstacle_set_position(RID p_obstacle, Vector3 p_position) override {}
void obstacle_set_vertices(RID p_obstacle, const Vector<Vector3> &p_vertices) override {}
void obstacle_set_avoidance_layers(RID p_obstacle, uint32_t p_layers) override {}
- void parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable()) override {}
- void bake_from_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback = Callable()) override {}
+ void parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable()) override {}
+ void bake_from_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback = Callable()) override {}
+ void bake_from_source_geometry_data_async(const Ref<NavigationMesh> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback = Callable()) override {}
void free(RID p_object) override {}
void set_active(bool p_active) override {}
void process(real_t delta_time) override {}
diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
index 2974e9c4a3..c98752fb39 100644
--- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
+++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
@@ -433,10 +433,11 @@ void RenderForwardClustered::_render_list_template(RenderingDevice::DrawListID p
RID index_array_rd;
//skeleton and blend shape
+ bool pipeline_motion_vectors = pipeline_color_pass_flags & SceneShaderForwardClustered::PIPELINE_COLOR_PASS_FLAG_MOTION_VECTORS;
if (surf->owner->mesh_instance.is_valid()) {
- mesh_storage->mesh_instance_surface_get_vertex_arrays_and_format(surf->owner->mesh_instance, surf->surface_index, pipeline->get_vertex_input_mask(), vertex_array_rd, vertex_format);
+ mesh_storage->mesh_instance_surface_get_vertex_arrays_and_format(surf->owner->mesh_instance, surf->surface_index, pipeline->get_vertex_input_mask(), pipeline_motion_vectors, vertex_array_rd, vertex_format);
} else {
- mesh_storage->mesh_surface_get_vertex_arrays_and_format(mesh_surface, pipeline->get_vertex_input_mask(), vertex_array_rd, vertex_format);
+ mesh_storage->mesh_surface_get_vertex_arrays_and_format(mesh_surface, pipeline->get_vertex_input_mask(), pipeline_motion_vectors, vertex_array_rd, vertex_format);
}
index_array_rd = mesh_storage->mesh_surface_get_index_array(mesh_surface, element_info.lod_index);
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 befb2c5504..90d770399e 100644
--- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
+++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
@@ -2139,9 +2139,9 @@ void RenderForwardMobile::_render_list_template(RenderingDevice::DrawListID p_dr
//skeleton and blend shape
if (surf->owner->mesh_instance.is_valid()) {
- mesh_storage->mesh_instance_surface_get_vertex_arrays_and_format(surf->owner->mesh_instance, surf->surface_index, pipeline->get_vertex_input_mask(), vertex_array_rd, vertex_format);
+ mesh_storage->mesh_instance_surface_get_vertex_arrays_and_format(surf->owner->mesh_instance, surf->surface_index, pipeline->get_vertex_input_mask(), false, vertex_array_rd, vertex_format);
} else {
- mesh_storage->mesh_surface_get_vertex_arrays_and_format(mesh_surface, pipeline->get_vertex_input_mask(), vertex_array_rd, vertex_format);
+ mesh_storage->mesh_surface_get_vertex_arrays_and_format(mesh_surface, pipeline->get_vertex_input_mask(), false, vertex_array_rd, vertex_format);
}
index_array_rd = mesh_storage->mesh_surface_get_index_array(mesh_surface, element_info.lod_index);
diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
index 0ee76e0aa4..d70bff8593 100644
--- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
@@ -900,9 +900,9 @@ void RendererCanvasRenderRD::_render_item(RD::DrawListID p_draw_list, RID p_rend
RD::VertexFormatID vertex_format = RD::INVALID_FORMAT_ID;
if (mesh_instance.is_valid()) {
- mesh_storage->mesh_instance_surface_get_vertex_arrays_and_format(mesh_instance, j, input_mask, vertex_array, vertex_format);
+ mesh_storage->mesh_instance_surface_get_vertex_arrays_and_format(mesh_instance, j, input_mask, false, vertex_array, vertex_format);
} else {
- mesh_storage->mesh_surface_get_vertex_arrays_and_format(surface, input_mask, vertex_array, vertex_format);
+ mesh_storage->mesh_surface_get_vertex_arrays_and_format(surface, input_mask, false, vertex_array, vertex_format);
}
RID pipeline = pipeline_variants->variants[light_mode][variant[primitive]].get_render_pipeline(vertex_format, p_framebuffer_format);
diff --git a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl
index 9214a953aa..d1cfda515f 100644
--- a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl
+++ b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl
@@ -18,7 +18,11 @@ layout(location = 0) in vec3 vertex_attrib;
layout(location = 1) in vec2 normal_attrib;
#endif
-#if defined(TANGENT_USED) || defined(NORMAL_MAP_USED) || defined(LIGHT_ANISOTROPY_USED)
+#if !defined(TANGENT_USED) && (defined(NORMAL_MAP_USED) || defined(LIGHT_ANISOTROPY_USED))
+#define TANGENT_USED
+#endif
+
+#ifdef TANGENT_USED
layout(location = 2) in vec2 tangent_attrib;
#endif
@@ -58,6 +62,18 @@ layout(location = 10) in uvec4 bone_attrib;
layout(location = 11) in vec4 weight_attrib;
#endif
+#ifdef MOTION_VECTORS
+layout(location = 12) in vec3 previous_vertex_attrib;
+
+#ifdef NORMAL_USED
+layout(location = 13) in vec2 previous_normal_attrib;
+#endif
+
+#ifdef TANGENT_USED
+layout(location = 14) in vec2 previous_tangent_attrib;
+#endif
+#endif // MOTION_VECTORS
+
vec3 oct_to_vec3(vec2 e) {
vec3 v = vec3(e.xy, 1.0 - abs(e.x) - abs(e.y));
float t = max(-v.z, 0.0);
@@ -85,7 +101,7 @@ layout(location = 3) out vec2 uv_interp;
layout(location = 4) out vec2 uv2_interp;
#endif
-#if defined(TANGENT_USED) || defined(NORMAL_MAP_USED) || defined(LIGHT_ANISOTROPY_USED)
+#ifdef TANGENT_USED
layout(location = 5) out vec3 tangent_interp;
layout(location = 6) out vec3 binormal_interp;
#endif
@@ -161,7 +177,14 @@ vec3 double_add_vec3(vec3 base_a, vec3 prec_a, vec3 base_b, vec3 prec_b, out vec
}
#endif
-void vertex_shader(in uint instance_index, in bool is_multimesh, in uint multimesh_offset, in SceneData scene_data, in mat4 model_matrix, out vec4 screen_pos) {
+void vertex_shader(vec3 vertex_input,
+#ifdef NORMAL_USED
+ in vec2 normal_input,
+#endif
+#ifdef TANGENT_USED
+ in vec2 tangent_input,
+#endif
+ in uint instance_index, in bool is_multimesh, in uint multimesh_offset, in SceneData scene_data, in mat4 model_matrix, out vec4 screen_pos) {
vec4 instance_custom = vec4(0.0);
#if defined(COLOR_USED)
color_interp = color_attrib;
@@ -289,15 +312,15 @@ void vertex_shader(in uint instance_index, in bool is_multimesh, in uint multime
model_normal_matrix = model_normal_matrix * mat3(matrix);
}
- vec3 vertex = vertex_attrib;
+ vec3 vertex = vertex_input;
#ifdef NORMAL_USED
- vec3 normal = oct_to_vec3(normal_attrib * 2.0 - 1.0);
+ vec3 normal = oct_to_vec3(normal_input * 2.0 - 1.0);
#endif
-#if defined(TANGENT_USED) || defined(NORMAL_MAP_USED) || defined(LIGHT_ANISOTROPY_USED)
- vec2 signed_tangent_attrib = tangent_attrib * 2.0 - 1.0;
- vec3 tangent = oct_to_vec3(vec2(signed_tangent_attrib.x, abs(signed_tangent_attrib.y) * 2.0 - 1.0));
- float binormalf = sign(signed_tangent_attrib.y);
+#ifdef TANGENT_USED
+ vec2 signed_tangent_input = tangent_input * 2.0 - 1.0;
+ vec3 tangent = oct_to_vec3(vec2(signed_tangent_input.x, abs(signed_tangent_input.y) * 2.0 - 1.0));
+ float binormalf = sign(signed_tangent_input.y);
vec3 binormal = normalize(cross(normal, tangent) * binormalf);
#endif
@@ -333,7 +356,7 @@ void vertex_shader(in uint instance_index, in bool is_multimesh, in uint multime
normal = model_normal_matrix * normal;
#endif
-#if defined(TANGENT_USED) || defined(NORMAL_MAP_USED) || defined(LIGHT_ANISOTROPY_USED)
+#ifdef TANGENT_USED
tangent = model_normal_matrix * tangent;
binormal = model_normal_matrix * binormal;
@@ -377,7 +400,7 @@ void vertex_shader(in uint instance_index, in bool is_multimesh, in uint multime
#endif
-#if defined(TANGENT_USED) || defined(NORMAL_MAP_USED) || defined(LIGHT_ANISOTROPY_USED)
+#ifdef TANGENT_USED
binormal = modelview_normal * binormal;
tangent = modelview_normal * tangent;
@@ -391,7 +414,7 @@ void vertex_shader(in uint instance_index, in bool is_multimesh, in uint multime
normal = (scene_data.view_matrix * vec4(normal, 0.0)).xyz;
#endif
-#if defined(TANGENT_USED) || defined(NORMAL_MAP_USED) || defined(LIGHT_ANISOTROPY_USED)
+#ifdef TANGENT_USED
binormal = (scene_data.view_matrix * vec4(binormal, 0.0)).xyz;
tangent = (scene_data.view_matrix * vec4(tangent, 0.0)).xyz;
#endif
@@ -403,7 +426,7 @@ void vertex_shader(in uint instance_index, in bool is_multimesh, in uint multime
normal_interp = normal;
#endif
-#if defined(TANGENT_USED) || defined(NORMAL_MAP_USED) || defined(LIGHT_ANISOTROPY_USED)
+#ifdef TANGENT_USED
tangent_interp = tangent;
binormal_interp = binormal;
#endif
@@ -472,16 +495,33 @@ void main() {
instance_index_interp = instance_index;
mat4 model_matrix = instances.data[instance_index].transform;
-#if defined(MOTION_VECTORS)
+
+#ifdef MOTION_VECTORS
+ // Previous vertex.
global_time = scene_data_block.prev_data.time;
- vertex_shader(instance_index, is_multimesh, draw_call.multimesh_motion_vectors_previous_offset, scene_data_block.prev_data, instances.data[instance_index].prev_transform, prev_screen_position);
- global_time = scene_data_block.data.time;
- vertex_shader(instance_index, is_multimesh, draw_call.multimesh_motion_vectors_current_offset, scene_data_block.data, model_matrix, screen_position);
+ vertex_shader(previous_vertex_attrib,
+#ifdef NORMAL_USED
+ previous_normal_attrib,
+#endif
+#ifdef TANGENT_USED
+ previous_tangent_attrib,
+#endif
+ instance_index, is_multimesh, draw_call.multimesh_motion_vectors_previous_offset, scene_data_block.prev_data, instances.data[instance_index].prev_transform, prev_screen_position);
#else
- global_time = scene_data_block.data.time;
+ // Unused output.
vec4 screen_position;
- vertex_shader(instance_index, is_multimesh, draw_call.multimesh_motion_vectors_current_offset, scene_data_block.data, model_matrix, screen_position);
#endif
+
+ // Current vertex.
+ global_time = scene_data_block.data.time;
+ vertex_shader(vertex_attrib,
+#ifdef NORMAL_USED
+ normal_attrib,
+#endif
+#ifdef TANGENT_USED
+ tangent_attrib,
+#endif
+ instance_index, is_multimesh, draw_call.multimesh_motion_vectors_current_offset, scene_data_block.data, model_matrix, screen_position);
}
#[fragment]
@@ -535,7 +575,11 @@ layout(location = 3) in vec2 uv_interp;
layout(location = 4) in vec2 uv2_interp;
#endif
-#if defined(TANGENT_USED) || defined(NORMAL_MAP_USED) || defined(LIGHT_ANISOTROPY_USED)
+#if !defined(TANGENT_USED) && (defined(NORMAL_MAP_USED) || defined(LIGHT_ANISOTROPY_USED))
+#define TANGENT_USED
+#endif
+
+#ifdef TANGENT_USED
layout(location = 5) in vec3 tangent_interp;
layout(location = 6) in vec3 binormal_interp;
#endif
@@ -771,7 +815,7 @@ void fragment_shader(in SceneData scene_data) {
float alpha = float(instances.data[instance_index].flags >> INSTANCE_FLAGS_FADE_SHIFT) / float(255.0);
-#if defined(TANGENT_USED) || defined(NORMAL_MAP_USED) || defined(LIGHT_ANISOTROPY_USED)
+#ifdef TANGENT_USED
vec3 binormal = normalize(binormal_interp);
vec3 tangent = normalize(tangent_interp);
#else
diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp
index 56f2ea0b0c..439d0702f5 100644
--- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp
@@ -29,7 +29,6 @@
/**************************************************************************/
#include "mesh_storage.h"
-#include "../../rendering_server_globals.h"
using namespace RendererRD;
@@ -854,8 +853,11 @@ void MeshStorage::_mesh_instance_clear(MeshInstance *mi) {
}
memfree(surface.versions);
}
- if (surface.vertex_buffer.is_valid()) {
- RD::get_singleton()->free(surface.vertex_buffer);
+
+ for (uint32_t i = 0; i < 2; i++) {
+ if (surface.vertex_buffer[i].is_valid()) {
+ RD::get_singleton()->free(surface.vertex_buffer[i]);
+ }
}
}
mi->surfaces.clear();
@@ -881,35 +883,38 @@ void MeshStorage::_mesh_instance_add_surface(MeshInstance *mi, Mesh *mesh, uint3
MeshInstance::Surface s;
if ((mesh->blend_shape_count > 0 || (mesh->surfaces[p_surface]->format & RS::ARRAY_FORMAT_BONES)) && mesh->surfaces[p_surface]->vertex_buffer_size > 0) {
- //surface warrants transform
- s.vertex_buffer = RD::get_singleton()->vertex_buffer_create(mesh->surfaces[p_surface]->vertex_buffer_size, Vector<uint8_t>(), true);
-
- Vector<RD::Uniform> uniforms;
- {
- RD::Uniform u;
- u.binding = 1;
- u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
- u.append_id(s.vertex_buffer);
- uniforms.push_back(u);
- }
- {
- RD::Uniform u;
- u.binding = 2;
- u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
- if (mi->blend_weights_buffer.is_valid()) {
- u.append_id(mi->blend_weights_buffer);
- } else {
- u.append_id(default_rd_storage_buffer);
- }
- uniforms.push_back(u);
- }
- s.uniform_set = RD::get_singleton()->uniform_set_create(uniforms, skeleton_shader.version_shader[0], SkeletonShader::UNIFORM_SET_INSTANCE);
+ _mesh_instance_add_surface_buffer(mi, mesh, &s, p_surface, 0);
}
mi->surfaces.push_back(s);
mi->dirty = true;
}
+void MeshStorage::_mesh_instance_add_surface_buffer(MeshInstance *mi, Mesh *mesh, MeshInstance::Surface *s, uint32_t p_surface, uint32_t p_buffer_index) {
+ s->vertex_buffer[p_buffer_index] = RD::get_singleton()->vertex_buffer_create(mesh->surfaces[p_surface]->vertex_buffer_size, Vector<uint8_t>(), true);
+
+ Vector<RD::Uniform> uniforms;
+ {
+ RD::Uniform u;
+ u.binding = 1;
+ u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
+ u.append_id(s->vertex_buffer[p_buffer_index]);
+ uniforms.push_back(u);
+ }
+ {
+ RD::Uniform u;
+ u.binding = 2;
+ u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
+ if (mi->blend_weights_buffer.is_valid()) {
+ u.append_id(mi->blend_weights_buffer);
+ } else {
+ u.append_id(default_rd_storage_buffer);
+ }
+ uniforms.push_back(u);
+ }
+ s->uniform_set[p_buffer_index] = RD::get_singleton()->uniform_set_create(uniforms, skeleton_shader.version_shader[0], SkeletonShader::UNIFORM_SET_INSTANCE);
+}
+
void MeshStorage::mesh_instance_check_for_update(RID p_mesh_instance) {
MeshInstance *mi = mesh_instance_owner.get_or_null(p_mesh_instance);
@@ -956,6 +961,8 @@ void MeshStorage::update_mesh_instances() {
}
//process skeletons and blend shapes
+ uint64_t frame = RSG::rasterizer->get_frame_number();
+ bool uses_motion_vectors = (RSG::viewport->get_num_viewports_with_motion_vectors() > 0);
RD::ComputeListID compute_list = RD::get_singleton()->compute_list_begin();
while (dirty_mesh_instance_arrays.first()) {
@@ -964,7 +971,29 @@ void MeshStorage::update_mesh_instances() {
Skeleton *sk = skeleton_owner.get_or_null(mi->skeleton);
for (uint32_t i = 0; i < mi->surfaces.size(); i++) {
- if (mi->surfaces[i].uniform_set == RID() || mi->mesh->surfaces[i]->uniform_set == RID()) {
+ if (mi->surfaces[i].uniform_set[0].is_null() || mi->mesh->surfaces[i]->uniform_set.is_null()) {
+ // Skip over mesh instances that don't require their own uniform buffers.
+ continue;
+ }
+
+ mi->surfaces[i].previous_buffer = mi->surfaces[i].current_buffer;
+
+ if (uses_motion_vectors && (frame - mi->surfaces[i].last_change) == 1) {
+ // Previous buffer's data can only be one frame old to be able to use motion vectors.
+ uint32_t new_buffer_index = mi->surfaces[i].current_buffer ^ 1;
+
+ if (mi->surfaces[i].uniform_set[new_buffer_index].is_null()) {
+ // Create the new vertex buffer on demand where the result for the current frame will be stored.
+ _mesh_instance_add_surface_buffer(mi, mi->mesh, &mi->surfaces[i], i, new_buffer_index);
+ }
+
+ mi->surfaces[i].current_buffer = new_buffer_index;
+ }
+
+ mi->surfaces[i].last_change = frame;
+
+ RID mi_surface_uniform_set = mi->surfaces[i].uniform_set[mi->surfaces[i].current_buffer];
+ if (mi_surface_uniform_set.is_null()) {
continue;
}
@@ -972,7 +1001,7 @@ void MeshStorage::update_mesh_instances() {
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, skeleton_shader.pipeline[array_is_2d ? SkeletonShader::SHADER_MODE_2D : SkeletonShader::SHADER_MODE_3D]);
- RD::get_singleton()->compute_list_bind_uniform_set(compute_list, mi->surfaces[i].uniform_set, SkeletonShader::UNIFORM_SET_INSTANCE);
+ RD::get_singleton()->compute_list_bind_uniform_set(compute_list, mi_surface_uniform_set, SkeletonShader::UNIFORM_SET_INSTANCE);
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, mi->mesh->surfaces[i]->uniform_set, SkeletonShader::UNIFORM_SET_SURFACE);
if (sk && sk->uniform_set_mi.is_valid()) {
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, sk->uniform_set_mi, SkeletonShader::UNIFORM_SET_SKELETON);
@@ -1032,7 +1061,7 @@ void MeshStorage::update_mesh_instances() {
RD::get_singleton()->compute_list_end();
}
-void MeshStorage::_mesh_surface_generate_version_for_input_mask(Mesh::Surface::Version &v, Mesh::Surface *s, uint32_t p_input_mask, MeshInstance::Surface *mis) {
+void MeshStorage::_mesh_surface_generate_version_for_input_mask(Mesh::Surface::Version &v, Mesh::Surface *s, uint32_t p_input_mask, bool p_input_motion_vectors, MeshInstance::Surface *mis) {
Vector<RD::VertexAttribute> attributes;
Vector<RID> buffers;
@@ -1105,7 +1134,7 @@ void MeshStorage::_mesh_surface_generate_version_for_input_mask(Mesh::Surface::V
}
if (mis) {
- buffer = mis->vertex_buffer;
+ buffer = mis->vertex_buffer[mis->current_buffer];
} else {
buffer = s->vertex_buffer;
}
@@ -1117,7 +1146,7 @@ void MeshStorage::_mesh_surface_generate_version_for_input_mask(Mesh::Surface::V
stride += sizeof(uint16_t) * 2;
if (mis) {
- buffer = mis->vertex_buffer;
+ buffer = mis->vertex_buffer[mis->current_buffer];
} else {
buffer = s->vertex_buffer;
}
@@ -1128,7 +1157,7 @@ void MeshStorage::_mesh_surface_generate_version_for_input_mask(Mesh::Surface::V
stride += sizeof(uint16_t) * 2;
if (mis) {
- buffer = mis->vertex_buffer;
+ buffer = mis->vertex_buffer[mis->current_buffer];
} else {
buffer = s->vertex_buffer;
}
@@ -1193,6 +1222,32 @@ void MeshStorage::_mesh_surface_generate_version_for_input_mask(Mesh::Surface::V
attributes.push_back(vd);
buffers.push_back(buffer);
+
+ if (p_input_motion_vectors) {
+ // Since the previous vertex, normal and tangent can't be part of the vertex format but they are required when motion
+ // vectors are enabled, we opt to push a copy of the vertex attribute with a different location and buffer (if it's
+ // part of an instance that has one).
+ switch (i) {
+ case RS::ARRAY_VERTEX: {
+ vd.location = ATTRIBUTE_LOCATION_PREV_VERTEX;
+ } break;
+ case RS::ARRAY_NORMAL: {
+ vd.location = ATTRIBUTE_LOCATION_PREV_NORMAL;
+ } break;
+ case RS::ARRAY_TANGENT: {
+ vd.location = ATTRIBUTE_LOCATION_PREV_TANGENT;
+ } break;
+ }
+
+ if (int(vd.location) != i) {
+ if (mis && buffer != mesh_default_rd_buffers[i]) {
+ buffer = mis->vertex_buffer[mis->previous_buffer];
+ }
+
+ attributes.push_back(vd);
+ buffers.push_back(buffer);
+ }
+ }
}
//update final stride
@@ -1202,7 +1257,7 @@ void MeshStorage::_mesh_surface_generate_version_for_input_mask(Mesh::Surface::V
}
int loc = attributes[i].location;
- if (loc < RS::ARRAY_COLOR) {
+ if ((loc < RS::ARRAY_COLOR) || ((loc >= ATTRIBUTE_LOCATION_PREV_VERTEX) && (loc <= ATTRIBUTE_LOCATION_PREV_TANGENT))) {
attributes.write[i].stride = stride;
} else if (loc < RS::ARRAY_BONES) {
attributes.write[i].stride = attribute_stride;
@@ -1212,6 +1267,9 @@ void MeshStorage::_mesh_surface_generate_version_for_input_mask(Mesh::Surface::V
}
v.input_mask = p_input_mask;
+ v.current_buffer = mis ? mis->current_buffer : 0;
+ v.previous_buffer = mis ? mis->previous_buffer : 0;
+ v.input_motion_vectors = p_input_motion_vectors;
v.vertex_format = RD::get_singleton()->vertex_format_create(attributes);
v.vertex_array = RD::get_singleton()->vertex_array_create(s->vertex_count, v.vertex_format, buffers);
}
diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.h b/servers/rendering/renderer_rd/storage_rd/mesh_storage.h
index c23a5b1449..99ba69f98a 100644
--- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.h
+++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.h
@@ -31,6 +31,7 @@
#ifndef MESH_STORAGE_RD_H
#define MESH_STORAGE_RD_H
+#include "../../rendering_server_globals.h"
#include "core/templates/local_vector.h"
#include "core/templates/rid_owner.h"
#include "core/templates/self_list.h"
@@ -90,6 +91,9 @@ private:
struct Version {
uint32_t input_mask = 0;
+ uint32_t current_buffer = 0;
+ uint32_t previous_buffer = 0;
+ bool input_motion_vectors = false;
RD::VertexFormatID vertex_format = 0;
RID vertex_array;
};
@@ -162,8 +166,11 @@ private:
Mesh *mesh = nullptr;
RID skeleton;
struct Surface {
- RID vertex_buffer;
- RID uniform_set;
+ RID vertex_buffer[2];
+ RID uniform_set[2];
+ uint32_t current_buffer = 0;
+ uint32_t previous_buffer = 0;
+ uint64_t last_change = 0;
Mesh::Surface::Version *versions = nullptr; //allocated on demand
uint32_t version_count = 0;
@@ -183,10 +190,11 @@ private:
weight_update_list(this), array_update_list(this) {}
};
- void _mesh_surface_generate_version_for_input_mask(Mesh::Surface::Version &v, Mesh::Surface *s, uint32_t p_input_mask, MeshInstance::Surface *mis = nullptr);
+ void _mesh_surface_generate_version_for_input_mask(Mesh::Surface::Version &v, Mesh::Surface *s, uint32_t p_input_mask, bool p_input_motion_vectors, MeshInstance::Surface *mis = nullptr);
void _mesh_instance_clear(MeshInstance *mi);
void _mesh_instance_add_surface(MeshInstance *mi, Mesh *mesh, uint32_t p_surface);
+ void _mesh_instance_add_surface_buffer(MeshInstance *mi, Mesh *mesh, MeshInstance::Surface *s, uint32_t p_surface, uint32_t p_buffer_index);
mutable RID_Owner<MeshInstance> mesh_instance_owner;
@@ -311,6 +319,12 @@ private:
Skeleton *skeleton_dirty_list = nullptr;
+ enum AttributeLocation {
+ ATTRIBUTE_LOCATION_PREV_VERTEX = 12,
+ ATTRIBUTE_LOCATION_PREV_NORMAL = 13,
+ ATTRIBUTE_LOCATION_PREV_TANGENT = 14
+ };
+
public:
static MeshStorage *get_singleton();
@@ -437,7 +451,7 @@ public:
}
}
- _FORCE_INLINE_ void mesh_surface_get_vertex_arrays_and_format(void *p_surface, uint32_t p_input_mask, RID &r_vertex_array_rd, RD::VertexFormatID &r_vertex_format) {
+ _FORCE_INLINE_ void mesh_surface_get_vertex_arrays_and_format(void *p_surface, uint32_t p_input_mask, bool p_input_motion_vectors, RID &r_vertex_array_rd, RD::VertexFormatID &r_vertex_format) {
Mesh::Surface *s = reinterpret_cast<Mesh::Surface *>(p_surface);
s->version_lock.lock();
@@ -445,9 +459,11 @@ public:
//there will never be more than, at much, 3 or 4 versions, so iterating is the fastest way
for (uint32_t i = 0; i < s->version_count; i++) {
- if (s->versions[i].input_mask != p_input_mask) {
+ if (s->versions[i].input_mask != p_input_mask || s->versions[i].input_motion_vectors != p_input_motion_vectors) {
+ // Find the version that matches the inputs required.
continue;
}
+
//we have this version, hooray
r_vertex_format = s->versions[i].vertex_format;
r_vertex_array_rd = s->versions[i].vertex_array;
@@ -459,7 +475,7 @@ public:
s->version_count++;
s->versions = (Mesh::Surface::Version *)memrealloc(s->versions, sizeof(Mesh::Surface::Version) * s->version_count);
- _mesh_surface_generate_version_for_input_mask(s->versions[version], s, p_input_mask);
+ _mesh_surface_generate_version_for_input_mask(s->versions[version], s, p_input_mask, p_input_motion_vectors);
r_vertex_format = s->versions[version].vertex_format;
r_vertex_array_rd = s->versions[version].vertex_array;
@@ -467,7 +483,7 @@ public:
s->version_lock.unlock();
}
- _FORCE_INLINE_ void mesh_instance_surface_get_vertex_arrays_and_format(RID p_mesh_instance, uint32_t p_surface_index, uint32_t p_input_mask, RID &r_vertex_array_rd, RD::VertexFormatID &r_vertex_format) {
+ _FORCE_INLINE_ void mesh_instance_surface_get_vertex_arrays_and_format(RID p_mesh_instance, uint32_t p_surface_index, uint32_t p_input_mask, bool p_input_motion_vectors, RID &r_vertex_array_rd, RD::VertexFormatID &r_vertex_format) {
MeshInstance *mi = mesh_instance_owner.get_or_null(p_mesh_instance);
ERR_FAIL_COND(!mi);
Mesh *mesh = mi->mesh;
@@ -475,15 +491,26 @@ public:
MeshInstance::Surface *mis = &mi->surfaces[p_surface_index];
Mesh::Surface *s = mesh->surfaces[p_surface_index];
+ uint32_t current_buffer = mis->current_buffer;
+
+ // Using the previous buffer is only allowed if the surface was updated this frame and motion vectors are required.
+ uint32_t previous_buffer = p_input_motion_vectors && (RSG::rasterizer->get_frame_number() == mis->last_change) ? mis->previous_buffer : current_buffer;
s->version_lock.lock();
//there will never be more than, at much, 3 or 4 versions, so iterating is the fastest way
for (uint32_t i = 0; i < mis->version_count; i++) {
- if (mis->versions[i].input_mask != p_input_mask) {
+ if (mis->versions[i].input_mask != p_input_mask || mis->versions[i].input_motion_vectors != p_input_motion_vectors) {
+ // Find the version that matches the inputs required.
continue;
}
+
+ if (mis->versions[i].current_buffer != current_buffer || mis->versions[i].previous_buffer != previous_buffer) {
+ // Find the version that corresponds to the correct buffers that should be used.
+ continue;
+ }
+
//we have this version, hooray
r_vertex_format = mis->versions[i].vertex_format;
r_vertex_array_rd = mis->versions[i].vertex_array;
@@ -495,7 +522,7 @@ public:
mis->version_count++;
mis->versions = (Mesh::Surface::Version *)memrealloc(mis->versions, sizeof(Mesh::Surface::Version) * mis->version_count);
- _mesh_surface_generate_version_for_input_mask(mis->versions[version], s, p_input_mask, mis);
+ _mesh_surface_generate_version_for_input_mask(mis->versions[version], s, p_input_mask, p_input_motion_vectors, mis);
r_vertex_format = mis->versions[version].vertex_format;
r_vertex_array_rd = mis->versions[version].vertex_array;