From d64aa8baceaab496251db4327abf2ac338a5f5ba Mon Sep 17 00:00:00 2001 From: jianglinjun Date: Sat, 18 Apr 2026 00:11:33 +0800 Subject: [PATCH 1/2] Add replay controls to frame and zone statistics Add replay support to Frame statistics and Find zone so users can watch histogram and summary statistics build up frame by frame instead of only seeing the final aggregated result. - add replay, pause/continue, exit, and speed controls - respect Limit to view / Limit range when choosing replay bounds - keep histogram axes fixed to the full replay range during playback - avoid initial empty-state flashing when replay starts - align the replay toolbar behavior across both statistics panels --- profiler/src/profiler/TracyImGui.hpp | 62 ++ profiler/src/profiler/TracyView.cpp | 13 +- profiler/src/profiler/TracyView.hpp | 50 ++ profiler/src/profiler/TracyView_FindZone.cpp | 642 ++++++++++++---- profiler/src/profiler/TracyView_TraceInfo.cpp | 684 +++++++++++++----- 5 files changed, 1109 insertions(+), 342 deletions(-) diff --git a/profiler/src/profiler/TracyImGui.hpp b/profiler/src/profiler/TracyImGui.hpp index 691e3b2a6a..68c9198496 100644 --- a/profiler/src/profiler/TracyImGui.hpp +++ b/profiler/src/profiler/TracyImGui.hpp @@ -5,6 +5,7 @@ # pragma warning( disable: 4244 ) // conversion from don't care to whatever, possible loss of data #endif +#include #include #include #include @@ -191,6 +192,67 @@ static constexpr const uint32_t AsmSyntaxColors[] = { } } +// Replay toolbars expose three user-visible states: start from the beginning, resume from a +// paused midpoint, or pause an actively running replay. +enum class ReplayButtonAction +{ + Replay, + Continue, + Pause +}; + +[[maybe_unused]] static inline ReplayButtonAction GetReplayButtonAction( bool active, bool paused, int currentFrame, int endFrameExclusive ) +{ + if( active && !paused ) return ReplayButtonAction::Pause; + if( active && paused && currentFrame < endFrameExclusive - 1 ) return ReplayButtonAction::Continue; + return ReplayButtonAction::Replay; +} + +[[maybe_unused]] static inline const char* GetReplayButtonLabel( ReplayButtonAction action ) +{ + switch( action ) + { + case ReplayButtonAction::Replay: return ICON_FA_PLAY " Replay"; + case ReplayButtonAction::Continue: return ICON_FA_PLAY " Continue"; + case ReplayButtonAction::Pause: return ICON_FA_PAUSE " Pause"; + default: + IM_ASSERT( false ); + return ""; + } +} + +[[maybe_unused]] static inline float GetReplaySpeedComboWidth( float scale ) +{ + return ImGui::CalcTextSize( "100x" ).x + ImGui::GetFrameHeight() + scale * 16; +} + +// Small helper for combo boxes backed by a fixed label array and a byte-sized selection index. +template +[[maybe_unused]] static inline bool StringArrayCombo( const char* id, const std::array& labels, uint8_t& selected ) +{ + IM_ASSERT( selected < labels.size() ); + + bool changed = false; + if( ImGui::BeginCombo( id, labels[selected] ) ) + { + for( uint8_t i = 0; i < labels.size(); i++ ) + { + const bool isSelected = selected == i; + if( ImGui::Selectable( labels[i], isSelected ) ) + { + selected = i; + changed = true; + } + if( isSelected ) + { + ImGui::SetItemDefaultFocus(); + } + } + ImGui::EndCombo(); + } + return changed; +} + [[maybe_unused]] static inline void DrawTextContrast( ImDrawList* draw, const ImVec2& pos, uint32_t color, const char* text ) { const auto scale = round( GetScale() ); diff --git a/profiler/src/profiler/TracyView.cpp b/profiler/src/profiler/TracyView.cpp index 0f1bf7b04d..5801ae8304 100644 --- a/profiler/src/profiler/TracyView.cpp +++ b/profiler/src/profiler/TracyView.cpp @@ -1498,11 +1498,14 @@ void View::SelectThread( uint64_t thread ) bool View::WasActive() const { return m_wasActive.load( std::memory_order_acquire ) || - m_zoomAnim.active || - m_notificationTime > 0 || - !m_playback.pause || - m_worker.IsConnected() || - !m_worker.IsBackgroundDone(); + m_zoomAnim.active || + m_notificationTime > 0 || + // Keep refreshing while statistics replays are actively advancing. + ( m_showInfo && m_frameSortData.playback.active && !m_frameSortData.playback.pause ) || + ( m_findZone.show && m_findZone.playback.active && !m_findZone.playback.pause ) || + !m_playback.pause || + m_worker.IsConnected() || + !m_worker.IsBackgroundDone(); } void View::AddLlmAttachment( const nlohmann::json& json ) diff --git a/profiler/src/profiler/TracyView.hpp b/profiler/src/profiler/TracyView.hpp index 63beb8d534..8403e6fdfc 100644 --- a/profiler/src/profiler/TracyView.hpp +++ b/profiler/src/profiler/TracyView.hpp @@ -51,6 +51,10 @@ constexpr const char* GpuContextNames[] = { "Rocprof" }; +static constexpr std::array StatisticsReplaySpeeds = { 1.f, 5.f, 10.f, 20.f, 50.f, 100.f }; +static constexpr std::array StatisticsReplaySpeedLabels = { "1x", "5x", "10x", "20x", "50x", "100x" }; +static constexpr uint8_t StatisticsReplayDefaultSpeed = 4; + struct MemoryPage; class FileRead; class SourceView; @@ -393,6 +397,10 @@ class View void DrawCallstackCalls( const CallstackFrameId* data, size_t size, uint16_t limit ) const; nlohmann::json GetCallstackJson( const CallstackFrameId* data, size_t size ) const; void SetViewToLastFrames(); + void ResetFindZonePlayback(); + void ResetFindZonePlaybackData(); + void SetFindZonePlaybackFrame( int idx ); + void StartFindZonePlayback( std::pair range ); int64_t GetZoneChildTime( const ZoneEvent& zone ); int64_t GetZoneChildTime( const GpuEvent& zone ); int64_t GetZoneChildTimeFast( const ZoneEvent& zone ); @@ -410,6 +418,11 @@ class View template void CalcZoneTimeDataImpl( const V& children, const ContextSwitch* ctx, unordered_flat_map& data, int64_t& ztime ); + void ResetFrameSortData(); + void ResetFrameSortPlayback(); + void SetFrameSortPlaybackFrame( int idx ); + void StartFrameSortPlayback( std::pair range ); + void SetPlaybackFrame( uint32_t idx ); bool Save( const char* fn, FileCompression comp, int zlevel, bool buildDict, int streams ); @@ -733,6 +746,23 @@ class View bool showZoneInFrames = false; Range range; RangeSlim rangeSlim; + struct + { + const FrameData* frameSet = nullptr; + // Final replay span selected by the UI; the live replay range only exposes a prefix of it. + std::pair targetFrameRange = { 0, 0 }; + // Full replay-range data used to keep histogram axes fixed while replay advances. + std::vector fullSorted; + // Effective time range shown by the current replay frame. + RangeSlim currentRange; + // Last replay range that accumulated find-zone statistics were built against. + RangeSlim rangeSlim; + int currentFrame = 0; + float timeLeft = 0; + uint8_t speed = StatisticsReplayDefaultSpeed; + bool active = false; + bool pause = true; + } playback; struct { @@ -749,6 +779,9 @@ class View void Reset() { + const auto playbackSpeed = playback.speed; + playback = {}; + playback.speed = playbackSpeed; ResetMatch(); match.clear(); selMatch = 0; @@ -760,6 +793,9 @@ class View void ResetMatch() { + const auto playbackSpeed = playback.speed; + playback = {}; + playback.speed = playbackSpeed; ResetGroups(); sorted.clear(); sortedNum = 0; @@ -909,6 +945,20 @@ class View bool limitToView = false; std::pair limitRange = { -1, 0 }; int minBinVal = 1; + struct + { + const FrameData* frameSet = nullptr; + // Final replay span selected by the UI; the live replay range only exposes a prefix of it. + std::pair targetRange = { 0, 0 }; + // Full replay-range frame times used to keep histogram axes fixed while replay advances. + std::vector fullData; + int currentFrame = 0; + float timeLeft = 0; + uint8_t speed = StatisticsReplayDefaultSpeed; + bool active = false; + bool pause = true; + bool limitToView = false; + } playback; } m_frameSortData; struct { diff --git a/profiler/src/profiler/TracyView_FindZone.cpp b/profiler/src/profiler/TracyView_FindZone.cpp index d170ce65d2..69040fd469 100644 --- a/profiler/src/profiler/TracyView_FindZone.cpp +++ b/profiler/src/profiler/TracyView_FindZone.cpp @@ -17,6 +17,123 @@ namespace tracy extern double s_time; +void View::ResetFindZonePlayback() +{ + const auto speed = m_findZone.playback.speed; + m_findZone.playback = {}; + m_findZone.playback.speed = speed; +} + +void View::ResetFindZonePlaybackData() +{ + // Clear only accumulated replay/statistics data. Search terms, selected match, and UI toggles + // are preserved so the window can resume from the same user context. + m_findZone.sorted.clear(); + m_findZone.sortedNum = 0; + m_findZone.average = 0; + m_findZone.median = 0; + m_findZone.p75 = 0; + m_findZone.p90 = 0; + m_findZone.p99 = 0; + m_findZone.p99_9 = 0; + m_findZone.total = 0; + m_findZone.tmin = std::numeric_limits::max(); + m_findZone.tmax = std::numeric_limits::min(); + m_findZone.ResetSelection(); + m_findZone.groups.clear(); + m_findZone.processed = 0; + m_findZone.groupId = 0; + m_findZone.selCs = 0; +} + +void View::SetFindZonePlaybackFrame( int idx ) +{ + auto& playback = m_findZone.playback; + playback.currentFrame = idx; + // Find-zone replay always grows the visible limit range from the replay start to the current + // frame so histogram and group statistics evolve cumulatively in-place. + playback.currentRange = { m_worker.GetFrameBegin( *playback.frameSet, playback.targetFrameRange.first ), m_worker.GetFrameEnd( *playback.frameSet, idx ), true }; + if( idx >= playback.targetFrameRange.second - 1 ) + { + playback.pause = true; + playback.timeLeft = 0; + } + else + { + // Replay cadence follows the real frame duration. + const auto frameTime = std::max( m_worker.GetFrameTime( *playback.frameSet, idx ), 1 ); + playback.timeLeft = frameTime / 1000000000.f; + } +} + +void View::StartFindZonePlayback( std::pair range ) +{ + if( range.first >= range.second ) + { + ResetFindZonePlayback(); + return; + } + + ResetFindZonePlaybackData(); + + auto& playback = m_findZone.playback; + playback.frameSet = m_frames; + playback.targetFrameRange = range; + // Capture the full target range up front so the histogram can keep stable axes during replay. + playback.fullSorted.clear(); + auto initialFrame = range.second - 1; + bool haveInitialFrame = false; + if( !m_findZone.match.empty() ) + { + auto& zoneData = m_worker.GetZonesForSourceLocation( m_findZone.match[m_findZone.selMatch] ); + auto& zones = zoneData.zones; + zones.ensure_sorted(); + + const auto rangeMin = m_worker.GetFrameBegin( *m_frames, range.first ); + const auto rangeMax = m_worker.GetFrameEnd( *m_frames, range.second - 1 ); + playback.fullSorted.reserve( zones.size() ); + + for( auto& ev : zones ) + { + auto& zone = *ev.Zone(); + const auto start = zone.Start(); + const auto end = zone.End(); + if( end > rangeMax || start < rangeMin ) continue; + + int64_t t = 0; + if( m_findZone.runningTime ) + { + const auto ctx = m_worker.GetContextSwitchData( m_worker.DecompressThread( ev.Thread() ) ); + if( !ctx ) break; + uint64_t cnt; + if( !GetZoneRunningTime( ctx, zone, t, cnt ) ) break; + } + else if( m_findZone.selfTime ) + { + t = end - start - GetZoneChildTimeFast( zone ); + } + else + { + t = end - start; + } + playback.fullSorted.emplace_back( t ); + // Start from the first frame that actually contributes data to avoid flashing an empty histogram. + const auto zoneEndTime = std::max( start, end - 1 ); + const auto zoneFrame = std::min( m_worker.GetFrameRange( *m_frames, zoneEndTime, zoneEndTime ).first, range.second - 1 ); + initialFrame = haveInitialFrame ? std::min( initialFrame, zoneFrame ) : zoneFrame; + haveInitialFrame = true; + } + + pdqsort_branchless( playback.fullSorted.begin(), playback.fullSorted.end() ); + } + playback.active = true; + playback.pause = false; + if( !haveInitialFrame ) initialFrame = range.first; + // Keep the first rendered replay state non-empty whenever the matched zones allow it. + SetFindZonePlaybackFrame( initialFrame ); + playback.rangeSlim = playback.currentRange; +} + #ifndef TRACY_NO_STATISTICS void View::FindZones() { @@ -78,7 +195,7 @@ void View::DrawZoneList( int id, const Vector>& zones ) { const auto zsz = zones.size(); char buf[32]; - sprintf( buf, "%i##zonelist", id ); + snprintf( buf, sizeof( buf ), "%i##zonelist", id ); if( !ImGui::BeginTable( buf, 3, ImGuiTableFlags_NoSavedSettings | ImGuiTableFlags_Resizable | ImGuiTableFlags_Hideable | ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_Sortable | ImGuiTableFlags_ScrollY, ImVec2( 0, ImGui::GetTextLineHeightWithSpacing() * std::min( zsz + 1, 15 ) ) ) ) { ImGui::TreePop(); @@ -260,6 +377,7 @@ void View::DrawFindZone() const auto scale = GetScale(); ImGui::SetNextWindowSize( ImVec2( 520 * scale, 800 * scale ), ImGuiCond_FirstUseEver ); ImGui::Begin( "Find zone", &m_findZone.show, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse ); + if( !m_findZone.show ) m_findZone.playback.pause = true; if( ImGui::GetCurrentWindowRead()->SkipItems ) { ImGui::End(); return; } #ifdef TRACY_NO_STATISTICS ImGui::TextWrapped( "Collection of statistical data is disabled in this build." ); @@ -332,6 +450,101 @@ void View::DrawFindZone() ToggleButton( ICON_FA_RULER " Limits", m_showRanges ); } + const auto framesUsed = m_worker.AreFramesUsed() && m_frames; + const auto fullFrameCount = framesUsed ? int( m_worker.GetFullFrameCount( *m_frames ) ) : 0; + const auto replayFrameRange = framesUsed ? ( m_findZone.range.active ? m_worker.GetFrameRange( *m_frames, m_findZone.range.min, m_findZone.range.max ) : std::make_pair( 0, fullFrameCount ) ) : std::make_pair( 0, 0 ); + const auto replayDisabled = replayFrameRange.first >= replayFrameRange.second; + const auto replayActive = m_findZone.playback.active; + const auto replayAction = GetReplayButtonAction( replayActive, m_findZone.playback.pause, m_findZone.playback.currentFrame, m_findZone.playback.targetFrameRange.second ); + const auto replayRunning = replayAction == ReplayButtonAction::Pause; + const auto replayCanExit = replayActive && m_findZone.playback.pause; + const auto replayLabel = GetReplayButtonLabel( replayAction ); + + ImGui::SameLine(); + if( ButtonDisablable( replayLabel, replayDisabled ) ) + { + if( replayRunning ) + { + m_findZone.playback.pause = true; + } + else if( replayAction == ReplayButtonAction::Continue ) + { + m_findZone.playback.pause = false; + } + else + { + StartFindZonePlayback( replayFrameRange ); + } + } + + ImGui::SameLine(); + ImGui::TextDisabled( "Speed:" ); + ImGui::SameLine(); + if( replayDisabled ) ImGui::BeginDisabled(); + ImGui::SetNextItemWidth( GetReplaySpeedComboWidth( scale ) ); + StringArrayCombo( "##findZoneReplaySpeed", StatisticsReplaySpeedLabels, m_findZone.playback.speed ); + if( replayDisabled ) ImGui::EndDisabled(); + + ImGui::SameLine(); + if( ButtonDisablable( ICON_FA_XMARK " Exit", !replayCanExit ) ) + { + ResetFindZonePlayback(); + ResetFindZonePlaybackData(); + } + + if( m_findZone.playback.active ) + { + if( m_findZone.playback.frameSet != m_frames || m_findZone.playback.targetFrameRange != replayFrameRange ) + { + // Changing frame set or limit range invalidates the replay target and all accumulated data. + ResetFindZonePlayback(); + ResetFindZonePlaybackData(); + } + else + { + if( !m_findZone.playback.pause ) + { + auto time = ImGui::GetIO().DeltaTime * StatisticsReplaySpeeds[m_findZone.playback.speed]; + while( !m_findZone.playback.pause && time > 0 ) + { + const auto dt = std::min( time, m_findZone.playback.timeLeft ); + time -= dt; + m_findZone.playback.timeLeft -= dt; + if( m_findZone.playback.timeLeft <= 0 ) + { + if( m_findZone.playback.currentFrame + 1 < m_findZone.playback.targetFrameRange.second ) + { + SetFindZonePlaybackFrame( m_findZone.playback.currentFrame + 1 ); + } + else + { + m_findZone.playback.pause = true; + } + } + } + m_wasActive = true; + } + + if( m_findZone.playback.rangeSlim.active != m_findZone.playback.currentRange.active || + m_findZone.playback.rangeSlim.min != m_findZone.playback.currentRange.min || + m_findZone.playback.rangeSlim.max != m_findZone.playback.currentRange.max ) + { + // Find-zone statistics and grouping caches are range-dependent, so advancing replay + // to a new effective range must rebuild them from scratch. + ResetFindZonePlaybackData(); + m_findZone.playback.rangeSlim = m_findZone.playback.currentRange; + } + } + + if( m_findZone.playback.active ) + { + const auto currentReplayFrame = m_findZone.playback.currentFrame - m_findZone.playback.targetFrameRange.first + 1; + const auto totalReplayFrames = m_findZone.playback.targetFrameRange.second - m_findZone.playback.targetFrameRange.first; + ImGui::SameLine(); + ImGui::TextDisabled( "(%s/%s)", RealToString( currentReplayFrame ), RealToString( totalReplayFrames ) ); + } + } + if( m_findZone.rangeSlim != m_findZone.range ) { m_findZone.ResetMatch(); @@ -366,8 +579,10 @@ void View::DrawFindZone() { Achieve( "findZone" ); - const auto rangeMin = m_findZone.range.min; - const auto rangeMax = m_findZone.range.max; + const auto effectiveRange = m_findZone.playback.active ? m_findZone.playback.currentRange : RangeSlim{ m_findZone.range.min, m_findZone.range.max, m_findZone.range.active }; + const auto rangeMin = effectiveRange.min; + const auto rangeMax = effectiveRange.max; + const auto rangeActive = effectiveRange.active; bool expand = ImGui::TreeNodeEx( "Matched source locations", ImGuiTreeNodeFlags_DefaultOpen ); ImGui::SameLine(); @@ -449,7 +664,7 @@ void View::DrawFindZone() size_t i; if( m_findZone.runningTime ) { - if( m_findZone.range.active ) + if( rangeActive ) { for( i=m_findZone.sortedNum; i 1 || m_findZone.range.active ) - { - if( m_findZone.logTime ) - { - const auto tMinLog = log10( tmin ); - const auto zmax = ( log10( tmax ) - tMinLog ) / numBins; - int64_t i; - for( i=0; i= m_findZone.minBinVal ) break; - sortedBegin = 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( sortedBegin, sortedEnd, nextBinVal ); - const auto distance = std::distance( nit, sortedEnd ); - if( distance >= m_findZone.minBinVal ) break; - sortedEnd = nit; - } - } - else - { - const auto zmax = tmax - tmin; - int64_t i; - for( i=0; i= m_findZone.minBinVal ) break; - sortedBegin = nit; - } - for( int64_t j=numBins-1; j>i; j-- ) - { - const auto nextBinVal = tmin + ( j-1 ) * zmax / numBins; - auto nit = std::lower_bound( sortedBegin, sortedEnd, nextBinVal ); - const auto distance = std::distance( nit, sortedEnd ); - if( distance >= m_findZone.minBinVal ) break; - sortedEnd = nit; - } - } - - if( sortedBegin != sorted.end() ) - { - tmin = *sortedBegin; - tmax = *(sortedEnd-1); - total = 0; - for( auto ptr = sortedBegin; ptr != sortedEnd; ptr++ ) total += *ptr; - } - } - if( numBins > m_findZone.numBins ) { m_findZone.numBins = numBins; @@ -956,115 +1110,316 @@ void View::DrawFindZone() const auto& binTime = m_findZone.binTime; const auto& selBin = m_findZone.selBin; - const auto distBegin = std::distance( sorted.begin(), sortedBegin ); - const auto distEnd = std::distance( sorted.begin(), sortedEnd ); - if( m_findZone.binCache.numBins != numBins || - m_findZone.binCache.distBegin != distBegin || - m_findZone.binCache.distEnd != distEnd ) + int64_t fixedAxisMaxVal = 0; + bool fixedReplayAxes = m_findZone.playback.active && !m_findZone.playback.fullSorted.empty(); + if( fixedReplayAxes ) { - m_findZone.binCache.numBins = numBins; - m_findZone.binCache.distBegin = distBegin; - m_findZone.binCache.distEnd = distEnd; - + // Replay fills the current bins progressively, but the axis extents stay locked to + // the full replay range so the histogram doesn't rescale every frame. memset( bins.get(), 0, sizeof( int64_t ) * numBins ); memset( binTime.get(), 0, sizeof( int64_t ) * numBins ); memset( selBin.get(), 0, sizeof( int64_t ) * numBins ); int64_t selectionTime = 0; + auto axisBegin = m_findZone.playback.fullSorted.begin(); + auto axisEnd = m_findZone.playback.fullSorted.end(); + while( axisBegin != axisEnd && *axisBegin == 0 ) ++axisBegin; - if( m_findZone.logTime ) + if( axisBegin == axisEnd ) + { + fixedReplayAxes = false; + } + else { - const auto tMinLog = log10( tmin ); - const auto zmax = ( log10( tmax ) - tMinLog ) / numBins; + tmin = *axisBegin; + tmax = *( axisEnd - 1 ); + if( m_findZone.minBinVal > 1 ) { - auto zit = sortedBegin; - for( int64_t i=0; i= s && *end <= e ) selectionTime += timeSum; + const auto nextBinVal = int64_t( pow( 10.0, tMinLog + ( i + 1 ) * zmax ) ); + auto nit = std::lower_bound( axisBegin, axisEnd, nextBinVal ); + const auto distance = std::distance( axisBegin, nit ); + if( distance >= m_findZone.minBinVal ) break; + axisBegin = 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( axisBegin, axisEnd, nextBinVal ); + const auto distance = std::distance( nit, axisEnd ); + if( distance >= m_findZone.minBinVal ) break; + axisEnd = nit; + } + } + else + { + const auto zmax = tmax - tmin; + int64_t i; + for( i = 0; i < numBins; i++ ) + { + const auto nextBinVal = tmin + ( i + 1 ) * zmax / numBins; + auto nit = std::lower_bound( axisBegin, axisEnd, nextBinVal ); + const auto distance = std::distance( axisBegin, nit ); + if( distance >= m_findZone.minBinVal ) break; + axisBegin = nit; + } + for( int64_t j = numBins - 1; j > i; j-- ) + { + const auto nextBinVal = tmin + ( j - 1 ) * zmax / numBins; + auto nit = std::lower_bound( axisBegin, axisEnd, nextBinVal ); + const auto distance = std::distance( nit, axisEnd ); + if( distance >= m_findZone.minBinVal ) break; + axisEnd = nit; } - zit = nit; } - const auto timeSum = std::accumulate( zit, sortedEnd, int64_t( 0 ) ); - bins[numBins-1] += std::distance( zit, sortedEnd ); - binTime[numBins-1] += timeSum; - if( m_findZone.highlight.active && *zit >= s && *(sortedEnd-1) <= e ) selectionTime += timeSum; } - if( m_findZone.selGroup != m_findZone.Unselected ) + if( axisBegin == axisEnd ) { - auto zit = m_findZone.selSort.begin(); - while( zit != m_findZone.selSort.end() && *zit == 0 ) ++zit; - for( int64_t i=0; i axisBins( numBins, 0 ); + std::vector axisBinTime( numBins, 0 ); + const auto valueToBin = [numBins, tmin, tmax, this]( int64_t value ) { + if( m_findZone.logTime ) + { + const auto ltmin = log10( tmin ); + const auto ltmax = log10( tmax ); + return std::clamp( int( ( log10( value ) - ltmin ) / ( ltmax - ltmin ) * numBins ), 0, int( numBins ) - 1 ); + } + return std::clamp( int( ( value - tmin ) / double( tmax - tmin ) * numBins ), 0, int( numBins ) - 1 ); + }; + + for( auto it = axisBegin; it != axisEnd; ++it ) { - selBin[i] = std::accumulate( zit, nit, int64_t( 0 ) ); + const auto bin = valueToBin( *it ); + axisBins[bin]++; + axisBinTime[bin] += *it; } - else + + for( auto value : m_findZone.sorted ) { - selBin[i] = std::distance( zit, nit ); + if( value <= 0 || value < tmin || value > tmax ) continue; + const auto bin = valueToBin( value ); + bins[bin]++; + binTime[bin] += value; + if( m_findZone.highlight.active && value >= s && value <= e ) selectionTime += value; } - zit = nit; + + if( m_findZone.selGroup != m_findZone.Unselected ) + { + for( auto value : m_findZone.selSort ) + { + if( value <= 0 || value < tmin || value > tmax ) continue; + const auto bin = valueToBin( value ); + if( cumulateTime ) selBin[bin] += value; + else selBin[bin]++; + } + } + + fixedAxisMaxVal = cumulateTime + ? *std::max_element( axisBinTime.begin(), axisBinTime.end() ) + : *std::max_element( axisBins.begin(), axisBins.end() ); + m_findZone.selTime = selectionTime; } } } - else + } + if( !fixedReplayAxes ) + { + const auto& sorted = m_findZone.sorted; + + auto sortedBegin = sorted.begin(); + auto sortedEnd = sorted.end(); + while( sortedBegin != sortedEnd && *sortedBegin == 0 ) ++sortedBegin; + + if( m_findZone.minBinVal > 1 || rangeActive ) { - const auto zmax = tmax - tmin; - auto zit = sortedBegin; - for( int64_t i=0; i= m_findZone.minBinVal ) break; + sortedBegin = nit; + } + for( int64_t j = numBins - 1; j > i; j-- ) { - auto end = nit == zit ? zit : nit-1; - if( *zit >= s && *end <= e ) selectionTime += timeSum; + const auto nextBinVal = int64_t( pow( 10.0, tMinLog + ( j - 1 ) * zmax ) ); + auto nit = std::lower_bound( sortedBegin, sortedEnd, nextBinVal ); + const auto distance = std::distance( nit, sortedEnd ); + if( distance >= m_findZone.minBinVal ) break; + sortedEnd = nit; + } + } + else + { + const auto zmax = tmax - tmin; + int64_t i; + for( i = 0; i < numBins; i++ ) + { + const auto nextBinVal = tmin + ( i + 1 ) * zmax / numBins; + auto nit = std::lower_bound( sortedBegin, sortedEnd, nextBinVal ); + const auto distance = std::distance( sortedBegin, nit ); + if( distance >= m_findZone.minBinVal ) break; + sortedBegin = nit; + } + for( int64_t j = numBins - 1; j > i; j-- ) + { + const auto nextBinVal = tmin + ( j - 1 ) * zmax / numBins; + auto nit = std::lower_bound( sortedBegin, sortedEnd, nextBinVal ); + const auto distance = std::distance( nit, sortedEnd ); + if( distance >= m_findZone.minBinVal ) break; + sortedEnd = nit; } - zit = nit; } - const auto timeSum = std::accumulate( zit, sortedEnd, int64_t( 0 ) ); - bins[numBins-1] += std::distance( zit, sortedEnd ); - binTime[numBins-1] += timeSum; - if( m_findZone.highlight.active && *zit >= s && *(sortedEnd-1) <= e ) selectionTime += timeSum; - if( m_findZone.selGroup != m_findZone.Unselected ) + if( sortedBegin != sorted.end() ) { - auto zit = m_findZone.selSort.begin(); - while( zit != m_findZone.selSort.end() && *zit == 0 ) ++zit; + tmin = *sortedBegin; + tmax = *( sortedEnd - 1 ); + total = 0; + for( auto ptr = sortedBegin; ptr != sortedEnd; ptr++ ) total += *ptr; + } + } + + const auto distBegin = std::distance( sorted.begin(), sortedBegin ); + const auto distEnd = std::distance( sorted.begin(), sortedEnd ); + if( m_findZone.binCache.numBins != numBins || + m_findZone.binCache.distBegin != distBegin || + m_findZone.binCache.distEnd != distEnd ) + { + m_findZone.binCache.numBins = numBins; + m_findZone.binCache.distBegin = distBegin; + m_findZone.binCache.distEnd = distEnd; + + memset( bins.get(), 0, sizeof( int64_t ) * numBins ); + memset( binTime.get(), 0, sizeof( int64_t ) * numBins ); + memset( selBin.get(), 0, sizeof( int64_t ) * numBins ); + + int64_t selectionTime = 0; + + if( m_findZone.logTime ) + { + const auto tMinLog = log10( tmin ); + const auto zmax = ( log10( tmax ) - tMinLog ) / numBins; + { + auto zit = sortedBegin; + for( int64_t i = 0; i < numBins; i++ ) + { + const auto nextBinVal = int64_t( pow( 10.0, tMinLog + ( i + 1 ) * zmax ) ); + auto nit = std::lower_bound( zit, sortedEnd, nextBinVal ); + const auto distance = std::distance( zit, nit ); + const auto timeSum = std::accumulate( zit, nit, int64_t( 0 ) ); + bins[i] = distance; + binTime[i] = timeSum; + if( m_findZone.highlight.active && zit != sortedEnd ) + { + auto end = nit == zit ? zit : nit - 1; + if( *zit >= s && *end <= e ) selectionTime += timeSum; + } + zit = nit; + } + if( zit != sortedEnd ) + { + const auto timeSum = std::accumulate( zit, sortedEnd, int64_t( 0 ) ); + bins[numBins - 1] += std::distance( zit, sortedEnd ); + binTime[numBins - 1] += timeSum; + if( m_findZone.highlight.active && *zit >= s && *( sortedEnd - 1 ) <= e ) selectionTime += timeSum; + } + } + + if( m_findZone.selGroup != m_findZone.Unselected ) + { + auto zit = m_findZone.selSort.begin(); + while( zit != m_findZone.selSort.end() && *zit == 0 ) ++zit; + for( int64_t i = 0; i < numBins; i++ ) + { + const auto nextBinVal = int64_t( pow( 10.0, tMinLog + ( i + 1 ) * zmax ) ); + auto nit = std::lower_bound( zit, m_findZone.selSort.end(), nextBinVal ); + if( cumulateTime ) + { + selBin[i] = std::accumulate( zit, nit, int64_t( 0 ) ); + } + else + { + selBin[i] = std::distance( zit, nit ); + } + zit = nit; + } + } + } + else + { + const auto zmax = tmax - tmin; + auto zit = sortedBegin; for( int64_t i=0; i= s && *end <= e ) selectionTime += timeSum; } - else + zit = nit; + } + if( zit != sortedEnd ) + { + const auto timeSum = std::accumulate( zit, sortedEnd, int64_t( 0 ) ); + bins[numBins - 1] += std::distance( zit, sortedEnd ); + binTime[numBins - 1] += timeSum; + if( m_findZone.highlight.active && *zit >= s && *( sortedEnd - 1 ) <= e ) selectionTime += timeSum; + } + + if( m_findZone.selGroup != m_findZone.Unselected ) + { + auto zit = m_findZone.selSort.begin(); + while( zit != m_findZone.selSort.end() && *zit == 0 ) ++zit; + for( int64_t i = 0; i < numBins; i++ ) { - selBin[i] = std::distance( zit, nit ); + const auto nextBinVal = tmin + ( i + 1 ) * zmax / numBins; + auto nit = std::lower_bound( zit, m_findZone.selSort.end(), nextBinVal ); + if( cumulateTime ) + { + selBin[i] = std::accumulate( zit, nit, int64_t( 0 ) ); + } + else + { + selBin[i] = std::distance( zit, nit ); + } + zit = nit; } - zit = nit; } } - } - m_findZone.selTime = selectionTime; + m_findZone.selTime = selectionTime; + } } int maxBin = 0; @@ -1093,6 +1448,7 @@ void View::DrawFindZone() } } } + const auto drawMaxVal = fixedReplayAxes ? fixedAxisMaxVal : maxVal; TextFocused( "Total time:", TimeToString( total ) ); ImGui::SameLine(); @@ -1123,7 +1479,7 @@ void View::DrawFindZone() } TextFocused( "Mode:", TimeToString( ( t0 + t1 ) / 2 ) ); } - if( !m_findZone.range.active && m_findZone.sorted.size() > 1 ) + if( !rangeActive && m_findZone.sorted.size() > 1 ) { const auto sz = m_findZone.sorted.size(); const auto avg = m_findZone.average; @@ -1243,7 +1599,7 @@ void View::DrawFindZone() if( m_findZone.logVal ) { - const auto hAdj = double( Height - 4 ) / log10( maxVal + 1 ); + const auto hAdj = double( Height - 4 ) / log10( drawMaxVal + 1 ); for( int i=0; i::max() - 1; uint64_t lastGid = invalidGid; @@ -2052,7 +2408,7 @@ void View::DrawFindZone() auto samplesBegin = samples->begin(); auto samplesEnd = samples->end(); - if( m_findZone.range.active ) + if( rangeActive ) { const auto rangeMin = m_findZone.range.min; const auto rangeMax = m_findZone.range.max; diff --git a/profiler/src/profiler/TracyView_TraceInfo.cpp b/profiler/src/profiler/TracyView_TraceInfo.cpp index d330951962..f7c0149e9d 100644 --- a/profiler/src/profiler/TracyView_TraceInfo.cpp +++ b/profiler/src/profiler/TracyView_TraceInfo.cpp @@ -11,11 +11,74 @@ namespace tracy extern double s_time; +void View::ResetFrameSortData() +{ + m_frameSortData.frameSet = nullptr; + m_frameSortData.frameNum = 0; + m_frameSortData.data.clear(); + m_frameSortData.average = 0; + m_frameSortData.median = 0; + m_frameSortData.total = 0; + m_frameSortData.limitRange = { -1, 0 }; +} + +void View::ResetFrameSortPlayback() +{ + const auto speed = m_frameSortData.playback.speed; + m_frameSortData.playback = {}; + m_frameSortData.playback.speed = speed; +} + +void View::SetFrameSortPlaybackFrame( int idx ) +{ + auto& playback = m_frameSortData.playback; + playback.currentFrame = idx; + if( idx >= playback.targetRange.second - 1 ) + { + playback.pause = true; + playback.timeLeft = 0; + } + else + { + const auto frameTime = std::max( m_worker.GetFrameTime( *playback.frameSet, idx ), 1 ); + playback.timeLeft = frameTime / 1000000000.f; + } +} + +void View::StartFrameSortPlayback( std::pair range ) +{ + if( range.first >= range.second ) + { + ResetFrameSortPlayback(); + return; + } + + ResetFrameSortData(); + + auto& playback = m_frameSortData.playback; + playback.frameSet = m_frames; + playback.targetRange = range; + playback.fullData.clear(); + playback.fullData.reserve( range.second - range.first ); + for( int i = range.first; i < range.second; i++ ) + { + const auto t = m_worker.GetFrameTime( *m_frames, i ); + if( t > 0 ) playback.fullData.emplace_back( t ); + } + pdqsort_branchless( playback.fullData.begin(), playback.fullData.end() ); + playback.limitToView = m_frameSortData.limitToView; + playback.active = true; + playback.pause = false; + + SetFrameSortPlaybackFrame( range.first + ( range.second - range.first > 1 ? 1 : 0 ) ); +} + 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( !m_showInfo ) m_frameSortData.playback.pause = true; if( ImGui::GetCurrentWindowRead()->SkipItems ) { ImGui::End(); return; } ImGui::PushFont( g_fonts.normal, FontBig ); TextFocused( "Program:", m_worker.GetCaptureProgram().c_str() ); @@ -223,93 +286,179 @@ void View::DrawInfo() TextColoredUnformatted( 0xFF00FFFF, ICON_FA_TRIANGLE_EXCLAMATION ); } - 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 ) ) + const auto viewFrameRange = m_worker.GetFrameRange( *m_frames, m_vd.zvStart, m_vd.zvEnd ); + const auto liveFrameRange = m_frameSortData.limitToView ? viewFrameRange : std::make_pair( 0, int( fsz ) ); + const auto replayDisabled = liveFrameRange.first >= liveFrameRange.second; + const auto replayActive = m_frameSortData.playback.active; + const auto replayAction = GetReplayButtonAction( replayActive, m_frameSortData.playback.pause, m_frameSortData.playback.currentFrame, m_frameSortData.playback.targetRange.second ); + const auto replayRunning = replayAction == ReplayButtonAction::Pause; + const auto replayCanExit = replayActive && m_frameSortData.playback.pause; + const auto replayLabel = GetReplayButtonLabel( replayAction ); + + ImGui::SameLine(); + if( SmallButtonDisablable( replayLabel, replayDisabled ) ) { - m_frameSortData.frameSet = m_frames; - m_frameSortData.frameNum = 0; - m_frameSortData.data.clear(); - m_frameSortData.total = 0; + if( replayRunning ) + { + m_frameSortData.playback.pause = true; + } + else if( replayAction == ReplayButtonAction::Continue ) + { + m_frameSortData.playback.pause = false; + } + else + { + StartFrameSortPlayback( liveFrameRange ); + } } - bool recalc = false; - int64_t total = 0; - if( !m_frameSortData.limitToView ) + + ImGui::SameLine(); + ImGui::TextDisabled( "Speed:" ); + ImGui::SameLine(); + if( replayDisabled ) ImGui::BeginDisabled(); + ImGui::PushStyleVar( ImGuiStyleVar_FramePadding, ImVec2( 0, 0 ) ); + ImGui::SetNextItemWidth( GetReplaySpeedComboWidth( scale ) ); + StringArrayCombo( "##frameReplaySpeed", StatisticsReplaySpeedLabels, m_frameSortData.playback.speed ); + ImGui::PopStyleVar(); + if( replayDisabled ) ImGui::EndDisabled(); + + ImGui::SameLine(); + if( SmallButtonDisablable( ICON_FA_XMARK " Exit", !replayCanExit ) ) + { + ResetFrameSortPlayback(); + ResetFrameSortData(); + } + + if( m_frameSortData.playback.active ) + { + const auto currentReplayFrame = m_frameSortData.playback.currentFrame - m_frameSortData.playback.targetRange.first + 1; + const auto totalReplayFrames = m_frameSortData.playback.targetRange.second - m_frameSortData.playback.targetRange.first; + ImGui::SameLine(); + ImGui::TextDisabled( "(%s/%s)", RealToString( currentReplayFrame ), RealToString( totalReplayFrames ) ); + } + + if( m_frameSortData.playback.active && ( m_frameSortData.playback.frameSet != m_frames || m_frameSortData.playback.limitToView != m_frameSortData.limitToView ) ) { - if( m_frameSortData.frameNum != fsz || m_frameSortData.limitRange.first != -1 ) + ResetFrameSortPlayback(); + ResetFrameSortData(); + } + + if( m_frameSortData.playback.active && !m_frameSortData.playback.pause ) + { + auto time = ImGui::GetIO().DeltaTime * StatisticsReplaySpeeds[m_frameSortData.playback.speed]; + while( !m_frameSortData.playback.pause && time > 0 ) { - 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 ) + if( m_frameSortData.playback.currentFrame + 1 < m_frameSortData.playback.targetRange.second ) { - vec.emplace_back( t ); - total += t; + SetFrameSortPlaybackFrame( m_frameSortData.playback.currentFrame + 1 ); + } + else + { + m_frameSortData.playback.pause = true; } } - 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; } + m_wasActive.store( true, std::memory_order_release ); + } + + const auto targetFrameRange = m_frameSortData.playback.active ? m_frameSortData.playback.targetRange : liveFrameRange; + auto activeFrameRange = targetFrameRange; + if( m_frameSortData.playback.active ) + { + activeFrameRange.second = std::min( m_frameSortData.playback.currentFrame + 1, targetFrameRange.second ); + } + + if( m_frameSortData.frameSet != m_frames || + m_frameSortData.limitRange.first != activeFrameRange.first || + m_frameSortData.frameNum < size_t( activeFrameRange.first ) || + m_frameSortData.frameNum > size_t( activeFrameRange.second ) ) + { + ResetFrameSortData(); + m_frameSortData.frameSet = m_frames; + m_frameSortData.frameNum = activeFrameRange.first; + m_frameSortData.limitRange = { activeFrameRange.first, activeFrameRange.first }; } - else + + bool recalc = false; + auto total = m_frameSortData.total; + if( m_frameSortData.frameNum != size_t( activeFrameRange.second ) ) { - if( m_frameSortData.limitRange != frameRange ) + auto& vec = m_frameSortData.data; + vec.reserve( activeFrameRange.second - activeFrameRange.first ); + const auto midSz = vec.size(); + for( size_t i = m_frameSortData.frameNum; i < size_t( activeFrameRange.second ); i++ ) { - auto& vec = m_frameSortData.data; - assert( vec.empty() ); - vec.reserve( frameRange.second - frameRange.first ); - for( int i=frameRange.first; i 0 ) { - const auto t = m_worker.GetFrameTime( *m_frames, i ); - if( t > 0 ) - { - vec.emplace_back( t ); - total += t; - } + vec.emplace_back( t ); + total += t; } - pdqsort_branchless( vec.begin(), vec.end() ); - recalc = true; - m_frameSortData.limitRange = frameRange; } + auto mid = vec.begin() + midSz; + pdqsort_branchless( mid, vec.end() ); + std::inplace_merge( vec.begin(), mid, vec.end() ); + m_frameSortData.frameNum = activeFrameRange.second; + recalc = true; } + m_frameSortData.limitRange = activeFrameRange; + 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; + if( vsz != 0 ) + { + m_frameSortData.average = float( total ) / vsz; + m_frameSortData.median = vec[vsz / 2]; + } + else + { + m_frameSortData.average = 0; + m_frameSortData.median = 0; + } } const auto profileSpan = m_worker.GetLastTime(); - TextFocused( "Count:", RealToString( fsz ) ); + const auto activeFrameCount = activeFrameRange.second - activeFrameRange.first; + TextFocused( "Count:", RealToString( activeFrameCount ) ); + if( activeFrameCount != int( fsz ) ) + { + ImGui::SameLine(); + ImGui::TextDisabled( "(%s total)", 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() ) + if( m_frameSortData.average > 0 ) { - ImGui::BeginTooltip(); - ImGui::Text( "%s FPS", RealToString( 1000000000.0 / m_frameSortData.average ) ); - ImGui::EndTooltip(); + 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() ) + if( m_frameSortData.median > 0 ) { - ImGui::BeginTooltip(); - ImGui::Text( "%s FPS", RealToString( 1000000000.0 / m_frameSortData.median ) ); - ImGui::EndTooltip(); + 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 ) ) @@ -317,176 +466,322 @@ void View::DrawInfo() const auto ty = ImGui::GetTextLineHeight(); auto& frames = m_frameSortData.data; - auto tmin = frames.front(); - auto tmax = frames.back(); - - if( tmin != std::numeric_limits::max() ) + if( frames.empty() ) { - 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 ) ) ); + ImGui::TextDisabled( "No frame data in selected range." ); + } + else + { + auto tmin = frames.front(); + auto tmax = frames.back(); - if( tmax - tmin > 0 ) + if( tmin != std::numeric_limits::max() ) { - const auto w = ImGui::GetContentRegionAvail().x; - - const auto numBins = int64_t( w - 4 ); - if( numBins > 1 ) + 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 ) { - if( numBins > m_frameSortData.numBins ) + const auto w = ImGui::GetContentRegionAvail().x; + + const auto numBins = int64_t( w - 4 ); + if( numBins > 1 ) { - m_frameSortData.numBins = numBins; - m_frameSortData.bins = std::make_unique( numBins ); - } + if( numBins > m_frameSortData.numBins ) + { + m_frameSortData.numBins = numBins; + m_frameSortData.bins = std::make_unique( numBins ); + } - const auto& bins = m_frameSortData.bins; + const auto& bins = m_frameSortData.bins; - memset( bins.get(), 0, sizeof( int64_t ) * numBins ); + memset( bins.get(), 0, sizeof( int64_t ) * numBins ); - auto framesBegin = frames.begin(); - auto framesEnd = frames.end(); - while( framesBegin != framesEnd && *framesBegin == 0 ) ++framesBegin; + auto framesBegin = frames.begin(); + auto framesEnd = frames.end(); + while( framesBegin != framesEnd && *framesBegin == 0 ) ++framesBegin; - if( m_frameSortData.minBinVal > 1 ) - { - if( m_frameSortData.logTime ) + int64_t fixedAxisMaxVal = 0; + bool fixedReplayAxes = m_frameSortData.playback.active && !m_frameSortData.playback.fullData.empty(); + if( fixedReplayAxes ) { - 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; + fixedReplayAxes = false; } - for( int64_t j=numBins-1; j>i; j-- ) + else { - 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; + tmin = *axisBegin; + tmax = *( axisEnd - 1 ); + + 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 < numBins; i++ ) + { + const auto nextBinVal = int64_t( pow( 10.0, tMinLog + ( i + 1 ) * zmax ) ); + auto nit = std::lower_bound( axisBegin, axisEnd, nextBinVal ); + const auto distance = std::distance( axisBegin, nit ); + if( distance >= m_frameSortData.minBinVal ) break; + axisBegin = 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( axisBegin, axisEnd, nextBinVal ); + const auto distance = std::distance( nit, axisEnd ); + if( distance >= m_frameSortData.minBinVal ) break; + axisEnd = nit; + } + } + else + { + const auto zmax = tmax - tmin; + int64_t i; + for( i = 0; i < numBins; i++ ) + { + const auto nextBinVal = tmin + ( i + 1 ) * zmax / numBins; + auto nit = std::lower_bound( axisBegin, axisEnd, nextBinVal ); + const auto distance = std::distance( axisBegin, nit ); + if( distance >= m_frameSortData.minBinVal ) break; + axisBegin = nit; + } + for( int64_t j = numBins - 1; j > i; j-- ) + { + const auto nextBinVal = tmin + ( j - 1 ) * zmax / numBins; + auto nit = std::lower_bound( axisBegin, axisEnd, nextBinVal ); + const auto distance = std::distance( nit, axisEnd ); + if( distance >= m_frameSortData.minBinVal ) break; + axisEnd = nit; + } + } + } + + if( axisBegin == axisEnd ) + { + fixedReplayAxes = false; + } + else + { + tmin = *axisBegin; + tmax = *( axisEnd - 1 ); + if( tmax <= tmin ) + { + fixedReplayAxes = false; + } + else + { + std::vector axisBins( numBins, 0 ); + if( m_frameSortData.logTime ) + { + const auto tMinLog = log10( tmin ); + const auto zmax = ( log10( tmax ) - tMinLog ) / numBins; + + auto axisFit = axisBegin; + for( int64_t i = 0; i < numBins; i++ ) + { + const auto nextBinVal = int64_t( pow( 10.0, tMinLog + ( i + 1 ) * zmax ) ); + auto nit = std::lower_bound( axisFit, axisEnd, nextBinVal ); + axisBins[i] = std::distance( axisFit, nit ); + axisFit = nit; + } + axisBins[numBins - 1] += std::distance( axisFit, axisEnd ); + + auto fit = std::lower_bound( framesBegin, framesEnd, tmin ); + const auto replayEnd = std::upper_bound( fit, framesEnd, tmax ); + for( int64_t i = 0; i < numBins; i++ ) + { + const auto nextBinVal = int64_t( pow( 10.0, tMinLog + ( i + 1 ) * zmax ) ); + auto nit = std::lower_bound( fit, replayEnd, nextBinVal ); + bins[i] = std::distance( fit, nit ); + fit = nit; + } + bins[numBins - 1] += std::distance( fit, replayEnd ); + } + else + { + const auto zmax = tmax - tmin; + + auto axisFit = axisBegin; + for( int64_t i = 0; i < numBins; i++ ) + { + const auto nextBinVal = tmin + ( i + 1 ) * zmax / numBins; + auto nit = std::lower_bound( axisFit, axisEnd, nextBinVal ); + axisBins[i] = std::distance( axisFit, nit ); + axisFit = nit; + } + axisBins[numBins - 1] += std::distance( axisFit, axisEnd ); + + auto fit = std::lower_bound( framesBegin, framesEnd, tmin ); + const auto replayEnd = std::upper_bound( fit, framesEnd, tmax ); + for( int64_t i = 0; i < numBins; i++ ) + { + const auto nextBinVal = tmin + ( i + 1 ) * zmax / numBins; + auto nit = std::lower_bound( fit, replayEnd, nextBinVal ); + bins[i] = std::distance( fit, nit ); + fit = nit; + } + bins[numBins - 1] += std::distance( fit, replayEnd ); + } + + fixedAxisMaxVal = *std::max_element( axisBins.begin(), axisBins.end() ); + } + } } } - else + + if( !fixedReplayAxes ) { - const auto zmax = tmax - tmin; - int64_t i; - for( i=0; i 1 ) { - const auto nextBinVal = tmin + ( i+1 ) * zmax / numBins; - auto nit = std::lower_bound( framesBegin, framesEnd, nextBinVal ); - const auto distance = std::distance( framesBegin, nit ); - if( distance >= m_frameSortData.minBinVal ) break; - framesBegin = nit; + if( m_frameSortData.logTime ) + { + const auto tMinLog = log10( tmin ); + const auto zmax = ( log10( tmax ) - tMinLog ) / numBins; + int64_t i; + for( i = 0; i < numBins; i++ ) + { + const auto nextBinVal = int64_t( pow( 10.0, tMinLog + ( i + 1 ) * zmax ) ); + auto nit = std::lower_bound( framesBegin, framesEnd, nextBinVal ); + const auto distance = std::distance( framesBegin, nit ); + if( distance >= 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 < numBins; i++ ) + { + const auto nextBinVal = tmin + ( i + 1 ) * zmax / numBins; + auto nit = std::lower_bound( framesBegin, framesEnd, nextBinVal ); + const auto distance = std::distance( framesBegin, nit ); + if( distance >= 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 ); } - for( int64_t j=numBins-1; j>i; j-- ) + + if( m_frameSortData.logTime ) { - 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; + const auto tMinLog = log10( tmin ); + const auto zmax = ( log10( tmax ) - tMinLog ) / numBins; + auto fit = framesBegin; + for( int64_t i = 0; i < numBins; i++ ) + { + const auto nextBinVal = int64_t( pow( 10.0, tMinLog + ( i + 1 ) * zmax ) ); + auto nit = std::lower_bound( fit, framesEnd, nextBinVal ); + bins[i] = std::distance( fit, nit ); + fit = nit; + } + bins[numBins - 1] += std::distance( fit, framesEnd ); + } + else + { + const auto zmax = tmax - tmin; + auto fit = framesBegin; + for( int64_t i = 0; i < numBins; i++ ) + { + const auto nextBinVal = tmin + ( i + 1 ) * zmax / numBins; + auto nit = std::lower_bound( fit, framesEnd, nextBinVal ); + bins[i] = std::distance( fit, nit ); + fit = nit; + } + bins[numBins - 1] += std::distance( fit, framesEnd ); } } - - 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; iAddRectFilled( wpos, wpos + ImVec2( w, Height ), 0x22FFFFFF ); + draw->AddRect( wpos, wpos + ImVec2( w, Height ), 0x88FFFFFF ); + + if( m_frameSortData.logVal ) { - const auto val = bins[i]; - if( val > 0 ) + const auto hAdj = double( Height - 4 ) / log10( drawMaxVal + 1 ); + for( int i = 0; i < numBins; i++ ) { - DrawLine( draw, dpos + ImVec2( 2+i, Height-3 ), dpos + ImVec2( 2+i, Height-3 - log10( val + 1 ) * hAdj ), 0xFF22DDDD ); + const auto val = bins[i]; + if( val > 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; + const auto hAdj = double( Height - 4 ) / drawMaxVal; for( int i=0; i Date: Sat, 18 Apr 2026 00:48:17 +0800 Subject: [PATCH 2/2] Fix ambiguous RangeSlim replay assignment --- profiler/src/profiler/TracyView_FindZone.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/profiler/src/profiler/TracyView_FindZone.cpp b/profiler/src/profiler/TracyView_FindZone.cpp index 69040fd469..e448ecfef9 100644 --- a/profiler/src/profiler/TracyView_FindZone.cpp +++ b/profiler/src/profiler/TracyView_FindZone.cpp @@ -52,7 +52,7 @@ void View::SetFindZonePlaybackFrame( int idx ) playback.currentFrame = idx; // Find-zone replay always grows the visible limit range from the replay start to the current // frame so histogram and group statistics evolve cumulatively in-place. - playback.currentRange = { m_worker.GetFrameBegin( *playback.frameSet, playback.targetFrameRange.first ), m_worker.GetFrameEnd( *playback.frameSet, idx ), true }; + playback.currentRange = RangeSlim{ m_worker.GetFrameBegin( *playback.frameSet, playback.targetFrameRange.first ), m_worker.GetFrameEnd( *playback.frameSet, idx ), true }; if( idx >= playback.targetFrameRange.second - 1 ) { playback.pause = true;