diff --git a/profiler/build/win32/Tracy.vcxproj b/profiler/build/win32/Tracy.vcxproj index 4fc85265..4560db14 100644 --- a/profiler/build/win32/Tracy.vcxproj +++ b/profiler/build/win32/Tracy.vcxproj @@ -144,6 +144,7 @@ + diff --git a/profiler/build/win32/Tracy.vcxproj.filters b/profiler/build/win32/Tracy.vcxproj.filters index a54c40d1..fca9b148 100644 --- a/profiler/build/win32/Tracy.vcxproj.filters +++ b/profiler/build/win32/Tracy.vcxproj.filters @@ -297,6 +297,9 @@ server + + server + diff --git a/server/TracyView.cpp b/server/TracyView.cpp index 496ffe7b..ac24a109 100644 --- a/server/TracyView.cpp +++ b/server/TracyView.cpp @@ -6882,796 +6882,6 @@ void View::DrawWaitStacks() ImGui::End(); } -template -static tracy_force_inline T* GetFrameTreeItemNoGroup( unordered_flat_map& tree, CallstackFrameId idx, const Worker& worker ) -{ - auto it = tree.find( idx.data ); - if( it == tree.end() ) - { - it = tree.emplace( idx.data, T( idx ) ).first; - } - return &it->second; -} - -template -static tracy_force_inline T* GetFrameTreeItemGroup( unordered_flat_map& tree, CallstackFrameId idx, const Worker& worker ) -{ - auto frameDataPtr = worker.GetCallstackFrame( idx ); - if( !frameDataPtr ) return nullptr; - - auto& frameData = *frameDataPtr; - auto& frame = frameData.data[frameData.size-1]; - auto fidx = frame.name.Idx(); - - auto it = tree.find( fidx ); - if( it == tree.end() ) - { - it = tree.emplace( fidx, T( idx ) ).first; - } - return &it->second; -} - -template -static tracy_force_inline T* GetParentFrameTreeItemGroup( unordered_flat_map& tree, CallstackFrameId idx, const Worker& worker ) -{ - auto frameDataPtr = idx.custom ? worker.GetParentCallstackFrame( idx ) : worker.GetCallstackFrame( idx ); - if( !frameDataPtr ) return nullptr; - - auto& frameData = *frameDataPtr; - auto& frame = frameData.data[frameData.size-1]; - auto fidx = frame.name.Idx(); - - auto it = tree.find( fidx ); - if( it == tree.end() ) - { - it = tree.emplace( fidx, T( idx ) ).first; - } - return &it->second; -} - - -unordered_flat_map View::GetCallstackPaths( const MemData& mem, MemRange memRange ) const -{ - unordered_flat_map pathSum; - pathSum.reserve( m_worker.GetCallstackPayloadCount() ); - - if( m_memInfo.range.active ) - { - auto it = std::lower_bound( mem.data.begin(), mem.data.end(), m_memInfo.range.min, []( const auto& lhs, const auto& rhs ) { return lhs.TimeAlloc() < rhs; } ); - if( it != mem.data.end() ) - { - auto end = std::lower_bound( mem.data.begin(), mem.data.end(), m_memInfo.range.max, []( const auto& lhs, const auto& rhs ) { return lhs.TimeAlloc() < rhs; } ); - if( memRange != MemRange::Full ) - { - while( it != end ) - { - auto& ev = *it++; - if( ev.CsAlloc() == 0 ) continue; - if( ( memRange == MemRange::Inactive ) == ( ev.TimeFree() >= 0 && ev.TimeFree() < m_memInfo.range.max ) ) continue; - auto pit = pathSum.find( ev.CsAlloc() ); - if( pit == pathSum.end() ) - { - pathSum.emplace( ev.CsAlloc(), MemPathData { 1, ev.Size() } ); - } - else - { - pit->second.cnt++; - pit->second.mem += ev.Size(); - } - } - } - else - { - while( it != end ) - { - auto& ev = *it++; - if( ev.CsAlloc() == 0 ) continue; - auto pit = pathSum.find( ev.CsAlloc() ); - if( pit == pathSum.end() ) - { - pathSum.emplace( ev.CsAlloc(), MemPathData { 1, ev.Size() } ); - } - else - { - pit->second.cnt++; - pit->second.mem += ev.Size(); - } - } - } - } - } - else - { - if( memRange != MemRange::Full ) - { - for( auto& ev : mem.data ) - { - if( ev.CsAlloc() == 0 ) continue; - if( ( memRange == MemRange::Inactive ) == ( ev.TimeFree() >= 0 ) ) continue; - auto it = pathSum.find( ev.CsAlloc() ); - if( it == pathSum.end() ) - { - pathSum.emplace( ev.CsAlloc(), MemPathData { 1, ev.Size() } ); - } - else - { - it->second.cnt++; - it->second.mem += ev.Size(); - } - } - } - else - { - for( auto& ev : mem.data ) - { - if( ev.CsAlloc() == 0 ) continue; - auto it = pathSum.find( ev.CsAlloc() ); - if( it == pathSum.end() ) - { - pathSum.emplace( ev.CsAlloc(), MemPathData { 1, ev.Size() } ); - } - else - { - it->second.cnt++; - it->second.mem += ev.Size(); - } - } - } - } - return pathSum; -} - -unordered_flat_map View::GetCallstackFrameTreeBottomUp( const MemData& mem ) const -{ - unordered_flat_map root; - auto pathSum = GetCallstackPaths( mem, m_memRangeBottomUp ); - if( m_groupCallstackTreeByNameBottomUp ) - { - for( auto& path : pathSum ) - { - auto& cs = m_worker.GetCallstack( path.first ); - auto base = cs.back(); - auto treePtr = GetFrameTreeItemGroup( root, base, m_worker ); - if( treePtr ) - { - treePtr->count += path.second.cnt; - treePtr->alloc += path.second.mem; - treePtr->callstacks.emplace( path.first ); - for( int i = int( cs.size() ) - 2; i >= 0; i-- ) - { - treePtr = GetFrameTreeItemGroup( treePtr->children, cs[i], m_worker ); - if( !treePtr ) break; - treePtr->count += path.second.cnt; - treePtr->alloc += path.second.mem; - treePtr->callstacks.emplace( path.first ); - } - } - } - } - else - { - for( auto& path : pathSum ) - { - auto& cs = m_worker.GetCallstack( path.first ); - auto base = cs.back(); - auto treePtr = GetFrameTreeItemNoGroup( root, base, m_worker ); - treePtr->count += path.second.cnt; - treePtr->alloc += path.second.mem; - treePtr->callstacks.emplace( path.first ); - for( int i = int( cs.size() ) - 2; i >= 0; i-- ) - { - treePtr = GetFrameTreeItemNoGroup( treePtr->children, cs[i], m_worker ); - treePtr->count += path.second.cnt; - treePtr->alloc += path.second.mem; - treePtr->callstacks.emplace( path.first ); - } - } - } - return root; -} - -unordered_flat_map View::GetCallstackFrameTreeBottomUp( const unordered_flat_map& stacks, bool group ) const -{ - unordered_flat_map root; - if( group ) - { - for( auto& path : stacks ) - { - auto& cs = m_worker.GetCallstack( path.first ); - auto base = cs.back(); - auto treePtr = GetFrameTreeItemGroup( root, base, m_worker ); - if( treePtr ) - { - treePtr->count += path.second; - for( int i = int( cs.size() ) - 2; i >= 0; i-- ) - { - treePtr = GetFrameTreeItemGroup( treePtr->children, cs[i], m_worker ); - if( !treePtr ) break; - treePtr->count += path.second; - } - } - } - } - else - { - for( auto& path : stacks ) - { - auto& cs = m_worker.GetCallstack( path.first ); - auto base = cs.back(); - auto treePtr = GetFrameTreeItemNoGroup( root, base, m_worker ); - treePtr->count += path.second; - for( int i = int( cs.size() ) - 2; i >= 0; i-- ) - { - treePtr = GetFrameTreeItemNoGroup( treePtr->children, cs[i], m_worker ); - treePtr->count += path.second; - } - } - } - return root; -} - -unordered_flat_map View::GetParentsCallstackFrameTreeBottomUp( const unordered_flat_map& stacks, bool group ) const -{ - unordered_flat_map root; - if( group ) - { - for( auto& path : stacks ) - { - auto& cs = m_worker.GetParentCallstack( path.first ); - auto base = cs.back(); - auto treePtr = GetParentFrameTreeItemGroup( root, base, m_worker ); - if( treePtr ) - { - treePtr->count += path.second; - for( int i = int( cs.size() ) - 2; i >= 0; i-- ) - { - treePtr = GetParentFrameTreeItemGroup( treePtr->children, cs[i], m_worker ); - if( !treePtr ) break; - treePtr->count += path.second; - } - } - } - } - else - { - for( auto& path : stacks ) - { - auto& cs = m_worker.GetParentCallstack( path.first ); - auto base = cs.back(); - auto treePtr = GetFrameTreeItemNoGroup( root, base, m_worker ); - treePtr->count += path.second; - for( int i = int( cs.size() ) - 2; i >= 0; i-- ) - { - treePtr = GetFrameTreeItemNoGroup( treePtr->children, cs[i], m_worker ); - treePtr->count += path.second; - } - } - } - return root; -} - - -unordered_flat_map View::GetCallstackFrameTreeTopDown( const MemData& mem ) const -{ - unordered_flat_map root; - auto pathSum = GetCallstackPaths( mem, m_memRangeTopDown ); - if( m_groupCallstackTreeByNameTopDown ) - { - for( auto& path : pathSum ) - { - auto& cs = m_worker.GetCallstack( path.first ); - auto base = cs.front(); - auto treePtr = GetFrameTreeItemGroup( root, base, m_worker ); - if( treePtr ) - { - treePtr->count += path.second.cnt; - treePtr->alloc += path.second.mem; - treePtr->callstacks.emplace( path.first ); - for( uint16_t i = 1; i < cs.size(); i++ ) - { - treePtr = GetFrameTreeItemGroup( treePtr->children, cs[i], m_worker ); - if( !treePtr ) break; - treePtr->count += path.second.cnt; - treePtr->alloc += path.second.mem; - treePtr->callstacks.emplace( path.first ); - } - } - } - } - else - { - for( auto& path : pathSum ) - { - auto& cs = m_worker.GetCallstack( path.first ); - auto base = cs.front(); - auto treePtr = GetFrameTreeItemNoGroup( root, base, m_worker ); - treePtr->count += path.second.cnt; - treePtr->alloc += path.second.mem; - treePtr->callstacks.emplace( path.first ); - for( uint16_t i = 1; i < cs.size(); i++ ) - { - treePtr = GetFrameTreeItemNoGroup( treePtr->children, cs[i], m_worker ); - treePtr->count += path.second.cnt; - treePtr->alloc += path.second.mem; - treePtr->callstacks.emplace( path.first ); - } - } - } - return root; -} - -unordered_flat_map View::GetCallstackFrameTreeTopDown( const unordered_flat_map& stacks, bool group ) const -{ - unordered_flat_map root; - if( group ) - { - for( auto& path : stacks ) - { - auto& cs = m_worker.GetCallstack( path.first ); - auto base = cs.front(); - auto treePtr = GetFrameTreeItemGroup( root, base, m_worker ); - if( treePtr ) - { - treePtr->count += path.second; - for( uint16_t i = 1; i < cs.size(); i++ ) - { - treePtr = GetFrameTreeItemGroup( treePtr->children, cs[i], m_worker ); - if( !treePtr ) break; - treePtr->count += path.second; - } - } - } - } - else - { - for( auto& path : stacks ) - { - auto& cs = m_worker.GetCallstack( path.first ); - auto base = cs.front(); - auto treePtr = GetFrameTreeItemNoGroup( root, base, m_worker ); - treePtr->count += path.second; - for( uint16_t i = 1; i < cs.size(); i++ ) - { - treePtr = GetFrameTreeItemNoGroup( treePtr->children, cs[i], m_worker ); - treePtr->count += path.second; - } - } - } - return root; -} - -unordered_flat_map View::GetParentsCallstackFrameTreeTopDown( const unordered_flat_map& stacks, bool group ) const -{ - unordered_flat_map root; - if( group ) - { - for( auto& path : stacks ) - { - auto& cs = m_worker.GetParentCallstack( path.first ); - auto base = cs.front(); - auto treePtr = GetParentFrameTreeItemGroup( root, base, m_worker ); - if( treePtr ) - { - treePtr->count += path.second; - for( uint16_t i = 1; i < cs.size(); i++ ) - { - treePtr = GetParentFrameTreeItemGroup( treePtr->children, cs[i], m_worker ); - if( !treePtr ) break; - treePtr->count += path.second; - } - } - } - } - else - { - for( auto& path : stacks ) - { - auto& cs = m_worker.GetParentCallstack( path.first ); - auto base = cs.front(); - auto treePtr = GetFrameTreeItemNoGroup( root, base, m_worker ); - treePtr->count += path.second; - for( uint16_t i = 1; i < cs.size(); i++ ) - { - treePtr = GetFrameTreeItemNoGroup( treePtr->children, cs[i], m_worker ); - treePtr->count += path.second; - } - } - } - return root; -} - -void View::DrawFrameTreeLevel( const unordered_flat_map& tree, int& idx ) -{ - auto& io = ImGui::GetIO(); - - std::vector::const_iterator> sorted; - sorted.reserve( tree.size() ); - for( auto it = tree.begin(); it != tree.end(); ++it ) - { - sorted.emplace_back( it ); - } - pdqsort_branchless( sorted.begin(), sorted.end(), [] ( const auto& lhs, const auto& rhs ) { return lhs->second.alloc > rhs->second.alloc; } ); - - int lidx = 0; - for( auto& _v : sorted ) - { - auto& v = _v->second; - const auto isKernel = ( m_worker.GetCanonicalPointer( v.frame ) >> 63 ) != 0; - idx++; - auto frameDataPtr = m_worker.GetCallstackFrame( v.frame ); - if( frameDataPtr ) - { - auto& frameData = *frameDataPtr; - auto frame = frameData.data[frameData.size-1]; - bool expand = false; - - const auto frameName = m_worker.GetString( frame.name ); - if( v.children.empty() ) - { - ImGui::Indent( ImGui::GetTreeNodeToLabelSpacing() ); - if( frameName[0] == '[' ) - { - TextDisabledUnformatted( frameName ); - } - else if( isKernel ) - { - TextColoredUnformatted( 0xFF8888FF, frameName ); - } - else - { - ImGui::TextUnformatted( frameName ); - } - ImGui::Unindent( ImGui::GetTreeNodeToLabelSpacing() ); - } - else - { - ImGui::PushID( lidx++ ); - if( frameName[0] == '[' ) ImGui::PushStyleColor( ImGuiCol_Text, 0x88FFFFFF ); - else if( isKernel ) ImGui::PushStyleColor( ImGuiCol_Text, 0xFF8888FF ); - if( tree.size() == 1 ) - { - expand = ImGui::TreeNodeEx( frameName, ImGuiTreeNodeFlags_DefaultOpen ); - } - else - { - expand = ImGui::TreeNode( frameName ); - } - if( isKernel || frameName[0] == '[' ) ImGui::PopStyleColor(); - ImGui::PopID(); - } - - if( ImGui::IsItemClicked( 1 ) ) - { - auto& mem = m_worker.GetMemoryNamed( m_memInfo.pool ).data; - const auto sz = mem.size(); - m_memInfo.showAllocList = true; - m_memInfo.allocList.clear(); - for( size_t i=0; iimageName.Active() ) TextDisabledUnformatted( m_worker.GetString( frameDataPtr->imageName ) ); - } - else - { - fileName = m_worker.GetString( frame.file ); - ImGui::TextDisabled( "%s:%i", fileName, frame.line ); - } - if( ImGui::IsItemHovered() ) - { - DrawSourceTooltip( fileName, frame.line ); - if( ImGui::IsItemClicked( 1 ) ) - { - if( !ViewDispatch( fileName, frame.line, frame.symAddr ) ) - { - m_callstackTreeBuzzAnim.Enable( idx, 0.5f ); - } - } - } - - ImGui::SameLine(); - if( v.children.empty() ) - { - ImGui::TextColored( ImVec4( 0.2, 0.8, 0.8, 1.0 ), "%s (%s)", MemSizeToString( v.alloc ), RealToString( v.count ) ); - TooltipIfHovered( "Cost in this node" ); - } - else - { - uint32_t childCost = 0; - uint64_t childAlloc = 0; - for( auto& c : v.children ) - { - childCost += c.second.count; - childAlloc += c.second.alloc; - } - const auto rc = v.count - childCost; - if( rc != 0 ) - { - ImGui::TextColored( ImVec4( 0.2, 0.8, 0.8, 1.0 ), "%s (%s)", MemSizeToString( v.alloc - childAlloc ), RealToString( rc ) ); - TooltipIfHovered( "Cost only in this node" ); - ImGui::SameLine(); - } - ImGui::TextColored( ImVec4( 0.8, 0.8, 0.2, 1.0 ), "%s (%s)", MemSizeToString( v.alloc ), RealToString( v.count ) ); - TooltipIfHovered( "Cost in this node and children" ); - } - - if( expand ) - { - DrawFrameTreeLevel( v.children, idx ); - ImGui::TreePop(); - } - } - } -} - -void View::DrawFrameTreeLevel( const unordered_flat_map& tree, int& idx ) -{ - std::vector::const_iterator> sorted; - sorted.reserve( tree.size() ); - for( auto it = tree.begin(); it != tree.end(); ++it ) - { - sorted.emplace_back( it ); - } - pdqsort_branchless( sorted.begin(), sorted.end(), [] ( const auto& lhs, const auto& rhs ) { return lhs->second.count > rhs->second.count; } ); - - int lidx = 0; - for( auto& _v : sorted ) - { - auto& v = _v->second; - const auto isKernel = ( m_worker.GetCanonicalPointer( v.frame ) >> 63 ) != 0; - idx++; - auto frameDataPtr = m_worker.GetCallstackFrame( v.frame ); - if( frameDataPtr ) - { - auto& frameData = *frameDataPtr; - auto frame = frameData.data[frameData.size-1]; - bool expand = false; - - const auto frameName = m_worker.GetString( frame.name ); - if( v.children.empty() ) - { - ImGui::Indent( ImGui::GetTreeNodeToLabelSpacing() ); - if( frameName[0] == '[' ) - { - TextDisabledUnformatted( frameName ); - } - else if( isKernel ) - { - TextColoredUnformatted( 0xFF8888FF, frameName ); - } - else - { - ImGui::TextUnformatted( frameName ); - } - ImGui::Unindent( ImGui::GetTreeNodeToLabelSpacing() ); - } - else - { - ImGui::PushID( lidx++ ); - if( frameName[0] == '[' ) ImGui::PushStyleColor( ImGuiCol_Text, 0x88FFFFFF ); - else if( isKernel ) ImGui::PushStyleColor( ImGuiCol_Text, 0xFF8888FF ); - if( tree.size() == 1 ) - { - expand = ImGui::TreeNodeEx( frameName, ImGuiTreeNodeFlags_DefaultOpen ); - } - else - { - expand = ImGui::TreeNode( frameName ); - } - if( isKernel || frameName[0] == '[' ) ImGui::PopStyleColor(); - ImGui::PopID(); - } - - if( m_callstackTreeBuzzAnim.Match( idx ) ) - { - const auto time = m_callstackTreeBuzzAnim.Time(); - const auto indentVal = sin( time * 60.f ) * 10.f * time; - ImGui::SameLine( 0, ImGui::GetStyle().ItemSpacing.x + indentVal ); - } - else - { - ImGui::SameLine(); - } - const char* fileName = nullptr; - if( frame.line == 0 ) - { - TextDisabledUnformatted( m_worker.GetString( frameDataPtr->imageName ) ); - } - else - { - fileName = m_worker.GetString( frame.file ); - ImGui::TextDisabled( "%s:%i", fileName, frame.line ); - } - if( ImGui::IsItemHovered() ) - { - DrawSourceTooltip( fileName, frame.line ); - if( ImGui::IsItemClicked( 1 ) ) - { - if( !ViewDispatch( fileName, frame.line, frame.symAddr ) ) - { - m_callstackTreeBuzzAnim.Enable( idx, 0.5f ); - } - } - } - - ImGui::SameLine(); - if( v.children.empty() ) - { - ImGui::TextColored( ImVec4( 0.2, 0.8, 0.8, 1.0 ), "(%s)", RealToString( v.count ) ); - TooltipIfHovered( "Cost in this node" ); - } - else - { - uint32_t childCost = 0; - for( auto& c : v.children ) childCost += c.second.count; - const auto r = v.count - childCost; - if( r != 0 ) - { - ImGui::TextColored( ImVec4( 0.2, 0.8, 0.8, 1.0 ), "(%s)", RealToString( r ) ); - TooltipIfHovered( "Cost only in this node" ); - ImGui::SameLine(); - } - ImGui::TextColored( ImVec4( 0.8, 0.8, 0.2, 1.0 ), "(%s)", RealToString( v.count ) ); - TooltipIfHovered( "Cost in this node and children" ); - } - - if( expand ) - { - DrawFrameTreeLevel( v.children, idx ); - ImGui::TreePop(); - } - } - } -} - -void View::DrawParentsFrameTreeLevel( const unordered_flat_map& tree, int& idx ) -{ - std::vector::const_iterator> sorted; - sorted.reserve( tree.size() ); - for( auto it = tree.begin(); it != tree.end(); ++it ) - { - sorted.emplace_back( it ); - } - pdqsort_branchless( sorted.begin(), sorted.end(), [] ( const auto& lhs, const auto& rhs ) { return lhs->second.count > rhs->second.count; } ); - - int lidx = 0; - for( auto& _v : sorted ) - { - auto& v = _v->second; - const auto isKernel = ( m_worker.GetCanonicalPointer( v.frame ) >> 63 ) != 0; - idx++; - auto frameDataPtr = v.frame.custom ? m_worker.GetParentCallstackFrame( v.frame ) : m_worker.GetCallstackFrame( v.frame ); - if( frameDataPtr ) - { - auto& frameData = *frameDataPtr; - auto frame = frameData.data[frameData.size-1]; - bool expand = false; - - const auto frameName = m_worker.GetString( frame.name ); - if( v.children.empty() ) - { - ImGui::Indent( ImGui::GetTreeNodeToLabelSpacing() ); - if( frameName[0] == '[' ) - { - TextDisabledUnformatted( frameName ); - } - else if( isKernel ) - { - TextColoredUnformatted( 0xFF8888FF, frameName ); - } - else - { - ImGui::TextUnformatted( frameName ); - } - ImGui::Unindent( ImGui::GetTreeNodeToLabelSpacing() ); - } - else - { - ImGui::PushID( lidx++ ); - if( frameName[0] == '[' ) ImGui::PushStyleColor( ImGuiCol_Text, 0x88FFFFFF ); - else if( isKernel ) ImGui::PushStyleColor( ImGuiCol_Text, 0xFF8888FF ); - if( tree.size() == 1 ) - { - expand = ImGui::TreeNodeEx( frameName, ImGuiTreeNodeFlags_DefaultOpen ); - } - else - { - expand = ImGui::TreeNode( frameName ); - } - if( isKernel || frameName[0] == '[' ) ImGui::PopStyleColor(); - ImGui::PopID(); - } - - if( m_callstackTreeBuzzAnim.Match( idx ) ) - { - const auto time = m_callstackTreeBuzzAnim.Time(); - const auto indentVal = sin( time * 60.f ) * 10.f * time; - ImGui::SameLine( 0, ImGui::GetStyle().ItemSpacing.x + indentVal ); - } - else - { - ImGui::SameLine(); - } - const char* fileName = nullptr; - if( frame.line == 0 ) - { - TextDisabledUnformatted( m_worker.GetString( frameDataPtr->imageName ) ); - } - else - { - fileName = m_worker.GetString( frame.file ); - ImGui::TextDisabled( "%s:%i", fileName, frame.line ); - } - if( ImGui::IsItemHovered() ) - { - DrawSourceTooltip( fileName, frame.line ); - if( ImGui::IsItemClicked( 1 ) ) - { - if( !ViewDispatch( fileName, frame.line, frame.symAddr ) ) - { - m_callstackTreeBuzzAnim.Enable( idx, 0.5f ); - } - } - } - - ImGui::SameLine(); - if( v.children.empty() ) - { - ImGui::TextColored( ImVec4( 0.2, 0.8, 0.8, 1.0 ), "(%s)", m_statSampleTime ? TimeToString( m_worker.GetSamplingPeriod() * v.count ) : RealToString( v.count ) ); - TooltipIfHovered( "Cost in this node" ); - } - else - { - uint32_t childCost = 0; - for( auto& c : v.children ) childCost += c.second.count; - const auto r = v.count - childCost; - if( r != 0 ) - { - ImGui::TextColored( ImVec4( 0.2, 0.8, 0.8, 1.0 ), "(%s)", m_statSampleTime ? TimeToString( m_worker.GetSamplingPeriod() * r ) : RealToString( r ) ); - TooltipIfHovered( "Cost only in this node" ); - ImGui::SameLine(); - } - ImGui::TextColored( ImVec4( 0.8, 0.8, 0.2, 1.0 ), "(%s)", m_statSampleTime ? TimeToString( m_worker.GetSamplingPeriod() * v.count ) : RealToString( v.count ) ); - TooltipIfHovered( "Cost in this node and children" ); - } - - if( expand ) - { - DrawParentsFrameTreeLevel( v.children, idx ); - ImGui::TreePop(); - } - } - } -} - void View::DrawAllocList() { const auto scale = GetScale(); diff --git a/server/TracyView_FrameTree.cpp b/server/TracyView_FrameTree.cpp new file mode 100644 index 00000000..e92e4843 --- /dev/null +++ b/server/TracyView_FrameTree.cpp @@ -0,0 +1,797 @@ +#include "TracyPrint.hpp" +#include "TracyView.hpp" + +namespace tracy +{ + +template +static tracy_force_inline T* GetFrameTreeItemNoGroup( unordered_flat_map& tree, CallstackFrameId idx, const Worker& worker ) +{ + auto it = tree.find( idx.data ); + if( it == tree.end() ) + { + it = tree.emplace( idx.data, T( idx ) ).first; + } + return &it->second; +} + +template +static tracy_force_inline T* GetFrameTreeItemGroup( unordered_flat_map& tree, CallstackFrameId idx, const Worker& worker ) +{ + auto frameDataPtr = worker.GetCallstackFrame( idx ); + if( !frameDataPtr ) return nullptr; + + auto& frameData = *frameDataPtr; + auto& frame = frameData.data[frameData.size-1]; + auto fidx = frame.name.Idx(); + + auto it = tree.find( fidx ); + if( it == tree.end() ) + { + it = tree.emplace( fidx, T( idx ) ).first; + } + return &it->second; +} + +template +static tracy_force_inline T* GetParentFrameTreeItemGroup( unordered_flat_map& tree, CallstackFrameId idx, const Worker& worker ) +{ + auto frameDataPtr = idx.custom ? worker.GetParentCallstackFrame( idx ) : worker.GetCallstackFrame( idx ); + if( !frameDataPtr ) return nullptr; + + auto& frameData = *frameDataPtr; + auto& frame = frameData.data[frameData.size-1]; + auto fidx = frame.name.Idx(); + + auto it = tree.find( fidx ); + if( it == tree.end() ) + { + it = tree.emplace( fidx, T( idx ) ).first; + } + return &it->second; +} + + +unordered_flat_map View::GetCallstackPaths( const MemData& mem, MemRange memRange ) const +{ + unordered_flat_map pathSum; + pathSum.reserve( m_worker.GetCallstackPayloadCount() ); + + if( m_memInfo.range.active ) + { + auto it = std::lower_bound( mem.data.begin(), mem.data.end(), m_memInfo.range.min, []( const auto& lhs, const auto& rhs ) { return lhs.TimeAlloc() < rhs; } ); + if( it != mem.data.end() ) + { + auto end = std::lower_bound( mem.data.begin(), mem.data.end(), m_memInfo.range.max, []( const auto& lhs, const auto& rhs ) { return lhs.TimeAlloc() < rhs; } ); + if( memRange != MemRange::Full ) + { + while( it != end ) + { + auto& ev = *it++; + if( ev.CsAlloc() == 0 ) continue; + if( ( memRange == MemRange::Inactive ) == ( ev.TimeFree() >= 0 && ev.TimeFree() < m_memInfo.range.max ) ) continue; + auto pit = pathSum.find( ev.CsAlloc() ); + if( pit == pathSum.end() ) + { + pathSum.emplace( ev.CsAlloc(), MemPathData { 1, ev.Size() } ); + } + else + { + pit->second.cnt++; + pit->second.mem += ev.Size(); + } + } + } + else + { + while( it != end ) + { + auto& ev = *it++; + if( ev.CsAlloc() == 0 ) continue; + auto pit = pathSum.find( ev.CsAlloc() ); + if( pit == pathSum.end() ) + { + pathSum.emplace( ev.CsAlloc(), MemPathData { 1, ev.Size() } ); + } + else + { + pit->second.cnt++; + pit->second.mem += ev.Size(); + } + } + } + } + } + else + { + if( memRange != MemRange::Full ) + { + for( auto& ev : mem.data ) + { + if( ev.CsAlloc() == 0 ) continue; + if( ( memRange == MemRange::Inactive ) == ( ev.TimeFree() >= 0 ) ) continue; + auto it = pathSum.find( ev.CsAlloc() ); + if( it == pathSum.end() ) + { + pathSum.emplace( ev.CsAlloc(), MemPathData { 1, ev.Size() } ); + } + else + { + it->second.cnt++; + it->second.mem += ev.Size(); + } + } + } + else + { + for( auto& ev : mem.data ) + { + if( ev.CsAlloc() == 0 ) continue; + auto it = pathSum.find( ev.CsAlloc() ); + if( it == pathSum.end() ) + { + pathSum.emplace( ev.CsAlloc(), MemPathData { 1, ev.Size() } ); + } + else + { + it->second.cnt++; + it->second.mem += ev.Size(); + } + } + } + } + return pathSum; +} + +unordered_flat_map View::GetCallstackFrameTreeBottomUp( const MemData& mem ) const +{ + unordered_flat_map root; + auto pathSum = GetCallstackPaths( mem, m_memRangeBottomUp ); + if( m_groupCallstackTreeByNameBottomUp ) + { + for( auto& path : pathSum ) + { + auto& cs = m_worker.GetCallstack( path.first ); + auto base = cs.back(); + auto treePtr = GetFrameTreeItemGroup( root, base, m_worker ); + if( treePtr ) + { + treePtr->count += path.second.cnt; + treePtr->alloc += path.second.mem; + treePtr->callstacks.emplace( path.first ); + for( int i = int( cs.size() ) - 2; i >= 0; i-- ) + { + treePtr = GetFrameTreeItemGroup( treePtr->children, cs[i], m_worker ); + if( !treePtr ) break; + treePtr->count += path.second.cnt; + treePtr->alloc += path.second.mem; + treePtr->callstacks.emplace( path.first ); + } + } + } + } + else + { + for( auto& path : pathSum ) + { + auto& cs = m_worker.GetCallstack( path.first ); + auto base = cs.back(); + auto treePtr = GetFrameTreeItemNoGroup( root, base, m_worker ); + treePtr->count += path.second.cnt; + treePtr->alloc += path.second.mem; + treePtr->callstacks.emplace( path.first ); + for( int i = int( cs.size() ) - 2; i >= 0; i-- ) + { + treePtr = GetFrameTreeItemNoGroup( treePtr->children, cs[i], m_worker ); + treePtr->count += path.second.cnt; + treePtr->alloc += path.second.mem; + treePtr->callstacks.emplace( path.first ); + } + } + } + return root; +} + +unordered_flat_map View::GetCallstackFrameTreeBottomUp( const unordered_flat_map& stacks, bool group ) const +{ + unordered_flat_map root; + if( group ) + { + for( auto& path : stacks ) + { + auto& cs = m_worker.GetCallstack( path.first ); + auto base = cs.back(); + auto treePtr = GetFrameTreeItemGroup( root, base, m_worker ); + if( treePtr ) + { + treePtr->count += path.second; + for( int i = int( cs.size() ) - 2; i >= 0; i-- ) + { + treePtr = GetFrameTreeItemGroup( treePtr->children, cs[i], m_worker ); + if( !treePtr ) break; + treePtr->count += path.second; + } + } + } + } + else + { + for( auto& path : stacks ) + { + auto& cs = m_worker.GetCallstack( path.first ); + auto base = cs.back(); + auto treePtr = GetFrameTreeItemNoGroup( root, base, m_worker ); + treePtr->count += path.second; + for( int i = int( cs.size() ) - 2; i >= 0; i-- ) + { + treePtr = GetFrameTreeItemNoGroup( treePtr->children, cs[i], m_worker ); + treePtr->count += path.second; + } + } + } + return root; +} + +unordered_flat_map View::GetParentsCallstackFrameTreeBottomUp( const unordered_flat_map& stacks, bool group ) const +{ + unordered_flat_map root; + if( group ) + { + for( auto& path : stacks ) + { + auto& cs = m_worker.GetParentCallstack( path.first ); + auto base = cs.back(); + auto treePtr = GetParentFrameTreeItemGroup( root, base, m_worker ); + if( treePtr ) + { + treePtr->count += path.second; + for( int i = int( cs.size() ) - 2; i >= 0; i-- ) + { + treePtr = GetParentFrameTreeItemGroup( treePtr->children, cs[i], m_worker ); + if( !treePtr ) break; + treePtr->count += path.second; + } + } + } + } + else + { + for( auto& path : stacks ) + { + auto& cs = m_worker.GetParentCallstack( path.first ); + auto base = cs.back(); + auto treePtr = GetFrameTreeItemNoGroup( root, base, m_worker ); + treePtr->count += path.second; + for( int i = int( cs.size() ) - 2; i >= 0; i-- ) + { + treePtr = GetFrameTreeItemNoGroup( treePtr->children, cs[i], m_worker ); + treePtr->count += path.second; + } + } + } + return root; +} + + +unordered_flat_map View::GetCallstackFrameTreeTopDown( const MemData& mem ) const +{ + unordered_flat_map root; + auto pathSum = GetCallstackPaths( mem, m_memRangeTopDown ); + if( m_groupCallstackTreeByNameTopDown ) + { + for( auto& path : pathSum ) + { + auto& cs = m_worker.GetCallstack( path.first ); + auto base = cs.front(); + auto treePtr = GetFrameTreeItemGroup( root, base, m_worker ); + if( treePtr ) + { + treePtr->count += path.second.cnt; + treePtr->alloc += path.second.mem; + treePtr->callstacks.emplace( path.first ); + for( uint16_t i = 1; i < cs.size(); i++ ) + { + treePtr = GetFrameTreeItemGroup( treePtr->children, cs[i], m_worker ); + if( !treePtr ) break; + treePtr->count += path.second.cnt; + treePtr->alloc += path.second.mem; + treePtr->callstacks.emplace( path.first ); + } + } + } + } + else + { + for( auto& path : pathSum ) + { + auto& cs = m_worker.GetCallstack( path.first ); + auto base = cs.front(); + auto treePtr = GetFrameTreeItemNoGroup( root, base, m_worker ); + treePtr->count += path.second.cnt; + treePtr->alloc += path.second.mem; + treePtr->callstacks.emplace( path.first ); + for( uint16_t i = 1; i < cs.size(); i++ ) + { + treePtr = GetFrameTreeItemNoGroup( treePtr->children, cs[i], m_worker ); + treePtr->count += path.second.cnt; + treePtr->alloc += path.second.mem; + treePtr->callstacks.emplace( path.first ); + } + } + } + return root; +} + +unordered_flat_map View::GetCallstackFrameTreeTopDown( const unordered_flat_map& stacks, bool group ) const +{ + unordered_flat_map root; + if( group ) + { + for( auto& path : stacks ) + { + auto& cs = m_worker.GetCallstack( path.first ); + auto base = cs.front(); + auto treePtr = GetFrameTreeItemGroup( root, base, m_worker ); + if( treePtr ) + { + treePtr->count += path.second; + for( uint16_t i = 1; i < cs.size(); i++ ) + { + treePtr = GetFrameTreeItemGroup( treePtr->children, cs[i], m_worker ); + if( !treePtr ) break; + treePtr->count += path.second; + } + } + } + } + else + { + for( auto& path : stacks ) + { + auto& cs = m_worker.GetCallstack( path.first ); + auto base = cs.front(); + auto treePtr = GetFrameTreeItemNoGroup( root, base, m_worker ); + treePtr->count += path.second; + for( uint16_t i = 1; i < cs.size(); i++ ) + { + treePtr = GetFrameTreeItemNoGroup( treePtr->children, cs[i], m_worker ); + treePtr->count += path.second; + } + } + } + return root; +} + +unordered_flat_map View::GetParentsCallstackFrameTreeTopDown( const unordered_flat_map& stacks, bool group ) const +{ + unordered_flat_map root; + if( group ) + { + for( auto& path : stacks ) + { + auto& cs = m_worker.GetParentCallstack( path.first ); + auto base = cs.front(); + auto treePtr = GetParentFrameTreeItemGroup( root, base, m_worker ); + if( treePtr ) + { + treePtr->count += path.second; + for( uint16_t i = 1; i < cs.size(); i++ ) + { + treePtr = GetParentFrameTreeItemGroup( treePtr->children, cs[i], m_worker ); + if( !treePtr ) break; + treePtr->count += path.second; + } + } + } + } + else + { + for( auto& path : stacks ) + { + auto& cs = m_worker.GetParentCallstack( path.first ); + auto base = cs.front(); + auto treePtr = GetFrameTreeItemNoGroup( root, base, m_worker ); + treePtr->count += path.second; + for( uint16_t i = 1; i < cs.size(); i++ ) + { + treePtr = GetFrameTreeItemNoGroup( treePtr->children, cs[i], m_worker ); + treePtr->count += path.second; + } + } + } + return root; +} + +void View::DrawFrameTreeLevel( const unordered_flat_map& tree, int& idx ) +{ + auto& io = ImGui::GetIO(); + + std::vector::const_iterator> sorted; + sorted.reserve( tree.size() ); + for( auto it = tree.begin(); it != tree.end(); ++it ) + { + sorted.emplace_back( it ); + } + pdqsort_branchless( sorted.begin(), sorted.end(), [] ( const auto& lhs, const auto& rhs ) { return lhs->second.alloc > rhs->second.alloc; } ); + + int lidx = 0; + for( auto& _v : sorted ) + { + auto& v = _v->second; + const auto isKernel = ( m_worker.GetCanonicalPointer( v.frame ) >> 63 ) != 0; + idx++; + auto frameDataPtr = m_worker.GetCallstackFrame( v.frame ); + if( frameDataPtr ) + { + auto& frameData = *frameDataPtr; + auto frame = frameData.data[frameData.size-1]; + bool expand = false; + + const auto frameName = m_worker.GetString( frame.name ); + if( v.children.empty() ) + { + ImGui::Indent( ImGui::GetTreeNodeToLabelSpacing() ); + if( frameName[0] == '[' ) + { + TextDisabledUnformatted( frameName ); + } + else if( isKernel ) + { + TextColoredUnformatted( 0xFF8888FF, frameName ); + } + else + { + ImGui::TextUnformatted( frameName ); + } + ImGui::Unindent( ImGui::GetTreeNodeToLabelSpacing() ); + } + else + { + ImGui::PushID( lidx++ ); + if( frameName[0] == '[' ) ImGui::PushStyleColor( ImGuiCol_Text, 0x88FFFFFF ); + else if( isKernel ) ImGui::PushStyleColor( ImGuiCol_Text, 0xFF8888FF ); + if( tree.size() == 1 ) + { + expand = ImGui::TreeNodeEx( frameName, ImGuiTreeNodeFlags_DefaultOpen ); + } + else + { + expand = ImGui::TreeNode( frameName ); + } + if( isKernel || frameName[0] == '[' ) ImGui::PopStyleColor(); + ImGui::PopID(); + } + + if( ImGui::IsItemClicked( 1 ) ) + { + auto& mem = m_worker.GetMemoryNamed( m_memInfo.pool ).data; + const auto sz = mem.size(); + m_memInfo.showAllocList = true; + m_memInfo.allocList.clear(); + for( size_t i=0; iimageName.Active() ) TextDisabledUnformatted( m_worker.GetString( frameDataPtr->imageName ) ); + } + else + { + fileName = m_worker.GetString( frame.file ); + ImGui::TextDisabled( "%s:%i", fileName, frame.line ); + } + if( ImGui::IsItemHovered() ) + { + DrawSourceTooltip( fileName, frame.line ); + if( ImGui::IsItemClicked( 1 ) ) + { + if( !ViewDispatch( fileName, frame.line, frame.symAddr ) ) + { + m_callstackTreeBuzzAnim.Enable( idx, 0.5f ); + } + } + } + + ImGui::SameLine(); + if( v.children.empty() ) + { + ImGui::TextColored( ImVec4( 0.2, 0.8, 0.8, 1.0 ), "%s (%s)", MemSizeToString( v.alloc ), RealToString( v.count ) ); + TooltipIfHovered( "Cost in this node" ); + } + else + { + uint32_t childCost = 0; + uint64_t childAlloc = 0; + for( auto& c : v.children ) + { + childCost += c.second.count; + childAlloc += c.second.alloc; + } + const auto rc = v.count - childCost; + if( rc != 0 ) + { + ImGui::TextColored( ImVec4( 0.2, 0.8, 0.8, 1.0 ), "%s (%s)", MemSizeToString( v.alloc - childAlloc ), RealToString( rc ) ); + TooltipIfHovered( "Cost only in this node" ); + ImGui::SameLine(); + } + ImGui::TextColored( ImVec4( 0.8, 0.8, 0.2, 1.0 ), "%s (%s)", MemSizeToString( v.alloc ), RealToString( v.count ) ); + TooltipIfHovered( "Cost in this node and children" ); + } + + if( expand ) + { + DrawFrameTreeLevel( v.children, idx ); + ImGui::TreePop(); + } + } + } +} + +void View::DrawFrameTreeLevel( const unordered_flat_map& tree, int& idx ) +{ + std::vector::const_iterator> sorted; + sorted.reserve( tree.size() ); + for( auto it = tree.begin(); it != tree.end(); ++it ) + { + sorted.emplace_back( it ); + } + pdqsort_branchless( sorted.begin(), sorted.end(), [] ( const auto& lhs, const auto& rhs ) { return lhs->second.count > rhs->second.count; } ); + + int lidx = 0; + for( auto& _v : sorted ) + { + auto& v = _v->second; + const auto isKernel = ( m_worker.GetCanonicalPointer( v.frame ) >> 63 ) != 0; + idx++; + auto frameDataPtr = m_worker.GetCallstackFrame( v.frame ); + if( frameDataPtr ) + { + auto& frameData = *frameDataPtr; + auto frame = frameData.data[frameData.size-1]; + bool expand = false; + + const auto frameName = m_worker.GetString( frame.name ); + if( v.children.empty() ) + { + ImGui::Indent( ImGui::GetTreeNodeToLabelSpacing() ); + if( frameName[0] == '[' ) + { + TextDisabledUnformatted( frameName ); + } + else if( isKernel ) + { + TextColoredUnformatted( 0xFF8888FF, frameName ); + } + else + { + ImGui::TextUnformatted( frameName ); + } + ImGui::Unindent( ImGui::GetTreeNodeToLabelSpacing() ); + } + else + { + ImGui::PushID( lidx++ ); + if( frameName[0] == '[' ) ImGui::PushStyleColor( ImGuiCol_Text, 0x88FFFFFF ); + else if( isKernel ) ImGui::PushStyleColor( ImGuiCol_Text, 0xFF8888FF ); + if( tree.size() == 1 ) + { + expand = ImGui::TreeNodeEx( frameName, ImGuiTreeNodeFlags_DefaultOpen ); + } + else + { + expand = ImGui::TreeNode( frameName ); + } + if( isKernel || frameName[0] == '[' ) ImGui::PopStyleColor(); + ImGui::PopID(); + } + + if( m_callstackTreeBuzzAnim.Match( idx ) ) + { + const auto time = m_callstackTreeBuzzAnim.Time(); + const auto indentVal = sin( time * 60.f ) * 10.f * time; + ImGui::SameLine( 0, ImGui::GetStyle().ItemSpacing.x + indentVal ); + } + else + { + ImGui::SameLine(); + } + const char* fileName = nullptr; + if( frame.line == 0 ) + { + TextDisabledUnformatted( m_worker.GetString( frameDataPtr->imageName ) ); + } + else + { + fileName = m_worker.GetString( frame.file ); + ImGui::TextDisabled( "%s:%i", fileName, frame.line ); + } + if( ImGui::IsItemHovered() ) + { + DrawSourceTooltip( fileName, frame.line ); + if( ImGui::IsItemClicked( 1 ) ) + { + if( !ViewDispatch( fileName, frame.line, frame.symAddr ) ) + { + m_callstackTreeBuzzAnim.Enable( idx, 0.5f ); + } + } + } + + ImGui::SameLine(); + if( v.children.empty() ) + { + ImGui::TextColored( ImVec4( 0.2, 0.8, 0.8, 1.0 ), "(%s)", RealToString( v.count ) ); + TooltipIfHovered( "Cost in this node" ); + } + else + { + uint32_t childCost = 0; + for( auto& c : v.children ) childCost += c.second.count; + const auto r = v.count - childCost; + if( r != 0 ) + { + ImGui::TextColored( ImVec4( 0.2, 0.8, 0.8, 1.0 ), "(%s)", RealToString( r ) ); + TooltipIfHovered( "Cost only in this node" ); + ImGui::SameLine(); + } + ImGui::TextColored( ImVec4( 0.8, 0.8, 0.2, 1.0 ), "(%s)", RealToString( v.count ) ); + TooltipIfHovered( "Cost in this node and children" ); + } + + if( expand ) + { + DrawFrameTreeLevel( v.children, idx ); + ImGui::TreePop(); + } + } + } +} + +void View::DrawParentsFrameTreeLevel( const unordered_flat_map& tree, int& idx ) +{ + std::vector::const_iterator> sorted; + sorted.reserve( tree.size() ); + for( auto it = tree.begin(); it != tree.end(); ++it ) + { + sorted.emplace_back( it ); + } + pdqsort_branchless( sorted.begin(), sorted.end(), [] ( const auto& lhs, const auto& rhs ) { return lhs->second.count > rhs->second.count; } ); + + int lidx = 0; + for( auto& _v : sorted ) + { + auto& v = _v->second; + const auto isKernel = ( m_worker.GetCanonicalPointer( v.frame ) >> 63 ) != 0; + idx++; + auto frameDataPtr = v.frame.custom ? m_worker.GetParentCallstackFrame( v.frame ) : m_worker.GetCallstackFrame( v.frame ); + if( frameDataPtr ) + { + auto& frameData = *frameDataPtr; + auto frame = frameData.data[frameData.size-1]; + bool expand = false; + + const auto frameName = m_worker.GetString( frame.name ); + if( v.children.empty() ) + { + ImGui::Indent( ImGui::GetTreeNodeToLabelSpacing() ); + if( frameName[0] == '[' ) + { + TextDisabledUnformatted( frameName ); + } + else if( isKernel ) + { + TextColoredUnformatted( 0xFF8888FF, frameName ); + } + else + { + ImGui::TextUnformatted( frameName ); + } + ImGui::Unindent( ImGui::GetTreeNodeToLabelSpacing() ); + } + else + { + ImGui::PushID( lidx++ ); + if( frameName[0] == '[' ) ImGui::PushStyleColor( ImGuiCol_Text, 0x88FFFFFF ); + else if( isKernel ) ImGui::PushStyleColor( ImGuiCol_Text, 0xFF8888FF ); + if( tree.size() == 1 ) + { + expand = ImGui::TreeNodeEx( frameName, ImGuiTreeNodeFlags_DefaultOpen ); + } + else + { + expand = ImGui::TreeNode( frameName ); + } + if( isKernel || frameName[0] == '[' ) ImGui::PopStyleColor(); + ImGui::PopID(); + } + + if( m_callstackTreeBuzzAnim.Match( idx ) ) + { + const auto time = m_callstackTreeBuzzAnim.Time(); + const auto indentVal = sin( time * 60.f ) * 10.f * time; + ImGui::SameLine( 0, ImGui::GetStyle().ItemSpacing.x + indentVal ); + } + else + { + ImGui::SameLine(); + } + const char* fileName = nullptr; + if( frame.line == 0 ) + { + TextDisabledUnformatted( m_worker.GetString( frameDataPtr->imageName ) ); + } + else + { + fileName = m_worker.GetString( frame.file ); + ImGui::TextDisabled( "%s:%i", fileName, frame.line ); + } + if( ImGui::IsItemHovered() ) + { + DrawSourceTooltip( fileName, frame.line ); + if( ImGui::IsItemClicked( 1 ) ) + { + if( !ViewDispatch( fileName, frame.line, frame.symAddr ) ) + { + m_callstackTreeBuzzAnim.Enable( idx, 0.5f ); + } + } + } + + ImGui::SameLine(); + if( v.children.empty() ) + { + ImGui::TextColored( ImVec4( 0.2, 0.8, 0.8, 1.0 ), "(%s)", m_statSampleTime ? TimeToString( m_worker.GetSamplingPeriod() * v.count ) : RealToString( v.count ) ); + TooltipIfHovered( "Cost in this node" ); + } + else + { + uint32_t childCost = 0; + for( auto& c : v.children ) childCost += c.second.count; + const auto r = v.count - childCost; + if( r != 0 ) + { + ImGui::TextColored( ImVec4( 0.2, 0.8, 0.8, 1.0 ), "(%s)", m_statSampleTime ? TimeToString( m_worker.GetSamplingPeriod() * r ) : RealToString( r ) ); + TooltipIfHovered( "Cost only in this node" ); + ImGui::SameLine(); + } + ImGui::TextColored( ImVec4( 0.8, 0.8, 0.2, 1.0 ), "(%s)", m_statSampleTime ? TimeToString( m_worker.GetSamplingPeriod() * v.count ) : RealToString( v.count ) ); + TooltipIfHovered( "Cost in this node and children" ); + } + + if( expand ) + { + DrawParentsFrameTreeLevel( v.children, idx ); + ImGui::TreePop(); + } + } + } +} + +}