diff --git a/profiler/src/profiler/TracyTimelineController.cpp b/profiler/src/profiler/TracyTimelineController.cpp index 75cb4e56..f2d32c08 100644 --- a/profiler/src/profiler/TracyTimelineController.cpp +++ b/profiler/src/profiler/TracyTimelineController.cpp @@ -55,20 +55,14 @@ void TimelineController::UpdateCenterItem( int pinnedTop ) // Pinned items are fixed to the viewport and excluded from the scrolling // flow, so centering only considers the normal items in the middle band. - const void* firstNormalKey = nullptr; - const void* lastNormalKey = nullptr; - for( auto& item : m_items ) - { - if( item->IsPinned() ) continue; - if( !firstNormalKey ) firstNormalKey = item->GetKey(); - lastNormalKey = item->GetKey(); - } + if( m_normalItems.empty() ) return; + const void* firstNormalKey = m_normalItems.front()->GetKey(); + const void* lastNormalKey = m_normalItems.back()->GetKey(); int yBegin = 0; int yEnd = pinnedTop; - for( auto& item : m_items ) + for( auto& item : m_normalItems ) { - if( item->IsPinned() ) continue; m_centerItemkey = item->GetKey(); yBegin = yEnd; yEnd += item->GetHeight(); @@ -96,9 +90,8 @@ std::optional TimelineController::CalculateScrollPosition( int pinnedTop ) int yBegin = 0; int yEnd = pinnedTop; - for( auto& item : m_items ) + for( auto& item : m_normalItems ) { - if( item->IsPinned() ) continue; yBegin = yEnd; yEnd += item->GetHeight(); @@ -116,16 +109,23 @@ std::optional TimelineController::CalculateScrollPosition( int pinnedTop ) void TimelineController::End( double pxns, const ImVec2& wpos, bool hover, bool vcenter, float yMin, float yMax ) { + // Snapshot the pin classification once per frame before any Draw runs. + // Toggling a pin flips its state mid-frame and since re-checking it in + // the draw passes would draw the item twice (normal and band passes), + // the grouping here defers the change to the next frame. // GetHeight() is 0 on the first frame, so the scroll extent would be shorter - // if a track was pinned at load, but would "self-correct" in the next frame. - // (Also, nothing is pinned at load anyway.) + // if a track was pinned at load, but self-corrects next frame (and nothing is + // pinned at load anyway). + m_normalItems.clear(); + m_pinnedTopItems.clear(); + m_pinnedBottomItems.clear(); int pinnedTop = 0; int pinnedBottom = 0; for( auto& item : m_items ) { - if( !item->IsPinned() ) continue; - if( item->PinToBottom() ) pinnedBottom += item->GetHeight(); - else pinnedTop += item->GetHeight(); + if( !item->IsPinned() ) m_normalItems.push_back( item ); + else if( item->PinToBottom() ) { m_pinnedBottomItems.push_back( item ); pinnedBottom += item->GetHeight(); } + else { m_pinnedTopItems.push_back( item ); pinnedTop += item->GetHeight(); } } auto shouldUpdateCenterItem = [&] () { @@ -171,75 +171,72 @@ void TimelineController::End( double pxns, const ImVec2& wpos, bool hover, bool // would cover top ones while leaving them clickable underneath. const int bottomStart = std::max( curScrollY + pinnedTop, curScrollY + windowHeight - pinnedBottom ); - // Pinned items add the scroll offset so they stay fixed as content scrolls. - auto itemYOffset = [&] ( TimelineItem* item, int topRun, int normalRun, int bottomRun ) -> int { - if( !item->IsPinned() ) return pinnedTop + normalRun; - return item->PinToBottom() ? bottomStart + bottomRun : curScrollY + topRun; - }; + // Screen-space band edges, shared by culling, the mouse tests, and the fills. + const float topBandBegin = wpos.y + curScrollY; + const float topBandEnd = topBandBegin + pinnedTop; + const float bottomBandBegin = wpos.y + bottomStart; + const float bottomBandEnd = topBandBegin + windowHeight; + TimelineContext ctxNormal = ctx; + if( pinnedTop > 0 ) ctxNormal.yMin = std::max( ctx.yMin, topBandEnd ); + if( pinnedBottom > 0 ) ctxNormal.yMax = std::min( ctx.yMax, bottomBandBegin ); + const auto mouseY = ImGui::GetMousePos().y; + const bool mouseInTopBand = pinnedTop > 0 && mouseY >= topBandBegin && mouseY < topBandEnd; + const bool mouseInBottomBand = pinnedBottom > 0 && mouseY >= bottomBandBegin && mouseY < bottomBandEnd; + ctxNormal.hover = ctx.hover && !mouseInTopBand && !mouseInBottomBand; + + // Preprocess runs before any Draw, so live pin state is stable here. Normal + // items are culled to the middle band and pinned items are always on screen. int topOffset = 0, normalOffset = 0, bottomOffset = 0; for( auto& item : m_items ) { - const int off = itemYOffset( item, topOffset, normalOffset, bottomOffset ); + const bool pinned = item->IsPinned(); + const bool toBottom = pinned && item->PinToBottom(); + const int off = !pinned ? pinnedTop + normalOffset : toBottom ? bottomStart + bottomOffset : curScrollY + topOffset; if( item->WantPreprocess() && item->IsVisible() ) { const auto yPos = wpos.y + off; - const bool visible = item->IsPinned() || m_firstFrame || ( yPos < yMax && yPos + item->GetHeight() >= yMin ); + const bool visible = pinned || m_firstFrame || ( yPos < ctxNormal.yMax && yPos + item->GetHeight() >= ctxNormal.yMin ); item->Preprocess( ctx, m_td, visible, yPos ); } const int h = m_firstFrame ? 0 : item->GetHeight(); - if( !item->IsPinned() ) normalOffset += h; - else if( item->PinToBottom() ) bottomOffset += h; + if( !pinned ) normalOffset += h; + else if( toBottom ) bottomOffset += h; else topOffset += h; } m_td.Sync(); auto draw = ImGui::GetWindowDrawList(); + // Matches the timeline background in normal builds: the root-window build + // overrides WindowBg per-window, so the fill can be a slightly off shade there. const auto bgColor = ImGui::GetColorU32( ImGuiCol_WindowBg ); - // Keep tracks scrolling under a pinned band inert (cull their headers to the - // middle region and drop hover while the mouse is over a band). - TimelineContext ctxNormal = ctx; - if( pinnedTop > 0 ) ctxNormal.yMin = std::max( ctx.yMin, wpos.y + curScrollY + pinnedTop ); - if( pinnedBottom > 0 ) ctxNormal.yMax = std::min( ctx.yMax, wpos.y + bottomStart ); - const auto mouseY = ImGui::GetMousePos().y; - const bool mouseInTopBand = pinnedTop > 0 && mouseY >= wpos.y + curScrollY && mouseY < wpos.y + curScrollY + pinnedTop; - const bool mouseInBottomBand = pinnedBottom > 0 && mouseY >= wpos.y + bottomStart && mouseY < wpos.y + curScrollY + windowHeight; - ctxNormal.hover = ctx.hover && !mouseInTopBand && !mouseInBottomBand; - int normalRunning = 0; - for( auto& item : m_items ) - { - if( item->IsPinned() ) continue; - auto h = item->GetHeight(); - item->Draw( m_firstFrame, ctxNormal, pinnedTop + normalRunning ); - if( m_firstFrame ) h = item->GetHeight(); - normalRunning += h; - } + // Draws a group at (base + accumulated height) with respect to the + // first-frame height bootstrap, and returns the total height drawn. + auto drawRun = [&]( std::vector& items, const TimelineContext& c, int base ) -> int { + int running = 0; + for( auto& item : items ) + { + auto h = item->GetHeight(); + item->Draw( m_firstFrame, c, base + running ); + if( m_firstFrame ) h = item->GetHeight(); + running += h; + } + return running; + }; - if( pinnedTop > 0 ) draw->AddRectFilled( ImVec2( wpos.x, wpos.y + curScrollY ), ImVec2( wpos.x + ctx.w, wpos.y + curScrollY + pinnedTop ), bgColor ); - int topRunning = 0; - for( auto& item : m_items ) - { - if( !item->IsPinned() || item->PinToBottom() ) continue; - auto h = item->GetHeight(); - item->Draw( m_firstFrame, ctx, curScrollY + topRunning ); - if( m_firstFrame ) h = item->GetHeight(); - topRunning += h; - } + const int normalRunning = drawRun( m_normalItems, ctxNormal, pinnedTop ); - if( pinnedBottom > 0 ) draw->AddRectFilled( ImVec2( wpos.x, wpos.y + bottomStart ), ImVec2( wpos.x + ctx.w, wpos.y + curScrollY + windowHeight ), bgColor ); - int bottomRunning = 0; - for( auto& item : m_items ) - { - if( !item->IsPinned() || !item->PinToBottom() ) continue; - auto h = item->GetHeight(); - item->Draw( m_firstFrame, ctx, bottomStart + bottomRunning ); - if( m_firstFrame ) h = item->GetHeight(); - bottomRunning += h; - } + // Opaque fills so tracks scrolling under a band do not show through. These also + // hide parent-list overlays drawn before the child. + if( pinnedTop > 0 ) draw->AddRectFilled( ImVec2( wpos.x, topBandBegin ), ImVec2( wpos.x + ctx.w, topBandEnd ), bgColor ); + drawRun( m_pinnedTopItems, ctx, curScrollY ); + if( pinnedBottom > 0 ) draw->AddRectFilled( ImVec2( wpos.x, bottomBandBegin ), ImVec2( wpos.x + ctx.w, bottomBandEnd ), bgColor ); + drawRun( m_pinnedBottomItems, ctx, bottomStart ); int yOffset = pinnedTop + normalRunning + pinnedBottom; + // pinnedTop is a pre-Draw height, so vertical-centre compensation lags one frame if( const auto scrollY = CalculateScrollPosition( pinnedTop ) ) { int clampedScrollY = std::min( *scrollY, std::max( yOffset - ImGui::GetWindowHeight(), 0 ) ); diff --git a/profiler/src/profiler/TracyTimelineController.hpp b/profiler/src/profiler/TracyTimelineController.hpp index 30983c2b..4c7791d0 100644 --- a/profiler/src/profiler/TracyTimelineController.hpp +++ b/profiler/src/profiler/TracyTimelineController.hpp @@ -50,6 +50,9 @@ private: std::optional CalculateScrollPosition( int pinnedTop ) const; std::vector m_items; + std::vector m_normalItems; + std::vector m_pinnedTopItems; + std::vector m_pinnedBottomItems; unordered_flat_map> m_itemMap; float m_height; diff --git a/profiler/src/profiler/TracyView.hpp b/profiler/src/profiler/TracyView.hpp index 22382bb7..7a4c172b 100644 --- a/profiler/src/profiler/TracyView.hpp +++ b/profiler/src/profiler/TracyView.hpp @@ -319,7 +319,7 @@ private: void DrawTimeline(); void DrawSampleList( const TimelineContext& ctx, const std::vector& drawList, const Vector& vec, int offset, uint64_t tid ); void DrawZoneList( const TimelineContext& ctx, const std::vector& drawList, int offset, uint64_t tid, int maxDepth, double margin ); - void DrawThreadCropper( const int depth, const uint64_t tid, const float xPos, const float yPos, const float ostep, const float cropperWidth, const bool hasCtxSwitches ); + void DrawThreadCropper( const int depth, const uint64_t tid, const float xPos, const float yPos, const float ostep, const float cropperWidth, const bool hasCtxSwitches, const bool hover ); void DrawContextSwitchList( const TimelineContext& ctx, const std::vector& drawList, const Vector& ctxSwitch, int offset, int endOffset, bool isFiber, uint64_t tid ); int DispatchGpuZoneLevel( const Vector>& vec, bool hover, double pxns, int64_t nspx, const ImVec2& wpos, int offset, int depth, uint64_t thread, float yMin, float yMax, int64_t begin, int drift ); template diff --git a/profiler/src/profiler/TracyView_CpuData.cpp b/profiler/src/profiler/TracyView_CpuData.cpp index bde2b96d..df31b69b 100644 --- a/profiler/src/profiler/TracyView_CpuData.cpp +++ b/profiler/src/profiler/TracyView_CpuData.cpp @@ -445,7 +445,7 @@ bool View::DrawCpuData( const TimelineContext& ctx, const std::vector 0 ) DrawThreadCropper( depth, thread.id, croppperPosX, yPos, ostep, cropperWidth, hasCtxSwitch ); + if( depth > 0 ) DrawThreadCropper( depth, thread.id, croppperPosX, yPos, ostep, cropperWidth, hasCtxSwitch, ctx.hover ); } } @@ -606,7 +606,7 @@ void View::DrawZoneList( const TimelineContext& ctx, const std::vectorAddCircle( center, hradius, 0xFFFFFFFF, 0, hoverCircleThickness ); const float wPosX = ImGui::GetWindowPos().x + ImGui::GetWindowContentRegionMin().x;