Skip to content

Commit acefbbb

Browse files
Ivorforceenetheru
andcommitted
Add support for profiling GDScript with tracy.
This adds macro `GodotProfileZoneGroupedFirstScript`, and uses interning for speedy lookups. Co-authored-by: Samuel Nicholas <[email protected]>
1 parent 9dd6c4d commit acefbbb

File tree

11 files changed

+201
-1
lines changed

11 files changed

+201
-1
lines changed

core/profiling/profiling.cpp

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,169 @@
3131
#include "profiling.h"
3232

3333
#if defined(GODOT_USE_TRACY)
34+
// Use the tracy profiler.
35+
36+
#include "core/os/mutex.h"
37+
#include "core/templates/paged_allocator.h"
38+
39+
namespace TracyInternal {
40+
static bool configured = false;
41+
42+
// Implementation similar to StringName.
43+
struct StringInternData {
44+
StringName name;
45+
CharString name_utf8;
46+
47+
uint32_t hash = 0;
48+
StringInternData *prev = nullptr;
49+
StringInternData *next = nullptr;
50+
51+
StringInternData() {}
52+
};
53+
54+
struct SourceLocationInternData {
55+
const StringInternData *file;
56+
const StringInternData *function;
57+
58+
tracy::SourceLocationData source_location_data;
59+
60+
uint32_t function_ptr_hash = 0;
61+
SourceLocationInternData *prev = nullptr;
62+
SourceLocationInternData *next = nullptr;
63+
64+
SourceLocationInternData() {}
65+
};
66+
67+
struct TracyInternTable {
68+
constexpr static uint32_t TABLE_BITS = 16;
69+
constexpr static uint32_t TABLE_LEN = 1 << TABLE_BITS;
70+
constexpr static uint32_t TABLE_MASK = TABLE_LEN - 1;
71+
72+
static inline BinaryMutex mutex;
73+
74+
static inline SourceLocationInternData *source_location_table[TABLE_LEN];
75+
static inline PagedAllocator<SourceLocationInternData> source_location_allocator;
76+
77+
static inline StringInternData *string_table[TABLE_LEN];
78+
static inline PagedAllocator<StringInternData> string_allocator;
79+
};
80+
81+
const StringInternData *_intern_name(const StringName &p_name) {
82+
CRASH_COND(!configured);
83+
84+
const uint32_t hash = p_name.hash();
85+
const uint32_t idx = hash & TracyInternTable::TABLE_MASK;
86+
87+
StringInternData *_data = TracyInternTable::string_table[idx];
88+
89+
while (_data) {
90+
if (_data->hash == hash) {
91+
return _data;
92+
}
93+
_data = _data->next;
94+
}
95+
96+
_data = TracyInternTable::string_allocator.alloc();
97+
_data->name = p_name;
98+
_data->name_utf8 = p_name.operator String().utf8();
99+
100+
_data->next = TracyInternTable::string_table[idx];
101+
_data->prev = nullptr;
102+
103+
if (TracyInternTable::string_table[idx]) {
104+
TracyInternTable::string_table[idx]->prev = _data;
105+
}
106+
TracyInternTable::string_table[idx] = _data;
107+
108+
return _data;
109+
}
110+
111+
const char *intern_name(const StringName &p_name) {
112+
MutexLock lock(TracyInternTable::mutex);
113+
return _intern_name(p_name)->name_utf8.get_data();
114+
}
115+
116+
const tracy::SourceLocationData *intern_source_location(const void *p_function_ptr, const StringName &p_file, const StringName &p_function, uint32_t p_line) {
117+
CRASH_COND(!configured);
118+
119+
const uint32_t hash = HashMapHasherDefault::hash(p_function_ptr);
120+
const uint32_t idx = hash & TracyInternTable::TABLE_MASK;
121+
122+
MutexLock lock(TracyInternTable::mutex);
123+
SourceLocationInternData *_data = TracyInternTable::source_location_table[idx];
124+
125+
while (_data) {
126+
if (_data->function_ptr_hash == hash && _data->source_location_data.line == p_line && _data->file->name == p_file && _data->function->name == p_function) {
127+
return &_data->source_location_data;
128+
}
129+
_data = _data->next;
130+
}
131+
132+
_data = TracyInternTable::source_location_allocator.alloc();
133+
134+
_data->function_ptr_hash = hash;
135+
_data->file = _intern_name(p_file);
136+
_data->function = _intern_name(p_function);
137+
138+
_data->source_location_data.file = _data->file->name_utf8.get_data();
139+
_data->source_location_data.function = _data->function->name_utf8.get_data();
140+
_data->source_location_data.name = _data->source_location_data.function;
141+
142+
_data->source_location_data.line = p_line;
143+
_data->source_location_data.color = 0x478cbf; // godot_logo_blue
144+
145+
_data->next = TracyInternTable::source_location_table[idx];
146+
_data->prev = nullptr;
147+
148+
if (TracyInternTable::source_location_table[idx]) {
149+
TracyInternTable::source_location_table[idx]->prev = _data;
150+
}
151+
TracyInternTable::source_location_table[idx] = _data;
152+
153+
return &_data->source_location_data;
154+
}
155+
} // namespace TracyInternal
156+
34157
void godot_init_profiler() {
158+
MutexLock lock(TracyInternal::TracyInternTable::mutex);
159+
ERR_FAIL_COND(TracyInternal::configured);
160+
161+
for (uint32_t i = 0; i < TracyInternal::TracyInternTable::TABLE_LEN; i++) {
162+
TracyInternal::TracyInternTable::source_location_table[i] = nullptr;
163+
}
164+
for (uint32_t i = 0; i < TracyInternal::TracyInternTable::TABLE_LEN; i++) {
165+
TracyInternal::TracyInternTable::string_table[i] = nullptr;
166+
}
167+
168+
TracyInternal::configured = true;
169+
35170
// Send our first event to tracy; otherwise it doesn't start collecting data.
36171
// FrameMark is kind of fitting because it communicates "this is where we started tracing".
37172
FrameMark;
38173
}
174+
175+
void godot_cleanup_profiler() {
176+
MutexLock lock(TracyInternal::TracyInternTable::mutex);
177+
ERR_FAIL_COND(!TracyInternal::configured);
178+
179+
for (uint32_t i = 0; i < TracyInternal::TracyInternTable::TABLE_LEN; i++) {
180+
while (TracyInternal::TracyInternTable::source_location_table[i]) {
181+
TracyInternal::SourceLocationInternData *d = TracyInternal::TracyInternTable::source_location_table[i];
182+
TracyInternal::TracyInternTable::source_location_table[i] = TracyInternal::TracyInternTable::source_location_table[i]->next;
183+
TracyInternal::TracyInternTable::source_location_allocator.free(d);
184+
}
185+
}
186+
for (uint32_t i = 0; i < TracyInternal::TracyInternTable::TABLE_LEN; i++) {
187+
while (TracyInternal::TracyInternTable::string_table[i]) {
188+
TracyInternal::StringInternData *d = TracyInternal::TracyInternTable::string_table[i];
189+
TracyInternal::TracyInternTable::string_table[i] = TracyInternal::TracyInternTable::string_table[i]->next;
190+
TracyInternal::TracyInternTable::string_allocator.free(d);
191+
}
192+
}
193+
194+
TracyInternal::configured = false;
195+
}
196+
39197
#elif defined(GODOT_USE_PERFETTO)
40198
PERFETTO_TRACK_EVENT_STATIC_STORAGE();
41199

