diff options
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; |