From 4aec4fbe6f62688bc7dee3c227c0241eee977c7e Mon Sep 17 00:00:00 2001 From: DS Date: Sun, 15 Sep 2024 13:47:45 +0200 Subject: [PATCH] Add support for Tracy profiler (#15113) --- CMakeLists.txt | 21 +++ doc/compiling/README.md | 2 + doc/developing/misc.md | 53 +++++++- doc/lua_api.md | 10 ++ irr/src/CMakeLists.txt | 4 + src/CMakeLists.txt | 6 + src/client/clientlauncher.cpp | 5 + src/client/clientmap.cpp | 3 + src/client/content_mapblock.cpp | 3 + src/client/game.cpp | 26 +++- src/client/mapblock_mesh.cpp | 3 + src/client/sound/sound_manager.cpp | 3 + src/cmake_config.h.in | 1 + src/gui/guiEngine.cpp | 7 +- src/porting.cpp | 3 + src/script/cpp_api/s_base.cpp | 8 ++ src/script/cpp_api/s_security.cpp | 12 +- src/server.cpp | 13 ++ src/util/tracy_wrapper.h | 200 +++++++++++++++++++++++++++++ 19 files changed, 379 insertions(+), 4 deletions(-) create mode 100644 src/util/tracy_wrapper.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 54a830089..a9a0ef000 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,6 +56,11 @@ if((WIN32 AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR APPLE) endif() set(ENABLE_LTO ${DEFAULT_ENABLE_LTO} CACHE BOOL "Use Link Time Optimization") +set(BUILD_WITH_TRACY FALSE CACHE BOOL + "Fetch and build with the Tracy profiler client") +set(FETCH_TRACY_GIT_TAG "master" CACHE STRING + "Git tag for fetching Tracy client. Match with your server (gui) version") + set(DEFAULT_RUN_IN_PLACE FALSE) if(WIN32) set(DEFAULT_RUN_IN_PLACE TRUE) @@ -370,3 +375,19 @@ if(BUILD_DOCUMENTATION) ) endif() endif() + +# Fetch Tracy +if(BUILD_WITH_TRACY) + include(FetchContent) + + message(STATUS "Fetching Tracy (${FETCH_TRACY_GIT_TAG})...") + FetchContent_Declare( + tracy + GIT_REPOSITORY https://github.com/wolfpld/tracy.git + GIT_TAG ${FETCH_TRACY_GIT_TAG} + GIT_SHALLOW TRUE + GIT_PROGRESS TRUE + ) + FetchContent_MakeAvailable(tracy) + message(STATUS "Fetching Tracy - done") +endif() diff --git a/doc/compiling/README.md b/doc/compiling/README.md index c394b2be0..bfe91950f 100644 --- a/doc/compiling/README.md +++ b/doc/compiling/README.md @@ -40,6 +40,8 @@ General options and their default values: ENABLE_UPDATE_CHECKER=TRUE - Whether to enable update checks by default INSTALL_DEVTEST=FALSE - Whether the Development Test game should be installed alongside Minetest USE_GPROF=FALSE - Enable profiling using GProf + BUILD_WITH_TRACY=FALSE - Fetch and build with the Tracy profiler client + FETCH_TRACY_GIT_TAG=master - Git tag for fetching Tracy client. Match with your server (gui) version VERSION_EXTRA= - Text to append to version (e.g. VERSION_EXTRA=foobar -> Minetest 0.4.9-foobar) Library specific options: diff --git a/doc/developing/misc.md b/doc/developing/misc.md index 1d3d8c941..2ac843caf 100644 --- a/doc/developing/misc.md +++ b/doc/developing/misc.md @@ -1,6 +1,6 @@ # Miscellaneous -## Profiling Minetest on Linux +## Profiling Minetest on Linux with perf We will be using a tool called "perf", which you can get by installing `perf` or `linux-perf` or `linux-tools-common`. @@ -36,3 +36,54 @@ Give both files to the developer and also provide: * commit the source was built from and/or modified source code (if applicable) Hotspot will resolve symbols correctly when pointing the sysroot option at the collected libs. + + +## Profiling with Tracy + +[Tracy](https://github.com/wolfpld/tracy) is +> A real time, nanosecond resolution, remote telemetry, hybrid frame and sampling +> profiler for games and other applications. + +It allows one to annotate important functions and generate traces, where one can +see when each individual function call happened, and how long it took. + +Tracy can also record when frames, e.g. server step, start and end, and inspect +frames that took longer than usual. Minetest already contains annotations for +its frames. + +See also [Tracy's official documentation](https://github.com/wolfpld/tracy/releases/latest/download/tracy.pdf). + +### Installing + +Tracy consists of a client (Minetest) and a server (the gui). + +Install the server, e.g. using your package manager. + +### Building + +Build Minetest with `-DDBUILD_WITH_TRACY=1`, this will fetch Tracy for building +the Tracy client. And use `FETCH_TRACY_GIT_TAG` to get a version matching your +Tracy server, e.g. `-DFETCH_TRACY_GIT_TAG=v0.11.0` if it's `0.11.0`. + +To actually use Tracy, you also have to enable it with Tracy's build options: +``` +-DTRACY_ENABLE=1 -DTRACY_ONLY_LOCALHOST=1 +``` + +See Tracy's documentation for more build options. + +### Using in C++ + +Start the Tracy server and Minetest. You should see Minetest in the menu. + +To actually get useful traces, you have to annotate functions with `ZoneScoped` +macros and recompile. Please refer to Tracy's official documentation. + +### Using in Lua + +Tracy also supports Lua. +If built with Tracy, Minetest loads its API in the global `tracy` table. +See Tracy's official documentation for more information. + +Note: The whole Tracy Lua API is accessible to all mods. And we don't check if it +is or becomes insecure. Run untrusted mods at your own risk. diff --git a/doc/lua_api.md b/doc/lua_api.md index ad8eacb06..83f186804 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -11394,6 +11394,16 @@ Functions: bit.tobit, bit.tohex, bit.bnot, bit.band, bit.bor, bit.bxor, bit.lshi See http://bitop.luajit.org/ for advanced information. +Tracy Profiler +-------------- + +Minetest can be built with support for the Tracy profiler, which can also be +useful for profiling mods and is exposed to Lua as the global `tracy`. + +See doc/developing/misc.md for details. + +Note: This is a development feature and not covered by compatibility promises. + Error Handling -------------- diff --git a/irr/src/CMakeLists.txt b/irr/src/CMakeLists.txt index f5bc675e4..742d27f72 100644 --- a/irr/src/CMakeLists.txt +++ b/irr/src/CMakeLists.txt @@ -468,6 +468,10 @@ foreach(object_lib target_include_directories(${object_lib} PRIVATE ${link_includes}) # Add objects from object library to main library target_sources(IrrlichtMt PRIVATE $) + + if(BUILD_WITH_TRACY) + target_link_libraries(${object_lib} PRIVATE Tracy::TracyClient) + endif() endforeach() # Alias target provides add_submodule compatibility diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0ea8a40a9..cad22ca6f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -648,6 +648,9 @@ if(BUILD_CLIENT) if(BUILD_UNITTESTS OR BUILD_BENCHMARKS) target_link_libraries(${PROJECT_NAME} Catch2::Catch2) endif() + if(BUILD_WITH_TRACY) + target_link_libraries(${PROJECT_NAME} Tracy::TracyClient) + endif() if(PRECOMPILE_HEADERS) target_precompile_headers(${PROJECT_NAME} PRIVATE ${PRECOMPILED_HEADERS_LIST}) @@ -715,6 +718,9 @@ if(BUILD_SERVER) if(BUILD_UNITTESTS OR BUILD_BENCHMARKS) target_link_libraries(${PROJECT_NAME}server Catch2::Catch2) endif() + if(BUILD_WITH_TRACY) + target_link_libraries(${PROJECT_NAME}server Tracy::TracyClient) + endif() if(PRECOMPILE_HEADERS) target_precompile_headers(${PROJECT_NAME}server PRIVATE ${PRECOMPILED_HEADERS_LIST}) diff --git a/src/client/clientlauncher.cpp b/src/client/clientlauncher.cpp index e86fb4425..1c9e397ca 100644 --- a/src/client/clientlauncher.cpp +++ b/src/client/clientlauncher.cpp @@ -35,6 +35,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "version.h" #include "renderingengine.h" #include "network/networkexceptions.h" +#include "util/tracy_wrapper.h" #include #include #include @@ -544,15 +545,19 @@ void ClientLauncher::main_menu(MainMenuData *menudata) video::IVideoDriver *driver = m_rendering_engine->get_video_driver(); infostream << "Waiting for other menus" << std::endl; + auto framemarker = FrameMarker("ClientLauncher::main_menu()-wait-frame").started(); while (m_rendering_engine->run() && !*kill) { if (!isMenuActive()) break; driver->beginScene(true, true, video::SColor(255, 128, 128, 128)); m_rendering_engine->get_gui_env()->drawAll(); driver->endScene(); + framemarker.end(); // On some computers framerate doesn't seem to be automatically limited sleep_ms(25); + framemarker.start(); } + framemarker.end(); infostream << "Waited for other menus" << std::endl; auto *cur_control = m_rendering_engine->get_raw_device()->getCursorControl(); diff --git a/src/client/clientmap.cpp b/src/client/clientmap.cpp index b4356dc27..12945ed1b 100644 --- a/src/client/clientmap.cpp +++ b/src/client/clientmap.cpp @@ -31,6 +31,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "settings.h" #include "camera.h" // CameraModes #include "util/basic_macros.h" +#include "util/tracy_wrapper.h" #include "client/renderingengine.h" #include @@ -714,6 +715,8 @@ void ClientMap::touchMapBlocks() void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) { + ZoneScoped; + bool is_transparent_pass = pass == scene::ESNRP_TRANSPARENT; std::string prefix; diff --git a/src/client/content_mapblock.cpp b/src/client/content_mapblock.cpp index 4f4056668..f7852ad1c 100644 --- a/src/client/content_mapblock.cpp +++ b/src/client/content_mapblock.cpp @@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/basic_macros.h" #include "util/numeric.h" #include "util/directiontables.h" +#include "util/tracy_wrapper.h" #include "mapblock_mesh.h" #include "settings.h" #include "nodedef.h" @@ -1750,6 +1751,8 @@ void MapblockMeshGenerator::drawNode() void MapblockMeshGenerator::generate() { + ZoneScoped; + for (cur_node.p.Z = 0; cur_node.p.Z < data->side_length; cur_node.p.Z++) for (cur_node.p.Y = 0; cur_node.p.Y < data->side_length; cur_node.p.Y++) for (cur_node.p.X = 0; cur_node.p.X < data->side_length; cur_node.p.X++) { diff --git a/src/client/game.cpp b/src/client/game.cpp index 36cb5f4bb..6e01f2ffe 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -79,6 +79,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "hud.h" #include "clientdynamicinfo.h" #include +#include "util/tracy_wrapper.h" #if USE_SOUND #include "client/sound/sound_openal.h" @@ -1140,6 +1141,8 @@ bool Game::startup(bool *kill, void Game::run() { + ZoneScoped; + ProfilerGraph graph; RunStats stats = {}; CameraOrientation cam_view_target = {}; @@ -1167,15 +1170,21 @@ void Game::run() const bool initial_window_maximized = !g_settings->getBool("fullscreen") && g_settings->getBool("window_maximized"); + auto framemarker = FrameMarker("Game::run()-frame").started(); + while (m_rendering_engine->run() && !(*kill || g_gamecallback->shutdown_requested || (server && server->isShutdownRequested()))) { + framemarker.end(); + // Calculate dtime = // m_rendering_engine->run() from this iteration // + Sleep time until the wanted FPS are reached draw_times.limit(device, &dtime, g_menumgr.pausesGame()); + framemarker.start(); + const auto current_dynamic_info = ClientDynamicInfo::getCurrent(); if (!current_dynamic_info.equal(client_display_info)) { client_display_info = current_dynamic_info; @@ -1232,6 +1241,8 @@ void Game::run() } } + framemarker.end(); + RenderingEngine::autosaveScreensizeAndCo(initial_screen_size, initial_window_maximized); } @@ -1671,9 +1682,13 @@ bool Game::connectToServer(const GameStartData &start_data, fps_control.reset(); + auto framemarker = FrameMarker("Game::connectToServer()-frame").started(); + while (m_rendering_engine->run()) { + framemarker.end(); fps_control.limit(device, &dtime); + framemarker.start(); // Update client and server step(dtime); @@ -1719,6 +1734,7 @@ bool Game::connectToServer(const GameStartData &start_data, // Update status showOverlayMessage(N_("Connecting to server..."), dtime, 20); } + framemarker.end(); } catch (con::PeerNotFoundException &e) { warningstream << "This should not happen. Please report a bug." << std::endl; return false; @@ -1736,9 +1752,11 @@ bool Game::getServerContent(bool *aborted) fps_control.reset(); + auto framemarker = FrameMarker("Game::getServerContent()-frame").started(); while (m_rendering_engine->run()) { - + framemarker.end(); fps_control.limit(device, &dtime); + framemarker.start(); // Update client and server step(dtime); @@ -1804,6 +1822,7 @@ bool Game::getServerContent(bool *aborted) texture_src, dtime, progress); } } + framemarker.end(); *aborted = true; infostream << "Connect aborted [device]" << std::endl; @@ -2773,6 +2792,8 @@ void Game::updatePauseState() inline void Game::step(f32 dtime) { + ZoneScoped; + if (server) { float fps_max = (!device->isWindowFocused() || g_menumgr.pausesGame()) ? g_settings->getFloat("fps_max_unfocused") : @@ -4052,6 +4073,7 @@ void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos, void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, const CameraOrientation &cam) { + ZoneScoped; TimeTaker tt_update("Game::updateFrame()"); LocalPlayer *player = client->getEnv().getLocalPlayer(); @@ -4311,6 +4333,8 @@ void Game::updateShadows() void Game::drawScene(ProfilerGraph *graph, RunStats *stats) { + ZoneScoped; + const video::SColor fog_color = this->sky->getFogColor(); const video::SColor sky_color = this->sky->getSkyColor(); diff --git a/src/client/mapblock_mesh.cpp b/src/client/mapblock_mesh.cpp index 32d559149..9c0caa9d0 100644 --- a/src/client/mapblock_mesh.cpp +++ b/src/client/mapblock_mesh.cpp @@ -28,6 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "minimap.h" #include "content_mapblock.h" #include "util/directiontables.h" +#include "util/tracy_wrapper.h" #include "client/meshgen/collector.h" #include "client/renderingengine.h" #include @@ -611,6 +612,8 @@ MapBlockMesh::MapBlockMesh(Client *client, MeshMakeData *data, v3s16 camera_offs m_last_crack(-1), m_last_daynight_ratio((u32) -1) { + ZoneScoped; + for (auto &m : m_mesh) m = new scene::SMesh(); m_enable_shaders = data->m_use_shaders; diff --git a/src/client/sound/sound_manager.cpp b/src/client/sound/sound_manager.cpp index 6aae5bb7f..fc171d565 100644 --- a/src/client/sound/sound_manager.cpp +++ b/src/client/sound/sound_manager.cpp @@ -26,6 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "sound_singleton.h" #include "util/numeric.h" // myrand() +#include "util/tracy_wrapper.h" #include "filesys.h" #include "porting.h" @@ -501,6 +502,8 @@ void *OpenALSoundManager::run() u64 t_step_start = porting::getTimeMs(); while (true) { + auto framemarker = FrameMarker("OpenALSoundManager::run()-frame").started(); + auto get_time_since_last_step = [&] { return (f32)(porting::getTimeMs() - t_step_start); }; diff --git a/src/cmake_config.h.in b/src/cmake_config.h.in index a8eb53edd..5dc6e4b74 100644 --- a/src/cmake_config.h.in +++ b/src/cmake_config.h.in @@ -41,3 +41,4 @@ #cmakedefine01 BUILD_UNITTESTS #cmakedefine01 BUILD_BENCHMARKS #cmakedefine01 USE_SDL2 +#cmakedefine01 BUILD_WITH_TRACY diff --git a/src/gui/guiEngine.cpp b/src/gui/guiEngine.cpp index fdc13fa14..988d2924b 100644 --- a/src/gui/guiEngine.cpp +++ b/src/gui/guiEngine.cpp @@ -40,6 +40,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include #include "client/imagefilters.h" +#include "util/tracy_wrapper.h" #if USE_SOUND #include "client/sound/sound_openal.h" @@ -329,9 +330,12 @@ void GUIEngine::run() fps_control.reset(); - while (m_rendering_engine->run() && !m_startgame && !m_kill) { + auto framemarker = FrameMarker("GUIEngine::run()-frame").started(); + while (m_rendering_engine->run() && !m_startgame && !m_kill) { + framemarker.end(); fps_control.limit(device, &dtime); + framemarker.start(); if (device->isWindowVisible()) { // check if we need to update the "upper left corner"-text @@ -371,6 +375,7 @@ void GUIEngine::run() m_menu->getAndroidUIInput(); #endif } + framemarker.end(); m_script->beforeClose(); diff --git a/src/porting.cpp b/src/porting.cpp index da972926a..f6409c56c 100644 --- a/src/porting.cpp +++ b/src/porting.cpp @@ -73,6 +73,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "filesys.h" #include "log.h" #include "util/string.h" +#include "util/tracy_wrapper.h" #include #include #include @@ -960,6 +961,8 @@ void TrackFreedMemory(size_t amount) void TriggerMemoryTrim() { + ZoneScoped; + constexpr auto MO = std::memory_order_relaxed; if (memory_freed.load(MO) >= MEMORY_TRIM_THRESHOLD) { // Synchronize call diff --git a/src/script/cpp_api/s_base.cpp b/src/script/cpp_api/s_base.cpp index e9907f304..bdd2514e6 100644 --- a/src/script/cpp_api/s_base.cpp +++ b/src/script/cpp_api/s_base.cpp @@ -32,6 +32,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/client.h" #endif +#if BUILD_WITH_TRACY + #include "tracy/TracyLua.hpp" +#endif extern "C" { #include "lualib.h" @@ -95,6 +98,11 @@ ScriptApiBase::ScriptApiBase(ScriptingType type): lua_pushstring(m_luastack, LUA_BITLIBNAME); lua_call(m_luastack, 1, 0); +#if BUILD_WITH_TRACY + // Load tracy lua bindings + tracy::LuaRegister(m_luastack); +#endif + // Make the ScriptApiBase* accessible to ModApiBase #if INDIRECT_SCRIPTAPI_RIDX *(void **)(lua_newuserdata(m_luastack, sizeof(void *))) = this; diff --git a/src/script/cpp_api/s_security.cpp b/src/script/cpp_api/s_security.cpp index 7c8ba8931..9a4b0763b 100644 --- a/src/script/cpp_api/s_security.cpp +++ b/src/script/cpp_api/s_security.cpp @@ -109,7 +109,12 @@ void ScriptApiSecurity::initializeSecurity() "string", "table", "math", - "bit" + "bit", + // Not sure if completely safe. But if someone enables tracy, they'll + // know what they do. +#if BUILD_WITH_TRACY + "tracy", +#endif }; static const char *io_whitelist[] = { "close", @@ -303,6 +308,11 @@ void ScriptApiSecurity::initializeSecurityClient() "table", "math", "bit", + // Not sure if completely safe. But if someone enables tracy, they'll + // know what they do. +#if BUILD_WITH_TRACY + "tracy", +#endif }; static const char *os_whitelist[] = { "clock", diff --git a/src/server.cpp b/src/server.cpp index 92f97172b..6421fc175 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -75,6 +75,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "gameparams.h" #include "particles.h" #include "gettext.h" +#include "util/tracy_wrapper.h" class ClientNotFoundException : public BaseException { @@ -101,6 +102,8 @@ private: void *ServerThread::run() { + ZoneScoped; + BEGIN_DEBUG_EXCEPTION_HANDLER /* @@ -110,6 +113,7 @@ void *ServerThread::run() * server-step frequency. Receive() is used for waiting between the steps. */ + auto framemarker = FrameMarker("ServerThread::run()-frame").started(); try { m_server->AsyncRunStep(0.0f, true); } catch (con::ConnectionBindFailed &e) { @@ -119,10 +123,12 @@ void *ServerThread::run() } catch (ModError &e) { m_server->setAsyncFatalError(e.what()); } + framemarker.end(); float dtime = 0.0f; while (!stopRequested()) { + framemarker.start(); ScopeProfiler spm(g_profiler, "Server::RunStep() (max)", SPT_MAX); u64 t0 = porting::getTimeUs(); @@ -149,6 +155,7 @@ void *ServerThread::run() } dtime = 1e-6f * (porting::getTimeUs() - t0); + framemarker.end(); } END_DEBUG_EXCEPTION_HANDLER @@ -607,6 +614,9 @@ void Server::step() void Server::AsyncRunStep(float dtime, bool initial_step) { + ZoneScoped; + auto framemarker = FrameMarker("Server::AsyncRunStep()-frame").started(); + { // Send blocks to clients SendBlocks(dtime); @@ -1055,6 +1065,9 @@ void Server::AsyncRunStep(float dtime, bool initial_step) void Server::Receive(float timeout) { + ZoneScoped; + auto framemarker = FrameMarker("Server::Receive()-frame").started(); + const u64 t0 = porting::getTimeUs(); const float timeout_us = timeout * 1e6f; auto remaining_time_us = [&]() -> float { diff --git a/src/util/tracy_wrapper.h b/src/util/tracy_wrapper.h new file mode 100644 index 000000000..0c61ba837 --- /dev/null +++ b/src/util/tracy_wrapper.h @@ -0,0 +1,200 @@ +/* +Minetest +Copyright (C) 2024 DS + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +/* + * Wrapper for , so that we can use Tracy's macros without + * having it as mandatory dependency. + * + * For annotations that you don't intend to upstream, you can also include + * directly (which also works in irr/). + */ + +#pragma once + +#include "config.h" +#include "util/basic_macros.h" + +#if BUILD_WITH_TRACY + +#include // IWYU pragma: export + +#else + +// Copied from Tracy.hpp + +#define TracyNoop + +#define ZoneNamed(x,y) +#define ZoneNamedN(x,y,z) +#define ZoneNamedC(x,y,z) +#define ZoneNamedNC(x,y,z,w) + +#define ZoneTransient(x,y) +#define ZoneTransientN(x,y,z) + +#define ZoneScoped +#define ZoneScopedN(x) +#define ZoneScopedC(x) +#define ZoneScopedNC(x,y) + +#define ZoneText(x,y) +#define ZoneTextV(x,y,z) +#define ZoneTextF(x,...) +#define ZoneTextVF(x,y,...) +#define ZoneName(x,y) +#define ZoneNameV(x,y,z) +#define ZoneNameF(x,...) +#define ZoneNameVF(x,y,...) +#define ZoneColor(x) +#define ZoneColorV(x,y) +#define ZoneValue(x) +#define ZoneValueV(x,y) +#define ZoneIsActive false +#define ZoneIsActiveV(x) false + +#define FrameMark +#define FrameMarkNamed(x) +#define FrameMarkStart(x) +#define FrameMarkEnd(x) + +#define FrameImage(x,y,z,w,a) + +#define TracyLockable( type, varname ) type varname +#define TracyLockableN( type, varname, desc ) type varname +#define TracySharedLockable( type, varname ) type varname +#define TracySharedLockableN( type, varname, desc ) type varname +#define LockableBase( type ) type +#define SharedLockableBase( type ) type +#define LockMark(x) (void)x +#define LockableName(x,y,z) + +#define TracyPlot(x,y) +#define TracyPlotConfig(x,y,z,w,a) + +#define TracyMessage(x,y) +#define TracyMessageL(x) +#define TracyMessageC(x,y,z) +#define TracyMessageLC(x,y) +#define TracyAppInfo(x,y) + +#define TracyAlloc(x,y) +#define TracyFree(x) +#define TracySecureAlloc(x,y) +#define TracySecureFree(x) + +#define TracyAllocN(x,y,z) +#define TracyFreeN(x,y) +#define TracySecureAllocN(x,y,z) +#define TracySecureFreeN(x,y) + +#define ZoneNamedS(x,y,z) +#define ZoneNamedNS(x,y,z,w) +#define ZoneNamedCS(x,y,z,w) +#define ZoneNamedNCS(x,y,z,w,a) + +#define ZoneTransientS(x,y,z) +#define ZoneTransientNS(x,y,z,w) + +#define ZoneScopedS(x) +#define ZoneScopedNS(x,y) +#define ZoneScopedCS(x,y) +#define ZoneScopedNCS(x,y,z) + +#define TracyAllocS(x,y,z) +#define TracyFreeS(x,y) +#define TracySecureAllocS(x,y,z) +#define TracySecureFreeS(x,y) + +#define TracyAllocNS(x,y,z,w) +#define TracyFreeNS(x,y,z) +#define TracySecureAllocNS(x,y,z,w) +#define TracySecureFreeNS(x,y,z) + +#define TracyMessageS(x,y,z) +#define TracyMessageLS(x,y) +#define TracyMessageCS(x,y,z,w) +#define TracyMessageLCS(x,y,z) + +#define TracySourceCallbackRegister(x,y) +#define TracyParameterRegister(x,y) +#define TracyParameterSetup(x,y,z,w) +#define TracyIsConnected false +#define TracyIsStarted false +#define TracySetProgramName(x) + +#define TracyFiberEnter(x) +#define TracyFiberEnterHint(x,y) +#define TracyFiberLeave + +#endif + + +// Helper for making sure frames end in all possible control flow path +class FrameMarker +{ + const char *m_name; + bool m_started = false; + +public: + FrameMarker(const char *name) : m_name(name) {} + + ~FrameMarker() { end(); } + + DISABLE_CLASS_COPY(FrameMarker) + + FrameMarker(FrameMarker &&other) noexcept : + m_name(other.m_name), m_started(other.m_started) + { + other.m_started = false; + } + + FrameMarker &operator=(FrameMarker &&other) noexcept + { + if (&other != this) { + end(); + m_name = other.m_name; + m_started = other.m_started; + other.m_started = false; + } + return *this; + } + + FrameMarker &&started() && + { + if (!m_started) { + FrameMarkStart(m_name); + m_started = true; + } + return std::move(*this); + } + + void start() + { + // no move happens, because we drop the reference + (void)std::move(*this).started(); + } + + void end() + { + if (m_started) { + m_started = false; + FrameMarkEnd(m_name); + } + } +};