@@ -47,8 +205,17 @@ void godot_init_profiler() {
47205
perfetto::Tracing::Initialize(args);
48206
perfetto::TrackEvent::Register();
49207
}
208+
209+
void godot_cleanup_profiler() {
210+
// Stub
211+
}
212+
50213
#else
51214
void godot_init_profiler() {
52215
// Stub
53216
}
217+
218+
void godot_cleanup_profiler() {
219+
// Stub
220+
}
54221
#endif

core/profiling/profiling.h

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030

3131
#pragma once
3232

33-
#include "core/typedefs.h"
3433
#include "profiling.gen.h"
3534

3635
// This header provides profiling primitives (implemented as macros) for various backends.
@@ -46,9 +45,17 @@
4645
#if defined(GODOT_USE_TRACY)
4746
// Use the tracy profiler.
4847

48+
#include "core/string/string_name.h"
49+
4950
#define TRACY_ENABLE
51+
5052
#include <tracy/Tracy.hpp>
5153

54+
namespace TracyInternal {
55+
const char *intern_name(const StringName &p_name);
56+
const tracy::SourceLocationData *intern_source_location(const void *p_function_ptr, const StringName &p_file, const StringName &p_function, uint32_t p_line);
57+
} //namespace TracyInternal
58+
5259
// Define tracing macros.
5360
#define GodotProfileFrameMark FrameMark
5461
#define GodotProfileZone(m_zone_name) ZoneNamedN(GD_UNIQUE_NAME(__godot_tracy_szone_), m_zone_name, true)
@@ -65,12 +72,15 @@
6572
static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location, TracyLine){ m_zone_name, TracyFunction, TracyFile, (uint32_t)TracyLine, 0 }; \
6673
new (&__godot_tracy_zone_##m_group_name) tracy::ScopedZone(&TracyConcat(__tracy_source_location, TracyLine), TRACY_CALLSTACK, true)
6774
#endif
75+
#define GodotProfileZoneGroupedFirstScript(m_varname, m_ptr, m_file, m_function, m_line) \
76+
tracy::ScopedZone __godot_tracy_zone_##m_group_name(TracyInternal::intern_source_location(m_ptr, m_file, m_function, m_line))
6877

6978
// Memory allocation
7079
#define GodotProfileAlloc(m_ptr, m_size) TracyAlloc(m_ptr, m_size)
7180
#define GodotProfileFree(m_ptr) TracyFree(m_ptr)
7281

7382
void godot_init_profiler();
83+
void godot_cleanup_profiler();
7484

7585
#elif defined(GODOT_USE_PERFETTO)
7686
// Use the perfetto profiler.
@@ -101,15 +111,19 @@ struct PerfettoGroupedEventEnder {
101111
#define GodotProfileZoneGrouped(m_group_name, m_zone_name) \
102112
__godot_perfetto_zone_##m_group_name._end_now(); \
103113
TRACE_EVENT_BEGIN("godot", m_zone_name);
114+
#define GodotProfileZoneGroupedFirstScript(m_varname, m_ptr, m_file, m_function, m_line) \\ TODO
104115

105116
#define GodotProfileAlloc(m_ptr, m_size)
106117
#define GodotProfileFree(m_ptr)
118+
107119
void godot_init_profiler();
120+
void godot_cleanup_profiler();
108121

109122
#else
110123
// No profiling; all macros are stubs.
111124

112125
void godot_init_profiler();
126+
void godot_cleanup_profiler();
113127

114128
// Tell the profiling backend that a new frame has started.
115129
#define GodotProfileFrameMark
@@ -128,4 +142,11 @@ void godot_init_profiler();
128142
// Tell the profiling backend that an allocation was freed.
129143
// There must be a one to one correspondence of GodotProfileAlloc and GodotProfileFree calls.
130144
#define GodotProfileFree(m_ptr)
145+
146+
// Define a zone with custom source information (for scripting)
147+
// m_varname is equivalent to GodotProfileZoneGrouped varnames.
148+
// m_ptr is a pointer to the function instance, which will be used for the lookup.
149+
// m_file, m_function are StringNames, m_line is a uint32_t, all used for the source location.
150+
#define GodotProfileZoneGroupedFirstScript(m_varname, m_ptr, m_file, m_function, m_line)
151+
131152
#endif

modules/gdscript/gdscript_vm.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include "gdscript_lambda_callable.h"
3434

3535
#include "core/os/os.h"
36+
#include "core/profiling/profiling.h"
3637

3738
#ifdef DEBUG_ENABLED
3839

@@ -495,6 +496,8 @@ void (*type_init_function_table[])(Variant *) = {
495496
#define METHOD_CALL_ON_FREED_INSTANCE_ERROR(method_pointer) "Cannot call method '" + (method_pointer)->get_name() + "' on a previously freed instance."
496497

497498
Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_args, int p_argcount, Callable::CallError &r_err, CallState *p_state) {
499+
GodotProfileZoneGroupedFirstScript(zone, this, source, name, _initial_line);
500+
498501
OPCODES_TABLE;
499502

500503
if (!_code_ptr) {

platform/android/java_godot_lib_jni.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ static void _terminate(JNIEnv *env, bool p_restart = false) {
129129
NetSocketAndroid::terminate();
130130

131131
cleanup_android_class_loader();
132+
godot_cleanup_profiler();
132133

133134
if (godot_java) {
134135
godot_java->on_godot_terminating(env);

platform/ios/main_ios.mm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,5 +74,6 @@ int apple_embedded_main(int argc, char **argv) {
7474

7575
void apple_embedded_finish() {
7676
Main::cleanup();
77+
godot_cleanup_profiler();
7778
delete os;
7879
}

platform/linuxbsd/godot_linuxbsd.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,5 +131,6 @@ int main(int argc, char *argv[]) {
131131
}
132132
free(cwd);
133133

134+
godot_cleanup_profiler();
134135
return os.get_exit_code();
135136
}

platform/macos/godot_main_macos.mm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,5 +151,6 @@ int main(int argc, char **argv) {
151151

152152
memdelete(os);
153153

154+
godot_cleanup_profiler();
154155
return exit_code;
155156
}

platform/macos/os_macos.mm

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1147,6 +1147,8 @@ static void handle_interrupt(int sig) {
11471147
}
11481148

11491149
void OS_MacOS_NSApp::terminate() {
1150+
godot_cleanup_profiler();
1151+
11501152
if (pre_wait_observer) {
11511153
CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), pre_wait_observer, kCFRunLoopCommonModes);
11521154
CFRelease(pre_wait_observer);

platform/visionos/main_visionos.mm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,5 +69,6 @@ int apple_embedded_main(int argc, char **argv) {
6969

7070
void apple_embedded_finish() {
7171
Main::cleanup();
72+
godot_cleanup_profiler();
7273
delete os;
7374
}

platform/web/web_main.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ void exit_callback() {
6666
int exit_code = OS_Web::get_singleton()->get_exit_code();
6767
memdelete(os);
6868
os = nullptr;
69+
godot_cleanup_profiler();
6970
emscripten_force_exit(exit_code); // Exit runtime.
7071
}
7172

0 commit comments

Comments
 (0)