diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 71f1bcf98a1..29982b4cec4 100644 --- a/editor/editor_node.cpp +++ b/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); - if (EDITOR_GET("interface/editor/automatically_open_screenshots")) { - OS::get_singleton()->shell_show_in_file_manager(ProjectSettings::get_singleton()->globalize_path(path), true); + 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(NodePath p_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 texture = viewport->get_texture(); + ERR_FAIL_COND_MSG(texture.is_null(), "Cannot get a viewport texture from the editor main screen."); + Ref 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 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(p_path), true); + } +} + +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() { diff --git a/editor/editor_node.h b/editor/editor_node.h index ceb1e8f7724..eacdc1b4997 100644 --- a/editor/editor_node.h +++ b/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(); diff --git a/editor/editor_run.cpp b/editor/editor_run.cpp index 08f74e3dc67..c3dbcd41e7a 100644 --- a/editor/editor_run.cpp +++ b/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) { diff --git a/editor/editor_run.h b/editor/editor_run.h index 250dea5cb48..07bd00f5565 100644 --- a/editor/editor_run.h +++ b/editor/editor_run.h @@ -33,6 +33,7 @@ #include "core/os/os.h" typedef void (*EditorRunInstanceStarting)(int p_index, List &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(); diff --git a/editor/plugins/game_view_plugin.cpp b/editor/plugins/game_view_plugin.cpp index 03ac3459a69..94d0e181a13 100644 --- a/editor/plugins/game_view_plugin.cpp +++ b/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 &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 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 &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)); diff --git a/editor/plugins/game_view_plugin.h b/editor/plugins/game_view_plugin.h index a9209838577..907112027c0 100644 --- a/editor/plugins/game_view_plugin.h +++ b/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 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 &r_arguments); void _instance_starting(int p_idx, List &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(); diff --git a/platform/macos/editor/embedded_game_view_plugin.h b/platform/macos/editor/embedded_game_view_plugin.h index 7b078cba8ed..b50d063c7b1 100644 --- a/platform/macos/editor/embedded_game_view_plugin.h +++ b/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); }; diff --git a/platform/macos/editor/embedded_game_view_plugin.mm b/platform/macos/editor/embedded_game_view_plugin.mm index 9bfd308a0d6..89655a2c010 100644 --- a/platform/macos/editor/embedded_game_view_plugin.mm +++ b/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 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; diff --git a/scene/debugger/scene_debugger.cpp b/scene/debugger/scene_debugger.cpp index c7ba3035b2e..2e7abcf7549 100644 --- a/scene/debugger/scene_debugger.cpp +++ b/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 texture = viewport->get_texture(); + ERR_FAIL_COND_V_MSG(texture.is_null(), ERR_UNCONFIGURED, "Cannot get a viewport texture from the main screen."); + Ref 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 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) { diff --git a/scene/debugger/scene_debugger.h b/scene/debugger/scene_debugger.h index 8351d96f444..23bddb6a081 100644 --- a/scene/debugger/scene_debugger.h +++ b/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);