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..e448ecfef9 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 = 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; + 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