diff --git a/core/config/engine.cpp b/core/config/engine.cpp index 814ad3d0761e..7fdea7d1aa2a 100644 --- a/core/config/engine.cpp +++ b/core/config/engine.cpp @@ -33,9 +33,7 @@ #include "core/authors.gen.h" #include "core/config/project_settings.h" #include "core/donors.gen.h" -#include "core/io/json.h" #include "core/license.gen.h" -#include "core/os/os.h" #include "core/variant/typed_array.h" #include "core/version.h" @@ -319,43 +317,6 @@ Engine::Engine() { singleton = this; } -void Engine::startup_begin() { - startup_benchmark_total_from = OS::get_singleton()->get_ticks_usec(); -} - -void Engine::startup_benchmark_begin_measure(const String &p_what) { - startup_benchmark_section = p_what; - startup_benchmark_from = OS::get_singleton()->get_ticks_usec(); -} -void Engine::startup_benchmark_end_measure() { - uint64_t total = OS::get_singleton()->get_ticks_usec() - startup_benchmark_from; - double total_f = double(total) / double(1000000); - - startup_benchmark_json[startup_benchmark_section] = total_f; -} - -void Engine::startup_dump(const String &p_to_file) { - uint64_t total = OS::get_singleton()->get_ticks_usec() - startup_benchmark_total_from; - double total_f = double(total) / double(1000000); - startup_benchmark_json["total_time"] = total_f; - - if (!p_to_file.is_empty()) { - Ref f = FileAccess::open(p_to_file, FileAccess::WRITE); - if (f.is_valid()) { - Ref json; - json.instantiate(); - f->store_string(json->stringify(startup_benchmark_json, "\t", false, true)); - } - } else { - List keys; - startup_benchmark_json.get_key_list(&keys); - print_line("STARTUP BENCHMARK:"); - for (const Variant &K : keys) { - print_line("\t-", K, ": ", startup_benchmark_json[K], +" sec."); - } - } -} - Engine::Singleton::Singleton(const StringName &p_name, Object *p_ptr, const StringName &p_class_name) : name(p_name), ptr(p_ptr), diff --git a/core/config/engine.h b/core/config/engine.h index 52408f4be175..5ea653ba6cd4 100644 --- a/core/config/engine.h +++ b/core/config/engine.h @@ -83,11 +83,6 @@ private: String write_movie_path; String shader_cache_path; - Dictionary startup_benchmark_json; - String startup_benchmark_section; - uint64_t startup_benchmark_from = 0; - uint64_t startup_benchmark_total_from = 0; - public: static Engine *get_singleton(); @@ -163,11 +158,6 @@ public: bool is_validation_layers_enabled() const; int32_t get_gpu_index() const; - void startup_begin(); - void startup_benchmark_begin_measure(const String &p_what); - void startup_benchmark_end_measure(); - void startup_dump(const String &p_to_file); - Engine(); virtual ~Engine() {} }; diff --git a/core/os/os.cpp b/core/os/os.cpp index 025dcfe982be..5704ef7a40d4 100644 --- a/core/os/os.cpp +++ b/core/os/os.cpp @@ -34,6 +34,7 @@ #include "core/input/input.h" #include "core/io/dir_access.h" #include "core/io/file_access.h" +#include "core/io/json.h" #include "core/os/midi_driver.h" #include "core/version_generated.gen.h" @@ -589,6 +590,59 @@ OS::PreferredTextureFormat OS::get_preferred_texture_format() const { #endif } +void OS::set_use_benchmark(bool p_use_benchmark) { + use_benchmark = p_use_benchmark; +} + +bool OS::is_use_benchmark_set() { + return use_benchmark; +} + +void OS::set_benchmark_file(const String &p_benchmark_file) { + benchmark_file = p_benchmark_file; +} + +String OS::get_benchmark_file() { + return benchmark_file; +} + +void OS::benchmark_begin_measure(const String &p_what) { +#ifdef TOOLS_ENABLED + start_benchmark_from[p_what] = OS::get_singleton()->get_ticks_usec(); +#endif +} +void OS::benchmark_end_measure(const String &p_what) { +#ifdef TOOLS_ENABLED + uint64_t total = OS::get_singleton()->get_ticks_usec() - start_benchmark_from[p_what]; + double total_f = double(total) / double(1000000); + + startup_benchmark_json[p_what] = total_f; +#endif +} + +void OS::benchmark_dump() { +#ifdef TOOLS_ENABLED + if (!use_benchmark) { + return; + } + if (!benchmark_file.is_empty()) { + Ref f = FileAccess::open(benchmark_file, FileAccess::WRITE); + if (f.is_valid()) { + Ref json; + json.instantiate(); + f->store_string(json->stringify(startup_benchmark_json, "\t", false, true)); + } + } else { + List keys; + startup_benchmark_json.get_key_list(&keys); + print_line("BENCHMARK:"); + for (const Variant &K : keys) { + print_line("\t-", K, ": ", startup_benchmark_json[K], +" sec."); + } + } +#endif +} + OS::OS() { singleton = this; diff --git a/core/os/os.h b/core/os/os.h index 09ed31b9cebc..f2787d638160 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -76,6 +76,12 @@ class OS { RemoteFilesystemClient default_rfs; + // For tracking benchmark data + bool use_benchmark = false; + String benchmark_file; + HashMap start_benchmark_from; + Dictionary startup_benchmark_json; + protected: void _set_logger(CompositeLogger *p_logger); @@ -299,6 +305,15 @@ public: virtual bool request_permissions() { return true; } virtual Vector get_granted_permissions() const { return Vector(); } + // For recording / measuring benchmark data. Only enabled with tools + void set_use_benchmark(bool p_use_benchmark); + bool is_use_benchmark_set(); + void set_benchmark_file(const String &p_benchmark_file); + String get_benchmark_file(); + virtual void benchmark_begin_measure(const String &p_what); + virtual void benchmark_end_measure(const String &p_what); + virtual void benchmark_dump(); + virtual void process_and_drop_events() {} virtual Error setup_remote_filesystem(const String &p_server_host, int p_port, const String &p_password, String &r_project_path); diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp index b8b811961816..e3f69fa9e10c 100644 --- a/core/register_core_types.cpp +++ b/core/register_core_types.cpp @@ -120,6 +120,7 @@ static ResourceUID *resource_uid = nullptr; static bool _is_core_extensions_registered = false; void register_core_types() { + OS::get_singleton()->benchmark_begin_measure("register_core_types"); //consistency check static_assert(sizeof(Callable) <= 16); @@ -294,6 +295,8 @@ void register_core_types() { GDREGISTER_NATIVE_STRUCT(ScriptLanguageExtensionProfilingInfo, "StringName signature;uint64_t call_count;uint64_t total_time;uint64_t self_time"); worker_thread_pool = memnew(WorkerThreadPool); + + OS::get_singleton()->benchmark_end_measure("register_core_types"); } void register_core_settings() { @@ -360,6 +363,8 @@ void unregister_core_extensions() { } void unregister_core_types() { + OS::get_singleton()->benchmark_begin_measure("unregister_core_types"); + memdelete(gdextension_manager); memdelete(resource_uid); @@ -425,4 +430,6 @@ void unregister_core_types() { ResourceCache::clear(); CoreStringNames::free(); StringName::cleanup(); + + OS::get_singleton()->benchmark_end_measure("unregister_core_types"); } diff --git a/editor/editor_fonts.cpp b/editor/editor_fonts.cpp index dfcb083ef9ea..74616bc0ce6f 100644 --- a/editor/editor_fonts.cpp +++ b/editor/editor_fonts.cpp @@ -107,6 +107,7 @@ Ref make_bold_font(const Ref &p_font, double p_embolden, Ty } void editor_register_fonts(Ref p_theme) { + OS::get_singleton()->benchmark_begin_measure("editor_register_fonts"); Ref dir = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); TextServer::FontAntialiasing font_antialiasing = (TextServer::FontAntialiasing)(int)EDITOR_GET("interface/editor/font_antialiasing"); @@ -443,4 +444,6 @@ void editor_register_fonts(Ref p_theme) { p_theme->set_font_size("status_source_size", "EditorFonts", default_font_size); p_theme->set_font("status_source", "EditorFonts", mono_other_fc); + + OS::get_singleton()->benchmark_end_measure("editor_register_fonts"); } diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index 2ddde4e507f5..2b8cace2f114 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -2297,6 +2297,7 @@ void EditorHelp::_gen_doc_thread(void *p_udata) { static bool doc_gen_use_threads = true; void EditorHelp::generate_doc(bool p_use_cache) { + OS::get_singleton()->benchmark_begin_measure("EditorHelp::generate_doc"); if (doc_gen_use_threads) { // In case not the first attempt. _wait_for_thread(); @@ -2327,6 +2328,7 @@ void EditorHelp::generate_doc(bool p_use_cache) { _gen_doc_thread(nullptr); } } + OS::get_singleton()->benchmark_end_measure("EditorHelp::generate_doc"); } void EditorHelp::_toggle_scripts_pressed() { diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index c9f356b82c1f..ce7702d5b0aa 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -1041,7 +1041,7 @@ void EditorNode::_sources_changed(bool p_exist) { if (waiting_for_first_scan) { waiting_for_first_scan = false; - Engine::get_singleton()->startup_benchmark_end_measure(); // editor_scan_and_reimport + OS::get_singleton()->benchmark_end_measure("editor_scan_and_import"); // Reload the global shader variables, but this time // loading textures, as they are now properly imported. @@ -1055,16 +1055,12 @@ void EditorNode::_sources_changed(bool p_exist) { _load_editor_layout(); if (!defer_load_scene.is_empty()) { - Engine::get_singleton()->startup_benchmark_begin_measure("editor_load_scene"); + OS::get_singleton()->benchmark_begin_measure("editor_load_scene"); load_scene(defer_load_scene); defer_load_scene = ""; - Engine::get_singleton()->startup_benchmark_end_measure(); + OS::get_singleton()->benchmark_end_measure("editor_load_scene"); - if (use_startup_benchmark) { - Engine::get_singleton()->startup_dump(startup_benchmark_file); - startup_benchmark_file = String(); - use_startup_benchmark = false; - } + OS::get_singleton()->benchmark_dump(); } } } @@ -4392,13 +4388,9 @@ void EditorNode::_editor_file_dialog_unregister(EditorFileDialog *p_dialog) { Vector EditorNode::_init_callbacks; void EditorNode::_begin_first_scan() { - Engine::get_singleton()->startup_benchmark_begin_measure("editor_scan_and_import"); + OS::get_singleton()->benchmark_begin_measure("editor_scan_and_import"); EditorFileSystem::get_singleton()->scan(); } -void EditorNode::set_use_startup_benchmark(bool p_use_startup_benchmark, const String &p_startup_benchmark_file) { - use_startup_benchmark = p_use_startup_benchmark; - startup_benchmark_file = p_startup_benchmark_file; -} Error EditorNode::export_preset(const String &p_preset, const String &p_path, bool p_debug, bool p_pack_only) { export_defer.preset = p_preset; diff --git a/editor/editor_node.h b/editor/editor_node.h index 814899e169c9..66da01956073 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -694,8 +694,6 @@ private: void _bottom_panel_raise_toggled(bool); void _begin_first_scan(); - bool use_startup_benchmark = false; - String startup_benchmark_file; protected: friend class FileSystemDock; @@ -871,7 +869,6 @@ public: void _copy_warning(const String &p_str); - void set_use_startup_benchmark(bool p_use_startup_benchmark, const String &p_startup_benchmark_file); Error export_preset(const String &p_preset, const String &p_path, bool p_debug, bool p_pack_only); Control *get_gui_base() { return gui_base; } diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp index aa98eb610332..25749a95894b 100644 --- a/editor/editor_themes.cpp +++ b/editor/editor_themes.cpp @@ -281,6 +281,7 @@ float get_gizmo_handle_scale(const String &gizmo_handle_name = "") { } void editor_register_and_generate_icons(Ref p_theme, bool p_dark_theme, float p_icon_saturation, int p_thumb_size, bool p_only_thumbs = false) { + OS::get_singleton()->benchmark_begin_measure("editor_register_and_generate_icons_" + String((p_only_thumbs ? "with_only_thumbs" : "all"))); // Before we register the icons, we adjust their colors and saturation. // Most icons follow the standard rules for color conversion to follow the editor // theme's polarity (dark/light). We also adjust the saturation for most icons, @@ -408,9 +409,11 @@ void editor_register_and_generate_icons(Ref p_theme, bool p_dark_theme, f p_theme->set_icon(editor_icons_names[index], SNAME("EditorIcons"), icon); } } + OS::get_singleton()->benchmark_end_measure("editor_register_and_generate_icons_" + String((p_only_thumbs ? "with_only_thumbs" : "all"))); } Ref create_editor_theme(const Ref p_theme) { + OS::get_singleton()->benchmark_begin_measure("create_editor_theme"); Ref theme = Ref(memnew(Theme)); // Controls may rely on the scale for their internal drawing logic. @@ -2093,10 +2096,13 @@ Ref create_editor_theme(const Ref p_theme) { theme->set_color("search_result_color", "CodeEdit", EDITOR_GET("text_editor/theme/highlighting/search_result_color")); theme->set_color("search_result_border_color", "CodeEdit", EDITOR_GET("text_editor/theme/highlighting/search_result_border_color")); + OS::get_singleton()->benchmark_end_measure("create_editor_theme"); + return theme; } Ref create_custom_theme(const Ref p_theme) { + OS::get_singleton()->benchmark_begin_measure("create_custom_theme"); Ref theme = create_editor_theme(p_theme); const String custom_theme_path = EDITOR_GET("interface/theme/custom_theme"); @@ -2107,6 +2113,7 @@ Ref create_custom_theme(const Ref p_theme) { } } + OS::get_singleton()->benchmark_end_measure("create_custom_theme"); return theme; } diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index 52e6b478f93f..da196d8de998 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -1963,6 +1963,9 @@ Ref ProjectManager::_file_dialog_get_thumbnail(const String &p_path) } void ProjectManager::_build_icon_type_cache(Ref p_theme) { + if (p_theme.is_null()) { + return; + } List tl; p_theme->get_icon_list(SNAME("EditorIcons"), &tl); for (List::Element *E = tl.front(); E; E = E->next()) { diff --git a/editor/register_editor_types.cpp b/editor/register_editor_types.cpp index 758565b266c5..0dd11d89484d 100644 --- a/editor/register_editor_types.cpp +++ b/editor/register_editor_types.cpp @@ -116,6 +116,8 @@ #include "editor/register_exporters.h" void register_editor_types() { + OS::get_singleton()->benchmark_begin_measure("register_editor_types"); + ResourceLoader::set_timestamp_on_load(true); ResourceSaver::set_timestamp_on_save(true); @@ -245,13 +247,19 @@ void register_editor_types() { GLOBAL_DEF("editor/version_control/autoload_on_startup", false); EditorInterface::create(); + + OS::get_singleton()->benchmark_end_measure("register_editor_types"); } void unregister_editor_types() { + OS::get_singleton()->benchmark_begin_measure("unregister_editor_types"); + EditorNode::cleanup(); EditorInterface::free(); if (EditorPaths::get_singleton()) { EditorPaths::free(); } + + OS::get_singleton()->benchmark_end_measure("unregister_editor_types"); } diff --git a/main/main.cpp b/main/main.cpp index 94fb7ecdfada..ec35b333213d 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -183,8 +183,6 @@ static int converter_max_line_length = 100000; HashMap> forwardable_cli_arguments; #endif static bool single_threaded_scene = false; -bool use_startup_benchmark = false; -String startup_benchmark_file; // Display @@ -498,8 +496,8 @@ void Main::print_help(const char *p_binary) { OS::get_singleton()->print(" --dump-gdextension-interface Generate GDExtension header file 'gdextension_interface.h' in the current folder. This file is the base file required to implement a GDExtension.\n"); OS::get_singleton()->print(" --dump-extension-api Generate JSON dump of the Godot API for GDExtension bindings named 'extension_api.json' in the current folder.\n"); OS::get_singleton()->print(" --validate-extension-api Validate an extension API file dumped (with the option above) from a previous version of the engine to ensure API compatibility. If incompatibilities or errors are detected, the return code will be non zero.\n"); - OS::get_singleton()->print(" --startup-benchmark Benchmark the startup time and print it to console.\n"); - OS::get_singleton()->print(" --startup-benchmark-file Benchmark the startup time and save it to a given file in JSON format.\n"); + OS::get_singleton()->print(" --benchmark Benchmark the run time and print it to console.\n"); + OS::get_singleton()->print(" --benchmark-file Benchmark the run time and save it to a given file in JSON format. The path should be absolute.\n"); #ifdef TESTS_ENABLED OS::get_singleton()->print(" --test [--help] Run unit tests. Use --test --help for more information.\n"); #endif @@ -728,11 +726,14 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph OS::get_singleton()->initialize(); + // Benchmark tracking must be done after `OS::get_singleton()->initialize()` as on some + // platforms, it's used to set up the time utilities. + OS::get_singleton()->benchmark_begin_measure("startup_begin"); + engine = memnew(Engine); MAIN_PRINT("Main: Initialize CORE"); - engine->startup_begin(); - engine->startup_benchmark_begin_measure("core"); + OS::get_singleton()->benchmark_begin_measure("core"); register_core_types(); register_core_driver_types(); @@ -1440,15 +1441,16 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph goto error; } - } else if (I->get() == "--startup-benchmark") { - use_startup_benchmark = true; - } else if (I->get() == "--startup-benchmark-file") { + } else if (I->get() == "--benchmark") { + OS::get_singleton()->set_use_benchmark(true); + } else if (I->get() == "--benchmark-file") { if (I->next()) { - use_startup_benchmark = true; - startup_benchmark_file = I->next()->get(); + OS::get_singleton()->set_use_benchmark(true); + String benchmark_file = I->next()->get(); + OS::get_singleton()->set_benchmark_file(benchmark_file); N = I->next()->next(); } else { - OS::get_singleton()->print("Missing argument for --startup-benchmark-file .\n"); + OS::get_singleton()->print("Missing argument for --benchmark-file .\n"); goto error; } @@ -1989,8 +1991,6 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph message_queue = memnew(MessageQueue); - engine->startup_benchmark_end_measure(); // core - Thread::release_main_thread(); // If setup2() is called from another thread, that one will become main thread, so preventively release this one. set_current_thread_safe_for_nodes(false); @@ -1998,6 +1998,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph return setup2(); } + OS::get_singleton()->benchmark_end_measure("core"); return OK; error: @@ -2050,6 +2051,9 @@ error: if (message_queue) { memdelete(message_queue); } + + OS::get_singleton()->benchmark_end_measure("core"); + OS::get_singleton()->finalize_core(); locale = String(); @@ -2063,7 +2067,7 @@ Error Main::setup2() { // Print engine name and version print_line(String(VERSION_NAME) + " v" + get_full_version_string() + " - " + String(VERSION_WEBSITE)); - engine->startup_benchmark_begin_measure("servers"); + OS::get_singleton()->benchmark_begin_measure("servers"); tsman = memnew(TextServerManager); @@ -2454,11 +2458,11 @@ Error Main::setup2() { ERR_FAIL_V_MSG(ERR_CANT_CREATE, "TextServer: Unable to create TextServer interface."); } - engine->startup_benchmark_end_measure(); // servers + OS::get_singleton()->benchmark_end_measure("servers"); MAIN_PRINT("Main: Load Scene Types"); - engine->startup_benchmark_begin_measure("scene"); + OS::get_singleton()->benchmark_begin_measure("scene"); register_scene_types(); register_driver_types(); @@ -2543,7 +2547,7 @@ Error Main::setup2() { print_verbose("EDITOR API HASH: " + uitos(ClassDB::get_api_hash(ClassDB::API_EDITOR))); MAIN_PRINT("Main: Done"); - engine->startup_benchmark_end_measure(); // scene + OS::get_singleton()->benchmark_end_measure("scene"); return OK; } @@ -2961,7 +2965,7 @@ bool Main::start() { if (!project_manager && !editor) { // game if (!game_path.is_empty() || !script.is_empty()) { //autoload - Engine::get_singleton()->startup_benchmark_begin_measure("load_autoloads"); + OS::get_singleton()->benchmark_begin_measure("load_autoloads"); HashMap autoloads = ProjectSettings::get_singleton()->get_autoload_list(); //first pass, add the constants so they exist before any script is loaded @@ -3027,14 +3031,14 @@ bool Main::start() { for (Node *E : to_add) { sml->get_root()->add_child(E); } - Engine::get_singleton()->startup_benchmark_end_measure(); // load autoloads + OS::get_singleton()->benchmark_end_measure("load_autoloads"); } } #ifdef TOOLS_ENABLED EditorNode *editor_node = nullptr; if (editor) { - Engine::get_singleton()->startup_benchmark_begin_measure("editor"); + OS::get_singleton()->benchmark_begin_measure("editor"); editor_node = memnew(EditorNode); sml->get_root()->add_child(editor_node); @@ -3043,12 +3047,7 @@ bool Main::start() { game_path = ""; // Do not load anything. } - Engine::get_singleton()->startup_benchmark_end_measure(); - - editor_node->set_use_startup_benchmark(use_startup_benchmark, startup_benchmark_file); - // Editor takes over - use_startup_benchmark = false; - startup_benchmark_file = String(); + OS::get_singleton()->benchmark_end_measure("editor"); } #endif sml->set_auto_accept_quit(GLOBAL_GET("application/config/auto_accept_quit")); @@ -3177,7 +3176,7 @@ bool Main::start() { if (!project_manager && !editor) { // game - Engine::get_singleton()->startup_benchmark_begin_measure("game_load"); + OS::get_singleton()->benchmark_begin_measure("game_load"); // Load SSL Certificates from Project Settings (or builtin). Crypto::load_default_certificates(GLOBAL_GET("network/tls/certificate_bundle_override")); @@ -3219,19 +3218,19 @@ bool Main::start() { } } - Engine::get_singleton()->startup_benchmark_end_measure(); // game_load + OS::get_singleton()->benchmark_end_measure("game_load"); } #ifdef TOOLS_ENABLED if (project_manager) { - Engine::get_singleton()->startup_benchmark_begin_measure("project_manager"); + OS::get_singleton()->benchmark_begin_measure("project_manager"); Engine::get_singleton()->set_editor_hint(true); ProjectManager *pmanager = memnew(ProjectManager); ProgressDialog *progress_dialog = memnew(ProgressDialog); pmanager->add_child(progress_dialog); sml->get_root()->add_child(pmanager); DisplayServer::get_singleton()->set_context(DisplayServer::CONTEXT_PROJECTMAN); - Engine::get_singleton()->startup_benchmark_end_measure(); + OS::get_singleton()->benchmark_end_measure("project_manager"); } if (project_manager || editor) { @@ -3261,10 +3260,8 @@ bool Main::start() { } } - if (use_startup_benchmark) { - Engine::get_singleton()->startup_dump(startup_benchmark_file); - startup_benchmark_file = String(); - } + OS::get_singleton()->benchmark_end_measure("startup_begin"); + OS::get_singleton()->benchmark_dump(); return true; } @@ -3509,6 +3506,7 @@ void Main::force_redraw() { * The order matters as some of those steps are linked with each other. */ void Main::cleanup(bool p_force) { + OS::get_singleton()->benchmark_begin_measure("Main::cleanup"); if (!p_force) { ERR_FAIL_COND(!_start_success); } @@ -3649,5 +3647,8 @@ void Main::cleanup(bool p_force) { uninitialize_modules(MODULE_INITIALIZATION_LEVEL_CORE); unregister_core_types(); + OS::get_singleton()->benchmark_end_measure("Main::cleanup"); + OS::get_singleton()->benchmark_dump(); + OS::get_singleton()->finalize_core(); } diff --git a/misc/dist/shell/_godot.zsh-completion b/misc/dist/shell/_godot.zsh-completion index 61291899f318..89fe84016637 100644 --- a/misc/dist/shell/_godot.zsh-completion +++ b/misc/dist/shell/_godot.zsh-completion @@ -88,6 +88,6 @@ _arguments \ '--build-solutions[build the scripting solutions (e.g. for C# projects)]' \ '--dump-gdextension-interface[generate GDExtension header file 'gdextension_interface.h' in the current folder. This file is the base file required to implement a GDExtension.]' \ '--dump-extension-api[generate JSON dump of the Godot API for GDExtension bindings named "extension_api.json" in the current folder]' \ - '--startup-benchmark[benchmark the startup time and print it to console]' \ - '--startup-benchmark-file[benchmark the startup time and save it to a given file in JSON format]:path to output JSON file' \ + '--benchmark[benchmark the run time and print it to console]' \ + '--benchmark-file[benchmark the run time and save it to a given file in JSON format]:path to output JSON file' \ '--test[run all unit tests; run with "--test --help" for more information]' diff --git a/misc/dist/shell/godot.bash-completion b/misc/dist/shell/godot.bash-completion index 79000da85d21..a7ce11e52410 100644 --- a/misc/dist/shell/godot.bash-completion +++ b/misc/dist/shell/godot.bash-completion @@ -91,8 +91,8 @@ _complete_godot_options() { --build-solutions --dump-gdextension-interface --dump-extension-api ---startup-benchmark ---startup-benchmark-file +--benchmark +--benchmark-file --test " -- "$1")) } diff --git a/misc/dist/shell/godot.fish b/misc/dist/shell/godot.fish index 8f521ec1a059..ed58d8dcf6d6 100644 --- a/misc/dist/shell/godot.fish +++ b/misc/dist/shell/godot.fish @@ -109,6 +109,6 @@ complete -c godot -l no-docbase -d "Disallow dumping the base types (used with - complete -c godot -l build-solutions -d "Build the scripting solutions (e.g. for C# projects)" complete -c godot -l dump-gdextension-interface -d "Generate GDExtension header file 'gdextension_interface.h' in the current folder. This file is the base file required to implement a GDExtension" complete -c godot -l dump-extension-api -d "Generate JSON dump of the Godot API for GDExtension bindings named 'extension_api.json' in the current folder" -complete -c godot -l startup-benchmark -d "Benchmark the startup time and print it to console" -complete -c godot -l startup-benchmark-file -d "Benchmark the startup time and save it to a given file in JSON format" -x +complete -c godot -l benchmark -d "Benchmark the run time and print it to console" +complete -c godot -l benchmark-file -d "Benchmark the run time and save it to a given file in JSON format" -x complete -c godot -l test -d "Run all unit tests; run with '--test --help' for more information" -x diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt index 42ef1436f34d..8b6efd572f6e 100644 --- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt +++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt @@ -113,6 +113,9 @@ open class GodotEditor : FullScreenGodotApp() { if (args != null && args.isNotEmpty()) { commandLineParams.addAll(listOf(*args)) } + if (BuildConfig.BUILD_TYPE == "dev") { + commandLineParams.add("--benchmark") + } } override fun getCommandLine() = commandLineParams diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.java b/platform/android/java/lib/src/org/godotengine/godot/Godot.java index 4c47ca976076..748a1c41fd05 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.java +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.java @@ -39,6 +39,7 @@ import org.godotengine.godot.io.file.FileAccessHandler; import org.godotengine.godot.plugin.GodotPlugin; import org.godotengine.godot.plugin.GodotPluginRegistry; import org.godotengine.godot.tts.GodotTTS; +import org.godotengine.godot.utils.BenchmarkUtils; import org.godotengine.godot.utils.GodotNetUtils; import org.godotengine.godot.utils.PermissionsUtil; import org.godotengine.godot.xr.XRMode; @@ -180,7 +181,8 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC public GodotIO io; public GodotNetUtils netUtils; public GodotTTS tts; - DirectoryAccessHandler directoryAccessHandler; + private DirectoryAccessHandler directoryAccessHandler; + private FileAccessHandler fileAccessHandler; public interface ResultCallback { void callback(int requestCode, int resultCode, Intent data); @@ -522,7 +524,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC } return cmdline; } catch (Exception e) { - e.printStackTrace(); + // The _cl_ file can be missing with no adverse effect return new String[0]; } } @@ -578,7 +580,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC netUtils = new GodotNetUtils(activity); Context context = getContext(); directoryAccessHandler = new DirectoryAccessHandler(context); - FileAccessHandler fileAccessHandler = new FileAccessHandler(context); + fileAccessHandler = new FileAccessHandler(context); mSensorManager = (SensorManager)activity.getSystemService(Context.SENSOR_SERVICE); mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); mGravity = mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY); @@ -605,6 +607,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC @Override public void onCreate(Bundle icicle) { + BenchmarkUtils.beginBenchmarkMeasure("Godot::onCreate"); super.onCreate(icicle); final Activity activity = getActivity(); @@ -652,6 +655,18 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC editor.putString("store_public_key", main_pack_key); editor.apply(); + i++; + } else if (command_line[i].equals("--benchmark")) { + BenchmarkUtils.setUseBenchmark(true); + new_args.add(command_line[i]); + } else if (has_extra && command_line[i].equals("--benchmark-file")) { + BenchmarkUtils.setUseBenchmark(true); + new_args.add(command_line[i]); + + // Retrieve the filepath + BenchmarkUtils.setBenchmarkFile(command_line[i + 1]); + new_args.add(command_line[i + 1]); + i++; } else if (command_line[i].trim().length() != 0) { new_args.add(command_line[i]); @@ -723,6 +738,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC mCurrentIntent = activity.getIntent(); initializeGodot(); + BenchmarkUtils.endBenchmarkMeasure("Godot::onCreate"); } @Override @@ -928,20 +944,6 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC // Do something here if sensor accuracy changes. } - /* - @Override public boolean dispatchKeyEvent(KeyEvent event) { - if (event.getKeyCode()==KeyEvent.KEYCODE_BACK) { - System.out.printf("** BACK REQUEST!\n"); - - GodotLib.quit(); - return true; - } - System.out.printf("** OTHER KEY!\n"); - - return false; - } - */ - public void onBackPressed() { boolean shouldQuit = true; @@ -1152,6 +1154,16 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC return mRenderView; } + @Keep + public DirectoryAccessHandler getDirectoryAccessHandler() { + return directoryAccessHandler; + } + + @Keep + public FileAccessHandler getFileAccessHandler() { + return fileAccessHandler; + } + @Keep private int createNewGodotInstance(String[] args) { if (godotHost != null) { @@ -1159,4 +1171,19 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC } return 0; } + + @Keep + private void beginBenchmarkMeasure(String label) { + BenchmarkUtils.beginBenchmarkMeasure(label); + } + + @Keep + private void endBenchmarkMeasure(String label) { + BenchmarkUtils.endBenchmarkMeasure(label); + } + + @Keep + private void dumpBenchmark(String benchmarkFile) { + BenchmarkUtils.dumpBenchmark(fileAccessHandler, benchmarkFile); + } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java index 330e2ede76f7..bc7234e2ad57 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java @@ -188,10 +188,10 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView try { Bitmap bitmap = null; if (!TextUtils.isEmpty(imagePath)) { - if (godot.directoryAccessHandler.filesystemFileExists(imagePath)) { + if (godot.getDirectoryAccessHandler().filesystemFileExists(imagePath)) { // Try to load the bitmap from the file system bitmap = BitmapFactory.decodeFile(imagePath); - } else if (godot.directoryAccessHandler.assetsFileExists(imagePath)) { + } else if (godot.getDirectoryAccessHandler().assetsFileExists(imagePath)) { // Try to load the bitmap from the assets directory AssetManager am = getContext().getAssets(); InputStream imageInputStream = am.open(imagePath); diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java index 34490d4625fa..5439f55b2511 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java @@ -162,10 +162,10 @@ public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderV try { Bitmap bitmap = null; if (!TextUtils.isEmpty(imagePath)) { - if (godot.directoryAccessHandler.filesystemFileExists(imagePath)) { + if (godot.getDirectoryAccessHandler().filesystemFileExists(imagePath)) { // Try to load the bitmap from the file system bitmap = BitmapFactory.decodeFile(imagePath); - } else if (godot.directoryAccessHandler.assetsFileExists(imagePath)) { + } else if (godot.getDirectoryAccessHandler().assetsFileExists(imagePath)) { // Try to load the bitmap from the assets directory AssetManager am = getContext().getAssets(); InputStream imageInputStream = am.open(imagePath); diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt b/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt index 357008ca666c..984bf607d000 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt @@ -46,7 +46,7 @@ class FileAccessHandler(val context: Context) { private val TAG = FileAccessHandler::class.java.simpleName private const val FILE_NOT_FOUND_ERROR_ID = -1 - private const val INVALID_FILE_ID = 0 + internal const val INVALID_FILE_ID = 0 private const val STARTING_FILE_ID = 1 internal fun fileExists(context: Context, storageScopeIdentifier: StorageScope.Identifier, path: String?): Boolean { @@ -96,13 +96,17 @@ class FileAccessHandler(val context: Context) { private fun hasFileId(fileId: Int) = files.indexOfKey(fileId) >= 0 fun fileOpen(path: String?, modeFlags: Int): Int { + val accessFlag = FileAccessFlags.fromNativeModeFlags(modeFlags) ?: return INVALID_FILE_ID + return fileOpen(path, accessFlag) + } + + internal fun fileOpen(path: String?, accessFlag: FileAccessFlags): Int { val storageScope = storageScopeIdentifier.identifyStorageScope(path) if (storageScope == StorageScope.UNKNOWN) { return INVALID_FILE_ID } try { - val accessFlag = FileAccessFlags.fromNativeModeFlags(modeFlags) ?: return INVALID_FILE_ID val dataAccess = DataAccess.generateDataAccess(storageScope, context, path!!, accessFlag) ?: return INVALID_FILE_ID files.put(++lastFileId, dataAccess) diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/BenchmarkUtils.kt b/platform/android/java/lib/src/org/godotengine/godot/utils/BenchmarkUtils.kt new file mode 100644 index 000000000000..1552c8f08226 --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/utils/BenchmarkUtils.kt @@ -0,0 +1,122 @@ +/**************************************************************************/ +/* BenchmarkUtils.kt */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +@file:JvmName("BenchmarkUtils") + +package org.godotengine.godot.utils + +import android.os.Build +import android.os.SystemClock +import android.os.Trace +import android.util.Log +import org.godotengine.godot.BuildConfig +import org.godotengine.godot.io.file.FileAccessFlags +import org.godotengine.godot.io.file.FileAccessHandler +import org.json.JSONObject +import java.nio.ByteBuffer +import java.util.concurrent.ConcurrentSkipListMap + +/** + * Contains benchmark related utilities methods + */ +private const val TAG = "GodotBenchmark" + +var useBenchmark = false +var benchmarkFile = "" + +private val startBenchmarkFrom = ConcurrentSkipListMap() +private val benchmarkTracker = ConcurrentSkipListMap() + +/** + * Start measuring and tracing the execution of a given section of code using the given label. + * + * Must be followed by a call to [endBenchmarkMeasure]. + * + * Note: Only enabled on 'editorDev' build variant. + */ +fun beginBenchmarkMeasure(label: String) { + if (BuildConfig.FLAVOR != "editor" || BuildConfig.BUILD_TYPE != "dev") { + return + } + startBenchmarkFrom[label] = SystemClock.elapsedRealtime() + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + Trace.beginAsyncSection(label, 0) + } +} + +/** + * End measuring and tracing of the section of code with the given label. + * + * Must be preceded by a call [beginBenchmarkMeasure] + * + * * Note: Only enabled on 'editorDev' build variant. + */ +fun endBenchmarkMeasure(label: String) { + if (BuildConfig.FLAVOR != "editor" || BuildConfig.BUILD_TYPE != "dev") { + return + } + val startTime = startBenchmarkFrom[label] ?: return + val total = SystemClock.elapsedRealtime() - startTime + benchmarkTracker[label] = total / 1000.0 + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + Trace.endAsyncSection(label, 0) + } +} + +/** + * Dump the benchmark measurements. + * If [filepath] is valid, the data is also written in json format to the specified file. + * + * * Note: Only enabled on 'editorDev' build variant. + */ +@JvmOverloads +fun dumpBenchmark(fileAccessHandler: FileAccessHandler?, filepath: String? = benchmarkFile) { + if (BuildConfig.FLAVOR != "editor" || BuildConfig.BUILD_TYPE != "dev") { + return + } + if (!useBenchmark) { + return + } + + val printOut = + benchmarkTracker.map { "\t- ${it.key} : ${it.value} sec." }.joinToString("\n") + Log.i(TAG, "BENCHMARK:\n$printOut") + + if (fileAccessHandler != null && !filepath.isNullOrBlank()) { + val fileId = fileAccessHandler.fileOpen(filepath, FileAccessFlags.WRITE) + if (fileId != FileAccessHandler.INVALID_FILE_ID) { + val jsonOutput = JSONObject(benchmarkTracker.toMap()).toString(4) + fileAccessHandler.fileWrite(fileId, ByteBuffer.wrap(jsonOutput.toByteArray())) + fileAccessHandler.fileClose(fileId) + } + } +} diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp index 2b504ad69bc8..862d9f0436e8 100644 --- a/platform/android/java_godot_wrapper.cpp +++ b/platform/android/java_godot_wrapper.cpp @@ -80,6 +80,9 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_ _on_godot_main_loop_started = p_env->GetMethodID(godot_class, "onGodotMainLoopStarted", "()V"); _create_new_godot_instance = p_env->GetMethodID(godot_class, "createNewGodotInstance", "([Ljava/lang/String;)I"); _get_render_view = p_env->GetMethodID(godot_class, "getRenderView", "()Lorg/godotengine/godot/GodotRenderView;"); + _begin_benchmark_measure = p_env->GetMethodID(godot_class, "beginBenchmarkMeasure", "(Ljava/lang/String;)V"); + _end_benchmark_measure = p_env->GetMethodID(godot_class, "endBenchmarkMeasure", "(Ljava/lang/String;)V"); + _dump_benchmark = p_env->GetMethodID(godot_class, "dumpBenchmark", "(Ljava/lang/String;)V"); // get some Activity method pointers... _get_class_loader = p_env->GetMethodID(activity_class, "getClassLoader", "()Ljava/lang/ClassLoader;"); @@ -371,3 +374,30 @@ int GodotJavaWrapper::create_new_godot_instance(List args) { return 0; } } + +void GodotJavaWrapper::begin_benchmark_measure(const String &p_label) { + if (_begin_benchmark_measure) { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + jstring j_label = env->NewStringUTF(p_label.utf8().get_data()); + env->CallVoidMethod(godot_instance, _begin_benchmark_measure, j_label); + } +} + +void GodotJavaWrapper::end_benchmark_measure(const String &p_label) { + if (_end_benchmark_measure) { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + jstring j_label = env->NewStringUTF(p_label.utf8().get_data()); + env->CallVoidMethod(godot_instance, _end_benchmark_measure, j_label); + } +} + +void GodotJavaWrapper::dump_benchmark(const String &benchmark_file) { + if (_dump_benchmark) { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + jstring j_benchmark_file = env->NewStringUTF(benchmark_file.utf8().get_data()); + env->CallVoidMethod(godot_instance, _dump_benchmark, j_benchmark_file); + } +} diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h index 05144380e644..245ab33dcf57 100644 --- a/platform/android/java_godot_wrapper.h +++ b/platform/android/java_godot_wrapper.h @@ -71,6 +71,9 @@ private: jmethodID _get_class_loader = nullptr; jmethodID _create_new_godot_instance = nullptr; jmethodID _get_render_view = nullptr; + jmethodID _begin_benchmark_measure = nullptr; + jmethodID _end_benchmark_measure = nullptr; + jmethodID _dump_benchmark = nullptr; public: GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_godot_instance); @@ -106,6 +109,9 @@ public: void vibrate(int p_duration_ms); String get_input_fallback_mapping(); int create_new_godot_instance(List args); + void begin_benchmark_measure(const String &p_label); + void end_benchmark_measure(const String &p_label); + void dump_benchmark(const String &benchmark_file); }; #endif // JAVA_GODOT_WRAPPER_H diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp index 73081e35e7b0..5c6c8454ec3d 100644 --- a/platform/android/os_android.cpp +++ b/platform/android/os_android.cpp @@ -675,6 +675,27 @@ String OS_Android::get_config_path() const { return get_user_data_dir().path_join("config"); } +void OS_Android::benchmark_begin_measure(const String &p_what) { +#ifdef TOOLS_ENABLED + godot_java->begin_benchmark_measure(p_what); +#endif +} + +void OS_Android::benchmark_end_measure(const String &p_what) { +#ifdef TOOLS_ENABLED + godot_java->end_benchmark_measure(p_what); +#endif +} + +void OS_Android::benchmark_dump() { +#ifdef TOOLS_ENABLED + if (!is_use_benchmark_set()) { + return; + } + godot_java->dump_benchmark(get_benchmark_file()); +#endif +} + bool OS_Android::_check_internal_feature_support(const String &p_feature) { if (p_feature == "system_fonts") { return true; diff --git a/platform/android/os_android.h b/platform/android/os_android.h index f1d08b7cfe0c..99fe501975fd 100644 --- a/platform/android/os_android.h +++ b/platform/android/os_android.h @@ -164,6 +164,10 @@ public: virtual Error setup_remote_filesystem(const String &p_server_host, int p_port, const String &p_password, String &r_project_path) override; + virtual void benchmark_begin_measure(const String &p_what) override; + virtual void benchmark_end_measure(const String &p_what) override; + virtual void benchmark_dump() override; + virtual bool _check_internal_feature_support(const String &p_feature) override; OS_Android(GodotJavaWrapper *p_godot_java, GodotIOJavaWrapper *p_godot_io_java, bool p_use_apk_expansion); ~OS_Android();