Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 167 additions & 0 deletions core/profiling/profiling.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<SourceLocationInternData> source_location_allocator;

static inline StringInternData *string_table[TABLE_LEN];
static inline PagedAllocator<StringInternData> 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();

Expand All @@ -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
23 changes: 22 additions & 1 deletion core/profiling/profiling.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -46,9 +45,17 @@
#if defined(GODOT_USE_TRACY)
// Use the tracy profiler.

#include "core/string/string_name.h"

#define TRACY_ENABLE

#include <tracy/Tracy.hpp>

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)
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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
3 changes: 3 additions & 0 deletions modules/gdscript/gdscript_vm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include "gdscript_lambda_callable.h"

#include "core/os/os.h"
#include "core/profiling/profiling.h"

#ifdef DEBUG_ENABLED

Expand Down Expand Up @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions platform/android/java_godot_lib_jni.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions platform/ios/main_ios.mm
Original file line number Diff line number Diff line change
Expand Up @@ -74,5 +74,6 @@ int apple_embedded_main(int argc, char **argv) {

void apple_embedded_finish() {
Main::cleanup();
godot_cleanup_profiler();
delete os;
}
1 change: 1 addition & 0 deletions platform/linuxbsd/godot_linuxbsd.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -131,5 +131,6 @@ int main(int argc, char *argv[]) {
}
free(cwd);

godot_cleanup_profiler();
return os.get_exit_code();
}
1 change: 1 addition & 0 deletions platform/macos/godot_main_macos.mm
Original file line number Diff line number Diff line change
Expand Up @@ -151,5 +151,6 @@ int main(int argc, char **argv) {

memdelete(os);

godot_cleanup_profiler();
return exit_code;
}
2 changes: 2 additions & 0 deletions platform/macos/os_macos.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions platform/visionos/main_visionos.mm
Original file line number Diff line number Diff line change
Expand Up @@ -69,5 +69,6 @@ int apple_embedded_main(int argc, char **argv) {

void apple_embedded_finish() {
Main::cleanup();
godot_cleanup_profiler();
delete os;
}
1 change: 1 addition & 0 deletions platform/web/web_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
}

Expand Down
1 change: 1 addition & 0 deletions platform/windows/godot_windows.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ int widechar_main(int argc, wchar_t **argv) {
}
delete[] argv_utf8;

godot_cleanup_profiler();
return os.get_exit_code();
}

Expand Down