浏览代码

Merge pull request #78838 from YuriSizov/4.0-cherrypicks

Cherry-picks for the 4.0 branch (future 4.0.4) - 2nd batch
Rémi Verschelde 2 年之前
父节点
当前提交
c8e0bd50c5
共有 89 个文件被更改,包括 873 次插入451 次删除
  1. 16 10
      .github/ISSUE_TEMPLATE/bug_report.yml
  2. 2 2
      .github/workflows/static_checks.yml
  3. 2 79
      CONTRIBUTING.md
  4. 1 1
      core/config/project_settings.cpp
  5. 4 0
      doc/classes/EditorSettings.xml
  6. 1 2
      drivers/gles3/rasterizer_gles3.cpp
  7. 2 2
      drivers/gles3/storage/mesh_storage.cpp
  8. 6 2
      drivers/gles3/storage/utilities.cpp
  9. 4 4
      drivers/vulkan/rendering_device_vulkan.cpp
  10. 4 16
      editor/code_editor.cpp
  11. 10 0
      editor/editor_file_system.cpp
  12. 4 16
      editor/editor_help.cpp
  13. 4 5
      editor/editor_inspector.cpp
  14. 8 18
      editor/editor_layouts_dialog.cpp
  15. 112 8
      editor/editor_node.cpp
  16. 4 0
      editor/editor_node.h
  17. 1 1
      editor/editor_properties_array_dict.cpp
  18. 2 1
      editor/editor_settings.cpp
  19. 34 7
      editor/editor_themes.cpp
  20. 22 4
      editor/filesystem_dock.cpp
  21. 6 0
      editor/filesystem_dock.h
  22. 6 2
      editor/plugins/canvas_item_editor_plugin.cpp
  23. 12 1
      editor/plugins/collision_shape_2d_editor_plugin.cpp
  24. 3 0
      editor/plugins/collision_shape_2d_editor_plugin.h
  25. 14 8
      editor/plugins/curve_editor_plugin.cpp
  26. 1 0
      editor/plugins/curve_editor_plugin.h
  27. 2 0
      editor/plugins/sprite_frames_editor_plugin.cpp
  28. 7 13
      editor/plugins/theme_editor_plugin.cpp
  29. 5 3
      editor/plugins/tiles/atlas_merging_dialog.cpp
  30. 2 2
      editor/plugins/version_control_editor_plugin.cpp
  31. 49 37
      editor/project_converter_3_to_4.cpp
  32. 2 2
      editor/project_converter_3_to_4.h
  33. 1 1
      misc/scripts/codespell.sh
  34. 8 0
      modules/gdscript/editor/gdscript_translation_parser_plugin.cpp
  35. 4 1
      modules/mono/csharp_script.cpp
  36. 12 0
      modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs
  37. 6 0
      modules/mono/editor/GodotTools/GodotTools/Build/DotNetFinder.cs
  38. 2 2
      modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs
  39. 1 1
      modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs
  40. 0 1
      modules/noise/icons/NoiseTexture.svg
  41. 1 0
      modules/noise/icons/NoiseTexture2D.svg
  42. 4 1
      platform/android/SCsub
  43. 1 0
      platform/android/android_keys_utils.h
  44. 3 0
      platform/android/detect.py
  45. 6 4
      platform/android/java/app/config.gradle
  46. 44 14
      platform/android/java/build.gradle
  47. 65 27
      platform/android/java/editor/build.gradle
  48. 4 0
      platform/android/java/editor/src/debug/res/values/strings.xml
  49. 10 13
      platform/android/java/lib/build.gradle
  50. 4 2
      platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java
  51. 4 0
      platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java
  52. 4 2
      platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java
  53. 13 12
      platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt
  54. 28 3
      platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java
  55. 12 1
      platform/android/java_godot_view_wrapper.cpp
  56. 1 0
      platform/android/java_godot_view_wrapper.h
  57. 5 5
      platform/android/os_android.cpp
  58. 2 0
      platform/linuxbsd/joypad_linux.cpp
  59. 29 24
      platform/linuxbsd/os_linuxbsd.cpp
  60. 1 2
      platform/uwp/os_uwp.cpp
  61. 4 0
      platform/web/display_server_web.cpp
  62. 2 0
      platform/web/display_server_web.h
  63. 9 5
      platform/windows/os_windows.cpp
  64. 5 4
      scene/2d/audio_stream_player_2d.cpp
  65. 13 16
      scene/3d/skeleton_3d.cpp
  66. 1 0
      scene/3d/skeleton_3d.h
  67. 2 2
      scene/animation/animation_player.cpp
  68. 7 6
      scene/gui/color_picker.cpp
  69. 1 1
      scene/gui/dialogs.cpp
  70. 4 3
      scene/gui/item_list.cpp
  71. 1 1
      scene/gui/popup.cpp
  72. 9 1
      scene/main/viewport.cpp
  73. 2 2
      scene/register_scene_types.cpp
  74. 8 0
      scene/resources/world_2d.cpp
  75. 3 3
      scene/resources/world_2d.h
  76. 7 5
      servers/rendering/renderer_canvas_cull.cpp
  77. 15 5
      servers/rendering/renderer_rd/environment/fog.cpp
  78. 34 12
      servers/rendering/renderer_rd/environment/gi.cpp
  79. 6 1
      servers/rendering/renderer_rd/renderer_scene_render_rd.cpp
  80. 2 2
      servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp
  81. 2 2
      servers/rendering/renderer_rd/storage_rd/texture_storage.cpp
  82. 0 1
      servers/rendering/renderer_scene_cull.cpp
  83. 24 0
      servers/rendering/renderer_scene_cull.h
  84. 42 16
      servers/text_server.cpp
  85. 1 0
      thirdparty/glslang/glslang/Include/Common.h
  86. 12 0
      thirdparty/openxr/patches/fix-gcc13-stdint.patch
  87. 1 0
      thirdparty/openxr/src/common/platform_utils.hpp
  88. 23 0
      thirdparty/vhacd/0006-fix-gcc13.patch
  89. 6 1
      thirdparty/vhacd/inc/vhacdManifoldMesh.h

+ 16 - 10
.github/ISSUE_TEMPLATE/bug_report.yml

@@ -5,18 +5,19 @@ body:
 - type: markdown
   attributes:
     value: |
