Skip to content

Commit c585681

Browse files
committed
Merge pull request #113342 from stuartcarnie/instruments_profiling
Core: Add Apple Instruments support
2 parents cd7cff8 + 93b6348 commit c585681

File tree

5 files changed

+127
-15
lines changed

5 files changed

+127
-15
lines changed

SConstruct

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,12 @@ opts.Add(BoolVariable("use_volk", "Use the volk library to load the Vulkan loade
202202
opts.Add(BoolVariable("accesskit", "Use AccessKit C SDK", True))
203203
opts.Add(("accesskit_sdk_path", "Path to the AccessKit C SDK", ""))
204204
opts.Add(BoolVariable("sdl", "Enable the SDL3 input driver", True))
205-
opts.Add(("profiler_path", "Path to the Profiler framework. Only tracy and perfetto are supported at the moment.", ""))
205+
opts.Add(
206+
EnumVariable(
207+
"profiler", "Specify the profiler to use", "none", ["none", "tracy", "perfetto", "instruments"], ignorecase=2
208+
)
209+
)
210+
opts.Add(("profiler_path", "Path to the Profiler framework.", ""))
206211
opts.Add(
207212
BoolVariable(
208213
"profiler_sample_callstack",

core/profiling/SCsub

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,35 +12,47 @@ Import("env")
1212
env.add_source_files(env.core_sources, "*.cpp")
1313

1414

15-
def get_profiler_and_path_from_path(path: pathlib.Path) -> tuple[str, pathlib.Path]:
15+
def find_perfetto_path(path: pathlib.Path) -> pathlib.Path:
1616
if not path.is_dir():
1717
print("profiler_path must be empty or point to a directory.")
1818
Exit(255)
1919

2020
if (path / "sdk" / "perfetto.cc").is_file():
2121
# perfetto root directory.
22-
return "perfetto", path / "sdk"
22+
return path / "sdk"
2323
if (path / "perfetto.cc").is_file():
2424
# perfetto sdk directory.
25-
return "perfetto", path
25+
return path
26+
27+
print("Invalid profiler_path. Unable to find perfetto.cc.")
28+
Exit(255)
29+
30+
31+
def find_tracy_path(path: pathlib.Path) -> pathlib.Path:
32+
if not path.is_dir():
33+
print("profiler_path must point to a directory.")
34+
Exit(255)
2635

2736
if (path / "public" / "TracyClient.cpp").is_file():
2837
# tracy root directory
29-
return "tracy", path / "public"
38+
return path / "public"
3039
if (path / "TracyClient.cpp").is_file():
3140
# tracy public directory
32-
return "tracy", path
41+
return path
3342

34-
print("Unrecognized profiler_path option. Please set a path to either tracy or perfetto.")
43+
print("Invalid profiler_path. Unable to find TracyClient.cpp.")
3544
Exit(255)
3645

3746

38-
env["profiler"] = None
39-
if env["profiler_path"]:
40-
profiler_name, profiler_path = get_profiler_and_path_from_path(pathlib.Path(env["profiler_path"]))
41-
env["profiler"] = profiler_name
42-
43-
if profiler_name == "tracy":
47+
if env["profiler"]:
48+
if env["profiler"] == "instruments":
49+
# Nothing else to do for Instruments.
50+
pass
51+
elif env["profiler"] == "tracy":
52+
if not env["profiler_path"]:
53+
print("profiler_path must be set when using the tracy profiler. Aborting.")
54+
Exit(255)
55+
profiler_path = find_tracy_path(pathlib.Path(env["profiler_path"]))
4456
env.Prepend(CPPPATH=[str(profiler_path.absolute())])
4557

4658
env_tracy = env.Clone()
@@ -55,7 +67,11 @@ if env["profiler_path"]:
5567
env_tracy.Append(CPPDEFINES=[("TRACY_CALLSTACK", 62)])
5668
env_tracy.disable_warnings()
5769
env_tracy.add_source_files(env.core_sources, str((profiler_path / "TracyClient.cpp").absolute()))
58-
elif profiler_name == "perfetto":
70+
elif env["profiler"] == "perfetto":
71+
if not env["profiler_path"]:
72+
print("profiler_path must be set when using the perfetto profiler. Aborting.")
73+
Exit(255)
74+
profiler_path = find_perfetto_path(pathlib.Path(env["profiler_path"]))
5975
env.Prepend(CPPPATH=[str(profiler_path.absolute())])
6076

6177
env_perfetto = env.Clone()
@@ -65,6 +81,8 @@ if env["profiler_path"]:
6581
env_perfetto.disable_warnings()
6682
env_perfetto.Prepend(CPPPATH=[str(profiler_path.absolute())])
6783
env_perfetto.add_source_files(env.core_sources, str((profiler_path / "perfetto.cc").absolute()))
68-
84+
elif env["profiler_path"]:
85+
print("profiler is required if profiler_path is set. Aborting.")
86+
Exit(255)
6987

7088
env.CommandNoCache("profiling.gen.h", [env.Value(env["profiler"])], env.Run(profiling_builders.profiler_gen_builder))

core/profiling/profiling.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,32 @@ void godot_cleanup_profiler() {
210210
// Stub
211211
}
212212

213+
#elif defined(GODOT_USE_INSTRUMENTS)
214+
215+
namespace apple::instruments {
216+
217+
os_log_t LOG;
218+
os_log_t LOG_TRACING;
219+
220+
} // namespace apple::instruments
221+
222+
void godot_init_profiler() {
223+
static bool initialized = false;
224+
if (initialized) {
225+
return;
226+
}
227+
initialized = true;
228+
apple::instruments::LOG = os_log_create("org.godotengine.godot", OS_LOG_CATEGORY_POINTS_OF_INTEREST);
229+
#ifdef INSTRUMENTS_SAMPLE_CALLSTACKS
230+
apple::instruments::LOG_TRACING = os_log_create("org.godotengine.godot", OS_LOG_CATEGORY_DYNAMIC_STACK_TRACING);
231+
#else
232+
apple::instruments::LOG_TRACING = os_log_create("org.godotengine.godot", "tracing");
233+
#endif
234+
}
235+
236+
void godot_cleanup_profiler() {
237+
}
238+
213239
#else
214240
void godot_init_profiler() {
215241
// Stub

core/profiling/profiling.h

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,65 @@ struct PerfettoGroupedEventEnder {
119119
void godot_init_profiler();
120120
void godot_cleanup_profiler();
121121

122+
#elif defined(GODOT_USE_INSTRUMENTS)
123+
124+
#include <os/log.h>
125+
#include <os/signpost.h>
126+
127+
namespace apple::instruments {
128+
129+
extern os_log_t LOG;
130+
extern os_log_t LOG_TRACING;
131+
132+
typedef void (*DeferFunc)();
133+
134+
class Defer {
135+
public:
136+
explicit Defer(DeferFunc p_fn) :
137+
_fn(p_fn) {}
138+
~Defer() {
139+
_fn();
140+
}
141+
142+
private:
143+
DeferFunc _fn;
144+
};
145+
146+
} // namespace apple::instruments
147+
148+
#define GodotProfileFrameMark \
149+
os_signpost_event_emit(apple::instruments::LOG, OS_SIGNPOST_ID_EXCLUSIVE, "Frame");
150+
151+
#define GodotProfileZoneGroupedFirst(m_group_name, m_zone_name) \
152+
os_signpost_interval_begin(apple::instruments::LOG_TRACING, OS_SIGNPOST_ID_EXCLUSIVE, m_zone_name); \
153+
apple::instruments::DeferFunc _GD_VARNAME_CONCAT_(defer__fn, _, m_group_name) = []() { \
154+
os_signpost_interval_end(apple::instruments::LOG_TRACING, OS_SIGNPOST_ID_EXCLUSIVE, m_zone_name); \
155+
}; \
156+
apple::instruments::Defer _GD_VARNAME_CONCAT_(__instruments_defer_zone_end__, _, m_group_name)(_GD_VARNAME_CONCAT_(defer__fn, _, m_group_name));
157+
158+
#define GodotProfileZoneGroupedEndEarly(m_group_name, m_zone_name) \
159+
_GD_VARNAME_CONCAT_(__instruments_defer_zone_end__, _, m_group_name).~Defer();
160+
161+
#define GodotProfileZoneGrouped(m_group_name, m_zone_name) \
162+
GodotProfileZoneGroupedEndEarly(m_group_name, m_zone_name); \
163+
os_signpost_interval_begin(apple::instruments::LOG_TRACING, OS_SIGNPOST_ID_EXCLUSIVE, m_zone_name); \
164+
_GD_VARNAME_CONCAT_(defer__fn, _, m_group_name) = []() { \
165+
os_signpost_interval_end(apple::instruments::LOG_TRACING, OS_SIGNPOST_ID_EXCLUSIVE, m_zone_name); \
166+
}; \
167+
new (&_GD_VARNAME_CONCAT_(__instruments_defer_zone_end__, _, m_group_name)) apple::instruments::Defer(_GD_VARNAME_CONCAT_(defer__fn, _, m_group_name));
168+
169+
#define GodotProfileZone(m_zone_name) \
170+
GodotProfileZoneGroupedFirst(__COUNTER__, m_zone_name)
171+
172+
#define GodotProfileZoneGroupedFirstScript(m_varname, m_ptr, m_file, m_function, m_line)
173+
174+
// Instruments has its own memory profiling, so these are no-ops.
175+
#define GodotProfileAlloc(m_ptr, m_size)
176+
#define GodotProfileFree(m_ptr)
177+
178+
void godot_init_profiler();
179+
void godot_cleanup_profiler();
180+
122181
#else
123182
// No profiling; all macros are stubs.
124183

core/profiling/profiling_builders.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,7 @@ def profiler_gen_builder(target, source, env):
1111
file.write("#define TRACY_CALLSTACK 62\n")
1212
if env["profiler"] == "perfetto":
1313
file.write("#define GODOT_USE_PERFETTO\n")
14+
if env["profiler"] == "instruments":
15+
file.write("#define GODOT_USE_INSTRUMENTS\n")
16+
if env["profiler_sample_callstack"]:
17+
file.write("#define INSTRUMENTS_SAMPLE_CALLSTACKS\n")

0 commit comments

Comments
 (0)