diff --git a/profiler/build/win32/Tracy.vcxproj b/profiler/build/win32/Tracy.vcxproj
index e64138f7..d598a5ac 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 5cfa94e8..25b97ceb 100644
--- a/profiler/build/win32/Tracy.vcxproj.filters
+++ b/profiler/build/win32/Tracy.vcxproj.filters
@@ -273,6 +273,9 @@
server
+
+ server
+
diff --git a/server/TracyView.cpp b/server/TracyView.cpp
index 18780e08..0860b75c 100644
--- a/server/TracyView.cpp
+++ b/server/TracyView.cpp
@@ -7767,948 +7767,6 @@ void View::DrawMemoryAllocWindow()
if( !show ) m_memoryAllocInfoWindow = -1;
}
-void View::DrawInfo()
-{
- const auto scale = GetScale();
- ImGui::SetNextWindowSize( ImVec2( 400 * scale, 650 * scale ), ImGuiCond_FirstUseEver );
- ImGui::Begin( "Trace information", &m_showInfo, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse );
- if( ImGui::GetCurrentWindowRead()->SkipItems ) { ImGui::End(); return; }
- ImGui::PushFont( m_bigFont );
- TextFocused( "Program:", m_worker.GetCaptureProgram().c_str() );
- ImGui::PopFont();
- const auto exectime = m_worker.GetExecutableTime();
- if( exectime != 0 )
- {
- char etmp[64];
- time_t et = exectime;
- auto elt = localtime( &et );
- strftime( etmp, 64, "%F %T", elt );
- TextFocused( "Build time:", etmp );
- }
- {
- char dtmp[64];
- time_t date = m_worker.GetCaptureTime();
- auto lt = localtime( &date );
- strftime( dtmp, 64, "%F %T", lt );
- TextFocused( "Capture time:", dtmp );
- }
- if( !m_filename.empty() )
- {
- TextFocused( "File:", m_filename.c_str() );
- if( m_userData.Valid() )
- {
- const auto save = m_userData.GetConfigLocation();
- if( save )
- {
- ImGui::SameLine();
- if( ImGui::SmallButton( ICON_FA_FOLDER ) )
- {
- ImGui::SetClipboardText( save );
- }
- TooltipIfHovered( "Copy user settings location to clipboard." );
- }
- }
- }
- {
- const auto& desc = m_userData.GetDescription();
- const auto descsz = std::min( 255, desc.size() );
- char buf[256];
- buf[descsz] = '\0';
- memcpy( buf, desc.c_str(), descsz );
- ImGui::SetNextItemWidth( -1 );
- if( ImGui::InputTextWithHint( "##traceDesc", "Enter description of the trace", buf, 256 ) )
- {
- m_userData.SetDescription( buf );
- }
- }
-
- ImGui::Separator();
- ImGui::BeginChild( "##info" );
-
- const auto ficnt = m_worker.GetFrameImageCount();
- if( ImGui::TreeNode( "Trace statistics" ) )
- {
- ImGui::TextDisabled( "Trace version:" );
- ImGui::SameLine();
- const auto version = m_worker.GetTraceVersion();
- ImGui::Text( "%i.%i.%i", version >> 16, ( version >> 8 ) & 0xFF, version & 0xFF );
- TextFocused( "Queue delay:", TimeToString( m_worker.GetDelay() ) );
- TextFocused( "Timer resolution:", TimeToString( m_worker.GetResolution() ) );
- TextFocused( "CPU zones:", RealToString( m_worker.GetZoneCount() ) );
- ImGui::SameLine();
- ImGui::Spacing();
- ImGui::SameLine();
- TextFocused( "Extra data:", RealToString( m_worker.GetZoneExtraCount() ) );
- TooltipIfHovered( "Count of zones containing any of the following: call stack trace, custom name, user text" );
- TextFocused( "GPU zones:", RealToString( m_worker.GetGpuZoneCount() ) );
- TextFocused( "Lock events:", RealToString( m_worker.GetLockCount() ) );
- TextFocused( "Plot data points:", RealToString( m_worker.GetPlotCount() ) );
- TooltipIfHovered( "User plots" );
- ImGui::SameLine();
- TextFocused( "+", RealToString( m_worker.GetTracyPlotCount() ) );
- TooltipIfHovered( "Automated Tracy plots" );
- auto& memNameMap = m_worker.GetMemNameMap();
- TextFocused( "Memory pools:", RealToString( memNameMap.size() ) );
- uint64_t memTotalCnt = 0;
- for( auto v : memNameMap ) memTotalCnt += v.second->data.size();
- TextFocused( "Memory allocations:", RealToString( memTotalCnt ) );
- TextFocused( "Source locations:", RealToString( m_worker.GetSrcLocCount() ) );
- TextFocused( "Strings:", RealToString( m_worker.GetStringsCount() ) );
- TextFocused( "Symbols:", RealToString( m_worker.GetSymbolsCount() ) );
- TextFocused( "Symbol code fragments:", RealToString( m_worker.GetSymbolCodeCount() ) );
- TooltipIfHovered( MemSizeToString( m_worker.GetSymbolCodeSize() ) );
- TextFocused( "Code locations:", RealToString( m_worker.GetCodeLocationsSize() ) );
- TextFocused( "Call stacks:", RealToString( m_worker.GetCallstackPayloadCount() ) );
- if( m_worker.AreCallstackSamplesReady() )
- {
- ImGui::SameLine();
- TextFocused( "+", RealToString( m_worker.GetCallstackParentPayloadCount() ) );
- TooltipIfHovered( "Parent call stacks for stack samples" );
- }
- TextFocused( "Call stack frames:", RealToString( m_worker.GetCallstackFrameCount() ) );
- if( m_worker.AreCallstackSamplesReady() )
- {
- ImGui::SameLine();
- TextFocused( "+", RealToString( m_worker.GetCallstackParentFrameCount() ) );
- TooltipIfHovered( "Parent call stack frames for stack samples" );
- }
- TextFocused( "Call stack samples:", RealToString( m_worker.GetCallstackSampleCount() ) );
- TextFocused( "Ghost zones:", RealToString( m_worker.GetGhostZonesCount() ) );
-#ifndef TRACY_NO_STATISTICS
- TextFocused( "Child sample symbols:", RealToString( m_worker.GetChildSamplesCountSyms() ) );
- if( ImGui::IsItemHovered() )
- {
- ImGui::BeginTooltip();
- TextFocused( "Child samples:", RealToString( m_worker.GetChildSamplesCountFull() ) );
- ImGui::EndTooltip();
- }
- TextFocused( "Context switch samples:", RealToString( m_worker.GetContextSwitchSampleCount() ) );
-#endif
- TextFocused( "Hardware samples:", RealToString( m_worker.GetHwSampleCount() ) );
- if( ImGui::IsItemHovered() )
- {
- ImGui::BeginTooltip();
- TextFocused( "Unique addresses:", RealToString( m_worker.GetHwSampleCountAddress() ) );
- ImGui::EndTooltip();
- }
- TextFocused( "Frame images:", RealToString( ficnt ) );
- if( ficnt != 0 && ImGui::IsItemHovered() )
- {
- const auto bytes = m_worker.GetTextureCompressionBytes();
- ImGui::BeginTooltip();
- TextFocused( "Input data:", MemSizeToString( bytes.first ) );
- TextFocused( "Compressed:", MemSizeToString( bytes.second ) );
- char buf[64];
- auto ptr = PrintFloat( buf, buf+62, 100. * bytes.second / bytes.first, 2 );
- memcpy( ptr, "%", 2 );
- TextFocused( "Ratio:", buf );
- ImGui::EndTooltip();
- }
- TextFocused( "Context switch regions:", RealToString( m_worker.GetContextSwitchCount() ) );
- TooltipIfHovered( "Detailed context switch data regarding application threads" );
- ImGui::SameLine();
- TextFocused( "+", RealToString( m_worker.GetContextSwitchPerCpuCount() ) );
- TooltipIfHovered( "Coarse CPU core context switch data" );
- if( m_worker.GetSourceFileCacheCount() == 0 )
- {
- TextFocused( "Source file cache:", "0" );
- }
- else
- {
- ImGui::PushStyleColor( ImGuiCol_Text, GImGui->Style.Colors[ImGuiCol_TextDisabled] );
- const bool expand = ImGui::TreeNode( "Source file cache:" );
- ImGui::PopStyleColor();
- ImGui::SameLine();
- ImGui::TextUnformatted( RealToString( m_worker.GetSourceFileCacheCount() ) );
- TooltipIfHovered( MemSizeToString( m_worker.GetSourceFileCacheSize() ) );
- if( expand )
- {
- auto& cache = m_worker.GetSourceFileCache();
- std::vector vec;
- vec.reserve( cache.size() );
- for( auto it = cache.begin(); it != cache.end(); ++it ) vec.emplace_back( it );
- pdqsort_branchless( vec.begin(), vec.end(), []( const auto& lhs, const auto& rhs ) { return strcmp( lhs->first, rhs->first ) < 0; } );
- for( auto& v : vec )
- {
- ImGui::BulletText( "%s", v->first );
- if( ImGui::IsItemClicked() ) ViewSource( v->first, 0 );
- ImGui::SameLine();
- ImGui::TextDisabled( "(%s)", MemSizeToString( v->second.len ) );
- }
- ImGui::TreePop();
- }
- }
- ImGui::TreePop();
- }
-
- if( ImGui::TreeNode( "Frame statistics" ) )
- {
- auto fsz = m_worker.GetFullFrameCount( *m_frames );
- if( fsz != 0 )
- {
- TextFocused( "Frame set:", m_frames->name == 0 ? "Frames" : m_worker.GetString( m_frames->name ) );
- ImGui::SameLine();
- ImGui::TextDisabled( "(%s)", m_frames->continuous ? "continuous" : "discontinuous" );
- ImGui::SameLine();
- ImGui::PushStyleVar( ImGuiStyleVar_FramePadding, ImVec2( 0, 0 ) );
- if( ImGui::BeginCombo( "##frameCombo", nullptr, ImGuiComboFlags_NoPreview ) )
- {
- auto& frames = m_worker.GetFrames();
- for( auto& fd : frames )
- {
- bool isSelected = m_frames == fd;
- if( ImGui::Selectable( fd->name == 0 ? "Frames" : m_worker.GetString( fd->name ), isSelected ) )
- {
- m_frames = fd;
- fsz = m_worker.GetFullFrameCount( *m_frames );
- }
- if( isSelected )
- {
- ImGui::SetItemDefaultFocus();
- }
- ImGui::SameLine();
- ImGui::TextDisabled( "(%s)", RealToString( fd->frames.size() ) );
- }
- ImGui::EndCombo();
- }
- ImGui::PopStyleVar();
- ImGui::SameLine();
- SmallCheckbox( "Limit to view", &m_frameSortData.limitToView );
- if( m_frameSortData.limitToView )
- {
- ImGui::SameLine();
- TextColoredUnformatted( 0xFF00FFFF, ICON_FA_EXCLAMATION_TRIANGLE );
- }
-
- const auto frameRange = m_worker.GetFrameRange( *m_frames, m_vd.zvStart, m_vd.zvEnd );
- if( m_frameSortData.frameSet != m_frames || ( m_frameSortData.limitToView && m_frameSortData.limitRange != frameRange ) || ( !m_frameSortData.limitToView && m_frameSortData.limitRange.first != -1 ) )
- {
- m_frameSortData.frameSet = m_frames;
- m_frameSortData.frameNum = 0;
- m_frameSortData.data.clear();
- m_frameSortData.total = 0;
- }
- bool recalc = false;
- int64_t total = 0;
- if( !m_frameSortData.limitToView )
- {
- if( m_frameSortData.frameNum != fsz || m_frameSortData.limitRange.first != -1 )
- {
- auto& vec = m_frameSortData.data;
- vec.reserve( fsz );
- const auto midSz = vec.size();
- total = m_frameSortData.total;
- for( size_t i=m_frameSortData.frameNum; i 0 )
- {
- vec.emplace_back( t );
- total += t;
- }
- }
- auto mid = vec.begin() + midSz;
- pdqsort_branchless( mid, vec.end() );
- std::inplace_merge( vec.begin(), mid, vec.end() );
- recalc = true;
- m_frameSortData.limitRange.first = -1;
- }
- }
- else
- {
- if( m_frameSortData.limitRange != frameRange )
- {
- auto& vec = m_frameSortData.data;
- assert( vec.empty() );
- vec.reserve( frameRange.second - frameRange.first );
- for( int i=frameRange.first; i 0 )
- {
- vec.emplace_back( t );
- total += t;
- }
- }
- pdqsort_branchless( vec.begin(), vec.end() );
- recalc = true;
- m_frameSortData.limitRange = frameRange;
- }
- }
- if( recalc )
- {
- auto& vec = m_frameSortData.data;
- const auto vsz = vec.size();
- m_frameSortData.average = float( total ) / vsz;
- m_frameSortData.median = vec[vsz/2];
- m_frameSortData.total = total;
- m_frameSortData.frameNum = fsz;
- }
-
- const auto profileSpan = m_worker.GetLastTime();
- TextFocused( "Count:", RealToString( fsz ) );
- TextFocused( "Total time:", TimeToString( m_frameSortData.total ) );
- ImGui::SameLine();
- ImGui::TextDisabled( "(%.2f%% of profile time span)", m_frameSortData.total / float( profileSpan ) * 100.f );
- TextFocused( "Mean frame time:", TimeToString( m_frameSortData.average ) );
- ImGui::SameLine();
- ImGui::TextDisabled( "(%s FPS)", RealToString( round( 1000000000.0 / m_frameSortData.average ) ) );
- if( ImGui::IsItemHovered() )
- {
- ImGui::BeginTooltip();
- ImGui::Text( "%s FPS", RealToString( 1000000000.0 / m_frameSortData.average ) );
- ImGui::EndTooltip();
- }
- TextFocused( "Median frame time:", TimeToString( m_frameSortData.median ) );
- ImGui::SameLine();
- ImGui::TextDisabled( "(%s FPS)", RealToString( round( 1000000000.0 / m_frameSortData.median ) ) );
- if( ImGui::IsItemHovered() )
- {
- ImGui::BeginTooltip();
- ImGui::Text( "%s FPS", RealToString( 1000000000.0 / m_frameSortData.median ) );
- ImGui::EndTooltip();
- }
-
- if( ImGui::TreeNodeEx( "Histogram", ImGuiTreeNodeFlags_DefaultOpen ) )
- {
- const auto ty = ImGui::GetTextLineHeight();
-
- auto& frames = m_frameSortData.data;
- auto tmin = frames.front();
- auto tmax = frames.back();
-
- if( tmin != std::numeric_limits::max() )
- {
- TextDisabledUnformatted( "Minimum values in bin:" );
- ImGui::SameLine();
- ImGui::SetNextItemWidth( ImGui::CalcTextSize( "123456890123456" ).x );
- ImGui::PushStyleVar( ImGuiStyleVar_FramePadding, ImVec2( 1, 1 ) );
- ImGui::InputInt( "##minBinVal", &m_frameSortData.minBinVal );
- if( m_frameSortData.minBinVal < 1 ) m_frameSortData.minBinVal = 1;
- ImGui::SameLine();
- if( ImGui::Button( "Reset" ) ) m_frameSortData.minBinVal = 1;
- ImGui::PopStyleVar();
-
- SmallCheckbox( "Log values", &m_frameSortData.logVal );
- ImGui::SameLine();
- SmallCheckbox( "Log time", &m_frameSortData.logTime );
-
- TextDisabledUnformatted( "FPS range:" );
- ImGui::SameLine();
- ImGui::Text( "%s FPS - %s FPS", RealToString( round( 1000000000.0 / tmin ) ), RealToString( round( 1000000000.0 / tmax ) ) );
-
- if( tmax - tmin > 0 )
- {
- const auto w = ImGui::GetContentRegionAvail().x;
-
- const auto numBins = int64_t( w - 4 );
- if( numBins > 1 )
- {
- if( numBins > m_frameSortData.numBins )
- {
- m_frameSortData.numBins = numBins;
- m_frameSortData.bins = std::make_unique( numBins );
- }
-
- const auto& bins = m_frameSortData.bins;
-
- memset( bins.get(), 0, sizeof( int64_t ) * numBins );
-
- auto framesBegin = frames.begin();
- auto framesEnd = frames.end();
- while( framesBegin != framesEnd && *framesBegin == 0 ) ++framesBegin;
-
- if( m_frameSortData.minBinVal > 1 )
- {
- if( m_frameSortData.logTime )
- {
- const auto tMinLog = log10( tmin );
- const auto zmax = ( log10( tmax ) - tMinLog ) / numBins;
- int64_t i;
- for( i=0; i= m_frameSortData.minBinVal ) break;
- framesBegin = nit;
- }
- for( int64_t j=numBins-1; j>i; j-- )
- {
- const auto nextBinVal = int64_t( pow( 10.0, tMinLog + ( j-1 ) * zmax ) );
- auto nit = std::lower_bound( framesBegin, framesEnd, nextBinVal );
- const auto distance = std::distance( nit, framesEnd );
- if( distance >= m_frameSortData.minBinVal ) break;
- framesEnd = nit;
- }
- }
- else
- {
- const auto zmax = tmax - tmin;
- int64_t i;
- for( i=0; i= m_frameSortData.minBinVal ) break;
- framesBegin = nit;
- }
- for( int64_t j=numBins-1; j>i; j-- )
- {
- const auto nextBinVal = tmin + ( j-1 ) * zmax / numBins;
- auto nit = std::lower_bound( framesBegin, framesEnd, nextBinVal );
- const auto distance = std::distance( nit, framesEnd );
- if( distance >= m_frameSortData.minBinVal ) break;
- framesEnd = nit;
- }
- }
-
- tmin = *framesBegin;
- tmax = *(framesEnd-1);
- }
-
- if( m_frameSortData.logTime )
- {
- const auto tMinLog = log10( tmin );
- const auto zmax = ( log10( tmax ) - tMinLog ) / numBins;
- auto fit = framesBegin;
- for( int64_t i=0; iAddRectFilled( wpos, wpos + ImVec2( w, Height ), 0x22FFFFFF );
- draw->AddRect( wpos, wpos + ImVec2( w, Height ), 0x88FFFFFF );
-
- if( m_frameSortData.logVal )
- {
- const auto hAdj = double( Height - 4 ) / log10( maxVal + 1 );
- for( int i=0; i 0 )
- {
- DrawLine( draw, dpos + ImVec2( 2+i, Height-3 ), dpos + ImVec2( 2+i, Height-3 - log10( val + 1 ) * hAdj ), 0xFF22DDDD );
- }
- }
- }
- else
- {
- const auto hAdj = double( Height - 4 ) / maxVal;
- for( int i=0; i 0 )
- {
- DrawLine( draw, dpos + ImVec2( 2+i, Height-3 ), dpos + ImVec2( 2+i, Height-3 - val * hAdj ), 0xFF22DDDD );
- }
- }
- }
-
- const auto xoff = 2;
- const auto yoff = Height + 1;
-
- DrawHistogramMinMaxLabel( draw, tmin, tmax, wpos + ImVec2( 0, yoff ), w, ty );
-
- const auto ty05 = round( ty * 0.5f );
- const auto ty025 = round( ty * 0.25f );
- if( m_frameSortData.logTime )
- {
- const auto ltmin = log10( tmin );
- const auto ltmax = log10( tmax );
- const auto start = int( floor( ltmin ) );
- const auto end = int( ceil( ltmax ) );
-
- const auto range = ltmax - ltmin;
- const auto step = w / range;
- auto offset = start - ltmin;
- int tw = 0;
- int tx = 0;
-
- auto tt = int64_t( pow( 10, start ) );
-
- static const double logticks[] = { log10( 2 ), log10( 3 ), log10( 4 ), log10( 5 ), log10( 6 ), log10( 7 ), log10( 8 ), log10( 9 ) };
-
- for( int i=start; i<=end; i++ )
- {
- const auto x = ( i - start + offset ) * step;
-
- if( x >= 0 )
- {
- DrawLine( draw, dpos + ImVec2( x, yoff ), dpos + ImVec2( x, yoff + ty05 ), 0x66FFFFFF );
- if( tw == 0 || x > tx + tw + ty * 1.1 )
- {
- tx = x;
- auto txt = TimeToString( tt );
- draw->AddText( wpos + ImVec2( x, yoff + ty05 ), 0x66FFFFFF, txt );
- tw = ImGui::CalcTextSize( txt ).x;
- }
- }
-
- for( int j=0; j<8; j++ )
- {
- const auto xoff = x + logticks[j] * step;
- if( xoff >= 0 )
- {
- DrawLine( draw, dpos + ImVec2( xoff, yoff ), dpos + ImVec2( xoff, yoff + ty025 ), 0x66FFFFFF );
- }
- }
-
- tt *= 10;
- }
- }
- else
- {
- const auto pxns = numBins / double( tmax - tmin );
- const auto nspx = 1.0 / pxns;
- const auto scale = std::max( 0.0f, round( log10( nspx ) + 2 ) );
- const auto step = pow( 10, scale );
-
- const auto dx = step * pxns;
- double x = 0;
- int tw = 0;
- int tx = 0;
-
- const auto sstep = step / 10.0;
- const auto sdx = dx / 10.0;
-
- static const double linelen[] = { 0.5, 0.25, 0.25, 0.25, 0.25, 0.375, 0.25, 0.25, 0.25, 0.25 };
-
- int64_t tt = int64_t( ceil( tmin / sstep ) * sstep );
- const auto diff = tmin / sstep - int64_t( tmin / sstep );
- const auto xo = ( diff == 0 ? 0 : ( ( 1 - diff ) * sstep * pxns ) ) + xoff;
- int iter = int( ceil( ( tmin - int64_t( tmin / step ) * step ) / sstep ) );
-
- while( x < numBins )
- {
- DrawLine( draw, dpos + ImVec2( xo + x, yoff ), dpos + ImVec2( xo + x, yoff + round( ty * linelen[iter] ) ), 0x66FFFFFF );
- if( iter == 0 && ( tw == 0 || x > tx + tw + ty * 1.1 ) )
- {
- tx = x;
- auto txt = TimeToString( tt );
- draw->AddText( wpos + ImVec2( xo + x, yoff + ty05 ), 0x66FFFFFF, txt );
- tw = ImGui::CalcTextSize( txt ).x;
- }
-
- iter = ( iter + 1 ) % 10;
- x += sdx;
- tt += sstep;
- }
- }
-
- if( m_frameSortData.drawAvgMed )
- {
- float ta, tm;
- if( m_frameSortData.logTime )
- {
- const auto ltmin = log10( tmin );
- const auto ltmax = log10( tmax );
-
- ta = ( log10( m_frameSortData.average ) - ltmin ) / float( ltmax - ltmin ) * numBins;
- tm = ( log10( m_frameSortData.median ) - ltmin ) / float( ltmax - ltmin ) * numBins;
- }
- else
- {
- ta = ( m_frameSortData.average - tmin ) / float( tmax - tmin ) * numBins;
- tm = ( m_frameSortData.median - tmin ) / float( tmax - tmin ) * numBins;
- }
- ta = round( ta );
- tm = round( tm );
-
- if( ta == tm )
- {
- DrawLine( draw, ImVec2( dpos.x + ta, dpos.y ), ImVec2( dpos.x + ta, dpos.y+Height-2 ), 0xFFFF88FF );
- }
- else
- {
- DrawLine( draw, ImVec2( dpos.x + ta, dpos.y ), ImVec2( dpos.x + ta, dpos.y+Height-2 ), 0xFF4444FF );
- DrawLine( draw, ImVec2( dpos.x + tm, dpos.y ), ImVec2( dpos.x + tm, dpos.y+Height-2 ), 0xFFFF8844 );
- }
- }
-
- if( hover && ImGui::IsMouseHoveringRect( wpos + ImVec2( 2, 2 ), wpos + ImVec2( w-2, Height + round( ty * 1.5 ) ) ) )
- {
- const auto ltmin = log10( tmin );
- const auto ltmax = log10( tmax );
-
- auto& io = ImGui::GetIO();
- DrawLine( draw, ImVec2( io.MousePos.x + 0.5f, dpos.y ), ImVec2( io.MousePos.x + 0.5f, dpos.y+Height-2 ), 0x33FFFFFF );
-
- const auto bin = int64_t( io.MousePos.x - wpos.x - 2 );
- int64_t t0, t1;
- if( m_frameSortData.logTime )
- {
- t0 = int64_t( pow( 10, ltmin + double( bin ) / numBins * ( ltmax - ltmin ) ) );
-
- // Hackfix for inability to select data in last bin.
- // A proper solution would be nice.
- if( bin+1 == numBins )
- {
- t1 = tmax;
- }
- else
- {
- t1 = int64_t( pow( 10, ltmin + double( bin+1 ) / numBins * ( ltmax - ltmin ) ) );
- }
- }
- else
- {
- t0 = int64_t( tmin + double( bin ) / numBins * ( tmax - tmin ) );
- t1 = int64_t( tmin + double( bin+1 ) / numBins * ( tmax - tmin ) );
- }
-
- ImGui::BeginTooltip();
- TextDisabledUnformatted( "Time range:" );
- ImGui::SameLine();
- ImGui::Text( "%s - %s", TimeToString( t0 ), TimeToString( t1 ) );
- ImGui::SameLine();
- ImGui::TextDisabled( "(%s FPS - %s FPS)", RealToString( round( 1000000000.0 / t0 ) ), RealToString( round( 1000000000.0 / t1 ) ) );
- TextFocused( "Count:", RealToString( bins[bin] ) );
- ImGui::EndTooltip();
- }
-
- if( m_frameHover != -1 )
- {
- const auto frameTime = m_worker.GetFrameTime( *m_frames, m_frameHover );
- float framePos;
- if( m_frameSortData.logTime )
- {
- const auto ltmin = log10( tmin );
- const auto ltmax = log10( tmax );
- framePos = round( ( log10( frameTime ) - ltmin ) / float( ltmax - ltmin ) * numBins );
- }
- else
- {
- framePos = round( ( frameTime - tmin ) / float( tmax - tmin ) * numBins );
- }
- const auto c = uint32_t( ( sin( s_time * 10 ) * 0.25 + 0.75 ) * 255 );
- const auto color = 0xFF000000 | ( c << 16 ) | ( c << 8 ) | c;
- DrawLine( draw, ImVec2( dpos.x + framePos, dpos.y ), ImVec2( dpos.x + framePos, dpos.y+Height-2 ), color );
- }
- }
- }
- }
-
- ImGui::TreePop();
- }
- }
- ImGui::TreePop();
- }
-
- auto& topology = m_worker.GetCpuTopology();
- if( !topology.empty() )
- {
- if( ImGui::TreeNode( "CPU topology" ) )
- {
- char buf[128];
-
- const auto ty = ImGui::GetFontSize();
- ImGui::PushFont( m_smallFont );
- const auto sty = ImGui::GetFontSize();
- ImGui::PopFont();
- const float margin = round( ty * 0.5 );
- const float small = round( sty * 0.5 );
-
- std::vector maxthreads( topology.size() );
-
- float ptsz = 0;
- float ctsz = 0;
- float ttsz = 0;
- for( auto& package : topology )
- {
- sprintf( buf, ICON_FA_BOX " Package %" PRIu32, package.first );
- ImGui::PushFont( m_smallFont );
- const auto psz = ImGui::CalcTextSize( buf ).x;
- if( psz > ptsz ) ptsz = psz;
- ImGui::PopFont();
-
- size_t mt = 0;
- for( auto& core : package.second )
- {
- sprintf( buf, ICON_FA_MICROCHIP "%" PRIu32, core.first );
- const auto csz = ImGui::CalcTextSize( buf ).x;
- if( csz > ctsz ) ctsz = csz;
-
- const auto tnum = core.second.size();
- if( tnum > mt ) mt = tnum;
-
- for( auto& thread : core.second )
- {
- sprintf( buf, ICON_FA_RANDOM "%" PRIu32, thread );
- const auto tsz = ImGui::CalcTextSize( buf ).x;
- if( tsz > ttsz ) ttsz = tsz;
- }
- }
- maxthreads[package.first] = (int)mt;
- }
-
- const auto remainingWidth = ImGui::GetContentRegionAvail().x;
- auto dpos = ImGui::GetCursorScreenPos() + ImVec2( margin, 0 );
- const auto draw = ImGui::GetWindowDrawList();
-
- float width = 0;
- float origy = dpos.y;
-
- std::vector tsort;
- tsort.reserve( topology.size() );
- for( auto it = topology.begin(); it != topology.end(); ++it ) tsort.emplace_back( it );
- std::sort( tsort.begin(), tsort.end(), [] ( const auto& l, const auto& r ) { return l->first < r->first; } );
- for( auto& package : tsort )
- {
- if( package->first != 0 ) dpos.y += ty;
- sprintf( buf, ICON_FA_BOX " Package %" PRIu32, package->first );
- draw->AddText( dpos, 0xFFFFFFFF, buf );
- dpos.y += ty;
-
- const auto inCoreWidth = ( ttsz + margin ) * maxthreads[package->first];
- const auto coreWidth = inCoreWidth + 2 * margin;
- const auto inCoreHeight = margin + 2 * small + ty;
- const auto coreHeight = inCoreHeight + ty;
- const auto cpl = std::max( 1, (int)floor( ( remainingWidth - 2 * margin ) / coreWidth ) );
- const auto cl = ( package->second.size() + cpl - 1 ) / cpl;
- const auto pw = cpl * coreWidth + 2 * margin;
- const auto ph = margin + cl * coreHeight;
- if( pw > width ) width = pw;
-
- draw->AddRect( dpos, dpos + ImVec2( margin + coreWidth * std::min( cpl, package->second.size() ), ph ), 0xFFFFFFFF );
-
- std::vectorsecond.begin())> csort;
- csort.reserve( package->second.size() );
- for( auto it = package->second.begin(); it != package->second.end(); ++it ) csort.emplace_back( it );
- std::sort( csort.begin(), csort.end(), [] ( const auto& l, const auto& r ) { return l->first < r->first; } );
- auto cpos = dpos + ImVec2( margin, margin );
- int ll = cpl;
- for( auto& core : csort )
- {
- sprintf( buf, ICON_FA_MICROCHIP "%" PRIu32, core->first );
- draw->AddText( cpos, 0xFFFFFFFF, buf );
- draw->AddRect( cpos + ImVec2( 0, ty ), cpos + ImVec2( inCoreWidth + small, inCoreHeight + small ), 0xFFFFFFFF );
-
- for( int i=0; isecond.size(); i++ )
- {
- sprintf( buf, ICON_FA_RANDOM "%" PRIu32, core->second[i] );
- draw->AddText( cpos + ImVec2( margin + i * ( margin + ttsz ), ty + small ), 0xFFFFFFFF, buf );
- }
-
- if( --ll == 0 )
- {
- ll = cpl;
- cpos.x -= (cpl-1) * coreWidth;
- cpos.y += coreHeight;
- }
- else
- {
- cpos.x += coreWidth;
- }
- }
- dpos.y += ph;
- }
- ImGui::ItemSize( ImVec2( width, dpos.y - origy ) );
- ImGui::TreePop();
- }
- }
-
- if( ImGui::TreeNode( "Source location substitutions" ) )
- {
- static char test[1024] = {};
- ImGui::SetNextItemWidth( -1 );
- ImGui::InputTextWithHint( "##srcSubstTest", "Enter example source location to test substitutions", test, 1024 );
- if( m_sourceRegexValid )
- {
- TextFocused( "Result:", SourceSubstitution( test ) );
- }
- else
- {
- ImGui::TextColored( ImVec4( 255, 0, 0, 255 ), "Error in regular expression" );
- }
- if( ImGui::SmallButton( "Add new substitution" ) ) m_sourceSubstitutions.emplace_back( SourceRegex {} );
- int idx = 0, remove = -1;
- bool changed = false;
- ImGui::Columns( 2, nullptr, false );
- for( auto& v : m_sourceSubstitutions )
- {
- ImGui::PushID( idx );
- if( ImGui::Button( ICON_FA_TRASH_ALT ) ) remove = idx;
- ImGui::SameLine();
- char tmp[1024];
- strncpy( tmp, v.pattern.c_str(), 1024 );
- ImGui::SetNextItemWidth( -1 );
- if( ImGui::InputTextWithHint( "##pattern", "Regex pattern", tmp, 1024 ) )
- {
- v.pattern.assign( tmp );
- changed = true;
- }
- ImGui::NextColumn();
- strncpy( tmp, v.target.c_str(), 1024 );
- ImGui::SetNextItemWidth( -1 );
- if( ImGui::InputTextWithHint( "##replacement", "Regex replacement", tmp, 1024 ) ) v.target.assign( tmp );
- ImGui::PopID();
- ImGui::NextColumn();
- idx++;
- }
- ImGui::EndColumns();
- if( remove != -1 )
- {
- m_sourceSubstitutions.erase( m_sourceSubstitutions.begin() + remove );
- changed = true;
- }
-
- if( changed )
- {
- bool regexValid = true;
- for( auto& v : m_sourceSubstitutions )
- {
- try
- {
- v.regex.assign( v.pattern );
- }
- catch( std::regex_error& err )
- {
- regexValid = false;
- break;
- }
- }
- m_sourceRegexValid = regexValid;
- }
-
- ImGui::TreePop();
- }
-
- ImGui::Separator();
- TextFocused( "PID:", RealToString( m_worker.GetPid() ) );
- TextFocused( "Host info:", m_worker.GetHostInfo().c_str() );
-
- const auto cpuId = m_worker.GetCpuId();
- if( cpuId != 0 )
- {
- const auto stepping = cpuId & 0xF;
- const auto baseModel = ( cpuId >> 4 ) & 0xF;
- const auto baseFamily = ( cpuId >> 8 ) & 0xF;
- const auto extModel = ( cpuId >> 12 ) & 0xF;
- const auto extFamily = ( cpuId >> 16 );
-
- const uint32_t model = ( baseFamily == 6 || baseFamily == 15 ) ? ( ( extModel << 4 ) | baseModel ) : baseModel;
- const uint32_t family = baseFamily == 15 ? baseFamily + extFamily : baseFamily;
-
- TextFocused( "CPU:", m_worker.GetCpuManufacturer() );
- ImGui::SameLine();
- TextFocused( "Family", RealToString( family ) );
- ImGui::SameLine();
- TextFocused( "Model", RealToString( model ) );
- ImGui::SameLine();
- TextFocused( "Stepping", RealToString( stepping ) );
- }
-
- auto& appInfo = m_worker.GetAppInfo();
- if( !appInfo.empty() )
- {
- ImGui::Separator();
- TextDisabledUnformatted( "Application info:" );
- for( auto& v : appInfo )
- {
- ImGui::TextUnformatted( m_worker.GetString( v ) );
- }
- }
-
- auto& crash = m_worker.GetCrashEvent();
- if( crash.thread != 0 )
- {
- ImGui::Separator();
- TextColoredUnformatted( ImVec4( 1.f, 0.2f, 0.2f, 1.f ), ICON_FA_SKULL " Application has crashed. " ICON_FA_SKULL );
- TextFocused( "Time of crash:", TimeToString( crash.time ) );
- SmallColorBox( GetThreadColor( crash.thread, 0 ) );
- ImGui::SameLine();
- TextFocused( "Thread:", m_worker.GetThreadName( crash.thread ) );
- ImGui::SameLine();
- ImGui::TextDisabled( "(%s)", RealToString( crash.thread ) );
- if( m_worker.IsThreadFiber( crash.thread ) )
- {
- ImGui::SameLine();
- TextColoredUnformatted( ImVec4( 0.2f, 0.6f, 0.2f, 1.f ), "Fiber" );
- }
- TextDisabledUnformatted( "Reason:" );
- ImGui::SameLine();
- ImGui::TextWrapped( "%s", m_worker.GetString( crash.message ) );
- if( ImGui::Button( ICON_FA_MICROSCOPE " Focus" ) )
- {
- CenterAtTime( crash.time );
- }
- if( crash.callstack != 0 )
- {
- ImGui::SameLine();
- bool hilite = m_callstackInfoWindow == crash.callstack;
- if( hilite )
- {
- SetButtonHighlightColor();
- }
- if( ImGui::Button( ICON_FA_ALIGN_JUSTIFY " Call stack" ) )
- {
- m_callstackInfoWindow = crash.callstack;
- }
- if( hilite )
- {
- ImGui::PopStyleColor( 3 );
- }
- if( ImGui::IsItemHovered() )
- {
- CallstackTooltip( crash.callstack );
- }
- }
- }
-
- ImGui::EndChild();
- ImGui::End();
-}
-
void View::DrawTextEditor()
{
const auto scale = GetScale();
diff --git a/server/TracyView_TraceInfo.cpp b/server/TracyView_TraceInfo.cpp
new file mode 100644
index 00000000..569ae7fb
--- /dev/null
+++ b/server/TracyView_TraceInfo.cpp
@@ -0,0 +1,953 @@
+#include
+
+#include "TracyPrint.hpp"
+#include "TracyView.hpp"
+
+namespace tracy
+{
+
+extern double s_time;
+
+void View::DrawInfo()
+{
+ const auto scale = GetScale();
+ ImGui::SetNextWindowSize( ImVec2( 400 * scale, 650 * scale ), ImGuiCond_FirstUseEver );
+ ImGui::Begin( "Trace information", &m_showInfo, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse );
+ if( ImGui::GetCurrentWindowRead()->SkipItems ) { ImGui::End(); return; }
+ ImGui::PushFont( m_bigFont );
+ TextFocused( "Program:", m_worker.GetCaptureProgram().c_str() );
+ ImGui::PopFont();
+ const auto exectime = m_worker.GetExecutableTime();
+ if( exectime != 0 )
+ {
+ char etmp[64];
+ time_t et = exectime;
+ auto elt = localtime( &et );
+ strftime( etmp, 64, "%F %T", elt );
+ TextFocused( "Build time:", etmp );
+ }
+ {
+ char dtmp[64];
+ time_t date = m_worker.GetCaptureTime();
+ auto lt = localtime( &date );
+ strftime( dtmp, 64, "%F %T", lt );
+ TextFocused( "Capture time:", dtmp );
+ }
+ if( !m_filename.empty() )
+ {
+ TextFocused( "File:", m_filename.c_str() );
+ if( m_userData.Valid() )
+ {
+ const auto save = m_userData.GetConfigLocation();
+ if( save )
+ {
+ ImGui::SameLine();
+ if( ImGui::SmallButton( ICON_FA_FOLDER ) )
+ {
+ ImGui::SetClipboardText( save );
+ }
+ TooltipIfHovered( "Copy user settings location to clipboard." );
+ }
+ }
+ }
+ {
+ const auto& desc = m_userData.GetDescription();
+ const auto descsz = std::min( 255, desc.size() );
+ char buf[256];
+ buf[descsz] = '\0';
+ memcpy( buf, desc.c_str(), descsz );
+ ImGui::SetNextItemWidth( -1 );
+ if( ImGui::InputTextWithHint( "##traceDesc", "Enter description of the trace", buf, 256 ) )
+ {
+ m_userData.SetDescription( buf );
+ }
+ }
+
+ ImGui::Separator();
+ ImGui::BeginChild( "##info" );
+
+ const auto ficnt = m_worker.GetFrameImageCount();
+ if( ImGui::TreeNode( "Trace statistics" ) )
+ {
+ ImGui::TextDisabled( "Trace version:" );
+ ImGui::SameLine();
+ const auto version = m_worker.GetTraceVersion();
+ ImGui::Text( "%i.%i.%i", version >> 16, ( version >> 8 ) & 0xFF, version & 0xFF );
+ TextFocused( "Queue delay:", TimeToString( m_worker.GetDelay() ) );
+ TextFocused( "Timer resolution:", TimeToString( m_worker.GetResolution() ) );
+ TextFocused( "CPU zones:", RealToString( m_worker.GetZoneCount() ) );
+ ImGui::SameLine();
+ ImGui::Spacing();
+ ImGui::SameLine();
+ TextFocused( "Extra data:", RealToString( m_worker.GetZoneExtraCount() ) );
+ TooltipIfHovered( "Count of zones containing any of the following: call stack trace, custom name, user text" );
+ TextFocused( "GPU zones:", RealToString( m_worker.GetGpuZoneCount() ) );
+ TextFocused( "Lock events:", RealToString( m_worker.GetLockCount() ) );
+ TextFocused( "Plot data points:", RealToString( m_worker.GetPlotCount() ) );
+ TooltipIfHovered( "User plots" );
+ ImGui::SameLine();
+ TextFocused( "+", RealToString( m_worker.GetTracyPlotCount() ) );
+ TooltipIfHovered( "Automated Tracy plots" );
+ auto& memNameMap = m_worker.GetMemNameMap();
+ TextFocused( "Memory pools:", RealToString( memNameMap.size() ) );
+ uint64_t memTotalCnt = 0;
+ for( auto v : memNameMap ) memTotalCnt += v.second->data.size();
+ TextFocused( "Memory allocations:", RealToString( memTotalCnt ) );
+ TextFocused( "Source locations:", RealToString( m_worker.GetSrcLocCount() ) );
+ TextFocused( "Strings:", RealToString( m_worker.GetStringsCount() ) );
+ TextFocused( "Symbols:", RealToString( m_worker.GetSymbolsCount() ) );
+ TextFocused( "Symbol code fragments:", RealToString( m_worker.GetSymbolCodeCount() ) );
+ TooltipIfHovered( MemSizeToString( m_worker.GetSymbolCodeSize() ) );
+ TextFocused( "Code locations:", RealToString( m_worker.GetCodeLocationsSize() ) );
+ TextFocused( "Call stacks:", RealToString( m_worker.GetCallstackPayloadCount() ) );
+ if( m_worker.AreCallstackSamplesReady() )
+ {
+ ImGui::SameLine();
+ TextFocused( "+", RealToString( m_worker.GetCallstackParentPayloadCount() ) );
+ TooltipIfHovered( "Parent call stacks for stack samples" );
+ }
+ TextFocused( "Call stack frames:", RealToString( m_worker.GetCallstackFrameCount() ) );
+ if( m_worker.AreCallstackSamplesReady() )
+ {
+ ImGui::SameLine();
+ TextFocused( "+", RealToString( m_worker.GetCallstackParentFrameCount() ) );
+ TooltipIfHovered( "Parent call stack frames for stack samples" );
+ }
+ TextFocused( "Call stack samples:", RealToString( m_worker.GetCallstackSampleCount() ) );
+ TextFocused( "Ghost zones:", RealToString( m_worker.GetGhostZonesCount() ) );
+#ifndef TRACY_NO_STATISTICS
+ TextFocused( "Child sample symbols:", RealToString( m_worker.GetChildSamplesCountSyms() ) );
+ if( ImGui::IsItemHovered() )
+ {
+ ImGui::BeginTooltip();
+ TextFocused( "Child samples:", RealToString( m_worker.GetChildSamplesCountFull() ) );
+ ImGui::EndTooltip();
+ }
+ TextFocused( "Context switch samples:", RealToString( m_worker.GetContextSwitchSampleCount() ) );
+#endif
+ TextFocused( "Hardware samples:", RealToString( m_worker.GetHwSampleCount() ) );
+ if( ImGui::IsItemHovered() )
+ {
+ ImGui::BeginTooltip();
+ TextFocused( "Unique addresses:", RealToString( m_worker.GetHwSampleCountAddress() ) );
+ ImGui::EndTooltip();
+ }
+ TextFocused( "Frame images:", RealToString( ficnt ) );
+ if( ficnt != 0 && ImGui::IsItemHovered() )
+ {
+ const auto bytes = m_worker.GetTextureCompressionBytes();
+ ImGui::BeginTooltip();
+ TextFocused( "Input data:", MemSizeToString( bytes.first ) );
+ TextFocused( "Compressed:", MemSizeToString( bytes.second ) );
+ char buf[64];
+ auto ptr = PrintFloat( buf, buf+62, 100. * bytes.second / bytes.first, 2 );
+ memcpy( ptr, "%", 2 );
+ TextFocused( "Ratio:", buf );
+ ImGui::EndTooltip();
+ }
+ TextFocused( "Context switch regions:", RealToString( m_worker.GetContextSwitchCount() ) );
+ TooltipIfHovered( "Detailed context switch data regarding application threads" );
+ ImGui::SameLine();
+ TextFocused( "+", RealToString( m_worker.GetContextSwitchPerCpuCount() ) );
+ TooltipIfHovered( "Coarse CPU core context switch data" );
+ if( m_worker.GetSourceFileCacheCount() == 0 )
+ {
+ TextFocused( "Source file cache:", "0" );
+ }
+ else
+ {
+ ImGui::PushStyleColor( ImGuiCol_Text, GImGui->Style.Colors[ImGuiCol_TextDisabled] );
+ const bool expand = ImGui::TreeNode( "Source file cache:" );
+ ImGui::PopStyleColor();
+ ImGui::SameLine();
+ ImGui::TextUnformatted( RealToString( m_worker.GetSourceFileCacheCount() ) );
+ TooltipIfHovered( MemSizeToString( m_worker.GetSourceFileCacheSize() ) );
+ if( expand )
+ {
+ auto& cache = m_worker.GetSourceFileCache();
+ std::vector vec;
+ vec.reserve( cache.size() );
+ for( auto it = cache.begin(); it != cache.end(); ++it ) vec.emplace_back( it );
+ pdqsort_branchless( vec.begin(), vec.end(), []( const auto& lhs, const auto& rhs ) { return strcmp( lhs->first, rhs->first ) < 0; } );
+ for( auto& v : vec )
+ {
+ ImGui::BulletText( "%s", v->first );
+ if( ImGui::IsItemClicked() ) ViewSource( v->first, 0 );
+ ImGui::SameLine();
+ ImGui::TextDisabled( "(%s)", MemSizeToString( v->second.len ) );
+ }
+ ImGui::TreePop();
+ }
+ }
+ ImGui::TreePop();
+ }
+
+ if( ImGui::TreeNode( "Frame statistics" ) )
+ {
+ auto fsz = m_worker.GetFullFrameCount( *m_frames );
+ if( fsz != 0 )
+ {
+ TextFocused( "Frame set:", m_frames->name == 0 ? "Frames" : m_worker.GetString( m_frames->name ) );
+ ImGui::SameLine();
+ ImGui::TextDisabled( "(%s)", m_frames->continuous ? "continuous" : "discontinuous" );
+ ImGui::SameLine();
+ ImGui::PushStyleVar( ImGuiStyleVar_FramePadding, ImVec2( 0, 0 ) );
+ if( ImGui::BeginCombo( "##frameCombo", nullptr, ImGuiComboFlags_NoPreview ) )
+ {
+ auto& frames = m_worker.GetFrames();
+ for( auto& fd : frames )
+ {
+ bool isSelected = m_frames == fd;
+ if( ImGui::Selectable( fd->name == 0 ? "Frames" : m_worker.GetString( fd->name ), isSelected ) )
+ {
+ m_frames = fd;
+ fsz = m_worker.GetFullFrameCount( *m_frames );
+ }
+ if( isSelected )
+ {
+ ImGui::SetItemDefaultFocus();
+ }
+ ImGui::SameLine();
+ ImGui::TextDisabled( "(%s)", RealToString( fd->frames.size() ) );
+ }
+ ImGui::EndCombo();
+ }
+ ImGui::PopStyleVar();
+ ImGui::SameLine();
+ SmallCheckbox( "Limit to view", &m_frameSortData.limitToView );
+ if( m_frameSortData.limitToView )
+ {
+ ImGui::SameLine();
+ TextColoredUnformatted( 0xFF00FFFF, ICON_FA_EXCLAMATION_TRIANGLE );
+ }
+
+ const auto frameRange = m_worker.GetFrameRange( *m_frames, m_vd.zvStart, m_vd.zvEnd );
+ if( m_frameSortData.frameSet != m_frames || ( m_frameSortData.limitToView && m_frameSortData.limitRange != frameRange ) || ( !m_frameSortData.limitToView && m_frameSortData.limitRange.first != -1 ) )
+ {
+ m_frameSortData.frameSet = m_frames;
+ m_frameSortData.frameNum = 0;
+ m_frameSortData.data.clear();
+ m_frameSortData.total = 0;
+ }
+ bool recalc = false;
+ int64_t total = 0;
+ if( !m_frameSortData.limitToView )
+ {
+ if( m_frameSortData.frameNum != fsz || m_frameSortData.limitRange.first != -1 )
+ {
+ auto& vec = m_frameSortData.data;
+ vec.reserve( fsz );
+ const auto midSz = vec.size();
+ total = m_frameSortData.total;
+ for( size_t i=m_frameSortData.frameNum; i 0 )
+ {
+ vec.emplace_back( t );
+ total += t;
+ }
+ }
+ auto mid = vec.begin() + midSz;
+ pdqsort_branchless( mid, vec.end() );
+ std::inplace_merge( vec.begin(), mid, vec.end() );
+ recalc = true;
+ m_frameSortData.limitRange.first = -1;
+ }
+ }
+ else
+ {
+ if( m_frameSortData.limitRange != frameRange )
+ {
+ auto& vec = m_frameSortData.data;
+ assert( vec.empty() );
+ vec.reserve( frameRange.second - frameRange.first );
+ for( int i=frameRange.first; i 0 )
+ {
+ vec.emplace_back( t );
+ total += t;
+ }
+ }
+ pdqsort_branchless( vec.begin(), vec.end() );
+ recalc = true;
+ m_frameSortData.limitRange = frameRange;
+ }
+ }
+ if( recalc )
+ {
+ auto& vec = m_frameSortData.data;
+ const auto vsz = vec.size();
+ m_frameSortData.average = float( total ) / vsz;
+ m_frameSortData.median = vec[vsz/2];
+ m_frameSortData.total = total;
+ m_frameSortData.frameNum = fsz;
+ }
+
+ const auto profileSpan = m_worker.GetLastTime();
+ TextFocused( "Count:", RealToString( fsz ) );
+ TextFocused( "Total time:", TimeToString( m_frameSortData.total ) );
+ ImGui::SameLine();
+ ImGui::TextDisabled( "(%.2f%% of profile time span)", m_frameSortData.total / float( profileSpan ) * 100.f );
+ TextFocused( "Mean frame time:", TimeToString( m_frameSortData.average ) );
+ ImGui::SameLine();
+ ImGui::TextDisabled( "(%s FPS)", RealToString( round( 1000000000.0 / m_frameSortData.average ) ) );
+ if( ImGui::IsItemHovered() )
+ {
+ ImGui::BeginTooltip();
+ ImGui::Text( "%s FPS", RealToString( 1000000000.0 / m_frameSortData.average ) );
+ ImGui::EndTooltip();
+ }
+ TextFocused( "Median frame time:", TimeToString( m_frameSortData.median ) );
+ ImGui::SameLine();
+ ImGui::TextDisabled( "(%s FPS)", RealToString( round( 1000000000.0 / m_frameSortData.median ) ) );
+ if( ImGui::IsItemHovered() )
+ {
+ ImGui::BeginTooltip();
+ ImGui::Text( "%s FPS", RealToString( 1000000000.0 / m_frameSortData.median ) );
+ ImGui::EndTooltip();
+ }
+
+ if( ImGui::TreeNodeEx( "Histogram", ImGuiTreeNodeFlags_DefaultOpen ) )
+ {
+ const auto ty = ImGui::GetTextLineHeight();
+
+ auto& frames = m_frameSortData.data;
+ auto tmin = frames.front();
+ auto tmax = frames.back();
+
+ if( tmin != std::numeric_limits::max() )
+ {
+ TextDisabledUnformatted( "Minimum values in bin:" );
+ ImGui::SameLine();
+ ImGui::SetNextItemWidth( ImGui::CalcTextSize( "123456890123456" ).x );
+ ImGui::PushStyleVar( ImGuiStyleVar_FramePadding, ImVec2( 1, 1 ) );
+ ImGui::InputInt( "##minBinVal", &m_frameSortData.minBinVal );
+ if( m_frameSortData.minBinVal < 1 ) m_frameSortData.minBinVal = 1;
+ ImGui::SameLine();
+ if( ImGui::Button( "Reset" ) ) m_frameSortData.minBinVal = 1;
+ ImGui::PopStyleVar();
+
+ SmallCheckbox( "Log values", &m_frameSortData.logVal );
+ ImGui::SameLine();
+ SmallCheckbox( "Log time", &m_frameSortData.logTime );
+
+ TextDisabledUnformatted( "FPS range:" );
+ ImGui::SameLine();
+ ImGui::Text( "%s FPS - %s FPS", RealToString( round( 1000000000.0 / tmin ) ), RealToString( round( 1000000000.0 / tmax ) ) );
+
+ if( tmax - tmin > 0 )
+ {
+ const auto w = ImGui::GetContentRegionAvail().x;
+
+ const auto numBins = int64_t( w - 4 );
+ if( numBins > 1 )
+ {
+ if( numBins > m_frameSortData.numBins )
+ {
+ m_frameSortData.numBins = numBins;
+ m_frameSortData.bins = std::make_unique( numBins );
+ }
+
+ const auto& bins = m_frameSortData.bins;
+
+ memset( bins.get(), 0, sizeof( int64_t ) * numBins );
+
+ auto framesBegin = frames.begin();
+ auto framesEnd = frames.end();
+ while( framesBegin != framesEnd && *framesBegin == 0 ) ++framesBegin;
+
+ if( m_frameSortData.minBinVal > 1 )
+ {
+ if( m_frameSortData.logTime )
+ {
+ const auto tMinLog = log10( tmin );
+ const auto zmax = ( log10( tmax ) - tMinLog ) / numBins;
+ int64_t i;
+ for( i=0; i= m_frameSortData.minBinVal ) break;
+ framesBegin = nit;
+ }
+ for( int64_t j=numBins-1; j>i; j-- )
+ {
+ const auto nextBinVal = int64_t( pow( 10.0, tMinLog + ( j-1 ) * zmax ) );
+ auto nit = std::lower_bound( framesBegin, framesEnd, nextBinVal );
+ const auto distance = std::distance( nit, framesEnd );
+ if( distance >= m_frameSortData.minBinVal ) break;
+ framesEnd = nit;
+ }
+ }
+ else
+ {
+ const auto zmax = tmax - tmin;
+ int64_t i;
+ for( i=0; i= m_frameSortData.minBinVal ) break;
+ framesBegin = nit;
+ }
+ for( int64_t j=numBins-1; j>i; j-- )
+ {
+ const auto nextBinVal = tmin + ( j-1 ) * zmax / numBins;
+ auto nit = std::lower_bound( framesBegin, framesEnd, nextBinVal );
+ const auto distance = std::distance( nit, framesEnd );
+ if( distance >= m_frameSortData.minBinVal ) break;
+ framesEnd = nit;
+ }
+ }
+
+ tmin = *framesBegin;
+ tmax = *(framesEnd-1);
+ }
+
+ if( m_frameSortData.logTime )
+ {
+ const auto tMinLog = log10( tmin );
+ const auto zmax = ( log10( tmax ) - tMinLog ) / numBins;
+ auto fit = framesBegin;
+ for( int64_t i=0; iAddRectFilled( wpos, wpos + ImVec2( w, Height ), 0x22FFFFFF );
+ draw->AddRect( wpos, wpos + ImVec2( w, Height ), 0x88FFFFFF );
+
+ if( m_frameSortData.logVal )
+ {
+ const auto hAdj = double( Height - 4 ) / log10( maxVal + 1 );
+ for( int i=0; i 0 )
+ {
+ DrawLine( draw, dpos + ImVec2( 2+i, Height-3 ), dpos + ImVec2( 2+i, Height-3 - log10( val + 1 ) * hAdj ), 0xFF22DDDD );
+ }
+ }
+ }
+ else
+ {
+ const auto hAdj = double( Height - 4 ) / maxVal;
+ for( int i=0; i 0 )
+ {
+ DrawLine( draw, dpos + ImVec2( 2+i, Height-3 ), dpos + ImVec2( 2+i, Height-3 - val * hAdj ), 0xFF22DDDD );
+ }
+ }
+ }
+
+ const auto xoff = 2;
+ const auto yoff = Height + 1;
+
+ DrawHistogramMinMaxLabel( draw, tmin, tmax, wpos + ImVec2( 0, yoff ), w, ty );
+
+ const auto ty05 = round( ty * 0.5f );
+ const auto ty025 = round( ty * 0.25f );
+ if( m_frameSortData.logTime )
+ {
+ const auto ltmin = log10( tmin );
+ const auto ltmax = log10( tmax );
+ const auto start = int( floor( ltmin ) );
+ const auto end = int( ceil( ltmax ) );
+
+ const auto range = ltmax - ltmin;
+ const auto step = w / range;
+ auto offset = start - ltmin;
+ int tw = 0;
+ int tx = 0;
+
+ auto tt = int64_t( pow( 10, start ) );
+
+ static const double logticks[] = { log10( 2 ), log10( 3 ), log10( 4 ), log10( 5 ), log10( 6 ), log10( 7 ), log10( 8 ), log10( 9 ) };
+
+ for( int i=start; i<=end; i++ )
+ {
+ const auto x = ( i - start + offset ) * step;
+
+ if( x >= 0 )
+ {
+ DrawLine( draw, dpos + ImVec2( x, yoff ), dpos + ImVec2( x, yoff + ty05 ), 0x66FFFFFF );
+ if( tw == 0 || x > tx + tw + ty * 1.1 )
+ {
+ tx = x;
+ auto txt = TimeToString( tt );
+ draw->AddText( wpos + ImVec2( x, yoff + ty05 ), 0x66FFFFFF, txt );
+ tw = ImGui::CalcTextSize( txt ).x;
+ }
+ }
+
+ for( int j=0; j<8; j++ )
+ {
+ const auto xoff = x + logticks[j] * step;
+ if( xoff >= 0 )
+ {
+ DrawLine( draw, dpos + ImVec2( xoff, yoff ), dpos + ImVec2( xoff, yoff + ty025 ), 0x66FFFFFF );
+ }
+ }
+
+ tt *= 10;
+ }
+ }
+ else
+ {
+ const auto pxns = numBins / double( tmax - tmin );
+ const auto nspx = 1.0 / pxns;
+ const auto scale = std::max( 0.0f, round( log10( nspx ) + 2 ) );
+ const auto step = pow( 10, scale );
+
+ const auto dx = step * pxns;
+ double x = 0;
+ int tw = 0;
+ int tx = 0;
+
+ const auto sstep = step / 10.0;
+ const auto sdx = dx / 10.0;
+
+ static const double linelen[] = { 0.5, 0.25, 0.25, 0.25, 0.25, 0.375, 0.25, 0.25, 0.25, 0.25 };
+
+ int64_t tt = int64_t( ceil( tmin / sstep ) * sstep );
+ const auto diff = tmin / sstep - int64_t( tmin / sstep );
+ const auto xo = ( diff == 0 ? 0 : ( ( 1 - diff ) * sstep * pxns ) ) + xoff;
+ int iter = int( ceil( ( tmin - int64_t( tmin / step ) * step ) / sstep ) );
+
+ while( x < numBins )
+ {
+ DrawLine( draw, dpos + ImVec2( xo + x, yoff ), dpos + ImVec2( xo + x, yoff + round( ty * linelen[iter] ) ), 0x66FFFFFF );
+ if( iter == 0 && ( tw == 0 || x > tx + tw + ty * 1.1 ) )
+ {
+ tx = x;
+ auto txt = TimeToString( tt );
+ draw->AddText( wpos + ImVec2( xo + x, yoff + ty05 ), 0x66FFFFFF, txt );
+ tw = ImGui::CalcTextSize( txt ).x;
+ }
+
+ iter = ( iter + 1 ) % 10;
+ x += sdx;
+ tt += sstep;
+ }
+ }
+
+ if( m_frameSortData.drawAvgMed )
+ {
+ float ta, tm;
+ if( m_frameSortData.logTime )
+ {
+ const auto ltmin = log10( tmin );
+ const auto ltmax = log10( tmax );
+
+ ta = ( log10( m_frameSortData.average ) - ltmin ) / float( ltmax - ltmin ) * numBins;
+ tm = ( log10( m_frameSortData.median ) - ltmin ) / float( ltmax - ltmin ) * numBins;
+ }
+ else
+ {
+ ta = ( m_frameSortData.average - tmin ) / float( tmax - tmin ) * numBins;
+ tm = ( m_frameSortData.median - tmin ) / float( tmax - tmin ) * numBins;
+ }
+ ta = round( ta );
+ tm = round( tm );
+
+ if( ta == tm )
+ {
+ DrawLine( draw, ImVec2( dpos.x + ta, dpos.y ), ImVec2( dpos.x + ta, dpos.y+Height-2 ), 0xFFFF88FF );
+ }
+ else
+ {
+ DrawLine( draw, ImVec2( dpos.x + ta, dpos.y ), ImVec2( dpos.x + ta, dpos.y+Height-2 ), 0xFF4444FF );
+ DrawLine( draw, ImVec2( dpos.x + tm, dpos.y ), ImVec2( dpos.x + tm, dpos.y+Height-2 ), 0xFFFF8844 );
+ }
+ }
+
+ if( hover && ImGui::IsMouseHoveringRect( wpos + ImVec2( 2, 2 ), wpos + ImVec2( w-2, Height + round( ty * 1.5 ) ) ) )
+ {
+ const auto ltmin = log10( tmin );
+ const auto ltmax = log10( tmax );
+
+ auto& io = ImGui::GetIO();
+ DrawLine( draw, ImVec2( io.MousePos.x + 0.5f, dpos.y ), ImVec2( io.MousePos.x + 0.5f, dpos.y+Height-2 ), 0x33FFFFFF );
+
+ const auto bin = int64_t( io.MousePos.x - wpos.x - 2 );
+ int64_t t0, t1;
+ if( m_frameSortData.logTime )
+ {
+ t0 = int64_t( pow( 10, ltmin + double( bin ) / numBins * ( ltmax - ltmin ) ) );
+
+ // Hackfix for inability to select data in last bin.
+ // A proper solution would be nice.
+ if( bin+1 == numBins )
+ {
+ t1 = tmax;
+ }
+ else
+ {
+ t1 = int64_t( pow( 10, ltmin + double( bin+1 ) / numBins * ( ltmax - ltmin ) ) );
+ }
+ }
+ else
+ {
+ t0 = int64_t( tmin + double( bin ) / numBins * ( tmax - tmin ) );
+ t1 = int64_t( tmin + double( bin+1 ) / numBins * ( tmax - tmin ) );
+ }
+
+ ImGui::BeginTooltip();
+ TextDisabledUnformatted( "Time range:" );
+ ImGui::SameLine();
+ ImGui::Text( "%s - %s", TimeToString( t0 ), TimeToString( t1 ) );
+ ImGui::SameLine();
+ ImGui::TextDisabled( "(%s FPS - %s FPS)", RealToString( round( 1000000000.0 / t0 ) ), RealToString( round( 1000000000.0 / t1 ) ) );
+ TextFocused( "Count:", RealToString( bins[bin] ) );
+ ImGui::EndTooltip();
+ }
+
+ if( m_frameHover != -1 )
+ {
+ const auto frameTime = m_worker.GetFrameTime( *m_frames, m_frameHover );
+ float framePos;
+ if( m_frameSortData.logTime )
+ {
+ const auto ltmin = log10( tmin );
+ const auto ltmax = log10( tmax );
+ framePos = round( ( log10( frameTime ) - ltmin ) / float( ltmax - ltmin ) * numBins );
+ }
+ else
+ {
+ framePos = round( ( frameTime - tmin ) / float( tmax - tmin ) * numBins );
+ }
+ const auto c = uint32_t( ( sin( s_time * 10 ) * 0.25 + 0.75 ) * 255 );
+ const auto color = 0xFF000000 | ( c << 16 ) | ( c << 8 ) | c;
+ DrawLine( draw, ImVec2( dpos.x + framePos, dpos.y ), ImVec2( dpos.x + framePos, dpos.y+Height-2 ), color );
+ }
+ }
+ }
+ }
+
+ ImGui::TreePop();
+ }
+ }
+ ImGui::TreePop();
+ }
+
+ auto& topology = m_worker.GetCpuTopology();
+ if( !topology.empty() )
+ {
+ if( ImGui::TreeNode( "CPU topology" ) )
+ {
+ char buf[128];
+
+ const auto ty = ImGui::GetFontSize();
+ ImGui::PushFont( m_smallFont );
+ const auto sty = ImGui::GetFontSize();
+ ImGui::PopFont();
+ const float margin = round( ty * 0.5 );
+ const float small = round( sty * 0.5 );
+
+ std::vector maxthreads( topology.size() );
+
+ float ptsz = 0;
+ float ctsz = 0;
+ float ttsz = 0;
+ for( auto& package : topology )
+ {
+ sprintf( buf, ICON_FA_BOX " Package %" PRIu32, package.first );
+ ImGui::PushFont( m_smallFont );
+ const auto psz = ImGui::CalcTextSize( buf ).x;
+ if( psz > ptsz ) ptsz = psz;
+ ImGui::PopFont();
+
+ size_t mt = 0;
+ for( auto& core : package.second )
+ {
+ sprintf( buf, ICON_FA_MICROCHIP "%" PRIu32, core.first );
+ const auto csz = ImGui::CalcTextSize( buf ).x;
+ if( csz > ctsz ) ctsz = csz;
+
+ const auto tnum = core.second.size();
+ if( tnum > mt ) mt = tnum;
+
+ for( auto& thread : core.second )
+ {
+ sprintf( buf, ICON_FA_RANDOM "%" PRIu32, thread );
+ const auto tsz = ImGui::CalcTextSize( buf ).x;
+ if( tsz > ttsz ) ttsz = tsz;
+ }
+ }
+ maxthreads[package.first] = (int)mt;
+ }
+
+ const auto remainingWidth = ImGui::GetContentRegionAvail().x;
+ auto dpos = ImGui::GetCursorScreenPos() + ImVec2( margin, 0 );
+ const auto draw = ImGui::GetWindowDrawList();
+
+ float width = 0;
+ float origy = dpos.y;
+
+ std::vector tsort;
+ tsort.reserve( topology.size() );
+ for( auto it = topology.begin(); it != topology.end(); ++it ) tsort.emplace_back( it );
+ std::sort( tsort.begin(), tsort.end(), [] ( const auto& l, const auto& r ) { return l->first < r->first; } );
+ for( auto& package : tsort )
+ {
+ if( package->first != 0 ) dpos.y += ty;
+ sprintf( buf, ICON_FA_BOX " Package %" PRIu32, package->first );
+ draw->AddText( dpos, 0xFFFFFFFF, buf );
+ dpos.y += ty;
+
+ const auto inCoreWidth = ( ttsz + margin ) * maxthreads[package->first];
+ const auto coreWidth = inCoreWidth + 2 * margin;
+ const auto inCoreHeight = margin + 2 * small + ty;
+ const auto coreHeight = inCoreHeight + ty;
+ const auto cpl = std::max( 1, (int)floor( ( remainingWidth - 2 * margin ) / coreWidth ) );
+ const auto cl = ( package->second.size() + cpl - 1 ) / cpl;
+ const auto pw = cpl * coreWidth + 2 * margin;
+ const auto ph = margin + cl * coreHeight;
+ if( pw > width ) width = pw;
+
+ draw->AddRect( dpos, dpos + ImVec2( margin + coreWidth * std::min( cpl, package->second.size() ), ph ), 0xFFFFFFFF );
+
+ std::vectorsecond.begin())> csort;
+ csort.reserve( package->second.size() );
+ for( auto it = package->second.begin(); it != package->second.end(); ++it ) csort.emplace_back( it );
+ std::sort( csort.begin(), csort.end(), [] ( const auto& l, const auto& r ) { return l->first < r->first; } );
+ auto cpos = dpos + ImVec2( margin, margin );
+ int ll = cpl;
+ for( auto& core : csort )
+ {
+ sprintf( buf, ICON_FA_MICROCHIP "%" PRIu32, core->first );
+ draw->AddText( cpos, 0xFFFFFFFF, buf );
+ draw->AddRect( cpos + ImVec2( 0, ty ), cpos + ImVec2( inCoreWidth + small, inCoreHeight + small ), 0xFFFFFFFF );
+
+ for( int i=0; isecond.size(); i++ )
+ {
+ sprintf( buf, ICON_FA_RANDOM "%" PRIu32, core->second[i] );
+ draw->AddText( cpos + ImVec2( margin + i * ( margin + ttsz ), ty + small ), 0xFFFFFFFF, buf );
+ }
+
+ if( --ll == 0 )
+ {
+ ll = cpl;
+ cpos.x -= (cpl-1) * coreWidth;
+ cpos.y += coreHeight;
+ }
+ else
+ {
+ cpos.x += coreWidth;
+ }
+ }
+ dpos.y += ph;
+ }
+ ImGui::ItemSize( ImVec2( width, dpos.y - origy ) );
+ ImGui::TreePop();
+ }
+ }
+
+ if( ImGui::TreeNode( "Source location substitutions" ) )
+ {
+ static char test[1024] = {};
+ ImGui::SetNextItemWidth( -1 );
+ ImGui::InputTextWithHint( "##srcSubstTest", "Enter example source location to test substitutions", test, 1024 );
+ if( m_sourceRegexValid )
+ {
+ TextFocused( "Result:", SourceSubstitution( test ) );
+ }
+ else
+ {
+ ImGui::TextColored( ImVec4( 255, 0, 0, 255 ), "Error in regular expression" );
+ }
+ if( ImGui::SmallButton( "Add new substitution" ) ) m_sourceSubstitutions.emplace_back( SourceRegex {} );
+ int idx = 0, remove = -1;
+ bool changed = false;
+ ImGui::Columns( 2, nullptr, false );
+ for( auto& v : m_sourceSubstitutions )
+ {
+ ImGui::PushID( idx );
+ if( ImGui::Button( ICON_FA_TRASH_ALT ) ) remove = idx;
+ ImGui::SameLine();
+ char tmp[1024];
+ strncpy( tmp, v.pattern.c_str(), 1024 );
+ ImGui::SetNextItemWidth( -1 );
+ if( ImGui::InputTextWithHint( "##pattern", "Regex pattern", tmp, 1024 ) )
+ {
+ v.pattern.assign( tmp );
+ changed = true;
+ }
+ ImGui::NextColumn();
+ strncpy( tmp, v.target.c_str(), 1024 );
+ ImGui::SetNextItemWidth( -1 );
+ if( ImGui::InputTextWithHint( "##replacement", "Regex replacement", tmp, 1024 ) ) v.target.assign( tmp );
+ ImGui::PopID();
+ ImGui::NextColumn();
+ idx++;
+ }
+ ImGui::EndColumns();
+ if( remove != -1 )
+ {
+ m_sourceSubstitutions.erase( m_sourceSubstitutions.begin() + remove );
+ changed = true;
+ }
+
+ if( changed )
+ {
+ bool regexValid = true;
+ for( auto& v : m_sourceSubstitutions )
+ {
+ try
+ {
+ v.regex.assign( v.pattern );
+ }
+ catch( std::regex_error& err )
+ {
+ regexValid = false;
+ break;
+ }
+ }
+ m_sourceRegexValid = regexValid;
+ }
+
+ ImGui::TreePop();
+ }
+
+ ImGui::Separator();
+ TextFocused( "PID:", RealToString( m_worker.GetPid() ) );
+ TextFocused( "Host info:", m_worker.GetHostInfo().c_str() );
+
+ const auto cpuId = m_worker.GetCpuId();
+ if( cpuId != 0 )
+ {
+ const auto stepping = cpuId & 0xF;
+ const auto baseModel = ( cpuId >> 4 ) & 0xF;
+ const auto baseFamily = ( cpuId >> 8 ) & 0xF;
+ const auto extModel = ( cpuId >> 12 ) & 0xF;
+ const auto extFamily = ( cpuId >> 16 );
+
+ const uint32_t model = ( baseFamily == 6 || baseFamily == 15 ) ? ( ( extModel << 4 ) | baseModel ) : baseModel;
+ const uint32_t family = baseFamily == 15 ? baseFamily + extFamily : baseFamily;
+
+ TextFocused( "CPU:", m_worker.GetCpuManufacturer() );
+ ImGui::SameLine();
+ TextFocused( "Family", RealToString( family ) );
+ ImGui::SameLine();
+ TextFocused( "Model", RealToString( model ) );
+ ImGui::SameLine();
+ TextFocused( "Stepping", RealToString( stepping ) );
+ }
+
+ auto& appInfo = m_worker.GetAppInfo();
+ if( !appInfo.empty() )
+ {
+ ImGui::Separator();
+ TextDisabledUnformatted( "Application info:" );
+ for( auto& v : appInfo )
+ {
+ ImGui::TextUnformatted( m_worker.GetString( v ) );
+ }
+ }
+
+ auto& crash = m_worker.GetCrashEvent();
+ if( crash.thread != 0 )
+ {
+ ImGui::Separator();
+ TextColoredUnformatted( ImVec4( 1.f, 0.2f, 0.2f, 1.f ), ICON_FA_SKULL " Application has crashed. " ICON_FA_SKULL );
+ TextFocused( "Time of crash:", TimeToString( crash.time ) );
+ SmallColorBox( GetThreadColor( crash.thread, 0 ) );
+ ImGui::SameLine();
+ TextFocused( "Thread:", m_worker.GetThreadName( crash.thread ) );
+ ImGui::SameLine();
+ ImGui::TextDisabled( "(%s)", RealToString( crash.thread ) );
+ if( m_worker.IsThreadFiber( crash.thread ) )
+ {
+ ImGui::SameLine();
+ TextColoredUnformatted( ImVec4( 0.2f, 0.6f, 0.2f, 1.f ), "Fiber" );
+ }
+ TextDisabledUnformatted( "Reason:" );
+ ImGui::SameLine();
+ ImGui::TextWrapped( "%s", m_worker.GetString( crash.message ) );
+ if( ImGui::Button( ICON_FA_MICROSCOPE " Focus" ) )
+ {
+ CenterAtTime( crash.time );
+ }
+ if( crash.callstack != 0 )
+ {
+ ImGui::SameLine();
+ bool hilite = m_callstackInfoWindow == crash.callstack;
+ if( hilite )
+ {
+ SetButtonHighlightColor();
+ }
+ if( ImGui::Button( ICON_FA_ALIGN_JUSTIFY " Call stack" ) )
+ {
+ m_callstackInfoWindow = crash.callstack;
+ }
+ if( hilite )
+ {
+ ImGui::PopStyleColor( 3 );
+ }
+ if( ImGui::IsItemHovered() )
+ {
+ CallstackTooltip( crash.callstack );
+ }
+ }
+ }
+
+ ImGui::EndChild();
+ ImGui::End();
+}
+
+}