From 0347c884640ca0b56510b4910a59bbc5edd67dfb Mon Sep 17 00:00:00 2001 From: Zachary Ferguson Date: Wed, 6 May 2026 12:59:54 -0400 Subject: [PATCH 1/3] Add optional Tracy frame profiler - Add CMake option IPC_TOOLKIT_WITH_TRACY and cmake/recipes/tracy.cmake to fetch Tracy via CPM - Expose IPC_TOOLKIT_WITH_TRACY in src/ipc/config.hpp.in - Include Tracy headers in profiler.hpp and add helper concat macros - Update dependency graph and docs to list Tracy --- CMakeLists.txt | 7 + cmake/recipes/tracy.cmake | 17 ++ docs/source/_static/graphviz/dependencies.dot | 3 + docs/source/_static/graphviz/dependencies.svg | 233 +++++++++--------- docs/source/about/dependencies.rst | 6 + src/ipc/config.hpp.in | 1 + src/ipc/utils/profiler.hpp | 45 +++- 7 files changed, 197 insertions(+), 115 deletions(-) create mode 100644 cmake/recipes/tracy.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index d52d55134..5d0cc3932 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,6 +82,7 @@ option(IPC_TOOLKIT_WITH_ABSEIL "Use Abseil's hash functions" option(IPC_TOOLKIT_WITH_FILIB "Use filib for interval arithmetic" ON) option(IPC_TOOLKIT_WITH_INEXACT_CCD "Use the original inexact CCD method of IPC" OFF) option(IPC_TOOLKIT_WITH_PROFILER "Enable performance profiler" OFF) +option(IPC_TOOLKIT_WITH_TRACY "Enable Tracy frame profiler" OFF) # Advanced options option(IPC_TOOLKIT_WITH_CODE_COVERAGE "Enable coverage reporting" OFF) @@ -250,6 +251,12 @@ if(IPC_TOOLKIT_WITH_PROFILER) target_link_libraries(ipc_toolkit PUBLIC nlohmann_json::nlohmann_json) endif() +# Tracy profiler +if(IPC_TOOLKIT_WITH_TRACY) + include(tracy) + target_link_libraries(ipc_toolkit PUBLIC Tracy::TracyClient) +endif() + # Extra warnings (link last for highest priority) include(ipc_toolkit_warnings) target_link_libraries(ipc_toolkit PRIVATE ipc::toolkit::warnings) diff --git a/cmake/recipes/tracy.cmake b/cmake/recipes/tracy.cmake new file mode 100644 index 000000000..cd2697660 --- /dev/null +++ b/cmake/recipes/tracy.cmake @@ -0,0 +1,17 @@ +# Tracy (https://github.com/wolfpld/tracy) +# License: BSD-3-Clause +if(TARGET Tracy::TracyClient) + return() +endif() + +message(STATUS "Third-party: creating target 'Tracy::TracyClient'") + +include(CPM) +CPMAddPackage( + URI "gh:wolfpld/tracy@0.13.1" + OPTIONS + "TRACY_ENABLE ${IPC_TOOLKIT_WITH_TRACY}" + "TRACY_ON_DEMAND ON" +) + +set_target_properties(TracyClient PROPERTIES FOLDER "ThirdParty") \ No newline at end of file diff --git a/docs/source/_static/graphviz/dependencies.dot b/docs/source/_static/graphviz/dependencies.dot index a2ae83709..50643f46d 100644 --- a/docs/source/_static/graphviz/dependencies.dot +++ b/docs/source/_static/graphviz/dependencies.dot @@ -84,4 +84,7 @@ digraph "IPC Toolkit Dependencies" { // ipc_toolkit -> nlohmann_json "node14" [label = "nlohmann_json\n(nlohmann_json::nlohmann_json)";shape = box;style = "rounded,filled";fillcolor = "#FFE6CC";color = "#DAA52D";]; "node5" -> "node14" [color = "#8FB976";]; + // ipc_toolkit -> tracy + "node16" [label = "TracyClient\n(Tracy::TracyClient)";shape = box;style = "rounded,filled";fillcolor = "#D5E8D4";color = "#8FB976";]; + "node5" -> "node16" [color = "#8FB976";]; } \ No newline at end of file diff --git a/docs/source/_static/graphviz/dependencies.svg b/docs/source/_static/graphviz/dependencies.svg index 0802bd436..d502307ca 100644 --- a/docs/source/_static/graphviz/dependencies.svg +++ b/docs/source/_static/graphviz/dependencies.svg @@ -1,299 +1,312 @@ - - + IPC Toolkit Dependencies clusterLegend -Legend +Legend legendNode0 - -Static Library + +Static Library legendNode1 - -Shared Library + +Shared Library legendNode0->legendNode1 - - -Public + + +Public legendNode2 - -Interface Library + +Interface Library legendNode1->legendNode2 - - -Private + + +Private legendNode2->legendNode0 - - -Interface + + +Interface node5 - -ipc_toolkit -(ipc::toolkit) + +ipc_toolkit +(ipc::toolkit) node0 - -Eigen3_Eigen -(Eigen3::Eigen) + +Eigen3_Eigen +(Eigen3::Eigen) node5->node0 - - + + node1 - -filib -(filib::filib) + +filib +(filib::filib) node5->node1 - - + + node2 - -igl_core -(igl::core) + +igl_core +(igl::core) node5->node2 - - + + node3 - -igl_predicates -(igl::predicates) + +igl_predicates +(igl::predicates) node5->node3 - - + + node15 - -xsimd -(xsimd::xsimd) + +xsimd +(xsimd::xsimd) node5->node15 - - + + node6 - -robin_map -(tsl::robin_map) + +robin_map +(tsl::robin_map) node5->node6 - - + + node7 - -scalable_ccd -(scalable_ccd::scalable_ccd) + +scalable_ccd +(scalable_ccd::scalable_ccd) node5->node7 - - + + node8 - -spdlog -(spdlog::spdlog) + +spdlog +(spdlog::spdlog) node5->node8 - - + + node9 - -tbb -(TBB::tbb) + +tbb +(TBB::tbb) node5->node9 - - + + node11 - -tight_inclusion -(tight_inclusion::tight_inclusion) + +tight_inclusion +(tight_inclusion::tight_inclusion) node5->node11 - - + + node12 - -absl_hash -(absl::hash) + +absl_hash +(absl::hash) node5->node12 - - + + node13 - -TinyAD -(TinyAD::TinyAD) + +TinyAD +(TinyAD::TinyAD) node5->node13 - - + + node14 - -nlohmann_json -(nlohmann_json::nlohmann_json) + +nlohmann_json +(nlohmann_json::nlohmann_json) node5->node14 - - + + + + + +node16 + +TracyClient +(Tracy::TracyClient) + + + +node5->node16 + + node2->node0 - - + + node3->node2 - - + + node4 - -predicates -(predicates::predicates) + +predicates +(predicates::predicates) node3->node4 - - + + node7->node0 - - + + node7->node8 - - + + node7->node9 - - + + node11->node0 - - + + node11->node8 - - + + node13->node0 - - + + node13->node9 - - + + diff --git a/docs/source/about/dependencies.rst b/docs/source/about/dependencies.rst index dce384b85..5ca6d6760 100644 --- a/docs/source/about/dependencies.rst +++ b/docs/source/about/dependencies.rst @@ -93,6 +93,12 @@ Additionally, IPC Toolkit may optionally use the following libraries: - `github.com/nlohmann/json `_ - |:white_large_square:| - ``IPC_TOOLKIT_WITH_PROFILER`` + * - Tracy + - Frame profiler + - BSD-3-Clause + - `github.com/wolfpld/tracy `_ + - |:white_large_square:| + - ``IPC_TOOLKIT_WITH_TRACY`` * - rational-cpp - Rational arithmetic used for exact intersection checks (requires `GMP `_ to be installed at a system level) - MIT diff --git a/src/ipc/config.hpp.in b/src/ipc/config.hpp.in index 2cfdba279..3c5c84e75 100644 --- a/src/ipc/config.hpp.in +++ b/src/ipc/config.hpp.in @@ -20,6 +20,7 @@ #cmakedefine IPC_TOOLKIT_WITH_ABSEIL #cmakedefine IPC_TOOLKIT_WITH_FILIB #cmakedefine IPC_TOOLKIT_WITH_PROFILER +#cmakedefine IPC_TOOLKIT_WITH_TRACY // #define IPC_TOOLKIT_DEBUG_AUTODIFF namespace ipc { diff --git a/src/ipc/utils/profiler.hpp b/src/ipc/utils/profiler.hpp index 766fd2097..525a00b78 100644 --- a/src/ipc/utils/profiler.hpp +++ b/src/ipc/utils/profiler.hpp @@ -2,6 +2,17 @@ #include +#ifdef IPC_TOOLKIT_WITH_TRACY +#include +#endif + +#include + +// Helper macro to stringify/paste after expansion +#define IPC_TOOLKIT_PROFILE_BLOCK_CONCAT_IMPL(a, b) a##b +#define IPC_TOOLKIT_PROFILE_BLOCK_CONCAT(a, b) \ + IPC_TOOLKIT_PROFILE_BLOCK_CONCAT_IMPL(a, b) + #ifdef IPC_TOOLKIT_WITH_PROFILER // clang-format off @@ -15,14 +26,25 @@ #include #include -// Helper macro to stringify/paste after expansion -#define IPC_TOOLKIT_PROFILE_BLOCK_CONCAT_IMPL(a, b) a##b -#define IPC_TOOLKIT_PROFILE_BLOCK_CONCAT(a, b) \ - IPC_TOOLKIT_PROFILE_BLOCK_CONCAT_IMPL(a, b) - +#if defined(IPC_TOOLKIT_WITH_TRACY) && defined(TRACY_ENABLE) +// ZoneScoped + ZoneName supports both compile-time string literals and +// runtime std::string expressions, unlike ZoneScopedN which requires a +// constexpr const char*. +#define IPC_TOOLKIT_PROFILE_BLOCK(...) \ + const std::string IPC_TOOLKIT_PROFILE_BLOCK_CONCAT( \ + __ipc_zone_name_, __LINE__)(__VA_ARGS__); \ + ipc::ProfilePoint IPC_TOOLKIT_PROFILE_BLOCK_CONCAT( \ + __ipc_profile_point_, __LINE__)( \ + IPC_TOOLKIT_PROFILE_BLOCK_CONCAT(__ipc_zone_name_, __LINE__)); \ + ZoneScoped; \ + ZoneName( \ + IPC_TOOLKIT_PROFILE_BLOCK_CONCAT(__ipc_zone_name_, __LINE__).c_str(), \ + IPC_TOOLKIT_PROFILE_BLOCK_CONCAT(__ipc_zone_name_, __LINE__).size()) +#else #define IPC_TOOLKIT_PROFILE_BLOCK(...) \ ipc::ProfilePoint IPC_TOOLKIT_PROFILE_BLOCK_CONCAT( \ __ipc_profile_point_, __COUNTER__)(__VA_ARGS__) +#endif namespace ipc { @@ -130,8 +152,21 @@ template class ProfilePoint { } // namespace ipc +#elif defined(IPC_TOOLKIT_WITH_TRACY) && defined(TRACY_ENABLE) + +// Custom profiler disabled: Tracy zone only. +// ZoneScoped + ZoneName supports runtime strings, unlike ZoneScopedN. +#define IPC_TOOLKIT_PROFILE_BLOCK(...) \ + const std::string IPC_TOOLKIT_PROFILE_BLOCK_CONCAT( \ + __ipc_zone_name_, __LINE__)(__VA_ARGS__); \ + ZoneScoped; \ + ZoneName( \ + IPC_TOOLKIT_PROFILE_BLOCK_CONCAT(__ipc_zone_name_, __LINE__).c_str(), \ + IPC_TOOLKIT_PROFILE_BLOCK_CONCAT(__ipc_zone_name_, __LINE__).size()) + #else +// No profiling enabled: no-op. #define IPC_TOOLKIT_PROFILE_BLOCK(...) #endif \ No newline at end of file From cfc5578134246bea7ba419b3d8d5f597d48f988c Mon Sep 17 00:00:00 2001 From: Zachary Ferguson Date: Wed, 6 May 2026 13:36:42 -0400 Subject: [PATCH 2/3] Drop support for runtime zone names --- src/ipc/potentials/potential.cpp | 22 +++++++-------- src/ipc/utils/profiler.hpp | 48 ++++++++------------------------ 2 files changed, 23 insertions(+), 47 deletions(-) diff --git a/src/ipc/potentials/potential.cpp b/src/ipc/potentials/potential.cpp index 839ae594c..f06c6c139 100644 --- a/src/ipc/potentials/potential.cpp +++ b/src/ipc/potentials/potential.cpp @@ -40,7 +40,7 @@ double Potential::operator()( Eigen::ConstRef X) const { assert(X.rows() == mesh.num_vertices()); - IPC_TOOLKIT_PROFILE_BLOCK(this->name() + "::operator()"); + IPC_TOOLKIT_PROFILE_BLOCK("Potential::operator()"); return tbb::parallel_reduce( tbb::blocked_range(size_t(0), collisions.size()), 0.0, @@ -63,7 +63,7 @@ Eigen::VectorXd Potential::gradient( Eigen::ConstRef X) const { assert(X.rows() == mesh.num_vertices()); - IPC_TOOLKIT_PROFILE_BLOCK(this->name() + "::gradient()"); + IPC_TOOLKIT_PROFILE_BLOCK("Potential::gradient()"); if (collisions.empty()) { return Eigen::VectorXd::Zero(X.size()); @@ -74,7 +74,7 @@ Eigen::VectorXd Potential::gradient( tbb::combinable grad(Eigen::VectorXd::Zero(X.size())); { - IPC_TOOLKIT_PROFILE_BLOCK("compute local gradients"); + IPC_TOOLKIT_PROFILE_BLOCK("Compute Local Gradients"); tbb::parallel_for(size_t(0), collisions.size(), [&](size_t i) { const TCollision& collision = collisions[i]; @@ -88,7 +88,7 @@ Eigen::VectorXd Potential::gradient( } { - IPC_TOOLKIT_PROFILE_BLOCK("combine local gradients"); + IPC_TOOLKIT_PROFILE_BLOCK("Combine Local Gradients"); return grad.combine([](const Eigen::VectorXd& a, const Eigen::VectorXd& b) { return a + b; }); } @@ -102,7 +102,7 @@ Eigen::SparseMatrix Potential::hessian( const PSDProjectionMethod project_hessian_to_psd) const { assert(X.rows() == mesh.num_vertices()); - IPC_TOOLKIT_PROFILE_BLOCK(this->name() + "::hessian()"); + IPC_TOOLKIT_PROFILE_BLOCK("Potential::gradient()"); if (collisions.empty()) { return Eigen::SparseMatrix(X.size(), X.size()); @@ -129,7 +129,7 @@ Eigen::SparseMatrix Potential::hessian( MatrixMaxNd local_hess; { - IPC_TOOLKIT_PROFILE_BLOCK("compute local hessian"); + IPC_TOOLKIT_PROFILE_BLOCK("Compute Local Hessian"); local_hess = this->hessian( collision, collision.dof(X, edges, faces), project_hessian_to_psd); @@ -137,7 +137,7 @@ Eigen::SparseMatrix Potential::hessian( { IPC_TOOLKIT_PROFILE_BLOCK( - "map local hessian to global triplets"); + "Map Local Hessian to Global Triplets"); local_hessian_to_global_triplets( local_hess, collision.vertex_ids(edges, faces), dim, *(hess_triplets.cache), mesh.num_vertices()); @@ -152,7 +152,7 @@ Eigen::SparseMatrix Potential::hessian( // storage { - IPC_TOOLKIT_PROFILE_BLOCK("prune local storages"); + IPC_TOOLKIT_PROFILE_BLOCK("Prune Local Storages"); tbb::parallel_for_each( storage.begin(), storage.end(), [](const auto& local_storage) { local_storage.cache->prune(); }); @@ -188,13 +188,13 @@ Eigen::SparseMatrix Potential::hessian( // Allocate triplets { - IPC_TOOLKIT_PROFILE_BLOCK("allocate triplets"); + IPC_TOOLKIT_PROFILE_BLOCK("Allocate Triplets"); triplets.resize(triplet_count); } // Parallel copy into triplets { - IPC_TOOLKIT_PROFILE_BLOCK("parallel copy into triplets"); + IPC_TOOLKIT_PROFILE_BLOCK("Parallel Copy into Triplets"); tbb::parallel_for(size_t(0), storage.size(), [&](size_t i) { const SparseMatrixCache& cache = dynamic_cast( @@ -214,7 +214,7 @@ Eigen::SparseMatrix Potential::hessian( // Sort and assemble { - IPC_TOOLKIT_PROFILE_BLOCK("assemble hessian from triplets"); + IPC_TOOLKIT_PROFILE_BLOCK("Assemble Hessian from Triplets"); hess.setFromTriplets(triplets.begin(), triplets.end()); } diff --git a/src/ipc/utils/profiler.hpp b/src/ipc/utils/profiler.hpp index 525a00b78..de955ad1b 100644 --- a/src/ipc/utils/profiler.hpp +++ b/src/ipc/utils/profiler.hpp @@ -4,15 +4,13 @@ #ifdef IPC_TOOLKIT_WITH_TRACY #include +#else +// Empty macro to avoid compilation errors when Tracy is not enabled. +#define ZoneScopedN(name) ((void)0) #endif #include -// Helper macro to stringify/paste after expansion -#define IPC_TOOLKIT_PROFILE_BLOCK_CONCAT_IMPL(a, b) a##b -#define IPC_TOOLKIT_PROFILE_BLOCK_CONCAT(a, b) \ - IPC_TOOLKIT_PROFILE_BLOCK_CONCAT_IMPL(a, b) - #ifdef IPC_TOOLKIT_WITH_PROFILER // clang-format off @@ -26,25 +24,15 @@ #include #include -#if defined(IPC_TOOLKIT_WITH_TRACY) && defined(TRACY_ENABLE) -// ZoneScoped + ZoneName supports both compile-time string literals and -// runtime std::string expressions, unlike ZoneScopedN which requires a -// constexpr const char*. -#define IPC_TOOLKIT_PROFILE_BLOCK(...) \ - const std::string IPC_TOOLKIT_PROFILE_BLOCK_CONCAT( \ - __ipc_zone_name_, __LINE__)(__VA_ARGS__); \ - ipc::ProfilePoint IPC_TOOLKIT_PROFILE_BLOCK_CONCAT( \ - __ipc_profile_point_, __LINE__)( \ - IPC_TOOLKIT_PROFILE_BLOCK_CONCAT(__ipc_zone_name_, __LINE__)); \ - ZoneScoped; \ - ZoneName( \ - IPC_TOOLKIT_PROFILE_BLOCK_CONCAT(__ipc_zone_name_, __LINE__).c_str(), \ - IPC_TOOLKIT_PROFILE_BLOCK_CONCAT(__ipc_zone_name_, __LINE__).size()) -#else +// Helper macro to stringify/paste after expansion +#define IPC_TOOLKIT_PROFILE_BLOCK_CONCAT_IMPL(a, b) a##b +#define IPC_TOOLKIT_PROFILE_BLOCK_CONCAT(a, b) \ + IPC_TOOLKIT_PROFILE_BLOCK_CONCAT_IMPL(a, b) + #define IPC_TOOLKIT_PROFILE_BLOCK(...) \ ipc::ProfilePoint IPC_TOOLKIT_PROFILE_BLOCK_CONCAT( \ - __ipc_profile_point_, __COUNTER__)(__VA_ARGS__) -#endif + __ipc_profile_point_, __COUNTER__)(__VA_ARGS__); \ + ZoneScopedN(__VA_ARGS__) namespace ipc { @@ -152,21 +140,9 @@ template class ProfilePoint { } // namespace ipc -#elif defined(IPC_TOOLKIT_WITH_TRACY) && defined(TRACY_ENABLE) - -// Custom profiler disabled: Tracy zone only. -// ZoneScoped + ZoneName supports runtime strings, unlike ZoneScopedN. -#define IPC_TOOLKIT_PROFILE_BLOCK(...) \ - const std::string IPC_TOOLKIT_PROFILE_BLOCK_CONCAT( \ - __ipc_zone_name_, __LINE__)(__VA_ARGS__); \ - ZoneScoped; \ - ZoneName( \ - IPC_TOOLKIT_PROFILE_BLOCK_CONCAT(__ipc_zone_name_, __LINE__).c_str(), \ - IPC_TOOLKIT_PROFILE_BLOCK_CONCAT(__ipc_zone_name_, __LINE__).size()) - #else -// No profiling enabled: no-op. -#define IPC_TOOLKIT_PROFILE_BLOCK(...) +// Custom profiler disabled: Tracy zone only. +#define IPC_TOOLKIT_PROFILE_BLOCK(...) ZoneScopedN(__VA_ARGS__) #endif \ No newline at end of file From 28bcfe70952d4b617ef0e80572ad1cb16280e92e Mon Sep 17 00:00:00 2001 From: Zachary Ferguson Date: Thu, 7 May 2026 11:19:04 -0400 Subject: [PATCH 3/3] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/ipc/potentials/potential.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ipc/potentials/potential.cpp b/src/ipc/potentials/potential.cpp index f06c6c139..d4c08feae 100644 --- a/src/ipc/potentials/potential.cpp +++ b/src/ipc/potentials/potential.cpp @@ -102,7 +102,7 @@ Eigen::SparseMatrix Potential::hessian( const PSDProjectionMethod project_hessian_to_psd) const { assert(X.rows() == mesh.num_vertices()); - IPC_TOOLKIT_PROFILE_BLOCK("Potential::gradient()"); + IPC_TOOLKIT_PROFILE_BLOCK("Potential::hessian()"); if (collisions.empty()) { return Eigen::SparseMatrix(X.size(), X.size());