diff --git a/core/profiling/profiling.cpp b/core/profiling/profiling.cpp index a809fee6b2ed..4b16d13b5be2 100644 --- a/core/profiling/profiling.cpp +++ b/core/profiling/profiling.cpp @@ -31,11 +31,169 @@ #include "profiling.h" #if defined(GODOT_USE_TRACY) +// Use the tracy profiler. + +#include "core/os/mutex.h" +#include "core/templates/paged_allocator.h" + +namespace TracyInternal { +static bool configured = false; + +// Implementation similar to StringName. +struct StringInternData { + StringName name; + CharString name_utf8; + + uint32_t hash = 0; + StringInternData *prev = nullptr; + StringInternData *next = nullptr; + + StringInternData() {} +}; + +struct SourceLocationInternData { + const StringInternData *file; + const StringInternData *function; + + tracy::SourceLocationData source_location_data; + + uint32_t function_ptr_hash = 0; + SourceLocationInternData *prev = nullptr; + SourceLocationInternData *next = nullptr; + + SourceLocationInternData() {} +}; + +struct TracyInternTable { + constexpr static uint32_t TABLE_BITS = 16; + constexpr static uint32_t TABLE_LEN = 1 << TABLE_BITS; + constexpr static uint32_t TABLE_MASK = TABLE_LEN - 1; + + static inline BinaryMutex mutex; + + static inline SourceLocationInternData *source_location_table[TABLE_LEN]; + static inline PagedAllocator source_location_allocator; + + static inline StringInternData *string_table[TABLE_LEN]; + static inline PagedAllocator string_allocator; +}; + +const StringInternData *_intern_name(const StringName &p_name) { + CRASH_COND(!configured); + + const uint32_t hash = p_name.hash(); + const uint32_t idx = hash & TracyInternTable::TABLE_MASK; + + StringInternData *_data = TracyInternTable::string_table[idx]; + + while (_data) { + if (_data->hash == hash) { + return _data; + } + _data = _data->next; + } + + _data = TracyInternTable::string_allocator.alloc(); + _data->name = p_name; + _data->name_utf8 = p_name.operator String().utf8(); + + _data->next = TracyInternTable::string_table[idx]; + _data->prev = nullptr; + + if (TracyInternTable::string_table[idx]) { + TracyInternTable::string_table[idx]->prev = _data; + } + TracyInternTable::string_table[idx] = _data; + + return _data; +} + +const char *intern_name(const StringName &p_name) { + MutexLock lock(TracyInternTable::mutex); + return _intern_name(p_name)->name_utf8.get_data(); +} + +const tracy::SourceLocationData *intern_source_location(const void *p_function_ptr, const StringName &p_file, const StringName &p_function, uint32_t p_line) { + CRASH_COND(!configured); + + const uint32_t hash = HashMapHasherDefault::hash(p_function_ptr); + const uint32_t idx = hash & TracyInternTable::TABLE_MASK; + + MutexLock lock(TracyInternTable::mutex); + SourceLocationInternData *_data = TracyInternTable::source_location_table[idx]; + + while (_data) { + if (_data->function_ptr_hash == hash && _data->source_location_data.line == p_line && _data->file->name == p_file && _data->function->name == p_function) { + return &_data->source_location_data; + } + _data = _data->next; + } + + _data = TracyInternTable::source_location_allocator.alloc(); + + _data->function_ptr_hash = hash; + _data->file = _intern_name(p_file); + _data->function = _intern_name(p_function); + + _data->source_location_data.file = _data->file->name_utf8.get_data(); + _data->source_location_data.function = _data->function->name_utf8.get_data(); + _data->source_location_data.name = _data->source_location_data.function; + + _data->source_location_data.line = p_line; + _data->source_location_data.color = 0x478cbf; // godot_logo_blue + + _data->next = TracyInternTable::source_location_table[idx]; + _data->prev = nullptr; + + if (TracyInternTable::source_location_table[idx]) { + TracyInternTable::source_location_table[idx]->prev = _data; + } + TracyInternTable::source_location_table[idx] = _data; + + return &_data->source_location_data; +} +} // namespace TracyInternal + void godot_init_profiler() { + MutexLock lock(TracyInternal::TracyInternTable::mutex); + ERR_FAIL_COND(TracyInternal::configured); + + for (uint32_t i = 0; i < TracyInternal::TracyInternTable::TABLE_LEN; i++) { + TracyInternal::TracyInternTable::source_location_table[i] = nullptr; + } + for (uint32_t i = 0; i < TracyInternal::TracyInternTable::TABLE_LEN; i++) { + TracyInternal::TracyInternTable::string_table[i] = nullptr; + } + + TracyInternal::configured = true; + // Send our first event to tracy; otherwise it doesn't start collecting data. // FrameMark is kind of fitting because it communicates "this is where we started tracing". FrameMark; } + +void godot_cleanup_profiler() { + MutexLock lock(TracyInternal::TracyInternTable::mutex); + ERR_FAIL_COND(!TracyInternal::configured); + + for (uint32_t i = 0; i < TracyInternal::TracyInternTable::TABLE_LEN; i++) { + while (TracyInternal::TracyInternTable::source_location_table[i]) { + TracyInternal::SourceLocationInternData *d = TracyInternal::TracyInternTable::source_location_table[i]; + TracyInternal::TracyInternTable::source_location_table[i] = TracyInternal::TracyInternTable::source_location_table[i]->next; + TracyInternal::TracyInternTable::source_location_allocator.free(d); + } + } + for (uint32_t i = 0; i < TracyInternal::TracyInternTable::TABLE_LEN; i++) { + while (TracyInternal::TracyInternTable::string_table[i]) { + TracyInternal::StringInternData *d = TracyInternal::TracyInternTable::string_table[i]; + TracyInternal::TracyInternTable::string_table[i] = TracyInternal::TracyInternTable::string_table[i]->next; + TracyInternal::TracyInternTable::string_allocator.free(d); + } + } + + TracyInternal::configured = false; +} + #elif defined(GODOT_USE_PERFETTO) PERFETTO_TRACK_EVENT_STATIC_STORAGE(); @@ -47,8 +205,17 @@ void godot_init_profiler() { perfetto::Tracing::Initialize(args); perfetto::TrackEvent::Register(); } + +void godot_cleanup_profiler() { + // Stub +} + #else void godot_init_profiler() { // Stub } + +void godot_cleanup_profiler() { + // Stub +} #endif diff --git a/core/profiling/profiling.h b/core/profiling/profiling.h index 40d35501519f..5ede472c07e3 100644 --- a/core/profiling/profiling.h +++ b/core/profiling/profiling.h @@ -30,7 +30,6 @@ #pragma once -#include "core/typedefs.h" #include "profiling.gen.h" // This header provides profiling primitives (implemented as macros) for various backends. @@ -46,9 +45,17 @@ #if defined(GODOT_USE_TRACY) // Use the tracy profiler. +#include "core/string/string_name.h" + #define TRACY_ENABLE + #include +namespace TracyInternal { +const char *intern_name(const StringName &p_name); +const tracy::SourceLocationData *intern_source_location(const void *p_function_ptr, const StringName &p_file, const StringName &p_function, uint32_t p_line); +} //namespace TracyInternal + // Define tracing macros. #define GodotProfileFrameMark FrameMark #define GodotProfileZone(m_zone_name) ZoneNamedN(GD_UNIQUE_NAME(__godot_tracy_szone_), m_zone_name, true) @@ -65,12 +72,15 @@ static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location, TracyLine){ m_zone_name, TracyFunction, TracyFile, (uint32_t)TracyLine, 0 }; \ new (&__godot_tracy_zone_##m_group_name) tracy::ScopedZone(&TracyConcat(__tracy_source_location, TracyLine), TRACY_CALLSTACK, true) #endif +#define GodotProfileZoneGroupedFirstScript(m_varname, m_ptr, m_file, m_function, m_line) \ + tracy::ScopedZone __godot_tracy_zone_##m_group_name(TracyInternal::intern_source_location(m_ptr, m_file, m_function, m_line)) // Memory allocation #define GodotProfileAlloc(m_ptr, m_size) TracyAlloc(m_ptr, m_size) #define GodotProfileFree(m_ptr) TracyFree(m_ptr) void godot_init_profiler(); +void godot_cleanup_profiler(); #elif defined(GODOT_USE_PERFETTO) // Use the perfetto profiler. @@ -101,15 +111,19 @@ struct PerfettoGroupedEventEnder { #define GodotProfileZoneGrouped(m_group_name, m_zone_name) \ __godot_perfetto_zone_##m_group_name._end_now(); \ TRACE_EVENT_BEGIN("godot", m_zone_name); +#define GodotProfileZoneGroupedFirstScript(m_varname, m_ptr, m_file, m_function, m_line) \\ TODO #define GodotProfileAlloc(m_ptr, m_size) #define GodotProfileFree(m_ptr) + void godot_init_profiler(); +void godot_cleanup_profiler(); #else // No profiling; all macros are stubs. void godot_init_profiler(); +void godot_cleanup_profiler(); // Tell the profiling backend that a new frame has started. #define GodotProfileFrameMark @@ -128,4 +142,11 @@ void godot_init_profiler(); // Tell the profiling backend that an allocation was freed. // There must be a one to one correspondence of GodotProfileAlloc and GodotProfileFree calls. #define GodotProfileFree(m_ptr) + +// Define a zone with custom source information (for scripting) +// m_varname is equivalent to GodotProfileZoneGrouped varnames. +// m_ptr is a pointer to the function instance, which will be used for the lookup. +// m_file, m_function are StringNames, m_line is a uint32_t, all used for the source location. +#define GodotProfileZoneGroupedFirstScript(m_varname, m_ptr, m_file, m_function, m_line) + #endif diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index 983ccd7ddb11..6a6e2a04873c 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -33,6 +33,7 @@ #include "gdscript_lambda_callable.h" #include "core/os/os.h" +#include "core/profiling/profiling.h" #ifdef DEBUG_ENABLED @@ -495,6 +496,8 @@ void (*type_init_function_table[])(Variant *) = { #define METHOD_CALL_ON_FREED_INSTANCE_ERROR(method_pointer) "Cannot call method '" + (method_pointer)->get_name() + "' on a previously freed instance." Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_args, int p_argcount, Callable::CallError &r_err, CallState *p_state) { + GodotProfileZoneGroupedFirstScript(zone, this, source, name, _initial_line); + OPCODES_TABLE; if (!_code_ptr) { diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp index dc8983f116fa..bce68124c7b2 100644 --- a/platform/android/java_godot_lib_jni.cpp +++ b/platform/android/java_godot_lib_jni.cpp @@ -129,6 +129,7 @@ static void _terminate(JNIEnv *env, bool p_restart = false) { NetSocketAndroid::terminate(); cleanup_android_class_loader(); + godot_cleanup_profiler(); if (godot_java) { godot_java->on_godot_terminating(env); diff --git a/platform/ios/main_ios.mm b/platform/ios/main_ios.mm index 7d9c054950e2..dab9e8804bb3 100644 --- a/platform/ios/main_ios.mm +++ b/platform/ios/main_ios.mm @@ -74,5 +74,6 @@ int apple_embedded_main(int argc, char **argv) { void apple_embedded_finish() { Main::cleanup(); + godot_cleanup_profiler(); delete os; } diff --git a/platform/linuxbsd/godot_linuxbsd.cpp b/platform/linuxbsd/godot_linuxbsd.cpp index f2c7d8eccf1c..acfd4d952ec4 100644 --- a/platform/linuxbsd/godot_linuxbsd.cpp +++ b/platform/linuxbsd/godot_linuxbsd.cpp @@ -131,5 +131,6 @@ int main(int argc, char *argv[]) { } free(cwd); + godot_cleanup_profiler(); return os.get_exit_code(); } diff --git a/platform/macos/godot_main_macos.mm b/platform/macos/godot_main_macos.mm index 4eefa7df5711..6af0e27661e2 100644 --- a/platform/macos/godot_main_macos.mm +++ b/platform/macos/godot_main_macos.mm @@ -151,5 +151,6 @@ int main(int argc, char **argv) { memdelete(os); + godot_cleanup_profiler(); return exit_code; } diff --git a/platform/macos/os_macos.mm b/platform/macos/os_macos.mm index db10fafaba63..53bc12544ff8 100644 --- a/platform/macos/os_macos.mm +++ b/platform/macos/os_macos.mm @@ -1147,6 +1147,8 @@ static void handle_interrupt(int sig) { } void OS_MacOS_NSApp::terminate() { + godot_cleanup_profiler(); + if (pre_wait_observer) { CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), pre_wait_observer, kCFRunLoopCommonModes); CFRelease(pre_wait_observer); diff --git a/platform/visionos/main_visionos.mm b/platform/visionos/main_visionos.mm index c0d1ba3b70a5..f3ff58939725 100644 --- a/platform/visionos/main_visionos.mm +++ b/platform/visionos/main_visionos.mm @@ -69,5 +69,6 @@ int apple_embedded_main(int argc, char **argv) { void apple_embedded_finish() { Main::cleanup(); + godot_cleanup_profiler(); delete os; } diff --git a/platform/web/web_main.cpp b/platform/web/web_main.cpp index 3fb3f2bcce8f..25801dcb63eb 100644 --- a/platform/web/web_main.cpp +++ b/platform/web/web_main.cpp @@ -66,6 +66,7 @@ void exit_callback() { int exit_code = OS_Web::get_singleton()->get_exit_code(); memdelete(os); os = nullptr; + godot_cleanup_profiler(); emscripten_force_exit(exit_code); // Exit runtime. } diff --git a/platform/windows/godot_windows.cpp b/platform/windows/godot_windows.cpp index 7355a9f925a8..086d8f0d21b4 100644 --- a/platform/windows/godot_windows.cpp +++ b/platform/windows/godot_windows.cpp @@ -107,6 +107,7 @@ int widechar_main(int argc, wchar_t **argv) { } delete[] argv_utf8; + godot_cleanup_profiler(); return os.get_exit_code(); }