From a85b0279fd6f592ed5cfcbd2d5038d2387e49443 Mon Sep 17 00:00:00 2001 From: Alex Threlfo Date: Fri, 13 Dec 2024 18:42:03 +1100 Subject: [PATCH 01/23] fix: space transformations in WorldPositionFromDepth visual shader node generation --- scene/resources/visual_shader_nodes.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/scene/resources/visual_shader_nodes.cpp b/scene/resources/visual_shader_nodes.cpp index b831a3835a0..f96dc860814 100644 --- a/scene/resources/visual_shader_nodes.cpp +++ b/scene/resources/visual_shader_nodes.cpp @@ -1747,12 +1747,13 @@ String VisualShaderNodeWorldPositionFromDepth::generate_code(Shader::Mode p_mode code += " float __log_depth = textureLod(" + make_unique_id(p_type, p_id, "depth_tex") + ", " + uv + ", 0.0).x;\n"; if (!RenderingServer::get_singleton()->is_low_end()) { - code += " vec4 __depth_view = INV_PROJECTION_MATRIX * vec4(" + uv + " * 2.0 - 1.0, __log_depth, 1.0);\n"; + code += " vec4 __ndc = vec4(" + uv + " * 2.0 - 1.0, __log_depth, 1.0);\n"; } else { - code += " vec4 __depth_view = INV_PROJECTION_MATRIX * vec4(vec3(" + uv + ", __log_depth) * 2.0 - 1.0, 1.0);\n"; + code += " vec4 __ndc = vec4(vec3(" + uv + ", __log_depth) * 2.0 - 1.0, 1.0);\n"; } - code += " __depth_view.xyz /= __depth_view.w;\n"; - code += vformat(" %s = (INV_VIEW_MATRIX * __depth_view).xyz;\n", p_output_vars[0]); + code += " vec4 __position_world = INV_VIEW_MATRIX * INV_PROJECTION_MATRIX * __ndc;\n"; + code += " __position_world.xyz /= __position_world.w;\n"; + code += vformat(" %s = __position_world.xyz;\n", p_output_vars[0]); code += " }\n"; return code; From 0edd24da6a2e5a6ab70649167460385f7ccdfd2d Mon Sep 17 00:00:00 2001 From: Summersay415 Date: Mon, 30 Dec 2024 12:23:43 +0700 Subject: [PATCH 02/23] Ensure icon and splash paths on export --- editor/export/editor_export_platform.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/editor/export/editor_export_platform.cpp b/editor/export/editor_export_platform.cpp index cb496fc9f09..1cf47f34865 100644 --- a/editor/export/editor_export_platform.cpp +++ b/editor/export/editor_export_platform.cpp @@ -35,6 +35,7 @@ #include "core/extension/gdextension.h" #include "core/io/file_access_encrypted.h" #include "core/io/file_access_pack.h" // PACK_HEADER_MAGIC, PACK_FORMAT_VERSION +#include "core/io/resource_uid.h" #include "core/io/zip_io.h" #include "core/version.h" #include "editor/editor_file_system.h" @@ -925,8 +926,8 @@ Vector EditorExportPlatform::get_forced_export_files() { files.push_back(ProjectSettings::get_singleton()->get_global_class_list_path()); - String icon = GLOBAL_GET("application/config/icon"); - String splash = GLOBAL_GET("application/boot_splash/image"); + String icon = ResourceUID::ensure_path(GLOBAL_GET("application/config/icon")); + String splash = ResourceUID::ensure_path(GLOBAL_GET("application/boot_splash/image")); if (!icon.is_empty() && FileAccess::exists(icon)) { files.push_back(icon); } From 8274e644029731e9fb0f136abe9a702aa716eb91 Mon Sep 17 00:00:00 2001 From: kit Date: Wed, 15 Jan 2025 17:59:07 -0500 Subject: [PATCH 03/23] Fix TextEdit breakpoint hover not hiding --- scene/gui/code_edit.cpp | 1 - scene/gui/text_edit.cpp | 101 ++++++++++++++++++----------------- scene/gui/text_edit.h | 1 + tests/scene/test_text_edit.h | 87 ++++++++++++++++++++++++++++++ 4 files changed, 140 insertions(+), 50 deletions(-) diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index 7f8b4dca786..66080e51035 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -275,7 +275,6 @@ void CodeEdit::_notification(int p_what) { } break; case NOTIFICATION_MOUSE_EXIT: { - queue_redraw(); symbol_tooltip_timer->stop(); } break; } diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 85a6dce028e..4ef4959162c 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -1675,6 +1675,10 @@ void TextEdit::_notification(int p_what) { drag_caret_force_displayed = false; queue_redraw(); } + if (hovered_gutter != Vector2i(-1, -1)) { + hovered_gutter = Vector2i(-1, -1); + queue_redraw(); + } } break; } } @@ -1843,18 +1847,18 @@ void TextEdit::gui_input(const Ref &p_gui_input) { int col = pos.x; // Gutters. + Vector2i current_hovered_gutter = _get_hovered_gutter(mpos); + if (current_hovered_gutter != hovered_gutter) { + hovered_gutter = current_hovered_gutter; + queue_redraw(); + } + if (hovered_gutter != Vector2i(-1, -1)) { + emit_signal(SNAME("gutter_clicked"), hovered_gutter.y, hovered_gutter.x); + return; + } int left_margin = theme_cache.style_normal->get_margin(SIDE_LEFT); - for (int i = 0; i < gutters.size(); i++) { - if (!gutters[i].draw || gutters[i].width <= 0) { - continue; - } - - if (mpos.x >= left_margin && mpos.x <= left_margin + gutters[i].width) { - emit_signal(SNAME("gutter_clicked"), line, i); - return; - } - - left_margin += gutters[i].width; + if (mpos.x < left_margin + gutters_width + gutter_padding) { + return; } // Minimap. @@ -2066,27 +2070,8 @@ void TextEdit::gui_input(const Ref &p_gui_input) { } } - // Check if user is hovering a different gutter, and update if yes. - Vector2i current_hovered_gutter = Vector2i(-1, -1); - - int left_margin = theme_cache.style_normal->get_margin(SIDE_LEFT); - if (mpos.x <= left_margin + gutters_width + gutter_padding) { - int hovered_row = get_line_column_at_pos(mpos).y; - for (int i = 0; i < gutters.size(); i++) { - if (!gutters[i].draw || gutters[i].width <= 0) { - continue; - } - - if (mpos.x >= left_margin && mpos.x < left_margin + gutters[i].width) { - // We are in this gutter i's horizontal area. - current_hovered_gutter = Vector2i(i, hovered_row); - break; - } - - left_margin += gutters[i].width; - } - } - + // Update hovered gutter. + Vector2i current_hovered_gutter = _get_hovered_gutter(mpos); if (current_hovered_gutter != hovered_gutter) { hovered_gutter = current_hovered_gutter; queue_redraw(); @@ -3115,24 +3100,16 @@ void TextEdit::drop_data(const Point2 &p_point, const Variant &p_data) { } Control::CursorShape TextEdit::get_cursor_shape(const Point2 &p_pos) const { - Point2i pos = get_line_column_at_pos(p_pos); - int row = pos.y; - - int left_margin = theme_cache.style_normal->get_margin(SIDE_LEFT); - int gutter = left_margin + gutters_width; - if (p_pos.x < gutter) { - for (int i = 0; i < gutters.size(); i++) { - if (!gutters[i].draw) { - continue; - } - - if (p_pos.x >= left_margin && p_pos.x < left_margin + gutters[i].width) { - if (gutters[i].clickable || is_line_gutter_clickable(row, i)) { - return CURSOR_POINTING_HAND; - } - } - left_margin += gutters[i].width; + Vector2i current_hovered_gutter = _get_hovered_gutter(p_pos); + if (current_hovered_gutter != Vector2i(-1, -1)) { + if (gutters[current_hovered_gutter.x].clickable || is_line_gutter_clickable(current_hovered_gutter.y, current_hovered_gutter.x)) { + return CURSOR_POINTING_HAND; + } else { + return CURSOR_ARROW; } + } + int left_margin = theme_cache.style_normal->get_margin(SIDE_LEFT); + if (p_pos.x < left_margin + gutters_width + gutter_padding) { return CURSOR_ARROW; } @@ -8321,9 +8298,35 @@ void TextEdit::_update_gutter_width() { if (gutters_width > 0) { gutter_padding = 2; } + if (get_viewport()) { + hovered_gutter = _get_hovered_gutter(get_local_mouse_position()); + } queue_redraw(); } +Vector2i TextEdit::_get_hovered_gutter(const Point2 &p_mouse_pos) const { + int left_margin = theme_cache.style_normal->get_margin(SIDE_LEFT); + if (p_mouse_pos.x > left_margin + gutters_width + gutter_padding) { + return Vector2i(-1, -1); + } + int hovered_row = get_line_column_at_pos(p_mouse_pos, false).y; + if (hovered_row == -1) { + return Vector2i(-1, -1); + } + for (int i = 0; i < gutters.size(); i++) { + if (!gutters[i].draw || gutters[i].width <= 0) { + continue; + } + + if (p_mouse_pos.x >= left_margin && p_mouse_pos.x < left_margin + gutters[i].width) { + return Vector2i(i, hovered_row); + } + + left_margin += gutters[i].width; + } + return Vector2i(-1, -1); +} + /* Syntax highlighting. */ Vector> TextEdit::_get_line_syntax_highlighting(int p_line) { if (syntax_highlighter.is_null() || setting_text) { diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index ef511096302..335fe9bf03e 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -565,6 +565,7 @@ class TextEdit : public Control { Vector2i hovered_gutter = Vector2i(-1, -1); // X = gutter index, Y = row. void _update_gutter_width(); + Vector2i _get_hovered_gutter(const Point2 &p_mouse_pos) const; /* Syntax highlighting. */ Ref syntax_highlighter; diff --git a/tests/scene/test_text_edit.h b/tests/scene/test_text_edit.h index c41eebdf3a4..d7192def621 100644 --- a/tests/scene/test_text_edit.h +++ b/tests/scene/test_text_edit.h @@ -8151,6 +8151,93 @@ TEST_CASE("[SceneTree][TextEdit] gutters") { // Merging tested via CodeEdit gutters. } + SUBCASE("[TextEdit] gutter mouse") { + DisplayServerMock *DS = (DisplayServerMock *)(DisplayServer::get_singleton()); + // Set size for mouse input. + text_edit->set_size(Size2(200, 200)); + + text_edit->set_text("test1\ntest2\ntest3\ntest4"); + text_edit->grab_focus(); + + text_edit->add_gutter(); + text_edit->set_gutter_name(0, "test_gutter"); + text_edit->set_gutter_width(0, 10); + text_edit->set_gutter_clickable(0, true); + + text_edit->add_gutter(); + text_edit->set_gutter_name(1, "test_gutter_not_clickable"); + text_edit->set_gutter_width(1, 10); + text_edit->set_gutter_clickable(1, false); + + text_edit->add_gutter(); + CHECK(text_edit->get_gutter_count() == 3); + text_edit->set_gutter_name(2, "test_gutter_3"); + text_edit->set_gutter_width(2, 10); + text_edit->set_gutter_clickable(2, true); + + MessageQueue::get_singleton()->flush(); + const int line_height = text_edit->get_line_height(); + + // Defaults to none. + CHECK(text_edit->get_hovered_gutter() == Vector2i(-1, -1)); + CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_ARROW); + + // Hover over gutter. + SEND_GUI_MOUSE_MOTION_EVENT(Point2(5, line_height + line_height / 2), MouseButtonMask::NONE, Key::NONE); + CHECK(text_edit->get_hovered_gutter() == Vector2i(0, 1)); + SIGNAL_CHECK_FALSE("gutter_clicked"); + CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_POINTING_HAND); + + // Click on gutter. + SEND_GUI_MOUSE_BUTTON_EVENT(Point2(5, line_height / 2), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->get_hovered_gutter() == Vector2i(0, 0)); + SIGNAL_CHECK("gutter_clicked", build_array(build_array(0, 0))); + + // Click on gutter on another line. + SEND_GUI_MOUSE_BUTTON_EVENT(Point2(5, line_height * 3 + line_height / 2), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->get_hovered_gutter() == Vector2i(0, 3)); + SIGNAL_CHECK("gutter_clicked", build_array(build_array(3, 0))); + + // Unclickable gutter can be hovered. + SEND_GUI_MOUSE_MOTION_EVENT(Point2(15, line_height + line_height / 2), MouseButtonMask::NONE, Key::NONE); + CHECK(text_edit->get_hovered_gutter() == Vector2i(1, 1)); + SIGNAL_CHECK_FALSE("gutter_clicked"); + CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_ARROW); + + // Unclickable gutter can be clicked. + SEND_GUI_MOUSE_BUTTON_EVENT(Point2(15, line_height * 2 + line_height / 2), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->get_hovered_gutter() == Vector2i(1, 2)); + SIGNAL_CHECK("gutter_clicked", build_array(build_array(2, 1))); + CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_ARROW); + + // Hover past last line. + SEND_GUI_MOUSE_MOTION_EVENT(Point2(5, line_height * 5), MouseButtonMask::NONE, Key::NONE); + CHECK(text_edit->get_hovered_gutter() == Vector2i(-1, -1)); + SIGNAL_CHECK_FALSE("gutter_clicked"); + CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_ARROW); + + // Click on gutter past last line. + SEND_GUI_MOUSE_BUTTON_EVENT(Point2(5, line_height * 5), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->get_hovered_gutter() == Vector2i(-1, -1)); + SIGNAL_CHECK_FALSE("gutter_clicked"); + + // Mouse exit resets hover. + SEND_GUI_MOUSE_MOTION_EVENT(Point2(5, line_height + line_height / 2), MouseButtonMask::NONE, Key::NONE); + CHECK(text_edit->get_hovered_gutter() == Vector2i(0, 1)); + SEND_GUI_MOUSE_MOTION_EVENT(Point2(-1, -1), MouseButtonMask::NONE, Key::NONE); + CHECK(text_edit->get_hovered_gutter() == Vector2i(-1, -1)); + + // Removing gutter updates hover. + SEND_GUI_MOUSE_MOTION_EVENT(Point2(25, line_height + line_height / 2), MouseButtonMask::NONE, Key::NONE); + CHECK(text_edit->get_hovered_gutter() == Vector2i(2, 1)); + text_edit->remove_gutter(2); + CHECK(text_edit->get_hovered_gutter() == Vector2i(-1, -1)); + + // Updating size updates hover. + text_edit->set_gutter_width(1, 20); + CHECK(text_edit->get_hovered_gutter() == Vector2i(1, 1)); + } + SIGNAL_UNWATCH(text_edit, "gutter_clicked"); SIGNAL_UNWATCH(text_edit, "gutter_added"); SIGNAL_UNWATCH(text_edit, "gutter_removed"); From 605b97075f9f5990c166ca38b715d2b254c1d10f Mon Sep 17 00:00:00 2001 From: Jamie Pate Date: Thu, 2 Jan 2025 12:44:40 -0800 Subject: [PATCH 04/23] Fix create_instance in android GodotApp so non-editor apps can restart Enables OS.create_instance(args) and OS.set_restart_on_exit(true, args) on android. Borrowed the logic from the editor, so it completely restarts the process so you can pass --rendering-method, --rendering-driver to switch between forward_plus, mobile, gl_compatibility etc on an exported app. Related: https://github.com/godotengine/godot-proposals/issues/6423 --- .../org/godotengine/editor/BaseGodotEditor.kt | 34 +++---------- .../org/godotengine/editor/GodotXRGame.kt | 4 +- .../lib/src/org/godotengine/godot/Godot.kt | 23 +++++++-- .../org/godotengine/godot/GodotActivity.kt | 49 +++++++++++++++++++ 4 files changed, 79 insertions(+), 31 deletions(-) diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt index b1fd8a1124d..45662dbae04 100644 --- a/platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt +++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt @@ -71,8 +71,6 @@ abstract class BaseGodotEditor : GodotActivity() { private const val WAIT_FOR_DEBUGGER = false - @JvmStatic - protected val EXTRA_COMMAND_LINE_PARAMS = "command_line_params" @JvmStatic protected val EXTRA_PIP_AVAILABLE = "pip_available" @JvmStatic @@ -130,7 +128,6 @@ abstract class BaseGodotEditor : GodotActivity() { } private val editorMessageDispatcher = EditorMessageDispatcher(this) - private val commandLineParams = ArrayList() private val editorLoadingIndicator: View? by lazy { findViewById(R.id.editor_loading_indicator) } override fun getGodotAppLayout() = R.layout.godot_editor_layout @@ -183,10 +180,6 @@ abstract class BaseGodotEditor : GodotActivity() { // requested on demand based on use cases. PermissionsUtil.requestManifestPermissions(this, getExcludedPermissions()) - val params = intent.getStringArrayExtra(EXTRA_COMMAND_LINE_PARAMS) - Log.d(TAG, "Starting intent $intent with parameters ${params.contentToString()}") - updateCommandLineParams(params?.asList() ?: emptyList()) - editorMessageDispatcher.parseStartIntent(packageManager, intent) if (BuildConfig.BUILD_TYPE == "dev" && WAIT_FOR_DEBUGGER) { @@ -219,20 +212,16 @@ abstract class BaseGodotEditor : GodotActivity() { } @CallSuper - protected open fun updateCommandLineParams(args: List) { - // Update the list of command line params with the new args - commandLineParams.clear() - if (args.isNotEmpty()) { - commandLineParams.addAll(args) - } - if (BuildConfig.BUILD_TYPE == "dev") { - commandLineParams.add("--benchmark") + protected override fun updateCommandLineParams(args: Array) { + val args = if (BuildConfig.BUILD_TYPE == "dev") { + args + "--benchmark" + } else { + args } + super.updateCommandLineParams(args); } - final override fun getCommandLine() = commandLineParams - - protected fun retrieveEditorWindowInfo(args: Array): EditorWindowInfo { + protected open fun retrieveEditorWindowInfo(args: Array): EditorWindowInfo { var hasEditor = false var xrMode = XR_MODE_DEFAULT @@ -335,14 +324,7 @@ abstract class BaseGodotEditor : GodotActivity() { val newInstance = getNewGodotInstanceIntent(editorWindowInfo, args) if (editorWindowInfo.windowClassName == javaClass.name) { Log.d(TAG, "Restarting ${editorWindowInfo.windowClassName} with parameters ${args.contentToString()}") - val godot = godot - if (godot != null) { - godot.destroyAndKillProcess { - ProcessPhoenix.triggerRebirth(this, activityOptions?.toBundle(), newInstance) - } - } else { - ProcessPhoenix.triggerRebirth(this, activityOptions?.toBundle(), newInstance) - } + triggerRebirth(activityOptions?.toBundle(), newInstance) } else { Log.d(TAG, "Starting ${editorWindowInfo.windowClassName} with parameters ${args.contentToString()}") newInstance.putExtra(EXTRA_NEW_LAUNCH, true) diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotXRGame.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotXRGame.kt index bfaef3abbd1..1b301846076 100644 --- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotXRGame.kt +++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotXRGame.kt @@ -40,7 +40,7 @@ open class GodotXRGame: GodotGame() { override fun overrideOrientationRequest() = true - override fun updateCommandLineParams(args: List) { + override fun updateCommandLineParams(args: Array) { val updatedArgs = ArrayList() if (!args.contains(XRMode.OPENXR.cmdLineArg)) { updatedArgs.add(XRMode.OPENXR.cmdLineArg) @@ -51,7 +51,7 @@ open class GodotXRGame: GodotGame() { } updatedArgs.addAll(args) - super.updateCommandLineParams(updatedArgs) + super.updateCommandLineParams(updatedArgs.toTypedArray()) } override fun getEditorWindowInfo() = XR_RUN_GAME_INFO diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt index 3fc3caed911..4c0192253dd 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt @@ -823,9 +823,26 @@ class Godot(private val context: Context) { * Returns true if `Vulkan` is used for rendering. */ private fun usesVulkan(): Boolean { - val renderer = GodotLib.getGlobal("rendering/renderer/rendering_method") - val renderingDevice = GodotLib.getGlobal("rendering/rendering_device/driver") - return ("forward_plus" == renderer || "mobile" == renderer) && "vulkan" == renderingDevice + var rendererSource = "ProjectSettings" + var renderer = GodotLib.getGlobal("rendering/renderer/rendering_method") + var renderingDeviceSource = "ProjectSettings" + var renderingDevice = GodotLib.getGlobal("rendering/rendering_device/driver") + val cmdline = getCommandLine() + var index = cmdline.indexOf("--rendering-method") + if (index > -1 && cmdline.size > index + 1) { + rendererSource = "CommandLine" + renderer = cmdline.get(index + 1) + } + index = cmdline.indexOf("--rendering-driver") + if (index > -1 && cmdline.size > index + 1) { + renderingDeviceSource = "CommandLine" + renderingDevice = cmdline.get(index + 1) + } + val result = ("forward_plus" == renderer || "mobile" == renderer) && "vulkan" == renderingDevice + Log.d(TAG, """usesVulkan(): ${result} + renderingDevice: ${renderingDevice} (${renderingDeviceSource}) + renderer: ${renderer} (${rendererSource})""") + return result } /** diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt b/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt index 474c6e9b2fb..72e63346fa0 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt @@ -31,6 +31,7 @@ package org.godotengine.godot import android.app.Activity +import android.content.ComponentName import android.content.Intent import android.content.pm.PackageManager import android.os.Bundle @@ -52,18 +53,32 @@ abstract class GodotActivity : FragmentActivity(), GodotHost { companion object { private val TAG = GodotActivity::class.java.simpleName + @JvmStatic + protected val EXTRA_COMMAND_LINE_PARAMS = "command_line_params" + @JvmStatic protected val EXTRA_NEW_LAUNCH = "new_launch_requested" + + // This window must not match those in BaseGodotEditor.RUN_GAME_INFO etc + @JvmStatic + private final val DEFAULT_WINDOW_ID = 664; } + private val commandLineParams = ArrayList() /** * Interaction with the [Godot] object is delegated to the [GodotFragment] class. */ protected var godotFragment: GodotFragment? = null private set + @CallSuper override fun onCreate(savedInstanceState: Bundle?) { + val params = intent.getStringArrayExtra(EXTRA_COMMAND_LINE_PARAMS) + Log.d(TAG, "Starting intent $intent with parameters ${params.contentToString()}") + updateCommandLineParams(params ?: emptyArray()) + super.onCreate(savedInstanceState) + setContentView(getGodotAppLayout()) handleStartIntent(intent, true) @@ -79,6 +94,29 @@ abstract class GodotActivity : FragmentActivity(), GodotHost { } } + override fun onNewGodotInstanceRequested(args: Array): Int { + Log.d(TAG, "Restarting with parameters ${args.contentToString()}") + val intent = Intent() + .setComponent(ComponentName(this, javaClass.name)) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + .putExtra(EXTRA_COMMAND_LINE_PARAMS, args) + triggerRebirth(null, intent) + // fake 'process' id returned by create_instance() etc + return DEFAULT_WINDOW_ID; + } + + protected fun triggerRebirth(bundle: Bundle?, intent: Intent) { + // Launch a new activity + val godot = godot + if (godot != null) { + godot.destroyAndKillProcess { + ProcessPhoenix.triggerRebirth(this, bundle, intent) + } + } else { + ProcessPhoenix.triggerRebirth(this, bundle, intent) + } + } + @LayoutRes protected open fun getGodotAppLayout() = R.layout.godot_app_layout @@ -176,4 +214,15 @@ abstract class GodotActivity : FragmentActivity(), GodotHost { protected open fun initGodotInstance(): GodotFragment { return GodotFragment() } + + @CallSuper + protected open fun updateCommandLineParams(args: Array) { + // Update the list of command line params with the new args + commandLineParams.clear() + if (args.isNotEmpty()) { + commandLineParams.addAll(args) + } + } + + final override fun getCommandLine() = commandLineParams } From bed2a1927fbbc444f57f7cbb1298504ce7edc6ca Mon Sep 17 00:00:00 2001 From: Riteo Siuga Date: Sat, 18 Jan 2025 23:46:03 +0100 Subject: [PATCH 05/23] Wayland: Check selection devices before using them Looks like we never actually stopped the code from using bad pointers. I even forgot the check in the primary selection code :facepalm: --- platform/linuxbsd/wayland/wayland_thread.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/platform/linuxbsd/wayland/wayland_thread.cpp b/platform/linuxbsd/wayland/wayland_thread.cpp index c6efeac9698..5ba412a6f26 100644 --- a/platform/linuxbsd/wayland/wayland_thread.cpp +++ b/platform/linuxbsd/wayland/wayland_thread.cpp @@ -4111,6 +4111,7 @@ void WaylandThread::selection_set_text(const String &p_text) { if (registry.wl_data_device_manager == nullptr) { DEBUG_LOG_WAYLAND_THREAD("Couldn't set selection, wl_data_device_manager global not available."); + return; } if (ss == nullptr) { @@ -4238,6 +4239,11 @@ void WaylandThread::primary_set_text(const String &p_text) { return; } + if (ss->wp_primary_selection_device == nullptr) { + DEBUG_LOG_WAYLAND_THREAD("Couldn't set primary selection, seat doesn't have wp_primary_selection_device."); + return; + } + ss->primary_data = p_text.to_utf8_buffer(); if (ss->wp_primary_selection_source == nullptr) { @@ -4245,10 +4251,10 @@ void WaylandThread::primary_set_text(const String &p_text) { zwp_primary_selection_source_v1_add_listener(ss->wp_primary_selection_source, &wp_primary_selection_source_listener, ss); zwp_primary_selection_source_v1_offer(ss->wp_primary_selection_source, "text/plain;charset=utf-8"); zwp_primary_selection_source_v1_offer(ss->wp_primary_selection_source, "text/plain"); - } - // TODO: Implement a good way of getting the latest serial from the user. - zwp_primary_selection_device_v1_set_selection(ss->wp_primary_selection_device, ss->wp_primary_selection_source, MAX(ss->pointer_data.button_serial, ss->last_key_pressed_serial)); + // TODO: Implement a good way of getting the latest serial from the user. + zwp_primary_selection_device_v1_set_selection(ss->wp_primary_selection_device, ss->wp_primary_selection_source, MAX(ss->pointer_data.button_serial, ss->last_key_pressed_serial)); + } // Wait for the message to get to the server before continuing, otherwise the // clipboard update might come with a delay. From 9014cdb59699bf5701298126cee2fe4b1766995a Mon Sep 17 00:00:00 2001 From: Tareq Anuar Date: Sat, 18 Jan 2025 19:46:03 +0800 Subject: [PATCH 06/23] Fix ResourceLoader.has_cached() and ResourceLoader.get_cached_ref() not handling UIDs. --- core/core_bind.cpp | 4 ++-- core/io/resource_loader.cpp | 2 +- core/io/resource_loader.h | 7 +++++++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/core/core_bind.cpp b/core/core_bind.cpp index 6974d98234c..acd589783ec 100644 --- a/core/core_bind.cpp +++ b/core/core_bind.cpp @@ -113,12 +113,12 @@ PackedStringArray ResourceLoader::get_dependencies(const String &p_path) { } bool ResourceLoader::has_cached(const String &p_path) { - String local_path = ProjectSettings::get_singleton()->localize_path(p_path); + String local_path = ::ResourceLoader::_validate_local_path(p_path); return ResourceCache::has(local_path); } Ref ResourceLoader::get_cached_ref(const String &p_path) { - String local_path = ProjectSettings::get_singleton()->localize_path(p_path); + String local_path = ::ResourceLoader::_validate_local_path(p_path); return ResourceCache::get_ref(local_path); } diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp index 8f873d859b6..7cc91ef4318 100644 --- a/core/io/resource_loader.cpp +++ b/core/io/resource_loader.cpp @@ -495,7 +495,7 @@ void ResourceLoader::_run_load_task(void *p_userdata) { curr_load_task = curr_load_task_backup; } -static String _validate_local_path(const String &p_path) { +String ResourceLoader::_validate_local_path(const String &p_path) { ResourceUID::ID uid = ResourceUID::get_singleton()->text_to_id(p_path); if (uid != ResourceUID::INVALID_ID) { return ResourceUID::get_singleton()->get_id_path(uid); diff --git a/core/io/resource_loader.h b/core/io/resource_loader.h index f933e88b23b..56052b6a6fc 100644 --- a/core/io/resource_loader.h +++ b/core/io/resource_loader.h @@ -36,6 +36,10 @@ #include "core/object/worker_thread_pool.h" #include "core/os/thread.h" +namespace core_bind { +class ResourceLoader; +} + class ConditionVariable; template @@ -101,6 +105,7 @@ typedef void (*ResourceLoadedCallback)(Ref p_resource, const String &p class ResourceLoader { friend class LoadToken; + friend class core_bind::ResourceLoader; enum { MAX_LOADERS = 64 @@ -217,6 +222,8 @@ class ResourceLoader { static bool _ensure_load_progress(); + static String _validate_local_path(const String &p_path); + public: static Error load_threaded_request(const String &p_path, const String &p_type_hint = "", bool p_use_sub_threads = false, ResourceFormatLoader::CacheMode p_cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE); static ThreadLoadStatus load_threaded_get_status(const String &p_path, float *r_progress = nullptr); From b753b87ec5ae3ef398fd9507fd6ae21f978b0d52 Mon Sep 17 00:00:00 2001 From: Anish Mishra Date: Mon, 20 Jan 2025 20:51:52 +0530 Subject: [PATCH 07/23] Android Editor: Disable magnify gesture in the ScriptEditor --- editor/code_editor.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp index 4c934104d19..9b53bd22b05 100644 --- a/editor/code_editor.cpp +++ b/editor/code_editor.cpp @@ -933,12 +933,14 @@ void CodeTextEditor::_text_editor_gui_input(const Ref &p_event) { } } +#ifndef ANDROID_ENABLED Ref magnify_gesture = p_event; if (magnify_gesture.is_valid()) { _zoom_to(zoom_factor * powf(magnify_gesture->get_factor(), 0.25f)); accept_event(); return; } +#endif Ref k = p_event; From b8480ffa21f8d713815017bc9974f7c0ab169d50 Mon Sep 17 00:00:00 2001 From: Thaddeus Crews Date: Tue, 1 Oct 2024 10:32:57 -0500 Subject: [PATCH 08/23] CI: Change godot-cpp from workflow to action --- .github/actions/godot-cpp-build/action.yml | 39 +++++++++++++++ .github/workflows/godot_cpp_test.yml | 57 ---------------------- .github/workflows/linux_builds.yml | 19 ++++---- .github/workflows/runner.yml | 12 ----- 4 files changed, 49 insertions(+), 78 deletions(-) create mode 100644 .github/actions/godot-cpp-build/action.yml delete mode 100644 .github/workflows/godot_cpp_test.yml diff --git a/.github/actions/godot-cpp-build/action.yml b/.github/actions/godot-cpp-build/action.yml new file mode 100644 index 00000000000..9596c5a8fd7 --- /dev/null +++ b/.github/actions/godot-cpp-build/action.yml @@ -0,0 +1,39 @@ +name: Build godot-cpp +description: Build godot-cpp with the provided options. + +env: + GODOT_CPP_BRANCH: 4.3 + +inputs: + bin: + description: Path to the Godot binary. + required: true + type: string + scons-flags: + description: Additional SCons flags. + type: string + scons-cache: + description: The SCons cache path. + default: ${{ github.workspace }}/.scons_cache/ + type: string + +runs: + using: composite + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + repository: godotengine/godot-cpp + ref: ${{ env.GODOT_CPP_BRANCH }} + path: godot-cpp + + - name: Extract API + shell: sh + run: ${{ inputs.bin }} --headless --dump-gdextension-interface --dump-extension-api + + - name: SCons Build + shell: sh + env: + SCONS_CACHE: ${{ inputs.scons-cache }} + run: scons --directory=./godot-cpp/test "gdextension_dir=${{ github.workspace }}" ${{ inputs.scons-flags }} diff --git a/.github/workflows/godot_cpp_test.yml b/.github/workflows/godot_cpp_test.yml deleted file mode 100644 index a1b085604eb..00000000000 --- a/.github/workflows/godot_cpp_test.yml +++ /dev/null @@ -1,57 +0,0 @@ -name: 🪲 Godot CPP -on: - workflow_call: - -# Global Settings -env: - # Used for the cache key. Add version suffix to force clean build. - GODOT_BASE_BRANCH: master - # Used for the godot-cpp checkout. - GODOT_CPP_BRANCH: 4.3 - -jobs: - godot-cpp-tests: - runs-on: ubuntu-24.04 - name: Build and test Godot CPP - timeout-minutes: 30 - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - sparse-checkout: | - .github - misc/utility/problem-matchers.json - - - name: Checkout godot-cpp - uses: actions/checkout@v4 - with: - submodules: recursive - repository: godotengine/godot-cpp - ref: ${{ env.GODOT_CPP_BRANCH }} - path: godot-cpp - - - name: Setup Python and SCons - uses: ./.github/actions/godot-deps - - - name: Download GDExtension interface and API dump - uses: ./.github/actions/download-artifact - with: - name: godot-api-dump - path: ./godot-cpp/gdextension - - # TODO: Enable caching when godot-cpp has proper cache limiting. - - # - name: Restore Godot build cache - # uses: ./.github/actions/godot-cache-restore - # with: - # cache-name: godot-cpp - # continue-on-error: true - - - name: Build godot-cpp test extension - run: scons --directory=./godot-cpp/test target=template_debug dev_build=yes verbose=yes - - # - name: Save Godot build cache - # uses: ./.github/actions/godot-cache-save - # with: - # cache-name: godot-cpp - # continue-on-error: true diff --git a/.github/workflows/linux_builds.yml b/.github/workflows/linux_builds.yml index 4d0a215c801..e978f9c8203 100644 --- a/.github/workflows/linux_builds.yml +++ b/.github/workflows/linux_builds.yml @@ -33,7 +33,9 @@ jobs: proj-conv: true api-compat: true artifact: true - cache-limit: 1 + # Validate godot-cpp compatibility on one arbitrary editor build. + godot-cpp: true + cache-limit: 2 - name: Editor with doubles and GCC sanitizers (target=editor, tests=yes, dev_build=yes, scu_build=yes, precision=double, use_asan=yes, use_ubsan=yes, linker=gold) cache-name: linux-editor-double-sanitizers @@ -44,8 +46,6 @@ jobs: build-mono: false tests: true proj-test: true - # Generate an API dump for godot-cpp tests. - api-dump: true # Skip 2GiB artifact speeding up action. artifact: false cache-limit: 7 @@ -158,6 +158,13 @@ jobs: tests: ${{ matrix.tests }} scons-cache-limit: ${{ matrix.cache-limit }} + - name: Compilation (godot-cpp) + uses: ./.github/actions/godot-cpp-build + if: matrix.godot-cpp + with: + bin: ${{ matrix.bin }} + scons-flags: target=template_debug dev_build=yes verbose=yes + - name: Save Godot build cache uses: ./.github/actions/godot-cache-save with: @@ -187,12 +194,6 @@ jobs: with: name: ${{ matrix.cache-name }} - - name: Dump Godot API - uses: ./.github/actions/godot-api-dump - if: matrix.api-dump - with: - bin: ${{ matrix.bin }} - - name: Unit tests if: matrix.tests run: | diff --git a/.github/workflows/runner.yml b/.github/workflows/runner.yml index 937f0e895d7..c88d44a098d 100644 --- a/.github/workflows/runner.yml +++ b/.github/workflows/runner.yml @@ -44,15 +44,3 @@ jobs: name: 🌐 Web needs: static-checks uses: ./.github/workflows/web_builds.yml - - # Third stage: Run auxiliary tests using build artifacts from previous jobs. - - # Can be turned off for PRs that intentionally break compat with godot-cpp, - # until both the upstream PR and the matching godot-cpp changes are merged. - godot-cpp-test: - name: 🪲 Godot CPP - # This can be changed to depend on another platform, if we decide to use it for - # godot-cpp instead. Make sure to move the .github/actions/godot-api-dump step - # appropriately. - needs: linux-build - uses: ./.github/workflows/godot_cpp_test.yml From be7a6d2a9f314544de2420ab0e195de0095beaa6 Mon Sep 17 00:00:00 2001 From: Hugo Locurcio Date: Mon, 20 Jan 2025 23:14:09 +0100 Subject: [PATCH 09/23] Fix URLs for Cubemap template images in documentation --- doc/classes/Cubemap.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/classes/Cubemap.xml b/doc/classes/Cubemap.xml index 8a850920f74..c55adcffe33 100644 --- a/doc/classes/Cubemap.xml +++ b/doc/classes/Cubemap.xml @@ -7,10 +7,10 @@ A cubemap is made of 6 textures organized in layers. They are typically used for faking reflections in 3D rendering (see [ReflectionProbe]). It can be used to make an object look as if it's reflecting its surroundings. This usually delivers much better performance than other reflection methods. This resource is typically used as a uniform in custom shaders. Few core Godot methods make use of [Cubemap] resources. To create such a texture file yourself, reimport your image files using the Godot Editor import presets. The expected image order is X+, X-, Y+, Y-, Z+, Z- (in Godot's coordinate system, so Y+ is "up" and Z- is "forward"). You can use one of the following templates as a base: - - [url=https://raw.githubusercontent.com/godotengine/godot-docs/master/img/cubemap_template_2x3.webp]2×3 cubemap template (default layout option)[/url] - - [url=https://raw.githubusercontent.com/godotengine/godot-docs/master/img/cubemap_template_3x2.webp]3×2 cubemap template[/url] - - [url=https://raw.githubusercontent.com/godotengine/godot-docs/master/img/cubemap_template_1x6.webp]1×6 cubemap template[/url] - - [url=https://raw.githubusercontent.com/godotengine/godot-docs/master/img/cubemap_template_6x1.webp]6×1 cubemap template[/url] + - [url=https://raw.githubusercontent.com/godotengine/godot-docs/master/tutorials/assets_pipeline/img/cubemap_template_2x3.webp]2×3 cubemap template (default layout option)[/url] + - [url=https://raw.githubusercontent.com/godotengine/godot-docs/master/tutorials/assets_pipeline/img/cubemap_template_3x2.webp]3×2 cubemap template[/url] + - [url=https://raw.githubusercontent.com/godotengine/godot-docs/master/tutorials/assets_pipeline/img/cubemap_template_1x6.webp]1×6 cubemap template[/url] + - [url=https://raw.githubusercontent.com/godotengine/godot-docs/master/tutorials/assets_pipeline/img/cubemap_template_6x1.webp]6×1 cubemap template[/url] [b]Note:[/b] Godot doesn't support using cubemaps in a [PanoramaSkyMaterial]. To use a cubemap as a skybox, convert the default [PanoramaSkyMaterial] to a [ShaderMaterial] using the [b]Convert to ShaderMaterial[/b] resource dropdown option, then replace its code with the following: [codeblock lang=text] shader_type sky; From ae0b7ff42ded286e3bc3c1159373419d0537fb9d Mon Sep 17 00:00:00 2001 From: Hilderin <81109165+Hilderin@users.noreply.github.com> Date: Mon, 20 Jan 2025 18:33:12 -0500 Subject: [PATCH 10/23] Fix Embedded Game Size --- editor/editor_run.cpp | 191 ++++++++++++++------------- editor/editor_run.h | 10 ++ editor/icons/FixedSize.svg | 1 + editor/icons/KeepAspect.svg | 2 +- editor/icons/Stretch.svg | 1 + editor/plugins/embedded_process.cpp | 56 +++++--- editor/plugins/embedded_process.h | 4 +- editor/plugins/game_view_plugin.cpp | 195 ++++++++++++++++++++++------ editor/plugins/game_view_plugin.h | 25 +++- editor/window_wrapper.cpp | 8 ++ editor/window_wrapper.h | 1 + 11 files changed, 343 insertions(+), 151 deletions(-) create mode 100644 editor/icons/FixedSize.svg create mode 100644 editor/icons/Stretch.svg diff --git a/editor/editor_run.cpp b/editor/editor_run.cpp index 5251f1e558d..84367971ce5 100644 --- a/editor/editor_run.cpp +++ b/editor/editor_run.cpp @@ -104,99 +104,16 @@ Error EditorRun::run(const String &p_scene, const String &p_write_movie, const V } } - int screen = EDITOR_GET("run/window_placement/screen"); - if (screen == -5) { - // Same as editor - screen = DisplayServer::get_singleton()->window_get_current_screen(); - } else if (screen == -4) { - // Previous monitor (wrap to the other end if needed) - screen = Math::wrapi( - DisplayServer::get_singleton()->window_get_current_screen() - 1, - 0, - DisplayServer::get_singleton()->get_screen_count()); - } else if (screen == -3) { - // Next monitor (wrap to the other end if needed) - screen = Math::wrapi( - DisplayServer::get_singleton()->window_get_current_screen() + 1, - 0, - DisplayServer::get_singleton()->get_screen_count()); + WindowPlacement window_placement = get_window_placement(); + if (window_placement.position != Point2i(INT_MAX, INT_MAX)) { + args.push_back("--position"); + args.push_back(itos(window_placement.position.x) + "," + itos(window_placement.position.y)); } - Rect2 screen_rect = DisplayServer::get_singleton()->screen_get_usable_rect(screen); - - int window_placement = EDITOR_GET("run/window_placement/rect"); - if (screen_rect != Rect2()) { - Size2 window_size; - window_size.x = GLOBAL_GET("display/window/size/viewport_width"); - window_size.y = GLOBAL_GET("display/window/size/viewport_height"); - - Size2 desired_size; - desired_size.x = GLOBAL_GET("display/window/size/window_width_override"); - desired_size.y = GLOBAL_GET("display/window/size/window_height_override"); - if (desired_size.x > 0 && desired_size.y > 0) { - window_size = desired_size; - } - - if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_HIDPI)) { - bool hidpi_proj = GLOBAL_GET("display/window/dpi/allow_hidpi"); - int display_scale = 1; - - if (OS::get_singleton()->is_hidpi_allowed()) { - if (hidpi_proj) { - display_scale = 1; // Both editor and project runs in hiDPI mode, do not scale. - } else { - display_scale = DisplayServer::get_singleton()->screen_get_max_scale(); // Editor is in hiDPI mode, project is not, scale down. - } - } else { - if (hidpi_proj) { - display_scale = (1.f / DisplayServer::get_singleton()->screen_get_max_scale()); // Editor is not in hiDPI mode, project is, scale up. - } else { - display_scale = 1; // Both editor and project runs in lowDPI mode, do not scale. - } - } - screen_rect.position /= display_scale; - screen_rect.size /= display_scale; - } - - switch (window_placement) { - case 0: { // top left - args.push_back("--position"); - args.push_back(itos(screen_rect.position.x) + "," + itos(screen_rect.position.y)); - } break; - case 1: { // centered - Vector2 pos = (screen_rect.position) + ((screen_rect.size - window_size) / 2).floor(); - args.push_back("--position"); - args.push_back(itos(pos.x) + "," + itos(pos.y)); - } break; - case 2: { // custom pos - Vector2 pos = EDITOR_GET("run/window_placement/rect_custom_position"); - pos += screen_rect.position; - args.push_back("--position"); - args.push_back(itos(pos.x) + "," + itos(pos.y)); - } break; - case 3: { // force maximized - Vector2 pos = screen_rect.position + screen_rect.size / 2; - args.push_back("--position"); - args.push_back(itos(pos.x) + "," + itos(pos.y)); - args.push_back("--maximized"); - } break; - case 4: { // force fullscreen - Vector2 pos = screen_rect.position + screen_rect.size / 2; - args.push_back("--position"); - args.push_back(itos(pos.x) + "," + itos(pos.y)); - args.push_back("--fullscreen"); - } break; - } - } else { - // Unable to get screen info, skip setting position. - switch (window_placement) { - case 3: { // force maximized - args.push_back("--maximized"); - } break; - case 4: { // force fullscreen - args.push_back("--fullscreen"); - } break; - } + if (window_placement.force_maximized) { + args.push_back("--maximized"); + } else if (window_placement.force_fullscreen) { + args.push_back("--fullscreen"); } List breakpoints; @@ -297,6 +214,98 @@ OS::ProcessID EditorRun::get_current_process() const { return pids.front()->get(); } +EditorRun::WindowPlacement EditorRun::get_window_placement() { + WindowPlacement placement = WindowPlacement(); + placement.screen = EDITOR_GET("run/window_placement/screen"); + if (placement.screen == -5) { + // Same as editor + placement.screen = DisplayServer::get_singleton()->window_get_current_screen(); + } else if (placement.screen == -4) { + // Previous monitor (wrap to the other end if needed) + placement.screen = Math::wrapi( + DisplayServer::get_singleton()->window_get_current_screen() - 1, + 0, + DisplayServer::get_singleton()->get_screen_count()); + } else if (placement.screen == -3) { + // Next monitor (wrap to the other end if needed) + placement.screen = Math::wrapi( + DisplayServer::get_singleton()->window_get_current_screen() + 1, + 0, + DisplayServer::get_singleton()->get_screen_count()); + } else if (placement.screen == -2) { + // Primary screen + placement.screen = DisplayServer::get_singleton()->get_primary_screen(); + } + + placement.size.x = GLOBAL_GET("display/window/size/viewport_width"); + placement.size.y = GLOBAL_GET("display/window/size/viewport_height"); + + Size2 desired_size; + desired_size.x = GLOBAL_GET("display/window/size/window_width_override"); + desired_size.y = GLOBAL_GET("display/window/size/window_height_override"); + if (desired_size.x > 0 && desired_size.y > 0) { + placement.size = desired_size; + } + + Rect2 screen_rect = DisplayServer::get_singleton()->screen_get_usable_rect(placement.screen); + + int window_placement = EDITOR_GET("run/window_placement/rect"); + if (screen_rect != Rect2()) { + if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_HIDPI)) { + bool hidpi_proj = GLOBAL_GET("display/window/dpi/allow_hidpi"); + int display_scale = 1; + + if (OS::get_singleton()->is_hidpi_allowed()) { + if (hidpi_proj) { + display_scale = 1; // Both editor and project runs in hiDPI mode, do not scale. + } else { + display_scale = DisplayServer::get_singleton()->screen_get_max_scale(); // Editor is in hiDPI mode, project is not, scale down. + } + } else { + if (hidpi_proj) { + display_scale = (1.f / DisplayServer::get_singleton()->screen_get_max_scale()); // Editor is not in hiDPI mode, project is, scale up. + } else { + display_scale = 1; // Both editor and project runs in lowDPI mode, do not scale. + } + } + screen_rect.position /= display_scale; + screen_rect.size /= display_scale; + } + + switch (window_placement) { + case 0: { // top left + placement.position = screen_rect.position; + } break; + case 1: { // centered + placement.position = (screen_rect.position) + ((screen_rect.size - placement.size) / 2).floor(); + } break; + case 2: { // custom pos + Vector2 pos = EDITOR_GET("run/window_placement/rect_custom_position"); + pos += screen_rect.position; + placement.position = pos; + } break; + case 3: { // force maximized + placement.force_maximized = true; + } break; + case 4: { // force fullscreen + placement.force_fullscreen = true; + } break; + } + } else { + // Unable to get screen info, skip setting position. + switch (window_placement) { + case 3: { // force maximized + placement.force_maximized = true; + } break; + case 4: { // force fullscreen + placement.force_fullscreen = true; + } break; + } + } + + return placement; +} + EditorRun::EditorRun() { status = STATUS_STOP; running_scene = ""; diff --git a/editor/editor_run.h b/editor/editor_run.h index 94fb977e931..5e342944130 100644 --- a/editor/editor_run.h +++ b/editor/editor_run.h @@ -45,6 +45,14 @@ class EditorRun { List pids; + struct WindowPlacement { + int screen = 0; + Point2i position = Point2i(INT_MAX, INT_MAX); + Size2i size; + bool force_maximized = false; + bool force_fullscreen = false; + }; + private: Status status; String running_scene; @@ -64,6 +72,8 @@ class EditorRun { int get_child_process_count() const { return pids.size(); } OS::ProcessID get_current_process() const; + static WindowPlacement get_window_placement(); + EditorRun(); }; diff --git a/editor/icons/FixedSize.svg b/editor/icons/FixedSize.svg new file mode 100644 index 00000000000..1ca5752c23c --- /dev/null +++ b/editor/icons/FixedSize.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/editor/icons/KeepAspect.svg b/editor/icons/KeepAspect.svg index 1cc831563fc..f178a2aa879 100644 --- a/editor/icons/KeepAspect.svg +++ b/editor/icons/KeepAspect.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/editor/icons/Stretch.svg b/editor/icons/Stretch.svg new file mode 100644 index 00000000000..f8cb75fe864 --- /dev/null +++ b/editor/icons/Stretch.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/editor/plugins/embedded_process.cpp b/editor/plugins/embedded_process.cpp index 06c207ca794..10c7188b5c5 100644 --- a/editor/plugins/embedded_process.cpp +++ b/editor/plugins/embedded_process.cpp @@ -50,7 +50,7 @@ void EmbeddedProcess::_notification(int p_what) { Rect2i new_global_rect = get_global_rect(); if (last_global_rect != new_global_rect) { last_global_rect = new_global_rect; - _queue_update_embedded_process(); + queue_update_embedded_process(); } } break; @@ -60,7 +60,7 @@ void EmbeddedProcess::_notification(int p_what) { case NOTIFICATION_RESIZED: case NOTIFICATION_VISIBILITY_CHANGED: case NOTIFICATION_WM_POSITION_CHANGED: { - _queue_update_embedded_process(); + queue_update_embedded_process(); } break; case NOTIFICATION_THEME_CHANGED: { focus_style_box = get_theme_stylebox(SNAME("FocusViewport"), EditorStringName(EditorStyles)); @@ -77,7 +77,7 @@ void EmbeddedProcess::_notification(int p_what) { } } break; case NOTIFICATION_FOCUS_ENTER: { - _queue_update_embedded_process(); + queue_update_embedded_process(); } break; case NOTIFICATION_APPLICATION_FOCUS_IN: { application_has_focus = true; @@ -88,7 +88,7 @@ void EmbeddedProcess::_notification(int p_what) { // or if the current window is a different popup or secondary window. if (embedding_completed && current_process_id != focused_process_id && window && window->has_focus()) { grab_focus(); - _queue_update_embedded_process(); + queue_update_embedded_process(); } } } break; @@ -102,27 +102,33 @@ void EmbeddedProcess::_notification(int p_what) { void EmbeddedProcess::set_window_size(const Size2i p_window_size) { if (window_size != p_window_size) { window_size = p_window_size; - _queue_update_embedded_process(); + queue_update_embedded_process(); } } void EmbeddedProcess::set_keep_aspect(bool p_keep_aspect) { if (keep_aspect != p_keep_aspect) { keep_aspect = p_keep_aspect; - _queue_update_embedded_process(); + queue_update_embedded_process(); } } Rect2i EmbeddedProcess::_get_global_embedded_window_rect() { Rect2i control_rect = get_global_rect(); - control_rect = Rect2i(control_rect.position, control_rect.size.maxi(1)); - if (keep_aspect) { - Rect2i desired_rect = control_rect; - float ratio = MIN((float)control_rect.size.x / window_size.x, (float)control_rect.size.y / window_size.y); - desired_rect.size = Size2i(window_size.x * ratio, window_size.y * ratio).maxi(1); + control_rect = Rect2i(control_rect.position + margin_top_left, (control_rect.size - get_margins_size()).maxi(1)); + if (window_size != Size2i()) { + Rect2i desired_rect = Rect2i(); + if (!keep_aspect && control_rect.size.x >= window_size.x && control_rect.size.y >= window_size.y) { + // Fixed at the desired size. + desired_rect.size = window_size; + } else { + float ratio = MIN((float)control_rect.size.x / window_size.x, (float)control_rect.size.y / window_size.y); + desired_rect.size = Size2i(window_size.x * ratio, window_size.y * ratio).maxi(1); + } desired_rect.position = Size2i(control_rect.position.x + ((control_rect.size.x - desired_rect.size.x) / 2), control_rect.position.y + ((control_rect.size.y - desired_rect.size.y) / 2)); return desired_rect; } else { + // Stretch, use all the control area. return control_rect; } } @@ -133,8 +139,28 @@ Rect2i EmbeddedProcess::get_screen_embedded_window_rect() { rect.position += window->get_position(); } - // Removing margins to make space for the focus border style. - return Rect2i(rect.position.x + margin_top_left.x, rect.position.y + margin_top_left.y, MAX(rect.size.x - (margin_top_left.x + margin_bottom_right.x), 1), MAX(rect.size.y - (margin_top_left.y + margin_bottom_right.y), 1)); + return rect; +} + +int EmbeddedProcess::get_margin_size(Side p_side) const { + ERR_FAIL_INDEX_V((int)p_side, 4, 0); + + switch (p_side) { + case SIDE_LEFT: + return margin_top_left.x; + case SIDE_RIGHT: + return margin_bottom_right.x; + case SIDE_TOP: + return margin_top_left.y; + case SIDE_BOTTOM: + return margin_bottom_right.y; + } + + return 0; +} + +Size2 EmbeddedProcess::get_margins_size() { + return margin_top_left + margin_bottom_right; } bool EmbeddedProcess::is_embedding_in_progress() { @@ -215,7 +241,7 @@ bool EmbeddedProcess::_is_embedded_process_updatable() { return window && current_process_id != 0 && embedding_completed; } -void EmbeddedProcess::_queue_update_embedded_process() { +void EmbeddedProcess::queue_update_embedded_process() { if (updated_embedded_process_queued || !_is_embedded_process_updatable()) { return; } @@ -287,7 +313,7 @@ void EmbeddedProcess::_check_mouse_over() { // When we already have the focus and the user moves the mouse over the embedded process, // we just need to refocus the process. if (focused) { - _queue_update_embedded_process(); + queue_update_embedded_process(); } else { grab_focus(); queue_redraw(); diff --git a/editor/plugins/embedded_process.h b/editor/plugins/embedded_process.h index 444cac1786b..14ad44fdaed 100644 --- a/editor/plugins/embedded_process.h +++ b/editor/plugins/embedded_process.h @@ -59,7 +59,6 @@ class EmbeddedProcess : public Control { Rect2i last_global_rect; void _try_embed_process(); - void _queue_update_embedded_process(); void _update_embedded_process(); void _timer_embedding_timeout(); void _draw(); @@ -78,8 +77,11 @@ class EmbeddedProcess : public Control { void set_window_size(const Size2i p_window_size); void set_keep_aspect(bool p_keep_aspect); + void queue_update_embedded_process(); Rect2i get_screen_embedded_window_rect(); + int get_margin_size(Side p_side) const; + Size2 get_margins_size(); bool is_embedding_in_progress(); bool is_embedding_completed(); int get_embedded_pid() const; diff --git a/editor/plugins/game_view_plugin.cpp b/editor/plugins/game_view_plugin.cpp index b1f7a8498a3..1c1b39ffbaa 100644 --- a/editor/plugins/game_view_plugin.cpp +++ b/editor/plugins/game_view_plugin.cpp @@ -41,6 +41,7 @@ #include "editor/editor_main_screen.h" #include "editor/editor_node.h" #include "editor/editor_settings.h" +#include "editor/editor_string_names.h" #include "editor/gui/editor_run_bar.h" #include "editor/plugins/embedded_process.h" #include "editor/themes/editor_scale.h" @@ -222,18 +223,49 @@ void GameView::_instance_starting(int p_idx, List &r_arguments) { if (!is_feature_enabled) { return; } - if (p_idx == 0 && embed_on_play && make_floating_on_play && !window_wrapper->get_window_enabled() && EditorNode::get_singleton()->is_multi_window_enabled()) { + if (p_idx == 0 && embed_on_play && make_floating_on_play && !window_wrapper->get_window_enabled() && EditorNode::get_singleton()->is_multi_window_enabled() && _get_embed_available() == EMBED_AVAILABLE) { // Set the Floating Window default title. Always considered in DEBUG mode, same as in Window::set_title. String appname = GLOBAL_GET("application/config/name"); appname = vformat("%s (DEBUG)", TranslationServer::get_singleton()->translate(appname)); window_wrapper->set_window_title(appname); - window_wrapper->restore_window_from_saved_position(floating_window_rect, floating_window_screen, floating_window_screen_rect); + _show_update_window_wrapper(); } _update_arguments_for_instance(p_idx, r_arguments); } +void GameView::_show_update_window_wrapper() { + EditorRun::WindowPlacement placement = EditorRun::get_window_placement(); + Point2 position = floating_window_rect.position; + Size2i size = floating_window_rect.size; + int screen = floating_window_screen; + + Size2 wrapped_margins_size = window_wrapper->get_margins_size(); + Point2 offset_embedded_process = embedded_process->get_global_position() - get_global_position(); + offset_embedded_process.x += embedded_process->get_margin_size(SIDE_LEFT); + offset_embedded_process.y += embedded_process->get_margin_size(SIDE_TOP); + + // Obtain the size around the embedded process control. Usually, the difference between the game view's get_size + // and the embedded control should work. However, when the control is hidden and has never been displayed, + // the size of the embedded control is not calculated. + Size2 old_min_size = embedded_process->get_custom_minimum_size(); + embedded_process->set_custom_minimum_size(Size2i()); + Size2 min_size = get_minimum_size(); + embedded_process->set_custom_minimum_size(old_min_size); + + Point2 size_diff_embedded_process = Point2(0, min_size.y) + embedded_process->get_margins_size(); + + if (placement.position != Point2i(INT_MAX, INT_MAX)) { + position = placement.position - offset_embedded_process; + screen = placement.screen; + } + if (placement.size != Size2i()) { + size = placement.size + size_diff_embedded_process + wrapped_margins_size; + } + window_wrapper->restore_window_from_saved_position(Rect2(position, size), screen, Rect2i()); +} + void GameView::_play_pressed() { if (!is_feature_enabled) { return; @@ -248,7 +280,7 @@ void GameView::_play_pressed() { screen_index_before_start = EditorNode::get_singleton()->get_editor_main_screen()->get_selected_index(); } - if (embed_on_play) { + if (embed_on_play && _get_embed_available() == EMBED_AVAILABLE) { // It's important to disable the low power mode when unfocused because otherwise // the button in the editor are not responsive and if the user moves the mouse quickly, // the mouse clicks are not registered. @@ -306,9 +338,18 @@ void GameView::_embedded_process_focused() { } } -void GameView::_project_settings_changed() { +void GameView::_editor_or_project_settings_changed() { // Update the window size and aspect ratio. _update_embed_window_size(); + + if (window_wrapper->get_window_enabled()) { + _show_update_window_wrapper(); + if (embedded_process->is_embedding_completed()) { + embedded_process->queue_update_embedded_process(); + } + } + + _update_ui(); } void GameView::_update_debugger_buttons() { @@ -368,27 +409,82 @@ void GameView::_embed_options_menu_menu_id_pressed(int p_id) { } break; } _update_embed_menu_options(); + _update_ui(); } -void GameView::_keep_aspect_button_pressed() { - embedded_process->set_keep_aspect(keep_aspect_button->is_pressed()); +void GameView::_size_mode_button_pressed(int size_mode) { + embed_size_mode = (EmbedSizeMode)size_mode; + + _update_embed_menu_options(); + _update_embed_window_size(); +} + +GameView::EmbedAvailability GameView::_get_embed_available() { + if (!DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_WINDOW_EMBEDDING)) { + return EMBED_NOT_AVAILABLE_FEATURE_NOT_SUPPORTED; + } + + EditorRun::WindowPlacement placement = EditorRun::get_window_placement(); + if (placement.force_fullscreen) { + return EMBED_NOT_AVAILABLE_FULLSCREEN; + } + if (placement.force_maximized) { + return EMBED_NOT_AVAILABLE_MAXIMIZED; + } + + DisplayServer::WindowMode window_mode = (DisplayServer::WindowMode)(GLOBAL_GET("display/window/size/mode").operator int()); + if (window_mode == DisplayServer::WindowMode::WINDOW_MODE_MINIMIZED) { + return EMBED_NOT_AVAILABLE_MINIMIZED; + } + if (window_mode == DisplayServer::WindowMode::WINDOW_MODE_MAXIMIZED) { + return EMBED_NOT_AVAILABLE_MAXIMIZED; + } + if (window_mode == DisplayServer::WindowMode::WINDOW_MODE_FULLSCREEN || window_mode == DisplayServer::WindowMode::WINDOW_MODE_EXCLUSIVE_FULLSCREEN) { + return EMBED_NOT_AVAILABLE_FULLSCREEN; + } + + return EMBED_AVAILABLE; } void GameView::_update_ui() { bool show_game_size = false; - if (!DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_WINDOW_EMBEDDING)) { - state_label->set_text(TTR("Game embedding not available on your OS.")); - } else if (embedded_process->is_embedding_completed()) { - state_label->set_text(""); - show_game_size = true; - } else if (embedded_process->is_embedding_in_progress()) { - state_label->set_text(TTR("Game starting...")); - } else if (EditorRunBar::get_singleton()->is_playing()) { - state_label->set_text(TTR("Game running not embedded.")); - } else if (embed_on_play) { - state_label->set_text(TTR("Press play to start the game.")); + EmbedAvailability available = _get_embed_available(); + + switch (available) { + case EMBED_AVAILABLE: + if (embedded_process->is_embedding_completed()) { + state_label->set_text(""); + show_game_size = true; + } else if (embedded_process->is_embedding_in_progress()) { + state_label->set_text(TTR("Game starting...")); + } else if (EditorRunBar::get_singleton()->is_playing()) { + state_label->set_text(TTR("Game running not embedded.")); + } else if (embed_on_play) { + state_label->set_text(TTR("Press play to start the game.")); + } else { + state_label->set_text(TTR("Embedding is disabled.")); + } + break; + case EMBED_NOT_AVAILABLE_FEATURE_NOT_SUPPORTED: + state_label->set_text(TTR("Game embedding not available on your OS.")); + break; + case EMBED_NOT_AVAILABLE_MINIMIZED: + state_label->set_text(TTR("Game embedding not available when the game starts minimized.\nConsider overriding the window mode project setting with the editor feature tag to Windowed to use game embedding while leaving the exported project intact.")); + break; + case EMBED_NOT_AVAILABLE_MAXIMIZED: + state_label->set_text(TTR("Game embedding not available when the game starts maximized.\nConsider overriding the window mode project setting with the editor feature tag to Windowed to use game embedding while leaving the exported project intact.")); + break; + case EMBED_NOT_AVAILABLE_FULLSCREEN: + state_label->set_text(TTR("Game embedding not available when the game starts in fullscreen.\nConsider overriding the window mode project setting with the editor feature tag to Windowed to use game embedding while leaving the exported project intact.")); + break; + } + + if (available == EMBED_AVAILABLE) { + if (state_label->has_theme_color_override(SceneStringName(font_color))) { + state_label->remove_theme_color_override(SceneStringName(font_color)); + } } else { - state_label->set_text(TTR("Embedding is disabled.")); + state_label->add_theme_color_override(SceneStringName(font_color), state_label->get_theme_color(SNAME("warning_color"), EditorStringName(Editor))); } game_size_label->set_visible(show_game_size); @@ -401,20 +497,22 @@ void GameView::_update_embed_menu_options() { // When embed is Off or in single window mode, Make floating is not available. menu->set_item_disabled(menu->get_item_index(EMBED_MAKE_FLOATING_ON_PLAY), !embed_on_play || !EditorNode::get_singleton()->is_multi_window_enabled()); + + fixed_size_button->set_pressed(embed_size_mode == SIZE_MODE_FIXED); + keep_aspect_button->set_pressed(embed_size_mode == SIZE_MODE_KEEP_ASPECT); + stretch_button->set_pressed(embed_size_mode == SIZE_MODE_STRETCH); } void GameView::_update_embed_window_size() { - Size2 window_size; - window_size.x = GLOBAL_GET("display/window/size/viewport_width"); - window_size.y = GLOBAL_GET("display/window/size/viewport_height"); - - Size2 desired_size; - desired_size.x = GLOBAL_GET("display/window/size/window_width_override"); - desired_size.y = GLOBAL_GET("display/window/size/window_height_override"); - if (desired_size.x > 0 && desired_size.y > 0) { - window_size = desired_size; + if (embed_size_mode == SIZE_MODE_FIXED || embed_size_mode == SIZE_MODE_KEEP_ASPECT) { + //The embedded process control will need the desired window size. + EditorRun::WindowPlacement placement = EditorRun::get_window_placement(); + embedded_process->set_window_size(placement.size); + } else { + //Stretch... No need for the window size. + embedded_process->set_window_size(Size2i()); } - embedded_process->set_window_size(window_size); + embedded_process->set_keep_aspect(embed_size_mode == SIZE_MODE_KEEP_ASPECT); } void GameView::_hide_selection_toggled(bool p_pressed) { @@ -475,7 +573,9 @@ void GameView::_notification(int p_what) { select_mode_button[RuntimeNodeSelect::SELECT_MODE_LIST]->set_button_icon(get_editor_theme_icon(SNAME("ListSelect"))); hide_selection->set_button_icon(get_editor_theme_icon(hide_selection->is_pressed() ? SNAME("GuiVisibilityHidden") : SNAME("GuiVisibilityVisible"))); + fixed_size_button->set_button_icon(get_editor_theme_icon(SNAME("FixedSize"))); keep_aspect_button->set_button_icon(get_editor_theme_icon(SNAME("KeepAspect"))); + stretch_button->set_button_icon(get_editor_theme_icon(SNAME("Stretch"))); embed_options_menu->set_button_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl"))); camera_override_button->set_button_icon(get_editor_theme_icon(SNAME("Camera"))); @@ -487,7 +587,7 @@ void GameView::_notification(int p_what) { // Embedding available. embed_on_play = EditorSettings::get_singleton()->get_project_metadata("game_view", "embed_on_play", true); make_floating_on_play = EditorSettings::get_singleton()->get_project_metadata("game_view", "make_floating_on_play", true); - keep_aspect_button->set_pressed(EditorSettings::get_singleton()->get_project_metadata("game_view", "keep_aspect", true)); + embed_size_mode = (EmbedSizeMode)(int)EditorSettings::get_singleton()->get_project_metadata("game_view", "embed_size_mode", SIZE_MODE_FIXED); _update_embed_menu_options(); EditorRunBar::get_singleton()->connect("play_pressed", callable_mp(this, &GameView::_play_pressed)); @@ -495,15 +595,15 @@ void GameView::_notification(int p_what) { EditorRun::instance_starting_callback = _instance_starting_static; // Listen for project settings changes to update the window size and aspect ratio. - ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &GameView::_project_settings_changed)); - - embedded_process->set_keep_aspect(keep_aspect_button->is_pressed()); + ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &GameView::_editor_or_project_settings_changed)); + EditorSettings::get_singleton()->connect("settings_changed", callable_mp(this, &GameView::_editor_or_project_settings_changed)); } else { // Embedding not available. embedding_separator->hide(); embed_options_menu->hide(); + fixed_size_button->hide(); keep_aspect_button->hide(); - keep_aspect_button->hide(); + stretch_button->hide(); } _update_ui(); @@ -558,7 +658,6 @@ Dictionary GameView::get_state() const { void GameView::set_window_layout(Ref p_layout) { floating_window_rect = p_layout->get_value("GameView", "floating_window_rect", Rect2i()); floating_window_screen = p_layout->get_value("GameView", "floating_window_screen", -1); - floating_window_screen_rect = p_layout->get_value("GameView", "floating_window_screen_rect", Rect2i()); } void GameView::get_window_layout(Ref p_layout) { @@ -568,14 +667,12 @@ void GameView::get_window_layout(Ref p_layout) { p_layout->set_value("GameView", "floating_window_rect", floating_window_rect); p_layout->set_value("GameView", "floating_window_screen", floating_window_screen); - p_layout->set_value("GameView", "floating_window_screen_rect", floating_window_screen_rect); } void GameView::_update_floating_window_settings() { if (window_wrapper->get_window_enabled()) { floating_window_rect = window_wrapper->get_window_rect(); floating_window_screen = window_wrapper->get_window_screen(); - floating_window_screen_rect = DisplayServer::get_singleton()->screen_get_usable_rect(floating_window_screen); } } @@ -610,7 +707,7 @@ void GameView::_remote_window_title_changed(String title) { } void GameView::_update_arguments_for_instance(int p_idx, List &r_arguments) { - if (p_idx != 0 || !embed_on_play || !DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_WINDOW_EMBEDDING)) { + if (p_idx != 0 || !embed_on_play || _get_embed_available() != EMBED_AVAILABLE) { return; } @@ -778,12 +875,26 @@ GameView::GameView(Ref p_debugger, WindowWrapper *p_wrapper) { embedding_separator = memnew(VSeparator); main_menu_hbox->add_child(embedding_separator); + fixed_size_button = memnew(Button); + main_menu_hbox->add_child(fixed_size_button); + fixed_size_button->set_toggle_mode(true); + fixed_size_button->set_theme_type_variation("FlatButton"); + fixed_size_button->set_tooltip_text(TTR("Embedded game size is based on project settings.\nThe 'Keep Aspect' mode is used when the Game Workspace is smaller than the desired size.")); + fixed_size_button->connect(SceneStringName(pressed), callable_mp(this, &GameView::_size_mode_button_pressed).bind(SIZE_MODE_FIXED)); + keep_aspect_button = memnew(Button); main_menu_hbox->add_child(keep_aspect_button); keep_aspect_button->set_toggle_mode(true); keep_aspect_button->set_theme_type_variation("FlatButton"); keep_aspect_button->set_tooltip_text(TTR("Keep the aspect ratio of the embedded game.")); - keep_aspect_button->connect(SceneStringName(pressed), callable_mp(this, &GameView::_keep_aspect_button_pressed)); + keep_aspect_button->connect(SceneStringName(pressed), callable_mp(this, &GameView::_size_mode_button_pressed).bind(SIZE_MODE_KEEP_ASPECT)); + + stretch_button = memnew(Button); + main_menu_hbox->add_child(stretch_button); + stretch_button->set_toggle_mode(true); + stretch_button->set_theme_type_variation("FlatButton"); + stretch_button->set_tooltip_text(TTR("Embedded game size stretches to fit the Game Workspace.")); + stretch_button->connect(SceneStringName(pressed), callable_mp(this, &GameView::_size_mode_button_pressed).bind(SIZE_MODE_STRETCH)); embed_options_menu = memnew(MenuButton); main_menu_hbox->add_child(embed_options_menu); @@ -817,8 +928,14 @@ GameView::GameView(Ref p_debugger, WindowWrapper *p_wrapper) { embedded_process->connect("embedded_process_focused", callable_mp(this, &GameView::_embedded_process_focused)); embedded_process->set_custom_minimum_size(Size2i(100, 100)); + MarginContainer *state_container = memnew(MarginContainer); + state_container->add_theme_constant_override("margin_left", 8 * EDSCALE); + state_container->add_theme_constant_override("margin_right", 8 * EDSCALE); + state_container->set_anchors_and_offsets_preset(PRESET_FULL_RECT); + panel->add_child(state_container); + state_label = memnew(Label()); - panel->add_child(state_label); + state_container->add_child(state_label); state_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); state_label->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER); state_label->set_autowrap_mode(TextServer::AUTOWRAP_WORD); diff --git a/editor/plugins/game_view_plugin.h b/editor/plugins/game_view_plugin.h index 959da73dcde..7d34b050049 100644 --- a/editor/plugins/game_view_plugin.h +++ b/editor/plugins/game_view_plugin.h @@ -94,6 +94,20 @@ class GameView : public VBoxContainer { EMBED_MAKE_FLOATING_ON_PLAY, }; + enum EmbedSizeMode { + SIZE_MODE_FIXED, + SIZE_MODE_KEEP_ASPECT, + SIZE_MODE_STRETCH, + }; + + enum EmbedAvailability { + EMBED_AVAILABLE, + EMBED_NOT_AVAILABLE_FEATURE_NOT_SUPPORTED, + EMBED_NOT_AVAILABLE_MINIMIZED, + EMBED_NOT_AVAILABLE_MAXIMIZED, + EMBED_NOT_AVAILABLE_FULLSCREEN, + }; + inline static GameView *singleton = nullptr; Ref debugger; @@ -106,10 +120,10 @@ class GameView : public VBoxContainer { bool embed_on_play = true; bool make_floating_on_play = true; + EmbedSizeMode embed_size_mode = SIZE_MODE_FIXED; Rect2i floating_window_rect; int floating_window_screen = -1; - Rect2i floating_window_screen_rect; Button *suspend_button = nullptr; Button *next_frame_button = nullptr; @@ -123,10 +137,11 @@ class GameView : public VBoxContainer { MenuButton *camera_override_menu = nullptr; VSeparator *embedding_separator = nullptr; + Button *fixed_size_button = nullptr; Button *keep_aspect_button = nullptr; + Button *stretch_button = nullptr; MenuButton *embed_options_menu = nullptr; Label *game_size_label = nullptr; - Panel *panel = nullptr; EmbeddedProcess *embedded_process = nullptr; Label *state_label = nullptr; @@ -140,7 +155,7 @@ class GameView : public VBoxContainer { void _node_type_pressed(int p_option); void _select_mode_pressed(int p_option); void _embed_options_menu_menu_id_pressed(int p_id); - void _keep_aspect_button_pressed(); + void _size_mode_button_pressed(int size_mode); void _play_pressed(); static void _instance_starting_static(int p_idx, List &r_arguments); @@ -150,12 +165,14 @@ class GameView : public VBoxContainer { void _embedding_failed(); void _embedded_process_updated(); void _embedded_process_focused(); - void _project_settings_changed(); + void _editor_or_project_settings_changed(); + EmbedAvailability _get_embed_available(); void _update_ui(); void _update_embed_menu_options(); void _update_embed_window_size(); void _update_arguments_for_instance(int p_idx, List &r_arguments); + void _show_update_window_wrapper(); void _hide_selection_toggled(bool p_pressed); diff --git a/editor/window_wrapper.cpp b/editor/window_wrapper.cpp index 37a3efc91e9..14cc10de1aa 100644 --- a/editor/window_wrapper.cpp +++ b/editor/window_wrapper.cpp @@ -324,6 +324,14 @@ void WindowWrapper::set_margins_enabled(bool p_enabled) { } } +Size2 WindowWrapper::get_margins_size() { + if (!margins) { + return Size2(); + } + + return Size2(margins->get_margin_size(SIDE_LEFT) + margins->get_margin_size(SIDE_RIGHT), margins->get_margin_size(SIDE_TOP) + margins->get_margin_size(SIDE_RIGHT)); +} + void WindowWrapper::grab_window_focus() { if (get_window_enabled() && is_visible()) { window->grab_focus(); diff --git a/editor/window_wrapper.h b/editor/window_wrapper.h index 3f4e9385861..92dffabf2c8 100644 --- a/editor/window_wrapper.h +++ b/editor/window_wrapper.h @@ -82,6 +82,7 @@ class WindowWrapper : public MarginContainer { void set_window_title(const String &p_title); void set_margins_enabled(bool p_enabled); + Size2 get_margins_size(); void grab_window_focus(); WindowWrapper(); From 9eed43d4298273f9d98179ac4d0c9504c47c4ffb Mon Sep 17 00:00:00 2001 From: Hilderin <81109165+Hilderin@users.noreply.github.com> Date: Tue, 21 Jan 2025 19:12:27 -0500 Subject: [PATCH 11/23] Fix NOTIFICATION_WM_CLOSE_REQUEST in Embedded Floating Window --- editor/plugins/game_view_plugin.cpp | 17 ++++++++++----- editor/plugins/game_view_plugin.h | 2 +- editor/window_wrapper.cpp | 23 ++++++++++++-------- editor/window_wrapper.h | 5 +++++ platform/linuxbsd/x11/display_server_x11.cpp | 18 +++++++++++++++ platform/windows/display_server_windows.cpp | 3 +++ 6 files changed, 53 insertions(+), 15 deletions(-) diff --git a/editor/plugins/game_view_plugin.cpp b/editor/plugins/game_view_plugin.cpp index b1f7a8498a3..f0f1a915f01 100644 --- a/editor/plugins/game_view_plugin.cpp +++ b/editor/plugins/game_view_plugin.cpp @@ -652,14 +652,20 @@ void GameView::_update_arguments_for_instance(int p_idx, List &r_argumen r_arguments.insert_after(N, itos(rect.size.x) + "x" + itos(rect.size.y)); } -void GameView::_window_before_closing() { +void GameView::_window_close_request() { // Before the parent window closed, we close the embedded game. That prevents // the embedded game to be seen without a parent window for a fraction of second. if (EditorRunBar::get_singleton()->is_playing() && (embedded_process->is_embedding_completed() || embedded_process->is_embedding_in_progress())) { + // Try to gracefully close the window. That way, the NOTIFICATION_WM_CLOSE_REQUEST + // notification should be propagated in the game process. embedded_process->reset(); - // Call deferred to prevent the _stop_pressed callback to be executed before the wrapper window - // actually closes. - callable_mp(EditorRunBar::get_singleton(), &EditorRunBar::stop_playing).call_deferred(); + + // When the embedding is not complete, we need to kill the process. + if (embedded_process->is_embedding_in_progress()) { + // Call deferred to prevent the _stop_pressed callback to be executed before the wrapper window + // actually closes. + callable_mp(EditorRunBar::get_singleton(), &EditorRunBar::stop_playing).call_deferred(); + } } } @@ -829,7 +835,8 @@ GameView::GameView(Ref p_debugger, WindowWrapper *p_wrapper) { p_debugger->connect("session_started", callable_mp(this, &GameView::_sessions_changed)); p_debugger->connect("session_stopped", callable_mp(this, &GameView::_sessions_changed)); - p_wrapper->connect("window_before_closing", callable_mp(this, &GameView::_window_before_closing)); + p_wrapper->set_override_close_request(true); + p_wrapper->connect("window_close_requested", callable_mp(this, &GameView::_window_close_request)); p_wrapper->connect("window_size_changed", callable_mp(this, &GameView::_update_floating_window_settings)); } diff --git a/editor/plugins/game_view_plugin.h b/editor/plugins/game_view_plugin.h index 959da73dcde..273652dae82 100644 --- a/editor/plugins/game_view_plugin.h +++ b/editor/plugins/game_view_plugin.h @@ -162,7 +162,7 @@ class GameView : public VBoxContainer { void _camera_override_button_toggled(bool p_pressed); void _camera_override_menu_id_pressed(int p_id); - void _window_before_closing(); + void _window_close_request(); void _update_floating_window_settings(); void _attach_script_debugger(); void _detach_script_debugger(); diff --git a/editor/window_wrapper.cpp b/editor/window_wrapper.cpp index 37a3efc91e9..22e2fda6856 100644 --- a/editor/window_wrapper.cpp +++ b/editor/window_wrapper.cpp @@ -101,12 +101,6 @@ void WindowWrapper::_set_window_enabled_with_rect(bool p_visible, const Rect2 p_ Node *parent = _get_wrapped_control_parent(); - // In the GameView plugin, we need to the the signal before the window is actually closed - // to prevent the embedded game to be seen the parent window for a fraction of a second. - if (!p_visible) { - emit_signal("window_before_closing"); - } - if (wrapped_control->get_parent() != parent) { // Move the control to the window. wrapped_control->reparent(parent, false); @@ -120,7 +114,7 @@ void WindowWrapper::_set_window_enabled_with_rect(bool p_visible, const Rect2 p_ } window->set_visible(p_visible); - if (!p_visible) { + if (!p_visible && !override_close_request) { emit_signal("window_close_requested"); } emit_signal("window_visibility_changed", p_visible); @@ -141,10 +135,17 @@ void WindowWrapper::_window_size_changed() { emit_signal(SNAME("window_size_changed")); } +void WindowWrapper::_window_close_request() { + if (override_close_request) { + emit_signal("window_close_requested"); + } else { + set_window_enabled(false); + } +} + void WindowWrapper::_bind_methods() { ADD_SIGNAL(MethodInfo("window_visibility_changed", PropertyInfo(Variant::BOOL, "visible"))); ADD_SIGNAL(MethodInfo("window_close_requested")); - ADD_SIGNAL(MethodInfo("window_before_closing")); ADD_SIGNAL(MethodInfo("window_size_changed")); } @@ -330,6 +331,10 @@ void WindowWrapper::grab_window_focus() { } } +void WindowWrapper::set_override_close_request(bool p_enabled) { + override_close_request = p_enabled; +} + WindowWrapper::WindowWrapper() { if (!EditorNode::get_singleton()->is_multi_window_enabled()) { return; @@ -342,7 +347,7 @@ WindowWrapper::WindowWrapper() { add_child(window); window->hide(); - window->connect("close_requested", callable_mp(this, &WindowWrapper::set_window_enabled).bind(false)); + window->connect("close_requested", callable_mp(this, &WindowWrapper::_window_close_request)); window->connect("size_changed", callable_mp(this, &WindowWrapper::_window_size_changed)); ShortcutBin *capturer = memnew(ShortcutBin); diff --git a/editor/window_wrapper.h b/editor/window_wrapper.h index 3f4e9385861..e33c8c179d5 100644 --- a/editor/window_wrapper.h +++ b/editor/window_wrapper.h @@ -50,12 +50,15 @@ class WindowWrapper : public MarginContainer { Ref enable_shortcut; + bool override_close_request = false; + Rect2 _get_default_window_rect() const; Node *_get_wrapped_control_parent() const; void _set_window_enabled_with_rect(bool p_visible, const Rect2 p_rect); void _set_window_rect(const Rect2 p_rect); void _window_size_changed(); + void _window_close_request(); protected: static void _bind_methods(); @@ -84,6 +87,8 @@ class WindowWrapper : public MarginContainer { void set_margins_enabled(bool p_enabled); void grab_window_focus(); + void set_override_close_request(bool p_enabled); + WindowWrapper(); ~WindowWrapper(); }; diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp index b17f72dc5c0..b1945eb47a2 100644 --- a/platform/linuxbsd/x11/display_server_x11.cpp +++ b/platform/linuxbsd/x11/display_server_x11.cpp @@ -5849,6 +5849,24 @@ Error DisplayServerX11::remove_embedded_process(OS::ProcessID p_pid) { } EmbeddedProcessData *ep = embedded_processes.get(p_pid); + + // Handle bad window errors silently because just in case the embedded window was closed. + int (*oldHandler)(Display *, XErrorEvent *) = XSetErrorHandler(&bad_window_error_handler); + + // Send the message to gracefully close the window. + XEvent ev; + memset(&ev, 0, sizeof(ev)); + ev.xclient.type = ClientMessage; + ev.xclient.window = ep->process_window; + ev.xclient.message_type = XInternAtom(x11_display, "WM_PROTOCOLS", True); + ev.xclient.format = 32; + ev.xclient.data.l[0] = XInternAtom(x11_display, "WM_DELETE_WINDOW", False); + ev.xclient.data.l[1] = CurrentTime; + XSendEvent(x11_display, ep->process_window, False, NoEventMask, &ev); + + // Restore default error handler. + XSetErrorHandler(oldHandler); + embedded_processes.erase(p_pid); memdelete(ep); diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index ac7fa8b3c69..1d3a6f0ca4f 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -2975,6 +2975,9 @@ Error DisplayServerWindows::remove_embedded_process(OS::ProcessID p_pid) { EmbeddedProcessData *ep = embedded_processes.get(p_pid); + // Send a close message to gracefully close the process. + PostMessage(ep->window_handle, WM_CLOSE, 0, 0); + // This is a workaround to ensure the parent window correctly regains focus after the // embedded window is closed. When the embedded window is closed while it has focus, // the parent window (the editor) does not become active. It appears focused but is not truly activated. From 660b5af677fcc0b8bfcee57626f2c66aee080173 Mon Sep 17 00:00:00 2001 From: Giganzo <158825920+Giganzo@users.noreply.github.com> Date: Wed, 22 Jan 2025 16:33:53 +0100 Subject: [PATCH 12/23] Fix ColorPicker Swatches button width --- scene/gui/color_picker.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index 6eb65a008cc..90b7b2b2b76 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -2365,13 +2365,10 @@ ColorPicker::ColorPicker() { btn_preset->set_toggle_mode(true); btn_preset->set_focus_mode(FOCUS_NONE); btn_preset->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT); + btn_preset->set_h_size_flags(SIZE_EXPAND_FILL); btn_preset->connect(SceneStringName(toggled), callable_mp(this, &ColorPicker::_show_hide_preset).bind(btn_preset, preset_container)); palette_box->add_child(btn_preset); - HBoxContainer *padding_box = memnew(HBoxContainer); - padding_box->set_h_size_flags(SIZE_EXPAND_FILL); - palette_box->add_child(padding_box); - menu_btn = memnew(Button); menu_btn->set_flat(true); menu_btn->set_tooltip_text(ETR("Show all options available.")); From 8ddd4985ea8ea60c209fbe9bbd8a5ee8afeebb06 Mon Sep 17 00:00:00 2001 From: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Wed, 8 Jan 2025 12:17:35 +0200 Subject: [PATCH 13/23] [Editor] Add editor setting to globally override project game mode settings. --- doc/classes/EditorSettings.xml | 3 +++ editor/editor_settings.cpp | 2 ++ editor/plugins/game_view_plugin.cpp | 32 +++++++++++++++++++++++++---- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml index fcabcf14600..3900a81502c 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -1136,6 +1136,9 @@ - [b]Launch in PiP mode[/b] will launch the Play window directly in picture-in-picture (PiP) mode if PiP mode is supported and enabled. When maximized, the Play window will occupy the same window as the Editor. [b]Note:[/b] Only available in the Android editor. + + Overrides game embedding setting for all newly opened projects. If enabled, game embedding settings are not saved. + Specifies the picture-in-picture (PiP) mode for the Play window. - [b]Disabled:[/b] PiP is disabled for the Play window. diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index 11695051439..9a5a51a18a2 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -940,6 +940,8 @@ void EditorSettings::_load_defaults(Ref p_extra_config) { String android_window_hints = "Auto (based on screen size):0,Same as Editor:1,Side-by-side with Editor:2,Launch in PiP mode:3"; EDITOR_SETTING_BASIC(Variant::INT, PROPERTY_HINT_ENUM, "run/window_placement/android_window", 0, android_window_hints) + EDITOR_SETTING_BASIC(Variant::INT, PROPERTY_HINT_ENUM, "run/window_placement/game_embed_mode", 0, "Use Per-Project Configuration:0,Embed Game:1,Make Game Workspace Floating:2,Disabled:3"); + int default_play_window_pip_mode = 0; #ifdef ANDROID_ENABLED default_play_window_pip_mode = 2; diff --git a/editor/plugins/game_view_plugin.cpp b/editor/plugins/game_view_plugin.cpp index ad0f7e636ea..bf5d55c76ef 100644 --- a/editor/plugins/game_view_plugin.cpp +++ b/editor/plugins/game_view_plugin.cpp @@ -401,11 +401,17 @@ void GameView::_embed_options_menu_menu_id_pressed(int p_id) { switch (p_id) { case EMBED_RUN_GAME_EMBEDDED: { embed_on_play = !embed_on_play; - EditorSettings::get_singleton()->set_project_metadata("game_view", "embed_on_play", embed_on_play); + int game_mode = EDITOR_GET("run/window_placement/game_embed_mode"); + if (game_mode == 0) { // Save only if not overridden by editor. + EditorSettings::get_singleton()->set_project_metadata("game_view", "embed_on_play", embed_on_play); + } } break; case EMBED_MAKE_FLOATING_ON_PLAY: { make_floating_on_play = !make_floating_on_play; - EditorSettings::get_singleton()->set_project_metadata("game_view", "make_floating_on_play", make_floating_on_play); + int game_mode = EDITOR_GET("run/window_placement/game_embed_mode"); + if (game_mode == 0) { // Save only if not overridden by editor. + EditorSettings::get_singleton()->set_project_metadata("game_view", "make_floating_on_play", make_floating_on_play); + } } break; } _update_embed_menu_options(); @@ -585,9 +591,27 @@ void GameView::_notification(int p_what) { case NOTIFICATION_READY: { if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_WINDOW_EMBEDDING)) { // Embedding available. - embed_on_play = EditorSettings::get_singleton()->get_project_metadata("game_view", "embed_on_play", true); - make_floating_on_play = EditorSettings::get_singleton()->get_project_metadata("game_view", "make_floating_on_play", true); + int game_mode = EDITOR_GET("run/window_placement/game_embed_mode"); + switch (game_mode) { + case 1: { // Embed. + embed_on_play = true; + make_floating_on_play = false; + } break; + case 2: { // Floating. + embed_on_play = true; + make_floating_on_play = true; + } break; + case 3: { // Disabled. + embed_on_play = false; + make_floating_on_play = false; + } break; + default: { + embed_on_play = EditorSettings::get_singleton()->get_project_metadata("game_view", "embed_on_play", true); + make_floating_on_play = EditorSettings::get_singleton()->get_project_metadata("game_view", "make_floating_on_play", true); + } break; + } embed_size_mode = (EmbedSizeMode)(int)EditorSettings::get_singleton()->get_project_metadata("game_view", "embed_size_mode", SIZE_MODE_FIXED); + keep_aspect_button->set_pressed(EditorSettings::get_singleton()->get_project_metadata("game_view", "keep_aspect", true)); _update_embed_menu_options(); EditorRunBar::get_singleton()->connect("play_pressed", callable_mp(this, &GameView::_play_pressed)); From 3973c0b64425f589e3c45724ad8a3d3d828991ac Mon Sep 17 00:00:00 2001 From: Travis Lange Date: Wed, 22 Jan 2025 11:29:52 -0500 Subject: [PATCH 14/23] fix gpu_particles_3d randomizing seed when set_one_shot is called --- scene/2d/gpu_particles_2d.cpp | 2 ++ scene/3d/gpu_particles_3d.cpp | 4 ---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/scene/2d/gpu_particles_2d.cpp b/scene/2d/gpu_particles_2d.cpp index 82b8006c384..23e90582809 100644 --- a/scene/2d/gpu_particles_2d.cpp +++ b/scene/2d/gpu_particles_2d.cpp @@ -912,6 +912,8 @@ GPUParticles2D::GPUParticles2D() { one_shot = false; // Needed so that set_emitting doesn't access uninitialized values set_emitting(true); set_one_shot(false); + set_use_fixed_seed(false); + set_seed(0); set_amount(8); set_amount_ratio(1.0); set_lifetime(1); diff --git a/scene/3d/gpu_particles_3d.cpp b/scene/3d/gpu_particles_3d.cpp index 96301d20f68..db2d2f41902 100644 --- a/scene/3d/gpu_particles_3d.cpp +++ b/scene/3d/gpu_particles_3d.cpp @@ -92,10 +92,6 @@ void GPUParticles3D::set_one_shot(bool p_one_shot) { if (is_emitting()) { if (!one_shot) { - if (!use_fixed_seed) { - set_seed(Math::rand()); - } - RenderingServer::get_singleton()->particles_restart(particles); } } From 2221f6ed13d9426db2e146c10c904616771bfb26 Mon Sep 17 00:00:00 2001 From: BlueCube3310 <53150244+BlueCube3310@users.noreply.github.com> Date: Wed, 22 Jan 2025 13:27:19 +0100 Subject: [PATCH 15/23] WebGL2: Fix 2D array textures with RGTC compression not rendering --- drivers/gles3/storage/texture_storage.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/gles3/storage/texture_storage.cpp b/drivers/gles3/storage/texture_storage.cpp index e74747954f5..c1b2784bfc0 100644 --- a/drivers/gles3/storage/texture_storage.cpp +++ b/drivers/gles3/storage/texture_storage.cpp @@ -1509,8 +1509,17 @@ void TextureStorage::_texture_set_data(RID p_texture, const Ref &p_image, GLenum internal_format; bool compressed = false; + bool needs_decompress = texture->resize_to_po2; + + // Support for RGTC-compressed Texture Arrays isn't mandated by GLES3/WebGL. + if (!RasterizerGLES3::is_gles_over_gl() && texture->target == GL_TEXTURE_2D_ARRAY) { + if (p_image->get_format() == Image::FORMAT_RGTC_R || p_image->get_format() == Image::FORMAT_RGTC_RG) { + needs_decompress = true; + } + } + Image::Format real_format; - Ref img = _get_gl_image_and_format(p_image, p_image->get_format(), real_format, format, internal_format, type, compressed, texture->resize_to_po2); + Ref img = _get_gl_image_and_format(p_image, p_image->get_format(), real_format, format, internal_format, type, compressed, needs_decompress); ERR_FAIL_COND(img.is_null()); if (texture->resize_to_po2) { if (p_image->is_compressed()) { From e75f679124aedf2792a0815549559c0130ef624f Mon Sep 17 00:00:00 2001 From: Hilderin <81109165+Hilderin@users.noreply.github.com> Date: Wed, 22 Jan 2025 16:34:01 -0500 Subject: [PATCH 16/23] Disable Game Embedding in Single Window Mode --- editor/plugins/game_view_plugin.cpp | 11 ++++++++--- editor/plugins/game_view_plugin.h | 1 + 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/editor/plugins/game_view_plugin.cpp b/editor/plugins/game_view_plugin.cpp index ad0f7e636ea..9a55a2ece12 100644 --- a/editor/plugins/game_view_plugin.cpp +++ b/editor/plugins/game_view_plugin.cpp @@ -223,7 +223,7 @@ void GameView::_instance_starting(int p_idx, List &r_arguments) { if (!is_feature_enabled) { return; } - if (p_idx == 0 && embed_on_play && make_floating_on_play && !window_wrapper->get_window_enabled() && EditorNode::get_singleton()->is_multi_window_enabled() && _get_embed_available() == EMBED_AVAILABLE) { + if (p_idx == 0 && embed_on_play && make_floating_on_play && !window_wrapper->get_window_enabled() && _get_embed_available() == EMBED_AVAILABLE) { // Set the Floating Window default title. Always considered in DEBUG mode, same as in Window::set_title. String appname = GLOBAL_GET("application/config/name"); appname = vformat("%s (DEBUG)", TranslationServer::get_singleton()->translate(appname)); @@ -423,6 +423,9 @@ GameView::EmbedAvailability GameView::_get_embed_available() { if (!DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_WINDOW_EMBEDDING)) { return EMBED_NOT_AVAILABLE_FEATURE_NOT_SUPPORTED; } + if (!EditorNode::get_singleton()->is_multi_window_enabled()) { + return EMBED_NOT_AVAILABLE_SINGLE_WINDOW_MODE; + } EditorRun::WindowPlacement placement = EditorRun::get_window_placement(); if (placement.force_fullscreen) { @@ -477,6 +480,9 @@ void GameView::_update_ui() { case EMBED_NOT_AVAILABLE_FULLSCREEN: state_label->set_text(TTR("Game embedding not available when the game starts in fullscreen.\nConsider overriding the window mode project setting with the editor feature tag to Windowed to use game embedding while leaving the exported project intact.")); break; + case EMBED_NOT_AVAILABLE_SINGLE_WINDOW_MODE: + state_label->set_text(TTR("Game embedding not available in single window mode.")); + break; } if (available == EMBED_AVAILABLE) { @@ -495,8 +501,7 @@ void GameView::_update_embed_menu_options() { menu->set_item_checked(menu->get_item_index(EMBED_RUN_GAME_EMBEDDED), embed_on_play); menu->set_item_checked(menu->get_item_index(EMBED_MAKE_FLOATING_ON_PLAY), make_floating_on_play); - // When embed is Off or in single window mode, Make floating is not available. - menu->set_item_disabled(menu->get_item_index(EMBED_MAKE_FLOATING_ON_PLAY), !embed_on_play || !EditorNode::get_singleton()->is_multi_window_enabled()); + menu->set_item_disabled(menu->get_item_index(EMBED_MAKE_FLOATING_ON_PLAY), !embed_on_play); fixed_size_button->set_pressed(embed_size_mode == SIZE_MODE_FIXED); keep_aspect_button->set_pressed(embed_size_mode == SIZE_MODE_KEEP_ASPECT); diff --git a/editor/plugins/game_view_plugin.h b/editor/plugins/game_view_plugin.h index c4fb9d37f9c..7402b270e3c 100644 --- a/editor/plugins/game_view_plugin.h +++ b/editor/plugins/game_view_plugin.h @@ -106,6 +106,7 @@ class GameView : public VBoxContainer { EMBED_NOT_AVAILABLE_MINIMIZED, EMBED_NOT_AVAILABLE_MAXIMIZED, EMBED_NOT_AVAILABLE_FULLSCREEN, + EMBED_NOT_AVAILABLE_SINGLE_WINDOW_MODE, }; inline static GameView *singleton = nullptr; From 41afe2de569b842cca8313d2a64534033dd8d245 Mon Sep 17 00:00:00 2001 From: Mikael Hermansson Date: Wed, 22 Jan 2025 23:46:28 +0100 Subject: [PATCH 17/23] Update Jolt Physics `BoxShape` to allow for zero-sized boxes --- .../jolt_physics/Jolt/Physics/Collision/Shape/BoxShape.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/thirdparty/jolt_physics/Jolt/Physics/Collision/Shape/BoxShape.cpp b/thirdparty/jolt_physics/Jolt/Physics/Collision/Shape/BoxShape.cpp index dc7ea482857..fc2281252f0 100644 --- a/thirdparty/jolt_physics/Jolt/Physics/Collision/Shape/BoxShape.cpp +++ b/thirdparty/jolt_physics/Jolt/Physics/Collision/Shape/BoxShape.cpp @@ -59,7 +59,7 @@ BoxShape::BoxShape(const BoxShapeSettings &inSettings, ShapeResult &outResult) : { // Check convex radius if (inSettings.mConvexRadius < 0.0f - || inSettings.mHalfExtent.ReduceMin() <= inSettings.mConvexRadius) + || inSettings.mHalfExtent.ReduceMin() < inSettings.mConvexRadius) { outResult.SetError("Invalid convex radius"); return; @@ -278,7 +278,7 @@ void BoxShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg void BoxShape::GetTrianglesStart(GetTrianglesContext &ioContext, const AABox &inBox, Vec3Arg inPositionCOM, QuatArg inRotation, Vec3Arg inScale) const { - new (&ioContext) GetTrianglesContextVertexList(inPositionCOM, inRotation, inScale, Mat44::sScale(mHalfExtent), sUnitBoxTriangles, sizeof(sUnitBoxTriangles) / sizeof(Vec3), GetMaterial()); + new (&ioContext) GetTrianglesContextVertexList(inPositionCOM, inRotation, inScale, Mat44::sScale(mHalfExtent), sUnitBoxTriangles, std::size(sUnitBoxTriangles), GetMaterial()); } int BoxShape::GetTrianglesNext(GetTrianglesContext &ioContext, int inMaxTrianglesRequested, Float3 *outTriangleVertices, const PhysicsMaterial **outMaterials) const From 721f8e793e60049a3cf3356892949bd1c2563ec4 Mon Sep 17 00:00:00 2001 From: Lily Date: Wed, 22 Jan 2025 20:31:45 -0500 Subject: [PATCH 18/23] Fix SceneTreeDock::_new_scene_from()'s reset_scale in 3D --- editor/scene_tree_dock.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 22fc2354e6e..b46cda63c70 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -3386,7 +3386,7 @@ void SceneTreeDock::_new_scene_from(const String &p_file) { copy_3d->set_rotation(Vector3(0, 0, 0)); } if (reset_scale) { - copy_3d->set_scale(Vector3(0, 0, 0)); + copy_3d->set_scale(Vector3(1, 1, 1)); } } From 046793b260905bec1a7e10ee710c79d9db180dbf Mon Sep 17 00:00:00 2001 From: arkology <43543909+arkology@users.noreply.github.com> Date: Thu, 23 Jan 2025 07:14:11 +0000 Subject: [PATCH 19/23] `TextureProgressBar` minimum size selection as maximum among all textures --- scene/gui/texture_progress_bar.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/scene/gui/texture_progress_bar.cpp b/scene/gui/texture_progress_bar.cpp index 151982312a4..3ed7bf2f511 100644 --- a/scene/gui/texture_progress_bar.cpp +++ b/scene/gui/texture_progress_bar.cpp @@ -81,15 +81,19 @@ bool TextureProgressBar::get_nine_patch_stretch() const { Size2 TextureProgressBar::get_minimum_size() const { if (nine_patch_stretch) { return Size2(stretch_margin[SIDE_LEFT] + stretch_margin[SIDE_RIGHT], stretch_margin[SIDE_TOP] + stretch_margin[SIDE_BOTTOM]); - } else if (under.is_valid()) { - return under->get_size(); - } else if (over.is_valid()) { - return over->get_size(); - } else if (progress.is_valid()) { - return progress->get_size(); } - return Size2(1, 1); + Size2 size = Size2(1, 1); + if (under.is_valid()) { + size = size.max(under->get_size()); + } + if (progress.is_valid()) { + size = size.max(progress->get_size()); + } + if (over.is_valid()) { + size = size.max(over->get_size()); + } + return size; } void TextureProgressBar::set_progress_texture(const Ref &p_texture) { From 7e9cbfbcb86f786a1c2a85ecdfecc10bfbd5f068 Mon Sep 17 00:00:00 2001 From: Kiro Date: Thu, 23 Jan 2025 20:11:25 +0100 Subject: [PATCH 20/23] Remove erroneous squared version --- modules/navigation/3d/nav_mesh_queries_3d.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/navigation/3d/nav_mesh_queries_3d.cpp b/modules/navigation/3d/nav_mesh_queries_3d.cpp index 1e82de34f37..b4ad057b01f 100644 --- a/modules/navigation/3d/nav_mesh_queries_3d.cpp +++ b/modules/navigation/3d/nav_mesh_queries_3d.cpp @@ -298,7 +298,7 @@ void NavMeshQueries3D::_query_task_build_path_corridor(NavMeshPathQueryTask3D &p if ((p_navigation_layers & owner->get_navigation_layers()) != 0) { Vector3 pathway[2] = { connection.pathway_start, connection.pathway_end }; const Vector3 new_entry = Geometry3D::get_closest_point_to_segment(least_cost_poly.entry, pathway); - const real_t new_traveled_distance = least_cost_poly.entry.distance_squared_to(new_entry) * poly_travel_cost + poly_enter_cost + least_cost_poly.traveled_distance; + const real_t new_traveled_distance = least_cost_poly.entry.distance_to(new_entry) * poly_travel_cost + poly_enter_cost + least_cost_poly.traveled_distance; // Check if the neighbor polygon has already been processed. gd::NavigationPoly &neighbor_poly = navigation_polys[connection.polygon->id]; @@ -310,7 +310,7 @@ void NavMeshQueries3D::_query_task_build_path_corridor(NavMeshPathQueryTask3D &p neighbor_poly.back_navigation_edge_pathway_end = connection.pathway_end; neighbor_poly.traveled_distance = new_traveled_distance; neighbor_poly.distance_to_destination = - new_entry.distance_squared_to(end_point) * + new_entry.distance_to(end_point) * owner->get_travel_cost(); neighbor_poly.entry = new_entry; From 663917449baffb7026ace44c6912efd2d671d19c Mon Sep 17 00:00:00 2001 From: Stuart Carnie Date: Thu, 23 Jan 2025 06:38:41 +1100 Subject: [PATCH 21/23] 2D: Fix `CanvasTexture` rendering when updating channels --- .../renderer_rd/renderer_canvas_render_rd.cpp | 28 +++++++++++++++++++ .../renderer_rd/renderer_canvas_render_rd.h | 5 ++++ .../storage_rd/texture_storage.cpp | 16 +++++++++++ .../renderer_rd/storage_rd/texture_storage.h | 6 ++++ 4 files changed, 55 insertions(+) diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp index 02e2486fb9c..de376599de4 100644 --- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp @@ -2971,8 +2971,23 @@ void RendererCanvasRenderRD::_uniform_set_invalidation_callback(void *p_userdata static_cast(singleton)->rid_set_to_uniform_set.erase(*key); } +void RendererCanvasRenderRD::_canvas_texture_invalidation_callback(bool p_deleted, void *p_userdata) { + KeyValue> *kv = static_cast> *>(p_userdata); + RD *rd = RD::get_singleton(); + for (RID rid : kv->value) { + // the invalidation callback will take care of clearing rid_set_to_uniform_set cache also + rd->free(rid); + } + kv->value.clear(); + if (p_deleted) { + static_cast(singleton)->canvas_texture_to_uniform_set.erase(kv->key); + } +} + void RendererCanvasRenderRD::_render_batch(RD::DrawListID p_draw_list, CanvasShaderData *p_shader_data, RenderingDevice::FramebufferFormatID p_framebuffer_format, Light *p_lights, Batch const *p_batch, RenderingMethod::RenderInfo *r_render_info) { { + RendererRD::TextureStorage *ts = RendererRD::TextureStorage::get_singleton(); + RIDSetKey key( p_batch->tex_info->state, state.canvas_instance_data_buffers[state.current_data_buffer_index].instance_buffers[p_batch->instance_buffer_index]); @@ -2992,6 +3007,19 @@ void RendererCanvasRenderRD::_render_batch(RD::DrawListID p_draw_list, CanvasSha const RIDCache::Pair *iter = rid_set_to_uniform_set.insert(key, rid); uniform_set = &iter->data; RD::get_singleton()->uniform_set_set_invalidation_callback(rid, RendererCanvasRenderRD::_uniform_set_invalidation_callback, (void *)&iter->key); + + // If this is a CanvasTexture, it must be tracked so that any changes to the diffuse, normal + // or specular channels invalidate all associated uniform sets. + if (ts->owns_canvas_texture(p_batch->tex_info->state.texture)) { + KeyValue> *kv = nullptr; + if (HashMap>::Iterator i = canvas_texture_to_uniform_set.find(p_batch->tex_info->state.texture); i == canvas_texture_to_uniform_set.end()) { + kv = &*canvas_texture_to_uniform_set.insert(p_batch->tex_info->state.texture, { *uniform_set }); + } else { + i->value.push_back(rid); + kv = &*i; + } + ts->canvas_texture_set_invalidation_callback(p_batch->tex_info->state.texture, RendererCanvasRenderRD::_canvas_texture_invalidation_callback, kv); + } } if (state.current_batch_uniform_set != *uniform_set) { diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.h b/servers/rendering/renderer_rd/renderer_canvas_render_rd.h index 3b071cd804a..0605b18ba44 100644 --- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.h +++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.h @@ -485,9 +485,14 @@ class RendererCanvasRenderRD : public RendererCanvasRender { static void _before_evict(RendererCanvasRenderRD::RIDSetKey &p_key, RID &p_rid); static void _uniform_set_invalidation_callback(void *p_userdata); + static void _canvas_texture_invalidation_callback(bool p_deleted, void *p_userdata); typedef LRUCache, HashMapComparatorDefault, _before_evict> RIDCache; RIDCache rid_set_to_uniform_set; + /// Maps a CanvasTexture to its associated uniform sets, which must + /// be invalidated when the CanvasTexture is updated, such as changing the + /// diffuse texture. + HashMap> canvas_texture_to_uniform_set; struct Batch { // Position in the UBO measured in bytes diff --git a/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp b/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp index a297b971781..69ae3d971a4 100644 --- a/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp @@ -43,9 +43,15 @@ using namespace RendererRD; void TextureStorage::CanvasTexture::clear_cache() { info_cache[0] = CanvasTextureCache(); info_cache[1] = CanvasTextureCache(); + if (invalidated_callback != nullptr) { + invalidated_callback(false, invalidated_callback_userdata); + } } TextureStorage::CanvasTexture::~CanvasTexture() { + if (invalidated_callback != nullptr) { + invalidated_callback(true, invalidated_callback_userdata); + } } /////////////////////////////////////////////////////////////////////////// @@ -735,6 +741,16 @@ TextureStorage::CanvasTextureInfo TextureStorage::canvas_texture_get_info(RID p_ return res; } +void TextureStorage::canvas_texture_set_invalidation_callback(RID p_canvas_texture, InvalidationCallback p_callback, void *p_userdata) { + CanvasTexture *ct = canvas_texture_owner.get_or_null(p_canvas_texture); + if (!ct) { + return; + } + + ct->invalidated_callback = p_callback; + ct->invalidated_callback_userdata = p_userdata; +} + /* Texture API */ RID TextureStorage::texture_allocate() { diff --git a/servers/rendering/renderer_rd/storage_rd/texture_storage.h b/servers/rendering/renderer_rd/storage_rd/texture_storage.h index bfee1e367e0..2073c34ded7 100644 --- a/servers/rendering/renderer_rd/storage_rd/texture_storage.h +++ b/servers/rendering/renderer_rd/storage_rd/texture_storage.h @@ -90,6 +90,8 @@ class TextureStorage : public RendererTextureStorage { _FORCE_INLINE_ bool is_null() const { return diffuse.is_null(); } }; + typedef void (*InvalidationCallback)(bool p_deleted, void *p_userdata); + private: friend class LightStorage; friend class MaterialStorage; @@ -118,6 +120,9 @@ class TextureStorage : public RendererTextureStorage { RS::CanvasItemTextureRepeat texture_repeat = RS::CANVAS_ITEM_TEXTURE_REPEAT_DEFAULT; CanvasTextureCache info_cache[2]; + InvalidationCallback invalidated_callback = nullptr; + void *invalidated_callback_userdata = nullptr; + Size2i size_cache = Size2i(1, 1); bool use_normal_cache = false; bool use_specular_cache = false; @@ -499,6 +504,7 @@ class TextureStorage : public RendererTextureStorage { virtual void canvas_texture_set_texture_repeat(RID p_item, RS::CanvasItemTextureRepeat p_repeat) override; CanvasTextureInfo canvas_texture_get_info(RID p_texture, RS::CanvasItemTextureFilter p_base_filter, RS::CanvasItemTextureRepeat p_base_repeat, bool p_use_srgb, bool p_texture_is_data); + void canvas_texture_set_invalidation_callback(RID p_canvas_texture, InvalidationCallback p_callback, void *p_userdata); /* Texture API */ From c30eff59868f6d07d6a651090dc252973b8cdf3e Mon Sep 17 00:00:00 2001 From: "Matias N. Goldberg" Date: Fri, 24 Jan 2025 00:08:29 -0300 Subject: [PATCH 22/23] Fix performance regression introduced in #90993 PR #90993 needed to get rid of VMA_MEMORY_USAGE_AUTO_PREFER_HOST because we no longer used vmaCreateBuffer so we could specify the allocation callbacks. This however resulted in the wrong memory pool being chosen, causing signficant performance slowdown. Indicate additional preferred flags to help VMA select the proper pool. Fixes #101905 --- drivers/vulkan/rendering_device_driver_vulkan.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/vulkan/rendering_device_driver_vulkan.cpp b/drivers/vulkan/rendering_device_driver_vulkan.cpp index 45c574914d6..f46015f1fc3 100644 --- a/drivers/vulkan/rendering_device_driver_vulkan.cpp +++ b/drivers/vulkan/rendering_device_driver_vulkan.cpp @@ -1530,10 +1530,12 @@ RDD::BufferID RenderingDeviceDriverVulkan::buffer_create(uint64_t p_size, BitFie if (is_src && !is_dst) { // Looks like a staging buffer: CPU maps, writes sequentially, then GPU copies to VRAM. alloc_create_info.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; + alloc_create_info.preferredFlags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; } if (is_dst && !is_src) { // Looks like a readback buffer: GPU copies from VRAM, then CPU maps and reads. alloc_create_info.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT; + alloc_create_info.preferredFlags |= VK_MEMORY_PROPERTY_HOST_CACHED_BIT; } alloc_create_info.requiredFlags = (VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT); } break; From 56fc0fd1758cbe15faede6dd2f330a6eebbbe3b3 Mon Sep 17 00:00:00 2001 From: Ricardo Buring Date: Wed, 22 Jan 2025 13:24:55 +0100 Subject: [PATCH 23/23] CPUParticles2D - Add ability to follow physics interpolated target Allows a non-interpolated particle system to closely follow an interpolated target without tracking ahead of the target, by performing fixed timestep interpolation on the particle system global transform, and using this for emission. Co-authored-by: lawnjelly --- doc/classes/CPUParticles2D.xml | 1 + scene/2d/camera_2d.cpp | 4 +- scene/2d/cpu_particles_2d.cpp | 102 +++++++++++++------ scene/2d/cpu_particles_2d.h | 11 ++ scene/2d/node_2d.cpp | 4 +- scene/main/canvas_item.cpp | 32 ++++++ scene/main/canvas_item.h | 3 + servers/rendering/renderer_canvas_cull.cpp | 27 ++++- servers/rendering/renderer_canvas_cull.h | 3 + servers/rendering/renderer_canvas_render.h | 2 + servers/rendering/rendering_server_default.h | 1 + servers/rendering_server.h | 1 + 12 files changed, 157 insertions(+), 34 deletions(-) diff --git a/doc/classes/CPUParticles2D.xml b/doc/classes/CPUParticles2D.xml index e52e7274006..646e025b115 100644 --- a/doc/classes/CPUParticles2D.xml +++ b/doc/classes/CPUParticles2D.xml @@ -241,6 +241,7 @@ Align Y axis of particle with the direction of its velocity. + Particle system starts as if it had already run for this many seconds. diff --git a/scene/2d/camera_2d.cpp b/scene/2d/camera_2d.cpp index 434671a1c7f..2ac935adda0 100644 --- a/scene/2d/camera_2d.cpp +++ b/scene/2d/camera_2d.cpp @@ -315,7 +315,9 @@ void Camera2D::_notification(int p_what) { } if (is_physics_interpolated_and_enabled()) { _ensure_update_interpolation_data(); - _interpolation_data.xform_curr = get_camera_transform(); + if (Engine::get_singleton()->is_in_physics_frame()) { + _interpolation_data.xform_curr = get_camera_transform(); + } } } break; diff --git a/scene/2d/cpu_particles_2d.cpp b/scene/2d/cpu_particles_2d.cpp index ba0232945ca..aa449beb1b9 100644 --- a/scene/2d/cpu_particles_2d.cpp +++ b/scene/2d/cpu_particles_2d.cpp @@ -32,6 +32,7 @@ #include "cpu_particles_2d.compat.inc" #include "core/math/random_number_generator.h" +#include "core/math/transform_interpolator.h" #include "scene/2d/gpu_particles_2d.h" #include "scene/resources/atlas_texture.h" #include "scene/resources/canvas_item_material.h" @@ -96,7 +97,14 @@ void CPUParticles2D::set_lifetime_randomness(double p_random) { void CPUParticles2D::set_use_local_coordinates(bool p_enable) { local_coords = p_enable; - set_notify_transform(!p_enable); + + // Prevent sending item transforms when using global coords, + // and inform the RenderingServer to use identity mode. + set_canvas_item_use_identity_transform(!local_coords); + + // We only need NOTIFICATION_TRANSFORM_CHANGED + // when following an interpolated target. + set_notify_transform(_interpolation_data.interpolated_follow); } void CPUParticles2D::set_speed_scale(double p_scale) { @@ -228,6 +236,27 @@ void CPUParticles2D::_texture_changed() { } } +void CPUParticles2D::_refresh_interpolation_state() { + if (!is_inside_tree()) { + return; + } + + // The logic for whether to do an interpolated follow. + // This is rather complex, but basically: + // If project setting interpolation is ON and this particle system is in global mode, + // we will follow the INTERPOLATED position rather than the actual position. + // This is so that particles aren't generated AHEAD of the interpolated parent. + bool follow = !local_coords && get_tree()->is_physics_interpolation_enabled(); + + if (follow == _interpolation_data.interpolated_follow) { + return; + } + + _interpolation_data.interpolated_follow = follow; + + set_physics_process_internal(_interpolation_data.interpolated_follow); +} + Ref CPUParticles2D::get_texture() const { return texture; } @@ -600,6 +629,9 @@ void CPUParticles2D::_update_internal() { return; } + // Change update mode? + _refresh_interpolation_state(); + double delta = get_process_delta_time(); if (!active && !emitting) { set_process_internal(false); @@ -679,7 +711,11 @@ void CPUParticles2D::_particles_process(double p_delta) { Transform2D emission_xform; Transform2D velocity_xform; if (!local_coords) { - emission_xform = get_global_transform(); + if (!_interpolation_data.interpolated_follow) { + emission_xform = get_global_transform(); + } else { + TransformInterpolator::interpolate_transform_2d(_interpolation_data.global_xform_prev, _interpolation_data.global_xform_curr, emission_xform, Engine::get_singleton()->get_physics_interpolation_fraction()); + } velocity_xform = emission_xform; velocity_xform[2] = Vector2(); } @@ -1142,6 +1178,17 @@ void CPUParticles2D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { set_process_internal(emitting); + + _refresh_interpolation_state(); + + set_physics_process_internal(emitting && _interpolation_data.interpolated_follow); + + // If we are interpolated following, then reset physics interpolation + // when first appearing. This won't be called by canvas item, as in the + // following mode, is_physics_interpolated() is actually FALSE. + if (_interpolation_data.interpolated_follow) { + notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION); + } } break; case NOTIFICATION_EXIT_TREE: { @@ -1170,37 +1217,28 @@ void CPUParticles2D::_notification(int p_what) { _update_internal(); } break; - case NOTIFICATION_TRANSFORM_CHANGED: { - inv_emission_transform = get_global_transform().affine_inverse(); - - if (!local_coords) { - int pc = particles.size(); - - float *w = particle_data.ptrw(); - const Particle *r = particles.ptr(); - float *ptr = w; - - for (int i = 0; i < pc; i++) { - Transform2D t = inv_emission_transform * r[i].transform; - - if (r[i].active) { - ptr[0] = t.columns[0][0]; - ptr[1] = t.columns[1][0]; - ptr[2] = 0; - ptr[3] = t.columns[2][0]; - ptr[4] = t.columns[0][1]; - ptr[5] = t.columns[1][1]; - ptr[6] = 0; - ptr[7] = t.columns[2][1]; - - } else { - memset(ptr, 0, sizeof(float) * 8); - } + case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { + if (_interpolation_data.interpolated_follow) { + // Keep the interpolated follow target updated. + _interpolation_data.global_xform_prev = _interpolation_data.global_xform_curr; + _interpolation_data.global_xform_curr = get_global_transform(); + } + } break; - ptr += 16; + case NOTIFICATION_TRANSFORM_CHANGED: { + if (_interpolation_data.interpolated_follow) { + // If the transform has been updated AFTER the physics tick, keep data flowing. + if (Engine::get_singleton()->is_in_physics_frame()) { + _interpolation_data.global_xform_curr = get_global_transform(); } } } break; + + case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: { + // Make sure current is up to date with any pending global transform changes. + _interpolation_data.global_xform_curr = get_global_transform_const(); + _interpolation_data.global_xform_prev = _interpolation_data.global_xform_curr; + } break; } } @@ -1559,6 +1597,12 @@ CPUParticles2D::CPUParticles2D() { set_color(Color(1, 1, 1, 1)); _update_mesh_texture(); + + // CPUParticles2D defaults to interpolation off. + // This is because the result often looks better when the particles are updated every frame. + // Note that children will need to explicitly turn back on interpolation if they want to use it, + // rather than relying on inherit mode. + set_physics_interpolation_mode(Node::PHYSICS_INTERPOLATION_MODE_OFF); } CPUParticles2D::~CPUParticles2D() { diff --git a/scene/2d/cpu_particles_2d.h b/scene/2d/cpu_particles_2d.h index f6f1d8a00ae..4c0ffff417a 100644 --- a/scene/2d/cpu_particles_2d.h +++ b/scene/2d/cpu_particles_2d.h @@ -189,6 +189,15 @@ class CPUParticles2D : public Node2D { Mutex update_mutex; + struct InterpolationData { + // Whether this particle is non-interpolated, but following an interpolated parent. + bool interpolated_follow = false; + + // If doing interpolated follow, we need to keep these updated per tick. + Transform2D global_xform_curr; + Transform2D global_xform_prev; + } _interpolation_data; + void _update_render_thread(); void _update_mesh_texture(); @@ -197,6 +206,8 @@ class CPUParticles2D : public Node2D { void _texture_changed(); + void _refresh_interpolation_state(); + protected: static void _bind_methods(); void _notification(int p_what); diff --git a/scene/2d/node_2d.cpp b/scene/2d/node_2d.cpp index 4c0f877f2e7..304e29253e6 100644 --- a/scene/2d/node_2d.cpp +++ b/scene/2d/node_2d.cpp @@ -374,7 +374,9 @@ void Node2D::set_transform(const Transform2D &p_transform) { transform = p_transform; _set_xform_dirty(true); - RenderingServer::get_singleton()->canvas_item_set_transform(get_canvas_item(), transform); + if (!_is_using_identity_transform()) { + RenderingServer::get_singleton()->canvas_item_set_transform(get_canvas_item(), transform); + } _notify_transform(); } diff --git a/scene/main/canvas_item.cpp b/scene/main/canvas_item.cpp index 2cbe9acb3e8..e978099a826 100644 --- a/scene/main/canvas_item.cpp +++ b/scene/main/canvas_item.cpp @@ -188,6 +188,20 @@ Transform2D CanvasItem::get_global_transform() const { return global_transform; } +// Same as get_global_transform() but no reset for `global_invalid`. +Transform2D CanvasItem::get_global_transform_const() const { + if (_is_global_invalid()) { + const CanvasItem *pi = get_parent_item(); + if (pi) { + global_transform = pi->get_global_transform_const() * get_transform(); + } else { + global_transform = get_transform(); + } + } + + return global_transform; +} + void CanvasItem::_set_global_invalid(bool p_invalid) const { if (is_group_processing()) { if (p_invalid) { @@ -1039,6 +1053,24 @@ void CanvasItem::_physics_interpolated_changed() { RenderingServer::get_singleton()->canvas_item_set_interpolated(canvas_item, is_physics_interpolated()); } +void CanvasItem::set_canvas_item_use_identity_transform(bool p_enable) { + // Prevent sending item transforms to RenderingServer when using global coords. + _set_use_identity_transform(p_enable); + + // Let RenderingServer know not to concatenate the parent transform during the render. + RenderingServer::get_singleton()->canvas_item_set_use_identity_transform(get_canvas_item(), p_enable); + + if (is_inside_tree()) { + if (p_enable) { + // Make sure item is using identity transform in server. + RenderingServer::get_singleton()->canvas_item_set_transform(get_canvas_item(), Transform2D()); + } else { + // Make sure item transform is up to date in server if switching identity transform off. + RenderingServer::get_singleton()->canvas_item_set_transform(get_canvas_item(), get_transform()); + } + } +} + Rect2 CanvasItem::get_viewport_rect() const { ERR_READ_THREAD_GUARD_V(Rect2()); ERR_FAIL_COND_V(!is_inside_tree(), Rect2()); diff --git a/scene/main/canvas_item.h b/scene/main/canvas_item.h index e3c154b0176..ece1aa2cf47 100644 --- a/scene/main/canvas_item.h +++ b/scene/main/canvas_item.h @@ -170,6 +170,8 @@ class CanvasItem : public Node { void item_rect_changed(bool p_size_changed = true); + void set_canvas_item_use_identity_transform(bool p_enable); + void _notification(int p_what); static void _bind_methods(); @@ -339,6 +341,7 @@ class CanvasItem : public Node { virtual Transform2D get_transform() const = 0; virtual Transform2D get_global_transform() const; + virtual Transform2D get_global_transform_const() const; virtual Transform2D get_global_transform_with_canvas() const; virtual Transform2D get_screen_transform() const; diff --git a/servers/rendering/renderer_canvas_cull.cpp b/servers/rendering/renderer_canvas_cull.cpp index 8d97537ceab..c9175466104 100644 --- a/servers/rendering/renderer_canvas_cull.cpp +++ b/servers/rendering/renderer_canvas_cull.cpp @@ -70,6 +70,11 @@ void RendererCanvasCull::_dependency_deleted(const RID &p_dependency, Dependency void RendererCanvasCull::_render_canvas_item_tree(RID p_to_render_target, Canvas::ChildItem *p_child_items, int p_child_item_count, const Transform2D &p_transform, const Rect2 &p_clip_rect, const Color &p_modulate, RendererCanvasRender::Light *p_lights, RendererCanvasRender::Light *p_directional_lights, RenderingServer::CanvasItemTextureFilter p_default_filter, RenderingServer::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_vertices_to_pixel, uint32_t p_canvas_cull_mask, RenderingMethod::RenderInfo *r_render_info) { RENDER_TIMESTAMP("Cull CanvasItem Tree"); + // This is used to avoid passing the camera transform down the rendering + // function calls, as it won't be used in 99% of cases, because the camera + // transform is normally concatenated with the item global transform. + _current_camera_transform = p_transform; + memset(z_list, 0, z_range * sizeof(RendererCanvasRender::Item *)); memset(z_last_list, 0, z_range * sizeof(RendererCanvasRender::Item *)); @@ -242,14 +247,14 @@ void RendererCanvasCull::_attach_canvas_item_for_draw(RendererCanvasCull::Item * } if (((ci->commands != nullptr || ci->visibility_notifier) && p_clip_rect.intersects(p_global_rect, true)) || ci->vp_render || ci->copy_back_buffer) { - //something to draw? + // Something to draw? if (ci->update_when_visible) { RenderingServerDefault::redraw_request(); } if (ci->commands != nullptr || ci->copy_back_buffer) { - ci->final_transform = p_transform; + ci->final_transform = !ci->use_identity_transform ? p_transform : _current_camera_transform; ci->final_modulate = p_modulate * ci->self_modulate; ci->global_rect_cache = p_global_rect; ci->global_rect_cache.position -= p_clip_rect.position; @@ -322,6 +327,10 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2 } } + // Always calculate final transform as if not using identity xform. + // This is so the expected transform is passed to children. + // However, if use_identity_xform is set, + // we can override the transform for rendering purposes for this item only. Transform2D self_xform; Transform2D final_xform; if (p_is_already_y_sorted) { @@ -360,7 +369,12 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2 ci->repeat_source_item = repeat_source_item; } - Rect2 global_rect = final_xform.xform(rect); + Rect2 global_rect; + if (!p_canvas_item->use_identity_transform) { + global_rect = final_xform.xform(rect); + } else { + global_rect = _current_camera_transform.xform(rect); + } if (repeat_source_item && (repeat_size.x || repeat_size.y)) { // Top-left repeated rect. Rect2 corner_rect = global_rect; @@ -686,6 +700,13 @@ void RendererCanvasCull::canvas_item_set_draw_behind_parent(RID p_item, bool p_e canvas_item->behind = p_enable; } +void RendererCanvasCull::canvas_item_set_use_identity_transform(RID p_item, bool p_enable) { + Item *canvas_item = canvas_item_owner.get_or_null(p_item); + ERR_FAIL_NULL(canvas_item); + + canvas_item->use_identity_transform = p_enable; +} + void RendererCanvasCull::canvas_item_set_update_when_visible(RID p_item, bool p_update) { Item *canvas_item = canvas_item_owner.get_or_null(p_item); ERR_FAIL_NULL(canvas_item); diff --git a/servers/rendering/renderer_canvas_cull.h b/servers/rendering/renderer_canvas_cull.h index 2c7e4128584..167ae792217 100644 --- a/servers/rendering/renderer_canvas_cull.h +++ b/servers/rendering/renderer_canvas_cull.h @@ -217,6 +217,8 @@ class RendererCanvasCull { RendererCanvasRender::Item **z_list; RendererCanvasRender::Item **z_last_list; + Transform2D _current_camera_transform; + public: void render_canvas(RID p_render_target, Canvas *p_canvas, const Transform2D &p_transform, RendererCanvasRender::Light *p_lights, RendererCanvasRender::Light *p_directional_lights, const Rect2 &p_clip_rect, RS::CanvasItemTextureFilter p_default_filter, RS::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_transforms_to_pixel, bool p_snap_2d_vertices_to_pixel, uint32_t p_canvas_cull_mask, RenderingMethod::RenderInfo *r_render_info = nullptr); @@ -250,6 +252,7 @@ class RendererCanvasCull { void canvas_item_set_self_modulate(RID p_item, const Color &p_color); void canvas_item_set_draw_behind_parent(RID p_item, bool p_enable); + void canvas_item_set_use_identity_transform(RID p_item, bool p_enable); void canvas_item_set_update_when_visible(RID p_item, bool p_update); diff --git a/servers/rendering/renderer_canvas_render.h b/servers/rendering/renderer_canvas_render.h index 07bbcffdb4f..ef98af8f907 100644 --- a/servers/rendering/renderer_canvas_render.h +++ b/servers/rendering/renderer_canvas_render.h @@ -321,6 +321,7 @@ class RendererCanvasRender { bool update_when_visible : 1; bool on_interpolate_transform_list : 1; bool interpolated : 1; + bool use_identity_transform : 1; struct CanvasGroup { RS::CanvasGroupMode mode; @@ -486,6 +487,7 @@ class RendererCanvasRender { repeat_source = false; on_interpolate_transform_list = false; interpolated = true; + use_identity_transform = false; } virtual ~Item() { clear(); diff --git a/servers/rendering/rendering_server_default.h b/servers/rendering/rendering_server_default.h index 14794f7bab7..8e5faaf8381 100644 --- a/servers/rendering/rendering_server_default.h +++ b/servers/rendering/rendering_server_default.h @@ -962,6 +962,7 @@ class RenderingServerDefault : public RenderingServer { FUNC2(canvas_item_set_self_modulate, RID, const Color &) FUNC2(canvas_item_set_draw_behind_parent, RID, bool) + FUNC2(canvas_item_set_use_identity_transform, RID, bool) FUNC6(canvas_item_add_line, RID, const Point2 &, const Point2 &, const Color &, float, bool) FUNC5(canvas_item_add_polyline, RID, const Vector &, const Vector &, float, bool) diff --git a/servers/rendering_server.h b/servers/rendering_server.h index 1206911e4b2..4880da43933 100644 --- a/servers/rendering_server.h +++ b/servers/rendering_server.h @@ -1545,6 +1545,7 @@ class RenderingServer : public Object { virtual void canvas_item_set_visibility_layer(RID p_item, uint32_t p_visibility_layer) = 0; virtual void canvas_item_set_draw_behind_parent(RID p_item, bool p_enable) = 0; + virtual void canvas_item_set_use_identity_transform(RID p_item, bool p_enabled) = 0; enum NinePatchAxisMode { NINE_PATCH_STRETCH,