#include #include "TracyFilesystem.hpp" #include "TracyImGui.hpp" #include "TracyPrint.hpp" #include "TracyMouse.hpp" #include "TracyView.hpp" namespace tracy { extern double s_time; template static inline uint32_t GetZoneCallstack( const T& ev, const Worker& worker ); template<> inline uint32_t GetZoneCallstack( const ZoneEvent& ev, const Worker& worker ) { return worker.GetZoneExtra( ev ).callstack.Val(); } template<> inline uint32_t GetZoneCallstack( const GpuEvent& ev, const Worker& worker ) { return ev.callstack.Val(); } void View::CalcZoneTimeData( unordered_flat_map& data, int64_t& ztime, const ZoneEvent& zone ) { assert( zone.HasChildren() ); const auto& children = m_worker.GetZoneChildren( zone.Child() ); if( children.is_magic() ) { CalcZoneTimeDataImpl>( *(Vector*)( &children ), data, ztime, zone ); } else { CalcZoneTimeDataImpl>( children, data, ztime, zone ); } } template void View::CalcZoneTimeDataImpl( const V& children, unordered_flat_map& data, int64_t& ztime, const ZoneEvent& zone ) { Adapter a; if( m_timeDist.exclusiveTime ) { int64_t zt = ztime; for( auto& child : children ) { const auto t = m_worker.GetZoneEnd( a(child) ) - a(child).Start(); zt -= t; } ztime = zt; } for( auto& child : children ) { const auto srcloc = a(child).SrcLoc(); const auto t = m_worker.GetZoneEnd( a(child) ) - a(child).Start(); auto it = data.find( srcloc ); if( it == data.end() ) { it = data.emplace( srcloc, ZoneTimeData { t, 1 } ).first; } else { it->second.time += t; it->second.count++; } if( a(child).Child() >= 0 ) CalcZoneTimeData( data, it->second.time, a(child) ); } } void View::CalcZoneTimeData( const ContextSwitch* ctx, unordered_flat_map& data, int64_t& ztime, const ZoneEvent& zone ) { assert( zone.HasChildren() ); const auto& children = m_worker.GetZoneChildren( zone.Child() ); if( children.is_magic() ) { CalcZoneTimeDataImpl>( *(Vector*)( &children ), ctx, data, ztime, zone ); } else { CalcZoneTimeDataImpl>( children, ctx, data, ztime, zone ); } } template void View::CalcZoneTimeDataImpl( const V& children, const ContextSwitch* ctx, unordered_flat_map& data, int64_t& ztime, const ZoneEvent& zone ) { Adapter a; if( m_timeDist.exclusiveTime ) { int64_t zt = ztime; for( auto& child : children ) { int64_t t; uint64_t cnt; const auto res = GetZoneRunningTime( ctx, a(child), t, cnt ); assert( res ); zt -= t; } ztime = zt; } for( auto& child : children ) { const auto srcloc = a(child).SrcLoc(); int64_t t; uint64_t cnt; const auto res = GetZoneRunningTime( ctx, a(child), t, cnt ); assert( res ); auto it = data.find( srcloc ); if( it == data.end() ) { it = data.emplace( srcloc, ZoneTimeData { t, 1 } ).first; } else { it->second.time += t; it->second.count++; } if( a(child).HasChildren() ) CalcZoneTimeData( ctx, data, it->second.time, a(child) ); } } template void DrawZoneTrace( T zone, const std::vector& trace, const Worker& worker, BuzzAnim& anim, View& view, bool& showUnknownFrames, std::function showZone ) { bool expand = ImGui::TreeNode( "Zone trace" ); ImGui::SameLine(); ImGui::TextDisabled( "(%s)", RealToString( trace.size() ) ); if( !expand ) return; const auto shortenName = view.GetShortenName(); ImGui::SameLine(); SmallCheckbox( "Show unknown frames", &showUnknownFrames ); int fidx = 1; TextDisabledUnformatted( "0." ); ImGui::SameLine(); TextDisabledUnformatted( "[this zone]" ); if( !trace.empty() ) { T prev = zone; const auto sz = trace.size(); for( size_t i=0; idata + frameData->size - 1; ImGui::TextDisabled( "%i.", fidx++ ); ImGui::SameLine(); const auto frameName = worker.GetString( frame->name ); const auto normalized = shortenName != ShortenName::Never ? ShortenZoneName( ShortenName::OnlyNormalize, frameName ) : frameName; TextDisabledUnformatted( normalized ); TooltipNormalizedName( frameName, normalized ); ImGui::SameLine(); ImGui::Spacing(); if( anim.Match( frame ) ) { const auto time = anim.Time(); const auto indentVal = sin( time * 60.f ) * 10.f * time; ImGui::SameLine( 0, ImGui::GetStyle().ItemSpacing.x + indentVal ); } else { ImGui::SameLine(); } const auto fileName = worker.GetString( frame->file ); TextDisabledUnformatted( LocationToString( fileName, frame->line ) ); if( ImGui::IsItemClicked( 1 ) ) { if( !view.ViewDispatch( fileName, frame->line, frame->symAddr ) ) { anim.Enable( frame, 0.5f ); } } } } showZone( curr, fidx ); prev = curr; } } auto last = trace.empty() ? zone : trace.back(); const auto lcv = GetZoneCallstack( *last, worker ); if( lcv == 0 ) { if( showUnknownFrames ) { ImGui::TextDisabled( "%i.", fidx++ ); ImGui::SameLine(); TextDisabledUnformatted( "[unknown frames]" ); } } else { auto& cs = worker.GetCallstack( lcv ); const auto csz = cs.size(); for( uint16_t i=1; idata + frameData->size - 1; ImGui::TextDisabled( "%i.", fidx++ ); ImGui::SameLine(); const auto frameName = worker.GetString( frame->name ); const auto normalized = shortenName != ShortenName::Never ? ShortenZoneName( ShortenName::OnlyNormalize, frameName ) : frameName; TextDisabledUnformatted( normalized ); TooltipNormalizedName( frameName, normalized ); ImGui::SameLine(); ImGui::Spacing(); if( anim.Match( frame ) ) { const auto time = anim.Time(); const auto indentVal = sin( time * 60.f ) * 10.f * time; ImGui::SameLine( 0, ImGui::GetStyle().ItemSpacing.x + indentVal ); } else { ImGui::SameLine(); } const auto fileName = worker.GetString( frame->file ); TextDisabledUnformatted( LocationToString( fileName, frame->line ) ); if( ImGui::IsItemClicked( 1 ) ) { if( !view.ViewDispatch( fileName, frame->line, frame->symAddr ) ) { anim.Enable( frame, 0.5f ); } } } } ImGui::TreePop(); } void View::DrawInfoWindow() { if( m_zoneInfoWindow ) { DrawZoneInfoWindow(); } else if( m_gpuInfoWindow ) { DrawGpuInfoWindow(); } } void View::DrawZoneInfoWindow() { auto& ev = *m_zoneInfoWindow; const auto& srcloc = m_worker.GetSourceLocation( ev.SrcLoc() ); const auto scale = GetScale(); ImGui::SetNextWindowSize( ImVec2( 500 * scale, 600 * scale ), ImGuiCond_FirstUseEver ); bool show = true; ImGui::Begin( "Zone info", &show, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse ); if( !ImGui::GetCurrentWindowRead()->SkipItems ) { if( ImGui::Button( ICON_FA_MICROSCOPE " Zoom to zone" ) ) { ZoomToZone( ev ); } auto parent = GetZoneParent( ev ); if( parent ) { ImGui::SameLine(); if( ImGui::Button( ICON_FA_ARROW_UP " Go to parent" ) ) { ShowZoneInfo( *parent ); } } #ifndef TRACY_NO_STATISTICS if( m_worker.AreSourceLocationZonesReady() ) { const auto sl = ev.SrcLoc(); const auto& slz = m_worker.GetZonesForSourceLocation( sl ); if( !slz.zones.empty() ) { ImGui::SameLine(); if( ImGui::Button( ICON_FA_CHART_BAR " Statistics" ) ) { m_findZone.ShowZone( sl, m_worker.GetString( srcloc.name.active ? srcloc.name : srcloc.function ) ); } } } #endif if( m_worker.HasZoneExtra( ev ) && m_worker.GetZoneExtra( ev ).callstack.Val() != 0 ) { const auto& extra = m_worker.GetZoneExtra( ev ); ImGui::SameLine(); bool hilite = m_callstackInfoWindow == extra.callstack.Val(); if( hilite ) { SetButtonHighlightColor(); } if( ImGui::Button( ICON_FA_ALIGN_JUSTIFY " Call stack" ) ) { m_callstackInfoWindow = extra.callstack.Val(); } if( hilite ) { ImGui::PopStyleColor( 3 ); } } const auto fileName = m_worker.GetString( srcloc.file ); if( SourceFileValid( fileName, m_worker.GetCaptureTime(), *this, m_worker ) ) { ImGui::SameLine(); bool hilite = m_sourceViewFile == fileName; if( hilite ) { SetButtonHighlightColor(); } if( ImGui::Button( ICON_FA_FILE_ALT " Source" ) ) { ViewSource( fileName, srcloc.line ); } if( hilite ) { ImGui::PopStyleColor( 3 ); } } if( !m_zoneInfoStack.empty() ) { ImGui::SameLine(); if( ImGui::Button( ICON_FA_ARROW_LEFT " Go back" ) ) { m_zoneInfoWindow = m_zoneInfoStack.back_and_pop(); } } ImGui::Separator(); auto threadData = GetZoneThreadData( ev ); assert( threadData ); const auto tid = threadData->id; if( m_worker.HasZoneExtra( ev ) && m_worker.GetZoneExtra( ev ).name.Active() ) { ImGui::PushFont( m_bigFont ); TextFocused( "Zone name:", m_worker.GetString( m_worker.GetZoneExtra( ev ).name ) ); ImGui::PopFont(); if( srcloc.name.active ) { ImGui::SameLine(); ImGui::TextDisabled( "(%s)", m_worker.GetString( srcloc.name ) ); } ImGui::SameLine(); if( ClipboardButton( 1 ) ) { if( srcloc.name.active ) { char tmp[1024]; sprintf( tmp, "%s (%s)", m_worker.GetString( m_worker.GetZoneExtra( ev ).name ), m_worker.GetString( srcloc.name ) ); ImGui::SetClipboardText( tmp ); } else { ImGui::SetClipboardText( m_worker.GetString( m_worker.GetZoneExtra( ev ).name ) ); } } TextFocused( "Function:", m_worker.GetString( srcloc.function ) ); ImGui::SameLine(); if( ClipboardButton( 2 ) ) ImGui::SetClipboardText( m_worker.GetString( srcloc.function ) ); } else if( srcloc.name.active ) { ImGui::PushFont( m_bigFont ); TextFocused( "Zone name:", m_worker.GetString( srcloc.name ) ); ImGui::PopFont(); ImGui::SameLine(); if( ClipboardButton( 1 ) ) ImGui::SetClipboardText( m_worker.GetString( srcloc.name ) ); TextFocused( "Function:", m_worker.GetString( srcloc.function ) ); ImGui::SameLine(); if( ClipboardButton( 2 ) ) ImGui::SetClipboardText( m_worker.GetString( srcloc.function ) ); } else { ImGui::PushFont( m_bigFont ); TextFocused( "Function:", m_worker.GetString( srcloc.function ) ); ImGui::PopFont(); ImGui::SameLine(); if( ClipboardButton( 1 ) ) ImGui::SetClipboardText( m_worker.GetString( srcloc.function ) ); } SmallColorBox( GetSrcLocColor( m_worker.GetSourceLocation( ev.SrcLoc() ), 0 ) ); ImGui::SameLine(); TextDisabledUnformatted( "Location:" ); ImGui::SameLine(); ImGui::TextUnformatted( LocationToString( m_worker.GetString( srcloc.file ), srcloc.line ) ); ImGui::SameLine(); if( ClipboardButton( 3 ) ) { ImGui::SetClipboardText( LocationToString( m_worker.GetString( srcloc.file ), srcloc.line ) ); } SmallColorBox( GetThreadColor( tid, 0 ) ); ImGui::SameLine(); TextFocused( "Thread:", m_worker.GetThreadName( tid ) ); ImGui::SameLine(); ImGui::TextDisabled( "(%s)", RealToString( tid ) ); if( m_worker.IsThreadFiber( tid ) ) { ImGui::SameLine(); TextColoredUnformatted( ImVec4( 0.2f, 0.6f, 0.2f, 1.f ), "Fiber" ); } if( m_worker.HasZoneExtra( ev ) && m_worker.GetZoneExtra( ev ).text.Active() ) { TextFocused( "User text:", m_worker.GetString( m_worker.GetZoneExtra( ev ).text ) ); } ImGui::Separator(); ImGui::BeginChild( "##zoneinfo" ); const auto end = m_worker.GetZoneEnd( ev ); const auto ztime = end - ev.Start(); const auto selftime = GetZoneSelfTime( ev ); TextFocused( "Time from start of program:", TimeToStringExact( ev.Start() ) ); TextFocused( "Execution time:", TimeToString( ztime ) ); #ifndef TRACY_NO_STATISTICS if( m_worker.AreSourceLocationZonesReady() ) { auto& zoneData = m_worker.GetZonesForSourceLocation( ev.SrcLoc() ); if( zoneData.total > 0 ) { ImGui::SameLine(); ImGui::TextDisabled( "(%.2f%% of mean time)", float( ztime ) / zoneData.total * zoneData.zones.size() * 100 ); } } #endif TextFocused( "Self time:", TimeToString( selftime ) ); if( ztime != 0 ) { char buf[64]; PrintStringPercent( buf, 100.f * selftime / ztime ); ImGui::SameLine(); TextDisabledUnformatted( buf ); } const auto ctx = m_worker.GetContextSwitchData( tid ); if( ctx ) { auto it = std::lower_bound( ctx->v.begin(), ctx->v.end(), ev.Start(), [] ( const auto& l, const auto& r ) { return (uint64_t)l.End() < (uint64_t)r; } ); if( it != ctx->v.end() ) { const auto end = m_worker.GetZoneEnd( ev ); auto eit = std::upper_bound( it, ctx->v.end(), end, [] ( const auto& l, const auto& r ) { return l < r.Start(); } ); bool incomplete = eit == ctx->v.end() && !m_worker.IsThreadFiber( tid ); uint64_t cnt = std::distance( it, eit ); if( cnt == 1 ) { if( !incomplete ) { TextFocused( "Running state time:", TimeToString( ztime ) ); ImGui::SameLine(); TextDisabledUnformatted( "(100%)" ); ImGui::Separator(); TextFocused( "Running state regions:", "1" ); if( !threadData->isFiber ) TextFocused( "CPU:", RealToString( it->Cpu() ) ); } } else if( cnt > 1 ) { uint8_t cpus[256] = {}; auto bit = it; int64_t running = it->End() - ev.Start(); cpus[it->Cpu()] = 1; ++it; for( uint64_t i=0; iEnd() - it->Start(); cpus[it->Cpu()] = 1; ++it; } running += end - it->Start(); cpus[it->Cpu()] = 1; TextFocused( "Running state time:", TimeToString( running ) ); if( ztime != 0 ) { char buf[64]; PrintStringPercent( buf, 100.f * running / ztime ); ImGui::SameLine(); TextDisabledUnformatted( buf ); } ImGui::Separator(); if( incomplete ) { TextColoredUnformatted( ImVec4( 1, 0, 0, 1 ), "Incomplete context switch data!" ); } TextFocused( "Running state regions:", RealToString( cnt ) ); if( !threadData->isFiber ) { int numCpus = 0; for( int i=0; i<256; i++ ) numCpus += cpus[i]; if( numCpus == 1 ) { TextFocused( "CPU:", RealToString( it->Cpu() ) ); } else { ImGui::TextDisabled( "CPUs (%i):", numCpus ); for( int i=0;; i++ ) { if( cpus[i] != 0 ) { ImGui::SameLine(); numCpus--; if( numCpus == 0 ) { ImGui::Text( "%i", i ); break; } else { int consecutive = 1; int remaining = numCpus; for(;;) { if( cpus[i+consecutive] == 0 ) break; consecutive++; if( --remaining == 0 ) break; } if( consecutive > 2 ) { if( remaining == 0 ) { ImGui::Text( "%i \xE2\x80\x93 %i", i, i+consecutive-1 ); break; } else { ImGui::Text( "%i \xE2\x80\x93 %i,", i, i+consecutive-1 ); i += consecutive - 1; numCpus = remaining; } } else { ImGui::Text( "%i,", i ); } } } } } } --eit; if( ImGui::TreeNode( "Wait regions" ) ) { ImGui::SameLine(); ImGui::Spacing(); ImGui::SameLine(); SmallCheckbox( "Time relative to zone start", &m_ctxSwitchTimeRelativeToZone ); const int64_t adjust = m_ctxSwitchTimeRelativeToZone ? ev.Start() : 0; const auto wrsz = eit - bit; const auto numColumns = threadData->isFiber ? 4 : 6; if( ImGui::BeginTable( "##waitregions", numColumns, ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable, ImVec2( 0, ImGui::GetTextLineHeightWithSpacing() * std::min( 1+wrsz, 15 ) ) ) ) { ImGui::TableSetupScrollFreeze( 0, 1 ); ImGui::TableSetupColumn( "Begin" ); ImGui::TableSetupColumn( "End" ); ImGui::TableSetupColumn( "Time" ); if( threadData->isFiber ) { ImGui::TableSetupColumn( "Thread" ); } else { ImGui::TableSetupColumn( "Wakeup" ); ImGui::TableSetupColumn( "CPU" ); ImGui::TableSetupColumn( "State" ); } ImGui::TableHeadersRow(); ImGuiListClipper clipper; clipper.Begin( wrsz ); while( clipper.Step() ) { for( auto i=clipper.DisplayStart; iisFiber ) { const auto ftid = m_worker.DecompressThread( bit[i].Thread() ); ImGui::TextUnformatted( m_worker.GetThreadName( ftid ) ); ImGui::SameLine(); ImGui::TextDisabled( "(%s)", RealToString( ftid ) ); } else { const auto cpu0 = bit[i].Cpu(); const auto reason = bit[i].Reason(); const auto state = bit[i].State(); const auto cpu1 = bit[i+1].Cpu(); if( cstart != cwakeup ) { if( ImGui::Selectable( TimeToString( cstart - cwakeup ) ) ) { ZoomToRange( cwakeup, cstart ); } } else { ImGui::TextUnformatted( "-" ); } ImGui::TableNextColumn(); if( cpu0 == cpu1 ) { ImGui::TextUnformatted( RealToString( cpu0 ) ); } else { ImGui::Text( "%i " ICON_FA_LONG_ARROW_ALT_RIGHT " %i", cpu0, cpu1 ); const auto tt0 = m_worker.GetThreadTopology( cpu0 ); const auto tt1 = m_worker.GetThreadTopology( cpu1 ); if( tt0 && tt1 ) { if( tt0->package != tt1->package ) { ImGui::SameLine(); TextDisabledUnformatted( "P" ); } else if( tt0->core != tt1->core ) { ImGui::SameLine(); TextDisabledUnformatted( "C" ); } } } ImGui::TableNextColumn(); const char* desc; if( reason == ContextSwitchData::NoState ) { ImGui::TextUnformatted( DecodeContextSwitchStateCode( state ) ); desc = DecodeContextSwitchState( state ); } else { ImGui::TextUnformatted( DecodeContextSwitchReasonCode( reason ) ); desc = DecodeContextSwitchReason( reason ); } if( *desc ) TooltipIfHovered( desc ); } ImGui::PopID(); } } ImGui::EndTable(); } ImGui::TreePop(); } } } } ImGui::Separator(); auto& memNameMap = m_worker.GetMemNameMap(); if( memNameMap.size() > 1 ) { ImGui::AlignTextToFramePadding(); TextDisabledUnformatted( ICON_FA_ARCHIVE " Memory pool:" ); ImGui::SameLine(); if( ImGui::BeginCombo( "##memoryPool", m_zoneInfoMemPool == 0 ? "Default allocator" : m_worker.GetString( m_zoneInfoMemPool ) ) ) { for( auto& v : memNameMap ) { if( ImGui::Selectable( v.first == 0 ? "Default allocator" : m_worker.GetString( v.first ) ) ) { m_zoneInfoMemPool = v.first; } } ImGui::EndCombo(); } } auto& mem = m_worker.GetMemoryNamed( m_zoneInfoMemPool ); if( mem.data.empty() ) { TextDisabledUnformatted( "No memory events." ); } else { if( !mem.plot ) { ImGui::Text( "Please wait, computing data..." ); DrawWaitingDots( s_time ); } else { const auto thread = m_worker.CompressThread( tid ); auto ait = std::lower_bound( mem.data.begin(), mem.data.end(), ev.Start(), [] ( const auto& l, const auto& r ) { return l.TimeAlloc() < r; } ); const auto aend = std::upper_bound( ait, mem.data.end(), end, [] ( const auto& l, const auto& r ) { return l < r.TimeAlloc(); } ); auto fit = std::lower_bound( mem.frees.begin(), mem.frees.end(), ev.Start(), [&mem] ( const auto& l, const auto& r ) { return mem.data[l].TimeFree() < r; } ); const auto fend = std::upper_bound( fit, mem.frees.end(), end, [&mem] ( const auto& l, const auto& r ) { return l < mem.data[r].TimeFree(); } ); const auto aDist = std::distance( ait, aend ); const auto fDist = std::distance( fit, fend ); if( aDist == 0 && fDist == 0 ) { TextDisabledUnformatted( "No memory events." ); } else { int64_t cAlloc = 0; int64_t cFree = 0; int64_t nAlloc = 0; int64_t nFree = 0; auto ait2 = ait; auto fit2 = fit; while( ait != aend ) { if( ait->ThreadAlloc() == thread ) { cAlloc += ait->Size(); nAlloc++; } ait++; } while( fit != fend ) { if( mem.data[*fit].ThreadFree() == thread ) { cFree += mem.data[*fit].Size(); nFree++; } fit++; } if( nAlloc == 0 && nFree == 0 ) { TextDisabledUnformatted( "No memory events." ); } else { ImGui::TextUnformatted( RealToString( nAlloc + nFree ) ); ImGui::SameLine(); TextDisabledUnformatted( "memory events." ); ImGui::TextUnformatted( RealToString( nAlloc ) ); ImGui::SameLine(); TextDisabledUnformatted( "allocs," ); ImGui::SameLine(); ImGui::TextUnformatted( RealToString( nFree ) ); ImGui::SameLine(); TextDisabledUnformatted( "frees." ); TextFocused( "Memory allocated:", MemSizeToString( cAlloc ) ); TextFocused( "Memory freed:", MemSizeToString( cFree ) ); TextFocused( "Overall change:", MemSizeToString( cAlloc - cFree ) ); if( ImGui::TreeNode( "Allocations list" ) ) { ImGui::SameLine(); ImGui::Spacing(); ImGui::SameLine(); SmallCheckbox( "Time relative to zone start", &m_allocTimeRelativeToZone ); std::vector v; v.reserve( nAlloc + nFree ); auto it = ait2; while( it != aend ) { if( it->ThreadAlloc() == thread ) { v.emplace_back( it ); } it++; } while( fit2 != fend ) { const auto ptr = &mem.data[*fit2++]; if( ptr->ThreadFree() == thread ) { if( ptr < ait2 || ptr >= aend ) { v.emplace_back( ptr ); } } } pdqsort_branchless( v.begin(), v.end(), [] ( const auto& l, const auto& r ) { return l->TimeAlloc() < r->TimeAlloc(); } ); ListMemData( v, []( auto v ) { ImGui::Text( "0x%" PRIx64, v->Ptr() ); }, nullptr, m_allocTimeRelativeToZone ? ev.Start() : -1, m_zoneInfoMemPool ); ImGui::TreePop(); } } } } } ImGui::Separator(); { if( threadData->messages.empty() ) { TextDisabledUnformatted( "No messages" ); } else { auto msgit = std::lower_bound( threadData->messages.begin(), threadData->messages.end(), ev.Start(), [] ( const auto& lhs, const auto& rhs ) { return lhs->time < rhs; } ); auto msgend = std::lower_bound( msgit, threadData->messages.end(), end+1, [] ( const auto& lhs, const auto& rhs ) { return lhs->time < rhs; } ); const auto dist = std::distance( msgit, msgend ); if( dist == 0 ) { TextDisabledUnformatted( "No messages" ); } else { bool expand = ImGui::TreeNode( "Messages" ); ImGui::SameLine(); ImGui::TextDisabled( "(%s)", RealToString( dist ) ); if( expand ) { ImGui::SameLine(); SmallCheckbox( "Time relative to zone start", &m_messageTimeRelativeToZone ); ImGui::SameLine(); SmallCheckbox( "Exclude children", &m_messagesExcludeChildren ); if( ImGui::BeginTable( "##messages", 2, ImGuiTableFlags_ScrollY | ImGuiTableFlags_BordersInnerV, ImVec2( 0, ImGui::GetTextLineHeightWithSpacing() * std::min( msgend-msgit+1, 15 ) ) ) ) { ImGui::TableSetupScrollFreeze( 0, 1 ); ImGui::TableSetupColumn( "Time", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoResize ); ImGui::TableSetupColumn( "Message" ); ImGui::TableHeadersRow(); do { if( m_messagesExcludeChildren && GetZoneChild( ev, (*msgit)->time ) ) continue; ImGui::PushID( *msgit ); ImGui::TableNextRow(); ImGui::TableNextColumn(); if( ImGui::Selectable( m_messageTimeRelativeToZone ? TimeToString( (*msgit)->time - ev.Start() ) : TimeToStringExact( (*msgit)->time ), m_msgHighlight == *msgit, ImGuiSelectableFlags_SpanAllColumns ) ) { CenterAtTime( (*msgit)->time ); } if( ImGui::IsItemHovered() ) { m_msgHighlight = *msgit; } ImGui::PopID(); ImGui::TableNextColumn(); ImGui::PushStyleColor( ImGuiCol_Text, (*msgit)->color ); ImGui::TextWrapped( "%s", m_worker.GetString( (*msgit)->ref ) ); ImGui::PopStyleColor(); } while( ++msgit != msgend ); ImGui::EndTable(); } ImGui::TreePop(); ImGui::Spacing(); } } } } ImGui::Separator(); std::vector zoneTrace; while( parent ) { zoneTrace.emplace_back( parent ); parent = GetZoneParent( *parent ); } int idx = 0; DrawZoneTrace( &ev, zoneTrace, m_worker, m_zoneinfoBuzzAnim, *this, m_showUnknownFrames, [&idx, this] ( const ZoneEvent* v, int& fidx ) { ImGui::TextDisabled( "%i.", fidx++ ); ImGui::SameLine(); const auto& srcloc = m_worker.GetSourceLocation( v->SrcLoc() ); SmallColorBox( GetSrcLocColor( srcloc, 0 ) ); ImGui::SameLine(); const auto txt = m_worker.GetZoneName( *v, srcloc ); ImGui::PushID( idx++ ); auto sel = ImGui::Selectable( txt, false ); auto hover = ImGui::IsItemHovered(); const auto fileName = m_worker.GetString( srcloc.file ); if( m_zoneinfoBuzzAnim.Match( v ) ) { const auto time = m_zoneinfoBuzzAnim.Time(); const auto indentVal = sin( time * 60.f ) * 10.f * time; ImGui::SameLine( 0, ImGui::GetStyle().ItemSpacing.x + indentVal ); } else { ImGui::SameLine(); } ImGui::TextDisabled( "(%s) %s", TimeToString( m_worker.GetZoneEnd( *v ) - v->Start() ), LocationToString( fileName, srcloc.line ) ); ImGui::PopID(); if( ImGui::IsItemClicked( 1 ) ) { if( SourceFileValid( fileName, m_worker.GetCaptureTime(), *this, m_worker ) ) { ViewSource( fileName, srcloc.line ); } else { m_zoneinfoBuzzAnim.Enable( v, 0.5f ); } } if( sel ) { ShowZoneInfo( *v ); } if( hover ) { m_zoneHighlight = v; if( IsMouseClicked( 2 ) ) { ZoomToZone( *v ); } ZoneTooltip( *v ); } } ); if( ev.HasChildren() ) { const auto& children = m_worker.GetZoneChildren( ev.Child() ); bool expand = ImGui::TreeNode( "Child zones" ); ImGui::SameLine(); ImGui::TextDisabled( "(%s)", RealToString( children.size() ) ); if( expand ) { if( children.is_magic() ) { DrawZoneInfoChildren>( *(Vector*)( &children ), ztime ); } else { DrawZoneInfoChildren>( children, ztime ); } ImGui::TreePop(); } expand = ImGui::TreeNode( "Time distribution" ); if( expand ) { ImGui::SameLine(); if( SmallCheckbox( "Self time", &m_timeDist.exclusiveTime ) ) m_timeDist.dataValidFor = nullptr; if( ctx ) { ImGui::SameLine(); if( SmallCheckbox( "Running time", &m_timeDist.runningTime ) ) m_timeDist.dataValidFor = nullptr; } if( m_timeDist.dataValidFor != &ev ) { m_timeDist.data.clear(); if( ev.IsEndValid() ) m_timeDist.dataValidFor = &ev; if( m_timeDist.runningTime ) { assert( ctx ); int64_t time; uint64_t cnt; if( !GetZoneRunningTime( ctx, ev, time, cnt ) ) { TextDisabledUnformatted( "Incomplete context switch data." ); m_timeDist.dataValidFor = nullptr; } else { auto it = m_timeDist.data.emplace( ev.SrcLoc(), ZoneTimeData{ time, 1 } ).first; CalcZoneTimeData( ctx, m_timeDist.data, it->second.time, ev ); } m_timeDist.fztime = 100.f / time; } else { auto it = m_timeDist.data.emplace( ev.SrcLoc(), ZoneTimeData{ ztime, 1 } ).first; CalcZoneTimeData( m_timeDist.data, it->second.time, ev ); m_timeDist.fztime = 100.f / ztime; } } if( !m_timeDist.data.empty() ) { std::vector::const_iterator> vec; vec.reserve( m_timeDist.data.size() ); for( auto it = m_timeDist.data.cbegin(); it != m_timeDist.data.cend(); ++it ) vec.emplace_back( it ); if( ImGui::BeginTable( "##timedist", 3, ImGuiTableFlags_Sortable | ImGuiTableFlags_BordersInnerV ) ) { ImGui::TableSetupColumn( "Zone", ImGuiTableColumnFlags_PreferSortDescending ); ImGui::TableSetupColumn( "Time", ImGuiTableColumnFlags_PreferSortDescending | ImGuiTableColumnFlags_DefaultSort | ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoResize ); ImGui::TableSetupColumn( "MTPC", ImGuiTableColumnFlags_PreferSortDescending | ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoResize ); ImGui::TableHeadersRow(); const auto& sortspec = *ImGui::TableGetSortSpecs()->Specs; switch( sortspec.ColumnIndex ) { case 0: if( sortspec.SortDirection == ImGuiSortDirection_Ascending ) { pdqsort_branchless( vec.begin(), vec.end(), []( const auto& lhs, const auto& rhs ) { return lhs->second.count < rhs->second.count; } ); } else { pdqsort_branchless( vec.begin(), vec.end(), []( const auto& lhs, const auto& rhs ) { return lhs->second.count > rhs->second.count; } ); } break; case 1: if( sortspec.SortDirection == ImGuiSortDirection_Ascending ) { pdqsort_branchless( vec.begin(), vec.end(), []( const auto& lhs, const auto& rhs ) { return lhs->second.time < rhs->second.time; } ); } else { pdqsort_branchless( vec.begin(), vec.end(), []( const auto& lhs, const auto& rhs ) { return lhs->second.time > rhs->second.time; } ); } break; case 2: if( sortspec.SortDirection == ImGuiSortDirection_Ascending ) { pdqsort_branchless( vec.begin(), vec.end(), []( const auto& lhs, const auto& rhs ) { return float( lhs->second.time ) / lhs->second.count < float( rhs->second.time ) / rhs->second.count; } ); } else { pdqsort_branchless( vec.begin(), vec.end(), []( const auto& lhs, const auto& rhs ) { return float( lhs->second.time ) / lhs->second.count > float( rhs->second.time ) / rhs->second.count; } ); } break; default: assert( false ); break; } for( auto& v : vec ) { ImGui::TableNextRow(); ImGui::TableNextColumn(); const auto& sl = m_worker.GetSourceLocation( v->first ); SmallColorBox( GetSrcLocColor( sl, 0 ) ); ImGui::SameLine(); const auto name = m_worker.GetZoneName( sl ); if( ImGui::Selectable( name, false, ImGuiSelectableFlags_SpanAllColumns ) ) { m_findZone.ShowZone( v->first, name, ev.Start(), m_worker.GetZoneEnd( ev ) ); } ImGui::SameLine(); ImGui::TextDisabled( "(\xc3\x97%s)", RealToString( v->second.count ) ); ImGui::TableNextColumn(); ImGui::TextUnformatted( TimeToString( v->second.time ) ); ImGui::SameLine(); char buf[64]; PrintStringPercent( buf, v->second.time * m_timeDist.fztime ); TextDisabledUnformatted( buf ); ImGui::TableNextColumn(); ImGui::TextUnformatted( TimeToString( v->second.time / v->second.count ) ); } ImGui::EndTable(); } } ImGui::TreePop(); } } ImGui::EndChild(); } ImGui::End(); if( !show ) { m_zoneInfoWindow = nullptr; m_zoneInfoStack.clear(); } } template void View::DrawZoneInfoChildren( const V& children, int64_t ztime ) { Adapter a; const auto rztime = 1.0 / ztime; const auto ty = ImGui::GetTextLineHeight(); ImGui::SameLine(); SmallCheckbox( "Group children locations", &m_groupChildrenLocations ); if( m_groupChildrenLocations ) { struct ChildGroup { int16_t srcloc; uint64_t t; Vector v; }; uint64_t ctime = 0; unordered_flat_map cmap; cmap.reserve( 128 ); for( size_t i=0; isecond.t += ct; it->second.v.push_back( i ); } auto msz = cmap.size(); Vector cgvec; cgvec.reserve_and_use( msz ); size_t idx = 0; for( auto& it : cmap ) { cgvec[idx++] = &it.second; } pdqsort_branchless( cgvec.begin(), cgvec.end(), []( const auto& lhs, const auto& rhs ) { return lhs->t > rhs->t; } ); ImGui::Columns( 2 ); ImGui::Indent( ImGui::GetTreeNodeToLabelSpacing() * 2 ); TextColoredUnformatted( ImVec4( 1.0f, 1.0f, 0.4f, 1.0f ), "Self time" ); ImGui::Unindent( ImGui::GetTreeNodeToLabelSpacing() * 2 ); ImGui::NextColumn(); char buf[128]; PrintStringPercent( buf, TimeToString( ztime - ctime ), double( ztime - ctime ) / ztime * 100 ); ImGui::ProgressBar( double( ztime - ctime ) * rztime, ImVec2( -1, ty ), buf ); ImGui::NextColumn(); for( size_t i=0; i( cgr.v.size() ); auto cti = std::make_unique( cgr.v.size() ); for( size_t i=0; i ctt[rhs]; } ); ImGuiListClipper clipper; clipper.Begin( cgr.v.size() ); while( clipper.Step() ) { for( auto i=clipper.DisplayStart; i( children.size() ); auto cti = std::make_unique( children.size() ); uint64_t ctime = 0; for( size_t i=0; i ctt[rhs]; } ); ImGui::Columns( 2 ); ImGui::Indent( ImGui::GetTreeNodeToLabelSpacing() ); TextColoredUnformatted( ImVec4( 1.0f, 1.0f, 0.4f, 1.0f ), "Self time" ); ImGui::Unindent( ImGui::GetTreeNodeToLabelSpacing() ); ImGui::NextColumn(); char buf[128]; PrintStringPercent( buf, TimeToString( ztime - ctime ), double( ztime - ctime ) / ztime * 100 ); ImGui::ProgressBar( double( ztime - ctime ) * rztime, ImVec2( -1, ty ), buf ); ImGui::NextColumn(); ImGuiListClipper clipper; clipper.Begin( children.size() ); while( clipper.Step() ) { for( auto i=clipper.DisplayStart; iSkipItems ) { if( ImGui::Button( ICON_FA_MICROSCOPE " Zoom to zone" ) ) { ZoomToZone( ev ); } auto parent = GetZoneParent( ev ); if( parent ) { ImGui::SameLine(); if( ImGui::Button( ICON_FA_ARROW_UP " Go to parent" ) ) { ShowZoneInfo( *parent, m_gpuInfoWindowThread ); } } if( ev.callstack.Val() != 0 ) { ImGui::SameLine(); bool hilite = m_callstackInfoWindow == ev.callstack.Val(); if( hilite ) { SetButtonHighlightColor(); } if( ImGui::Button( ICON_FA_ALIGN_JUSTIFY " Call stack" ) ) { m_callstackInfoWindow = ev.callstack.Val(); } if( hilite ) { ImGui::PopStyleColor( 3 ); } } const auto fileName = m_worker.GetString( srcloc.file ); if( SourceFileValid( fileName, m_worker.GetCaptureTime(), *this, m_worker ) ) { ImGui::SameLine(); bool hilite = m_sourceViewFile == fileName; if( hilite ) { SetButtonHighlightColor(); } if( ImGui::Button( ICON_FA_FILE_ALT " Source" ) ) { ViewSource( fileName, srcloc.line ); } if( hilite ) { ImGui::PopStyleColor( 3 ); } } if( !m_gpuInfoStack.empty() ) { ImGui::SameLine(); if( ImGui::Button( ICON_FA_ARROW_LEFT " Go back" ) ) { m_gpuInfoWindow = m_gpuInfoStack.back_and_pop(); } } ImGui::Separator(); const auto tid = GetZoneThread( ev ); ImGui::PushFont( m_bigFont ); TextFocused( "Zone name:", m_worker.GetString( srcloc.name ) ); ImGui::PopFont(); ImGui::SameLine(); if( ClipboardButton( 1 ) ) ImGui::SetClipboardText( m_worker.GetString( srcloc.name ) ); TextFocused( "Function:", m_worker.GetString( srcloc.function ) ); ImGui::SameLine(); if( ClipboardButton( 2 ) ) ImGui::SetClipboardText( m_worker.GetString( srcloc.function ) ); SmallColorBox( GetZoneColor( ev ) ); ImGui::SameLine(); TextDisabledUnformatted( "Location:" ); ImGui::SameLine(); ImGui::TextUnformatted( LocationToString( m_worker.GetString( srcloc.file ), srcloc.line ) ); ImGui::SameLine(); if( ClipboardButton( 3 ) ) { ImGui::SetClipboardText( LocationToString( m_worker.GetString( srcloc.file ), srcloc.line ) ); } SmallColorBox( GetThreadColor( tid, 0 ) ); ImGui::SameLine(); TextFocused( "Thread:", m_worker.GetThreadName( tid ) ); ImGui::SameLine(); ImGui::TextDisabled( "(%s)", RealToString( tid ) ); if( m_worker.IsThreadFiber( tid ) ) { ImGui::SameLine(); TextColoredUnformatted( ImVec4( 0.2f, 0.6f, 0.2f, 1.f ), "Fiber" ); } ImGui::Separator(); ImGui::BeginChild( "##gpuinfo" ); const auto end = m_worker.GetZoneEnd( ev ); const auto ztime = end - ev.GpuStart(); const auto selftime = GetZoneSelfTime( ev ); TextFocused( "Time from start of program:", TimeToStringExact( ev.GpuStart() ) ); TextFocused( "GPU execution time:", TimeToString( ztime ) ); TextFocused( "GPU self time:", TimeToString( selftime ) ); if( ztime != 0 ) { char buf[64]; PrintStringPercent( buf, 100.f * selftime / ztime ); ImGui::SameLine(); TextDisabledUnformatted( buf ); } TextFocused( "CPU command setup time:", TimeToString( ev.CpuEnd() - ev.CpuStart() ) ); auto ctx = GetZoneCtx( ev ); if( !ctx ) { TextFocused( "Delay to execution:", TimeToString( ev.GpuStart() - ev.CpuStart() ) ); } else { const auto td = ctx->threadData.size() == 1 ? ctx->threadData.begin() : ctx->threadData.find( m_worker.DecompressThread( ev.Thread() ) ); assert( td != ctx->threadData.end() ); int64_t begin; if( td->second.timeline.is_magic() ) { begin = ((Vector*)&td->second.timeline)->front().GpuStart(); } else { begin = td->second.timeline.front()->GpuStart(); } const auto drift = GpuDrift( ctx ); TextFocused( "Delay to execution:", TimeToString( AdjustGpuTime( ev.GpuStart(), begin, drift ) - ev.CpuStart() ) ); } ImGui::Separator(); std::vector zoneTrace; while( parent ) { zoneTrace.emplace_back( parent ); parent = GetZoneParent( *parent ); } int idx = 0; DrawZoneTrace( &ev, zoneTrace, m_worker, m_zoneinfoBuzzAnim, *this, m_showUnknownFrames, [&idx, this] ( const GpuEvent* v, int& fidx ) { ImGui::TextDisabled( "%i.", fidx++ ); ImGui::SameLine(); const auto& srcloc = m_worker.GetSourceLocation( v->SrcLoc() ); const auto txt = m_worker.GetZoneName( *v, srcloc ); ImGui::PushID( idx++ ); auto sel = ImGui::Selectable( txt, false ); auto hover = ImGui::IsItemHovered(); const auto fileName = m_worker.GetString( srcloc.file ); if( m_zoneinfoBuzzAnim.Match( v ) ) { const auto time = m_zoneinfoBuzzAnim.Time(); const auto indentVal = sin( time * 60.f ) * 10.f * time; ImGui::SameLine( 0, ImGui::GetStyle().ItemSpacing.x + indentVal ); } else { ImGui::SameLine(); } ImGui::TextDisabled( "(%s) %s", TimeToString( m_worker.GetZoneEnd( *v ) - v->GpuStart() ), LocationToString( fileName, srcloc.line ) ); ImGui::PopID(); if( ImGui::IsItemClicked( 1 ) ) { if( SourceFileValid( fileName, m_worker.GetCaptureTime(), *this, m_worker ) ) { ViewSource( fileName, srcloc.line ); } else { m_zoneinfoBuzzAnim.Enable( v, 0.5f ); } } if( sel ) { ShowZoneInfo( *v, m_gpuInfoWindowThread ); } if( hover ) { m_gpuHighlight = v; if( IsMouseClicked( 2 ) ) { ZoomToZone( *v ); } ZoneTooltip( *v ); } } ); if( ev.Child() >= 0 ) { const auto& children = m_worker.GetGpuChildren( ev.Child() ); bool expand = ImGui::TreeNode( "Child zones" ); ImGui::SameLine(); ImGui::TextDisabled( "(%s)", RealToString( children.size() ) ); if( expand ) { if( children.is_magic() ) { DrawGpuInfoChildren>( *(Vector*)( &children ), ztime ); } else { DrawGpuInfoChildren>( children, ztime ); } ImGui::TreePop(); } } ImGui::EndChild(); } ImGui::End(); if( !show ) { m_gpuInfoWindow = nullptr; m_gpuInfoStack.clear(); } } template void View::DrawGpuInfoChildren( const V& children, int64_t ztime ) { Adapter a; const auto rztime = 1.0 / ztime; const auto ty = ImGui::GetTextLineHeight(); ImGui::SameLine(); SmallCheckbox( "Group children locations", &m_groupChildrenLocations ); if( m_groupChildrenLocations ) { struct ChildGroup { int16_t srcloc; uint64_t t; Vector v; }; uint64_t ctime = 0; unordered_flat_map cmap; cmap.reserve( 128 ); for( size_t i=0; isecond.t += ct; it->second.v.push_back( i ); } auto msz = cmap.size(); Vector cgvec; cgvec.reserve_and_use( msz ); size_t idx = 0; for( auto& it : cmap ) { cgvec[idx++] = &it.second; } pdqsort_branchless( cgvec.begin(), cgvec.end(), []( const auto& lhs, const auto& rhs ) { return lhs->t > rhs->t; } ); ImGui::Columns( 2 ); ImGui::Indent( ImGui::GetTreeNodeToLabelSpacing() ); TextColoredUnformatted( ImVec4( 1.0f, 1.0f, 0.4f, 1.0f ), "Self time" ); ImGui::Unindent( ImGui::GetTreeNodeToLabelSpacing() ); ImGui::NextColumn(); char buf[128]; PrintStringPercent( buf, TimeToString( ztime - ctime ), double( ztime - ctime ) / ztime * 100 ); ImGui::ProgressBar( double( ztime - ctime ) * rztime, ImVec2( -1, ty ), buf ); ImGui::NextColumn(); for( size_t i=0; i( cgr.v.size() ); auto cti = std::make_unique( cgr.v.size() ); for( size_t i=0; i ctt[rhs]; } ); for( size_t i=0; i( children.size() ); auto cti = std::make_unique( children.size() ); uint64_t ctime = 0; for( size_t i=0; i ctt[rhs]; } ); ImGui::Columns( 2 ); TextColoredUnformatted( ImVec4( 1.0f, 1.0f, 0.4f, 1.0f ), "Self time" ); ImGui::NextColumn(); char buf[128]; PrintStringPercent( buf, TimeToString( ztime - ctime ), double( ztime - ctime ) / ztime * 100 ); ImGui::ProgressBar( double( ztime - ctime ) / ztime, ImVec2( -1, ty ), buf ); ImGui::NextColumn(); for( size_t i=0; i 0 ) { ImGui::SameLine(); ImGui::TextDisabled( "(%.2f%% of mean time)", float( ztime ) / zoneData.total * zoneData.zones.size() * 100 ); } } #endif TextFocused( "Self time:", TimeToString( selftime ) ); if( ztime != 0 ) { char buf[64]; PrintStringPercent( buf, 100.f * selftime / ztime ); ImGui::SameLine(); TextDisabledUnformatted( buf ); } const auto ctx = m_worker.GetContextSwitchData( tid ); if( ctx ) { int64_t time; uint64_t cnt; if( GetZoneRunningTime( ctx, ev, time, cnt ) ) { TextFocused( "Running state time:", TimeToString( time ) ); if( ztime != 0 ) { char buf[64]; PrintStringPercent( buf, 100.f * time / ztime ); ImGui::SameLine(); TextDisabledUnformatted( buf ); } TextFocused( "Running state regions:", RealToString( cnt ) ); } } if( m_worker.HasZoneExtra( ev ) && m_worker.GetZoneExtra( ev ).text.Active() ) { ImGui::NewLine(); TextColoredUnformatted( ImVec4( 0xCC / 255.f, 0xCC / 255.f, 0x22 / 255.f, 1.f ), m_worker.GetString( m_worker.GetZoneExtra( ev ).text ) ); } ImGui::EndTooltip(); } void View::ZoneTooltip( const GpuEvent& ev ) { const auto tid = GetZoneThread( ev ); const auto& srcloc = m_worker.GetSourceLocation( ev.SrcLoc() ); const auto end = m_worker.GetZoneEnd( ev ); const auto ztime = end - ev.GpuStart(); const auto selftime = GetZoneSelfTime( ev ); ImGui::BeginTooltip(); ImGui::TextUnformatted( m_worker.GetString( srcloc.name ) ); ImGui::TextUnformatted( m_worker.GetString( srcloc.function ) ); ImGui::Separator(); SmallColorBox( GetSrcLocColor( srcloc, 0 ) ); ImGui::SameLine(); ImGui::TextUnformatted( LocationToString( m_worker.GetString( srcloc.file ), srcloc.line ) ); SmallColorBox( GetThreadColor( tid, 0 ) ); ImGui::SameLine(); TextFocused( "Thread:", m_worker.GetThreadName( tid ) ); ImGui::SameLine(); ImGui::TextDisabled( "(%s)", RealToString( tid ) ); if( m_worker.IsThreadFiber( tid ) ) { ImGui::SameLine(); TextColoredUnformatted( ImVec4( 0.2f, 0.6f, 0.2f, 1.f ), "Fiber" ); } ImGui::Separator(); TextFocused( "GPU execution time:", TimeToString( ztime ) ); TextFocused( "GPU self time:", TimeToString( selftime ) ); if( ztime != 0 ) { char buf[64]; PrintStringPercent( buf, 100.f * selftime / ztime ); ImGui::SameLine(); TextDisabledUnformatted( buf ); } TextFocused( "CPU command setup time:", TimeToString( ev.CpuEnd() - ev.CpuStart() ) ); auto ctx = GetZoneCtx( ev ); if( !ctx ) { TextFocused( "Delay to execution:", TimeToString( ev.GpuStart() - ev.CpuStart() ) ); } else { const auto td = ctx->threadData.size() == 1 ? ctx->threadData.begin() : ctx->threadData.find( m_worker.DecompressThread( ev.Thread() ) ); assert( td != ctx->threadData.end() ); int64_t begin; if( td->second.timeline.is_magic() ) { begin = ((Vector*)&td->second.timeline)->front().GpuStart(); } else { begin = td->second.timeline.front()->GpuStart(); } const auto drift = GpuDrift( ctx ); TextFocused( "Delay to execution:", TimeToString( AdjustGpuTime( ev.GpuStart(), begin, drift ) - ev.CpuStart() ) ); } ImGui::EndTooltip(); } }