From f6850efbf04f8b77e74ba34e08f7c52a074e66d8 Mon Sep 17 00:00:00 2001 From: Camille Martel Date: Fri, 9 Oct 2026 16:54:39 -0700 Subject: [PATCH] Add filtering and hierarchical view to plots selection - Filter text allows restricting plots to ones matching the name - Hierarchical view displays plots as a tree, separating levels based on slash-separated names, i.e.: "GPU/Counters/Draws" and "GPU/Counters/Primitives" will be leaf nodes under GPU > Counters --- profiler/src/profiler/TracyUserData.cpp | 2 + profiler/src/profiler/TracyView.hpp | 1 + profiler/src/profiler/TracyViewData.hpp | 1 + profiler/src/profiler/TracyView_Options.cpp | 161 ++++++++++++++++++-- 4 files changed, 156 insertions(+), 9 deletions(-) diff --git a/profiler/src/profiler/TracyUserData.cpp b/profiler/src/profiler/TracyUserData.cpp index 6af420bc..aff8b0ed 100644 --- a/profiler/src/profiler/TracyUserData.cpp +++ b/profiler/src/profiler/TracyUserData.cpp @@ -177,6 +177,7 @@ bool UserData::Save() { "frameTarget", m_viewData.frameTarget }, { "shortenName", (int)m_viewData.shortenName }, { "plotHeight", m_viewData.plotHeight }, + { "plotsHierarchy", m_viewData.plotsHierarchy }, } }, }; @@ -278,6 +279,7 @@ bool UserData::Load() LoadValue( options, "frameTarget", m_viewData.frameTarget ); LoadValueCast( options, "shortenName", m_viewData.shortenName ); LoadValue( options, "plotHeight", m_viewData.plotHeight ); + LoadValue( options, "plotsHierarchy", m_viewData.plotsHierarchy ); } if( json.contains( "sourceSubstitutions" ) ) diff --git a/profiler/src/profiler/TracyView.hpp b/profiler/src/profiler/TracyView.hpp index c68ec688..476ec3ba 100644 --- a/profiler/src/profiler/TracyView.hpp +++ b/profiler/src/profiler/TracyView.hpp @@ -637,6 +637,7 @@ private: ImGuiTextFilter m_statisticsFilter; ImGuiTextFilter m_statisticsImageFilter; ImGuiTextFilter m_userTextFilter; + ImGuiTextFilter m_plotFilter; unordered_flat_set m_filteredZones; Region m_highlight; diff --git a/profiler/src/profiler/TracyViewData.hpp b/profiler/src/profiler/TracyViewData.hpp index e35efc19..d776c343 100644 --- a/profiler/src/profiler/TracyViewData.hpp +++ b/profiler/src/profiler/TracyViewData.hpp @@ -54,6 +54,7 @@ struct ViewData uint8_t inheritParentColors = true; uint8_t forceColors = false; uint8_t ghostZones = true; + uint8_t plotsHierarchy = false; ShortenName shortenName = ShortenName::NoSpaceAndNormalize; uint32_t frameTarget = 60; diff --git a/profiler/src/profiler/TracyView_Options.cpp b/profiler/src/profiler/TracyView_Options.cpp index 03afdbb2..5195593f 100644 --- a/profiler/src/profiler/TracyView_Options.cpp +++ b/profiler/src/profiler/TracyView_Options.cpp @@ -1,5 +1,8 @@ #include #include +#include +#include +#include #include "TracyFilesystem.hpp" #include "TracyImGui.hpp" @@ -28,6 +31,102 @@ static void DefaultMarker( bool active, bool tooltip = true ) } } +struct PlotTreeNode +{ + std::map children; + std::vector plots; +}; + +static void BuildPlotTree( PlotTreeNode& root, const std::vector& plots, const Worker& worker ) +{ + for( const auto* p : plots ) + { + const char* name = worker.GetString( p->name ); + PlotTreeNode* node = &root; + const char* segStart = name; + for(;;) + { + const char* slash = strchr( segStart, '/' ); + if( !slash ) + { + node->plots.emplace_back( p ); + break; + } + node = &node->children[std::string( segStart, slash - segStart )]; + segStart = slash + 1; + } + } +} + +static void CountPlotTreeVisibility( const PlotTreeNode& node, TimelineController& tc, size_t& total, size_t& visible ) +{ + for( const auto& child : node.children ) CountPlotTreeVisibility( child.second, tc, total, visible ); + for( const auto* p : node.plots ) + { + total++; + if( tc.GetItem( p ).IsVisible() ) visible++; + } +} + +static void SetPlotTreeVisibility( PlotTreeNode& node, TimelineController& tc, bool visible ) +{ + for( auto& child : node.children ) SetPlotTreeVisibility( child.second, tc, visible ); + for( const auto* p : node.plots ) + { + tc.GetItem( p ).SetVisible( visible ); + } +} + +// fullPath is the '/'-joined path to this node, used as a stable ImGui ID (the tree is rebuilt every frame). +static void DrawPlotTreeNode( const std::string& label, const std::string& fullPath, PlotTreeNode& node, TimelineController& tc, const Worker& worker ) +{ + size_t total = 0, visibleCount = 0; + CountPlotTreeVisibility( node, tc, total, visibleCount ); + bool allVisible = total > 0 && visibleCount == total; + const bool mixed = visibleCount > 0 && visibleCount < total; + + const bool nodeExpand = ImGui::TreeNodeEx( fullPath.c_str(), ImGuiTreeNodeFlags_None, "" ); + ImGui::SameLine(); + const std::string checkboxId = "##hdr_" + fullPath; + ImGui::PushItemFlag( ImGuiItemFlags_MixedValue, mixed ); + const bool clicked = SmallCheckbox( checkboxId.c_str(), &allVisible ); + ImGui::PopItemFlag(); + if( clicked ) + { + SetPlotTreeVisibility( node, tc, allVisible ); + } + ImGui::SameLine(); + ImGui::TextUnformatted( label.c_str() ); + + if( nodeExpand ) + { + for( auto& child : node.children ) + { + DrawPlotTreeNode( child.first, fullPath + "/" + child.first, child.second, tc, worker ); + } + for( const auto* p : node.plots ) + { + const char* fullName = worker.GetString( p->name ); + const char* slash = strrchr( fullName, '/' ); + const char* leaf = slash ? slash + 1 : fullName; + + SmallColorBox( GetPlotColor( *p, worker ) ); + ImGui::SameLine(); + bool visible = tc.GetItem( p ).IsVisible(); + char leafId[256]; + snprintf( leafId, sizeof( leafId ), "%s##%p", leaf, (const void*)p ); + if( SmallCheckbox( leafId, &visible ) ) + { + tc.GetItem( p ).SetVisible( visible ); + } + ImGui::SameLine(); + ImGui::TextDisabled( "%s data points", RealToString( p->data.size() ) ); + } + ImGui::TreePop(); + } +} + + void View::DrawOptions() { static bool default_markers_active = false; @@ -678,9 +777,25 @@ void View::DrawOptions() if( expand ) { ImGui::SameLine(); + bool hierarchy = m_vd.plotsHierarchy; + if( ImGui::RadioButton( "Flat", !hierarchy ) ) m_vd.plotsHierarchy = false; + ImGui::SameLine(); + if( ImGui::RadioButton( "Hierarchical", hierarchy ) ) m_vd.plotsHierarchy = true; + hierarchy = m_vd.plotsHierarchy; + + std::vector filteredPlots; + filteredPlots.reserve( m_worker.GetPlots().size() ); + for( const auto& p : m_worker.GetPlots() ) + { + if( m_plotFilter.PassFilter( m_worker.GetString( p->name ) ) ) + { + filteredPlots.emplace_back( p ); + } + } + if( ImGui::SmallButton( "Select all" ) ) { - for( const auto& p : m_worker.GetPlots() ) + for( const auto* p : filteredPlots ) { m_tc.GetItem( p ).SetVisible( true ); } @@ -688,19 +803,47 @@ void View::DrawOptions() ImGui::SameLine(); if( ImGui::SmallButton( "Unselect all" ) ) { - for( const auto& p : m_worker.GetPlots() ) + for( const auto* p : filteredPlots ) { m_tc.GetItem( p ).SetVisible( false ); } } - - for( const auto& p : m_worker.GetPlots() ) + ImGui::SameLine(); + m_plotFilter.Draw( ICON_FA_FILTER "###plotFilter", 200 ); + ImGui::SameLine(); + if( ImGui::Button( ICON_FA_DELETE_LEFT " Clear###plotFilterClear" ) ) { - SmallColorBox( GetPlotColor( *p, m_worker ) ); - ImGui::SameLine(); - m_tc.GetItem( p ).VisibilityCheckbox(); - ImGui::SameLine(); - ImGui::TextDisabled( "%s data points", RealToString( p->data.size() ) ); + m_plotFilter.Clear(); + } + + if( hierarchy ) + { + PlotTreeNode root; + BuildPlotTree( root, filteredPlots, m_worker ); + + for( auto& child : root.children ) + { + DrawPlotTreeNode( child.first, child.first, child.second, m_tc, m_worker ); + } + for( const auto* p : root.plots ) + { + SmallColorBox( GetPlotColor( *p, m_worker ) ); + ImGui::SameLine(); + m_tc.GetItem( p ).VisibilityCheckbox(); + ImGui::SameLine(); + ImGui::TextDisabled( "%s data points", RealToString( p->data.size() ) ); + } + } + else + { + for( const auto* p : filteredPlots ) + { + SmallColorBox( GetPlotColor( *p, m_worker ) ); + ImGui::SameLine(); + m_tc.GetItem( p ).VisibilityCheckbox(); + ImGui::SameLine(); + ImGui::TextDisabled( "%s data points", RealToString( p->data.size() ) ); + } } ImGui::TreePop(); }