-      - Read our [CONTRIBUTING.md guide](https://github.com/godotengine/godot/blob/master/CONTRIBUTING.md#reporting-bugs) on reporting bugs.
+      - When reporting bugs, you'll make our life simpler (and the fix will come sooner) if you follow the guidelines in this template.
       - Write a descriptive issue title above.
-      - Search [open](https://github.com/godotengine/godot/issues) and [closed](https://github.com/godotengine/godot/issues?q=is%3Aissue+is%3Aclosed) issues to ensure it has not already been reported.
+      - The golden rule is to **always open *one* issue for *one* bug**. If you notice several bugs and want to report them, make sure to create one new issue for each of them.
+      - Search [open](https://github.com/godotengine/godot/issues) and [closed](https://github.com/godotengine/godot/issues?q=is%3Aissue+is%3Aclosed) issues to ensure it has not already been reported. If you don't find a relevant match or if you're unsure, don't hesitate to **open a new issue**. The bugsquad will handle it from there if it's a duplicate.
       - Verify that you are using a [supported Godot version](https://docs.godotengine.org/en/stable/about/release_policy.html).
 
 - type: input
   attributes:
     label: Godot version
     description: >
-      Specify the Git commit hash if using a development or non-official build.
+      Specify the Godot version, including the Git commit hash if using a development or non-official build.
       If you use a custom build, please test if your issue is reproducible in official builds too.
-    placeholder: 3.3.stable, 4.0.dev (3041becc6)
+    placeholder: 3.5.stable, 4.0.dev (3041becc6)
   validations:
     required: true
 
@@ -24,9 +25,12 @@ body:
   attributes:
     label: System information
     description: |
-      Specify the OS version, and when relevant hardware information.
-      For graphics-related issues, specify the GPU model, driver version, and the rendering backend (GLES2, GLES3, Vulkan).
-    placeholder: Windows 10, GLES3, Intel HD Graphics 620 (27.20.100.9616)
+      - Specify the OS version, and when relevant hardware information.
+      - For issues that are likely OS-specific and/or graphics-related, please specify the CPU model and architecture.
+      - For graphics-related issues, specify the GPU model, driver version, and the rendering backend (GLES2, GLES3, Vulkan).
+      - **Bug reports not including the required information may be closed at the maintainers' discretion.** If in doubt, always include all the requested information; it's better to include too much information than not enough information.
+      - **Starting from Godot 4.1, you can copy this information to your clipboard by using *Help > Copy System Info* at the top of the editor window.**
+    placeholder: Windows 10 - Godot v4.0.3.stable - Vulkan (Forward+) - dedicated NVIDIA GeForce GTX 970 (nvidia, 510.85.02) - Intel Core i7-10700KF CPU @ 3.80GHz (16 Threads)
   validations:
     required: true
 
@@ -52,8 +56,10 @@ body:
   attributes:
     label: Minimal reproduction project
     description: |
-      A small Godot project which reproduces the issue, with no unnecessary files included. Be sure to not include the `.godot` folder in the archive (but keep `project.godot`).
-      Required, unless the reproduction steps are trivial and don't require any project files to be followed. In this case, write "N/A" in the field.
-      Drag and drop a ZIP archive to upload it. **Do not select another field until the project is done uploading.**
+      - A small Godot project which reproduces the issue, with no unnecessary files included. Be sure to not include the `.godot` folder in the archive (but keep `project.godot`).
+      - Required, unless the reproduction steps are trivial and don't require any project files to be followed. In this case, write "N/A" in the field.
+      - Drag and drop a ZIP archive to upload it. **Do not select another field until the project is done uploading.**
+      - **Note for C# users:** If your issue is *not* Mono-specific, please upload a minimal reproduction project written in GDScript or VisualScript. This will make it easier for contributors to reproduce the issue locally as not everyone has a Mono setup available.
+      - **If you've been asked by a maintainer to upload a minimal reproduction project, you *must* do so within 7 days.** Otherwise, your bug report will be closed as it'll be considered too difficult to diagnose.
   validations:
     required: true

+ 2 - 2
.github/workflows/static_checks.yml

@@ -103,8 +103,8 @@ jobs:
 
       - name: Spell checks via codespell
         if: github.event_name == 'pull_request' && env.CHANGED_FILES != ''
-        uses: codespell-project/actions-codespell@v1
+        uses: codespell-project/actions-codespell@v2
         with:
           skip: "./bin,./thirdparty,*.desktop,*.gen.*,*.po,*.pot,*.rc,./AUTHORS.md,./COPYRIGHT.txt,./DONORS.md,./core/input/gamecontrollerdb.txt,./core/string/locales.h,./editor/project_converter_3_to_4.cpp,./misc/scripts/codespell.sh,./platform/android/java/lib/src/com,./platform/web/node_modules,./platform/web/package-lock.json"
-          ignore_words_list: "curvelinear,doubleclick,expct,findn,gird,hel,inout,lod,nd,numer,ot,te,vai"
+          ignore_words_list: "curvelinear,doubleclick,expct,findn,gird,hel,inout,lod,mis,nd,numer,ot,te,vai"
           path: ${{ env.CHANGED_FILES }}

+ 2 - 79
CONTRIBUTING.md

@@ -8,87 +8,10 @@
 - [Contributing to Godot's translation](#contributing-to-godots-translation)
 - [Communicating with developers](#communicating-with-developers)
 
-**Please read the first section before reporting a bug!**
-
 ## Reporting bugs
 
-The golden rule is to **always open *one* issue for *one* bug**. If you notice
-several bugs and want to report them, make sure to create one new issue for
-each of them.
-
-If you're reporting a new bug, you'll make our life simpler (and the
-fix will come sooner) by following these guidelines:
-
-### Search first in the existing database
-
-Issues are often reported several times by various users. It's good practice to
-**search first in the [issue tracker](https://github.com/godotengine/godot/issues)
-before reporting your issue**. If you don't find a relevant match or if you're
-unsure, don't hesitate to **open a new issue**. The bugsquad will handle it
-from there if it's a duplicate.
-
-### Specify the platform
-
-Godot runs on a large variety of platforms and operating systems and devices.
-**In your bug reports, please always specify:**
-
-- Operating system and version (e.g. Windows 10, macOS 10.15, Ubuntu 19.10)
-- Godot version (e.g. 3.2, 3.1.2, or the Git commit hash if you're using a development branch)
-
-For bugs that are likely OS-specific and/or graphics-related, please also specify:
-
-- Device (CPU model including architecture, e.g. x86_64, arm64, etc.)
-- GPU model (and the driver version in use if you know it)
-
-**Bug reports not including the required information may be closed at the
-maintainers' discretion.** If in doubt, always include all the requested
-information; it's better to include too much information than not enough
-information.
-
-### Specify steps to reproduce
-
-Many bugs can't be reproduced unless specific steps are taken. Please **specify
-the exact steps** that must be taken to reproduce the condition, and try to
-keep them as minimal as possible. If you're describing a procedure to follow
-in the editor, don't hesitate to include screenshots.
-
-Making your bug report easy to reproduce will make it easier for contributors
-to fix the bug.
-
-### Provide a simple example project
-
-Sometimes, unexpected behavior can happen in your project. In such case,
-understand that:
-
-- What happens to you may not happen to other users.
-- We can't take the time to look at your project, understand how it is set up
-  and then figure out why it's failing.
-- On the contributors' end, recreating a test project from scratch takes valuable
-  time that can be saved by uploading a *minimal* project.
-
-To speed up our work, **please upload a minimal project** that isolates
-and reproduces the issue. This is always the **best way for us to fix it**.
-We recommend attaching a ZIP file with the minimal project directly to the bug report,
-by drag and dropping the file in the GitHub edition field. This ensures the file
-can remain available for a long period of time. Only use third-party file hosts
-if your ZIP file isn't accepted by GitHub because it's too large.
-
-We recommend always attaching a minimal reproduction project, even if the issue
-may seem simple to reproduce manually.
-
-**Note for C# users:** If your issue is *not* .NET-specific, please upload a
-minimal reproduction project written in GDScript.
-This will make it easier for contributors to reproduce the issue
-locally as not everyone has a .NET setup available.
-
-**If you've been asked by a maintainer to upload a minimal reproduction project,
-you *must* do so within 7 days.** Otherwise, your bug report will be closed as
-it'll be considered too difficult to diagnose.
-
-Now that you've read the guidelines, click the link below to create a
-bug report:
-
-- **[Report a bug](https://github.com/godotengine/godot/issues/new?assignees=&labels=&template=bug_report.yml)**
+Report bugs [here](https://github.com/godotengine/godot/issues/new?assignees=&labels=&template=bug_report.yml).
+Please follow the instructions in the template when you do.
 
 ## Proposing features or improvements
 

+ 1 - 1
core/config/project_settings.cpp

@@ -640,7 +640,7 @@ Error ProjectSettings::_setup(const String &p_path, const String &p_main_pack, b
 
 Error ProjectSettings::setup(const String &p_path, const String &p_main_pack, bool p_upwards, bool p_ignore_override) {
 	Error err = _setup(p_path, p_main_pack, p_upwards, p_ignore_override);
-	if (err == OK) {
+	if (err == OK && !p_ignore_override) {
 		String custom_settings = GLOBAL_GET("application/config/project_settings_override");
 		if (!custom_settings.is_empty()) {
 			_load_settings_text(custom_settings);

+ 4 - 0
doc/classes/EditorSettings.xml

@@ -635,6 +635,10 @@
 			If [code]true[/code], increases the scrollbar touch area to improve usability on touchscreen devices.
 			[b]Note:[/b] Defaults to [code]true[/code] on touchscreen devices.
 		</member>
+		<member name="interface/touchscreen/scale_gizmo_handles" type="float" setter="" getter="">
+			Specify the multiplier to apply to the scale for the editor gizmo handles to improve usability on touchscreen devices.
+			[b]Note:[/b] Defaults to [code]1[/code] on non-touchscreen devices.
+		</member>
 		<member name="network/debug/remote_host" type="String" setter="" getter="">
 			The address to listen to when starting the remote debugger. This can be set to [code]0.0.0.0[/code] to allow external clients to connect to the remote debugger (instead of restricting the remote debugger to connections from [code]localhost[/code]).
 		</member>

+ 1 - 2
drivers/gles3/rasterizer_gles3.cpp

@@ -179,8 +179,7 @@ typedef void (*DEBUGPROCARB)(GLenum source,
 typedef void (*DebugMessageCallbackARB)(DEBUGPROCARB callback, const void *userParam);
 
 void RasterizerGLES3::initialize() {
-	// NVIDIA suffixes all GPU model names with "/PCIe/SSE2" in OpenGL (but not Vulkan). This isn't necessary to display nowadays, so it can be trimmed.
-	print_line(vformat("OpenGL API %s - Compatibility - Using Device: %s - %s", RS::get_singleton()->get_video_adapter_api_version(), RS::get_singleton()->get_video_adapter_vendor(), RS::get_singleton()->get_video_adapter_name().trim_suffix("/PCIe/SSE2")));
+	print_line(vformat("OpenGL API %s - Compatibility - Using Device: %s - %s", RS::get_singleton()->get_video_adapter_api_version(), RS::get_singleton()->get_video_adapter_vendor(), RS::get_singleton()->get_video_adapter_name()));
 }
 
 void RasterizerGLES3::finalize() {

+ 2 - 2
drivers/gles3/storage/mesh_storage.cpp

@@ -534,7 +534,7 @@ AABB MeshStorage::mesh_get_aabb(RID p_mesh, RID p_skeleton) {
 
 			if (skeleton->use_2d) {
 				for (int j = 0; j < bs; j++) {
-					if (skbones[0].size == Vector3()) {
+					if (skbones[j].size == Vector3(-1, -1, -1)) {
 						continue; //bone is unused
 					}
 
@@ -561,7 +561,7 @@ AABB MeshStorage::mesh_get_aabb(RID p_mesh, RID p_skeleton) {
 				}
 			} else {
 				for (int j = 0; j < bs; j++) {
-					if (skbones[0].size == Vector3()) {
+					if (skbones[j].size == Vector3(-1, -1, -1)) {
 						continue; //bone is unused
 					}
 

+ 6 - 2
drivers/gles3/storage/utilities.cpp

@@ -328,11 +328,15 @@ uint64_t Utilities::get_rendering_info(RS::RenderingInfo p_info) {
 }
 
 String Utilities::get_video_adapter_name() const {
-	return (const char *)glGetString(GL_RENDERER);
+	const String rendering_device_name = (const char *)glGetString(GL_RENDERER);
+	// NVIDIA suffixes all GPU model names with "/PCIe/SSE2" in OpenGL (but not Vulkan). This isn't necessary to display nowadays, so it can be trimmed.
+	return rendering_device_name.trim_suffix("/PCIe/SSE2");
 }
 
 String Utilities::get_video_adapter_vendor() const {
-	return (const char *)glGetString(GL_VENDOR);
+	const String rendering_device_vendor = (const char *)glGetString(GL_VENDOR);
+	// NVIDIA suffixes its vendor name with " Corporation". This is neither necessary to process nor display.
+	return rendering_device_vendor.trim_suffix(" Corporation");
 }
 
 RenderingDevice::DeviceType Utilities::get_video_adapter_type() const {

+ 4 - 4
drivers/vulkan/rendering_device_vulkan.cpp

@@ -3647,7 +3647,7 @@ VkRenderPass RenderingDeviceVulkan::_render_pass_create(const Vector<AttachmentF
 					} else {
 						description.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
 						description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
-						description.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; // Don't care what is there.
+						description.finalLayout = VK_IMAGE_LAYOUT_UNDEFINED; // Don't care what is there.
 						// TODO: What does this mean about the next usage (and thus appropriate dependency masks.
 					}
 				} break;
@@ -5931,10 +5931,10 @@ Vector<uint8_t> RenderingDeviceVulkan::buffer_get_data(RID p_buffer, uint32_t p_
 		ERR_FAIL_V_MSG(Vector<uint8_t>(), "Buffer is either invalid or this type of buffer can't be retrieved. Only Index and Vertex buffers allow retrieving.");
 	}
 
-	// Make sure no one is using the buffer -- the "false" gets us to the same command buffer as below.
-	_buffer_memory_barrier(buffer->buffer, 0, buffer->size, src_stage_mask, VK_PIPELINE_STAGE_TRANSFER_BIT, src_access_mask, VK_ACCESS_TRANSFER_READ_BIT, false);
+	// Make sure no one is using the buffer -- the "true" gets us to the same command buffer as below.
+	_buffer_memory_barrier(buffer->buffer, 0, buffer->size, src_stage_mask, VK_PIPELINE_STAGE_TRANSFER_BIT, src_access_mask, VK_ACCESS_TRANSFER_READ_BIT, true);
 
-	VkCommandBuffer command_buffer = frames[frame].setup_command_buffer;
+	VkCommandBuffer command_buffer = frames[frame].draw_command_buffer;
 
 	// Size of buffer to retrieve.
 	if (!p_size) {

+ 4 - 16
editor/code_editor.cpp

@@ -122,24 +122,12 @@ void FindReplaceBar::unhandled_input(const Ref<InputEvent> &p_event) {
 	ERR_FAIL_COND(p_event.is_null());
 
 	Ref<InputEventKey> k = p_event;
-	if (!k.is_valid() || !k->is_pressed()) {
-		return;
-	}
-
-	Control *focus_owner = get_viewport()->gui_get_focus_owner();
-	if (text_editor->has_focus() || (focus_owner && vbc_lineedit->is_ancestor_of(focus_owner))) {
-		bool accepted = true;
 
-		switch (k->get_keycode()) {
-			case Key::ESCAPE: {
-				_hide_bar();
-			} break;
-			default: {
-				accepted = false;
-			} break;
-		}
+	if (k.is_valid() && k->is_action_pressed(SNAME("ui_cancel"), false, true)) {
+		Control *focus_owner = get_viewport()->gui_get_focus_owner();
 
-		if (accepted) {
+		if (text_editor->has_focus() || (focus_owner && vbc_lineedit->is_ancestor_of(focus_owner))) {
+			_hide_bar();
 			accept_event();
 		}
 	}

+ 10 - 0
editor/editor_file_system.cpp

@@ -1504,6 +1504,16 @@ void EditorFileSystem::_save_late_updated_files() {
 }
 
 Vector<String> EditorFileSystem::_get_dependencies(const String &p_path) {
+	// Avoid error spam on first opening of a not yet imported project by treating the following situation
+	// as a benign one, not letting the file open error happen: the resource is of an importable type but
+	// it has not been imported yet.
+	if (ResourceFormatImporter::get_singleton()->recognize_path(p_path)) {
+		const String &internal_path = ResourceFormatImporter::get_singleton()->get_internal_resource_path(p_path);
+		if (!internal_path.is_empty() && !FileAccess::exists(internal_path)) { // If path is empty (error), keep the code flow to the error.
+			return Vector<String>();
+		}
+	}
+
 	List<String> deps;
 	ResourceLoader::get_dependencies(p_path, &deps);
 

+ 4 - 16
editor/editor_help.cpp

@@ -2567,22 +2567,10 @@ void FindBar::unhandled_input(const Ref<InputEvent> &p_event) {
 	ERR_FAIL_COND(p_event.is_null());
 
 	Ref<InputEventKey> k = p_event;
-	if (k.is_valid()) {
-		if (k->is_pressed() && (rich_text_label->has_focus() || is_ancestor_of(get_viewport()->gui_get_focus_owner()))) {
-			bool accepted = true;
-
-			switch (k->get_keycode()) {
-				case Key::ESCAPE: {
-					_hide_bar();
-				} break;
-				default: {
-					accepted = false;
-				} break;
-			}
-
-			if (accepted) {
-				accept_event();
-			}
+	if (k.is_valid() && k->is_action_pressed(SNAME("ui_cancel"), false, true)) {
+		if (rich_text_label->has_focus() || is_ancestor_of(get_viewport()->gui_get_focus_owner())) {
+			_hide_bar();
+			accept_event();
 		}
 	}
 }

+ 4 - 5
editor/editor_inspector.cpp

@@ -592,6 +592,9 @@ void EditorProperty::add_focusable(Control *p_control) {
 
 void EditorProperty::select(int p_focusable) {
 	bool already_selected = selected;
+	if (!selectable) {
+		return;
+	}
 
 	if (p_focusable >= 0) {
 		ERR_FAIL_INDEX(p_focusable, focusables.size());
@@ -665,11 +668,7 @@ void EditorProperty::gui_input(const Ref<InputEvent> &p_event) {
 			mpos.x = get_size().x - mpos.x;
 		}
 
-		if (!selected && selectable) {
-			selected = true;
-			emit_signal(SNAME("selected"), property, -1);
-			queue_redraw();
-		}
+		select();
 
 		if (keying_rect.has_point(mpos)) {
 			accept_event();

+ 8 - 18
editor/editor_layouts_dialog.cpp

@@ -42,25 +42,15 @@ void EditorLayoutsDialog::_line_gui_input(const Ref<InputEvent> &p_event) {
 	Ref<InputEventKey> k = p_event;
 
 	if (k.is_valid()) {
-		if (!k->is_pressed()) {
-			return;
-		}
-
-		switch (k->get_keycode()) {
-			case Key::KP_ENTER:
-			case Key::ENTER: {
-				if (get_hide_on_ok()) {
-					hide();
-				}
-				ok_pressed();
-				set_input_as_handled();
-			} break;
-			case Key::ESCAPE: {
+		if (k->is_action_pressed(SNAME("ui_accept"), false, true)) {
+			if (get_hide_on_ok()) {
 				hide();
-				set_input_as_handled();
-			} break;
-			default:
-				break;
+			}
+			ok_pressed();
+			set_input_as_handled();
+		} else if (k->is_action_pressed(SNAME("ui_cancel"), false, true)) {
+			hide();
+			set_input_as_handled();
 		}
 	}
 }

+ 112 - 8
editor/editor_node.cpp

@@ -761,7 +761,9 @@ void EditorNode::_notification(int p_what) {
 					EditorSettings::get_singleton()->check_changed_settings_in_group("text_editor/theme") ||
 					EditorSettings::get_singleton()->check_changed_settings_in_group("text_editor/help/help") ||
 					EditorSettings::get_singleton()->check_changed_settings_in_group("filesystem/file_dialog/thumbnail_size") ||
-					EditorSettings::get_singleton()->check_changed_settings_in_group("run/output/font_size");
+					EditorSettings::get_singleton()->check_changed_settings_in_group("run/output/font_size") ||
+					EditorSettings::get_singleton()->check_changed_settings_in_group("interface/touchscreen/increase_scrollbar_touch_area") ||
+					EditorSettings::get_singleton()->check_changed_settings_in_group("interface/touchscreen/scale_gizmo_handles");
 
 			if (theme_changed) {
 				theme = create_custom_theme(theme_base->get_theme());
@@ -836,6 +838,7 @@ void EditorNode::_notification(int p_what) {
 			help_menu->set_item_icon(help_menu->get_item_index(HELP_DOCS), gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")));
 			help_menu->set_item_icon(help_menu->get_item_index(HELP_QA), gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")));
 			help_menu->set_item_icon(help_menu->get_item_index(HELP_REPORT_A_BUG), gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")));
+			help_menu->set_item_icon(help_menu->get_item_index(HELP_COPY_SYSTEM_INFO), gui_base->get_theme_icon(SNAME("ActionCopy"), SNAME("EditorIcons")));
 			help_menu->set_item_icon(help_menu->get_item_index(HELP_SUGGEST_A_FEATURE), gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")));
 			help_menu->set_item_icon(help_menu->get_item_index(HELP_SEND_DOCS_FEEDBACK), gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")));
 			help_menu->set_item_icon(help_menu->get_item_index(HELP_COMMUNITY), gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")));
@@ -3168,6 +3171,10 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) {
 		case HELP_REPORT_A_BUG: {
 			OS::get_singleton()->shell_open("https://github.com/godotengine/godot/issues");
 		} break;
+		case HELP_COPY_SYSTEM_INFO: {
+			String info = _get_system_info();
+			DisplayServer::get_singleton()->clipboard_set(info);
+		} break;
 		case HELP_SUGGEST_A_FEATURE: {
 			OS::get_singleton()->shell_open("https://github.com/godotengine/godot-proposals#readme");
 		} break;
@@ -4647,6 +4654,98 @@ void EditorNode::progress_end_task_bg(const String &p_task) {
 	singleton->progress_hb->end_task(p_task);
 }
 
+String EditorNode::_get_system_info() const {
+	String distribution_name = OS::get_singleton()->get_distribution_name();
+	if (distribution_name.is_empty()) {
+		distribution_name = OS::get_singleton()->get_name();
+	}
+	if (distribution_name.is_empty()) {
+		distribution_name = "Other";
+	}
+	const String distribution_version = OS::get_singleton()->get_version();
+
+	String godot_version = "Godot v" + String(VERSION_FULL_CONFIG);
+	if (String(VERSION_BUILD) != "official") {
+		String hash = String(VERSION_HASH);
+		hash = hash.is_empty() ? String("unknown") : vformat("(%s)", hash.left(9));
+		godot_version += " " + hash;
+	}
+
+	String driver_name = GLOBAL_GET("rendering/rendering_device/driver");
+	String rendering_method = GLOBAL_GET("rendering/renderer/rendering_method");
+
+	const String rendering_device_name = RenderingServer::get_singleton()->get_video_adapter_name();
+
+	RenderingDevice::DeviceType device_type = RenderingServer::get_singleton()->get_video_adapter_type();
+	String device_type_string;
+	switch (device_type) {
+		case RenderingDevice::DeviceType::DEVICE_TYPE_INTEGRATED_GPU:
+			device_type_string = "integrated";
+			break;
+		case RenderingDevice::DeviceType::DEVICE_TYPE_DISCRETE_GPU:
+			device_type_string = "dedicated";
+			break;
+		case RenderingDevice::DeviceType::DEVICE_TYPE_VIRTUAL_GPU:
+			device_type_string = "virtual";
+			break;
+		case RenderingDevice::DeviceType::DEVICE_TYPE_CPU:
+			device_type_string = "(software emulation on CPU)";
+			break;
+		case RenderingDevice::DeviceType::DEVICE_TYPE_OTHER:
+		case RenderingDevice::DeviceType::DEVICE_TYPE_MAX:
+			break; // Can't happen, but silences warning for DEVICE_TYPE_MAX
+	}
+
+	const Vector<String> video_adapter_driver_info = OS::get_singleton()->get_video_adapter_driver_info();
+
+	const String processor_name = OS::get_singleton()->get_processor_name();
+	const int processor_count = OS::get_singleton()->get_processor_count();
+
+	// Prettify
+	if (driver_name == "vulkan") {
+		driver_name = "Vulkan";
+	} else if (driver_name == "opengl3") {
+		driver_name = "GLES3";
+	}
+	if (rendering_method == "forward_plus") {
+		rendering_method = "Forward+";
+	} else if (rendering_method == "mobile") {
+		rendering_method = "Mobile";
+	} else if (rendering_method == "gl_compatibility") {
+		rendering_method = "Compatibility";
+	}
+
+	// Join info.
+	Vector<String> info;
+	info.push_back(godot_version);
+	if (!distribution_version.is_empty()) {
+		info.push_back(distribution_name + " " + distribution_version);
+	} else {
+		info.push_back(distribution_name);
+	}
+	info.push_back(vformat("%s (%s)", driver_name, rendering_method));
+
+	String graphics;
+	if (!device_type_string.is_empty()) {
+		graphics = device_type_string + " ";
+	}
+	graphics += rendering_device_name;
+	if (video_adapter_driver_info.size() == 2) { // This vector is always either of length 0 or 2.
+		String vad_name = video_adapter_driver_info[0];
+		String vad_version = video_adapter_driver_info[1]; // Version could be potentially empty on Linux/BSD.
+		if (!vad_version.is_empty()) {
+			graphics += vformat(" (%s; %s)", vad_name, vad_version);
+		} else {
+			graphics += vformat(" (%s)", vad_name);
+		}
+	}
+	info.push_back(graphics);
+
+	info.push_back(vformat("%s (%d Threads)", processor_name, processor_count));
+
+	return String(" - ").join(info);
+}
+
 Ref<Texture2D> EditorNode::_file_dialog_get_icon(const String &p_path) {
 	EditorFileSystemDirectory *efsd = EditorFileSystem::get_singleton()->get_filesystem_path(p_path.get_base_dir());
 	if (efsd) {
@@ -7374,14 +7473,8 @@ EditorNode::EditorNode() {
 	project_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/project_settings", TTR("Project Settings..."), Key::NONE, TTR("Project Settings")), RUN_SETTINGS);
 	project_menu->connect("id_pressed", callable_mp(this, &EditorNode::_menu_option));
 
-	vcs_actions_menu = VersionControlEditorPlugin::get_singleton()->get_version_control_actions_panel();
-	vcs_actions_menu->set_name("Version Control");
-	vcs_actions_menu->connect("index_pressed", callable_mp(this, &EditorNode::_version_control_menu_option));
 	project_menu->add_separator();
-	project_menu->add_child(vcs_actions_menu);
-	project_menu->add_submenu_item(TTR("Version Control"), "Version Control");
-	vcs_actions_menu->add_item(TTR("Create Version Control Metadata"), RUN_VCS_METADATA);
-	vcs_actions_menu->add_item(TTR("Version Control Settings"), RUN_VCS_SETTINGS);
+	project_menu->add_item(TTR("Version Control"), VCS_MENU);
 
 	project_menu->add_separator();
 	project_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/export", TTR("Export..."), Key::NONE, TTR("Export")), FILE_EXPORT_PROJECT);
@@ -7501,6 +7594,8 @@ EditorNode::EditorNode() {
 	help_menu->add_icon_shortcut(gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")), ED_SHORTCUT_AND_COMMAND("editor/online_docs", TTR("Online Documentation")), HELP_DOCS);
 	help_menu->add_icon_shortcut(gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")), ED_SHORTCUT_AND_COMMAND("editor/q&a", TTR("Questions & Answers")), HELP_QA);
 	help_menu->add_icon_shortcut(gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")), ED_SHORTCUT_AND_COMMAND("editor/report_a_bug", TTR("Report a Bug")), HELP_REPORT_A_BUG);
+	help_menu->add_icon_shortcut(gui_base->get_theme_icon(SNAME("ActionCopy"), SNAME("EditorIcons")), ED_SHORTCUT_AND_COMMAND("editor/copy_system_info", TTR("Copy System Info")), HELP_COPY_SYSTEM_INFO);
+	help_menu->set_item_tooltip(-1, TTR("Copies the system info as a single-line text into the clipboard."));
 	help_menu->add_icon_shortcut(gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")), ED_SHORTCUT_AND_COMMAND("editor/suggest_a_feature", TTR("Suggest a Feature")), HELP_SUGGEST_A_FEATURE);
 	help_menu->add_icon_shortcut(gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")), ED_SHORTCUT_AND_COMMAND("editor/send_docs_feedback", TTR("Send Docs Feedback")), HELP_SEND_DOCS_FEEDBACK);
 	help_menu->add_icon_shortcut(gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")), ED_SHORTCUT_AND_COMMAND("editor/community", TTR("Community")), HELP_COMMUNITY);
@@ -7974,6 +8069,15 @@ EditorNode::EditorNode() {
 	raise_bottom_panel_item(AnimationPlayerEditor::get_singleton());
 
 	add_editor_plugin(VersionControlEditorPlugin::get_singleton());
+
+	vcs_actions_menu = VersionControlEditorPlugin::get_singleton()->get_version_control_actions_panel();
+	vcs_actions_menu->set_name("Version Control");
+	vcs_actions_menu->connect("index_pressed", callable_mp(this, &EditorNode::_version_control_menu_option));
+	vcs_actions_menu->add_item(TTR("Create Version Control Metadata"), RUN_VCS_METADATA);
+	vcs_actions_menu->add_item(TTR("Version Control Settings"), RUN_VCS_SETTINGS);
+	project_menu->add_child(vcs_actions_menu);
+	project_menu->set_item_submenu(project_menu->get_item_index(VCS_MENU), "Version Control");
+
 	add_editor_plugin(memnew(AudioBusesEditorPlugin(audio_bus_editor)));
 
 	for (int i = 0; i < EditorPlugins::get_plugin_count(); i++) {

+ 4 - 0
editor/editor_node.h

@@ -199,6 +199,7 @@ private:
 		RUN_USER_DATA_FOLDER,
 		RELOAD_CURRENT_PROJECT,
 		RUN_PROJECT_MANAGER,
+		VCS_MENU,
 		RUN_VCS_METADATA,
 		RUN_VCS_SETTINGS,
 		SETTINGS_UPDATE_CONTINUOUSLY,
@@ -229,6 +230,7 @@ private:
 		HELP_DOCS,
 		HELP_QA,
 		HELP_REPORT_A_BUG,
+		HELP_COPY_SYSTEM_INFO,
 		HELP_SUGGEST_A_FEATURE,
 		HELP_SEND_DOCS_FEEDBACK,
 		HELP_COMMUNITY,
@@ -521,6 +523,8 @@ private:
 	static int plugin_init_callback_count;
 	static Vector<EditorNodeInitCallback> _init_callbacks;
 
+	String _get_system_info() const;
+
 	static void _dependency_error_report(void *ud, const String &p_path, const String &p_dep, const String &p_type) {
 		EditorNode *en = static_cast<EditorNode *>(ud);
 		if (!en->dependency_errors.has(p_path)) {

+ 1 - 1
editor/editor_properties_array_dict.cpp

@@ -278,7 +278,7 @@ void EditorPropertyArray::update_property() {
 
 			size_slider = memnew(EditorSpinSlider);
 			size_slider->set_step(1);
-			size_slider->set_max(1000000);
+			size_slider->set_max(INT32_MAX);
 			size_slider->set_h_size_flags(SIZE_EXPAND_FILL);
 			size_slider->set_read_only(is_read_only());
 			size_slider->connect("value_changed", callable_mp(this, &EditorPropertyArray::_length_changed));

+ 2 - 1
editor/editor_settings.cpp

@@ -469,6 +469,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
 	set_restart_if_changed("interface/touchscreen/enable_long_press_as_right_click", true);
 	EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/touchscreen/enable_pan_and_scale_gestures", has_touchscreen_ui, "")
 	set_restart_if_changed("interface/touchscreen/enable_pan_and_scale_gestures", true);
+	EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "interface/touchscreen/scale_gizmo_handles", has_touchscreen_ui ? 3 : 1, "1,5,1")
 
 	// Scene tabs
 	EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "interface/scene_tabs/display_close_button", 1, "Never,If Tab Active,Always"); // TabBar::CloseButtonDisplayPolicy
@@ -688,7 +689,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
 	_initial_set("editors/tiles_editor/grid_color", Color(1.0, 0.5, 0.2, 0.5));
 
 	// Polygon editor
-	_initial_set("editors/polygon_editor/point_grab_radius", 8);
+	_initial_set("editors/polygon_editor/point_grab_radius", has_touchscreen_ui ? 32 : 8);
 	_initial_set("editors/polygon_editor/show_previous_outline", true);
 
 	// Animation

+ 34 - 7
editor/editor_themes.cpp

@@ -255,6 +255,28 @@ static Ref<ImageTexture> editor_generate_icon(int p_index, float p_scale, float
 	return ImageTexture::create_from_image(img);
 }
 
+float get_gizmo_handle_scale(const String &gizmo_handle_name = "") {
+	const float scale_gizmo_handles_for_touch = EDITOR_GET("interface/touchscreen/scale_gizmo_handles");
+	if (scale_gizmo_handles_for_touch > 1.0f) {
+		// The names of the icons that require additional scaling.
+		static HashSet<StringName> gizmo_to_scale;
+		if (gizmo_to_scale.is_empty()) {
+			gizmo_to_scale.insert("EditorHandle");
+			gizmo_to_scale.insert("EditorHandleAdd");
+			gizmo_to_scale.insert("EditorHandleDisabled");
+			gizmo_to_scale.insert("EditorCurveHandle");
+			gizmo_to_scale.insert("EditorPathSharpHandle");
+			gizmo_to_scale.insert("EditorPathSmoothHandle");
+		}
+
+		if (gizmo_to_scale.has(gizmo_handle_name)) {
+			return EDSCALE * scale_gizmo_handles_for_touch;
+		}
+	}
+
+	return EDSCALE;
+}
+
 void editor_register_and_generate_icons(Ref<Theme> p_theme, bool p_dark_theme, float p_icon_saturation, int p_thumb_size, bool p_only_thumbs = false) {
 	// Before we register the icons, we adjust their colors and saturation.
 	// Most icons follow the standard rules for color conversion to follow the editor
@@ -314,22 +336,23 @@ void editor_register_and_generate_icons(Ref<Theme> p_theme, bool p_dark_theme, f
 		for (int i = 0; i < editor_icons_count; i++) {
 			Ref<ImageTexture> icon;
 
-			if (accent_color_icons.has(editor_icons_names[i])) {
-				icon = editor_generate_icon(i, EDSCALE, 1.0, accent_color_map);
+			const String &editor_icon_name = editor_icons_names[i];
+			if (accent_color_icons.has(editor_icon_name)) {
+				icon = editor_generate_icon(i, get_gizmo_handle_scale(editor_icon_name), 1.0, accent_color_map);
 			} else {
 				float saturation = p_icon_saturation;
-				if (saturation_exceptions.has(editor_icons_names[i])) {
+				if (saturation_exceptions.has(editor_icon_name)) {
 					saturation = 1.0;
 				}
 
-				if (conversion_exceptions.has(editor_icons_names[i])) {
-					icon = editor_generate_icon(i, EDSCALE, saturation);
+				if (conversion_exceptions.has(editor_icon_name)) {
+					icon = editor_generate_icon(i, get_gizmo_handle_scale(editor_icon_name), saturation);
 				} else {
-					icon = editor_generate_icon(i, EDSCALE, saturation, color_conversion_map);
+					icon = editor_generate_icon(i, get_gizmo_handle_scale(editor_icon_name), saturation, color_conversion_map);
 				}
 			}
 
-			p_theme->set_icon(editor_icons_names[i], SNAME("EditorIcons"), icon);
+			p_theme->set_icon(editor_icon_name, SNAME("EditorIcons"), icon);
 		}
 	}
 
@@ -395,6 +418,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
 	Color base_color = EDITOR_GET("interface/theme/base_color");
 	float contrast = EDITOR_GET("interface/theme/contrast");
 	bool increase_scrollbar_touch_area = EDITOR_GET("interface/touchscreen/increase_scrollbar_touch_area");
+	const float gizmo_handle_scale = EDITOR_GET("interface/touchscreen/scale_gizmo_handles");
 	bool draw_extra_borders = EDITOR_GET("interface/theme/draw_extra_borders");
 	float icon_saturation = EDITOR_GET("interface/theme/icon_saturation");
 	float relationship_line_opacity = EDITOR_GET("interface/theme/relationship_line_opacity");
@@ -592,6 +616,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
 	theme->set_constant("thumb_size", "Editor", thumb_size);
 	theme->set_constant("dark_theme", "Editor", dark_theme);
 	theme->set_constant("color_picker_button_height", "Editor", 28 * EDSCALE);
+	theme->set_constant("gizmo_handle_scale", "Editor", gizmo_handle_scale);
 
 	// Register editor icons.
 	// If the settings are comparable to the old theme, then just copy them over.
@@ -607,8 +632,10 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
 		const bool prev_dark_theme = (bool)p_theme->get_constant(SNAME("dark_theme"), SNAME("Editor"));
 		const Color prev_accent_color = p_theme->get_color(SNAME("accent_color"), SNAME("Editor"));
 		const float prev_icon_saturation = p_theme->get_color(SNAME("icon_saturation"), SNAME("Editor")).r;
+		const float prev_gizmo_handle_scale = (float)p_theme->get_constant(SNAME("gizmo_handle_scale"), SNAME("Editor"));
 
 		keep_old_icons = (Math::is_equal_approx(prev_scale, EDSCALE) &&
+				Math::is_equal_approx(prev_gizmo_handle_scale, gizmo_handle_scale) &&
 				prev_dark_theme == dark_theme &&
 				prev_accent_color == accent_color &&
 				prev_icon_saturation == icon_saturation);

+ 22 - 4
editor/filesystem_dock.cpp

@@ -434,6 +434,8 @@ void FileSystemDock::_notification(int p_what) {
 		} break;
 
 		case NOTIFICATION_THEME_CHANGED: {
+			overwrite_dialog_scroll->add_theme_style_override("panel", get_theme_stylebox("panel", "Tree"));
+
 			if (is_visible_in_tree()) {
 				_update_display_mode(true);
 			}
@@ -1686,10 +1688,10 @@ void FileSystemDock::_move_operation_confirm(const String &p_to_path, bool p_ove
 		Vector<String> conflicting_items = _check_existing();
 		if (!conflicting_items.is_empty()) {
 			// Ask to do something.
-			overwrite_dialog->set_text(vformat(
-					TTR("The following files or folders conflict with items in the target location '%s':\n\n%s\n\nDo you wish to overwrite them?"),
-					to_move_path,
-					String("\n").join(conflicting_items)));
+			overwrite_dialog_header->set_text(vformat(
+					TTR("The following files or folders conflict with items in the target location '%s':"), to_move_path));
+			overwrite_dialog_file_list->set_text(String("\n").join(conflicting_items));
+			overwrite_dialog_footer->set_text(TTR("Do you wish to overwrite them?"));
 			overwrite_dialog->popup_centered();
 			return;
 		}
@@ -3298,6 +3300,22 @@ FileSystemDock::FileSystemDock() {
 	add_child(overwrite_dialog);
 	overwrite_dialog->connect("confirmed", callable_mp(this, &FileSystemDock::_move_with_overwrite));
 
+	VBoxContainer *overwrite_dialog_vb = memnew(VBoxContainer);
+	overwrite_dialog->add_child(overwrite_dialog_vb);
+
+	overwrite_dialog_header = memnew(Label);
+	overwrite_dialog_vb->add_child(overwrite_dialog_header);
+
+	overwrite_dialog_scroll = memnew(ScrollContainer);
+	overwrite_dialog_vb->add_child(overwrite_dialog_scroll);
+	overwrite_dialog_scroll->set_custom_minimum_size(Vector2(400, 600) * EDSCALE);
+
+	overwrite_dialog_file_list = memnew(Label);
+	overwrite_dialog_scroll->add_child(overwrite_dialog_file_list);
+
+	overwrite_dialog_footer = memnew(Label);
+	overwrite_dialog_vb->add_child(overwrite_dialog_footer);
+
 	duplicate_dialog = memnew(ConfirmationDialog);
 	VBoxContainer *duplicate_dialog_vb = memnew(VBoxContainer);
 	duplicate_dialog->add_child(duplicate_dialog_vb);

+ 6 - 0
editor/filesystem_dock.h

@@ -151,7 +151,13 @@ private:
 	LineEdit *duplicate_dialog_text = nullptr;
 	ConfirmationDialog *make_dir_dialog = nullptr;
 	LineEdit *make_dir_dialog_text = nullptr;
+
 	ConfirmationDialog *overwrite_dialog = nullptr;
+	ScrollContainer *overwrite_dialog_scroll = nullptr;
+	Label *overwrite_dialog_header = nullptr;
+	Label *overwrite_dialog_footer = nullptr;
+	Label *overwrite_dialog_file_list = nullptr;
+
 	SceneCreateDialog *make_scene_dialog = nullptr;
 	ScriptCreateDialog *make_script_dialog = nullptr;
 	ShaderCreateDialog *make_shader_dialog = nullptr;

+ 6 - 2
editor/plugins/canvas_item_editor_plugin.cpp

@@ -2413,7 +2413,7 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) {
 		}
 	}
 
-	if (k.is_valid() && k->is_pressed() && k->get_keycode() == Key::ESCAPE && drag_type == DRAG_NONE && tool == TOOL_SELECT) {
+	if (k.is_valid() && k->is_action_pressed(SNAME("ui_cancel"), false, true) && drag_type == DRAG_NONE && tool == TOOL_SELECT) {
 		// Unselect everything
 		editor_selection->clear();
 		viewport->queue_redraw();
@@ -4589,7 +4589,11 @@ void CanvasItemEditor::_popup_callback(int p_op) {
 		} break;
 		case SKELETON_MAKE_BONES: {
 			HashMap<Node *, Object *> &selection = editor_selection->get_selection();
-			Node *editor_root = EditorNode::get_singleton()->get_edited_scene()->get_tree()->get_edited_scene_root();
+			Node *editor_root = get_tree()->get_edited_scene_root();
+
+			if (!editor_root || selection.is_empty()) {
+				return;
+			}
 
 			undo_redo->create_action(TTR("Create Custom Bone2D(s) from Node(s)"));
 			for (const KeyValue<Node *, Object *> &E : selection) {

+ 12 - 1
editor/plugins/collision_shape_2d_editor_plugin.cpp

@@ -33,6 +33,7 @@
 #include "canvas_item_editor_plugin.h"
 #include "core/os/keyboard.h"
 #include "editor/editor_node.h"
+#include "editor/editor_settings.h"
 #include "editor/editor_undo_redo_manager.h"
 #include "scene/resources/capsule_shape_2d.h"
 #include "scene/resources/circle_shape_2d.h"
@@ -44,6 +45,10 @@
 #include "scene/resources/world_boundary_shape_2d.h"
 #include "scene/scene_string_names.h"
 
+CollisionShape2DEditor::CollisionShape2DEditor() {
+	grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius");
+}
+
 void CollisionShape2DEditor::_node_removed(Node *p_node) {
 	if (p_node == node) {
 		node = nullptr;
@@ -307,7 +312,7 @@ bool CollisionShape2DEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_e
 		if (mb->get_button_index() == MouseButton::LEFT) {
 			if (mb->is_pressed()) {
 				for (int i = 0; i < handles.size(); i++) {
-					if (xform.xform(handles[i]).distance_to(gpoint) < 8) {
+					if (xform.xform(handles[i]).distance_to(gpoint) < grab_threshold) {
 						edit_handle = i;
 
 						break;
@@ -529,6 +534,12 @@ void CollisionShape2DEditor::_notification(int p_what) {
 				_shape_changed();
 			}
 		} break;
+
+		case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
+			if (EditorSettings::get_singleton()->check_changed_settings_in_group("editors/polygon_editor/point_grab_radius")) {
+				grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius");
+			}
+		} break;
 	}
 }
 

+ 3 - 0
editor/plugins/collision_shape_2d_editor_plugin.h

@@ -69,6 +69,7 @@ class CollisionShape2DEditor : public Control {
 	int shape_type = -1;
 	int edit_handle = -1;
 	bool pressed = false;
+	real_t grab_threshold = 8;
 	Variant original;
 	Transform2D original_transform;
 	Vector2 original_point;
@@ -90,6 +91,8 @@ public:
 	bool forward_canvas_gui_input(const Ref<InputEvent> &p_event);
 	void forward_canvas_draw_over_viewport(Control *p_overlay);
 	void edit(Node *p_node);
+
+	CollisionShape2DEditor();
 };
 
 class CollisionShape2DEditorPlugin : public EditorPlugin {

+ 14 - 8
editor/plugins/curve_editor_plugin.cpp

@@ -47,6 +47,7 @@ CurveEditor::CurveEditor() {
 	_tangents_length = 40;
 	_dragging = false;
 	_has_undo_data = false;
+	_gizmo_handle_scale = EDITOR_GET("interface/touchscreen/scale_gizmo_handles");
 
 	set_focus_mode(FOCUS_ALL);
 	set_clip_contents(true);
@@ -103,6 +104,11 @@ void CurveEditor::_notification(int p_what) {
 		case NOTIFICATION_DRAW: {
 			_draw();
 		} break;
+		case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
+			if (EditorSettings::get_singleton()->check_changed_settings_in_group("interface/touchscreen/scale_gizmo_handles")) {
+				_gizmo_handle_scale = EDITOR_GET("interface/touchscreen/scale_gizmo_handles");
+			}
+		} break;
 	}
 }
 
@@ -395,7 +401,7 @@ int CurveEditor::get_point_at(Vector2 pos) const {
 	}
 	const Curve &curve = **_curve_ref;
 
-	const float true_hover_radius = Math::round(_hover_radius * EDSCALE);
+	const float true_hover_radius = Math::round(_hover_radius * _gizmo_handle_scale * EDSCALE);
 	const float r = true_hover_radius * true_hover_radius;
 
 	for (int i = 0; i < curve.get_point_count(); ++i) {
@@ -415,14 +421,14 @@ CurveEditor::TangentIndex CurveEditor::get_tangent_at(Vector2 pos) const {
 
 	if (_selected_point != 0) {
 		Vector2 control_pos = get_tangent_view_pos(_selected_point, TANGENT_LEFT);
-		if (control_pos.distance_to(pos) < _hover_radius) {
+		if (control_pos.distance_to(pos) < _hover_radius * _gizmo_handle_scale) {
 			return TANGENT_LEFT;
 		}
 	}
 
 	if (_selected_point != _curve_ref->get_point_count() - 1) {
 		Vector2 control_pos = get_tangent_view_pos(_selected_point, TANGENT_RIGHT);
-		if (control_pos.distance_to(pos) < _hover_radius) {
+		if (control_pos.distance_to(pos) < _hover_radius * _gizmo_handle_scale) {
 			return TANGENT_RIGHT;
 		}
 	}
@@ -560,7 +566,7 @@ Vector2 CurveEditor::get_tangent_view_pos(int i, TangentIndex tangent) const {
 	Vector2 point_pos = get_view_pos(_curve_ref->get_point_position(i));
 	Vector2 control_pos = get_view_pos(_curve_ref->get_point_position(i) + dir);
 
-	return point_pos + Math::round(_tangents_length * EDSCALE) * (control_pos - point_pos).normalized();
+	return point_pos + Math::round(_tangents_length * _gizmo_handle_scale * EDSCALE) * (control_pos - point_pos).normalized();
 }
 
 Vector2 CurveEditor::get_view_pos(Vector2 world_pos) const {
@@ -705,13 +711,13 @@ void CurveEditor::_draw() {
 		if (i != 0) {
 			Vector2 control_pos = get_tangent_view_pos(i, TANGENT_LEFT);
 			draw_line(get_view_pos(pos), control_pos, tangent_color, Math::round(EDSCALE));
-			draw_rect(Rect2(control_pos, Vector2(1, 1)).grow(Math::round(2 * EDSCALE)), tangent_color);
+			draw_rect(Rect2(control_pos, Vector2(1, 1)).grow(Math::round(2 * _gizmo_handle_scale * EDSCALE)), tangent_color);
 		}
 
 		if (i != curve.get_point_count() - 1) {
 			Vector2 control_pos = get_tangent_view_pos(i, TANGENT_RIGHT);
 			draw_line(get_view_pos(pos), control_pos, tangent_color, Math::round(EDSCALE));
-			draw_rect(Rect2(control_pos, Vector2(1, 1)).grow(Math::round(2 * EDSCALE)), tangent_color);
+			draw_rect(Rect2(control_pos, Vector2(1, 1)).grow(Math::round(2 * _gizmo_handle_scale * EDSCALE)), tangent_color);
 		}
 	}
 
@@ -734,7 +740,7 @@ void CurveEditor::_draw() {
 
 	for (int i = 0; i < curve.get_point_count(); ++i) {
 		Vector2 pos = curve.get_point_position(i);
-		draw_rect(Rect2(get_view_pos(pos), Vector2(1, 1)).grow(Math::round(3 * EDSCALE)), i == _selected_point ? selected_point_color : point_color);
+		draw_rect(Rect2(get_view_pos(pos), Vector2(1, 1)).grow(Math::round(3 * _gizmo_handle_scale * EDSCALE)), i == _selected_point ? selected_point_color : point_color);
 		// TODO Circles are prettier. Needs a fix! Or a texture
 		//draw_circle(pos, 2, point_color);
 	}
@@ -744,7 +750,7 @@ void CurveEditor::_draw() {
 	if (_hover_point != -1) {
 		const Color hover_color = line_color;
 		Vector2 pos = curve.get_point_position(_hover_point);
-		draw_rect(Rect2(get_view_pos(pos), Vector2(1, 1)).grow(Math::round(_hover_radius * EDSCALE)), hover_color, false, Math::round(EDSCALE));
+		draw_rect(Rect2(get_view_pos(pos), Vector2(1, 1)).grow(Math::round(_hover_radius * _gizmo_handle_scale * EDSCALE)), hover_color, false, Math::round(EDSCALE));
 	}
 
 	// Help text

+ 1 - 0
editor/plugins/curve_editor_plugin.h

@@ -115,6 +115,7 @@ private:
 	// Constant
 	float _hover_radius;
 	float _tangents_length;
+	float _gizmo_handle_scale = 1.0;
 };
 
 class EditorInspectorPluginCurve : public EditorInspectorPlugin {

+ 2 - 0
editor/plugins/sprite_frames_editor_plugin.cpp

@@ -1735,6 +1735,7 @@ SpriteFramesEditor::SpriteFramesEditor() {
 	frame_list = memnew(ItemList);
 	frame_list->set_v_size_flags(SIZE_EXPAND_FILL);
 	frame_list->set_icon_mode(ItemList::ICON_MODE_TOP);
+	frame_list->set_texture_filter(TEXTURE_FILTER_NEAREST_WITH_MIPMAPS);
 
 	frame_list->set_max_columns(0);
 	frame_list->set_icon_mode(ItemList::ICON_MODE_TOP);
@@ -1883,6 +1884,7 @@ SpriteFramesEditor::SpriteFramesEditor() {
 
 	split_sheet_preview = memnew(TextureRect);
 	split_sheet_preview->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE);
+	split_sheet_preview->set_texture_filter(TEXTURE_FILTER_NEAREST_WITH_MIPMAPS);
 	split_sheet_preview->set_mouse_filter(MOUSE_FILTER_PASS);
 	split_sheet_preview->connect("draw", callable_mp(this, &SpriteFramesEditor::_sheet_preview_draw));
 	split_sheet_preview->connect("gui_input", callable_mp(this, &SpriteFramesEditor::_sheet_preview_input));

+ 7 - 13
editor/plugins/theme_editor_plugin.cpp

@@ -1827,19 +1827,13 @@ void ThemeItemEditorDialog::_edit_theme_item_gui_input(const Ref<InputEvent> &p_
 			return;
 		}
 
-		switch (k->get_keycode()) {
-			case Key::KP_ENTER:
-			case Key::ENTER: {
-				_confirm_edit_theme_item();
-				edit_theme_item_dialog->hide();
-				edit_theme_item_dialog->set_input_as_handled();
-			} break;
-			case Key::ESCAPE: {
-				edit_theme_item_dialog->hide();
-				edit_theme_item_dialog->set_input_as_handled();
-			} break;
-			default:
-				break;
+		if (k->is_action_pressed(SNAME("ui_accept"), false, true)) {
+			_confirm_edit_theme_item();
+			edit_theme_item_dialog->hide();
+			edit_theme_item_dialog->set_input_as_handled();
+		} else if (k->is_action_pressed(SNAME("ui_cancel"), false, true)) {
+			edit_theme_item_dialog->hide();
+			edit_theme_item_dialog->set_input_as_handled();
 		}
 	}
 }

+ 5 - 3
editor/plugins/tiles/atlas_merging_dialog.cpp

@@ -106,10 +106,11 @@ void AtlasMergingDialog::_generate_merged(Vector<Ref<TileSetAtlasSource>> p_atla
 				// Create tiles and alternatives, then copy their properties.
 				for (int alternative_index = 0; alternative_index < atlas_source->get_alternative_tiles_count(tile_mapping.key); alternative_index++) {
 					int alternative_id = atlas_source->get_alternative_tile_id(tile_mapping.key, alternative_index);
+					int changed_id = -1;
 					if (alternative_id == 0) {
 						merged->create_tile(tile_mapping.value, atlas_source->get_tile_size_in_atlas(tile_mapping.key));
 					} else {
-						merged->create_alternative_tile(tile_mapping.value, alternative_index);
+						changed_id = merged->create_alternative_tile(tile_mapping.value, alternative_index);
 					}
 
 					// Copy the properties.
@@ -117,7 +118,7 @@ void AtlasMergingDialog::_generate_merged(Vector<Ref<TileSetAtlasSource>> p_atla
 					List<PropertyInfo> properties;
 					src_tile_data->get_property_list(&properties);
 
-					TileData *dst_tile_data = merged->get_tile_data(tile_mapping.value, alternative_id);
+					TileData *dst_tile_data = merged->get_tile_data(tile_mapping.value, changed_id == -1 ? alternative_id : changed_id);
 					for (PropertyInfo property : properties) {
 						if (!(property.usage & PROPERTY_USAGE_STORAGE)) {
 							continue;
@@ -296,7 +297,7 @@ AtlasMergingDialog::AtlasMergingDialog() {
 	atlas_merging_atlases_list->set_fixed_icon_size(Size2(60, 60) * EDSCALE);
 	atlas_merging_atlases_list->set_h_size_flags(Control::SIZE_EXPAND_FILL);
 	atlas_merging_atlases_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
-	atlas_merging_atlases_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST);
+	atlas_merging_atlases_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST_WITH_MIPMAPS);
 	atlas_merging_atlases_list->set_custom_minimum_size(Size2(100, 200));
 	atlas_merging_atlases_list->set_select_mode(ItemList::SELECT_MULTI);
 	atlas_merging_atlases_list->connect("multi_selected", callable_mp(this, &AtlasMergingDialog::_update_texture).unbind(2));
@@ -304,6 +305,7 @@ AtlasMergingDialog::AtlasMergingDialog() {
 
 	VBoxContainer *atlas_merging_right_panel = memnew(VBoxContainer);
 	atlas_merging_right_panel->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+	atlas_merging_right_panel->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST_WITH_MIPMAPS);
 	atlas_merging_h_split_container->add_child(atlas_merging_right_panel);
 
 	// Settings.

+ 2 - 2
editor/plugins/version_control_editor_plugin.cpp

@@ -942,7 +942,7 @@ VersionControlEditorPlugin::VersionControlEditorPlugin() {
 	metadata_dialog->set_title(TTR("Create Version Control Metadata"));
 	metadata_dialog->set_min_size(Size2(200, 40));
 	metadata_dialog->get_ok_button()->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_create_vcs_metadata_files));
-	add_child(metadata_dialog);
+	get_editor_interface()->get_base_control()->add_child(metadata_dialog);
 
 	VBoxContainer *metadata_vb = memnew(VBoxContainer);
 	metadata_dialog->add_child(metadata_vb);
@@ -971,7 +971,7 @@ VersionControlEditorPlugin::VersionControlEditorPlugin() {
 	set_up_dialog->set_min_size(Size2(600, 100));
 	set_up_dialog->add_cancel_button("Cancel");
 	set_up_dialog->set_hide_on_ok(true);
-	add_child(set_up_dialog);
+	get_editor_interface()->get_base_control()->add_child(set_up_dialog);
 
 	Button *set_up_apply_button = set_up_dialog->get_ok_button();
 	set_up_apply_button->set_text(TTR("Apply"));

+ 49 - 37
editor/project_converter_3_to_4.cpp

@@ -390,7 +390,7 @@ bool ProjectConverter3To4::convert() {
 				rename_gdscript_functions(source_lines, reg_container, false); // Require to additional rename.
 
 				rename_common(RenamesMap3To4::project_settings_renames, reg_container.project_settings_regexes, source_lines);
-				rename_gdscript_keywords(source_lines, reg_container);
+				rename_gdscript_keywords(source_lines, reg_container, false);
 				rename_common(RenamesMap3To4::gdscript_properties_renames, reg_container.gdscript_properties_regexes, source_lines);
 				rename_common(RenamesMap3To4::gdscript_signals_renames, reg_container.gdscript_signals_regexes, source_lines);
 				rename_common(RenamesMap3To4::shaders_renames, reg_container.shaders_regexes, source_lines);
@@ -408,7 +408,7 @@ bool ProjectConverter3To4::convert() {
 				rename_gdscript_functions(source_lines, reg_container, true); // Require to do additional renames.
 
 				rename_common(RenamesMap3To4::project_settings_renames, reg_container.project_settings_regexes, source_lines);
-				rename_gdscript_keywords(source_lines, reg_container);
+				rename_gdscript_keywords(source_lines, reg_container, true);
 				rename_common(RenamesMap3To4::gdscript_properties_renames, reg_container.gdscript_properties_regexes, source_lines);
 				rename_common(RenamesMap3To4::gdscript_signals_renames, reg_container.gdscript_signals_regexes, source_lines);
 				rename_common(RenamesMap3To4::shaders_renames, reg_container.shaders_regexes, source_lines);
@@ -572,7 +572,7 @@ bool ProjectConverter3To4::validate_conversion() {
 				changed_elements.append_array(check_for_rename_gdscript_functions(lines, reg_container, false));
 
 				changed_elements.append_array(check_for_rename_common(RenamesMap3To4::project_settings_renames, reg_container.project_settings_regexes, lines));
-				changed_elements.append_array(check_for_rename_gdscript_keywords(lines, reg_container));
+				changed_elements.append_array(check_for_rename_gdscript_keywords(lines, reg_container, false));
 				changed_elements.append_array(check_for_rename_common(RenamesMap3To4::gdscript_properties_renames, reg_container.gdscript_properties_regexes, lines));
 				changed_elements.append_array(check_for_rename_common(RenamesMap3To4::gdscript_signals_renames, reg_container.gdscript_signals_regexes, lines));
 				changed_elements.append_array(check_for_rename_common(RenamesMap3To4::shaders_renames, reg_container.shaders_regexes, lines));
@@ -590,7 +590,7 @@ bool ProjectConverter3To4::validate_conversion() {
 				changed_elements.append_array(check_for_rename_gdscript_functions(lines, reg_container, true));
 
 				changed_elements.append_array(check_for_rename_common(RenamesMap3To4::project_settings_renames, reg_container.project_settings_regexes, lines));
-				changed_elements.append_array(check_for_rename_gdscript_keywords(lines, reg_container));
+				changed_elements.append_array(check_for_rename_gdscript_keywords(lines, reg_container, true));
 				changed_elements.append_array(check_for_rename_common(RenamesMap3To4::gdscript_properties_renames, reg_container.gdscript_properties_regexes, lines));
 				changed_elements.append_array(check_for_rename_common(RenamesMap3To4::gdscript_signals_renames, reg_container.gdscript_signals_regexes, lines));
 				changed_elements.append_array(check_for_rename_common(RenamesMap3To4::shaders_renames, reg_container.shaders_regexes, lines));
@@ -863,22 +863,26 @@ bool ProjectConverter3To4::test_conversion(RegExContainer &reg_container) {
 	valid = valid && test_conversion_with_regex("Spatial.shader", "Spatial.shader", &ProjectConverter3To4::rename_classes, "classes", reg_container);
 	valid = valid && test_conversion_with_regex("Spatial.other", "Node3D.other", &ProjectConverter3To4::rename_classes, "classes", reg_container);
 
-	valid = valid && test_conversion_with_regex("\nonready", "\n@onready", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
-	valid = valid && test_conversion_with_regex("onready", "@onready", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
-	valid = valid && test_conversion_with_regex(" onready", " onready", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
-	valid = valid && test_conversion_with_regex("\nexport", "\n@export", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
-	valid = valid && test_conversion_with_regex("\texport", "\t@export", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
-	valid = valid && test_conversion_with_regex("\texport_dialog", "\texport_dialog", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
-	valid = valid && test_conversion_with_regex("export", "@export", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
-	valid = valid && test_conversion_with_regex(" export", " export", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
-	valid = valid && test_conversion_with_regex("\n\nremote func", "\n\n@rpc(\"any_peer\") func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
-	valid = valid && test_conversion_with_regex("\n\nremotesync func", "\n\n@rpc(\"any_peer\", \"call_local\") func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
-	valid = valid && test_conversion_with_regex("\n\nsync func", "\n\n@rpc(\"any_peer\", \"call_local\") func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
-	valid = valid && test_conversion_with_regex("\n\nslave func", "\n\n@rpc func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
-	valid = valid && test_conversion_with_regex("\n\npuppet func", "\n\n@rpc func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
-	valid = valid && test_conversion_with_regex("\n\npuppetsync func", "\n\n@rpc(\"call_local\") func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
-	valid = valid && test_conversion_with_regex("\n\nmaster func", "\n\nThe master and mastersync rpc behavior is not officially supported anymore. Try using another keyword or making custom logic using get_multiplayer().get_remote_sender_id()\n@rpc func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
-	valid = valid && test_conversion_with_regex("\n\nmastersync func", "\n\nThe master and mastersync rpc behavior is not officially supported anymore. Try using another keyword or making custom logic using get_multiplayer().get_remote_sender_id()\n@rpc(\"call_local\") func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
+	valid = valid && test_conversion_gdscript_builtin("\nonready", "\n@onready", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container, false);
+	valid = valid && test_conversion_gdscript_builtin("onready", "@onready", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container, false);
+	valid = valid && test_conversion_gdscript_builtin(" onready", " onready", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container, false);
+	valid = valid && test_conversion_gdscript_builtin("\nexport", "\n@export", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container, false);
+	valid = valid && test_conversion_gdscript_builtin("\texport", "\t@export", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container, false);
+	valid = valid && test_conversion_gdscript_builtin("\texport_dialog", "\texport_dialog", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container, false);
+	valid = valid && test_conversion_gdscript_builtin("export", "@export", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container, false);
+	valid = valid && test_conversion_gdscript_builtin(" export", " export", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container, false);
+	valid = valid && test_conversion_gdscript_builtin("\n\nremote func", "\n\n@rpc(\"any_peer\") func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container, false);
+	valid = valid && test_conversion_gdscript_builtin("\n\nremote func", "\n\n@rpc(\\\"any_peer\\\") func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container, true);
+	valid = valid && test_conversion_gdscript_builtin("\n\nremotesync func", "\n\n@rpc(\"any_peer\", \"call_local\") func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container, false);
+	valid = valid && test_conversion_gdscript_builtin("\n\nremotesync func", "\n\n@rpc(\\\"any_peer\\\", \\\"call_local\\\") func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container, true);
+	valid = valid && test_conversion_gdscript_builtin("\n\nsync func", "\n\n@rpc(\"any_peer\", \"call_local\") func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container, false);
+	valid = valid && test_conversion_gdscript_builtin("\n\nsync func", "\n\n@rpc(\\\"any_peer\\\", \\\"call_local\\\") func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container, true);
+	valid = valid && test_conversion_gdscript_builtin("\n\nslave func", "\n\n@rpc func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container, false);
+	valid = valid && test_conversion_gdscript_builtin("\n\npuppet func", "\n\n@rpc func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container, false);
+	valid = valid && test_conversion_gdscript_builtin("\n\npuppetsync func", "\n\n@rpc(\"call_local\") func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container, false);
+	valid = valid && test_conversion_gdscript_builtin("\n\npuppetsync func", "\n\n@rpc(\\\"call_local\\\") func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container, true);
+	valid = valid && test_conversion_gdscript_builtin("\n\nmaster func", "\n\nThe master and mastersync rpc behavior is not officially supported anymore. Try using another keyword or making custom logic using get_multiplayer().get_remote_sender_id()\n@rpc func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container, false);
+	valid = valid && test_conversion_gdscript_builtin("\n\nmastersync func", "\n\nThe master and mastersync rpc behavior is not officially supported anymore. Try using another keyword or making custom logic using get_multiplayer().get_remote_sender_id()\n@rpc(\"call_local\") func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container, false);
 
 	valid = valid && test_conversion_gdscript_builtin("var size: Vector2 = Vector2() setget set_function, get_function", "var size: Vector2 = Vector2(): get = get_function, set = set_function", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
 	valid = valid && test_conversion_gdscript_builtin("var size: Vector2 = Vector2() setget set_function, ", "var size: Vector2 = Vector2(): set = set_function", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
@@ -2442,7 +2446,15 @@ Vector<String> ProjectConverter3To4::check_for_rename_csharp_attributes(Vector<S
 	return found_renames;
 }
 
-void ProjectConverter3To4::rename_gdscript_keywords(Vector<SourceLine> &source_lines, const RegExContainer &reg_container) {
+_FORCE_INLINE_ static String builtin_escape(const String &p_str, bool p_builtin) {
+	if (p_builtin) {
+		return p_str.replace("\"", "\\\"");
+	} else {
+		return p_str;
+	}
+}
+
+void ProjectConverter3To4::rename_gdscript_keywords(Vector<SourceLine> &source_lines, const RegExContainer &reg_container, bool builtin) {
 	static String error_message = "The master and mastersync rpc behavior is not officially supported anymore. Try using another keyword or making custom logic using get_multiplayer().get_remote_sender_id()\n";
 
 	for (SourceLine &source_line : source_lines) {
@@ -2462,13 +2474,13 @@ void ProjectConverter3To4::rename_gdscript_keywords(Vector<SourceLine> &source_l
 				line = reg_container.keyword_gdscript_onready.sub(line, "@onready", true);
 			}
 			if (line.contains("remote")) {
-				line = reg_container.keyword_gdscript_remote.sub(line, "@rpc(\"any_peer\") func", true);
+				line = reg_container.keyword_gdscript_remote.sub(line, builtin_escape("@rpc(\"any_peer\") func", builtin), true);
 			}
 			if (line.contains("remote")) {
-				line = reg_container.keyword_gdscript_remotesync.sub(line, "@rpc(\"any_peer\", \"call_local\") func", true);
+				line = reg_container.keyword_gdscript_remotesync.sub(line, builtin_escape("@rpc(\"any_peer\", \"call_local\") func", builtin), true);
 			}
 			if (line.contains("sync")) {
-				line = reg_container.keyword_gdscript_sync.sub(line, "@rpc(\"any_peer\", \"call_local\") func", true);
+				line = reg_container.keyword_gdscript_sync.sub(line, builtin_escape("@rpc(\"any_peer\", \"call_local\") func", builtin), true);
 			}
 			if (line.contains("slave")) {
 				line = reg_container.keyword_gdscript_slave.sub(line, "@rpc func", true);
@@ -2477,19 +2489,19 @@ void ProjectConverter3To4::rename_gdscript_keywords(Vector<SourceLine> &source_l
 				line = reg_container.keyword_gdscript_puppet.sub(line, "@rpc func", true);
 			}
 			if (line.contains("puppet")) {
-				line = reg_container.keyword_gdscript_puppetsync.sub(line, "@rpc(\"call_local\") func", true);
+				line = reg_container.keyword_gdscript_puppetsync.sub(line, builtin_escape("@rpc(\"call_local\") func", builtin), true);
 			}
 			if (line.contains("master")) {
 				line = reg_container.keyword_gdscript_master.sub(line, error_message + "@rpc func", true);
 			}
 			if (line.contains("master")) {
-				line = reg_container.keyword_gdscript_mastersync.sub(line, error_message + "@rpc(\"call_local\") func", true);
+				line = reg_container.keyword_gdscript_mastersync.sub(line, error_message + builtin_escape("@rpc(\"call_local\") func", builtin), true);
 			}
 		}
 	}
 }
 
-Vector<String> ProjectConverter3To4::check_for_rename_gdscript_keywords(Vector<String> &lines, const RegExContainer &reg_container) {
+Vector<String> ProjectConverter3To4::check_for_rename_gdscript_keywords(Vector<String> &lines, const RegExContainer &reg_container, bool builtin) {
 	Vector<String> found_renames;
 
 	int current_line = 1;
@@ -2531,25 +2543,25 @@ Vector<String> ProjectConverter3To4::check_for_rename_gdscript_keywords(Vector<S
 
 			if (line.contains("remote")) {
 				old = line;
-				line = reg_container.keyword_gdscript_remote.sub(line, "@rpc(\"any_peer\") func", true);
+				line = reg_container.keyword_gdscript_remote.sub(line, builtin_escape("@rpc(\"any_peer\") func", builtin), true);
 				if (old != line) {
-					found_renames.append(line_formatter(current_line, "remote func", "@rpc(\"any_peer\") func", line));
+					found_renames.append(line_formatter(current_line, "remote func", builtin_escape("@rpc(\"any_peer\") func", builtin), line));
 				}
 			}
 
 			if (line.contains("remote")) {
 				old = line;
-				line = reg_container.keyword_gdscript_remotesync.sub(line, "@rpc(\"any_peer\", \"call_local\")) func", true);
+				line = reg_container.keyword_gdscript_remotesync.sub(line, builtin_escape("@rpc(\"any_peer\", \"call_local\")) func", builtin), true);
 				if (old != line) {
-					found_renames.append(line_formatter(current_line, "remotesync func", "@rpc(\"any_peer\", \"call_local\")) func", line));
+					found_renames.append(line_formatter(current_line, "remotesync func", builtin_escape("@rpc(\"any_peer\", \"call_local\")) func", builtin), line));
 				}
 			}
 
 			if (line.contains("sync")) {
 				old = line;
-				line = reg_container.keyword_gdscript_sync.sub(line, "@rpc(\"any_peer\", \"call_local\")) func", true);
+				line = reg_container.keyword_gdscript_sync.sub(line, builtin_escape("@rpc(\"any_peer\", \"call_local\")) func", builtin), true);
 				if (old != line) {
-					found_renames.append(line_formatter(current_line, "sync func", "@rpc(\"any_peer\", \"call_local\")) func", line));
+					found_renames.append(line_formatter(current_line, "sync func", builtin_escape("@rpc(\"any_peer\", \"call_local\")) func", builtin), line));
 				}
 			}
 
@@ -2571,9 +2583,9 @@ Vector<String> ProjectConverter3To4::check_for_rename_gdscript_keywords(Vector<S
 
 			if (line.contains("puppet")) {
 				old = line;
-				line = reg_container.keyword_gdscript_puppetsync.sub(line, "@rpc(\"call_local\") func", true);
+				line = reg_container.keyword_gdscript_puppetsync.sub(line, builtin_escape("@rpc(\"call_local\") func", builtin), true);
 				if (old != line) {
-					found_renames.append(line_formatter(current_line, "puppetsync func", "@rpc(\"call_local\") func", line));
+					found_renames.append(line_formatter(current_line, "puppetsync func", builtin_escape("@rpc(\"call_local\") func", builtin), line));
 				}
 			}
 
@@ -2587,9 +2599,9 @@ Vector<String> ProjectConverter3To4::check_for_rename_gdscript_keywords(Vector<S
 
 			if (line.contains("master")) {
 				old = line;
-				line = reg_container.keyword_gdscript_master.sub(line, "@rpc(\"call_local\") func", true);
+				line = reg_container.keyword_gdscript_master.sub(line, builtin_escape("@rpc(\"call_local\") func", builtin), true);
 				if (old != line) {
-					found_renames.append(line_formatter(current_line, "mastersync func", "@rpc(\"call_local\") func", line));
+					found_renames.append(line_formatter(current_line, "mastersync func", builtin_escape("@rpc(\"call_local\") func", builtin), line));
 				}
 			}
 		}

+ 2 - 2
editor/project_converter_3_to_4.h

@@ -90,8 +90,8 @@ class ProjectConverter3To4 {
 	void rename_csharp_attributes(Vector<SourceLine> &source_lines, const RegExContainer &reg_container);
 	Vector<String> check_for_rename_csharp_attributes(Vector<String> &lines, const RegExContainer &reg_container);
 
-	void rename_gdscript_keywords(Vector<SourceLine> &source_lines, const RegExContainer &reg_container);
-	Vector<String> check_for_rename_gdscript_keywords(Vector<String> &lines, const RegExContainer &reg_container);
+	void rename_gdscript_keywords(Vector<SourceLine> &r_source_lines, const RegExContainer &p_reg_container, bool p_builtin);
+	Vector<String> check_for_rename_gdscript_keywords(Vector<String> &r_lines, const RegExContainer &p_reg_container, bool p_builtin);
 
 	void rename_input_map_scancode(Vector<SourceLine> &source_lines, const RegExContainer &reg_container);
 	Vector<String> check_for_rename_input_map_scancode(Vector<String> &lines, const RegExContainer &reg_container);

+ 1 - 1
misc/scripts/codespell.sh

@@ -3,6 +3,6 @@ SKIP_LIST="./.*,./**/.*,./bin,./thirdparty,*.desktop,*.gen.*,*.po,*.pot,*.rc,./A
 SKIP_LIST+="./core/input/gamecontrollerdb.txt,./core/string/locales.h,./editor/renames_map_3_to_4.cpp,./misc/scripts/codespell.sh,"
 SKIP_LIST+="./platform/android/java/lib/src/com,./platform/web/node_modules,./platform/web/package-lock.json,"
 
-IGNORE_LIST="curvelinear,doubleclick,expct,findn,gird,hel,inout,lod,nd,numer,ot,te,vai"
+IGNORE_LIST="curvelinear,doubleclick,expct,findn,gird,hel,inout,lod,mis,nd,numer,ot,te,vai"
 
 codespell -w -q 3 -S "${SKIP_LIST}" -L "${IGNORE_LIST}" --builtin "clear,rare,en-GB_to_en-US"

+ 8 - 0
modules/gdscript/editor/gdscript_translation_parser_plugin.cpp

@@ -311,6 +311,14 @@ void GDScriptEditorTranslationParserPlugin::_extract_from_call(GDScriptParser::C
 			}
 		}
 	}
+
+	if (p_call->callee && p_call->callee->type == GDScriptParser::Node::SUBSCRIPT) {
+		GDScriptParser::SubscriptNode *subscript_node = static_cast<GDScriptParser::SubscriptNode *>(p_call->callee);
+		if (subscript_node->base && subscript_node->base->type == GDScriptParser::Node::CALL) {
+			GDScriptParser::CallNode *call_node = static_cast<GDScriptParser::CallNode *>(subscript_node->base);
+			_extract_from_call(call_node);
+		}
+	}
 }
 
 void GDScriptEditorTranslationParserPlugin::_extract_fd_literals(GDScriptParser::ExpressionNode *p_expression) {

+ 4 - 1
modules/mono/csharp_script.cpp

@@ -1532,7 +1532,10 @@ CSharpInstance *CSharpInstance::create_for_managed_type(Object *p_owner, CSharpS
 		instance->_reference_owner_unsafe();
 	}
 
-	p_script->instances.insert(p_owner);
+	{
+		MutexLock lock(CSharpLanguage::get_singleton()->get_script_instances_mutex());
+		p_script->instances.insert(p_owner);
+	}
 
 	return instance;
 }

+ 12 - 0
modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs

@@ -41,6 +41,12 @@ namespace GodotTools.Build
             startInfo.EnvironmentVariables["DOTNET_CLI_UI_LANGUAGE"]
                 = ((string)editorSettings.GetSetting("interface/editor/editor_language")).Replace('_', '-');
 
+            if (OperatingSystem.IsWindows())
+            {
+                startInfo.StandardOutputEncoding = Encoding.UTF8;
+                startInfo.StandardErrorEncoding = Encoding.UTF8;
+            }
+
             // Needed when running from Developer Command Prompt for VS
             RemovePlatformVariable(startInfo.EnvironmentVariables);
 
@@ -105,6 +111,12 @@ namespace GodotTools.Build
             startInfo.EnvironmentVariables["DOTNET_CLI_UI_LANGUAGE"]
                 = ((string)editorSettings.GetSetting("interface/editor/editor_language")).Replace('_', '-');
 
+            if (OperatingSystem.IsWindows())
+            {
+                startInfo.StandardOutputEncoding = Encoding.UTF8;
+                startInfo.StandardErrorEncoding = Encoding.UTF8;
+            }
+
             // Needed when running from Developer Command Prompt for VS
             RemovePlatformVariable(startInfo.EnvironmentVariables);
 

+ 6 - 0
modules/mono/editor/GodotTools/GodotTools/Build/DotNetFinder.cs

@@ -4,6 +4,7 @@ using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using System.IO;
 using System.Runtime.InteropServices;
+using System.Text;
 using JetBrains.Annotations;
 using OS = GodotTools.Utils.OS;
 
@@ -58,6 +59,11 @@ namespace GodotTools.Build
                 RedirectStandardOutput = true
             };
 
+            if (OperatingSystem.IsWindows())
+            {
+                process.StartInfo.StandardOutputEncoding = Encoding.UTF8;
+            }
+
             process.StartInfo.EnvironmentVariables["DOTNET_CLI_UI_LANGUAGE"] = "en-US";
 
             var lines = new List<string>();

+ 2 - 2
modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs

@@ -103,7 +103,7 @@ namespace Godot.NativeInterop
         {
             try
             {
-                if (NativeFuncs.godotsharp_internal_script_debugger_is_active())
+                if (NativeFuncs.godotsharp_internal_script_debugger_is_active().ToBool())
                 {
                     SendToScriptDebugger(e);
                 }
@@ -122,7 +122,7 @@ namespace Godot.NativeInterop
         {
             try
             {
-                if (NativeFuncs.godotsharp_internal_script_debugger_is_active())
+                if (NativeFuncs.godotsharp_internal_script_debugger_is_active().ToBool())
                 {
                     SendToScriptDebugger(e);
                 }

+ 1 - 1
modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs

@@ -56,7 +56,7 @@ namespace Godot.NativeInterop
             in godot_string p_file, int p_line, in godot_string p_err, in godot_string p_descr,
             godot_bool p_warning, in DebuggingUtils.godot_stack_info_vector p_stack_info_vector);
 
-        internal static partial bool godotsharp_internal_script_debugger_is_active();
+        internal static partial godot_bool godotsharp_internal_script_debugger_is_active();
 
         internal static partial IntPtr godotsharp_internal_object_get_associated_gchandle(IntPtr ptr);
 

+ 0 - 1
modules/noise/icons/NoiseTexture.svg

@@ -1 +0,0 @@
-<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m2 1c-.55228 0-1 .44772-1 1v12c0 .55228.44772 1 1 1h12c.55228 0 1-.44772 1-1v-12c0-.55228-.44772-1-1-1zm1 2h10v8h-10zm3 1v2h2v-2zm2 2v2h2v2h2v-6h-2v2zm0 2h-2v-2h-2v4h4z" fill="#e0e0e0" fill-opacity=".99608"/></svg>

+ 1 - 0
modules/noise/icons/NoiseTexture2D.svg

@@ -0,0 +1 @@
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M2 1a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zm1 2h10v8H3zm3 1v2h2V4zm2 2v2h2v2h2V4h-2v2zm0 2H6V6H4v4h4z" fill="#e0e0e0"/></svg>

+ 4 - 1
platform/android/SCsub

@@ -56,7 +56,10 @@ if lib_arch_dir != "":
     if env.dev_build:
         lib_type_dir = "dev"
     elif env.debug_features:
-        lib_type_dir = "debug"
+        if env.editor_build and env["store_release"]:
+            lib_type_dir = "release"
+        else:
+            lib_type_dir = "debug"
     else:  # Release
         lib_type_dir = "release"
 

+ 1 - 0
platform/android/android_keys_utils.h

@@ -60,6 +60,7 @@ static AndroidGodotCodePair android_godot_code_pairs[] = {
 	{ AKEYCODE_DPAD_DOWN, Key::DOWN }, // (20) Directional Pad Down key.
 	{ AKEYCODE_DPAD_LEFT, Key::LEFT }, // (21) Directional Pad Left key.
 	{ AKEYCODE_DPAD_RIGHT, Key::RIGHT }, // (22) Directional Pad Right key.
+	{ AKEYCODE_DPAD_CENTER, Key::ENTER }, // (23) Directional Pad Center key.
 	{ AKEYCODE_VOLUME_UP, Key::VOLUMEUP }, // (24) Volume Up key.
 	{ AKEYCODE_VOLUME_DOWN, Key::VOLUMEDOWN }, // (25) Volume Down key.
 	{ AKEYCODE_POWER, Key::STANDBY }, // (26) Power key.

+ 3 - 0
platform/android/detect.py

@@ -22,6 +22,8 @@ def can_build():
 
 
 def get_opts():
+    from SCons.Variables import BoolVariable
+
     return [
         ("ANDROID_SDK_ROOT", "Path to the Android SDK", get_env_android_sdk_root()),
         (
@@ -29,6 +31,7 @@ def get_opts():
             'Target platform (android-<api>, e.g. "android-' + str(get_min_target_api()) + '")',
             "android-" + str(get_min_target_api()),
         ),
+        BoolVariable("store_release", "Editor build for Google Play Store (for official builds only)", False),
     ]
 
 

+ 6 - 4
platform/android/java/app/config.gradle

@@ -135,14 +135,16 @@ ext.generateGodotLibraryVersion = { List<String> requiredKeys ->
                     String statusValue = map["status"]
                     if (statusValue == null) {
                         statusCode = 0
-                    } else if (statusValue.startsWith("alpha")) {
+                    } else if (statusValue.startsWith("dev")) {
                         statusCode = 1
-                    } else if (statusValue.startsWith("beta")) {
+                    } else if (statusValue.startsWith("alpha")) {
                         statusCode = 2
-                    } else if (statusValue.startsWith("rc")) {
+                    } else if (statusValue.startsWith("beta")) {
                         statusCode = 3
-                    } else if (statusValue.startsWith("stable")) {
+                    } else if (statusValue.startsWith("rc")) {
                         statusCode = 4
+                    } else if (statusValue.startsWith("stable")) {
+                        statusCode = 5
                     } else {
                         statusCode = 0
                     }

+ 44 - 14
platform/android/java/build.gradle

@@ -9,7 +9,7 @@ buildscript {
     dependencies {
         classpath libraries.androidGradlePlugin
         classpath libraries.kotlinGradlePlugin
-        classpath 'io.github.gradle-nexus:publish-plugin:1.1.0'
+        classpath 'io.github.gradle-nexus:publish-plugin:1.3.0'
     }
 }
 
@@ -38,9 +38,7 @@ ext {
     supportedAbis = ["arm32", "arm64", "x86_32", "x86_64"]
     supportedFlavors = ["editor", "template"]
     supportedFlavorsBuildTypes = [
-        // The editor can't be used with target=release as debugging tools are then not
-        // included, and it would crash on errors instead of reporting them.
-        "editor": ["dev", "debug"],
+        "editor": ["dev", "debug", "release"],
         "template": ["dev", "debug", "release"]
     ]
 
@@ -54,6 +52,7 @@ ext {
 
 def rootDir = "../../.."
 def binDir = "$rootDir/bin/"
+def androidEditorBuildsDir = "$binDir/android_editor_builds/"
 
 def getSconsTaskName(String flavor, String buildType, String abi) {
     return "compileGodotNativeLibs" + flavor.capitalize() + buildType.capitalize() + abi.capitalize()
@@ -221,18 +220,46 @@ def isAndroidStudio() {
     return sysProps != null && sysProps['idea.platform.prefix'] != null
 }
 
-task copyEditorDebugBinaryToBin(type: Copy) {
+task copyEditorReleaseApkToBin(type: Copy) {
+    dependsOn ':editor:assembleRelease'
+    from('editor/build/outputs/apk/release')
+    into(androidEditorBuildsDir)
+    include('android_editor-release*.apk')
+}
+
+task copyEditorReleaseAabToBin(type: Copy) {
+    dependsOn ':editor:bundleRelease'
+    from('editor/build/outputs/bundle/release')
+    into(androidEditorBuildsDir)
+    include('android_editor-release*.aab')
+}
+
+task copyEditorDebugApkToBin(type: Copy) {
     dependsOn ':editor:assembleDebug'
     from('editor/build/outputs/apk/debug')
-    into(binDir)
-    include('android_editor.apk')
+    into(androidEditorBuildsDir)
+    include('android_editor-debug.apk')
 }
 
-task copyEditorDevBinaryToBin(type: Copy) {
+task copyEditorDebugAabToBin(type: Copy) {
+    dependsOn ':editor:bundleDebug'
+    from('editor/build/outputs/bundle/debug')
+    into(androidEditorBuildsDir)
+    include('android_editor-debug.aab')
+}
+
+task copyEditorDevApkToBin(type: Copy) {
     dependsOn ':editor:assembleDev'
     from('editor/build/outputs/apk/dev')
-    into(binDir)
-    include('android_editor_dev.apk')
+    into(androidEditorBuildsDir)
+    include('android_editor-dev.apk')
+}
+
+task copyEditorDevAabToBin(type: Copy) {
+    dependsOn ':editor:bundleDev'
+    from('editor/build/outputs/bundle/dev')
+    into(androidEditorBuildsDir)
+    include('android_editor-dev.aab')
 }
 
 /**
@@ -253,7 +280,8 @@ task generateGodotEditor {
             && targetLibs.isDirectory()
             && targetLibs.listFiles() != null
             && targetLibs.listFiles().length > 0) {
-            tasks += "copyEditor${target.capitalize()}BinaryToBin"
+            tasks += "copyEditor${target.capitalize()}ApkToBin"
+            tasks += "copyEditor${target.capitalize()}AabToBin"
         }
     }
 
@@ -301,9 +329,11 @@ task cleanGodotEditor(type: Delete) {
     // Delete the generated binary apks
     delete("editor/build/outputs/apk")
 
-    // Delete the Godot editor apks in the Godot bin directory
-    delete("$binDir/android_editor.apk")
-    delete("$binDir/android_editor_dev.apk")
+    // Delete the generated aab binaries
+    delete("editor/build/outputs/bundle")
+
+    // Delete the Godot editor apks & aabs in the Godot bin directory
+    delete(androidEditorBuildsDir)
 }
 
 /**

+ 65 - 27
platform/android/java/editor/build.gradle

@@ -13,22 +13,67 @@ dependencies {
 }
 
 ext {
-    // Build number added as a suffix to the version code, and incremented for each build/upload to
-    // the Google Play store.
-    // This should be reset on each stable release of Godot.
-    editorBuildNumber = 0
+    // Retrieve the build number from the environment variable; default to 0 if none is specified.
+    // The build number is added as a suffix to the version code for upload to the Google Play store.
+    getEditorBuildNumber = { ->
+        int buildNumber = 0
+        String versionStatus = System.getenv("GODOT_VERSION_STATUS")
+        if (versionStatus != null && !versionStatus.isEmpty()) {
+            try {
+                buildNumber = Integer.parseInt(versionStatus.replaceAll("[^0-9]", ""));
+            } catch (NumberFormatException ignored) {
+                buildNumber = 0
+            }
+        }
+
+        return buildNumber
+    }
     // Value by which the Godot version code should be offset by to make room for the build number
     editorBuildNumberOffset = 100
+
+    // Return the keystore file used for signing the release build.
+    getGodotKeystoreFile = { ->
+        def keyStore = System.getenv("GODOT_ANDROID_SIGN_KEYSTORE")
+        if (keyStore == null) {
+            return null
+        }
+        return file(keyStore)
+    }
+
+    // Return the key alias used for signing the release build.
+    getGodotKeyAlias = { ->
+        def kAlias = System.getenv("GODOT_ANDROID_KEYSTORE_ALIAS")
+        return kAlias
+    }
+
+    // Return the password for the key used for signing the release build.
+    getGodotSigningPassword = { ->
+        def signingPassword = System.getenv("GODOT_ANDROID_SIGN_PASSWORD")
+        return signingPassword
+    }
+
+    // Returns true if the environment variables contains the configuration for signing the release
+    // build.
+    hasReleaseSigningConfigs = { ->
+        def keystoreFile = getGodotKeystoreFile()
+        def keyAlias = getGodotKeyAlias()
+        def signingPassword = getGodotSigningPassword()
+
+        return keystoreFile != null && keystoreFile.isFile()
+            && keyAlias != null && !keyAlias.isEmpty()
+            && signingPassword != null && !signingPassword.isEmpty()
+    }
 }
 
 def generateVersionCode() {
     int libraryVersionCode = getGodotLibraryVersionCode()
-    return (libraryVersionCode * editorBuildNumberOffset) + editorBuildNumber
+    return (libraryVersionCode * editorBuildNumberOffset) + getEditorBuildNumber()
 }
 
 def generateVersionName() {
     String libraryVersionName = getGodotLibraryVersionName()
-    return libraryVersionName + ".$editorBuildNumber"
+    int buildNumber = getEditorBuildNumber()
+    return buildNumber == 0 ? libraryVersionName : libraryVersionName + ".$buildNumber"
 }
 
 android {
@@ -45,6 +90,7 @@ android {
         targetSdkVersion versions.targetSdk
 
         missingDimensionStrategy 'products', 'editor'
+        setProperty("archivesBaseName", "android_editor")
     }
 
     compileOptions {
@@ -56,6 +102,15 @@ android {
         jvmTarget = versions.javaVersion
     }
 
+    signingConfigs {
+        release {
+            storeFile getGodotKeystoreFile()
+            storePassword getGodotSigningPassword()
+            keyAlias getGodotKeyAlias()
+            keyPassword getGodotSigningPassword()
+        }
+    }
+
     buildTypes {
         dev {
             initWith debug
@@ -64,15 +119,14 @@ android {
 
         debug {
             initWith release
-
-            // Need to swap with the release signing config when this is ready for public release.
+            applicationIdSuffix ".debug"
             signingConfig signingConfigs.debug
         }
 
         release {
-            // This buildtype is disabled below.
-            // The editor can't be used with target=release only, as debugging tools are then not
-            // included, and it would crash on errors instead of reporting them.
+            if (hasReleaseSigningConfigs()) {
+                signingConfig signingConfigs.release
+            }
         }
     }
 
@@ -82,20 +136,4 @@ android {
             doNotStrip '**/*.so'
         }
     }
-
-    // Disable 'release' buildtype.
-    // The editor can't be used with target=release only, as debugging tools are then not
-    // included, and it would crash on errors instead of reporting them.
-    variantFilter { variant ->
-        if (variant.buildType.name == "release") {
-            setIgnore(true)
-        }
-    }
-
-    applicationVariants.all { variant ->
-        variant.outputs.all { output ->
-            def suffix = variant.name == "dev" ? "_dev" : ""
-            output.outputFileName = "android_editor${suffix}.apk"
-        }
-    }
 }

+ 4 - 0
platform/android/java/editor/src/debug/res/values/strings.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+	<string name="godot_editor_name_string">Godot Editor 4 (debug)</string>
+</resources>

+ 10 - 13
platform/android/java/lib/build.gradle

@@ -80,19 +80,11 @@ android {
         release.jniLibs.srcDirs = ['libs/release']
 
         // Editor jni library
+        editorRelease.jniLibs.srcDirs = ['libs/tools/release']
         editorDebug.jniLibs.srcDirs = ['libs/tools/debug']
         editorDev.jniLibs.srcDirs = ['libs/tools/dev']
     }
 
-    // Disable 'editorRelease'.
-    // The editor can't be used with target=release as debugging tools are then not
-    // included, and it would crash on errors instead of reporting them.
-    variantFilter { variant ->
-        if (variant.name == "editorRelease") {
-            setIgnore(true)
-        }
-    }
-
     libraryVariants.all { variant ->
         def flavorName = variant.getFlavorName()
         if (flavorName == null || flavorName == "") {
@@ -105,9 +97,14 @@ android {
         }
 
         boolean devBuild = buildType == "dev"
+        boolean runTests = devBuild
+        boolean productionBuild = !devBuild
+        boolean storeRelease = buildType == "release"
 
         def sconsTarget = flavorName
         if (sconsTarget == "template") {
+            // Tests are not supported on template builds
+            runTests = false
             switch (buildType) {
                 case "release":
                     sconsTarget += "_release"
@@ -135,10 +132,10 @@ android {
         def sconsExts = (org.gradle.internal.os.OperatingSystem.current().isWindows()
             ? [".bat", ".cmd", ".ps1", ".exe"]
             : [""])
-        logger.lifecycle("Looking for $sconsName executable path")
+        logger.debug("Looking for $sconsName executable path")
         for (ext in sconsExts) {
             String sconsNameExt = sconsName + ext
-            logger.lifecycle("Checking $sconsNameExt")
+            logger.debug("Checking $sconsNameExt")
             sconsExecutableFile = org.gradle.internal.os.OperatingSystem.current().findInPath(sconsNameExt)
             if (sconsExecutableFile != null) {
                 // We're done!
@@ -155,7 +152,7 @@ android {
         if (sconsExecutableFile == null) {
             throw new GradleException("Unable to find executable path for the '$sconsName' command.")
         } else {
-            logger.lifecycle("Found executable path for $sconsName: ${sconsExecutableFile.absolutePath}")
+            logger.debug("Found executable path for $sconsName: ${sconsExecutableFile.absolutePath}")
         }
 
         for (String selectedAbi : selectedAbis) {
@@ -167,7 +164,7 @@ android {
             def taskName = getSconsTaskName(flavorName, buildType, selectedAbi)
             tasks.create(name: taskName, type: Exec) {
                 executable sconsExecutableFile.absolutePath
-                args "--directory=${pathToRootDir}", "platform=android", "dev_mode=${devBuild}", "dev_build=${devBuild}", "target=${sconsTarget}", "arch=${selectedAbi}", "-j" + Runtime.runtime.availableProcessors()
+                args "--directory=${pathToRootDir}", "platform=android", "store_release=${storeRelease}", "production=${productionBuild}", "dev_mode=${devBuild}", "dev_build=${devBuild}", "tests=${runTests}", "target=${sconsTarget}", "arch=${selectedAbi}", "-j" + Runtime.runtime.availableProcessors()
             }
 
             // Schedule the tasks so the generated libs are present before the aar file is packaged.

+ 4 - 2
platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java

@@ -166,8 +166,10 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView
 
 	@Override
 	public void requestPointerCapture() {
-		super.requestPointerCapture();
-		inputHandler.onPointerCaptureChange(true);
+		if (canCapturePointer()) {
+			super.requestPointerCapture();
+			inputHandler.onPointerCaptureChange(true);
+		}
 	}
 
 	@Override

+ 4 - 0
platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java

@@ -51,4 +51,8 @@ public interface GodotRenderView {
 	void configurePointerIcon(int pointerType, String imagePath, float hotSpotX, float hotSpotY);
 
 	void setPointerIcon(int pointerType);
+
+	default boolean canCapturePointer() {
+		return getInputHandler().canCapturePointer();
+	}
 }

+ 4 - 2
platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java

@@ -134,8 +134,10 @@ public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderV
 
 	@Override
 	public void requestPointerCapture() {
-		super.requestPointerCapture();
-		mInputHandler.onPointerCaptureChange(true);
+		if (canCapturePointer()) {
+			super.requestPointerCapture();
+			mInputHandler.onPointerCaptureChange(true);
+		}
 	}
 
 	@Override

+ 13 - 12
platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt

@@ -227,35 +227,36 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi
 				)
 				dragInProgress = false
 			}
-			return true
 		}
 
-		dragInProgress = true
-
 		val x = terminusEvent.x
 		val y = terminusEvent.y
-		if (terminusEvent.pointerCount >= 2 && panningAndScalingEnabled && !pointerCaptureInProgress) {
+		if (terminusEvent.pointerCount >= 2 && panningAndScalingEnabled && !pointerCaptureInProgress && !dragInProgress) {
 			GodotLib.pan(x, y, distanceX / 5f, distanceY / 5f)
-		} else {
+		} else if (!scaleInProgress){
+			dragInProgress = true
 			GodotInputHandler.handleMotionEvent(terminusEvent)
 		}
 		return true
 	}
 
 	override fun onScale(detector: ScaleGestureDetector): Boolean {
-		if (!panningAndScalingEnabled || pointerCaptureInProgress) {
+		if (!panningAndScalingEnabled || pointerCaptureInProgress || dragInProgress) {
 			return false
 		}
-		GodotLib.magnify(
-			detector.focusX,
-			detector.focusY,
-			detector.scaleFactor
-		)
+
+		if (detector.scaleFactor >= 0.8f && detector.scaleFactor != 1f && detector.scaleFactor <= 1.2f) {
+			GodotLib.magnify(
+					detector.focusX,
+					detector.focusY,
+					detector.scaleFactor
+			)
+		}
 		return true
 	}
 
 	override fun onScaleBegin(detector: ScaleGestureDetector): Boolean {
-		if (!panningAndScalingEnabled || pointerCaptureInProgress) {
+		if (!panningAndScalingEnabled || pointerCaptureInProgress || dragInProgress) {
 			return false
 		}
 		scaleInProgress = true

+ 28 - 3
platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java

@@ -66,6 +66,11 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
 	private final ScaleGestureDetector scaleGestureDetector;
 	private final GodotGestureHandler godotGestureHandler;
 
+	/**
+	 * Used to decide whether mouse capture can be enabled.
+	 */
+	private int lastSeenToolType = MotionEvent.TOOL_TYPE_UNKNOWN;
+
 	public GodotInputHandler(GodotRenderView godotView) {
 		final Context context = godotView.getView().getContext();
 		mRenderView = godotView;
@@ -105,6 +110,10 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
 		return (source & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK || (source & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD || (source & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD;
 	}
 
+	public boolean canCapturePointer() {
+		return lastSeenToolType == MotionEvent.TOOL_TYPE_MOUSE;
+	}
+
 	public void onPointerCaptureChange(boolean hasCapture) {
 		godotGestureHandler.onPointerCaptureChange(hasCapture);
 	}
@@ -174,6 +183,8 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
 	}
 
 	public boolean onTouchEvent(final MotionEvent event) {
+		lastSeenToolType = event.getToolType(0);
+
 		this.scaleGestureDetector.onTouchEvent(event);
 		if (this.gestureDetector.onTouchEvent(event)) {
 			// The gesture detector has handled the event.
@@ -198,6 +209,8 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
 	}
 
 	public boolean onGenericMotionEvent(MotionEvent event) {
+		lastSeenToolType = event.getToolType(0);
+
 		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && gestureDetector.onGenericMotionEvent(event)) {
 			// The gesture detector has handled the event.
 			return true;
@@ -471,15 +484,27 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
 	}
 
 	static boolean handleMouseEvent(int eventAction, int buttonsMask, float x, float y, float deltaX, float deltaY, boolean doubleClick, boolean sourceMouseRelative) {
+		// Fix the buttonsMask
+		switch (eventAction) {
+			case MotionEvent.ACTION_CANCEL:
+			case MotionEvent.ACTION_UP:
+				// Zero-up the button state
+				buttonsMask = 0;
+				break;
+			case MotionEvent.ACTION_DOWN:
+			case MotionEvent.ACTION_MOVE:
+				if (buttonsMask == 0) {
+					buttonsMask = MotionEvent.BUTTON_PRIMARY;
+				}
+				break;
+		}
+
 		// We don't handle ACTION_BUTTON_PRESS and ACTION_BUTTON_RELEASE events as they typically
 		// follow ACTION_DOWN and ACTION_UP events. As such, handling them would result in duplicate
 		// stream of events to the engine.
 		switch (eventAction) {
 			case MotionEvent.ACTION_CANCEL:
 			case MotionEvent.ACTION_UP:
-				// Zero-up the button state
-				buttonsMask = 0;
-				// FALL THROUGH
 			case MotionEvent.ACTION_DOWN:
 			case MotionEvent.ACTION_HOVER_ENTER:
 			case MotionEvent.ACTION_HOVER_EXIT:

+ 12 - 1
platform/android/java_godot_view_wrapper.cpp

@@ -49,6 +49,8 @@ GodotJavaViewWrapper::GodotJavaViewWrapper(jobject godot_view) {
 		_request_pointer_capture = env->GetMethodID(_cls, "requestPointerCapture", "()V");
 		_release_pointer_capture = env->GetMethodID(_cls, "releasePointerCapture", "()V");
 	}
+
+	_can_capture_pointer = env->GetMethodID(_cls, "canCapturePointer", "()Z");
 }
 
 bool GodotJavaViewWrapper::can_update_pointer_icon() const {
@@ -56,7 +58,16 @@ bool GodotJavaViewWrapper::can_update_pointer_icon() const {
 }
 
 bool GodotJavaViewWrapper::can_capture_pointer() const {
-	return _request_pointer_capture != nullptr && _release_pointer_capture != nullptr;
+	// We can capture the pointer if the other jni capture method ids are initialized,
+	// and GodotView#canCapturePointer() returns true.
+	if (_request_pointer_capture != nullptr && _release_pointer_capture != nullptr && _can_capture_pointer != nullptr) {
+		JNIEnv *env = get_jni_env();
+		ERR_FAIL_NULL_V(env, false);
+
+		return env->CallBooleanMethod(_godot_view, _can_capture_pointer);
+	}
+
+	return false;
 }
 
 void GodotJavaViewWrapper::request_pointer_capture() {

+ 1 - 0
platform/android/java_godot_view_wrapper.h

@@ -44,6 +44,7 @@ private:
 
 	jobject _godot_view;
 
+	jmethodID _can_capture_pointer = 0;
 	jmethodID _request_pointer_capture = 0;
 	jmethodID _release_pointer_capture = 0;
 

+ 5 - 5
platform/android/os_android.cpp

@@ -182,7 +182,7 @@ String OS_Android::get_name() const {
 }
 
 String OS_Android::get_system_property(const char *key) const {
-	static String value;
+	String value;
 	char value_str[PROP_VALUE_MAX];
 	if (__system_property_get(key, value_str)) {
 		value = String(value_str);
@@ -230,20 +230,20 @@ String OS_Android::get_version() const {
 		"ro.potato.version", "ro.xtended.version", "org.evolution.version", "ro.corvus.version", "ro.pa.version",
 		"ro.crdroid.version", "ro.syberia.version", "ro.arrow.version", "ro.lineage.version" };
 	for (int i = 0; i < roms.size(); i++) {
-		static String rom_version = get_system_property(roms[i]);
+		String rom_version = get_system_property(roms[i]);
 		if (!rom_version.is_empty()) {
 			return rom_version;
 		}
 	}
 
-	static String mod_version = get_system_property("ro.modversion"); // Handles other Android custom ROMs.
+	String mod_version = get_system_property("ro.modversion"); // Handles other Android custom ROMs.
 	if (!mod_version.is_empty()) {
 		return mod_version;
 	}
 
 	// Handles stock Android.
-	static String sdk_version = get_system_property("ro.build.version.sdk_int");
-	static String build = get_system_property("ro.build.version.incremental");
+	String sdk_version = get_system_property("ro.build.version.sdk_int");
+	String build = get_system_property("ro.build.version.incremental");
 	if (!sdk_version.is_empty()) {
 		if (!build.is_empty()) {
 			return vformat("%s.%s", sdk_version, build);

+ 2 - 0
platform/linuxbsd/joypad_linux.cpp

@@ -74,6 +74,7 @@ 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() {
@@ -93,6 +94,7 @@ static bool detect_sandbox() {
 
 	return false;
 }
+#endif // UDEV_ENABLED
 
 JoypadLinux::JoypadLinux(Input *in) {
 #ifdef UDEV_ENABLED

+ 29 - 24
platform/linuxbsd/os_linuxbsd.cpp

@@ -210,48 +210,54 @@ String OS_LinuxBSD::get_name() const {
 }
 
 String OS_LinuxBSD::get_systemd_os_release_info_value(const String &key) const {
-	static String info;
-	if (info.is_empty()) {
-		Ref<FileAccess> f = FileAccess::open("/etc/os-release", FileAccess::READ);
-		if (f.is_valid()) {
-			while (!f->eof_reached()) {
-				const String line = f->get_line();
-				if (line.find(key) != -1) {
-					return line.split("=")[1].strip_edges();
-				}
+	Ref<FileAccess> f = FileAccess::open("/etc/os-release", FileAccess::READ);
+	if (f.is_valid()) {
+		while (!f->eof_reached()) {
+			const String line = f->get_line();
+			if (line.find(key) != -1) {
+				String value = line.split("=")[1].strip_edges();
+				value = value.trim_prefix("\"");
+				return value.trim_suffix("\"");
 			}
 		}
 	}
-	return info;
+	return "";
 }
 
 String OS_LinuxBSD::get_distribution_name() const {
-	static String systemd_name = get_systemd_os_release_info_value("NAME"); // returns a value for systemd users, otherwise an empty string.
-	if (!systemd_name.is_empty()) {
-		return systemd_name;
+	static String distribution_name = get_systemd_os_release_info_value("NAME"); // returns a value for systemd users, otherwise an empty string.
+	if (!distribution_name.is_empty()) {
+		return distribution_name;
 	}
 	struct utsname uts; // returns a decent value for BSD family.
 	uname(&uts);
-	return uts.sysname;
+	distribution_name = uts.sysname;
+	return distribution_name;
 }
 
 String OS_LinuxBSD::get_version() const {
-	static String systemd_version = get_systemd_os_release_info_value("VERSION"); // returns a value for systemd users, otherwise an empty string.
-	if (!systemd_version.is_empty()) {
-		return systemd_version;
+	static String release_version = get_systemd_os_release_info_value("VERSION"); // returns a value for systemd users, otherwise an empty string.
+	if (!release_version.is_empty()) {
+		return release_version;
 	}
 	struct utsname uts; // returns a decent value for BSD family.
 	uname(&uts);
-	return uts.version;
+	release_version = uts.version;
+	return release_version;
 }
 
 Vector<String> OS_LinuxBSD::get_video_adapter_driver_info() const {
-	if (RenderingServer::get_singleton()->get_rendering_device() == nullptr) {
+	if (RenderingServer::get_singleton() == nullptr) {
 		return Vector<String>();
 	}
 
-	const String rendering_device_name = RenderingServer::get_singleton()->get_rendering_device()->get_device_name(); // e.g. `NVIDIA GeForce GTX 970`
-	const String rendering_device_vendor = RenderingServer::get_singleton()->get_rendering_device()->get_device_vendor_name(); // e.g. `NVIDIA`
+	static Vector<String> info;
+	if (!info.is_empty()) {
+		return info;
+	}
+
+	const String rendering_device_name = RenderingServer::get_singleton()->get_video_adapter_name(); // e.g. `NVIDIA GeForce GTX 970`
+	const String rendering_device_vendor = RenderingServer::get_singleton()->get_video_adapter_vendor(); // e.g. `NVIDIA`
 	const String card_name = rendering_device_name.trim_prefix(rendering_device_vendor).strip_edges(); // -> `GeForce GTX 970`
 
 	String vendor_device_id_mappings;
@@ -315,8 +321,8 @@ Vector<String> OS_LinuxBSD::get_video_adapter_driver_info() const {
 	Vector<String> class_display_device_drivers = OS_LinuxBSD::lspci_get_device_value(class_display_device_candidates, kernel_lit, dummys);
 	Vector<String> class_3d_device_drivers = OS_LinuxBSD::lspci_get_device_value(class_3d_device_candidates, kernel_lit, dummys);
 
-	static String driver_name;
-	static String driver_version;
+	String driver_name;
+	String driver_version;
 
 	// Use first valid value:
 	for (const String &driver : class_3d_device_drivers) {
@@ -336,7 +342,6 @@ Vector<String> OS_LinuxBSD::get_video_adapter_driver_info() const {
 		}
 	}
 
-	Vector<String> info;
 	info.push_back(driver_name);
 
 	String modinfo;

+ 1 - 2
platform/uwp/os_uwp.cpp

@@ -450,8 +450,7 @@ String OS_UWP::get_distribution_name() const {
 
 String OS_UWP::get_version() const {
 	winrt::hstring df_version = VersionInfo().DeviceFamilyVersion();
-	static String version = String(winrt::to_string(df_version).c_str());
-	return version;
+	return String(winrt::to_string(df_version).c_str());
 }
 
 OS::DateTime OS_UWP::get_datetime(bool p_utc) const {

+ 4 - 0
platform/web/display_server_web.cpp

@@ -1070,6 +1070,10 @@ bool DisplayServerWeb::can_any_window_draw() const {
 	return true;
 }
 
+DisplayServer::VSyncMode DisplayServerWeb::window_get_vsync_mode(WindowID p_vsync_mode) const {
+	return DisplayServer::VSYNC_ENABLED;
+}
+
 void DisplayServerWeb::process_events() {
 	Input::get_singleton()->flush_buffered_events();
 	if (godot_js_input_gamepad_sample() == OK) {

+ 2 - 0
platform/web/display_server_web.h

@@ -213,6 +213,8 @@ public:
 
 	virtual bool can_any_window_draw() const override;
 
+	virtual DisplayServer::VSyncMode window_get_vsync_mode(WindowID p_vsync_mode) const override;
+
 	// events
 	virtual void process_events() override;
 

+ 9 - 5
platform/windows/os_windows.cpp

@@ -352,20 +352,25 @@ String OS_Windows::get_version() const {
 }
 
 Vector<String> OS_Windows::get_video_adapter_driver_info() const {
-	if (RenderingServer::get_singleton()->get_rendering_device() == nullptr) {
+	if (RenderingServer::get_singleton() == nullptr) {
 		return Vector<String>();
 	}
 
+	static Vector<String> info;
+	if (!info.is_empty()) {
+		return info;
+	}
+
 	REFCLSID clsid = CLSID_WbemLocator; // Unmarshaler CLSID
 	REFIID uuid = IID_IWbemLocator; // Interface UUID
 	IWbemLocator *wbemLocator = NULL; // to get the services
 	IWbemServices *wbemServices = NULL; // to get the class
 	IEnumWbemClassObject *iter = NULL;
 	IWbemClassObject *pnpSDriverObject[1]; // contains driver name, version, etc.
-	static String driver_name;
-	static String driver_version;
+	String driver_name;
+	String driver_version;
 
-	const String device_name = RenderingServer::get_singleton()->get_rendering_device()->get_device_name();
+	const String device_name = RenderingServer::get_singleton()->get_video_adapter_name();
 	if (device_name.is_empty()) {
 		return Vector<String>();
 	}
@@ -439,7 +444,6 @@ Vector<String> OS_Windows::get_video_adapter_driver_info() const {
 	SAFE_RELEASE(wbemServices)
 	SAFE_RELEASE(iter)
 
-	Vector<String> info;
 	info.push_back(driver_name);
 	info.push_back(driver_version);
 

+ 5 - 4
scene/2d/audio_stream_player_2d.cpp

@@ -157,7 +157,6 @@ void AudioStreamPlayer2D::_update_panning() {
 	Vector2 global_pos = get_global_position();
 
 	HashSet<Viewport *> viewports = world_2d->get_viewports();
-	viewports.insert(get_viewport()); // TODO: This is a mediocre workaround for #50958. Remove when that bug is fixed!
 
 	volume_vector.resize(4);
 	volume_vector.write[0] = AudioFrame(0, 0);
@@ -188,11 +187,11 @@ void AudioStreamPlayer2D::_update_panning() {
 		float dist = global_pos.distance_to(listener_in_global); // Distance to listener, or screen if none.
 
 		if (dist > max_distance) {
-			continue; //can't hear this sound in this viewport
+			continue; // Can't hear this sound in this viewport.
 		}
 
 		float multiplier = Math::pow(1.0f - dist / max_distance, attenuation);
-		multiplier *= Math::db_to_linear(volume_db); //also apply player volume!
+		multiplier *= Math::db_to_linear(volume_db); // Also apply player volume!
 
 		float pan = relative_to_listener.x / screen_size.x;
 		// Don't let the panning effect extend (too far) beyond the screen.
@@ -206,7 +205,9 @@ void AudioStreamPlayer2D::_update_panning() {
 		float l = 1.0 - pan;
 		float r = pan;
 
-		volume_vector.write[0] = AudioFrame(l, r) * multiplier;
+		const AudioFrame &prev_sample = volume_vector[0];
+		AudioFrame new_sample = AudioFrame(l, r) * multiplier;
+		volume_vector.write[0] = AudioFrame(MAX(prev_sample[0], new_sample[0]), MAX(prev_sample[1], new_sample[1]));
 	}
 
 	for (const Ref<AudioStreamPlayback> &playback : stream_playbacks) {

+ 13 - 16
scene/3d/skeleton_3d.cpp

@@ -378,15 +378,12 @@ uint64_t Skeleton3D::get_version() const {
 }
 
 void Skeleton3D::add_bone(const String &p_name) {
-	ERR_FAIL_COND(p_name.is_empty() || p_name.contains(":") || p_name.contains("/"));
-
-	for (int i = 0; i < bones.size(); i++) {
-		ERR_FAIL_COND(bones[i].name == p_name);
-	}
+	ERR_FAIL_COND(p_name.is_empty() || p_name.contains(":") || p_name.contains("/") || name_to_bone_index.has(p_name));
 
 	Bone b;
 	b.name = p_name;
 	bones.push_back(b);
+	name_to_bone_index.insert(p_name, bones.size() - 1);
 	process_order_dirty = true;
 	version++;
 	rest_dirty = true;
@@ -395,13 +392,8 @@ void Skeleton3D::add_bone(const String &p_name) {
 }
 
 int Skeleton3D::find_bone(const String &p_name) const {
-	for (int i = 0; i < bones.size(); i++) {
-		if (bones[i].name == p_name) {
-			return i;
-		}
-	}
-
-	return -1;
+	const int *bone_index_ptr = name_to_bone_index.getptr(p_name);
+	return bone_index_ptr != nullptr ? *bone_index_ptr : -1;
 }
 
 String Skeleton3D::get_bone_name(int p_bone) const {
@@ -409,17 +401,21 @@ String Skeleton3D::get_bone_name(int p_bone) const {
 	ERR_FAIL_INDEX_V(p_bone, bone_size, "");
 	return bones[p_bone].name;
 }
+
 void Skeleton3D::set_bone_name(int p_bone, const String &p_name) {
 	const int bone_size = bones.size();
 	ERR_FAIL_INDEX(p_bone, bone_size);
 
-	for (int i = 0; i < bone_size; i++) {
-		if (i != p_bone) {
-			ERR_FAIL_COND_MSG(bones[i].name == p_name, "Skeleton3D: '" + get_name() + "', bone name:  '" + p_name + "' is already exist.");
-		}
+	const int *bone_index_ptr = name_to_bone_index.getptr(p_name);
+	if (bone_index_ptr != nullptr) {
+		ERR_FAIL_COND_MSG(*bone_index_ptr != p_bone, "Skeleton3D: '" + get_name() + "', bone name:  '" + p_name + "' already exists.");
+		return; // No need to rename, the bone already has the given name.
 	}
 
+	name_to_bone_index.erase(bones[p_bone].name);
 	bones.write[p_bone].name = p_name;
+	name_to_bone_index.insert(p_name, p_bone);
+
 	version++;
 }
 
@@ -547,6 +543,7 @@ bool Skeleton3D::is_show_rest_only() const {
 
 void Skeleton3D::clear_bones() {
 	bones.clear();
+	name_to_bone_index.clear();
 	process_order_dirty = true;
 	version++;
 	_make_dirty();

+ 1 - 0
scene/3d/skeleton_3d.h

@@ -125,6 +125,7 @@ private:
 	bool process_order_dirty = false;
 
 	Vector<int> parentless_bones;
+	HashMap<String, int> name_to_bone_index;
 
 	void _make_dirty();
 	bool dirty = false;

+ 2 - 2
scene/animation/animation_player.cpp

@@ -1766,7 +1766,7 @@ void AnimationPlayer::set_current_animation(const String &p_anim) {
 	} else if (!is_playing()) {
 		play(p_anim);
 	} else if (playback.assigned != p_anim) {
-		float speed = get_playing_speed();
+		float speed = playback.current.speed_scale;
 		play(p_anim, -1.0, speed, signbit(speed));
 	} else {
 		// Same animation, do not replay from start
@@ -1779,7 +1779,7 @@ String AnimationPlayer::get_current_animation() const {
 
 void AnimationPlayer::set_assigned_animation(const String &p_anim) {
 	if (is_playing()) {
-		float speed = get_playing_speed();
+		float speed = playback.current.speed_scale;
 		play(p_anim, -1.0, speed, signbit(speed));
 	} else {
 		ERR_FAIL_COND_MSG(!animation_set.has(p_anim), vformat("Animation not found: %s.", p_anim));

+ 7 - 6
scene/gui/color_picker.cpp

@@ -561,13 +561,14 @@ void ColorPicker::_update_presets() {
 
 #ifdef TOOLS_ENABLED
 	if (editor_settings) {
-		// Only load preset buttons when the only child is the add-preset button.
-		if (preset_container->get_child_count() == 1) {
-			for (int i = 0; i < preset_cache.size(); i++) {
-				_add_preset_button(preset_size, preset_cache[i]);
-			}
-			_notification(NOTIFICATION_VISIBILITY_CHANGED);
+		// Rebuild swatch color buttons, keeping the add-preset button in the first position.
+		for (int i = 1; i < preset_container->get_child_count(); i++) {
+			preset_container->get_child(i)->queue_free();
 		}
+		for (int i = 0; i < preset_cache.size(); i++) {
+			_add_preset_button(preset_size, preset_cache[i]);
+		}
+		_notification(NOTIFICATION_VISIBILITY_CHANGED);
 	}
 #endif
 }

+ 1 - 1
scene/gui/dialogs.cpp

@@ -39,7 +39,7 @@
 
 void AcceptDialog::_input_from_window(const Ref<InputEvent> &p_event) {
 	Ref<InputEventKey> key = p_event;
-	if (close_on_escape && key.is_valid() && key->is_pressed() && key->get_keycode() == Key::ESCAPE) {
+	if (close_on_escape && key.is_valid() && key->is_action_pressed(SNAME("ui_cancel"), false, true)) {
 		_cancel_pressed();
 	}
 }

+ 4 - 3
scene/gui/item_list.cpp

@@ -1366,12 +1366,13 @@ void ItemList::_notification(int p_what) {
 						text_ofs += base_ofs;
 						text_ofs += items[i].rect_cache.position;
 
+						float text_w = width - text_ofs.x;
+						items.write[i].text_buf->set_width(text_w);
+
 						if (rtl) {
-							text_ofs.x = size.width - text_ofs.x - max_len;
+							text_ofs.x = size.width - width;
 						}
 
-						items.write[i].text_buf->set_width(width - text_ofs.x);
-
 						if (rtl) {
 							items.write[i].text_buf->set_alignment(HORIZONTAL_ALIGNMENT_RIGHT);
 						} else {

+ 1 - 1
scene/gui/popup.cpp

@@ -36,7 +36,7 @@
 
 void Popup::_input_from_window(const Ref<InputEvent> &p_event) {
 	Ref<InputEventKey> key = p_event;
-	if (get_flag(FLAG_POPUP) && key.is_valid() && key->is_pressed() && key->get_keycode() == Key::ESCAPE) {
+	if (get_flag(FLAG_POPUP) && key.is_valid() && key->is_action_pressed(SNAME("ui_cancel"), false, true)) {
 		_close_pressed();
 	}
 }

+ 9 - 1
scene/main/viewport.cpp

@@ -177,7 +177,7 @@ void ViewportTexture::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_viewport_path_in_scene", "path"), &ViewportTexture::set_viewport_path_in_scene);
 	ClassDB::bind_method(D_METHOD("get_viewport_path_in_scene"), &ViewportTexture::get_viewport_path_in_scene);
 
-	ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "viewport_path", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "SubViewport", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NODE_PATH_FROM_SCENE_ROOT), "set_viewport_path_in_scene", "get_viewport_path_in_scene");
+	ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "viewport_path", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Viewport", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NODE_PATH_FROM_SCENE_ROOT), "set_viewport_path_in_scene", "get_viewport_path_in_scene");
 }
 
 ViewportTexture::ViewportTexture() {
@@ -544,6 +544,7 @@ void Viewport::_notification(int p_what) {
 			gui.mouse_in_viewport = false;
 			_drop_physics_mouseover();
 			_drop_mouse_over();
+			_gui_cancel_tooltip();
 			// When the mouse exits the viewport, we want to end mouse_over, but
 			// not mouse_focus, because, for example, we want to continue
 			// dragging a scrollbar even if the mouse has left the viewport.
@@ -1080,6 +1081,10 @@ void Viewport::set_world_2d(const Ref<World2D> &p_world_2d) {
 		RenderingServer::get_singleton()->viewport_remove_canvas(viewport, current_canvas);
 	}
 
+	if (world_2d.is_valid()) {
+		world_2d->remove_viewport(this);
+	}
+
 	if (p_world_2d.is_valid()) {
 		world_2d = p_world_2d;
 	} else {
@@ -1087,6 +1092,7 @@ void Viewport::set_world_2d(const Ref<World2D> &p_world_2d) {
 		world_2d = Ref<World2D>(memnew(World2D));
 	}
 
+	world_2d->register_viewport(this);
 	_update_audio_listener_2d();
 
 	if (is_inside_tree()) {
@@ -2828,6 +2834,7 @@ bool Viewport::_sub_windows_forward_input(const Ref<InputEvent> &p_event) {
 
 void Viewport::push_input(const Ref<InputEvent> &p_event, bool p_local_coords) {
 	ERR_FAIL_COND(!is_inside_tree());
+	ERR_FAIL_COND(p_event.is_null());
 
 	if (disable_input) {
 		return;
@@ -4155,6 +4162,7 @@ void Viewport::_validate_property(PropertyInfo &p_property) const {
 
 Viewport::Viewport() {
 	world_2d = Ref<World2D>(memnew(World2D));
+	world_2d->register_viewport(this);
 
 	viewport = RenderingServer::get_singleton()->viewport_create();
 	texture_rid = RenderingServer::get_singleton()->viewport_get_texture(viewport);

+ 2 - 2
scene/register_scene_types.cpp

@@ -695,6 +695,8 @@ void register_scene_types() {
 	GDREGISTER_CLASS(VisualShaderNodeParticleAccelerator);
 	GDREGISTER_CLASS(VisualShaderNodeParticleEmit);
 
+	GDREGISTER_VIRTUAL_CLASS(Material);
+	GDREGISTER_CLASS(PlaceholderMaterial);
 	GDREGISTER_CLASS(ShaderMaterial);
 	GDREGISTER_ABSTRACT_CLASS(CanvasItem);
 	GDREGISTER_CLASS(CanvasTexture);
@@ -802,11 +804,9 @@ void register_scene_types() {
 	GDREGISTER_CLASS(TubeTrailMesh);
 	GDREGISTER_CLASS(RibbonTrailMesh);
 	GDREGISTER_CLASS(PointMesh);
-	GDREGISTER_VIRTUAL_CLASS(Material);
 	GDREGISTER_ABSTRACT_CLASS(BaseMaterial3D);
 	GDREGISTER_CLASS(StandardMaterial3D);
 	GDREGISTER_CLASS(ORMMaterial3D);
-	GDREGISTER_CLASS(PlaceholderMaterial);
 	GDREGISTER_CLASS(ProceduralSkyMaterial);
 	GDREGISTER_CLASS(PanoramaSkyMaterial);
 	GDREGISTER_CLASS(PhysicalSkyMaterial);

+ 8 - 0
scene/resources/world_2d.cpp

@@ -82,6 +82,14 @@ PhysicsDirectSpaceState2D *World2D::get_direct_space_state() {
 	return PhysicsServer2D::get_singleton()->space_get_direct_state(get_space());
 }
 
+void World2D::register_viewport(Viewport *p_viewport) {
+	viewports.insert(p_viewport);
+}
+
+void World2D::remove_viewport(Viewport *p_viewport) {
+	viewports.erase(p_viewport);
+}
+
 World2D::World2D() {
 	canvas = RenderingServer::get_singleton()->canvas_create();
 }

+ 3 - 3
scene/resources/world_2d.h

@@ -52,9 +52,6 @@ protected:
 	static void _bind_methods();
 	friend class Viewport;
 
-	void _register_viewport(Viewport *p_viewport);
-	void _remove_viewport(Viewport *p_viewport);
-
 public:
 	RID get_canvas() const;
 	RID get_space() const;
@@ -62,6 +59,9 @@ public:
 
 	PhysicsDirectSpaceState2D *get_direct_space_state();
 
+	void register_viewport(Viewport *p_viewport);
+	void remove_viewport(Viewport *p_viewport);
+
 	_FORCE_INLINE_ const HashSet<Viewport *> &get_viewports() { return viewports; }
 
 	World2D();

+ 7 - 5
servers/rendering/renderer_canvas_cull.cpp

@@ -76,7 +76,7 @@ void RendererCanvasCull::_render_canvas_item_tree(RID p_to_render_target, Canvas
 	}
 }
 
-void _collect_ysort_children(RendererCanvasCull::Item *p_canvas_item, Transform2D p_transform, RendererCanvasCull::Item *p_material_owner, RendererCanvasCull::Item **r_items, int &r_index, int p_z) {
+void _collect_ysort_children(RendererCanvasCull::Item *p_canvas_item, Transform2D p_transform, RendererCanvasCull::Item *p_material_owner, const Color &p_modulate, RendererCanvasCull::Item **r_items, int &r_index, int p_z) {
 	int child_item_count = p_canvas_item->child_items.size();
 	RendererCanvasCull::Item **child_items = p_canvas_item->child_items.ptrw();
 	for (int i = 0; i < child_item_count; i++) {
@@ -87,6 +87,7 @@ void _collect_ysort_children(RendererCanvasCull::Item *p_canvas_item, Transform2
 				child_items[i]->ysort_xform = p_transform;
 				child_items[i]->ysort_pos = p_transform.xform(child_items[i]->xform.columns[2]);
 				child_items[i]->material_owner = child_items[i]->use_parent_material ? p_material_owner : nullptr;
+				child_items[i]->ysort_modulate = p_modulate;
 				child_items[i]->ysort_index = r_index;
 				child_items[i]->ysort_parent_abs_z_index = p_z;
 
@@ -101,7 +102,7 @@ void _collect_ysort_children(RendererCanvasCull::Item *p_canvas_item, Transform2
 			r_index++;
 
 			if (child_items[i]->sort_y) {
-				_collect_ysort_children(child_items[i], p_transform * child_items[i]->xform, child_items[i]->use_parent_material ? p_material_owner : child_items[i], r_items, r_index, abs_z);
+				_collect_ysort_children(child_items[i], p_transform * child_items[i]->xform, child_items[i]->use_parent_material ? p_material_owner : child_items[i], p_modulate * child_items[i]->modulate, r_items, r_index, abs_z);
 			}
 		}
 	}
@@ -301,7 +302,7 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
 		if (allow_y_sort) {
 			if (ci->ysort_children_count == -1) {
 				ci->ysort_children_count = 0;
-				_collect_ysort_children(ci, Transform2D(), p_material_owner, nullptr, ci->ysort_children_count, p_z);
+				_collect_ysort_children(ci, Transform2D(), p_material_owner, Color(1, 1, 1, 1), nullptr, ci->ysort_children_count, p_z);
 			}
 
 			child_item_count = ci->ysort_children_count + 1;
@@ -310,14 +311,15 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
 			ci->ysort_parent_abs_z_index = parent_z;
 			child_items[0] = ci;
 			int i = 1;
-			_collect_ysort_children(ci, Transform2D(), p_material_owner, child_items, i, p_z);
+			_collect_ysort_children(ci, Transform2D(), p_material_owner, Color(1, 1, 1, 1), child_items, i, p_z);
 			ci->ysort_xform = ci->xform.affine_inverse();
+			ci->ysort_modulate = Color(1, 1, 1, 1);
 
 			SortArray<Item *, ItemPtrSort> sorter;
 			sorter.sort(child_items, child_item_count);
 
 			for (i = 0; i < child_item_count; i++) {
-				_cull_canvas_item(child_items[i], xform * child_items[i]->ysort_xform, p_clip_rect, modulate, child_items[i]->ysort_parent_abs_z_index, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, (Item *)child_items[i]->material_owner, false, canvas_cull_mask);
+				_cull_canvas_item(child_items[i], xform * child_items[i]->ysort_xform, p_clip_rect, modulate * child_items[i]->ysort_modulate, child_items[i]->ysort_parent_abs_z_index, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, (Item *)child_items[i]->material_owner, false, canvas_cull_mask);
 			}
 		} else {
 			RendererCanvasRender::Item *canvas_group_from = nullptr;

+ 15 - 5
servers/rendering/renderer_rd/environment/fog.cpp

@@ -312,11 +312,21 @@ ALBEDO = vec3(1.0);
 void Fog::free_fog_shader() {
 	MaterialStorage *material_storage = MaterialStorage::get_singleton();
 
-	volumetric_fog.process_shader.version_free(volumetric_fog.process_shader_version);
-	RD::get_singleton()->free(volumetric_fog.volume_ubo);
-	RD::get_singleton()->free(volumetric_fog.params_ubo);
-	material_storage->shader_free(volumetric_fog.default_shader);
-	material_storage->material_free(volumetric_fog.default_material);
+	if (volumetric_fog.process_shader_version.is_valid()) {
+		volumetric_fog.process_shader.version_free(volumetric_fog.process_shader_version);
+	}
+	if (volumetric_fog.volume_ubo.is_valid()) {
+		RD::get_singleton()->free(volumetric_fog.volume_ubo);
+	}
+	if (volumetric_fog.params_ubo.is_valid()) {
+		RD::get_singleton()->free(volumetric_fog.params_ubo);
+	}
+	if (volumetric_fog.default_shader.is_valid()) {
+		material_storage->shader_free(volumetric_fog.default_shader);
+	}
+	if (volumetric_fog.default_material.is_valid()) {
+		material_storage->material_free(volumetric_fog.default_material);
+	}
 }
 
 void Fog::FogShaderData::set_code(const String &p_code) {

+ 34 - 12
servers/rendering/renderer_rd/environment/gi.cpp

@@ -3581,18 +3581,40 @@ void GI::init(SkyRD *p_sky) {
 }
 
 void GI::free() {
-	RD::get_singleton()->free(default_voxel_gi_buffer);
-	RD::get_singleton()->free(voxel_gi_lights_uniform);
-	RD::get_singleton()->free(sdfgi_ubo);
-
-	voxel_gi_debug_shader.version_free(voxel_gi_debug_shader_version);
-	voxel_gi_shader.version_free(voxel_gi_lighting_shader_version);
-	shader.version_free(shader_version);
-	sdfgi_shader.debug_probes.version_free(sdfgi_shader.debug_probes_shader);
-	sdfgi_shader.debug.version_free(sdfgi_shader.debug_shader);
-	sdfgi_shader.direct_light.version_free(sdfgi_shader.direct_light_shader);
-	sdfgi_shader.integrate.version_free(sdfgi_shader.integrate_shader);
-	sdfgi_shader.preprocess.version_free(sdfgi_shader.preprocess_shader);
+	if (default_voxel_gi_buffer.is_valid()) {
+		RD::get_singleton()->free(default_voxel_gi_buffer);
+	}
+	if (voxel_gi_lights_uniform.is_valid()) {
+		RD::get_singleton()->free(voxel_gi_lights_uniform);
+	}
+	if (sdfgi_ubo.is_valid()) {
+		RD::get_singleton()->free(sdfgi_ubo);
+	}
+
+	if (voxel_gi_debug_shader_version.is_valid()) {
+		voxel_gi_debug_shader.version_free(voxel_gi_debug_shader_version);
+	}
+	if (voxel_gi_lighting_shader_version.is_valid()) {
+		voxel_gi_shader.version_free(voxel_gi_lighting_shader_version);
+	}
+	if (shader_version.is_valid()) {
+		shader.version_free(shader_version);
+	}
+	if (sdfgi_shader.debug_probes_shader.is_valid()) {
+		sdfgi_shader.debug_probes.version_free(sdfgi_shader.debug_probes_shader);
+	}
+	if (sdfgi_shader.debug_shader.is_valid()) {
+		sdfgi_shader.debug.version_free(sdfgi_shader.debug_shader);
+	}
+	if (sdfgi_shader.direct_light_shader.is_valid()) {
+		sdfgi_shader.direct_light.version_free(sdfgi_shader.direct_light_shader);
+	}
+	if (sdfgi_shader.integrate_shader.is_valid()) {
+		sdfgi_shader.integrate.version_free(sdfgi_shader.integrate_shader);
+	}
+	if (sdfgi_shader.preprocess_shader.is_valid()) {
+		sdfgi_shader.preprocess.version_free(sdfgi_shader.preprocess_shader);
+	}
 
 	if (voxel_gi_lights) {
 		memdelete_arr(voxel_gi_lights);

+ 6 - 1
servers/rendering/renderer_rd/renderer_scene_render_rd.cpp

@@ -964,7 +964,12 @@ void RendererSceneRenderRD::render_scene(const Ref<RenderSceneBuffers> &p_render
 		scene_data.z_far = p_camera_data->main_projection.get_z_far();
 
 		// this should be the same for all cameras..
-		scene_data.lod_distance_multiplier = p_camera_data->main_projection.get_lod_multiplier();
+		const float lod_distance_multiplier = p_camera_data->main_projection.get_lod_multiplier();
+
+		// Also, take into account resolution scaling for the multiplier, since we have more leeway with quality
+		// degradation visibility. Conversely, allow upwards scaling, too, for increased mesh detail at high res.
+		const float scaling_3d_scale = GLOBAL_GET("rendering/scaling_3d/scale");
+		scene_data.lod_distance_multiplier = lod_distance_multiplier * (1.0 / scaling_3d_scale);
 
 		if (get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_DISABLE_LOD) {
 			scene_data.screen_mesh_lod_threshold = 0.0;

+ 2 - 2
servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp

@@ -627,7 +627,7 @@ AABB MeshStorage::mesh_get_aabb(RID p_mesh, RID p_skeleton) {
 
 			if (skeleton->use_2d) {
 				for (int j = 0; j < bs; j++) {
-					if (skbones[0].size == Vector3()) {
+					if (skbones[j].size == Vector3(-1, -1, -1)) {
 						continue; //bone is unused
 					}
 
@@ -654,7 +654,7 @@ AABB MeshStorage::mesh_get_aabb(RID p_mesh, RID p_skeleton) {
 				}
 			} else {
 				for (int j = 0; j < bs; j++) {
-					if (skbones[0].size == Vector3()) {
+					if (skbones[j].size == Vector3(-1, -1, -1)) {
 						continue; //bone is unused
 					}
 

+ 2 - 2
servers/rendering/renderer_rd/storage_rd/texture_storage.cpp

@@ -1153,8 +1153,8 @@ void TextureStorage::texture_3d_update(RID p_texture, const Vector<Ref<Image>> &
 				image = image->duplicate();
 				image->convert(tex->validated_format);
 			}
-			all_data_size += images[i]->get_data().size();
-			images.push_back(image);
+			all_data_size += image->get_data().size();
+			images.write[i] = image;
 		}
 
 		all_data.resize(all_data_size); //consolidate all data here

+ 0 - 1
servers/rendering/renderer_scene_cull.cpp

@@ -34,7 +34,6 @@
 #include "core/object/worker_thread_pool.h"
 #include "core/os/os.h"
 #include "rendering_server_default.h"
-#include "rendering_server_globals.h"
 
 #include <new>
 

+ 24 - 0
servers/rendering/renderer_scene_cull.h

@@ -42,6 +42,7 @@
 #include "servers/rendering/renderer_scene_occlusion_cull.h"
 #include "servers/rendering/renderer_scene_render.h"
 #include "servers/rendering/rendering_method.h"
+#include "servers/rendering/rendering_server_globals.h"
 #include "servers/rendering/storage/utilities.h"
 #include "servers/xr/xr_interface.h"
 
@@ -516,6 +517,29 @@ public:
 			} else if (p_dependency == instance->skeleton) {
 				singleton->instance_attach_skeleton(instance->self, RID());
 			} else {
+				// It's possible the same material is used in multiple slots,
+				// so we check whether we need to clear them all.
+				if (p_dependency == instance->material_override) {
+					singleton->instance_geometry_set_material_override(instance->self, RID());
+				}
+				if (p_dependency == instance->material_overlay) {
+					singleton->instance_geometry_set_material_overlay(instance->self, RID());
+				}
+				for (int i = 0; i < instance->materials.size(); i++) {
+					if (p_dependency == instance->materials[i]) {
+						singleton->instance_set_surface_override_material(instance->self, i, RID());
+					}
+				}
+				if (instance->base_type == RS::INSTANCE_PARTICLES) {
+					RID particle_material = RSG::particles_storage->particles_get_process_material(instance->base);
+					if (p_dependency == particle_material) {
+						RSG::particles_storage->particles_set_process_material(instance->base, RID());
+					}
+				}
+
+				// Even if no change is made we still need to call `_instance_queue_update`.
+				// This dependency could also be a result of the freed material being used
+				// by the mesh this mesh instance uses.
 				singleton->_instance_queue_update(instance, false, true);
 			}
 		}

+ 42 - 16
servers/text_server.cpp

@@ -693,6 +693,7 @@ PackedInt32Array TextServer::shaped_text_get_line_breaks_adv(const RID &p_shaped
 
 	real_t width = 0.f;
 	int line_start = MAX(p_start, range.x);
+	int last_end = line_start;
 	int prev_safe_break = 0;
 	int last_safe_break = -1;
 	int word_count = 0;
@@ -718,12 +719,18 @@ PackedInt32Array TextServer::shaped_text_get_line_breaks_adv(const RID &p_shaped
 					while ((start_pos < end_pos) && ((l_gl[end_pos].flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE || (l_gl[end_pos].flags & GRAPHEME_IS_BREAK_HARD) == GRAPHEME_IS_BREAK_HARD || (l_gl[end_pos].flags & GRAPHEME_IS_BREAK_SOFT) == GRAPHEME_IS_BREAK_SOFT)) {
 						end_pos -= l_gl[end_pos].count;
 					}
-					lines.push_back(l_gl[start_pos].start);
-					lines.push_back(l_gl[end_pos].end);
+					if (last_end <= l_gl[start_pos].start) {
+						lines.push_back(l_gl[start_pos].start);
+						lines.push_back(l_gl[end_pos].end);
+						last_end = l_gl[end_pos].end;
+					}
 					trim_next = true;
 				} else {
-					lines.push_back(line_start);
-					lines.push_back(l_gl[last_safe_break].end);
+					if (last_end <= line_start) {
+						lines.push_back(line_start);
+						lines.push_back(l_gl[last_safe_break].end);
+						last_end = l_gl[last_safe_break].end;
+					}
 				}
 				line_start = l_gl[last_safe_break].end;
 				prev_safe_break = last_safe_break + 1;
@@ -751,12 +758,18 @@ PackedInt32Array TextServer::shaped_text_get_line_breaks_adv(const RID &p_shaped
 						while ((start_pos < end_pos) && ((l_gl[end_pos].flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE || (l_gl[end_pos].flags & GRAPHEME_IS_BREAK_HARD) == GRAPHEME_IS_BREAK_HARD || (l_gl[end_pos].flags & GRAPHEME_IS_BREAK_SOFT) == GRAPHEME_IS_BREAK_SOFT)) {
 							end_pos -= l_gl[end_pos].count;
 						}
-						lines.push_back(l_gl[start_pos].start);
-						lines.push_back(l_gl[end_pos].end);
+						if (last_end <= l_gl[start_pos].start) {
+							lines.push_back(l_gl[start_pos].start);
+							lines.push_back(l_gl[end_pos].end);
+							last_end = l_gl[end_pos].end;
+						}
 						trim_next = false;
 					} else {
-						lines.push_back(line_start);
-						lines.push_back(l_gl[i].end);
+						if (last_end <= line_start) {
+							lines.push_back(line_start);
+							lines.push_back(l_gl[i].end);
+							last_end = l_gl[i].end;
+						}
 					}
 					line_start = l_gl[i].end;
 					prev_safe_break = i + 1;
@@ -812,6 +825,7 @@ PackedInt32Array TextServer::shaped_text_get_line_breaks(const RID &p_shaped, do
 
 	double width = 0.f;
 	int line_start = MAX(p_start, range.x);
+	int last_end = line_start;
 	int prev_safe_break = 0;
 	int last_safe_break = -1;
 	int word_count = 0;
@@ -836,12 +850,18 @@ PackedInt32Array TextServer::shaped_text_get_line_breaks(const RID &p_shaped, do
 					while ((start_pos < end_pos) && ((l_gl[end_pos].flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE || (l_gl[end_pos].flags & GRAPHEME_IS_BREAK_HARD) == GRAPHEME_IS_BREAK_HARD || (l_gl[end_pos].flags & GRAPHEME_IS_BREAK_SOFT) == GRAPHEME_IS_BREAK_SOFT)) {
 						end_pos -= l_gl[end_pos].count;
 					}
-					lines.push_back(l_gl[start_pos].start);
-					lines.push_back(l_gl[end_pos].end);
+					if (last_end <= l_gl[start_pos].start) {
+						lines.push_back(l_gl[start_pos].start);
+						lines.push_back(l_gl[end_pos].end);
+						last_end = l_gl[end_pos].end;
+					}
 					trim_next = true;
 				} else {
-					lines.push_back(line_start);
-					lines.push_back(l_gl[last_safe_break].end);
+					if (last_end <= line_start) {
+						lines.push_back(line_start);
+						lines.push_back(l_gl[last_safe_break].end);
+						last_end = l_gl[last_safe_break].end;
+					}
 				}
 				line_start = l_gl[last_safe_break].end;
 				prev_safe_break = last_safe_break + 1;
@@ -863,11 +883,17 @@ PackedInt32Array TextServer::shaped_text_get_line_breaks(const RID &p_shaped, do
 							end_pos -= l_gl[end_pos].count;
 						}
 						trim_next = false;
-						lines.push_back(l_gl[start_pos].start);
-						lines.push_back(l_gl[end_pos].end);
+						if (last_end <= l_gl[start_pos].start) {
+							lines.push_back(l_gl[start_pos].start);
+							lines.push_back(l_gl[end_pos].end);
+							last_end = l_gl[end_pos].end;
+						}
 					} else {
-						lines.push_back(line_start);
-						lines.push_back(l_gl[i].end);
+						if (last_end <= line_start) {
+							lines.push_back(line_start);
+							lines.push_back(l_gl[i].end);
+							last_end = l_gl[i].end;
+						}
 					}
 					line_start = l_gl[i].end;
 					prev_safe_break = i + 1;

+ 1 - 0
thirdparty/glslang/glslang/Include/Common.h

@@ -45,6 +45,7 @@
 #include <cmath>
 #endif
 #include <cstdio>
+#include <cstdint>
 #include <cstdlib>
 #include <list>
 #include <map>

+ 12 - 0
thirdparty/openxr/patches/fix-gcc13-stdint.patch

@@ -0,0 +1,12 @@
+diff --git a/thirdparty/openxr/src/common/platform_utils.hpp b/thirdparty/openxr/src/common/platform_utils.hpp
+index 85d5cdab10..2d870cfea7 100644
+--- a/thirdparty/openxr/src/common/platform_utils.hpp
++++ b/thirdparty/openxr/src/common/platform_utils.hpp
+@@ -11,6 +11,7 @@
+ 
+ #include "xr_dependencies.h"
+ #include <string>
++#include <stdint.h>
+ #include <stdlib.h>
+ 
+ // OpenXR paths and registry key locations

+ 1 - 0
thirdparty/openxr/src/common/platform_utils.hpp

@@ -11,6 +11,7 @@
 
 #include "xr_dependencies.h"
 #include <string>
+#include <stdint.h>
 #include <stdlib.h>
 
 // OpenXR paths and registry key locations

+ 23 - 0
thirdparty/vhacd/0006-fix-gcc13.patch

@@ -13,3 +13,26 @@ index 132bdcfb3e..925584cf52 100644
  namespace VHACD {
  //!    Incremental Convex Hull algorithm (cf. http://cs.smith.edu/~orourke/books/ftp.html ).
  enum ICHullError {
+diff --git a/thirdparty/vhacd/inc/vhacdManifoldMesh.h b/thirdparty/vhacd/inc/vhacdManifoldMesh.h
+index a48f53c5c5..5eed4e13aa 100644
+--- a/thirdparty/vhacd/inc/vhacdManifoldMesh.h
++++ b/thirdparty/vhacd/inc/vhacdManifoldMesh.h
+@@ -18,6 +18,11 @@ All rights reserved.
+ #include "vhacdCircularList.h"
+ #include "vhacdSArray.h"
+ #include "vhacdVector.h"
++
++// -- GODOT start --
++#include <cstdint>
++// -- GODOT end --
++
+ namespace VHACD {
+ class TMMTriangle;
+ class TMMEdge;
+@@ -139,4 +144,4 @@ private:
+     friend class ICHull;
+ };
+ }
+-#endif // VHACD_MANIFOLD_MESH_H
+\ No newline at end of file
++#endif // VHACD_MANIFOLD_MESH_H

+ 6 - 1
thirdparty/vhacd/inc/vhacdManifoldMesh.h

@@ -18,6 +18,11 @@ All rights reserved.
 #include "vhacdCircularList.h"
 #include "vhacdSArray.h"
 #include "vhacdVector.h"
+
+// -- GODOT start --
+#include <cstdint>
+// -- GODOT end --
+
 namespace VHACD {
 class TMMTriangle;
 class TMMEdge;
@@ -139,4 +144,4 @@ private:
     friend class ICHull;
 };
 }
-#endif // VHACD_MANIFOLD_MESH_H
+#endif // VHACD_MANIFOLD_MESH_H