mirror of
https://github.com/wolfpld/tracy.git
synced 2024-11-29 08:54:34 +00:00
633 lines
22 KiB
C++
633 lines
22 KiB
C++
#include <algorithm>
|
|
#include <limits>
|
|
|
|
#include "TracyImGui.hpp"
|
|
#include "TracyMouse.hpp"
|
|
#include "TracyPrint.hpp"
|
|
#include "TracyTimelineContext.hpp"
|
|
#include "TracyTimelineItemThread.hpp"
|
|
#include "TracyView.hpp"
|
|
#include "TracyWorker.hpp"
|
|
|
|
namespace tracy
|
|
{
|
|
|
|
constexpr float MinVisSize = 3;
|
|
constexpr float MinCtxSize = 4;
|
|
|
|
|
|
TimelineItemThread::TimelineItemThread( View& view, Worker& worker, const ThreadData* thread )
|
|
: TimelineItem( view, worker, thread, true )
|
|
, m_thread( thread )
|
|
, m_ghost( false )
|
|
{
|
|
auto name = worker.GetThreadName( thread->id );
|
|
if( strncmp( name, "Tracy ", 6 ) == 0 )
|
|
{
|
|
m_showFull = false;
|
|
}
|
|
}
|
|
|
|
bool TimelineItemThread::IsEmpty() const
|
|
{
|
|
auto& crash = m_worker.GetCrashEvent();
|
|
return crash.thread != m_thread->id &&
|
|
m_thread->timeline.empty() &&
|
|
m_thread->messages.empty() &&
|
|
m_thread->ghostZones.empty();
|
|
}
|
|
|
|
uint32_t TimelineItemThread::HeaderColor() const
|
|
{
|
|
auto& crash = m_worker.GetCrashEvent();
|
|
if( crash.thread == m_thread->id ) return 0xFF2222FF;
|
|
if( m_thread->isFiber ) return 0xFF88FF88;
|
|
return 0xFFFFFFFF;
|
|
}
|
|
|
|
uint32_t TimelineItemThread::HeaderColorInactive() const
|
|
{
|
|
auto& crash = m_worker.GetCrashEvent();
|
|
if( crash.thread == m_thread->id ) return 0xFF111188;
|
|
if( m_thread->isFiber ) return 0xFF448844;
|
|
return 0xFF888888;
|
|
}
|
|
|
|
uint32_t TimelineItemThread::HeaderLineColor() const
|
|
{
|
|
return 0x33FFFFFF;
|
|
}
|
|
|
|
const char* TimelineItemThread::HeaderLabel() const
|
|
{
|
|
return m_worker.GetThreadName( m_thread->id );
|
|
}
|
|
|
|
int64_t TimelineItemThread::RangeBegin() const
|
|
{
|
|
int64_t first = std::numeric_limits<int64_t>::max();
|
|
const auto ctx = m_worker.GetContextSwitchData( m_thread->id );
|
|
if( ctx && !ctx->v.empty() )
|
|
{
|
|
first = ctx->v.begin()->Start();
|
|
}
|
|
if( !m_thread->timeline.empty() )
|
|
{
|
|
if( m_thread->timeline.is_magic() )
|
|
{
|
|
auto& tl = *((Vector<ZoneEvent>*)&m_thread->timeline);
|
|
first = std::min( first, tl.front().Start() );
|
|
}
|
|
else
|
|
{
|
|
first = std::min( first, m_thread->timeline.front()->Start() );
|
|
}
|
|
}
|
|
if( !m_thread->messages.empty() )
|
|
{
|
|
first = std::min( first, m_thread->messages.front()->time );
|
|
}
|
|
for( const auto& lock : m_worker.GetLockMap() )
|
|
{
|
|
const auto& lockmap = *lock.second;
|
|
if( !lockmap.valid ) continue;
|
|
auto it = lockmap.threadMap.find( m_thread->id );
|
|
if( it == lockmap.threadMap.end() ) continue;
|
|
const auto thread = it->second;
|
|
auto lptr = lockmap.timeline.data();
|
|
while( lptr->ptr->thread != thread ) lptr++;
|
|
if( lptr->ptr->Time() < first ) first = lptr->ptr->Time();
|
|
}
|
|
return first;
|
|
}
|
|
|
|
int64_t TimelineItemThread::RangeEnd() const
|
|
{
|
|
int64_t last = -1;
|
|
const auto ctx = m_worker.GetContextSwitchData( m_thread->id );
|
|
if( ctx && !ctx->v.empty() )
|
|
{
|
|
const auto& back = ctx->v.back();
|
|
last = back.IsEndValid() ? back.End() : back.Start();
|
|
}
|
|
if( !m_thread->timeline.empty() )
|
|
{
|
|
if( m_thread->timeline.is_magic() )
|
|
{
|
|
auto& tl = *((Vector<ZoneEvent>*)&m_thread->timeline);
|
|
last = std::max( last, m_worker.GetZoneEnd( tl.back() ) );
|
|
}
|
|
else
|
|
{
|
|
last = std::max( last, m_worker.GetZoneEnd( *m_thread->timeline.back() ) );
|
|
}
|
|
}
|
|
if( !m_thread->messages.empty() )
|
|
{
|
|
last = std::max( last, m_thread->messages.back()->time );
|
|
}
|
|
for( const auto& lock : m_worker.GetLockMap() )
|
|
{
|
|
const auto& lockmap = *lock.second;
|
|
if( !lockmap.valid ) continue;
|
|
auto it = lockmap.threadMap.find( m_thread->id );
|
|
if( it == lockmap.threadMap.end() ) continue;
|
|
const auto thread = it->second;
|
|
auto eptr = lockmap.timeline.data() + lockmap.timeline.size() - 1;
|
|
while( eptr->ptr->thread != thread ) eptr--;
|
|
if( eptr->ptr->Time() > last ) last = eptr->ptr->Time();
|
|
}
|
|
return last;
|
|
}
|
|
|
|
void TimelineItemThread::HeaderTooltip( const char* label ) const
|
|
{
|
|
m_view.HighlightThread( m_thread->id );
|
|
|
|
ImGui::BeginTooltip();
|
|
SmallColorBox( GetThreadColor( m_thread->id, 0, m_view.GetViewData().dynamicColors ) );
|
|
ImGui::SameLine();
|
|
ImGui::TextUnformatted( m_worker.GetThreadName( m_thread->id ) );
|
|
ImGui::SameLine();
|
|
ImGui::TextDisabled( "(%s)", RealToString( m_thread->id ) );
|
|
auto& crash = m_worker.GetCrashEvent();
|
|
if( crash.thread == m_thread->id )
|
|
{
|
|
ImGui::SameLine();
|
|
TextColoredUnformatted( ImVec4( 1.f, 0.2f, 0.2f, 1.f ), ICON_FA_SKULL " Crashed" );
|
|
}
|
|
if( m_thread->isFiber )
|
|
{
|
|
ImGui::SameLine();
|
|
TextColoredUnformatted( ImVec4( 0.2f, 0.6f, 0.2f, 1.f ), "Fiber" );
|
|
}
|
|
|
|
const auto ctx = m_worker.GetContextSwitchData( m_thread->id );
|
|
const auto first = RangeBegin();
|
|
const auto last = RangeEnd();
|
|
|
|
ImGui::Separator();
|
|
|
|
size_t lockCnt = 0;
|
|
for( const auto& lock : m_worker.GetLockMap() )
|
|
{
|
|
const auto& lockmap = *lock.second;
|
|
if( !lockmap.valid ) continue;
|
|
auto it = lockmap.threadMap.find( m_thread->id );
|
|
if( it == lockmap.threadMap.end() ) continue;
|
|
lockCnt++;
|
|
}
|
|
|
|
if( last >= 0 )
|
|
{
|
|
const auto lifetime = last - first;
|
|
const auto traceLen = m_worker.GetLastTime() - m_worker.GetFirstTime();
|
|
|
|
TextFocused( "Appeared at", TimeToString( first ) );
|
|
TextFocused( "Last event at", TimeToString( last ) );
|
|
TextFocused( "Lifetime:", TimeToString( lifetime ) );
|
|
ImGui::SameLine();
|
|
char buf[64];
|
|
PrintStringPercent( buf, lifetime / double( traceLen ) * 100 );
|
|
TextDisabledUnformatted( buf );
|
|
|
|
if( ctx )
|
|
{
|
|
TextFocused( "Time in running state:", TimeToString( ctx->runningTime ) );
|
|
ImGui::SameLine();
|
|
PrintStringPercent( buf, ctx->runningTime / double( lifetime ) * 100 );
|
|
TextDisabledUnformatted( buf );
|
|
}
|
|
}
|
|
|
|
ImGui::Separator();
|
|
if( !m_thread->timeline.empty() )
|
|
{
|
|
TextFocused( "Zone count:", RealToString( m_thread->count ) );
|
|
TextFocused( "Top-level zones:", RealToString( m_thread->timeline.size() ) );
|
|
}
|
|
if( !m_thread->messages.empty() )
|
|
{
|
|
TextFocused( "Messages:", RealToString( m_thread->messages.size() ) );
|
|
}
|
|
if( lockCnt != 0 )
|
|
{
|
|
TextFocused( "Locks:", RealToString( lockCnt ) );
|
|
}
|
|
if( ctx )
|
|
{
|
|
TextFocused( "Running state regions:", RealToString( ctx->v.size() ) );
|
|
}
|
|
if( !m_thread->samples.empty() )
|
|
{
|
|
TextFocused( "Call stack samples:", RealToString( m_thread->samples.size() ) );
|
|
if( m_thread->kernelSampleCnt != 0 )
|
|
{
|
|
TextFocused( "Kernel samples:", RealToString( m_thread->kernelSampleCnt ) );
|
|
ImGui::SameLine();
|
|
ImGui::TextDisabled( "(%.2f%%)", 100.f * m_thread->kernelSampleCnt / m_thread->samples.size() );
|
|
}
|
|
}
|
|
ImGui::EndTooltip();
|
|
}
|
|
|
|
void TimelineItemThread::HeaderExtraContents( const TimelineContext& ctx, int offset, float labelWidth )
|
|
{
|
|
m_view.DrawThreadMessagesList( ctx, m_msgDraw, offset, m_thread->id );
|
|
|
|
#ifndef TRACY_NO_STATISTICS
|
|
const bool hasGhostZones = m_worker.AreGhostZonesReady() && !m_thread->ghostZones.empty();
|
|
if( hasGhostZones && !m_thread->timeline.empty() )
|
|
{
|
|
auto draw = ImGui::GetWindowDrawList();
|
|
const auto ty = ImGui::GetTextLineHeight();
|
|
|
|
const auto color = m_ghost ? 0xFFAA9999 : 0x88AA7777;
|
|
draw->AddText( ctx.wpos + ImVec2( 1.5f * ty + labelWidth, offset ), color, ICON_FA_GHOST );
|
|
float ghostSz = ImGui::CalcTextSize( ICON_FA_GHOST ).x;
|
|
|
|
if( ctx.hover && ImGui::IsMouseHoveringRect( ctx.wpos + ImVec2( 1.5f * ty + labelWidth, offset ), ctx.wpos + ImVec2( 1.5f * ty + labelWidth + ghostSz, offset + ty ) ) )
|
|
{
|
|
if( IsMouseClicked( 0 ) )
|
|
{
|
|
m_ghost = !m_ghost;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool TimelineItemThread::DrawContents( const TimelineContext& ctx, int& offset )
|
|
{
|
|
m_view.DrawThread( ctx, *m_thread, m_draw, m_ctxDraw, m_samplesDraw, offset, m_depth, m_hasCtxSwitch, m_hasSamples );
|
|
if( m_depth == 0 && !m_hasMessages )
|
|
{
|
|
auto& crash = m_worker.GetCrashEvent();
|
|
return crash.thread == m_thread->id;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void TimelineItemThread::DrawOverlay( const ImVec2& ul, const ImVec2& dr )
|
|
{
|
|
m_view.DrawThreadOverlays( *m_thread, ul, dr );
|
|
}
|
|
|
|
void TimelineItemThread::DrawFinished()
|
|
{
|
|
m_samplesDraw.clear();
|
|
m_ctxDraw.clear();
|
|
m_draw.clear();
|
|
m_msgDraw.clear();
|
|
}
|
|
|
|
void TimelineItemThread::Preprocess( const TimelineContext& ctx, TaskDispatch& td, bool visible, int yPos )
|
|
{
|
|
assert( m_samplesDraw.empty() );
|
|
assert( m_ctxDraw.empty() );
|
|
assert( m_draw.empty() );
|
|
assert( m_msgDraw.empty() );
|
|
|
|
td.Queue( [this, &ctx, visible] {
|
|
#ifndef TRACY_NO_STATISTICS
|
|
if( m_worker.AreGhostZonesReady() && ( m_ghost || ( m_view.GetViewData().ghostZones && m_thread->timeline.empty() ) ) )
|
|
{
|
|
m_depth = PreprocessGhostLevel( ctx, m_thread->ghostZones, 0, visible );
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
m_depth = PreprocessZoneLevel( ctx, m_thread->timeline, 0, visible );
|
|
}
|
|
} );
|
|
|
|
const auto& vd = m_view.GetViewData();
|
|
|
|
m_hasCtxSwitch = false;
|
|
if( vd.drawContextSwitches )
|
|
{
|
|
auto ctxSwitch = m_worker.GetContextSwitchData( m_thread->id );
|
|
if( ctxSwitch )
|
|
{
|
|
td.Queue( [this, &ctx, ctxSwitch, visible] {
|
|
PreprocessContextSwitches( ctx, *ctxSwitch, visible );
|
|
} );
|
|
}
|
|
}
|
|
|
|
m_hasSamples = false;
|
|
if( vd.drawSamples && !m_thread->samples.empty() )
|
|
{
|
|
td.Queue( [this, &ctx, visible, yPos] {
|
|
PreprocessSamples( ctx, m_thread->samples, visible, yPos );
|
|
} );
|
|
}
|
|
|
|
m_hasMessages = false;
|
|
td.Queue( [this, &ctx, visible, yPos] {
|
|
PreprocessMessages( ctx, m_thread->messages, m_thread->id, visible, yPos );
|
|
} );
|
|
}
|
|
|
|
#ifndef TRACY_NO_STATISTICS
|
|
int TimelineItemThread::PreprocessGhostLevel( const TimelineContext& ctx, const Vector<GhostZone>& vec, int depth, bool visible )
|
|
{
|
|
const auto nspx = ctx.nspx;
|
|
const auto vStart = ctx.vStart;
|
|
const auto vEnd = ctx.vEnd;
|
|
|
|
const auto MinVisNs = int64_t( round( GetScale() * MinVisSize * nspx ) );
|
|
|
|
auto it = std::lower_bound( vec.begin(), vec.end(), std::max<int64_t>( 0, vStart - 2 * MinVisNs ), [] ( const auto& l, const auto& r ) { return l.end.Val() < r; } );
|
|
if( it == vec.end() ) return depth;
|
|
|
|
const auto zitend = std::lower_bound( it, vec.end(), vEnd, [] ( const auto& l, const auto& r ) { return l.start.Val() < r; } );
|
|
if( it == zitend ) return depth;
|
|
if( (zitend-1)->end.Val() < vStart ) return depth;
|
|
|
|
int maxdepth = depth + 1;
|
|
|
|
while( it < zitend )
|
|
{
|
|
auto& ev = *it;
|
|
const auto end = ev.end.Val();
|
|
const auto zsz = end - ev.start.Val();
|
|
if( zsz < MinVisNs )
|
|
{
|
|
auto nextTime = end + MinVisNs;
|
|
auto next = it + 1;
|
|
for(;;)
|
|
{
|
|
next = std::lower_bound( next, zitend, nextTime, [] ( const auto& l, const auto& r ) { return l.end.Val() < r; } );
|
|
if( next == zitend ) break;
|
|
auto prev = next - 1;
|
|
const auto pt = prev->end.Val();
|
|
const auto nt = next->end.Val();
|
|
if( nt - pt >= MinVisNs ) break;
|
|
nextTime = nt + MinVisNs;
|
|
}
|
|
if( visible ) m_draw.emplace_back( TimelineDraw { TimelineDrawType::GhostFolded, uint16_t( depth ), (void**)&ev, (next-1)->end } );
|
|
it = next;
|
|
}
|
|
else
|
|
{
|
|
if( ev.child >= 0 )
|
|
{
|
|
const auto d = PreprocessGhostLevel( ctx, m_worker.GetGhostChildren( ev.child ), depth + 1, visible );
|
|
if( d > maxdepth ) maxdepth = d;
|
|
}
|
|
if( visible ) m_draw.emplace_back( TimelineDraw { TimelineDrawType::Ghost, uint16_t( depth ), (void**)&ev } );
|
|
++it;
|
|
}
|
|
}
|
|
|
|
return maxdepth;
|
|
}
|
|
#endif
|
|
|
|
int TimelineItemThread::PreprocessZoneLevel( const TimelineContext& ctx, const Vector<short_ptr<ZoneEvent>>& vec, int depth, bool visible )
|
|
{
|
|
if( vec.is_magic() )
|
|
{
|
|
return PreprocessZoneLevel<VectorAdapterDirect<ZoneEvent>>( ctx, *(Vector<ZoneEvent>*)( &vec ), depth, visible );
|
|
}
|
|
else
|
|
{
|
|
return PreprocessZoneLevel<VectorAdapterPointer<ZoneEvent>>( ctx, vec, depth, visible );
|
|
}
|
|
}
|
|
|
|
template<typename Adapter, typename V>
|
|
int TimelineItemThread::PreprocessZoneLevel( const TimelineContext& ctx, const V& vec, int depth, bool visible )
|
|
{
|
|
const auto delay = m_worker.GetDelay();
|
|
const auto resolution = m_worker.GetResolution();
|
|
const auto vStart = ctx.vStart;
|
|
const auto vEnd = ctx.vEnd;
|
|
const auto nspx = ctx.nspx;
|
|
|
|
const auto MinVisNs = int64_t( round( GetScale() * MinVisSize * nspx ) );
|
|
|
|
// cast to uint64_t, so that unended zones (end = -1) are still drawn
|
|
auto it = std::lower_bound( vec.begin(), vec.end(), std::max<int64_t>( 0, vStart - std::max<int64_t>( delay, 2 * MinVisNs ) ), [] ( const auto& l, const auto& r ) { Adapter a; return (uint64_t)a(l).End() < (uint64_t)r; } );
|
|
if( it == vec.end() ) return depth;
|
|
|
|
const auto zitend = std::lower_bound( it, vec.end(), vEnd + resolution, [] ( const auto& l, const auto& r ) { Adapter a; return a(l).Start() < r; } );
|
|
if( it == zitend ) return depth;
|
|
Adapter a;
|
|
if( !a(*it).IsEndValid() && m_worker.GetZoneEnd( a(*it) ) < vStart ) return depth;
|
|
if( m_worker.GetZoneEnd( a(*(zitend-1)) ) < vStart ) return depth;
|
|
|
|
int maxdepth = depth + 1;
|
|
|
|
while( it < zitend )
|
|
{
|
|
auto& ev = a(*it);
|
|
const auto end = m_worker.GetZoneEnd( ev );
|
|
const auto zsz = end - ev.Start();
|
|
if( zsz < MinVisNs )
|
|
{
|
|
auto nextTime = end + MinVisNs;
|
|
auto next = it + 1;
|
|
for(;;)
|
|
{
|
|
next = std::lower_bound( next, zitend, nextTime, [this] ( const auto& l, const auto& r ) { Adapter a; return m_worker.GetZoneEnd( a(l) ) < r; } );
|
|
if( next == zitend ) break;
|
|
auto prev = next - 1;
|
|
const auto pt = m_worker.GetZoneEnd( a(*prev) );
|
|
const auto nt = m_worker.GetZoneEnd( a(*next) );
|
|
if( nt - pt >= MinVisNs ) break;
|
|
nextTime = nt + MinVisNs;
|
|
}
|
|
if( visible ) m_draw.emplace_back( TimelineDraw { TimelineDrawType::Folded, uint16_t( depth ), (void**)&ev, m_worker.GetZoneEnd( a(*(next-1)) ), uint32_t( next - it ) } );
|
|
it = next;
|
|
}
|
|
else
|
|
{
|
|
if( ev.HasChildren() )
|
|
{
|
|
const auto d = PreprocessZoneLevel( ctx, m_worker.GetZoneChildren( ev.Child() ), depth + 1, visible );
|
|
if( d > maxdepth ) maxdepth = d;
|
|
}
|
|
if( visible ) m_draw.emplace_back( TimelineDraw { TimelineDrawType::Zone, uint16_t( depth ), (void**)&ev } );
|
|
++it;
|
|
}
|
|
}
|
|
|
|
return maxdepth;
|
|
}
|
|
|
|
void TimelineItemThread::PreprocessContextSwitches( const TimelineContext& ctx, const ContextSwitch& ctxSwitch, bool visible )
|
|
{
|
|
const auto nspx = ctx.nspx;
|
|
const auto vStart = ctx.vStart;
|
|
const auto vEnd = ctx.vEnd;
|
|
|
|
auto& vec = ctxSwitch.v;
|
|
auto it = std::lower_bound( vec.begin(), vec.end(), std::max<int64_t>( 0, vStart ), [] ( const auto& l, const auto& r ) { return (uint64_t)l.End() < (uint64_t)r; } );
|
|
if( it == vec.end() ) return;
|
|
if( it != vec.begin() ) --it;
|
|
|
|
auto citend = std::lower_bound( it, vec.end(), vEnd, [] ( const auto& l, const auto& r ) { return l.Start() < r; } );
|
|
if( it == citend ) return;
|
|
if( citend != vec.end() ) ++citend;
|
|
|
|
m_hasCtxSwitch = true;
|
|
if( !visible ) return;
|
|
|
|
const auto MinCtxNs = int64_t( round( GetScale() * MinCtxSize * nspx ) );
|
|
const auto& sampleData = m_thread->samples;
|
|
|
|
bool first = true;
|
|
while( it < citend )
|
|
{
|
|
auto& ev = *it;
|
|
if( first )
|
|
{
|
|
first = false;
|
|
}
|
|
else
|
|
{
|
|
uint32_t waitStack = 0;
|
|
if( !sampleData.empty() )
|
|
{
|
|
auto sdit = std::lower_bound( sampleData.begin(), sampleData.end(), ev.Start(), [] ( const auto& l, const auto& r ) { return l.time.Val() < r; } );
|
|
bool found = sdit != sampleData.end() && sdit->time.Val() == ev.Start();
|
|
if( !found && it != vec.begin() )
|
|
{
|
|
auto eit = it;
|
|
--eit;
|
|
sdit = std::lower_bound( sampleData.begin(), sampleData.end(), eit->End(), [] ( const auto& l, const auto& r ) { return l.time.Val() < r; } );
|
|
found = sdit != sampleData.end() && sdit->time.Val() == eit->End();
|
|
}
|
|
if( found ) waitStack = sdit->callstack.Val();
|
|
}
|
|
m_ctxDraw.emplace_back( ContextSwitchDraw { ContextSwitchDrawType::Waiting, uint32_t( it - vec.begin() ), waitStack } );
|
|
}
|
|
|
|
const auto end = ev.IsEndValid() ? ev.End() : m_worker.GetLastTime();
|
|
const auto zsz = end - ev.Start();
|
|
if( zsz < MinCtxNs )
|
|
{
|
|
auto nextTime = end + MinCtxNs;
|
|
auto next = it + 1;
|
|
for(;;)
|
|
{
|
|
next = std::lower_bound( next, citend, nextTime, [this] ( const auto& l, const auto& r ) { return ( l.IsEndValid() ? l.End() : m_worker.GetLastTime() ) < r; } );
|
|
if( next == citend ) break;
|
|
auto prev = next - 1;
|
|
const auto pt = prev->IsEndValid() ? prev->End() : m_worker.GetLastTime();
|
|
const auto nt = next->IsEndValid() ? next->End() : m_worker.GetLastTime();
|
|
if( nt - pt >= MinCtxNs ) break;
|
|
nextTime = nt + MinCtxNs;
|
|
}
|
|
m_ctxDraw.emplace_back( ContextSwitchDraw { ContextSwitchDrawType::Folded, uint32_t( it - vec.begin() ), uint32_t( next - it ) } );
|
|
it = next;
|
|
}
|
|
else
|
|
{
|
|
m_ctxDraw.emplace_back( ContextSwitchDraw { ContextSwitchDrawType::Running, uint32_t( it - vec.begin() ) } );
|
|
++it;
|
|
}
|
|
}
|
|
}
|
|
|
|
void TimelineItemThread::PreprocessSamples( const TimelineContext& ctx, const Vector<SampleData>& vec, bool visible, int yPos )
|
|
{
|
|
const auto vStart = ctx.vStart;
|
|
const auto vEnd = ctx.vEnd;
|
|
const auto nspx = ctx.nspx;
|
|
const auto ty = ctx.ty;
|
|
const auto ostep = ty + 1;
|
|
const auto pos = yPos + ostep;
|
|
|
|
const auto MinVis = 5 * GetScale();
|
|
const auto MinVisNs = int64_t( round( MinVis * nspx ) );
|
|
|
|
auto it = std::lower_bound( vec.begin(), vec.end(), vStart - MinVisNs, [] ( const auto& l, const auto& r ) { return l.time.Val() < r; } );
|
|
if( it == vec.end() ) return;
|
|
const auto itend = std::lower_bound( it, vec.end(), vEnd, [] ( const auto& l, const auto& r ) { return l.time.Val() < r; } );
|
|
if( it == itend ) return;
|
|
|
|
m_hasSamples = true;
|
|
if( !visible ) return;
|
|
|
|
const auto ty0375 = pos + round( ty * 0.375f );
|
|
const auto ty02 = round( ty * 0.2f );
|
|
const auto y0 = ty0375 - ty02 - 3;
|
|
const auto y1 = ty0375 + ty02 - 1;
|
|
if( y0 > ctx.yMax || y1 < ctx.yMin ) return;
|
|
|
|
while( it < itend )
|
|
{
|
|
auto next = it + 1;
|
|
if( next != itend )
|
|
{
|
|
const auto t0 = it->time.Val();
|
|
auto nextTime = t0 + MinVisNs;
|
|
for(;;)
|
|
{
|
|
next = std::lower_bound( next, itend, nextTime, [] ( const auto& l, const auto& r ) { return l.time.Val() < r; } );
|
|
if( next == itend ) break;
|
|
auto prev = next - 1;
|
|
const auto pt = prev->time.Val();
|
|
const auto nt = next->time.Val();
|
|
if( nt - pt >= MinVisNs ) break;
|
|
nextTime = nt + MinVisNs;
|
|
}
|
|
}
|
|
m_samplesDraw.emplace_back( SamplesDraw { uint32_t( next - it - 1 ), uint32_t( it - vec.begin() ) } );
|
|
it = next;
|
|
}
|
|
}
|
|
|
|
void TimelineItemThread::PreprocessMessages( const TimelineContext& ctx, const Vector<short_ptr<MessageData>>& vec, uint64_t tid, bool visible, int yPos )
|
|
{
|
|
const auto vStart = ctx.vStart;
|
|
const auto vEnd = ctx.vEnd;
|
|
const auto nspx = ctx.nspx;
|
|
|
|
const auto MinVisNs = int64_t( round( GetScale() * MinVisSize * nspx ) );
|
|
|
|
auto it = std::lower_bound( vec.begin(), vec.end(), vStart, [] ( const auto& lhs, const auto& rhs ) { return lhs->time < rhs; } );
|
|
if( it == vec.end() ) return;
|
|
auto end = std::lower_bound( it, vec.end(), vEnd+1, [] ( const auto& lhs, const auto& rhs ) { return lhs->time < rhs; } );
|
|
if( it == end ) return;
|
|
|
|
m_hasMessages = true;
|
|
if( !visible ) return;
|
|
if( yPos > ctx.yMax || yPos + ctx.ty < ctx.yMin ) return;
|
|
|
|
const auto hMsg = m_view.GetMessageHighlight();
|
|
const auto hThread = hMsg ? m_worker.DecompressThread( hMsg->thread ) : 0;
|
|
|
|
while( it < end )
|
|
{
|
|
const auto msgTime = (*it)->time;
|
|
const auto nextTime = msgTime + MinVisNs;
|
|
const auto next = std::upper_bound( it, vec.end(), nextTime, [] ( const auto& lhs, const auto& rhs ) { return lhs < rhs->time; } );
|
|
const auto num = next - it;
|
|
bool hilite;
|
|
if( num == 1 )
|
|
{
|
|
hilite = hMsg == *it;
|
|
}
|
|
else
|
|
{
|
|
if( hMsg && hThread == tid )
|
|
{
|
|
const auto hTime = hMsg->time;
|
|
hilite = (*it)->time <= hTime && ( next == vec.end() || (*next)->time > hTime );
|
|
}
|
|
else
|
|
{
|
|
hilite = false;
|
|
}
|
|
}
|
|
m_msgDraw.emplace_back( MessagesDraw { *it, hilite, uint32_t( num ) } );
|
|
it = next;
|
|
}
|
|
}
|
|
|
|
}
|