Browse Source

Merge pull request #107038 from bruvzg/emb_scr

Add support for taking embedded window screenshots.
Rémi Verschelde 1 month ago
parent
commit
042ad3a62f

+ 34 - 4
editor/editor_node.cpp

@@ -96,6 +96,7 @@
 #include "editor/editor_property_name_processor.h"
 #include "editor/editor_resource_picker.h"
 #include "editor/editor_resource_preview.h"
+#include "editor/editor_run.h"
 #include "editor/editor_script.h"
 #include "editor/editor_settings.h"
 #include "editor/editor_settings_dialog.h"
@@ -3419,14 +3420,39 @@ void EditorNode::_request_screenshot() {
 
 void EditorNode::_screenshot(bool p_use_utc) {
 	String name = "editor_screenshot_" + Time::get_singleton()->get_datetime_string_from_system(p_use_utc).remove_char(':') + ".png";
-	NodePath path = String("user://") + name;
-	_save_screenshot(path);
+	String path = String("user://") + name;
+
+	if (!EditorRun::request_screenshot(callable_mp(this, &EditorNode::_save_screenshot_with_embedded_process).bind(path))) {
+		_save_screenshot(path);
+	}
+}
+
+void EditorNode::_save_screenshot_with_embedded_process(int64_t p_w, int64_t p_h, const String &p_emb_path, const Rect2i &p_rect, const String &p_path) {
+	Control *main_screen_control = editor_main_screen->get_control();
+	ERR_FAIL_NULL_MSG(main_screen_control, "Cannot get the editor main screen control.");
+	Viewport *viewport = main_screen_control->get_viewport();
+	ERR_FAIL_NULL_MSG(viewport, "Cannot get a viewport from the editor main screen.");
+	Ref<ViewportTexture> texture = viewport->get_texture();
+	ERR_FAIL_COND_MSG(texture.is_null(), "Cannot get a viewport texture from the editor main screen.");
+	Ref<Image> img = texture->get_image();
+	ERR_FAIL_COND_MSG(img.is_null(), "Cannot get an image from a viewport texture of the editor main screen.");
+	img->convert(Image::FORMAT_RGBA8);
+	ERR_FAIL_COND(p_emb_path.is_empty());
+	Ref<Image> overlay = Image::load_from_file(p_emb_path);
+	DirAccess::remove_absolute(p_emb_path);
+	ERR_FAIL_COND_MSG(overlay.is_null(), "Cannot get an image from a embedded process.");
+	overlay->convert(Image::FORMAT_RGBA8);
+	overlay->resize(p_rect.size.x, p_rect.size.y);
+	img->blend_rect(overlay, Rect2i(0, 0, p_w, p_h), p_rect.position);
+	Error error = img->save_png(p_path);
+	ERR_FAIL_COND_MSG(error != OK, "Cannot save screenshot to file '" + p_path + "'.");
+
 	if (EDITOR_GET("interface/editor/automatically_open_screenshots")) {
-		OS::get_singleton()->shell_show_in_file_manager(ProjectSettings::get_singleton()->globalize_path(path), true);
+		OS::get_singleton()->shell_show_in_file_manager(ProjectSettings::get_singleton()->globalize_path(p_path), true);
 	}
 }
 
-void EditorNode::_save_screenshot(NodePath p_path) {
+void EditorNode::_save_screenshot(const String &p_path) {
 	Control *main_screen_control = editor_main_screen->get_control();
 	ERR_FAIL_NULL_MSG(main_screen_control, "Cannot get the editor main screen control.");
 	Viewport *viewport = main_screen_control->get_viewport();
@@ -3437,6 +3463,10 @@ void EditorNode::_save_screenshot(NodePath p_path) {
 	ERR_FAIL_COND_MSG(img.is_null(), "Cannot get an image from a viewport texture of the editor main screen.");
 	Error error = img->save_png(p_path);
 	ERR_FAIL_COND_MSG(error != OK, "Cannot save screenshot to file '" + p_path + "'.");
+
+	if (EDITOR_GET("interface/editor/automatically_open_screenshots")) {
+		OS::get_singleton()->shell_show_in_file_manager(ProjectSettings::get_singleton()->globalize_path(p_path), true);
+	}
 }
 
 void EditorNode::_check_system_theme_changed() {

+ 2 - 1
editor/editor_node.h

@@ -548,7 +548,8 @@ private:
 
 	void _request_screenshot();
 	void _screenshot(bool p_use_utc = false);
-	void _save_screenshot(NodePath p_path);
+	void _save_screenshot(const String &p_path);
+	void _save_screenshot_with_embedded_process(int64_t p_w, int64_t p_h, const String &p_emb_path, const Rect2i &p_rect, const String &p_path);
 
 	void _check_system_theme_changed();
 

+ 8 - 0
editor/editor_run.cpp

@@ -189,6 +189,14 @@ Error EditorRun::run(const String &p_scene, const String &p_write_movie, const V
 	return OK;
 }
 
+bool EditorRun::request_screenshot(const Callable &p_callback) {
+	if (instance_rq_screenshot_callback) {
+		return instance_rq_screenshot_callback(p_callback);
+	} else {
+		return false;
+	}
+}
+
 bool EditorRun::has_child_process(OS::ProcessID p_pid) const {
 	for (const OS::ProcessID &E : pids) {
 		if (E == p_pid) {

+ 4 - 0
editor/editor_run.h

@@ -33,6 +33,7 @@
 #include "core/os/os.h"
 
 typedef void (*EditorRunInstanceStarting)(int p_index, List<String> &r_arguments);
+typedef bool (*EditorRunInstanceRequestScreenshot)(const Callable &p_callback);
 
 class EditorRun {
 public:
@@ -58,6 +59,7 @@ private:
 
 public:
 	inline static EditorRunInstanceStarting instance_starting_callback = nullptr;
+	inline static EditorRunInstanceRequestScreenshot instance_rq_screenshot_callback = nullptr;
 
 	Status get_status() const;
 	String get_running_scene() const;
@@ -71,6 +73,8 @@ public:
 	int get_child_process_count() const { return pids.size(); }
 	OS::ProcessID get_current_process() const;
 
+	static bool request_screenshot(const Callable &p_callback);
+
 	static WindowPlacement get_window_placement();
 
 	EditorRun();

+ 74 - 0
editor/plugins/game_view_plugin.cpp

@@ -32,6 +32,7 @@
 
 #include "core/config/project_settings.h"
 #include "core/debugger/debugger_marshalls.h"
+#include "core/debugger/engine_debugger.h"
 #include "core/string/translation_server.h"
 #include "editor/debugger/editor_debugger_node.h"
 #include "editor/debugger/script_editor_debugger.h"
@@ -225,6 +226,61 @@ void GameViewDebugger::_bind_methods() {
 	ADD_SIGNAL(MethodInfo("session_stopped"));
 }
 
+bool GameViewDebugger::add_screenshot_callback(const Callable &p_callaback, const Rect2i &p_rect) {
+	bool found = false;
+	for (Ref<EditorDebuggerSession> &I : sessions) {
+		if (I->is_active()) {
+			ScreenshotCB sd;
+			sd.cb = p_callaback;
+			sd.rect = p_rect;
+			screenshot_callbacks[scr_rq_id] = sd;
+
+			Array arr;
+			arr.append(scr_rq_id);
+			I->send_message("scene:rq_screenshot", arr);
+			scr_rq_id++;
+			found = true;
+		}
+	}
+	return found;
+}
+
+bool GameViewDebugger::_msg_get_screenshot(const Array &p_args) {
+	ERR_FAIL_COND_V_MSG(p_args.size() != 4, false, "get_screenshot: invalid number of arguments");
+
+	int64_t id = p_args[0];
+	int64_t w = p_args[1];
+	int64_t h = p_args[2];
+	const String &path = p_args[3];
+
+	if (screenshot_callbacks.has(id)) {
+		if (screenshot_callbacks[id].cb.is_valid()) {
+			screenshot_callbacks[id].cb.call(w, h, path, screenshot_callbacks[id].rect);
+		}
+		screenshot_callbacks.erase(id);
+	}
+	return true;
+}
+
+bool GameViewDebugger::capture(const String &p_message, const Array &p_data, int p_session) {
+	Ref<EditorDebuggerSession> session = get_session(p_session);
+	ERR_FAIL_COND_V(session.is_null(), true);
+
+	if (p_message == "game_view:get_screenshot") {
+		return _msg_get_screenshot(p_data);
+	} else {
+		// Any other messages with this prefix should be ignored.
+		WARN_PRINT("GameViewDebugger unknown message: " + p_message);
+		return false;
+	}
+
+	return true;
+}
+
+bool GameViewDebugger::has_capture(const String &p_capture) const {
+	return p_capture == "game_view";
+}
+
 GameViewDebugger::GameViewDebugger() {
 	EditorFeatureProfileManager::get_singleton()->connect("current_feature_profile_changed", callable_mp(this, &GameViewDebugger::_feature_profile_changed));
 
@@ -287,6 +343,23 @@ void GameView::_instance_starting(int p_idx, List<String> &r_arguments) {
 	_update_arguments_for_instance(p_idx, r_arguments);
 }
 
+bool GameView::_instance_rq_screenshot_static(const Callable &p_callback) {
+	ERR_FAIL_NULL_V(singleton, false);
+	return singleton->_instance_rq_screenshot(p_callback);
+}
+
+bool GameView::_instance_rq_screenshot(const Callable &p_callback) {
+	if (debugger.is_null() || window_wrapper->get_window_enabled() || !embedded_process || !embedded_process->is_embedding_completed()) {
+		return false;
+	}
+	Rect2 r = embedded_process->get_adjusted_embedded_window_rect(embedded_process->get_rect());
+	r.position += embedded_process->get_global_position();
+#ifndef MACOS_ENABLED
+	r.position -= embedded_process->get_window()->get_position();
+#endif
+	return debugger->add_screenshot_callback(p_callback, r);
+}
+
 void GameView::_show_update_window_wrapper() {
 	EditorRun::WindowPlacement placement = EditorRun::get_window_placement();
 	Point2 position = floating_window_rect.position;
@@ -756,6 +829,7 @@ void GameView::_notification(int p_what) {
 				EditorRunBar::get_singleton()->connect("play_pressed", callable_mp(this, &GameView::_play_pressed));
 				EditorRunBar::get_singleton()->connect("stop_pressed", callable_mp(this, &GameView::_stop_pressed));
 				EditorRun::instance_starting_callback = _instance_starting_static;
+				EditorRun::instance_rq_screenshot_callback = _instance_rq_screenshot_static;
 
 				// Listen for project settings changes to update the window size and aspect ratio.
 				ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &GameView::_editor_or_project_settings_changed));

+ 17 - 0
editor/plugins/game_view_plugin.h

@@ -60,10 +60,25 @@ private:
 
 	void _feature_profile_changed();
 
+	struct ScreenshotCB {
+		Callable cb;
+		Rect2i rect;
+	};
+
+	int64_t scr_rq_id = 0;
+	HashMap<uint64_t, ScreenshotCB> screenshot_callbacks;
+
+	bool _msg_get_screenshot(const Array &p_args);
+
 protected:
 	static void _bind_methods();
 
 public:
+	virtual bool capture(const String &p_message, const Array &p_data, int p_session) override;
+	virtual bool has_capture(const String &p_capture) const override;
+
+	bool add_screenshot_callback(const Callable &p_callaback, const Rect2i &p_rect);
+
 	void set_suspend(bool p_enabled);
 	void next_frame();
 
@@ -173,6 +188,8 @@ class GameView : public VBoxContainer {
 	void _play_pressed();
 	static void _instance_starting_static(int p_idx, List<String> &r_arguments);
 	void _instance_starting(int p_idx, List<String> &r_arguments);
+	static bool _instance_rq_screenshot_static(const Callable &p_callback);
+	bool _instance_rq_screenshot(const Callable &p_callback);
 	void _stop_pressed();
 	void _embedding_completed();
 	void _embedding_failed();

+ 0 - 1
platform/macos/editor/embedded_game_view_plugin.h

@@ -60,7 +60,6 @@ class GameViewDebuggerMacOS : public GameViewDebugger {
 
 public:
 	virtual bool capture(const String &p_message, const Array &p_data, int p_session) override;
-	virtual bool has_capture(const String &p_capture) const override;
 
 	GameViewDebuggerMacOS(EmbeddedProcessMacOS *p_embedded_process);
 };

+ 1 - 7
platform/macos/editor/embedded_game_view_plugin.mm

@@ -109,10 +109,6 @@ void GameViewDebuggerMacOS::_init_capture_message_handlers() {
 	parse_message_handlers["game_view:joy_stop"] = &GameViewDebuggerMacOS::_msg_joy_stop;
 }
 
-bool GameViewDebuggerMacOS::has_capture(const String &p_capture) const {
-	return p_capture == "game_view";
-}
-
 bool GameViewDebuggerMacOS::capture(const String &p_message, const Array &p_data, int p_session) {
 	Ref<EditorDebuggerSession> session = get_session(p_session);
 	ERR_FAIL_COND_V(session.is_null(), true);
@@ -121,9 +117,7 @@ bool GameViewDebuggerMacOS::capture(const String &p_message, const Array &p_data
 	if (fn_ptr) {
 		return (this->**fn_ptr)(p_data);
 	} else {
-		// Any other messages with this prefix should be ignored.
-		WARN_PRINT("GameViewDebuggerMacOS unknown message: " + p_message);
-		return ERR_SKIP;
+		return GameViewDebugger::capture(p_message, p_data, p_session);
 	}
 
 	return true;

+ 43 - 0
scene/debugger/scene_debugger.cpp

@@ -32,9 +32,11 @@
 
 #include "core/debugger/debugger_marshalls.h"
 #include "core/debugger/engine_debugger.h"
+#include "core/io/dir_access.h"
 #include "core/io/marshalls.h"
 #include "core/math/math_fieldwise.h"
 #include "core/object/script_language.h"
+#include "core/os/time.h"
 #include "core/templates/local_vector.h"
 #include "scene/gui/popup_menu.h"
 #include "scene/main/canvas_layer.h"
@@ -423,6 +425,46 @@ Error SceneDebugger::_msg_runtime_node_select_reset_camera_3d(const Array &p_arg
 
 // endregion
 
+// region Embedded process screenshot.
+
+Error SceneDebugger::_msg_rq_screenshot(const Array &p_args) {
+	ERR_FAIL_COND_V(p_args.is_empty(), ERR_INVALID_DATA);
+
+	Viewport *viewport = SceneTree::get_singleton()->get_root();
+	ERR_FAIL_NULL_V_MSG(viewport, ERR_UNCONFIGURED, "Cannot get a viewport from the main screen.");
+	Ref<ViewportTexture> texture = viewport->get_texture();
+	ERR_FAIL_COND_V_MSG(texture.is_null(), ERR_UNCONFIGURED, "Cannot get a viewport texture from the main screen.");
+	Ref<Image> img = texture->get_image();
+	ERR_FAIL_COND_V_MSG(img.is_null(), ERR_UNCONFIGURED, "Cannot get an image from a viewport texture of the main screen.");
+	img->clear_mipmaps();
+
+	const String TEMP_DIR = OS::get_singleton()->get_temp_path();
+	uint32_t suffix_i = 0;
+	String path;
+	while (true) {
+		String datetime = Time::get_singleton()->get_datetime_string_from_system().remove_chars("-T:");
+		datetime += itos(Time::get_singleton()->get_ticks_usec());
+		String suffix = datetime + (suffix_i > 0 ? itos(suffix_i) : "");
+		path = TEMP_DIR.path_join("scr-" + suffix + ".png");
+		if (!DirAccess::exists(path)) {
+			break;
+		}
+		suffix_i += 1;
+	}
+	img->save_png(path);
+
+	Array arr;
+	arr.append(p_args[0]);
+	arr.append(img->get_width());
+	arr.append(img->get_height());
+	arr.append(path);
+	EngineDebugger::get_singleton()->send_message("game_view:get_screenshot", arr);
+
+	return OK;
+}
+
+// endregion
+
 HashMap<String, SceneDebugger::ParseMessageFunc> SceneDebugger::message_handlers;
 
 Error SceneDebugger::parse_message(void *p_user, const String &p_msg, const Array &p_args, bool &r_captured) {
@@ -490,6 +532,7 @@ void SceneDebugger::_init_message_handlers() {
 #ifndef _3D_DISABLED
 	message_handlers["runtime_node_select_reset_camera_3d"] = _msg_runtime_node_select_reset_camera_3d;
 #endif
+	message_handlers["rq_screenshot"] = _msg_rq_screenshot;
 }
 
 void SceneDebugger::_save_node(ObjectID id, const String &p_path) {

+ 1 - 0
scene/debugger/scene_debugger.h

@@ -119,6 +119,7 @@ private:
 #ifndef _3D_DISABLED
 	static Error _msg_runtime_node_select_reset_camera_3d(const Array &p_args);
 #endif
+	static Error _msg_rq_screenshot(const Array &p_args);
 
 public:
 	static Error parse_message(void *p_user, const String &p_msg, const Array &p_args, bool &r_captured);